Precompile Summary

PropertyValue
Address0x0F
NameBLS12_PAIRING_CHECK
Gas37,700 base + 32,600 per input pair (37700 + 32600 * k). For k = 1: 70,300. For k = 2: 102,900. For k = 10: 363,700. For k = 100: ~3.3M gas.
Input384 * k bytes with k ≥ 1. Each 384-byte slice encodes one pair: G1 point (128 bytes) concatenated with G2 point (256 bytes). G1 points are 64-byte big-endian x followed by 64-byte big-endian y (Fp elements, top 16 bytes zero, value < p). G2 points are 128-byte x (Fp2) followed by 128-byte y (Fp2), each Fp2 element encoded as c0 (64 bytes) ‖ c1 (64 bytes).
Output32 bytes: first 31 bytes 0x00, last byte 0x01 (true) or 0x00 (false).
IntroducedPectra hardfork (EIP-2537, May 2025)
BehaviorPerforms a multi-pairing check on the BLS12-381 curve: verifies that e(P₁,Q₁) · e(P₂,Q₂) · … · e(Pₖ,Qₖ) = 1 in the target group GT. Performs subgroup checks on both G1 and G2 input points — unlike BLS12_G1ADD (0x0B) and BLS12_G2ADD (0x0D), which skip subgroup validation. Empty input (k = 0) returns error. On any error (invalid encoding, point not on curve, point not in subgroup, empty input, input length not divisible by 384), all gas forwarded to the precompile is burned and no return data is produced.

Threat Surface

BLS12_PAIRING_CHECK is the highest-stakes precompile in the BLS12-381 family (0x0B–0x0F) and occupies the same architectural position for BLS12-381 that BN256PAIRING (0x08) occupies for BN254: it is the final verification gate in pairing-based cryptographic protocols. While G1ADD, G1MSM, G2ADD, and G2MSM perform auxiliary curve arithmetic, BLS12_PAIRING_CHECK is where the security question is decided — does the pairing equation hold? Every BLS signature verifier, every aggregate BLS verification scheme, and every BLS12-381-based zero-knowledge proof system deployed on Ethereum’s execution layer ultimately reduces to a single call: does BLS12_PAIRING_CHECK return 0x01?

This position at the apex of the BLS12-381 verification chain gives the precompile a uniquely concentrated threat profile. A bug in G1ADD corrupts an intermediate sum; a bug in G2MSM produces a wrong multi-scalar product; but a bug in the pairing check directly determines whether an invalid signature or proof is accepted as valid. The consequences extend beyond individual contracts: Ethereum’s consensus layer uses BLS12-381 for validator signatures, and EIP-2537 enables execution-layer contracts to verify those same signatures — bridging the beacon chain’s cryptographic guarantees into smart contract logic.

The threat surface divides into five categories:

1. BLS signature verification correctness as the primary use case. BLS signature verification on BLS12-381 reduces to a pairing equation: e(sig, G2_gen) = e(H(m), pubkey), equivalently e(sig, G2_gen) · e(−H(m), pubkey) = 1. If BLS12_PAIRING_CHECK returns 0x01 (true) for an invalid signature, the BLS security guarantee collapses entirely. Use cases include bringing Ethereum consensus-layer BLS signature verification to the execution layer, cross-chain BLS verification, and aggregate BLS signature verification for multi-party schemes.

2. Subgroup validation as the critical correctness gate. BLS12_PAIRING_CHECK is the only ADD/MSM/PAIRING precompile that enforces subgroup checks on both G1 and G2 points in a single operation. For G1, the cofactor is 1, so the subgroup check is mathematically redundant but provides defense-in-depth. For G2, the cofactor is enormous (~3 × 10¹⁴⁷), and subgroup validation is essential — a non-subgroup G2 point in a pairing computation produces mathematically meaningless results. CVE-2025-30147 on BN254 demonstrated that even “redundant” curve membership checks can be the difference between a secure implementation and a consensus-splitting vulnerability.

3. Behavioral divergence from BN256PAIRING on empty input. BN256PAIRING (0x08) returns 1 (true) for empty input — the empty product of pairings is the identity element in GT. BLS12_PAIRING_CHECK returns error for empty input (k = 0), burning all forwarded gas. This semantic difference is a trap for developers porting BN254 pairing code to BLS12-381: code that treats an empty pairing as a vacuously true check will silently break, potentially with catastrophic consequences if the error is not caught.

4. Variable gas cost enabling economic attacks. Gas scales linearly with the number of pairs at 32,600 gas per pair plus a 37,700 base. Contracts that accept user-controlled pairing inputs without bounding k face gas griefing. The gas formula uses floor(len / 384) to determine k — if the input length is not divisible by 384, gas is charged for floor(k) pairs but the precompile errors and burns all forwarded gas, creating a mismatch between cost and outcome.

