7Block Labs
Technical

ByAUJay

How Do Upgrade Paths Typically Work When Moving From Single-Proof Verification to Aggregated Proofs on the Same Contract?


Executive summary for decision‑makers

  • Since the Dencun upgrade in March 2024 (that's EIP-4844) and the Pectra activation on May 7, 2025, Ethereum has made some significant strides. We’re talking about affordable blob DA, a KZG point-evaluation precompile located at 0x0A, and BLS12-381 crypto precompiles (thanks to EIP-2537). These changes really shake things up when it comes to how proof submission and verification work, allowing for smoother transitions from verifying single proofs to aggregated ones without needing to switch up addresses. You can check out more details here.
  • When it comes to typical verifier upgrades, the trend is to go for an additive ABI. This means you keep your original verify(bytes, inputs) entrypoint but also add in verifyAggregated(...), connect new verifying keys, set pin statement domains, and manage the upgrades through UUPS/Transparent proxies or Diamond facets. The main goal here? To run both single and aggregated verification in parallel until those aggregated paths are not just theoretical but actually proven and measurable in practice. For more info, take a look at this.

Why teams are upgrading now: the economics changed

  • When it comes to verifying a single Groth16 proof, you’re looking at around 200k to 300k gas on Layer 1, plus about 7k gas for each public input. On the flip side, STARK verifications often shoot up to more than 1 million gas. But if you use recursive aggregation or SNARK-packers, you can compress those batches to a more manageable verification cost, generally in the hundreds of thousands of gas, which helps spread the cost across multiple proofs. (7blocklabs.com)
  • EIP-4844 introduced a cool feature with blob DA and a KZG point-evaluation precompile located at 0x0A. It has a fixed cost of 50,000 gas per opening, making it super cost-effective to bind proofs to blob content while keeping calldata small. This is especially crucial considering EIP-7623’s calldata floor. (eips.ethereum.org)
  • Pectra, set to launch on May 7, 2025 (epoch 364032), rolled out BLS12-381 precompiles (EIP-2537). For pairings, you’re looking at an on-chain cost of 32,600·k + 37,700 gas, plus some inexpensive MSM operations. This is great for BLS signature aggregation or BLS-based SNARK verification flows. (blog.ethereum.org)

What “aggregation” really brings to the table:

  • Instead of doing N single verifies, you get to handle one on-chain verify for the whole batch. How cool is that?
  • When it comes to real-world usage, you’ll often hear numbers like ~300k-350k gas for each aggregated verify. Add in about ~7k gas for the proof bookkeeping, or if you’re looking at a wrapped zkVM proof, it’s around ~275k-300k gas. Check out the details here.
  • If you're exploring off-chain options, Aligned’s Verification Layer is a solid choice. They use restaked operators to handle lots of proofs off-chain and then post aggregate BLS attestations on L1. You’re looking at around ~350k gas for a single batch, and approximately 40k per proof when you hit a batch size of 20. This setup can totally fit into a hybrid rollout strategy, combining a fast path with L1 aggregation for that sweet finality. For more info, take a peek here.

Design goal: upgrade on the same address, keep the original API working

Most teams prefer not to change the verifier address too often. Here are two solid options:

  1. Upgradeable proxy with an additive ABI
  • Leave the verify(bytes proof, uint256[] publicInputs) function as it is.
  • Introduce verifyAggregated(...), and you might want to include a read-only view that shows the current verifying key hash and circuit version.
  • Go with UUPS or Transparent proxies, and make sure to perform storage-layout checks using OpenZeppelin plugins before any upgrades. (docs.openzeppelin.com)

2) Diamond (ERC‑2535) Facets

  • So, if your verifier is getting close to that pesky 24KB EIP‑170 size limit, consider shifting the verifier functions into a “VerifierFacet.” You can bundle up the logic into an “AggregatorFacet” too. Just use diamondCut to make the switch without having to alter the address. This approach also allows you to deploy various curves and circuits as independent facets. Check out more details here.

Quick Note on Size Limits

Just a heads up: the mainnet still sticks to a 24KB limit per contract (thanks to EIP-170). There are some draft EIPs floating around that talk about possible increases, but it's best to work with the 24KB limit for now or think about breaking things up into smaller pieces. Check out more details here.


ABI patterns we recommend (additive and versioned)

Expose These Minimal, Additive Entry Points:

// Original single-proof entrypoint (unchanged)
function verify(bytes calldata proof, uint256[] calldata pubInputs)
  external view returns (bool ok);

