JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

ZK-rollups: proving the new state is correct

Optimistic rollups assume a batch is honest and wait a week to catch fraud. ZK-rollups do the opposite: they attach a succinct validity proof that the batch ran correctly, and Ethereum checks it before the new state ever counts — no waiting, just math.

From 'catch the liar' to 'prove you told the truth'

In the previous guide you met the optimistic rollup: a layer 2 where the sequencer posts a new state root and simply asserts it is right. For about seven days, anyone may challenge it by submitting a fraud proof during the challenge period. Picture a referee who lets every goal stand unless someone files a formal protest within the week. It works — but your money is innocent until proven fraudulent, and you wait a week to withdraw to L1.

A ZK-rollup flips the burden of proof. Instead of "assume valid, hope someone catches fraud," it attaches a mathematical receipt to every batch — a validity proof — that the batch was executed correctly. The L1 contract checks the receipt before accepting the new state. No goal counts until the proof verifies; there is no week-long protest window because there is nothing to protest. The sequencer cannot post a bad state and dare you to catch it: a false statement simply has no valid proof.

The deal: a tiny proof of a huge computation

The whole trick rests on an asymmetry. Generating the proof is expensive: a prover re-runs the entire batch inside a proving circuit and does heavy cryptography. But verifying it is cheap and fast — and, crucially, the verification cost is nearly constant no matter how many transactions the batch held. A proof covering 50 transfers and a proof covering 50,000 verify in roughly the same small amount of gas. That constant-cost check is the entire scaling engine: thousands of L2 transactions amortize down to one cheap proof verification on L1.

