7Block Labs
Blockchain Technology

ByAUJay

I’m Designing a Bridge: Best Way to Aggregate Thousands of Plonk Proofs With BLS for One On-Chain Attest?


TL;DR for decision‑makers

  • Looking for sub-second to low-second latency and keeping an eye on costs? Go for BLS aggregate signatures with a single batch message (FastAggregateVerify) and verify it on-chain using EIP-2537. Expect to use around 103k gas for the pairing check, plus a bit more if you decide to store an aggregated public key. You can check out the details here: (eips.ethereum.org)
  • If you want that solid, cryptographic finality with as little reliance on any committee as possible, roll up your proofs off-chain into one Halo2-KZG/PLONK-style aggregated proof and just verify it on-chain once. This will typically run you about 350-420k gas per batch, and you can even still use a BLS attestation to control inclusion if you like. More info can be found here: (docs.nebra.one)

You can totally mix these two approaches: use BLS attestation for that quick “fast path” settlement, and then go for periodic recursive aggregation when it comes to the “final settlement.”


What changed since 2025 and why it matters

  • On May 7, 2025, Ethereum Pectra rolled out EIP‑2537, bringing native BLS12‑381 precompiles into the mix. Now, you've got seven EC/pairing operations available at fixed addresses ranging from 0x0b to 0x11, and the gas costs for pairing come to 37,700 plus 32,600 times k (where k is the number of pairs). This update really makes it feasible to do on-chain BLS aggregate signature verification on both L1 and L2s that adopt Pectra. Check out more details here.
  • Coming up in March 2024, Cancun/Deneb will introduce the KZG point-evaluation precompile at address 0x0a, which will cost exactly 50,000 gas for blob commitments. This means you can easily attach a hefty batch manifest (think hundreds of KB) to a transaction and reference it in your attestation without breaking the bank. For the nitty-gritty, head over to this link.

Your design space: three viable aggregation patterns

1) BLS-attested off‑chain verification (fastest, cheapest)

  • So, here’s how it works: each prover (or an aggregator) takes on the task of checking loads of PLONK proofs off‑chain.
  • A committee made up of N BLS signers then gets together to sign one batch message, let’s call it M, that confirms “all proofs in batch B have been verified.”
  • When it comes to on-chain, we just need to verify one aggregate signature using EIP‑2537 and the FastAggregateVerify method (k = 2 pairings). The gas cost for the pairing core comes out to roughly 37,700 + 2 × 32,600, which totals about 102,900 gas. To keep things efficient, we recommend storing or updating an aggregated public key instead of summing up all the public keys on-chain. Check out the details at (eips.ethereum.org).
  • Just a heads up, the security here really hinges on your committee and their incentives--like restaked operators and slashing--rather than just pure cryptography. A real-world example of this kind of verification model is Aligned Layer’s approach to BLS-attested off‑chain verification. You can read more about it here: (blog.alignedlayer.com).

2) Cryptographic aggregation (trustless, higher latency)

  • So, here’s the deal: we can bundle up a bunch of proofs into a single one using PLONKish techniques like accumulation or recursion (think Halo2‑KZG or aPlonK). You only need to verify it once on-chain, which will run you about ≈350k-420k gas for each batch. After that, claimants can submit relatively inexpensive inclusion proofs. The result? You’re looking at roughly ≈20k-50k gas per proof when you’re batching between 32 and 1,000. You can check out more details here.
  • Just to give you some context, this is inspired by previous works like SnarkPack for Groth16 (which has logarithmic verification time) and aPlonK for aggregating PLONK proofs. You can find more production docs and measurements from Nebra UPA here.
  1. Hybrid (what we ship most)
  • Right off the bat, we send a BLS-attested batch to keep things snappy and hit those latency/SLO targets. Then, we roll out a cryptographic super-proof every so often (like every X minutes or blocks) to ensure we have that “hard” finality and solid audit trail. This approach helps us safeguard against any committee slip-ups while keeping our economic risk in check.