// New: aggregated batch verification
function verifyAggregated(
  bytes calldata aggProof,        // the recursive/aggregated proof
  bytes32 aggVkHash,              // verifying key hash for the aggregator circuit
  bytes32 batchId,                // opaque batch identifier
  bytes32 batchStatementHash      // domain-separated hash of all included statements
) external view returns (bool ok);

// Optional: per-proof inclusion checks against the stored batch root
function isIncluded(bytes32 batchId, bytes32 statementLeaf, bytes32[] calldata merkleProof)
  external view returns (bool);

// Introspection: help off-chain ops pin exact VKs / circuit versions
function verifierConfig() external view returns (
  bytes32 singleVkHash,
  bytes32 aggVkHash,
  uint64 singleCircuitVersion,
  uint64 aggCircuitVersion
);
  • Ensure that verify(...) remains stable for current integrators.
  • Avoid sending large per-proof metadata in calldata as outlined in EIP-7623; instead, stick to committing a Merkle root in batchStatementHash and verify membership on the fly when necessary (this approach is more cost-effective and adaptable). (eips.ethereum.org)

Verification key handling: store, pin, and roll safely

Your upgrade is going to bring in fresh verifying keys (VKs) for the aggregator circuit. Here are some practical patterns to consider:

  • Pin the VKs by their content hash right in the contract storage, and make sure the aggVkHash lines up. This way, you avoid any confusion when there are multiple VKs hanging around.
  • Keep those bulky VK bytes with SSTORE2 pointers and store both the pointer and its keccak256 in storage. When you need to read them, use EXTCODECOPY since it's cheaper than SLOAD for larger blobs; plus, updates are designed to be “write-once.” Check it out here: (github.com)
  • Don't forget to version your circuits clearly. A straightforward uint64 version along with the VK hash does the trick for traceability and rollbacks.
  • Make sure to emit those VerifierUpgraded(vkHash, circuitVersion) and VerifierKeyRotated(oldHash, newHash) events.

Tip: Whenever a statement mentions chain data, make sure to include the block number and chain-id in your statement hash. Thanks to EIP‑2935 and EIP‑4788, you can verify L1 blockhashes and beacon roots directly on-chain without needing any external oracles. This really helps to enhance your domain separation. Check it out here: (eips.ethereum.org)


Curve and precompile choices: BN254 vs BLS12‑381 post‑Pectra

  • If you're working with legacy verifiers on Ethereum, chances are you're using BN254 (alt_bn128). This is a popular choice thanks to the long-standing precompiles and the gas savings from EIP-1108 for ECADD, ECMUL, and pairing, which will set you back around ~34,000·k + 45,000 gas. If your single-proof verifier is a BN254 Groth16, you can stick with it for now. (eips.ethereum.org)
  • On the flip side, if you're diving into new aggregated circuits or BLS aggregation, BLS12‑381 (EIP‑2537) has officially made its mark on the mainnet with dedicated MSM and pairing precompiles. The costs? Pairings will run about 32,600·k + 37,700 gas, while G1/G2 additions are priced at 375/600 gas, and mapping for FP/FP2 will cost 5,500/23,800 gas. Always double-check that your aggregator’s curve aligns with the precompile options available on your deployment chains. (eips.ethereum.org)
  • And don’t forget about the mixed ecosystems: Some Layer 2s are gradually picking up Pectra equivalents, so it’s smart to check what's happening on your target chain. The OP Stack has been keeping tabs on BLS12-381 support as it relates to its Pectra/Isthmus setup--so don’t just assume that every chain has EIP-2537 up and running yet. (github.com)

Keep calldata small: blobs + point‑evaluation precompile

If you need to reference a bunch of leaf statements in your aggregated proof, don’t just throw them all into calldata (EIP‑7623 makes data-heavy transactions more expensive). Instead:

  • Publish the per-proof metadata into blobs (EIP-4844).
  • In your contract, check small field-element bindings using the 0x0A point-evaluation precompile (which costs 50k gas) to make sure you're connected to the correct blob data, and verify your single succinct aggregated proof. (eips.ethereum.org)

Result: you keep your calldata nice and compact, steer clear of the worst-case floor pricing, and your batch still gets to enjoy L1 security, all while using blobspace--which, by the way, has seen its capacity double to a 6/9 target/max per block in Pectra through EIP-7691. (eips.ethereum.org)


Memory/copy costs matter in verifiers: target MCOPY