5. Cross-client library divergence on the most complex BLS12 operation. The pairing computation involves Miller loop computation, final exponentiation, and both G1 and G2 point validation. Different clients use different libraries — blst (C), gnark-crypto (Go), arkworks (Rust) — each of which must handle subgroup checks, point-at-infinity semantics, and final exponentiation identically. Pairings are the most computationally intensive and algorithmically complex BLS12-381 operation, making them the highest-risk surface for implementation divergence. CVE-2025-30147 on BN254 was exactly this class of bug in Besu’s pairing implementation.


Smart Contract Threats

T1: BLS Signature Verification Correctness (Critical)

BLS12_PAIRING_CHECK is the final gate for BLS signature verification on BLS12-381. The standard BLS signature check reduces to: verify that e(sig, G2_generator) · e(−H(m), pubkey) = 1 in GT. If the pairing check returns 0x01 for an invalid signature, the entire BLS security guarantee collapses.

  • Complete security collapse on false positives. If BLS12_PAIRING_CHECK can be made to return 0x01 for a pair set that does not satisfy the pairing equation, any protocol relying on BLS signature verification is compromised. The unforgeability property — “no adversary without the secret key can produce a valid signature” — depends entirely on the pairing computation’s correctness.

  • Ethereum consensus bridge. EIP-2537 enables execution-layer contracts to verify the same BLS12-381 signatures used by Ethereum’s consensus layer. A pairing bug would allow forged “consensus attestations” to be accepted on-chain, enabling fraudulent beacon-state claims, cross-layer bridges, and validator-set manipulations in smart contracts.

  • Aggregate BLS signature verification. Aggregate BLS signatures verify multiple signer-message pairs in a single pairing check with k pairs. The efficiency gain is the primary motivation for BLS aggregation, but it also means a single pairing bug can compromise the verification of arbitrarily many signatures simultaneously.

  • Direct analogy to BN256PAIRING for Groth16. BN256PAIRING (0x08) is the final verification step for Groth16 zkSNARK proofs; BLS12_PAIRING_CHECK is the final verification step for BLS signatures. Both occupy the same architectural position — the single point of failure for their respective cryptographic ecosystems — and share the same threat model: a false positive is catastrophic.

Why it matters: BLS12_PAIRING_CHECK is the single point of failure for every BLS12-381-based verification system on Ethereum’s execution layer. Unlike application-level bugs that affect individual contracts, a pairing implementation bug threatens every BLS verifier simultaneously.

T2: Subgroup Check Correctness on Both G1 and G2 (High)

BLS12_PAIRING_CHECK enforces subgroup membership for both G1 and G2 input points — the only BLS12 ADD/MSM/PAIRING precompile to validate both groups in a single call.

  • G1 subgroup check is mathematically redundant but operationally critical. BLS12-381 G1 has cofactor 1, so every point on the G1 curve is in the prime-order subgroup. The subgroup check cannot reject a valid on-curve G1 point. However, CVE-2025-30147 demonstrated that implementation bugs can confuse subgroup checks with curve membership checks — gnark-crypto performed subgroup checks on BN254 without verifying curve membership, which is meaningless for off-curve points. The “redundant” G1 subgroup check serves as defense-in-depth against this class of implementation error.

  • G2 subgroup check is essential. G2’s cofactor is enormous (~3.05 × 10¹⁴⁷). Without a correct subgroup check, non-subgroup G2 points would enter the Miller loop, producing results that are mathematically undefined with respect to the pairing equation. The pairing could return 0x01 for inputs that should fail, or 0x00 for inputs that should succeed — either outcome is catastrophic.

  • Implementation ordering matters. The subgroup check must be performed after curve membership verification, not instead of it. A subgroup check on an off-curve point is undefined — the point is not a group element, so “subgroup membership” has no meaning. This is exactly what CVE-2025-30147 got wrong: the library checked subgroup membership but assumed curve membership, creating a window where off-curve points were processed as valid.

  • Cross-precompile validation asymmetry. G1ADD (0x0B) and G2ADD (0x0D) perform no subgroup checks. G1MSM (0x0C) and G2MSM (0x0E) perform subgroup checks. PAIRING_CHECK performs subgroup checks. Points that flow through ADD operations into PAIRING_CHECK may be rejected at the pairing step, causing unexpected failures in pipelines that assumed all points were pre-validated.

Why it matters: The subgroup check is the last line of defense before the pairing computation. If it fails to catch a non-subgroup point — especially on G2 — the pairing result is meaningless, and any protocol relying on it is broken.

T3: Variable Gas Cost and DoS (Medium)

