Precompile Summary
| Property | Value |
|---|---|
| Address | 0x0C |
| Name | BLS12_G1MSM (G1 multi-scalar multiplication) |
| Gas | Variable. For k point–scalar pairs: k * 12000 * discount(k) / 1000 (integer division), using the EIP-2537 Pippenger discount table. k = 1 → 12,000 gas (no discount; behaves as a single scalar multiply). Example: k = 128 → 12000 * 128 * 519 / 1000 ≈ 796,416 gas. For k > 128, the discount caps at 519 (max_discount). k is derived from input length as floor(len(input) / 160). |
| Input | 160 × k bytes with k ≥ 1. Each 160-byte record is G1 point (128 bytes) ∥ scalar (32 bytes, big-endian). Scalars are not required to be < subgroup order q. |
| Output | 128 bytes: resulting G1 point s₁P₁ + s₂P₂ + … + sₖPₖ (affine encoding per EIP-2537). |
| Introduced | Pectra hardfork (EIP-2537, May 2025) |
| Behavior | Multi-scalar multiplication in G1 using Pippenger’s algorithm (per EIP-2537). Subgroup checks are performed: each point must be on the G1 curve and in the correct prime-order subgroup. Scalars are interpreted as 256-bit integers as-is (no mandated reduction mod q before execution; the group result agrees with scalar mod q). k = 0 (empty input) is an error. On any error (invalid encoding, not on curve, wrong subgroup, wrong length including non-multiple of 160), the precompile burns all gas forwarded to the call. |
Threat Surface
BLS12_G1MSM is the batch scalar-multiplication entry point for BLS12-381 G1: it linearly combines many weighted points in one precompile call. Like other EIP-2537 curve precompiles, it is consensus-critical and uses the punishing failure model where invalid inputs consume the entire forwarded gas stipend.
The dominant threat themes are economic and integration:
1. Non-linear gas versus attacker-controlled batch size. Gas scales with k and a discount(k) factor that makes per-point gas cheaper at large k than at k = 1. Contracts that expose user-chosen MSM batch sizes without explicit gas budgets or off-chain caps can be pushed into high total gas paths or into mis-priced internal accounting if they assume linear cost. The formula uses floor(len / 160) for k: if the calldata length is not divisible by 160, gas is charged for fewer pairs than a naive “number of records” parser might assume, yet the precompile still fails and burns the stipend — a schedule mismatch between charged gas and developer expectations.
2. Validation asymmetry versus G1ADD. G1MSM performs subgroup checks; G1ADD does not (by EIP-2537 design). For current BLS12-381 G1, cofactor 1 makes subgroup membership redundant with on-curve checks, but the API inconsistency confuses audits and encourages wrong assumptions if curves or specs ever change.
3. Scalar semantics. 32-byte scalars are not required to be canonical mod q. Implementations should reduce internally; timing or micro-architectural differences for huge scalars remain a low-level implementation concern, not a consensus math bug, but matter for side-channel hardening in adjacent systems.
4. Operational and client variance. MSM gas pricing was repriced (discount table doubled in development, e.g. execution-spec PR #8910, September 2024) after early benchmarks showed MSM could be under-priced relative to naive multiplies for small k. The spec recommends Pippenger but does not nail every implementation detail; different valid algorithms can change constant factors and worst-case behavior across clients — relevant for DoS margins at the protocol level, not for bit-identical results.
Smart Contract Threats
T1: Gas Cost Complexity and DoS (High)
The discount table creates a non-linear relationship between k, total gas, and gas per point. For k = 1, cost is 12,000 (single multiply). As k grows, discount(k) increases (capped at 519 for k > 128), so average cost per point falls relative to isolated multiplies.
- User-controlled k without bounds can force very large total MSM gas (e.g. k = 1000 → on the order of millions of gas for the precompile alone) while still being cheaper per point than repeated k = 1 calls — complicating economic defenses that assume naive linearity.
- Pricing history: the discount table was doubled in September 2024 (PR #8910) because initial MSM pricing was too cheap versus real computation (MSM was slower than naive multiply for small k). Future repricing could again shift relative attractiveness of MSM-heavy attack paths.
- floor(len/160) for k: if input length is not a multiple of 160, gas is computed for floor(len/160) pairs, but the precompile errors and burns forwarded gas — callers may mis-estimate cost versus valid full chunks.
Why it matters: Contracts that accept unbounded MSM inputs or forward gas() on untrusted data risk griefing, unexpected OOG, or wrong internal gas reservations relative to actual execution cost.
T2: Subgroup Check Enforcement (Medium)
Unlike BLS12_G1ADD, G1MSM does perform subgroup checks (in addition to on-curve checks). For BLS12-381 G1, cofactor = 1, so every valid curve point is in the subgroup — the check is mathematically redundant today but still executed.
- Developer confusion: “BLS G1 precompile” is not one validation story; ADD vs MSM differ.
- Future-proofing: if a future curve or encoding ever introduced non-trivial cofactor on a “G1-like” slot, MSM’s check would become essential while ADD semantics might still lag — integration bugs could follow.
Why it matters: Incorrect mental models lead to skipping necessary checks elsewhere (e.g. mixing unchecked points into pairing pipelines) or over-trusting ADD outputs in places that MSM-style assumptions require.
T3: Scalar Not Bounded by Group Order (Medium)
Scalars are 32-byte big-endian integers; values ≥ q are allowed and used as-is at the interface. The algebraic result equals using scalar mod q, but implementation paths may differ in internal reduction strategy and performance.
- Very large scalars might stress non-constant-time or optimization-dependent code paths in some client builds (primarily a side-channel class concern for adjacent secret-handling code, not a consensus result divergence if the spec is met).
- Callers must not rely on canonical scalar encoding for security; only the mod q class matters for group outputs.
Why it matters: Protocols that assume “scalar < q” for game-theoretic or encoding uniqueness reasons can be wrong on-chain; duplicate scalar representations can normalize to the same point.
T4: Single-Point Multiplication via k = 1 (Informational)
There is no separate G1MUL precompile; k = 1 MSM is the supported single-scalar multiply. Implementations should optimize the k = 1 path (avoid full Pippenger setup overhead).
- If a client did not fast-path k = 1, users would pay unnecessary algorithmic overhead while gas is fixed at 12,000 — a performance / fairness issue for client quality, not a soundness bug by itself.
Why it matters: Verifiers and libraries should prefer documented gas tables over micro-benchmarks of a single client when budgeting gas for single multiplies.
T5: Gas Burning on Error (Medium)
As for all EIP-2537 BLS12 precompiles, all forwarded gas is burned on failure. MSM’s variable gas makes it easy to forward far more than the nominal precompile cost for a given k.
- A contract that uses
staticcall(gas(), 0x0C, …)on attacker-controlled input can lose the entire remaining frame gas on malformed data. - Length mismatch (not divisible by 160) still triggers failure after gas was derived from floor(len/160) — predictable griefing if stipends are high.
Why it matters: Griefing and accidental OOG scale with stipend, not just with intended k.
Protocol-Level Threats
P1: MSM Gas Repricing History
MSM pricing evolved during EIP-2537 rollout: the Pippenger discount table was doubled (PR #8910, September 2024) after measurements showed MSM could be cheaper than warranted versus naive multiply for small k, and in worst-case comparisons to primitives like ECRECOVER at certain k. Future hardforks could repricing MSM again, changing relative costs of verifier designs and DoS margins.
P2: Pippenger and Algorithmic Freedom
The specification states MSM should use Pippenger’s method but does not mandate identical low-level strategies. Different correct implementations may have different constant factors and memory profiles. Consensus requires matching results on valid inputs; performance and worst-case resource usage may still differ by client — relevant for client operators and stress testing, not for on-chain equality of outputs.
P3: Variable-Length Input and Gas Schedule Complexity
Gas uses k = floor(len / 160). k = 0 yields 0 precompile gas in the schedule, but the call still fails on empty input while the outer CALL costs non-zero gas — subtle for accounting and static analysis tools. Non-multiple lengths create the charged-k vs expected-k discrepancy described under T1.
Edge Cases
| Edge Case | Behavior | Security Implication |
|---|---|---|
| k = 0 (empty input) | Error; precompile gas component 0, but call fails; forwarded gas burned; CALL overhead still applies | Do not assume “zero MSM gas” means a cheap no-op |
| k = 1 | Valid single scalar multiply; 12,000 gas | Same security class as one multiply; no dedicated G1MUL |
| len(input) not divisible by 160 | k = floor(len/160) for gas; precompile errors (invalid length) | Griefing and accounting bugs if developers confuse gas k with logical record count |
| Scalar = 0 | Contribution is point at infinity; sum remains well-defined | Valid; encoding of infinity in output per EIP-2537 |
| Scalar = 1 | Contribution is that affine point | Valid |
| Scalar > q | Mathematically same as scalar mod q for group output | Do not rely on canonical 32-byte scalar form for uniqueness |
| All points infinity | Result is infinity (subject to encoding rules) | Callers must handle infinity outputs in verifiers |
| Very large k (e.g. 1000) | Gas ≈ 1000 * 12000 * 519 / 1000 = 6,228,000 (plus CALL costs) | DoS / block gas pressure if contracts allow unbounded batches |
Real-World Exploits
No Known Mainnet Exploits Specific to BLS12_G1MSM; BN254 Precedent
As of early 2026, there is no widely published exploit chain targeting BLS12_G1MSM specifically. The precompile shipped with Pectra (EIP-2537) and sees growing but still young adoption versus decades-old primitives.
The closest real-world lesson for curve precompile implementations remains CVE-2025-30147 (Hyperledger Besu, BN256 family): incorrect validation ordering between on-curve and subgroup checks led to consensus divergence risk. BLS12 MSM’s explicit subgroup check reduces that specific confusion class for G1 MSM, but any future client bug in parsing, infinity, or batching could still replicate the general pattern — cross-client tests and rapid patching matter.
References:
- EIP-2537: BLS12-381 Curve Operations
- CVE-2025-30147 - Ethereum Foundation Blog
- execution-specs PR #8910 (MSM discount repricing discussion)
Attack Scenarios
Scenario A: Gas Griefing via Length Not a Multiple of 160
// Attacker chooses len % 160 != 0: gas charged for floor(len/160) pairs, precompile ERRORS, forwarded gas BURNED.
pragma solidity ^0.8.0;
contract MsmLengthGrief {
address internal constant BLS12_G1MSM = address(0x0C);
function msmUserBytes(bytes calldata data) external {
// VULNERABLE: forwards all remaining gas; wrong length still burns it
bytes memory out = new bytes(128);
bool ok;
assembly {
ok := staticcall(gas(), BLS12_G1MSM, data.offset, data.length, add(out, 32), 128)
}
// If attacker sends e.g. 161 bytes: k_gas = 1, but call fails; entire forwarded gas can be lost.
require(ok, "msm failed");
}
}Scenario B: User-Controlled k with Fixed Gas Stipend
// Contract caps stipend without considering discount table growth of TOTAL MSM gas vs k.
pragma solidity ^0.8.0;
contract MsmFixedGas {
address internal constant BLS12_G1MSM = address(0x0C);
uint256 constant STIPEND = 500_000; // too small for large k, or mis-sized vs intended k
function msm(bytes calldata data) external returns (bytes memory) {
require(data.length % 160 == 0, "bad packing");
uint256 k = data.length / 160;
require(k >= 1, "empty msm");
// Insufficient STIPEND => OOG / failed call; sufficient STIPEND => may still overpay on error paths
bytes memory out = new bytes(128);
bool ok;
assembly {
ok := staticcall(STIPEND, BLS12_G1MSM, data.offset, data.length, add(out, 32), 128)
}
require(ok, "msm OOG or invalid input");
return out;
}
}
// Failure modes:
// 1. Honest user with legitimate large k hits OOG because STIPEND does not track EIP-2537 MSM gas schedule.
// 2. Attacker supplies invalid points/encodings: entire STIPEND burned per attempt (griefing).Mitigations
| Threat | Mitigation | Implementation |
|---|---|---|
| T1: Gas / k complexity | Bound k at the application layer; document EIP-2537 gas formula; reserve gas using official tables | require(k ⇐ K_MAX); off-chain batching; tests against execution-spec vectors |
| T1: Length / gas mismatch | require(input.length % 160 == 0) before call; require(input.length / 160 == expectedK) when applicable | Cheap calldata checks; never rely on floor semantics implicitly |
| T2: ADD vs MSM checks | Document which precompile implies subgroup checks; audit pipelines mixing ADD, MSM, pairing | Architecture notes; single BLS wrapper module |
| T3: Scalar canonicality | Treat scalars as integers mod q for cryptographic reasoning; reduce mod q off-chain for canonical logs | Do not use raw 32-byte equality as protocol unique IDs |
| T4: k = 1 path | Prefer k = 1 MSM over home-grown multiply; trust client optimizations for hot paths | Library support; benchmark verifiers on target networks |
| T5: Gas burning | Cap staticcall gas to computed MSM gas (from k) plus small buffer — not gas() | Explicit stipend from EIP-2537 schedule; handle success == false without reading stale buffers |
Compiler/EIP-Based Protections
- EIP-2537 (Pectra, May 2025): Defines BLS12_G1MSM at 0x0C, input/output layout, Pippenger MSM gas function with discount table, subgroup checks for G1 MSM, and all forwarded gas burned on failure.
- Execution-spec-tests: Conformance coverage for gas and outputs across clients; operators still need version hygiene after repricing PRs such as #8910.
- Solidity / Yul: No compiler-enforced success or returndata checks for precompiles — use explicit
staticcallsuccess,returndatasize() == 128, and gas stipends.
Severity Summary
| Threat ID | Category | Severity | Likelihood | Notes |
|---|---|---|---|---|
| T1 | Smart contract / DoS economics | High | Medium | Unbounded k, mis-accounted gas, length vs discount confusion |
| T2 | Smart contract / Spec understanding | Medium | Medium | ADD vs MSM validation asymmetry |
| T3 | Smart contract / Encoding | Medium | Low–Medium | Scalar non-canonical form; implementation timing nuance |
| T4 | Implementation quality | Informational | Low | k = 1 fast path — performance, not soundness |
| T5 | Smart contract / Griefing | Medium | Medium | Gas burn on error with variable MSM cost |
| P1 | Protocol / Economic | Medium | N/A | MSM repricing history; future hardfork risk |
| P2 | Protocol / Client performance | Medium | Low | Algorithm freedom; DoS margin variance |
| P3 | Protocol / Gas schedule | Medium | Medium | floor(len/160), k = 0 corner cases |
Related Precompiles
| Precompile | Relationship |
|---|---|
| BLS12_G1ADD (0x0B) | G1 addition; no subgroup check vs MSM’s subgroup check — integration asymmetry |
| BLS12_G2MSM (0x0E) | G2 multi-scalar multiply; same batch gas discount ideas, different curve security details |
| BLS12_PAIRING_CHECK (0x0F) | Pairing checks consuming G1/G2 points produced upstream by MSM / ADD / hash-to-curve |
| BN256MUL (0x07) | Earlier alt_bn128 scalar multiply; different encoding, gas, failure semantics |
| BLS12_MAP_FP_TO_G1 (0x10) | Hash-to-curve / map-to-G1; outputs feed MSM pipelines |