Dencun introduced EIP‑5656 (MCOPY), and with Solidity 0.8.25, the code generation now uses mcopy() automatically. This neat little update slashes the gas costs for memory copies in proof parsing and MSM inputs. If you're planning to switch to aggregated verification--where you get larger calldata sections in a single call--make sure to compile with version 0.8.25 or higher and set the EVM version to “cancun” to take full advantage of these savings. Check it out here: (soliditylang.org).


Three aggregation paths we see in production

1) Recursive SNARK Wrapping (e.g., STARK/FRI → Groth16/Plonk)

So, here's the deal: leaf proofs are verified using a recursion circuit and then wrapped up into a nice, compact proof. When it comes to verifying on-chain, this means you’re looking at O(1) pairings with some pretty small public inputs. It’s impressive how batches of thousands can come in, often clocking in around 300k-900k gas for the entire batch. If you're curious about the details, check out the full scoop over at 7blocklabs.com.

2) Aggregation schemes like SnarkPack/aPlonk

  • These schemes let you pile up a ton of existing Groth16/Plonk proofs without having to mess around with the leaf circuits. SnarkPack has shared some impressive numbers, reporting the ability to aggregate 8192 Groth16 proofs in about 8-9 seconds, and the off-chain verification takes under 200 milliseconds. When you're on-chain, you just need to verify a succinct aggregator proof. (eprint.iacr.org)

3) External Aggregation/Verification Layers

So, let's talk about some examples of external aggregation or verification layers. You've got options like Aligned (which uses AVS on EigenLayer) and universal proof aggregators like NEBRA UPA and Electron. The way it works is pretty straightforward: you submit or register your proofs off-chain, and then they shoot back onto the chain with just one aggregated proof or a BLS-signed result.

As for gas costs, right now you're looking at around 300k to 380k fixed per batch, along with some smaller fees for each proof. The cool thing is that the gas cost per proof decreases as your batch size increases. If you want to dive deeper into this, check out Aligned's blog.

Which to Use?

  • If you own the circuits and you're dealing with high throughput, going with recursion or wrapping is a smart move. It cuts down on per-proof gas, making it ideal for rollups.
  • Got a bunch of different circuits or can't re-engineer the leaves? Then a SnarkPack-style aggregation is probably your best bet.
  • Looking to hit the market quickly and okay with an AVS or an external protocol dependency? You might want to think about integrating an aggregation/verification service. Just make sure to keep a backup plan with a direct L1 verification for solid finality. (blog.alignedlayer.com)

A concrete upgrade plan that keeps your address

Phase 0 -- Prep (1-2 sprints)

  • Lock in the current verifier ABI to keep things steady for verification.
  • Create the aggregator circuit and keys; make sure to note down the aggVkHash; think about how to expand the storage layout; and draft up some migration tests.
  • Choose the proxy pattern: Will we go for UUPS (which overrides _authorizeUpgrade) or a Diamond facet structure with a split between VerifierFacet and AggregatorFacet? (docs.openzeppelin.com)

Phase 1 -- Additive Deployment (1 Sprint)

  • We're rolling out a new implementation that includes:

    • A verifyAggregated(...) function (view) designed to check aggVkHash and ensure we have a domain-separated batchStatementHash.
    • A verifierConfig() that exposes the hashes and versions we’re using.
    • An optional isIncluded(...) method featuring a Merkle verifier.
  • We’ll also be storing those larger verification keys (VKs) with SSTORE2 pointers, while pinning the hashes in storage. You can check it out here.

Phase 2 -- Shadow Mode (2-4 Sprints)

  • The off-chain aggregator is up and running, but on-chain consumers are still just using the single verify(...) call.
  • Make sure to submit those aggregated verifies regularly to test out the path and log the batchIds. Keep an eye on gas usage and latency while you're at it.
  • If your statements are getting bulky, it's a good time to roll out those blob flows and 0x0A point-evaluation bindings. Just double-check that your calldata doesn’t go over the EIP-7623 floor thresholds with your current workloads. You can find more details over at (eips.ethereum.org).

Phase 3 -- Controlled Traffic Shift

  • Kick things off by adding a routing flag in your app contracts. This will help you prefer aggregated verification when the batch availability SLOs are met. If they're not, just revert to single verification.
  • Here’s how to set your SLOs based on the chain:

    • Ethereum L1: Aim for a budget of around 300k-350k gas for each aggregated verify. Also, try to keep the per-proof inclusion costs away from the hot paths (think log events and Merkle membership on demand). You can check more details here.
    • Pectra-aligned chains: Make use of BLS12-381 precompiles for any BLS attestation or BLS-based SNARKs in your setup. For more insights, take a look at this blog post.