Gas cost is 37,700 + 32,600 × k, where k is the number of pairs. A contract that accepts user-controlled pairing inputs without bounding k is exposed to gas griefing.

  • Linear cost scaling. For k = 1: 70,300 gas. For k = 10: 363,700 gas. For k = 100: ~3.3M gas. An attacker who controls k can force expensive computations within a single transaction.

  • Floor-length gas mismatch. The precompile derives k = floor(len / 384). If the input length is not divisible by 384, gas is charged for floor(k) pairs, but the precompile errors and burns all forwarded gas. An attacker can submit 384 * k + 1 bytes: the contract or caller pays gas proportional to k, but the precompile fails and all that gas is consumed.

  • Relayer and keeper asymmetry. Systems where a relayer or keeper pays gas to verify user-submitted BLS signatures are especially vulnerable: the user chooses the signature aggregation structure (and thus k), while the relayer bears the gas cost.

  • Comparison with BN256PAIRING. BN256PAIRING charges 45,000 + 34,000 × k — similar structure but different constants. Direct gas constant reuse across the two precompiles will produce incorrect estimates.

Why it matters: While linear gas scaling is well-designed, contracts accepting variable-length pairing inputs must enforce explicit bounds on k and validate that len % 384 == 0 before forwarding to the precompile.

T4: Empty Input Behavioral Difference from BN256PAIRING (High)

BN256PAIRING (0x08) returns 1 (true) for empty input — the empty product of pairings is the identity element in GT. BLS12_PAIRING_CHECK returns error for empty input (k = 0), burning all forwarded gas and producing no return data.

  • Porting trap. Developers migrating BN254 pairing code to BLS12-381 may assume the same empty-input semantics. Code that relies on k = 0 returning true as a base case (e.g., recursive or batch verification with a dynamic number of pairs) will fail silently on BLS12.

  • Stale memory exploitation. If the calling contract does not check the return value and the output buffer contained 0x01 from a previous operation, a failed empty-pairing call leaves that stale 0x01 in place. The contract reads “true” for a check that actually errored. This combines T4 with T5.

  • Gas impact. The empty call burns the full 37,700 base gas (plus whatever gas the contract forwarded) on error. This is not a free “vacuous truth” — it is an expensive failure.

Why it matters: This behavioral difference is one of the most likely sources of subtle bugs when BN254 pairing systems are ported to BLS12-381. The failure mode — silent error interpreted as success via stale memory — is especially dangerous.

T5: Unchecked Return Value (High)

If the CALL to 0x0F fails — due to invalid points, insufficient gas, empty input, or malformed encoding — and the calling contract does not check the return value, execution continues with whatever data was already in the output memory region. If that stale data happens to represent 0x01 (true), the contract treats a failed or invalid pairing check as verified.

  • Silent failure pattern. BLS12_PAIRING_CHECK produces no return data on failure (0 bytes returned). A low-level staticcall in Solidity or assembly that writes to a pre-allocated memory buffer will leave the buffer unchanged on failure. If the buffer was not zeroed, or if a previous successful call left 0x01 in the same location, the contract reads stale success.

  • Gas burning amplifies the risk. On failure, all forwarded gas is burned. If the contract forwarded most of its gas to the pairing call and the call fails, the contract may not have enough remaining gas to revert — it continues execution in a degraded state, potentially accepting the stale result.

  • Interaction with T4. A contract ported from BN254 that expects empty input to return true, combined with an unchecked return value, will read stale 0x01 from the output buffer — accepting a “verification” that never executed.

Why it matters: The combination of no-return-data-on-failure, gas burning, and stale-memory risk makes BLS12_PAIRING_CHECK particularly dangerous to call without rigorous return value checking. Every contract calling this precompile should verify both success == true and returndatasize() == 32.

T6: Cross-Group Composition Bugs (Medium)

Each 384-byte input slice has strict internal structure: 128 bytes for a G1 point followed by 256 bytes for a G2 point.

  • Size-based rejection of swapped groups. A G1 point is 128 bytes; a G2 point is 256 bytes. Placing a G2 point in the G1 position (or vice versa) changes the total pair size, causing the precompile to reject the input as malformed. This provides some protection against group confusion.

  • Partial parsing risk. In a buggy implementation, providing 128 bytes of valid G1 encoding followed by 256 bytes that happen to form a valid-looking G2 encoding — but constructed from G1 coordinates — might be partially parsed. While EIP-2537-conformant implementations must reject any point not on its respective curve, implementation bugs in curve membership or subgroup checks could allow unexpected behavior.

  • Fp vs Fp2 confusion. G1 coordinates are Fp elements (64 bytes each); G2 coordinates are Fp2 elements (128 bytes each, composed of two 64-byte Fp components). Misinterpreting the field structure could cause implementations to read Fp2 coefficients from wrong offsets, especially in hand-written assembly callers.

Why it matters: The heterogeneous input structure (mixed G1 and G2 encoding within each pair) creates a wider surface for serialization errors than precompiles that accept only one group’s points.


Protocol-Level Threats

