7Block Labs
Blockchain Technology

ByAUJay

Summary: On Ethereum L1, a one‑off Groth16 verification cannot be pushed under 100k gas because the bn254/BLS12 pairing precompiles alone cost more than that. To get “<100k gas per proof” in practice, you either amortize via on‑chain batch verification (random‑linear‑combination) or outsource verification to a proof‑verification layer and attest with an aggregated BLS signature.

What’s the Cleanest Way to Optimize an On‑Chain Groth16 Verifier So Each Proof Costs Under 100k Gas?

Decision‑makers ask us this a lot when budgeting L1 costs: “Can we get our Groth16 verifies under 100k gas?” Short answer on Ethereum mainnet: not for a single, stand‑alone verify. The pairing precompile imposes a hard lower bound that is already above 100k. But with the right architecture you can hit sub‑100k per proof consistently—either by amortizing many verifies into one on‑chain check or by using a verification layer that attests on‑chain.

Below is the precise math, what’s changed since 2025 (Pectra), and the cleanest implementation paths we’ve productionized for clients.

The hard lower bound: pairing precompile math you cannot out‑optimize

  • On BN254 (alt_bn128) after EIP‑1108, the pairing check precompile at 0x08 costs 45,000 + 34,000·k gas, where k is the number of G1×G2 pairs passed in one multi‑pairing. Common Solidity Groth16 verifiers pass four pairs in a single call (−A with B, plus three constant/product terms), so the pairing alone costs 181,000 gas. That’s before you add input MSM and scaffolding. You can’t “inline” or micro‑opt your way around this floor. (eips.ethereum.org)

  • Conceptually, Groth16 needs three pairings, but standard Ethereum templates feed four pairs to the precompile to express the product‑equals‑identity check in one call; in practice you should budget with k=4. (alinush.github.io)

  • Since May 7, 2025, Ethereum mainnet also exposes BLS12‑381 precompiles (EIP‑2537, activated with Pectra). BLS pairing pricing is 37,700 + 32,600·k gas, so three pairings cost 135,500 gas and four cost 168,100 gas—still >100k. This widens your curve options but does not change the one‑off floor. (blog.ethereum.org)

  • Typical empirical totals for a single Groth16 verify (BN254) remain ≈200k–250k gas depending on public input count (the MSM to compute vk_x adds ≈6.15k gas per input via EC MUL/ADD precompiles). A widely cited back‑of‑the‑envelope is ≈207,700 + 7,160×ℓ gas for ℓ public inputs. (hackmd.io)

Conclusion: A single, isolated on‑chain Groth16 verify on Ethereum L1 won’t get under 100k gas. To hit that target, you must amortize or attest.

Two clean paths to “<100k gas per proof”

Path A — On‑chain batch verification (random linear combination)

Batch verification lets you check n proofs with roughly n+2 pairings total instead of 3–4 per proof. Pairings are the bottleneck; once you spread those over n proofs, per‑proof cost falls beneath 100k quickly.

  • The trick: derive a challenge r from the batch (Fiat–Shamir) and verify the sum of scaled equations. For Groth16, the arithmetic reduces n checks to one multi‑pairing plus O(n) G1 MSMs. On Ethereum that translates to one pairing call with k≈n+2 and n MSMs for vk_x. (fractalyze.gitbook.io)

  • Gas model on BN254:

    • Pairing: 45,000 + 34,000·(n+2)
    • MSM for inputs: ≈6,150·ℓ per proof
    • Overhead: a few thousand gas for calldata, bounds checks, and the STATICCALL itself
    • Amortized per‑proof pairing cost approaches ≈34k as n grows. With one public input (ℓ=1), your per‑proof total is roughly 34k + 6.15k + overhead ≈ 45k–60k. With ℓ=3, ≈52k–70k. Under 100k is routine for n≥4–8 unless you have unusually large public IO. (eips.ethereum.org)
  • Randomness source: r must be unpredictable to provers when they create the batch. A clean pattern on Ethereum:

    • Build r = keccak256(abi.encode(block.prevrandao, msg.sender, keccak256(allProofBytes), vkHash)).
    • Use commit–reveal if submitters can influence prevrandao (e.g., on small L2s) or derive r from a VRF/logically prior commitment. (fractalyze.gitbook.io)
  • When to use: you control the flow of many proofs targeting the same VK (e.g., wallet privacy set, allowlist membership, small business logic circuits), and a few seconds of batching latency is acceptable.

