Precompile Summary

PropertyValue
Address0x06
NameBN256ADD (also called ECADD, alt_bn128 addition)
Gas150 (post-EIP-1108, Istanbul, December 2019). Was 500 pre-Istanbul (Byzantium through EIP-196; repriced by EIP-1108).
Input128 bytes: two elliptic curve points on the alt_bn128 (BN254) curve — x1 (32 bytes), y1 (32 bytes), x2 (32 bytes), y2 (32 bytes)
Output64 bytes: the resulting point (x3, y3) on the curve, or empty (0 bytes) on failure
BehaviorPerforms point addition on the alt_bn128 (BN254) curve. Both input points must lie on the curve or represent the point at infinity as (0, 0). Returns the sum point. If either point is not on the curve (or coordinates are invalid, e.g. >= field modulus), the call fails and returns empty output. Introduced in Byzantium (EIP-196). Used as a building block for zkSNARK verification, BLS-style aggregation patterns, and privacy protocols.

Threat Surface

BN256ADD is a low-level elliptic-curve primitive: it adds two affine points on Ethereum’s standardized alt_bn128 (BN254) curve and returns their sum. It does not interpret application semantics — no nonces, domains, or authorization context — so every safety property (correct inputs, checking failures, binding results to a proof system) must be enforced by the calling contract and by the broader protocol stack.

The threat surface has three dominant themes:

1. Validation and silent misuse at the contract boundary. The precompile signals failure by returning no data. Solidity and assembly callers that do not verify returndatasize, the success flag of the CALL, or the length of returned bytes may continue execution with stale memory, treating garbage or prior buffer contents as a valid curve point. That failure mode is especially dangerous in SNARK verifiers and custom crypto pipelines where a single bogus point corrupts an entire proof check.

2. Cross-client implementation risk on a consensus-critical primitive. Point addition is deterministic and must agree across all nodes. Any discrepancy in whether a coordinate is accepted, how padding interacts with parsing, or — most severely — whether points are verified to lie on the curve before arithmetic, can produce state-root divergence. CVE-2025-30147 showed that swapping cryptographic backends (e.g., moving to a library that emphasized subgroup checks but missed on-curve validation) can introduce subtle consensus bugs even for “mature” precompiles.

3. Economic and compositional coupling. EIP-1108 made BN256 operations cheap enough for on-chain SNARK verification, which increased reliance on this precompile inside rollups, mixers, and privacy tooling. A flaw in addition does not stay local: it propagates to every gadget that uses these points (pairing-based checks, scalar multiplication pipelines, verification keys). Gas repricing also means any contract that hardcoded old call gas may behave incorrectly under boundary conditions (out-of-gas mid-verifier), though that is usually a reliability issue rather than a consensus one.


Smart Contract Threats

T1: Point Validation Bypass — CVE-2025-30147 (Critical)

Hyperledger Besu (versions 24.7.1 through 25.2.2) contained a consensus bug: BN256ADD (together with BN256MUL and BN256PAIRING) did not properly validate that input points lay on the curve. The implementation used gnark-crypto’s bn254 stack, which relied on subgroup-oriented checks but failed to ensure points were actually on the curve. Crafted points could sit in the expected subgroup yet be off-curve, yielding incorrect addition results. Besu nodes then produced different state roots than geth, Nethermind, and reth — a network-splitting class of defect. CVSS 4.0 score 8.7 (HIGH). Fixed in besu-native 1.3.0 and Besu 25.3.0.

Why it matters: This is not a hypothetical smart-contract bug; it demonstrates that client-level validation gaps surface as on-chain divergence. Contracts cannot mitigate a broken client, but they must assume heterogeneous deployments and never rely on “usually correct” library behavior across chains or minor clients.

T2: Invalid Point Input Handling (Medium)

When input points are not on the curve (or coordinates exceed the field), the precompile must fail and return empty output. Callers that omit returndatasize checks, ignore the CALL success bit, or abi.decode without length checks may use stale memory as if it were (x3, y3), breaking downstream hashes, commitments, or pairing inputs.

Why it matters: Failure is silent at the EVM level (failed precompile call, empty return) but catastrophic at the application level — verifiers proceed with attacker-influenced or arbitrary points.

T3: Gas Repricing Disruption (Low)