P1: Cross-Client Library Divergence on the Most Complex BLS12 Operation

Different EVM clients implement BLS12_PAIRING_CHECK using different cryptographic libraries:

ClientLibrary
gethgnark-crypto (Go)
Rethblst / arkworks (Rust)
Besugnark-crypto / besu-native
Nethermindblst (via bindings)
Erigongnark-crypto (Go)

The pairing computation is the most algorithmically complex BLS12-381 operation: it involves point validation, Miller loop computation over the sextic twist, and final exponentiation in a degree-12 extension field. Each library must produce identical results for all inputs — including edge cases involving points at infinity, non-subgroup points, non-canonical encodings, and boundary field elements. Any divergence is a consensus bug.

P2: BLS12-381 Provides ~120-Bit Security — the Upgrade from BN254

BLS12-381 offers approximately 120–128 bits of security, versus BN254’s ~100 bits. This improved security margin was the primary motivation for EIP-2537 and the introduction of BLS12-381 precompiles. The pairing check on BLS12-381 is the mechanism that brings this improved security to on-chain verification.

  • Migration from BN254. New pairing-based protocols should use BLS12-381 via BLS12_PAIRING_CHECK rather than BN254 via BN256PAIRING. However, the two curves have different field sizes, encoding formats, gas costs, and edge-case behaviors (particularly around empty input), making migration non-trivial.

  • Consensus layer alignment. Ethereum’s beacon chain already uses BLS12-381 for validator signatures. EIP-2537 enables execution-layer contracts to verify those same signatures, bridging the consensus and execution layers’ cryptographic foundations.

P3: CVE-2025-30147 Applies Directly to BLS12 Pairing Implementations

CVE-2025-30147 affected BN254 pairing operations in Hyperledger Besu: gnark-crypto performed subgroup checks without verifying curve membership, allowing off-curve points to produce incorrect pairing results. The same vulnerability class applies to BLS12-381 pairings:

  • BLS12-381 has a larger field (381 bits vs 254 bits), more complex G2 serialization (Fp2 elements), and different subgroup check algorithms. The attack surface for validation gaps is wider than for BN254.

  • The fix for BN254 (explicit curve membership checks before subgroup checks) must be applied consistently to BLS12-381 implementations. A client that fixed the issue for BN254 but uses a different code path for BLS12-381 could still be vulnerable.

  • BLS12_PAIRING_CHECK is the highest-impact target for this vulnerability class within the BLS12 family, because it directly determines verification outcomes.

P4: Implementation-Level Optimization Divergence

The pairing computation admits many performance optimizations: lazy reduction in extension-field arithmetic, optimized final exponentiation (via Frobenius endomorphism shortcuts), batch Miller loop computation, and parallel subgroup checking. Different libraries apply different subsets of these optimizations.

  • Timing differences. Even when results are correct, different optimization strategies produce different execution times. On slower validator hardware, a worst-case pairing input that triggers expensive code paths in a specific library could delay block processing.

  • Correctness coupling. Aggressive optimizations (e.g., skipping intermediate reductions, combining exponentiations) increase the risk of subtle numerical bugs that produce incorrect results only for specific input patterns. Such bugs may not be caught by standard test vectors but could be triggered by adversarial inputs.


Edge Cases

Edge CaseBehaviorSecurity Implication
k = 0 (empty input)Error. All forwarded gas burned.Critical difference from BN256PAIRING, which returns 1 (true) for empty input. Contracts porting from BN254 that rely on the “empty product = identity” semantics will break. Combined with unchecked return values (T5), this can cause stale-memory exploitation.
Input length not divisible by 384Error. Gas charged for floor(len / 384) pairs. All forwarded gas burned.Floor-length mismatch: gas is computed based on partial pair count, but precompile fails. Attacker can force gas waste with 384k + 1 byte inputs.
Point at infinity in G1 (128 zero bytes)Valid. Pairing e(O, Q) contributes the identity element to the product.Legitimate in some protocol constructions. Should not be rejected.
Point at infinity in G2 (256 zero bytes)Valid. Pairing e(P, O) contributes the identity element to the product.Same as G1 infinity — valid and produces identity contribution.
All points at infinity (all pairs)Returns 0x01 (true). Product of identity elements is identity.Edge case that must be handled correctly. Could be exploitable if a verifier accidentally passes zeroed memory as input — the “verification” succeeds vacuously.
Single pair (k = 1)Checks whether e(P, Q) = 1 in GT. 70,300 gas.Valid but uncommon in standard BLS verification (which typically uses 2 pairs). Useful for testing whether a specific pairing evaluates to the identity.
G1 point not on BLS12-381 curveError. All forwarded gas burned.Correct behavior. Implementations must check curve membership before subgroup membership (CVE-2025-30147 lesson).
G1 point on curve but not in subgroupError. All forwarded gas burned.For BLS12-381 G1 (cofactor 1), this case cannot arise for a valid on-curve point. The check is redundant but serves as defense-in-depth.
G2 point not on BLS12-381 twist curveError. All forwarded gas burned.Correct behavior. G2 curve membership must be verified independently of subgroup membership.
G2 point on curve but not in prime-order subgroupError. All forwarded gas burned.Essential check. G2 cofactor is ~3 × 10¹⁴⁷; non-subgroup G2 points are abundant and would produce meaningless pairing results.
Maximum practical pair countGas-limited. At ~32,600 gas per pair + 37,700 base, ~920 pairs fill a 30M gas block.DoS vector if pair count is user-controlled. See T3.
Fp coordinate ≥ field modulusError. Gas burned.Canonical Fp encoding required: value < p, top 16 bytes zero.
Fp2 c0/c1 components swappedIf resulting point is on-curve and in subgroup: accepted with wrong pairing result. If not: error.Silent logic bug when the misordered Fp2 element happens to produce a valid point — especially dangerous because the wrong pairing result may still be 0x01 or 0x00 with no indication of the encoding error.