Practical note: You’ll still do O(n) work to compute vk_x on‑chain; keep ℓ (number of public inputs) very small to avoid moving the bottleneck from pairings to MSM. (hackmd.io)

Path B — Off‑chain verification + on‑chain BLS attestation (verification layer)

If you can accept economically secured attestation rather than raw verification in your contract, push proof checks off‑chain and post a BLS‑signed verdict to Ethereum.

  • Modern “verification layers” (e.g., Aligned Layer) verify arbitrary SNARK/STARK proofs off‑chain and aggregate operator signatures. On L1, your contract checks one aggregated signature and reads the batch result. Reported on‑chain cost lands around 350k gas per batch base and ≈40k gas per proof at batch size 20—well under 100k. (blog.alignedlayer.com)

  • With Pectra’s BLS12‑381 precompiles, verifying an aggregate BLS signature is just two pairings plus small hashing/MSM—≈103k gas for the pairings component, and when amortized across a batch it drops far below 100k per proof. (eips.ethereum.org)

  • When to use: you need low per‑proof gas immediately, can trust an economically secured committee (EigenLayer‑backed operators in Aligned’s case), and prefer milliseconds‑level verification latency with batched settlement on Ethereum.

Micro‑optimizing your Solidity verifier still matters (and how to do it cleanly)

Even if you batch or attest, you’ll maintain on‑chain verifiers (for fallbacks, spot checks, or L2s). Here’s how to trim every avoidable gas unit without risking correctness.

  1. Use the precompiles correctly and once
  • BN254 addresses: 0x06 (ECADD), 0x07 (ECMUL), 0x08 (PAIRING). Always call PAIRING exactly once with all pairs; multiple calls pay the base 45k every time. (eips.ethereum.org)
  • BLS12‑381 addresses (since Pectra): 0x0b..0x11 (G1/G2 add, MSM, pairings, and maps). If you target BLS12‑381, use MSM precompiles to compute vk_x more cheaply than repeated ECMUL+ECADD. (eips.ethereum.org)
  1. Keep the verification key out of storage
  • Hard‑code VK elements as immutables or constants. Avoid SLOADs. snarkjs‑style templates that store IC as memory constants and avoid dynamic arrays are already a win; make IC a fixed‑size array to save bound checks. (github.com)
  1. Do mandatory bound checks in Solidity before calling precompiles
  • Prevent malleability and “input aliasing”:
    • Ensure all G1/G2 coordinates of A, B, C are < p (the curve base field modulus).
    • Ensure all public inputs are < r (the scalar field modulus)—several codegens historically checked p instead of r, enabling aliasing exploits. Audit for this explicitly if you use generated verifiers. (github.com)
  1. Minimize memory expansion and calldata copies
  • Use assembly to write the pairing input buffer contiguously and STATICCALL once. Don’t allocate new arrays in loops. Pre‑size buffers; reuse scratch space. This routinely saves a couple thousand gas per verify in our measurements. Reference implementations of “Pairing.sol” demonstrate the single‑call pattern. (rareskills.io)
  1. Compute vk_x with the fewest ops
  • For BN254, vk_x = IC0 + Σ xi·ICi costs ≈ (6000 + 150)·ℓ gas after EIP‑1108. Use tight loops and unrolled adds for small ℓ. For BLS12‑381, prefer G1MSM (0x0c) over k separate ECMUL calls; the precompile’s Pippenger‑style discounts make it cheaper for k≥2. (eips.ethereum.org)
  1. Pack calldata tightly and avoid decompression on-chain
  • Precompiles expect uncompressed big‑endian coordinates: BN254 G1/G2 use 64/128‑byte pairs; BLS12‑381 uses 128/256 bytes for G1/G2. Don’t send compressed points and decompress on‑chain; it burns gas and is error‑prone. (eips.ethereum.org)
  1. Be chain‑aware: gas schedules vary outside Ethereum L1
  • Some chains and L2s tweak precompile pricing; never hardcode the pairing gas stipend. zkSync, for example, changed ECADD/ECMUL/PAIRING pricing via ZIP‑11; audits have found failures due to hardcoded 120k limits for k=2 pairings. Make the gas passed to STATICCALL a parameter or compute it from k. (github.com)

