ByAUJay
How Does BLS Signature Aggregation Actually Reduce Gas Cost When Batching Groth16 Proofs on Ethereum?
Short summary: Since Ethereum’s Pectra upgrade (May 7, 2025) added BLS12‑381 precompiles, you can replace many on‑chain Groth16 verifications (multiple expensive BN254 pairings and heavy calldata) with a single BLS aggregate signature check on a Merkle root of proofs. The result: order‑of‑magnitude lower gas and calldata under the new EIP‑7623 pricing, with clear design patterns to keep security tight. (blog.ethereum.org)
TL;DR for decision‑makers
- Verifying n Groth16 proofs individually on L1 costs roughly 45,000 + 34,000×(3n) gas just for pairings (BN254 precompile), which dominates your budget. Even batch‑verifying Groth16 still scales with n pairings. A BLS fast‑aggregate signature (same message) costs a near‑constant ~102,900 gas for the pairing check plus ~23,800 gas to map the message to G2, regardless of n. (eips.ethereum.org)
- After EIP‑7623 raised calldata floor pricing in Pectra, shrinking payloads matters much more: replace O(n) Groth16 proof bytes with a single BLS signature and a Merkle root. (eips.ethereum.org)
Why this changed in 2025
- Pectra shipped EIP‑2537 (BLS12‑381 precompiles) and EIP‑7623 (reprices calldata). Pairing on BLS12‑381 now costs 32,600×k + 37,700 gas per call, with k the number of (G1,G2) pairs. These precompiles also expose mapping and MSM helpers for efficient aggregation. (blog.ethereum.org)
- BN254 (alt_bn128) pairing precompile remains 34,000×k + 45,000 gas; it’s what typical L1 Groth16 verifiers use today. BLS pairings are now slightly cheaper per pair and on a stronger curve. (eips.ethereum.org)
Baseline: what verifying Groth16 costs on L1
- A single Groth16 verification uses 3 pairings in theory; most Solidity templates still call 4 pairings due to how the product equation is constructed (e.g., snarkjs’ verifier template), incurring an avoidable extra 34k gas. (docs.pantherprotocol.io)
- Gas math (pairing cost only, BN254 on 0x08):
Gas ≈ 45,000 + 34,000 × (3 to 4) = 147,000–181,000 per proof, before EC adds/muls and calldata. (eips.ethereum.org) - Calldata footprint per Groth16 proof (BN254): A∈G1 (64 B) + B∈G2 (128 B) + C∈G1 (64 B) ≈ 256 B, plus public inputs. For BLS12‑381 the encodings are bigger (G1=128 B, G2=256 B per EIP‑2537 uncompressed ABI), but see below—we won’t be shipping individual proofs on chain. (eips.ethereum.org)
On‑chain batch verification of Groth16 helps, but still scales with n
If you keep verification on chain and use the standard random‑linear‑combination trick, n Groth16 proofs verify with roughly n+2 pairings (vs 3n), plus MSMs and field ops. That’s better, but still linear in n—and you must generate the randomness so the aggregator can’t bias it. (fractalyze.gitbook.io)
- Example: n=20.
Batch verify pairings ≈ 45,000 + 34,000×(22) = 793,000 gas (pairings only). You still do MSMs and carry O(n) calldata. If proofs are BN254, calldata ≈ 20 × 256 B = 5,120 B, which is newly expensive under EIP‑7623’s higher floor for data‑heavy txs. (eips.ethereum.org)
The BLS aggregation pivot: verify attestations, not every proof
Instead of verifying each Groth16 proof, have a committee of verifiers check the proofs off‑chain and BLS‑sign a single message: the Merkle root of all (proof, public input) tuples. Submit the root once plus a single aggregate signature. On chain, you verify only the BLS aggregate signature and, optionally, store/emit the root. This shifts on‑chain cost from “many BN254 pairings + heavy calldata” to “one BLS signature check + tiny calldata.” (eips.ethereum.org)
What makes this cheap now:
- BLS fast‑aggregate verify (all signers sign the same message) uses 2 pairings regardless of n. With EIP‑2537: 37,700 + 32,600×2 = 102,900 gas for the pairing check, plus 23,800 gas to map the message to G2, and a few hundred gas for G1 ops if you aggregate pubkeys on chain. Call it ~125–135k gas end‑to‑end. (eips.ethereum.org)
- Calldata is tiny: one G2 signature (256 B in EIP‑2537 encoding), one G1 aggregate pubkey if not stored (128 B), and the 32‑byte Merkle root. You removed O(n) Groth16 proof bytes entirely from transaction calldata, which matters more post‑EIP‑7623. (eips.ethereum.org)
Security notes that make this robust:
- Use the IETF BLS “FastAggregateVerify” with proof‑of‑possession (POP) to preclude rogue‑key attacks; either pre‑register committee keys on chain or require POPs in a setup phase. (ietf.org)
- Hash‑to‑curve must follow RFC 9380 and EIP‑2537’s ABI. For MinPk (pubkeys in G1, signatures in G2—Ethereum consensus style), your contract implements hash_to_field per RFC 9380 and then calls the 0x11 map‑to‑G2 precompile (23,800 gas). (ietf.org)
Concrete gas: side‑by‑side
Assume BN254 Groth16 proofs, n = 20:
- Individual Groth16 verifies on chain (4 pairings template):
45,000 + 34,000×80 ≈ 2,765,000 gas for pairings alone; add EC ops and calldata. (xn--2-umb.com) - Batch Groth16 on chain (n+2 pairings):
45,000 + 34,000×22 ≈ 793,000 gas for pairings, plus MSMs and 5,120 B calldata for the proofs. (fractalyze.gitbook.io) - BLS aggregate attestation (same message):
Pairings: 37,700 + 32,600×2 = 102,900 gas; map_to_G2 = 23,800 gas; total ≈ 126,700 gas (+ small fixed overhead). Calldata ≈ 256 B signature + 128 B agg‑pubkey (or 0 if stored) + 32 B root. (eips.ethereum.org)
Even if your on‑chain Groth16 verifier is tight (3 pairings) and you compress public inputs, the BLS route stays a 5–15× improvement for moderate n, and grows with batch size because cost is near‑constant. (eips.ethereum.org)
Architecture pattern we see winning in production
- Off‑chain aggregation service (or decentralized committee):
- Inputs: n Groth16 proofs and public inputs.
- Steps:
- Verify each Groth16 proof off‑chain with a standard library (arkworks/gnark). (github.com)
- Build a Merkle tree over canonicalized (proof_commitment, public_inputs_hash) leaves; produce Merkle root R.
- Each committee member BLS‑signs domain_sep || chain_id || contract_addr || R || batch_metadata. Aggregate signatures. (ietf.org)
- On‑chain contract:
- Stores committee public keys and their aggregate (G1).
- Verifies AggregateSignature on R via one 0x0f call with k=2 pairs (fast‑aggregate verify).
- Emits or stores R; optionally posts a small index for later per‑item inclusion proofs (Merkle proofs supplied by users). (eips.ethereum.org)
Why this aligns with 2025+ Ethereum:
- EIP‑7623 raised calldata costs; this pattern is minimal‑calldata by construction. (eips.ethereum.org)
- If you attach proof batches to 4844 blobs, your DA is priced in blob gas and verified by the 0x0a point‑evaluation precompile, while your correctness attestation remains the same BLS signature check. (eips.ethereum.org)
Implementation details that save real gas
- Curve and precompiles: Use EIP‑2537 addresses 0x0b–0x11; pairings at 0x0f; map_to_G2 at 0x11. Encodings are big‑endian, uncompressed: G1=128 bytes, G2=256 bytes; infinity is all‑zeros. Build calldata with abi.encodePacked to avoid endianness surprises. (eips.ethereum.org)
- Ciphersuite: Stick to the Ethereum/IRTF ciphersuites and POP flow; “FastAggregateVerify” requires POP‑validated keys. (ietf.org)
- Hash‑to‑curve: Implement RFC 9380 hash_to_field in Solidity and call 0x11 to map Fp2→G2 (23,800 gas). Doing byte‑to‑field off‑chain and passing field elements is acceptable if you trust the source; the precompile enforces subgroup checks. (ietf.org)
- Store the aggregate pubkey in contract storage. Recomputing it on each call via G1ADD or MSM is cheap but avoidable; the precompile provides G1MSM/G2MSM with discounts if you need to compute aggregates on chain. (eips.ethereum.org)
- Treat gas constants as chain‑specific. Don’t hardcode EIP‑1108 pairing gas if you target multiple EVM chains—several have diverged. (infsec.io)
What about using BLS for the proofs themselves?
You have two adjacent options:
- Keep Groth16 on BN254 and attest with BLS (described above). This is the least invasive change if your circuits and tooling are already BN254.
- Move the Groth16 system to BLS12‑381 (gnark supports it) and verify on chain with the BLS precompile. Three pairings on BLS12‑381 cost 37,700 + 32,600×3 = 135,500 gas—slightly cheaper per verification than BN254 and higher security, but you still pay O(n) in pairings for n proofs. BLS aggregation still wins at scale. (docs.gnark.consensys.net)
Numbers you can plan around (post‑Pectra L1)
- BN254 pairing (0x08): 34,000×k + 45,000 gas. Typical Groth16 template calls 4 pairings. (eips.ethereum.org)
- BLS12‑381 pairing (0x0f): 32,600×k + 37,700 gas. Fast‑aggregate verify: k=2. Distinct‑message aggregate verify: k = n+1. (eips.ethereum.org)
- Map Fp2→G2 (0x11): 23,800 gas. Map Fp→G1 (0x10): 5,500 gas. (eips.ethereum.org)
- EIP‑7623 calldata repricing: raises the floor to 10/40 gas per byte for data‑heavy txs (up from 4/16), further penalizing O(n)‑size proof payloads. (eips.ethereum.org)
Worked example: 100‑proof batch
- Pure on‑chain Groth16 (4 pairings each): k = 400 ⇒ 45,000 + 34,000×400 = 13,645,000 gas for pairings alone. Also ≈ 25.6 KB of proof calldata. This is well over typical per‑tx targets and competes with the block gas limit. (eips.ethereum.org)
- Groth16 batch verify (n+2 pairings): k = 102 ⇒ 45,000 + 34,000×102 = 3,513,000 gas for pairings, plus MSMs and ≈25.6 KB calldata. (fractalyze.gitbook.io)
- BLS aggregate fast‑verify: k=2 ⇒ 102,900 gas pairings + 23,800 gas map ≈ 126,700 gas. Calldata: 256 B signature + 32 B root + optionally 128 B agg‑pubkey (or 0 if stored). (eips.ethereum.org)
Result: >25× cheaper on pairings and ~100× smaller calldata at n=100, and the cost stays flat as n grows.
Security and correctness tips (don’t skip)
- Randomness in Groth16 batch verification: If you still do on‑chain batch verify, derive r_i unpredictably from data the submitter can’t precompute against (e.g., keccak(commitment||block.prevrandao) in the same tx) to avoid batch‑forgery pitfalls. (ethresear.ch)
- POPs for BLS aggregation: Require POPs up‑front on committee keys. “FastAggregateVerify” assumes POP‑validated keys; otherwise rogue‑key attacks are trivial. (ietf.org)
- Encoding fidelity: EIP‑2537 mandates big‑endian, uncompressed coordinates; infinity is all zeros. Feed exactly 128 B for G1, 256 B for G2, and 384 B per pairing pair. Fail closed on any malformed input. (eips.ethereum.org)
When not to use BLS aggregation
- If your trust model requires L1 to check each proof cryptographically without any attesting committee, you must verify Groth16 on chain (possibly as a recursive SNARK to compress many proofs into one). Tooling like SnarkPack and recursive constructions can reduce verifier work but reintroduce prover overhead; use when the trust/UX trade‑off demands pure on‑chain proof soundness. (research.protocol.ai)
Emerging best practices we recommend
- Prefer BLS MinPk (pubkeys in G1, signatures in G2) with POP—the same scheme used in Ethereum consensus—so you can reuse audited libraries and mental models. (ethereum.github.io)
- Domain separation: Tag your batch attestation message with versioned domains (e.g., BATCH_ATTESTATION_V1|chainid|contract|R) and use RFC‑9380 hash_to_curve with explicit DSTs. (ietf.org)
- Store committee keys and their aggregate once; rotate on governance events only. Keep the “fast path” call doing just: verify aggregate sig → update/emit batch root. (eips.ethereum.org)
- If you also post data to blobs (EIP‑4844), validate them with the 0x0a point‑evaluation precompile and keep correctness attestation on BLS. This keeps both DA and correctness cheap and native. (eips.ethereum.org)
- Don’t assume gas constants across chains. If you deploy to multiple EVMs, fetch chain‑specific precompile prices or leave headroom—some rollups diverge from EIP‑1108’s schedule. (infsec.io)
Bottom line
With EIP‑2537 live, the most cost‑effective way to “batch Groth16 proofs” on Ethereum L1 is not to pile more pairings on chain—it’s to verify them off‑chain and attest once via a BLS aggregate signature on a Merkle root. You move from O(n) pairings and calldata to a near‑constant ~130k gas and a few hundred bytes. Combined with EIP‑7623’s calldata repricing and 4844 blobs, this is the pragmatic path to scale zk‑heavy applications on mainnet without sacrificing security hygiene. (blog.ethereum.org)
References and specs cited
- EIP‑1108 (BN254 precompile gas reprice), EIP‑196/197 (BN254 precompiles). (eips.ethereum.org)
- EIP‑2537 (BLS12‑381 precompiles; gas schedule, encodings, mapping). (eips.ethereum.org)
- Pectra mainnet announcement (EIP set and activation date), EIP‑7623 (calldata repricing). (blog.ethereum.org)
- IETF BLS signatures draft (fast/aggregate verify, POP). (ietf.org)
- Batch verification of Groth16 and its caveats. (fractalyze.gitbook.io)
- Tooling: arkworks/circom‑compat, gnark curves. (github.com)
- Gotchas: chain‑specific precompile pricing. (infsec.io)
Like what you're reading? Let's build together.
Get a free 30‑minute consultation with our engineering team.