Phase 4 -- Hardening

  • Let's throw in some timelocks and a kill-switch to ensure we stick to the single-proof path if the aggregator's liveness starts to falter.
  • We should lock VK hashes after a little cooldown period; governance will need to step in for any rotations.
  • Time to roll out those dashboards! We’ll track success rates, batch sizes, blob fee changes (thanks to the EIP-7691 updates which shake up blob dynamics), and keep an eye on calldata/MCOPY hit rates. (eips.ethereum.org)

Gas and capacity planning you can take to a budgeting meeting

  • When it comes to a single Groth16 verification, you’re looking at around 200k-300k gas, plus about 7k gas for each public input. A good rule of thumb here is roughly 207,700 + 7,160·l, where l is the number of public signals. To keep l low, make sure to hash any bulky statements. (7blocklabs.com)
  • For aggregated verification using Halo2‑KZG, the reported cost is about 350k gas as a base per batch, plus roughly 7k gas per proof for bookkeeping. If you set N to 32, you’ll get around 18k gas per proof when spread out. (docs.nebra.one)
  • When it comes to KZG binding to blobs, it’s about 50k gas for each point evaluation at 0x0A, so plan for one or just a few openings per batch. (eips.ethereum.org)
  • As for BLS12‑381 pairing math, you’re looking at 32,600·k + 37,700 gas for k pairings. It’s a smart move to use MSM precompile for larger multi‑signer checks instead of looping through MUL/ADD. (eips.ethereum.org)

Capacity Implications

When you're dealing with a 45M gas block, it’s pretty tough to squeeze in thousands of individual verifies. However, with just one aggregated verify, plus some inclusion bookkeeping (or even a few batches), you’re in a good spot. This is exactly why aggregation can really help reduce end-to-end latency--especially when the hold-up is about verifying inclusion rather than finality. Check out more details here.


Security and operational pitfalls (and how to avoid them)

  • Domain Separation: Set batchStatementHash as keccak256("agg:v1" || chainId || l1Block || vkHash || MerkleRoot || …). Don’t mess around with the single-proof message domain.
  • VK Pinning: Make sure aggVkHash matches what’s in storage, and don’t forget to emit events when there’s a rotation. Also, avoid accepting VK bytes from calldata unless they match the expected hash.
  • Liveness Fallback: If an aggregator gets stuck, it shouldn’t stop your whole protocol. Keep the single verify function callable and rated for safety.
  • Gas Griefing: If you mark inclusion with SSTORE for each proof, attackers could force you into those annoying 20k-gas writes. It’s better to use logs for audits and on-demand Merkle membership checks instead of “mark and store” for every leaf.
  • Cross-Chain Differences: Not every L2 has EIP-2537 support yet, so check for feature-detecting precompiles. It’s a good idea to ship a BN254 verifier facet as a backup. Keep an eye on OP Stack and other rollup stacks’ Pectra support. (github.com)
  • Code Size Creep: If adding aggregation gets you close to that 24KB limit, consider moving proof parsing or VK bytes behind SSTORE2, or think about splitting it into Diamond facets. (eips.ethereum.org)

Example: additive migration diff (Solidity pseudocode)

contract Verifier is UUPSUpgradeable {
  // v1 fields (unchanged)
  bytes32 public singleVkHash;
  uint64  public singleCircuitVersion;

  // v2 fields (new)
  bytes32 public aggVkHash;            // hash of Aggregator VK
  uint64  public aggCircuitVersion;
  address public aggVkPointer;         // SSTORE2 pointer with VK bytes

  event VerifierUpgraded(bytes32 singleVkHash, uint64 singleVer, bytes32 aggVkHash, uint64 aggVer);
  event BatchVerified(bytes32 batchId, bytes32 batchStatementHash);

  function verify(bytes calldata proof, uint256[] calldata pubInputs) external view returns (bool) {
    // existing single-proof verification (unchanged)
  }

  function verifyAggregated(
    bytes calldata aggProof,
    bytes32 aggVkHash_,
    bytes32 batchId,
    bytes32 batchStatementHash
  ) external view returns (bool ok) {
    require(aggVkHash_ == aggVkHash, "VK_MISMATCH");
    // 1) Optionally call 0x0A to bind to blob content for this batch
    // 2) Run aggregator verifier using BLS12-381 precompiles (EIP-2537) or BN254, depending on your circuit
    // 3) Domain-separate and return true on success
  }

  function isIncluded(bytes32 batchId, bytes32 leaf, bytes32[] calldata merkleProof)
    external view returns (bool) {
    // verify membership against the batch root embedded in batchStatementHash
  }

  function _authorizeUpgrade(address newImpl) internal override onlyOwner {}
}
  • Let's make sure the old verify() stays as it is to keep those integrator contracts working smoothly.
  • We’re logging BatchVerified for batches, so downstreams can pick up off-chain whenever they need to, and just hit isIncluded when necessary.
  • If your aggregator circuit uses BLS12-381, go ahead and map it to the EIP-2537 precompiles for MSM + pairings; if that’s not an option on your target L2, just send it to the BN254 fallback. (eips.ethereum.org)