Real-World Exploits

Exploit 1: No Known Exploits Specific to BLS12_PAIRING_CHECK (Precompile Active Since May 2025)

Root cause: N/A — no confirmed exploitation of BLS12_PAIRING_CHECK has occurred as of early 2026. The precompile has approximately ten months of mainnet exposure.

Details: BLS12_PAIRING_CHECK was introduced with the Pectra hardfork (EIP-2537, May 2025). Multiple client teams have invested in cross-client testing, and the execution-spec-tests repository contains comprehensive test vectors for all BLS12-381 precompiles, including edge cases for pairing operations, subgroup validation, and encoding errors. The Ethereum Foundation’s cryptography team and external auditors have reviewed the BLS12-381 implementations used by major clients.

However, the absence of known exploits should not be interpreted as proof of correctness. The BN254 precompiles operated for eight years before CVE-2025-30147 was discovered, and the BLS12 pairing is both newer and more complex than BN254 pairing. BLS12_PAIRING_CHECK’s relative novelty and algorithmic complexity make it a high-priority target for ongoing security research.

BLS12_PAIRING_CHECK’s role: The precompile is the sole on-chain mechanism for BLS12-381 pairing verification. Its correctness is assumed by every BLS signature verifier, every aggregate verification scheme, and every future BLS12-381-based proof system on the execution layer.

Impact: No impact to date. Potential impact of a future exploit is critical — simultaneous compromise of every BLS12-381-based verification system on Ethereum.

References:


Exploit 2: CVE-2025-30147 — BN254 Pairing Consensus Bug as Direct Precedent

Root cause: Hyperledger Besu’s integration of gnark-crypto for BN254 precompile operations introduced a point validation gap: gnark-crypto’s subgroup check assumed curve membership without verifying it. Off-curve points entered the pairing computation, producing incorrect results that differed from other clients.

Details: CVE-2025-30147 affected all three BN256 precompiles (0x06, 0x07, 0x08), with BN256PAIRING (0x08) as the highest-impact target. An attacker could craft a transaction with off-curve G1 or G2 points that Besu would accept (passing the meaningless subgroup check) while geth, Nethermind, and other clients would correctly reject. On Besu, the pairing computation would proceed with off-curve inputs, potentially returning a false 1 (accepting an invalid proof) or false 0 (rejecting a valid proof). This divergence constitutes a consensus split.

BLS12_PAIRING_CHECK’s role: The vulnerability class — subgroup check without curve membership verification — applies directly to BLS12-381 pairing implementations. BLS12-381 has a larger field (381 bits vs 254 bits), more complex G2 serialization (Fp2 elements with c0 ‖ c1 ordering), and different subgroup check algorithms, making the implementation surface area wider than BN254. A client that fixed CVE-2025-30147 for BN254 but uses a different code path for BLS12-381 could still be vulnerable to the same class of bug.

Impact: CVE-2025-30147 was patched in Besu 25.3.0 before mainnet exploitation. On Ethereum mainnet (where Besu is a minority client), the potential impact was consensus divergence. On any network where Besu was the majority client, an attacker could have enshrined invalid state — accepting forged proofs, corrupting ZK rollup state, or stealing funds from pairing-dependent protocols. The same impact profile applies to BLS12_PAIRING_CHECK if an analogous bug exists in any client’s BLS12-381 implementation.

References:


Exploit 3: Ethereum Beacon Chain BLS Verification — Execution Layer Bridge (Assessed Systemic Risk)

Root cause: N/A — this is an assessed systemic dependency, not a specific exploit.