The BLS‑first architecture that works today

Here's a solid, ready-to-use pattern designed for Ethereum after Pectra, as well as for L2s aligned with Pectra.

1) Choose the BLS ciphersuite and serialization

  • Go with the IETF CFRG BLS12‑381 “minimal‑pubkey‑size, proof‑of‑possession” ciphersuite:
    BLS_SIG_BLS12381G2_XMD:SHA‑256_SSWU_RO_POP_ (you’ll have public keys in G1 and signatures in G2; that means 48-byte public keys and 96-byte signatures. Plus, the proof of possession during registration helps keep rogue-key attacks at bay). This setup aligns perfectly with Ethereum’s consensus and has solid tooling available. (docs.rs)
  • For hashing to the curve, follow RFC 9380. When you’re on-chain, you can perform hash_to_field using SHA‑256 in the EVM, and then use the MAP_FP2_TO_G2 precompile (0x11) to map to G2. Alternatively, you can do the heavy lifting off-chain and just pass in the uncompressed points. (ietf.org)

Encoding Details (EIP‑2537 ABI)

  • G1 Point: 128 bytes (x||y)
  • G2 Point: 256 bytes, big-endian, uncompressed
  • Infinity Point: Represented by all zeros

When it comes to costs:

  • Pairing Check: 32,600 × k + 37,700 gas
  • Mapping Costs:
    • Fp→G1: 5,500 gas
    • Fp2→G2: 23,800 gas

You can find more information over at (eips.ethereum.org).

2) Register a committee with Proof‑of‑Possession

  • When you’re setting things up, each signer needs to submit:
    • a pk_i (that’s a 48-byte compressed public key which gets expanded off-chain to a 128-byte uncompressed G1 for the precompile),
    • a PoP_i over a fixed domain string to guard against rogue-key attacks,
    • and if they want, an optional stake or bond that allows slashing.
  • You can choose to store either:
    • the entire validator set and keep an “aggregated public key” AP = Σ pk_i in state, updating it during churn events, or
    • a Merkle root of the committee along with an on-chain AP that only gets updated when there are changes in the set.
      This approach helps you avoid piling on O(N) G1 additions for each verification. (Just a heads up, G1ADD costs 375 gas each if you need it.) (eips.ethereum.org)

3) Define the batch message M precisely

Use a single message M for everyone who's signing (this is a must for FastAggregateVerify). Make sure it's self-describing, tough against replay attacks across different chains, and closely linked to the specific proofs you say you've verified.

A Solid Byte Layout:

Creating a well-structured byte layout is essential for software development. It helps in organizing data efficiently and makes your application faster and more reliable. Here’s a quick rundown on how to set it up:

Why a Good Byte Layout Matters

A proper byte layout can optimize memory usage and improve performance. It can also reduce bugs and make it easier to understand your code. Here's why you should care:

  • Efficiency: Helps manage memory better.
  • Performance: Can speed up your application.
  • Clarity: Makes the code easier to read and maintain.

Key Principles

When crafting your byte layout, keep these principles in mind:

  1. Alignment: Ensure that data types are aligned to their natural boundaries.
  2. Packing: Minimize wasted space by packing data tightly.
  3. Consistency: Use the same order for similar data across your application.

Example Layout

Here’s an example of a simple byte layout that you might find useful:

struct MyData {
    char name[50];   // 50 bytes
    int age;        // 4 bytes
    float salary;   // 4 bytes
    double balance; // 8 bytes
};

In this case, you want to align the int, float, and double types properly to make the most out of your memory.

Tips for Success

  • Always keep an eye on performance as you design your layout.
  • Test thoroughly to catch any alignment issues early.
  • Document your layout decisions to help others (and your future self) understand why you made them.

For more detailed information on byte layout, check out this resource. It’s a great way to dive deeper into the subject.

By following these guidelines, you’ll be well on your way to creating a robust byte layout that enhances your application's efficiency and usability.

