Precompile Summary
| Property | Value |
|---|---|
| Address | 0x08 |
| Name | BN256PAIRING (alt_bn128 pairing check, ecPairing) |
| Gas | 45,000 base + 34,000 per input pair (post-EIP-1108, Istanbul). Previously 100,000 + 80,000 per pair (Byzantium). |
| Input | Variable length, must be a multiple of 192 bytes. Each 192-byte chunk encodes one pair: G1 point (64 bytes: x, y) followed by G2 point (128 bytes: x_imag, x_real, y_imag, y_real). |
| Output | 32 bytes: 1 (true) if the pairing check succeeds, 0 (false) otherwise. Returns empty on invalid input. |
| Introduced | Byzantium (EIP-197) |
| Behavior | Performs an elliptic curve pairing check on the alt_bn128 (BN254) curve. Verifies that the product of pairings e(a₁,b₁) · e(a₂,b₂) · … · e(aₖ,bₖ) = 1 (the identity element in the target group GT). This is the fundamental on-chain operation for zkSNARK proof verification (Groth16). If any input point is not on its respective curve or in the correct subgroup, the call fails and returns empty output. |
Threat Surface
BN256PAIRING is the most security-critical of the three BN256 precompiles (0x06, 0x07, 0x08) and arguably the most security-critical precompile on Ethereum. While BN256ADD and BN256MUL perform auxiliary elliptic curve arithmetic, BN256PAIRING is the final verification gate in zkSNARK proof systems. Every Groth16 verifier deployed on Ethereum—whether for a privacy protocol, a ZK rollup state transition, or a verifiable computation scheme—ultimately reduces to a single question: does BN256PAIRING return 1?
This position at the apex of the verification chain gives BN256PAIRING a unique threat profile. A bug in point addition (0x06) corrupts an intermediate computation; a bug in scalar multiplication (0x07) produces a wrong group element; but a bug in pairing verification directly determines whether an invalid proof is accepted as valid. The consequences are not abstract: ZK rollups (zkSync, Polygon zkEVM, Scroll, Linea) use BN256PAIRING to validate state transitions, meaning a pairing bug could allow an attacker to enshrine an invalid state root—effectively minting tokens, stealing funds, or corrupting the entire rollup state.
The threat surface divides into four categories:
1. Point validation failures causing false verification results. The pairing check requires that all G1 points lie on the BN254 curve and all G2 points lie on both the curve and the correct prime-order subgroup. CVE-2025-30147 demonstrated that even well-maintained client implementations can fail to enforce these checks: Besu’s gnark-crypto integration performed subgroup checks without verifying curve membership, allowing off-curve points to produce incorrect pairing results. In a zkSNARK context, a false 1 return means accepting a forged proof.
2. zkSNARK proof forgery through pairing manipulation. Because Groth16 verification reduces to a pairing equation, any way to make BN256PAIRING return 1 for an invalid proof collapses the entire security guarantee of the SNARK system. Attack vectors include providing off-curve points that pass validation in buggy implementations, exploiting subgroup check weaknesses to find points that satisfy the pairing equation without a valid witness, and submitting crafted inputs that trigger implementation-specific edge cases.
3. Smart contract integration failures. Contracts that call BN256PAIRING may fail to check the return value, may not validate that inputs are well-formed before passing them to the precompile, or may forward insufficient gas for the variable-cost computation. Each of these patterns can convert a secure pairing check into an exploitable vulnerability.
4. Cross-client implementation divergence. Different EVM clients use different pairing libraries—gnark-crypto (Besu), bn256 from Google/Cloudflare (geth), arkworks (Reth), and others. Each library must handle G2 subgroup checks, point decompression, and final exponentiation identically. Any divergence means the same proof is valid on some clients and invalid on others, causing consensus splits.
Smart Contract Threats
T1: Point Validation Bypass — CVE-2025-30147 (Critical)
Hyperledger Besu’s transition to the gnark-crypto library introduced a gap in point validation: the library performed subgroup membership checks but did not independently verify that input points lie on the BN254 curve. This affected all three BN256 precompiles, but BN256PAIRING is the most impactful target because it is the final verification step in zkSNARK proofs.
-
Off-curve points in G1 or G2 could cause the pairing computation to return
1(true) for invalid proofs, or0(false) for valid proofs. Either outcome is catastrophic in a verification context: a false positive means accepting a forged proof, while a false negative means rejecting legitimate operations. -
In a zkSNARK verifier, a false positive allows an attacker to forge proofs of knowledge. In privacy protocols (Tornado Cash-style mixers), this means fabricating withdrawal proofs without a valid deposit. In ZK rollups, this means submitting invalid state transitions that the L1 contract accepts as valid.
-
CVSS 4.0: 8.7 (HIGH). The vulnerability affects consensus: a Besu node accepting a proof that geth rejects (or vice versa) causes chain divergence. On any network where Besu is the majority client, an attacker could enshrine an invalid state.
-
Root cause specifics. The gnark-crypto library’s subgroup check implicitly assumes the point is on the curve, because subgroup membership for a point not on the curve is undefined. Besu’s integration code did not add an explicit curve membership check before delegating to gnark-crypto, creating a window where off-curve points were processed as if they were valid group elements.
Why it matters: CVE-2025-30147 is the canonical example of how a single missed validation step in a pairing precompile can threaten the integrity of every zkSNARK-based system on the network. The fix was straightforward (add explicit curve checks), but the potential impact—consensus divergence and proof forgery—was severe.
T2: zkSNARK Proof Forgery via Pairing Manipulation (Critical)
zkSNARK verifiers (Groth16) call BN256PAIRING as their core verification step. The verifier constructs a set of G1/G2 point pairs from the proof, the verification key, and the public inputs, then calls BN256PAIRING to check the pairing equation. If the pairing check returns 1, the proof is accepted.
-
Complete security collapse on false positives. If BN256PAIRING can be made to return
1for an invalid proof, the entire security guarantee of the SNARK system collapses. The soundness property—“no computationally bounded adversary can produce a valid proof for a false statement”—depends entirely on the correctness of the pairing computation. -
Attack vectors. (1) Providing off-curve points that pass validation in buggy implementations (CVE-2025-30147). (2) Exploiting subgroup check weaknesses to find points in the trace-zero subgroup or other non-prime-order subgroups that satisfy the pairing equation without a valid witness. (3) Submitting inputs that trigger numerical edge cases in specific library implementations (e.g., incorrect handling of the twist curve in G2).
-
ZK rollup impact. zkSync, Polygon zkEVM, Scroll, and Linea all rely on BN256PAIRING for state transition validity proofs. A pairing bug that produces false positives could allow an attacker to submit an invalid state root—minting arbitrary tokens, draining bridges, or corrupting account balances—that the L1 verifier contract accepts.
-
Privacy protocol impact. Protocols like Tornado Cash use Groth16 proofs verified via BN256PAIRING to prove knowledge of a valid deposit without revealing which deposit. A pairing bug allowing proof forgery means an attacker can withdraw funds without ever depositing them.
Why it matters: BN256PAIRING is the single point of failure for all Groth16-based systems on Ethereum. Unlike application-level bugs that affect individual contracts, a pairing implementation bug threatens every zkSNARK verifier simultaneously.
T3: Gas Griefing via Multiple Pairs (Medium)
Gas cost scales linearly with the number of input pairs: 45,000 + 34,000 × k where k is the number of pairs. A contract that accepts user-supplied pairing inputs without bounding k can be subjected to gas griefing.
-
Unbounded pair count. A contract that forwards a fixed amount of gas to BN256PAIRING but allows the caller to specify an arbitrary number of pairs will fail when the gas cost exceeds the forwarded amount. If the contract does not handle this failure correctly (see T4), the result is either a denial of service or stale-data exploitation.
-
Economic asymmetry. Even with post-EIP-1108 pricing, 10 pairs cost 385,000 gas and 20 pairs cost 725,000 gas. An attacker who can influence the pair count in a verification call can make certain operations prohibitively expensive within a single transaction.
-
Relayer and keeper impact. Systems where a relayer or keeper pays gas to verify user-submitted proofs are especially vulnerable: the user chooses the proof structure (and thus the number of pairs), while the relayer bears the gas cost.
Why it matters: While the linear gas scaling is well-designed, contracts that accept variable-length pairing inputs must enforce explicit bounds on the number of pairs to prevent economic denial-of-service attacks.
T4: Unchecked Return Value on Pairing Check (High)
If the CALL to 0x08 fails—due to invalid points, insufficient gas, or malformed input—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 1 (true), the contract treats an invalid or failed proof as verified.
-
Silent failure pattern. BN256PAIRING returns empty output (0 bytes) on failure, not a revert. A low-level
callin 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 left1in the same location, the contract reads stale success. -
Gas forwarding failures. The 63/64 gas rule means that if the calling contract has consumed most of its gas before the pairing call, only a fraction may be forwarded to the precompile. For large pair counts, this fraction may be insufficient, causing the precompile call to fail silently.
-
Interaction with T2. A contract that both fails to validate inputs (T2) and fails to check the return value (T4) is doubly vulnerable: invalid inputs cause the precompile to fail, and the unchecked failure is interpreted as success.
Why it matters: The combination of variable gas cost, empty-output failure mode, and stale-memory risk makes BN256PAIRING particularly dangerous to call without rigorous return value checking. Every Solidity contract calling this precompile should verify both success == true and returndatasize() == 32.
Protocol-Level Threats
P1: CVE-2025-30147 — Consensus Divergence from Point Validation Gap
CVE-2025-30147 is the canonical example of how precompile implementation bugs threaten consensus. Besu’s switch to gnark-crypto introduced a point-on-curve validation gap: the library performed subgroup checks but assumed curve membership, while the integration code did not add explicit curve membership verification.
-
Consensus impact. A transaction containing a proof with off-curve points would produce different results on Besu versus geth/Nethermind/Erigon. On Besu, the pairing might return
1(accepting an invalid proof) or0(rejecting a valid proof). On other clients, the call would correctly fail. This divergence means the same block could be valid on one client and invalid on another—the definition of a consensus split. -
Minority vs. majority client risk. On Ethereum mainnet, Besu is a minority client, so the network would follow the majority (geth) result. But on any network where Besu is the majority or sole client (enterprise networks, testnets), an attacker could exploit the validation gap to enshrine invalid state.
-
Fixed in Besu 25.3.0. The Ethereum Foundation published “The curious case of subgroup check on Besu” detailing the vulnerability. The fix added explicit point-on-curve checks before delegating to gnark-crypto.
P2: Gas Repricing — EIP-1108 and zkSNARK Economic Viability
EIP-1108 (Istanbul, December 2019) reduced BN256PAIRING gas from 100,000 + 80,000 × k to 45,000 + 34,000 × k—a 57% reduction. This repricing was the key enabler for economically viable on-chain zkSNARK verification.
-
Before EIP-1108, a typical Groth16 verification with 3 pairs cost 340,000 gas. After, the same verification costs 147,000 gas. This reduction made privacy protocols and ZK rollups practical on mainnet.
-
Repricing risk. Gas repricing can introduce economic imbalances if the new cost underestimates actual computational effort. For pairing operations, the 57% reduction was justified by hardware improvements and optimized library implementations, but it assumes clients maintain those optimizations.
P3: BN254 Curve Security Margin Degradation
The BN254 (alt_bn128) curve has an embedding degree of 12, which means the security of the pairing-based system relies on the hardness of the discrete logarithm problem in a degree-12 extension field. The number field sieve (NFS) and its variants are more efficient against such extension fields than against prime-order groups of comparable size.
-
Current security level is approximately 100 bits, not the 128 bits that the 254-bit prime might suggest. Academic research (Kim-Barbulescu, 2016; Menezes-Sarkar-Singh, 2017) has progressively refined NFS complexity estimates for BN curves, lowering the effective security below 128 bits.
-
Long-term replacement. BLS12-381, which offers approximately 128-bit security, is the expected successor for new pairing-based protocols. The POINT_EVALUATION precompile (0x0A, EIP-4844) already uses BLS12-381. However, BN254 remains entrenched in deployed Groth16 verifiers and the 0x06/0x07/0x08 precompile family.
-
Migration timeline. Replacing BN254 with BLS12-381 for zkSNARK verification would require new precompiles (or EVM-native operations), redeployment of all verifier contracts, and regeneration of proving/verification keys. This is a multi-year ecosystem effort.
P4: Cross-Client Library Divergence
Different EVM clients implement BN256PAIRING using different cryptographic libraries:
| Client | Library |
|---|---|
| geth | bn256 (Google/Cloudflare) |
| Besu | gnark-crypto |
| Reth | arkworks-based |
| Nethermind | bn256 (custom bindings) |
| Erigon | bn256 (Google/Cloudflare) |
Each library must produce identical results for all inputs, including edge cases: G2 subgroup checks, point-at-infinity handling, final exponentiation, and rejection of off-curve or non-canonical points. Any divergence—even on inputs that “should never appear” in honest usage—is a consensus bug, because adversaries craft inputs specifically to trigger differential behavior.
Edge Cases
| Edge Case | Behavior | Security Implication |
|---|---|---|
| Empty input (0 pairs) | Returns 1 (true). The empty product of pairings is the identity element in GT. | Critical to understand: an empty pairing check always succeeds. A verifier that passes zero pairs to BN256PAIRING will accept any “proof.” Contracts must validate that the input contains the expected number of pairs. |
| Input not a multiple of 192 bytes | Call fails, returns empty output (0 bytes). | If the contract does not check for empty return, stale memory may be interpreted as a result. See T4. |
| Point at infinity in G1 | Valid input. The point at infinity is the identity element; its pairing with any G2 point contributes the identity to the product. | Legitimate in some proof constructions. Should not be rejected. |
| Point at infinity in G2 | Valid input. Analogous to G1 point at infinity. | Same as above. |
| G1 point not on the BN254 curve | Call fails, returns empty output. | Correct behavior. CVE-2025-30147 showed what happens when this check is missing. |
| G2 point not on the BN254 twist curve | Call fails, returns empty output. | Correct behavior. Must be enforced independently of subgroup checks. |
| G2 point on curve but not in the correct prime-order subgroup | Should fail, returns empty output. | This is exactly what CVE-2025-30147 got wrong in Besu. Subgroup checks on G2 are computationally expensive but security-critical. |
| Single pair (k = 1) | Checks whether e(a, b) = 1 in GT. | Reduces to checking whether the single pairing evaluates to the identity. Valid but unusual in Groth16 (which typically uses 3–4 pairs). |
| All pairs use points at infinity | Returns 1 (true). Product of identity elements is identity. | Edge case that should be handled correctly. Could be exploitable if a verifier accidentally passes zeroed memory as input. |
| Maximum practical pair count | Gas-limited. At 34,000 gas per pair, ~880 pairs fill a 30M gas block. | DoS vector if pair count is user-controlled. See T3. |
Real-World Exploits
Exploit 1: CVE-2025-30147 — Besu Consensus Bug via Missing Curve Membership Validation (2025)
Root cause: Hyperledger Besu’s integration of the gnark-crypto library for BN256 precompile operations introduced a point validation gap. The gnark-crypto library’s subgroup membership check assumes the input point is already on the curve, but Besu’s integration code did not perform an explicit curve membership check before calling into gnark-crypto. This meant off-curve points could be processed through the pairing computation, producing incorrect results.
Details: The vulnerability affected all three BN256 precompiles (0x06 BN256ADD, 0x07 BN256MUL, 0x08 BN256PAIRING), but BN256PAIRING is the highest-impact target. An attacker could craft a transaction containing a call to BN256PAIRING with G1 or G2 points that are not on the BN254 curve (or its twist). On Besu, these points would pass the subgroup check (which is meaningless for off-curve points) and enter the pairing computation, producing either a false 1 or false 0. On geth, Nethermind, and other clients, the same call would correctly fail and return empty output.
BN256PAIRING’s role: As the final verification step in zkSNARK proofs, BN256PAIRING is where the security consequence materializes. An off-curve point in BN256ADD or BN256MUL produces an incorrect intermediate value; an off-curve point in BN256PAIRING directly determines whether a proof is accepted or rejected. A false 1 return on a forged proof could allow an attacker to execute invalid state transitions in any zkSNARK-verified system running on the affected client.
Impact: Consensus divergence between Besu and other clients. On Ethereum mainnet, Besu is a minority client, so no mainnet exploit occurred. However, the potential impact on any network where Besu is the majority client was severe: invalid state could be enshrined, funds could be stolen from zkSNARK-based protocols, and ZK rollup state roots could be corrupted. Fixed in Besu 25.3.0.
References:
- Ethereum Foundation: “The curious case of subgroup check on Besu”
- CVE-2025-30147 (NVD)
- Besu 25.3.0 Release Notes
Exploit 2: Potential Impact on ZK Rollup State Integrity (No Mainnet Exploit — Assessed Severity)
Root cause: No known mainnet exploitation of BN256PAIRING has occurred, because Besu is a minority client on Ethereum mainnet and the vulnerability was patched before it could be weaponized. However, the assessed severity is instructive.
Details: ZK rollups submit validity proofs to L1 verifier contracts. These contracts call BN256PAIRING to verify the proof. If BN256PAIRING returns 1 for an invalid proof on a majority of validators, the invalid state root is finalized on L1. The attacker can then use the invalid state to withdraw funds from the rollup bridge—funds that correspond to balances that don’t actually exist in the rollup. This is the most severe possible impact of a pairing bug: it bypasses the entire cryptographic security model of the rollup.
BN256PAIRING’s role: The precompile is the sole on-chain enforcement mechanism for ZK rollup proof validity. There is no secondary check—if BN256PAIRING says the proof is valid, the L1 contract accepts the state transition.
Impact: Assessed as potentially catastrophic. ZK rollups collectively secure billions of dollars in TVL. A pairing bug exploitable on the majority client would threaten all of it simultaneously.
Attack Scenarios
Scenario A: zkSNARK Verifier Passing Unvalidated Inputs to Pairing Check
A verifier contract that accepts proof elements from calldata and passes them directly to BN256PAIRING without validating point membership. On a client with a point validation bug (CVE-2025-30147-class), off-curve points could cause the pairing to return 1 for an invalid proof.
pragma solidity ^0.8.0;
contract VulnerableVerifier {
function verifyProof(
uint256[2] calldata a, // G1 point (proof element)
uint256[2][2] calldata b, // G2 point (proof element)
uint256[2] calldata c, // G1 point (proof element)
uint256[] calldata pubInputs
) external view returns (bool) {
// Construct pairing input from proof and verification key
// VULNERABLE: no explicit check that a, b, c are on their respective curves
// Relies entirely on precompile to reject off-curve points
// On a buggy client (CVE-2025-30147), off-curve points may pass
uint256[12] memory input;
// Pair 1: -A, B (negation of proof element A, paired with B)
input[0] = a[0];
input[1] = (21888242871839275222246405745257275088696311157297823662689037894645226208583 - a[1]) % 21888242871839275222246405745257275088696311157297823662689037894645226208583;
input[2] = b[0][0]; input[3] = b[0][1];
input[4] = b[1][0]; input[5] = b[1][1];
// Pair 2: alpha, beta (from verification key -- trusted, not shown)
// ... (abbreviated for clarity)
// Pair 3: vk_x (computed from public inputs), gamma
// ...
bool success;
uint256 result;
assembly {
success := staticcall(gas(), 0x08, input, 384, result, 0x20)
}
// PARTIALLY VULNERABLE: checks success but does not independently
// validate that input points are on the curve before calling precompile.
// If the precompile itself has a validation bug, invalid proofs pass.
require(success, "pairing failed");
return result == 1;
}
}Scenario B: Unchecked Return Value — Stale Memory as Verification Result
A contract that calls BN256PAIRING without checking the return value. If the call fails (invalid input, insufficient gas), the output memory is not written, and stale data is interpreted as the pairing result.
pragma solidity ^0.8.0;
contract UncheckedPairingResult {
function verifyUnsafe(bytes calldata proofData) external view returns (bool) {
uint256 result;
assembly {
// Pre-set result to 1 (e.g., from a previous computation or initialization)
// This is the dangerous stale value
mstore(0x00, 1)
// Call BN256PAIRING without checking success
// If proofData is malformed (not multiple of 192, off-curve points),
// the call fails silently and result memory at 0x00 retains value 1
let ok := staticcall(
gas(),
0x08,
add(proofData.offset, 0x20),
proofData.length,
0x00,
0x20
)
// BUG: 'ok' is never checked
result := mload(0x00)
}
// If staticcall failed, result is still 1 from the mstore above
// Contract incorrectly treats this as "proof verified"
return result == 1;
}
}
// Attack:
// 1. Submit proofData that is NOT a multiple of 192 bytes (e.g., 100 bytes)
// 2. staticcall to 0x08 fails, returns 0 bytes of output
// 3. Memory at 0x00 still contains 1 from the mstore
// 4. Contract returns true -- "proof verified" for garbage inputScenario C: Gas Griefing via Variable-Length Pairing Input
A contract that accepts user-specified pairing inputs without bounding the number of pairs. An attacker submits many pairs to force excessive gas consumption.
pragma solidity ^0.8.0;
contract UnboundedPairingVerifier {
event VerificationResult(bool success);
function verifyPairing(bytes calldata pairingInput) external {
require(pairingInput.length % 192 == 0, "invalid length");
// VULNERABLE: no upper bound on number of pairs
// Gas cost: 45,000 + 34,000 * (pairingInput.length / 192)
// 20 pairs = 725,000 gas; 100 pairs = 3,445,000 gas
uint256 result;
bool success;
assembly {
success := staticcall(
gas(),
0x08,
add(pairingInput.offset, 0x20),
pairingInput.length,
result,
0x20
)
}
emit VerificationResult(success && result == 1);
}
}
// Attack:
// 1. Attacker calls verifyPairing with 100 pairs (19,200 bytes)
// 2. Precompile consumes 3,445,000 gas
// 3. If the contract is called by a relayer with a gas stipend,
// the relayer pays for the expensive computation
// 4. Repeated calls drain the relayer's ETH balance
//
// Mitigation: require(pairingInput.length / 192 <= MAX_PAIRS, "too many pairs");Mitigations
| Threat | Mitigation | Implementation |
|---|---|---|
| T1: Point validation bypass (CVE-2025-30147) | Ensure client implementations perform explicit curve membership checks before subgroup checks | Client developers: add is_on_curve() check for G1 and G2 points before any algebraic operation. Validate both curve membership and subgroup membership independently. |
| T1/T2: Proof forgery | Use audited, maintained verifier contracts generated by trusted toolchains | Use snarkjs, circom, or gnark to generate verifier contracts. Do not hand-roll pairing verification logic. Pin to specific audited versions. |
| T2: zkSNARK proof forgery | Defense in depth: add application-level sanity checks beyond the pairing result | Verify that public inputs are within expected ranges; add withdrawal limits and time delays in ZK rollup bridge contracts; implement fraud proof fallback mechanisms. |
| T3: Gas griefing | Enforce maximum pair count | require(input.length / 192 <= MAX_PAIRS) where MAX_PAIRS matches the expected proof structure (typically 3–4 for Groth16). |
| T3: Gas griefing | Use explicit gas forwarding | Calculate required gas as 45000 + 34000 * numPairs and forward exactly that amount plus a safety margin. |
| T4: Unchecked return value | Always check STATICCALL success and return data size | require(success && returndatasize() == 32, "pairing call failed"). Zero the output buffer before the call. |
| T4: Stale memory | Zero the output buffer before calling the precompile | In assembly: mstore(outputPtr, 0) before the staticcall. This ensures that a failed call leaves 0 (false), not stale 1 (true). |
| General | Use well-tested verifier generators | snarkjs exportSolidityVerifier and similar toolchains generate correct pairing call sequences with proper return value checking. Prefer these over hand-written assembly. |
| P3: Curve security degradation | Plan migration path to BLS12-381 | For new systems, consider BLS12-381 (used by EIP-4844’s POINT_EVALUATION precompile). Monitor academic research on BN254 security margins. |
Compiler/EIP-Based Protections
-
EIP-197 (Byzantium, October 2017): Introduced BN256PAIRING as a precompile. The specification requires that input points be validated for curve membership and subgroup membership before the pairing computation proceeds. Implementations that deviate from this requirement are non-conformant.
-
EIP-1108 (Istanbul, December 2019): Reduced gas costs from
100,000 + 80,000kto45,000 + 34,000k, making on-chain zkSNARK verification economically viable. The repricing was based on benchmarks of optimized pairing implementations across clients. -
EIP-4844 (Cancun, March 2024): Introduced the POINT_EVALUATION precompile (0x0A) using BLS12-381, establishing the precedent for BN254’s eventual successor in pairing-based protocols.
-
Verifier toolchains (snarkjs, gnark, circom): Generate Solidity verifier contracts that correctly construct pairing inputs, check return values, and handle edge cases. Using generated verifiers rather than hand-written pairing code is the single most effective mitigation for T2 and T4.
-
Client-level differential testing: The Ethereum Foundation maintains cross-client test vectors for all precompiles, including edge cases for BN256PAIRING. Clients are expected to pass all vectors before release. CVE-2025-30147 was discovered through exactly this kind of differential testing.
Severity Summary
| Threat ID | Category | Severity | Likelihood | Real-World Precedent |
|---|---|---|---|---|
| T1 | Smart Contract / Protocol | Critical | Medium | CVE-2025-30147: Besu point validation bypass. No mainnet exploit (minority client), but assessed impact was consensus divergence and potential proof forgery on Besu-majority networks. |
| T2 | Smart Contract | Critical | Low | No known mainnet proof forgery via pairing manipulation. However, the impact (ZK rollup state corruption, privacy protocol bypass) would be catastrophic. Severity driven by impact, not likelihood. |
| T3 | Smart Contract | Medium | Medium | Gas griefing is a generic pattern. No known incident specific to BN256PAIRING, but contracts accepting variable-length pairing inputs without bounds are vulnerable. |
| T4 | Smart Contract | High | Medium | Unchecked return values are a recurring audit finding across precompile-calling contracts. The combination of variable gas cost and empty-output failure mode makes this especially dangerous for BN256PAIRING. |
| P1 | Protocol | Critical | Low | CVE-2025-30147 demonstrated consensus divergence risk. Fixed in Besu 25.3.0. Ongoing risk from future library changes in any client. |
| P2 | Protocol | Medium | N/A | EIP-1108 gas repricing was the economic enabler for zkSNARK adoption. Historical context, not an active threat. |
| P3 | Protocol | Medium | Low (near-term) | BN254 security margin (~100 bits) is below the 128-bit target. No practical attack exists today, but the margin is narrowing. Long-term risk for systems that cannot migrate to BLS12-381. |
| P4 | Protocol | High | Low | Cross-client library divergence is an ongoing risk. Each library update or optimization change is a potential consensus bug. Mitigated by differential testing but never fully eliminated. |
Related Precompiles
| Precompile | Relationship |
|---|---|
| BN256ADD (0x06) | Performs point addition on the BN254 G1 curve. Used in verifier contracts to compute linear combinations of verification key elements. Subject to the same CVE-2025-30147 point validation vulnerability, but with lower direct impact (produces wrong intermediate values rather than wrong final verification results). |
| BN256MUL (0x07) | Performs scalar multiplication on BN254 G1. Used to multiply verification key points by public inputs in Groth16 verifiers. Also affected by CVE-2025-30147. Together, 0x06, 0x07, and 0x08 form the complete on-chain Groth16 verification pipeline. |
| ECRECOVER (0x01) | ECDSA signature recovery on secp256k1. Shares the pattern of “cryptographic verification precompile where silent failure is dangerous,” but operates on a different curve and for a different purpose (authentication vs. proof verification). |
| MODEXP (0x05) | Modular exponentiation. Used in RSA signature verification and other big-integer arithmetic. Shares the variable-gas-cost pattern with BN256PAIRING, making both susceptible to gas griefing when input size is user-controlled. |
| POINT_EVALUATION (0x0A) | KZG point evaluation for EIP-4844 blob verification. Uses BLS12-381 rather than BN254, representing the next generation of pairing-based verification on Ethereum. BLS12-381 offers stronger security margins (~128 bits vs. ~100 bits for BN254). |