ByAUJay
What’s the Cleanest Design Pattern for a Rollup That Can Swap Proof Formats Later Without Redeploying Everything?
TL;DR (for decision‑makers)
- If you're looking for the cleanest and low-risk setup, check this out: RollupCore (immutable) → VerifierProxy (this one’s an upgradeable pointer) → VerifierAdapters (these are specific to each proof family) + VKRegistry (just keep adding to it).
- Evolving proofs on Ethereum has become way easier and more cost-effective. Thanks to Dencun (EIP-4844), we got blob-based data availability for rollups and a KZG point-evaluation precompile. Plus, with Pectra coming on May 7, 2025, we're seeing BLS12-381 precompiles, increased blob throughput, and a recalibration of calldata pricing--these changes really push us toward those swappable, modular verifiers. (eips.ethereum.org)
- Some real projects are already rolling out modular, multi-proof stacks (think OP Stack’s “multi-proof nirvana” or Arbitrum BoLD), which separate the question “is this state root valid?” from “which proof produced it?” This means your L2 can adopt a similar setup and easily switch proof formats with just a configuration update. (blog.oplabs.co)
Why plan for proof‑format swaps now?
- We've got some exciting news: affordable and plentiful data availability (DA) through blobs is finally here! The KZG point-evaluation precompile at 0x0A allows contracts to verify commitments without needing to dive into the blob contents, keeping those blobs hidden from the EVM by design. This change shifts our focus to “commitments” rather than just raw batch data. Check out more on it here: (eips.ethereum.org).
- Pectra went live on the mainnet on May 7, 2025, and here’s what it brought along:
- EIP‑2537 BLS12‑381 precompiles (0x0b-0x11), which means we can now do native BLS multi-scalar multiplications (MSMs) and pairings--making BLS-friendly verifiers practical right on Layer 1.
- EIP‑7691 bumps up blob throughput (aiming for 6, with a max of 9 blobs).
- EIP‑7623 raises calldata costs, nudging us to shift DA from calldata into blobs. Want to know more? Dive into the details here: (blog.ethereum.org).
- Optimism’s fault-proof system was created to be super flexible and aim for that ideal “multi-proof nirvana.” On the other side, Arbitrum launched BoLD to ensure limited dispute times while allowing for permissionless validation--these both reflect a smart approach to “proof of state” with easily interchangeable backends. (blog.oplabs.co)
Result: you’re looking for a rollup that can easily integrate new curves, schemes, or wrappers as the economics and L1 features evolve--without needing to switch your bridge or re-issue any assets.
The pattern: Verifier Proxy + Adapters + VK Registry
Think of verification like a module you can swap out, while everything else stays the same.
- RollupCore (immutable)
- Takes care of the main L2 state root, along with the deposit and withdrawal message trees, plus all the system settings.
- Keeps a single reference to VerifierProxy.
- VerifierProxy (upgradeable pointer via timelocked governance)
- Handles the verify() calls and sends them off to the current VerifierAdapter based on the given formatId.
- VerifierAdapters (one for each proof family)
- They’re the ones running the format-specific logic (like Groth16 on BN254, PLONK on BLS12-381, and even STARK→SNARK wrappers).
- Pull in verifying-key metadata from VKRegistry and make sure the public-input binding is enforced.
- VKRegistry (append-only, timelocked)
- Links up (formatId, circuitId, version) to vkHash + metadata (including curve, pairings, pubInputsLayout, proofLen).
- Fires off events; no edits can be made in place; while entries can be deactivated, they can’t be changed once they’re in.
This is similar to how OP Stack separates the fault-proof framework from any particular proof, just like Arbitrum has split its dispute protocol (BoLD) from the chain logic. (blog.oplabs.co)
Minimal interfaces (Solidity)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
struct ProofContext {
uint64 l2BlockNumber;
bytes32 prevStateRoot;
bytes32 newStateRoot;
bytes32[] blobVersionedHashes; // EIP-4844 commitments
bytes32 daCommitment; // e.g., Merkle root or KZG commitment hash
bytes32 stfVersion; // hash of the state transition fn version
bytes32 vkId; // (formatId,circuitId,version) -> VKRegistry key
}
interface IVerifierAdapter {
function verify(bytes calldata proof, ProofContext calldata ctx)
external view returns (bool ok);
}
interface IVKRegistry {
function metadata(bytes32 vkId) external view returns (
uint32 formatId, bytes32 circuitId, uint16 version,
bytes32 vkHash, bytes4 curveId, uint8 pairingCount, uint32 pubInputsLen
);
function active(bytes32 vkId) external view returns (bool);
}
contract VerifierProxy {
address public owner; // behind timelock
mapping(uint32 => address) public adapterOf; // formatId -> adapter
function verify(uint32 formatId, bytes calldata proof, ProofContext calldata ctx)
external view returns (bool)
{
address adapter = adapterOf[formatId];
require(adapter != address(0), "NO_ADAPTER");
// Adapters MUST re-derive and check all public inputs & blob commitments.
return IVerifierAdapter(adapter).verify(proof, ctx);
}
}
- Let RollupCore stay out of the details of proof specifics; it just needs to know that VerifierProxy gives a thumbs up for the provided context and the new state root.
- VKRegistry can align with the idea behind ERC‑1922 (which standardizes verifier parameters using a verificationKeyId). (ercs.ethereum.org)
Practical migration example: Groth16 (BN254) → PLONK (BLS12‑381)
- Register the new verifying key
- Add vkId for PLONK/BLS12‑381 in the VKRegistry, including the pub-input layout metadata.
- Share the circuits and vkHash so that watchers can rebuild and do some comparisons.
- Deploying a PLONK/BLS12‑381 Adapter
- Go for the EIP‑2537 precompiles: the cost for pairing comes out to 32,600·k + 37,700 gas; that’s for k pairings. For a bunch of designs, this setup turns out to be a bit cheaper per pairing compared to BN254 after the EIP‑1108 changes. Check out more details here: (eips.ethereum.org)
3) Dual-accept window
- Governance includes
adapterOf[PLONK]in VerifierProxy while keeping Groth16 as the default option. - Sequencers and provers generate both proof types for overlapping checkpoints, and RollupCore will accept whichever one verifies first. This redundancy reflects the OP Stack's move towards supporting multiple proofs. (blog.oplabs.co)
- Flip the default and phase out the old vkId
- After N days, let's turn off the new Groth16 proofs by deactivating the legacy vkIds in the VKRegistry.
- We'll hold on to the legacy adapter just in case we need it for historical verification or if there's an emergency fallback.
- Optional “proof‑of‑proof” safety net
- For those high-stakes transitions, it’s a good idea to throw in a little SNARK that basically says, “Hey, the old Groth16 verifier would’ve given the thumbs-up for this input.” This makes it faster to roll things back if any adapter hiccups pop up. Teams like Succinct and RISC Zero are already rolling out their recursion/wrapping stacks where they’ve got SNARK‑of‑STARK or SNARK‑of‑SNARK setups all ready for action. (blog.succinct.xyz)
Gas, latency, and DA economics to budget against
- Groth16 on BN254: You’re looking at around ~207,700 gas for a fixed cost plus about ~7,160 gas for each public input after EIP-1108. It’s a good idea to try to merge public inputs into a single hash wherever you can. For example, Polygon zkEVM does this with an aggregator pattern to minimize inputs. (medium.com)
- STARK verification on Ethereum: Typically, you’re looking at around ~5-6M gas (like with Starknet’s SHARP/Stone trains). Because of this hefty gas cost, a lot of stacks end up wrapping STARKs in SNARKs for L1. (community.starknet.io)
- BLS12-381 on Pectra: Here, you’ve got native MSMs plus pairing checks that range from 0x0b to 0x11; the pairing gas comes out to about ~32,600·k + 37,700. This setup enables verifiers and signature checks that meet modern security standards while keeping gas costs competitive. (eips.ethereum.org)
- Blobs vs calldata: Just a heads up--blobs aren’t readable by the EVM. Contracts use the 0x0A precompile to verify KZG openings against versioned blob hashes. With EIP-7623 increasing the calldata floor cost, consider calldata as more of an emergency option; aim to design your on-chain interfaces to be blob-commitment-oriented. (eips.ethereum.org)
- Blob capacity: Pectra has upped its target/max to 6/9 blobs, which is great because it boosts L2 batch DA headroom and helps level out fees during those dual-proof migration windows. (eips.ethereum.org)
Case studies: what today’s leaders are doing
- OP Stack: Fault proofs are getting modular and are on the path to working alongside different proof systems. The goal is to run several proofs together to hit that “Stage 2” decentralization. Think of your VerifierProxy/Adapter setup as a stand-in for the validity-proof side. You can read more about it here.
- Arbitrum BoLD: This is an upgrade to the dispute protocol that ensures a quick resolution time and allows for permissionless validation on the mainnet, starting February 12, 2025. It’s pretty cool because it shows you can totally revamp proof and dispute logic without messing with user-facing bridge addresses. Check out the details here.
- Polygon zkEVM: They’re actively trying to cut down verifier costs (like with FFLONK and combining public inputs into one hash) to keep on-chain verification affordable and easy to use. You can dive deeper into it here.
- STARK→SNARK wrappers: RISC Zero and Succinct’s SP1 are compressing large STARKs into smaller, pairing-friendly proofs for Ethereum verification. This is exactly the type of format shift your adapters should be ready for. Learn more about it here.
Design checklists you’ll actually use
Security Essentials
- Public input binding: Make sure to recompute the digest within each adapter using keccak256 with the parameters like
[prevRoot, newRoot, l2BlockNumber, blob hashes, stfVersion, DA commitment], and then compare it with the circuit’s single public input. Always steer clear of accepting a digest that's provided by the caller. You can check out Polygon’s documentation for a solid explanation of why using one public input helps save gas and simplifies the binding process. (docs.polygon.technology) - VK immutability: The VKRegistry should be append-only, meaning no in-place edits allowed. Make sure to emit events for any additions or removals, and implement a delay before anything goes live.
- Timelocks and kill-switches: It’s best to place the timelock on the VerifierProxy and VKRegistry, not on RollupCore. If you need to, an emergency pause should only affect proof acceptance and should never touch deposits or exits.
- Subgroup and encoding checks: The BLS12-381 precompiles do a great job enforcing subgroup checks for MSM/pairing, but you should still validate encodings and the infinity rules. (eips.ethereum.org)
- Auditor and chaos tests: It’s essential to test dual-accept periods with adversarial proofs and malformed KZG inputs. Remember, even the best teams run into bugs--like the SP1 verifier issue in 2025--so always be prepared for quick adapter swap-outs. (blockworks.co)
Reliability and Operations
- Dual-run Policy: When you're migrating, make sure to generate both proof types for M checkpoints. You only confirm a state root after one of the proofs is verified on L1.
- Deterministic STF Hash: Keep your STF (state transition function) versioned with a content hash, and tie it into the ProofContext. This helps prevent any “silent circuit drift.”
- DA Discipline: For any fields you need to verify on-chain (like batch metadata), put the blob versioned hashes in context and check KZG openings using precompile 0x0A. You can find more details on this at eips.ethereum.org.
Governance and Upgrade Transparency
- ERC‑1967/UUPS or Diamond: Stick with a familiar upgrade path for the VerifierProxy pointer. Let everyone know about vkId and adapter changes ahead of time by using a timelock, and share reproducible builds for those keeping an eye on things. (eips.ethereum.org)
Implementation notes that save weeks
- One public input: Take a page from Polygon’s playbook--set up the outer verifier to use just one public input, which should be the hash of all the inner publics. This can save around 7k in gas for each omitted public input and makes things easier for adapters. (hackmd.io)
- BN254 vs BLS12‑381: With EIP‑2537 now in action, BLS12‑381 verifiers and BLS signatures are officially a big deal on L1. Here’s how they stack up: BN254 comes in at 34,000·k + 45,000, while BLS12‑381 is a bit better at 32,600·k + 37,700. If you’re starting fresh in 2026, definitely lean towards BLS12‑381 for that extra security cushion and gas savings, or even better! (voltaire.tevm.sh)
- STARK wrapping--when’s it a good idea? So, native STARK verification can get pretty pricey at about 5-6M gas. Instead, consider SNARK‑wrapping for a much lighter on-chain footprint and blob-friendly calldata. And don’t forget to use adapters; this way, you can easily switch up your “wrapper SNARK” later (like going from Groth16 to PLONK). (community.starknet.io)
- Blob realities: Just a heads up--the EVM can’t actually read the contents of blobs; you can only see versioned hashes and KZG proofs. So, make sure to structure your ProofContext and circuits around commitments and point evaluations instead of dealing with raw batch bytes. (eips.ethereum.org)
Migration runbook (calendarized)
- T‑30 days: Time to get the ball rolling! Let's publish the RFC for the new format (formatId, circuits, vkHash, and pub-input layout). Also, we'll kick off the shadow prover.
- T‑21 days: We'll dive into auditing the adapter and circuits. Then, we'll deploy the adapter on a canary L1 alias.
- T‑14 days: The timelock will add adapterOf[formatId] and activate the vkId entries. We'll start the dual-run on testnet.
- T‑7 days: It's time to enable dual-accept on mainnet. The watchdogs will have their work cut out for them, comparing newRoot across both proofs and sounding the alarm if there's a divergence greater than 1 block.
- T‑2 days: Get ready for the announcement of the flip block! We'll also share some dry-run metrics, like proof latency and gas usage.
- T day: Today’s the day! We’ll flip the default to the new format. The legacy adapter and vkId will still be active, but they’ll take a back seat.
- T+14 days: We’ll deactivate the old vkId, but we’ll keep a read-only adapter for forensics. Plus, we’ll publish a post-mortem to wrap things up.
This is pretty much the same approach OP Stack uses to roll out alternate proofs on testnets before hitting mainnet, just like Arbitrum did with BoLD while keeping user-facing contracts intact. You can check out more about it here.
Example: STARK→SNARK wrapper adapter
- Off‑chain: First up, we create a recursive STARK proof. Then, we whip up a Groth16/PLONK wrapper that has just one public input. This input ties together the previous and new roots, blob commitments, and the STF version.
- On‑chain: Over here, the adapter makes sure that this single public input matches up with
keccak256(encoded ProofContext). After that, it calls either the BN254 or BLS12‑381 verifier. This is the route that zkVM stacks like RISC Zero and SP1 take to upload compact proofs to Ethereum. (7blocklabs.com)
What to monitor post‑Pectra
- Throughput: Thanks to EIP‑7691, the bigger blob capacity makes it easier to deal with data availability (DA) constraints. This allows for overlapping proof windows during migrations. Plus, don’t forget to keep an eye on the blob base-fee dynamics with the new 2:3 target:max schedule. (eips.ethereum.org)
- Costs: With EIP‑7623 shaking things up, the pricing for calldata has changed. It’s a good idea to steer clear of calldata-heavy verifiers in busy areas and lean towards compact proof encodings that don’t require too many public inputs. (eips.ethereum.org)
- Proof vendors: The zkVM teams are moving at lightning speed! They’re hitting real-time proving milestones, but be prepared for the occasional critical bug. It's smart to have a plan to quickly revert adapters under a timelock just in case. (blog.succinct.xyz)
Antipatterns to avoid
- Baking the verifier into RollupCore: If you want to tweak a curve or VK, you’ll have to redeploy the bridge or move user balances around, which is a no-go for live production chains.
- Mutable VKs: Always remember, never edit a VK directly! Stick to appending, activating, or deactivating instead.
- Unbound public inputs: Be cautious! If your circuits take a caller-supplied digest, just one bad adapter can mess up the bridge or let through an invalid state.
- Assuming calldata availability: After Pectra, data availability should be in blobs. So, design with commitments in mind, not just bytes. Check it out here: (eips.ethereum.org)
The upshot
A Verifier Proxy + Adapter + VK Registry setup keeps your L2 nice and steady on the surface--same bridge address, same state root semantics--while allowing you the flexibility to upgrade your proof systems as needed. Whether you're moving from Groth16 to PLONK, switching from BN254 to BLS12‑381, or transitioning from STARK to SNARK, you can do it all with just a simple timelocked config change. It’s definitely the smoothest and safest approach to evolving your proofs without the hassle of redeploying everything.
If you're on the lookout for a solid reference implementation, gas models for your circuits, or even a migration dry-run plan, 7Block Labs has got your back! They can help you design, audit, and deliver everything you need--from adapters all the way to governance policies.
References
- EIP‑4844 blobs and the point‑evaluation precompile (0x0A); just a heads up, blobs can’t be accessed by the EVM, so we’re sticking with versioned hashes and KZG openings. Check it out here: (eips.ethereum.org)
- Pectra is coming your way on May 7, 2025! This includes EIP‑2537 BLS12‑381 precompiles, EIP‑7691 for better blob throughput, and EIP‑7623 for calldata repricing. Get the scoop: (blog.ethereum.org)
- Curious about the gas costs for BN254 pairing or the formula for Groth16 verification? Plus, we’ve got the details on STARK L1 verification costs too. Dive in here: (voltaire.tevm.sh)
- Don’t miss the OP Stack’s fault‑proof modularity and what they’re calling “multi‑proof nirvana,” along with the Arbitrum BoLD mainnet rollout. More info can be found here: (blog.oplabs.co)
- We’ve got a neat public‑input aggregation design happening with Polygon zkEVM. Check out the details: (docs.polygon.technology)
Like what you're reading? Let's build together.
Get a free 30-minute consultation with our engineering team.
Related Posts
ByAUJay
Building a Donation-Based Crowdfunding Platform That Gives Tax Receipts
**Summary:** Donation-based crowdfunding that includes tax receipts has become quite the complex puzzle across different regions. You've got to navigate IRS Pub 1771/526 rules, UK Gift Aid declarations, Canada’s CRA receipting, and the new eIDAS/OpenID4VCI wallets--all while keeping everything running smoothly.
ByAUJay
Why 'Full-Lifecycle Advisory' Beats Just Coding
**Summary:** Engineering teams that focus solely on “writing Solidity” often find themselves caught off guard by shifts in protocols, the need for composable security, and the procurement hurdles that are now impacting real ROI. Our full-lifecycle advisory service bridges the gap by connecting EIP-7702 smart accounts, modular decentralized applications (DA), and ZK-based compliance solutions.
ByAUJay
Why Your Project Could Really Use a 'Protocol Economist
Summary: A lot of Web3 teams are missing a crucial player: the “protocol economist.” And you can really see the impact--value slips away through MEV routing, token incentives that are all out of whack, and those sneaky changes to wallets after Pectra that end up messing with the unit economics. In this playbook, we’ll explore what a protocol economist can do to tackle these issues head-on.