M = keccak256(encode({
  version:     uint16,                      // protocol message version
  srcChainId:  uint64,
  dstChainId:  uint64,
  bridgeId:    bytes16,                     // contract identity
  batchIndex:  uint64,                      // monotonically increasing
  batchCount:  uint32,                      // number of proofs claimed
  vkRoot:      bytes32,                     // Merkle root of verifying keys used
  proofsRoot:  bytes32,                     // Merkle root of per-proof commitments
  blobHash:    bytes32,                     // OPTIONAL: EIP-4844 versioned-hash bound to manifest blob
  timeWindow:  uint64[2],                   // [notBefore, notAfter] seconds since epoch
  dstProgram:  bytes32,                     // what program/contract tuple is authorized to consume
  domainTag:   "7BLK-PLONK-BATCH-v1"        // DST for hash_to_curve per RFC 9380
}))

Notes:

  • proofsRoot is tied to a specific proof tuple, which looks something like this: (proofId, vkHash, pubInputsHash).
  • If you've got a blobHash hanging around, it links to a batch manifest in a blob (this is a cost-effective way to handle data availability); you can take a peek at the 0x0a point-evaluation precompile or at least snag the versioned hash using the BLOBHASH opcode in the same transaction. Check it out here: (eips.ethereum.org)

4) Off‑chain workflow

  • The aggregator(s) gather proofs, check each PLONK proof off-chain (whether it’s PLONK, TurboPLONK, Halo2-KZG, or whatever else), and then create:
    • a proofsRoot that’s based on the proof descriptors,
    • a vkRoot for the verifying keys that were actually used,
    • and optionally, a 4844 blob that holds the manifest (which includes proof descriptors, inclusion paths, etc.).
  • Meanwhile, the BLS committee takes care of signing M. They combine the signatures off-chain into S = Σ sig_i and will calculate AP if needed (or just use the previously stored AP).

Performance Reference

When it comes to production systems, the BLS-attest off-chain checks show that batches are settling on-chain for about 350k gas. This includes the bookkeeping part, while the BLS verification itself is around 100-120k gas. You can read more about it here.

5) On‑chain verification function sketch (FastAggregateVerify)

  • First up, compute H = hash_to_field(M, domainTag), and then use MAP_FP2_TO_G2(H) to transform H(M) into G2.
  • Next, you'll want to call the pairing precompile just once with k set to 2 pairs:
    • Pair 1: (AP, H(M))
    • Pair 2: (−G1, S)
  • You should accept the result if e(AP, H(M)) · e(−G1, S) equals 1.
    For the gas costs, you’re looking at about 102,900 gas for the pairing core; if you map on-chain, that tacks on roughly 23,800 more. Plus, keccak/SHA‑256 will add a couple thousand gas. You can check it out here: (eips.ethereum.org).

Implementation facts you’ll care about:

  • EIP‑2537 is all about making sure subgroup checks are done in MSM/pairing. So, it's super important not to pass in any unvalidated additions into MSM. Instead, just stick to storing the AP you’ve calculated from the registered public keys. Check it out here.
  • Make sure to follow the IETF domain-separation tips outlined in RFC 9380. When tagging, include the protocol name and version to keep everything nice and organized. You can read more about it here.

Gas and latency you can actually budget for

  • FastAggregateVerify on L1 with pre-stored AP:

    • Pairing: You'll be looking at around 102,900 gas (with k = 2).
    • Hash_to_field + map-to-curve: This usually ranges from ~3k to 5k gas, plus an additional 23,800 gas if you're handling it on-chain. A lot of teams actually precompute H(M) off-chain and just send the point directly.
    • Total verification path: Expect roughly 110k to 135k gas, and that's not including any extra storage if you're keeping batch metadata. Check it out here.
  • If you choose to sum K pubkeys on-chain, be ready to add about ≈375 × (K−1) gas for G1ADD; that's why there's a push to store the AP in state. More info can be found here.
  • Cryptographic aggregation reference (Halo2-KZG batch verify):

    • It takes roughly 350k gas to verify the aggregated proof, plus around ~7-22k gas for each proof you include, depending on how you design your contract. In the end, you’re looking at an amortized cost of about 20k-50k gas per proof for a reasonable N. Dive deeper here.
  • Blob linkage (optional): If you need to check a value from the blob on-chain (like for a spot consistency check), the 0x0a point-evaluation precompile charges a flat 50,000 gas per check. You can read more about it here.