EIP-1108 reduced BN256ADD gas from 500 to 150 (Istanbul). Contracts that hardcoded gas stipends for CALL to 0x06 assuming the higher cost may run out of gas mid-verification or follow unintended fallback paths. The repricing was intentional to make zkSNARK verification economically viable on-chain.

Why it matters: Mostly affects compatibility, test fixtures, and brittle inline assembly — not cryptographic soundness — but can brick upgradeable modules that embedded old constants.


Protocol-Level Threats

P1: Client Implementation Divergence When Changing Cryptographic Libraries

CVE-2025-30147 showed that even well-tested precompiles can develop consensus bugs when clients change underlying libraries. Besu’s move to gnark-crypto introduced a subtle validation gap (subgroup vs on-curve). Any future refactor across clients reopens this risk class for BN256ADD, BN256MUL, and BN256PAIRING.

P2: Gas Repricing History and Assumptions

Pricing moved from 500 gas (Byzantium / EIP-196 baseline) to 150 (Istanbul / EIP-1108). The reduction reflected faster implementations (notably improvements such as Cloudflare’s bn256 work, cited in EIP-1108 motivation): older code may still encode pre-repricing economics or gas budgets, causing behavioral differences under block gas limits or nested calls.

P3: Ecosystem-Wide Impact of alt_bn128 Arithmetic

Ethereum’s SNARK-friendly curve appears across zk rollups, legacy mixer designs, and privacy protocols. Errors in point addition compound through verification circuits: a wrong sum poisons pairing inputs, challenge values, and any hash-of-curve-point pattern. The blast radius is protocol-level, not single-contract.


Edge Cases

Edge CaseBehaviorSecurity Implication
Point at infinity represented as (0, 0)(0, 0) + P = P (identity semantics per precompile convention)Callers must treat (0, 0) as a special sentinel, not an ordinary affine point, when porting algorithms from textbooks that use formal infinity encoding
P + (-P)Yields point at infinity (0, 0)Correct behavior; verifiers must accept infinity as a valid intermediate if the math requires it
P + P (doubling case)Handled internally as part of additionImplementation must follow spec; historical client bugs are a reminder to test doubling paths
Input shorter than 128 bytesZero-padded; may yield (0, 0) for missing coordinatesPadding can synthesize infinity or unintended points — treat short input as untrusted; prefer enforcing exactly 128 bytes off-chain or in calldata checks
Coordinates not on curveReturns empty (call fails)Safe if caller checks return data; critical if caller ignores failure (see T2)
Coordinates >= field modulusReturns empty (call fails)Same as above; canonical reduced inputs are required for predictable behavior

Real-World Exploits

Exploit 1: CVE-2025-30147 — Besu Consensus Divergence on BN256ADD / MUL / PAIRING (2025)

Root cause: Besu did not fully validate that inputs were on the alt_bn128 curve before performing precompile arithmetic, while other major clients did. Off-curve points could produce incorrect sums yet pass higher-level checks in homogeneous Besu networks.

Details: Affected Besu versions 24.7.1–25.2.2 (fixed in 25.3.0 with besu-native 1.3.0). The defect was assigned CVE-2025-30147 with CVSS 4.0 8.7. Because Besu was a minority client on Ethereum mainnet, widespread enshrining of divergent state was avoided, but homogeneous Besu networks could have finalized blocks inconsistent with the rest of the ecosystem.

References:

BN256ADD’s role: Point addition is the first step in many SNARK verifier decompositions; incorrect addition shifts every dependent intermediate.


Attack Scenarios

Scenario A: Stale Memory After Ignoring Failed BN256ADD

// VULNERABLE PATTERN: ignores staticcall success and returndatasize
pragma solidity ^0.8.0;
 
contract UnsafeBn256Add {
    function addPoints(
        bytes32 x1, bytes32 y1,
        bytes32 x2, bytes32 y2
    ) external view returns (bytes32 x3, bytes32 y3) {
        bytes memory input = abi.encodePacked(x1, y1, x2, y2);
        bytes memory output = new bytes(64);
 
        bool success;
        assembly {
            success := staticcall(
                gas(),
                0x06,
                add(input, 32),
                128,
                add(output, 32),
                64
            )
            // BUG: `success` never checked. If the precompile fails, `output` may
            // still be read as if it contained a valid point (often all zeroes).
        }
 
        assembly {
            x3 := mload(add(output, 32))
            y3 := mload(add(output, 64))
        }
    }
}
 