Details: Ethereum’s consensus layer uses BLS12-381 signatures for validator attestations, sync committee signatures, and block proposals. EIP-2537 enables execution-layer smart contracts to verify these same signatures via BLS12_PAIRING_CHECK, creating a bridge between the consensus and execution layers. Protocols that rely on this bridge — such as light client verifiers, cross-chain bridges consuming beacon state proofs, and restaking protocols verifying validator commitments — inherit BLS12_PAIRING_CHECK as a critical dependency.

If BLS12_PAIRING_CHECK were compromised, an attacker could forge beacon chain signatures that execution-layer contracts accept as valid, enabling: fabricated sync committee attestations for cross-chain bridges, forged validator signatures for restaking/slashing protocols, and fake finality proofs for light client bridges.

BLS12_PAIRING_CHECK’s role: It is the execution-layer enforcement mechanism for consensus-layer BLS signature validity. No secondary verification exists within the EVM.

Impact: A pairing vulnerability affecting beacon chain BLS verification would have cascading consequences across the entire ecosystem of protocols that bridge consensus-layer data to execution-layer logic.

References:


Attack Scenarios

Scenario A: BLS Signature Verifier That Doesn’t Check PAIRING_CHECK Return Value

A contract that verifies BLS signatures by calling BLS12_PAIRING_CHECK but does not check the staticcall return value. On failure (invalid signature encoding, insufficient gas, empty input), the output buffer retains stale data that may be interpreted as a successful verification.

pragma solidity ^0.8.0;
 
contract VulnerableBLSVerifier {
    address constant BLS12_PAIRING_CHECK = address(0x0F);
 
    function verifyBLSSignature(
        bytes calldata pairingInput  // 768 bytes: 2 pairs for BLS sig check
    ) external view returns (bool) {
        uint256 result;
 
        assembly {
            // Pre-set result to 1 (dangerous stale value)
            mstore(0x00, 1)
 
            // Call BLS12_PAIRING_CHECK without checking success
            let ok := staticcall(
                gas(),
                BLS12_PAIRING_CHECK,
                add(pairingInput.offset, 0x20),
                pairingInput.length,
                0x00,
                0x20
            )
            // BUG: 'ok' is never checked.
            // On failure: no return data written, mstore(0x00, 1) persists.
            result := mload(0x00)
        }
 
        // If staticcall failed (invalid input, OOG), result is still 1
        // Contract treats this as "BLS signature verified"
        return result == 1;
    }
}
 
// Attack:
// 1. Submit pairingInput with an invalid G2 point (not on curve).
// 2. BLS12_PAIRING_CHECK errors, burns all gas, returns 0 bytes.
// 3. Memory at 0x00 still contains 1 from the mstore.
// 4. Contract returns true — "BLS signature verified" for invalid input.
//
// Mitigation: check both `ok` and `returndatasize() == 32`.
// Zero the output buffer before the call: mstore(0x00, 0).

Scenario B: Contract Ported from BN254 That Assumes Empty Pairing Returns True

A contract originally written for BN256PAIRING (0x08) is adapted for BLS12-381. The original code relies on the fact that BN256PAIRING returns 1 for empty input (0 pairs). On BLS12_PAIRING_CHECK, empty input returns error.

pragma solidity ^0.8.0;
 
contract PortedFromBN254 {
    address constant BLS12_PAIRING_CHECK = address(0x0F);
 
    // Batch BLS signature verification with dynamic number of pairs.
    // Original BN254 version: empty batch = vacuously true.
    // BLS12 version: empty batch = ERROR.
    function batchVerify(bytes calldata pairingData) external view returns (bool) {
        // This worked on BN256PAIRING (0x08): empty input returns 1 (true)
        // On BLS12_PAIRING_CHECK: empty input returns ERROR, burns all gas
 
        if (pairingData.length % 384 != 0) {
            return false;
        }
        // BUG: does not check pairingData.length > 0
        // When pairingData is empty (0 bytes), k=0, which is an error on BLS12
 
        (bool success, bytes memory result) = BLS12_PAIRING_CHECK.staticcall(
            pairingData
        );
 
        if (!success || result.length != 32) {
            // On BN254 this branch was never hit for empty input.
            // On BLS12, empty input always hits this branch.
            return false;
        }
 
        return abi.decode(result, (uint256)) == 1;
    }
}
 
// Impact:
// 1. Caller passes empty pairingData (e.g., batch with 0 signatures to verify).
// 2. On BN254: returned true (vacuously true, correct for empty batch).
// 3. On BLS12: staticcall fails, all forwarded gas burned, returns false.
// 4. If the caller's logic treats false as "batch invalid" rather than "error",
//    the system may reject legitimate empty batches or waste gas.
// 5. Worse: if another code path pre-populates the output buffer with 0x01
//    and the staticcall failure is not properly handled, stale data is read as true.
//
// Mitigation: explicitly handle k=0 before calling the precompile.
// if (pairingData.length == 0) return true; // or false, per protocol semantics