Putting numbers on it: three worked examples

  1. Single Groth16 verify on Ethereum L1 (BN254), ℓ=3
  • Pairing (k=4): 181,000 gas
  • MSM for inputs: ≈6,150×3 ≈ 18,450 gas
  • Calldata + scaffolding: ≈4–8k gas
  • Total: ≈205–210k gas. This matches common production measurements. (eips.ethereum.org)
  1. Batch 16 Groth16 proofs on Ethereum L1 (BN254), ℓ=1 each
  • One pairing call, k≈n+2=18: 45,000 + 34,000×18 = 657,000 gas
  • MSM across 16 proofs: ≈6,150×16 = 98,400 gas
  • Overhead: ≈15–25k gas
  • Total batch: ≈770–780k gas ⇒ per proof ≈48–49k gas. Well under 100k; add ℓ=3 and you’re still ≈60–70k per proof. (eips.ethereum.org)
  1. Verification layer attestation (BLS12‑381)
  • Off‑chain: operators verify any proof system; on‑chain: verify one aggregate BLS signature and update batch state.
  • Reported: ≈350k gas base per batch and ≈40k gas per proof at batch size ≈20. The BLS precompiles installed by Pectra make this predictable on mainnet. (blog.alignedlayer.com)

BN254 vs BLS12‑381 in 2026: what changed and what didn’t

  • BN254 remains the cheapest curve for raw Groth16 on L1 because EIP‑1108 slashed ECADD/ECMUL/PAIRING prices years ago; that’s why most Ethereum‑targeted circuits still compile to BN254. (eips.ethereum.org)

  • Since Pectra (mainnet 2025‑05‑07), BLS12‑381 is first‑class on Ethereum with seven precompiles, including MSM and field‑to‑curve mappings. If your ecosystem already uses BLS12‑381 (validator tooling, light clients, BLS wallets), you can switch your verifier curve without paying a large gas penalty; in fact, the BLS pairing is slightly cheaper per pair than BN254’s, but the larger point encodings and different MSM economics often make totals similar. Evaluate end‑to‑end with your actual ℓ and calldata patterns. (blog.ethereum.org)

  • Calldata pricing matters: EIP‑7623 (shipped with Pectra) raised calldata cost for data‑heavy transactions. Groth16 proofs are small (≈256–300 bytes), so the impact is minor, but it further disfavors “fat proof” schemes on L1 and strengthens the case for batching/attestation. (blog.ethereum.org)

A clean implementation blueprint you can ship

If you own both the prover and the consumer contracts, this architecture hits sub‑100k/proof reliably without exotic cryptography:

  1. Keep public inputs tiny
  • Redesign the circuit API to minimize ℓ (pack, hash, or commit to application state off‑chain and expose only necessary scalars). Each public input costs ≈6.15k gas on BN254. (hackmd.io)
  1. Implement batch verification with non‑malleable r
  • Add a verifyBatch() entrypoint that:
    • Takes an array of tightly encoded proofs and public inputs for one VK.
    • Derives r = keccak256(prevrandao || vkHash || keccak256(batchBytes)).
    • Computes all vk_x values in a tight loop.
    • Builds one pairing input buffer with k≈n+2 pairs.
    • STATICCALLs the pairing precompile once.
  • Fail the entire batch if the check fails; include per‑proof bitmaps only if you must pinpoint offenders (extra gas). (fractalyze.gitbook.io)
  1. Harden the verifier
  • Reject coordinates ≥ p, and public inputs ≥ r, before calling precompiles (protects against malleability and aliasing). Ensure fixed‑length IC arrays and no dynamic allocations on hot paths. (github.com)
  1. Make gas portable
  • Compute the pairing gas stipend from k; don’t hardcode. Expose it as a constant or function of the input length. This avoids cross‑chain/L2 failures when precompile pricing differs. (github.com)
  1. If latency or multi‑system support matters, add an attestation path
  • Integrate a verification‑layer adapter that accepts an aggregate BLS signature over your proof digests. Gate state transitions on either “raw verify” or “attested‑verified” and price both. This lets power users choose lower latency or lower gas. (blog.alignedlayer.com)

