From a cave riddle to a global industry
In the first guide of this rung you proved you knew a secret using a cave and a coin flip, and it felt like a parlor trick. The same three properties — completeness, soundness, and zero-knowledge — now sit underneath billions of dollars of value. Here is the picture to hold onto: a single zero-knowledge proof of roughly 200 bytes can convince Ethereum that a separate chain correctly executed a million transactions Ethereum never saw, in a check that costs a few hundred thousand gas no matter how big that computation was. That is not a riddle anymore. That is infrastructure.
Everything ZK does in production falls into three families, and it pays to see which property of the proof is doing the work in each. Scaling: prove a computation was done correctly so nobody has to redo it — this leans on succinctness (a small, fast-to-check proof) and powers zkEVMs and zk-rollups. Privacy: prove a statement is true while hiding the inputs that make it true — this leans on the zero-knowledge property and powers shielded payments and private contracts. Identity and membership: prove a fact about yourself — that you're in some set, that you're over 18 — without revealing which person you are. The same circuits and proving systems from the last three guides serve all three.
zkEVMs: proving the world computer ran correctly
Recall the scaling rung: a zk-rollup executes transactions off-chain, then posts a succinct proof that the new state root follows correctly from the old one, which Ethereum verifies cheaply as the settlement layer. A zkEVM is the circuit that makes this work for arbitrary smart contracts: it proves that the EVM itself — every opcode, every storage read and write — executed exactly as the protocol says.
Why is this hard? Because a proof needs the whole computation expressed as arithmetic constraints over a finite field, and the EVM was never designed for that. The killer is hashing: the Keccak-256 used everywhere in Ethereum is a bitwise algorithm, and bit operations are miserable to express in field arithmetic — a single Keccak can cost on the order of tens of thousands of constraints, and a block does thousands of them. Storage proofs over the Merkle-Patricia trie are similarly brutal. A huge fraction of zkEVM engineering is just making these primitives provable at a tolerable cost.
Vitalik Buterin's well-known Type 1–4 taxonomy organizes the trade-off. *Type 1* is fully Ethereum-equivalent — it proves mainnet blocks exactly as they are, the gold standard for compatibility but the slowest and costliest to prove (Taiko aims here). *Type 2* is EVM-equivalent (same behavior, slightly different internals). *Type 3* is almost EVM-equivalent — a few precompiles or opcodes differ to ease proving (early Scroll and Polygon zkEVM lived here). *Type 4* compiles high-level Solidity down to a ZK-friendly VM instead of matching EVM bytecode — fastest to prove, but existing bytecode and tooling may not just work (zkSync Era, and Starknet via its Cairo VM). The rule of thumb: lower number = more compatible but slower and pricier to prove.
// --- Off-chain: the rollup's prover ---
batch = orderTransactions(mempool)
preRoot = currentStateRoot
postRoot = executeEVM(batch, preRoot) // actually run every opcode
proof = prove(
circuit = "correct EVM execution",
public = { preRoot, postRoot, dataHash(batch) }, // what L1 will check
witness = { batch, every intermediate stack / memory / storage state }
)
postToL1(preRoot, postRoot, dataHash(batch), proof)
// --- On-chain: the L1 verifier contract (cheap, ~constant cost) ---
function submitBatch(pre, post, dataHash, proof) {
require(pre == stateRoot, "wrong starting state");
require(verifier.verify(proof, [pre, post, dataHash]), "invalid proof");
stateRoot = post; // finalized at once — no 7-day challenge window
}What makes proving a whole EVM tractable at all is proof recursion: split the work into chunks, prove each chunk, then prove a single outer statement that says "all of these inner proofs verify." Folding and aggregation schemes (Plonky2, Nova-style folding) collapse thousands of proofs into one small proof to post on-chain, and let many machines prove in parallel. The honest trade-off remains: proving is expensive and hardware-hungry — minutes of GPU or cluster time per batch — while verification on L1 is cheap and roughly constant. ZK rollups buy fast finality and trustless withdrawals at the price of a heavy prover.
Proving you belong without saying who you are
Now switch to the zero-knowledge property. The workhorse pattern for private identity is membership in a set. Build a Merkle tree — a cryptographic accumulator — with one leaf per member, each leaf a commitment to a member's secret. The single Merkle root is published. To act, you prove in zero-knowledge: *"I know a secret whose commitment is one of the leaves under this root"* — without revealing which leaf. The verifier learns you are a legitimate member and nothing else. This is the Merkle tree from the cryptography rung, now used as a privacy primitive.
But anonymity creates a problem: what stops you from acting twice? The answer is the nullifier. It's a value deterministically derived from your secret (and a topic identifier), which you reveal publicly each time you act. It is unlinkable to your identity — nobody can tell which member produced it — yet it is unique per (member, topic), so the contract simply rejects a nullifier it has already seen. This single idea is how Tornado Cash withdrawals and the Semaphore protocol both prevent double-spending and double-signaling while preserving anonymity.
// Proves: "I am a registered member, and here is my one-time tag for this topic." // Private witness : secret, pathElements[], pathIndices[] // Public statement : root, externalNullifier, signalHash // Public output : nullifierHash commitment = Poseidon(secret) // my leaf; Poseidon = ZK-friendly hash computedRoot = MerkleRoot(commitment, pathElements, pathIndices) assert(computedRoot == root) // (1) membership, leaf stays hidden nullifierHash = Poseidon(secret, externalNullifier) // (2) unique + unlinkable tag signalHashSquared = signalHash * signalHash // (3) bind the message into the proof // ^ a dummy constraint so the proof commits to signalHash and can't be // replayed to carry a different vote/message
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IZKVerifier {
function verifyProof(uint256[8] calldata proof, uint256[4] calldata input)
external view returns (bool);
}
contract AnonymousVote {
IZKVerifier public verifier;
uint256 public immutable externalNullifier; // = id of THIS poll
mapping(uint256 => bool) public nullifierUsed; // stops double-voting
mapping(uint256 => uint256) public tally; // vote => count
function castVote(
uint256 root, // Merkle root of eligible voters
uint256 nullifierHash, // anonymous, one-per-voter tag
uint256 vote, // the signal, e.g. 0 = no, 1 = yes
uint256[8] calldata proof
) external {
require(isKnownRoot(root), "unknown member set");
require(!nullifierUsed[nullifierHash], "already voted");
require(
verifier.verifyProof(
proof,
[root, nullifierHash, uint256(keccak256(abi.encode(vote))), externalNullifier]
),
"invalid proof"
);
nullifierUsed[nullifierHash] = true; // voter stays anonymous; the vote counts once
tally[vote] += 1;
}
}These are real deployments. Semaphore is a general anonymous-signaling toolkit built on exactly this circuit. Worldcoin's World ID uses Semaphore on top of iris-verified humans to let you prove "I am a unique person" anonymously. A wave of zk-passport projects verify a government's digital signature on your passport's NFC chip inside a circuit, so you can prove "over 18" or "citizen of X" while disclosing nothing else — privacy-preserving selective disclosure. Be honest about the limits, though: someone must build the member set or issue the credential correctly (a trusted issuer), and ZK gives you privacy, not Sybil resistance — proving you're in the set doesn't prove you're a distinct human, which is exactly why projects reach for biometrics, and why those carry their own privacy controversies.
Private state: when the inputs themselves are secret
Scaling proofs hide nothing — the transactions are public, ZK is only there for succinctness. Privacy systems go further: they prove a statement is true over inputs that stay secret. You already met the seed of this in the privacy preview. Zcash's shielded pool represents money as commitments: spending creates new note commitments and reveals a nullifier for each note consumed, while a SNARK proves the spend is valid — you own the notes, and inputs equal outputs — without revealing sender, receiver, or amount. That is a shielded transaction: a confidential transfer whose correctness is fully proven yet whose contents are invisible.
Private smart contracts generalize this from payments to arbitrary logic. In an Aztec-style design, contract state lives as encrypted UTXO-like notes; users execute and prove the state transition on their own machine (client-side proving), then publish only the resulting commitments and nullifiers plus a validity proof. The network verifies the proof without ever seeing the inputs, the balances, or the logic's intermediate values. A privacy pool is a simple instance of the same idea: a shared shielded set everyone deposits into and withdraws from anonymously.
Be honest about the composability tax. Public DeFi works because any contract can read any other contract's state in the same transaction; when balances and state are hidden, that easy interaction breaks. Private systems lean on client-side proving (the user's device does real cryptographic work), asynchronous flows, and careful designs to recover composability. This is precisely why private DeFi is hard and still maturing — the privacy is real, but it is not free.
Finally, ZK is not the only tool for computing over secrets, and a mature engineer keeps all three on the bench. Zero-knowledge proves a fact without revealing inputs, but the prover sees everything. Fully homomorphic encryption computes directly on ciphertext, so even the machine doing the work never decrypts — at a large performance cost. Secure multi-party computation splits a secret across several parties who jointly compute a result none of them could alone. Increasingly these are combined (e.g. MPC for a threshold of provers), but they make different trust and performance trade-offs — don't reach for ZK when the real requirement is FHE or MPC.
The price of a proof
Choosing a proving system in production comes down to three numbers: proof size, verification cost, and proving cost. The earlier guides gave you the families; here is how they cash out.
Proof size and verification. A Groth16 proof is famously tiny — about 200 bytes (three elliptic-curve elements) — and verifies in roughly constant ~200–300k gas on Ethereum via the pairing precompiles, no matter how large the computation was. That constant-cost verification is the whole point of succinctness. The catch is that Groth16 needs a per-circuit trusted setup. PLONK uses a universal setup (a KZG commitment scheme), trading slightly larger proofs for one ceremony that serves every circuit. STARKs need no setup and are post-quantum, but proofs run from tens to hundreds of kilobytes and cost more gas to verify — you pay in bytes for dropping the setup assumption.
Proving cost is the real bottleneck. Generating the proof for a large EVM batch can take minutes on GPUs or whole clusters, and it dominates the economics of every zk-rollup. The mitigations are exactly what you've seen: recursion and aggregation to amortize and parallelize, dedicated hardware acceleration moving from GPU to FPGA to ASIC, and emerging proof markets that let provers compete. The trend line matters more than any single number — proving cost is falling fast, which is precisely why ZK keeps eating more of the stack each year.
Where this leads
Zero-knowledge now sits under two things you've already met and one you're about to. It gives zk-rollups their fast finality and trustless withdrawals (the scaling rung), and it is the engine of the privacy rung you're entering next. Beyond those, the frontier keeps widening: zk coprocessors that let a contract trustlessly use the result of a heavy query over historical chain state proven off-chain; zk bridges that prove one chain's consensus to another instead of trusting a multisig; and zk identity that could one day replace passwords with proofs. The scalability trilemma hasn't been repealed, but succinct proofs bend it further than anything before.
When you face a real design and wonder whether ZK is the right tool, run this checklist:
- Do you need to convince someone of a computation they can't or won't re-execute? If yes, you want succinctness — this is the scaling story (zkEVMs, rollups), and you may not need the zero-knowledge property at all.
- Do you need to prove a fact while hiding the inputs that make it true? If yes, you want the zero-knowledge property — this is privacy and identity (shielded transfers, membership proofs).
- Name which property you're actually using. Most production systems lean on succinctness OR privacy, not both — being precise here prevents a lot of confused threat modeling.
- Can you tolerate the prover's cost and latency, and budget for specialized circuit audits? If the under-constrained-circuit risk is unacceptable and your computation is small, a simpler primitive may be safer.
- Pick the system by its trade-off: a SNARK for the smallest proof and cheapest verify (accepting a trusted setup), or a STARK for no setup and post-quantum security (accepting larger proofs and more verify gas).
The cave riddle grew up. What looked like a clever party trick — proving you know a secret without saying it — turned out to be a general machine for outsourcing trust: convince anyone of anything you computed, reveal only what you choose. It's still early, the provers are still heavy, and the circuits still need careful eyes. But you now understand, end to end, why so many people believe zero-knowledge is the foundation the next decade of blockchains will be built on.