Scenario C: Gas Griefing by Submitting Many Pairs

A contract that accepts user-specified BLS pairing inputs without bounding the number of pairs. An attacker submits many pairs to force excessive gas consumption.

pragma solidity ^0.8.0;
 
contract UnboundedBLSPairingVerifier {
    address constant BLS12_PAIRING_CHECK = address(0x0F);
    event VerificationResult(bool verified);
 
    function verifyPairing(bytes calldata pairingInput) external {
        require(pairingInput.length % 384 == 0, "invalid length");
        require(pairingInput.length > 0, "empty input");
        // VULNERABLE: no upper bound on number of pairs.
        // Gas cost: 37,700 + 32,600 * (pairingInput.length / 384)
        // 10 pairs = 363,700 gas; 50 pairs = 1,667,700 gas; 100 pairs = 3,297,700 gas
 
        (bool success, bytes memory result) = BLS12_PAIRING_CHECK.staticcall{
            gas: gasleft()
        }(pairingInput);
 
        if (success && result.length == 32) {
            emit VerificationResult(abi.decode(result, (uint256)) == 1);
        } else {
            emit VerificationResult(false);
        }
    }
}
 
// Attack:
// 1. Attacker calls verifyPairing with 100 pairs (38,400 bytes).
// 2. Precompile consumes 3,297,700 gas.
// 3. If a relayer with a gas stipend pays for verification, the relayer bears the cost.
// 4. Repeated calls with maximal pair counts drain the relayer's ETH.
// 5. Even if the points are invalid and the call fails, all forwarded gas is burned.
//
// Mitigation: require(pairingInput.length / 384 <= MAX_PAIRS, "too many pairs");
// For BLS signature verification, MAX_PAIRS is typically 2 (single sig) or small.

Mitigations

ThreatMitigationImplementation
T1: BLS signature forgery via pairing bugUse audited BLS verification libraries; defense in depth beyond the pairing resultWrap BLS12_PAIRING_CHECK in a well-tested library. Add application-level sanity checks: nonce validation, domain separation, rate limiting. Do not hand-roll pairing verification logic.
T1: False positive from implementation bugClient implementations must validate curve membership before subgroup checks for both G1 and G2Explicit is_on_curve() followed by is_in_subgroup() for each point. Never rely on subgroup check alone (CVE-2025-30147 lesson).
T2: Subgroup check correctnessFuzz subgroup check implementations across libraries; use execution-spec-tests vectorsCross-client differential testing. Test non-subgroup G2 points are rejected. Verify that on-curve but non-subgroup points produce errors, not incorrect results.
T3: Gas griefingEnforce maximum pair count before calling precompilerequire(pairingInput.length / 384 <= MAX_PAIRS) where MAX_PAIRS matches the expected protocol structure (typically 2 for BLS sig verification).
T3: Floor-length mismatchValidate exact input length divisibilityrequire(pairingInput.length % 384 == 0 && pairingInput.length > 0) before the staticcall.
T4: Empty input errorExplicitly handle k = 0 before calling precompileif (pairingInput.length == 0) { /* handle per protocol semantics */ } — do not let empty input reach the precompile.
T5: Unchecked return valueAlways check staticcall success and return data sizerequire(success && returndatasize() == 32, "pairing call failed"). Zero the output buffer before the call: mstore(outputPtr, 0).
T5: Stale memoryZero the output buffer before calling the precompileIn assembly: mstore(outputPtr, 0) before the staticcall. Ensures a failed call leaves 0 (false), not stale 1 (true).
T6: Encoding errorsUse canonical serialization helpers for G1 and G2 pointsValidate Fp element ranges (< p, top 16 bytes zero) and Fp2 c0 ‖ c1 ordering. Use tested off-chain libraries (blst, arkworks) for encoding.
GeneralCentralize BLS12 precompile calls in one audited moduleSingle wrapper for G1ADD/G1MSM/G2ADD/G2MSM/PAIRING_CHECK with uniform error handling, gas stipend management, and return value checking.

