ByAUJay
What’s the Cleanest Design Pattern for a Rollup That Can Swap Proof Formats Later Without Redeploying Everything?
A concise design is to decouple “state transition” from “proof verification” on L1: keep your rollup’s core bridge contract immutable, and route proofs through a minimal Verifier Proxy that dispatches to pluggable Verifier Adapters. This lets you switch from Groth16→PLONK, BN254→BLS12-381, or STARK→SNARK-wrapped proofs by swapping an adapter or flipping a registry pointer—no redeploy of the bridge or re-auditing of unrelated code paths.
This post lays out the concrete architecture, exact onchain interfaces, migration runbook, and 2025-era gotchas (EIP-4844 blobs, EIP‑2537 BLS precompiles, and OP Stack’s multi-proof trajectory) so your team can change proof systems with low risk and minimal downtime. (eips.ethereum.org)
TL;DR (Description)
- Use a Verifier Proxy + Verifier Adapter pattern behind your rollup bridge. Keep the STF and data-availability logic fixed; make the verifier swappable behind a registry pointer with timelock.
- In 2025 you can also move to BLS12‑381 verifiers (EIP‑2537 in Pectra) and bigger blob bandwidth (EIP‑7691), which materially changes gas/data tradeoffs and makes “proof format upgrades” cheaper and cleaner. (blog.ethereum.org)
The problem: proof systems evolve faster than bridges
Provers and proof systems improve every quarter (new recursion schemes, better arithmetic backends, zkVMs like SP1/Hyperspeed, proof markets), but the rollup bridge on L1 should be the most boring, unchanging part of your system. You want to:
- Swap Groth16 on BN254 for PLONK on BLS12‑381 (post‑Pectra) to raise security from ~80‑bit to ~128‑bit and reduce pairing cost;
- Wrap STARK shards into a single SNARK for cheaper L1 verification, or switch zkVM vendors;
- Add a second proving path for redundancy, à la “multi‑proof” roadmaps.
Do this without redeploying your L1 rollup contract, migrating state, or re-auditing everything. (eips.ethereum.org)
The clean design pattern: Verifier Proxy + Adapters
Structure your L1 contracts into four parts:
- RollupCore (immutable):
- Owns canonical state commitments (e.g., latest L2 state root).
- Accepts new batches and calls VerifierProxy.verify to validate the claimed state transition.
- Emits events; manages finality windows; enforces DA references (blob versioned hashes).
- Never decodes proof bytes or VKs directly.
- VerifierProxy (upgradeable via ERC‑1967 or governed registry):
- Single entrypoint: verify(bytes proof, ProofContext ctx) returns bool.
- Dispatches to a registered VerifierAdapter keyed by proofFormatId (and version).
- Maintains allowlist of (formatId, vkHash) and an effectiveFrom block/epoch. (eips.ethereum.org)
- VerifierAdapters (one per proof family):
- Example adapters: Groth16Bn254Adapter, PlonkBls381Adapter, StarkWrappedInSnarkAdapter.
- Each adapter knows how to parse its proof encoding, fetch or check VKs, and run the right precompiles (BN254 EIP‑196/197/1108; BLS12‑381 EIP‑2537). (eips.ethereum.org)
- VerificationKeyRegistry (append-only):
- Maps (formatId, circuitId, version) → vkHash, vkMetadata (curve, pairings, publicInputs).
- Separate governance/timelock; RollupCore only accepts proofs whose vkHash is registered and active.
This is intentionally small and auditable. The RollupCore never changes; only the VerifierProxy’s pointer/registry entries do. It mirrors the OP Stack’s “modular fault-proof framework” so multiple proof systems can be added without retooling the bridge. (blog.oplabs.co)
Why this works on Ethereum in 2025
- EIP‑4844 blobs: Cheaper DA with blob commitments accessible in EVM; you pin blob versioned hashes and/or data roots in ProofContext, but the proof verifier never needs to read the blob’s full contents on L1. This stabilizes your STF interface as you change proof formats. (eips.ethereum.org)
- EIP‑2537 BLS12‑381 precompiles (Pectra): Native pairings/MSM on BLS12‑381 remove the historical BN254 lock-in for verifiers. You can adopt PLONK/FFlonk/BLS-friendly verifiers with competitive or better gas than BN254 pairings. (blog.ethereum.org)
- Pairing gas math: Post‑EIP‑1108, BN254 pairings cost 34,000·k + 45,000 gas (k pairings). BLS12‑381 pairings introduced in Pectra are similar or lower per pair, changing the calculus for verifier choices. Expect typical Groth16 verifications around ~200–300k gas; PLONK/FFLONK often reduce pairings to 2–3, saving gas. (eips.ethereum.org)
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 calls only this method, constrained by stored policy (allowed formatId/vkVersion; DA checks). You never redeploy RollupCore, and you can run “dual accept” windows (old + new formats) during migrations.
ProofContext should also include a compact encoding of the public inputs your STF requires (e.g., Keccak of pre/post roots, L2 timestamp, inbox/outbox roots). The adapter recomputes this digest internally to prevent misbinding attacks.
Minimal L1 flow with blobs (EIP‑4844)
- Sequencer posts batch data as blobs; RollupCore stores blob versioned hashes (BLOBHASH opcode) and/or a rollup-level DA commitment.
- Prover generates a proof over the batch; proof bytes are submitted to RollupCore→VerifierProxy.
- VerifierAdapter checks:
- public input commitments match pre/post state roots;
- DA reference matches the blob’s versioned hash;
- the proof verifies over the registered VK.
- If OK, RollupCore updates the canonical state root.
Blob data is pruned after ~4096 epochs (~18 days), which is fine because finalization windows are typically shorter; the onchain commitment remains. Use the point-evaluation precompile (0x0a) only if you need to open KZG evaluations in dispute games or light-client flows. (ethereum.org)
Practical migration: Groth16 (BN254) → PLONK (BLS12‑381) after Pectra
What changes:
- VerifierAdapter only. Deploy PlonkBls381Adapter that calls EIP‑2537 precompiles (0x0b–0x11) for MSM and pairings. Keep RollupCore and DA logic unchanged.
- VK Registry entries: add circuitId=“rollup-stf-main”, version=2, vkHash=… (curve=BLS12‑381; pairings=k=2 or 3 depending on construction).
- Gas/data: BN254 Groth16 commonly lands around 200–300k gas depending on pubinputs; BLS12‑381 pairings can be slightly cheaper per pair; proof objects and VKs differ in size, so aggregate off-chain when possible to minimize calldata. Benchmarks vary by implementation; sanity-check on your circuits. (hackmd.io)
A minimal dispatcher (ownable + timelock):
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; pre-schedules activation at batch N with a 30‑day delay for user safety (Stage‑1 style “training wheels”). (blog.oplabs.co)
Practical migration: STARK shards → single SNARK (proof-of-proofs)
If your prover stack is STARK-first, verify on L1 via a SNARK wrapper to keep gas deterministic:
- Produce STARK shard proofs off-chain; recursively aggregate; then wrap in a SNARK (PLONK/Groth16) that your adapter verifies on L1.
- Succinct’s SP1 offers exactly this: generate onchain-verifiable proofs by first doing STARK recursion and then wrapping in Groth16/PLONK. You can switch between Groth16 (BN254) and PLONK (BLS12‑381) at the wrapper level while leaving your STF unchanged. (docs.succinct.xyz)
Operationally: deploy StarkWrappedInSnarkAdapter; register its VK; accept both formats (old Groth16 wrapper and new PLONK wrapper) for an overlap window; then deprecate the old. (docs.succinct.xyz)
Governance and upgradability details that won’t bite you later
- Use ERC‑1967 storage slots and either UUPS or a Beacon pattern for the VerifierProxy only. Leave RollupCore immutable. Document the storage layout and add tests for storage collisions. (eips.ethereum.org)
- Timelock + Security Council: mirror OP Stack “Stage 1” practice—permissionless proofs with a time‑boxed, high‑quorum council able to pause/rollback verifiers in emergencies. Publish clear thresholds (e.g., ≥75% council quorum, ≥30‑day delay). (blog.oplabs.co)
- If you need many functions across a large system, Diamonds (EIP‑2535) are an option—but keep the verifier surface small to reduce audit scope. Diamonds shine for monoliths; for verifiers, a slim proxy + adapters is simpler and safer. (eips.ethereum.org)
ProofContext: fields you should standardize now
Include these in an ABI-encoded tuple to future-proof across proof formats:
- preStateRoot, postStateRoot (bytes32)
- daCommitment (bytes32): e.g., Merkle root of blob versioned hashes for the batch
- batchNumber (uint64) and L2 timestamp/epoch if relevant
- formatId (uint32), vkVersion (uint32)
- circuitId and vkHash (or their hashes) inside extra bytes
- publicInputsDigest (bytes32): Keccak of the exact public inputs your STF expects
Adapters recompute the digest from decoded public inputs and compare. This avoids “VK-mixup” or “input swapping” attacks across formats.
Data availability: blobs, commitments, and challenges
- Post‑Dencun, rollups should use blobs for DA. The EVM can’t read blob data, but it can read versioned hashes (BLOBHASH) and verify point evaluations via the KZG precompile if your dispute game needs element-level checks. Blobs are retained ~18 days; set finalization within that window. (ethereum.org)
- Pectra’s EIP‑7691 increases blob capacity (target 3→6, max 9), improving throughput and making “proof plus data” schedules less spiky. Update your batcher to arbitrage between calldata (now pricier per EIP‑7623) and blobs, but prefer blobs. (blog.ethereum.org)
Gas and cost notes you can actually budget
- BN254 pairings: 34,000·k + 45,000 gas (EIP‑1108). Typical Groth16 uses ~2–4 pairings; real verifiers land around ~200k–300k gas depending on public input size and MSM. (eips.ethereum.org)
- BLS12‑381 pairings/MSM are now native (EIP‑2537 in Pectra). Depending on construction (FFLONK/PLONK), 2–3 pairings are common; baselines are competitive with BN254 and provide 128‑bit security. Benchmark your circuit; watch calldata size—BLS points are larger, so aggregate off‑chain. (blog.ethereum.org)
Step‑by‑step migration runbook (what we use with clients)
- Prepare
- Audit the new VerifierAdapter; generate test vectors that tie STF inputs → expected postStateRoot.
- Register VK in the registry with vkVersion+1; publish vkHash and circuit metadata.
- Shadow mode
- Enable both (formatId=old,new) in VerifierProxy.allowlist.
- Sequencer produces both proofs for a subset of batches; compare adapter verdicts off-chain.
- Activation
- Announce governance activation N days ahead; set effectiveFrom batchNumber.
- Flip the default proof format in the prover pipeline.
- Decommission old format
- After T days and M finalized batches, remove old formatId from allowlist.
- Keep adapter and VK in the registry but marked deprecated for reproducibility.
- Post‑mortem & monitoring
- Monitor verifier gas, revert rates, proof size, and L1 blob usage.
- Archive proofs and contexts; retain at least 18 days of DA alignment (blobs). (ethereum.org)
Advanced: multi‑proof and proof‑of‑proofs
- A “multi‑proof” VerifierAdapter can accept any of k subformats and succeed if one verifies. This lets you add a second proving backend for redundancy. The OP Stack explicitly designed its fault‑proof system to be modular and evolve towards a multi‑proof “nirvana.” (blog.oplabs.co)
- Proof‑of‑proof: you can ship a small SNARK that proves “the old verifier would have returned true on input X.” This neatly severs coupling to the original curve or precompiles. zkVM stacks like SP1 provide off‑the‑shelf flows to produce onchain‑verifiable SNARK wrappers. (docs.succinct.xyz)
Security checklists (don’t skip)
- Public input binding: your adapter must recompute the digest from structured inputs; never trust a caller-supplied hash alone.
- VK immutability: entries are append-only; never mutate vkHash in place.
- Timelocks and emergency pause: ≥30‑day notice for format switches; emergency pause scoped to VerifierProxy only. (blog.oplabs.co)
- Storage safety: if you upgrade VerifierProxy via ERC‑1967/UUPS, test storage layout across versions; keep RollupCore immutable. (eips.ethereum.org)
- DA invariants: require at least one blob versioned hash per batch; verify the DA commitment format id matches the batcher mode (calldata vs blobs). (eips.ethereum.org)
Example: moving to PLONK on BLS12‑381 after Pectra
- Why now: Pectra shipped EIP‑2537 so BLS12‑381 operations (pairing, MSM) are native; EIP‑7691 increased blob capacity; EIP‑7623 raised calldata price. Net effect: better to move to blob-heavy batches and BLS‑based verifiers to cut verification cost and strengthen security margins. (blog.ethereum.org)
Concrete steps:
- Deploy PlonkBls381Adapter using 0x0b–0x11 precompiles; register VK v2.
- Run 2 weeks of dual proofs (Groth16 + PLONK) and compare.
- Flip formatId to PLONK; deprecate Groth16; keep an emergency switch in governance with a strong quorum.
- Expect modest gas reductions per proof and easier aggregation paths; measure real-world totals because calldata composition and public-input counts matter. (blog.ethereum.org)
A note on ecosystem direction
- Ethereum’s Dencun (Mar 13, 2024) made blobs real; Pectra (May 7, 2025) added BLS12‑381 precompiles and doubled blob targets, both of which favor modular verifiers and easier proof swaps. The OP Stack is shipping modular, multi‑proof‑ready fault proofs; Polygon’s AggLayer and other networks lean on proof aggregation strategies. All these trends reward the proxy+adapter pattern described above. (ethereum.org)
Bottom line
If you separate the STF and DA plumbing from the proof verifier behind a Verifier Proxy, your rollup can:
- Switch curves (BN254→BLS12‑381), schemes (Groth16→PLONK/FFLONK), or wrappers (STARK→SNARK) without redeploying the bridge;
- Add a backup proving path or migrate vendors with an overlap window;
- Exploit 2025-era L1 features (blobs and BLS precompiles) for lower fees and higher security—without changing the user-visible chain.
This is the cleanest path to evolve your proofs while keeping the core of your L2 boring and dependable.
References and specs
- EIP‑4844 blobs and point-evaluation precompile; Dencun timing and ~18-day availability window. (eips.ethereum.org)
- BN254 precompiles and gas cuts (EIP‑196/197/1108). (eips.ethereum.org)
- Pectra meta-EIP list (incl. EIP‑2537, 7691, 7623) and mainnet activation on May 7, 2025. (blog.ethereum.org)
- OP Stack fault-proof modularity and Stage‑2 trajectory (multi‑proof). (blog.oplabs.co)
- SP1: generate onchain-verifiable proofs by STARK recursion then SNARK-wrapping. (docs.succinct.xyz)
Like what you're reading? Let's build together.
Get a free 30‑minute consultation with our engineering team.