Emerging best practices we recommend in 2026

  • Prefer one multi‑pairing per call always; any design that does multiple pairing precompile calls per transaction leaks 45k gas per extra call (BN254) or 37.7k (BLS12‑381). (eips.ethereum.org)

  • If you are starting fresh post‑Pectra and don’t have BN254 dependencies, reconsider targeting BLS12‑381 for end‑to‑end cryptography. You get better security margins and native MSM/mapping precompiles; verification costs are competitive. (eips.ethereum.org)

  • Treat calldata as a budget line item. Proofs are small, but if you batch many in a single tx, your zeros/non‑zeros distribution under EIP‑2028/EIP‑7623 and ABI packing start to matter. Keep everything big‑endian and uncompressed for precompile compatibility. (eips.ethereum.org)

  • Keep verifiers upgradable behind a timelock or use an indirection (e.g., Registry → Verifier) so you can swap BN254↔BLS12‑381 verifiers or switch between raw and attested paths without migrating the application logic. The ecosystem is moving quickly; don’t hard‑wire yourself. (7blocklabs.com)

Reality check: when “just optimize the verifier” is enough

If you only verify an occasional proof and can live with ≈200–250k gas on L1, a hardened, single‑call BN254 verifier with tiny ℓ is fine—and operationally simpler. The moment you foresee verifying many proofs per block or across multiple chains, you should either:

  • Batch on‑chain via random‑linear‑combination and hit 40–80k gas/proof, or
  • Use a verification layer and hit ≈40k/proof (plus a fixed batch base) with milliseconds‑level latency and Ethereum‑derived security. (fractalyze.gitbook.io)

Brief, in‑depth details: why you can’t reduce to “2 pairings” on Ethereum

Groth16’s verifier equation can be written with three pairings mathematically, but Solidity verifiers feed four pairs into the multipairing precompile to avoid GT arithmetic (not available in the EVM) and to keep everything as a single “product = 1” check. Without GT operations or specialized precompiles, you can’t collapse it to two pairings on Ethereum; thus the 181k/168.1k floors for BN254/BLS12 respectively when k=4. If you see a claim of “<100k per single proof on L1,” it’s either amortized, off‑chain‑attested, or not Groth16. (medium.com)


If you want us to turn this into a drop‑in verifier for your stack, we’ll benchmark both Batch‑BN254 and Attested‑BLS12 paths against your real circuits and public IO, then hand you exact budgets and swap‑ready contracts.

References:

  • EIP‑1108: reduced BN254 precompile costs; pairing = 45,000 + 34,000·k, ECADD=150, ECMUL=6,000. (eips.ethereum.org)
  • EIP‑2537: BLS12‑381 precompiles since Pectra (2025‑05‑07); pairing = 37,700 + 32,600·k; includes MSM and map precompiles. (blog.ethereum.org)
  • Typical Groth16 gas math on Ethereum; ≈207.7k + 7.16k×ℓ; common verifiers use 4 pairings in one call. (hackmd.io)
  • Batch verification reduces to n+2 pairings with RLC; per‑proof pairing cost ≈34k on BN254 at scale. (fractalyze.gitbook.io)
  • Verification layer economics (Aligned): ≈350k base per batch; ≈40k/proof at batch size ~20; ms‑level verification. (blog.alignedlayer.com)
  • Precompile usage and pitfalls; generated verifier hardenings; input aliasing foot‑guns. (rareskills.io)
  • Cross‑chain variance in precompile gas; don’t hardcode STATICCALL stipends. (github.com)

Like what you're reading? Let's build together.

Get a free 30‑minute consultation with our engineering team.

Related Posts

7BlockLabs

Full-stack blockchain product studio: DeFi, dApps, audits, integrations.

7Block Labs is a trading name of JAYANTH TECHNOLOGIES LIMITED.

Registered in England and Wales (Company No. 16589283).

Registered Office address: Office 13536, 182-184 High Street North, East Ham, London, E6 2JA.

© 2025 7BlockLabs. All rights reserved.