Compiler/EIP-Based Protections

  • EIP-2537 (Pectra, May 2025): Defines BLS12_PAIRING_CHECK at address 0x0F with gas 37,700 + 32,600k, input/output layouts, mandatory subgroup checks on both G1 and G2 points, empty-input error behavior, and burn-all-forwarded-gas-on-error semantics. The specification requires that implementations reject non-canonical encodings, off-curve points, and non-subgroup points before the pairing computation proceeds.

  • EIP-2537 subgroup check mandate: Unlike the ADD precompiles (which skip subgroup checks by design), PAIRING_CHECK must enforce subgroup membership. This is the strongest validation guarantee in the BLS12 precompile family.

  • Execution-spec-tests: The Ethereum Foundation maintains comprehensive test vectors for BLS12_PAIRING_CHECK covering valid pairings, invalid pairings, edge-case encodings, non-subgroup points, empty input, non-divisible lengths, and points at infinity. All client implementations must pass these vectors before release.

  • Cross-client differential testing: Following CVE-2025-30147, client teams have invested in differential fuzzing between BLS12-381 library implementations. This is the primary defense against implementation divergence in the pairing computation.

  • Solidity / Yul: No compiler-enforced check of precompile success or return length. Mitigations are purely programmatic: explicit staticcall result checking, returndatasize() validation, output buffer zeroing, and gas stipend management.


Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart Contract / ProtocolCriticalLow–MediumNo known BLS12 pairing forgery. Direct analogy to BN256PAIRING’s role in Groth16 — a false positive would compromise every BLS12-381-based verifier on Ethereum simultaneously. CVE-2025-30147 demonstrated this vulnerability class is practical, not theoretical.
T2Smart Contract / ProtocolHighLowCVE-2025-30147 showed that subgroup-check-without-curve-check is a real implementation pattern. BLS12-381 G2 subgroup checks are essential (cofactor ~3 × 10¹⁴⁷) and more complex to implement than BN254’s, increasing the risk of analogous bugs.
T3Smart ContractMediumMediumGas griefing is a generic pattern for variable-cost precompiles. No known incident specific to BLS12_PAIRING_CHECK, but contracts accepting variable-length pairing inputs without bounds are vulnerable. Same pattern as BN256PAIRING T3.
T4Smart ContractHighMediumEmpty-input behavioral difference from BN256PAIRING is a direct porting trap. No known exploit, but the failure mode (stale memory interpreted as success) is well-documented in BN254 pairing audit findings.
T5Smart ContractHighMediumUnchecked return values are a recurring audit finding across precompile-calling contracts. The combination of gas burning, no return data on failure, and stale-memory risk makes this especially dangerous. Same pattern as BN256PAIRING T4.
T6Smart ContractMediumLowNo known exploit from G1/G2 encoding confusion in pairing calls. The 128/256 byte size difference provides natural protection, but Fp2 ordering bugs within G2 can produce silent wrong answers.
P1ProtocolHighLowCross-client library divergence is an ongoing risk. CVE-2025-30147 demonstrated consensus divergence from pairing library bugs. BLS12-381 pairing is more complex than BN254, widening the attack surface.
P2ProtocolInformationalN/ABLS12-381’s improved security margin (~120 bits vs ~100 bits for BN254) is the motivation for EIP-2537. No active threat — this is a security improvement.
P3ProtocolCriticalLowCVE-2025-30147 is the direct precedent. The vulnerability class (incomplete point validation in pairing implementations) is confirmed practical and applies to BLS12-381 with an even wider implementation surface.
P4ProtocolMediumLowOptimization-induced divergence is a known risk class for complex cryptographic operations. Mitigated by differential testing but never fully eliminated.

PrecompileRelationship
BLS12_G1ADD (0x0B)G1 point addition without subgroup check. Used in intermediate BLS12-381 computations that may feed into PAIRING_CHECK. Points accepted by G1ADD are also accepted by PAIRING_CHECK (G1 cofactor = 1), but developers should not generalize this to G2.
BLS12_G1MSM (0x0C)G1 multi-scalar multiplication with subgroup check. Computes linear combinations of G1 points (e.g., computing H(m) from public inputs). Outputs may be used as G1 inputs to PAIRING_CHECK.
BLS12_G2ADD (0x0D)G2 point addition without subgroup check. G2 cofactor is enormous, so G2ADD outputs may be non-subgroup points that PAIRING_CHECK rejects. The validation gap between G2ADD (no subgroup check) and PAIRING_CHECK (subgroup check enforced) is a key architectural concern.
BLS12_G2MSM (0x0E)G2 multi-scalar multiplication with subgroup check. Like PAIRING_CHECK, enforces G2 subgroup membership. Points that pass G2MSM are guaranteed to be accepted by PAIRING_CHECK’s G2 validation.
BN256PAIRING (0x08)BN254 pairing check for Groth16 zkSNARK verification. Direct predecessor to BLS12_PAIRING_CHECK in architectural role. Key differences: BN254 uses a different curve with weaker security (~100 bits), returns 1 for empty input (BLS12 returns error), uses different gas constants (45,000 + 34,000k), and uses 192-byte pairs (vs 384-byte). CVE-2025-30147 affected BN256PAIRING and is the direct precedent for BLS12_PAIRING_CHECK vulnerability research.
POINT_EVALUATION (0x0A)KZG point evaluation for EIP-4844 blob verification. Also uses BLS12-381 internally (for the KZG pairing check), but as a fixed-format single-purpose precompile rather than a general-purpose pairing primitive. Shares BLS12-381 library dependencies and cross-client divergence risks.