Real numbers make it concrete. A Groth16 SNARK verifies on Ethereum in roughly 200,000–300,000 gas and is only a few hundred bytes long — whether the batch moved 50 transactions or 50,000. What the prover is actually attesting is one statement: *"starting from the old state root S_old, applying this batch under the protocol rules yields the new state root S_new."* The proof is sound — if the sequencer tried to slip in an invalid transaction (spending coins it doesn't have, an off-by-one balance), no valid proof exists for that false claim, so the L1 verifier would reject it. You stop trusting the sequencer and start trusting the math.

The L1 verifier: where the whole thing bottoms out

On L1, a ZK-rollup ultimately reduces to one contract that stores the current state root and holds a proof verifier. Every batch stands or falls on a single `require` check. Here is the heart of it, stripped to the essentials:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IVerifier {
    // Returns true iff `proof` attests that applying the batch
    // takes the rollup from `oldRoot` to `newRoot`, and that the
    // batch the proof covers hashes to `batchDataHash`.
    function verifyProof(
        bytes calldata proof,
        bytes32 oldRoot,
        bytes32 newRoot,
        bytes32 batchDataHash
    ) external view returns (bool);
}

contract ZkRollup {
    bytes32 public stateRoot;          // the rollup's current state commitment
    IVerifier public immutable verifier;

    event BatchSettled(bytes32 oldRoot, bytes32 newRoot);

    constructor(IVerifier _verifier, bytes32 genesisRoot) {
        verifier  = _verifier;
        stateRoot = genesisRoot;
    }

    /// Settle one batch. Reverts unless the validity proof checks out.
    function submitBatch(
        bytes calldata batchData,      // the L2 txs, posted for data availability
        bytes32 newRoot,
        bytes calldata proof
    ) external {
        bytes32 oldRoot  = stateRoot;
        bytes32 dataHash = keccak256(batchData);

        // The ENTIRE security of the rollup is this one line.
        require(
            verifier.verifyProof(proof, oldRoot, newRoot, dataHash),
            "invalid validity proof"
        );

        stateRoot = newRoot;           // final immediately - no challenge window
        emit BatchSettled(oldRoot, newRoot);
    }
}
A minimal ZK-rollup settlement contract. The single require on the proof is the rollup's whole trust model.

Two things deserve attention. First, `submitBatch` updates `stateRoot` in the same transaction that verifies the proof — there is no challenge window, so this L2 state is final the moment Ethereum includes the batch (subject only to L1's own finality). Second, `batchData` is still posted on-chain. The proof guarantees the transition is correct, but it does not by itself make the data available: anyone who wants to reconstruct the L2 — to run a node, prove ownership, or force an exit — still needs the raw transactions. That is the data availability problem, and it is the subject of the next guide. Post the proof but withhold the data and you've built a different beast called a validium, which trades the on-chain DA guarantee for cheaper costs.

A batch's life, end to end

  1. Users sign L2 transactions and send them to the sequencer, which orders them and hands back a soft pre-confirmation almost instantly.
  2. The sequencer executes the batch off-chain against the current state, producing the new state root S_new. This step is cheap — it is just ordinary execution, no cryptography yet.
  3. A prover — often a separate, GPU-heavy machine — re-runs the batch inside the proving circuit and generates the validity proof. This is the expensive, slow step: seconds to minutes, and a lot of hardware.
  4. The sequencer posts three things to L1: the batch's transaction data (for data availability), the new state root, and the proof.
  5. The L1 verifier contract checks the proof. If it passes, S_new is accepted and final immediately; if it fails, the entire submission reverts and nothing on L1 changes.
  6. Because state is final the instant it is proven, a withdrawal to L1 can be honored as soon as the batch containing it is proven and verified — no seven-day wait like an optimistic rollup.

Two flavors of proof: SNARKs, STARKs, and what you trust

Two proving families dominate. zk-SNARKs (Groth16, PLONK) give the smallest proofs and cheapest verification, but many need a trusted setup: a one-time ceremony that produces public parameters, whose secret randomness — the "toxic waste" — must be destroyed. The reassuring part is the *1-of-N* trust model: if even a single participant in a powers-of-tau ceremony is honest and deletes their share, the setup is safe; only if all of them colluded and kept it could they forge proofs. PLONK-style systems use one universal setup reusable across circuits; Groth16 needs a fresh setup per circuit.

zk-STARKs (StarkWare's Cairo) need no trusted setup at all — they are transparent, built only from hash functions, which also makes them plausibly post-quantum. The price is bigger proofs that cost more L1 gas to verify (millions of gas, versus a few hundred thousand for a SNARK). A popular compromise wraps a STARK inside a SNARK: you get STARK-style transparent, fast proving, then a final cheap SNARK verification on-chain. There is no free lunch — every choice trades proof size, prover speed, verification gas, and trust assumptions against each other.

The zkEVM challenge, and how to choose

The hard part of a general-purpose ZK-rollup is proving the EVM itself. Early validity rollups dodged this by being application-specific: Loopring and zkSync Lite proved a fixed menu of payment operations; StarkEx proved trading and NFTs for dYdX v3, Immutable X and Sorare. Proving a whole Ethereum-style world-state update under arbitrary smart contracts is far harder — every opcode, every storage write, every keccak has to be expressed inside the circuit. A zkEVM that can do this for general Solidity is the holy grail, and general-purpose zkEVMs only reached mainnet from 2023 onward (Polygon zkEVM, zkSync Era, Scroll, Linea; Starknet via Cairo).

Vitalik's Type 1–4 zkEVM taxonomy captures the central trade-off: EVM-equivalence versus prover speed. A Type 1 zkEVM proves exact Ethereum execution, perfectly equivalent but the slowest and most expensive to prove. Type 2 (e.g. Scroll, Polygon zkEVM) is EVM-equivalent at the bytecode level with minor internal tweaks. Type 4 compiles high-level Solidity down to a ZK-friendly VM (Starknet's Cairo, zkSync Era's approach) — the fastest to prove, but the least bytecode-compatible, so some tooling and edge-case contract behavior differs. The closer you sit to raw Ethereum, the heavier the prover you must run.

Set against the optimistic rollup from the previous guide, the trade-offs line up cleanly. Withdrawals — ZK: minutes-to-hours once proven; optimistic: ~7 days. Security — ZK: cryptographic, with no need for a watchful honest party, but carrying circuit and trusted-setup risk; optimistic: needs at least one honest watcher to challenge within the window, plus a censorship-resistant L1 so the fraud proof can land. Cost & maturity — ZK: heavy proving, harder EVM compatibility, newer; optimistic: cheap, simple, battle-tested earlier. And a point that catches people out: both still post their data to L1 — neither escapes data availability, which is the real, shared bottleneck the next guide tackles.