// Failure mode:
// 1. Provide coordinates that are not on the curve (or out of range).
// 2. Precompile returns empty; staticcall sets success = false.
// 3. Contract still decodes `output` as (x3, y3) and continues verification.

Scenario B: Off-Curve Points to Exploit Client Validation Gaps

// Conceptual: adversary crafts (x1,y1,x2,y2) that are invalid on-curve but trigger
// divergent behavior between a buggy client and spec-conformant clients.
pragma solidity ^0.8.0;
 
contract ProbeBn256Add {
    event Result(bytes32 x, bytes32 y, bool ok, uint256 rds);
 
    function probe(
        bytes32 x1, bytes32 y1,
        bytes32 x2, bytes32 y2
    ) external {
        bytes memory input = abi.encodePacked(x1, y1, x2, y2);
        bytes memory out = new bytes(64);
 
        bool success;
        uint256 returnSize;
        assembly {
            success := staticcall(gas(), 0x06, add(input, 32), 128, add(out, 32), 64)
            returnSize := returndatasize()
        }
 
        bytes32 ox;
        bytes32 oy;
        if (returnSize == 64) {
            assembly {
                ox := mload(add(out, 32))
                oy := mload(add(out, 64))
            }
        }
 
        emit Result(ox, oy, success, returnSize);
    }
}
 
// Notes:
// - On correct clients, invalid points => empty return => returnSize == 0.
// - Historically, a defective client might return 64 bytes of wrong curve points,
//   enabling consensus divergence (CVE-2025-30147 class), not a single-contract theft.

Mitigations

ThreatMitigationImplementation
T1: Client validation bypassRun diverse client sets on critical networks; monitor for consensus alertsOperators avoid single-client liveness; protocols do not assume byte-identical precompile internals across minor clients
T2: Silent failure / stale memoryTreat BN256ADD like any external call: check success and returndatasize == 64Wrap in Solidity: require(ok && returndatasize() == 64); then returndatacopy into a fixed buffer; reject other sizes
T2: Input sanityReject non-canonical encodingsWhere possible, require uint256(x) < FIELD_MODULUS and same for y before call; still rely on precompile for on-curve check
T3: Gas repricingAvoid hardcoded gas; use gas() or documented opcode budgetsPrefer high-level libraries that track current costs; test under Istanbul+ rules
GeneralCentralize curve opsOne audited module for 0x06/0x07/0x08 usage with uniform error handling

Compiler/EIP-Based Protections

  • EIP-196 (Byzantium): Introduced alt_bn128 precompiles including BN256ADD; defines the curve and operation set at protocol level.
  • EIP-1108 (Istanbul): Repriced BN256 precompiles (500 → 150 gas for addition) to reflect faster implementations; contracts must not assume pre-Istanbul gas.
  • Solidity / Yul: No compiler-enforced check for precompile return length — mitigations are purely programmatic (explicit returndatasize and staticcall success checks).
  • Client releases: Track security advisories for Besu 25.3.0+ / besu-native 1.3.0+ regarding CVE-2025-30147 class fixes.

Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart Contract / ClientCriticalLow (mainnet heterogeneous) / Higher on single-client netsCVE-2025-30147 (Besu 24.7.1–25.2.2); CVSS 4.0 8.7
T2Smart ContractMediumMediumCommon audit pattern: unchecked staticcall to precompiles
T3Smart ContractLowLowHardcoded gas after EIP-1108 causing OOG or failed verifiers
P1ProtocolCriticalLowSame as T1 — library migration induced consensus divergence
P2ProtocolLowLowEIP-1108 repricing documentation and test-net drift
P3ProtocolHighN/A (impact model)SNARK / rollup ecosystem reliance on alt_bn128 pipeline

PrecompileRelationship
BN256MUL (0x07)Scalar multiplication on the same curve; typically chained with BN256ADD in verifier arithmetic
BN256PAIRING (0x08)Pairing check that consumes curve points produced by add/mul pipelines
ECRECOVER (0x01)Different curve (secp256k1), but same lesson: precompile failure returns empty — callers must validate outputs
MODEXP (0x05)Often co-located in cryptographic pipelines (RSA, proofs); shared patterns for gas and return-data handling