ByAUJay
What’s the Cleanest Design Pattern for a Rollup That Can Swap Proof Formats Later Without Redeploying Everything?
A clean approach is to separate "state transition" from "proof verification" on Layer 1. You can keep your rollup's main bridge contract unchanged and run proofs through a lightweight Verifier Proxy that sends them to different Verifier Adapters. This means you can easily switch from Groth16 to PLONK, BN254 to BLS12-381, or STARK to SNARK-wrapped proofs just by changing an adapter or updating a registry pointer--no need to redeploy the bridge or go back through unrelated code paths for another audit.
In this post, we’ll dive into the detailed architecture, the specific on-chain interfaces, a handy migration runbook, and some 2025-era gotchas like EIP-4844 blobs, EIP-2537 BLS precompiles, and the OP Stack’s multi-proof trajectory. This way, your team can switch up proof systems with less risk and minimal downtime. Check it out here: (eips.ethereum.org).
TL;DR (Description)
- Go for a Verifier Proxy along with a Verifier Adapter pattern for your rollup bridge. Keep the STF and data-availability logic stable but make sure the verifier can be swapped out using a registry pointer with a timelock.
- By 2025, you’ll have the option to upgrade to BLS12‑381 verifiers (thanks to EIP‑2537 in Pectra) and increase your blob bandwidth (EIP‑7691). This will really shake up the gas/data trade-offs and make those “proof format upgrades” a lot smoother and more cost-effective. Check it out here: (blog.ethereum.org).
The problem: proof systems evolve faster than bridges
What You Want from Your Rollup Bridge on L1
Provers and proof systems are getting better every quarter--think new recursion schemes, slicker arithmetic backends, zkVMs like SP1 and Hyperspeed, along with the emergence of proof markets. But the rollup bridge on L1? That should be the most straightforward, stable part of your setup. Here's what you really want:
- Replace Groth16 on BN254 with PLONK on BLS12‑381 (after Pectra) to boost security from about 80 bits to around 128 bits and lower the pairing costs;
- Bundle STARK shards into a single SNARK to cut down on L1 verification costs, or consider switching zkVM vendors;
- Introduce a second proving path for added redundancy, similar to “multi-proof” roadmaps.
You can achieve this without having to redeploy your L1 rollup contract, migrate state, or go through the hassle of re-auditing everything. For more details, check out the full discussion on EIP-2537.
The clean design pattern: Verifier Proxy + Adapters
Breaking Down Your L1 Contracts into Four Parts
When you're diving into your L1 contracts, it can be super helpful to break them down into four clear sections. Here's how to do it:
1. Introduction
Start things off with a brief overview. This part should cover what the contract is about, who’s involved, and the general purpose. Think of it as setting the stage.
2. Obligations and Responsibilities
Next, get into the nitty-gritty of what each party is expected to do. Lay out the obligations clearly so everyone knows what’s on their plate. You might want to include:
- Specific duties
- Deadlines
- Quality standards
3. Terms and Conditions
Here’s where you can cover the important legal details. Don’t skip this part! Include stuff like:
- Duration of the contract
- Termination clauses
- Confidentiality agreements
4. Signatures and Acknowledgments
Finally, wrap it up with the signatures. This is where everyone officially agrees to the terms laid out. Make sure to provide space for:
- Names
- Dates
- Contact information
By structuring your L1 contracts this way, you’ll help ensure that everything is clear and easy to understand for everyone involved!
- RollupCore (immutable):
- It keeps track of the official state commitments, like the most recent L2 state root.
- Welcomes new batches and uses VerifierProxy.verify to check the claimed state transition.
- Triggers events, manages finality windows, and upholds DA references (with blob versioned hashes).
- Never directly decodes proof bytes or verification keys (VKs).
2) VerifierProxy (upgradeable via ERC‑1967 or governed registry):
- It has one main function:
verify(bytes proof, ProofContext ctx) returns bool. - This function sends the request to a registered
VerifierAdapterbased on theproofFormatId(and version). - It keeps an allowlist of
(formatId, vkHash)along with aeffectiveFromblock/epoch. Check it out here: (eips.ethereum.org)
3) VerifierAdapters (one for each proof family):
- Check out these example adapters: Groth16Bn254Adapter, PlonkBls381Adapter, StarkWrappedInSnarkAdapter.
- Each adapter is designed to handle its specific proof encoding, pull or verify VKs, and execute the appropriate precompiles (BN254 EIP‑196/197/1108; BLS12‑381 EIP‑2537). You can find more details at (eips.ethereum.org).
4) VerificationKeyRegistry (append-only):
- This registry keeps a map of (formatId, circuitId, version) to vkHash and vkMetadata (which includes curve, pairings, and publicInputs).
- It has its own governance and timelock system; RollupCore will only accept proofs if the corresponding vkHash is registered and currently active.
This setup is designed to be compact and easy to audit. The RollupCore stays the same all the time; it's just the VerifierProxy’s pointers and registry entries that get updated. It reflects the OP Stack’s “modular fault-proof framework,” meaning we can add different proof systems without having to redo the whole bridge. (blog.oplabs.co)
Why this works on Ethereum in 2025
- EIP‑4844 Blobs: This one’s all about making data access way cheaper! With these blob commitments, you can pin the blob versioned hashes or data roots in the ProofContext without needing to read the full blob content on L1 for the proof verifier. This really helps keep your STF interface stable when you're switching up proof formats. Check out more details here.
- EIP‑2537 BLS12‑381 Precompiles (Pectra): Say goodbye to the old BN254 lock-in for verifiers! With native pairings and MSM on BLS12‑381, you can now go for PLONK/FFLONK/BLS-friendly verifiers that are just as competitive--or even better--on gas costs compared to BN254 pairings. Dive into the specifics on the blog.
- Pairing Gas Math: After EIP‑1108 hit, BN254 pairings are running at about 34,000·k + 45,000 gas (that's k pairings, by the way). With the BLS12‑381 pairings we got in Pectra, you’ll find that they cost roughly the same or even less per pair, which really shakes up how you choose your verifiers. You can expect typical Groth16 verifications to land around ~200-300k gas; but with PLONK/FFLONK, you usually just need 2-3 pairings, which is a nice little gas saver! More info can be found here.
Onchain interface: keep it tiny and proof‑agnostic
// SPDX-License-Identifier: MIT
interface IVerifierProxy {
// ProofContext is ABI-encoded:
// - bytes32 preStateRoot
// - bytes32 postStateRoot
// - bytes32 daCommitment (e.g., blob versioned hash Merkle root)
// - uint64 batchNumber
// - uint32 formatId // e.g., 1=Groth16_bn254, 2=PLONK_bls381, 3=STARK_wrapped
// - uint32 vkVersion
// - bytes extra // e.g., public inputs hash, circuit id, etc.
function verify(bytes calldata proof, bytes calldata proofContext) external view returns (bool);
}
Your RollupCore just uses this method, sticking to the stored policy (which checks allowed formatId/vkVersion and does DA checks). You don’t ever redeploy RollupCore, and you can have “dual accept” windows (for old and new formats) while you’re migrating.
ProofContext should also have a neat encoding of the public inputs that your STF needs, like the Keccak of the pre/post roots, the L2 timestamp, and the inbox/outbox roots. The adapter takes care of recomputing this digest on its own to help avoid any misbinding attacks.
Minimal L1 flow with blobs (EIP‑4844)
- The sequencer takes care of posting batch data as blobs, while RollupCore stores those blob versioned hashes with the BLOBHASH opcode and/or a rollup-level DA commitment.
- Next up, the prover generates a proof for the batch, and those proof bytes get sent over to RollupCore through VerifierProxy.
- Then, the VerifierAdapter steps in to verify a few things:
- It checks that public input commitments are in line with the pre/post state roots;
- It confirms that the DA reference matches the blob’s versioned hash;
- And, it makes sure the proof holds up against the registered VK.
- If everything checks out, RollupCore goes ahead and updates the canonical state root.
Blob data gets pruned after about 4096 epochs (which is roughly 18 days). That’s not a big deal since finalization windows usually don’t last that long; the on-chain commitment stays intact. If you need to open KZG evaluations in dispute games or light-client processes, then go ahead and use the point-evaluation precompile (0x0a). You can find more info here.
Practical migration: Groth16 (BN254) → PLONK (BLS12‑381) after Pectra
What’s different:
- Use the VerifierAdapter only. Deploy the PlonkBls381Adapter that taps into those EIP‑2537 precompiles (0x0b-0x11) for MSM and pairings. Keep the RollupCore and DA stuff just the way it is.
- For the VK Registry entries, make sure to add circuitId=“rollup-stf-main,” version=2, and vkHash=… (with curve=BLS12‑381; pairings k=2 or 3 based on your setup).
- When it comes to gas and data: BN254 Groth16 typically uses around 200-300k gas, depending on the public inputs. On the flip side, BLS12‑381 pairings can be a bit cheaper per pair. Just a heads up, proof objects and VKs can vary in size, so try to aggregate off-chain whenever you can to keep that calldata down. Remember, benchmarks can shift based on how you implement things, so it’s a good idea to sanity-check your circuits. (hackmd.io)
A Minimal Dispatcher (Ownable + Timelock)
When it comes to setting up a minimal dispatcher that includes an ownable and timelock feature, we're combining a couple of key components. Here’s how you can go about it:
Features
- Ownable: This ensures that there's a designated owner who has control over the dispatcher. The owner can perform specific actions, making it easy to manage the system.
- Timelock: This adds an extra layer of security by requiring a delay before certain operations can be executed. It gives users time to react to any changes or decisions, which is super helpful.
Code Structure
Here's a simple code snippet to illustrate how you might implement this dispatcher:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MinimalDispatcher {
address public owner;
uint256 public timelock;
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
constructor() {
owner = msg.sender;
}
function setTimelock(uint256 _timelock) external onlyOwner {
timelock = _timelock;
}
// Add more functions and logic here as needed
}
Summary
By integrating both the ownable and timelock features, you've created a streamlined and secure dispatcher. This setup not only makes managing the dispatcher straightforward but also adds an important layer of protection for your operations. Happy coding!
contract VerifierProxy is IVerifierProxy {
struct Key { uint32 formatId; uint32 vkVersion; }
mapping(bytes32 => bool) public allowedVkHash; // hash(formatId|circuitId|vkVersion|vkDigest)
mapping(uint32 => address) public adapters; // formatId -> VerifierAdapter
function verify(bytes calldata proof, bytes calldata ctx) external view returns (bool) {
( , , , , uint32 formatId, uint32 vkVersion, ) = abi.decode(
ctx,(bytes32,bytes32,bytes32,uint64,uint32,uint32,bytes)
);
address adapter = adapters[formatId];
require(adapter != address(0), "format");
return IVerifierAdapter(adapter).verifyWithContext(proof, ctx, allowedVkHash);
}
}
Governance updates adapters[2] = PlonkBls381Adapter; we’ve set it up so activation is pre-scheduled at batch N, with a 30-day delay for user safety--think of it like Stage-1's “training wheels.” Check out more details in this blog post.
Practical migration: STARK shards → single SNARK (proof-of-proofs)
If you're using a STARK-first prover stack, you can verify on L1 with a SNARK wrapper to make sure your gas costs stay predictable:
- Kick off by creating STARK shard proofs off-chain, then recursively bundle them up, and finally, wrap it all in a SNARK (whether you’re using PLONK or Groth16) that your adapter checks on L1.
- That’s exactly what Succinct’s SP1 brings to the table: it allows you to whip up on-chain verifiable proofs by first rolling through STARK recursion and then capping it off with Groth16 or PLONK. Plus, you can flip between Groth16 (BN254) and PLONK (BLS12‑381) right at the wrapper level while keeping your STF intact. Check it out in their documentation!
Operationally, here’s what you need to do: first, deploy the StarkWrappedInSnarkAdapter. Next, register its verification key (VK). You'll want to accept both formats--the old Groth16 wrapper and the new PLONK wrapper--for a bit of an overlap period. Finally, you can go ahead and phase out the old one. For more info, check out the docs.succinct.xyz.
Governance and upgradability details that won’t bite you later
- Go with ERC‑1967 storage slots and use either the UUPS or Beacon pattern just for the VerifierProxy. Keep RollupCore as is--immutable. Make sure to document the storage layout and run some tests to catch any storage collisions. (eips.ethereum.org)
- For the Timelock + Security Council, let’s take a cue from OP Stack’s “Stage 1” approach. We’ll have permissionless proofs along with a council that can step in during emergencies--just make sure it’s high quorum and time-boxed so they can pause or roll back verifiers if needed. Don’t forget to lay out the thresholds clearly (like a council quorum of at least 75% and a delay of at least 30 days). (blog.oplabs.co)
- If you’re looking at a bunch of functions for a large system, you might want to consider Diamonds (EIP‑2535). But remember, keep the verifier surface lean to limit the audit workload. Diamonds are great for monolithic setups, but when it comes to verifiers, a simple proxy with adapters tends to be safer and easier. (eips.ethereum.org)
ProofContext: fields you should standardize now
Include these in an ABI-encoded tuple to make sure we're ready for different proof formats in the future:
- preStateRoot, postStateRoot (bytes32)
- daCommitment (bytes32): Think of it as the Merkle root of those versioned hashes for the batch.
- batchNumber (uint64) and the L2 timestamp or epoch, if it matters for what you’re doing.
- formatId (uint32), vkVersion (uint32)
- circuitId and vkHash (or their hashes) tucked away in the extra bytes.
- publicInputsDigest (bytes32): This is the Keccak hash of the exact public inputs your STF is expecting.
Adapters take the decoded public inputs and recompute the digest to check for accuracy. This helps prevent issues like “VK-mixup” or “input swapping” attacks that could occur between different formats.
Data availability: blobs, commitments, and challenges
- After Dencun, rollups will start using blobs for data availability. Now, while the EVM can’t directly read blob data, it can handle versioned hashes (BLOBHASH) and check point evaluations using the KZG precompile if your dispute game needs some detailed checks. Keep in mind, blobs hang around for about 18 days, so make sure everything's finalized within that timeframe. (ethereum.org)
- Check out Pectra’s EIP‑7691, which is all about boosting blob capacity (aiming for an increase from 3 to 6, with a max of 9). This change should really help smooth out throughput and make those “proof plus data” schedules a bit less erratic. Don't forget to update your batcher to take advantage of the pricing difference between calldata (which just got pricier per EIP‑7623) and blobs, but definitely lean towards using blobs. (blog.ethereum.org)
Gas and cost notes you can actually budget
- For BN254 pairings, you're looking at about 34,000·k + 45,000 gas (thanks to EIP‑1108). Typically, Groth16 uses around 2-4 pairings, but real verifiers usually end up around 200k-300k gas, depending on how much public input you have and the Multi-Scalar Multiplication (MSM). Check out more details here.
- On the other hand, BLS12‑381 pairings and MSM are now native, thanks to EIP‑2537 in Pectra. Depending on your setup (like using FFLONK or PLONK), you’ll generally see 2-3 pairings, and the baselines stack up pretty well against BN254 while offering 128-bit security. It’s smart to benchmark your circuit and keep an eye on calldata size--BLS points are a bit bulkier, so it’s better to handle those off-chain. You can read more about it here.
Step‑by‑step migration runbook (what we use with clients)
- Get Ready
- Take a close look at the new VerifierAdapter and create test vectors that link STF inputs to the expected postStateRoot.
- Add the VK to the registry with vkVersion+1; don’t forget to publish the vkHash and circuit metadata.
2) Shadow mode
- Turn on both
formatId=old,newin VerifierProxy.allowlist. - The Sequencer generates proofs for a portion of batches, and then we’ll compare the adapter verdicts off-chain.
3) Activation
- Give everyone a heads-up about the governance activation N days in advance and set effectiveFrom batchNumber.
- Change the default proof format in the prover pipeline.
- Decommission Old Format
- Once T days have passed and we’ve wrapped up M finalized batches, let’s go ahead and remove the old formatId from the allowlist.
- We’ll keep the adapter and VK in the registry, but they’ll be marked as deprecated for the sake of reproducibility.
5) Post-mortem & monitoring
- Keep an eye on verifier gas, revert rates, proof size, and L1 blob usage.
- Make sure to archive proofs and contexts; you should hold onto at least 18 days of DA alignment (blobs). (ethereum.org)
Advanced: multi‑proof and proof‑of‑proofs
- A “multi-proof” VerifierAdapter is pretty cool because it can handle any of k subformats and will succeed as long as at least one of them checks out. This feature allows you to bring in a second proving backend for a bit of redundancy. The OP Stack has been thoughtfully crafted with a fault-proof system that’s modular and is evolving toward this ideal multi-proof state. (blog.oplabs.co)
- Then there's the proof-of-proof: you can send out a small SNARK that confirms “the old verifier would have returned true on input X.” This cleverly breaks the link to the original curve or precompiles. zkVM stacks, like SP1, come with ready-made flows to create on-chain verifiable SNARK wrappers. (docs.succinct.xyz)
Security checklists (don’t skip)
- Public input binding: Make sure your adapter recalculates the digest from structured inputs--never just rely on a hash provided by the caller.
- VK immutability: Remember, entries should only be added; don’t change vkHash directly.
- Timelocks and emergency pause: You need to give at least 30 days' notice before making format changes; any emergency pause should only apply to VerifierProxy. (blog.oplabs.co)
- Storage safety: If you’re upgrading VerifierProxy using ERC‑1967/UUPS, be sure to test the storage layout for different versions and keep RollupCore untouched. (eips.ethereum.org)
- DA invariants: Always include at least one blob versioned hash per batch, and make sure the DA commitment format ID aligns with the batcher mode (whether it's calldata or blobs). (eips.ethereum.org)
Example: moving to PLONK on BLS12‑381 after Pectra
- Why now: Pectra has rolled out EIP‑2537, making BLS12‑381 operations (like pairing and MSM) a native feature. Plus, EIP‑7691 bumped up the blob capacity, and EIP‑7623 upped the price of calldata. The result? It’s way better to switch to blob-heavy batches and BLS-based verifiers. This move helps lower verification costs and boosts security measures. Check out more details here.
Concrete Steps:
- Identify Your Goals
- Start by figuring out what you really want to achieve. Write down your goals--big or small. This will give you a clear direction.
- Break It Down
- Take those big goals and break them into bite-sized tasks. This makes everything feel way more manageable and less overwhelming.
- Set a Timeline
- Give yourself deadlines for each task. This will help keep you on track and create a sense of urgency.
- Gather Resources
- Make sure you have everything you need to get started. This could be tools, information, or even support from friends or family.
- Stay Flexible
- Things don’t always go as planned, and that’s okay! Be ready to adjust your plan if something isn’t working out.
- Track Your Progress
- Keep an eye on how you’re doing. Celebrate those little wins along the way--they really add up!
- Seek Feedback
- Don’t hesitate to ask for input from others. Getting different perspectives can help you improve and stay motivated.
- Stay Committed
- Remember, persistence is key. Even if it gets tough, keep pushing forward towards your goals!
- Reflect and Revise
- Take some time to look back on what you’ve done. Reflect on what worked and what didn’t, and adjust your approach as needed.
- Keep Learning
- Stay curious! Look for new information or skills that can help you along your journey.
- Let's get the PlonkBls381Adapter up and running using the 0x0b-0x11 precompiles, and don’t forget to register VK v2.
- We’re going to test out dual proofs for a couple of weeks--Groth16 and PLONK--and see how they stack up against each other.
- Time to switch things up! We're flipping the formatId to PLONK and phasing out Groth16. But just in case, let’s keep an emergency switch in governance that requires a solid quorum.
- We’re anticipating some nice gas savings per proof and a smoother path for aggregation. Remember to keep an eye on real-world totals since things like calldata composition and public-input counts really matter. Check out more about it here: (blog.ethereum.org)
A note on ecosystem direction
- Ethereum’s Dencun on March 13, 2024, really brought blobs into the spotlight. Then, Pectra on May 7, 2025, kicked it up a notch by introducing BLS12‑381 precompiles and doubling blob targets, which is a win for modular verifiers and makes proof swaps way easier. The OP Stack is rolling out modular, multi-proof-ready fault proofs, and Polygon’s AggLayer along with other networks are diving into proof aggregation strategies. All of these trends are giving a nice boost to the proxy+adapter pattern we talked about earlier. (ethereum.org)
Bottom line
If you split the STF and DA plumbing from the proof verifier using a Verifier Proxy, your rollup can:
- Easily switch between curves (BN254→BLS12‑381), schemes (Groth16→PLONK/FFLONK), or wrappers (STARK→SNARK) without having to redeploy the bridge.
- You can add a backup proving path or shift vendors while keeping an overlap window open.
- Take advantage of the cool L1 features coming in 2025 (like blobs and BLS precompiles) to enjoy lower fees and better security--no need to alter the user-facing chain.
This is the smoothest way to upgrade your proofs while making sure your L2 stays solid and reliable.
References and specs
- EIP‑4844 has introduced blobs along with a point-evaluation precompile, plus there's some timing around Dencun, with an availability window of about 18 days. Check it out here: (eips.ethereum.org)
- We’ve got BN254 precompiles making waves along with some gas cuts thanks to EIP‑196, 197, and 1108. You can read more about it here: (eips.ethereum.org)
- The Pectra meta-EIP list is out, featuring EIP‑2537, 7691, and 7623, with a big mainnet activation planned for May 7, 2025. Dive into the details here: (blog.ethereum.org)
- OP Stack has some exciting developments with fault-proof modularity and a Stage‑2 trajectory focusing on multi‑proofs. More info is available here: (blog.oplabs.co)
- SP1 is really cool--it lets you generate on-chain verifiable proofs using STARK recursion followed by SNARK-wrapping. If you want to get started, check out the guide here: (docs.succinct.xyz)
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.