Security and ops guardrails (don’t skip these)

  • Rogue-key resistance: Make sure to enforce Proof-of-Possession (PoP) during key registration and re-registration, and don't accept keys that don’t have PoP. Use the POP ciphersuite for this. Check it out here.
  • Replay safety: To keep things secure, add source/destination chain IDs, the bridge contract ID, and a batchIndex that keeps getting higher in M. Also, make sure to expire stale signatures with a timeWindow to tighten security.
  • Liveness/redundancy: It’s smart to have at least two aggregators and an M-of-N committee. If you're into restaking (like AVSs), be sure to penalize operators who sign off on invalid batches or who miss deadlines. Get more info here.
  • Anti-censorship: Consider supporting both on-chain and off-chain proof submission options. On-chain submissions might hit your wallet a bit harder, but they’re tougher to censor; this is the approach Nebra UPA takes to balance costs and safety. Learn more here.
  • Blob hygiene: If you’re using blobHash, don’t forget to keep a record of the versioned hash and, when necessary, call the 0x0a precompile on important values. It's crucial never to assume that application contracts can pull blob bytes directly; instead, reference commitments.
  • Upgradability: To stay flexible, keep your verifier behind a timelock or direct it to an upgradeable verifier target. This way, you can easily switch between BN254 and BLS12-381 paths without having to move all your state around. Check out more about this here.

Where PLONK‑level aggregation still shines

Even if you can snag a budget BLS:

  • Compliance/finality: Some counterparties are looking for a bit more than just a committee signature; they want a cryptographic proof of inclusion or validity. Aggregated Halo2‑KZG or aPlonK proofs can provide that with a single on-chain verification. Check it out here.
  • Large‑N economics: When you're dealing with N in the thousands, the costs per proof really start to drop thanks to cryptographic aggregation. This can save you money compared to the traditional signature-per-proof model. This is particularly helpful for end users who might need inclusion checks later, which can rack up around 16-22k gas each. More details can be found here.
  • Multi‑system compatibility: With SnarkPack, you can aggregate Groth16 proofs from different producers, and there are similar options for PLONK variants. If your ecosystem is a bit of a mixed bag, “universal” aggregation can really simplify things. Dive deeper into it here.

A concrete example bridge plan (what we’d ship in 6-8 weeks)

  1. Committee + contracts
  • Launch the CommitteeRegistry using PoP-verified BLS public keys and make sure to maintain the AP in the state.
  • Set up the BridgeAttestor with the verifyBatch(M, S) function, utilizing the EIP-2537 precompiles; remember to store the batch roots and emit events. (eips.ethereum.org)

2) Batch format

  • For every proof descriptor ( d_i ), we use the formula: ( d_i = \text{keccak256}(vkHash_i || pubInputsHash_i || proofId_i) ).
  • Then, we calculate the proofsRoot as ( \text{MerkleRoot}(d_1…d_N) ).
  • If you want, you can also embed the manifest in a blob and record the blobHash in ( M ). (eips.ethereum.org)

3) Off‑chain

  • So, here’s how it goes: Aggregators handle the verification of all proofs, put together the manifest, and then go ahead and post a transaction that carries a blob. After that, they ask for BLS signatures on the precise M.
  • Next up, they aggregate those signatures S and submit them all at once to BridgeAttestor.verifyBatch.