“Gotchas” born from new protocol rules

  • With EIP‑7623's new calldata repricing, you might face penalties for large submissions that are heavy on calldata. The good news? Blobs and the 0x0A adjustment can sort that out. If you need to send data through calldata, try to keep a solid balance between execution gas and calldata to steer clear of those pesky higher costs. (eips.ethereum.org)
  • Pectra has boosted blob throughput, aiming for a target of 6 and a max of 9, which usually keeps those blob fees nice and low. Just a heads-up, though: you’ll want to keep an eye on things--don’t just assume that low fees will stick around forever. (eips.ethereum.org)
  • MCOPY is only going to be useful if you’ve got your Solidity and EVM settings just right; make sure you double-check your optimizer and the evmVersion in your CI. (soliditylang.org)

Benchmarks and references you can cite internally

  • SnarkPack (Groth16): This bad boy can aggregate 8192 proofs in just about 8-9 seconds on a 32-core CPU. When it comes to verification, we're looking at around 33-163 ms off-chain, while the on-chain verification stays as a neat pairing check. Check out more details here.
  • NEBRA UPA (Halo2‑KZG aggregation): For the base verification, it’s roughly 350k gas, plus about 7k for each proof you include; with N=32, that jumps to around 18k gas per proof. More info can be found here.
  • Succinct SP1 wrap: Expect to spend around 275-300k gas for each compressed proof on the EVM. More and more production deployments are leaning on this to help manage zkVM workloads. You can read more about it here.
  • Aligned Verification Layer (AVS): You’re looking at about 350k gas for a batch of one, which works out to roughly 40k per proof when you hit a batch size of 20. Latency is in the milliseconds range, and they’ve also got a solid aggregation path if you need it. Dive into the details here.

Upgrade checklist (use this with your PM and auditor)

  • Governance

    • Consider using a Timelocked UUPS/Transparent proxy or a Diamond setup with a limited diamondCut feature. (docs.openzeppelin.com)
    • Implement a multisig wallet along with a straightforward Runbook for switching back to single-proof verification easily.
  • Storage and ABI

    • We've got new fields added, and there are storage layout differences thanks to some OpenZeppelin plugins. Check it out on GitHub.
    • SSTORE2 pointers are now available for handling large VKs, and VK hashes are pinned for better tracking. More details can be found on GitHub.
    • The verify() function hasn't changed, but we've introduced verifyAggregated() and verifierConfig() for added functionality.
  • Cryptography

    • Make sure the curve and precompile availability are audited for each chain (BN254 vs. BLS12‑381). (eips.ethereum.org)
    • Check that domain separation covers chainId and the block context (look at EIP‑2935/4788 where it makes sense). (eips.ethereum.org)
  • Data plumbing

    • Start with blob-first for those heavy metadata loads; link it up with those 0x0A point-evaluation openings in the contract. (eips.ethereum.org)
    • Keep an eye on the calldata footprint to make sure it stays within EIP-7623 limits. (eips.ethereum.org)
    • Upgrade to Solidity version 0.8.25 or higher to take advantage of MCOPY for proof parsing. (soliditylang.org)
  • Rollout

    • Shadow mode: testing the aggregated path in production while keeping things smooth for consumers.
    • SLOs: we've got batch size, latency, and failure fallback all set; plus, our dashboards are showing blob fee health.

Bottom line

Shifting from single-proof verification to aggregated proofs for the same contract is a smart move for 2026--low risk and great return on investment. Here’s the game plan: keep your current ABI up and running, add an aggregated entry point, lock in your VKs and domains, store those hefty artifacts off-storage, and send any bulky metadata to blobs verified through 0x0A. Thanks to Pectra’s BLS12‑381 precompiles and the new calldata pricing, these choices are actually cheaper and safer than they were back in 2023-2024. With a dual-run rollout and solid fallbacks, most teams are seeing immediate gas savings, fewer on-chain transactions, and improved throughput--plus, they don’t have to compromise on their address or integrator compatibility. Check out the details here: (blog.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.