4) Consumption Path

  • When consumers are on the destination chain, they use the isBatchValid(batchIndex) call and provide a Merkle path to d_i to make their claim.
  • To boost your assurance windows, set up a parallel recursive aggregation job to run every Y minutes. Once you have the aggregated proof, just post it to an AggregatedVerifier contract. (docs.nebra.one)

Budget Example:

  • N = 1,024 PLONK proofs
    • BLS verification (just once): around 110-135k gas.
    • For each claimant’s inclusion check (that’s the Merkle path + some light bookkeeping): usually falls between 15-25k gas.
    • If you decide to go for the optional aggregated super-proof: that’ll be about 350-420k gas for each batch when it’s produced. (docs.nebra.one)

PLONK‑specific tuning tips

  • If you're set on doing direct PLONK verifications, go for that SHPLONK-style multi-opening to keep the on-chain verifier costs down. But if you can swing it, just keep everything off-chain and stick to committing to vkRoot and proofsRoot. Check it out here.
  • If you're already on the BLS12-381 (KZG) train, good news! Pectra has tackled the big hurdle for verifying BLS-curve proofs directly on EVM. It might be time to rethink any old BN254 wrapping you’ve been holding onto "just for the precompiles." Dive into the details here.
  • When it comes to recursion, the Halo2-KZG setup using modern accumulation schemes and GPU provers can knock out 32-way batches in just a few seconds. So, plan those batches based on your arrival rate λ (just remember, N/λ gives you your fill time). More info can be found here.

When to pick which (quick rubric)

  • If your latency SLO is 5 seconds or less, and you’re cool with economic finality, go ahead with just BLS attestation.
  • For a latency SLO between 5 and 30 seconds, where auditors are asking for crypto proofs, use BLS now but make sure to aggregate proof every 1 to 5 minutes.
  • In the case of a high-value bridge, where courts or counterparties need that solid mathematical finality for every batch, make sure to get an aggregated proof for each batch. You can still use BLS for a quick pre-attestation if you want!

References you can hand to engineering and audit

  • EIP‑2537 (BLS12‑381 precompiles): This includes the addresses, ABIs, and gas costs (pairing: 37,700 + 32,600·k; map precompiles at 0x10/0x11). You can find more details on this here.
  • EIP‑4844 (KZG point‑evaluation precompile 0x0a, 50,000 gas): This one's all about blob-bound manifests. Get the scoop here.
  • Pectra mainnet activation: Mark your calendars for May 7, 2025; that’s when it goes live at epoch 364032. More info here.
  • IETF RFC 9380: This covers Hashing to Elliptic Curves and includes the CFRG BLS signature draft (with ciphersuites like POP). Check it out here.
  • PLONK/Groth16 aggregation: Learn about aPlonK and SnarkPack in the context of this aggregation. More details are available here.
  • Production aggregation economics and gas numbers: Dive into Nebra UPA and verification layers for insights on this. You can read more here.

Bottom line

  • So, if you're wondering, “what's the best way to combine a bunch of PLONK proofs into a single on-chain attest for a bridge?” - the way to go is off-chain verification + one BLS aggregate signature over a carefully designed batch root M. Then, just verify it using EIP-2537’s pairings. It’s super fast, affordable, and now officially supported on Ethereum.
  • But hey, if you want to boost your assurances or squeeze out even more gas savings per claim, consider adding a periodic Halo2-KZG/aPlonK super-proof. This mix gives you the speedy benefits of BLS while also providing the cryptographic finality of ZK, all with clear operational boundaries.

Sure thing! We can provide you with a solid Solidity snippet for FastAggregateVerify that uses the 0x0f pairing precompile. Plus, we’ve also got a message encoder/hasher that's all set to go, aligning with RFC 9380 and the POP ciphersuite. And don’t forget, we’ll throw in a batch manifest template that fits right into your bridge contracts. Check it out here: (eips.ethereum.org).


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

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

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.

© 2026 7BlockLabs. All rights reserved.