zkBook Cover

Minimizing Trust

The Architecture of Verifiable Secrets

by particle


Note: This book is a work in progress. If you find mistakes, typos, or have suggestions for improvements, please open a pull request or issue. Contributions are welcome!


This book teaches you how to build zero-knowledge proofs from the ground up.

Zero-knowledge proofs represent one of the most remarkable achievements in cryptography: the ability to prove that a statement is true without revealing anything beyond its truth. They enable a world where verification replaces trust, where privacy and transparency coexist, and where mathematical certainty can be achieved without exposing the underlying data.

What You'll Learn

This book takes you from foundational concepts to cutting-edge constructions:

  • Foundations: The trust problem, polynomial magic, and the sum-check protocol
  • Core Protocols: GKR, polynomial commitments, and hash-based constructions
  • SNARK Systems: Groth16, PLONK, and STARKs explained in depth
  • Zero-Knowledge: How to add privacy to proof systems
  • Advanced Topics: Recursion, composition, and practical considerations

Prerequisites

This book assumes familiarity with:

  • Finite field algebra
  • Elliptic curve cryptography
  • Basic concepts of cryptography

Let's begin by understanding why we need zero-knowledge proofs in the first place.

zkBook Back Cover

Chapter 1: The Trust Problem

In the summer of 1821, two mathematicians sat in a room in London, exhausted and frustrated. Charles Babbage and John Herschel had been tasked with checking the Nautical Almanac, a book of astronomical tables that sailors used to navigate the globe.

At the time, a "computer" was not a machine. It was a job title. Clerks calculated these tables by hand, other clerks checked their work, and printers typeset the results. Every step was a point of failure. As Babbage and Herschel compared the calculations against the printed proofs, they found error after error. A wrong digit in a logarithm didn't just mean a failed exam; it meant a ship running aground on a reef in the West Indies.

Exasperated, Babbage slammed the table and declared: "I wish to God these calculations had been executed by steam!"

That outburst launched the age of mechanical computation. Babbage spent the rest of his life designing engines to generate mathematical tables automatically, removing the human element from execution. If the machine was built correctly, its outputs could be trusted.

Two centuries later, we have fulfilled Babbage's wish. We have steam, now silicon, executing calculations at speeds he couldn't have imagined. But in solving the speed problem, we reintroduced the trust problem in a new form.

You send your calculation to the cloud. The cloud sends back an answer.

Why should you believe it?

The server might be compromised. The operator might be malicious. The hardware might be faulty. The software might contain bugs. Even if everything works correctly, how would you know? The only evidence you have is the answer itself, and the answer, by itself, proves nothing.

Here's the asymmetry: executing a computation takes resources (time, memory, energy). But checking whether the computation was done correctly also takes resources. In many cases, the same resources. If you could check the answer cheaply, you wouldn't have outsourced the computation in the first place.

This is the trust problem in computation: how do you verify without redoing all the work?

Truth Without a Judge

For millennia, knowledge has traveled through testimony. One person tells another: "I computed this result." The listener judges whether to believe. This judgment rests on reputation, authority, past behavior. All the machinery of social trust.

What if claims could carry their own evidence? Not testimony backed by reputation. Not certificates issued by authorities. Something stranger: an object that proves itself. If the claim is false, the object cannot exist. If the object exists and passes inspection, the claim must be true. No judge required.

This is not metaphor. The technology exists. A computational claim can be accompanied by a mathematical object, a proof, that anyone can verify in milliseconds. The proof works not because you trust the prover, but because mathematics makes cheating impossible. Two machines that have never communicated can verify the same proof and reach the same conclusion, not because they negotiated, but because the structure of mathematics forces the same answer from any system capable of arithmetic.

We will call this arithmetic consensus: agreement enforced by structure rather than achieved by persuasion. Chapter 2 develops the mechanism (the Schwartz-Zippel lemma) and explores why this represents a genuinely new foundation for intersubjective truth. For now, hold this question: what becomes possible when "I trust you" can be replaced with "I verified the math"?

This book teaches you how to build such proofs.

When Verification Is Easy

Before confronting the hard case, consider situations where verification is easy, given the right certificate.

Factorization: Given and a claim that are its prime factors. Verification: multiply , check it equals , verify and are prime. Finding those factors is believed to require exponential time; checking them takes polynomial time.

Graph coloring: Given a graph and a claimed 3-coloring. Verification: for each edge, check that its endpoints have different colors. Finding such a coloring is NP-hard; verifying one is linear in the number of edges.

Satisfying assignments: Given a Boolean formula and a claimed satisfying assignment. Verification: substitute the values and evaluate each clause. Finding such an assignment is NP-complete; checking one is polynomial.

These are problems in NP: the class of problems where, if someone hands you a proposed solution, you can check whether it's correct in reasonable time (polynomial in the input size). NP doesn't say anything about how hard it is to find a solution, only how hard it is to verify one. The proposed solution serves as a witness or certificate of correctness.

Note the asymmetry: NP captures "easy to verify," not "hard to find." Some NP problems are easy to solve (every problem in P is also in NP). The interesting cases are those where finding appears hard but verifying is easy. This gap is what proof systems exploit.

When Verification Seems As Hard As Computation

But many problems don't have short certificates.

The obvious verification strategy is to recompute: run the same algorithm on the same inputs and compare results. This works, but it defeats the purpose. You outsourced because you couldn't (or didn't want to) pay the computational cost. Verification that costs as much as the original computation is no verification at all.

For a moment, consider what "cheap verification" would even mean. The computation processes some input of size , takes steps, and produces an output. Cheap verification would mean checking correctness in time : strictly less than the original computation. Ideally, much less. Ideally, polylogarithmic in , or even constant.

But this seems impossible. How can you verify a computation without understanding what it computed? How can you understand what it computed without retracing its steps? The answer is computed from the input through a long chain of operations; surely checking requires following that chain?

The Cost of Blind Trust

On February 25, 1991, during the Gulf War, a Patriot missile battery in Dhahran, Saudi Arabia, failed to intercept an incoming Iraqi Scud. The missile struck an American barracks, killing 28 soldiers.

The cause was a software bug. The Patriot's tracking system measured time in tenths of a second using a 24-bit register, then multiplied by 0.1 to convert to seconds. But 0.1 has no exact binary representation; it's a repeating fraction, like 1/3 in decimal. The system truncated it, introducing a tiny error of about 0.000000095 seconds per tenth.

Tiny, but cumulative. The battery had been running for 100 hours. Over that time, the error accumulated to 0.34 seconds. For a Scud traveling at Mach 5, that's a tracking error of over 600 meters. The missile defense system calculated that the incoming Scud was outside its range gate and didn't fire.

The bug had been discovered two weeks earlier. Israeli defense forces, who had noticed the drift, warned the U.S. Army and recommended rebooting the system regularly to reset the clock. A software patch was developed. It arrived in Dhahran on February 26, one day after the attack.

Twenty-eight soldiers died because a computation was trusted without verification. The system worked exactly as programmed; the program was wrong. No one checked.

Whether the error comes from a hacker in the server room or a rounding bug in the floating-point unit, the result is the same: a wrong answer accepted as truth. Validity proofs don't care about intent; they care about correctness. They catch malice and accident alike.

The discovery of the 1980s and 1990s was that cheap verification is possible.

Interactive Proofs: The Breakthrough

The insight came from complexity theory. It involved a conceptual leap: interaction and randomness together can create verification power that neither possesses alone.

A computationally unbounded prover claims to have solved a problem. A polynomially bounded verifier wants to check this claim. The verifier cannot solve the problem themselves (that's the whole point), but they can engage in a conversation with the prover.

In an interactive proof, the verifier sends random challenges, the prover responds, and after some number of rounds, the verifier decides whether to accept or reject the claim.

The magic is in two properties:

Completeness: If the claim is true, an honest prover can always convince the verifier to accept.

Soundness: If the claim is false, no prover, no matter how clever or powerful, can convince the verifier to accept, except with negligible probability.

The probability in soundness comes from the verifier's randomness. The prover doesn't know in advance what challenges the verifier will send. A cheating prover must prepare for all possible challenges, and this is where they fail. The space of possible challenges is exponentially large; the prover cannot succeed at all of them if the claim is false.

Randomness Creates Asymmetry

Suppose I claim two polynomials and are identical. Both polynomials have degree at most , and their coefficients are elements of a large finite field of size .

Without randomness, verifying this claim requires comparing all coefficients. If is large, this is expensive.

With randomness, verification becomes trivial:

  1. Pick a random

  2. Evaluate and

  3. Accept if they're equal, reject otherwise

If , then for all . Verification always succeeds.

If , then is a nonzero polynomial of degree at most . Such a polynomial has at most roots. The probability that our random hits a root is at most .

With and :

This is so small it's effectively zero. One random evaluation suffices.

This is the Schwartz-Zippel lemma in action. We'll see it again and again throughout this book. It is the central tool in interactive proofs. Random evaluation catches disagreement between polynomials with overwhelming probability.

From IP to Succinctness

The theoretical study of interactive proofs established profound results:

IP = PSPACE: Interactive proofs with polynomial-time verifiers can verify exactly the class PSPACE. What is PSPACE? It's the class of problems solvable using a reasonable amount of memory (polynomial in the input size), but with no limit on time. A PSPACE algorithm might run for centuries, but it can only use a bounded scratch pad. This includes problems like determining the winner in generalized chess (with an board) or evaluating quantified Boolean formulas ("for all , there exists , such that..."). These problems are believed to be far harder than NP. The verifier's randomness and the prover's computational power combine to verify claims that seem uncheckable.

But these theoretical protocols had a problem: they weren't succinct. The total communication (the number of bits exchanged between prover and verifier) could be polynomial in the computation size. Better than redoing the computation, but not by much.

The goal of succinct arguments is more ambitious: proofs that are polylogarithmic in the computation size, or even constant. A computation taking billions of steps should yield a proof of hundreds or thousands of bits, not billions.

Achieving this goal required new proof models: extensions and variants of interactive proofs that enabled different trade-offs between interaction, query access, and succinctness.

The Proof System Zoo

The path from interactive proofs to modern SNARKs runs through several distinct proof models. Understanding this taxonomy clarifies where different techniques come from and why modern systems take the forms they do.

This section mentions several complexity classes (IP, MIP, PSPACE, NEXP). These are categories that computer scientists use to classify problems by how hard they are to solve or verify. Don't worry if the distinctions feel abstract on first reading. The key intuition is that different proof models have different "verification power," meaning some can verify harder problems than others. The specific class names matter less than the pattern: adding constraints to the prover (like forbidding communication between multiple provers) paradoxically increases what the verifier can check.

Interactive Proofs (IP)

The starting point. A prover and verifier exchange messages. The verifier uses randomness to catch cheating. Security is information-theoretic: even an all-powerful prover cannot convince the verifier of a false statement (except with negligible probability).

Think of it as courtroom cross-examination. The prover (witness) wants to convince the verifier (judge) of some claim. The judge cannot independently verify the facts; they weren't there, they don't have the evidence. But through clever questioning, the judge can probe for inconsistencies. An honest witness has nothing to hide; their answers will be consistent. A lying witness must maintain a web of fabrications, and random probing questions will eventually find a thread that unravels it.

The class IP contains all languages with such protocols where the verifier runs in polynomial time. A language here is a set of strings , the formal way complexity theory encodes decision problems: means the answer to the problem on input is "yes." The theorem IP = PSPACE (Shamir, 1990) shows this class is remarkably large, far larger than NP. The verifier's random questions, combined with the prover's unbounded computational power, can verify claims that no static certificate could capture.

Multi-Prover Interactive Proofs (MIP)

IP was powerful (it captured all of PSPACE), but verification still required multiple rounds of back-and-forth, and proofs weren't succinct. What if we could constrain the prover more tightly to gain more verification power?

What if the verifier could interrogate multiple provers who cannot communicate with each other?

Imagine two suspects in separate rooms: the classic police interrogation. The detective asks each suspect questions, comparing answers for consistency. If the suspects are telling the truth, their stories align effortlessly. If they're lying, they can't coordinate their lies without communicating, and they can't communicate. The detective doesn't need to know the truth themselves; they only need to catch inconsistencies between the two stories.

In a Multi-Prover Interactive Proof, two or more provers share the witness but cannot exchange messages during the protocol. The verifier sends different challenges to each prover and cross-checks their responses.

The deep insight here is non-adaptivity. In a single-prover IP, the prover sees the verifier's first challenge before answering, then sees the second challenge before answering again. The prover adapts to each challenge in sequence. With two non-communicating provers, the verifier can send different questions simultaneously; neither prover knows what the other was asked. This forces both provers to commit to a consistent story before seeing the cross-examination.

This apparently simple change unleashes enormous verification power:

MIP = NEXP (Babai, Fortnow, Lund, 1991): Multi-prover proofs can verify problems in NEXP, which stands for nondeterministic exponential time. What does this mean? Recall that NP is the class where solutions can be verified quickly. NEXP is the exponentially larger cousin: problems where the solution itself might be exponentially large (so even writing it down takes exponential time), but once written, it can be checked in exponential time. These are vastly harder problems than NP or even PSPACE.

The gap from PSPACE to NEXP is vast. The non-communication constraint is what makes it possible: the verifier can probe two points of a story simultaneously, catching inconsistencies that a single adaptive prover could finesse.

This idea of forcing commitment before challenge reappears throughout SNARK design. When we study polynomial commitment schemes, we'll see the same principle: the prover commits to a polynomial, then the verifier challenges. The commitment plays the role of the second prover: it locks in answers before the questions are known.

Probabilistically Checkable Proofs (PCP)

MIP was even more powerful (it captured NEXP), but it required two separate provers. In practice, we usually have just one prover. Could we get similar power without needing to literally interrogate two parties in separate rooms?

Here the model shifts from interaction to query access. The prover writes down a static proof string (potentially very long), which is just a sequence of symbols like . The verifier doesn't read the whole string. Instead, they pick a few positions at random and look only at those symbols. For example, the verifier might flip some coins, decide to look at positions 17, 42, and 803, read , , and , and make a decision based only on those three values.

A PCP is characterized by two parameters, both functions of the input size :

  • How many random bits the verifier uses (to decide which positions to query)

  • How many positions in the proof string the verifier queries

The verifier's decision depends only on the input, their random coin flips, and the few symbols they read from the proof.

The PCP Theorem (Arora, Safra; Arora, Lund, Motwani, Sudan, Szegedy; 1992) is one of the landmark results of complexity theory:

Notation: is the class of languages decidable by a probabilistic verifier that uses random bits and queries positions in a proof string. The theorem says: using only random bits (polynomially many possible random choices) and reading proof positions (a constant, independent of input size), you can verify any NP statement with constant soundness error.

What does "every NP problem has a PCP" mean? Recall that an NP problem is one where solutions can be verified quickly given a witness (like checking that a proposed graph coloring is valid). The PCP theorem says something stronger: for any such problem, there exists a way to encode the witness into a longer proof string such that the verifier uses only random bits and queries only a constant number of proof positions. The proof might be polynomial-size, but verification reads only bits.

How can this possibly work? The key is structured redundancy.

Think of a completed Sudoku puzzle. The puzzle has internal constraints: each row, column, and 3×3 box must contain the digits 1-9 exactly once. Now imagine "corrupting" one cell by changing a 7 to a 3. This single error violates the constraint for its row, its column, and its box. One mistake creates evidence in multiple places. A random spot-check has a decent chance of catching it.

PCPs work the same way, but with vastly more redundancy. The proof is not the raw witness; it's an encoded version where local constraints interlock globally. The encoding transforms the witness into a form where any error, any deviation from a valid proof, creates detectable inconsistencies across many positions.

The technology: low-degree polynomial encoding. The witness is interpreted as evaluations of a polynomial, then extended to many more points. Polynomial structure ensures that errors propagate: a polynomial that's wrong at even one point must disagree with the correct polynomial almost everywhere (Schwartz-Zippel, again). Random queries catch these disagreements with high probability.

Consider what this means. A satisfying assignment to a million-variable formula might require a million bits to write down. But there exists an encoding, a PCP, where checking validity requires reading only, say, 3 bits. The encoding has redundancy; errors anywhere propagate everywhere, detectable by sparse sampling.

The MIP-PCP Connection

There's a deep connection between multi-prover proofs and PCPs. The two non-communicating provers in an MIP can be simulated by a single long proof string: each possible pair of questions to the two provers corresponds to a position in the string, with the answer pair as the value at that position.

The non-communication constraint in MIP becomes a consistency requirement in PCP: the answers at different positions must be consistent with some underlying witness. The verifier's power to cross-check provers becomes the power to query random positions and check consistency.

This connection was key to proving MIP = NEXP and to subsequent PCP constructions.

Interactive Oracle Proofs (IOP)

The PCP theorem was a landmark: it showed any NP statement has a proof checkable with constant queries. But PCPs require enormous proof strings, and they're non-interactive (the prover must anticipate all possible verifier randomness). IP had efficient interaction but no query access. Could we combine the best of both?

Interactive Oracle Proofs do exactly that.

In an IOP, the protocol has multiple rounds. In each round, the prover sends a proof string (or, more abstractly, an oracle). The verifier can query this oracle at chosen positions, then sends a challenge. The prover responds with another oracle, and so on.

Why combine interaction and oracles? Each compensates for the other's weakness. Pure PCPs require enormous proof strings to achieve low soundness error; the proof must anticipate all possible verifier randomness. Pure IPs require many rounds of back-and-forth; the verifier probes incrementally, each round narrowing the space of consistent lies. IOPs get the best of both: the prover commits to an oracle (like a PCP), then the verifier challenges (like an IP), then another oracle, another challenge. Each oracle only needs to handle the challenges that could follow given previous commitments.

This hybrid captures what modern SNARK constructions actually do:

  1. Prover commits to a polynomial (an oracle that the verifier can query for evaluations)

  2. Verifier sends a random challenge

  3. Prover commits to another polynomial

  4. Repeat

  5. Verifier makes a few queries and decides

The IOP abstraction separates the protocol logic from the implementation of oracles. The oracle is abstract; the verifier magically gets evaluations at chosen points. Chapter 11 shows how polynomial commitment schemes instantiate these oracles cryptographically.

Linear PCPs

IOPs gave us a clean abstraction, but implementing them required a way to make oracles concrete and binding. A key insight: if we restrict what kind of queries the verifier can make, we can use cryptography to enforce that restriction. This leads to Linear PCPs.

In a standard PCP (as described above), the proof is a string of symbols and the verifier reads a few specific positions: "give me , , and ." In a Linear PCP, the proof is still a vector of values , but the verifier can only ask for linear combinations: "give me for my chosen weights ."

Think of it as a library where you can't open the books (that would reveal the witness). You can only ask the librarian to weigh books in specific combinations. "Put 2 copies of book 1 on the scale, plus 3 copies of book 3, plus 1 copy of book 7, and tell me the total weight." The librarian answers with a single number. You ask several such questions. From these weighted sums, you try to verify some property of the books without ever seeing their contents.

The linearity constraint turns out to be exactly what we need. If the verifier is restricted to weighted-sum queries, we can use cryptography to enforce this restriction. Here's the key insight: certain cryptographic structures only allow weighted-sum operations, nothing else.

Elliptic curve groups have this property. In an elliptic curve group, you can add points together and multiply points by numbers, but you cannot multiply two points together. Think of it like a calculator that has + and × buttons, but the × only works when one input is a regular number. If the proof values are encoded as elliptic curve points, then anyone holding those points can only compute weighted sums of them.

Concretely: the prover knows the proof values and a special group point . The prover creates encoded values (multiply each value by the point ). The verifier receives these encoded points. Given weights , the verifier can compute , which equals the encoding of . The verifier gets the weighted sum, but cannot extract the individual values or compute anything beyond weighted sums. The elliptic curve structure itself forces the verifier to play by the Linear PCP rules.

Groth16 (Chapter 12) is built on linear PCPs. The prover's messages are linear combinations of structured reference string elements, which are themselves encodings of powers of a secret. The verifier checks linear relationships via pairings: bilinear maps that allow one multiplication in the exponent, just enough to check quadratic constraints.

From Proof Models to SNARKs

All modern SNARKs arise from one of these proof models combined with cryptographic compilation:

Proof Model+ Cryptography= SNARK Family
IP+ Polynomial CommitmentsSpartan, HyperPlonk
IOP (polynomial)+ KZG / FRIPLONK, Marlin, STARKs
Linear PCP+ PairingsGroth16, BCTV14
PCP+ Merkle treesKilian-style arguments

The pattern: start with an information-theoretically secure protocol, then use cryptography to make the prover's messages short and binding.

Polynomial Commitment Schemes (Chapter 9-10) instantiate IOP oracles: the prover commits to a polynomial, the verifier queries evaluations, and a short proof demonstrates correctness of each evaluation.

Fiat-Shamir (Chapter 11) eliminates interaction: derive the verifier's challenges from hashes of the transcript. The prover computes the entire interaction locally and outputs a static proof.

The combination yields SNARKs: Succinct Non-interactive Arguments of Knowledge. A SNARK for a computation of size has:

  • Proof size: or even

  • Verification time: or

  • Prover time: or similar quasi-linear

The asymmetry is achieved. Verification is exponentially cheaper than computation.

Zero-Knowledge: Proving Without Revealing

There's another dimension to this story. So far, we've focused on soundness: preventing false claims from being verified. But what about privacy?

Suppose you want to prove you know a password without revealing the password itself. Or that you have sufficient funds for a transaction without revealing your balance. Or that you satisfy some credential requirement without exposing your identity.

Zero-knowledge proofs achieve exactly this. The proof convinces the verifier that the statement is true, but reveals nothing beyond this single bit of information. The verifier learns "yes, this is true" and nothing else.

The formal definition involves a simulator: an algorithm that produces transcripts indistinguishable from real proof transcripts, without access to the secret witness. If such a simulator exists, the proof is zero-knowledge; the transcript could have been generated by someone who didn't know the secret, so the transcript cannot leak the secret.

Zero-knowledge adds a layer of privacy to succinct verification. Together, they form zkSNARKs: Zero-Knowledge Succinct Non-interactive Arguments of Knowledge.

The Architecture of Modern Proofs

This book develops the theory and practice of zkSNARKs. The architecture has emerged from decades of research, but it follows a consistent pattern:

1. Arithmetization (Chapters 4-8): Convert the computational claim into algebraic form. A program becomes a circuit. A circuit becomes a system of polynomial equations. The claim "I computed correctly" becomes "these polynomials satisfy this identity."

2. Information-Theoretic Protocol (Chapters 3, 7): Design an interactive protocol where the prover sends polynomials (or claims about polynomials) and the verifier checks them via random evaluations. This protocol is sound against unbounded provers; no cryptographic assumptions yet.

3. Cryptographic Compilation (Chapters 6, 9-10): Replace the abstract polynomials with cryptographic commitments. The prover commits to polynomials before seeing challenges. Polynomial commitment schemes (KZG, FRI, IPA) provide this binding.

4. Fiat-Shamir Transform (Chapter 11): Eliminate interaction. The verifier's random challenges are derived from a hash of the transcript. The prover computes the entire interaction locally and outputs a static proof.

The result: a proof that anyone can verify, that reveals nothing about the witness, and that is exponentially smaller than the computation it attests to.

Why This Matters

Each application is a trust assumption eliminated.

Verifiable computation removes trust in the cloud. You outsource to untrusted servers, receive a proof with the result, and verify cheaply. The server's incentives, security practices, and internal controls become irrelevant. You don't trust the server; you verify the proof.

Blockchain scalability removes trust in centralized sequencers. Layer 2 solutions process thousands of transactions off-chain, producing a single proof that the main chain verifies. The sequencer cannot lie about execution. Transaction throughput increases by orders of magnitude without introducing new trust assumptions.

Privacy-preserving credentials remove trust in identity intermediaries. Prove you're over 21 without revealing your birthdate. Prove you passed a background check without revealing what was checked. The verifier learns exactly one bit: valid or not. No data broker, no identity provider, no linkable trail.

Computational integrity removes trust in institutions. Scientific simulations, machine learning inference, financial calculations: any computation can be accompanied by a proof of correctness. The question changes from "do I trust this organization?" to "does this proof verify?"

The pattern is consistent: find a trust assumption, replace it with mathematics.

The Road Ahead

The chapters that follow develop this technology piece by piece.

We begin with polynomials (Chapter 2), the universal language of algebraic proof systems. The sum-check protocol (Chapter 3) shows how to verify exponential sums in polynomial time, the foundational technique underlying almost everything that follows.

Multilinear extensions (Chapter 4) and univariate polynomials (Chapter 5) provide two complementary encoding schemes for computational data. Commitment schemes (Chapter 6) bind provers to their claims.

The GKR protocol (Chapter 7) verifies arbitrary circuits using sum-check. Arithmetization (Chapter 8) shows how real computations become circuits.

Polynomial commitment schemes (Chapters 9-10) provide the cryptographic foundation: KZG, IPA, and FRI, each with different trade-offs between proof size, verification time, and trust assumptions.

The SNARK recipe (Chapter 11) explains how these pieces assemble. Groth16 (Chapter 12), PLONK (Chapter 13), lookup arguments (Chapter 14), and STARKs (Chapter 15) are complete systems, each optimizing different aspects.

-protocols (Chapter 16) and zero-knowledge (Chapters 17-18) add privacy.

Chapters 19-21 form Part VI on prover optimization, covering fast sum-check proving, fast STARK proving, and techniques for minimizing commitment costs. These chapters are optional on a first read: they go into engineering depth that matters for implementers but is not required to understand the rest of the book. Chapter 22 then synthesizes by comparing the two PIOP paradigms (quotienting vs. sum-check) and is part of the main thread.

Composition and recursion (Chapter 23) enable proofs about proofs: unlimited computation with constant verification. The book concludes with system selection guidance (Chapter 24), MPC's parallel path (Chapter 25), open frontiers (Chapter 26), and the broader cryptographic landscape (Chapter 27).

By the end, you'll understand not just what zkSNARKs do, but how they work: the mathematical structures that make the impossible possible.

Key Takeaways

  1. Verification should be cheaper than computation. If Alice outsources a computation to Bob, she shouldn't have to redo the entire work to check his answer. The goal is asymmetric verification: Bob does the hard work once, Alice checks quickly.

  2. Randomness creates verification power. A deterministic verifier who can't compute the answer can't check it either. But a randomized verifier can probe for inconsistencies. Random questions catch cheaters with high probability.

  3. Schwartz-Zippel is the fundamental tool. Two different degree- polynomials agree on at most points. Evaluating at a random point catches disagreement with probability at least . Polynomials are central to proof systems because errors propagate almost everywhere.

  4. Proof models evolved by constraining the prover. IP captures PSPACE. MIP (multiple non-communicating provers) captures NEXP. PCPs allow constant-query verification. IOPs combine interaction with oracle access. Paradoxically, more constraints on the prover give the verifier more power.

  5. The PCP theorem is foundational. NP = PCP[, ]. Any NP statement has a proof where the verifier reads constantly many bits. This requires encoding the witness with structured redundancy so that any error creates detectable inconsistencies.

  6. Polynomial commitments instantiate oracles. The prover commits to a polynomial; the verifier queries evaluations; a short proof demonstrates each evaluation is correct. Different schemes (KZG, FRI, IPA) offer different trade-offs.

  7. Fiat-Shamir eliminates interaction. Replace the verifier's random challenges with hashes of the transcript. The prover computes the entire interaction locally and outputs a static proof.

  8. The architecture is modular. Arithmetization encodes computation as constraints. An IOP proves the constraints are satisfied. Cryptographic compilation (PCS + Fiat-Shamir) makes proofs short and non-interactive. Each layer can be swapped independently.

  9. Zero-knowledge is orthogonal to succinctness. The proof can reveal nothing beyond the statement's truth. Privacy and compression are independent properties; modern systems achieve both.

Chapter 2: The Power of Polynomials

In 1960, Irving Reed and Gustave Solomon were trying to solve a practical problem: how do you send data through space?

The spacecraft transmitting from millions of miles away couldn't retransmit lost bits. The signal would be corrupted by cosmic radiation, hardware glitches, and the irreducible noise of the universe. Reed and Solomon needed a way to encode information so that even after some of it was destroyed, the original could be perfectly recovered.

Their solution was startlingly simple. Instead of sending raw data, they evaluated a polynomial at many points and transmitted the evaluations. A polynomial of degree is uniquely determined by points, so if you send many more than evaluations, some can be corrupted or lost, and the receiver can still reconstruct the original polynomial from what remains.

What Reed and Solomon had discovered, without quite realizing it, was one of the most powerful ideas in all of computer science: polynomials are rigid. A low-degree polynomial cannot "cheat locally." If you change even a single coefficient, the polynomial's values change at almost every point. This rigidity, this inability to lie in one place without being caught elsewhere, would turn out to be exactly what cryptographers needed, thirty years later, to build systems where cheating is mathematically impossible.

The Motivating Problem: Beyond NP

Before we explore polynomials, let's understand the problem they solve. In Chapter 1, we saw that some problems have the useful property that their solutions are easy to check: multiply the claimed factors to verify factorization, check each edge to verify graph coloring. These are NP problems; the solution serves as its own certificate.

But what about problems that don't have short certificates?

The SAT Problem: The Mother of All NP Problems

The Boolean Satisfiability Problem (SAT) asks: given a Boolean formula, is there an assignment of True/False values to its variables that makes the formula evaluate to True?

Consider the formula (where means OR, means AND, and means NOT):

This is in Conjunctive Normal Form (CNF): an AND of ORs. Each parenthesized group is a clause, and each or is a literal.

The question: does there exist an assignment that satisfies all clauses simultaneously? This is what makes SAT hard: you must determine whether any solution exists, not find a specific one. With 3 variables there are possibilities; with 100 variables there are . No known algorithm avoids checking exponentially many cases in the worst case.

For this toy example, we can reason through it. Clause 2 () needs at least one of: , , or . Clause 3 needs at least one variable true. Setting helps both. With fixed, Clause 1 becomes , requiring or true. Try :

  • Clause 1:
  • Clause 2:
  • Clause 3:

We found a satisfying assignment, so the formula is satisfiable. But notice: finding this solution required insight or luck. If no solution existed, we would have had to check all possibilities to be certain.

Why SAT matters: The Cook-Levin theorem (1971) proved that SAT is NP-complete: every problem in NP can be efficiently reduced to a SAT instance. If you can solve SAT efficiently, you can solve any NP problem efficiently. This makes SAT the canonical "hard" problem.

The good news for verification: Once someone has a solution, checking it is easy: just plug in the values. The assignment is a certificate that proves satisfiability. The asymmetry is striking: finding a solution may take exponential time, but verifying one takes linear time.

#SAT: When Even Certificates Don't Help

Now consider a harder question: how many satisfying assignments does a formula have?

This is the #SAT problem (pronounced "sharp SAT" or "number SAT"). It's in a complexity class called #P, which is believed to be harder than NP.

Why? Because even if someone tells you "there are exactly 47 satisfying assignments," there's no obvious way to verify this without enumerating possibilities. Having one satisfying assignment doesn't tell you there aren't 46 others. Having 47 assignments doesn't prove there isn't a 48th.

For a formula with variables, there are possible assignments. For , that's about assignments (more than the number of atoms in a human body). Even at a trillion checks per second, verifying by enumeration would take longer than the age of the universe.

This is the hopeless case. The output is just a number. There's no obvious certificate that proves the count is correct.

Or so it seems.

The breakthrough insight of interactive proofs is that through interaction and randomness, we can verify even #SAT efficiently. The prover doesn't give us a certificate; instead, we have a conversation that forces a lying prover to contradict themselves.

Polynomials are the key to making this work. They transform #SAT, this hopelessly unverifiable counting problem, into a series of polynomial identity checks where cheating is detectable with overwhelming probability.

We'll see exactly how in Chapter 3 when we study the sum-check protocol. But first, we need to understand why polynomials have this magical power.

Why Polynomials?

If you've read any paper on zero-knowledge proofs, you've noticed something striking: polynomials are everywhere. Witnesses become polynomial evaluations. Constraints become polynomial identities. Verification reduces to checking polynomial properties. The entire field seems obsessed with these algebraic objects.

This is not an accident. Polynomials possess a trinity of properties that make them uniquely suited for verifiable computation:

  1. Representation: Any discrete data can be encoded as a polynomial

  2. Compression: A million local constraints become one global identity

  3. Randomization: The entire polynomial can be tested from a single random point

The rest of this chapter develops each pillar in turn.

Pillar 1: Representation - From Data to Polynomials

The first magical property: any finite dataset can be encoded as a polynomial.

But first, we must define the terrain. Where do these polynomials live? Not in the real numbers. Remember the Patriot missile from Chapter 1: a rounding error of 0.000000095 seconds, accumulated over time, killed 28 soldiers. Real number arithmetic is treacherous. Equality is approximate, errors accumulate, and 0.1 has no exact binary representation.

Polynomials in ZK proofs live in finite fields, mathematical structures where arithmetic is exact. In a finite field, isn't ; it's a precise integer. There's no rounding, no overflow, no approximation. Two values are either exactly equal or they're not. This exactness is what makes polynomial "rigidity" possible: if two polynomials differ, they differ exactly, and we can detect it.

It is a historical irony that this structure was discovered by someone who knew he was about to die. In May 1832, twenty-year-old Évariste Galois spent his final night frantically writing mathematics. He had been challenged to a duel the next morning and expected to lose. In those desperate hours, he outlined a new theory of algebraic symmetry, describing number systems that behaved like familiar arithmetic (you could add, subtract, multiply, and divide) but were finite. They didn't stretch to infinity; they looped back on themselves, like a clock.

The next morning, Galois was shot in the abdomen and died the following day. But his "finite fields" turned out to be the perfect environment for computation. Every SNARK, every polynomial commitment, and every error-correcting code in this book lives inside the structure Galois sketched the night before his death.

Two Ways to Encode Data

Given a vector of field elements, we have two natural polynomial representations:

Coefficient Encoding: Treat the values as coefficients:

This polynomial has degree at most . Its coefficients are the data. Evaluating at any point gives us a "fingerprint" of the entire vector: a single value that depends on all the data.

Evaluation Encoding: Treat the values as evaluations at fixed points. Find the unique polynomial of degree at most such that:

This polynomial exists and is unique, a fact guaranteed by Lagrange interpolation, which we'll explore momentarily. Here, the data becomes "the shape of a curve that passes through specific points."

Both encodings are useful in different contexts. Coefficient encoding is natural for fingerprinting; evaluation encoding is natural when we want to extend a function defined on a small domain to a larger one.

Lagrange Interpolation: The Existence Guarantee

Why does a polynomial passing through specified points always exist and why is it unique?

Picture a flexible curve that you need to pin down at specific points. With one point, infinitely many curves pass through it. With two points, you've constrained the curve more, but many still fit. The uniqueness guarantee: with points, there's exactly one polynomial of degree at most that passes through all of them. The points completely determine the curve.

Theorem (Lagrange Interpolation). Given distinct points in a field , there exists a unique polynomial of degree at most such that for all .

Construction: Define the Lagrange basis polynomials:

Each has a special property: and for . It's a polynomial that "activates" only at point .

The interpolating polynomial is then:

Let's verify: at point , we get .

Worked Example: Find the polynomial through .

The Lagrange basis polynomials:

The interpolating polynomial:

Expanding (this is tedious but instructive):

Verification: , , . All match.

Uniqueness: If two degree- polynomials and agree at points, their difference is a polynomial of degree at most . But vanishes at each of the points where and agree, meaning it has roots. A nonzero polynomial of degree can have at most roots, so must be the zero polynomial. Therefore .

The Rigidity of Polynomials

Here's the key property that makes verification possible:

Two different degree- polynomials can agree on at most points.

Proof: Let and be distinct polynomials of degree at most . Their difference is non-zero (since ) and has degree at most . A non-zero polynomial of degree has at most roots. Therefore for at most values of .

This seems like a simple algebraic fact, but its consequences are profound. Consider what this means:

If you and I each have a degree-99 polynomial, and they're different polynomials, then they can agree on at most 99 input values. Out of, say, possible inputs in a cryptographic field, they disagree on all but at most 99 of them.

This is rigidity. A polynomial can't "cheat locally." If a prover tries to construct a fake polynomial that agrees with the honest one at a few strategic points, the fake will disagree almost everywhere else.

Compare this to arbitrary functions. Two functions could agree on 99% of inputs and differ on just 1%. But a degree-99 polynomial that differs from another anywhere must differ on essentially all points. The disagreement isn't localized; it's smeared across the domain.

This rigidity has a striking consequence: you cannot construct a degree- polynomial that matches another degree- polynomial at strategically chosen points while differing elsewhere. If two degree- polynomials differ at all, they differ almost everywhere. A local patch is impossible; any change propagates globally.

A polynomial cannot lie consistently. It must betray itself almost everywhere.

This property alone is purely mathematical. To turn it into a verification tool, we need one more ingredient: randomness.

Pillar 2: Randomization - The Schwartz-Zippel Lemma

In 1976, Gary Miller discovered a fast algorithm to test whether a number is prime. There was one problem: proving it correct required assuming the Riemann Hypothesis, one of the deepest unsolved problems in mathematics. Four years later, Michael Rabin found a way out. He modified Miller's test to use random sampling. The new algorithm couldn't guarantee the right answer, but it could make errors arbitrarily unlikely, say, less likely than a cosmic ray flipping a bit in your computer's memory. By embracing randomness, Rabin traded an unproven conjecture for a proven bound on failure probability.

This is the paradigm shift: randomness as a resource for verification. A cheating prover might fool a deterministic check, but fooling a random check requires being lucky, and we can make luck arbitrarily improbable.

The rigidity of polynomials becomes a verification tool through one of the most important theorems in computational complexity:

Schwartz-Zippel Lemma. Let be a non-zero polynomial of total degree over a field . If we choose uniformly at random from a finite subset , then:

In plain English: A non-zero polynomial almost never evaluates to zero at a random point, provided the field is much larger than the polynomial's degree.

Why This Is Profound

Consider verifying whether two polynomials and are equal. The naive approach: compare their coefficients one by one. If each polynomial has degree 1 million, that's a million comparisons.

Schwartz-Zippel offers a shortcut: pick a random and check if .

  • If : The check always passes.
  • If : The polynomial is non-zero with degree at most 1 million. By Schwartz-Zippel, .

In a field of size , this probability is about (far smaller than the odds of guessing a 256-bit private key).

One random evaluation distinguishes degree- polynomials with probability .

A Proof Sketch

For a single variable, the proof is straightforward. A non-zero polynomial of degree has at most roots. If has elements, the probability of hitting a root is at most .

For multiple variables, the proof proceeds by induction. Write as a polynomial in with coefficients that are polynomials in :

At least one coefficient polynomial is non-zero (otherwise ). Call it . By induction, a random choice of makes with probability at least . Conditioned on this, is a non-zero univariate polynomial of degree at most , so a random makes it zero with probability at most . The union bound gives the result.

Application: Polynomial Fingerprinting for File Comparison

Consider a practical problem: Alice and Bob each have a massive file (think terabytes) and want to check if their files are identical. Sending entire files is prohibitively expensive. Can they compare with minimal communication?

The setup: Interpret each file as a vector of field elements: for Alice, for Bob. Encode them as polynomials:

The protocol:

  1. Alice picks a random

  2. Alice computes her fingerprint:

  3. Alice sends to Bob (just two field elements!)

  4. Bob computes and checks if

Analysis:

  • Completeness: If , the polynomials are identical, so always. Bob correctly accepts.

  • Soundness: If , then is non-zero with degree at most . By Schwartz-Zippel:

They've compared a terabyte of data by exchanging two field elements. The probability of error is negligible.

A Worked Example with Actual Numbers:

Alice has , Bob has . Working in (the field of integers modulo 11).

Their polynomials:

Alice picks :

Since , Bob correctly concludes the vectors differ.

When would they collide? Only if :

To solve for , we need the multiplicative inverse of 4 modulo 11. Since , we have .

So .

The only "bad" random choice is . With 11 possible choices, the collision probability is exactly . In a cryptographic field with elements, this would be (essentially zero).

Application: Batch Verification of Signatures

The same principle powers batch verification in signature schemes like Schnorr.

Recall that a Schnorr signature on message under public key satisfies: where is the challenge hash. Verifying this requires two scalar multiplications.

Now suppose a node must verify 1000 signatures. Checking each individually costs 2000 scalar multiplications. Can we do better?

The batch verification trick: Take a random linear combination of all verification equations. If each equation holds, then for any coefficients :

This is a single multi-scalar multiplication (MSM), dramatically faster than 1000 separate verifications using algorithms like Pippenger's.

Why random coefficients? If we just summed the equations (), an attacker could forge two invalid signatures whose errors cancel: one with error , another with . The batch check would pass, but individual signatures would fail.

Random prevent this. If any signature is invalid, the batch equation becomes a non-zero polynomial in the variables. By Schwartz-Zippel, random satisfy a non-zero polynomial with negligible probability.

This is polynomial identity testing in disguise. The honest case gives the zero polynomial; any cheating gives a non-zero polynomial that random evaluation catches.

Arithmetic Consensus

Step back and notice something strange about what just happened.

In the fingerprinting protocol, Alice and Bob reached agreement about whether their files match. They didn't trust each other. They didn't consult a third party. They didn't negotiate or compare credentials. They simply evaluated polynomials at the same random point, and mathematics forced them to the same conclusion.

This is a new kind of agreement. Philosophers have long studied how agents come to share beliefs. The epistemology of testimony asks how we gain knowledge from what others tell us, and the answer always involves trust: we believe the speaker because of their reputation, authority, or our assessment of their incentives. Social epistemology studies how groups arrive at consensus, and the mechanisms are social: communication, persuasion, deference to experts.

Schwartz-Zippel enables something different. Two systems that share no trust relationship, that have never communicated, that know nothing about each other's reliability, can independently verify the same polynomial identity and reach the same conclusion. Not because they agreed to agree, but because the structure of low-degree polynomials leaves no room for disagreement. If , then for almost all . Any system capable of field arithmetic will detect the difference.

Call this arithmetic consensus: agreement forced by mathematical structure rather than achieved by social process. The boundaries of this regime are precise. Any statement reducible to polynomial identity testing can be verified this way. Any claim expressible as "these two low-degree polynomials are equal" becomes a claim that any arithmetic system can check, with the same answer guaranteed.

This relates to a tradition in the philosophy of mathematics. Intuitionism, developed by Brouwer in the early 20th century, held that a mathematical statement is meaningful only if we can construct a proof of it. For the intuitionist, "there exists an x" means "we can exhibit an x." Truth is inseparable from proof.

Arithmetic consensus takes a different but related position: for statements about polynomial identities, truth is inseparable from verification. The proof object (a random evaluation point and its result) doesn't require trust in whoever produced it. Any verifier running the same arithmetic reaches the same conclusion. This is intersubjective truth without intersubjectivity. The agreement happens not between minds but between any systems capable of arithmetic.

The applications in cryptography (verifiable computation, zero-knowledge proofs, blockchain consensus) are engineering achievements. But underneath them lies a philosophical shift: for a certain class of claims, we can replace "I trust the speaker" with "I checked the math." This is not a small thing. It's a new foundation for agreement in a world of untrusted parties.

Error-Correcting Codes: The Deeper Structure

The polynomial fingerprinting protocol is an instance of a deeper mathematical structure that appears throughout ZK proofs: error-correcting codes.

What Is an Error-Correcting Code?

Imagine sending a message through a noisy channel (think: radio transmission through interference, reading data from a scratched DVD, or communicating with a spacecraft). Some bits might get flipped. How do you ensure the receiver can still recover the original message?

The naive approach: send the message three times and take a majority vote. If you send "1" as "111" and one bit flips to "011," the receiver sees two 1s and one 0, guesses "1," and succeeds.

But this is inefficient; you've tripled your transmission length to correct one error.

Error-correcting codes provide a systematic way to add redundancy that can detect and correct errors far more efficiently.

The Key Definitions

An (n, k, d) code over alphabet consists of:

  • A set of messages of length

  • An encoding function that maps each message to a codeword of length

  • A minimum distance : any two distinct codewords differ in at least positions

The minimum distance determines the code's power:

  • Error detection: Can detect up to errors (if we see something that's not a valid codeword, we know an error occurred)

  • Error correction: Can correct up to errors (the corrupted codeword is still closest to the original)

Example: Repetition Code. Encode message bit as (repeat it 3 times). This is a (3, 1, 3) code: codewords are "000" and "111," which differ in all 3 positions. It can detect 2 errors and correct 1 error.

Reed-Solomon Codes: Polynomials as Codewords

The most important family of error-correcting codes for our purposes is the Reed-Solomon code, discovered by Irving Reed and Gustave Solomon in 1960.

Construction: Work over a field with at least elements. Choose distinct evaluation points .

  • Messages: Polynomials of degree at most (equivalently, vectors of coefficients)

  • Encoding: Evaluate the polynomial at all points:

  • Codewords: Vectors of field elements

The minimum distance: If are distinct polynomials of degree at most , then is a non-zero polynomial of degree at most , which has at most roots. Therefore and can agree on at most of the evaluation points, meaning they differ on at least positions.

This gives an code: an optimal relationship between redundancy and distance, known as a Maximum Distance Separable (MDS) code.

Worked Example: Consider a Reed-Solomon code over with evaluation points and message polynomials of degree at most (so ).

Message: the polynomial (coefficients ).

Codeword: evaluate at each point:

Codeword: .

The minimum distance is . Any two codewords differ in at least 5 positions. This code can correct up to errors.

Why Reed-Solomon Codes Power ZK Proofs

The connection to zero-knowledge proofs is now clear:

Error-Correcting CodesZK Proof Systems
MessageWitness (prover's secret)
EncodingPolynomial evaluation over large domain
CodewordProver's committed values
Distance propertyCheating changes most of the codeword
Random samplingVerifier's random challenges

In ZK:

  1. The prover's witness is encoded as a polynomial .

  2. The polynomial is "committed" by evaluating it at many points (or via a polynomial commitment scheme).

  3. The verifier samples random points and checks consistency.

  4. If the prover cheated (wrong witness), the polynomial won't satisfy required properties, and this corruption spreads across almost all evaluation points due to the Reed-Solomon distance property.

The key insight: Reed-Solomon encoding is distance-amplifying. A small, localized lie (wrong witness value) becomes a large, detectable corruption (wrong polynomial evaluations everywhere).

Real-World Applications of Reed-Solomon

Before we move on, it's worth appreciating how ubiquitous Reed-Solomon codes are:

  • QR codes: The chunky squares on product labels use Reed-Solomon to remain readable even when partially obscured or damaged.

  • CDs, DVDs, Blu-rays: Scratches that destroy data are corrected by Reed-Solomon coding.

  • Deep-space communication: Voyager, Cassini, and other spacecraft use Reed-Solomon codes to send data across billions of miles despite noise and signal degradation.

  • RAID storage: Disk arrays use Reed-Solomon to survive drive failures.

  • Digital television (DVB): Broadcast signals use Reed-Solomon to handle transmission errors.

The same mathematical structure that lets your scratched DVD still play a movie is what lets ZK proofs detect a lying prover from a single random query.

Pillar 3: Compression - From Many Constraints to One

We've seen how polynomials encode data and how random sampling detects differences. The third pillar explains how polynomials let us aggregate many checks into one.

The Compression Problem

A computation consists of many local constraints. Consider a circuit with a million gates. Each multiplication gate with inputs and and output imposes a constraint: .

Checking all million constraints individually takes a million operations. Can we do better?

The key insight: We can aggregate all constraints into a single polynomial identity.

The Vanishing Polynomial Technique

Suppose we have constraints that should all equal zero:

Step 1: Encode as a polynomial. Find a polynomial such that:

Step 2: The equivalence. The statement "all constraints are satisfied" is equivalent to:

Step 3: Use the Factor Theorem. The polynomial equals zero at points if and only if is divisible by the vanishing polynomial:

Think of as a stencil with holes at . If truly equals zero at those points, it passes through the holes perfectly: the division comes out clean with no remainder. If misses even one hole (nonzero at some constraint point), it hits the stencil, and the division leaves a remainder. The polynomial doesn't fit.

Step 4: The divisibility test. The statement "all constraints are satisfied" becomes: there exists a quotient polynomial such that:

Step 5: Random verification. By Schwartz-Zippel, if this identity holds everywhere, it holds at a random point with high probability. Conversely, if it fails anywhere, it fails at with probability .

So the verifier only needs to check:

A million local checks become one divisibility test, verified at a single random point.

A Worked Example: Three Constraints

Let's see this concretely. Suppose we have three constraints that should be zero:

  • (at )

  • (at )

  • (at )

Working in , suppose an honest prover has , so is the zero polynomial on .

The vanishing polynomial:

If all constraints are satisfied, for some .

Now suppose a cheating prover has , (wrong!), . The polynomial passes through .

Using Lagrange interpolation:

where .

So .

Is this divisible by ? Let's check: .

Since , but , the division would have a pole at . The divisibility fails, and no valid quotient exists.

The verifier, picking a random , will find that for any claimed with overwhelming probability.

Freivald's Algorithm: Polynomials in Disguise

Let's examine a beautiful algorithm that shows the polynomial paradigm in a surprising context: verifying matrix multiplication.

The Problem

Given three matrices , , and , determine whether .

The naive approach: Compute directly and compare with . Using the standard algorithm, this takes multiplications. Even with the fastest known algorithm (Strassen's descendants), it's (still much worse than ).

If we're trying to verify that someone else computed the product correctly, do we really need to redo all their work?

Freivald's Insight (1977)

Rüdiger Freivald proposed a remarkably simple test:

  1. Pick a random vector

  2. Compute (one matrix-vector product: )

  3. Compute (another matrix-vector product: )

  4. Compute (another matrix-vector product: )

  5. Check if

Total work: Three matrix-vector products, so (a full factor of faster than matrix multiplication!).

Why It Works

If : Then , so always. The test passes.

If : Let . The test passes only if .

Since , at least one row of is non-zero. Call it row , with entries not all zero.

The -th component of is:

This is a linear polynomial in the variables . For this polynomial to equal zero, we need:

If we pick each uniformly at random from , what's the probability this equation holds?

Claim: For a non-zero linear polynomial over , a random input is a root with probability exactly .

Proof: Suppose for some . We can rewrite:

For any fixed choice of , there's exactly one value of that makes the sum zero. Since is chosen uniformly from possibilities, the probability of hitting that one value is .

So with a single random vector, Freivald's algorithm detects incorrect matrix multiplication with probability at least .

Amplifying Confidence

If isn't small enough, we can repeat with independent random vectors:

  1. Pick independent random vectors

  2. For each, check if

  3. Accept if all checks pass

If , each check passes with probability at most , and the checks are independent. So:

With and repetitions, the false acceptance probability is (cryptographically negligible).

Freivald's Algorithm as Polynomial Identity Testing

Here's the connection to polynomials that might not be immediately obvious.

Consider the matrices , , as defining a polynomial identity. The claim is equivalent to the matrix identity:

We can view each entry as a polynomial in the entries of the matrices. The test is checking that a related set of linear polynomials (one for each row of ) all vanish at the random point .

More directly: the expression for random vectors defines a bilinear polynomial in the entries of . This polynomial is non-zero if and only if . By a bilinear version of Schwartz-Zippel, random inputs make a non-zero bilinear form non-zero with high probability.

Freivald's test is polynomial identity testing in disguise.

This is a recurring theme: many efficient verification algorithms, when analyzed carefully, turn out to be checking polynomial identities via random evaluation.

A Complete Worked Example

Let's verify a matrix multiplication over .

First, the honest computation:

Suppose the prover claims (correct).

Pick random .

Compute :

Compute :

Compute :

Since , the test passes.

Now suppose a cheating prover claims (wrong in position (1,2)).

With the same :

Compute :

We have .

The test fails, catching the cheater.

Beyond Schwartz-Zippel: Why Polynomials Are Uniquely Suited

You might wonder: could we use other functions besides polynomials? What makes them special?

1. Low-Degree Extension

Pillar 1 established that any finite dataset can be encoded as a polynomial via Lagrange interpolation. The cryptographic payoff is the low-degree extension: given a function defined on a small domain like (just points), we can extend it to a unique low-degree polynomial over the entire field (potentially points). The extension is determined: there's exactly one degree- polynomial that agrees with on the Boolean hypercube. This is the foundation of the sum-check protocol and the GKR protocol. Compare this to a hash function , which can take any value at any input. Knowing at a million points tells you nothing about at the next point. There's no interpolation, no structure to exploit.

2. Efficient Evaluation

Given a polynomial's coefficients, we can compute its value at any point in time using Horner's method:

This is multiplications and additions (optimal).

3. Homomorphic Structure

Polynomials form a ring: we can add and multiply them, and these operations correspond to coefficient-wise operations. This algebraic structure is what makes polynomial commitment schemes like KZG possible. They allow us to verify polynomial relationships "in the exponent" without revealing the polynomials themselves. If we commit to and , we can check without learning any coefficients.

4. FFT Speedups

Over special domains, specifically the -th roots of unity in a field, polynomial evaluation and interpolation can be performed in time via the Fast Fourier Transform.

Without FFT, evaluating a degree- polynomial at points takes operations. With FFT over roots of unity, it's .

This speedup is necessary for practical ZK systems. Prover complexity in many SNARKs is dominated by FFT operations.

5. Composability

Polynomials compose predictably:

  • If has degree and has degree , then has degree

  • Products have degree

  • Sums have degree

This predictability enables rigorous protocol analysis. When the verifier asks for , they know the result should come from a polynomial of degree , and can set the soundness parameters accordingly.

The Polynomial Paradigm: A Unified View

We can now state the polynomial paradigm that underlies nearly all modern ZK proofs:

  1. Represent the computation as polynomials: witness values, constraint evaluations, everything becomes polynomial data

  2. Compress many constraints into a single polynomial identity, typically a divisibility condition or a summation equality

  3. Randomize to check the identity: evaluate at random points, relying on Schwartz-Zippel to catch any cheating

This paradigm appears in every major ZK system:

  • Groth16: R1CS constraints become a QAP divisibility check:

  • PLONK: Gate constraints and wiring constraints become polynomial identities checked via random challenges

  • STARKs: AIR constraints become low-degree polynomial conditions verified by the FRI protocol

  • Sum-check: Summation claims over exponentially many terms reduce to a single polynomial evaluation

Key Takeaways

  1. The counting problem (#SAT) motivates why polynomials matter: Some computations have no obvious short certificate, but polynomial encodings enable efficient verification through interaction and randomness.

  2. Polynomials encode data: Any finite dataset becomes a polynomial through coefficient encoding (data = coefficients) or evaluation encoding (data = values at fixed points). Lagrange interpolation guarantees this encoding exists and is unique.

  3. Polynomials are rigid: Two different degree- polynomials agree on at most points. Local differences become global differences; you can't cheat in one place without affecting almost everywhere.

  4. Schwartz-Zippel enables efficient testing: A non-zero polynomial evaluates to zero at a random point with probability at most . For cryptographic fields, this is negligible.

  5. This is an error-correcting code: The polynomial paradigm is the Reed-Solomon code applied to computation verification. A small lie in the witness becomes corruption across essentially all evaluation points.

  6. Freivald's algorithm is polynomial identity testing: Matrix multiplication verification in time (instead of ) works because it's checking linear polynomial identities via random evaluation.

  7. Constraints compress to identities: Many local constraints become a single polynomial divisibility condition: where is the vanishing polynomial.

  8. The structure is unique: Polynomials combine efficient evaluation, unique interpolation, homomorphic properties, FFT speedups, and composability in ways no other mathematical object does.

  9. The paradigm is universal: Every major ZK system (Groth16, PLONK, STARKs, sum-check) uses the same three-step approach: represent as polynomials, compress constraints to identities, verify via random evaluation.

  10. Commitment + evaluation = proof architecture: Committing to a polynomial locks the prover to a single function; random evaluation checks that function is correct. This commit-then-evaluate pattern is the skeleton of every modern SNARK.

Chapter 3: The Sum-Check Protocol

In late 1989, the field of complexity theory was stuck.

Researchers believed that Interactive Proofs were a relatively weak tool, capable of verifying only a handful of graph problems. The consensus was clear: interaction helped, but not by much.

Then came the email.

Noam Nisan, a master's student at Hebrew University, sent a draft to Lance Fortnow at the University of Chicago. It contained a protocol that used polynomials to verify something thought impossible: the permanent of a matrix. Fortnow showed it to his colleagues Howard Karloff and Carsten Lund. They realized the technique didn't just apply to matrices. It applied to everything in the polynomial hierarchy.

When the paper was released, it didn't just solve a problem. It caused a crisis. The result implied that "proofs" were far more powerful than anyone had imagined. Within weeks, Adi Shamir (the "S" in RSA) used the same technique to prove IP = PSPACE: interactive proofs could verify any problem solvable with polynomial memory, even if finding the solution took eons.

The engine powering this revolution was a single elegant idea: the sum-check protocol.

The sum-check protocol takes a claim that seems expensive to verify, the sum of a polynomial over all points of a high-dimensional domain, and reduces it to something trivial: a single evaluation at a random point. The verifier's work scales linearly with the number of variables, not exponentially with the size of the domain.

This chapter develops the sum-check protocol from first principles. We'll see exactly how the protocol works, why it's sound, and how any lie propagates through the protocol until it becomes a simple falsehood the verifier can catch. Along the way, we'll trace through complete worked examples with actual field values, because this protocol is too important to understand only abstractly.

The protocol requires only basic polynomial facts from Chapter 2 (Schwartz-Zippel, evaluation, degree). The next two chapters develop the polynomial representations used in practice: multilinear extensions (Chapter 4) enable linear-time provers and scale to domains of size , while univariate techniques (Chapter 5) offer smaller proofs via FFT-friendly structure. Sum-check itself is agnostic to representation; it works on any multivariate polynomial.

The Problem: Verifying Exponential Sums

Suppose a prover claims to know the value of the following sum:

Here is a -variate polynomial over a finite field , and the sum ranges over all points of the boolean hypercube : the set of all binary strings of length . Think of it as the corners of a -dimensional cube, where each coordinate is either 0 or 1. For , these are the eight vertices . The prover says the answer is . Do you believe it?

A naive verifier would evaluate at every point of the hypercube and add up the results. But this requires evaluations, exponential in the number of variables. For , this is hopelessly infeasible.

The sum-check protocol solves this problem. It allows a verifier to check the claimed value of with high probability, in time that is only linear in and the time it takes to evaluate at a single random point. This represents an exponential speedup.

But how can you verify a sum without computing it? The answer lies in a beautiful idea: claim reduction via deferred evaluation. Instead of computing the sum directly, the verifier engages in a multi-round dialogue with the prover. In each round, the prover makes a smaller, more specific claim, and the verifier uses randomness to drill down on a single point. An initial lie, no matter how cleverly constructed, gets amplified at each step until it becomes a simple falsehood about a single evaluation, which the verifier catches at the end.

The Compression Game

Think of the sum-check protocol as a game of progressive compression, or better yet, as a police interrogation.

The suspect (prover) claims to have an alibi for every minute of a 24-hour day ( moments). The detective (verifier) cannot review surveillance footage for the entire day. Instead, the detective asks for a summary: "Tell me the sum of your activities."

The suspect provides a summary polynomial.

The detective picks one random second () and asks: "Explain this specific moment in detail." To answer, the suspect must provide a new summary for that specific timeframe. If the suspect lied about the total day, they must now lie about that specific second to make the math add up. The detective drills down again: "Okay, explain this millisecond."

The lie has to move. It has to hide in smaller and smaller gaps. Eventually, the detective asks about a single instant that can be fact-checked directly. If the suspect's story at that final instant doesn't match the evidence, the whole alibi crumbles.

More precisely: the prover holds an enormous object, a table of values. The verifier wants to know their sum but cannot afford to examine the table. In round 1, the prover compresses the table into a univariate polynomial. The verifier probes it at a random point , and that answer becomes the new target: a compressed representation of a table half the size.

Each round, the table shrinks by half while the verifier accumulates random coordinates. After rounds, the "table" has size 1: a single value. The verifier can compute that value herself.

Honest compression is consistent, but lies leave fingerprints. If the prover's initial polynomial doesn't represent the true sum, it must differ from the honest polynomial somewhere. The random probes find these differences with overwhelming probability. A cheating prover would need to predict all random challenges in advance; against cryptographic randomness, that's impossible.

The Protocol Specification

Let's make this precise. The sum-check protocol verifies a claim of the form:

where is a -variate polynomial of degree at most in each variable. (More generally, each variable can have its own degree bound ; we use uniform for simplicity.) The verifier must know these degree bounds before the protocol begins—they're part of the problem specification, not something the prover provides. If the prover could claim arbitrary degree bounds, soundness would collapse: a high-degree polynomial can pass through any finite set of points while matching an honest polynomial elsewhere. The sum ranges over boolean points, but is a polynomial over the field and can have degree greater than 1. For example, has ; when we sum over , we get . The degree bound matters because it determines how many coefficients the prover must send in each round: a degree- univariate polynomial requires coefficients. The protocol proceeds in rounds.

Round 1

The prover computes and sends a univariate polynomial , claimed to equal:

In words: is the polynomial obtained by summing over all boolean values of the last variables, leaving as a formal variable.

The verifier performs two checks:

  1. Consistency check: Verify that . This ensures the prover's polynomial is consistent with the claimed total sum.

  2. Degree check: Verify that has degree at most in . This is necessary for soundness; without it, the protocol breaks completely. (We'll see why shortly.)

If either check fails, the verifier rejects. Otherwise, she samples a random field element and sends it to the prover.

The verifier now evaluates the prover's polynomial at this random point, computing . This value represents what the prover is implicitly asserting about a reduced sum. The verifier doesn't compute this sum herself; she simply records what the prover's polynomial claims it to be. This becomes the target for round 2: the prover must now justify that the sum over points, with the first variable fixed to , actually equals .

The verifier has now reduced the original claim about a sum over points to a new claim about a sum over points. Specifically, the prover is now implicitly claiming that:

Why the degree check matters: The soundness argument relies on Schwartz-Zippel: two distinct degree- polynomials agree on at most points, so a random evaluation catches the difference with probability . But what if the prover sends a high-degree polynomial instead?

Suppose the true sum is but the prover claims . The honest polynomial is , with . The prover needs a polynomial passing through and where .

Without a degree bound, the cheating prover can construct a degree- polynomial with two properties:

  1. (passes the consistency check with the false claim )
  2. for every (agrees with the honest polynomial everywhere the verifier might query)

A degree- polynomial has coefficients, enough freedom to prescribe its value at every point in independently. So the prover sets , (with ), and for all other .

Here is why this is devastating. The verifier samples some and computes the reduced claim . Since , this reduced claim is correct: it equals the true partial sum . From this point on, the prover can follow the honest protocol for rounds 2 through , and the final oracle check will pass. The false sum was injected in round 1, but the cheat left no trace in any subsequent round.

The degree bound is the handcuffs. It forces the polynomial to be stiff. If it must pass through the wrong sum, its stiffness forces it to miss the honest polynomial almost everywhere else.

Formal argument: Suppose the prover sends with . The difference is a non-zero polynomial of degree at most , so it has at most roots. Therefore and agree on at most points. When the verifier samples uniformly from , the probability that is at most . If , the cheating prover is now committed to a false claim that propagates through subsequent rounds.

Round (for )

At the start of round , the verifier holds a value from the previous round. This represents the prover's implicit claim about a sum over points.

The prover sends the next univariate polynomial , claimed to equal:

The verifier checks:

  1. Consistency check:

  2. Degree check:

If checks pass, she samples and computes .

Final Check (After Round )

After rounds, the verifier has received and chosen . The prover's final claim is that .

The verifier now evaluates at the single point , using her "oracle access" to , and checks whether this equals .

If the values match, she accepts. Otherwise, she rejects.

A Note on Oracle Access

In complexity theory, we say the verifier has "oracle access" to . Sometimes this is trivial: if encodes a multiplication gate, the verifier knows that and just plugs in the random values . No magic needed.

But in many SNARK constructions, depends on the prover's private data. The verifier cannot evaluate on her own, because she doesn't know the inputs that define it. Sum-check has done its job, reducing an exponential sum to one evaluation, but the verifier is stuck at the last step. How this gap is closed (using polynomial commitment schemes) is a central question we return to in Chapter 9.

Why Does This Work?

Completeness

If the prover is honest, all checks pass trivially. The polynomials are computed exactly as specified, so the consistency checks hold by construction. The verifier accepts.

Soundness

The soundness argument is more subtle and relies on the polynomial rigidity we developed in Chapter 2.

Suppose the prover's initial claim is false: the true sum is . For the first consistency check to pass, the prover must send some polynomial such that .

Let be the true polynomial: the one computed by honestly summing over the hypercube. By assumption, . So the prover's polynomial must be different from .

This is exactly where rigidity traps the cheater. The prover wants to send a polynomial that passes through the lie () but behaves like the truth () everywhere else. Rigidity makes this impossible. The polynomial is too stiff: if , they can agree on at most points.

By the Schwartz-Zippel lemma, when the verifier samples a random from , the probability that is at most .

With overwhelming probability, . The prover has "gotten lucky" only if the random challenge happened to land on one of the few points where the two polynomials agree.

But what does mean? It means the prover is now committed to defending a false claim in round 2: he must convince the verifier that the sum equals , when in fact it equals .

The same logic cascades through all rounds. In each round, either the prover gets lucky (probability ) or he's forced to defend a new false claim. By the final round, the prover must convince the verifier that , but the verifier checks this directly.

By a union bound, the total probability that a cheating prover succeeds is at most:

In cryptographic applications, is enormous (e.g., ), so this probability is negligible.

Worked Example: Honest Prover and Cheating Prover

Let's trace through the entire protocol with actual values: first with an honest prover, then with a cheater. Seeing both cases with the same polynomial makes the soundness argument concrete.

Setup: Consider the polynomial over a large field . We have variables.

Goal: The prover wants to convince the verifier of the sum over :

The Honest Case

Round 1: The prover claims and sends:

The verifier checks: .

She samples and computes .

Round 2: The prover sends .

The verifier checks: .

She samples .

Final check: The verifier queries her oracle for and compares to . They match. Accept.

The Cheating Case

Now suppose the prover lies: he claims instead of the true sum .

Round 1: To pass the consistency check, the prover must send some with . The true polynomial sums to 6, so he can't use it.

He sends a lie: . Check: .

The verifier samples .

  • Prover's value:
  • True value:

The prover is now committed to defending a false claim: . But the true sum is .

Round 2: The prover needs . He sends .

The verifier samples .

Final check:

  • Prover claims:
  • Verifier queries oracle:

. Reject.

The Moral

The initial lie forced the prover to send polynomials different from the true ones. By Schwartz-Zippel, the random challenges almost certainly landed on points where these polynomials disagreed. The lie didn't just persist; it amplified through the rounds until it became a simple, detectable falsehood.

Notice what happened to the cheating prover. After sending the first dishonest polynomial, they weren't free. The verifier's random challenge created a new constraint: the prover must now justify that . But they didn't choose 5; the verifier did, unpredictably. The prover is forced to fabricate an answer for a question they couldn't anticipate.

Each round tightens the trap. The second lie must be consistent with the first. The third with the second. Each fabrication constrains the next, and the prover never controls which constraints they'll face. By the final round, the accumulated lies have painted the cheater into a corner: they must claim that when any honest evaluation reveals 25. The system of fabrications collapses under its own weight.

The prover's only hope is that every random challenge happens to land on a point where the cheating polynomial agrees with the true one. For degree- polynomials over a field of size , this probability is at most per round, negligible in cryptographic settings.

The Protocol Flow: A Visual Guide

The following diagram traces the claim reduction through each round:

flowchart TB
    subgraph init["INITIAL CLAIM"]
        I["H = Σ g(b₁, b₂, ..., bᵥ) over 2ᵛ points"]
    end

    subgraph r1["ROUND 1"]
        R1P["Prover sends g₁(X₁)"]
        R1V["Verifier checks: g₁(0) + g₁(1) = H"]
        R1C["Verifier picks random r₁"]
        R1N["New claim: g₁(r₁) = Σ g(r₁, b₂, ..., bᵥ)<br/>over 2ᵛ⁻¹ points"]
        R1P --> R1V --> R1C --> R1N
    end

    subgraph dots["..."]
        D["ν rounds total"]
    end

    subgraph rv["ROUND ν"]
        RVP["Prover sends gᵥ(Xᵥ)"]
        RVV["Verifier checks: gᵥ(0) + gᵥ(1) = gᵥ₋₁(rᵥ₋₁)"]
        RVC["Verifier picks random rᵥ"]
        RVN["Final claim: gᵥ(rᵥ) = g(r₁, r₂, ..., rᵥ)<br/>A SINGLE POINT!"]
        RVP --> RVV --> RVC --> RVN
    end

    subgraph final["FINAL CHECK"]
        F1["Verifier evaluates g(r₁, ..., rᵥ) directly"]
        F2{"g(r₁,...,rᵥ) = gᵥ(rᵥ)?"}
        F3["✓ ACCEPT"]
        F4["✗ REJECT"]
        F1 --> F2
        F2 -->|Yes| F3
        F2 -->|No| F4
    end

    init --> r1 --> dots --> rv --> final

The reduction is exponential: .

Application: Counting Satisfying Assignments (#SAT)

The sum-check protocol becomes truly powerful when combined with arithmetization: the process of translating discrete, combinatorial problems into the language of polynomials over finite fields. We touched on #SAT in Chapter 2 as motivation for why polynomials matter in complexity theory. Now we see exactly how the translation works and why it enables efficient verification. The full theory of arithmetization will occupy later chapters; for now, we need just enough to see sum-check in action.

The #SAT problem: Given a boolean formula with variables, count how many of the possible assignments make true.

This is a canonical #P-complete problem, even harder than NP. Verifying the count naively requires checking all assignments. But with sum-check, a prover can convince a verifier of the correct count in polynomial time.

Arithmetization of Boolean Formulas

The key insight is to transform the boolean formula into a polynomial that equals 1 on satisfying assignments and 0 otherwise.

Step 1: Arithmetize literals

  • The variable stays as

  • The negation becomes

Over , these give the right values: if , then , and .

Step 2: Arithmetize clauses Consider a clause where each is a literal. The clause is false only when all three literals are false. So:

where each is the polynomial form of the literal.

Example: For the clause :

This equals 0 precisely when , , : the only assignment that falsifies the clause.

Step 3: Arithmetize the full formula For a CNF formula , the formula is satisfied when all clauses are satisfied:

Over , this product equals 1 if all clauses are satisfied and 0 otherwise.

The Protocol

The number of satisfying assignments is:

This is exactly a sum over the boolean hypercube! The prover can use the sum-check protocol to convince the verifier of this count.

Degree analysis: For a 3-CNF formula, each clause polynomial has degree at most 3. With clauses, the product has total degree at most . The degree in any single variable is at most as well (though often much smaller due to variable sharing).

Verifier's work: The verifier performs rounds of sum-check, checking polynomials of degree at most . The final check requires evaluating at a random point; this takes time since is a product of clause polynomials.

Total verifier time: , polynomial in the formula size, despite the exponentially large space of assignments.

Worked Example: A Tiny #SAT Instance

Consider the formula with variables and clauses.

Step 1: Arithmetize.

Clause 1:

Clause 2:

Full formula:

Step 2: Evaluate on .

Clause 1Clause 2 satisfied?
No
Yes
No
Yes

Step 3: Count.

The formula has exactly 2 satisfying assignments: and (both require ).

The prover uses sum-check to convince the verifier of this count. The polynomial has degree 2 in each variable (degree 4 total), so each round polynomial has degree at most 2, requiring 3 field elements per round.

The Magic of Deferred Evaluation

The sum-check protocol embodies a profound principle: you don't need to compute a sum to verify it.

Consider what the verifier actually does:

  1. She receives polynomials from the prover.

  2. She checks consistency: does equal the previous round's value?

  3. She checks degree bounds.

  4. At the very end, she evaluates at a single random point.

The verifier never computes any intermediate sums. She never evaluates at any point of the boolean hypercube. All the hard work, computing the actual sums, is done by the prover. The verifier merely checks that the prover's story is internally consistent.

This is claim reduction in action. Each round, the claim shrinks:

  • Round 0: "The sum over points is "

  • Round 1: "The sum over points (at a random slice) is "

  • Round 2: "The sum over points is "

  • ...

  • Round : "The value at one specific point is "

By the end, we've reduced an exponential claim to a trivial one. And the random challenges ensure that any cheating at an earlier stage propagates into a detectable error at the final stage.

Complexity Analysis

Let's be precise about the efficiency gains.

Prover complexity: In round , the prover must compute a univariate polynomial of degree at most . To specify this polynomial, the prover evaluates it at points (say, ). For each such point , the prover computes:

This requires summing over terms. Across all rounds, the prover's total work is:

The prover does work proportional to the size of the hypercube, but crucially, this is what the prover would need to do anyway to compute the sum. The sum-check protocol doesn't add significant overhead to the prover. Note that achieving per round (rather than recomputing from scratch each time) requires an algorithmic trick: maintaining and folding intermediate arrays so that each round reuses the previous round's work. Chapter 19 develops this technique in detail.

Verifier complexity: In each round, the verifier:

  • Receives a degree- polynomial (specified by coefficients)

  • Checks that equals the previous value

  • Samples a random field element

  • Evaluates at the random point

This is work per round, or total.

At the end, the verifier evaluates at a single point . Let be the time to evaluate at one point. The verifier's total work is:

The speedup: The verifier avoids evaluating at points, an exponential savings. If arises from a "structured" computation (like a circuit or formula), then is polynomial in the description of that structure, making the whole protocol efficient.

Communication complexity: The prover sends univariate polynomials, each of degree at most . Naively, this requires field elements per polynomial (to specify the coefficients), for a total of field elements. But there's a trick.

The one-coefficient trick: At each round, the verifier checks . This is one linear equation in the polynomial's coefficients, so the polynomial has only degrees of freedom, not .

Write . Then:

So: .

The prover sends only , and the verifier recovers from the constraint. This saves one field element per round: field elements total instead of .

For the common case of multilinear polynomials (), this halves communication: one field element per round instead of two.

Soundness error: As computed earlier, the probability that a cheating prover succeeds is at most . For a 256-bit field and reasonable values of and , this is negligible.

Why Sum-Check Enables Everything Else

The sum-check protocol is the foundation upon which much of modern verifiable computation is built.

The celebrated IP = PSPACE theorem, which shows that every problem solvable in polynomial space has an efficient interactive proof, uses sum-check as its core building block. The LFKN protocol arithmetizes quantified boolean formulas and applies sum-check recursively. To verify that an arithmetic circuit was evaluated correctly, the GKR protocol (Chapter 7) expresses the relationship between adjacent circuit layers as a sum over a hypercube, then uses sum-check to reduce a claim about one layer to a claim about the next, peeling back the circuit layer by layer until we reach the inputs.

Many of today's practical succinct arguments (Spartan, HyperPlonk, and the entire family of "sum-check based" SNARKs) use sum-check as their information-theoretic core. The protocol's structure, where a prover commits to polynomials and a verifier checks random evaluations, maps cleanly onto polynomial commitment schemes. As we'll see in the next chapter, multilinear polynomials (those with degree at most 1 in each variable) have a natural correspondence with functions on the boolean hypercube. Sum-check works especially elegantly with multilinear polynomials, and this paradigm has become one of the two major approaches to building modern proof systems.

For years after the initial theoretical breakthroughs, practical SNARK systems moved away from sum-check toward other approaches (PCPs, linear PCPs, univariate techniques). But recently, sum-check has made a dramatic comeback. Systems like Lasso and Jolt use sum-check at their core, achieving remarkable prover efficiency. It turns out that sum-check provers can run in linear time for structured polynomials, and the protocol meshes beautifully with modern polynomial commitment schemes. We'll explore this renaissance in depth in Chapter 19.

The sum-check protocol is where the abstract power of polynomials (their rigidity, their compression of constraints, their amenability to random testing) first crystallizes into a concrete verification procedure. Every protocol we study from here forward either uses sum-check directly or is in dialogue with the principles it established.

The Last Mile: From Oracle Access to Polynomial Commitments

We noted earlier that the final check of sum-check requires evaluating , and that sometimes the verifier cannot do this herself because depends on the prover's private data. This deserves a closer look, because the mechanism that closes this gap is what turns sum-check from a complexity-theoretic curiosity into a practical proof system.

Consider two representative scenarios.

When the verifier can compute directly. If is built entirely from public information, the final evaluation is straightforward. For instance, in the #SAT application above, is a product of clause polynomials derived from the public formula . The verifier knows , so she can evaluate in time. Similarly, in the GKR protocol (Chapter 7), the polynomial at each layer encodes the circuit's wiring pattern, which is public. No additional machinery is needed.

When depends on the prover's private witness. This is the harder and more common case in SNARK constructions. In systems like Spartan (Chapter 19), the polynomial involves the multilinear extension of the prover's private witness vector . (We develop multilinear extensions in Chapter 4.) The verifier can compute the public parts of at the random point, but the witness-dependent part requires a value she does not know.

Sum-check has reduced the exponential sum to a single evaluation, but the verifier cannot complete the check alone. The protocol needs one more ingredient: a way for the prover to credibly reveal a single evaluation of a polynomial she committed to earlier, without revealing the polynomial itself.

This ingredient is a polynomial commitment scheme (PCS), developed in Chapter 9. The mechanism is simple in outline:

  1. Before sum-check begins, the prover sends a short, binding commitment to the witness polynomial. "Binding" means the prover cannot change the polynomial after sending .

  2. During sum-check, the random challenges are determined. The commitment was sent before any challenges were chosen, so the prover cannot adapt the polynomial to the challenge point.

  3. After sum-check, the prover opens the commitment at : she provides the evaluation together with a proof that is consistent with . The verifier checks and uses to complete the final sum-check check.

This is where sum-check transitions from an information-theoretic protocol (what the literature calls an "Interactive Oracle Proof") to a cryptographic argument. The PCS replaces oracle access with commit-then-open. The prover is bound to a specific polynomial before seeing the evaluation point, and the succinctness of the commitment means the verifier never needs to see the full polynomial.

Every sum-check-based SNARK makes this transition at the final step. The oracle is not a black box. It is a commitment scheme.

Key Takeaways

  1. The sum-check protocol verifies exponential sums efficiently: A prover can convince a verifier that with the verifier doing only work, plus one evaluation of . The verifier never computes any sum herself.

  2. Claim reduction is the key mechanism: Each round reduces a claim about points to a claim about points. After rounds, the exponential sum becomes a single evaluation.

  3. Lies propagate and amplify: A false initial claim forces the prover to send dishonest polynomials. Random challenges catch the discrepancy with probability per round. The lie can't hide; it gets cornered.

  4. The degree bound is essential: Without it, a cheating prover could craft high-degree polynomials that pass consistency checks at 0 and 1 while matching the honest polynomial elsewhere. The degree bound forces rigidity.

  5. Arithmetization connects sum-check to computation: Problems like #SAT encode as sums over the boolean hypercube. The prover does work; the verifier does . This asymmetry is what makes verification useful.

  6. The final evaluation is the bridge to cryptography: Sum-check reduces an exponential sum to one evaluation. When that evaluation depends on the prover's private data, a polynomial commitment scheme (Chapter 9) closes the gap: the prover commits before seeing the challenge point, then opens at the end. This is the "last mile" that turns the information-theoretic protocol into a SNARK.

  7. Sum-check is foundational: IP = PSPACE, GKR, Spartan, Lasso, and most multilinear SNARKs build on sum-check. The protocol's comeback in practical systems (Chapter 19) shows it wasn't just theoretically elegant but practically powerful.

Chapter 4: Multilinear Extensions

In 1971, the Mariner 9 probe became the first spacecraft to orbit another planet. Its mission: map the surface of Mars. But transmitting high-resolution images across 100 million miles of static-filled space was a nightmare. A single burst of cosmic noise could turn a crater into a glitch.

NASA didn't send raw pixels. They used a code developed years earlier by Irving Reed and David Muller: treat the pixel data as evaluations of a multivariate polynomial. The Reed-Muller code could correct up to seven bit errors per 32-bit word. When Mariner 9 arrived to find Mars engulfed in a planet-wide dust storm, mission control reprogrammed the spacecraft from Earth and waited. When the dust cleared, the code delivered 7,329 images, mapping 85% of the Martian surface.

Why not Reed-Solomon? In Chapter 2, we encoded values as a univariate polynomial of degree . That works when is modest. But Mariner's data was indexed by bit positions: a 32-bit word has bit combinations, a memory address space has locations, a boolean formula with 100 variables has possible assignments. Encoding values as a univariate polynomial means degree . Impossible.

The solution: let each bit be its own variable. A 100-bit index becomes 100 coordinates, each 0 or 1. The polynomial has 100 variables instead of degree . Data lives not on a line but on a hypercube. This chapter develops that theory.


In Chapter 2, we turned data into polynomials via Lagrange interpolation: given values, construct the unique degree- univariate polynomial passing through them. That was interpolation over a line.

Now we need interpolation over a hypercube. The data lives at vertices, indexed by bit strings. The polynomial must agree with the data at these vertices and extend smoothly to all of . The construction is analogous to univariate Lagrange, but the geometry is different, and the efficiency implications are dramatic.

This chapter develops the theory of multilinear extensions: the canonical way to extend functions from the Boolean hypercube to polynomials over . These extensions are the workhorses of sum-check-based proof systems, encoding everything from circuit wire values to constraint satisfaction.

The Boolean Hypercube

Consider the set , all -bit binary strings. This is the Boolean hypercube, and it contains exactly points.

n = 2:
    (1,1)
     /  \
 (0,1)  (1,0)
     \  /
    (0,0)

n = 3: A cube with 8 vertices

Any function assigns a field element to each vertex of this hypercube. There are vertices, so is essentially a table of values.

For example:

  • A vector can be viewed as where converts the bit string to an index

  • The output values of a layer of circuit gates

  • A database of records indexed by -bit keys

Why does the hypercube matter? Because computation is fundamentally boolean. A memory address is a bit string. A circuit's inputs are bits. A satisfying assignment to a boolean formula is a point in . When we want to verify a computation, the objects we care about (wire values, memory contents, constraint satisfaction) are naturally indexed by binary strings. The hypercube is where computational problems live.

But polynomials live over fields, not just . We want a polynomial that agrees with on the hypercube but extends smoothly to all of . This extension is what lets us apply the algebraic machinery (Schwartz-Zippel, sum-check) that makes verification efficient.

Why Multilinear?

In Chapter 2, we used univariate polynomials (Reed-Solomon). Why switch to multivariate now?

The problem with univariate encoding is degree: if you encode data points into a single-variable polynomial , that polynomial has degree about one million. Manipulating degree-million polynomials is expensive, requiring heavy FFT operations.

Multilinear polynomials avoid this. If you encode the same points into a 20-variable multilinear polynomial, the degree in each variable is just 1. The total degree is only 20. By increasing the number of variables, we drastically lower the per-variable degree. This tradeoff (more variables, lower degree) enables the linear-time prover algorithms that power modern systems like HyperPlonk and Lasso, avoiding the expensive FFTs required by univariate approaches.

A polynomial in variables has terms like with various exponents. The degree in variable is the maximum exponent of across all terms.

A polynomial is multilinear if its degree in every variable is at most 1. Every term looks like a product of distinct variables (or subsets thereof). We write (with a tilde) to denote the multilinear extension of a function :

For example, with :

There are possible subsets , hence coefficients. A multilinear polynomial in variables is fully specified by numbers, exactly matching the number of points in the hypercube.

This is not a coincidence. It's the key theorem:

Theorem (Multilinear Extension). For any function , there exists a unique multilinear polynomial such that for all .

The function is called the multilinear extension (MLE) of .

Constructing the Multilinear Extension

The theorem claims uniqueness. How do we actually construct ?

The Lagrange Basis

For each point , define the Lagrange basis polynomial:

Here is a fixed boolean vector, where each . You can read as the binary representation of an index from 0 to , addressing one of the vertices of the hypercube. Meanwhile is a vector of formal variables where each ranges over all of . Geometrically, lives at a corner of the unit hypercube, while can be any point in , including points "between" corners. The polynomial is defined over all of , but it has a special property on the hypercube: it equals 1 at and 0 at every other boolean point.

To see why, consider what happens at point :

  • If : the factor is , which evaluates to

  • If : the factor is , which evaluates to

Every factor equals 1, so .

At any other point :

  • There exists some coordinate where

  • If and : the factor evaluates to

  • If and : the factor evaluates to

One factor is zero, so .

The Extension Formula

The multilinear extension is now simply:

At any hypercube point :

The extension agrees with on the hypercube. Since it's a sum of multilinear terms (each is multilinear), is multilinear.

Uniqueness

Claim: If a multilinear polynomial vanishes on all of , then .

Proof by induction on :

Base case (): A multilinear polynomial in one variable has form . If and , then and , so . Thus .

Inductive step: Write where are multilinear in variables. Evaluating at : . Since vanishes on all of , in particular vanishes on . By induction, . Similarly, vanishes on , so . Thus .

Corollary: If two multilinear polynomials agree on , their difference vanishes there, hence is identically zero, so they are equal.

The Equality Polynomial

One Lagrange basis polynomial deserves special attention: the equality polynomial.

This is the MLE of the equality function:

for .

The Lagrange basis polynomials are just the equality polynomial with one input fixed:

The equality polynomial appears constantly in sum-check-based protocols, through the identity:

This follows directly from the Lagrange formula: . Summing weighted by over the hypercube gives the MLE of evaluated at . This means evaluating an MLE at a random challenge reduces to a sum-check on .

This immediately gives a powerful zero test. Suppose the verifier wants to check that vanishes on the entire Boolean hypercube. By the identity above, checking that all values are zero is the same as checking that . The verifier picks a random and runs sum-check on:

This is a random linear combination of all values. If truly vanishes on the hypercube, then (by the uniqueness theorem above), so the sum is always 0. If even one value , then is a nonzero multilinear polynomial, and Schwartz-Zippel guarantees with probability at least . Over a 254-bit field, this is negligible. This "zero-on-hypercube" test is the foundation of Spartan and related sum-check-based proof systems.

Worked Example: A 2-Variable Function

Let's trace through a complete example.

Consider defined by the table:

The Lagrange basis polynomials are:

The multilinear extension is then:

Expanding:

We can verify this matches the table:

  • (matches)

  • (matches)

  • (matches)

  • (matches)

What happens at a non-boolean point? Evaluating at :

This value has no "meaning" on the hypercube; isn't a Boolean point. But this is exactly what we want: the polynomial is defined everywhere, and random evaluation is the key to probabilistic verification.

Efficient Evaluation

Given the table of values and a query point , how fast can we compute ?

The naive approach sums over all terms:

Each takes to compute. Total: .

We can do better with streaming evaluation. is computable in time with the following observation.

Define as the "partial extension" using only the first variables of :

At : (the original table).

At : (a single value).

The recursion from to :

Each step halves the table size. Total work: .

This is linear in the table size, optimal for any algorithm that must touch all values.

Worked Example: Streaming Evaluation

Let's trace through this algorithm with our earlier function :

We want to compute at the point .

Step 0: Initialize

is just the original table, a function of both variables:

Think of it as four values indexed by :

Step 1: Compute by "folding in"

The recursion says:

This is a weighted combination of the two rows, using and :

The table has shrunk from 4 values to 2 values: .

Step 2: Compute by "folding in"

The table has shrunk from 2 values to 1 value. This single value is .

We can verify using the explicit formula :

This works because the Lagrange basis polynomial factorizes into independent pieces, one per coordinate:

where and are univariate selectors. This factorization holds because the multilinear Lagrange formula is a product over coordinates:

Each factor depends only on one coordinate of and one coordinate of . So evaluating at gives a product of independent terms.

The algorithm exploits this factorization. The MLE evaluation is:

Rearranging the sum (grouping by ):

The inner sum is exactly what Step 1 computes: for each value of , it combines the two cases using weights and . The result has half as many entries. Step 2 then folds in the weights similarly.

An analogy helps here: think of a single-elimination tournament with players. In each round, pairs compete and half are eliminated. After rounds, one champion remains. The streaming algorithm works the same way: table entries enter, each round uses a random weight to combine pairs, and after rounds a single evaluation emerges. The tournament bracket is the structure of multilinear computation.

This pattern of using a random challenge to collapse pairs of values and halving the problem size will reappear throughout this book. In Chapter 10 (FRI), we'll name it folding and see it as one of the central techniques in zero-knowledge proofs.

Code: Streaming MLE Evaluation

The algorithm above translates directly to code. Each coordinate of folds the table in half.

def mle_eval(table, r):
    """
    Evaluate the multilinear extension of `table` at point `r`.

    Args:
        table: List of 2^n field elements (the function values on hypercube)
        r: Tuple of n coordinates (r_1, ..., r_n)

    Returns: The value of the MLE at r
    """
    T = table.copy()

    for r_i in r:
        half = len(T) // 2
        # Fold: T'[j] = (1 - r_i) * T[2j] + r_i * T[2j+1]
        T = [(1 - r_i) * T[2*j] + r_i * T[2*j + 1]
             for j in range(half)]

    return T[0]  # Single value remains

# Example from the worked example above
table = [3, 7, 2, 5]  # f(0,0)=3, f(0,1)=7, f(1,0)=2, f(1,1)=5
r = (0.4, 0.7)

result = mle_eval(table, r)
print(f"Streaming: MLE({r}) = {result}")

# Verify against explicit formula: f(X1,X2) = 3 - X1 + 4*X2 - X1*X2
explicit = 3 - 0.4 + 4*0.7 - 0.4*0.7
print(f"Explicit:  MLE({r}) = {explicit}")

Output:

Streaming: MLE((0.4, 0.7)) = 5.12
Explicit:  MLE((0.4, 0.7)) = 5.12

The streaming algorithm touches each table entry exactly once. For a table of size , total work is .

Tensor Product Structure

The factorization we used in the streaming algorithm generalizes to any number of variables. For :

where and .

This is a tensor product structure. To see what this means concretely, consider . Define the vectors:

Their tensor product is the matrix (or equivalently, length-4 vector) of all pairwise products:

Reading off the entries: . The tensor product is the vector of Lagrange evaluations.

For general , the vector of all Lagrange evaluations is:

The streaming algorithm exploits this tensor structure. Instead of computing all Lagrange values (expensive), it processes one coordinate at a time, folding the tensor product incrementally. This is why MLE evaluation costs instead of . The same tensor structure enables:

  • Efficient prover algorithms for sum-check (Chapter 19)

  • Recursive proof constructions

  • Memory-efficient streaming over large tables

Multilinear Extensions of Functions on Larger Domains

What if our function isn't defined on ?

Suppose for some . We can interpret the domain as via binary encoding:

Any function on a power-of-two domain has a natural multilinear extension.

For domains not of size , we can pad with zeros or use more sophisticated encodings. The key insight: as long as the domain is finite, we can always encode it in binary and take the MLE.

Connection to Sum-Check

The sum-check protocol (Chapter 3) proves claims of the form:

for some polynomial . When is the multilinear extension of a function , this sum equals , the sum of all function values on the hypercube.

As an example, suppose we want to prove that a vector with sums to a claimed value .

Let be the MLE encoding the vector. Then:

Sum-check verifies this identity without the verifier seeing all of . The protocol reduces the sum to a single random evaluation , which the prover supplies (with a commitment proof).

This is the bridge from "data" to "proof": encode data as an MLE, verify properties via sum-check, bind via polynomial commitment.

Evaluations and Coefficients

A perspective that clarifies many constructions:

A multilinear polynomial has coefficients (the values in the monomial expansion ). These coefficients live in an abstract "coefficient space."

But also has evaluations on the hypercube. These evaluations are just , the original table values you started with.

These are not the same numbers. The table entry in our worked example is not a coefficient of the polynomial. The polynomial has coefficients , while the table values are . They're related by the Lagrange interpolation formula.

For multilinear polynomials, the evaluation table is a complete description. You can recover coefficients from evaluations and vice versa. They're just two bases for the same -dimensional vector space.

The transformation between bases is exactly the Lagrange interpolation formula and its inverse. Both can be computed in time.

This means:

  • Committing to a multilinear polynomial = committing to its evaluation table

  • Evaluating at a random point = a linear combination of table entries

  • Sum-check over an MLE = verifying global properties through local queries

The table has entries. The verifier touches of them. The polynomial is what bridges the gap: it's a compressed representation that can be probed at random points, and those random probes reveal whether the full table satisfies the claimed property. Extension creates redundancy; redundancy enables compression; compression enables succinctness.

Polynomial Evaluation as Inner Product

There's a beautiful way to see this algebraically: polynomial evaluation is an inner product.

For a multilinear polynomial, the evaluation at any point is:

where is the table of values and is the vector of Lagrange basis evaluations at .

This linear algebra perspective is surprisingly powerful. For decades, sum-check was seen as a beautiful theoretical result with limited practical use. Then came the realization: polynomial evaluation is an inner product, and inner products interact beautifully with commitment schemes. No FFTs, no trusted setups, just vectors and dot products. Systems like Spartan, HyperPlonk, and Lasso all exploit this insight. Chapter 19 tells the full story of this "Sum-Check Renaissance."

The consequences are immediate:

  • Commitment: Committing to means committing to the vector
  • Evaluation proof: Proving means proving an inner product claim
  • The verifier knows : Given , anyone can compute the Lagrange evaluations

This reduces polynomial evaluation proofs to inner product proofs, and inner products interact beautifully with homomorphic commitments. We'll exploit this connection in Chapters 6 and 9.

Key Takeaways

  1. The Boolean hypercube is the natural domain for multilinear polynomials. It has points.

  2. Multilinear extension (MLE): The unique polynomial of degree at most 1 in each variable that agrees with on the hypercube.

  3. Lagrange basis polynomials equal 1 at and 0 elsewhere. The MLE is .

  4. The equality polynomial is the MLE of the equality indicator. Lagrange bases are .

  5. Tensor product structure: . The basis factorizes, enabling fast algorithms.

  6. Efficient evaluation: Given the table and a point, compute the MLE in time via streaming.

  7. Sum over the hypercube: . Sum-check verifies such sums efficiently.

  8. Evaluations = coefficients: For MLEs, the table of values completely determines the polynomial. They're dual representations.

  9. Binary encoding: Any function on can be encoded as a function on , then extended multilinearly.

  10. The bridge to proofs: MLEs encode data; sum-check verifies properties; polynomial commitment binds the prover. This trinity underlies sum-check-based SNARKs.

Chapter 5: Univariate Polynomials and Finite Fields

Gauss discovered the Fast Fourier Transform in 1805. He needed to predict the orbits of asteroids Pallas and Juno, so he wrote an algorithm that computed transforms in time instead of . He wrote it in Latin, in a notebook, and never published it. The algorithm waited 160 years for someone to notice.

Cooley and Tukey rediscovered it in 1965. They gave it a name. It became one of the most important algorithms in computing: MRI machines, audio compression, the entire edifice of digital signal processing. All of it built on mathematics that had been sitting, unread, in the papers of a man who died in 1855.

Why does the same algorithm keep appearing? Because the symmetries of roots of unity make it inevitable. Once you see the structure, the algorithm writes itself. Those symmetries now power zero-knowledge proofs.

This chapter develops the univariate polynomial paradigm: finite fields, roots of unity, and the techniques that make systems like Groth16, PLONK, and STARKs possible. Where Chapter 4 explored multilinear polynomials over the Boolean hypercube, here we explore a single variable of high degree over a very different domain.

Finite Fields: The Algebraic Foundation

Zero-knowledge proofs live in finite fields. Not the real numbers, not the integers; finite fields, where arithmetic wraps around and every division is exact.

A finite field consists of the integers with arithmetic modulo a prime . Addition and multiplication work as usual, then you take the remainder when dividing by :

The magic is in division. Every nonzero element has a multiplicative inverse: this is guaranteed because is prime. (More generally, finite fields exist for any prime power , but prime fields are the simplest case.) In , we have because . You can divide by any nonzero element, and the result is exact (no fractions, no approximations).

This is why we call it a field. A ring (like the integers ) lets you add, subtract, and multiply. A field lets you also divide. The integers are not a field because isn't an integer. But in , division always works: , since .

The nonzero elements form a cyclic group under multiplication. This is fundamental: there exists a generator such that every nonzero element is some power of .

Example in : The element generates everything:

Power

Every nonzero element appears exactly once. The powers cycle through all of before returning to 1.

For cryptographic applications, we use primes of 256 bits or more. The field is vast, roughly elements, making exhaustive search impossible.

Roots of Unity

Because is cyclic of order , it contains subgroups of every order dividing . The most useful are the roots of unity.

An element is an -th root of unity if . It's a primitive -th root if additionally for any : the smallest positive power that gives 1 is exactly .

If is a primitive -th root, the complete set of -th roots is:

This is a subgroup of order . It's the evaluation domain that powers univariate-based SNARKs.

Worked Example: Fourth Roots in

Take . The multiplicative group has order . Since divides , fourth roots of unity exist.

Is a primitive fourth root?

Yes. The fourth roots of unity are:

Notice the structure: and are negatives of each other, as are and . This is not a coincidence.

The Symmetries

Roots of unity have two key symmetries that enable fast algorithms.

Symmetry 1: Squaring Halves the Group

When is even:

Why is this true? Start with the defining property: . Taking the square root of both sides: . So is a square root of 1. In any field, the square roots of 1 are exactly and . But because is primitive: its first power to equal 1 is , not . Therefore .

This has a remarkable consequence. If you square every element of :

The squares form the -th roots of unity. And since , each square root of unity appears exactly twice.

In : Squaring the fourth roots :

The squares are : the square roots of unity, each appearing twice.

Symmetry 2: Opposite Elements are Negatives

Elements half a cycle apart are negatives:

In :

  • and
  • and

These two symmetries, squaring halves the group and opposites are negatives, are the engine of the Fast Fourier Transform.

The DFT Is Polynomial Evaluation

Here is one of those facts that seems almost too good to be true.

The Discrete Fourier Transform (DFT) is defined as a matrix-vector multiplication. Given a vector , the DFT produces a new vector whose -th entry is:

where is a primitive -th root of unity.

If you've seen the continuous Fourier transform, this is the same idea. The continuous version projects a function onto via integration, measuring how much of each frequency is present. Here, the integral becomes a sum, and the exponentials become -th roots of unity: , equally spaced points on the unit circle. The projection interpretation is identical. You're decomposing a signal into frequency components; the discretization just replaces integration with summation.

Now look at polynomial evaluation. Given a polynomial , evaluate it at :

They are identical. The DFT of the coefficient vector is the evaluation vector at roots of unity. This is not a useful analogy or a computational trick. It is a mathematical identity.

The FFT, then, is not "like" converting between polynomial representations. It is converting between polynomial representations. Coefficient form and evaluation form are the two natural bases for the same vector space, and the DFT matrix is the change-of-basis matrix. The FFT is the fast algorithm for this change of basis, made possible by the recursive structure of roots of unity.

This is why the same algorithm appears in signal processing, image compression, and zero-knowledge proofs. They are the same mathematical operation in different disguises.

Two Representations of Polynomials

A polynomial of degree less than can be viewed in two ways.

Coefficient form: The polynomial is stored as its coefficients.

Evaluation form: The polynomial is stored as its values at distinct points. Using the -th roots of unity:

These two forms carry exactly the same information. A polynomial of degree less than is uniquely determined by its values at any points (this is Lagrange interpolation). The coefficient form and evaluation form are just two different coordinate systems for the same object.

Why care about evaluation form? In zero-knowledge proofs, constraints are naturally expressed as evaluations. Gate must satisfy some relation; this becomes: the constraint polynomial must equal zero at . The evaluation form directly represents these constraints.

Polynomial Evaluation as Inner Product

In Chapter 4, we saw that evaluating a multilinear polynomial is an inner product: . The same structure appears for univariate polynomials, in two forms.

In coefficient form:

where is the coefficient vector and is the "powers of " vector.

In evaluation form, the same polynomial can be written via Lagrange interpolation:

where is the evaluation vector and is the vector of Lagrange basis evaluations. Each is the unique degree- polynomial that equals 1 at and 0 at all other roots of unity (Chapter 2). We'll see a cleaner closed form for roots of unity later in this chapter.

Either way, polynomial evaluation is an inner product. Committing to a polynomial reduces to committing to a vector; proving an evaluation reduces to proving an inner product claim.

The difference from Chapter 4 is computational. For multilinear polynomials, the Lagrange basis factors beautifully: Each term depends on one coordinate; the product of terms costs per basis element. With basis elements, streaming through all of them takes total.

For univariate polynomials, no such factorization exists. Each is a product of terms that all depend on the same variable . Computing one basis element costs ; computing all of them naively costs . The FFT is what rescues us.

We'll exploit the inner product connection extensively in Chapter 9.

Two ways to commit: This duality (coefficient form vs evaluation form) manifests directly in polynomial commitment schemes:

  • KZG (Chapter 9) commits in coefficient form: . The commitment encodes "evaluate the coefficients at a secret point ."

  • FRI (Chapter 10) commits in evaluation form: a Merkle tree over . The commitment is a hash of all the evaluations.

The FFT is what makes these equivalent: you can convert between representations in time. But the choice of representation affects everything: proof size, prover cost, setup requirements, and the algebraic tricks available for verification.

The Fast Fourier Transform

Converting between coefficient and evaluation form naively takes operations: you'd compute each of evaluations, each requiring work.

The Fast Fourier Transform (FFT) does it in . This speedup is essential; without it, the polynomials in modern proof systems would be computationally intractable.

The FFT exploits the symmetries of roots of unity through divide-and-conquer.

The Core Idea

Split a polynomial into its even and odd terms:

where:

  • (even-indexed coefficients)
  • (odd-indexed coefficients)

Both have half the degree of .

Now, when we square the -th roots of unity, we get the -th roots (each appearing twice). So to evaluate at all of , we:

  1. Recursively evaluate and at the -th roots
  2. Combine the results

The combination uses the antisymmetry property:

Proof of first equation: By definition, . Substituting : .

Proof of second equation: Substitute : . The even part is unchanged (squaring kills the sign); the odd part flips sign.

Two evaluations of from one evaluation each of and : the same work computes both, with just an addition versus subtraction.

Worked Example: 4-Point FFT

Evaluate at in .

Split:

  • (coefficients , )
  • (coefficients , )

Evaluate on (the square roots of unity):

Combine using , , , :

Result: .

Verification: . Correct.

The inverse FFT, going from evaluations back to coefficients, uses the same algorithm with instead of and a factor of .

A note on terminology. When the FFT operates over a finite field rather than the complex numbers, it is called the Number Theoretic Transform (NTT). The algorithm is identical. The only difference is the domain: complex roots of unity become finite-field roots of unity . Every FFT computation in this book is technically an NTT. Implementation papers and libraries (gnark, arkworks, Plonky2) use "NTT" exclusively, so recognizing the equivalence matters when moving from theory to code.

The Vanishing Polynomial

Here is the central insight of univariate arithmetization.

The vanishing polynomial of a set is:

For the -th roots of unity, this simplifies dramatically:

Proof: By definition, means , so every element of is a root of . Since and has degree , these are all the roots. By the factor theorem, .

The key theorem: A polynomial vanishes at every point of if and only if divides .

Proof: () If , then for any : .

() If for all , then each divides . Since the are coprime (distinct linear factors), their product divides .

This is the compression at the heart of univariate SNARKs:

  1. Encode constraints as: " for all "
  2. This is equivalent to: " divides "
  3. Which is equivalent to: "There exists such that "

One polynomial divisibility condition captures separate constraint checks.

The Divisibility Check

How do we verify divisibility efficiently?

The prover computes the quotient and commits to it. The verifier picks a random challenge and checks:

If as polynomials, this equation holds for all , including the random one.

If , their difference is a nonzero polynomial. By Schwartz-Zippel, a random catches this disagreement with probability at least , where is the degree.

One random check. constraints verified. This is the magic.

Lagrange Interpolation over Roots of Unity

We saw earlier that the Lagrange basis is the polynomial that equals 1 at and 0 at all other roots. For roots of unity, this product simplifies to a closed form:

Why does this work? The numerator vanishes at all -th roots of unity. Dividing by removes the zero at , leaving a polynomial that vanishes at all roots except . The prefactor normalizes so that .

Worked example: Let in . Here is a primitive 4th root of unity: , , , . The roots are .

For , the polynomial that equals 1 at and 0 at :

In , we have (since ), so .

Factor over . Dividing out :

Check at : . ✓

Check at : . ✓

The polynomial passing through points is then .

Cosets: Shifting the Domain

Lagrange interpolation just did something powerful: it extended values defined on (the roots of unity) to a polynomial defined on all of . This is the univariate analog of multilinear extension from Chapter 4. There, we extended a function on the Boolean hypercube to all of . Here, we extend a function on roots of unity to all of .

But sometimes we need more than just extension. We need structured evaluation points outside . Cosets provide exactly this.

If is any nonzero field element, then:

is a coset of . It's a "shifted" copy: new points, disjoint from .

Worked example: In , let (a primitive 4th root: , , ). The subgroup is .

Take . The coset is . The two sets are disjoint, giving 8 evaluation points.

The key property: to evaluate on , you don't need a new algorithm. If , then evaluating at is the same as evaluating at . Scale the coefficients by powers of , then run the standard FFT on . Cosets give you new evaluation domains for free.

Why cosets matter in ZK: Several proof systems crucially depend on cosets:

  • PLONK's permutation argument: Uses multiple cosets to encode wire positions. If you have gates with 3 wires each (, , ), PLONK encodes them on , , and (three disjoint domains of size each). This lets the permutation polynomial distinguish "wire of gate 5" from "wire of gate 5."

  • FRI's low-degree testing: The prover evaluates on a domain larger than the polynomial's degree (for "rate" or "blowup"). Using doubles the evaluation domain while maintaining FFT structure.

  • Quotient degree management: If has degree but we've only committed to evaluations on (size ), we need more points to pin down the quotient. Using gives points (enough to determine a polynomial of degree less than ).

The FFT works on cosets too: just multiply each root of unity by before running the algorithm.

The Quotient Argument

The divisibility check above verified vanishing on a set of points (all of ). The quotient argument is the single-point version: prove that for a committed polynomial .

The factor theorem says: if and only if divides .

The prover computes:

If , this is a polynomial. If not, the division has a remainder; isn't a polynomial.

The verifier checks the polynomial identity:

at a random point. This is the foundation of KZG opening proofs (Chapter 9).

Univariate vs. Multilinear

We now have two paradigms for polynomial proofs:

AspectMultilinearUnivariate
Variables variables, degree 1 each1 variable, degree
DomainBoolean hypercube Roots of unity
Size points points
Constraint encodingSum over hypercubeDivisibility by
Key algorithmRecursive halvingFFT
Prover cost (linear) (quasi-linear)
VerificationSum-check protocolRandom evaluation
SystemsGKR, Spartan, LassoPLONK, Marlin, STARKs

Both achieve the same essential goal: reduce exponentially many constraint checks to a constant number of random evaluations. They're complementary perspectives on the same phenomenon (the rigidity of low-degree polynomials).

A note on Groth16: Groth16 uses univariate polynomials but doesn't require roots of unity; it encodes constraints via QAP (Quadratic Arithmetic Programs) and verifies satisfaction through pairing equations, not divisibility checks at structured domains. Provers can use FFT as an optimization for polynomial arithmetic, but it's not fundamental to the protocol. PLONK and STARKs, by contrast, rely structurally on roots of unity: constraints are encoded as "polynomial vanishes on ," checked via the divisibility pattern described above.

Key Takeaways

  1. Finite fields provide exact arithmetic with every nonzero element invertible. The nonzero elements form a cyclic group.

  2. Roots of unity are elements with . They form a subgroup of size when divides .

  3. The key symmetries: Squaring halves the group; opposite elements are negatives. These enable the FFT.

  4. Two representations: Polynomials can be stored as coefficients or evaluations. The FFT converts between them in time.

  5. The vanishing polynomial captures all roots of unity. A polynomial vanishes on iff divides it.

  6. Constraint compression: constraints "" become one divisibility "", verified by one random check.

  7. Lagrange interpolation over roots of unity has a clean closed form exploiting the structure of .

  8. Cosets extend the domain while preserving FFT-friendliness.

  9. Quotient arguments prove evaluation claims: to show , prove divides .

  10. The FFT exists because of roots of unity. The algorithm is a direct consequence of the symmetries and .

Chapter 6: Commitment Schemes: Cryptographic Binding

In 1981, Manuel Blum posed a simple question: can two people play a fair game of coin-flipping over the telephone?

Blum was working on what cryptographers called Mental Poker: how can two people play a card game over the phone without a trusted dealer? How do I know you didn't shuffle the Aces to the top of the deck? The coin flip was the atomic unit of this problem. Get that right, and you could build up to full card games.

The problem seems impossible. Alice flips a coin and announces "heads." Bob has no way to verify she actually flipped anything. She might have waited to hear his guess first. Or she might change her answer after hearing his response. Without shared physical reality, without a coin both parties can see, how can either trust the outcome?

Blum's solution introduced one of the most fundamental primitives in cryptography. Alice doesn't announce her flip directly. Instead, she first sends a commitment: a cryptographic object that locks in her choice without revealing it. Only after Bob makes his guess does Alice open the commitment, proving what she had chosen all along. The commitment is binding (Alice cannot change her answer after sending it) and hiding (Bob learns nothing until the reveal).

This two-phase structure, commit then reveal, turns out to be exactly what our proof systems need. You've designed a protocol where the prover claims a polynomial evaluates to some value, and you want to check this with random queries. But the prover responds after seeing your challenge. What stops them from constructing a fake polynomial that happens to pass your spot-checks?

This is the binding problem. The verifier's randomness is meant to catch a cheating prover off-guard. But if the prover can adapt their answers after seeing the challenge, they can tailor responses to pass. The polynomial identity testing that underlies our protocols becomes meaningless.

We need a mechanism that forces the prover to fix their polynomial before verification begins.

The Trust Problem Revisited

Consider the sum-check protocol from Chapter 3. The verifier sends random challenges , and the prover responds with univariate polynomials. At the end, the verifier must check that some claimed evaluation matches the actual polynomial. But how does the verifier know the prover didn't just fabricate a polynomial that happens to satisfy the final check?

The issue is subtle. Our soundness proofs assumed the prover is committed to some polynomial before the interaction begins. But in a raw interactive protocol, nothing enforces this. A dishonest prover could:

  1. Wait to see all the verifier's challenges
  2. Work backwards to construct a polynomial that passes
  3. Claim they had this polynomial all along

This attack doesn't violate the information-theoretic soundness of the protocol; it violates the execution model. We assumed a sequential game where the prover moves first; in reality, we need cryptography to enforce this ordering.

The Commitment Paradigm

A commitment scheme solves this problem through a two-phase protocol:

Phase 1 (Commit): The prover publishes a commitment, a short, seemingly random string that binds them to a value without revealing it.

Phase 2 (Reveal): Later, the prover can open the commitment by revealing the original value. Anyone can verify that the revealed value matches the original commitment.

Formal Properties:

  • Binding: Once committed, the committer cannot open to a different value. More precisely, no efficient adversary can find two different values that produce the same commitment.

  • Hiding: The commitment reveals nothing about the committed value. An observer cannot distinguish between commitments to different values.

These properties exist in tension. Perfect binding means each value maps to a unique commitment, but then the commitment might leak information about the value. Perfect hiding means commitments are statistically indistinguishable, but then multiple values might share commitments. Cryptographic schemes typically achieve one property perfectly and the other computationally.

Pedersen Commitments: The Discrete Log Approach

The most elegant commitment scheme comes from a surprising source: the hardness of computing discrete logarithms in cyclic groups.

Setup: Let be a cyclic group of prime order (think of an elliptic curve group). Select two generators and such that nobody knows the discrete logarithm . The public parameters are .

Commit: To commit to a value , the committer:

  1. Chooses a random blinding factor
  2. Computes the commitment

Reveal: To open, the committer reveals . The verifier checks that .

The scheme uses multiplicative notation, but on elliptic curves (the dominant implementation), we write using additive notation.

Why Binding Holds

Suppose Alice commits and later wants to open it as a different value . She needs to find such that:

Rearranging:

This means:

But computing is the discrete logarithm problem! If Alice could find such , she could break DLog in . The binding property holds computationally, as long as discrete log is hard.

Formal reduction: Suppose adversary breaks binding with non-negligible probability, outputting and with and . We construct a DLog solver : given challenge , run to get the two openings, then compute . Note since would otherwise give , implying . Thus solves DLog whenever breaks binding.

Why Hiding Holds

The commitment is perfectly hiding. Here's the key insight: since is uniformly random in , and is a generator of , the term is uniformly distributed over all of .

For any message , the commitment is a uniformly random group element. This means:

The two distributions are identical: not just computationally indistinguishable, but statistically identical. Even an unbounded adversary cannot determine the committed value from the commitment alone.

The Independence Requirement

There's a critical subtlety: the generators and must be independently chosen such that nobody knows .

If Alice knows that for some , she can break binding:

She can open this as for any by computing . The verification passes because:

If Alice knows this relationship , she holds a trapdoor. It allows her to open the commitment to any value she wants. This is why trusted setups in SNARKs are so sensitive: if the creator knows the "toxic waste" (the secret exponents used to generate the parameters), they can forge proofs. We prevent this by generating and from "nothing-up-my-sleeve" numbers like the digits of or by hashing different strings to curve points, ensuring nobody knows the discrete log relationship.

Worked Example: Pedersen Commitment in

Let's trace through a concrete example using the multiplicative group modulo 23.

Setup: Work in , which has order . Take generators and . We assume nobody knows .

Commitment to :

  • Choose random blinding factor
  • Compute

Computing :

Computing :

So .

Verification: Given , the verifier checks:

The commitment opens correctly.

The Homomorphic Property

Pedersen commitments have a remarkable algebraic property: they are additively homomorphic. You can compute on committed values without knowing what they are.

Given two commitments:

Their product is:

This is a valid commitment to with blinding factor !

Worked Example (continuing):

Commit to with :

Computing and .

So .

Homomorphic addition:

This should be a commitment to with blinding factor .

Verification:

For .

For .

So .

It matches .

This property is extraordinarily useful. A verifier can combine multiple commitments, add constants, or compute linear combinations, all without learning the committed values. This enables protocols where computations happen "in the encrypted domain."

Scalar Multiplication

The homomorphic property extends to scalar multiplication. For a constant :

This is a commitment to with blinding factor . The verifier can scale committed values without opening them.

From Scalar to Vector Commitments

The Pedersen scheme naturally extends from committing to a single value to committing to an entire vector. Given independent generators and a blinding generator , we can commit to a vector :

This Pedersen vector commitment is still a single group element, regardless of the vector length. The homomorphic property extends: adding two vector commitments yields a commitment to the component-wise sum.

But here's where things get interesting for our purposes. Recall from Chapters 4 and 5 that a polynomial evaluation is just an inner product:

where are the coefficients and is the evaluation vector.

If we commit to the coefficient vector using a Pedersen vector commitment, we've effectively committed to the polynomial itself. The verifier knows the evaluation point , so they know . The prover knows . Proving that a claimed value equals becomes an inner product argument: the prover convinces the verifier that their committed vector has the right inner product with the public vector .

Homomorphism alone doesn't give us inner products. But it's a key ingredient: inner product arguments (like Bulletproofs) use the additive homomorphism to recursively fold commitments, shrinking the problem size logarithmically. The commitment structure enables the protocol; additional machinery makes it work.

This observation, that polynomial evaluation reduces to inner product, is the conceptual bridge from simple commitments to full polynomial commitment schemes. We'll cross that bridge in Chapter 9.

Proving Knowledge of an Opening

A commitment alone proves nothing; the prover must eventually reveal the opening to be useful. But what if we want to prove we know a valid opening without revealing it?

This is where -protocols (Chapter 16) enter the picture. A prover who knows the opening for a commitment can convince a verifier they know it without revealing the values. This is a proof of knowledge: the prover demonstrates possession of the witness , not a property of .

Setup: The prover has committed , where is the secret message and is the blinding factor. Both are hidden. The prover wants to prove they know without revealing either.

The protocol follows the classic three-move structure:

Round 1 (Commit to randomness): The prover picks random and sends .

Round 2 (Challenge): The verifier sends a random challenge .

Round 3 (Response): The prover computes:

and sends .

Verification: The verifier checks:

Why it works: Expanding the right side:

The equation holds if the prover knows .

Why it's zero-knowledge: The values and look random because they're masked by the truly random and . The verifier learns nothing about or beyond the fact that the prover knows them.

Why it's sound: A prover who doesn't know cannot answer two different challenges and consistently. Given two accepting transcripts with the same but different challenges, one can extract the witness; this is the "special soundness" property.

Worked Example: Proof of Knowledge

Continuing our example with , in , suppose the prover committed and claims to know the opening.

Prover's commitment:

  • Choose random ,
  • Compute
  • ,

Verifier's challenge:

Prover's response (recall , ):

  • (arithmetic mod group order)

Verification:

  • Left side:

    • (since )
    • . We have , ,
    • Left side:
  • Right side:

    • Right side:

Both sides equal 8. The proof verifies.

Beyond Pedersen: A Landscape of Commitment Schemes

Pedersen commitments are beautiful but not the only option. Different commitment schemes offer different trade-offs:

Hash-Based Commitments: Commit as where is a cryptographic hash. Binding follows from collision resistance; hiding follows from the hash acting as a random oracle. These are simple and quantum-resistant, but they lack the homomorphic property.

Polynomial Commitments: The heart of modern SNARKs. Instead of committing to a single value, we commit to an entire polynomial and can later prove evaluations at arbitrary points. Chapter 9 explores KZG (using pairings) and IPA (using discrete log) in depth.

ElGamal-style Commitments: Related to encryption, where the commitment can be "decrypted" with a secret key. Useful in some multi-party protocols.

Each scheme involves trade-offs between:

  • Setup: Does it require a trusted setup?
  • Assumptions: Discrete log? Pairings? Hashes?
  • Efficiency: Commitment size, proof size, computation time
  • Properties: Homomorphic? Additively? Multiplicatively?
  • Quantum resistance: Will it survive quantum computers?

Why Commitments Matter for ZK Proofs

We opened this chapter with the binding problem: how do we ensure the prover doesn't cheat by choosing their polynomial after seeing the verifier's challenges?

Commitment schemes provide the answer through the commit-and-prove paradigm:

  1. Commit phase: Before any interaction, the prover commits to their polynomial (or the witness encoding it).

  2. Interaction phase: The verifier sends challenges, the prover responds. But the prover's polynomial was fixed in step 1.

  3. Opening phase: At the end, the prover opens relevant parts of their commitment. The verifier checks consistency.

The binding property ensures the prover cannot change their polynomial mid-protocol. The hiding property ensures the commitment itself doesn't leak information about the witness. Every modern SNARK (Groth16, PLONK, STARKs) follows this pattern, varying only in which commitment scheme they use (KZG for Groth16/PLONK, Merkle trees for STARKs).

The Hiding-Binding Tradeoff

There's a fundamental tension in commitment schemes that deserves attention: you cannot have both perfect hiding and perfect binding simultaneously.

Perfect binding means each commitment corresponds to exactly one value: no two distinct messages ever produce the same commitment. This is an information-theoretic guarantee: even with unlimited computation, opening to a different value is impossible.

Perfect hiding means the commitment reveals nothing about the value: all messages produce statistically indistinguishable commitment distributions. Again, this is information-theoretic: even unbounded adversaries learn nothing.

Why can't we have both? Consider what each requires:

  • Perfect binding needs the commitment function to be injective (one-to-one). Every value maps to a unique commitment.

  • Perfect hiding needs all commitments to look identical regardless of the input. The commitment must be independent of the value.

These requirements conflict. Perfect hiding means the distributions and are identical for all messages . But if the distributions are identical, every commitment value must be reachable from both and (otherwise we could distinguish them). So there exist openings and that both produce . Binding is broken.

The resolution: Relax one property to computational rather than information-theoretic.

An information-theoretic (or statistical, or perfect) guarantee holds against adversaries with unlimited computational power. No amount of computation can break it. A computational guarantee holds only against efficient (polynomial-time) adversaries. An unbounded adversary could break it, but doing so requires solving a problem believed to be hard (like discrete log or factoring).

The tradeoff:

  • Perfectly hiding, computationally binding: Pedersen commitments. As we proved earlier, for any message there exists an that produces any given commitment, so an unbounded adversary cannot determine which value is inside. But finding two openings requires solving discrete log, so binding holds against efficient adversaries. Even an all-powerful being cannot tell which value is committed (perfect hiding), but a quantum computer could eventually break the lock (computational binding).

  • Perfectly binding, computationally hiding: Hash-based commitments . A hash function is deterministic: each pair maps to exactly one commitment, and collision resistance means you cannot find two pairs that collide. The value is locked in tight (perfect binding). But an unbounded adversary could brute-force all possible inputs to find (computational hiding).

This tradeoff shapes the design space. For ZK proofs, we typically want hiding (don't reveal the witness) and accept computational binding (secure against poly-time adversaries). Pedersen commitments are the natural choice: the witness stays perfectly hidden, and binding holds as long as discrete log is hard.

Looking Ahead

We've established the cryptographic primitive that makes succinct proofs possible. Commitments transform interactive protocols, where timing and ordering are honor-system, into cryptographically enforced games where cheating is computationally infeasible.

In Chapter 9, we'll see how polynomial commitment schemes (KZG, IPA, and FRI) extend these ideas to commit to polynomials and prove evaluations. These are the engines that power modern SNARKs.

But first, we need to understand what we're proving. Chapter 7 introduces the GKR protocol, which uses sum-check to verify layered arithmetic circuits. And Chapter 8 shows how arbitrary computations become circuits, which become polynomials. Together, these chapters complete the story of how a computation becomes a succinct proof.

Key Takeaways

  1. The binding problem: Interactive proofs need cryptographic enforcement to prevent provers from adapting their answers to verifier challenges.

  2. Commitment = seal: A commitment locks in a value before revealing it. Binding ensures it can't change; hiding ensures it reveals nothing.

  3. Pedersen commitments: achieves perfect hiding and computational binding (from discrete log hardness). The generators and must have unknown discrete log relationship, or binding fails.

  4. Homomorphic structure: Pedersen commitments allow addition in the committed domain ( commits to ), and extend naturally to vectors. Committing to a coefficient vector effectively commits to a polynomial.

  5. Proof of knowledge: Sigma protocols let a prover demonstrate they know a commitment's opening without revealing it.

  6. Commit-and-prove paradigm: The foundation of all modern SNARKs: commit first, then prove properties of the committed values.

  7. Bridge to polynomial commitments: Polynomial evaluation is an inner product. This connects vector commitments to the polynomial commitment schemes (Chapter 9) that power SNARKs.

Chapter 7: The GKR Protocol: Verifying Circuits Layer by Layer

In 2006, Amazon launched AWS, and the world changed. Companies stopped buying servers and started renting "compute" from invisible data centers. It was efficient, but it created a trust gap. If a bank rents a server to calculate interest rates, how do they know the server isn't buggy, or malicious?

Verifying the computation by re-running it defeats the purpose of outsourcing. You want the cloud to do the heavy lifting, and you want to check the work with the effort of a text message.

In 2008, Shafi Goldwasser, Yael Kalai, and Guy Rothblum published a theoretical solution. They proposed a protocol where a supercomputer could prove a massive calculation to a laptop, and the laptop could verify it in seconds. While it took a decade for hardware and cryptographic engineering to catch up to their math, every modern rollup and scaling solution on Ethereum is spiritually a descendant of that 2008 paper.

The sum-check protocol is versatile. It transforms exponentially large sums ( terms) into verification that runs in time, logarithmic in the sum size. But every application we've seen requires a custom polynomial tailored to that specific problem. Each new computation demands a new arithmetization.

What if we want to verify any computation, not just counting problems? The GKR protocol provides a universal framework for verifying any computation that can be expressed as an arithmetic circuit (which turns out to be everything). Rather than designing a new protocol for each problem, GKR gives us a machine: feed in a circuit, get out an efficient verification protocol.

From Sum-Check to General Computation

Let's understand the conceptual leap. The sum-check protocol verifies claims of the form:

Given a polynomial , it checks whether the claimed sum is correct. The polynomial encodes the problem, and sum-check verifies the encoding.

But computation is more than summation. A real computation involves:

  • Input values
  • Intermediate calculations (additions, multiplications)
  • Data dependencies (the output of one step becomes the input to another)
  • A final output

The insight of GKR is that these computations have layered structure. A circuit consists of gates organized into layers, where each layer's outputs feed into the next layer's inputs. And crucially, the relationship between adjacent layers can be expressed as a polynomial identity: one that sum-check can verify.

Remark (GKR as a chain of sum-checks). GKR is a sequence of sum-checks, each reducing a claim about layer to a claim about layer . This is a special case of a more general pattern: sum-checks composing into directed graphs, where each sum-check is a node and evaluation claims are edges. GKR's graph is a path (linear chain from output to input). More complex protocols like Spartan (Chapter 19) have branching structure: one outer sum-check spawns multiple inner sum-checks. The graph perspective, where depth determines sequential stages and width enables batching, becomes crucial for understanding prover efficiency in Chapters 19-20.

Layered Arithmetic Circuits

GKR operates on layered arithmetic circuits: directed acyclic graphs (graphs with edges that have direction, and no cycles; you can never follow edges back to where you started) where:

  1. Layers: Gates are organized into layers

    • Layer is the input layer
    • Layer is the output layer
    • Wires only connect adjacent layers (from layer to layer )
  2. Gate operations: Each gate performs either addition or multiplication, with exactly two inputs

  3. Indexing: Gates within each layer are numbered using binary strings

    • If layer has gates, we use bits to index them
    • Gate in layer has label

Any circuit can be transformed into this layered form. If a wire spans multiple layers, we insert "pass-through" gates (identity gates that output their input unchanged).

Example Circuit: Let's trace through a simple circuit computing .

flowchart TB
    subgraph L2["Layer 2 (Inputs)"]
        x1["x₁"]
        x2["x₂"]
        x3["x₃"]
    end

    subgraph L1["Layer 1 (Middle)"]
        add["[+]"]
        pass["[pass]"]
    end

    subgraph L0["Layer 0 (Output)"]
        mult["[×]"]
    end

    x1 --> add
    x2 --> add
    x3 --> pass
    add --> mult
    pass --> mult
    mult --> output["output"]

Gate labeling:

  • Layer 2 (inputs): bits needed for 3 gates
    • , ,
  • Layer 1: bit for 2 gates
    • Addition gate , pass-through
  • Layer 0 (output): bit for 1 gate
    • Multiplication gate

The Wiring Predicates

The circuit's structure is encoded by wiring predicates: functions that describe which gates connect to which.

For layer , we define:

For our example circuit, look at layer 0. It contains a single multiplication gate, labeled . This gate multiplies the outputs of gate (the addition gate computing ) and gate (the pass-through carrying ) from layer 1. The wiring predicate encodes exactly this:

Reading this: "Gate in layer 0 is a multiplication gate whose left input comes from gate in layer 1, and whose right input comes from gate in layer 1." The predicate returns 1 only for this specific triple; all other combinations yield 0.

The layer has no addition gates, so is identically zero.

These predicates depend only on the circuit structure, not on the input values. The verifier, who knows the circuit, can compute these predicates efficiently.

Gate Values as Polynomials

For each layer , define as the function mapping each gate label in layer to its output value. There is exactly one such function per layer (not a family): captures all gate values in the output layer, captures all gate values in layer 1, and so on. The prover, having evaluated the circuit on specific inputs, knows all of .

We extend these to multilinear polynomials over . Similarly, we extend the wiring predicates to multilinear polynomials and .

For our example with inputs , , :

Layer 2 values (inputs):

(The fourth entry is padding: we have 3 inputs but need slots for 2-bit indexing. Unused slots are set to 0.)

The MLE is:

Layer 1 values:

The MLE is:

Layer 0 values (output):

The MLE is:

The Layer Reduction Lemma

The heart of GKR is a beautiful algebraic identity that links adjacent layers:

GKR Lemma: For any point :

Here is the number of bits indexing gates in layer , so the sum ranges over all possible pairs of gate indices from that layer. The polynomial is defined as:

Why does this work? The sum ranges over all possible pairs of input gates . For most pairs, the wiring predicates are zero: gate doesn't receive input from those gates. Only the actual input pair contributes, and for that pair:

  • If is an addition gate: , contributing
  • If is a multiplication gate: , contributing

The sum collapses to exactly what gate should compute.

This identity expresses the output of layer as a sum, which is exactly what sum-check can verify.

The Protocol

The GKR protocol reduces verification of the entire circuit to a single check on the input layer.

Initial Setup:

The verifier knows three things: (1) the circuit structure, meaning the wiring predicates and for each layer; (2) the inputs to the circuit; and (3) the claimed output. She does not know the intermediate gate values. Those are computed by the prover and never directly revealed.

  1. The prover evaluates the circuit and sends the claimed output to the verifier
  2. The verifier picks a random point and computes
  3. The goal: verify that is correct

Layer-by-Layer Reduction (for ):

At the start of round , the verifier holds a claim: ""

  1. Invoke sum-check: Using the Layer Reduction Lemma, the verifier expresses as a sum:

    The prover and verifier run sum-check on this polynomial. The number of variables is .

  2. Sum-check conclusion: Sum-check runs for rounds. In each round, the verifier sends a random field element as a challenge. The first challenges become ; the next become . At the end, the verifier must verify:

  3. The problem: The verifier can compute the wiring predicates (she knows the circuit), but she doesn't know and ; those depend on intermediate gate values only the prover knows.

  4. Reduce two claims to one: The prover sends the claimed values and . But now the verifier has two claims to verify in the next round. To maintain efficiency, we reduce them to one:

    • The verifier picks a fresh random challenge
    • Define (a random point on the line through and )
    • The prover sends a univariate polynomial of degree
    • The verifier checks and against the prover's earlier claims
    • Set , which equals

    The key insight: restricting a multilinear polynomial to a line yields a low-degree univariate polynomial. The random serves double duty: (1) it tests consistency; (2) it produces a fresh random point that combines both claims into one for the next round.

    Why does this catch inconsistency? If the prover lied about either or , they cannot produce a degree- polynomial that passes through both false values while also being the restriction of the true to the line . The degree bound is the handcuff: a low-degree polynomial through the wrong points must differ from the true polynomial almost everywhere. By Schwartz-Zippel, the probability that the random lands on one of the at most points where a false happens to agree with the truth is at most , which is negligible

    Alternative: random linear combination. Some implementations (Church-Forbes-Spooner 2017) instead use for fresh random , verifying via a single combined claim. Both approaches achieve the same goal with similar security.

Final Check:

After reductions, the verifier holds a claim: ""

But layer is the input layer! The verifier knows the inputs. She computes herself and checks if it equals .

flowchart TB
    subgraph setup["SETUP"]
        S1["Prover sends claimed output W₀"]
        S2["Verifier picks random r₀"]
        S3["V₀ = W̃₀(r₀)"]
        S1 --> S2 --> S3
    end

    subgraph layer0["LAYER 0 → LAYER 1"]
        L0A["Claim: W̃₀(r₀) = V₀"]
        L0B["Run sum-check on<br/>V₀ = Σ f₀(r₀, b, c)"]
        L0C["Sum-check yields points s_b, s_c"]
        L0D["Prover claims W̃₁(s_b) and W̃₁(s_c)"]
        L0E["Reduce two claims to one via<br/>random α on line through s_b, s_c"]
        L0F["New claim: W̃₁(r₁) = V₁"]
        L0A --> L0B --> L0C --> L0D --> L0E --> L0F
    end

    subgraph layeri["LAYER i → LAYER i+1"]
        LIA["Claim: W̃ᵢ(rᵢ) = Vᵢ"]
        LIB["Run sum-check on<br/>Vᵢ = Σ fᵢ(rᵢ, b, c)"]
        LIC["Reduce to single claim"]
        LID["New claim: W̃ᵢ₊₁(rᵢ₊₁) = Vᵢ₊₁"]
        LIA --> LIB --> LIC --> LID
    end

    subgraph final["FINAL CHECK (Layer d = Inputs)"]
        F1["Claim: W̃_d(r_d) = V_d"]
        F2["Verifier computes W̃_d(r_d)<br/>directly from known inputs"]
        F3{"Match?"}
        F4["✓ ACCEPT"]
        F5["✗ REJECT"]
        F1 --> F2 --> F3
        F3 -->|Yes| F4
        F3 -->|No| F5
    end

    setup --> layer0
    layer0 --> layeri
    layeri -.->|"d-1 reductions"| final

Worked Example: Verifying

Let's trace through the protocol with , , .

Honest computation:

  • Layer 2: , ,
  • Layer 1: ,
  • Layer 0:

The prover claims the output is 20.

Round 0: Reducing Layer 0 to Layer 1

The verifier picks (say). Recall from earlier that (the MLE of the single output value 20). She computes:

The sum to verify (by the GKR Lemma):

(The term vanishes since layer 0 has no addition gates.)

The wiring predicate's MLE: Since and is 0 elsewhere:

At :

The sum becomes:

Sum-check on this polynomial proceeds for 2 rounds (one for , one for ). The verifier sends random challenges after each round. Suppose these random challenges result in evaluation points and ; these are where the verifier needs to know .

The prover claims:

Now the verifier has two claims to verify. To reduce to one, she picks random and considers the line passing through (at ) and (at ). The prover sends the univariate polynomial . The verifier checks:

  • matches the claimed
  • matches the claimed

The verifier computes and . She now holds a new claim for the next round: .

Round 1: Reducing Layer 1 to Layer 2

The verifier now holds the claim: .

Using the GKR Lemma for layer 1:

Another sum-check reduces this to claims about at random points.

Final Check (Layer 2):

After the sum-check for layer 1, the verifier holds a claim about at some random point . This point emerged from the sum-check challenges and the two-to-one reduction, just as emerged in the previous round. She computes:

using the known inputs , , . If this matches the prover's claim, she accepts.

Why GKR Works

Completeness: If the prover is honest, all polynomials they send in sum-check are correct, and all claimed evaluations are accurate. Every check passes.

Soundness: Suppose the prover claims a wrong output. Then is incorrect. By the Layer Reduction Lemma, either:

  • The sum-check protocol catches a lie (soundness of sum-check), or
  • The prover's claimed values for layer 1 are inconsistent

The lie propagates backward through the layers. By induction, if the original claim is false, either some sum-check fails, or the final claim about the input layer is false (which the verifier catches by direct computation).

The soundness error is bounded by:

where is the circuit depth and is the degree of the sum-check polynomial.

Efficiency Analysis

Verifier's work:

  • For each layer, participate in a sum-check with rounds (where is the layer size)
  • Evaluate wiring predicates at random points (depends on circuit structure)
  • Final check: compute in time where is the number of inputs

Total: for a depth- circuit with layers of size at most .

For circuits with "regular" wiring (like FFT butterflies or matrix multiplication), evaluating wiring predicates takes time. The verifier achieves polylogarithmic verification in the circuit size!

Why structure is the holy grail. If the circuit is random (spaghetti wiring), the verifier has to store the entire wiring diagram ( work), which defeats the purpose of succinctness. But if the circuit is structured, like a matrix multiplication where the same wiring pattern repeats thousands of times, the verifier doesn't need to read a massive list of wires. She can write a tiny loop that generates the wiring predicates on the fly. This data parallelism is what makes GKR efficient in practice. It is why modern provers like Lasso and Jolt are so fast: they treat computation not as a random circuit, but as a structured, repeating pattern.

Prover's work:

  • Must compute the univariate polynomials for each sum-check round
  • Requires summing over all gate values in each layer
  • Total: where is the total number of gates

The prover does work linear in the circuit size: roughly the cost of evaluation itself, with logarithmic overhead.

The Circuit Model: Power and Limitations

GKR works for any layered arithmetic circuit. This is remarkably general: any polynomial-time computation can be expressed as a polynomial-size arithmetic circuit.

Why addition and multiplication suffice: Over a finite field, these two operations generate all polynomial functions. And any Boolean function can be computed by polynomials: represent true as 1, false as 0, then AND becomes multiplication (), NOT becomes subtraction from 1 (), and OR follows from De Morgan (). Since Boolean circuits are universal for computation (any Turing machine can be simulated), arithmetic circuits inherit this universality. The overhead is polynomial: a computation with steps and space becomes a circuit of size .

What circuits capture well:

  • Numerical computations (matrix operations, polynomial evaluation)
  • Field arithmetic (cryptographic operations)
  • Regular patterns (FFT, convolutions)

Challenges:

  • Data-dependent control flow (if-then-else based on inputs) requires unrolling all branches
  • Memory access patterns: Random access memory is expensive to arithmetize
  • Bit operations: Non-arithmetic operations require special encoding

Chapter 8 will explore arithmetization, the art of expressing computations as circuits, in depth. We'll see how R1CS and QAP provide systematic ways to convert programs into the algebraic form that protocols like GKR can verify.

The Bigger Picture

GKR represents a conceptual leap in verifiable computation. Instead of designing a custom protocol for each problem:

  1. Express the computation as a circuit (a general, mechanical process)
  2. Apply GKR (a universal verification protocol)
  3. Achieve efficient verification (polylogarithmic in circuit size for regular circuits)

This modularity is powerful. The "frontend" (how to express a computation as a circuit) separates from the "backend" (how to verify circuit evaluation). Improvements to either benefit all applications.

But GKR as originally described is an interactive protocol. The prover and verifier exchange messages over multiple rounds. For practical applications (blockchain verification, privacy-preserving credentials) we want non-interactive proofs that anyone can verify without interaction.

Chapter 11 will show how to compile interactive protocols like GKR into non-interactive SNARKs using polynomial commitment schemes and the Fiat-Shamir transformation. The journey from sum-check to practical zero-knowledge proofs passes through GKR as a crucial waypoint.

Is GKR actually used? For years, GKR was primarily of theoretical interest; the prover overhead and circuit structure requirements made pairing-based SNARKs (Groth16, PLONK) more practical. But GKR is experiencing a resurgence. Modern systems like Lasso and Jolt use GKR-style sum-check reductions as their core verification mechanism, achieving state-of-the-art prover performance for certain computations.

The key insight is that GKR's prover is native, working directly with the computation's structure rather than reducing to generic polynomial arithmetic. To see why this matters, consider the alternative. In R1CS-based systems (Groth16, Spartan), every computation, no matter how structured, gets flattened into a uniform constraint system: thousands of equations of the form . A 256-bit multiplication, a hash function, a simple addition: all become rows in the same homogeneous matrix. The prover then does generic linear algebra over this matrix, blind to the original structure.

GKR is different. The prover traverses the actual circuit layer by layer, computing the sum-check polynomials from the wiring predicates and gate values directly. If your circuit has repeated structure, say 1000 copies of the same subcircuit, the prover can exploit that. If a layer is sparse (few gates), the work is proportionally smaller. The algorithm "sees" the computation's shape.

This becomes dramatic for certain operations. Lookup tables, for instance: proving "this value appears in that table" via R1CS requires encoding the entire table as constraints. GKR-based approaches (like Lasso) can instead prove lookups with work proportional to the number of lookups, not the table size. For memory operations, range checks, and other structured primitives, native provers can be orders of magnitude faster.

GKR is also transparent (no trusted setup) and plausibly post-quantum when instantiated with hash-based commitments. The protocol you've learned here isn't a historical curiosity; it's foundational to an active and growing family of proof systems.

Key Takeaways

  1. Backward propagation: GKR reduces output verification to input verification by propagating claims backward through layers. Each layer reduction is a sum-check.

  2. Wiring predicates as circuit DNA: The functions and encode the circuit's structure. The verifier can evaluate these efficiently because she knows the circuit.

  3. Two claims to one: Without the line-restriction trick, claims would double each layer (exponential blowup). The random on a line through two points compresses them into one.

  4. Structure is everything: GKR verification is polylogarithmic only when wiring predicates have efficient descriptions. Random spaghetti circuits defeat the purpose.

  5. Native prover advantage: Unlike R1CS systems that flatten all structure into uniform constraints, GKR's prover traverses the actual circuit. Repeated patterns, sparse layers, and regular wiring all translate to concrete speedups.

  6. Grounded in inputs: The reduction chain terminates at the input layer, which the verifier knows. This is what makes the protocol sound: lies cannot hide when the final claim is directly checkable.

Chapter 8: From Circuits to Polynomials

In 1931, Kurt Gödel shattered the foundations of mathematics. He proved that any formal system powerful enough to express arithmetic is "haunted": it contains true statements that cannot be proven. More precisely: if a formal system is consistent (it cannot prove both a statement and its negation) and capable of expressing basic arithmetic, then is incomplete (there exists a statement such that neither nor is provable in ). To establish this, Gödel had to solve a technical nightmare: how do you make math talk about itself?

His solution was Gödel numbering. He assigned a unique integer to every logical symbol (, , ), turning logical statements into integers and logical proofs into arithmetic relationships between those integers. He turned logic into arithmetic so that arithmetic could reason about logic.

What we do in zero-knowledge proofs is a direct descendant of Gödel's trick. We take the logic of a computer program (loops, conditionals, memory access) and encode it as polynomial equations. This translation is called arithmetization, and it's the subject of this chapter.

Arithmetic Circuits

An arithmetic circuit over a field is a directed acyclic graph where each node is either an input, a constant, or a gate (addition or multiplication). Wires carry field elements from gate outputs to gate inputs. The circuit computes a function by propagating values from inputs through gates to outputs.

Think of it as a recipe: inputs enter at the top, flow through a network of additions and multiplications, and produce outputs at the bottom. The recipe is fixed (the circuit structure), but you can run it on different ingredients (input values).

Why circuits? They're the universal language of computation. Any program, any algorithm, any function computable by a computer can be expressed as a (possibly enormous) arithmetic circuit. This universality is what makes circuit-based proof systems so powerful: prove you can verify circuits, and you can verify anything.

Two Problems, Two Paradigms

Before diving in, we must distinguish two fundamentally different problems:

Circuit Evaluation: Given a circuit and input , prove that .

The prover claims they computed the circuit correctly. The verifier could recompute it themselves, but the proof system makes verification faster. GKR handles this directly.

Circuit Satisfiability: Given a circuit , public input , and output , prove there exists a secret witness such that .

The prover claims they know a secret input that makes the circuit output the desired value. They reveal nothing about this secret. This is the paradigm behind most real-world ZK applications, and it's what enables privacy.

Note that GKR (Chapter 7) natively handles circuit evaluation, not satisfiability: it proves "" for public inputs, with no secrets involved. To handle satisfiability, where the prover has a private witness, you need additional machinery: polynomial commitments that hide the witness values, combined with sum-check to verify the computation. Systems like Jolt use GKR-style sum-check reductions but wrap them with commitment schemes that provide zero-knowledge. The distinction matters: "GKR-based" doesn't mean "evaluation only"; it means the verification logic uses sum-check over layered structure, while commitments handle privacy.

Example: Proving Knowledge of a Hash Preimage

Suppose for some secret . The prover wants to demonstrate they know without revealing it.

  • The circuit implements SHA256
  • The public input is (essentially) empty
  • The public output is (the hash)
  • The witness is (the secret preimage)

The prover demonstrates: "I know a value such that when I run SHA256 on it, I get exactly ." The verifier learns nothing about except that it exists.

This satisfiability paradigm underlies almost all practical ZK applications: proving password knowledge, transaction validity, computation integrity, and more.

Understanding the Witness

The witness is central to zero-knowledge proofs. It's what separates a mere computation from a proof of knowledge.

What Exactly Is a Witness?

A witness is an input that, together with the public inputs, satisfies the circuit's constraints. In zero-knowledge proofs, the witness is kept private. In the equation , the witness is . Anyone can verify that , but the prover is demonstrating they know this solution.

More precisely, for a relation , a witness for statement is a value such that . The relation encodes the computational problem:

  • Hash preimage: iff
  • Digital signature: iff
  • Sudoku solution: iff the solution correctly fills the puzzle

The Sudoku Analogy. Think of a ZK proof as a solved Sudoku puzzle. The circuit is the rules of Sudoku: every row, column, and 3×3 square must contain the digits 1 through 9. The public input is the pre-filled numbers printed in the newspaper. The witness is the numbers you penciled in to solve it. Verifying the solution is easy: check the rows, columns, and squares (the constraints). You don't need to know the order in which the solver filled the numbers, nor the mental logic they used. You just check that the final grid (witness + public input) satisfies the rules.

The Execution Trace: Witness as Computation History

Modern arithmetization uses a clever insight: instead of building a circuit that performs the computation, we build a circuit that verifies a claimed execution trace.

What Is an Execution Trace?

An execution trace is a complete record of a computation's execution: every instruction, every intermediate value, every memory access. Think of it as a detailed log file that captures everything that happened during the computation.

The key insight: checking that a trace is valid is much easier than producing the computation. Validity checking is local. To verify a trace, you only need to check that each step follows from the previous one according to the program's rules. The prover does the hard computational work; the circuit does the much easier work of checking consistency.

For simple computations (evaluating a polynomial, computing a hash), the trace is just the sequence of intermediate values at each gate. For more complex computations like CPU execution, the trace includes registers, program counters, and memory operations. The machinery for handling such traces (time consistency, memory consistency via permutation arguments) is developed in Chapter 21 in the context of efficient proving techniques. Here, we focus on the simpler case: a circuit where the witness captures all intermediate gate values.

R1CS: The Constraint Language

How do we express these checks algebraically? The classic approach is Rank-1 Constraint System (R1CS).

An R1CS instance consists of:

  • Three matrices of dimension
  • A witness vector of length

The constraint is: for each row ,

In words: (linear combination) × (linear combination) = (linear combination).

The matrices encode which wires participate in each constraint. Each row enforces one multiplication gate.

Why this particular form? The fundamental reason is that degree-2 polynomial constraints are the simplest non-trivial form that's still universal. Linear constraints (degree 1) can't express multiplication. Degree 2 is the minimal step up, and it turns out to be enough: any computation can be decomposed into steps involving at most one multiplication each. Historically, pairings reinforced this choice. A bilinear map can verify one multiplication "for free," so early SNARKs (Groth16, BCTV14) were designed around degree-2 constraints. But the format isn't pairing-specific: modern systems verify R1CS using FRI or IPA, no pairings required.

At first glance, "one multiplication per constraint" seems limiting. What if you need to compute ? That requires two multiplications, not one. What about ? That's three multiplications. How can a format that allows only one multiplication per constraint express arbitrary computations?

The answer: introduce intermediate variables. To compute , define a helper variable , then write two constraints:

  • Constraint 1:
  • Constraint 2:

Each constraint has exactly one multiplication. The witness vector grows to include , but that's fine since the prover computed it anyway. This is the general pattern: any polynomial computation of degree can be flattened into R1CS constraints by naming intermediate products.

Addition, by contrast, is free. To constrain , we write , which costs one constraint but involves no "real" multiplication. More generally, we can pack arbitrary additions into either side of a multiplication: is still a single R1CS row. Why? Because computes a weighted sum of witness variables. Matrix-vector multiplication is just addition, so combining into one linear combination costs nothing. We only "pay" when we multiply the result of by the result of .

This decomposition is why R1CS can encode arbitrary arithmetic circuits. Every gate becomes one constraint. The "one multiplication" rule isn't a limitation; it's a normal form that any computation can be converted into.

Any arithmetic circuit with multiplication gates and addition gates can be expressed as an R1CS with exactly constraints. The witness vector has length at most . Addition gates require no constraints; they're absorbed into the linear combinations.

The Witness Vector in R1CS

The witness vector in R1CS has a specific structure. It concatenates three parts:

The constant 1: Always the first element. This allows encoding constants and pure additions. To constrain , write . For addition , write .

The public inputs/outputs (io): Values the verifier knows. For a hash preimage proof, this is the hash value . For a transaction validity proof, it might include the transaction amount and recipient.

The private witness (W): The secret values only the prover knows, plus all intermediate computation values.

For example, proving with secret :

IndexValueDescription
1Constant
35Public output
3Private:
9Private:
27Private:
30Private:
35Private:

The witness includes not just the input , but all intermediate values. The constraint system checks that each step was performed correctly.

Basic Gates in R1CS

Multiplication ():

  • Row of selects from
  • Row of selects from
  • Row of selects from
  • Constraint:

Addition ():

  • Set to select the constant 1
  • Row of selects both and (with coefficients 1, 1)
  • Row of selects
  • Constraint:

Constant multiplication ():

  • Row of selects
  • Row of selects constant (or encode in )
  • Row of selects

Worked Example:

Let's arithmetize a complete example. The prover claims to know such that . (The secret is .)

Step 1: Flatten to Basic Operations

Break the computation into primitive gates:

v1 = x * x        (compute x²)
v2 = v1 * x       (compute x³)
v3 = v2 + x       (compute x³ + x)
v4 = v3 + 5       (compute x³ + x + 5)
assert: v4 = 35   (check the result)

Step 2: Define the Witness Vector

The witness contains:

  • The constant 1 (always included)
  • The public output 35
  • The secret input
  • All intermediate values

With :

Step 3: Build the Constraint Matrices

Each gate becomes a row in the matrices:

Gate 1:

  • : selects
  • : selects
  • : selects

Check:

Gate 2:

  • : selects
  • : selects
  • : selects

Check:

Gate 3:

For addition, we use the trick:

  • : selects
  • : selects constant 1
  • : selects

Check:

Gate 4:

  • : selects
  • : selects 1
  • : selects

Check:

Gate 5: (the public output constraint)

  • : selects
  • : selects 1
  • : selects

Check:

All five constraints are satisfied. The R1CS captures the entire computation.

The complete matrices:

Each row corresponds to one constraint. The columns are indexed by . Notice the sparsity: most entries are zero. This is typical of R1CS matrices and is why efficient implementations use sparse representations.

Two Ways to Prove R1CS

Once we have R1CS constraints, how do we prove they're all satisfied? There are two major approaches.

Approach 1: QAP (Quadratic Arithmetic Program)

QAP was introduced by Gennaro, Gentry, Parno, and Rabin in the Pinocchio system (2013), one of the first practical SNARKs. Groth16 (2016) refined and optimized this approach, achieving the smallest proof size known for pairing-based systems. Today, QAP is primarily associated with Groth16. Modern systems have moved to other arithmetizations (PLONKish, AIR, sum-check), but QAP remains important for applications where proof size is paramount.

The key idea: instead of checking separate constraints, check one polynomial divisibility.

For each column of the R1CS matrices, define polynomials that interpolate the column values at points . (So equals the entry in row , column of matrix .)

Now let be the witness vector, the full assignment including the constant 1, public inputs, and private witness values. Define:

Each is a scalar (from the witness), while is a polynomial. The sum computes a linear combination, exactly mirroring how R1CS constraints are matrix-vector products.

The R1CS is satisfied iff at all constraint points .

By the Factor Theorem, this means the vanishing polynomial divides .

The prover exhibits a quotient polynomial such that:

We develop QAP fully in Chapter 12, where Groth16 uses it to achieve the smallest possible pairing-based proofs.

Approach 2: Sum-Check on Multilinear Extensions (Spartan)

Spartan was introduced by Setty in 2019, reviving ideas from the GKR protocol (2008) and sum-check literature. While Groth16 uses univariate polynomials and FFTs, Spartan showed that multilinear extensions and the sum-check protocol could handle R1CS directly: no Lagrange interpolation, no roots of unity, optimal prover time. This "sum-check renaissance" led to systems like Lasso, Jolt, and HyperNova.

R1CS constraint satisfaction can be expressed as a polynomial sum equaling zero:

Here , , are the MLEs of the matrix-vector products , , respectively, each viewed as a function from row index to a field element.

This formulation matters for three reasons:

  1. Time-optimal proving: The prover's work is where is the number of constraints, just reading the constraints, no FFTs.

  2. Sparsity-preserving: Multilinear extensions preserve the structure of sparse matrices. In R1CS, most matrix entries are zero. The MLE directly reflects this sparsity.

  3. Natural fit with sum-check: The sum-check protocol (Chapter 3) is designed exactly for this type of problem.

Comparing QAP and Spartan:

PropertyQAP (Groth16)Spartan
Polynomial typeUnivariate, high-degreeMultilinear
Core techniqueDivisibility by Sum-check
Prover time
SetupCircuit-specific trustedTransparent

When to use each:

  • When proof size matters most: Use QAP-based systems (Groth16, BCTV14, Pinocchio). On-chain verification on Ethereum costs gas proportional to proof size, making Groth16's ~200-byte proofs attractive despite the circuit-specific setup. Groth16 is the most optimized of this family and dominates in practice.

  • When prover time matters most: Use Spartan or other sum-check systems. The prover (vs for FFT-based systems) becomes significant at scale. Transparent setup avoids trust assumptions entirely. Natural fit for recursive composition and folding schemes (Nova, HyperNova). The tradeoff: larger proofs and more expensive verification.

PLONKish Arithmetization

R1CS isn't the only way to encode computations. PLONKish takes a fundamentally different approach, one that has become widely adopted in production ZK systems.

Historical context: PLONK (Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge) was introduced by Gabizon, Williamson, and Ciobotaru in 2019. It addressed Groth16's main limitation, circuit-specific trusted setup, by providing a universal setup: one ceremony works for any circuit up to a given size. PLONK spawned a family of "PLONKish" systems (Halo 2, Plonky2, HyperPlonk) that now power most production ZK applications.

The Universal Gate Equation

PLONK's core innovation is a single standardized gate equation:

The values are selectors, public constants that "program" each gate.

Addition gate (): Set , rest zero.

Multiplication gate (): Set , rest zero.

Public input (): Set , rest zero.

The same equation handles all gate types!

Copy Constraints: The Permutation Argument

PLONK's gate equation only relates wires within a single gate. It doesn't enforce that the output of gate 1 feeds into the input of gate 5.

This is where the permutation argument enters. Number all wire positions in the circuit as . Some positions must hold equal values (because one gate's output connects to another's input). We encode these equalities as a permutation : positions that must be equal form cycles under . The constraint "all wiring is respected" becomes:

where is the value at position . PLONK proves this via a grand product check. With random challenges :

The intuition: each fraction pairs a value with its position. If copy constraints hold, the numerators and denominators rearrange to cancel. If any constraint fails, the random ensure the product differs from 1 with overwhelming probability. We develop the full permutation argument in Chapter 13.

When to Use PLONKish

PLONKish shines when you need flexibility without sacrificing succinctness:

  • Universal setup (vs Groth16's circuit-specific): One ceremony covers all circuits up to a size bound
  • Custom gates: Optimize specific operations (hash functions, range checks, elliptic curve arithmetic)

The tradeoff versus Groth16 (which uses R1CS + QAP): slightly larger proofs (~2-3x), but no circuit-specific ceremony.

Note: Sum-check systems like Spartan go further with fully transparent setup (no ceremony at all), but with larger proofs.

AIR: Algebraic Intermediate Representation

A third constraint format takes yet another path, designed specifically for computations with repetitive structure: state machines, virtual machines, and iterative algorithms.

An AIR consists of:

  • Execution trace: A table where each row represents a "state" and columns hold state variables
  • Transition constraints: Polynomials that relate row to row (the local rules)
  • Boundary constraints: Conditions on specific rows (initial state, final state)

The insight: many computations are naturally described as "apply the same transition rule repeatedly." A CPU executes instructions in a loop. A hash function applies the same round function many times. AIR captures this by encoding the transition rule once and proving it holds for all consecutive row pairs.

Example: A simple counter that increments by 1:

  • Transition constraint:
  • Boundary constraint: (start at zero)

This single transition constraint, applied to rows, proves correct execution of steps.

The algebraic formulation uses a clever trick. Interpolate each trace column as a polynomial over a domain where is a -th root of unity. Now gives the value at step , and gives the value at step . So the "next step" value is .

The transition constraint becomes the polynomial identity:

If this holds, the constraint polynomial vanishes on , so the quotient is a polynomial (not a rational function with poles). The prover commits to and proves it's low-degree via FRI. Boundary constraints work similarly: becomes being a polynomial.

AIR is the native format for STARKs, which we develop fully in Chapter 15. The combination of AIR's repetitive structure with FRI's hash-based commitments yields transparent, plausibly post-quantum proofs.

Comparing the Three Formats

PropertyR1CSPLONKishAIR
StructureSparse matricesGates + selectorsExecution trace + transitions
Gate flexibilityOne mult/constraintCustom gatesTransition polynomials
Best forSimple circuitsComplex, irregular opsRepetitive state machines
Used byGroth16, SpartanPLONK, Halo 2STARKs, Cairo

In practice:

  • R1CS + Groth16: When proof size dominates (on-chain verification)
  • PLONKish: When you need flexibility and universal setup
  • AIR + STARKs: When transparency and post-quantum security matter

CCS: Unifying the Constraint Formats

We now have three constraint formats (R1CS, PLONKish, AIR) each with distinct strengths. But this proliferation creates fragmentation: tools, optimizations, and folding schemes must be reimplemented for each format.

Why do we need yet another format? The answer is folding (Chapter 23). Newer protocols like Nova and HyperNova work by "folding" two proof instances into one. R1CS folds easily, but PLONKish constraints do not. Customizable Constraint Systems (CCS) was invented to give us both: the expressiveness of PLONK's custom gates with the foldability of R1CS's matrix structure. CCS provides a unifying abstraction that captures all three formats without overhead.

The CCS Framework

A CCS instance consists of:

  • Matrices : sparse matrices over , encoding constraint structure
  • Constraint specifications: which matrices combine in each constraint, with what operation

The key insight: any constraint system can be expressed as:

where:

  • is the witness vector (including public inputs and the constant 1)
  • specifies which matrices participate in term
  • is the Hadamard (element-wise) product:
  • are scalar coefficients

The notation means: for each matrix index in the set , compute the vector , then Hadamard-multiply all those vectors together. If , you get . If (a single matrix), you just get with no Hadamard.

Each term in the sum takes a subset of matrices , multiplies each by the witness vector , Hadamard-multiplies the results together, and scales by . The constraint is satisfied when all terms sum to zero.

Every constraint format we've seen boils down to two operations: (1) selecting and summing witness values (matrix-vector products), and (2) multiplying those sums together (Hadamard products). CCS makes these two operations explicit and composable:

  • Linear constraints: A single matrix-vector product, no Hadamard
  • Quadratic constraints: Hadamard of two matrix-vector products
  • Higher-degree constraints: Hadamard of more products
  • Mixed constraints: Different terms can have different degrees

Recovering Standard Formats

R1CS as CCS:

  • Three matrices: , ,
  • Two terms: (Hadamard of and ), (just )
  • Coefficients: ,

The CCS formula becomes:

which is exactly , the R1CS equation.

PLONKish as CCS:

The PLONK gate equation becomes:

  • Matrices: (selects wire ), (selects wire ), (selects wire ), (selector), , , ,
  • Terms map to the gate equation:
    • : Hadamard of selector and wire →
    • : Hadamard of three matrices →
    • ...and so on for each term

The CCS formula becomes:

Each term in PLONK's gate equation maps to one term in the CCS sum.

AIR as CCS:

Recall from the AIR section that the transition constraint becomes the polynomial identity . CCS captures this same structure with matrices instead of the shift.

A transition constraint like becomes:

  • Matrices: (extracts current-row values), (extracts next-row values), (constant column)
  • The constraint becomes:

The matrix plays the role of the shift: it extracts "next step" values from the witness vector, just as evaluates the polynomial at the next domain point.

Here all terms have (no Hadamard products), so the constraint is purely linear in state variables. Quadratic AIR constraints (like ) would use Hadamard: .

Why CCS Matters

CCS enables unified tooling: compilers, analyzers, and optimizers can target CCS once. The specific frontend (Circom, Cairo, Noir) produces CCS; the backend (Spartan, Nova, HyperNova) consumes it. HyperNova folds CCS instances directly, so any constraint format expressible as CCS inherits folding for free. Matrix sparsity, constraint reordering, and parallel proving apply uniformly regardless of the original constraint format. Theoretical results about CCS apply to all formats it subsumes.

CCS in Practice

Modern systems increasingly use CCS as their internal representation:

  • HyperNova: Folds CCS directly, achieving the benefits of PLONK's flexibility with Nova's efficiency
  • Sonobe: A folding framework that targets CCS
  • Research prototypes: Use CCS for cleaner proofs of concept

The constraint format ecosystem is consolidating. R1CS, PLONKish, and AIR remain useful surface-level abstractions, but CCS provides the common substrate beneath.

Handling Non-Arithmetic Operations

Real programs use operations that aren't native to field arithmetic: comparisons, bitwise operations, conditionals, hash functions. These require careful encoding, and this is where constraint counts explode.

Bit Decomposition: The Fundamental Technique

The standard technique: represent an integer as bits .

Enforce "bitness": Each must satisfy . This polynomial is zero iff .

Why? If : (satisfied). If : (satisfied). If : (fails).

Reconstruct the value: Verify .

Constraint Costs: A Reality Check

Here's where things get expensive. Let's count constraints for common operations:

OperationConstraintsNotes
Field addition0Free! Just combine wires
Field multiplication1Native R1CS operation
64-bit decomposition64One per bit (bitness check)
64-bit reconstruction1Sum with powers of 2
64-bit AND~130Decompose both, multiply bits, reconstruct
64-bit XOR~130Decompose both, compute XOR per bit
64-bit comparison~200Decompose, subtract, check sign bit
64-bit range proof~65Decompose + bitness checks
SHA256 hash~20,000Many bitwise operations
Poseidon hash~250Field-native design

Bitwise operations are roughly 100x more expensive than field operations. This is why:

  • ZK-friendly hash functions (Poseidon, Rescue) exist: they avoid bit operations
  • zkVMs are expensive because they must handle arbitrary CPU instructions
  • Custom circuits beat general-purpose approaches for specific computations

Simulating Logic Gates

With bits exposed, we can simulate Boolean logic:

AND (): For each bit position : Cost: 1 multiplication constraint per bit

OR (): For each bit position : Cost: 1 multiplication constraint per bit

XOR (): For each bit position : Cost: 1 multiplication constraint per bit

NOT (): For each bit position : Cost: 0 (just linear combination)

Range Proofs: Proving

To prove a value is within a range :

  1. Decompose into bits
  2. Check each bit satisfies
  3. Verify reconstruction:

Cost: bitness constraints + 1 reconstruction constraint

Comparison: Proving

To prove for values in range :

Approach 1: Subtraction with underflow

  1. Compute (shifted to avoid underflow)
  2. Decompose into bits
  3. Check the most significant bit equals 1 (meaning , so )

Cost: ~ constraints for bit decomposition + bitness checks

Approach 2: Lexicographic comparison

  1. Decompose both and into bits
  2. Starting from the MSB, find the first position where they differ
  3. At that position, check and

Cost: More complex, often not better for general comparisons

The pattern is clear: anything involving bits is expensive. For years, circuit designers accepted this cost as unavoidable, until lookup arguments changed everything.

Lookup Arguments: Breaking the Bit Decomposition Wall

The constraint costs above create a fundamental problem. A silicon CPU executes a XOR b in one cycle. In R1CS, that same XOR costs ~25 constraints: decompose both operands into bits, check bitness, compute per-bit XOR, reconstruct. For a 64-bit instruction set, every operation requires hundreds of constraints. Building a zkVM this way is like simulating a Ferrari using wooden gears.

Lookup arguments solve this by replacing computation with table membership. Instead of proving how you computed a result, prove that the result appears in a precomputed table.

To prove an 8-bit XOR:

  • Bit decomposition: 16 bitness checks + 8 XOR computations + reconstruction ≈ 25 constraints
  • Lookup: Precompute all valid XOR triples . Prove is in the table ≈ 3 constraints

The savings compound. A 64-bit XOR via bit decomposition costs ~130 constraints. Via lookups on 8-bit chunks: 8 lookups × 3 constraints = 24 constraints.

This changes what's feasible:

OperationWithout LookupsWith Lookups
16-bit range check17 constraints~3 constraints
8-bit XOR~25 constraints~3 constraints
64-bit XOR~130 constraints~24 constraints
SHA-256 (via chunks)~20,000 constraints~2,000 constraints

The "how" of lookup arguments (Plookup's grand products, LogUp's logarithmic derivatives, Lasso's decomposition for huge tables) is developed in Chapter 14. The key insight for arithmetization is architectural: non-field-native operations that would otherwise dominate constraint counts can be handled via table membership at roughly constant cost per lookup.

This is why modern zkVMs are practical. Cairo, RISC-Zero, SP1, and Jolt prove instruction execution not by encoding CPU semantics in constraints, but by verifying that each instruction's behavior matches a precomputed table. The paradigm shifted from encoding logic to referencing data.

The Frontend/Backend Split

This chapter describes frontends, compilers that transform high-level programs into arithmetic form. The backend is the proof system (GKR, Groth16, PLONK, STARKs) that proves the resulting constraints.

CPU-style frontends (Cairo, RISC-Zero, SP1, Jolt):

  • Define a virtual machine with a fixed instruction set
  • Any program compiles to that instruction set
  • The arithmetization verifies instruction execution
  • General-purpose but with overhead

ASIC-style frontends (Circom, custom circuits):

  • Create a specialized circuit for each specific program
  • Maximum efficiency for fixed computations
  • Poor for general-purpose or data-dependent control flow

Hybrid approaches:

  • Use custom circuits for the common case
  • Fall back to general VM for edge cases
  • Example: Specialized hash circuit + general VM for the rest

The choice depends on your use case. Verifying a hash? A custom circuit is fastest. Running arbitrary computation? You need a zkVM. Running the same computation millions of times? The circuit development cost is amortized.

Key Takeaways

  1. The pipeline: Program → execution trace (witness) → constraint system → polynomial identity → proof. Arithmetization is the bridge between computation and algebra.

  2. Circuit satisfiability vs. evaluation: Most applications prove knowledge of a secret witness, not just correct evaluation.

  3. The witness is everything: It's the complete set of values (public, private, and intermediate) that satisfies the constraints.

  4. Three constraint formats: R1CS (sparse matrices, ), PLONKish (universal gate + permutation), AIR (transition polynomials). CCS unifies them all.

  5. Bit decomposition is expensive: A 64-bit operation costs ~65-200 constraints via traditional encoding. Lookup arguments (Chapter 14) reduce this to ~3 constraints per table lookup.

  6. Frontend/backend split: Frontends handle arithmetization; backends handle proving. They can be mixed and matched.

  7. Constraint cost guides design: Choose field-friendly operations (hashes, curves) over bit-heavy operations.

Chapter 9: Polynomial Commitment Schemes: The Cryptographic Engine

In 2016, six people met in a hotel room to birth the Zcash privacy protocol. Their task: generate a cryptographic secret so dangerous that if even one of them kept a copy, it could forge unlimited fake coins, undetectable forever. They called it "toxic waste."

The ceremony was a paranoid ballet. Participants were scattered across the globe, connected by encrypted channels. One flew to an undisclosed location, computed on an air-gapped laptop, then incinerated the machine and its hard drive. Another broadcast their participation live so viewers could verify no one was coercing them. The randomness they generated was combined through multi-party computation, ensuring that if any single participant destroyed their secret, the final parameters would be safe.

Why such extreme measures? Because polynomial commitment schemes, the cryptographic engine that makes SNARKs work, sometimes require a structured reference string: public parameters computed from a secret that must then cease to exist. The Zcash ceremony became legendary in cryptography circles, part security protocol, part performance art. It demonstrated both the power and the peril of pairing-based commitments.

This chapter explores that peril and its alternatives. We'll see two fundamental approaches to polynomial commitments: KZG, which achieves constant-size proofs at the cost of trusted setup, and IPA/Bulletproofs, which eliminates the toxic waste but pays with linear verification. Each represents a different answer to the same question: how do you prove facts about a polynomial without revealing it? A third major scheme, FRI, takes a fundamentally different approach based on hashing rather than algebraic assumptions; we cover it in Chapter 10. (For advanced schemes like Dory that achieve logarithmic verification without trusted setup, see Appendix D.)


Everything we've built (sum-check, GKR, arithmetization) reduces complex claims to polynomial identities. A prover claims that polynomial has certain properties: it equals another polynomial, it vanishes on a domain, it evaluates to a specific value at a point.

But here's the catch: verifying these claims directly would require the verifier to see the entire polynomial. For a polynomial of degree , that's coefficients, exactly as much data as the original computation. We've achieved nothing.

Polynomial Commitment Schemes (PCS) solve this problem. A PCS allows a prover to commit to a polynomial with a short commitment, then later prove claims about the polynomial (its evaluations at specific points) without revealing the polynomial itself. The commitment is binding (the prover can't change the polynomial), and the proofs are succinct (much smaller than the polynomial).

This is where abstract algebra meets cryptography.

The PCS Abstraction

A polynomial commitment scheme consists of three algorithms:

Commit : Given a polynomial , produce a short commitment .

Open : Given the polynomial , a point , compute the evaluation and a proof that this evaluation is correct.

Verify : Given the commitment, point, claimed value, and proof, check correctness.

Properties:

  • Binding: A commitment can only be opened to evaluations consistent with one polynomial (computationally)
  • Hiding (optional): The commitment reveals nothing about the polynomial
  • Succinctness: Commitments and proofs are much smaller than the polynomial

The key insight: if the prover is bound to a specific polynomial before seeing the verifier's challenge point, and the commitment is much smaller than the polynomial, then we can verify polynomial identities by checking at random points.

KZG: Constant-Size Proofs from Pairings

The Kate-Zaverucha-Goldberg (KZG) scheme achieves the holy grail: constant-size commitments and constant-size evaluation proofs. No matter how large the polynomial, the proof is just one group element.

The Magic Ingredient: Pairings

A bilinear pairing is a map between three groups with the property:

for all scalars and group elements , .

This seemingly simple equation has profound consequences. It allows us to check multiplicative relationships in the exponent. Given commitments and , we cannot compute (that would break CDH). But if someone claims to know , we can verify their claim by checking:

One multiplication check "for free" in the hidden exponent world. This is exactly what polynomial evaluation needs.

The Trusted Setup

KZG requires a structured reference string (SRS): a set of public parameters computed from a secret:

  1. Choose a random secret (the "toxic waste")
  2. Compute the SRS:
  3. Destroy

The SRS encodes powers of the secret "in the exponent." Anyone can use these elements without knowing itself. But if anyone learns , they can forge proofs for false statements, so the setup must ensure is never known to any party. In practice, this is done via multi-party computation ceremonies where many participants contribute randomness, and security holds as long as any one participant is honest.

Commitment

To commit to a polynomial :

The prover computes this using the SRS elements, never learning . The result is a single group element: the polynomial "evaluated at the secret point , hidden in the exponent."

Evaluation Proof

To prove for a public point :

  1. The polynomial identity: If , then divides . Define: This quotient is a valid polynomial of degree .

  2. The proof: Commit to the quotient:

  3. Verification: The verifier checks:

Why Verification Works

The verification equation is equivalent to the polynomial identity . To see this, substitute the definitions:

This gives:

By bilinearity:

This holds iff , which is exactly the polynomial identity evaluated at .

Why this implies soundness: Suppose the prover lies; they claim when actually . Then is not divisible by , so no polynomial satisfies the identity . Without such a , the prover must instead find some where the identity fails as polynomials but happens to hold at :

But the prover doesn't know ; it's hidden in the SRS. From their perspective, is a random field element. Two distinct degree- polynomials agree on at most points (Schwartz-Zippel), so the probability that a "wrong" accidentally satisfies the check at the unknown is at most (negligible for large fields).

Formal soundness statement: Let be the committed polynomial of degree at most . For any adversary that outputs with : where the probability is over the random choice of in the trusted setup. Under the -Strong Diffie-Hellman assumption (that computing from the SRS is hard), this bound holds even for adversaries who choose adaptively.

Worked Example: KZG in Action

Let's trace through a complete example.

Setup: Maximum degree , secret .

  • SRS:

Commit to :

Prove :

  • Check:

  • Quotient:

    Factor:

    So

  • Proof:

Verify:

  • LHS:
  • RHS:

Both sides equal. The verification passes.

Batch Opening

KZG has a remarkable property: proving evaluations at multiple points is barely more expensive than proving one.

To prove :

  1. Define the vanishing polynomial
  2. Compute the interpolating polynomial such that
  3. The quotient exists iff all evaluations are correct
  4. The proof is just (still one group element!)

This is why KZG scales so well in practice. A SNARK verifier might need to check dozens of polynomial evaluations; with batch opening, these collapse into a single pairing check.

KZG: Properties and Trade-offs

Advantages:

  • Constant commitment size: One group element, regardless of polynomial degree
  • Constant proof size: One group element per evaluation
  • Constant verification time: A few pairings and exponentiations
  • Batch opening: Multiple evaluations verified with a single proof

Disadvantages:

  • Trusted setup: The "toxic waste" must be destroyed. If compromised, soundness breaks.
  • Not post-quantum: Pairing-based cryptography falls to quantum computers
  • Degree-bounded: The SRS size caps the maximum polynomial degree

Managing Toxic Waste: Powers of Tau Ceremonies

The trusted setup creates a serious practical problem: someone must generate τ, compute the powers, and then verifiably destroy τ. How do you convince the world that the toxic waste is truly gone?

The solution is multi-party computation (MPC) ceremonies. Instead of trusting a single party, we chain together contributions from many independent participants:

  1. Participant 1 picks secret , computes and destroys
  2. Participant 2 picks secret , raises each element to , getting and destroys
  3. Continue for hundreds or thousands of participants...

The final structured reference string encodes powers of . The crucial insight: the setup is secure if any single participant destroyed their secret. This is the "1-of-N" trust model; you only need to trust that one honest participant existed among potentially thousands.

The Zcash Powers of Tau ceremony (2017-2018) had 87 participants contribute to a universal phase, followed by circuit-specific ceremonies for Sapling. The Ethereum KZG Ceremony (2023) dwarfed this with over 140,000 contributions for EIP-4844 blob commitments.

Some ceremonies produce parameters usable for any circuit up to a size bound (universal), while others are tailored to specific circuits. KZG setups are inherently universal; the same powers of tau work for any polynomial of degree at most .

The scale of modern ceremonies makes collusion effectively impossible. When 140,000 independent participants contribute, the probability that all of them colluded or were compromised approaches zero.

IPA/Bulletproofs: No Trusted Setup

The Inner Product Argument emerged from a different lineage than KZG. Bootle et al. (2016) introduced the core folding technique for efficient inner product proofs. Bünz et al. (2017) refined this into Bulletproofs, originally designed for range proofs, proving that a committed value lies in a range without revealing it. This was motivated by confidential transactions in cryptocurrencies: prove your balance is non-negative without revealing the amount.

The terminology can be confusing:

  • IPA (Inner Product Argument) is the technique: the recursive folding protocol that proves
  • Bulletproofs is the system that used IPA for range proofs and general arithmetic circuits

Today, "IPA" and "Bulletproofs" are often used interchangeably to describe the folding-based polynomial commitment scheme. The key innovation: achieving transparency (no toxic waste) at the cost of logarithmic proofs and linear verification.

The Key Insight: Polynomial Evaluation as Inner Product

As we saw in Chapters 4 and 5, polynomial evaluation is an inner product. For univariate polynomials:

where are coefficients and is the powers vector. For multilinear polynomials, the structure differs: , where contains evaluations on the Boolean hypercube and contains Lagrange basis weights. Both cases reduce polynomial evaluation to an inner product claim, but the vectors involved differ. If we can prove inner product claims efficiently, we can prove polynomial evaluations. IPA does exactly this: it reduces the inner product by folding both vectors with random challenges, halving the dimension each round. This is the same algebraic trick as sum-check, with different cryptographic wrapping. We'll develop IPA using the univariate representation (coefficients × powers), but the technique applies to any inner product.

From Vector Commitments to Inner Product Claims

We've reduced polynomial evaluation to an inner product, and inner products operate on vectors. So to commit to a polynomial, we commit to a vector (its coefficients). Pedersen commitments provide exactly this: a way to commit to a vector such that we can later prove inner product claims about it.

The basic Pedersen vector commitment uses generators (one per coefficient) and for blinding:

This commits to the polynomial's coefficient vector . The commitment binds us to these coefficients, but to prove an evaluation , we need to bind the claimed value into the protocol as well. IPA does this by introducing a separate generator and forming:

Think of as encoding two things simultaneously: the coefficient vector (via ) and the claimed inner product (via ). The folding protocol will manipulate both parts together, and only if is the true inner product will everything stay consistent through the recursion.

The Folding Trick

The brilliant idea of IPA is recursive "folding" that shrinks the problem by half each round.

Setup

Prover holds coefficient vector of length . They've committed to it as where is the claimed evaluation. (We omit the blinding term for clarity.)

One round of folding

  1. Split into two halves

  2. Split and similarly

  3. Prover computes and sends cross-term commitments:

    Note: commits to the left coefficients using right generators, plus the cross inner product. Similarly for .

  4. Verifier sends random challenge

  5. Prover computes the folded coefficient vector (secretly):

  6. Both parties compute (using public information):

    • Folded evaluation vector:
    • Folded generators:
    • Updated commitment:

Why this works

We need to show that is a valid commitment to under the folded generators .

First, expand what should be if the prover is honest:

where is the new inner product claim.

Now expand using the folding formulas:

Distributing the inner product (which is bilinear):

Similarly, expanding the new inner product :

Now look at and expand each term:

So:

Collecting terms:

This equals . The update formula produces exactly the right commitment!

The recursion

After rounds, the vectors have length 1. The prover reveals the final scalar, and the verifier checks directly.

Final Verification: The Endgame

After rounds of folding, the vectors have length 1:

  • Prover holds a single scalar (the folded coefficient)
  • The -vector has folded to (known to both parties)
  • The commitment has transformed to through all the updates

The prover reveals

  • The final coefficient
  • The final blinding factor

The verifier must check: does actually correspond to the final commitment?

where is the final folded evaluation point (known to both parties).

But what is ? It's the result of folding all the generators through all rounds:

Computing this folded generator is the verifier's bottleneck: it requires applying all folding operations to the original generators, taking group operations. The verifier needs to know what commitment value a correctly-folded polynomial should produce, and there's no shortcut without computing the folded generators. This is IPA's fundamental trade-off: no trusted setup, but linear verification. We'll analyze when this cost is acceptable after the worked example.

Worked Example: IPA Verification

Let's trace through a complete IPA proof for a polynomial with 4 coefficients. This requires 2 rounds of folding. We work in , where (since ) and (since ).

Setup

  • Coefficient vector: (prover's secret)
  • Evaluation point: , so (public)
  • Claimed evaluation:
  • Generators: (for coefficients), (for inner product)
  • Initial commitment:

The verifier knows: , , , and all generators. The verifier does not know .

Round 1 (reduce from 4 to 2 elements)

Prover's work (uses secret ):

Split: , , ,

Compute cross inner products:

Send commitments to verifier:

Verifier's challenge: , so

Both parties compute (verifier uses only public information):

Folded generators:

Folded evaluation vector:

Updated commitment:

(Here )

The -coefficient of becomes .

Prover also computes (secretly):

Sanity check:

Round 2 (reduce from 2 to 1 element)

Prover's work:

Split: , , ,

Compute cross inner products:

Send commitments:

Verifier's challenge: , so

Both parties compute:

Folded generator:

Folded evaluation point:

Updated commitment:

(Here )

The -coefficient of becomes .

Prover computes:

Sanity check:

Final verification

Prover reveals:

Verifier computes the fully folded generator in terms of original generators:

This is the work: computing a linear combination of all original generators.

Verifier checks:

Substituting:

Expanding (mod 101):

The verifier also computes from the commitment updates: , which traces back through to the original commitment . Both sides match, so the proof is valid. The verifier is convinced that the prover knows such that , without ever learning .

Efficiency

  • Commitment size: One group element (same as KZG)
  • Proof size: group elements (the cross-terms from each round)
  • Verifier time: (must compute folded generators; this is the fundamental limitation)
  • Prover time:

The verifier's linear work is the main drawback compared to KZG's constant verification. However, IPA requires no trusted setup; the generators can be chosen transparently (e.g., by hashing).

The Linear Verifier Problem

This verification cost is a serious limitation. For a polynomial with coefficients (about 1 million), the verifier must perform over a million group operations, each involving expensive elliptic curve arithmetic. A scalar multiplication on an elliptic curve involves roughly 400 group additions, and each group addition involves 6-12 base field operations. The result: verification can be ~4000× slower than simple field arithmetic.

For interactive proofs where verification happens once, this is acceptable. For applications like blockchains where proofs are verified by thousands of nodes, or for recursive proof composition, linear verification becomes prohibitive.

This limitation motivated the development of schemes like Hyrax and Dory that exploit additional structure to achieve sublinear verification. (See Appendix D for Dory's approach.)

Comparing KZG and IPA

PropertyKZGIPA/Bulletproofs
Trusted setupRequiredNone
Commitment size
Proof size
Verification time
Prover time
AssumptionPairings (q-SDH)DLog only
Quantum-safeNoNo
Batch verificationExcellentGood
  • KZG is the right choice when verification efficiency is paramount and a trusted setup is acceptable. Most production SNARKs (Groth16, PLONK with KZG) use this approach.
  • IPA is the right choice when trust minimization is critical, or in systems designed for transparent setups (Halo, Pasta curves).

What if we want both transparency and efficient verification? Schemes like Hyrax and Dory achieve sublinear verification without trusted setup by exploiting additional algebraic structure. The machinery is more complex, so we cover these advanced schemes in Appendix D.

Multilinear Polynomial Commitments

Both KZG and IPA extend naturally to multilinear polynomials, exploiting the tensor structure of Lagrange basis evaluations.

Multilinear KZG uses an SRS encoding Lagrange basis polynomials at a secret point. Opening proofs require commitments (one witness polynomial per variable), with verification using pairings. Proof size grows linearly with the number of variables, not exponentially with coefficient count.

Multilinear IPA exploits the tensor structure of multilinear extensions. The evaluation vector has product structure that folding can exploit systematically, achieving logarithmic proof size with linear verification time.

The Role of PCS in SNARKs: Replacing Oracle Access

Polynomial commitment schemes are the cryptographic core that transforms interactive protocols into succinct, non-interactive proofs. To understand why, recall the gap we flagged in Chapter 3.

The Oracle Gap

Throughout this book, interactive proof protocols assume the verifier can evaluate certain polynomials at chosen points. Complexity theorists call this "oracle access": the verifier sends a query point, the oracle returns the correct evaluation, and the protocol moves on. Sum-check (Chapter 3) is the concrete example we have already seen: its final step requires the verifier to evaluate . But the pattern is entirely general. Any interactive oracle proof (IOP) assumes that the verifier can query prover-supplied polynomials at arbitrary points. In practice, no oracle exists.

Sometimes the verifier can evaluate herself. If is built entirely from public data (circuit structure, known constants, Fiat-Shamir challenges), the verifier just computes. But in most SNARK applications, depends on the prover's private witness. To make this concrete, consider Spartan's sum-check for R1CS satisfaction (we will study Spartan in detail in a later chapter):

Recall from Chapter 4 that is the equality polynomial: it "pins" the sum to a random evaluation point chosen by the verifier. The terms , , are MLEs of matrix-vector products involving the witness . The verifier can compute on her own (she chose and knows from the sum-check challenges), but she cannot compute , , or without knowing the witness. Sum-check has done its job, reducing an exponential sum to evaluations at a single random point, but the verifier is stuck at the last mile.

How PCS Closes the Gap

The pattern for any IOP is the same. The prover holds a polynomial that the verifier needs to query. A PCS turns the abstract oracle into a concrete mechanism via three steps:

  1. Before the IOP begins, the prover commits to the polynomial: . This commitment is short (a single group element for KZG, a hash root for FRI) and binding: the prover cannot change after sending .

  2. During the IOP, random challenges are determined interactively (or via Fiat-Shamir). The commitment was sent before any challenges were chosen, so the prover cannot adapt to the query point.

  3. When the verifier needs an evaluation, say at some challenge point , the prover provides the value along with an opening proof . The verifier checks , confirming that is the evaluation of the committed polynomial at the challenge point.

To return to our sum-check example: the prover commits to the witness polynomial before the protocol starts, the sum-check challenges are generated during the interaction, and at the end the prover opens with a proof that the verifier checks against the commitment. But the same three-step structure applies whenever an IOP assumes oracle access, regardless of which protocol produced the query point.

The binding property is what makes this work. Because the prover committed to before seeing the evaluation point, they cannot cheat: the committed polynomial is fixed, and the opening proof ties them to it. Schwartz-Zippel guarantees that checking at a random point catches any discrepancy with overwhelming probability.

This is the bridge between information theory and cryptography. An IOP achieves soundness assuming the verifier has oracle access to prover-supplied polynomials. The PCS instantiates that oracle, compiling the IOP into a cryptographic argument. Sum-check-based SNARKs are the most prominent example, but the compilation is universal: any IOP can be paired with any PCS. Chapter 11 develops this in full generality.

The Complete PCS Landscape

Now that we've seen both commitment schemes in depth, let's compare them systematically (including Dory from Appendix D and FRI from Chapter 10 for reference):

PropertyKZGIPA/BulletproofsDory (App. D)FRI (Ch. 10)
Trusted setupRequiredNoneNoneNone
Commitment size
Proof size
Verification time
Prover time
Assumptionq-SDH + PairingsDLog onlyDLog + PairingsHash collision
Post-quantumNoNoNoYes
BatchingExcellentGoodVery goodGood

KZG dominates when verification cost matters and trust is acceptable, which is why Ethereum L1 and most production SNARKs use it. IPA suits applications where trust minimization outweighs verification speed, like privacy-focused systems. FRI is the only option that survives quantum computers.

Key Takeaways

The Core Abstraction

  1. Polynomial commitments bridge theory and practice. Interactive proofs reduce complex claims to polynomial identities, but verifying those identities directly requires seeing the entire polynomial. A PCS lets the prover commit to a polynomial with a short commitment, then prove evaluations at specific points without revealing anything else.

  2. The interface is simple: Commit, Open, Verify. Binding ensures the prover can't change the polynomial after committing. Succinctness ensures commitments and proofs are much smaller than the polynomial itself. These two properties are what make succinct proofs possible.

  3. Polynomial evaluation reduces to inner product. For a polynomial , the evaluation . This connection underlies IPA, which proves inner products directly via recursive folding.

The Two Paradigms

  1. KZG achieves constant-size proofs via pairings. The key insight: if divides , then . The prover commits to the quotient; the verifier checks divisibility at a secret point using one pairing equation. No matter the polynomial's size, the proof is one group element.

  2. KZG requires trusted setup. The structured reference string encodes powers of a secret . If anyone learns , they can forge proofs. Multi-party ceremonies with thousands of participants ensure security under the "1-of-N" trust model: security holds if any single participant was honest.

  3. IPA eliminates trusted setup via recursive folding. Each round halves the problem size by combining left and right halves with a random challenge. After rounds, the prover reveals a single scalar. The verifier checks consistency by tracking commitment updates through all rounds.

  4. IPA's bottleneck is linear verification. The verifier must compute folded generators, requiring group operations. This is acceptable for single proofs but prohibitive for recursive composition or blockchain verification where proofs are checked thousands of times. Schemes like Dory (Appendix D) address this limitation.

Practical Considerations

  1. Batching amortizes costs across many polynomials. KZG batches evaluations at multiple points into one proof. For systems with dozens of committed polynomials, batching dominates the cost savings.

  2. The choice of PCS determines SNARK properties. KZG gives constant verification with trusted setup (Groth16, PLONK). IPA gives transparency with linear verification (Halo). FRI (next chapter) gives post-quantum security. The right choice depends on whether you prioritize verification speed, trust minimization, or quantum resistance.

Chapter 10: Hash-Based Polynomial Commitments and FRI

In 2016, the National Institute of Standards and Technology issued a warning that sent cryptographers scrambling. Quantum computers were coming, and they would break everything built on elliptic curves: RSA, Diffie-Hellman, ECDSA. This included every SNARK that existed. Groth16, the darling of the blockchain world, would become worthless the day a sufficiently powerful quantum computer came online.

The "toxic waste" problem of trusted setups was bad. The "quantum apocalypse" was existential.

This urgency drove the creation of a new kind of proof system. The goal was not just to remove the trusted setup; it was to build on cryptographic primitives believed to resist quantum attacks. Hash functions are one such primitive (lattice-based cryptography is another).

One answer came from Eli Ben-Sasson and collaborators: FRI (2017) and STARKs (2018). These are proof systems built entirely on hash functions, where "transparency" is not marketing but a technical property. No secrets. No ceremonies. No trapdoors that could compromise the system if they leaked, because no trapdoors exist at all.


The Merkle Tree: Committing to Evaluations

The foundation of hash-based commitments is the Merkle tree. If you've worked with Git or blockchain systems, you've already used one. The idea is simple: commit to a large dataset with a single hash value, then later prove any element is in the dataset without revealing the rest.

Construction:

  1. Start with your data elements at the bottom (these are the "leaves")
  2. Hash pairs of adjacent elements together:
  3. Now you have half as many values. Repeat: hash pairs together again
  4. Keep going until only one hash remains, the root
graph BT
    subgraph Data
        D1[d₁]
        D2[d₂]
        D3[d₃]
        D4[d₄]
    end
    subgraph Level 1
        H1[H₁]
        H2[H₂]
        H3[H₃]
        H4[H₄]
    end
    subgraph Level 2
        H12[H₁₂]
        H34[H₃₄]
    end
    subgraph Root
        R[H₁₂₃₄]
    end
    D1 --> H1
    D2 --> H2
    D3 --> H3
    D4 --> H4
    H1 --> H12
    H2 --> H12
    H3 --> H34
    H4 --> H34
    H12 --> R
    H34 --> R

The root is your commitment. It's just 32 bytes, regardless of whether you're committing to 8 values or 8 million.

Opening a value: Suppose someone wants to verify that element is at position :

  1. The prover provides plus the "authentication path," which consists of the sibling hashes needed to recompute the path from up to the root
  2. The verifier recomputes hashes from leaf to root, checking the result matches the committed root

If any element were different, the root would change (assuming collision-resistant hashes). This makes the commitment binding.

Properties:

  • Commitment size: One hash (32 bytes typically)
  • Opening proof size: hashes
  • Binding: Changing any leaf changes the root (collision-resistance of hash)

For polynomial commitments, we commit to the polynomial's evaluations over a domain. The Merkle root becomes the polynomial commitment.

The Core Problem: Low-Degree Testing

Suppose the prover commits to a function by Merkle-committing its evaluations on a domain of size . The prover claims is a low-degree polynomial (say degree less than ).

A polynomial evaluation vector is a Reed-Solomon codeword. In coding theory, a codeword is simply the encoded version of some message. If you have a polynomial of degree and you evaluate it at points (where ), the resulting vector is a codeword of the Reed-Solomon code with parameters . The polynomial's coefficients are the "message"; its evaluations are the "codeword." The extra evaluations beyond the needed to specify the polynomial are the "redundancy" that lets us detect errors.

How can the verifier check that a Merkle-committed polynomial is actually low-degree without reading all evaluations? The naive approach of checking random points doesn't help much: a function that agrees with a degree- polynomial on all but one point would pass most spot-checks but isn't low-degree. The key is that low-degree polynomials are a sparse subset of all possible functions, and a function that's not low-degree must differ from every valid codeword in many positions. FRI exploits this structure to catch deviations with high probability.

Strictly speaking, FRI does not prove that is a low-degree polynomial. It proves that is close to one, meaning it differs from some valid codeword in at most a small fraction of positions (say, 10%). This distinction matters because a cheater could take a valid polynomial and change just one evaluation point. FRI might miss that single corrupted point on any given query.

More formally, a function is -close to degree if there exists a polynomial of degree such that and agree on at least points. The distance measures how far is from being low-degree. We tune the parameters (rate, number of queries) so that being "close" is good enough for our application, or so that the probability of missing the difference is cryptographically negligible (e.g., ). In practice, the gap between "is low-degree" and "is close to low-degree" vanishes into the security parameter.

The Two Phases of FRI

FRI has two phases. In the commit phase, the prover repeatedly folds the polynomial: each round, commit to the current polynomial's evaluations via Merkle tree, receive a random challenge, fold to a smaller polynomial. This continues until the polynomial becomes a constant. In the query phase, the verifier spot-checks that the prover actually followed the folding rules, rather than committing to arbitrary values.

The commit phase is where the prover does the work; the query phase is where the verifier checks it.

The Commit Phase: Split and Fold

FRI transforms the low-degree testing problem through a recursive technique.

Any polynomial can be decomposed into even and odd parts:

where:

  • contains the even-power coefficients:
  • contains the odd-power coefficients:

If , then and . More precisely, and . This degree halving is the crucial property that makes FRI work.

Given a random challenge from the verifier, we fold the polynomial:

This new polynomial has degree . The claim "f has degree at most " reduces to " has degree at most ," a strictly smaller problem.

Where do Merkle trees fit in? Each round, the prover:

  1. Evaluates the current polynomial on domain
  2. Builds a Merkle tree over these evaluations (leaves are the field elements)
  3. Sends the Merkle root to the verifier
  4. Receives a random challenge
  5. Computes the folded polynomial and repeats

The Merkle root commits the prover to specific evaluation values before seeing the challenge. This ordering is crucial: if the prover could see first, they could craft fake evaluations that satisfy the folding check. By committing first, cheating becomes detectable.

Let's trace through the algebra with a concrete example.

Commit Phase: Worked Example

Let's trace through folding in .

Setup:

  • Initial polynomial: (degree 3, so )

  • Domain : The subgroup of order 8 generated by

Round 0: Commit to

The prover evaluates on and builds a Merkle tree over the 8 evaluations. The prover sends the Merkle root to the verifier.

Step 1: Decompose into even and odd parts

Coefficients:

  • Even part:
  • Odd part:

Verify:

Step 2: Receive challenge and fold

Verifier sends challenge (only after receiving ).

Result: We've reduced proving to proving .

Step 3: New domain

The new domain consists of the squares of elements in :

(size 4)

Round 1: Commit to

The prover evaluates on :

The prover builds a Merkle tree over these 4 evaluations and sends root to the verifier.

Step 4: Next challenge and fold

Verifier sends challenge (only after receiving ).

is a constant! The recursion terminates. The prover sends the constant in the clear.

After rounds, the verifier holds Merkle roots (one per round), the random challenges , and a claimed final constant . But how does the verifier know the prover didn't just make up a convenient constant? The Merkle commitments bind the prover to specific values, but the verifier hasn't actually checked any of them yet. This is where the query phase comes in.

The Query Phase

The commit phase produced Merkle trees, one for each folding round. The -th tree commits to the evaluations of on domain , where . Each folding halves the domain size, so the trees get progressively smaller: has leaves, has , and so on down to with leaves. A leaf in the -th tree is a single evaluation for some , and an "opening" is a Merkle path proving that leaf belongs to the committed root.

The verifier's goal is to check that these committed codewords are consistent with honest folding. If the prover cheated anywhere, the folding relationships won't hold for most positions. The verifier catches this by spot-checking: pick random positions and verify the folding formula.

The rate of a Reed-Solomon code is , where is the degree bound and is the domain size. This is the fraction of positions that carry "real information" vs. redundancy. For example, if we commit to a degree- polynomial by evaluating on a domain of size , then .

Why does rate matter? A cheating prover who committed to the wrong polynomial faces this problem: the wrong polynomial differs from the correct one at most positions (they can agree on at most points). Each random query has probability roughly of hitting one of the "lucky" positions where cheating goes undetected. So each query catches the prover with probability at least .

With independent queries, the probability that all queries miss the cheating is at most . To achieve -bit security (soundness error ), we need , which gives:

For and bits of security: queries. Lower rate means more redundancy and fewer queries needed, but larger proof size during the commit phase.

Each query works as follows. The verifier picks a random point in the final domain and traces backward through all Merkle trees. Each folded domain is the set of squares from , i.e., . Since , every point has exactly two preimages in : some and its negation .

To check the folding from to , the verifier needs three values: , , and . The prover opens two leaves in the -th Merkle tree (at positions and ) and one leaf in the -th tree (at position ). Each opening includes a Merkle path proving the leaf belongs to the committed root. The verifier then checks that the folding formula holds:

This is the same folding from before, rewritten to use evaluations. The first term recovers and the second term recovers , since and .

This pairing structure relies on multiplicative subgroups: if generates , then , so is in the group whenever is.

The consistency check includes the final round: the verifier computes what should be from the last committed codeword , and checks that it equals the claimed constant . If the prover lied about , this check will fail with high probability.

In summary, one query opens Merkle paths (two leaves per round for the sibling pairs, plus the positions in subsequent rounds). The verifier repeats this times with independent random positions, achieving soundness error as described above.

Query Phase: Worked Example

Let's continue our earlier example and trace through a complete query. Recall:

  • over
  • Domain (8 elements)
  • Challenge produced
  • Domain (4 elements)
  • Challenge produced (constant)

The prover has built two Merkle trees: one committing to 's evaluations on (8 leaves), another committing to 's evaluations on (4 leaves). The prover sent both Merkle roots during the commit phase, then sent the final constant 15.

Step 1: Verifier picks a random query point

The verifier chooses a random position in , say .

Step 2: Unfold to find preimages

What points in square to 13? We need such that .

Checking: and .

So the preimages are and .

Step 3: Query the prover

The verifier requests:

  • and from the first Merkle tree
  • from the second Merkle tree

The prover supplies these values with Merkle authentication paths. Let's compute:

Step 4: Verify consistency (Round 0 → 1)

The verifier checks: does equal the folded value from and ?

The consistency formula recovers the even and odd parts from evaluations at and :

Why this works: Since , we have and where . Adding these gives ; subtracting gives . Solving recovers the even and odd parts.

With , , :

For the odd part, note that :

Now apply the folding with :

The Round 0 → 1 consistency check passes.

Step 5: Verify consistency (Round 1 → 2)

Now check: does the claimed constant match what we'd get from folding ?

For the final round, the "domain" has collapsed to a single point. The verifier checks:

We have , so .

We need . The verifier requests this from the second Merkle tree (the prover opens the leaf at position 4 with a Merkle path). We have .

For the second term, we need :

The query passes. Both consistency checks hold, confirming that (at this query point) the prover's commitments are consistent with honest folding.

If the prover had lied about the constant, say claimed instead of 15, this final check would fail: .

The verifier repeats this process at multiple random query points. Each independent query that passes increases confidence that the prover's polynomial truly has low degree.

The Folding Paradigm

FRI's "split and fold" is not an isolated trick; it's an instance of one of the most powerful patterns in zero-knowledge proofs. Now that we've seen both phases concretely, let's step back and recognize where we've encountered folding before.

The core idea: use a random challenge to collapse two objects into one, halving the problem size while preserving the ability to detect cheating. More precisely:

  1. You have a claim about a "large" object (size , degree , dimension )
  2. Split the object into two "halves"
  3. Receive a random challenge
  4. Combine the halves via weighted sum:
  5. The claim about the original reduces to a claim about the folded object (size , degree , dimension )
  6. Repeat until trivial

Randomness is what makes this work. If the original object was "bad" (not low-degree, not satisfying a constraint), the two halves encode this badness. A cheater would need the errors in left and right to cancel: . But they committed to both halves before seeing , so this requires (a single value out of the entire field). Probability .

We have already seen this pattern multiple times:

  • Sum-check (Chapter 3): Each round folds the hypercube in half. A claim "" becomes "".

  • MLE streaming evaluation (Chapter 4): Fold a table of values down to one. Each step combines with weights .

  • IPA/Bulletproofs (Chapter 9): Fold the commitment vector. Two group elements become one: .

  • FRI (this chapter): Fold the polynomial's coefficient space. A degree- polynomial becomes degree- via .

The deep insight is that folding is dimension reduction via randomness. High-dimensional objects are hard to verify directly; you'd need to check exponentially many conditions. But each random fold projects away one dimension while preserving the distinction between valid and invalid objects (with overwhelming probability). After folds, you're left with a trivial claim.

And yet the structure persists. At each level, the polynomial is smaller but the relationships that matter (the algebraic constraints, the divisibility conditions, the distance from invalidity) all survive the descent. You're looking at a different polynomial in a smaller domain, but it's recognizably the same kind of object, facing the same kind of test. The recursion doesn't change the nature of the problem, only its scale.

Algebraically, this works because the objects being folded have low-degree polynomial structure. Schwartz-Zippel guarantees that distinct low-degree polynomials disagree almost everywhere. A random linear combination of two distinct polynomials is still distinct from the "honest" combination; you can't make errors cancel without predicting the randomness.

Another way to see it: one way to test if a polynomial is zero is to evaluate at a random point. Folding is this idea applied recursively with structure. Each fold is a random evaluation in disguise, and the structure ensures that evaluations compose coherently across rounds.

This paradigm extends beyond what we cover here. Nova and folding schemes (Chapter 23) fold entire R1CS instances: not polynomials, but constraint systems. The same principle applies: random linear combination of two instances yields a "relaxed" instance that's satisfiable iff both originals were.

Soundness and DEEP-FRI

The original FRI analysis (Ben-Sasson et al. 2018) established soundness but with somewhat pessimistic bounds. Achieving 128-bit security required many queries, increasing proof size.

DEEP-FRI (Ben-Sasson et al. 2019) improves soundness by sampling outside the evaluation domain. The idea: after the prover commits to the polynomial , the verifier picks a random point outside and asks the prover to reveal . This "out-of-domain" sample provides additional security because a cheating prover can't anticipate which external point will be queried.

The name stands for Domain Extending for Eliminating Pretenders. The technique achieves tighter soundness bounds, reducing the number of queries needed for a given security level. More recent work continues to improve these bounds: STIR (2024) achieves query complexity compared to FRI's , where is the security parameter and is the degree bound. WHIR (2024) further improves verification time to a few hundred microseconds. These protocols maintain FRI's core split-and-fold structure while optimizing the recursion.

FRI as a Polynomial Commitment Scheme

So far we've shown how FRI proves that a function is close to a low-degree polynomial. But a polynomial commitment scheme needs to prove evaluation claims: "my committed polynomial satisfies ." How do we bridge this gap?

The answer uses the divisibility trick from earlier chapters.

Applying the Divisibility Trick

Recall that if and only if divides . When the claim is true, the quotient is a polynomial of degree . When the claim is false, this "quotient" has a pole at ; it's not a polynomial at all.

This transforms evaluation proofs into degree bounds:

If...Then the quotient ...
(honest)is a polynomial of degree
(cheating)has a pole at ; not a polynomial at all

To prove , the prover constructs and runs FRI configured with degree bound to prove that has degree . This is not the same as proving is merely "low-degree" in some vague sense; FRI must target the specific bound matching the claimed degree of . If a cheating prover submitted a of degree (one too high), FRI with bound would catch it.

But FRI on alone is not sufficient. It shows has the right degree; it does not show that is actually rather than some unrelated polynomial of the same degree. The verifier must also spot-check the relationship at random query points. If both checks pass, the quotient has the right degree and is correctly derived from , which together imply .

The Full Protocol

Setup: Fix an evaluation domain of size (a multiplicative subgroup), a hash function for Merkle trees, and a degree bound .

Commit (prover):

  1. Evaluate on to get
  2. Build a Merkle tree over these evaluations
  3. Send the Merkle root to the verifier

After commit, the verifier holds only . The prover holds , all evaluations, and the full Merkle tree .

Open (interactive, to prove ):

Step 1: Construct the quotient

  • Prover computes for each
  • Prover builds Merkle tree over evaluations of , sends

Step 2: FRI commit phase (folding)

For where :

  • Verifier sends random challenge
  • Prover computes folded polynomial
  • Prover evaluates on the folded domain
  • Prover builds Merkle tree , sends

After rounds, is a constant . Prover sends .

Step 3: FRI query phase

  • Verifier sends random query positions
  • For each query, prover opens:
    • from (to check divisibility relation)
    • from (to check divisibility relation)
    • Sibling pairs from each (to check folding consistency)

Verify:

  1. Check Merkle proofs for all opened values
  2. Check divisibility at each query:
  3. Check folding consistency at each query: for each round , verify
  4. Check final constant: the last folding step yields

The verifier never sees the full polynomials or . They only see spot-checked evaluations, verified against the Merkle commitments.

Note that FRI doesn't speed anything up. It is the low-degree test. Without FRI, you'd have a Merkle commitment but no way to prove anything about degree: the prover could commit to arbitrary garbage. FRI is what makes this a polynomial commitment scheme rather than just a vector commitment.

There is a subtlety in the protocol above that deserves explicit attention. The FRI run proves has degree , and the spot-check proves is consistent with . Together these imply , but only if itself has degree . What if the prover committed to a high-degree (or arbitrary non-polynomial values) in the Merkle tree? Then could pass the degree check by coincidence: a high-degree minus , divided by , might produce a low-degree quotient if the high-degree terms cancel. The spot-check at query points would still pass because it verifies , which holds by construction regardless of the degree of .

In the standalone FRI-as-PCS protocol presented here, needs its own degree proof. The prover must either run a separate FRI instance on to establish , or bundle into a batched FRI alongside (the next section shows how). In the STARK setting (Chapter 15), this issue is handled differently: FRI runs on the composition polynomial, which is a random linear combination of constraint quotients. The composition polynomial's degree bound implicitly constrains the trace polynomials, so no separate degree proof for the trace is needed. But in any context where FRI serves as a general-purpose PCS for opening evaluations, the degree of the committed polynomial must be established independently.

Batching

Multiple polynomials and evaluation points can be combined into a single FRI proof. Suppose we have opening claims: . Each claim produces a quotient polynomial .

The verifier sends a random batching challenge . The prover computes the combined quotient:

Now the prover runs FRI on :

  1. Build a Merkle tree over evaluations of on domain
  2. For each folding round, build a Merkle tree over the folded polynomial
  3. Send all Merkle roots and the final constant

The individual quotients don't need their own FRI proofs since they're combined into before FRI runs. The savings come from running one FRI proof instead of .

However, the verifier still needs to check that each original divisibility relation holds. At each query point , the verifier:

  • Opens from their respective Merkle trees
  • Opens from
  • Computes each
  • Checks that

The FRI cost (the expensive part) is amortized across all claims. The divisibility spot-checks scale linearly with , but these are just field arithmetic, cheap compared to FRI.

Practical Considerations

The Blow-up Factor

FRI evaluates polynomials on a domain much larger than their degree. If a polynomial has degree , the evaluation domain has size where is the rate.

Typical choices: to (blow-up factor 4x to 16x).

Trade-off: Lower rate (more redundancy) means:

  • Larger initial commitment (more evaluations)
  • But stronger soundness per query (fewer queries needed)
  • Net effect often neutral on total proof size

Chapter 20 quantifies this tradeoff for STARK provers, showing how grinding (proof-of-work) and batched FRI interact with the blowup factor to determine the optimal operating point for prover speed versus proof size.

Coset Domains

The examples above used multiplicative subgroups directly: where . In practice, FRI implementations typically use cosets instead: sets of the form where is a multiplicative subgroup and is a generator offset.

Why the difference? Subgroups always contain 1, and satisfy for all elements. This structure can be exploited in certain attacks. Cosets avoid this: no element satisfies (since ), removing a potential attack surface.

The folding arithmetic works identically. If , then squaring every element gives where . The sibling structure ( and mapping to the same ) is preserved. The only change is bookkeeping: the verifier tracks the coset offset alongside the subgroup.

Hash Function Choice

STARKs using FRI rely on collision-resistant hash functions:

  • Traditional: SHA-256, Keccak
  • SNARK-friendly: Poseidon, Rescue (fewer constraints when verified in-circuit)

The hash function determines concrete security. If the hash has 256-bit output, and we assume collision-resistance, FRI inherits 128-bit security (birthday bound).

Comparing FRI to Algebraic PCS

PropertyFRIKZGIPA
Trusted setupNoneRequiredNone
AssumptionHash collision-resistancePairings + DLogDLog
Post-quantumYesNoNo
Commitment size
Proof size
Verifier time
Prover time

When to use FRI:

  • Trust minimization is critical (no setup ceremony)
  • Post-quantum security is required
  • Larger proofs are acceptable (still polylogarithmic)

When to avoid FRI:

  • Proof size must be constant (KZG better)
  • On-chain verification cost is critical (pairing checks cheaper than FRI verification)

FRI in the Wild: STARKs

FRI is the cryptographic backbone of STARKs (Scalable Transparent ARguments of Knowledge):

  1. Arithmetization: Convert computation to polynomial constraints (AIR format)
  2. Low-degree extension: Encode computation trace as polynomial evaluations
  3. Constraint checking: Combine with composition polynomial
  4. FRI: Prove the composed polynomial is low-degree

The "T" in STARK stands for "Transparent": no trusted setup, enabled by FRI. The "S" stands for "Scalable": prover time is quasi-linear, enabled by FFT and the recursive structure of FRI.

Modern systems like Plonky2 and Plonky3 combine PLONK's flexible arithmetization with FRI-based commitments, getting the best of both worlds.

Key Takeaways

  1. Merkle trees commit to evaluations, not coefficients. FRI commits to a polynomial by hashing its evaluations over a domain . The Merkle root is 32 bytes regardless of polynomial size. Opening a single evaluation costs hashes.

  2. FRI proves proximity to low-degree, not exact low-degree. A function passing FRI is -close to some degree- polynomial (agrees on at least points). For cryptographic applications, "close enough" suffices because the gap vanishes into the security parameter.

  3. Folding halves the degree per round. The decomposition splits a degree- polynomial into two degree- parts. A random combination preserves errors: if wasn't low-degree, neither is (with overwhelming probability).

  4. Commit before challenge, verify after. Each round the prover Merkle-commits to the current polynomial's evaluations, then receives the folding challenge . This ordering prevents the prover from crafting fake evaluations that happen to satisfy the folding check.

  5. Query cost depends on rate. With rate , each query catches cheating with probability . For -bit security: queries. Lower rate means fewer queries but larger commitments.

  6. Divisibility converts evaluation claims to degree bounds. To prove , show that is a polynomial of degree . If , then has a pole at and isn't a polynomial at all.

  7. FRI is the mechanism, not an optimization. Without FRI, a Merkle commitment is just a vector commitment with no degree guarantees. FRI is what makes this a polynomial commitment scheme.

  8. Transparency comes from hash functions. The only cryptographic assumption is collision-resistance of the hash. No trusted setup, no toxic waste, no trapdoors. Anyone can verify proofs with the same public parameters.

  9. Post-quantum security. Hash functions are believed to resist quantum attacks (Grover's algorithm only provides quadratic speedup). FRI-based proofs remain secure when elliptic curve schemes break.

  10. The cost is proof size. FRI proofs are compared to KZG's . For applications where on-chain verification cost dominates (Ethereum L1), this matters. For applications prioritizing trust minimization or quantum resistance, FRI wins.

Chapter 11: The SNARK Recipe: Assembling the Pieces

Before compilers, programmers wrote machine code by hand. Each program required intimate knowledge of the target CPU's instruction set. A program for one machine wouldn't run on another. It was slow, error-prone, and expertise barely transferred between architectures.

Then came FORTRAN (1957) and the idea of a compiler: a standardized translation process that takes a high-level program and produces machine code for any target. The programmer writes once; the compiler handles the details. Different programs produce different executables, but the methodology is uniform.

For the first 30 years of zero-knowledge (1985–2015), protocols were like hand-written assembly. A cryptographer would craft a protocol for Graph Isomorphism, then start from scratch for Hamiltonian Cycles. Each proof system was a custom creation.

Modern SNARKs are like compilers. You feed in a computation, and out comes a proof. Different computations produce different proofs, but the recipe is standardized. This chapter describes that recipe. It powers every modern SNARK from Groth16 to Halo 2 to STARKs.

(The analogy extends further: a zkVM is like compiling an interpreter once, then running arbitrary programs through it. One circuit, any computation. If you're unfamiliar with zkVMs, don't worry; the concept will make more sense after seeing how circuits work.)

Modern SNARKs decompose into three layers, each with a distinct role. Understanding this decomposition is more valuable than memorizing any particular system; it provides the conceptual vocabulary to navigate the entire landscape. The key abstraction enabling this modularity is the Interactive Oracle Proof (IOP), introduced by Ben-Sasson, Chiesa, and Spooner in 2016. IOPs unified the earlier notions of interactive proofs and probabilistically checkable proofs into a single framework that makes the "IOP + PCS" compilation strategy possible.

The Three-Layer Architecture

Every modern SNARK follows the same structural pattern:

flowchart TB
    COMP["COMPUTATION<br/>'I know x such that f(x) = y'"]
    ARITH["ARITHMETIZATION<br/>R1CS, PLONK gates, AIR"]

    IOP["LAYER 1: IOP<br/>Protocol logic: rounds, challenges, checks<br/>Polynomials sent abstractly (oracle model)"]

    PCS["LAYER 2: PCS<br/>Instantiate oracles cryptographically<br/>Commit, then open at queried points"]

    FS["LAYER 3: Fiat-Shamir<br/>Hash transcript → challenges<br/>Interactive → Non-interactive"]

    SNARK["SNARK<br/>Succinct proof, fast verification"]

    COMP --> ARITH --> IOP --> PCS --> FS --> SNARK

Layer 1 defines the protocol logic: the sequence of rounds, what polynomials the prover "sends," what queries the verifier makes, and what checks determine acceptance. This is where sum-check lives, where PLONK's permutation argument is specified, where GKR's layer-by-layer reduction happens. The prover "sends polynomials" in an abstract sense; the verifier has oracle access (can query any evaluation without seeing the full polynomial). Layer 1 specifies what to prove and how to check it.

Layer 2 instantiates the oracle model cryptographically. Oracle access becomes commitment and opening: the prover commits to a polynomial before seeing queries, then provides evaluation proofs at requested points. The binding property of the commitment scheme ensures the prover cannot retroactively modify their polynomial.

Layer 3 eliminates interaction. The verifier's random challenges are replaced by hash function outputs computed from the transcript. The prover simulates the entire interaction locally and outputs a static proof.

This separation enables genuine modularity: the same IOP can be compiled with different commitment schemes, yielding systems with different trust assumptions, proof sizes, and verification costs. PLONK with KZG gives constant-size proofs requiring trusted setup. PLONK with FRI gives larger proofs but no trusted setup and post-quantum security. The IOP is unchanged; only the cryptographic instantiation differs.

Layer 1: Interactive Oracle Proofs

An Interactive Oracle Proof (IOP) is an interactive protocol where the prover sends polynomials rather than field elements, and the verifier has oracle access to these polynomials: they can query any evaluation without seeing the full polynomial description. The IOP defines the protocol logic: what polynomials are exchanged, what queries the verifier makes, and what checks determine acceptance.

Example: Sum-Check as an IOP

To make this concrete, consider how the sum-check protocol fits into the IOP framework. (This is just one example; other IOPs like PLONK's permutation argument or GKR have different structures.)

  1. Prover sends univariate polynomial
  2. Verifier evaluates and , checks (the claimed sum)
  3. Verifier sends random challenge
  4. Prover sends univariate polynomial
  5. Verifier evaluates and , checks
  6. Continue for rounds
  7. Final step: Verifier queries the original polynomial at and checks

The univariate polynomials are low-degree (degree at most in one variable), so they can be sent explicitly as coefficients. But the final step requires oracle access to : the verifier must query to verify that the sum-check reductions were honest. This is where the PCS comes in.

IOP Quality Metrics

Not all IOPs are equivalent. The critical parameters:

Query complexity: The number of evaluation queries the verifier makes. Each query becomes an evaluation proof in the compiled SNARK, directly affecting proof size.

Round complexity: The number of prover-verifier exchanges. Each round becomes a hash computation in Fiat-Shamir. Sum-check has rounds; some IOPs achieve constant rounds.

Prover complexity: The computational cost of generating the prover's messages. This should be quasi-linear in the computation size: or . Quadratic prover complexity renders the system impractical for large computations.

Soundness error: The probability that a cheating prover convinces the verifier. Typically per round, where is the maximum polynomial degree.

These parameters trade off against each other. Fewer queries mean smaller proofs but often require more prover work or stronger assumptions. The art of IOP design lies in navigating these trade-offs for specific applications.

From Oracle Model to Cryptography

IOPs assume the verifier can query certain polynomials at points of their choosing, with the polynomial fixed before the query point is revealed. In sum-check, the univariate polynomials are sent explicitly, so the verifier evaluates them directly. But the original polynomial is too large to send. The verifier needs to query at the final step, and this query must be answered by something other than sending the entire polynomial. This is where oracle access matters.

Why does the ordering matter? Recall the Schwartz-Zippel lemma: a nonzero polynomial of degree has at most roots. If the verifier picks a random point from a field of size , a cheating prover's polynomial (which should be zero but isn't) will fail the check with probability at least . With typical parameters (, ), a single random query catches cheating with overwhelming probability.

But this analysis assumes the polynomial is fixed before is chosen. If the prover sees first, they can construct a polynomial that passes the check at while being wrong elsewhere. The oracle model captures this constraint abstractly; Layer 2 enforces it cryptographically through commitment schemes.

Layer 2: Polynomial Commitment Schemes

The IOP assumes the verifier can query polynomial evaluations. In reality, there is no oracle: the prover must send something over a communication channel. The polynomial commitment scheme (PCS) bridges the gap, turning the abstract oracle into a concrete cryptographic mechanism. Chapters 9 and 10 covered PCS in detail; here's the quick reminder of what matters for compilation.

A PCS provides three operations: Commit (polynomial to short commitment), Open (produce evaluation proof), and Verify (check the proof). The critical property is binding: once the prover sends a commitment, they cannot open it to evaluations of a different polynomial. For arguments of knowledge, the PCS must also be extractable: if a prover can pass verification, there exists an extractor that can reconstruct the polynomial they committed to.

Compilation

The compilation from IOP to interactive argument, a protocol where prover and verifier exchange messages with soundness based on cryptographic assumptions rather than information-theoretic guarantees, is mechanical:

  • When the IOP specifies "prover sends polynomial ," the compiled protocol has the prover send
  • When the IOP specifies "verifier queries ," the compiled protocol has the verifier announce , the prover respond with and proof , and the verifier check

Why Compilation Preserves Soundness

The IOP's soundness proof assumes the verifier receives the true evaluation when they query. After compilation, the verifier instead receives a claimed value with a proof .

The binding property ensures the prover can only open to evaluations the committed polynomial actually takes. Since the prover sends before seeing the query point , binding cryptographically enforces the ordering that the oracle model assumes. If binding fails, the prover could commit to one polynomial and open to another, collapsing soundness entirely.

PCS Choices

Different commitment schemes offer different trade-offs:

PCSSetupProof SizeVerificationAssumption
KZGTrustedq-SDH + Pairings
IPATransparentDLog
FRITransparentCollision-resistant hash

The choice is application-dependent. On-chain verification pays per byte and per operation; KZG's constant-size proofs minimize gas costs. Systems prioritizing trust minimization accept larger proofs for transparent setup. Long-term security considerations may favor FRI's resistance to quantum attacks.

Soundness Composition

Recall that soundness error is the probability a cheating prover convinces the verifier of a false statement, and binding error is the probability a prover can open a commitment to two different values. Both are negligible for secure constructions.

Let the IOP have soundness error and the PCS have binding error . The resulting SNARK (IOP + PCS) has soundness error at most .

Proof sketch: A cheating prover either (1) breaks the IOP soundness by finding a cheating strategy that succeeds with the committed polynomial, or (2) breaks binding by opening to evaluations inconsistent with the commitment. By union bound, cheating succeeds with probability at most .

Layer 3: The Fiat-Shamir Transformation

The Fiat-Shamir transformation is deceptively simple but foundational. Virtually every deployed SNARK uses it, and subtle implementation errors have led to real-world vulnerabilities.

Adi Shamir and Amos Fiat introduced the technique in 1986, originally to convert interactive identification schemes into digital signatures. Their insight was that if the verifier's only role is to provide randomness, a hash function can play that role instead. The idea predates SNARKs by decades, but it applies directly: after PCS compilation, we have an interactive argument where the verifier's only contribution is random challenges. For many applications (blockchain verification, credential systems, asynchronous protocols) this interaction is unacceptable. We need a static proof that anyone can verify without engaging in a conversation.

The Fiat-Shamir transformation achieves this by replacing the verifier's random challenges with hash function outputs.

In the interactive protocol:

Prover -> commitment C_1 -> Verifier
Verifier -> random r_1 -> Prover
Prover -> commitment C_2 -> Verifier
Verifier -> random r_2 -> Prover
...

After Fiat-Shamir:

Prover computes:
  C_1 = Commit(f_1)
  r_1 = Hash(C_1)
  C_2 = Commit(f_2)
  r_2 = Hash(C_1 || r_1 || C_2)
  ...
Prover outputs: (C_1, C_2, ..., evaluations, proofs)

The verifier reconstructs challenges from the transcript and performs all checks.

Security Analysis

The interactive protocol's soundness rests on unpredictability: the prover commits to without knowing what challenge will be. This prevents the prover from crafting commitments that exploit specific challenges.

In an interactive proof, the verifier sends a random challenge after the prover commits. The prover cannot change the past. In a non-interactive proof, the prover generates the challenge themselves. What stops them from cheating?

Fiat-Shamir preserves unpredictability under the random oracle model: the assumption that the hash function behaves like a truly random function. If the prover cannot predict before choosing , they face the same constraint as in the interactive setting.

A cheating prover's only recourse is to try many values of , compute for each, and hope to find one yielding a favorable challenge. This is a grinding attack. If the underlying protocol has soundness error , and the prover can compute hashes, the effective soundness error becomes roughly .

For a protocol with and an adversary computing hashes, the effective soundness is (still negligible). Larger fields provide additional margin.

Transcript Construction

A subtle but critical requirement: the hash must include the entire transcript up to that point.

The challenge must depend on:

  • The public statement being proved
  • All previous commitments
  • All previous challenges
  • All previous evaluation proofs

Omitting the public statement allows the same proof to verify for different statements (a complete soundness failure). Omitting previous challenges may allow the prover to fork the transcript and find favorable paths. These aren't hypothetical concerns: the "Frozen Heart" vulnerability (2022) affected Bulletproofs, PlonK, and multiple production codebases because public inputs weren't included in transcript hashes. The "Last Challenge Attack" (2024) exploited similar issues in KZG batching. A 2023 survey found over 30 weak Fiat-Shamir implementations across 12 different proof systems.

Modern implementations prevent these errors using the sponge model for transcript construction. Every time the prover speaks, they "absorb" their message into the sponge state. Every time they need a challenge, they "squeeze" to extract random bits. This ensures each challenge depends on the entire history, not just the most recent message. You cannot get fresh randomness out without first putting your commitment in, and once something is absorbed, it permanently affects all future outputs.

The Random Oracle Caveat

Fiat-Shamir security is proven in the random oracle model. Real hash functions are not random oracles; they are deterministic algorithms with internal structure.

No practical attacks are known against carefully instantiated Fiat-Shamir. But there is no proof of security from standard assumptions. The hash function must be collision-resistant, but collision resistance alone does not suffice for Fiat-Shamir security.

This remains one of the gaps between theory and practice in deployed cryptography.

Concrete Trace: R1CS to SNARK

The three layers assume the computation is already expressed as polynomial identities. This prior step, arithmetization, converts the statement "I know such that " into constraint systems (R1CS, PLONK gates, AIR) that the IOP can work with.

Consider proving knowledge of a satisfying R1CS witness.

Arithmetization

The R1CS constraint must hold for the witness vector , where io contains the public inputs/outputs and contains the private values. The full witness is encoded as its multilinear extension : the unique polynomial of degree at most 1 in each variable satisfying for all . Define such that vanishes on all of if and only if the constraints are satisfied.

IOP (Sum-Check)

To prove all constraints are satisfied, the prover proves:

Each sum-check round, the prover sends the univariate polynomial in the clear (it's low-degree, so this is just a few field elements). After rounds, this reduces to a single evaluation of at a random point .

PCS Compilation (with KZG)

The only polynomial requiring commitment is (too large to send explicitly):

  • Prover sends at the start
  • Final evaluation comes with a KZG opening proof

In the Fiat-Shamir transform, each challenge is computed as . The final proof is the transcript of round polynomials plus the opening proof.

Proof Size Analysis

For a circuit with variables (approximately one million gates), with KZG:

  • Sum-check round polynomials: ~20 rounds × ~3 coefficients × 32 bytes = ~2 KB
  • Batched KZG opening proof: ~48 bytes

Total: approximately 2 KB.

The witness contains millions of field elements. The proof is five orders of magnitude smaller. This is succinctness.

With FRI instead of KZG, proof size grows to ~100 KB (larger, but still succinct, and requiring no trusted setup).

Zero-Knowledge

We have focused on succinctness and soundness. The basic construction does not provide zero-knowledge: the sum-check polynomials reveal information about the witness.

A proof system is zero-knowledge if there exists a simulator that, given only the statement (not the witness), produces transcripts indistinguishable from real proofs. Intuitively: the proof reveals nothing about the witness beyond the truth of the statement. The verifier could have generated the same transcript themselves without seeing the witness.

Adding zero-knowledge requires additional techniques:

  • Hiding commitments: randomized commitments (Pedersen with blinding factors) so the commitment reveals nothing about the polynomial
  • Masking polynomials: random low-degree polynomials added to the prover's messages that sum to zero (preserving correctness) but obscure individual evaluations

Chapter 17 develops these techniques in detail. The key point here: zero-knowledge is a property layered on top of the basic SNARK construction. The three-layer architecture applies equally to zero-knowledge and non-zero-knowledge systems.

Modularity in Practice

The three-layer decomposition has practical consequences beyond conceptual clarity.

  • Upgradability: When a better PCS is developed, existing IOPs can adopt it. PLONK was originally specified with KZG. It now has FRI-based variants (Plonky2, Plonky3) that inherit PLONK's arithmetization and IOP while gaining transparency and post-quantum resistance.
  • Specialized optimization: Each layer can be optimized independently. Improvements to sum-check proving (Chapter 19) benefit all sum-check-based SNARKs regardless of their PCS. Improvements to KZG batch opening benefit all KZG-based systems regardless of their IOP.
  • Analysis decomposition: Security analysis can proceed layer by layer. The IOP's soundness is analyzed in the oracle model. The PCS's binding property is analyzed under its cryptographic assumption. Fiat-Shamir security is analyzed in the random oracle model. Each analysis is self-contained.
  • System comprehension: When encountering a new SNARK, the first questions are: What is the IOP? What is the PCS? This decomposition makes the landscape navigable. New systems become variations on known themes rather than entirely novel constructions.

Taxonomy

With the three-layer model, we can classify the SNARK landscape:

By IOP:

  • Linear PCP-based: Groth16 (the prover's messages are linear combinations of wire values, enabling constant verification via encrypted linear checks)
  • Polynomial IOP-based: PLONK, Marlin (the prover sends polynomials, the verifier checks polynomial identities)
  • Sum-check-based: Spartan, Lasso (verification reduces to sum-check over multilinear polynomials)
  • FRI-based: STARKs (low-degree testing via the FRI protocol)

By PCS:

  • Pairing-based: KZG (constant-size proofs, trusted setup)
  • Discrete-log-based: IPA/Bulletproofs (logarithmic proofs, transparent)
  • Hash-based: FRI (polylogarithmic proofs, post-quantum)

By setup requirements:

  • Circuit-specific: Groth16 (new trusted setup per circuit)
  • Universal: PLONK, Marlin (single trusted setup for all circuits up to a size bound)
  • Transparent: STARKs, Spartan+IPA (no trusted setup)

No single system dominates all metrics. The choice depends on what constraints bind most tightly in a given application. The coming chapters examine many of these systems in detail: Groth16, PLONK, STARKs, Spartan, and others.

Key Takeaways

  1. Three-layer architecture: IOP defines protocol logic, PCS provides cryptographic binding, Fiat-Shamir eliminates interaction. Each layer is analyzed independently.

  2. Commitment ordering is the key: The prover commits before the verifier queries. The PCS's binding property cryptographically enforces this ordering, which is what enables random evaluation to catch cheating.

  3. Fiat-Shamir security requires complete transcripts: Every prover message must enter the hash, including the public statement. Omissions break soundness; grinding attacks bound the effective advantage.

  4. Modularity is structural: Same IOP, different PCS yields different systems. This is how the field evolves.

  5. Query complexity determines proof size: Each IOP query becomes a PCS opening proof.

  6. Zero-knowledge is additive: The basic construction gives succinctness and soundness. Zero-knowledge requires additional masking.

  7. No universal optimum: KZG minimizes proof size with trusted setup. FRI eliminates setup with larger proofs. IPA trades verification time for transparency. The choice is application-dependent.

Chapter 12: Groth16: The Pairing-Based Optimal

In 2016, when Zcash was preparing to launch, they faced a practical problem. Blockchain transactions are expensive. Every byte costs money. The existing SNARKs (Pinocchio and its descendants) required proofs of nearly 300 bytes. It was workable, but clunky.

Then Jens Groth published a paper that seemed to violate the laws of physics. He shaved the proof down to 128 bytes on BN254. To demonstrate just how small this was, developers realized they could fit an entire zero-knowledge proof, verifying a computation of millions of steps, into a single tweet:

[Proof: 0x1a2b3c...] #Zcash

This was not just optimization. It was the theoretical minimum. Groth proved mathematically that for pairing-based systems, you literally cannot get smaller than 3 group elements. He had found the floor.

The paper, "On the Size of Pairing-based Non-interactive Arguments," became the most deployed SNARK in history. When Zcash launched its Sapling upgrade in 2018, it used Groth16. When Tornado Cash and dozens of other privacy applications needed succinct proofs, they used Groth16. The answer to "what's the smallest possible proof?" turned out to be the answer the entire field needed.


The SNARKs we've studied follow a common pattern: construct an IOP, compile it with a polynomial commitment scheme, apply Fiat-Shamir. This modular approach yields flexible systems (swap the PCS, change the trust assumptions) but leaves efficiency on the table.

Groth16 takes a different path. Rather than instantiating a generic framework, it was designed from first principles to minimize proof size. The layers are fused: optimized as a unit rather than composed as modules. Chapter 8 introduced QAP as one approach to arithmetization; here we develop it fully.

This optimality comes with constraints. The trusted setup is circuit-specific: change a single gate and you need a new ceremony. The prover cannot be made faster than without giving up something else. Zero-knowledge requires careful blinding woven into the protocol's fabric rather than layered on top.

From R1CS to Polynomial Identity

Chapter 8 introduced R1CS: the prover demonstrates knowledge of a witness vector satisfying

where , , are matrices encoding the circuit and denotes the Hadamard (element-wise) product. Each row enforces one constraint of the form .

Groth16's first move is to transform this system of constraints into a single polynomial identity.

The QAP Transformation

Fix a set of distinct evaluation points in the field . For each column of the matrices, define polynomials , , by Lagrange interpolation:

These are the basis polynomials: one for each wire in the circuit. They encode the circuit's structure: which wires participate in which constraints, with what coefficients.

Given witness , form the witness polynomials:

The construction ensures that at each evaluation point , the witness polynomial equals the dot product : exactly the value appearing in the -th constraint. The polynomial encapsulates all constraints simultaneously.

The R1CS Condition Becomes a Polynomial Vanishing Condition

The R1CS is satisfied if and only if:

This says the polynomial vanishes at every . By the factor theorem, must be divisible by the vanishing polynomial:

The R1CS is satisfied if and only if there exists a polynomial , the quotient or cofactor, such that:

This is the QAP (Quadratic Arithmetic Program) identity. It compresses constraint checks into one polynomial divisibility claim.

Worked Example: Continuing

From Chapter 8, we have 5 constraints encoding the circuit: , , , , and output . This gives 7 witness positions. Let the evaluation points be .

The witness is representing .

For the second column (corresponding to variable ), the column vector in is , representing that appears in constraints 1 and 3. The basis polynomial interpolates through points :

where is the -th Lagrange basis polynomial (recall from Chapter 2: , satisfying and for ).

Each basis polynomial , , has degree at most . Once we compute all of them, the witness polynomials are:

and similarly for and . Each witness polynomial has degree at most .

The polynomial has degree at most . Since the R1CS is satisfied, vanishes at all five evaluation points , so the vanishing polynomial divides . The quotient has degree .

In practice, the prover computes via polynomial division: evaluate and at enough points, divide pointwise, then interpolate. FFT-based methods make this efficient.

The Core Protocol Idea

Verifying the QAP identity directly requires evaluating polynomials of degree , far too expensive for succinctness. The Schwartz-Zippel approach suggests evaluating at a random point : if , then the identity holds with overwhelming probability.

But the witness polynomials encode the secret witness. We cannot simply send to the verifier.

Groth16 solves this with three ideas working in concert:

  1. Homomorphic hiding: Evaluate in the exponent. Send instead of .

  2. Pairing verification: Check multiplication via bilinear pairing. The equation lets the verifier check multiplicative relations on hidden values.

  3. Structured randomness: Embed the check into the trusted setup. The verifier never sees ; they receive encoded values that enable verification without knowing the secret.

Linear PCPs: The Abstraction

Groth16 is best understood through the lens of Linear PCPs, introduced in Chapter 1. Recall: in a standard PCP, the verifier queries specific positions of a proof string. In a Linear PCP, the "proof" is a linear function , and the verifier can only ask for linear combinations for chosen query vectors .

This restriction enables a clever trick: if the queries are encrypted as , the prover can compute homomorphically—without ever learning itself.

Groth16's trusted setup embeds carefully chosen query vectors into group elements. The prover computes responses using only scalar multiplication: linear operations on the encrypted queries. The verifier checks a quadratic relation using a single pairing equation.

This is why the proof has exactly three elements. Verification is a single pairing equation of the form . Pairings take one element from and one from , so the proof needs elements in both source groups: two in (conventionally called and ) and one in (called ).

The Trusted Setup

Groth16 requires a Structured Reference String (SRS) generated by a trusted ceremony. The ceremony has two phases with fundamentally different properties.

Phase 1: Powers of Tau (Universal)

A secret random value is chosen. The ceremony outputs encrypted powers:

where is large enough to support circuits up to a certain size.

This phase is universal: the same Powers of Tau can be used for any circuit within the size bound. Public ceremonies like "Perpetual Powers of Tau" provide reusable parameters. The MPC ceremony structure (1-of-N trust model, chained contributions) was covered in Chapter 9.

Phase 2: Circuit-Specific Secrets

Phase 2 generates additional secrets that are specific to the circuit being proven. Their roles will become clear when we see the verification equation; for now, here's the intuition:

and (Cross-term cancellation): When the prover constructs their proof elements, the verification equation produces "cross-terms" like . The blinding ensures these terms cancel correctly without revealing the witness.

(Public input binding): Separates public from private inputs in the verification equation. The verifier computes a commitment to the public inputs and checks it against the -scaled portion of the SRS.

(Private witness binding): Forces the prover to use consistent values across the , , and polynomials. Without , the prover could use different witnesses for different polynomials (a completeness attack).

Why Phase 2 Cannot Be Universal

The Phase 2 parameters are not generic encrypted powers; they are circuit-specific combinations like:

These encode the basis polynomials directly. Change the circuit, change the basis polynomials, and these elements no longer make cryptographic sense.

At a deeper level, computing these elements requires knowing in the clear. After the ceremony, these secrets are destroyed. They cannot be recovered to compute new circuit-specific values.

This is Groth16's central tradeoff. The circuit-specific encoding enables the minimal proof size. It also mandates a new ceremony for every circuit.

Protocol Specification

With setup complete, we specify the prover and verifier algorithms. We first present the soundness core without zero-knowledge, then show how randomization achieves privacy.

Common Reference String

The Proving Key contains:

  • Encrypted powers: ,

  • Blinding elements: , , , ,

  • Basis polynomial commitments: , ,

  • Consistency check elements for private inputs:

  • Quotient polynomial support:

The Verification Key contains:

  • Pairing elements: , , ,

  • Public input consistency elements:

Prover Algorithm (Soundness Core)

Given witness where are public inputs and is the private witness:

  1. Compute witness polynomials: Form from the witness.

  2. Compute quotient: Calculate .

  3. Construct proof elements (without zero-knowledge):

The terms enforce that the prover uses the same witness in , , and . Without them, a cheating prover could use inconsistent values.

Adding Zero-Knowledge

The soundness-only version above leaks information: given multiple proofs for related statements, an adversary might learn about the witness. To achieve zero-knowledge, the prover adds randomization.

Sample fresh randomness: .

Randomized proof elements:

The formula looks arbitrary, but it follows from a constraint: the verification equation must still hold. We need .

With blinding, expands to (in exponent form):

This contains new cross-terms: , , , , and . These don't appear in the soundness-only version.

The term contributes to the equation. So must contain terms that, when multiplied by , cancel the unwanted cross-terms. Working backwards:

  • To cancel : include in 's exponent (becomes after multiplying by )
  • To cancel : include
  • To cancel : include
  • To cancel : include
  • To cancel : include

Grouping: . But 's exponent is , so we can write . The corrects for double-counting.

The formula is not arbitrary—it's the unique solution ensuring the blinding terms cancel while the QAP check remains intact.

The prover outputs .

Proof Size

On the BN254 curve:

  • : 32 bytes (compressed)
  • : 64 bytes (compressed)
  • : 32 bytes (compressed)

Total: 128 bytes.

This is the smallest proof size achieved by any pairing-based SNARK. The paper proves a lower bound: any SNARK in this model requires at least two group elements. Groth16's three elements are close to optimal.

Verifier Algorithm

The verification equation is identical for both versions—the verifier doesn't know (or care) whether zero-knowledge randomization was used. The terms cancel algebraically.

Given public inputs where :

  1. Compute public input combination: where

  2. Check pairing equation:

The verifier accepts if the equation holds, rejects otherwise. Note that only , , come from the proof; the elements , , , are part of the verification key (fixed per circuit).

Verification Cost

The verification requires:

  • One multi-scalar multiplication in (size proportional to public input count)
  • Four pairing computations (or three pairings after rearrangement)

Pairings are expensive: roughly 2-3ms each on modern hardware. But the cost is independent of circuit size. A circuit with a million constraints verifies as fast as one with a hundred.

Why the Verification Equation Works

We first verify the soundness-only version (without ), then show how the zero-knowledge terms cancel.

The Core Check (Without Zero-Knowledge)

With the simplified proof elements , :

Using bilinearity, the exponent in is:

On the right-hand side:

Term 1: contributes exponent .

Term 2: contributes:

after the cancels.

Term 3: contributes the private witness consistency check plus the quotient:

after the cancels.

Combining public and private terms:

The RHS exponent is:

Setting LHS = RHS and canceling matching terms:

  • cancels
  • cancels
  • cancels

What remains:

This is exactly the QAP identity.

The Full Check (With Zero-Knowledge)

With the full proof elements (including ):

Using bilinearity, the exponent in is:

Expanding:

This contains the desired term mixed with cross-terms involving the randomness .

Term 3 now contributes additional terms: includes (after the cancels):

The RHS exponent becomes:

Setting LHS = RHS and canceling:

  • cancels
  • cancels
  • cancels
  • All terms cancel: , , , ,

What remains is unchanged:

The elaborate construction of provides exactly the terms needed to cancel the zero-knowledge blinding while preserving the soundness check.

Soundness

If the QAP is not satisfied (i.e., as polynomials), then the difference is a non-zero polynomial. By Schwartz-Zippel, it vanishes at the random point with probability at most . Since is hidden in the SRS, a cheating prover cannot target it. Thus false proofs are rejected with overwhelming probability.

Security and the Generic Group Model

Groth16's security proof relies on the Generic Bilinear Group Model: an idealization where the adversary can only perform group operations without exploiting the specific structure of the underlying curve.

The Model

In this model, group elements are represented by opaque handles. The adversary can:

  • Add/subtract group elements
  • Check equality
  • Compute pairings

The adversary cannot:

  • Look inside a group element to see its discrete log
  • Exploit number-theoretic structure of the curve

The SRS contains group elements encoding powers of and combinations involving . The prover never sees these scalars directly—only their encrypted forms. To produce a valid proof, the prover must construct group elements satisfying the verification equation.

The security argument asks: what group elements can a prover actually compute? They can only form linear combinations of SRS elements (scalar multiplication and addition). The proof shows that any linear combination satisfying the verification equation must encode a valid QAP solution. There's no way to "forge" the right algebraic structure without knowing a witness, because the prover can't extract from or construct arbitrary polynomials evaluated at .

What the Model Implies

Under this model, Groth16 is knowledge-sound: any adversary that produces a valid proof must "know" a valid witness. More precisely, there exists an extractor that, given the adversary's state, can produce a witness.

The model also implies the proof is zero-knowledge: the proof reveals nothing about the witness beyond what follows from the public statement.

The Assumption's Strength

The generic group model is non-standard. Real elliptic curves have algebraic structure; real adversaries might exploit it. No attacks are known against Groth16 on standard curves, but the security proof doesn't rule out structure-dependent attacks.

This is the price of efficiency. Schemes provable under weaker assumptions (discrete log, CDH) typically have larger proofs. Groth16 achieves optimal size by assuming more.

Concrete Assumptions

At a technical level, security reduces to the following assumptions:

  • q-Strong Diffie-Hellman (q-SDH): Given , it's hard to produce for any .
  • Knowledge of Exponent: If an adversary outputs , they must "know" .

These are strong but well-studied assumptions on pairing groups.

Proof Malleability

Groth16 proofs are malleable: given a valid proof , the tuple is also valid for the same statement. This follows from the verification equation; negating both and preserves the pairing product since .

Malleability is not forgery. This distinction is important. Malleability allows an attacker to change the appearance of a valid proof (flipping signs), but not the content. They cannot change the public inputs or the witness. It is like taking a valid check and folding it in half: it is still a valid check for the same amount, but the physical object has changed. This matters for transaction IDs (which often hash the proof), but not for the validity of the statement itself.

This matters for applications that use proofs as unique identifiers or assume proof uniqueness (e.g., preventing double-spending by rejecting duplicate proofs). Mitigations include hashing the proof into the transaction identifier, or requiring proof elements to lie in a specific half of the group.

Trusted Setup: Practical Considerations

The circuit-specific setup is Groth16's most significant operational constraint.

What "Toxic Waste" Means

The secrets must be destroyed after the ceremony. If any participant retains them:

  • Knowing breaks binding: allows computing arbitrary polynomial evaluations
  • Knowing allows forging proofs for false statements

The secrets are called "toxic waste" because their existence post-ceremony compromises all proofs using that SRS.

Multi-Party Ceremonies

Production deployments run MPC ceremonies with many participants. Each participant raises the current parameters to a fresh random power, then destroys their secret; the mechanism was covered in Chapter 9. The 1-of-N trust model applies: security holds if any single participant destroyed their contribution.

Groth16's Phase 2 requires the same ceremony structure but with circuit-specific parameters. Each circuit needs its own Phase 2, coordinated among willing participants.

Phase 2 Complexity

Phase 1 (Powers of Tau) is performed once per maximum circuit size and reused indefinitely.

Phase 2 requires:

  • Computing circuit-specific elements for every wire
  • MPC ceremony among willing participants
  • Verification that each contribution was correct

For a circuit with wires, Phase 2 generates group elements. Large circuits require large ceremonies.

When Circuit-Specific Setup Is Acceptable

Groth16 makes sense when:

  1. The circuit is fixed: Same computation proved repeatedly (e.g., confidential transactions)
  2. Proof size dominates costs: On-chain verification where bytes are expensive
  3. Verification speed is critical: Applications requiring <10ms verification
  4. Trust model is manageable: Established communities can coordinate ceremonies

It makes less sense when:

  1. Circuits change frequently: Development, iteration, bug fixes
  2. Many different circuits needed: General-purpose computation
  3. No trusted community exists: Public good infrastructure without coordination

Comparison with Universal SNARKs

Since 2016, the field has developed universal SNARKs: systems with a single trusted setup reusable across circuits.

PLONK (Chapter 13)

  • Setup: Universal, updatable
  • Proof size: ~400-500 bytes (with KZG)
  • Verification: ~10ms (several pairings)
  • Prover: Comparable to Groth16

PLONK trades 3-4x larger proofs for the ability to prove any circuit without new ceremonies.

Marlin/Sonic

These are universal SNARKs that emerged around the same time as PLONK. Sonic (2019) pioneered the "universal and updateable" trusted setup: a single ceremony works for any circuit up to a size bound, and users can add their own randomness to strengthen trust. Marlin (2020) keeps R1CS arithmetization (like Groth16) but achieves universality through algebraic holographic proofs. Both have similar proof sizes to PLONK (~500 bytes) but different verification costs and prover trade-offs. In practice, PLONK's flexibility and ecosystem support led to wider adoption.

STARKs (Chapter 15)

  • Setup: Transparent (no trusted setup)
  • Proof size: ~100 KB
  • Verification: ~10-50ms (hash-based)
  • Prover: Faster than pairing-based systems

STARKs eliminate trust assumptions entirely but with much larger proofs.

The Trade-Off Summary

SystemSetupProof SizeVerificationSecurity Model
Groth16Circuit-specific128 bytes3 pairingsGeneric Group
PLONK+KZGUniversal~500 bytes~10 pairingsq-SDH
PLONK+IPATransparent~10 KBO(n)DLog
STARKsTransparent~100 KBO(log²n)Hash collision

Groth16 remains optimal when proof size is the binding constraint and circuit stability justifies the setup cost.

Implementation Considerations

Curve Selection

Groth16 requires pairing-friendly curves. Common choices:

BN254 (alt_bn128):

  • 254-bit prime field
  • Fast pairing computation
  • Ethereum precompiles at addresses 0x06, 0x07, 0x08
  • ~100 bits of security (debated; some analyses suggest less)

BLS12-381:

  • 381-bit prime field
  • Higher security (~120 bits)
  • Slower pairings
  • Used by Zcash Sapling, Ethereum 2.0 BLS signatures

Prover Complexity

The prover performs:

  • scalar multiplications to form witness polynomials from basis polynomials
  • operations for polynomial multiplication and division (computing )
  • Multi-scalar multiplications (MSM) to compute proof elements

The MSM dominates for large circuits. Significant engineering effort goes into MSM optimization: Pippenger's algorithm, parallelization, GPU acceleration.

On-Chain Verification

Ethereum's precompiled contracts enable efficient Groth16 verification:

  • ecAdd (0x06): Elliptic curve addition in
  • ecMul (0x07): Scalar multiplication in
  • ecPairing (0x08): Multi-pairing check

A typical Groth16 verifier contract:

  1. Computes via ecMul and ecAdd for each public input
  2. Calls ecPairing with four pairs:
  3. Returns true if the pairing product equals 1

Gas cost: ~200,000-300,000 gas depending on public input count.

Key Takeaways

  1. Optimal proof size. Three group elements (128 bytes on BN254). Groth proved this is the theoretical minimum for pairing-based SNARKs.

  2. QAP compresses constraints. R1CS's constraint checks become one polynomial divisibility condition: . Lagrange interpolation encodes constraint participation into basis polynomials.

  3. Pairings check multiplication on hidden values. The verification equation checks that without revealing or the witness polynomials. Bilinearity is the mechanism.

  4. The prover is algebraically constrained. The SRS contains group elements encoding , , , , in specific combinations. The prover can only form linear combinations of these. Any proof satisfying the verification equation must encode a valid QAP solution—there's no way to "forge" the algebraic structure.

  5. Circuit-specific setup. Phase 1 (powers of tau) is universal. Phase 2 embeds the circuit's basis polynomials into the SRS. Change one gate, redo Phase 2.

  6. 1-of-N trust. If any ceremony participant destroys their toxic waste, the setup is secure. This makes the trust assumption practical despite requiring a trusted setup.

  7. Zero-knowledge by algebraic design. The blinding terms in are not arbitrary—they're the unique values ensuring the , masks cancel in the verification equation. The protocol's ZK property is woven into its algebraic structure.

  8. Generic group model. Security relies on assuming adversaries cannot exploit the curve's number-theoretic structure. Stronger than standard assumptions, but no practical attacks are known.

Chapter 13: PLONK: Universal SNARKs and the Permutation Argument

By 2018, Groth16 had proven SNARKs worked in production. Zcash was live, proofs were 128 bytes, verification was fast. But every protocol upgrade required a new trusted setup ceremony—a multi-party computation specific to that circuit. For a project planning rapid iteration, this was a bottleneck. The cryptographic world wanted a setup you could perform once and reuse for any circuit.

Ariel Gabizon, Zachary Williamson, and Oana Ciobotaru found the path. Their insight was permutations: instead of encoding circuit structure directly into the setup, separate two concerns: what each gate computes (local) and how gates connect (global). The wiring could be encoded as a permutation, checked with a polynomial argument that worked identically for any circuit.

The result was PLONK (2019): Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge. "Oecumenical" signals universality: one ceremony suffices for all circuits up to a maximum size. Since PLONK needs only powers of tau (no circuit-specific Phase 2), the entire setup is updatable: anyone can strengthen security by adding a contribution, without coordinating with previous participants.

PLONK's modularity extends to the commitment scheme. The core is a Polynomial IOP: an interactive protocol where the prover sends polynomials and the verifier queries evaluations. Compile it with KZG for constant-size proofs with trusted setup. Compile with FRI for larger proofs without trust assumptions. The IOP is unchanged; only the cryptographic layer differs.

The cost of universality is larger proofs (~400-500 bytes versus 128) and more verification work (~10 pairings versus 3). Whether this trade-off makes sense depends on deployment constraints: Groth16 remains preferred when proof size or verification cost is the priority; PLONK variants dominate when development velocity or custom gates matter more.

Architecture: Gates and Copy Constraints

Chapter 8 introduced PLONKish arithmetization: the universal gate equation and the permutation argument for copy constraints. Here we develop the full protocol.

The key architectural distinction from R1CS: PLONK separates gate constraints (each gate satisfies a polynomial equation relating its wires) from copy constraints (wires at different positions carry equal values when the circuit's topology demands it).

This separation has consequences for extensibility. Gate logic becomes uniform: one equation for all gates. Wiring becomes explicit: a permutation argument proves all copy constraints simultaneously. Because gate definitions and wiring are independent, adding custom gates or lookup arguments doesn't require rethinking the copy constraint mechanism.

The Gate Equation

Recall from Chapter 8: every gate has three wires (, , ) and the universal gate equation

where selectors are public constants that program each gate's operation. Addition sets ; multiplication sets ; constant assignment sets . Modern variants extend to more wires (5+ instead of 3) and higher-degree terms ( for Poseidon S-boxes).

From Discrete Checks to Polynomial Identity

The circuit has gates. We want to verify all gate equations simultaneously.

Define a domain where is a primitive -th root of unity. The -th gate corresponds to domain point .

Each selector has one value per gate. For , we have a vector where is the left-wire selector at gate . Interpolation finds the unique polynomial of degree passing through the points . The result: for all . We do the same for , and for the witness polynomials (where , the left input at gate ).

The witness structure differs from R1CS. In R1CS (Chapter 8), the witness is a single flattened vector . Each wire has exactly one index in . When two constraints reference the same wire, they use the same index; wiring is implicit in the indexing scheme.

PLONK structures the witness differently: three separate vectors , each of length (the number of gates). Entry is gate 's left input; is its right input; is its output. When the same value appears in multiple positions (say, a variable feeding two different gates) it occupies multiple slots in these vectors. This has a direct consequence: PLONK needs explicit "copy constraints" to enforce that slots holding the same logical wire actually contain the same value. We'll see how this works shortly.

To make this concrete, consider with , , so .

R1CS representation (2 constraints, 5 wires):

Witness vector: where .

(Columns correspond to ; we omit the constant column for brevity.)

Row 1:

checks .

Row 2:

checks .

The matrices encode which wires participate in which constraints. Wire (column 2) appears in both rows; the matrix structure encodes this sharing.

PLONK representation (2 gates):

Gate
132511-100
2521000-110

Witness vectors: , , .

Gate 1:

(addition)

Gate 2:

(multiplication)

Notice: appears twice ( and ), and appears twice ( and ). The gate equations don't enforce or ; a cheating prover could use different values. Copy constraints will enforce these equalities.

The structural difference: R1CS matrices select from a shared witness vector (same wire, same column, automatic equality). PLONK has vectors where each gate slot is independent (same value, different slots, explicit copy constraints needed).

How does this compare to QAP (Chapter 12)? In QAP, each wire gets basis polynomials encoding how that wire participates across all constraints. The witness appears as coefficients weighting these basis polynomials: . The basis polynomials encode the circuit structure.

PLONK separates these concerns differently:

  • Selector polynomials (): Define the circuit. Fixed once the circuit is designed. Different circuits have different selectors.
  • Witness polynomials (): Computed fresh by the prover for each proof. Different inputs produce different witness values, interpolated into different polynomials.

Circuit structure lives in the selector polynomials, which are ordinary polynomials—not special objects requiring circuit-specific setup. This separation is what enables universality: the same trusted setup works for any circuit, because it doesn't need to "know" about selectors in advance.

With all these polynomials defined, the per-gate equation becomes a polynomial identity:

for all .

If this holds on , the vanishing polynomial divides the left side. There exists quotient with:

The prover demonstrates this divisibility: a single polynomial identity encoding all gate constraints.

The Copy Constraint Problem

Gate equations ensure internal consistency: the output of each gate equals the specified function of its inputs. They say nothing about how gates connect.

Consider a circuit computing :

  • Gate 1: Addition, output
  • Gate 2: Multiplication, output

The wiring requires (Gate 1's output feeds Gate 2's left input) and (variable feeds both gates).

Because PLONK's witness consists of three separate vectors , nothing in the gate equation relates to ; they're independent entries. A cheating prover could satisfy all gate equations with disconnected, inconsistent values. The circuit would "verify" despite computing garbage.

Copy constraints are the explicit assertions: wire equals wire . The challenge is proving all copy constraints efficiently (potentially thousands of equality assertions) without enumerating them individually.

The name "copy constraint" is slightly misleading. We aren't copying data from one location to another. We are enforcing equality: two wire slots that represent the same logical variable must contain identical values. The permutation argument detects whether slots that should hold the same value actually do.

The Permutation Argument

PLONK's central innovation is reducing all copy constraints to a single polynomial identity via a permutation argument, building on techniques from Bayer and Groth (Eurocrypt 2012).

From Gates to Cycles

Before diving into the mechanism, understand the key mental shift. So far, we've thought of circuits as gates: local computational units that take inputs and produce outputs. Copy constraints seem like connections between gates: wire connects to wire .

The permutation argument reframes this. Instead of "connections," think of equivalence classes. All wires that should hold the same value belong to the same class. Within each class, the wires form a cycle under a permutation: (a 2-cycle), or longer chains like (a 3-cycle). Wires with no copy constraints form trivial 1-cycles (fixed points).

If we traverse each cycle, do all the values match? This shift from "gates and wires" to "values and cycles" is what makes efficient verification possible—we're not checking connections one by one, but verifying that the entire wiring topology is consistent in one algebraic test.

Representing Wiring as a Permutation

The circuit's wiring defines a permutation on wire slots. If two wires must hold the same value, maps one to the other (and vice versa, forming a cycle). Unconnected wires map to themselves: .

All copy constraints hold if and only if every wire's value equals the value at the position maps it to:

Example: For our circuit with 2 gates, label the 6 wire slots as . The copy constraints are (output of gate 1 feeds gate 2) and (variable used twice). The permutation encodes this: , (a 2-cycle), and , (another 2-cycle). Wires and aren't copied anywhere, so and (fixed points).

The Grand Product Check

How do we verify this equality-under-permutation efficiently?

For a circuit with gates, there are wire slots (each gate has wires , , ). Consider two multisets: the wire values and the same values permuted according to . If copy constraints hold, these multisets are identical; they contain the same elements, just in different order.

A naive approach checks whether the products match:

This fails: and have equal products but differ. Adding a random challenge fixes this:

Why is this sound? If the multisets differ (some value appears with different multiplicities), then the polynomials and are distinct. By Schwartz-Zippel, distinct degree- polynomials agree on at most points, so a random satisfies the equality with probability at most (negligible for cryptographic fields).

Binding Values to Locations

The multiset check has a flaw. A cheating prover could satisfy copy constraints on some wires by violating them on others, as long as they swap equal amounts. The overall multiset remains unchanged even though specific equalities fail.

Example: Circuit requires . Honest values: , . Cheating prover sets , , but compensates by swapping some other wire that should be to . The multiset of all values is preserved.

The fix: bind each value to its location using a second challenge :

Each wire slot gets a unique identity :

  • Gate 's left wire:
  • Gate 's right wire:
  • Gate 's output wire:

where are distinct constants separating the three wire columns.

The grand product check becomes:

The left side combines each wire's value with its own identity. The right side combines each wire's value with its permuted identity.

To see why this works, consider two wires that should be equal: (output of gate 1, identity ) and (left input of gate 2, identity ), both holding value . The permutation swaps their identities: , .

Left side:

Right side (using and ):

Same factors, just reordered, so the products match.

Now suppose a cheating prover violates the copy constraint by putting value at but value at . The left side becomes:

The right side becomes:

These are different factors, so the products don't match. The term tags each value with its location, so the check detects when two positions that should hold equal values actually don't.

If (copy constraint holds), the term for on the right equals the term for on the left; they cancel in the product. If , no cancellation occurs; the products differ.

The Accumulator Polynomial

Computing a product over terms naively requires work per verification query, which is not succinct. PLONK encodes the product as a polynomial.

The accumulator polynomial computes a running product across all gates. It starts at 1, and at each gate multiplies in a ratio: numerator terms use the wire's own identity, denominator terms use the permuted identity. If all copy constraints hold, numerators and denominators cancel across the full circuit, and the accumulator returns to 1.

Define recursively:

Initialization:

Recursion: For domain points :

The permutation polynomials encode where maps each wire's identity. For each gate :

  • : where the left wire of gate maps to
  • : where the right wire of gate maps to
  • : where the output wire of gate maps to

If wire (identity ) connects to wire (identity ), then . Unconnected wires map to themselves: if has no copy constraint, .

The permutation constraints:

  1. Initialization:

    We need this constraint to hold only at the first domain point, not everywhere. Recall from Chapter 5 that is the Lagrange basis polynomial that equals 1 at and 0 at all other roots of unity. Multiplying by "activates" the constraint only where we want it:

    At : , so is enforced. At other : , satisfied regardless of .

  2. Recursion: The step-by-step product relation holds across the domain.

    At each gate , the accumulator must satisfy:

    As a polynomial identity, this becomes:

    Evaluating at gives the recurrence: evaluated at equals .

Both constraints, like the gate constraint, reduce to divisibility by .

Worked Example: The Permutation Argument in Action

The abstraction clarifies; the concrete convinces. Let's trace through the permutation argument on a minimal circuit: proving for inputs , .

The Circuit

Gate 1 (addition): Gate 2 (multiplication):

Witness assignment (for , , ):

  • Gate 1: , ,
  • Gate 2: , ,

Copy constraints:

  • (the intermediate value 5 feeds from Gate 1's output to Gate 2's left input)
  • (the input is used in both gates)

Wire Identities

With domain (two gates) and constants :

WireIdentityValue

The Permutation

The wiring groups wire identities into cycles:

Cycle 1 (the input):

Cycle 2 (the intermediate value):

Fixed points (unconnected wires):

Permutation Polynomials

The polynomials , , encode for each wire column.

(the wires):

  • (wire is a fixed point)
  • (wire connects to )

(the wires):

  • (wire connects to )
  • (wire connects to )

(the wires):

  • (wire connects to )
  • (wire is a fixed point)

These evaluations uniquely determine the permutation polynomials (degree at most 1 over a domain of size 2).

The Accumulator Trace

Let random challenges be and . The accumulator computes a running product.

Initialization:

Step at (processing Gate 1):

Substituting values:

Numerator =

Denominator =

The term appears in both numerator and denominator; it cancels (wire is a fixed point).

The numerator term is ; the denominator has .

The numerator term is ; the denominator has .

Step at (processing Gate 2):

Substituting:

Numerator =

Denominator =

The term appears in both numerator and denominator of step 2, so it cancels immediately (wire is a fixed point).

The interesting cancellations happen across steps. Consider wire (value 5, identity ):

  • Step 1 numerator: : the value plus its own identity
  • Step 2 denominator:

Why does step 2's denominator have ? Because asks "where does wire map under ?" Since is a copy constraint, maps 's identity () to 's identity (). So .

Similarly for wire (value 3):

  • Step 1 numerator:
  • Step 2 denominator:

Here because maps 's identity () to 's identity ().

The converse cancellations work the same way: step 1's denominator terms match step 2's numerator terms because the permutation is symmetric (if maps , it also maps ).

Every term cancels. The result: .

Since for , we have as required. The accumulator returns to its starting value, confirming all copy constraints hold.

What If a Constraint Were Violated?

Suppose the prover cheats: sets instead of (breaking ).

The term from no longer matches from the fraudulent . No cancellation occurs. The accumulator ends at a value , and the constraint fails.

The random challenges ensure this failure is detectable with overwhelming probability.

The Full Protocol

The core ideas are now in place: the gate equation checks local correctness, the permutation argument enforces wiring via a grand product, and the accumulator polynomial encodes this product for efficient verification. This section specifies the complete protocol with KZG commitments. It can be skipped on first reading without losing the conceptual thread.

Preprocessed Data (Circuit-Specific)

Fixed at circuit compilation:

  • Selector polynomial commitments:
  • Permutation polynomial commitments:

Common Reference String (Universal)

The SRS, shared across all circuits up to size :

The prover needs the full sequence. The verifier needs only , an asymmetry that enables efficient verification.

Round 1: Commit to Witness

The prover:

  1. Computes witness polynomials by interpolating wire values
  2. Blinds each polynomial for zero-knowledge: , where are random field elements
  3. Commits: sends

Why does blinding work? The term is zero on (since for all ), so adding it doesn't change the polynomial's values at gate positions; correctness is preserved. But outside , this random term "scrambles" the polynomial, hiding information about the original witness values. The verifier will later query the polynomial at a random point ; without blinding, these evaluations could leak witness information.

Round 2: Commit to Accumulator

The prover:

  1. Derives challenges via Fiat-Shamir (hash of transcript including Round 1 commitments)
  2. Computes accumulator polynomial from the recursive definition
  3. Blinds with higher-degree term (three random scalars, since is checked at two points: and )
  4. Commits: sends

Round 3: Compute Quotient

The prover:

  1. Derives challenge via Fiat-Shamir
  2. Forms the combined constraint polynomial using for random linear combination:

The gate constraint is , the polynomial identity from earlier that encodes all gate equations. The permutation recursion forces the accumulator to update correctly at each step: the polynomial form of "" from the grand product. The permutation initialization is the boundary condition: the accumulator must start at 1, encoded as where is the Lagrange polynomial that equals 1 at and 0 elsewhere.

  1. Computes quotient:
  2. Splits into lower-degree pieces for commitment (since )
  3. Commits to quotient pieces

Round 4: Evaluate and Open

The prover:

  1. Derives evaluation point via Fiat-Shamir

  2. Evaluates all relevant polynomials at :

    • Witness:
    • Accumulator: , and (the shifted evaluation)
    • Permutation:
  3. Sends evaluations to verifier

  4. Computes batched opening proofs (we explain the linearization trick in the verification section below)

Round 5: Batched Opening Proofs

The prover:

  1. Derives batching challenge via Fiat-Shamir
  2. Constructs opening proof for all evaluations at (batched)
  3. Constructs opening proof for evaluation at (the shifted point)
  4. Sends two KZG proofs

Verification

The verifier performs the following steps:

1. Reconstruct Challenges

From the transcript (all prover commitments), derive:

  • from Round 1 commitments (for permutation argument)
  • from Round 2 commitments (for constraint aggregation)
  • from Round 3 commitments (evaluation point)
  • from Round 4 evaluations (batching challenge)

All challenges are deterministic functions of the transcript via Fiat-Shamir.

2. Compute the Linearization Polynomial Commitment

The combined constraint polynomial contains products like . The verifier has commitments , , but cannot compute from these—there's no way to multiply group elements to get a commitment to a product of polynomials.

The linearization trick solves this. Once the prover sends evaluations as field elements, these become scalars. The verifier can compute:

This scalar multiplication is possible and gives the right contribution at point . The verifier constructs the linearized commitment :

  • Gate constraint:
  • Permutation recursion (scaled by ): Terms involving , the permutation polynomials, and the evaluated witness values
  • Permutation initialization (scaled by ):

3. Compute the Expected Evaluation

The verifier computes what should equal if the prover is honest. This involves:

  • The quotient polynomial contribution:
  • Witness polynomial contributions at

4. Batched Opening Verification

The verifier checks two batched KZG opening proofs:

Opening at : All polynomials evaluated at are batched using challenge :

The verifier checks that opens to the batched evaluation:

Opening at : The accumulator's shifted evaluation:

where is the KZG opening proof for evaluation at .

5. Pairing Check

The final verification reduces to two pairing equations (often combined into one via random linear combination):

where is a random challenge for batching the two opening proofs, and is the commitment to the expected evaluations.

Verification Cost

OperationCount
Scalar multiplications in ~15-20
Field multiplications~30-50
Pairing computations2

Total verification time: ~5-10ms on commodity hardware, independent of circuit size.

Proof Size Analysis

With KZG over BN254:

ElementSizeCountTotal
commitments32 bytes~10320 bytes
opening proofs32 bytes264 bytes
Field element evaluations32 bytes~7224 bytes

Total: ~600 bytes (varies with optimizations)

This is 4-5× larger than Groth16's 128 bytes. The cost buys universality: one setup ceremony, any circuit.

Why Roots of Unity?

PLONK's use of roots of unity (multiplicative subgroup of order ) is not arbitrary. Three properties make them necessary:

  • Polynomial operations (interpolation, multiplication, division) run in via FFT. Without roots of unity, these cost .
  • The vanishing polynomial has a simple form: . Compact representation, efficient evaluation.
  • The accumulator's recursive relation compares and . Multiplication by shifts through the domain cyclically, which encodes the step-by-step product check.

Groth16 uses an arithmetic progression because its prover doesn't interpolate; it computes linear combinations of precomputed basis polynomials. The FFT advantage doesn't apply.

Comparison: PLONK vs. Groth16

The preceding sections developed these architectural differences in detail. Here's a side-by-side summary:

AspectGroth16PLONK
Witness roleCoefficients weighting basis polynomialsEvaluations interpolated into polynomials
Copy constraintsImplicit (R1CS matrix reuses indices)Explicit (permutation argument)
SetupCircuit-specific (basis polynomials in SRS)Universal (only powers of )
Constraint form
Proof size128 bytes~500 bytes
Verification3 pairings2 pairings + ~15 scalar muls
Prover workMSM-dominatedFFT + MSM
ExtensibilityFixedCustom gates, lookups

Custom Gates and Extensions

PLONK's gate equation generalizes naturally. Custom gates aren't exclusive to PLONKish systems—Spartan's CCS (Customizable Constraint Systems) also supports arbitrary polynomial constraints, generalizing both R1CS and PLONKish arithmetization. But PLONK variants were the first to deploy custom gates widely in production.

More Wires

Modern systems (Halo2, UltraPLONK) use 5+ wires per gate:

More wires mean fewer gates for complex operations.

Higher-Degree Terms

The Poseidon hash uses in its S-box. A custom gate term computes this in one gate rather than five multiplications.

Non-Native Arithmetic

A major driver for custom gates is non-native arithmetic: computing over a field different from the proof system's native field. PLONK (with BN254) operates over a ~254-bit prime field. But many applications require arithmetic over other fields: Bitcoin uses secp256k1's scalar field, Ethereum signatures use different curve parameters, and recursive proof verification requires operating over the "inner" proof's field.

Without custom gates, non-native field multiplication requires decomposing elements into limbs, performing schoolbook multiplication with carries, and range-checking intermediate results. A single non-native multiplication can cost 50+ native gates. Custom gates can batch these operations, reducing the cost by 5-10×. This is why efficient ECDSA verification (for Ethereum account abstraction or Bitcoin bridge verification) demands sophisticated custom gate design.

Boolean Constraints

Enforcing requires , equivalently . With selector :

One gate, one constraint.

Lookup Arguments

The most powerful extension. Rather than computing a function in gates, prove that (input, output) pairs appear in a precomputed table.

Example: Range check. Proving via bit decomposition costs 16 gates. A lookup into a table of costs ~3 constraints.

Chapter 14 develops lookup arguments in detail.

UltraPLONK

"UltraPLONK" denotes PLONK variants combining custom gates and lookup arguments. These systems achieve dramatic efficiency gains for real-world circuits: composite gates encode multiple operations simultaneously (e.g., and in one gate), the permutation argument extends to prove set membership in lookup tables, and Poseidon-specific gates reduce hash computation by 10-20× compared to vanilla PLONK. The architecture remains a polynomial IOP compiled with KZG (or alternatives)—the IOP grows more sophisticated, but the verification structure persists.

Aztec Labs, co-founded by Zac Williamson (one of PLONK's creators), developed UltraPLONK in their Barretenberg library. Their system has since evolved to Honk, which replaces the univariate polynomial IOP with sum-check over multilinear polynomials (similar to Spartan's approach). Honk retains PLONKish arithmetization but gains the memory efficiency of sum-check (Chapter 19 explains why: sum-check's linear memory access pattern is cache-friendly, unlike FFT's butterfly shuffles). For on-chain verification, Aztec compresses Honk proofs into UltraPLONK proofs; UltraPLONK's simpler verifier (fewer selector polynomials, no multilinear machinery) reduces gas costs. Their Goblin PLONK technique further optimizes recursive proof composition by deferring expensive elliptic curve operations rather than computing them at each recursion layer.

Security Considerations

Trusted Setup

PLONK's universality doesn't eliminate trust; it redistributes it.

The SRS still encodes secret . If known, proofs can be forged. The advantage is logistical: one ceremony covers all circuits. Updates strengthen security without coordination.

Production deployments (Aztec, zkSync, Scroll) run multi-party ceremonies with hundreds of participants. The 1-of-N trust model, where security holds if any participant is honest, provides strong guarantees.

Soundness Assumptions

PLONK's security depends on the polynomial commitment scheme used:

  • With KZG: Security relies on pairing-based assumptions (q-SDH, discrete log). These are well-studied but would break under quantum computers.
  • With FRI: Security relies only on collision-resistant hashing. Fewer assumptions, and potentially quantum-resistant, but larger proofs.

Key Takeaways

  1. Universal setup: One ceremony works for all circuits up to a size bound. This comes from treating witness values as polynomial evaluations (interpolated at proving time) rather than coefficients (baked into setup).

  2. Separation of concerns: Gate constraints check local correctness (each gate's equation holds). Copy constraints check global wiring (connected wires hold equal values). Each has its own polynomial mechanism.

  3. The permutation argument: All copy constraints reduce to one polynomial identity. The accumulator polynomial computes a running product; if all constraints hold, it returns to 1.

  4. Roots of unity: FFT enables polynomial operations. The shift structure ( vs ) encodes the accumulator's step-by-step recursion.

  5. The linearization trick: The verifier can't compute commitments to polynomial products. Linearization uses the prover's evaluation values to turn polynomial multiplications into scalar multiplications of commitments.

  6. Proof size vs setup trade-off: ~500 bytes (vs Groth16's 128 bytes) buys universality. Whether this trade-off makes sense depends on deployment constraints.

Chapter 14: Lookup Arguments

In 2019, ZK engineers hit a wall.

They wanted to verify standard computer programs, things like SHA-256 or ECDSA signatures, but the circuits were exploding in size. The culprit was bit decomposition. Operations that are trivial in silicon (bitwise XOR, range checks, comparisons) require decomposing values into individual bits, processing each bit, and reassembling. A single XOR takes roughly 30 constraints. A range check proving costs 32 boolean constraints. Verifying a 64-bit CPU instruction set was like simulating a Ferrari using only wooden gears.

Ariel Gabizon and Zachary Williamson realized they didn't need to simulate the gears. They just needed to check the answer key. This realization, that you can replace computation with table lookups, broke the bottleneck. Instead of decomposing values into bits, just look up the answer in a precomputed table.

The insight built on earlier work (Bootle et al.'s 2018 "Arya" paper had explored lookup-style arguments), but Plookup made it practical by repurposing PLONK's permutation machinery. Range checks become a lookup into a table of valid values. Bitwise operations become a lookup into a table of valid input-output triples. Membership in these tables costs a few constraints, regardless of what the table encodes. The architecture shifted, and complexity moved from constraint logic to precomputed data.

The field accelerated. Haböck's LogUp (2022) replaced grand products with sums of logarithmic derivatives, eliminating sorting overhead and enabling cleaner multi-table arguments. Setty, Thaler, and Wahby's Lasso (2023) achieved prover costs scaling with lookups performed rather than table size, enabling tables of size , large enough to hold the evaluation table of any 64-bit instruction. The "lookup singularity" emerged: a vision of circuits that do nothing but look things up in precomputed tables.

Today, every major zkVM relies on lookups. Cairo, RISC-Zero, SP1, and Jolt prove instruction execution not by encoding CPU semantics in constraints, but by verifying that each instruction's behavior matches its entry in a precomputed table. Complexity moves from constraint logic to precomputed data.


The Lookup Problem

Chapter 13 introduced the grand product argument for copy constraints in PLONK. The idea: to prove that wire values at positions related by permutation are equal, compute . If the permutation constraint is satisfied (values at linked positions match), this product telescopes to 1. Lookup arguments generalize this technique from equality to containment, proving not that two multisets are the same, but that one is contained in another.

The formal problem:

Given a multiset of witness values (the "lookups") and a public multiset (the "table"), prove .

The name "lookup" comes from how these proofs work in practice. Imagine you're proving a circuit that computes XOR. The table contains all valid XOR triples: . Your circuit claims for some witness values. Rather than encoding XOR algebraically, you "look up" the triple in the table. If it's there, the XOR is correct. The multiset collects all the triples your circuit needs to verify; the subset claim says every lookup found a valid entry.

A dictionary example makes this concrete. Imagine you want to prove you spelled "Cryptography" correctly. The arithmetic approach would be to write down the rules of English grammar and phonetics, then derive the spelling from first principles. Slow, complex, error-prone. The lookup approach would be to open the Oxford English Dictionary to page 412, point to the word "Cryptography," and say "there." The lookup argument is proving that your tuple (the word you claim) exists in the set (all valid English words). You don't need to understand why it's valid; you just need to show it's in the book.

The Naive Approach: Product of Roots

A natural idea: two multisets are equal iff the polynomials having those elements as roots are equal. If every lookup appears in the table , we can write:

where counts how many times table entry appears among the lookups.

Example: Lookups into table .

  • Left side:
  • Right side:

The polynomials match because the multisets match: contains two 2s and one 5, which is exactly what the multiplicities , encode.

This identity is mathematically valid, but expensive to verify in a circuit. Computing requires the binary decomposition of each multiplicity . If lookups can repeat up to times, each multiplicity needs bits, blowing up the circuit inputs.

Different lookup protocols avoid this cost in different ways. Plookup sidesteps multiplicities entirely by using a sorted merge. LogUp transforms the product into a sum where multiplicities become simple coefficients rather than exponents.


Plookup

Plookup's insight is to transform the subset claim into a permutation claim. The construction involves three objects:

  • : the lookup values (what you're looking up, your witness data)
  • : the table (all valid values, public and precomputed)
  • : the sorted merge of and (auxiliary, constructed by prover)

The key is that encodes how fits into . If every is in , then is just with duplicates inserted at the right places.

Plookup's Sorted Vector

Define , the concatenation of lookup values and table values, sorted.

If , then every element of appears somewhere in . In the sorted vector , elements from "slot in" next to their matching elements from .

For every adjacent pair in , either:

  1. (a repeated value, meaning some was inserted next to its matching ), or
  2. is also an adjacent pair in the sorted table

If some , then contains a transition that doesn't exist in , and the check fails.

Example (3-bit range check):

  • Lookups: (prover claims both are in )
  • Table:
  • Sorted:

Adjacent pairs in :

The pairs and are repeats; these correspond to the lookups. All other pairs appear as adjacent pairs in . The subset claim holds.

If instead :

  • Sorted:
  • The pair is neither a repeat nor an adjacent pair in
  • The subset claim fails

Plookup's Grand Product Check

The adjacent-pair property translates to a polynomial identity via a grand product. The construction is clever, so let's build it step by step.

The core idea is to encode each adjacent pair as a single field element . The term acts as a "separator": different pairs map to different field elements (with high probability over random ). Multiplying all these pair-encodings together gives a fingerprint of the multiset of adjacent pairs.

, the fingerprint of 's adjacent pairs:

This is just the product of all adjacent-pair encodings in the sorted vector .

, the fingerprint we expect if :

Where does this come from? Think about what looks like when . The sorted merge contains the table as a "backbone," with lookup values from inserted as duplicates next to their matches. So the adjacent pairs in fall into two categories:

  1. Pairs from : The consecutive pairs from the original table. These appear in regardless of what contains; they're the skeleton that gets merged into. In , these correspond to the last product , which doesn't factorize.

  2. Repeated pairs from inserting : When a lookup value slots into next to its matching table entry, we get a repeated pair . The encoding of is . This does factorize. So the repeated pairs contribute to .

is the fingerprint of exactly these pairs, the table backbone plus valid duplicate insertions. If (the actual fingerprint of ) equals , then has the right structure: no "bad" transitions like that would appear if some .

Let's use a 3-element table to see the algebra concretely.

  • Table: (so )
  • Lookups: (so )
  • Sorted merge:

Computing (fingerprint of 's adjacent pairs):

The pairs in are: . Encode each:

Computing (expected fingerprint):

  • Table pairs : and
  • Lookup duplicate: contributes

Why ? Notice that the pair in encodes as . This factors! So 's middle term equals 's term. The other two terms match directly. The products are identical.

Claim (Plookup): if and only if and is correctly formed.

Completeness: If , then consists of 's pairs plus repeated pairs for each lookup. Each repeated pair encodes as , which exactly matches 's structure.

Soundness: If some , then when sorted into , creates an adjacent pair or where neither nor equals . This "bad transition" doesn't appear in 's table backbone, and can't factor as either. For random , the probability that despite this mismatch is at most by Schwartz-Zippel (the products have total degree at most in ).

The following implementation computes and for the 3-bit range check example above:

def encode_pair(a, b, beta, gamma):
    """Encode adjacent pair (a, b) as a field element."""
    return gamma * (1 + beta) + a + beta * b

def plookup_check(lookups, table, beta=2, gamma=5):
    """Verify lookups subset of table via Plookup grand product."""
    s = sorted(lookups + table)

    # G: fingerprint of s's adjacent pairs
    G = 1
    for i in range(len(s) - 1):
        G *= encode_pair(s[i], s[i+1], beta, gamma)

    # F: expected fingerprint = (1+beta)^n * prod(gamma + f_i) * prod(table pairs)
    F = (1 + beta) ** len(lookups)
    for f in lookups:
        F *= (gamma + f)
    for i in range(len(table) - 1):
        F *= encode_pair(table[i], table[i+1], beta, gamma)

    return F, G, (F == G)

# 3-bit range check: {2, 5} in [0, 7]
plookup_check([2, 5], list(range(8)))  # (563374005, 563374005, True)

# Invalid: 9 not in table
plookup_check([2, 9], list(range(8)))  # F != G, returns False

Integrating with PLONK

The grand product check is the mathematical core of Plookup (Gabizon-Williamson 2020). But to use it in a SNARK, we need to encode the check as polynomial constraints that PLONK can verify. This means:

  • The table becomes a polynomial committed during setup
  • The sorted vector becomes polynomials the prover commits to
  • The check becomes an accumulator that the verifier checks via a single polynomial identity

Setup

The table is public and fixed before any proof. Encode it as a polynomial where for each table entry. This polynomial is committed once and reused across all proofs; the verifier never touches the full table during verification.

The prover holds witness values to look up. These are private.

Prover Computation

The prover's job is to construct the sorted vector and prove without revealing the witness values.

  1. Construct : Merge and , then sort. This is the -sorted vector from the theory above.

  2. Split into : The sorted vector has length (lookups plus table), but PLONK's evaluation domain has size matching the circuit. To fit into the constraint system, split it into two polynomials and . The constraints will check adjacent pairs within each half and across the boundary.

  3. Commit to sorted polynomials: Send to the verifier.

  4. Receive challenges: After Fiat-Shamir, obtain . These randomize the fingerprint encoding, making it infeasible for a cheating prover to forge a valid .

  5. Build accumulator: Construct , the polynomial that computes the running ratio. It starts at 1, accumulates one ratio term per domain point, and returns to 1 if the lookup is valid.

  6. Commit to accumulator: Send .

Constraints

Recall the goal: prove , where is the expected fingerprint and is the actual fingerprint of 's adjacent pairs. In PLONK, we encode this as polynomial identities checked via the quotient polynomial.

The accumulator computes a running ratio of and terms. If , the ratio telescopes to 1 over the full domain.

Initialization: starts at 1.

Recursion: At each domain point, accumulates one step of the ratio. The left side encodes adjacent pairs from (split across ); the right side encodes the expected terms (table pairs and lookup duplicates):

The parameter is the number of lookups per gate (typically 1 or 2).

If , then returns to 1 at the end of the domain as the product telescopes. We don't add an explicit finalization constraint for this. Instead, the recursion constraint forces . Since by initialization, and we're working over a cyclic domain, the constraint system implicitly checks that the final value is 1.

The accumulator alone isn't sufficient. It verifies that adjacent pairs in are valid, but what if the prover constructs a fake that doesn't actually contain the lookup values ? The grand product equality handles this: the left side of the recursion constraint multiplies over pairs from , while the right side multiplies over and . For the products to match, the multisets must be equal. This is the same principle as the permutation argument in Chapter 13, but here it's embedded directly in the accumulator constraint rather than as a separate check.

The constraint assumes is sorted, since that's what makes duplicates land next to their matches. Plookup enforces this implicitly rather than with an explicit sorting check. The adjacent-pair encoding captures ordering information: since must be "sorted by " (elements appear in the same order as in ), each adjacent pair in must appear exactly once as an adjacent pair in . If the prover reorders , the adjacent pairs change, and the grand product fails. The randomness prevents the prover from constructing a fake that happens to produce the same product despite having different pairs.

Both properties are enforced by the single recursion constraint:

  1. The grand product equality ensures contains exactly , with no values conjured from thin air.
  2. The adjacent-pair encoding ensures every consecutive pair is valid (either a repeat or a table step).
  3. The same encoding implicitly enforces sorting: reordering changes its adjacent pairs, breaking the grand product.

If all hold, every element in found a matching entry in . A cheating prover cannot slip in a value outside the table since it would create an invalid pair that breaks the accumulator.

Verification

The verifier checks the polynomial identities (initialization, recursion) via the standard PLONK batched evaluation. Crucially, the verifier never touches the table directly. The table polynomial was committed during setup, and the verifier only checks openings at random evaluation points. Verification cost is independent of table size : a lookup into a 256-entry table costs the same as a lookup into a million-entry table.

Comparison: Custom Gates vs. Lookup Tables

Both custom gates and lookup tables extend PLONK beyond vanilla arithmetic, but they solve different problems.

Custom gates add terms to the universal gate equation. For example, adding a selector enables computation in a single constraint:

This works well for Poseidon S-boxes, which need fifth powers. The constraint is low-degree, requires no precomputation, and adds no extra commitments. But custom gates hit a wall when the relation isn't algebraically compact. A boolean check is easy: has degree 2. A 16-bit range check would need , a degree-65536 polynomial that no proof system can handle efficiently.

Lookup tables solve this by shifting complexity from constraint degree to table size. Instead of encoding "x is in " as a high-degree polynomial, we precompute a table of valid values and prove membership via the grand product. As we saw in the Verification section, the verifier never touches the table directly, so verification cost scales with the number of lookups, not the table size.

The tradeoff is that lookups add overhead. Each lookup requires entries in the sorted vector , contributions to the accumulator polynomial, and additional commitment openings. For a simple boolean check, this machinery is overkill. For a 64-bit range check or an 8-bit XOR operation, lookups are necessary.

ProblemCustom GateLookup Table
Boolean check ()IdealOverkill
8-bit range checkPossibleEfficient
64-bit range checkImpracticalEssential
XOR/AND/OR operationsComplexClean
Poseidon One gateUnnecessary
Valid opcode checkComplexDirect

Modern systems like UltraPLONK use both: custom gates for algebraic primitives, lookup tables for everything else.

Alternative Lookup Protocols

Plookup was seminal but not unique. Several alternatives offer different trade-offs.

LogUp: The Logarithmic Derivative Approach

Recall the naive product identity from the beginning of this chapter:

Plookup avoided the multiplicity problem by using the sorted merge . LogUp takes a different route: transform the product into a sum where multiplicities become coefficients rather than exponents. Taking the logarithmic derivative (i.e., ) of both sides, and using and :

The exponentiation that required binary decomposition becomes simple scalar multiplication . Over finite fields, we don't actually compute logs or derivatives; the identity is purely algebraic. If the multisets match, the rational functions are equal. Evaluating at a random challenge gives Schwartz-Zippel soundness.

This matters for several reasons:

  1. No sorting required. Plookup requires constructing and committing to the sorted vector . LogUp skips this entirely: no sorted polynomial, no sorting constraints.

  2. Additive structure. Products become sums of fractions. This enables:

    • Simpler multi-table handling (just add the sums)
    • Natural integration with sum-check protocols
    • Easier batching of multiple lookup arguments
  3. Better cross-table lookups. When a circuit uses multiple tables (range, XOR, opcodes), LogUp handles them in a unified sum rather than separate grand products.

Worked Example: Lookups into table over .

The multiplicities are . The verifier sends random challenge . Both sides evaluate at :

Left side (lookups):

Over : and (since and ). So the left side is .

Right side (table with multiplicities):

Both sides equal 15. The identity holds.

Verification:

If we had tried to look up (with ), the left side would include . The left sum becomes . No assignment of multiplicities to the table entries can make the right side equal 74, so the check fails with overwhelming probability over the choice of .

The LogUp bus

LogUp's additive structure enables a pattern that has become the standard architecture in STARK-based zkVMs: the bus argument. When a system has multiple specialized components (an ALU chip, a memory chip, a program counter chip), each component produces or consumes values that must be consistent across components. A CPU chip "sends" an addition operation to the ALU chip, which "receives" it and checks .

The bus formalizes this as a global sum constraint. Each sender contributes for a value it sends. Each receiver contributes for a value it receives. If every sent value is received exactly once, the global sum is zero:

This is just the LogUp identity rewritten: senders play the role of lookups , receivers play the role of table . The zero-sum condition replaces the multiset equality check. Each component adds one auxiliary "running sum" column to its trace, accumulating its contribution row by row. The boundary constraint asserts that the global sum of all components' final running-sum values is zero.

The bus scales linearly with the number of components ( for tables) rather than quadratically () as pairwise permutation arguments would require. Every major STARK-based zkVM (SP1, RISC Zero, Stwo, OpenVM) now uses LogUp bus arguments for inter-component consistency. Chapter 20 discusses how interaction columns implementing LogUp fit into the STARK prover pipeline.

LogUp-GKR combines the bus with the GKR protocol (Chapter 7) for even greater efficiency. Instead of committing to a helper column for the reciprocals , the prover uses a GKR interactive proof to verify the fractional sums directly. This eliminates helper columns entirely, adding only interaction rounds. StarkWare's Stwo prover uses LogUp-GKR over Mersenne31.

cq (Cached Quotients)

A refinement of the logarithmic derivative approach optimized for repeated lookups.

cq pre-computes quotient polynomials for the table, amortizing table processing across multiple lookup batches. The trade-off is setup overhead; benefits emerge with many lookups against the same table.

Caulk and Caulk+

Caulk (2022) asked a different question: what if the table is huge but you only perform a few lookups? Plookup's prover work scales linearly with table size, making it impractical for tables of size or larger.

The core idea: encode the set (or table) as a polynomial , whose roots are exactly the set elements. To prove that a value is in the set, observe that divides iff is a root. KZG lets you prove this divisibility via a quotient polynomial , without revealing which root is. The quotient commitment can be computed from the table commitment using properties of KZG, and this computation is sublinear in .

Prover work is for lookups into a table of size , sublinear in when . The trade-off: Caulk requires trusted setup (KZG), and the quadratic term in limits scalability for many lookups.

Caulk is actually a general membership proof protocol: given a KZG commitment to a set, prove that certain values belong to that set without revealing which positions they occupy. This makes it useful beyond lookup tables, e.g., as an alternative to Merkle proofs for set membership. Plookup and LogUp can't serve this role because they require the prover to process the entire table during proving, which defeats the purpose of a compact membership proof. Caulk's sublinear prover cost is what enables the generalization.

Caulk+ refined this to prover complexity, removing the term entirely.

Halo2 Lookups

Halo2, developed by the Electric Coin Company (Zcash), integrates lookups natively with a "permutation argument" variant rather than Plookup's grand product.

The core idea: to prove (lookups are contained in table ), the prover constructs permuted columns and such that is a permutation of , is a permutation of , and in each row either (a repeat) or (a table match). This forces every element in to equal some element in . The permutation constraints are enforced via a grand product argument similar to PLONK's copy constraints. Unlike Plookup, there is no explicit sorted merge; the "sorting" happens implicitly through the permutation.

Halo2's lookup API lets developers define tables declaratively. The proving system handles the constraint generation automatically. This made Halo2 popular for application circuits: you specify what to look up, not how the lookup argument works. Scroll, Taiko, and other L2s built on Halo2 rely on its lookup system for zkEVM implementation.

Lasso and Jolt

All the protocols above (Plookup, LogUp, Caulk, Halo2) share a limitation: the prover must commit to polynomials whose degree scales with table size.

For Plookup, the sorted vector has length (lookups plus table). For LogUp, the multiplicity polynomial has degree . For Caulk, the table polynomial must be committed during setup. In every case, a table of size means million-coefficient polynomials. A table of size means polynomials with more coefficients than atoms in a grain of sand.

This is a hard wall, not a soft cost. The evaluation table of a 64-bit ADD instruction has entries. No computer can store that polynomial, let alone commit to it.

Early zkVMs worked around this by using small tables (8-bit or 16-bit operations) and paying the cost in constraint complexity for larger operations. A 64-bit addition became a cascade of 8-bit additions with carry propagation. It worked, but it was slow.

Lasso (2023, Setty-Thaler-Wahby) breaks through this wall: prover costs scale with lookups performed rather than table size.

Static vs. Dynamic Tables

Before diving into Lasso's mechanism, distinguish two types of lookups:

Static tables (read-only): Fixed functions like XOR, range checks, or AES S-boxes. The table never changes during execution. Plookup, LogUp, and Lasso excel here.

Dynamic tables (read-write): Simulating RAM (random access memory). The table starts empty and fills up as the program runs. This requires different techniques (like memory-checking arguments or timestamp-based permutation checks) because the table itself is witness-dependent.

Lasso focuses on static tables, but its decomposition insight is what makes truly large tables tractable.

Decomposable Tables

Lasso exploits decomposable tables. Many tables have structure: their MLE (multilinear extension) can be written as a weighted sum of smaller subtables:

Each subtable looks at only a small chunk of the total input . This "Structure of Sums" (SoS) property enables dramatic efficiency gains. (This is a cousin of the tensor product structure for Lagrange bases in Chapter 4—both exploit how multilinear functions over product domains inherit structure from their factors.)

Consider 64-bit AND. The conceptual table has entries (all pairs of 64-bit inputs). But bitwise AND decomposes perfectly: split inputs into sixteen 4-bit chunks, perform 16 lookups into a tiny 256-entry AND_4 table, concatenate results. The prover never touches the -entry table.

Why Prover Costs Scale with Lookups

Lasso represents the sparse access pattern—which indices were hit, how many times—using commitment schemes optimized for sparse polynomials, then proves correctness via sum-check. The prover commits only to the accessed entries and their multiplicities, never to the full table. For structured tables, the verifier can evaluate at a random challenge point in time using the table's algebraic formula, without ever seeing the table itself.

Jolt: A zkVM Built on Lasso

Jolt applies Lasso to build a complete zkVM for RISC-V. The philosophy: replace arithmetization of instruction semantics with lookups.

The entire RISC-V instruction set can be viewed as one giant table mapping (opcode, operand1, operand2) to results. This table is far too large to materialize, but it's decomposable: most instructions break into independent operations on small chunks. A 64-bit XOR decomposes into 16 lookups into a 256-entry XOR_4 table. The subtables are tiny, pre-computed once, and reused across all instructions.

Jolt combines Lasso (for instruction semantics) with R1CS constraints (for wiring: program counter updates, register consistency, data flow). Why this hybrid? Arithmetizing a 64-bit XOR in R1CS requires 64+ constraints for bit decomposition; Jolt proves it with 16 cheap lookups. But simple wiring constraints are trivial in R1CS. Use each tool where it excels.

Limitations

Lasso and Jolt require decomposable table structure. Tables without chunk-independent structure don't benefit. But for CPU instruction sets, the structure is natural: most operations are bitwise or arithmetic with clean chunk decompositions.

The field continues evolving. The core insight (reducing set membership to polynomial identity) admits many instantiations, each optimizing for different table sizes, structures, and use cases.

Lookups Across Proving Systems

The lookup techniques above: Plookup, LogUp, Lasso, adapt to different proving backends. Plookup and Halo2 integrate naturally with PLONK's polynomial commitment model. Lasso and Jolt use sum-check and R1CS (via Spartan). STARK-based systems take a different path.

In STARKs, computation is represented as an execution trace: a matrix where each row is a state and columns hold registers, memory, and auxiliary values. Lookup arguments integrate by adding columns to this trace:

  • The lookup table becomes one or more public columns (known to the verifier)
  • Values to be looked up appear in witness columns
  • A running product column accumulates the grand product (Plookup-style) or running sum (LogUp-style)
  • Transition constraints enforce the recursive accumulator relation row-by-row

The FRI-based polynomial commitment then proves that these trace columns satisfy all constraints. The lookup argument's algebraic core is unchanged; only the commitment mechanism differs.

STARK-based zkVMs (Cairo, RISC0, SP1) rely heavily on this integration. Their execution traces naturally represent VM state transitions, and lookups handle instruction semantics, memory consistency, and range checks. The trace-based model makes it easy to add new lookup tables: just add columns and constraints.

Key Takeaways

General principles (apply to all lookup arguments):

  1. Lookup arguments shift complexity from logic to data: Precompute valid tuples; prove membership rather than computation. This is the core insight shared by Plookup, LogUp, Lasso, and all variants.

  2. The formal problem: Given lookups and table , prove . Different protocols reduce this multiset inclusion to different polynomial identities.

  3. Cost structure: Lookup-based proofs achieve roughly constant cost per lookup, independent of the logical complexity of what the table encodes. A 16-bit range check or an 8-bit XOR costs the same as a simple membership test.

  4. Complements custom gates: Lookups handle non-algebraic constraints; custom gates handle algebraic primitives. Modern systems (UltraPLONK, Halo2) use both.

  5. zkVM foundation: Without lookup arguments, verifying arbitrary computation at scale would be infeasible. Every major zkVM relies on lookups for instruction semantics.

Plookup-specific mechanics (the sorted-merge approach from Section 2):

  1. Sorted vector reduction: Plookup transforms into a claim about the sorted merge .

  2. Adjacent pair property: In Plookup, every consecutive pair in is either a repeat (from slotting in) or exists as adjacent in .

  3. Grand product identity: The polynomial identity encodes Plookup's adjacent-pair check. The accumulator enforces this recursively, integrating with PLONK's permutation machinery.

Alternative approaches (different trade-offs):

  1. LogUp replaces products with sums of logarithmic derivatives: no sorting, cleaner multi-table handling, natural sum-check integration.

  2. Caulk achieves sublinear prover work in table size via KZG-based subset arguments, useful when few lookups access a huge table.

  3. Halo2 uses permutation arguments rather than sorted merges, with lookups integrated into the constraint system declaratively.

  4. Lasso exploits decomposable tables (SoS structure) to achieve prover costs scaling with lookups performed, not table size. Combined with sparse polynomial commitments, this enables effective tables of size . Jolt applies this to build a complete zkVM.

  5. STARK integration: Lookup arguments adapt to trace-based proving via running product/sum columns and transition constraints, used by Cairo, RISC0, and SP1.

Chapter 15: STARKs

While Gabizon and Williamson were building PLONK, a parallel revolution was underway.

Eli Ben-Sasson had been working on probabilistically checkable proofs (PCPs) since the early 2000s: the discovery that any proof can be encoded so a verifier need only spot-check a few random bits to detect errors. PCPs transformed complexity theory but remained practically useless. The constructions were galactic.

All the pairing-based SNARKs we've seen (Groth16, PLONK) require trusted setup. Ben-Sasson asked a different question: could you build proof systems using nothing but hash functions?

In 2018, Ben-Sasson and colleagues (Bentov, Horesh, Riabzev) published the STARK (Scalable Transparent ARgument of Knowledge) construction: transparent (no trusted setup), post-quantum (no pairings), with security based only on collision-resistant hashing. The theoretical ingredients, Interactive Oracle Proofs (IOPs), the FRI protocol (see Chapter 10), the ALI protocol (Algebraic Linking IOP), had been developed over the preceding years, often by the same researchers. The 2018 paper synthesized them into a complete, practical system.

STARKs have since become one of the two dominant proof system families, with independent implementations by StarkWare, Polygon, and RISC Zero. This chapter develops the STARK paradigm: how FRI combines with a state-machine model of computation to yield transparent, scalable, quantum-resistant proofs, at the cost of larger proof sizes than their pairing-based cousins.


Why Not Pairings?

The most efficient SNARKs in Chapters 12-13 rely on pairing-based polynomial commitments. Groth16 builds pairings directly into its verification equation. PLONK is a polynomial IOP, agnostic to the commitment scheme, but achieves its smallest proofs when compiled with KZG, which requires pairings. The bilinear map is what enables constant-size proofs and verification.

This foundation is remarkably productive. But it carries costs that grow heavier with scrutiny.

The first cost is trust. A KZG commitment scheme requires a structured reference string: powers of a secret encoded in the group. Someone generated that . If they kept it, they can forge proofs. The elaborate ceremonies of Chapter 12 (the multi-party computations, the public randomness beacons, the trusted participants) exist to distribute this trust. But distributed trust is still trust. The ceremony could fail. Participants could collude. The procedures could contain subtle flaws discovered years later.

The second cost is quantum vulnerability. Shor's algorithm solves discrete logarithms in polynomial time on a quantum computer. The security of KZG, Groth16, and IPA all rest on the hardness of discrete log in elliptic curve groups. Pairings add structure on top of this assumption but don't change the underlying vulnerability. When a sufficiently large quantum computer exists, all these schemes break. When that day comes is uncertain. That it will come seems increasingly likely. A proof verified today may need to remain trusted for decades.

The third cost is field rigidity. Only a small family of elliptic curves support efficient pairings while remaining cryptographically secure, and each curve dictates a specific large prime field (e.g., the 254-bit field of BN254, the 381-bit field of BLS12-381). Pairing-based proof systems are locked into these fields, ruling out optimizations over smaller or differently structured fields where arithmetic is dramatically cheaper.

STARKs abandon elliptic curves entirely. They ask a more primitive question: what can we prove using only hash functions?

The Hash Function Gambit

A collision-resistant hash function is perhaps the most conservative cryptographic assumption we have. SHA-256, Blake3, Keccak: these primitives are analyzed relentlessly, deployed universally, and trusted implicitly. They offer no algebraic structure, no homomorphisms, no elegant equations. Just a box that takes input and produces output, where finding two inputs with the same output is computationally infeasible.

The quantum story here differs from discrete log in kind. Grover's algorithm provides a quadratic speedup for unstructured search, reducing the security of a 256-bit hash from to operations. This is manageable: use a larger hash output and security is restored. Contrast this with Shor's exponential speedup against discrete log, which breaks the problem entirely rather than merely weakening it.

This seems like a step backward. Algebraic structure is what made polynomial commitments possible. KZG works because preserves polynomial relationships, because the commitment scheme respects the algebra of the underlying object. A hash function respects nothing. . The hash of a polynomial evaluation tells you nothing about the polynomial.

Yet hash functions offer something pairings cannot: a Merkle tree. Chapter 10 developed this machinery in detail; here we summarize the key ideas before showing how STARKs compose them into a complete proof system.

Commit to a sequence of values by hashing them into a binary tree. The root is the commitment. To open any leaf, provide the authentication path, the hashes connecting that leaf to the root. The binding property is information-theoretic within the random oracle model: changing any leaf changes the root. No trapdoors, no toxic waste, no ceremonies.

The problem is that a Merkle commitment is simultaneously too strong and too weak. It's too strong in that opening a single position already costs hash values, compared to for KZG. And it's too weak in that there's no way to prove anything about the committed values without opening them. A KZG commitment to a polynomial lets you prove at any point with a single group element. A Merkle commitment to evaluations of on a domain lets you prove only if happens to be in the domain, and only by opening that leaf explicitly.

The insight behind STARKs is that these limitations can be overcome by a shift in perspective. Instead of proving polynomial identities directly, we prove that a committed function is close to a low-degree polynomial. This is the domain of coding theory, not algebra. And coding theory has powerful tools for detecting errors through random sampling.

The Reed-Solomon Lens

Every proof system we've seen reduces computation to polynomial constraints. The prover commits to polynomials; the verifier checks that these polynomials satisfy certain identities. In pairing-based systems (Groth16, PLONK), the commitment scheme itself enforces polynomial structure: KZG commitments can only represent polynomials, and pairing checks verify evaluations algebraically. Low-degree-ness is built into the commitment.

With Merkle trees, this is no longer free. A Merkle tree commits to arbitrary sequences of field elements, with no structural guarantee. The prover claims the committed values are evaluations of a low-degree polynomial, but nothing about the commitment prevents them from committing garbage.

The Reed-Solomon encoding (Chapter 2) solves this. The prover's polynomial has degree at most , determined by evaluations. But the prover evaluates it on a much larger domain of points, with , and commits to all values. This serves two purposes. First, it creates something to check: any field elements are consistent with some degree- polynomial (by Lagrange interpolation), so a commitment to just values can never be invalid. But most sequences of values are not consistent with any low-degree polynomial, so cheating becomes detectable. Second, the verifier queries random points in the extended domain rather than the trace domain, so the actual computation data is never revealed.

The Reed-Solomon distance property quantifies the first point. If the committed values don't correspond to a degree- polynomial, they disagree with every such polynomial on at least of the positions. A random query hits a disagreement with probability at least , and independent queries miss all disagreements with probability at most . For a blowup factor and queries: . A random sample suffices.

The FRI protocol (Chapter 10) turns this sampling argument into a complete interactive low-degree test, replacing the structural guarantee that KZG provides for free.

So STARKs have a way to commit to polynomials (Merkle trees) and a way to verify they're low-degree (FRI). But FRI only proves a degree bound: the committed function is close to some low-degree polynomial. We still need to prove it's the right polynomial, one that encodes a valid computation. That requires a way to express computation as polynomial constraints.

Computation as State Evolution

How should we encode computation into polynomials for this framework? The proof systems of previous chapters use circuits: directed acyclic graphs where wires carry values and gates impose constraints. This works, but it handles iteration awkwardly. A loop executing times becomes copies of the loop body, each a separate subcircuit. The repetition that made the loop simple to write is obscured in the flattened graph.

STARKs adopt a different model: the state machine.

A computation is a sequence of states evolving over discrete time. Each state is a tuple of register values. A transition function maps to , and is the same at every timestep. Only the register values change.

This uniformity is what makes the model efficient. A hash function running for rounds, a CPU executing instructions: both are applications of the same transition function. In a circuit, each iteration contributes its own gates and constraints, scaling linearly with . In a state machine, the transition constraints describe a single step and apply identically at every timestep. The description has fixed size, independent of .

Suppose we want to prove we computed . The state machine has two registers: a counter and an accumulator . The transition rule: and . The trace:

Step
001
113
229
3327
4481
55243
66729
772187
886561

This table is a trace: a matrix with registers and rows. The "Step" column is just a label; the actual data is the and columns. Each row captures the complete state at one moment; each column tracks one register's evolution through time.

The transition constraint ("next accumulator equals current accumulator times 3") is the same at every row. We don't need 8 separate multiplication gates; we need one constraint that holds 8 times. The prover commits to the entire trace, then proves the constraint holds everywhere. For , the constraint is still just one equation; only the trace grows longer.

Algebraic Intermediate Representation

An AIR (Algebraic Intermediate Representation) encodes the trace and its transition constraints in polynomial form.

The trace is a matrix with columns (registers) and rows (timesteps). Each column, viewed as a sequence of field elements, becomes a polynomial via interpolation. Choose a domain where is a primitive -th root of unity. The column polynomial is the unique polynomial of degree less than satisfying .

In the trace, we have two registers: (the counter) and (the accumulator). These become two column polynomials:

  • : the unique degree-8 polynomial passing through
  • : the unique degree-8 polynomial passing through

Since is the value of register at step , replacing with shifts forward by one step: , which is step . This lets us express "next row" algebraically. The transition constraint "next accumulator = current accumulator × 3" becomes . At , this says , i.e., . The single polynomial identity encodes all 8 transition checks at once.

Another example: if a different transition function requires that register at step equals at step , this becomes:

This identity must hold for , covering all transitions. Define the constraint polynomial:

If the trace is valid, vanishes on . By the factor theorem, is divisible by the vanishing polynomial . The quotient:

is a polynomial of known degree. If doesn't vanish on (if the trace violates the transition constraint somewhere) then isn't a polynomial. It's a rational function with poles at the violation points.

Why Constraint Degree Matters

The degree of the constraint polynomial directly impacts prover cost. If a transition constraint involves , that term has degree (since has degree ). The composition polynomial inherits this: . The prover must commit to this polynomial over the LDE domain, and FRI must prove its degree bound.

This creates a trade-off. Higher-degree constraints let you express more complex transitions in a single step, but they blow up the prover's work. A degree-8 constraint over a million-step trace produces a composition polynomial of degree ~8 million, requiring proportionally more commitment and FRI work. Most practical AIR systems keep constraint degree between 2 and 4, accepting more trace columns (more registers) to avoid high-degree terms. The art of AIR design is balancing expressiveness against this degree bottleneck. Chapter 20 quantifies this tradeoff and develops the AIR design patterns (auxiliary columns, periodic columns, wide versus tall traces) that production systems use to minimize prover cost.

Transition constraints enforce the rules at every step, but they say nothing about which computation we're proving. We also need boundary constraints to pin down the inputs and outputs. In our example:

  • Input: (accumulator starts at 1)
  • Output: (accumulator ends at )

Each becomes a divisibility check. If the input requires register 0 to equal 5 at step 0, the constraint becomes vanishing at , quotient .

We now have multiple constraint quotients: for the transition, and for boundaries, possibly more. Rather than prove each separately, we batch them into a single polynomial using random challenges (derived via Fiat-Shamir):

Why does this work? If all quotients are polynomials, their linear combination is a polynomial. If any quotient has a pole (from a violated constraint), the random combination almost certainly preserves that pole: the values would need to be precisely chosen to cancel it, which happens with negligible probability over a large field.

Putting it together for our example, the three quotients are:

  1. Transition: is a polynomial (each step follows the rules)
  2. Input boundary: is a polynomial (accumulator starts at 1)
  3. Output boundary: is a polynomial (accumulator ends at 6561)

If any constraint fails, the corresponding quotient has a pole, the composition polynomial inherits it, and FRI rejects it as non-low-degree.

To make this concrete: the trace polynomials have degree at most , since the trace domain has points (9 in our example). The prover evaluates them not on alone, but on a larger domain , typically 4 to 16 times larger. This is the low-degree extension (LDE). As we saw in the Reed-Solomon section, this redundancy is what makes cheating detectable: FRI's random queries in catch the non-low-degree composition polynomial. The prover commits to these LDE evaluations via Merkle tree, with the root as the commitment.

The Complete Protocol

Prover's Algorithm:

  1. Execute the computation, producing the execution trace.

  2. Interpolate each trace column to obtain polynomials over domain .

  3. Evaluate all polynomials on the LDE domain , forming a matrix. Commit this matrix in a single Merkle tree: each leaf is the hash of one row for a domain point . Send the trace root to the verifier.

  4. Derive random challenges by hashing the transcript (Fiat-Shamir).

  5. Compute constraint polynomials, form quotients, and batch them into the composition polynomial using the challenges from step 4.

  6. Evaluate the composition polynomial on . Commit via a second Merkle tree and send the composition root to the verifier.

  7. Run FRI on the composition polynomial, proving it has degree less than the known bound.

  8. Derive query points by hashing the transcript (Fiat-Shamir). For each : open the trace polynomials and composition polynomial, providing Merkle authentication paths.

Each query catches a cheater with probability roughly , where is the blowup factor (). With queries, soundness error is roughly . For 128-bit security with blowup factor 8, around 45 queries suffice.

Verifier's Algorithm:

  1. Receive the Merkle roots (trace and composition), FRI commitments, and query responses.

  2. Derive all Fiat-Shamir challenges from the transcript.

  3. Verify FRI: check that the committed function is close to a low-degree polynomial.

  4. For each query point :

    • The prover opens the trace Merkle tree at , providing the row and an authentication path. The verifier hashes the row and checks it against the trace root.
    • The prover also opens the composition Merkle tree at , providing and its authentication path. The verifier checks it against the composition root (which FRI proved corresponds to a low-degree polynomial).
    • The verifier plugs the trace values into the constraint equations, forms the quotients, applies the batching coefficients , and locally recomputes what should be. If this doesn't match the opened composition value, reject.
  5. Accept if all checks pass.

This last sub-step is the AIR-FRI link: it connects FRI (which only proves low-degree-ness, knowing nothing about constraints or computations) to the actual claim being verified. Without it, a cheating prover could commit to , pass FRI trivially, and hope the verifier is satisfied.

Why is this sound? The prover committed to the trace before learning the query points (Fiat-Shamir). If the trace violates any constraint, the composition polynomial has poles and isn't low-degree; FRI catches this. If the trace is valid but the prover committed to a different composition polynomial, the opened value and the locally recomputed value disagree at most points (Schwartz-Zippel); the random queries catch this.

There is a subtle gap in standard FRI: the verifier only queries points in the LDE domain , so a cheating prover could commit to a function that's low-degree on but encodes wrong trace values. DEEP-FRI (introduced in Chapter 10) closes this gap. The verifier samples a random point outside and requires the prover to open the trace polynomials there. Since honest trace polynomials are globally low-degree, they can be evaluated anywhere; a cheater who faked values only on cannot consistently answer at . In the STARK context, this means the AIR-FRI link is checked at a point the prover could not have anticipated when constructing the trace commitment, which is why most STARK implementations use DEEP-FRI rather than standard FRI. Chapter 20 develops the full DEEP-ALI optimization, showing how it also eliminates the separate composition polynomial commitment.

A Concrete Example: Fibonacci

Let's trace the protocol on a minimal computation: proving knowledge of the 7th Fibonacci number.

The claim: starting from , the sequence satisfies . The trace has two registers representing consecutive Fibonacci numbers, with 6 rows (steps 0-5):

Step
011
112
223
335
458
5813

The transition constraints enforce, at each step :

  • (the next is the current )
  • (the next is the sum)

The boundary constraints pin down the endpoints:

  • (initial condition)
  • (initial condition)
  • (the claimed output )

Let be a primitive 6th root of unity. Interpolating the columns gives with and with . Using the -shift from the AIR section, the constraint polynomials are:

  • : next equals current
  • : next equals current
  • , vanishing at
  • , vanishing at
  • , vanishing at

Each constraint polynomial is divided by the appropriate vanishing polynomial. The transition constraints must hold at steps 0-4, so they're divided by . Batching with random challenges :

If the trace is valid (and it is) this composition is a polynomial of degree roughly .

Now the commitment step. The prover evaluates and on a larger LDE domain (say 48 points, with blowup factor 8). Each leaf of the trace Merkle tree holds the pair for one . The prover sends the trace root. After deriving the Fiat-Shamir challenges , the prover evaluates on , commits it in a second Merkle tree, and sends the composition root.

At query time, one detail this example reveals: to check , the verifier needs trace values at both and . So queries come in pairs: the prover opens the trace Merkle tree at and together, giving the verifier both the "current row" and "next row" values. The prover also opens the composition tree at . The verifier recomputes from the trace values and checks it matches the opened composition value.

FRI then proves the composition polynomial is low-degree via the folding protocol from Chapter 10. For our degree-5 polynomial over a 48-point LDE domain (blowup factor 8), three folding rounds reduce it to a constant. At each round, the verifier spot-checks that the folded layer is consistent with the previous one. The same query points serve both the AIR consistency check (opening trace values) and FRI verification (opening composition values at and for folding), so one set of openings handles both.

Adding Zero-Knowledge

The protocol as described so far is a transparent argument of knowledge, but it is not zero-knowledge. When the verifier queries a point and the prover opens the trace Merkle tree, the verifier learns the actual values . These are evaluations of the trace polynomials, and they leak information about the witness (the execution trace).

Chapter 18 covers the general theory of making proof systems zero-knowledge. Two broad techniques apply: commit-and-prove (hiding values behind homomorphic commitments) and polynomial masking (adding randomness that is invisible on the constraint domain but randomizes the verifier's queries). Here we focus on the approach specific to STARKs: trace randomization.

The idea is to extend the execution trace with random data before committing. The prover appends random rows to the trace (typically to ), filled with random field elements, extending it from to rows. The trace polynomials are then interpolated over a domain of size rather than .

Why does this help? The trace polynomials now encode both the real computation (on the first rows) and random noise (on the last rows). A low-degree polynomial is globally determined by its values, so the random rows "contaminate" evaluations everywhere outside the original domain . More precisely, each trace polynomial has degree , determined by real values and random values. The random degrees of freedom make the polynomial's evaluations at any points outside statistically independent of the real trace. Since the verifier's queries land in , the opened values reveal nothing about the witness.

The constraint system requires only minor adjustments. The random rows do not satisfy the transition constraints, but they don't need to: already vanishes only at , so the quotient remains a polynomial even though is nonzero at the random row positions. Boundary constraints are unaffected since they pin specific rows within the original trace (e.g., ). The composition polynomial is formed as before but over the larger domain, and FRI proves the slightly larger degree bound .

Verification works directly on the blinded polynomials. The verifier never needs to see the actual trace values on . At a query point , the prover opens the blinded evaluations , and the verifier recomputes from them, checking consistency with FRI. The quotient check confirms that some low-degree polynomial satisfies the constraints on , which is all the verifier needs. The boundary constraints are verified through their own quotient terms in the composition polynomial.

A simulator that knows only the public inputs and outputs can produce identically distributed transcripts: it picks random trace polynomials consistent with the boundary constraints and simulates the protocol. The random rows provide enough freedom to match any set of query responses the real prover would produce. This technique is specific to the STARK setting because it exploits the separation between the trace domain and the query domain . Pairing-based systems use different masking strategies suited to their algebraic structure (see Chapter 18).

The Trust and Size Trade-off

STARKs achieve transparency at a cost: proof size.

PropertyGroth16PLONK (KZG)STARKs
Trusted setupPer-circuitUniversalNone
Proof size128 bytes~500 bytes20-100 KB
VerificationO(1)O(1)O(polylog )
Post-quantumNoNoYes
AssumptionsPairing-basedq-SDHHash function

The gap is stark: two orders of magnitude in proof size, from hundreds of bytes to tens of kilobytes. For on-chain verification, where every byte costs gas, this matters enormously. A Groth16 proof costs perhaps 200K gas to verify on Ethereum. A raw STARK proof would cost millions.

But the size gap has motivated clever engineering. Proof wrapping is a general composition technique where one proof system verifies the output of another, and any system can in principle be wrapped. STARKs benefit from this the most because their large proofs are precisely the problem wrapping solves. Concretely, a STARK proves the bulk of the computation (transparently, with the state machine model's natural fit for VMs), then a Groth16 proof attests "I verified a valid STARK proof." The Groth16 verification circuit is fixed-size and small. The on-chain cost is the cost of verifying Groth16, regardless of the original computation's size.

This hybrid architecture is deployed in production systems like StarkNet, zkSync, and Polygon zkEVM. The STARK itself remains fully transparent, relying only on hash functions. Pairings enter only through the Groth16 wrapper, which verifies a fixed, auditable circuit. Part of why STARKs dominate in these systems is AIR's natural fit for virtual machines: the transition constraints encode the VM's instruction set once, and the trace varies with the program while the constraints stay fixed. The circuit model would require a different circuit for each program, or "unrolling" the VM for a fixed number of steps. AIR handles arbitrary-length execution with fixed constraint complexity.

Circle STARKs and Small-Field Proving

Throughout this chapter, we interpolated trace columns over a domain of roots of unity. This choice wasn't arbitrary: roots of unity enable the FFT, which is what makes interpolation and evaluation over efficient ( rather than ). But FFT requires a multiplicative subgroup of size , which constrains the field: we need primes where is divisible by a large power of 2. Fields like Goldilocks () and BabyBear () are carefully constructed to meet this requirement.

Circle STARKs remove this constraint by working over a different algebraic structure: the circle group.

The Circle Group

Consider a prime and the set of points satisfying over . This is an algebraic curve, specifically a "circle" over a finite field.

For Mersenne primes like , the circle group has particularly nice structure:

  • The group has order , a perfect power of 2
  • This enables FFT-like algorithms directly, without the divisibility constraint
  • Mersenne primes have fast modular arithmetic (reduction is just addition and shift)

The group operation on the circle is defined via the "complex multiplication" formula:

This is the standard multiplication formula for complex numbers restricted to the unit circle. Over , it's well-defined and creates a cyclic group.

The M31 Advantage

The Mersenne prime deserves special attention. Two properties converge to make it exceptionally efficient for STARKs.

The first is cheap arithmetic, a property of Mersenne primes themselves. For any product , split the result into low and high 31-bit parts, . Since , reduction is just plus a conditional subtraction. No division, no extended multiplication. Since elements range from to , each fits in a single 32-bit word, so CPUs handle them natively and SIMD instructions process 4-8 elements per cycle. Compare this to 64-bit Goldilocks (needs 64-bit multiplies, harder to vectorize) or 254-bit BN254 (requires multi-precision arithmetic, roughly 10x slower per operation). This fast arithmetic is a property of the prime, not the circle group. STARKs can exploit it because their security comes from hash functions, not from discrete log hardness over the field, so 31-bit elements provide enough room. Pairing-based systems like Groth16 and PLONK (with KZG) cannot: the pairing-friendly curve fixes the scalar field at ~254 bits, and no pairing-friendly curve exists over a 31-bit field. Sum-check based systems occupy a middle ground: sum-check itself is field-agnostic, but the PCS dictates the field. With KZG commitments, they inherit the same ~254-bit constraint. With hash-based commitments (Brakedown, Binius), they too can use small fields.

The second property is the circle group's order. Over M31, the multiplicative group has order , which is not a large power of 2, so traditional FFT-based STARKs cannot use M31 directly. But the circle group has order , a perfect power of 2, enabling FFT-like algorithms over the circle. Trace lengths of or divide evenly with no wasted bits.

These advantages compound. Implementations using M31 Circle STARKs, such as StarkWare's Stwo and Polygon's Plonky3, report order-of-magnitude speedups over provers using larger fields. The security model is unchanged: the circle structure is used for FFTs, not for cryptographic assumptions.

The Trade-off

Circle STARKs require adapting the polynomial machinery:

  • Polynomials are defined over the circle group, not a multiplicative subgroup
  • FRI folding uses the circle structure
  • Some constraint types require reformulation

The implementation complexity is higher. But for systems targeting maximum prover speed, particularly zkVMs where prover time dominates, Circle STARKs offer a path to concrete performance improvements. Chapter 20 develops Circle FRI's folding mechanism in detail, traces the full prover pipeline over M31, and shows how Stwo achieves over 500,000 Poseidon2 hashes per second.

The Broader Lesson

Circle STARKs exemplify a general principle: match the algebraic structure to hardware capabilities. Traditional STARKs chose fields for mathematical convenience (large primes with smooth multiplicative order). Circle STARKs choose fields for computational efficiency (Mersenne primes with fast reduction), then build the necessary mathematical structure (the circle group) around that choice. Binius (Chapter 26) pushes this further by working over binary tower fields, where addition is XOR and field elements match the computer's native data types. As proof systems mature, field choice increasingly reflects hardware realities rather than purely mathematical aesthetics.

Key Takeaways

  1. STARKs eliminate trusted setup by building on hash functions rather than pairings. Merkle trees provide binding commitments; FRI proves low-degree properties.

  2. Computation becomes a trace. The state machine model represents computation as a matrix of register values over timesteps. Each column interpolates to a polynomial over a root-of-unity domain , and uniform transition constraints relate consecutive rows via the shift.

  3. The algebraic pipeline reduces all constraints to a single degree check. Constraint satisfaction becomes polynomial divisibility (quotients), quotients batch into a composition polynomial via random weights, and FRI verifies the degree bound. Low-degree extension over ensures any violation spreads across most of .

  4. The AIR-FRI link. The verifier opens trace values at query points, locally recomputes the composition, and checks it matches the committed value. The same queries feed into FRI consistency checks: one query, two purposes.

  5. Trace randomization adds zero-knowledge. Appending random rows before committing contaminates evaluations outside , so queries in reveal nothing about the witness. The existing constraint structure accommodates this with no changes to the vanishing polynomial.

  6. Circle STARKs unlock small-field proving. By replacing multiplicative subgroups with the circle group, STARKs can use Mersenne primes like , where 31-bit arithmetic and SIMD vectorization yield order-of-magnitude speedups. This is possible because STARK security depends on hash functions, not on field size.

  7. The STARK trade-off: post-quantum security and transparency at the cost of larger proofs (tens of kilobytes versus hundreds of bytes). Hybrid architectures wrap STARKs in pairing-based proofs for on-chain verification.

Chapter 16: -Protocols: The Simplest Zero-Knowledge Proofs

In 1989, a Belgian cryptographer named Jean-Jacques Quisquater faced an unusual challenge: explaining zero-knowledge proofs to his children.

The mathematics was forbidding. Goldwasser, Micali, and Rackoff had formalized the concept four years earlier, but their definitions involved Turing machines, polynomial-time simulators, and computational indistinguishability. Quisquater wanted something a six-year-old could grasp.

So he invented a cave.

The Children's Story

In Quisquater's tale, Peggy (the Prover) wants to prove to Victor (the Verifier) that she knows the magic word to open a door deep inside a cave. The cave splits into two paths (Left and Right) that reconnect at the magic door.

Peggy enters the cave and takes a random path while Victor waits outside. Victor then walks to the fork and shouts: "Come out the Left path!"

If Peggy knows the magic word, she can always comply. If she originally went Left, she walks out. If she went Right, she opens the door with the magic word and exits through the Left. Either way, Victor sees her emerge from the Left.

If Peggy doesn't know the word, she's trapped. Half the time, Victor shouts for the path she's already on (she succeeds). Half the time, he shouts for the other side (she fails, stuck behind a locked door).

They repeat this 20 times. A faker has a ≈ one-in-a-million chance of consistently appearing from the correct side. But someone who knows the word succeeds every time.

This story, published as "How to Explain Zero-Knowledge Protocols to Your Children," captures the essence of what we now call a -protocol: Commitment (entering the cave), Challenge (Victor shouting), Response (appearing from the correct side). Almost all modern cryptography, from your credit card chip to your blockchain wallet, is a mathematical version of this cave.

The paper became a classic, despite the fact that most children would probably stop listening after "takes a random path" to ask what "random" means. The cave analogy appears in nearly every introductory cryptography course regardless. What makes it so powerful is that it captures the structure of zero-knowledge: the prover commits to a position before knowing the challenge, then demonstrates knowledge by responding correctly.

This chapter develops the mathematics behind the cave. A prover commits to something random. A verifier challenges with something random. The prover responds with something that combines both randomnesses with their secret. The verifier checks a simple algebraic equation. If it holds, accept; if not, reject.

This is a -protocol. The name comes from the shape of the message flow: three arrows forming the Greek letter when drawn between prover and verifier. The structure is so pervasive that it appears everywhere cryptography touches authentication: digital signatures, identification schemes, credential systems, and as building blocks within the complex SNARKs we've studied.

Why study something so simple after the machinery of Groth16 and STARKs?

Because -protocols crystallize the core ideas of zero-knowledge. The simulator that we'll construct, picking the response first then computing what the commitment "must have been," is the archetype of all simulation arguments. The special soundness property (that two accepting transcripts with different challenges allow witness extraction) is the template for proofs of knowledge everywhere. And the Fiat-Shamir transform, which converts interaction into non-interaction, was developed precisely for -protocols.

Understand -protocols, and the zero-knowledge property itself becomes clear. This chapter prepares the ground for Chapter 17, where we formalize what "zero-knowledge" means. Here, we see it in its simplest form.

The Discrete Logarithm Problem

We return to familiar ground. Chapter 6 introduced the discrete logarithm problem as the foundation for Pedersen commitments. Now it serves a different purpose: enabling proofs of knowledge.

The setting is a cyclic group of prime order with generator . Every element can be written as for some . This is the discrete logarithm of with respect to . Computing from is hard; computing from is easy. This asymmetry, the one-wayness that made Pedersen commitments binding, now enables something new.

We use multiplicative notation throughout this chapter. In practice, most implementations use elliptic curves, where the group operation is written additively: becomes , becomes , and the Schnorr verification equation becomes . The mathematics is identical; only the symbols change.

The prover knows . The verifier sees but cannot compute directly. The prover wants to convince the verifier that they know without revealing what is.

The naive approach fails immediately. If the prover just sends , the verifier can check , but the secret is exposed. If the prover sends nothing, the verifier has no basis for belief. There seems to be no middle ground.

Interactive proofs create that middle ground.

Schnorr's Protocol

Claus Schnorr discovered the canonical solution in 1989. The protocol is three messages, two exponentiations for the prover, two exponentiations for the verifier.

Both parties know a group , a generator , and the public value . The prover alone knows the witness . The protocol proceeds in three moves:

  1. Commitment. The prover samples a random and computes . The prover sends to the verifier.

  2. Challenge. The verifier samples a random and sends to the prover.

  3. Response. The prover computes and sends to the verifier.

  4. Verification. The verifier checks whether . Accept if yes, reject otherwise.

sequenceDiagram
    participant P as Prover (knows w)
    participant V as Verifier

    Note over P: Sample r ← ℤq
    Note over P: Compute a = gʳ
    P->>V: a (commitment)

    Note over V: Sample e ← ℤq
    V->>P: e (challenge)

    Note over P: Compute z = r + w·e
    P->>V: z (response)

    Note over V: Check gᶻ = a · hᵉ
    Note over V: Accept / Reject

That's the entire protocol. The diagram above makes the shape visible: three arrows zigzagging between prover and verifier. Let's understand why it works.

Completeness. An honest prover with the correct always passes verification:

The algebra is straightforward. The commitment hides ; the response reveals a linear combination of and ; but one equation in two unknowns doesn't determine either.

Soundness. A prover who doesn't know can cheat only by guessing the challenge before committing. Once they send , they're locked in. For a random , there's exactly one that satisfies the verification equation (namely ). A cheating prover who doesn't know cannot compute this .

More precisely: suppose a cheater could answer two different challenges and for the same commitment . Then we'd have:

Dividing these equations:

The extractor computes from the known exponents:

This is well-defined since and is prime. To verify: .

This extraction is a proof technique, not something the verifier can actually do. In a real execution, the prover answers only one challenge, so stays hidden. But the argument shows that anyone who could answer two challenges for the same commitment must already know . Contrapositively, someone who doesn't know cannot reliably answer even a single random challenge. This property is called special soundness: two accepting transcripts with different challenges allow extracting the witness. It is why -protocols prove you know something, not merely that something exists.

There is a clean geometric way to see this. Schnorr's protocol is secretly proving you know the equation of a line. In , think of as the slope and as the y-intercept. The prover commits to the intercept (, hidden as ). The verifier picks an x-coordinate (). The prover reveals the y-coordinate (). In a single execution, the verifier learns one point on the line, which is consistent with infinitely many slopes, so stays hidden. But if an extractor could rewind and obtain a second point on the same line (same intercept , since was fixed), two points would determine the slope.

Honest-verifier zero-knowledge (HVZK). Here is where things become subtle. What follows is a restricted form of zero-knowledge that assumes the verifier behaves honestly (samples uniformly at random). Chapter 17 formalizes the full definition, which must handle malicious verifiers. For now, consider a simulator that doesn't know but wants to produce a valid-looking transcript . The simulator proceeds backwards:

  1. Sample (the challenge first!)
  2. Sample (the response, uniform and independent)
  3. Compute (the commitment that makes the equation hold)

Check: .

The transcript is valid. And its distribution is identical to a real transcript:

  • In a real transcript: is uniform (verifier's randomness), is uniform (because is uniform), and is determined.
  • In a simulated transcript: is uniform (simulator's choice), is uniform (simulator's choice), and is determined.

Both distributions have and uniform and independent, with determined by the verification equation. They are identical.

More formally, let denote the distribution of real transcripts and the simulator's output. Both are distributions over . In : where . In : where . In both cases, and are uniform and independent (in the real case, is uniform because is uniform and independent of ). The value is then uniquely determined by the verification equation . Since both distributions have identical marginals on and is a deterministic function of , we have (perfect equality, not just computational indistinguishability).

The transcript reveals nothing about that the verifier couldn't have generated alone.

In real execution, events unfold forward: Commitment → Challenge → Response. The simulator reverses this. It picks the answer first (), invents a question that fits (), then back-calculates what the commitment "must have been" (). This temporal reversal is invisible in the final transcript. Anyone looking at cannot tell whether it was produced forward (by someone who knows ) or backward (by someone who doesn't). If a transcript can be faked without the secret, then having the secret cannot be what makes the transcript convincing. The transcript itself carries no information about .

A Concrete Computation

Let's trace through Schnorr's protocol with actual numbers, then see how a simulator fakes a transcript.

Work in (order 10) with generator . The prover knows , and the public value is .

Real transcript: The prover samples , computes , and sends it. The verifier sends challenge . The prover computes (note: we reduce modulo the group order 10, not the prime 11). Verification: and . Both sides match.

The transcript is .

Simulated transcript: A simulator who doesn't know picks and (both uniform), then computes (since ). The simulated transcript is , identical to the real one. The simulator produced a valid proof without knowing . This is HVZK in action: the transcript carries no information about the witness.

Pedersen Commitments and -Protocols

Schnorr's protocol proves knowledge of a single discrete log. But in practice, we often need to prove knowledge of values hidden inside commitments. Chapter 6 introduced Pedersen commitments: commits to message with blinding factor , where are generators with unknown discrete log relation. -protocols let us go further and prove things about committed values.

This is not a coincidence. Schnorr's protocol and Pedersen commitments are algebraically the same construction. In Schnorr, the prover commits to and later reveals (a linear combination of the randomness and the secret). In Pedersen, the committer computes (a linear combination of two generators weighted by the message and randomness). Both rely on the same hardness assumption; both achieve the same hiding property.

Recall from Chapter 6: a Pedersen commitment is perfectly hiding (reveals nothing about ) and computationally binding (opening to a different value requires solving discrete log). The additive homomorphism lets us compute on committed values.

What Chapter 6 couldn't address: how does a prover demonstrate they know the opening without revealing it? This is precisely what -protocols provide.

Proving Knowledge of Openings

Schnorr handles one exponent; Pedersen commitments involve two: . To prove knowledge of the opening , we need the two-dimensional generalization. The structure mirrors Schnorr exactly (commit, challenge, respond) but now with two secrets handled in parallel:

  1. Commitment. Prover samples and sends .

  2. Challenge. Verifier sends random .

  3. Response. Prover sends and .

  4. Verification. Check .

This is just two Schnorr protocols glued together. One proves knowledge of the message part (, committed via ), the other proves knowledge of the randomness part (, committed via ). The same challenge binds them, ensuring the prover cannot mix-and-match unrelated values.

The analysis parallels Schnorr's protocol:

Completeness.

Special soundness. Two transcripts with the same but different challenges yield: From which both and can be extracted.

HVZK. Simulator picks uniformly, sets .

The prover demonstrates knowledge of the commitment opening without revealing what that opening is.

Proving Relations on Committed Values

The homomorphic property enables proving statements about committed values without revealing them.

For addition, given commitments , we can prove that the committed values satisfy .

Consider the product . Expanding the Pedersen structure:

If the relation holds, the exponent vanishes:

The combined commitment collapses to a pure power of . To prove the relation holds, the prover demonstrates knowledge of this exponent (a single Schnorr proof with base and public element ).

Multiplication is harder. Pedersen commitments aren't multiplicatively homomorphic. Given , , , how do we prove ?

The key insight is to change bases. Observe that:

If , then can also be viewed as:

Now substitute :

This expresses as a "Pedersen commitment with base " to the value with blinding factor .

The prover runs three parallel -protocols:

  1. Prove knowledge of opening (standard Pedersen opening)
  2. Prove knowledge of opening (standard Pedersen opening)
  3. Prove knowledge of opening with respect to bases

The third proof links to the second because the same appears. This linking requires careful protocol design, but the core technique is -protocol composition with shared secrets.

Fiat-Shamir: From Interaction to Non-Interaction

Interactive proofs are impractical for many applications. A signature scheme cannot require real-time communication with every verifier. A blockchain proof must be verifiable by anyone, at any time, without the prover present.

The Fiat-Shamir transform removes interaction. The idea is elegant: replace the verifier's random challenge with a hash of the transcript.

In Schnorr's protocol:

  1. Prover computes
  2. Instead of waiting for verifier's , prover computes (or for domain separation)
  3. Prover computes
  4. Proof is

Verification:

  1. Recompute
  2. Check

The transform works because is modeled as a random oracle: a function that returns uniformly random output for each new input. The prover cannot predict before choosing . Once is fixed, the hash determines deterministically. The prover faces a random challenge, just as in the interactive version.

In practice, is a cryptographic hash function like SHA-256. The random oracle model is an idealization (hash functions aren't truly random functions) but the heuristic is empirically robust for well-designed protocols.

Schnorr signatures are the direct application. Given secret key and public key :

  • Sign message : Compute , , . Signature is .
  • Verify: Check where .

Schnorr patented his signature scheme in 1989 (U.S. Patent 4,995,082). NIST needed a standard and designed DSA, later ECDSA, specifically to work around the patent. The result was a signing equation that includes a modular inversion . This non-linearity is the algebraic cost of the workaround: you cannot simply add ECDSA signatures, because the inverses don't combine.

The patent expired in 2008, and Schnorr signatures finally entered widespread use as EdDSA (Ed25519), now standard in TLS, SSH, and cryptocurrency systems. Bitcoin launched in 2009, but ECDSA was already the entrenched standard, so Satoshi used it. Ethereum launched in 2015 with ECDSA as well: audited Schnorr implementations on secp256k1 simply did not exist yet, and Ethereum still uses ECDSA today. It took until the 2021 Taproot upgrade for Bitcoin to adopt Schnorr. The linearity of enables what ECDSA cannot:

  • Batch verification: check many signatures faster than individually by taking random linear combinations (Schwartz-Zippel ensures invalid signatures can't cancel)
  • Native aggregation: multiple signers can combine signatures into one. MuSig2 produces a single 64-byte signature for parties that verifies against an aggregate public key
  • ZK-friendliness: no modular inversions, so Schnorr verification is cheap inside arithmetic circuits

Composition: AND and OR

-protocols compose cleanly, enabling proofs of complex statements from simple building blocks.

For AND composition, to prove "I know such that AND such that ":

  1. Run both protocols in parallel with independent commitments
  2. Use the same challenge for both
  3. Check both verification equations

If the prover knows both witnesses, they can respond to any challenge. If they lack either witness, they can't respond correctly.

OR composition is more subtle. To prove "I know OR " (without revealing which):

  1. For the witness you don't know, simulate a transcript (using the honest-verifier simulator from the zero-knowledge property)
  2. For the witness you do know, commit honestly to
  3. When you receive the verifier's challenge , set
  4. Respond honestly to using your witness

The verifier checks:

  • Both verification equations hold

As an example, suppose Alice knows the discrete log of but not . She wants to prove she knows at least one of them.

  1. Simulate the unknown: Alice picks and at random, then computes . This is a valid-looking transcript for .

  2. Commit honestly for the known: Alice picks and computes . She sends to the verifier.

  3. Split the challenge: The verifier sends . Alice sets .

  4. Respond honestly: Alice computes and sends .

The verifier checks and , plus . Both equations hold. The verifier cannot tell which transcript was simulated; the simulated is statistically identical to an honest execution.

You can prove you know one of two secrets without revealing which. Ring signatures, anonymous credentials, and many privacy-preserving constructions build on this technique.

Key Takeaways

  1. Three messages suffice for zero-knowledge proofs of knowledge. Commit → Challenge → Response. The commitment must come before the challenge; reversing this order destroys soundness.

  2. Special soundness: two accepting transcripts with different challenges enable witness extraction. This makes -protocols proofs of knowledge, not merely proofs of existence.

  3. Zero-knowledge via simulation: pick the challenge and response first, compute what the commitment must have been. If a transcript can be faked without the secret, the transcript carries no information about the secret.

  4. Schnorr is the archetype. Every -protocol in this chapter is a variation on : Pedersen openings run two Schnorr proofs in parallel, relations on committed values reduce to Schnorr proofs after algebraic simplification.

  5. Fiat-Shamir removes interaction by hashing the commitment to derive the challenge. This yields Schnorr signatures and non-interactive proofs.

  6. Composition builds complex proofs from simple ones. AND runs protocols in parallel with a shared challenge. OR uses simulation for the unknown witness; the verifier cannot tell which branch is real.

  7. Minimal assumptions: -protocols require only the discrete logarithm assumption. No pairings, no trusted setup, no hash functions beyond Fiat-Shamir.

Chapter 17: The Zero-Knowledge Property

In 1982, Shafi Goldwasser and Silvio Micali submitted a paper to STOC proposing that a proof could convince a verifier of a statement's truth while revealing nothing beyond that single bit: true or false. The program committee rejected it. The concept seemed contradictory. A proof, by its nature, is a demonstration: it convinces by showing. How could showing suffice for conviction while simultaneously revealing nothing?

They persisted. The paper, expanded with Charles Rackoff, was published in 1985 as "The Knowledge Complexity of Interactive Proof Systems." It won the Gödel Prize in 1993. Goldwasser and Micali received the Turing Award in 2012. The reviewers' skepticism was not foolish; it reflected a genuine conceptual difficulty that the paper resolved.

Their resolution was a definition so clean it still underlies every modern proof system. A proof reveals nothing if everything the verifier sees could have been produced by a simulator who knows nothing about the secret. If real and simulated transcripts are indistinguishable, the real one carries no information about the witness. This immediately raises the question the rest of the chapter answers: if a fake transcript looks identical to a real one, what did the prover actually contribute?

The definition is deceptively simple. The consequences are not. Simulation is not a property that protocols possess by default. The sum-check protocol, central to this book, leaks witness data through its round polynomials: no simulator can fake them without the witness, because the protocol was never designed to allow it. Understanding when simulation is possible, what makes it possible, and what flavors of indistinguishability suffice is the work of this chapter.

The Simulation Argument

We have already seen a simulator in action. Chapter 16 constructed one for the Schnorr protocol: to produce a valid transcript without knowing the witness , pick and first (both uniformly random), then compute . The transcript satisfies the verification equation by construction, and its distribution matches a real transcript exactly.

What does this buy us? Recall that the witness is the prover's secret (a private key, a satisfying assignment, the preimage of a hash), and the transcript is the full sequence of messages exchanged during the protocol. If someone who never touched can produce transcripts identical to a real prover's, then the transcript itself cannot encode anything about , even though the real prover used to compute it. The computation depends on the witness; the distribution does not. The verifier, holding only the transcript, learns nothing. This reasoning generalizes beyond Schnorr. A proof system is zero-knowledge if a simulator (an efficient algorithm with no access to the witness) can produce transcripts indistinguishable from real protocol executions. This is the simulation paradigm. In short: a simulator is a machine that takes only the public statement (everything except the witness), generates challenges on behalf of the verifier, fabricates prover responses, and outputs a complete transcript whose distribution is indistinguishable from a real execution.

To make the argument precise: suppose the verifier could extract some information about the witness from a real transcript. The simulator doesn't know the witness, so its transcript cannot possibly encode . But the two transcripts are indistinguishable, so any extraction procedure that works on real transcripts must also work on simulated ones, yet simulated ones contain no witness information to extract. The assumption that is extractable leads to a contradiction. Real transcripts don't leak . The proof is convincing precisely because it could have been fabricated, and this indistinguishability is not a flaw to be patched; it is the definition of success.

The Graph Non-Isomorphism Protocol

We claimed the simulation paradigm is general. To build conviction, let's see it work in a protocol with entirely different structure: no group elements, no algebraic equations, just graphs and permutations. The Graph Non-Isomorphism protocol from Chapter 1 makes the mechanics of simulation visible without any algebraic machinery to hide behind.

Both parties see two graphs and (the public statement). The prover claims they are non-isomorphic: no permutation of the vertex set satisfies . Graph Isomorphism is in NP (the permutation itself is the witness), but Graph Non-Isomorphism is not known to be in NP. There is no obvious short certificate for the absence of an isomorphism, and the verifier cannot efficiently check the claim on her own. This is precisely what makes GNI a natural candidate for interactive proofs.

The protocol works as follows. The verifier picks a secret bit , applies a random permutation to , and sends to the prover. The prover must identify which graph came from. If the graphs are truly non-isomorphic, they have different structural fingerprints (spectrum, degree sequence, triangle counts), so an unbounded prover can determine with certainty and sends back . The key observation: if the graphs were isomorphic, and would be identically distributed, and no prover could do better than guessing correctly with probability . Repeating times drives the soundness error to . Success at this task therefore proves non-isomorphism.

Now consider what the verifier actually sees after a successful execution:

  • The challenge that she generated herself
  • The bit that matches her secret

But was her own random choice. was her own computation. The prover's response just echoes her own randomness back. The transcript contains nothing the verifier didn't already know.

A simulator can exploit this. Given only the graphs (not the prover's ability to distinguish them), it plays both sides of the conversation:

  1. Pick uniformly at random (playing the verifier)
  2. Pick uniformly from permutations of the vertex set (playing the verifier)
  3. Compute (playing the verifier)
  4. Output the transcript (playing the prover, using the it already chose)

The simulator does not need to distinguish the graphs. It knows because it generated itself. A real cheating prover, facing a live verifier, would have to guess from alone (and could do no better than ). The simulator sidesteps this entirely by controlling both sides. The resulting distribution over is identical to what an honest verifier would see in a real execution. The simulated and real distributions are not merely close; they are identical. This is perfect zero-knowledge: the statistical distance between real and simulated transcripts is exactly zero.

Simulation and Polynomial Commitments

The algebraic protocols that dominate the rest of this book share a structure that GNI lacks: a commit → challenge → respond sequence. A real prover commits to a polynomial , receives a verifier-chosen evaluation point , and responds with . The simulator, just as in Schnorr, reverses this: it picks and first, then constructs a commitment consistent with these choices. The commitment "could have been" to any polynomial that passes through , because a single evaluation does not determine a polynomial. One pair in Schnorr is consistent with infinitely many secrets ; one evaluation is consistent with infinitely many polynomials. The simulator exploits this ambiguity. The real prover is bound by her earlier commitment; the simulator is free to work backward from the challenge.

This is why KZG requires the verifier to choose after the commitment, why FRI queries come after the oracle is fixed, and why Fiat-Shamir hashes the commitment before deriving challenges. The temporal ordering (commit → challenge → respond) is what makes the live proof convincing. The simulator's freedom from that ordering is what makes the transcript uninformative.

Formal Definition

Let be an interactive proof system for a language (recall from Chapter 1: a set of yes-instances for some decision problem). On input , the prover holds a witness ; the verifier sees only .

The verifier's view consists of:

  1. The statement
  2. The verifier's random coins
  3. All messages received from the prover

We write for this random variable.

Definition (Zero-Knowledge). The proof system is zero-knowledge if there exists a probabilistic polynomial-time algorithm (the simulator) such that for all :

The symbol denotes indistinguishability; its precise meaning yields three flavors.

Three Flavors of Zero-Knowledge

Perfect zero-knowledge (PZK). The distributions are identical:

No adversary, even with unlimited computational power, can distinguish real from simulated transcripts. The two distributions have zero statistical distance.

This is the strongest notion. The Schnorr protocol (Chapter 16) achieves PZK against honest verifiers: the simulator's output has exactly the same distribution as a real transcript.

Statistical zero-knowledge (SZK). The distributions are statistically close:

where the statistical distance (or total variation distance) between distributions and is defined as:

This is the maximum advantage any distinguisher (even computationally unbounded) can achieve. An unbounded adversary might distinguish the distributions, but only with probability (effectively never).

SZK allows for protocols where perfect simulation is impossible but the gap is cryptographically small. To see how this arises in practice, return to Schnorr. The simulator picks uniformly from and achieves a perfect match. But suppose the implementation samples uniformly from instead of (a common shortcut when ). The real transcript samples from , so is uniform over ; the simulated transcript has uniform over a slightly larger range. The statistical distance is on the order of , which is negligible when is close to . No unbounded adversary can distinguish with non-negligible advantage, but the distributions are no longer identical. This is SZK, not PZK.

Computational zero-knowledge (CZK). No efficient algorithm can distinguish the distributions:

The distributions might be statistically far apart, but every polynomial-time distinguisher's advantage is negligible. Security relies on computational hardness; an unbounded adversary could distinguish.

CZK is the weakest but most practical notion. Modern SNARKs typically achieve CZK. The simulator might use pseudorandom values where the real protocol uses true randomness; distinguishing requires breaking the underlying assumption.

Honest Verifiers and Malicious Verifiers

The definition above assumes the verifier follows the protocol honestly. What if she doesn't?

Honest-verifier zero-knowledge (HVZK). Zero-knowledge is guaranteed only when the verifier follows the protocol as specified, hence "honest." The simulator can hardcode this known strategy into its construction. For Schnorr, the honest verifier samples uniformly and independently of , and the simulator exploits exactly this: it picks first, then derives . The independence of from is what makes the reversal work. If the verifier instead chose for some function , the simulator would need to find such that equals the it already committed to, which it generally cannot do.

Malicious-verifier zero-knowledge. The simulator must produce indistinguishable output against any efficient verifier strategy , including:

  • Adversarial challenge selection
  • Auxiliary information from other sources
  • Arbitrary protocol deviations

To see what malicious verification looks like concretely, consider the Graph Non-Isomorphism protocol again. An honest verifier sends for her secret . But a malicious verifier could send some other graph (perhaps one she suspects is isomorphic to but isn't sure). The all-powerful prover will correctly identify whether matches , , or neither. The verifier learns something she couldn't efficiently compute herself!

The protocol is HVZK but not malicious-verifier ZK. The prover, dutifully answering whatever question is posed, inadvertently becomes an oracle for graph isomorphism.

Closing this gap requires additional machinery:

  • Coin-flipping protocols force the verifier to commit to her randomness before seeing the prover's messages. The verifier's challenges become unpredictable even to her.
  • Trapdoor commitments let the simulator "equivocate": commit to one value, then open to another after seeing the verifier's behavior.
  • The Fiat-Shamir transform eliminates interaction entirely. With no verifier messages, there's no room for malicious behavior. The simulator controls the random oracle and programs it as needed.

Non-interactive proofs (after Fiat-Shamir) largely dissolve the HVZK/malicious distinction. The "verifier" merely checks a static proof string.

For malicious verifiers in interactive protocols, the simulator often needs a stronger technique: rewinding. Rather than constructing the transcript in one shot (as Schnorr's simulator does), it runs the verifier multiple times, replaying from an earlier state with fresh randomness until it finds a challenge it can handle. Rewinding is a proof technique, not a real capability: it shows that the transcript could have been generated without the witness, even though no real prover could rewind a live verifier.

This brings us back to the question posed in the introduction: if a simulator can produce valid transcripts without the witness, what did the prover actually contribute? The answer is not data but compliance: she demonstrates she can respond correctly to challenges she could not have predicted. That is soundness. Zero-knowledge is the other side of the same coin: the simulator's success shows that the static transcript, stripped of temporal ordering, contains no extractable information about the witness. The two properties coexist because they concern different things. Soundness is about the live process (commit, then challenge, then respond). Zero-knowledge is about the information content of the record. The simulator can fake the record precisely because it is free from the ordering that makes the process convincing.

The Limits of Zero-Knowledge

Perfect and statistical zero-knowledge seem strictly stronger than computational. Are they always preferable?

No. There are limits.

Theorem (Fortnow, Aiello-Håstad). Any language with a statistical zero-knowledge proof lies in .

The class (Arthur-Merlin) consists of languages decidable by a two-move interactive proof in which the verifier's coins are public: the verifier sends a random string, the prover responds, and the verifier decides deterministically. Unlike IP, where the verifier's randomness is private, AM exposes it to the prover. The class contains languages whose complements are in AM. Graph Non-Isomorphism, the protocol we studied earlier, is a natural example of a problem in .

The intersection is believed to be much smaller than NP. Under standard complexity-theoretic conjectures, it contains no NP-complete problems. The implication is stark: statistical zero-knowledge proofs for NP-complete problems likely do not exist.

The intuition is that statistical zero-knowledge is too good at hiding. If a simulator can reproduce the verifier's view without the witness, and no unbounded distinguisher can tell the difference, then the proof isn't leveraging the witness in any meaningful way. An all-powerful observer could use the simulator itself to decide membership: simulate the transcript, check if the distribution is close to what a real execution would produce, and conclude whether . This effectively places both and its complement in AM. For NP-hard problems, where the witness should be "hard to avoid using," this is too much to ask.

The way forward is to relax both soundness and zero-knowledge:

  • Computational soundness (arguments): Security against cheating provers who are computationally bounded.
  • Computational zero-knowledge: Security against distinguishers who are computationally bounded.

Modern SNARKs take both paths. They are arguments (computationally sound) with computational zero-knowledge. This combination enables practical ZK proofs for arbitrary computations, including NP-complete problems and beyond.

Witness Indistinguishability

Sometimes, full zero-knowledge is too expensive or impossible to achieve. A weaker but often sufficient property is Witness Indistinguishability (WI). This guarantees that if there are multiple valid witnesses (e.g., two different private keys that both sign the same message, or two different paths through a maze), the verifier cannot tell which one the prover used.

WI doesn't promise that the verifier learns nothing; it only promises they can't distinguish which witness was used. For many privacy applications (anonymous credentials, ring signatures), WI suffices and is easier to achieve than full ZK.

Zero-Knowledge in the Wild: Sum-Check

Let's ground this in the core protocol of the book. The sum-check protocol proves:

In each round, the prover sends a univariate polynomial , the restriction of to a partial evaluation. The verifier checks degree bounds and eventually evaluates at a random point.

Is sum-check zero-knowledge? Not inherently. The univariate polynomials reveal partial information about . If encodes secret witness data, this information leaks. For applications where is derived from public inputs (verifiable computation on public data), this leakage is harmless. For private-witness applications, we need modifications.

Several masking techniques (developed in Chapter 18) add zero-knowledge to sum-check:

  • Add random low-degree polynomials that cancel in the sum
  • Commit to intermediate values instead of revealing them
  • Use randomization to hide the structure of

The key insight: zero-knowledge is a system-level property, not a per-protocol property. We can compose non-ZK building blocks (sum-check, FRI, polynomial commitments) into ZK systems by carefully controlling what the verifier sees.

Zero-knowledge vs. knowledge soundness

This chapter has focused on what the verifier learns. An orthogonal question is what the prover demonstrates. A proof system can be zero-knowledge without being a proof of knowledge (GNI proves membership in a language but extracts no witness), and a proof of knowledge without being zero-knowledge (the prover could send the witness in the clear). These are independent axes: zero-knowledge constrains the verifier's view, knowledge soundness (Chapter 16) constrains the prover's ability to cheat without knowing a witness. Practical SNARKs target both, but they are achieved by separate mechanisms: simulation for the former, extraction for the latter.

Auxiliary Input

If zero-knowledge is a system-level property achieved by composing building blocks, we need a definition that survives composition. The standard simulation definition assumes the verifier starts with only the public statement. But when a ZK proof runs as a subroutine in a larger protocol, the verifier may carry information from earlier stages: an IP address, previous proofs, partial knowledge of the secret from another source. A secure ZK protocol must ensure that even with this extra context, the proof leaks nothing new.

Definition (Auxiliary-Input ZK). A protocol is auxiliary-input zero-knowledge if for every efficient verifier with auxiliary input :

The simulator receives the same auxiliary input as the verifier. The key requirement: whatever the verifier knew beforehand, the proof adds nothing to it.

This definition handles composed protocols. Even if the verifier has side information about the statement or witness, the proof reveals nothing new. The simulator, given the same side information, produces indistinguishable transcripts.

Auxiliary-input ZK is necessary for security in complex systems where many proofs interleave.

Key Takeaways

  1. Zero-knowledge means existence of a simulator: an efficient algorithm that, without the witness, produces transcripts indistinguishable from real executions. If the transcript could have been fabricated, it carries no information about the witness.

  2. Three flavors: Perfect (identical distributions), Statistical (negligible statistical distance), Computational (no efficient distinguisher). Modern SNARKs typically achieve computational ZK.

  3. HVZK vs. malicious-verifier ZK: HVZK assumes the verifier follows the protocol; malicious-verifier ZK protects against arbitrary verifier strategies. Non-interactive proofs (post Fiat-Shamir) largely collapse this distinction.

  4. Simulation does not break soundness. The simulator works offline, fabricating transcripts of true statements. A cheating prover faces a live verifier on false statements. Rewinding (the simulator's key technique) is a proof method, not a real capability.

  5. Limits of SZK: Statistical zero-knowledge proofs exist only for languages in , likely excluding NP-complete problems. Computational ZK, paired with computational soundness, sidesteps this barrier.

  6. Sum-check is not inherently ZK: The round polynomials leak witness information. Masking techniques (Chapter 18) restore privacy. Zero-knowledge is a system-level property, not a per-protocol property.

  7. Auxiliary-input ZK ensures security under composition: even when the verifier carries side information from other protocol stages, the proof leaks nothing new.

Chapter 18: Making Proofs Zero-Knowledge

A conventional proof convinces by exposing its internals. The verifier sees intermediate values, checks each step, and traces the chain of reasoning from hypothesis to conclusion. Conviction comes from transparency: every piece of the argument is laid bare for inspection.

Zero-knowledge proofs must convince without this transparency. The verifier still receives messages, checks relationships, and follows a protocol. But the values she sees are randomized so that they carry no information about the witness. She inspects a full transcript and becomes convinced the statement is true, yet the transcript could have come from any valid witness, or from no witness at all (a simulator). The challenge is preserving the structure that makes verification work while destroying the information that would make the witness recoverable.

This requires care.

Most proof systems were not designed with privacy in mind. The interactive proofs of the 1980s and 1990s were built to make verification cheap: a weak verifier checking claims from a powerful prover. The sum-check protocol, GKR, and the algebraic machinery underlying modern SNARKs all emerged from complexity theory, where the goal was efficient verification, not confidential computation. Privacy became necessary only later, as these tools migrated from theory to practice and applications like blockchain transactions, private credentials, and anonymous voting demanded that proofs reveal nothing beyond validity. The result is a retrofit problem: taking elegant machinery built for transparency and making it opaque.

We saw zero-knowledge informally in -protocols (Chapter 16), then formally in Chapter 17. We have also seen it applied in specific systems: the random scalars in Groth16 (Chapter 12), the blinding polynomials in PLONK (Chapter 13). Strip these additions and the proof systems still work, they are still sound and succinct, but they leak witness information. This chapter develops the general theory behind those additions. How do we take a working proof system and add the layer that makes it reveal nothing?

The chapter develops two general techniques, then shows how specific proof systems apply them.

Commit-and-prove works for any protocol: hide every witness-dependent value behind a commitment, then prove the required relations via -protocols. This is general but expensive, with cost proportional to the number of multiplications.

Masking polynomials applies specifically to protocols where the prover sends polynomials (notably sum-check): add random noise that preserves validity while hiding the witness. This is efficient but requires algebraic structure.

Neither technique is used in isolation by production systems. Groth16 and PLONK each implement their own variants, tailored to their algebraic structure. After developing the general theory, we examine how these systems achieve zero-knowledge in practice.

The Leakage Problem

Let's be concrete about what leaks. Consider the sum-check protocol proving:

When encodes private witness values, the verifier should not learn beyond what is necessary for verification. In a proper ZK protocol, the verifier would only learn at a single random point at the end (via a commitment opening), not the polynomial itself. But sum-check requires the prover to send intermediate polynomials.

In round , the prover sends a univariate polynomial representing the partial sum with variable free:

This polynomial depends on . Its coefficients encode information about the witness.

To see this concretely, suppose encodes a computation with secret witness values :

The verifier does not know this polynomial; they only know they are verifying a sum. The first round polynomial is:

The prover sends this polynomial to the verifier. The constant term is exactly . The coefficient of is . The verifier learns linear combinations of the secrets directly from the protocol message.

Consider what these witness values could represent. Suppose you are proving eligibility for a loan without revealing your finances. Your witness might encode: = your salary, = your social security number, = your total debt. The computation verifies that your debt-to-income ratio meets some threshold. From that single round polynomial, the verifier learns your SSN directly (the constant term) and a linear combination of your salary and debt. They did not need to learn any of this to verify your eligibility. The protocol leaked it anyway.

This isn't zero-knowledge. We need to hide these coefficients while still allowing verification.

Technique 1: Commit-and-Prove

The commit-and-prove approach is conceptually simple: never send a value in the clear. Always send a commitment, then prove the committed values satisfy the required relations.

The Paradigm

For any public-coin protocol that sends witness-dependent values (public-coin means the verifier's messages are random and visible to both parties, which is the case for sum-check, GKR, and all Fiat-Shamir-compiled protocols):

  1. Replace values with commitments. Instead of sending , send (a Pedersen commitment with random blinding ).

  2. Prove relations in zero-knowledge. For each algebraic relation the original protocol checks (e.g., "this value equals that value," "this is the product of those two"), run a -protocol on the committed values.

The verifier never sees actual values. They see commitments (opaque group elements that reveal nothing about the committed data). The -protocols convince them the data satisfies the required structure.

Pedersen's Homomorphism as Leverage

Recall from Chapter 6 that Pedersen commitments () are perfectly hiding (the commitment reveals nothing about , even to an unbounded adversary) and additively homomorphic (). This means the verifier can check additive relations on committed values for free, without any interaction or -protocol: given , verify by checking .

Multiplication is more expensive. Checking on committed values requires a -protocol that proves the multiplicative relation without revealing the values. This takes three group elements and three field elements per multiplication gate.

Applying to Circuits

Since arithmetic circuits consist entirely of addition and multiplication gates, the cost of commit-and-prove is determined by the multiplication count : the prover commits to every wire value, addition gates are verified for free via homomorphism, and each multiplication gate requires one -protocol (~3 group elements). The proof contains group elements (one -protocol transcript per multiplication gate), and verification requires group exponentiations, each costing field multiplications for security parameter .

This is not succinct. A circuit with a million multiplications produces a proof with millions of group elements. But it achieves perfect zero-knowledge: the simulator can produce indistinguishable transcripts by simulating each -protocol independently.

Recovering Succinctness: Proof on a Proof

Commit-and-prove costs per multiplication gate, so it is impractical for large circuits. But it does not need to be applied to the original circuit. The idea is to split the work into two layers:

  1. First layer (not zero-knowledge). Run an efficient interactive proof, such as GKR (Chapter 7), on the original circuit . GKR is sound and succinct: the verifier's work is polylogarithmic in . The protocol produces a transcript consisting of prover messages, verifier challenges, and a final evaluation claim. This transcript is not zero-knowledge; the prover's messages leak witness information.

  2. Second layer (zero-knowledge). The GKR verifier is itself a computation: given , check consistency and output accept or reject. Express this verification as a small circuit of size . Now apply commit-and-prove to : the prover commits to all transcript values (which include the witness-derived quantities), then proves via -protocols that these commitments would make accept.

This is the "proof on a proof": the first layer proves correctness (via GKR), the second layer proves that the first layer's transcript is valid without revealing it (via commit-and-prove on the small verifier circuit). The cost of the second layer depends on 's multiplication count, which is polylogarithmic in , not on itself.

The key detail is the structure of . Recall from Chapter 7 that GKR verification consists mostly of sum-check consistency checks (pure additions, which Pedersen homomorphism handles for free). The only multiplications arise at layer boundaries, where the verifier checks an equation involving the product of two sub-circuit evaluations: one multiplication per layer. A circuit of depth thus requires only -protocols in the second layer, not the that direct commit-and-prove on the original circuit would require.

The verifier sees the public inputs and outputs (part of the statement), Pedersen commitments to all transcript values, and -protocol proofs that the committed values satisfy GKR verification. The witness is still encoded in the transcript coefficients (the chain is ), but the commitments are perfectly hiding. The -protocols prove only structural facts about these coefficients (that they satisfy certain arithmetic relations), never semantic facts (what they represent in the original computation). Every valid witness producing the same output yields commitments with the same distribution, so the verifier cannot distinguish which was used.

Technique 2: Masking Polynomials

Commit-and-prove hides values behind commitments and proves relations one at a time. This is general but expensive: the cost scales with the number of multiplications. For polynomial-based protocols like sum-check, a lighter approach exists: instead of hiding each value individually, randomize the polynomial itself so that the values the verifier sees carry no information about the witness.

The Core Idea

Whenever a protocol requires the prover to send a polynomial derived from the witness (as sum-check does with its round polynomials), the prover can mask it. Instead of sending directly, the prover sends:

where is a random polynomial (committed in advance) and is a random scalar from the verifier.

Since is random and is chosen after the commitment, acts like a one-time pad: the verifier sees but cannot extract without knowing .

The natural concern is soundness. The original protocol verified ; now the verifier sees instead. The masked sum is:

where is sent alongside the commitment to . The verifier checks . For a false claim , this requires , which implies . Masking is a soundness-preserving transformation: it changes the representation but not the truth value.

Constructing the Masking Polynomial

The masking polynomial must have the same degree structure as (otherwise would fail the verifier's degree checks), known aggregate sum (so the verifier can adjust the check), and genuinely random coefficients (so the masking actually hides ).

Protocol flow:

  1. Before the main protocol, the prover commits to a random masking polynomial and sends its sum .
  2. The verifier sends a random .
  3. The prover runs sum-check on , sending masked round polynomials.
  4. The verifier checks that round polynomials sum correctly to (the adjusted claim).

The verifier sees and knows with , but only has a commitment to , not itself. For any polynomial the verifier might guess for , there exists a consistent with the observed . This is the polynomial one-time pad: the random masking makes all witness polynomials equally plausible. In the multivariate case, the prover commits to a masking polynomial with the same structure as , and each sum-check round polynomial derived from is masked.

Masking the Final Evaluation

The round polynomials are now hidden, but there is a remaining leak. At the end of sum-check, the prover must open at the random evaluation point (typically via a polynomial commitment). This final evaluation is a deterministic function of the witness and reveals information about it.

To handle this, the prover adds random terms that vanish on the Boolean hypercube but randomize evaluations outside it. Instead of committing to directly, the prover commits to a randomized version:

where are random field elements chosen by the prover and never revealed. Since when , we have on the Boolean hypercube: correctness is preserved, and the sum is unchanged. But at a random point (where the verifier queries after sum-check), the evaluation becomes . The verifier sees via the commitment opening but does not know the , so it cannot recover .

Worked example. Let (a single-variable polynomial encoding witness data). Randomize with :

On the hypercube: and . At a random point : (would leak), but (masked). Different values produce different evaluations at , hiding .

For an -variable polynomial, the random scalars provide enough entropy: the verifier learns one evaluation , which for uniform is uniformly distributed over regardless of . A simulator who does not know can produce identically distributed evaluations by choosing random .

The Simulator

Chapter 17 defined zero-knowledge via simulation: a proof system is ZK if an efficient simulator, given only the public statement, can produce transcripts indistinguishable from real executions. Chapter 17 also observed that vanilla sum-check fails this test: the round polynomials are deterministic functions of the witness, and no simulator can produce them without it. Masking repairs this. To close the loop, we construct the simulator explicitly and verify indistinguishability.

Real protocol:

  1. Prover commits to random masking polynomial
  2. Verifier sends random
  3. Parties execute sum-check on
  4. Prover opens and at random point

Simulator (no access to the witness, and therefore no access to ):

  1. Commit to random polynomial
  2. Choose a random polynomial of the same degree as
  3. Execute sum-check on
  4. Open and

The simulator replaces with a random . The question is whether the verifier can tell the difference.

The verifier's view in both cases consists of: a commitment to , sum-check round messages derived from or , the scalar , and the final evaluations. The commitment to is a random group element in both cases (Pedersen hiding). The round messages are derived from (real) or (simulated). Since is uniformly random and independent of , and is chosen after is committed, the polynomial is uniformly distributed among polynomials of its degree. Adding a uniform polynomial to any fixed produces a uniform result, just as adding a uniform field element to any fixed value produces a uniform field element. The distribution of depends only on the randomness in and , not on . The same holds for . The two distributions are identical.

For the final evaluation: the verifier learns and (real) or and (simulated). Since is uniformly random, is uniform over . The masking of the final evaluation (via the terms from the previous section) ensures that is also uniformly distributed from the verifier's perspective. Both views are identically distributed, completing the simulation argument.

Comparing the General Techniques

AspectCommit-and-ProveMasking Polynomials
GeneralityWorks for any public-coin protocolSpecialized for polynomial protocols
Overhead -protocols for multiplications additional commitments
SuccinctnessRequires "proof on a proof"Naturally preserves succinctness
Post-quantumNo (relies on discrete log)Yes (with hash-based PCS)
ComplexityConceptually straightforwardRequires algebraic design

The cost difference reflects a difference in abstraction level. Commit-and-prove works on scalars: each field element gets its own commitment, and relations are proved one at a time. Masking polynomials works on functions: a single random polynomial masks all coefficients at once. Hiding scalars requires commitments; hiding an -coefficient polynomial requires one random polynomial. The jump from scalar to function is what makes masking efficient for polynomial-based protocols.

Most production systems use masking for the main protocol body and commit-and-prove for auxiliary statements (range proofs, committed value equality, etc.).

A third approach, developed in the HyperNova paper (Kothapalli, Setty, Tzialla, 2023), sidesteps both techniques entirely. Instead of masking round polynomials or wrapping each check in a -protocol, the prover replaces every sum-check message with a Pedersen commitment and then proves, via Nova folding, that the committed values satisfy the verifier's checks. The folding step acts as an algebraic one-time pad: the real witness is combined with a random satisfying instance, producing a folded witness that is uniformly distributed regardless of the original. The cost is roughly 3 KB of additional proof data and negligible prover overhead. This technique, called BlindFold, is what made production zkVMs (notably Jolt) genuinely zero-knowledge. Chapter 22 develops it in full after introducing the folding machinery it depends on.

Zero-Knowledge in Practice

The general techniques above provide the conceptual foundation, but production systems do not apply them directly. Groth16 and PLONK each exploit their own algebraic structure to achieve zero-knowledge more efficiently. The underlying principle is the same (randomize what the verifier sees while preserving what the verifier checks), but the mechanisms are system-specific.

Groth16

Recall from Chapter 12 that the prover constructs QAP polynomials encoding the witness, evaluates them at the secret point from the structured reference string, and packages the results as three group elements . The polynomials never leave the prover; the verifier sees only these group elements. Without additional randomization, however, the proof elements are deterministic functions of the witness: same witness, same proof. An observer comparing two proofs could detect whether they use the same witness.

Groth16 addresses this the same way Pedersen commitments hide a message: by adding randomness in the exponent. The prover samples fresh scalars and incorporates them into the proof elements, making uniformly distributed while preserving the pairing verification equation.

The Blinding Mechanism

Concretely, the prover samples and incorporates them:

The and terms add randomness. But where do they go? They'd break the verification equation unless compensated. The construction of absorbs them:

The terms , , , , and in exactly cancel the cross-terms that appear when expanding .

Why This Works

The verification equation checks:

Expanding with blinding:

The exponent becomes , which expands to include cross-terms: , , , , .

The construction is designed so that when paired with , it produces exactly these cross-terms (plus the core QAP check). Everything cancels except the QAP identity .

Different produce different valid proofs for the same witness, making proofs for distinct witnesses indistinguishable from proofs for the same witness with different randomness. Note that the blinding depends on : the prover computes as using the proving key, without knowing as a field element. The setup secret is required by the mechanism.

PLONK

PLONK's approach is closer to masking polynomials than to Groth16's element-level randomization. Recall from Chapter 13 that PLONK encodes constraints as polynomial identities that must hold on a multiplicative subgroup . The prover commits to witness polynomials whose values on encode the wire assignments. After Fiat-Shamir, the verifier queries these polynomials at a random point outside to check the constraints.

The separation between the constraint domain () and the query point () is what PLONK exploits for zero-knowledge. Unlike Groth16, which randomizes proof elements, PLONK randomizes the polynomials themselves before committing: add a random multiple of the vanishing polynomial , which is zero on . The committed polynomial agrees with the original where constraints are checked but is randomized where the verifier queries.

The Vanishing Polynomial Trick

Concretely, to blind a witness polynomial , add a random low-degree polynomial times :

where are random field elements.

On the constraint-check domain:

Why a polynomial, not just a scalar? The verifier queries at a random point , receiving . A single scalar would add the fixed value , which might not provide enough entropy depending on what else the verifier learns. Using ensures sufficient randomness for simulation arguments.

Blinding the Accumulator

PLONK's permutation argument uses an accumulator polynomial that tracks whether wire values are correctly copied. This polynomial also reveals structure.

The accumulator is checked at two points: and (the "shifted" evaluation). To mask both, use three random scalars:

The boundary condition and the recursive multiplicative relation are preserved on . Outside , both and are randomized.

The same blinding applies to every polynomial PLONK commits to, including the quotient polynomial (which is split into pieces for degree reasons, each blinded independently).

The Unifying Principle

Despite different mechanisms, Groth16 and PLONK follow the same pattern: find the null space of the verification procedure (transformations that don't affect the outcome) and inject randomness there. In Groth16, the null space is the set of shifts that cancel in the pairing. In PLONK, it is the space of multiples that vanish on the constraint domain. This connects directly to the simulation paradigm from Chapter 17: the simulator can produce valid-looking transcripts because it can choose randomness within this null space.

Key Takeaways

  1. Every ZK technique finds the null space of the verification procedure and injects randomness there. Transformations that don't affect the verification outcome are the prover's freedom. Soundness constrains what the prover can do; zero-knowledge exploits what the prover is free to randomize.

  2. Commit-and-prove is general but expensive. It works for any public-coin protocol by hiding values behind Pedersen commitments and proving relations via -protocols. Cost scales with multiplication count (), but the "proof on a proof" trick recovers succinctness by applying commit-and-prove to the verifier circuit instead of the original computation.

  3. Masking polynomials are efficient but specialized. For polynomial-sending protocols like sum-check, adding (a committed random polynomial scaled by a verifier challenge) acts as a one-time pad. Succinctness is preserved naturally. Final evaluations require separate treatment via terms like that vanish on the Boolean hypercube.

  4. Production systems tailor the null space to their algebraic structure.

    • Groth16: fresh scalars shift the proof elements; absorbs the cross-terms so the pairing equation is unchanged.
    • PLONK: random multiples of vanish on the constraint domain but randomize evaluations at the query point .
  5. Production systems blend approaches. Masking handles the core polynomial protocol. Commit-and-prove handles auxiliary statements (range proofs, committed value equality). BlindFold (Chapter 22) offers a third path via folding.

Chapter 19: Fast Sum-Check Proving

This chapter, along with Chapters 20 and 21, forms Part VI on prover optimization. These three chapters are optional on a first read: the rest of the book stands without them. They are essential for anyone designing or implementing a prover, and reward repeat reading once the foundations feel solid.

Most chapters in this book can be read with pencil and paper. This one assumes you've already internalized the sum-check protocol (Chapter 3) and multilinear extensions (Chapter 4), not as definitions to look up, but as tools you can wield. If those still feel foreign, consider this chapter a preview of where the road leads, and return when the foundations feel solid.

In 1992, the sum-check protocol solved the problem of succinct verification. Lund, Fortnow, Karloff, and Nisan had achieved something that sounds impossible: verifying a sum over terms while the verifier performs only work. Exponential compression in verification time. The foundation of succinct proofs.

Then, for three decades, almost nobody used it.

Why? Because everyone thought the prover was too slow. The total work across all rounds sums to (as Chapter 3 showed via the geometric series), but achieving this requires the prover to evaluate the partially-fixed polynomial efficiently at each round. Without a way to reuse work across rounds, each round's evaluations require going back to the original -entry table, inflating the cost to . For , that's over 30 billion operations per proof. Researchers chased other paths: PCPs, pairing-based SNARKs, trusted setups. Groth16 and PLONK took univariate polynomials, quotient-based constraints, FFT-driven arithmetic. Sum-check remained a theoretical marvel, admired in complexity circles but dismissed as impractical.

They were wrong.

It turned out that a simple algorithmic trick, available since the 90s but overlooked, made the prover linear time. With the right algorithms, sum-check proving runs in time, linear in the number of terms. For sparse sums where only terms are non-zero, prover time drops to . These are not approximations or heuristics; they're exact algorithms exploiting algebraic structure that was always present.

When this was rediscovered and popularized by Justin Thaler in the late 2010s, it triggered a revolution. The field realized it had been sitting on the "Holy Grail" of proof systems for three decades without noticing. This chapter explains the trick that woke up the giant, and then shows how it enables Spartan, the SNARK that proved sum-check alone suffices for practical zero-knowledge proofs. No univariate encodings. No pairing-based trusted setup. Just multilinear polynomials, sum-check, and a commitment scheme.

Why This Matters: The zkVM Motivation

These techniques find their most compelling application in zkVMs (zero-knowledge virtual machines): SNARKs that prove correct execution of arbitrary programs over an instruction set like RISC-V. A million CPU cycles at 50 constraints each yields 50 million constraints. At this scale, versus proving is the difference between minutes and seconds. Even the constant factor matters. Fast sum-check proving is what makes zkVMs practical.

The Prover's Apparent Problem

Let's examine the naive prover cost more carefully.

The sum-check protocol proves:

where is an -variate polynomial. The prover begins by sending the claimed sum (this is ). Then in round , the prover sends a univariate polynomial capturing the partial sum with left as a formal variable:

The polynomial is univariate in , with degree equal to the degree of in that variable. Call this degree . A degree- univariate is determined by evaluations, but the consistency check (where is the claim from the previous round) lets the verifier derive one evaluation for free, so the prover sends only values.

For simplicity, assume has individual degree in every variable (the common case in practice). Computing requires evaluating it at points, and each evaluation sums over terms of the form .

Here is the problem. In round 1, no variables have been fixed to challenges yet, so each term in the sum for has the form with all remaining coordinates Boolean. For , these are values of on the hypercube, which the prover computed before the protocol began. For (the non-Boolean evaluation points needed to determine ), the prover must interpolate, but only in the first variable. Round 1 is manageable. But from round 2 onward, the first variables are fixed to non-Boolean challenges . The values were never precomputed. Without a way to access them cheaply, the prover must recompute them from scratch each round by interpolating over the full Boolean evaluations. This costs per round, and over rounds the total is .

Notice, however, that round only sums over terms. The work should shrink by half each round, and the geometric series gives:

(using the geometric series identity with ). The bottleneck is not the number of terms but access: can the prover obtain the partially-fixed values for round without recomputing them from the original values each time? If not, each round costs regardless of how few terms it sums, and the total remains .

The Halving Trick

The answer to the access problem is a single identity from Chapter 4: multilinear folding. After each challenge , the prover can update a multilinear polynomial's table of Boolean evaluations in place, producing the restricted polynomial's table in half the space. No recomputation from scratch.

But folding applies to multilinear polynomials, and in the interesting sum-check instances has degree > 1 in each variable, so itself is not multilinear. The trick is that does not need to be multilinear as long as it decomposes into multilinear factors. If (or more generally a sum of products of MLEs), the prover folds each factor's table independently and recomputes 's values from the folded factors each round. This covers essentially all practical cases: GKR's layer reductions (Chapter 18) use products of MLEs with the equality polynomial, R1CS verification uses , and Spartan (later in this chapter) reduces to the same form.

We develop the algorithm for the simplest case: , a product of two multilinear polynomials over variables.

Since multilinear polynomials have degree at most 1 in each variable, their product has degree at most 2. So , and each round the prover sends two field elements (say and ); the verifier recovers from the consistency check.

The Multilinear Folding Identity

Recall from Chapter 4 the streaming evaluation identity: for any multilinear polynomial and field element ,

This is linear interpolation: restricted to is a line through and , given by . The identity evaluates that line at any . Chapter 4 used it with challenges in to evaluate MLEs in time. Here we also need non-Boolean points: setting gives , extrapolating the line beyond its defining points using only stored Boolean evaluations.

This fact enables folding: after receiving challenge , we can compute the restricted polynomial from the unrestricted polynomial in linear time.

The Algorithm

Initialization. Store all evaluations and for in arrays and .

Round 1. Compute three evaluations of :

For and , we read directly from the stored arrays. For , apply the folding identity with : , and similarly for .

Each evaluation sums over terms, so the three evaluations cost operations total. The prover sends two; the verifier recovers the third from .

Fold after round 1. Receive challenge . Create a new array of size , indexed by :

Discard the old array and rename . The array now stores the restricted polynomial evaluated on the -dimensional hypercube. Similarly fold .

Round (general). After folds, the arrays and have size , storing on the remaining Boolean hypercube. The array splits naturally into two halves of size : entries with and entries with . Then:

  • : the sum over the half
  • : the sum over the half
  • : apply the folding identity with to get , then sum the products

Each evaluation sums over terms, costing operations total. The prover sends two values, then folds and using challenge , halving the arrays to size .

The arrays shrink by half each round: . By round , the arrays are singletons and the protocol terminates.

Folding solves the access problem. After each challenge , the prover updates the arrays in place via the folding identity, producing exactly the partially-fixed values needed for round . No recomputation from scratch. Round costs for evaluation and for folding, with a constant field operations per entry for the product . The total is the geometric series from above:

This is optimal: any prover must read all inputs at least once.

Worked Example: The Halving Trick with

Let's trace through a complete example. Take variables and consider the sum-check claim:

Suppose the tables are:

Product
236
515
428
3412

The true sum is .

Round 1: Compute .

We need three evaluations to specify this degree-2 polynomial:

  • : First interpolate and at :

Verifier checks: .

Prover sends . Verifier sends challenge .

Fold after Round 1.

Update arrays using :

Similarly for :

Arrays now have size 2 (down from 4).

Round 2: Compute .

Verifier checks: .

This is the core consistency check of sum-check. The prover committed to before knowing the challenge . Now the verifier demands that (the next round's polynomial) sum to the value . If the prover lied about , the fabricated polynomial almost certainly evaluates incorrectly at the random point , and the check fails.

Compute from the degree-2 polynomial through points :

Using Lagrange interpolation:

At : .

Total operations: Round 1 touched 4 entries; Round 2 touched 2 entries. Total: 6 operations, not as naive analysis suggests. For larger , the savings compound: instead of .

Each round, the arrays halve in size. The total work across all rounds is the geometric series . This is optimal: any prover must read all inputs at least once.

Beyond Black-Box Arithmetic

The halving trick achieves field operations, which is optimal. For a textbook, the story could end here. But in practice, sum-check provers over 256-bit fields remain slow even at optimal operation count, because each field multiplication carries a different concrete cost depending on the size of its operands. The next three sections (this one, high-degree products, and small-value proving) progressively reduce the concrete cost by exploiting structure that asymptotic analysis ignores. All three build on the same observation: not all field multiplications are equal.

Not all field multiplications are equal. Over a 256-bit prime field (BN254, BLS12-381), multiplying two arbitrary field elements requires multi-limb integer arithmetic plus modular reduction. But when one operand fits in a single 64-bit machine word, the cost drops dramatically. Three classes emerge:

  • big-big (bb): two arbitrary field elements. Roughly 8x the cost of sb.
  • small-big (sb): one machine-word integer, one field element.
  • small-small (ss): two machine-word integers. A single native multiplication, roughly 30x cheaper than bb.

A further optimization, delayed reduction, avoids redundant modular reductions when accumulating a linear combination with small coefficients . Instead of reducing each product separately, the prover accumulates unreduced integer products and performs a single reduction at the end. This nearly halves the cost of sb-dominated loops, which is precisely the structure of the sum-check prover's inner loop.

Why does this matter for sum-check? In round 1, all evaluations lie on the Boolean hypercube. In a zkVM, these are witness values (register contents, memory entries), typically 32- or 64-bit integers stored in a 256-bit field. Round 1 uses only ss and sb operations. After the verifier sends challenge , subsequent rounds involve full-width random elements and require bb multiplications.

Round 1 is not a small fraction of the work. By the geometric series, round 1 accounts for half the total operations ( out of ). Rounds 1 and 2 together account for three-quarters. The most expensive rounds are precisely those where values are small. This observation, that the prover's bottleneck rounds coincide with the regime where cheap arithmetic applies, is the starting point for small-value proving.

High-Degree Products

The halving trick as presented handles , a product of two multilinear factors with degree . Each round, the prover evaluates the product at points, spending a constant number of multiplications per summand. The same idea generalizes to , a product of multilinear factors: fold each factor independently, then multiply. The folding is unchanged; what changes is the cost of multiplying factors together at evaluation points.

Modern proof systems demand this generalization. In batch-evaluation arguments and lookup protocols, the sum-check polynomial is a product of multilinear factors where can be 16 or 32. The naive approach evaluates each factor at points by extrapolation from the Boolean evaluations at 0 and 1, then multiplies pointwise. This costs bb multiplications per summand. At , that is nearly 1000 bb multiplications per term per round, and the prover's cost balloons to .

The question is whether the factor is intrinsic to the problem.

Divide-and-Conquer via Extrapolation

It is not. A recursive algorithm reduces the bb cost from to per evaluation point.

We work entirely in evaluation representation: each polynomial is stored as its values at a fixed set of points. A linear polynomial (degree 1) is determined by two evaluations (at 0 and 1). A degree- product needs evaluations. Multiplying two polynomials in evaluation form is just pointwise multiplication of their values at each point: one field multiplication per point.

Given evaluations of linear polynomials at the two points , the goal is to compute evaluations of their product at points.

  1. Split the polynomials into two halves of size and .
  2. Recursively compute the product of each half. Each half-product has degree and is known at points.
  3. Extrapolate both half-products from their known points to the full set of points, using Lagrange interpolation. The interpolation weights are small integers (derived from the evaluation-point coordinates ), so each multiplication is sb. Cost: sb multiplications per polynomial.
  4. Multiply pointwise: at each of the points, multiply the two half-product values. Both values are arbitrary field elements (results of prior recursion), so each multiplication is bb. Cost: bb multiplications.

The only source of bb multiplications is step 4. Each level of recursion contributes pointwise products, and the two recursive calls handle the subproblems. Writing for the total bb count:

The extrapolation steps contribute sb multiplications total, but since sb is far cheaper than bb, the wall-clock cost is dominated by the bb multiplications.

This extends to the multivariate case. In the sum-check prover's inner loop, the product involves multilinear polynomials in variables, evaluated on a grid of points. Multivariate extrapolation reduces to repeated univariate extrapolation along each coordinate dimension. The bb cost becomes for , improving by a factor of over the naive .

When used as a subroutine in the linear-time sum-check prover, the total bb cost across all rounds drops from to . For , this represents roughly a 5x reduction in the dominant arithmetic cost.

Small-Value Round-Batching

There is a structural inefficiency hiding in the halving trick. In round 1, all evaluations lie on the Boolean hypercube: the values come from the witness table and fit in machine words. Round 1 uses only ss/sb operations. But the moment the prover binds (a random 256-bit challenge), every subsequent value becomes a full-width field element. From round 2 onward, bb multiplications are unavoidable.

Round 1 accounts for half the total work. Round 2 accounts for a quarter. The most expensive rounds are exactly where values are small. Can we extend the cheap-arithmetic regime beyond a single round?

The idea is delayed binding: instead of binding immediately, treat the first variables as symbolic and precompute the -variate polynomial:

Every summand has Boolean and small witness values, so the entire precomputation uses only ss multiplications. The polynomial is stored as its evaluations on a grid. Once computed, the prover answers rounds 1 through by evaluating at the received challenges (which does require bb, but only work per round instead of ). After rounds, the prover binds all challenges at once and resumes the standard halving trick on arrays of size .

The optimal window size balances the ss precomputation cost against the bb savings. For over 256-bit fields, or 5 rounds. The asymptotic complexity is unchanged, but the concrete runtime drops substantially because the largest rounds (which dominate the geometric series) now use the cheapest arithmetic.

Streaming provers

Round-batching generalizes beyond the small-value setting. For truly massive computations ( terms), even memory becomes prohibitive: a terabyte of field elements. The halving trick is optimal in time but demands linear space.

A streaming prover applies round-batching iteratively, processing the input in sequential passes with sublinear memory. Instead of batching only the first rounds, the streaming prover batches every group of rounds into windows. For a window of rounds, the prover scans the relevant terms in one pass, computes a -variate polynomial on a grid, answers rounds from it, then moves to the next window. Early windows are small (the input is large). Later windows grow larger (the remaining input shrinks exponentially). A final phase switches to the standard linear-time algorithm.

With a tunable parameter , the streaming prover achieves space and time. For : two passes and memory. This exploits the algebraic structure of sum-check directly, without recursive proof composition.

Sparse Sums

The halving trick solves the dense case: when all terms are present, we achieve optimal proving time. But many applications involve sparse sums, where only terms are non-zero, and here the halving trick falls short.

Consider a lookup table with possible indices but only actual lookups. The halving trick still touches all positions, folding arrays of zeros. We're wasting a factor of 1000 in both time and space.

Can the prover exploit sparsity?

Separable Product Structure

A clarification first: "sparse sum" means the input data is sparse (the table on the Boolean hypercube has mostly zeros). The multilinear extension of a sparse vector is typically dense over the continuous domain. Sparsity in the table is what we exploit. Doing so requires a specific factorization.

In lookup arguments and memory-checking protocols, the problem is constructed with a natural variable split: a prefix encodes an address or row index, and a suffix encodes a value or column index. The constraints on addresses and values are independent by design, but a sparse selector connects them: most (address, value) pairs are unused, and only entries are active. For instance, a memory with addresses and possible values has (address, value) pairs, but a program that performs memory accesses touches only of them.

This gives the factorization:

where is a sparse selector with only non-zero entries, depends only on prefix variables (dense, size ), and depends only on suffix variables (dense, size ). The separability is what makes sparsity exploitable: to compute an aggregate like , the prover touches only the positions where is non-zero.

Two-Stage Proving

Given the separable product structure, we prove the sum in two stages: an outer sum-check over the prefix variables (addresses) and an inner sum-check over the suffix variables (values) to verify the outer stage's evaluation claim. Each stage handles half the variables, building dense arrays of size by scanning only the sparse entries.

Stage 1 (outer): Sum-check over prefix variables.

Define aggregated arrays and , each of size , indexed by prefix bit-vectors :

The array pre-sums all suffix contributions into a single value per prefix. This is the key move: the suffix variables are absorbed before the protocol starts, collapsing the original double sum into a single sum over prefixes . Computing requires one pass over the non-zero entries: for each non-zero pair, add to .

To see why this is correct, expand the original claim:

So proving the original sum reduces to proving , a sum-check with only variables. Here and are the multilinear extensions of arrays and .

Run the dense halving algorithm on these -sized arrays. Time: to build from sparse entries, plus for the dense sum-check.

Stage 2 (inner): Sum-check over suffix variables.

Like any sum-check, Stage 1 ends with a final evaluation claim: "I claim ." The verifier can check via polynomial commitment. But is itself defined as a sum over suffix variables:

This is where Stage 2 comes in: it runs sum-check over the suffix variables to verify this claim. Stage 1 summed out the prefixes; Stage 2 sums out the suffixes. Together they cover all variables, but each stage operates on arrays of size instead of .

Define arrays and , each of size , indexed by suffix bit-vectors :

Here is the sparse selector with its prefix fixed to the random challenges: it answers "what is the selector's value at address ?" The factor is now a constant (computed once from the dense array) that multiplies the entire Stage 2 sum.

Computing requires the MLE interpolation identity: . For each sparse entry , we need the Lagrange coefficient to weight its contribution to .

(Recall from Chapter 4: is the multilinear Lagrange basis function, and .)

A naive approach computes each independently in field ops, giving total. But we can do better: precompute all values for every Boolean in time using the product structure of . Then each sparse entry requires only a table lookup plus one multiplication. Total: for precomputation, for the pass over sparse entries.

Run the dense halving algorithm on and for the remaining rounds. Time: for precomputing values, to accumulate into , plus for the dense sum-check.

The structure is two chained sum-checks:

  1. Stage 1 ( rounds): proves the sum equals , ends with evaluation claim about
  2. Stage 2 ( rounds): proves that evaluation claim, ends with evaluation of and

Together: rounds, matching the original -variable sum-check. But prover work is only:

Two passes over sparse terms (one per stage), plus two -sized dense sum-checks. With appropriate parameters, this can be much less than .

Worked Example: Sparse Sum with ,

Consider a table of size (so variables), but only entries are non-zero. We want to prove:

where is the 2-bit prefix and is the 2-bit suffix.

Suppose the only non-zero entries are:

IndexPrefix Suffix Product
532424
951420
1423742

True sum: .

A dense approach would store all 16 entries and fold arrays of size 16 → 8 → 4 → 2 → 1, touching all 16 positions even though 13 are zero. The sparse two-stage approach avoids this.

Stage 1: Build aggregated prefix array .

Scan the 3 non-zero terms and accumulate:

  • Entry : Add to
  • Entry : Add to
  • Entry : Add to

Result: (indexed by prefix ).

Also store :

.

Run dense sum-check on for 2 rounds.

This is a size-4 sum-check (not size-16). Suppose after rounds 1-2, we get challenges .

Stage 2: Verify Stage 1's evaluation claim.

Stage 1 ended with the claim "." The verifier can check via polynomial commitment, but is defined as a sum:

Stage 2 is a second sum-check to prove this. Define arrays indexed by suffix :

To build , first precompute the Lagrange table for all 4 Boolean prefixes:

This takes field operations. Now scan the 3 sparse entries, looking up weights from the table:

  • Entry , : Add to
  • Entry , : Add to
  • Entry , : Add to

Result: .

Building : Just copy from the values: .

Run dense sum-check on for 2 rounds to prove .

Work analysis:

  • Stage 1: to build + for dense sum-check = 3 + 4 = 7 operations
  • Stage 2: to precompute table + to build + for dense sum-check = 4 + 3 + 4 = 11 operations
  • Total: = 18 operations instead of for the dense approach

(In this tiny example, sparse isn't faster because and are similar to . The win comes at scale.)

For realistic parameters (, ), the savings are dramatic: instead of , a 1000× speedup.

Generalization to Chunks

Split into chunks instead of 2. Each stage handles variables:

  • Time:
  • Space:

Choosing yields prover time with polylogarithmic space. The prover runs in time proportional to the number of non-zero terms, with only logarithmic overhead.

Spartan: Sum-Check for R1CS

What's the simplest possible SNARK?

Not in terms of assumptions (transparent or trusted setup, pairing-based or hash-based). In terms of conceptual machinery. What's the minimum set of ideas needed to go from "here's a constraint system" to "here's a succinct proof"?

Spartan (Setty, 2020) provides a surprisingly clean answer: sum-check plus polynomial commitments. Nothing else. No univariate encodings, no FFTs over roots of unity, no quotient polynomials, no PCP constructions. Just the two building blocks we've already developed.

The R1CS Setup

An R1CS instance consists of sparse matrices and a constraint: find a witness such that where denotes the Hadamard (entrywise) product. Each row of this equation is a constraint; the system has constraints over variables.

The Multilinear View

Interpret the witness as evaluations of a multilinear polynomial over the Boolean hypercube :

Similarly, view the matrices as bivariate functions: is the entry at row , column . Their multilinear extensions are defined over .

The constraint becomes: for every row index ,

Define the error at row :

The R1CS constraint is satisfied iff for all .

This multilinear view differs from the QAP approach in Chapter 12 (Groth16). There, R1CS matrices become univariate polynomials via Lagrange interpolation over roots of unity. The constraint transforms into a polynomial divisibility condition: , where is the vanishing polynomial over the evaluation domain. Proving satisfaction means exhibiting the quotient .

Spartan takes a different path. Instead of interpolating over roots of unity, it interprets vectors and matrices as multilinear extensions over the Boolean hypercube. Instead of checking divisibility by a vanishing polynomial, it checks that an error polynomial evaluates to zero on all Boolean inputs, via sum-check. No quotient polynomial, no FFT, no roots of unity. Just multilinear algebra and sum-check.

Both approaches reduce R1CS to polynomial claims. QAP reduces to divisibility; Spartan reduces to vanishing on the hypercube. The sum-check approach avoids the FFT costs and the trusted setup of pairing-based SNARKs, at the cost of larger proofs (logarithmic in the constraint count rather than constant).

The Zero-on-Hypercube Reduction

Spartan's R1CS encoding requires checking that vanishes on the Boolean hypercube, i.e., for all . The technique that handles this reduces separate equality checks to a single sum-check, and works for any polynomial, not just R1CS errors.

A natural first attempt is to prove via sum-check. If vanishes on the hypercube, this sum is indeed zero. But the converse fails. Suppose and with ; then despite being non-zero at two points. Positive and negative values cancel. A bare sum cannot distinguish "all zeros" from "zeros that happen to add up."

The fix is to weight each term with a pseudorandom coefficient so that accidental cancellation becomes overwhelmingly unlikely. Recall from Chapter 4 the equality polynomial :

On Boolean inputs, each factor equals 1 when and 0 when they differ, so the product is the indicator . The formula extends smoothly to all field elements: this is the multilinear extension of the equality indicator over . By the MLE evaluation formula (Chapter 4), for any with multilinear extension .

The reduction works as follows. The verifier samples random and asks the prover to demonstrate:

This sum is a random linear combination of , with coefficients determined by . If every , the sum is trivially zero. If even one , the sum is nonzero with high probability because the pseudorandom weights prevent cancellation. The equality polynomial turns "check values are all zero" into "check one random linear combination is zero."

We can bound the probability that a cheating prover passes this check. Define . Let . Then: Proof. If , then is a nonzero multilinear polynomial (since for some Boolean ). A nonzero multilinear polynomial has total degree at most . By Schwartz-Zippel, a nonzero polynomial of degree over has at most roots in . Thus the probability of hitting a root is at most .

This reduces "check vanishes on points" to "run sum-check on one random linear combination and verify it equals zero."

Spartan's outer sum-check

  1. Verifier sends random
  2. Prover claims , where
  3. Run sum-check on this claim

At the end, the verifier holds a random point and needs to evaluate . This requires three matrix-vector products: , , and .

Spartan's inner sum-checks

Each of these is itself a sum over the hypercube, requiring three more sum-checks. But now the sums are over , and the polynomials have the form for the fixed from the outer sum-check.

After running these three inner sum-checks (which can be batched into one using random linear combinations), the verifier holds a random point and needs to check:

  • , , : evaluations of the matrix MLEs
  • : evaluation of the witness MLE

The matrix evaluations are handled by SPARK (below). The witness evaluation is where polynomial commitments enter: the prover opens the committed at the random point , and the verifier checks the opening proof.

This is the full reduction: R1CS satisfaction → zero-on-hypercube (outer sum-check) → matrix-vector products (inner sum-checks) → point evaluations (polynomial commitment openings).

Handling sparse matrices with SPARK

The inner sum-checks end with evaluation claims: the verifier needs , , at the random points produced by the protocol. But the matrices , , are , and a dense representation costs space. Committing to them naively would dominate the entire protocol.

R1CS matrices are sparse. A circuit with constraints typically has only non-zero entries total, not . A sparse matrix with non-zero entries can be stored as a list of tuples at cost. The question is how to evaluate the matrix MLEs at from this sparse representation.

Applying the MLE evaluation formula to the bivariate function gives:

Since for most entries, this simplifies to a sum over only the non-zero entries:

For each non-zero entry , we need and . Computing directly from the formula costs . Over entries, total cost: .

SPARK reduces this to by precomputing lookup tables.

  1. Precompute row weights. Build a table for all . This costs using the standard MLE evaluation algorithm (stream through bit-vectors, accumulate products).

  2. Precompute column weights. Build a table for all . Cost: .

  3. Evaluate via lookups. Initialize a running sum to zero. For each non-zero entry , look up and , then add to the running sum. After processing all entries, the sum equals . Cost: .

Total: , linear in the sparse representation size.

The remaining question is who checks the lookups. The prover claims to have read the correct values from the precomputed tables, but the verifier does not have those tables. SPARK resolves this with a memory-checking argument: a protocol that verifies the prover's reads against the table contents by comparing random fingerprints of both. If any lookup is incorrect, the fingerprints mismatch with high probability. Chapter 21 develops this technique in full. The overhead is in proof size and verification time, preserving SPARK's linear prover efficiency.

The full Spartan protocol

Putting it together:

  1. Commitment phase. The prover commits to the witness using a multilinear polynomial commitment scheme. The matrices , , are public (part of the circuit description), so no commitment is needed for them.

  2. Outer sum-check. The verifier sends random . The prover and verifier run sum-check on: This reduces to evaluating at a random point .

  3. Inner sum-checks. Evaluating requires three matrix-vector products: , , and . The verifier sends random , and the parties run a single sum-check on the combined claim: where is the prover's claimed value for the batched sum. At the end of sum-check, the verifier holds a random point and a claimed evaluation of the polynomial at .

  4. SPARK. The prover provides claimed values , , and proves they're consistent with the sparse matrix representation via memory-checking fingerprints.

  5. Witness opening. The prover opens using the polynomial commitment scheme. The verifier checks the opening proof and obtains the value .

  6. Final verification. The verifier computes using the values from steps 4-5, and checks that it equals the final claimed value from the inner sum-check. This is the "reduction endpoint": if the prover cheated anywhere in the sum-check, this equality fails with high probability.

Complexity

ComponentProverVerifierCommunicationTechnique
Outer sum-checkHalving trick
Inner sum-checksHalving trick + batching
SPARKPrecomputed tables + memory checking
Witness commitmentdepends on PCSdepends on PCSdepends on PCSMultilinear PCS (IPA, FRI, etc.)

Why each step achieves its complexity:

  • Outer sum-check : The halving trick from earlier in this chapter. Instead of recomputing terms each round, fold the evaluation tables after each challenge. Total work across all rounds: .

  • Inner sum-checks : Same halving trick, but applied to three matrix-vector products at once. Batching with random coefficients combines the three sums into one sum-check, avoiding a overhead.

  • SPARK : Precompute for all row indices and for all column indices in time. Then each of the non-zero entries requires only two table lookups and one multiplication, with no logarithmic-cost computations per entry. Memory-checking fingerprints verify the lookups in additional work.

  • Verifier : The verifier never touches the full tables. In sum-check, it receives evaluations per round and performs field operations to check consistency. Over rounds, that's work. SPARK verification adds for the memory-checking fingerprint comparison.

With non-zero matrix entries, total prover work is , linear in the instance size. No trusted setup is required when using IPA or FRI as the polynomial commitment.

Why This Matters

Step back and consider what we've built. Spartan proves R1CS satisfaction, the standard constraint system for zkSNARKs, using only sum-check and polynomial commitments. No univariate polynomial encodings (like PLONK's permutation argument). No pairing-based trusted setup (like Groth16). No PCP constructions (like early STARKs).

The architecture is minimal: multilinear polynomials, sum-check, commitment scheme. Three ideas, combined cleanly. This simplicity is the reason Spartan became the template for subsequent systems. Lasso added lookup arguments; Jolt extended further to prove virtual machine execution. Each built on the same foundation.

Notice the graph structure emerging. Spartan has two levels: an outer sum-check (over constraints) and inner sum-checks (over matrix-vector products). The outer sum-check ends with a claim; the inner sum-checks prove that claim. This is exactly the depth-two graph from the remark at the chapter's start. More complex protocols like Lasso (for lookups) and Jolt (for full RISC-V execution) extend this graph to dozens of nodes across multiple stages, but the pattern remains: sum-checks reducing claims to other sum-checks, bottoming out at committed polynomials.

When a construction is this simple, it becomes a building block for everything that follows.

The PCP Detour and Sum-Check's Return

Now that we've seen Spartan's architecture (sum-check plus commitments, nothing more), the historical question becomes pressing: why did the field spend two decades pursuing a different path?

The PCP path

In 1990, sum-check arrived. Two years later, the PCP theorem landed: every NP statement has a proof checkable by reading only a constant number of bits. This captured the field's imagination completely.

The PCP theorem seemed to obsolete sum-check. Why settle for logarithmic verification when you could have constant-query verification? Kilian showed how to compile PCPs into succinct arguments: commit to the PCP via Merkle tree, let the verifier query random locations, authenticate responses with hash paths. This became the template for succinct proofs.

Sum-check faded into the background, remembered as a stepping stone rather than a destination.

The redundant indirection

In hindsight, the PCP-based pipeline contained a redundancy. The PCP theorem transforms an interactive proof into a static proof string that the verifier queries non-adaptively. Interaction removed. But the proof string is enormous, so Kilian's construction has the prover commit to it via a Merkle tree and the verifier interactively requests query locations. Interaction reintroduced. Then Fiat-Shamir makes the protocol non-interactive. Interaction removed again.

The transformations: IP → PCP (remove interaction) → Kilian argument (add interaction back) → Fiat-Shamir (remove interaction again). Two removals of interaction. If Fiat-Shamir handles the final step anyway, why not apply it directly to the original interactive proof based on sum-check?

The return

Starting around 2018, the missing pieces fell into place: fast proving algorithms (the halving trick, sparse sums) and polynomial commitment schemes (KZG, FRI, IPA) that could handle multilinear polynomials directly. A wave of systems returned to sum-check:

  • Hyrax (2018), Libra (2019): early sum-check-based SNARKs with linear-time provers
  • Spartan (2020): sum-check for R1CS without trusted setup
  • HyperPlonk (2023): sum-check meets Plonkish arithmetization
  • Lasso/Jolt (2023-24): sum-check plus lookup arguments for zkVMs
  • Binius (2024): sum-check over binary fields

The pattern: sum-check as the core interactive proof, polynomial commitments for cryptographic binding, Fiat-Shamir applied once.

What the PCP path got right

The architectural redundancy does not mean the PCP path was wasted. It produced STARKs, which remain among the most deployed proof systems. STARKs compile an IOP (the AIR + FRI pipeline from Chapter 15) using only hash functions, no elliptic curves. This gives them a property that sum-check-based systems struggle to match: post-quantum security out of the box.

Sum-check itself is information-theoretic and quantum-safe. But it produces evaluation claims that must be resolved by a polynomial commitment scheme, and the most mature multilinear PCS options (KZG, IPA, Dory) rely on discrete-log assumptions that Shor's algorithm breaks. Post-quantum alternatives exist: hash-based multilinear commitments and lattice-based schemes are active areas of research, but they remain less mature than the FRI-based commitments that STARKs use today.

The practical landscape reflects this. For applications where post-quantum security matters now (long-lived proofs, regulatory environments, sovereign infrastructure), STARKs offer a proven path. For applications where prover speed dominates and classical assumptions suffice, sum-check-based systems like Jolt and Binius achieve prover times closer to the witness computation itself. The two approaches are converging: Binius uses sum-check over binary fields with FRI-based commitments, combining both traditions. Chapter 20 develops the STARK-side optimization story in parallel with this chapter, showing how small-field techniques, NTT optimization, and FRI batching close the gap between STARK proving and witness computation from the other direction.

Key Takeaways

  1. The halving trick achieves prover time. Fold evaluation tables after each challenge: via multilinear interpolation. Total work is the geometric series .

  2. Not all field multiplications are equal. Over 256-bit fields, bb multiplications are roughly 8x more expensive than sb and 30x more expensive than ss. Delayed reduction amortizes modular reduction across linear combinations. These distinctions dominate wall-clock time despite being invisible in notation.

  3. High-degree products cost , not . A divide-and-conquer algorithm splits factors in half, recurses, extrapolates via Lagrange (sb work), and multiplies pointwise (bb work). Only the pointwise step is expensive.

  4. Small-value round-batching exploits the geometric series. The first rounds dominate total work and operate on small witness values. Treating these variables as symbolic replaces bb multiplications with ss, reducing the concrete cost of the most expensive portion of the protocol.

  5. Streaming provers trade passes for memory. Applying round-batching iteratively gives space for any , without recursive proof composition.

  6. Sparse sums exploit separable structure. When the polynomial factors into a sparse selector and dense prefix/suffix components, two chained sum-checks over variables each achieve cost instead of .

  7. Spartan reduces R1CS to sum-check. The zero-on-hypercube reduction converts " vanishes on " into a single sum-check weighted by , which acts as a random linear combination preventing cancellation. An outer sum-check () plus batched inner sum-checks () plus SPARK () handle the full R1CS constraint system.

  8. Sum-check graphs structure complex protocols. Each sum-check ends with evaluation claims. If the polynomial is committed, open it. If it is virtual, another sum-check proves the evaluation. The result is a DAG where depth determines sequential stages and width enables batching. Chapter 21 develops this perspective.

  9. The PCP path and the sum-check path are converging. The IP → PCP → Kilian → Fiat-Shamir pipeline contains an architectural redundancy (interaction removed, reintroduced, removed again). Sum-check + Fiat-Shamir skips this. But the PCP lineage produced STARKs, which offer post-quantum security via hash-based commitments. Sum-check systems need a post-quantum PCS to match, and those remain less mature. Binius bridges both traditions: sum-check over binary fields with FRI-based commitments.

Chapter 20: Fast STARK Proving

This chapter is part of Part VI (Prover Optimization, Chapters 19-21), which is optional on a first read. The rest of the book does not depend on it. The material here is essential for anyone designing or implementing a STARK prover.

Specific prerequisites: fluency with the STARK pipeline (Chapter 15), FRI (Chapter 10), and the small-field/small-value ideas from Chapter 19. This chapter parallels Chapter 19's treatment of sum-check prover optimization, now applied to the STARK side. Together they give a complete picture of how both proof traditions close the gap between witness computation and proof generation.

A STARK prover does far more work than the computation it proves. Executing a million steps of a hash function takes microseconds. Generating a proof of that execution takes seconds. The prover overhead, the ratio of proof generation time to raw computation time, exceeded 1000× in early systems. Where does all that prover time go?

The answer is not one bottleneck but a shifting pipeline of them. For small traces, constraint evaluation and polynomial arithmetic consume most of the prover's cycles. For medium traces, the number-theoretic transform (NTT) takes over, since its cost eventually dominates linear-time constraint evaluation. For the largest traces, Merkle hashing for FRI commitments becomes the wall. Profiling data from production provers confirms this progression: NTT can account for up to 91% of prover runtime in workloads dominated by polynomial operations, while Merkle tree construction dominates at roughly 60% in hash-intensive recursive proving workloads. The prover engineer's task is to push each bottleneck down until the next one surfaces, then push that one down too.

The trajectory of improvement across the ecosystem has been dramatic. Early STARK provers (circa 2021) achieved roughly 10,000 Poseidon hashes per second. By 2024, provers built on small-field techniques and Circle STARKs over Mersenne31 exceeded 500,000 Poseidon2 hashes per second on commodity quad-core hardware, with some configurations surpassing 620,000 per second. That is a 50× improvement from algorithmic and field-choice optimizations alone, without GPU acceleration. Multiple independent teams converged on similar techniques: 31-bit prime fields, AIR-based constraint systems, batched FRI, LogUp bus arguments. Much of this convergence crystallized around Plonky3 (Polygon), an open-source framework providing shared field arithmetic (BabyBear, Mersenne31), an AIR trait interface, and a modular FRI backend. SP1 (Succinct), OpenVM (Axiom/Scroll), and several other production provers build on Plonky3, while StarkWare's Stwo and RISC Zero's prover implement the same ideas independently. Understanding the shared principles behind these gains is the subject of this chapter.


The prover pipeline

The STARK prover executes a sequence of stages, each feeding into the next. Understanding where time goes requires tracing this pipeline end to end. The following variables recur throughout this chapter:

  • — trace length (number of rows/timesteps), always a power of two
  • — trace width (number of columns/registers)
  • — maximum constraint degree across all AIR transition polynomials
  • — blowup factor, the ratio between the LDE evaluation domain and the trace domain (typically 2, 4, or 8)
  • — number of FRI query repetitions (security parameter)
  • — cost of one hash invocation measured in field multiplications

Stage 1: Trace generation. The prover runs the computation, filling the execution trace, a matrix with columns (registers) and rows (timesteps). For a hash function like Poseidon with 30 rounds and state width 12, the trace might have 12-24 columns and rows for input blocks. This stage performs the same arithmetic the original computation would, plus bookkeeping for each intermediate state. Cost: field operations with a small constant per cell.

Stage 2: Constraint evaluation. The prover evaluates the AIR constraint polynomials at every row. If the maximum constraint degree is over registers, each row costs field operations. Total: .

Stage 3: Composition and quotient formation. The prover forms the composition polynomial by batching all constraint quotients with random Fiat-Shamir challenges (Chapter 15). The composition polynomial has degree roughly .

Stage 4: Low-degree extension (LDE). The prover evaluates trace polynomials and the composition polynomial on a domain that is times larger than , where is the blowup factor. The evaluation proceeds by inverse NTT (interpolation from to coefficient form) followed by forward NTT (evaluation on ). The number-theoretic transform (NTT) is the finite-field analogue of the FFT: it converts between coefficient and evaluation representations of a polynomial over a domain of roots of unity, using the same butterfly algorithm that Chapter 5 developed for the discrete Fourier transform over . Each polynomial requires two NTTs costing . With columns, the total is .

Stage 5: Merkle commitment. The prover hashes every row of the LDE matrix into a Merkle tree. The ethSTARK specification (Ben-Sasson et al., 2021), which formalized the production STARK pipeline into a reference document, groups all field elements in a row of the trace LDE into a single leaf, so the tree has leaves. Building it requires hash invocations.

Stage 6: FRI protocol. The prover executes FRI folding rounds, each halving the polynomial via an NTT and committing the result in a Merkle tree. The total across rounds is a geometric series of NTTs and trees, dominated by the first round.

Stage 7: Query responses. The prover opens Merkle paths at queried positions. This is fast (logarithmic per query) and rarely a bottleneck.

The relative costs shift with scale. At , constraint evaluation can dominate. At , NTT takes over. At , Merkle hashing becomes comparable to NTT. Optimization must address each stage in sequence.

The following table summarizes the cost model. Here is the trace width, the trace length, the maximum constraint degree, the blowup factor, the number of FRI queries, and the cost of one hash invocation measured in field multiplications.

StageCostDominates when
Trace generationRarely (linear, small constant)
Constraint evaluationSmall ()
CompositionHigh constraint degree
LDE (NTT)Medium ( to )
Merkle commitmentLarge with cheap field arithmetic
FRI (folding + trees)Comparable to LDE + Merkle combined
Query responsesNever

The ratio is what determines the crossover between NTT-dominated and hash-dominated regimes. Over a 256-bit field, is small relative to field multiplication cost, so NTT dominates. Over a 31-bit field like BabyBear, field multiplications become so cheap that grows relatively large, shifting the bottleneck toward Merkle hashing. This ratio is the single most useful diagnostic for predicting where a given prover spends its time.

The cost table reveals two levers for optimization. The first is to reduce the inputs to the pipeline: the parameters , , and that determine how much work each stage performs. This is the domain of AIR design. The second is to reduce the cost per operation within each stage: faster field arithmetic (small fields), better memory access (cache-friendly NTTs), fewer redundant commitments (FRI optimizations). The remainder of this chapter addresses these in order.


AIR design and the degree-blowup tradeoff

The encoding of a computation into an AIR often matters more than any algorithmic optimization applied afterward. Two designs for the same computation can differ in prover time by an order of magnitude, because they feed different values of and into the same pipeline. The cost table makes this concrete: doubling doubles every row from "Composition" downward, while doubling only adds to "LDE" and "Merkle commitment."

The central tension is between trace width and constraint degree. A wider trace (more columns) with low-degree constraints breaks complex expressions into simpler pieces by storing intermediate values. A narrower trace (fewer columns) requires higher-degree constraints to compress the same logic.

The reason width is cheap and degree is expensive comes from how they propagate through the pipeline. Adding a column costs one extra NTT of size and widens each Merkle leaf; the cost grows linearly in . Raising the constraint degree from to doubles the composition polynomial's degree, doubling the LDE domain, every NTT, every Merkle tree, and all FRI work. Degree is a multiplicative cost on the entire pipeline; width is an additive cost on one stage. The heuristic follows: add columns to reduce constraint degree until further splitting no longer lowers to the next power of two. Production STARK frameworks formalize this by setting the blowup factor to the smallest power of two greater than or equal to the highest transition constraint degree, so any reduction in that crosses a power-of-two boundary halves the downstream cost.

Consider a transition that computes . With one column, the constraint is , degree 8. With three auxiliary columns storing , , , the constraints become four degree-2 checks, where each squaring is . The constraint degree drops from 8 to 2, at the cost of widening the trace from 1 to 4 columns.

Why does constraint degree matter so much? The composition polynomial has degree roughly . FRI must prove a degree bound on this polynomial, so the LDE domain must be at least points. Every doubling of doubles the NTT size, the Merkle tree size, all FRI operations. A degree-8 constraint over rows produces a composition polynomial of degree , requiring an LDE domain of at blowup . Reducing to degree 2 drops the LDE domain to , a 4× reduction in all subsequent stages. Most production systems keep constraint degree between 2 and 4.

The overarching recipe: keep by trading additive cost in for multiplicative savings through . Two design patterns achieve this (periodic columns and trace widening), while a third pattern (interaction columns) addresses a different problem: extending AIR expressiveness to handle constraints that span distant rows.

Reducing degree: periodic columns

Many computations repeat structure at regular intervals. A hash function applies the same round constants in a cycle of length . A CPU cycles through a fixed instruction decode pattern. Naively, these constants would occupy a committed trace column, adding one NTT and one column's worth of Merkle leaf data. Periodic columns avoid this cost entirely.

A periodic column encodes public constants that repeat on a fixed cycle. Suppose a hash function uses round constants and the trace has rows. The constants repeat: row 0 gets , row 1 gets , ..., row 4 gets again, and so on. The key observation is that on the trace domain , the map collapses all rows sharing the same round position to the same value (since ). So the periodic column is really a polynomial of degree in the "compressed" variable , cycling automatically because roots of unity wrap around. Both prover and verifier can compute from the public constants without any commitment. The prover saves one NTT of size and all associated Merkle leaf contributions.

The tradeoff is that contributes degree when it appears multiplicatively in a constraint. The design rule: use a periodic column when , where is the constraint degree already imposed by other terms. In that case the periodic column adds no degree overhead and saves an entire committed column. If , the periodic column would raise the effective constraint degree, potentially crossing a power-of-two boundary and doubling all downstream costs. In that case, commit the constants as a regular trace column instead.

Poseidon2 illustrates the tension. The S-box degree is 5 (or 3 after decomposition into auxiliary columns), while the round-constant cycle has . Since , treating the round constants as periodic would push from 3 to 7. Most implementations therefore either commit the round constants as ordinary columns or restructure the cycle into shorter sub-periods.

Reducing degree: wide versus tall traces

The principle above (add columns to reduce degree) applies not just to individual constraints like but to the overall trace architecture. The total number of trace cells is roughly fixed by the computation, so the question is how to partition that area: many columns with few rows, or few columns with many rows?

For hash functions, where the computation is regular and the state width is fixed, the trace width maps naturally to the state size. For virtual machines, the choice is less obvious. A zkVM instruction like ADD R1, R2, R3 touches three registers, a program counter, various flags. Representing all of these as separate columns creates a wide trace (50-100 columns in practice) with degree-2 or degree-3 constraints. Alternatively, encoding multiple values per column via bit-packing creates a narrower trace with higher-degree constraints to extract individual fields.

Production systems overwhelmingly favor wide traces. When is fixed, doubling while halving leaves the Merkle commitment cost () unchanged and saves roughly one butterfly stage per NTT ( vs ). But the decisive reason is that wider traces enable lower constraint degree, and as established above, each halving of that crosses a power-of-two boundary cuts all downstream costs in half.

Extending expressiveness: interaction columns and LogUp

Periodic columns and trace widening reduce the cost of constraints the AIR can already express. But there is a class of constraints that a pure AIR cannot express at all: relationships between non-adjacent rows. The AIR model from Chapter 15 sees only consecutive pairs (row , row ). Memory consistency, lookup arguments, and register-file reads all require matching values across distant rows. This is not a control-flow issue (a JUMP merely updates the program counter between adjacent rows) but a data consistency problem: values written at one timestep must be readable at another.

Consider a concrete example. A zkVM executes LOAD R1, [addr] at row 3,912, reading a value that was written by STORE R1, [addr] at row 47. The transition constraint at row 3,912 can verify that the instruction is well-formed (correct opcode, valid register index), but it sees only rows 3,912 and 3,913. It has no way to reach back to row 47 and check that the loaded value matches what was stored there.

The solution is to avoid checking distant-row consistency directly. Instead, the prover builds a running summary of all writes and all reads, then proves the two summaries match. If every read returned the value that was written, the summaries agree; if any read was faked, they disagree with overwhelming probability. The summary itself is stored as an auxiliary column that accumulates one entry per row, turning the global consistency check into a local transition constraint (each row updates the running total).

For this to be sound, the prover must not be able to choose summary values that hide an inconsistency. The protocol achieves this by making the summary depend on a random challenge that the verifier provides (via Fiat-Shamir) after the prover has already committed to the main trace. The prover then extends the trace with auxiliary columns computed from . Because was unknown when the main trace was fixed, the prover cannot game the summary.

The most widely deployed version of this idea is LogUp (Chapter 14). If two multisets are equal, then for a random the sums and must agree (where counts how many times table entry was looked up). This global sum-equality becomes a local transition constraint by introducing an accumulator column . Row increments by , so walks through the partial sums. A boundary constraint checks and equals the expected table-side sum. If any lookup is invalid (some ), the sums disagree with probability over the random choice of .

AIR constraints are polynomial equations; they can multiply but not divide. The prover cannot write directly in a constraint. Instead, the prover stores each reciprocal as a witness value in an auxiliary column and adds the constraint , which is degree 2. The verifier never computes the division; it just checks that the product equals 1. Each LogUp bus therefore adds 2 auxiliary columns: the accumulator and the reciprocal column .

Each of these columns requires one additional NTT of size and widens the Merkle leaf. For a zkVM with three buses (memory, instruction lookup, range checks), the auxiliary columns total roughly 6, increasing by 6. Compared to the main trace width of 50-100, this is a 6-12% increase in per-column costs. Unlike periodic columns and trace widening, interaction columns are not an optimization. They are a requirement. A pure AIR without auxiliary columns cannot express memory consistency, lookup arguments, or any constraint relating non-adjacent rows. LogUp is what makes it possible to build zkVMs on top of the AIR model at all. The cost (a few extra columns per bus) is simply the price of expressiveness.


AIR design reduces the parameters feeding the pipeline. The next three sections reduce the cost per operation within the pipeline stages: field arithmetic (this section), the NTT (next section), and FRI (the section after).

Small fields and extension field lifting

Every cost in the pipeline table is measured in field multiplications. Making each multiplication cheaper is the most direct way to speed up the prover. The question is: how small can the field be before soundness breaks?

Chapter 19 showed that sum-check provers exploit small witness values within a large 256-bit field. STARK provers take a more radical approach: they work over a genuinely small field, where every element fits in a single machine word.

Chapter 19 introduced the cost hierarchy within a 256-bit field: bb (big-by-big) multiplications dominate because both operands span multiple machine words. Over BN254, a bb multiply splits each 254-bit element into four 64-bit limbs and performs multiple limb-by-limb products with carry propagation, typically 30-50 CPU cycles per field multiplication. STARK provers sidestep this hierarchy entirely by working over a prime small enough that every element fits in a single machine word, eliminating multi-limb arithmetic altogether.

Why 31 bits specifically? The constraint comes from hardware. Multiplying two -bit values produces a -bit result. For the product to fit in a single 64-bit register without multi-word handling, we need , giving . A 31-bit prime is the largest that satisfies this while leaving room for modular reduction. Two 31-bit field elements multiply via a single 32×32→64 hardware instruction, and the 62-bit result reduces modulo in 3-4 cycles total. Compare this with the 30-50 cycles for a 256-bit bb multiply. (64-bit primes like Goldilocks, , also avoid multi-limb arithmetic since x86 MUL produces a 128-bit result in two registers, but the reduction is more expensive and vectorization is half as dense.)

Vectorization amplifies the advantage further. Modern CPUs process multiple field elements in parallel through SIMD (Single Instruction, Multiple Data) registers. A 512-bit vector register packs 16 elements of a 31-bit field (such as BabyBear or Mersenne31, introduced below) side by side, performing 16 independent multiplications in a single instruction. Over a 64-bit field like Goldilocks (, the previous generation of STARK-friendly primes), the same register holds only 8 elements. Over BN254, field multiplication cannot be vectorized at all because each multiply already consumes the full register width for its multi-limb computation. The combined speedup from native arithmetic and vectorization exceeds 10× per element compared to 256-bit fields, with some analyses reporting 40× improvement in end-to-end prover time.

The speedup comes at a cost to soundness. Every interactive protocol in this book (sum-check, FRI, DEEP-ALI) derives security from the verifier's random challenges being hard to predict. A cheating prover guessing a random challenge succeeds with probability . Over BN254, that probability is , negligible. Over a 31-bit field, it is , far from the target. Shrinking the field made arithmetic cheaper but made each challenge weaker.

The solution is extension field lifting, which separates where the data lives from where the randomness lives. The trace, constraint evaluation, and NTT all operate over the base field (cheap, 31-bit arithmetic). Verifier challenges, which must be unpredictable, live in an extension field for or . An element of is a tuple , representing modulo an irreducible quartic. Multiplication costs roughly 9 base field multiplications via Karatsuba (compared to 16 for schoolbook expansion). The field size jumps to , providing adequate soundness. Only the parts of the protocol that involve verifier randomness (FRI folding challenges, DEEP-ALI point , LogUp challenge ) use extension arithmetic; the bulk of the prover's work never leaves the base field.

How much does extension arithmetic actually cost? The two largest stages provide the comparison. The NTT runs once per trace column, processing polynomials in the base field. FRI folding processes only a single batched polynomial, but each operation uses an extension challenge and therefore costs roughly 9× a base multiplication. So the FRI work, measured in base field operations, is about × the cost of a single column's NTT, while the total NTT work scales with all columns. The extension overhead is a fraction roughly of the NTT cost. For a typical trace with , this is around 22%: a noticeable surcharge, but far from dominant.

Two 31-bit primes dominate modern STARK proving, chosen for different algebraic reasons:

BabyBear (). What matters is the multiplicative group order . The factor means the field contains a subgroup of order , which serves as the NTT domain. This supports traces with up to million rows. BabyBear is the field behind RISC Zero's zkVM.

Mersenne31 (). This is a Mersenne prime, giving the cheapest possible modular reduction: since , reducing a 62-bit product is just splitting it into a low 31-bit half and a high half, adding them, and doing one conditional subtract. No multi-limb arithmetic at all. The tradeoff: has only one factor of 2, so the multiplicative group has no large power-of-two subgroup. Standard NTTs are impossible over this field. Circle STARKs (discussed in the FRI section below) resolve this by replacing the multiplicative group with the circle group , which has order . Stwo (StarkWare), Plonky3 (Polygon), and Airbender (ZKsync) all use M31 through this mechanism.

Worked example: counting base multiplications inside an extension multiply.

Let (BabyBear). A base field multiplication is one hardware operation: , which reduces to with a single conditional subtract.

Now consider the same operation in the quadratic extension , which uses 2 coefficients per element (we use the quadratic case for clarity; the quartic extends the same idea). Let and . The schoolbook expansion is: where the last step uses . This requires 4 base multiplications: , , , .

Standard polynomial-multiplication tricks (Karatsuba's algorithm, which substitutes two of these multiplications with sums of the other products) reduce the count from 4 to 3 in the quadratic case. Applied recursively to the quartic extension, this drops the naive 16 base multiplications to roughly 9. That is the source of the figure used in the overhead estimate above.

Concretely, with and : the schoolbook computation gives . Each coefficient of the result requires 2 base multiplications and one addition, totaling 4 base multiplications for the full extension product.


NTT optimization

Small fields make each multiplication cheaper but do nothing to reduce the number of multiplications. If anything they raise the count slightly, since the parts of the protocol that touch extension elements pay roughly 9 base multiplies per extension multiply. The pipeline stage that consumes most of those multiplications is the low-degree extension (Stage 4): the prover takes each of the trace polynomials defined on the trace domain and re-evaluates it on the larger LDE domain of size . The algorithm that does this efficiently is the number-theoretic transform (NTT), which converts between coefficient and evaluation representations of a polynomial in field operations.

The arithmetic cost is fixed by the domain size: multiplications per polynomial, total across all trace columns. For a trace with rows, columns, and blowup , this comes to roughly field multiplications in the NTT alone. Even at 3-4 cycles per BabyBear multiply, this is over a second on a single core. The NTT is not merely a subroutine; for medium-to-large traces, it is the prover.

The algorithm is the same Cooley-Tukey butterfly as the FFT from Chapter 5; in the STARK context, "NTT" and "FFT" are interchangeable, with "NTT" emphasizing the finite-field setting. (Lattice cryptography also uses NTTs, but over a different domain: the reduction polynomial is rather than , so the transform evaluates at primitive -th roots of unity and computes a negacyclic convolution. The STARK NTT uses -th roots and computes a standard cyclic convolution, matching the vanishing polynomial from Chapter 15.) Beyond the LDE, the prover also runs NTTs in each FRI folding round to extract even/odd parts of the polynomial, but those are smaller and form a geometric series dominated by the first round.

The asymptotic cost is not what makes NTTs hard to optimize. The problem is memory access. Modern CPUs have a small, fast on-chip memory called the cache, organized in layers (L1 at roughly 32-64 KB per core, accessed in 4 cycles; L2 at hundreds of KB; L3 in megabytes). Anything not in cache must be fetched from main RAM, which costs 100-300 cycles, fifty times slower than a hit. An algorithm that touches data already in cache runs near peak compute throughput; an algorithm that constantly misses spends most of its time stalled, waiting for RAM.

An NTT of size performs multiplications in "butterfly" stages. Each stage pairs elements at distance . The first stages access widely separated memory addresses (stride ), so each butterfly's two operands sit far apart in memory and almost certainly cause cache misses. The last stages access nearby elements, which are cache-friendly. For , the first stage's stride is elements ( MB for 32-bit fields), far exceeding any L1 or L2 cache. A naive implementation spends most of its time waiting for RAM rather than computing.

The four-step NTT

The four-step NTT rearranges the computation so that all but one stage operates on chunks small enough to fit entirely in L1 cache. Instead of one large NTT of size , the prover treats the data as a matrix and runs many small NTTs of size .

The procedure has four steps:

  1. Perform small NTTs of size , one along each row of the matrix
  2. Multiply each element by a twiddle factor (a precomputed root of unity)
  3. Transpose the matrix
  4. Perform small NTTs along each row again

Why this is faster: each small NTT reads and writes only elements, which for realistic STARK sizes fits in L1 cache. For , elements, occupying KB over a 31-bit field. That fits in a 32 KB L1 cache with room to spare. The CPU loads the row once, runs the entire small NTT against fast on-chip memory, and writes the result back. Cache-miss penalties no longer dominate.

The naive NTT, by contrast, has butterfly stages, and the early stages have stride elements ( MB), causing a cache miss on essentially every butterfly. The four-step layout incurs cache-line-friendly access in steps 1 and 4, and concentrates the unavoidable long-stride memory traffic into a single transposition (step 3) that can be done in cache-friendly blocked fashion.

GPU implementations extend this further. Research on GPU NTT optimization (Özcan, 2023) implements both "Merge" and "4-Step" NTT models for GPU architectures, where the memory hierarchy (global memory → shared memory → registers) creates an analogous cache structure. The four-step decomposition maps naturally to GPU thread blocks, with each block handling one row NTT in fast shared memory.

The blowup factor tradeoff

The four-step decomposition makes each NTT faster at fixed size . The other way to make NTTs cheaper is to make smaller. Recall that the NTT operates on the LDE domain , whose size is where is the blowup factor. Halving halves , which directly halves the work in every NTT, every Merkle tree, and every FRI round. The only obstacle is that is not a free parameter: FRI's soundness depends on it.

The mechanism is straightforward. FRI proves that a committed function is close to a low-degree polynomial by spot-checking. A cheating prover who deviates from a low-degree polynomial must disagree with every degree- polynomial on at least a fraction of the LDE domain (a Reed-Solomon distance bound from Chapter 10). Each random query catches such a deviation with probability at least , so queries miss the deviation with probability at most . Each query contributes bits of security.

This relationship encodes a direct tradeoff. Going from to shrinks the LDE by 8× (massive prover speedup) but cuts the security per query from 4 bits to 1 bit, requiring 4× more queries to maintain the same target. Each extra query adds Merkle path openings to the proof, increasing proof size.

One subtlety affects the security accounting. The figure depends on a strong assumption called the proximity gap conjecture. The conjecture concerns batched FRI, where the prover combines committed polynomials into a single random linear combination and runs FRI once on instead of times on each . The natural worry is that even if some is far from low-degree, the combination might accidentally land close to low-degree (because the randomness canceled the deviations). The proximity gap conjecture asserts this cannot happen with non-negligible probability: the set of "bad" that move across the soundness threshold is exponentially small. If true, batched FRI is as sound as running FRI on the worst individual , giving bits per query. The fully proven analysis (which makes no conjecture) loses a factor of 2, giving bits per query.

For years, deployed STARK systems used the conjectured numbers, halving query counts compared to the proven bound. In late 2025, counterexamples showed the conjecture does not hold in full generality: certain pathological codes admit "bad" probabilities that the conjecture's quantitative claim ruled out. The counterexamples do not break Reed-Solomon FRI in deployment, but they invalidate the strongest version of the conjecture and force a re-examination of soundness margins. Production systems are responding by either increasing (widening the safety margin between conjectured and proven security) or switching to the proven analysis directly. The Ethereum Foundation's proving roadmap now targets 100 bits of provable security by mid-2026 and 128 bits provable by year-end, driving conservative parameter choices across the ecosystem.

For systems targeting 128-bit security, the parameter choices tighten:

Blowup Queries for 128 bits (conjectured)Queries for 128 bits (proven)LDE size ()
2128256
464128
8
163264

Smaller blowup saves prover time (smaller NTTs and Merkle trees) but increases proof size (more query openings). Most modern systems use combined with grinding to compensate.

For a concrete sense of the tradeoff: moving from to on a typical trace (, ) makes the LDE 4× larger and the NTT work roughly 4.4× more expensive, but cuts the FRI query count by about 3× (from 128 conjectured queries to 43), shrinking the proof from MB to MB at the same grinding. The blowup factor is the primary knob for trading prover speed against proof size.


FRI optimization

The NTT produces evaluations on the LDE domain. The Merkle tree commits them. But the proof is not yet succinct: the commitment alone proves nothing about degree. FRI (Chapter 10) is what makes the Merkle commitment into a polynomial commitment, by interactively testing that the committed function is close to a low-degree polynomial. The base FRI protocol is already efficient, but four optimizations, each addressing a different cost, combine to reduce FRI's contribution to prover time and proof size by an order of magnitude.

DEEP-ALI

The base STARK protocol (Chapter 15) commits to two things separately: the trace polynomials and the composition polynomial. Recall that the composition polynomial is the random linear combination of all constraint quotients, a single polynomial whose low-degree-ness certifies that every transition and boundary constraint is satisfied. Committing it requires a full Merkle tree of leaves, just as expensive as the trace commitment. DEEP-ALI eliminates this second commitment entirely. As a bonus, it tightens FRI's per-query soundness.

The mechanism is a redirection. The composition polynomial is built algebraically from the trace polynomials and known constraint equations. For a Fibonacci-like recurrence , for example, the composition polynomial has the form where is the vanishing polynomial of the trace domain. The key point is that is determined by and the constraint, not an independent object. Once the verifier knows 's value at a point, it can compute 's value at that point with no help from the prover.

DEEP-ALI exploits this. Instead of asking the prover to commit separately, the verifier picks a random point outside the LDE domain and asks the prover to evaluate the trace at (and at , to capture the "next row" needed by transition constraints). The prover sends the values and for each trace column. The verifier plugs these into the constraint equations, divides by , and obtains on its own. No Merkle tree for is needed; one reconstructed value at one point is all the protocol uses.

For this redirection to be sound, the verifier must check that the prover's claimed evaluation actually agrees with the committed trace polynomial . The trick is the DEEP quotient: If the claimed is correct, this quotient is a polynomial of degree . If the claim is wrong, the numerator does not vanish at , so has a pole there and is not a polynomial at all. The prover batches all DEEP quotients into a single polynomial via random linear combination and runs FRI on the result. If FRI accepts (i.e., the batched quotient is close to low-degree), then with overwhelming probability every was honest, which means the verifier's reconstructed composition value is correct, which means all constraints hold.

To make the contrast concrete, consider the Fibonacci AIR with one trace column of length and blowup (LDE domain size ).

Without DEEP-ALI, the prover's commitment phase is:

  1. Build a Merkle tree over the trace evaluations on (one tree).
  2. Compute the composition polynomial on (one full NTT).
  3. Build a Merkle tree over the composition evaluations on (a second tree).
  4. Run FRI on (folding rounds and additional Merkle trees).

With DEEP-ALI, the same phase becomes:

  1. Build a Merkle tree over the trace evaluations on (one tree).
  2. Receive a random point from the verifier.
  3. Send and (two field elements).
  4. Form the DEEP quotient on (a divide-by-linear pass, cheaper than a full NTT).
  5. Run FRI on .

The composition polynomial never gets an NTT and never gets a Merkle tree. The prover's only added work is computing two trace evaluations at and forming the DEEP quotient. The verifier's only added work is computing from and via the constraint formula, a constant-time operation. (For traces with multiple columns, all DEEP quotients combine into a single polynomial via batched FRI, the optimization covered in the next subsection.)

The first benefit is direct: the prover saves the entire composition-polynomial Merkle tree, leaves of hashing, plus the corresponding NTT to evaluate the composition polynomial on . For typical parameters this is a 30-50% reduction in commitment work.

The second benefit is subtler. Standard FRI has a per-query soundness gap: a cheating prover who deviates from low-degree only on a small subset of might still pass FRI's spot-checks. By demanding answers at a point chosen outside , DEEP-ALI closes this gap. A cheater can fudge values inside to look low-degree but cannot anticipate where will land. The DEEP-FRI paper (Ben-Sasson et al., 2019) proves this raises per-query soundness from a constant below to arbitrarily close to 1.

The underlying heuristic generalizes: any polynomial that is algebraically determined by already-committed polynomials does not need to be committed. Evaluating it at a single random point suffices, since the verifier can reconstruct the value from the committed components. The composition polynomial is the obvious case, but the same principle reappears in sum-check-based systems (Chapter 21) under the name virtual polynomials. DEEP-ALI is the STARK-side instance of this idea.

In practice, DEEP-ALI is a strict improvement: it removes a Merkle tree, removes an NTT, and tightens soundness. There is no tradeoff against it. It is universal in production STARK provers.

Grinding

Each FRI query buys bits of security (under the conjectured analysis) but adds proof bytes: a query requires opening Merkle paths across every committed layer, costing tens of KB per query at typical parameters. The query count therefore sets the proof size. At , achieving 128-bit security requires 128 queries, which produces a proof of several megabytes. The question grinding answers: can the prover trade computation for proof bytes, paying CPU time at proving to reduce the number of queries needed?

The mechanism is a hash puzzle. After the FRI commitment phase ends, the verifier's query positions are determined by hashing the transcript. The prover is required to find a 64-bit nonce such that hashing (transcript nonce) yields a digest with leading zero bits. Such a nonce exists with probability per attempt, so finding one costs hash evaluations on average. Crucially, the puzzle binds every committed value: a cheating prover who alters any Merkle root changes the hash input and must restart the search from scratch. Inverting a -bit hash prefix costs work, so grinding contributes exactly bits of security to the total budget.

To make the trade concrete, consider a target of 128-bit security at and trace width over BabyBear with rows.

Without grinding: 128 queries are needed (1 bit per query). Each query opens Merkle paths of depth for each of the 40 columns at 32 bytes per hash, costing KB of authentication paths. Total proof contribution from queries: KB MB.

With bits of grinding: only queries are needed. Proof size from queries drops to KB MB, a savings of roughly 520 KB. The grinding cost is hash evaluations, which a modern CPU completes in under a millisecond. Trading sub-millisecond compute for half a megabyte of proof is an extraordinarily favorable trade.

The heuristic that emerges: grinding is essentially free up to the point where hash evaluations approach the prover's other costs. For modern provers running in seconds, between 16 and 32 fits comfortably under "free" and shaves substantial proof bytes. Beyond , grinding starts taking measurable wall-clock time (4 billion hashes), and the marginal proof savings shrink. Production systems converge on this range: ethSTARK specifies 32 bits of grinding, RISC Zero uses 16, and typical BabyBear configurations land between 15 and 24.

Batched FRI

A STARK prover typically needs to prove low-degree-ness of many polynomials over the same domain: each of the trace columns (or, with DEEP-ALI, each DEEP quotient). The naive approach runs an independent FRI instance for each one, which means independent folding rounds and independent Merkle trees per round. For trace columns and folding rounds, this is Merkle trees built during FRI alone, dominating commitment costs. Batched FRI replaces all of these with a single FRI instance, paying for one set of folding rounds total.

The mechanism is random linear combination. The verifier provides challenges via Fiat-Shamir, and the prover forms: A single FRI instance then proves has degree less than . The soundness argument is the same one that makes random linear combinations work throughout this book: if any violates the degree bound, the combination inherits that violation unless the verifier-chosen accidentally cancel it. This is exactly the situation governed by the proximity gap conjecture from the blowup-factor section above. Under the conjectured analysis, the cancellation probability is negligible (Schwartz-Zippel bounds the linear case at over a 124-bit extension field). Under the proven analysis, the bound is weaker by a constant factor, costing a few extra queries to compensate.

To make the savings concrete, take the same example as before: trace columns (or DEEP quotients), trace length , blowup . The first FRI fold operates on a polynomial over the -element LDE domain, with subsequent folds halving each time, giving folding rounds in total.

Without batching, each round builds 50 separate Merkle trees (one per polynomial). The first round alone hashes leaves. Across 22 rounds the geometric series doubles this, totaling hashes for FRI commitments.

With batching, each round builds one Merkle tree. The first round hashes leaves; the geometric series across rounds totals hashes. That is a 50× reduction in FRI Merkle work, exactly the trace width.

The cost of batching is small: one extension multiplication per polynomial per LDE point during the random linear combination step, a single pass. For typical parameters this is a few percent of the total prover time, far less than the FRI hashing it eliminates.

Combined with DEEP-ALI, the batched polynomial incorporates both the DEEP quotients and the trace polynomials in a single linear combination, so one FRI instance simultaneously handles degree verification, out-of-domain evaluation consistency, and composition polynomial correctness.

The heuristic: any time a prover needs to prove low-degree-ness of multiple polynomials over the same domain, batching wins. The savings scale linearly with the number of polynomials being batched, and the soundness loss is negligible. No production STARK system runs FRI without it.

Circle FRI

The small-field story has a gap. BabyBear () supports NTTs natively because has a large power-of-two factor (). But Mersenne31 () has the cheapest arithmetic of any 31-bit prime, since reduction is a single addition plus a conditional subtract. Its multiplicative group order has only one factor of 2, far too few for a power-of-two NTT domain. The cheapest field cannot use the standard algorithm.

Circle STARKs (Chapter 15) resolve this by replacing the multiplicative group with the circle group , which has order , a perfect power of two. Circle FRI adapts the FRI folding protocol to this group structure.

Polynomials on the circle are not standard univariates but elements of a Riemann-Roch space, consisting of polynomials modulo the relation . This means terms reduce to , so every polynomial expression on the circle involves at most linearly.

The first round of Circle FRI exploits the -symmetry. For opposite points and on the circle, the folding decomposes a function into even and odd parts:

Given a random challenge , the folded function is , now depending only on . This halves the domain.

Subsequent rounds use the doubling map , which arises from the angle-doubling formula . Opposite -values (points at angles and ) map to the same doubled coordinate. The folding at each subsequent round is:

Each round halves the domain, just as standard FRI halves via the squaring map . The total work across all rounds forms the same geometric series, giving total field operations for the folding itself.

The takeaway is that Circle FRI delivers the algorithmic capabilities of standard FRI (low-degree testing via halving folds, NTTs, folding work) over a field where standard FRI cannot run. The win is the ability to use Mersenne31 arithmetic, whose modular reduction is roughly 1.4× faster per multiplication than BabyBear. The Circle STARKs paper (Haböck, Levit, Papini, 2024) measures this speedup directly on real workloads. Combined with the 4× advantage of 31-bit over 64-bit arithmetic that motivated small fields in the first place, Circle STARKs over Mersenne31 represent the current frontier of STARK proving speed and are deployed in Stwo (StarkWare), Plonky3 (Polygon), and Airbender (ZKsync).

The design rule: if your prover's bottleneck is field arithmetic and you can build the rest of your stack (extension fields, hash function, recursion) over Mersenne31, Circle FRI is worth the additional machinery. If the bottleneck is elsewhere (constraint evaluation, Merkle hashing with an expensive hash function), the BabyBear/standard-FRI combination is simpler and gives most of the same benefit.


A worked example: proving Poseidon2 hashes

The chapter has introduced optimizations one at a time. To see how they compound, consider a single concrete task and trace what each optimization saves. The task: prove 1024 invocations of the Poseidon2 hash function. We will work through two configurations in parallel, a naive baseline and an optimized prover, and compare the cost at each stage.

The computation. Poseidon2 with state width 16 uses 8 full rounds and 14 partial rounds per permutation, for 22 rounds total. Each round applies a non-linear function called an S-box (substitution box, the standard term for the non-linear component of a hash or block cipher) to one or more state elements; in Poseidon2 the S-box is simply . Full rounds apply it to all 16 state elements; partial rounds apply it to only one. After each S-box, an MDS matrix linearly mixes the state. The total trace length for 1024 hashes is rows, rounded up to for NTT compatibility.

Configuration A: naive baseline. Use BN254 (a 254-bit field), encode each S-box directly as a degree-5 constraint, commit the composition polynomial separately, run a fresh FRI instance per polynomial.

  • Field arithmetic: each multiplication is 30-50 cycles (multi-limb 254-bit operations).
  • Trace columns: (just the state elements).
  • Constraint degree: (degree of ).
  • LDE blowup: (must satisfy ).
  • LDE domain size: .
  • Per-column NTT: multiplications. Across 16 trace columns plus a composition polynomial of degree-bound , total NTT work is roughly multiplications times another factor of , giving multiplications. At 30 cycles each on a 3 GHz core, that is seconds for NTT alone.
  • Commitment: two large Merkle trees (trace + composition), hash invocations.
  • FRI: 16 separate FRI instances (one per trace column) plus one for the composition polynomial.

Configuration B: optimized prover. Switch to Mersenne31 with Circle STARKs, decompose S-boxes into auxiliary columns to reduce degree, apply DEEP-ALI to skip the composition commitment, batch all FRI instances into one, add 20 bits of grinding.

  • Field arithmetic: each multiplication is roughly 3 cycles (single 32-bit multiply + Mersenne reduction), with 8-wide AVX2 SIMD.
  • Trace columns: ( state + auxiliary columns averaged across full and partial rounds).
  • Constraint degree: (using via the auxiliary column, splitting into degree-2 and degree-3 checks).
  • LDE blowup: (the smallest practical value, compensated by grinding).
  • LDE domain size: .
  • Per-column NTT: M31 multiplications.
  • Total NTT across 24 columns: multiplications.
  • With SIMD throughput of multiplications per second on a single core, NTT wall time: ms.
  • Commitment: one Merkle tree on the trace, leaves of 24 field elements each, Poseidon2 hash invocations. No composition polynomial commitment (DEEP-ALI).
  • FRI: one batched instance (all 24 DEEP quotients combined).
  • Queries: 108 (128 bits target − 20 bits grinding) at .

Where the savings come from. Comparing the two configurations stage by stage:

StageNaive (Config A)Optimized (Config B)Source of savings
Field multiplication cost30-50 cycles cycles, 8-wide SIMDMersenne31 + small fields
Constraint degree53Auxiliary columns
Blowup factor82Allowed by lower
LDE domain sizeLower
NTT workSmaller domain, more columns offset by lower
NTT wall time ms msAll of the above plus SIMD
Merkle trees committed (trace + composition) (trace only)DEEP-ALI
FRI instancesBatched FRI
Queries128 (no grinding)108Grinding shifts 20 bits

The optimized prover finishes in a few milliseconds where the naive baseline would take hundreds of milliseconds, a roughly improvement. No single optimization in the table is worth more than a single-digit factor on its own; the orders-of-magnitude gap comes from the stack. This matches the trajectory described in the chapter introduction, where production STARK provers improved by 50× from algorithmic and field-choice optimizations alone, with commodity hardware delivering the rest.


Comparison with sum-check optimization

Chapters 19 and 20 solve the same problem from opposite directions. The techniques differ because the cost structures differ.

Sum-check provers run in field operations. The bottleneck lies in the sum-check rounds themselves plus polynomial commitment openings. Optimization focuses on reducing cost per operation through small-value tricks, delayed binding, Karatsuba for high-degree products. Polynomial commitments (MSM for curve-based schemes) are a separate cost center, often the dominant one, addressed in Chapter 21.

STARK provers pay an NTT cost that sum-check avoids entirely, since multilinear polynomials need no Fourier transform. But their Merkle commitments are vastly cheaper than the MSMs that curve-based sum-check systems require. A Merkle commitment costs one hash per element; a KZG commitment costs one elliptic curve scalar multiplication per element, roughly 3000× more expensive per operation.

Both traditions exploit the principle of doing most work in cheap arithmetic. Sum-check provers work over 256-bit fields but exploit small witness values for fast ss/sb multiplications in early rounds (Chapter 19). STARK provers work over 31-bit base fields, lifting to 124-bit extension fields only for verifier challenges. The mechanism differs (small values within a large field vs. a genuinely small field with extensions) but the economics are identical: the prover's heaviest rounds coincide with the regime where the cheapest arithmetic applies.

A second shared principle is the avoidance of unnecessary commitments. DEEP-ALI (this chapter) eliminates the composition polynomial commitment by exploiting that the composition polynomial is algebraically determined by the trace. Sum-check systems take the same idea further with virtual polynomials (Chapter 21): any polynomial computable from already-committed polynomials can be evaluated at a verifier-chosen point without ever being committed. The principle generalizes: derived data should not be paid for twice. Different systems implement this differently (DEEP quotients, virtual polynomials, quotient-free PCS designs), but the underlying observation is the same.

Neither tradition dominates universally. STARKs pay overhead per element but get cheap commitments. Sum-check achieves linear time but faces expensive commitments. At small scales with structured computations (hashing), STARKs excel because their Merkle-based commitments scale linearly while curve-based MSMs grow superlinearly. At large scales with sparse constraints, sum-check's sparse proving (Chapter 19) pulls ahead because the NTT processes the entire trace regardless of sparsity.

The convergence of the two traditions is already underway. Binius (Chapter 26) uses sum-check over binary tower fields with FRI-based commitments, combining sum-check's linear-time proving with hash-based post-quantum commitments. Systems like Plonky3 support both quotienting-based and sum-check-based frontends over the same small-field backend. Chapter 22 develops this comparison fully.


Key takeaways

  1. The STARK prover bottleneck shifts with scale. Constraint evaluation dominates for small traces; the NTT dominates for medium traces ( to ); Merkle hashing dominates at the largest scales. The crossover ratio is , the cost of one hash relative to one field multiplication; the larger , the earlier hashing takes over. Optimization must address whichever stage currently dominates.

  2. AIR design is the highest-leverage optimization. Two encodings of the same computation can differ in prover time by an order of magnitude because they feed different and into the pipeline. Width is an additive cost on a few stages; degree is a multiplicative cost on the entire pipeline downstream of the composition polynomial. The design rule: add columns to reduce degree until stops crossing power-of-two boundaries downward.

  3. Interaction columns are a requirement, not an optimization. A pure AIR sees only adjacent rows and cannot express memory consistency, lookup arguments, or any constraint relating distant rows. LogUp uses verifier randomness to convert global multiset equalities into local accumulator constraints, at the cost of a few extra columns per bus. Without this mechanism, AIR-based zkVMs would be impossible.

  4. Small fields exploit the hardware register hierarchy. A 31-bit prime is the largest whose product fits in a single 64-bit register, eliminating multi-limb arithmetic. Combined with SIMD packing (16 elements per 512-bit vector), small fields deliver 10× per-element speedup over 256-bit fields. Extension fields supply the missing soundness for verifier challenges, at a cost of overhead relative to base field NTT work.

  5. The NTT optimizes by fitting into cache, not by algorithmic improvement. The four-step decomposition restructures a size- NTT into small NTTs that fit in L1 cache, eliminating the cache misses that dominate naive implementations. The asymptotic stays but the constant factor improves dramatically because the CPU stops waiting for RAM.

  6. The blowup factor trades prover speed against proof size. Larger means each FRI query catches a cheater with higher probability, so fewer queries are needed and proofs are smaller, but every NTT and Merkle tree grows proportionally. Most production systems use with grinding to compensate.

  7. Many "optimizations" are really commitment avoidance. DEEP-ALI eliminates the composition polynomial commitment by reconstructing it from an out-of-domain trace evaluation; the same principle reappears as virtual polynomials in sum-check systems (Chapter 21). The general rule: any polynomial algebraically determined by already-committed polynomials does not need its own commitment. Evaluating it at one verifier-chosen point suffices.

  8. Random linear combination is the universal batching technique. Batched FRI replaces separate FRI instances with one by combining all polynomials into a random linear combination, reducing FRI hashing by a factor of . The soundness rests on the proximity gap conjecture, whose late-2025 partial refutation has driven the ecosystem toward larger blowup factors and provable-security parameter regimes.

  9. Grinding is essentially free up to . Replacing FRI queries with proof-of-work shrinks proof size at sub-millisecond compute cost. Beyond 32 bits of grinding the wall-clock cost becomes noticeable, so production systems converge on between 16 and 32.

  10. Circle STARKs unlock Mersenne31. The circle group over has order , enabling NTT-like algorithms (via the doubling map ) over the field with the cheapest arithmetic of any 31-bit prime. Production provers using this stack achieve over 500,000 Poseidon2 hashes per second on commodity hardware.

  11. No single optimization is worth more than a single-digit factor. The 50-300× speedup achieved by modern STARK provers compared to early ones comes from compounding many small wins: small fields, extension lifting, AIR width tuning, four-step NTT, DEEP-ALI, batched FRI, grinding, Circle STARKs. Each contributes individually; none replaces the others.

  12. STARKs and sum-check systems converge on the same principles via different mechanisms. Both push the bulk of work into cheap arithmetic (small fields with extensions vs. small values within large fields). Both avoid unnecessary commitments (DEEP-ALI vs. virtual polynomials). The pipelines differ ( NTT for STARKs vs. sum-check; cheap Merkle commitments for STARKs vs. expensive MSMs for curve-based sum-check), but the design philosophies rhyme. Chapter 22 develops this comparison.

Chapter 21: Minimizing Commitment Costs

This chapter closes Part VI (Prover Optimization, Chapters 19-21), which is optional on a first read. The rest of the book does not depend on it. The material here is essential for anyone designing or implementing a fast prover.

This chapter lives at the frontier. The techniques here, some from papers published in 2024 and 2025, represent the current edge of what's known about fast proving. We assume comfort with polynomial commitments (Chapter 9), sum-check (Chapter 3), and the memory checking ideas from Chapter 14. First-time readers may find themselves reaching for earlier chapters often; that's expected. The reward for persisting is a view of how the fastest SNARKs actually work.

Profile any modern SNARK prover and the same pattern appears. The proving algorithm touches each constraint once. The information-theoretic protocol is near-optimal. Yet wall-clock time is dominated by something else entirely: polynomial commitments.

For elliptic curve-based systems, the bottleneck is multi-scalar multiplication (MSM): computing where each is a scalar and each is a curve point. A single curve exponentiation costs roughly 3,000 field multiplications. An MSM over points costs about exponentiations. For a polynomial of degree , commitment alone requires field operations, while the proving algorithm itself, after the linear-time sum-check techniques of Chapter 19, runs in only . The cryptography dwarfs the algebra. The two surrounding chapters develop the rest of the picture: Chapter 19 establishes why sum-check provers are now fast enough that commitments dominate, and Chapter 20 traces the STARK-side optimization story, where the bottleneck instead concentrates in NTT and hashing because FRI absorbs the commitment cost into the prover pipeline.

This chapter focuses on the elliptic curve setting, where sum-check-based minimization techniques apply most directly.

This observation crystallizes into a design principle: commit to as little as possible. Not zero (some commitment is necessary for succinctness) but the absolute minimum required for soundness.

This chapter develops the techniques that make minimization possible. Together with fast sum-check proving, they form the foundation of the fastest modern SNARKs.

The Two-Stage Paradigm

Every modern SNARK decomposes into two phases. First, the prover commits to the witness, to intermediate values, and to auxiliary polynomials that will help later proofs. Second, the prover runs an interactive argument that demonstrates those committed objects satisfy the required constraints.

Both phases cost time. And here's the trap: more commitment means more proving. Every committed object must later be shown well-formed. If you commit to a polynomial, you'll eventually need to prove something about it: its evaluations, its degree, its relationship to other polynomials. Each such proof compounds the cost.

The obvious extremes are both suboptimal. Commit nothing, and proofs cannot be succinct: the verifier must read the entire witness. Commit everything, and you drown in overhead: each intermediate value requires cryptographic operations and well-formedness proofs.

The art lies in the middle: commit to exactly what enables succinct verification. No more.

Untrusted Advice

Sometimes the sweet spot involves enlarging the witness: adding extra values that the prover must compute alongside the original ones. The witness is what gets committed, so adding a few helper values just makes the same witness polynomial slightly longer. The trade-off can be favorable: the extra values often let the constraint system avoid hard operations entirely.

Consider division. Proving "I correctly computed " by directly encoding division as a constraint is expensive, since division is not a native operation in polynomial constraint systems. The constraint system speaks the language of multiplication and addition over a finite field, not Euclidean division.

The workaround is to enlarge the witness with the quotient and remainder , and then verify the multiplicative identity:

  1. The prover adds and to the witness vector. They are committed as part of the same polynomial(s) that already hold and , with no separate commitment object.
  2. The constraint system enforces and .

Every value lives inside the committed witness polynomial; the verifier never sees any of them in the clear. The constraint is checked the same way every other constraint is: as a polynomial identity opened at a random point via the PCS. The win is that this identity uses only multiplication and a range check, both native, instead of requiring the constraint system to implement division. The prover paid for slightly more witness entries to avoid encoding a hard operation, and the verifier never had to learn what and actually are.

This pattern is called untrusted advice: the prover volunteers additional witness data that, if the constraints check out, accelerates the overall proof. The verifier does not trust the advice blindly; the constraints guarantee it is consistent with the original claim.

The trade-off is specific: we pay for a slightly longer witness polynomial (more entries to commit, so a slightly larger MSM) to save on constraint degree. The constraints that check the enlarged witness can be lower-degree than the constraints that would have encoded the hard operation directly. Since high-degree constraints are expensive to prove via sum-check, the exchange often favors a longer witness with simpler constraints.

The pattern generalizes. Any computation with an efficient verification shortcut benefits:

Square roots. To prove , the prover commits to and proves and . One multiplication plus a range check, rather than implementing the square root algorithm in constraints.

Sorting. To prove a list is sorted, the prover commits to the sorted output and proves: (1) it's a permutation of the input (via permutation argument), and (2) adjacent elements satisfy . Linear comparisons rather than sorting constraints.

Inverses. To prove , commit to and check . Field inversion (expensive to express directly) becomes a single multiplication.

Exponentiation. To prove , the prover commits to and all intermediate values from the square-and-multiply algorithm: . Each step satisfies (if bit ) or (if ). Verifying quadratic constraints is far cheaper than expressing the full exponentiation logic.

Whenever verifying a result costs less than computing it, the prover should compute and commit while the constraint system only checks. The prover bears the computational burden; the constraint system bears only the verification burden. This division of labor is the essence of succinct proofs, now applied within the proof system itself.

Batch Evaluation Arguments

Suppose the prover has committed to addresses and claimed read results , the values the prover claims it received from each lookup. A public function is known to all. The prover wants to demonstrate:

One approach: prove each evaluation separately. That's independent proofs, linear in the number of evaluations. Can we do better?

Think of as a memory array indexed by -bit addresses. Each pair is a read operation, "I read value from address ," and the prover claims all reads are consistent with the memory . (Later in this chapter we will see that this read-only setting is the simpler half of a more general memory checking problem, where the table itself can be updated over time.)

One approach uses lookup arguments (Chapter 14), proving that each pair exists in the table . But sum-check offers a more direct path that exploits the structure of the problem.

Three Flavors of Batching

Before diving into sum-check, let's map the batching landscape. The term "batching" appears throughout this book, but it means different things in different contexts.

Approach 1: Batching verification equations. The simplest form. Suppose you have equations to check: . Sample a random and check the single combined equation . By Schwartz-Zippel, if any original equation fails, the combined equation fails with high probability. This reduces verification checks to one.

Chapter 2 uses this for Schnorr batch verification. Chapter 13 uses it to combine PLONK's constraint polynomials. Chapter 15 uses it to merge STARK quotients. The pattern is ubiquitous: random linear combination collapses many checks into one.

Approach 2: Batching PCS openings. Polynomial commitment schemes often support proving multiple evaluations cheaper than proving each separately. KZG's batch opening (Chapter 9) proves with a single group element, using the quotient where is the interpolant of the claimed evaluations and is the vanishing polynomial of the query points. This quotient exists as a polynomial iff every claimed evaluation is correct, so its commitment doubles as the batch proof. Proof size stays constant regardless of . This batching is PCS-specific; other schemes have different mechanisms.

Approach 3: Batching via domain-level sum-check. This is what this section develops. Rather than batch the claims directly, we restructure the problem as a sum over the domain of . The key equation:

This sum nominally has terms (one per address in the domain), but is sparse: out of possible entries, only are non-zero, since each access touches exactly one address. Sum-check exploits this sparsity in the access matrix, not in itself ( can be perfectly dense). At the end of the protocol, the verifier needs a single evaluation at a random point: one PCS opening, not .

Comparing the three approaches

The three approaches batch at different levels, and that is what determines what each one saves. Approaches 1 and 2 operate at the claim level: the prover must still open at all points . Approach 1 saves verifier work (one check instead of ) but does not reduce openings; Approach 2 compresses the proof but still requires the prover to compute all evaluations internally. Approach 3 batches at the domain level: the point evaluations collapse into a single random evaluation, and the prover opens at exactly one point.

Each approach therefore answers a different question.

Approach 1 (batch verification equations) answers "I have many unrelated checks; can the verifier handle them in one shot?" Use it whenever you have multiple equations to verify, even outside the PCS setting. The combiner is just transcript-level randomness, costing nothing beyond sampling one field element. The prover does the same work either way; only verifier work shrinks. This is what PLONK uses to combine constraint polynomials and what STARK quotient batching uses.

Approach 2 (PCS batch opening) answers "I have one committed polynomial; how do I send many opening proofs in one go?" Use it when is already committed (typically via KZG) and you need to prove evaluations at multiple points. The win is purely in proof size: one group element instead of . The prover still computes all evaluations internally and does the corresponding MSM work; nothing about 's structure or the access pattern matters.

Approach 3 (sum-check over the domain) answers "I have many evaluations of the same polynomial with structured access; can the prover do less work overall?" Use it when (a) you are proving many evaluations of the same , and (b) the access pattern has structure the sum-check can exploit, in particular the one-hot or tensor-decomposable structure of the access matrix . Crucially, this is structure in how the polynomial is queried, not structure in the polynomial itself. The decisive parameters are (number of accesses) and (domain size): when , exploiting the access sparsity is what makes accesses to a -sized table feasible. Without that structure, Approach 3 has nothing to exploit and Approach 2 is simpler.

There is a deeper connection across all three. Evaluating an MLE at a random point is a random linear combination, weighted by the Lagrange basis rather than powers of . The sum-check formulation in Approach 3 is random linear combination in MLE clothing, but operating at the domain level unlocks optimizations that claim-level batching (Approaches 1 and 2) cannot reach.

The Sum-Check Approach

Now we develop Approach 3 in detail. Let be the multilinear extension of . The access matrix from the previous section is the Boolean matrix with iff , so each column is one-hot at the row corresponding to address .

Example. Suppose is defined on 2-bit addresses , and we have accesses to addresses , , . The access matrix is:

Each column encodes "which address did access hit?" as a one-hot vector: column equals the basis vector . Here column 1 is (since ), column 2 is (since ), and column 3 is again (since ).

For a single evaluation, we can write:

This looks like overkill. The one-hot structure of zeroes out every term except the one at address , so the sum trivially collapses to . Why bother?

The heuristic that turns this into a single check is the multilinear extension trick used throughout the book: lift a vector of values defined on the Boolean hypercube into a polynomial on the full field, then evaluate that polynomial at one random point off the hypercube. By Schwartz-Zippel, that one evaluation catches any error in the original vector with overwhelming probability.

Define the "error" at index as the gap between the claimed output and what the lookup should return:

There are such errors, one per access. All evaluations are correct iff for every . Checking separate equalities defeats the purpose of batching, so we apply the trick. The vector is defined on the hypercube . Its multilinear extension is a polynomial on , and is the zero polynomial iff every . The verifier picks a random and asks: is ? If all vanish, the answer is yes for any ; if any is non-zero, Schwartz-Zippel says the answer is no with overwhelming probability. One evaluation, checks collapsed.

Substituting the definition of and using the linearity of the MLE construction, the check becomes:

If this single identity holds at the random , all original evaluations are correct with high probability. The separate access claims have collapsed into one identity over the entire domain , which sum-check is built to prove.

Sum-check proves this identity. The prover commits to and , then runs sum-check to verify consistency with the public .

The Sparsity Advantage

The sum nominally ranges over all addresses, potentially enormous (imagine for CPU word operations). The reason it stays tractable is the structure of the access matrix. A vector or matrix is one-hot if every column contains exactly one non-zero entry, and that entry equals 1. The access matrix is one-hot by construction: each access touches exactly one address , so column has a 1 at row and zeros everywhere else.

The consequence is dramatic. The matrix has dimensions with , so naively it has entries, but only of them are non-zero. Any sum that appears to range over positions actually touches only the non-zero terms. This is why batch evaluation costs rather than : the one-hot structure makes the exponentially large table effectively linear-sized. When (as in Jolt's instruction lookups), this is the difference between tractable and impossible.

One-hotness handles the access side (only non-zero terms in ) but the sum still nominally folds the dense polynomial over the full -element domain. Naive sum-check over this dense factor still costs . The prefix-suffix algorithm from Chapter 19 closes the remaining gap: by splitting the variables into halves and running two chained sum-checks, the dense work shrinks from to for any constant . Combined with the one-hot access, the prover runs in total. Compared to proving each evaluation separately (which costs just to state the claims), the batch approach matches the lower bound while providing cryptographic guarantees.

Virtual Polynomials

Start with a toy case. Suppose the prover has committed to multilinear polynomials and , and the protocol later refers to . Should the prover separately commit to ?

No, because contains no information beyond what is already in and . Whenever the verifier needs at a random point , the protocol can ask for and instead, then compute locally. The polynomial is virtual: it exists implicitly through the formula , never committed, never stored. The prover saves one MSM; the verifier loses nothing.

The general principle behind virtualization is that any polynomial algebraically determined by already-committed polynomials does not need its own commitment. Whenever the verifier needs an evaluation of the virtual polynomial at some point , the protocol reduces that demand to evaluations of the source polynomials at , and the verifier reconstructs the result from the formula. The savings cascade: if a virtual polynomial's sources are themselves virtual, the same trick applies recursively, and only the root polynomials in the dependency graph ever get committed.

This principle is what makes the access matrix tractable. In our batch evaluation, has rows (one per possible address) and columns (one per access). For a zkVM with 32-bit addresses, , so the matrix has billions of entries. Committing to it directly is impossible. The escape is to not commit as a single object: instead, decompose it into smaller pieces that can be committed and treat the full as virtual. The next subsection develops this decomposition.

Tensor Decomposition

The access matrix is the natural target for virtualization, but virtualization needs source polynomials to factor through. The trick is that addresses themselves are bit strings, and matching an -bit address means matching every bit. We can therefore factor the address-match into separate per-chunk matches, each over a much smaller space.

Concretely, an address splits into chunks of bits each:

For each chunk , define a smaller access matrix where iff the -th chunk of access equals . Each has dimensions , exponentially smaller than the original .

The full access happens when every chunk matches, which is exactly the product:

The original never gets committed. The prover commits only to the small matrices , and the full exists virtually through this product formula.

Example. Return to our 2-bit addresses with accesses , , . Split each address into chunks of 1 bit each: , , .

The chunk matrices are (columns: ):

In : row 0 has 1s in columns 1 and 3 because accesses and have first bit 0. Row 1 has a 1 in column 2 because has first bit 1.

In : row 1 has 1s in all columns because all three accesses () have second bit 1.

To recover : check . Indeed, access 1 hit address 01. For : . Access 1 did not hit address 10.

Instead of one matrix (12 entries), we store two matrices (12 entries total, same here, but the savings grow with ).

The commitment savings are dramatic. Instead of a matrix, the prover commits to matrices of size each. For and : from to .

The exponential has become polynomial.

Virtualizing Everything

Once you see virtualization, you see it everywhere. The product example above is the smallest case; in real systems the same principle applies to entire computation traces. A zkVM executing a million instructions touches several polynomials per instruction: opcode, operands, intermediate values, flags. Naive commitment requires millions of polynomials, each with its own MSM. Virtualization reduces this to perhaps a dozen root polynomials, with everything else derived. The difference is a 30-second proof versus a 3-second proof.

The read values need not exist. Recall the batch evaluation setup: is the vector of read results, with being the value returned when the prover read address in step . These feel like primary data; in a zkVM they are exactly the values an instruction sees coming out of memory, and the rest of the computation depends on them. Surely they need to be committed?

They do not. The read results are completely determined by the access pattern (which addresses were read) and the table (what each address contains). Concretely:

The right side defines implicitly from and . The prover never commits to . When the verifier needs , sum-check reduces this evaluation to evaluations of and , both of which are already committed (the access matrix) or public (the table). The pattern is the same as from earlier, just with a sum instead of a product as the defining formula.

GKR as virtualization. The GKR protocol (Chapter 7) builds an entire verification strategy from this idea. A layered arithmetic circuit computes layer by layer from input to output. The naive approach commits to every layer's values. GKR commits to almost nothing:

Let denote the multilinear extension of gate values at layer . The layer reduction identity:

Each layer's values are virtual: defined via sum-check in terms of the previous layer. Iterate from output to input: only (the input layer) is ever committed. A circuit with 100 layers has 99 virtual layers that exist only as claims passed through sum-check reductions.

More examples. The pattern appears throughout modern SNARKs.

  • Constraint polynomials. In Spartan (Chapter 19), the polynomial is never committed. Sum-check verifies it equals zero on the hypercube by evaluating at random points.

  • Grand products. Permutation arguments express as a running product. Each is determined by and the current term. One starting value plus a recurrence defines everything.

  • Folding. In Nova (Chapter 23), the accumulated instance is virtual. Each fold updates a claim about what could be verified (not data sitting in memory).

  • Write values from read values. In read-write memory checking, the prover commits to read addresses, write addresses, and increments . What about write values? They need not be committed: . The write value at cycle is the previous value at that address plus the change. Three committed objects define four.

The design principle that emerges from these examples is to ask not "what do I need to store?" but "what can I define implicitly?" Every polynomial expressible as a function of others is a candidate for virtualization. Every value recoverable from a sum-check reduction need never be committed. The fastest provers are the ones that commit least, because computation is cheap but cryptography is expensive.

Sum-checks as a DAG

The design principle above applies to individual polynomials, but virtualization at scale creates a structural picture worth seeing in its own right. When a sum-check ends at a random point and the polynomial it was reasoning about is virtual, the resulting evaluation claim has to be discharged by another sum-check. That second sum-check might itself end with a claim about another virtual polynomial, requiring a third, and so on. The dependencies form a directed acyclic graph (DAG): each sum-check is a node, the output claims it produces are outgoing edges, and the input claims it consumes are incoming edges. Committed polynomials are sources (no incoming edges from other sum-checks); the final opening proof is the sink.

The DAG induces a partial order, and that partial order determines the minimum number of stages the protocol must run in. Two sum-checks can share a stage only if neither depends on the other's output. The longest path in the DAG sets a lower bound on the number of stages: protocols with deep chains of virtualization unavoidably have many sequential rounds. Jolt, which proves RISC-V execution, runs roughly 40 sum-checks organized into 8 stages by this dependency structure.

Within each stage, independent sum-checks can be batched via random linear combination. Sample from the verifier's transcript, form , and run one sum-check on the combined claim. This is the horizontal dimension of optimization: batching within a stage. Stages are the vertical dimension: sequential dependencies that cannot be avoided. The design recipe for a fast prover is to map the full DAG, minimize the number of stages (constrained by the longest path), and batch every independent sum-check within each stage.

A small example illustrates the structure:

graph TD
    Claim["Top-level claim"]

    subgraph Stage1["Stage 1"]
        S1["sum-check A"]
    end

    subgraph Stage2["Stage 2"]
        S2a["sum-check B"]
        S2b["sum-check C"]
    end

    subgraph Stage3["Stage 3"]
        O1["open P₁"]
        O2["open P₂"]
        O3["open P₃"]
    end

    Claim --> S1
    S1 --> S2a
    S1 --> S2b
    S2a --> O1
    S2a --> O2
    S2b --> O2
    S2b --> O3

Read top-to-bottom for execution order. Stage 1 runs one sum-check that ends with two residual claims, both about virtual polynomials. Stage 2 discharges those residual claims with two independent sum-checks (B and C), which collapse into a single batched sum-check via random linear combination. Stage 3 discharges the resulting claims with PCS openings on the three committed polynomials, which collapse into a single batched opening.

The vertical axis (stages) is bounded by dependencies: stage 2 cannot start until stage 1 has produced its residual claims, and stage 3 cannot start until stage 2 is done. The horizontal axis within each stage is free, so anything independent collapses via batching. A protocol designer cannot shrink the height (stages) without restructuring the protocol's data dependencies, but they can always shrink the width by batching anything independent.

Time-Varying Functions

So far virtualization has applied to static objects: a derived polynomial , an access matrix that factors into chunks, a vector of read results determined by addresses and a fixed table. The next test for the principle is a moving target: state that changes over time. This is the third instance of the virtualization theme, now applied to the trickier case where the table being read evolves between accesses.

Batch evaluation proves claims of the form where is fixed. Real computation does not work that way. Registers change. Memory gets written. The lookup tables from Chapter 14 assume static data, yet a CPU's registers are anything but static. When a zkVM executes ADD R1, R2, R3, it reads R1 and R2, computes the sum, writes to R3. The next instruction might read R3 and get the new value. The value at R3 depends on when you query it.

The general phenomenon is the time-varying function problem. A function that gets updated at certain steps; a query at time returns the value held at that moment. The claim "I correctly evaluated " depends on the timing of the evaluation.

Setup and the Naive Cost

Formally, over time steps the computation performs operations on a table with entries. Each operation is either a read (query position , receive value ) or a write (set position to value ). The prover's job is to demonstrate that every read returns the value from the most recent write to that position.

The naive way to verify this is to commit to a matrix where entry records the value at position after step . For a zkVM with 32 registers and a million instructions, this is entries: expensive but conceivable. For RAM with addresses and a million instructions, this is entries, vastly beyond what any prover could commit. Direct commitment is impossible at zkVM scale.

This is exactly the situation virtualization was built for. The state table is enormous, but it is determined by the write history. We do not need to commit it; we need to commit only the data that uniquely determines it.

The Unified Principle

What lets us virtualize the state table is that read-only and time-varying tables turn out to share the same verification structure. Both answer the question "what value should this read return?" the same way: as a sum over positions, weighted by an access indicator, verified via sum-check. The only difference is whether the table itself is fixed or reconstructed from a write history. Throughout this subsection, is the table size (number of positions), is the number of operations, and we use the standard memory-checking notation: for read addresses, for read values (the same object as in the batch evaluation section), for write addresses, for write values. The parallel naming makes the read/write symmetry visible.

Recall the read-only case from the batch evaluation section: the value at position is just a fixed , the verification equation is , and the prover commits to the tensor-decomposed chunks of while leaving the read values virtual. The function itself is public or preprocessed; nothing about needs to be committed at all.

The read-write case has the same verification equation but with one critical change: now depends on time. Define as "what value is stored at position just before time ?" Then:

The challenge is that is now a table, far too large to commit. The previous trick (tensor decomposition) does not save us: the time-dependence does not factor through chunking the way an address does. We need a different escape, and virtualization provides it. The state table is determined by the write history, so we can reconstruct it from writes rather than store it. Let denote the address written to at step , and the value added to that address (zero if step is a read, non-zero if it is a write). Then:

Read this as a walk through history. For each past step , the indicator asks "did we write to address at step ?" If yes, include the increment ; if no, skip it. The sum picks out exactly the prior writes that targeted address and adds them to the initial value.

The massive state table dissolves into two sparse objects. The first is a -vector of write addresses . Just like the read addresses , each entry of is an -bit position in the same -sized table, so the same tensor decomposition applies: split each -bit address into chunks of bits, encode as smaller chunk matrices of size each, and treat the full as virtual through the product . Nothing about the read versus write distinction changes how chunking works; it depends only on addresses being bit strings. The second sparse object is a length- increment vector , which has no address structure to chunk and gets committed directly. The state table itself is virtual.

The committed objects in each case:

CaseCommittedVirtual
Read-only chunks, table (public)
Read-write chunks, chunks, , state table

The read-write prover commits to a few extra objects (write addresses and the increment vector) but never commits the state table. This is what makes time-varying memory tractable.

DataChanges?TechniqueCommittedVirtual
Instruction tablesNoRead-only chunks, table
BytecodeNoRead-only chunks, table
RegistersYesRead-write, chunks, , state
RAMYesRead-write, chunks, , state

Both techniques use the same sum-check structure. The difference is that read-only tables have fixed (public or preprocessed), while read-write tables have that must be virtualized from the write history.

Both paths lead to the same destination, where commitment cost is proportional to operations (not table size ). A table with entries costs no more to access than one with .

Why This Matters for Real Systems

In a zkVM proving a million CPU cycles, memory operations dominate the execution trace. Every instruction reads registers, many access RAM, all fetch from bytecode. A RISC-V instruction like lw t0, 0(sp) involves: one bytecode fetch (read-only), one register read for sp (read-write), one memory read (read-write), one register write to t0 (read-write). Four memory operations for one instruction.

If each memory operation required commitment proportional to memory size, proving would be impossible. A million instructions × four operations × addresses = commitments. The sun would burn out first.

The techniques above make it tractable. Registers, RAM, and bytecode all reduce to the same pattern: commit to addresses and values (or increments), virtualize everything else. The distinction between "read-only" and "read-write" is simply whether the table is fixed or must be reconstructed from writes.

What emerges is a surprising economy. A zkVM with bytes of addressable RAM, 32 registers, and a megabyte of bytecode commits roughly the same amount per cycle regardless of these sizes. The commitment cost tracks operations, not capacity. Memory becomes (in a sense) free. You pay for what you use, not what you could use.

There is a deeper connection worth noting. Circuit wiring (the copy-constraint problem from Chapter 13) is itself a memory access pattern. When the output of gate feeds into gate as an input, the circuit is "reading" a value that was "written" by gate . Quotienting-based systems handle this through permutation arguments (grand products over accumulated ratios). In the memory-checking framework developed here, the same constraint reduces to a read-write access pattern over a table of wire values, verified via the same / machinery. Chapter 22 develops this parallel explicitly, showing that wiring constraints are where the two PIOP paradigms diverge most sharply in abstraction while converging in purpose.

The Padding Problem and Jagged Commitments

We've virtualized polynomials, memory states, and intermediate circuit layers. But a subtler waste remains: the boundaries between different-sized objects.

This problem emerged when zkVM teams tried to build universal recursion circuits. Recursion (Chapter 23) is the technique of proving that a verifier accepted another proof, expressed as a circuit and proven about. The dream of universal recursion is one such circuit that can verify any program's proof, regardless of what instructions that program used, so the same recursive infrastructure handles every workload. The reality was that different programs have different instruction mixes, and the verifier circuit seemed to depend on those mixes.

The Problem: Tables of Different Sizes

A zkVM's computation trace comprises multiple tables, one per CPU instruction type. The ADD table holds every addition executed; the MULT table every multiplication; the LOAD table every memory read. These tables have wildly different sizes depending on what the program actually does.

Consider two programs:

  • Program A: heavy on arithmetic. 1 million ADDs, 500,000 MULTs, 10,000 LOADs.
  • Program B: heavy on memory. 100,000 ADDs, 50,000 MULTs, 800,000 LOADs.

Same total operations, but completely different table shapes. If the verifier circuit depends on these shapes, we need a different circuit for every possible program behavior. That's not universal recursion but combinatorial explosion.

Now we need to commit to all this data. What are our options?

Option 1: Commit to each table separately. Each table becomes its own polynomial commitment. The problem is that verifier cost scales linearly with the number of tables. In a real zkVM with dozens of instruction types and multiple columns per table, verification becomes expensive. Worse, in recursive proving, where we prove that a verifier accepted, each separate commitment adds complexity to the circuit we're proving.

Option 2: Pad everything to the same size. Put all tables in one big matrix, padding shorter tables with zeros until they match the longest. Now we commit once. The problem is that if the longest table has rows and the shortest has , we're committing to a million zeros for the short table. Across many tables, the wasted commitments dwarf the actual data.

Neither option is satisfactory. We want the efficiency of a single commitment without paying for empty space.

The Intuition: Stacking Books on a Shelf

Think of each table as a stack of books. The ADD table is a tall stack (many additions). The MULT table is shorter (fewer multiplications). The LOAD table is somewhere in between.

If we arrange them side by side, we get a jagged skyline: different heights and lots of empty space above the shorter stacks. Committing to the whole rectangular region wastes the empty space.

But what if we packed the books differently? Take all the books off the shelf and line them up end-to-end in a single row. The first million books come from ADD, the next 50,000 from MULT, then 200,000 from LOAD. No gaps and no wasted space. The total length equals exactly the number of actual books.

This is the jagged commitment idea, which is to pack different-sized tables into one dense array. We commit to the packed array (cheap and without wasted space) and separately tell the verifier where each table's data begins and ends.

A Concrete Example

Suppose we have three tiny tables:

TableDataHeight
A[a₀, a₁, a₂]3
B[b₀, b₁]2
C[c₀, c₁, c₂, c₃]4

If we arranged them as columns in a matrix, padding to height 4:

     A    B    C
0:  a₀   b₀   c₀
1:  a₁   b₁   c₁
2:  a₂    0   c₂
3:   0    0   c₃

We'd commit to 12 entries, but only 9 contain real data. The three zeros are waste.

Instead, pack them consecutively into a single array:

Index:  0   1   2   3   4   5   6   7   8
Value: a₀  a₁  a₂  b₀  b₁  c₀  c₁  c₂  c₃

Now we commit to exactly 9 values: the real data. We also record the cumulative heights: table A ends at index 3, table B ends at index 5, table C ends at index 9. Given these boundaries, we can recover which table any index belongs to, and its position within that table.

From Intuition to Protocol

Now formalize this. We have tables (columns), each with its own height . Arranged as a matrix, this forms a jagged function where is the row (up to ) and identifies the table. The function satisfies whenever row (beyond that table's height).

The total non-zero entries number . This sum is the trace area, the only quantity that actually matters for proving.

The prover packs all non-zero entries into a single dense array of length , deterministically: table 0's entries first, then table 1's, and so on. The 2D table with variable-height columns becomes a 1D array that skips the padding zeros entirely. We will call this operation flattening, since the variable-height skyline of the original tables is collapsed into a single flat row.

The cumulative heights track where each column starts in the flattened array. Given a dense index , two functions recover the original coordinates:

  • : the row within the padded table (offset from that column's start)
  • : which column belongs to (found by comparing against cumulative heights)

For example, with heights , the cumulative heights are (one entry per column, recording where each column starts in the dense array). The total trace area is , the position just past the last entry. Column 2 therefore occupies the range . Index falls in column 2 (since ) at row .

The prover commits to:

  • : the dense array of length containing all actual values
  • The cumulative heights , sent in the clear (just integers)

The jagged polynomial is never committed. It exists only as a relationship between the dense and the boundary information.

Making It Checkable

The verifier wants to query the original jagged polynomial and ask, "what is ?" This asks for a weighted combination of entries from table at rows weighted by .

The key equation translates this into a sum over the dense array:

The two factors are selectors. The first, , picks out entries belonging to the requested table; the second, , picks out entries at the requested row. Their product enforces double selection: a term contributes only when dense index maps to both the correct row and the correct column.

This is a sum over terms and exactly the sum-check form we've used throughout the chapter. The prover runs sum-check; at the end, the verifier needs at a random point (handled by the underlying PCS) and the selector function evaluated at that point.

The selector function (despite involving and ) is efficiently computable, since it's a simple comparison of against the cumulative heights. This comparison can be done by a small read-once branching program (essentially a specialized circuit that checks if an index falls within a specific range using very few operations). This means its multilinear extension evaluates in field operations.

Remark (Batching selector evaluations). During sum-check, the verifier must evaluate the selector function at each round's challenge point. With rounds, that's evaluations at each, totaling . A practical optimization: the prover claims all evaluations upfront, and the verifier batches them via random linear combination. Sample random , check where are the claimed values. The left side collapses to a single evaluation at a combined point. Cost drops from to .

The Payoff

The prover performs roughly field multiplications, or five per actual trace element, regardless of how elements are distributed across tables. The constant 5 comes from the sum-check structure: the summand is a product of three multilinear factors ( and the two selectors), giving a degree-3 polynomial in each variable. The halving trick from Chapter 19, applied to a degree- sum-check, costs roughly field multiplications across all rounds (the factor for folding each multilinear piece each round, the for forming the round polynomial's evaluations). With and , that lands at . No padding, no wasted commitment, and a constant that does not depend on table count or table heights.

For the verifier, something useful happens. The verification circuit depends only on (the log of total trace area), not on the individual table heights . Whether the trace has 100 tables of equal size or 100 tables of wildly varying sizes, the verifier does the same work.

This is the solution to the universal recursion problem from the beginning of this section. When proving proofs of proofs, the verifier circuit becomes the statement being proved. A circuit whose size depends on table configuration creates the combinatorial explosion we feared. But a circuit depending only on total trace area yields one universal recursion circuit.

One circuit to verify any program. The jagged boundaries dissolve into a single integer: total trace size.

The Deeper Point

Each virtualization earlier in this chapter replaced a polynomial with a formula: avoided committing ; the tensor decomposition avoided committing the full access matrix ; the write-history formula avoided committing the state table . In each case, the thing being virtualized was a value at each point.

Jagged commitments extend the same idea to structure. What gets virtualized is not a polynomial's values but its shape: the staircase of boundaries where each table ends. The prover never commits to the -sized jagged polynomial with its zeros above each table's height. Instead it commits to the dense -sized array and sends the cumulative heights in the clear. The boundary information (which index belongs to which table, at which row) exists only through the formula that compares an index against the heights. The zeros that a padded approach would waste space on were never real data; they were artifacts of forcing variable-height tables into a rectangular grid. Flattening eliminates the grid, and the boundaries become metadata rather than committed content.

This is the chapter's recurring theme pushed to its furthest application: ask not what exists but what can be computed. Values, access patterns, state history, and now shape itself dissolve into formulas over committed roots.

Small-Value Preservation

We've focused on what to commit, but how large the committed values are matters too. Real witness values are usually small: 8-bit bytes, 32-bit words, 64-bit addresses. These fit in a single machine word even though the protocol's field has 256-bit elements. The dominant cost in curve-based commitment, computing via double-and-add, scales as group operations. If is a 64-bit integer rather than a 256-bit field element, exponentiation takes 64 steps instead of 256, a 4× speedup. For an MSM over a million points, this translates to seconds of wall-clock time.

The optimization follows from keeping values small for as long as possible. Random challenges injected by the verifier are the main source of large field elements. Once a small witness value gets multiplied by a 256-bit challenge, the result is 256 bits and the cheapness is gone. A well-designed protocol postpones this inflation, arranging computations so that the bulk of the prover's work touches values that still fit in machine words. Jolt, Lasso, and related systems (Arasu et al., 2024) reported 4× prover speedups simply from tracking value sizes through the protocol, maintaining separate "small" and "large" categories for polynomials, and routing each to the appropriate arithmetic.

The impact compounds everywhere:

  • MSM with 64-bit scalars: 4× faster than 256-bit
  • Hashing small values has fewer field reductions
  • FFT with small inputs gives smaller intermediate values and fewer overflows
  • Sum-check products where inputs fit in 64 bits yield products that fit in 128 bits, so no modular reduction is needed

Modern sum-check-based systems track value sizes explicitly, and Jolt, Lasso, and related systems maintain separate "small" and "large" polynomial categories. Small polynomials get optimized 64-bit arithmetic. Large polynomials get full field operations. The boundary is tracked through the protocol.

The difference between a 10-second prover and a 2-second prover often lies in these details.

Key Takeaways

  1. Commitment dominates prover cost in curve-based systems. A single elliptic curve exponentiation costs field multiplications; an MSM over points costs exponentiations. After the linear-time sum-check techniques of Chapter 19, the prover spends more time committing than proving. Every optimization in this chapter reduces what the prover must commit.

  2. Enlarge the witness to simplify constraints (untrusted advice). Adding helper values (quotients, square roots, intermediate exponentiation steps) to the witness polynomial makes the commitment slightly larger but lets the constraint system avoid expensive non-native operations. The prover computes; the constraints only verify.

  3. Batch many lookups into one evaluation via sum-check. Proving evaluations reduces to a single sum-check instance over the domain of , exploiting the one-hot sparsity of the access matrix . The access matrix factors via tensor decomposition (-bit addresses split into chunks of bits), reducing commitment from to . A table with entries costs no more to access than one with .

  4. Virtualization is the chapter's unifying principle. Any polynomial algebraically determined by already-committed polynomials does not need its own commitment. This applies to derived polynomials (), read results (defined by access pattern and table), time-varying state (reconstructed from write history via ), and GKR layer values (each defined via sum-check in terms of the previous layer). The same principle appears on the STARK side as DEEP-ALI (Chapter 20).

  5. Virtualization creates a DAG of sum-checks. Each virtual polynomial requires another sum-check to discharge the evaluation claim. The protocol's structure is a directed acyclic graph: committed polynomials are sources, the final opening proof is the sink. The longest path determines the minimum number of sequential stages; everything independent within a stage collapses via batching.

  6. Jagged commitments virtualize structure, not values. Flattening variable-height tables into one dense array avoids committing to padding zeros. The verifier circuit depends only on total trace area , not individual table heights, enabling one universal recursion circuit for all programs.

  7. Keep values small as long as possible. MSM cost scales with scalar bit-width ( group operations per exponentiation). Witness values are typically 8-64 bits; random challenges are 256 bits. Postponing the inflation from multiplying small values by large challenges keeps the bulk of the prover's work in cheap machine-word arithmetic.

  8. Commitment cost tracks operations, not capacity. A zkVM with addressable memory, dozens of instruction types, and millions of cycles commits roughly the same amount per cycle regardless of memory size or instruction mix. Memory is free; only actual computation costs.

Chapter 22: The Two Classes of PIOPs

Every modern SNARK, stripped to its essence, follows the same recipe: a Polynomial Interactive Oracle Proof (PIOP), compiled with a Polynomial Commitment Scheme (PCS), made non-interactive via Fiat-Shamir. The PIOP provides information-theoretic security: it would be sound even against unbounded provers if the verifier could magically check polynomial evaluations. The PCS adds cryptographic binding. Fiat-Shamir removes interaction.

But within this unifying framework, two distinct philosophies have emerged. They use different polynomial types, different domains, different proof strategies. They lead to systems with different performance profiles.

Understanding when to use which is not academic curiosity; it shapes every SNARK design decision.

The Divide

The two paradigms differ in their fundamental approach to constraint verification. At the deepest level, the split is geometric: where does your data live?

Quotienting-based PIOPs (Groth16, PLONK, STARKs) encode data as univariate polynomials of degree and evaluate them on roots of unity, elements that cycle around the unit circle in the multiplicative group. Constraints become questions about divisibility: does the error polynomial vanish on this domain? The machinery is algebraic (division, remainder, quotient). PLONK and STARKs rely on the FFT to convert between evaluations and coefficients; Groth16 uses the same roots-of-unity domain for its QAP but performs its heavy work through MSMs in the exponent rather than FFTs.

Sum-check-based PIOPs (Spartan, HyperPlonk, Jolt) encode data as multilinear polynomials, variables each of degree 1, and evaluate them on the Boolean hypercube . Constraints become questions about sums: does the weighted average over all vertices equal zero? The machinery is probabilistic (randomization collapses exponentially many constraints into one) and the key algorithm is the halving trick, which scans data linearly.

The polynomial type and the domain are linked. Univariate polynomials need a structured evaluation domain with FFT-friendly symmetry (roots of unity provide this). Multilinear polynomials need the hypercube because that is where their Lagrange basis is defined and where the halving trick's fold-in-half structure applies. Choosing one determines the other.

For a decade, the circle dominated because its mathematical tools (pairings, FFTs) matured first. But the hypercube has risen recently because it fits better with how computers actually work: bits, arrays, and linear memory scans.

Both achieve the same goal: succinct verification of arbitrary computations. Both ultimately reduce to polynomial evaluation queries. But they arrive there by different paths, and those paths have consequences.

Historical Arc

The divide between paradigms has a history.

The PCP Era (1990s-2000s)

The theoretical foundations came from PCPs (Probabilistically Checkable Proofs). A PCP is a single, static proof string that the verifier queries at random positions, non-interactive by construction.

PCPs used univariate polynomials implicitly. The prover encoded the computation as polynomial evaluations; the verifier checked random positions. Soundness came from low-degree testing and divisibility arguments, the ancestors of quotienting.

Merkle trees provided commitment. Kilian showed how to make the proof succinct by hashing the full proof string, letting the verifier query random positions, and having the prover open those positions with Merkle paths.

The SNARK Era (2010s)

Groth16, PLONK, and their relatives refined the quotienting approach. KZG's constant-size proofs made verification fast (just a few pairings), and the trusted setup was an acceptable trade-off for many applications.

These systems dominated deployed ZK applications: Zcash, various rollups, privacy protocols. Quotienting became synonymous with "practical SNARKs."

The Sum-Check Renaissance (2020s)

Systems like Spartan, Lasso, and Jolt demonstrated that sum-check-based designs achieve the fastest prover times. The key insight, crystallized in Chapter 19, is that interaction is a resource, and removing it twice (once in the PIOP, once via Fiat-Shamir) is wasteful.

GKR's layer-by-layer virtualization, combined with efficient multilinear PCS, enabled provers to approach linear time. Virtual polynomials slashed commitment costs.

The modern view is that quotienting and sum-check are both valid tools. Neither dominates universally. The choice depends on the application's specific constraints.

A Common Task: Proving

To make the comparison concrete, consider the entrywise product constraint:

where . The prover has committed to vectors and must prove this relationship holds at every coordinate.

This constraint captures half the logic of circuit satisfiability: verifying that gate outputs equal products of gate inputs. (The other half, wiring constraints that enforce copying, we'll address shortly.) Let's trace both paradigms through this single task.

The Quotienting Path

Setup

Choose an evaluation domain of size . The standard choice: the -th roots of unity, where .

Define univariate polynomials by Lagrange interpolation:

  • of degree : the unique polynomial satisfying
  • and similarly

These are univariate low-degree extensions of the vectors, anchored at the roots of unity.

From pointwise constraints to divisibility

The constraint for all is equivalent to saying that for all .

By the Factor Theorem, a polynomial vanishes on all of if and only if it's divisible by the vanishing polynomial:

So the constraint becomes: there exists a polynomial such that

The quotient is the witness to divisibility.

The Protocol

  1. Prover commits to using a univariate PCS (typically KZG)
  2. Prover computes the quotient:
  3. Prover commits to
  4. Verifier sends random challenge
  5. Prover provides evaluations with opening proofs
  6. Verifier checks:

Why Roots of Unity?

For arbitrary , computing requires operations: a factor of for each element. But when consists of -th roots of unity:

The verifier computes in time via repeated squaring. This simple structure, an accident of multiplicative group theory, makes quotienting practical. Chapter 13 develops this further: roots of unity also enable FFT-based polynomial arithmetic and the shift structure needed for accumulator checks.

Soundness

If the constraint fails at some , then is not divisible by . Any claimed quotient will fail: the polynomial

is non-zero. By Schwartz-Zippel, a random catches this with probability at least (overwhelming for large fields).

Cost Analysis

The quotient polynomial has degree at most . Computing it requires polynomial division, typically done via FFT in time. Committing to costs additional PCS work.

The prover's dominant costs: FFT for quotient computation, MSM for commitment.

The hidden cost in univariate systems is not just the time complexity but the memory access pattern. FFTs require "butterfly" operations that shuffle data across the entire memory space: element interacts with element , then , and so on. These non-local accesses cause massive cache misses on modern CPUs. In contrast, sum-check's halving trick scans data linearly (adjacent pairs combine), which is cache-friendly and easy to parallelize across cores. For large , the memory bottleneck often dominates the arithmetic.

The Sum-Check Path

Setup

The quotienting approach indexed vectors by roots of unity: at . Sum-check indexes them by bit-strings instead: for , where . For : positions become . Same data, different addressing scheme.

Define multilinear polynomials, the unique extensions that are linear in each variable:

  • : satisfies for all
  • and similarly

Where quotienting uses Lagrange interpolation over roots of unity to get univariate polynomials of degree , sum-check uses multilinear extension over the hypercube to get -variate polynomials of degree 1 in each variable. Both encodings uniquely determine the original vector; they just live in different polynomial spaces.

From pointwise constraints to a random linear combination

The constraint for all means:

Define . We want to vanish on the hypercube.

Instead of proving divisibility (which would require a quotient polynomial), sum-check takes a random linear combination. Define:

for verifier-chosen random .

The polynomial is the multilinear extension of the equality predicate: it equals 1 when (on the hypercube) and 0 otherwise. But for general field elements, it acts as a random weighting function:

If any , then is a non-zero polynomial in . By Schwartz-Zippel, with probability at least .

The Protocol

  1. Prover commits to using an MLE-based PCS
  2. Verifier sends random
  3. Prover and verifier run sum-check on , claimed to equal 0
  4. Sum-check reduces to evaluating at a random point
  5. Prover provides with opening proofs
  6. Verifier computes directly (just multiplications) and checks that equals the claimed final value

Cost Analysis

Sum-check proving via the halving trick (Chapter 19) takes time for dense polynomials. The prover provides three opening proofs, no quotient commitment needed.

The prover's dominant costs: sum-check field operations, PCS opening proofs.

The Comparison

AspectQuotientingSum-Check
Polynomial typeUnivariate, degree Multilinear, variables
DomainRoots of unity Boolean hypercube
Constraint verification divides errorRandom linear combination
Extra commitmentQuotient None
Prover time for FFT dense, sparse
Interaction1 round (after commitment) rounds (sum-check)
Sparsity handlingQuotient typically denseNatural via prefix-suffix

The two paradigms embody different engineering mindsets, and an analogy helps sharpen the distinction. Quotienting is signal processing. It treats data like a sound wave, running a Fourier transform (FFT) to convert the signal into a frequency domain where errors stick out like a sour note. Divisibility by is the test: a clean signal has no energy at the forbidden frequencies. Sum-check is statistics. It treats data like a population, taking a random weighted average over the whole population and checking whether that average is zero. No frequency analysis required, just a linear scan.

The performance gap follows from this distinction. FFTs require butterfly operations that shuffle data across the entire memory space (Chapter 20's discussion of cache misses in the NTT), while sum-check's halving trick scans data linearly, which is cache-friendly and trivially parallelizable. Sparsity widens the gap further. Quotienting always pays for the FFT regardless of how many constraints are non-trivial, and the quotient polynomial must be committed even when most of the constraint evaluations are zero. Sum-check's cost drops to for non-zero terms, ignoring the zeros entirely (Chapter 19). At zkVM scale, where , this difference is orders of magnitude. Prover speed is not the whole story, however. The PCS pairing and the "Choosing a Paradigm" sections below will show that quotienting recovers the advantage on proof size and verifier efficiency, dimensions where the tradeoff runs in the opposite direction.

Wiring Constraints: The Second Half

The constraint checks that gate computations are correct. But a circuit also has wiring: the output of gate might feed into gates and as inputs. We must verify that copied values match, that and .

This is the "copy constraint" problem, and the two paradigms handle it differently.

Quotienting: Permutation Arguments

PLONK-style systems encode wiring as a permutation. Consider all wire values arranged in a single vector. The permutation maps each wire position to the position that should hold the same value.

The constraint: for all .

PLONK verifies this through a grand product argument (Chapter 13). For each wire position, form the ratio:

If the permutation constraint is satisfied, multiplying all these ratios gives 1: a massive cancellation of numerators and denominators.

Proving this grand product requires an accumulator polynomial: . The prover commits to this accumulator and proves it satisfies the recurrence relation via... quotienting. An additional quotient polynomial for the accumulator constraint.

Sum-Check: Memory Checking

Sum-check systems take a different view: wiring is memory access.

Each wire value is "written" to a memory cell when it's computed. Each wire position that uses that value "reads" from the cell. The constraint: reads return the values that were written.

The verification reduces to sum-check over access patterns. For each read at position , define an access indicator if the read targets cell , and 0 otherwise. The read value must satisfy:

where is the value stored at cell . This equation says: "the value I read equals the sum over all cells, but the indicator zeroes out everything except the cell I actually accessed."

For read-only tables (like bytecode or lookup tables), is fixed. For read-write memory (like registers or RAM), becomes : the value at cell at time , reconstructed from the history of writes. Chapter 21 shows how this state table can be virtualized: rather than committing to the full matrix, commit only to write addresses and value increments, then compute the state implicitly via sum-check.

The access indicator matrix is sparse (each read touches exactly one cell) and decomposes via tensor structure, making commitment cost proportional to operations rather than memory size.

Wiring: The Comparison

AspectPermutation ArgumentMemory Checking
AbstractionWires as permutation cyclesWires as memory cells
Core mechanismGrand product of ratiosSum over access indicators
Extra commitmentAccumulator polynomial Access matrices (tensor-decomposed)
Structured accessNo special benefitExploits sparsity naturally
Read-write memoryRequires separate handlingUnified with wiring

The algebraic structure reflects this split. Permutation arguments use products (accumulators that multiply ratios), while memory checking uses sums (access counts weighted by values). In finite fields, sums are generally cheaper than products. Sums linearize naturally (the sum of two access patterns is the combined access pattern), while products require careful accumulator bookkeeping. This is why memory checking integrates more cleanly with sum-check's additive structure.

For circuits with random wiring, both approaches have similar cost. The permutation argument requires an accumulator commitment; memory checking requires access matrices. The difference emerges with structure: repeated reads from the same cell, locality in access patterns, or mixing read-only and read-write data all favor the memory checking view.

The PCS Connection

Each PIOP paradigm pairs with a matching polynomial commitment scheme, and the matching is not arbitrary. The reason is that every PIOP ends the same way: sum-check or quotient verification reduces the original claim to "evaluate this committed polynomial at a random point." The shape of that random point determines which PCS can serve it. A quotienting PIOP ends with a univariate evaluation query, "what is for ?" A sum-check PIOP ends with a multilinear evaluation query, "what is for ?" The PCS must handle exactly the query type the PIOP produces.

Univariate PCS for quotienting. The query is a single field element . KZG handles this with a single group element commitment and a constant-size opening proof (one pairing check), at the cost of a trusted setup and pairing-friendly curves. FRI handles it with Merkle commitments and logarithmic-size proofs via folding, transparent and post-quantum but larger. Both operate over the same roots-of-unity domain that the PIOP already uses for FFT-based quotient computation.

Multilinear PCS for sum-check. The query is an -dimensional point . Bulletproofs/IPA handle this via recursive folding that halves the polynomial one variable at a time (logarithmic proofs, no trusted setup). Dory uses pairing-based inner products for efficient batch opening. Hyrax and Ligero use Merkle trees and linear codes. All commit to evaluation tables over and open at arbitrary points in , matching the query shape sum-check produces.

In principle, any PIOP can use any PCS of the matching polynomial type. In practice, the best systems co-optimize PIOP and PCS: the FFT that the quotienting PIOP uses for quotient computation is the same FFT that prepares the polynomial for KZG or FRI commitment, and the halving structure that sum-check uses for proving is the same halving structure that IPA uses for opening. The algorithm is shared; only its role changes.

Choosing a Paradigm

The comparisons above reveal a pattern. Quotienting and sum-check differ not just in mechanism but in what they optimize for.

Quotienting excels when structure is fixed and dense. The quotient polynomial costs regardless of how many constraints actually matter. FFT runs in regardless of sparsity. The permutation argument handles any wiring pattern equally. This uniformity is a strength when constraints fill the domain densely and circuit topology is known at compile time. Small circuits with degree-2 or degree-3 constraints, existing infrastructure with optimized KZG and FFT libraries, applications where proof size matters more than prover time: these favor quotienting. Chapter 20 shows how small-field NTT optimization, DEEP-ALI, and batched FRI reduce the concrete cost of this path to the point where it competes with sum-check for structured workloads like hashing.

Sum-check excels when structure is dynamic and sparse. The prefix-suffix algorithm runs in for non-zero terms, ignoring the zeros entirely. Memory checking handles structured access patterns (locality, repeated reads) more efficiently than permutation arguments. Virtual polynomials let you skip commitment entirely for intermediate values. This adaptivity matters for large circuits with billions of gates, memory-intensive computation with lookup arguments and batch evaluation, and zkVMs where the constraint pattern depends on the program being executed.

The wiring story reinforces this. Permutation arguments treat all wire patterns uniformly: a random scramble costs the same as a structured dataflow. Memory checking adapts: tensor decomposition exploits address structure, virtualization skips commitment to state tables, and read-only versus read-write falls out of the same framework.

A useful heuristic: if you know exactly what your circuit looks like at compile time and it fits comfortably in memory, quotienting's simplicity wins. If your circuit's shape depends on runtime data, or if you're pushing toward billions of constraints, sum-check's adaptivity wins.

Key Takeaways

  1. One choice determines the rest. Quotienting uses univariate polynomials over roots of unity and proves constraints via divisibility ( divides the error). Sum-check uses multilinear polynomials over the Boolean hypercube and proves constraints via random linear combination. Polynomial type, domain, and constraint strategy are linked; choosing one determines the other two.

  2. Quotienting is signal processing; sum-check is statistics. Quotienting runs an FFT to move data into a frequency domain where errors violate divisibility. Sum-check takes a random weighted average and checks whether it vanishes. The FFT shuffles data across memory (cache misses); the halving trick scans linearly (cache-friendly). This explains the prover-speed gap.

  3. Sparsity is where the paradigms diverge most in cost. Quotienting pays for the FFT and commits a quotient polynomial regardless of how many constraints are non-trivial. Sum-check pays for non-zero terms, ignoring the rest. When (the zkVM regime), the difference is orders of magnitude in prover time.

  4. Proof size and verifier cost favor quotienting. KZG-compiled quotienting gives constant-size proofs verified in a few pairings. Sum-check proofs grow logarithmically and require rounds of verifier work. The prover-speed advantage of sum-check trades against this.

  5. Wiring constraints expose a deep abstraction gap. Quotienting encodes copy constraints as permutations (grand product accumulators over ratios). Sum-check encodes them as memory access (sparse indicator matrices verified via the / machinery of Chapter 21). Same constraint, different algebraic worlds.

  6. The PCS must match the PIOP's query shape. A quotienting PIOP ends with a univariate evaluation query ( for ); a sum-check PIOP ends with a multilinear one ( for ). KZG and FRI serve the first; IPA, Dory, and Hyrax serve the second. The algorithms often coincide: the FFT that computes quotients is the same FFT that prepares KZG commitments; the halving that drives sum-check is the same halving that drives IPA opening.

  7. Both paradigms avoid unnecessary commitments, by different mechanisms. Sum-check systems use virtual polynomials (Chapter 21): any polynomial computable from committed ones is never committed. STARK-side quotienting uses DEEP-ALI (Chapter 20): the composition polynomial is reconstructed at a single out-of-domain point rather than committed. The principle is shared; the implementation diverges.

  8. Neither paradigm dominates; choose based on your bottleneck. Fixed circuit, dense constraints, proof size matters: quotienting. Dynamic structure, sparse constraints, prover speed matters: sum-check. The two traditions are converging (Binius uses sum-check with FRI-based commitments; Plonky3 supports both frontends over the same small-field backend), but the choice still shapes every downstream design decision.

Chapter 23: Composition and Recursion

Could you build a proof system that runs forever? A proof that updates itself every second, attesting to the entire history of a computation, but never growing in size?

The only way to keep a proof from growing is to compress it at every step. That means each new proof must verify the previous proof and then replace it, absorbing all the history into a fixed-size certificate. The proof system must verify its own verification logic, "eating itself." For years, this remained a theoretical curiosity, filed under "proof-carrying data" and assumed impractical.

This chapter traces how the impossible became routine. We start with composition: wrapping one proof inside another to combine their strengths. We then reach recursion: proofs that verify themselves, enabling unbounded computation with constant-sized attestations. Finally, we arrive at folding: a recent revolution that makes recursion cheap by deferring verification entirely. The destination is IVC (incrementally verifiable computation), where proofs grow with time but stay constant-sized. Today's zkEVMs and app-chains are built on this foundation.

No single SNARK dominates all others. Fast provers tend to produce large proofs. Small proofs come from slower provers. Transparent systems avoid trusted setup but sacrifice verification speed. Post-quantum security demands hash-based constructions that bloat proof size. Every deployed system occupies a point in this multi-dimensional trade-off space.

But here's a thought: what if we could combine systems? Use a fast prover for the heavy computational lifting, then wrap its output in a small-proof system for efficient delivery to verifiers. Or chain proofs together, where each proof attests to the validity of the previous, enabling unlimited computation with constant verification.

These ideas, composition and recursion, transform SNARKs from isolated verification tools into composable building blocks. The result is proof systems that achieve properties no single construction could reach alone.

Composition: Proving a Proof Is Valid

Composition means generating a new proof that an existing proof was valid. The distinction from verification is that the output is itself a proof, not a yes/no verdict. You have a proof of some statement. Verifying is a computation. You express that verification as a circuit, then prove the circuit was satisfied. The result is a second proof that attests to 's validity, potentially with different properties (smaller, faster to verify, based on different assumptions) than itself.

Why do this? Different proof systems have different strengths. A STARK proves quickly but produces a 100KB proof. Groth16 produces a 128-byte proof but proves slowly. Composition lets you have both: prove the computation with a STARK, then compose the result into Groth16 for compact delivery. The formal treatment below shows why this combination inherits the fast prover of the first system and the small proof of the second, without either system's weakness dominating.

Inner and Outer

The names inner and outer describe the nesting:

  • The inner proof is created first. It proves the statement you actually care about ("I executed this program correctly," "I know a secret satisfying this relation").

  • The outer proof is the wrapper, created second. It proves "I ran the inner verifier and it accepted."

flowchart TB
    subgraph inner["INNER PROOF (fast prover, large proof)"]
        I1["Statement: 'I know w such that C(w) = y'"]
        I2["Inner Prover"]
        I3["Inner Proof π"]
        I1 --> I2 --> I3
    end

    subgraph outer["OUTER PROOF (small-proof system)"]
        O1["Statement: 'The inner verifier accepted π'"]
        O2["Outer Prover"]
        O3["Outer Proof π'"]
        O1 --> O2 --> O3
    end

    subgraph delivery["DELIVERY"]
        D1["Verifier receives only π'"]
        D2["Verifier checks π'"]
        D3["✓ Original statement validated"]
        D1 --> D2 --> D3
    end

    I3 -->|"becomes witness for"| O1
    O3 -->|"π discarded"| D1

The verifier of the outer proof never sees the inner proof or the original witness. They see only and check that it's valid. If the outer system is zero-knowledge, nothing leaks about or .

Think of it like nested containers: the inner proof is a large box containing detailed evidence. The outer proof is a small envelope containing a signed attestation that someone trustworthy opened the box and verified its contents. Recipients need only check the signature on the envelope.

The Composition Construction

Composition works because verification is itself a computation, and any computation can be proven. To see why the composed system inherits the best of both components, we trace the construction step by step and analyze its cost.

Consider two SNARKs with complementary profiles. Let denote the original circuit size.

Inner SNARK (fast prover, large proofs): prover time , proof size , verification time . Example: STARK-like systems.

Outer SNARK (slow prover, tiny proofs): prover time , proof size , verification time . Example: Groth16.

Step 1: Run the inner prover. The prover executes on the original circuit with witness , producing proof . Cost: .

Step 2: Arithmetize the inner verifier. The verification algorithm of the inner SNARK is a computation: it reads the proof, performs checks, outputs accept or reject. Express this verification as a circuit with public input (the original statement), witness , and output 1 iff accepts. Since has verification time, , far smaller than .

Step 3: Run the outer prover. The prover executes on the verifier circuit , using as the witness. Cost: , since the outer prover is superlinear but operates on a circuit of size , not .

Step 4: Deliver only the outer proof. The prover discards and sends only to the verifier. The inner proof was a means to an end; it never leaves the prover's machine.

Step 5: Verify. The end verifier runs on . Cost: (inherited from the outer system). The verifier never sees or .

The cost analysis makes the payoff visible. Total prover time is , dominated by the fast inner prover. The slow outer prover contributes negligibly because it only processes the small verifier circuit. Proof size and verification time both inherit from : constant and fast.

For a concrete sense of scale: a million-gate circuit () might take 5 seconds to prove with the inner STARK, producing a proof the verifier can check in operations. The verifier circuit has gates. Groth16 proves that 1000-gate circuit in about 1 second. Total: seconds. Proof size: bytes. Verification: 3 pairings. Without composition, running Groth16 directly on the full circuit would take minutes.

Soundness, witnesses, and delivery

The original witness is consumed entirely in Step 1. The outer proof's witness is (the inner proof), not . The outer system proves "I possess a valid inner proof," not "I know the original witness." The soundness chain is:

The outer proof transitively guarantees the original statement without directly involving . Only is delivered; is discarded. If the outer system is zero-knowledge, nothing leaks about or .

Field mismatches and verifier circuit blowup

The analysis above assumed the inner verifier circuit is small and easy to express in the outer system. But what if the inner and outer systems speak different languages? STARKs operate over one field; Groth16 operates over another. Encoding foreign field arithmetic can blow up the verifier circuit by orders of magnitude. Trusted setup requirements, field mismatches, and post-quantum concerns all constrain which combinations actually work. The later sections on The Verifier Circuit Problem and Curve Cycles address these issues in detail.

Adding Zero-Knowledge

Here's a bonus. Suppose the inner SNARK lacks zero-knowledge: some STARK variants reveal execution traces that leak witness information. But the outer SNARK is fully ZK.

The composed system inherits zero-knowledge from the outer layer. The final proof proves knowledge of a valid inner proof without revealing itself. Since depends on the witness , hiding suffices to hide .

The inner SNARK's lack of ZK is encapsulated and hidden by the outer layer.

Recursion: Composing with Yourself

If composing two different SNARKs is useful, what about composing a SNARK with itself?

Shrinking the verifier tower

Take a hypothetical SNARK where verifying a proof for a circuit of size costs operations. (This is pedagogical; real SNARKs have verification like Groth16, or like STARKs. The gives clean math for illustration.)

Now trace what happens when we recurse:

Layer 0: Prove the original circuit (size ). This produces proof . Verifying costs operations.

Layer 1: Wrap in another proof. The circuit being proved is now the verifier for , which has size . This produces . Verifying costs operations.

Layer 2: Wrap . The circuit is the verifier for , size . Verifying costs operations.

The pattern: each layer proves "the previous verifier accepted," and since verifiers are smaller than the circuits they verify, each layer's circuit shrinks.

After layers:

Verification cost reaches a constant after layers, which is the recursion threshold. The derivation is short: we need for some constant , giving , so . Each layer halves the exponent; doing this times reduces it to a constant.

We are not proving the original circuit over and over. Each layer proves a different (smaller) circuit: the verifier of the previous layer. The shrinking comes from the fact that verification is cheaper than computation.

Proof of Proof of Proof...

From the prover's perspective, deep recursion means building a tower of proofs:

  1. : proves "I know witness satisfying circuit "
  2. : proves "I know a valid proof "
  3. : proves "I know a valid proof "
  4. Continue until the verifier circuit is minimal

Each is a proof about the previous proof. The final can be verified in constant time regardless of the original computation's size.

The Strange Loop

A proof that proves a proof that proves a proof: the structure feels like it should be paradoxical. Gödel showed that sufficiently powerful formal systems can express statements about their own provability, and this self-reference produces incompleteness. "This statement is unprovable" is a sentence the system can formulate but cannot resolve.

Recursive SNARKs avoid the trap because they ask a different question. Gödel's self-reference asks "is this provable?", a meta-logical assertion the system cannot settle about itself. Recursive SNARKs ask "is this verifiable?", and verification is a concrete, bounded computation: read the proof, check some equations, output accept or reject. A proof system can prove statements about its own verifier for the same reason it can prove statements about any other circuit. The self-reference leads not to paradox but to compression.

The Extraction Caveat

Everything above assumed recursive SNARKs are sound. They are, in practice. But the standard way of proving soundness breaks down with recursion depth, and understanding why reveals a genuine gap between what we can prove and what we believe.

The problem in one sentence: proving a SNARK is secure requires running the attacker many times to extract a witness, and each layer of recursion multiplies the number of runs exponentially. At depth , the security proof requires runs of the attacker, where is the extraction cost per layer. For and , this is operations, far beyond anything meaningful. The security theorem degrades to vacuity even though no one can actually break the system.

To see where this exponential comes from, we need to trace how SNARK security proofs work. We cannot prove a cryptographic system is secure in an absolute sense (that would require proving and more). Instead, we prove relative security: "if someone can break system X, they can also break problem Y." If we believe Y is hard, then X must be hard too.

A SNARK security proof constructs an algorithm (the "reducer") that treats any successful attacker as a black box. If the attacker can forge proofs, the reducer extracts a valid witness from those proofs. The witness encodes a solution to a hard problem like discrete log, because the commitment scheme was constructed so that knowing a valid witness implies knowing a discrete log. Since we believe discrete log is hard, forging proofs must also be hard.

The extraction step is where the cost enters. To extract a witness, the reducer uses rewinding: run the prover once, record its state, then rewind to an earlier point and run it again with a different random challenge. Two runs with different challenges on the same commitment overdetermine the witness.

Worked example (rewinding in a -protocol). Consider a -protocol (Chapter 16) where the prover sends commitment , receives challenge , and responds with . The extractor recovers the witness as follows:

  1. Run the prover. It sends , you send challenge , it responds with .
  2. Rewind to just after the prover sent . (In a proof, we model the prover as a stateful algorithm we can checkpoint and restore.)
  3. Send a different challenge .
  4. The prover responds with .
  5. From and with the same , algebraically solve for the witness.

This works because -protocols have special soundness (Chapter 16): the commitment fixes enough structure that two different challenge-response pairs overdetermine the witness. In Schnorr's protocol, for instance, where is the secret. Two transcripts give , so . Not all interactive proofs have this property, but -protocols are designed for it.

The -protocol example needed only rewinds (two transcripts with different challenges). More complex SNARKs may need more: a protocol with rounds of interaction generally requires rewinds, where is the degree of the round polynomials. For a modern SNARK, might be in the hundreds. Recursion compounds this cost. For a single-layer proof, extraction costs prover runs. For a 2-layer recursive proof, you must:

  1. Extract the inner proof from the outer layer: runs
  2. For each of those runs, extract the witness from : more runs each
  3. Total: runs

For depth : runs. At depth 100 with , that's operations.

This breaks the security proof. Security theorems have the form: "if an attacker breaks the SNARK, our reducer solves discrete log."

But the reducer must be efficient. If the reducer takes operations to extract a witness, the theorem becomes: "if an attacker breaks the SNARK, discrete log can be solved in operations." This is useless. We already know discrete log can be brute-forced in operations. The reduction no longer rules out attackers, and the provable security level drops accordingly: each additional layer of recursion multiplies the reducer's running time by , weakening the security guarantee by bits per layer.

To be clear: this degradation is in the provable bound, not in the system's actual resistance to attack. More rewinds doesn't make the system easier to break. It makes our proof technique too slow to demonstrate security. The reducer's inefficiency is a problem for the theorist writing the proof, not for the attacker trying to exploit the system.

In practice, the system might be perfectly secure. No one has found attacks that exploit the recursive structure, and the underlying hard problems (discrete log, collision resistance) remain hard. What breaks is not the system but the proof technique: standard reductions become too expensive to carry through, so the security theorem degrades even though the system itself does not weaken.

The practical heuristic for recursive SNARKs is therefore: security degrades on paper with recursion depth, but not in reality. A system with 100 layers of recursion has the same concrete security as one with 2 layers (no known attack exploits the depth), but its provable security guarantee is weaker because the reduction's running time grows as . This parallels the random oracle model, where hash functions are used in ways that resist all known attacks but lack full theoretical justification. Practitioners accept the gap and ship; researchers work on tighter proof techniques (folding schemes, discussed next, partly sidestep this issue by avoiding deep recursion entirely).

The Curve Cycle Problem

For pairing-based SNARKs like Groth16, recursion faces a fundamental obstacle: field mismatch.

Two Fields, One Problem

Every pairing-based SNARK involves two distinct fields. To understand why, recall how elliptic curve cryptography works.

An elliptic curve is defined over a base field . Points on the curve have coordinates where . When you add two points or compute (scalar multiplication), you're doing arithmetic in : additions, multiplications, and inversions of these coordinates.

But the scalars live in a different field. The curve's points form a group under addition, and this group has an order : the number of elements in the group. For any point , we have (the identity). Scalars are integers modulo , giving us the scalar field .

A concrete example with BN254 (the curve Ethereum uses for precompiles):

  • Base field: where (coordinates of curve points live here; all curve arithmetic, including point addition and pairings, is computed over )
  • Scalar field: where (a completely different prime of similar size; scalars in , witness values, and circuit constraints live here)
  • A point on the curve: with
  • A Groth16 proof element: where (scalar field) and the result is a curve point with coordinates in (base field)

Where each field appears in Groth16:

  • Scalar field : Your circuit's witness values and all constraint equations. If you're proving "I know such that ," then . The reason constraints must live in is that the commitment scheme requires it. In Groth16, committing to a witness value means computing (a scalar multiplication on the curve). For this to be a well-defined group operation, must be an element of , because the curve group has order and scalars are taken modulo . The constraint system inherits this field because it constrains the same values the commitment scheme binds.

  • Base field : The proof elements themselves. The proof consists of elliptic curve points, which have coordinates in . Verification requires point additions and pairings, all computed over .

In a single proof, the two fields coexist without friction. The prover uses both (scalars from feed the constraint system and commitment scheme; curve arithmetic in constructs the proof elements), but the constraint system lives entirely in . The verifier checks the proof using operations (pairings, point arithmetic), but that happens outside any circuit. Recursion forces the two fields into the same circuit. When the inner proof becomes the witness of the outer circuit, the outer circuit must verify , which means performing pairing checks and point arithmetic on the coordinates of . Those coordinates are elements. But the outer circuit's constraints are polynomial equations over (the scalar field of whatever curve the outer system uses). The outer circuit must therefore manipulate values using arithmetic.

To do arithmetic inside an circuit, you must emulate it: decompose each element into multiple -sized limbs and implement multiplication with schoolbook carry propagation, as Chapter 13 described for non-native arithmetic in PLONK. A single multiplication can cost 50-100+ constraints. The verifier circuit explodes from a few thousand native operations to hundreds of thousands of emulated ones.

Two terms clarify this distinction. When we say arithmetic is native, we mean it's cheap inside the circuit: one field operation becomes one constraint. A circuit over can do arithmetic natively. It must emulate arithmetic, paying 100+ constraints per operation. The curve cycle trick ensures we're always doing native arithmetic by switching fields at every recursive step.

Cycles of Curves

For single composition, the fix is straightforward: choose an outer curve whose scalar field matches the inner curve's base field. If the inner verifier does arithmetic, use an outer system over . One wrap, native arithmetic, done.

For deep recursion, this isn't enough. After wrapping once, you have a new proof whose verifier does arithmetic in some other field. To wrap again natively, you need yet another matching curve. The solution is a cycle of elliptic curves :

  • has scalar field and base field
  • has scalar field and base field

The fields swap roles between curves. Recursion alternates:

  • Proof on curve : verifier performs arithmetic
  • Proof on curve : verifier performs arithmetic, can natively prove about 's verification
  • Proof on curve : can natively prove about 's verification
  • And so on indefinitely

Each step's verifier circuit uses native field arithmetic. The alternation continues as long as needed, with no expensive cross-field emulation at any layer.

Practical Curve Cycles

Pasta curves (Pallas and Vesta): A true cycle, meaning the scalar field of each equals the base field of the other. This enables unbounded recursion: the prover alternates between the two curves at every step, and each step's verifier circuit uses native arithmetic because the next curve's scalar field matches the previous curve's base field. Neither curve is pairing-friendly, but both support efficient Pedersen commitments and inner-product arguments. Used in Halo 2 and related systems.

BN254 / Grumpkin: Also a true cycle (Grumpkin is obtained by swapping BN254's base and scalar fields), enabling the same unbounded alternating recursion as Pasta. The difference is asymmetry: BN254 is pairing-friendly while Grumpkin is not, so the cycle alternates between pairing-based proofs (on the BN254 side) and inner-product-based proofs (on the Grumpkin side). Since BN254 has Ethereum precompiles, proofs that land on the BN254 side can be verified cheaply on-chain. Aztec uses this cycle for their rollup architecture.

The choice between cycles depends on the deployment target. BN254/Grumpkin is the default when on-chain Ethereum verification matters, because BN254 has EVM precompiles and pairings enable constant-size final proofs (Groth16-style). The cost is a trusted setup on the BN254 side. Pasta is preferred when transparency matters (IPA on both sides, no trusted setup), as in Zcash's Orchard protocol via Halo 2, but lacks EVM support and produces larger proofs.

BLS12-377 / BW6-761: Both curves are pairing-friendly, giving pairings on both sides of the recursion (unlike BN254/Grumpkin where only one side has pairings). BW6-761's scalar field matches BLS12-377's base field, allowing native verification of BLS12-377 proofs. The pair is called a "half-cycle" because the match goes in one direction only (BW6-761 can natively verify BLS12-377 proofs, but not vice versa), so it supports efficient one-step composition rather than unbounded alternating recursion. Aleo uses this pair for their proof system.

A related curiosity: embedded curves. BabyJubjub is defined over BN254's scalar field , so BabyJubjub point operations can be expressed natively as BN254 circuit constraints. This enables in-circuit cryptography: EdDSA signatures, Pedersen hashes, and other EC-based primitives. BabyJubjub doesn't form a cycle with BN254 (its group order is much smaller than BN254's base field), so it cannot be used for recursion. The reader might wonder why Grumpkin doesn't replace BabyJubjub entirely, since both have their base field equal to BN254's scalar field. In principle it could: any curve with the right base field supports native in-circuit arithmetic. The reason BabyJubjub persists is practical. It is a twisted Edwards curve with complete addition formulas (no special cases for doubling or identity), making in-circuit point addition slightly cheaper than on Grumpkin (a short Weierstrass curve). It also predates Grumpkin and has years of deployed implementations and audited libraries. In a greenfield design you could use Grumpkin for both roles; in existing ecosystems the two curves coexist because they were optimized for different concerns.

Finding curve cycles is mathematically delicate. The size constraints (both fields must be large primes), security requirements (curves must resist known attacks), and efficiency demands (curves should have fast arithmetic) severely restrict the design space.

Incrementally Verifiable Computation (IVC)

Composition combines different proof systems; the recursion we've seen so far compresses proofs through towers of wrapping. But there is a different problem that recursion solves, one that isn't about shrinking proofs or mixing systems: proving computation that hasn't finished yet.

A blockchain processes transactions one by one. A verifiable delay function (VDF) computes a hash chain for hours, proving that real time elapsed. A zkVM executes a program instruction by instruction. In each case, the computation is sequential: step depends on step . You can't parallelize it. You can't wait until the end to start proving (the end might be days away, or never).

What you want is a proof that grows with the computation. After step 1, you have a proof of step 1. After step 1000, you have a proof of steps 1 through 1000. Crucially, the proof at step 1000 shouldn't be 1000× larger than the proof at step 1. And creating the proof for step 1000 shouldn't require re-proving steps 1 through 999.

This is incrementally verifiable computation, or IVC: proofs that extend cheaply, verify in constant time, and accumulate the history of an unbounded sequential process. The term appears throughout the literature; systems like Nova, SuperNova, and ProtoStar are "IVC schemes."

The Setting

Consider a function iterated times:

For iterations, directly proving this requires a circuit of size : billions of gates. Even fast provers choke on circuits this large. And you'd have to wait until iteration completes before generating any proof at all.

The Incremental Approach

Generate proofs incrementally, one step at a time:

  • : trivial (base case, no computation yet)
  • : proves " and I know a valid "
  • : proves " and I know a valid "
  • : proves " and I know a valid "

Each has constant size and proves the entire computation from to . The proof for step subsumes all previous proofs.

The Recursive Circuit

At step , the prover runs a circuit that:

  1. Verifies : Checks that the previous proof is valid
  2. Computes : Performs one step of the function
  3. Produces : Outputs a new proof

The circuit size is : the cost of verifying the previous proof plus the cost of one function evaluation. The overhead of recursion is , the verifier circuit size.

For a SNARK with efficient verification, might be a few thousand gates. If is also a few thousand gates (a hash function, say), the overhead roughly doubles the per-step cost. For larger , the overhead is proportionally smaller.

Where IVC Shines

Verifiable Delay Functions (VDFs). The canonical example: repeated squaring for an RSA modulus . Each squaring depends on the previous result; you can't compute faster than sequential multiplications (without knowing the factorization of ). After computing , the prover produces a proof that is correct, verifiable in time much less than . IVC is natural here: the function is inherently sequential, and the proof accumulates with each step.

Succinct Blockchains. Each block contains a proof that:

  1. This block's transactions are valid
  2. The previous block's proof was valid

A new node syncing to the chain verifies a single proof (the most recent one) rather than replaying every transaction since genesis. Mina Protocol pioneered this approach.

Proof Aggregation. Multiple independent provers generate separate proofs. An aggregator combines them into one proof via recursive composition. Batch verification becomes constant-time regardless of the number of original proofs.

Folding Schemes: Cheaper IVC

The IVC construction above requires the prover to fully verify the previous proof at every step. This verification is itself a circuit (), and it can be large. If the verifier circuit has gates and the step function has gates, the prover spends 90% of each step proving it verified correctly, only 10% proving it computed correctly. For tiny (say, a single hash invocation), the ratio gets even worse. The verification overhead, not the computation itself, becomes the IVC bottleneck.

Folding schemes address this by replacing verification with something cheaper.

Folding instead of verifying

Instead of fully verifying at step , we fold the claim about step with the claim about step . Folding combines two claims into one claim of the same structure, without verifying either. The accumulated claim grows no larger than each individual claim. Verification is deferred entirely: the prover folds at every step (a few group operations each) and produces a single real SNARK proof only at the very end. The cost of folding per step is drastically cheaper than the cost of in-circuit verification, which is what makes IVC practical for millions of steps.

Nova's Approach

Nova, the pioneering folding scheme (Kothapalli, Setty, Tzialla, 2021), introduced a modified constraint system: relaxed R1CS. Recall from Chapter 19's Spartan section that standard R1CS demands:

where are sparse matrices and is the entrywise (Hadamard) product. This is the same equation Spartan proves via sum-check. Nova adds two slack terms to make folding possible:

where is a scalar and is an "error vector." A standard R1CS instance has and . Relaxed instances can have and , but satisfying a relaxed instance still proves something about the underlying computation.

Why Relaxation Enables Folding

What makes relaxed R1CS useful is that instances can be linearly combined. Suppose we want to fold two instances by taking a random linear combination with challenge :

This is the core of folding: two separate witnesses and become a single witness . The folded witness has the same dimension as the originals; we're not concatenating, we're combining. Think of it geometrically: and are points in ; the fold is another point on the line through them, selected by the random challenge .

What happens when we plug this combined witness into the constraint? Let's compute :

The Hadamard product distributes, but it creates cross-terms: the middle expression mixes the two instances. This is the "interaction" between them.

For standard R1CS, these cross-terms would break everything: the equation has no room for interaction terms that belong to neither instance. This is exactly why relaxation was introduced. The error vector acts as a "trash can" that absorbs the cross-terms, keeping the equation valid despite the algebraic mess that linear combination creates. Define:

This is the cross-term vector. To understand where each term comes from, recall that each relaxed R1CS instance has its own slack factor: instance 1 has and instance 2 has . When we expand the right side of the relaxed constraint using the folded values and :

The coefficient of on the right side is . On the left side (the Hadamard product expansion above), the coefficient of is . The cross-term is exactly the difference between these: what the left side produces at minus what the right side produces at . This mismatch gets absorbed into the error vector.

Note that the coefficient works out automatically: the left side gives and the right side gives , which is exactly instance 2's original constraint (up to ). The folded error absorbs the second instance's error at .

The Folding Protocol

A relaxed R1CS instance consists of a witness vector , an error vector , and a slack scalar . A fresh (non-folded) instance has and ; after folding, both accumulate non-trivial values.

Given two instances and , the protocol folds them into one. But the verifier doesn't see the actual witness vectors, since that would defeat the point. Instead, the verifier works with commitments.

What the verifier holds: Commitments to the witness vectors, commitments to the error vectors, and the public scalars . (Public inputs are also visible, but we omit them for clarity.)

The protocol:

  1. Prover computes the cross-term : The formula above requires knowing both witnesses, so only the prover can compute it.

  2. Prover commits to : Sends commitment to the verifier. This is the only new cryptographic operation per fold.

  3. Verifier sends random challenge .

  4. Both compute the folded instance using commitments:

    • (the verifier computes this from the commitments)
    • (public scalars, both can compute)
    • (again from commitments)

The verifier never sees , or directly. They work entirely with commitments. Because commitments are additively homomorphic (Pedersen commitments satisfy ), the folded commitment is a valid commitment to the folded witness , which only the prover knows.

Meanwhile, the prover computes the actual folded witness and the actual folded error . The prover holds these for the next fold (or for the final SNARK).

The folded error vector absorbs the cross-terms at the coefficient and the second instance's error at the coefficient. This is exactly what makes the constraint hold: the expansion of produces terms at powers , , and , and the folded and absorb them all.

Expanding using the folded values shows why this is sound: all terms cancel if and only if both original instances satisfied their constraints. The random acts as a Schwartz-Zippel check: a cheating prover who folds two unsatisfied instances would need the folded instance to satisfy the constraint, but this happens with negligible probability over random .

Two claims have become one, without verifying either. The prover paid the cost of one commitment (to ) and some field operations. No expensive SNARK proving.

IVC with Folding

Now we connect the folding protocol to the IVC setting from earlier in the chapter. Recall the problem: prove for large without circuits that grow with .

The folding protocol combines two relaxed R1CS instances into one. For IVC, we maintain a running instance that accumulates all previous steps and fold in each new step as it happens.

What gets folded: At each step, we have two things:

  • The running instance , where is the Pedersen commitment to the accumulated witness, is the commitment to the accumulated error vector, and is the accumulated scalar. Together these represent "all steps so far are correct."
  • The step instance , a fresh claim that "step was computed correctly." It is always a standard (non-relaxed) R1CS instance: and .

The step instance is always fresh: and because it comes from a standard (non-relaxed) R1CS. Only the running instance accumulates non-trivial slack.

The IVC loop in detail:

Step 0 (Base case): Initialize the running instance to a trivial satisfiable state. No computation yet.

Step (for ):

  1. Compute: Execute

  2. Create the step instance: Express "" as an R1CS constraint. Build the witness vector (the same object as or in the folding derivation above), encoding the input , output , and any intermediate values the step function produces. Commit to get .

  3. Fold: The two inputs to the folding protocol are the running instance from the previous iteration and the step instance just created. This is exactly the two-instance fold from the derivation above, with the running instance playing the role of instance 1 and the step instance playing instance 2:

    • Prover computes the cross-term (from the two witnesses and ) and commits to get
    • Challenge is derived via Fiat-Shamir from the transcript
    • Both parties compute the new running instance using the homomorphic update:
      • (fold the witnesses)
      • (fold the scalars)
      • (absorb the cross-term)
    • The error update is where the cross-terms go. Recall from the Hadamard expansion that folding produces a degree-1 cross-term in . The term absorbs it into the error commitment; the term accounts for the step instance's error (which is zero for a fresh instance, so this term vanishes in practice).
    • Prover updates the actual witnesses (which only the prover holds): ,
  4. Repeat: The new running instance becomes input to step

After steps: The prover holds a final running instance with (accumulated from all the folds). This single instance encodes the claim "all steps were computed correctly." The individual steps, their witnesses, and their cross-terms have all been absorbed into the running instance's commitments and scalar. No trace of the intermediate states remains.

The final SNARK: The prover now produces one conventional SNARK proof demonstrating that the running instance is satisfiable, i.e., that there exists a witness and error vector such that:

This is the only expensive cryptographic operation in the entire protocol. The preceding folds each cost only a few group operations (one commitment to , one scalar multiplication per running-instance component). The full SNARK proof happens once, at the end, not at every step.

The verifier's job is correspondingly simple. It receives three things: the final running instance , the SNARK proof for that instance, and the claimed output . It verifies the SNARK (confirming the running instance is satisfiable) and checks that matches the output encoded in the instance. If both pass, the verifier is convinced that . It never sees any of the intermediate states, witnesses, or fold challenges. The entire computation history has been compressed into one relaxed R1CS instance and one proof.

Security Considerations for Folding

Folding schemes have a reputation in the zkVM community for being where security problems arise. This isn't accidental; the architecture creates several subtle attack surfaces.

Deferred verification. Traditional recursion verifies at each step: if something is wrong, you catch it immediately. Folding defers all verification to the final SNARK. Errors compound silently across thousands of folds before manifesting. Debugging becomes archaeology, trying to identify which of 10,000 folds went wrong.

The commitment to must be binding. The cross-term must be committed before the verifier sends challenge . If the prover can open this commitment to different values after seeing , soundness breaks completely: the prover can fold unsatisfied instances and make them appear satisfied. Nova uses Pedersen commitments (computationally binding under discrete log), so breaking the binding property would require solving discrete log. But implementation bugs in commitment handling have caused real vulnerabilities.

Accumulator state is prover-controlled. Between folding steps, the prover holds the running accumulated instance . The final SNARK proves this accumulated instance is satisfiable, but doesn't directly verify it came from honest folding. A malicious prover who can inject a satisfiable-but-fake accumulated instance breaks the chain of trust. The "decider" circuit must carefully check that public inputs match the accumulator state.

Soundness error accumulates. Each fold uses a random challenge to combine two instances. A cheating prover escapes detection only if the degree- cross-term identity accidentally holds at that , which Schwartz-Zippel bounds at probability per fold. Over independent folds, a union bound gives total soundness error . For folds over a 256-bit field with , this is , negligible. But for smaller fields or exotic parameters, verify the concrete security.

Implementation complexity. Folding has more moving parts than traditional recursion: cross-term computation, accumulator updates, commitment bookkeeping, the interaction between folding and the final decider SNARK. Each is a potential bug location. Several folding implementations have had soundness bugs discovered post-audit. The abstraction is elegant, but the implementation details are unforgiving.

Post-quantum vulnerability. Every folding scheme discussed in this chapter (Nova, HyperNova, ProtoStar) uses Pedersen commitments for the cross-term and the accumulated witness. Pedersen binding relies on discrete log, which Shor's algorithm breaks. A quantum attacker who can open commitments to different values after seeing the folding challenge destroys soundness entirely, since they can fold unsatisfied instances and make them appear satisfied. The inner folding loop is therefore not post-quantum safe. The same vulnerability appears in composition: the STARK→Groth16 wrapper that production zkVMs use (the hybrid row in the compatibility table below) reintroduces pairing assumptions at the final step, making the on-chain proof quantum-vulnerable even though the inner STARK is hash-based. Lattice-based folding schemes (LatticeFold, Neo, SuperNeo) replace Pedersen with Module-SIS commitments to address this, though none are in production as of this writing. Hash-based full recursive verification (STARK proving a STARK verifier) avoids the problem entirely but at higher per-step cost.

None of this means folding is insecure against classical adversaries. It means the security argument is more delicate than "run a SNARK at each step." The efficiency gains are real, but so is the need for careful implementation, thorough auditing, and awareness of the quantum horizon.

Folding and the PIOP paradigms

Folding schemes operate at a different level of the proof-system stack than PIOPs or PCS:

  1. Constraint system: R1CS, Plonkish, CCS, AIR
  2. PIOP paradigm: How you prove constraints (quotienting or sum-check)
  3. Recursion strategy: How you chain proofs (full verification, folding, accumulation)

Nova's folding operates at level 3. It takes R1CS instances and folds them algebraically, without committing to a specific PIOP for the final proof. Folding originated in the sum-check lineage (Nova came from the Spartan team, and relaxed R1CS fits naturally with multilinear machinery), but it is no longer confined to it. The "Beyond Nova" section below develops how folding has generalized to CCS, Plonkish, and pairing-based systems, and a compatibility table after that section maps the full landscape of which components pair naturally across all three levels.

Folding versus traditional recursion

AspectTraditional RecursionFolding (Nova)
Per-step overheadFull SNARK verificationTwo group operations
Curves neededPairing-friendly or cycleAny curve works
Final proofProves last recursive stepProves folded instance
Prover bottleneckVerification overheadActual computation

For small (hash function evaluations, state machine transitions), folding is an order of magnitude faster than traditional recursion. The per-step cost drops from thousands of gates to tens of operations.

Beyond Nova: HyperNova and ProtoStar

Nova achieves cheap IVC for R1CS. R1CS is fully general (any NP computation can be expressed, as Spartan demonstrated in Chapter 19), but its degree-2 restriction means that higher-degree operations must be decomposed into auxiliary variables. Encoding Poseidon's S-box, for example, requires intermediate squaring steps that inflate the constraint count. Custom gates (which handle common operations in fewer constraints) and higher-degree constraints (which pack more logic per gate) can reduce this overhead substantially. Lookups and structured access patterns are a separate concern: R1CS-based systems handle these efficiently through memory checking (Chapter 21's Lasso), not through the constraint system itself. The motivation for CCS is primarily constraint degree and gate flexibility, not lookup support. The question is whether folding can extend to these richer constraint representations without losing Nova's per-step efficiency.

The CCS Abstraction

The answer starts with a common language for constraints. Customizable Constraint Systems (CCS), introduced in Chapter 8, unify R1CS, Plonkish, and AIR under one framework. As a reminder, the core equation is:

Each term takes a Hadamard product () over the matrix-vector products for matrices in multiset , then scales by coefficient . The multiset sizes determine constraint degree: R1CS uses (degree 2), higher-degree gates use larger multisets, linear constraints use .

CCS matters for folding because it gives the scheme designer one interface to target. HyperNova folds CCS instances directly, so any constraint system expressible as CCS, which includes R1CS, Plonkish, and AIR, inherits folding automatically. You can fold circuits written in different constraint languages without converting to a common format first. The abstraction pays for itself when you want custom gates, higher-degree constraints, or mixed constraint types within a single IVC computation.

HyperNova: Folding CCS

HyperNova extends Nova's folding approach to CCS, but the generalization isn't straightforward. The degree problem that Nova sidestepped returns with a vengeance.

The degree problem. Recall Nova's cross-term: when folding into a degree-2 constraint, the expansion produces terms at , , and . The error vector absorbs the cross-term at .

For a degree- constraint, folding produces terms at powers . Each intermediate power generates a cross-term that must be absorbed. Naive relaxation requires error vectors, each requiring a commitment. The prover cost scales with degree.

HyperNova avoids this by observing that if one of the two instances is already linear (degree 1), then the cross-terms don't explode. Folding a linear instance with a degree- instance produces at most degree , with manageable cross-terms.

LCCCS (Linearized CCS). HyperNova converts an accumulated CCS instance into a different form before folding. Recall from above that a CCS constraint is a vector equation: all entries of must equal zero. The "linearized" version collapses this to a scalar equation by taking a random linear combination of all constraints. Given random , weight each constraint by (the multilinear extension of the equality predicate from Chapter 4):

By Schwartz-Zippel, if any entry of the original vector is non-zero, this scalar equation fails with high probability over random . This is the standard "batch to a single equation" trick.

The resulting scalar can be expressed in terms of multilinear extension evaluations: is the MLE of evaluated at . The witness now appears only through these evaluation claims, which sum-check can reduce to polynomial openings.

Why call this "linearized"? The term refers to how the folding works, not the constraint degree. When folding an LCCCS (which is a scalar evaluation claim) with a fresh CCS instance (a vector constraint), the interaction between them produces manageable cross-terms. The scalar form of LCCCS means folding doesn't multiply the number of error terms the way naive CCS folding would.

HyperNova therefore folds asymmetrically: the two instances being combined have different shapes, and this mismatch is what prevents the cross-term explosion that would occur if both were high-degree.

To see the contrast, recall that Nova folds two things of the same shape: relaxed R1CS instance + relaxed R1CS instance → relaxed R1CS instance. Both are degree-2, and the error vector absorbs the single degree-1 cross-term.

HyperNova folds two things of different shapes:

  • Running instance (LCCCS): A scalar claim about polynomial evaluations
  • Fresh instance (CCS): A vector constraint over entries

You're not combining "vector + vector → vector." You're combining "scalar + vector → scalar." This asymmetry is what prevents cross-term explosion.

Sum-check is what bridges the two shapes. It takes a claim about a sum (the CCS vector constraint, batched into a scalar) and reduces it to an evaluation claim at a random point. After sum-check, both the running LCCCS and the fresh CCS have been reduced to evaluation claims at the same random point. These scalar claims can be linearly combined without degree blowup.

The loop:

  1. Running LCCCS: A scalar claim ""
  2. Fresh CCS arrives: A vector constraint that must hold at all positions
  3. Sum-check: Batch the CCS into a scalar claim at a new random point , then combine with the LCCCS
  4. Result: A new scalar claim at , another LCCCS ready for the next fold

The sum-check rounds are the cost of generality: rounds of interaction (or Fiat-Shamir hashing). But once sum-check finishes, combining the evaluation claims needs only one multi-scalar multiplication, the same per-fold cost as Nova regardless of constraint degree.

In Nova, the error vector absorbs degree-2 cross-terms algebraically. In HyperNova, sum-check absorbs arbitrary-degree cross-terms interactively. Different mechanisms, same goal: constant prover cost per fold.

Additional benefits:

  • Multi-instance folding: Fold instances simultaneously by running sum-check over all at once. The cost is additional sum-check rounds. This enables efficient PCD (proof-carrying data), where proofs from multiple sources combine into one.
  • Zero-knowledge requires additional work: Sum-check round polynomials leak witness information (their coefficients are linear combinations of witness entries). Neither Nova nor HyperNova is zero-knowledge out of the box. The BlindFold technique (discussed later in this chapter) addresses this by committing to round polynomial coefficients and deferring their verification via folding.

ProtoStar: Accumulation for Special-Sound Protocols

ProtoStar takes a different generalization path. Rather than targeting a specific constraint system (as Nova targets R1CS and HyperNova targets CCS), it provides accumulation for any special-sound interactive protocol, regardless of how many messages the prover and verifier exchange. Sigma protocols (Chapter 16) are the simplest case: 3 messages, special soundness from two transcripts. But many proof components (sum-check rounds, polynomial evaluation arguments, lookup arguments) are also special-sound protocols with more rounds. ProtoStar accumulates them all under one framework.

Why special-soundness enables accumulation. A special-sound protocol has a key property: the verifier's check is a low-degree polynomial equation , where is the public input, is the prover's messages, and is the verifier's challenge. The degree of in is typically small (often 1 or 2).

This algebraic structure is exactly what folding exploits. Given two protocol instances with the same structure, you can take a random linear combination:

If both and , then for any . If either is non-zero, with probability at most over random . The accumulated check is equivalent to both original checks, with negligible soundness loss.

The cost difference. ProtoStar's per-fold cost is roughly 3 scalar multiplications, compared to 1 MSM for Nova/HyperNova (the comparison table below summarizes this). This reflects different trade-offs:

  • Nova/HyperNova commit to the cross-term (or run sum-check), requiring one multi-scalar multiplication per fold
  • ProtoStar works directly with the protocol's algebraic structure, avoiding new commitments but requiring the prover to compute and send "error polynomials" that capture the cross-terms

For degree-2 checks (like most -protocols), this means a few scalar multiplications instead of an MSM. The MSM dominates for large witnesses, so ProtoStar can be faster when the step function is small.

Lookup support. ProtoStar handles lookup arguments with overhead in the lookup table size, compared to for HyperNova. The difference: HyperNova encodes lookups via sum-check over the table, adding rounds. ProtoStar accumulates the lookup protocol directly, paying only for the protocol's native degree. For applications with large tables (memory, range checks), this matters.

ProtoGalaxy. A refinement of ProtoStar that reduces the recursive verifier's work further. The key observation: when folding instances, naive accumulation requires verifier work. ProtoGalaxy uses a Lagrange-basis trick to compress this to field operations plus a constant number of hash evaluations. For multi-instance aggregation (combining proofs from many sources), ProtoGalaxy approaches the minimal possible overhead.

Comparison

FeatureNovaHyperNovaProtoStar
Constraint systemR1CS onlyAny CCS (R1CS, Plonk, AIR)Any special-sound protocol
Constraint degree2ArbitraryArbitrary
Per-step prover cost1 MSM1 MSM3 scalar muls
Lookup supportVia R1CS encoding
Zero-knowledgeRequires blindingFree from foldingRequires blinding
Multi-instanceSequential onlyNative supportNative support

When to Use What: A Practitioner's Guide

The progression from Nova to HyperNova to ProtoStar isn't a simple linear improvement. Each occupies a different point in the design space, and the "best" choice depends on your bottleneck.

The deciding factor is where the prover spends its time. Decompose total proving cost into two parts:

  • Step cost : Proving one iteration of your function (one hash, one VM instruction, one state transition)
  • Accumulation overhead : The cost of folding/recursing that step into the running proof

For traditional IVC (recursive SNARKs), is the verifier circuit size, typically thousands to tens of thousands of constraints. For folding, drops to a handful of group operations. The ratio determines whether folding helps.

Folding wins when is small:

  • VDFs (repeated squaring): a few hundred constraints per square
  • Simple state machines: hundreds to low thousands
  • Hash chain proofs: constraint count of one hash invocation

In these cases, traditional IVC spends most of its time proving the verifier, not the computation. Folding eliminates this overhead almost entirely.

Folding's advantage shrinks when is large:

  • zkVM instruction execution: to constraints per instruction
  • Complex smart contract proofs: dominates regardless
  • Batch proofs of many operations: amortization across the batch matters more than per-step overhead

When , the prover spends 95%+ of time on the step function whether using folding or traditional IVC. Folding's 100× reduction in becomes a 5% improvement in total cost.

Engineering maturity matters beyond raw performance:

  • Folding schemes are newer. Less battle-tested, fewer audits, more subtle security pitfalls.
  • AIR/STARK tooling is mature. Well-understood compilation, debugging, and optimization paths.
  • Folding debugging is harder. Errors compound across folds; traditional recursion catches bugs per-step.

Some production teams (Nexus, for example) explored folding and reverted to AIR-based approaches. Not because folding is inferior in theory, but because for their specific (complex zkVM execution), the engineering complexity didn't pay off.

The following table summarizes the decision:

ScenarioRecommended Approach
Small step function (< 1000 constraints), millions of stepsFolding (Nova/HyperNova)
Large step function (> 10000 constraints), complex logicTraditional IVC or STARK
Need multi-instance aggregationHyperNova or ProtoStar
Custom gates, non-R1CS constraintsHyperNova (CCS) or ProtoStar
Maximum simplicity, proven toolingSTARK/AIR
Smallest possible final proofFold, then wrap in Groth16

CycleFold: Efficient Curve Switching

All folding schemes face the curve-cycle problem from earlier in this chapter: the folding verifier performs group operations, which are expensive to prove in-circuit over a different field. But folding has a unique advantage here that traditional recursion doesn't: the "verifier work" per step is tiny (a few scalar multiplications), not a full SNARK verification. CycleFold exploits this.

In Nova's IVC loop, the prover updates the running commitment:

This is a scalar multiplication on curve . If our main circuit is over the scalar field of , we can't compute this operation natively. The curve points have coordinates in (the base field), and arithmetic inside an circuit is expensive. This is the same field mismatch from the curve cycle section above, but now at the folding level rather than the full-verification level. CycleFold's solution relies on having a curve cycle (Pasta, BN254/Grumpkin) where a second curve has scalar field , making the expensive operation native on .

Traditional recursion would embed the entire verifier (including pairings) in the circuit, paying hundreds of thousands of constraints. But Nova's "verifier" is just this one scalar multiplication. Can we handle it more cheaply?

The CycleFold idea. Instead of proving the scalar multiplication in the main circuit, defer it to a separate accumulator on the cycle curve .

Recall the cycle: has scalar field and base field ; has scalar field and base field . The scalar multiplication on involves arithmetic (the curve operations). But circuits are natively over . So:

  1. Main circuit (on ): Proves " was computed correctly" and that the folding challenges were derived correctly. It takes the result of the commitment update as a public input but does not check the scalar multiplication that produced it. The main circuit trusts this value for now.

  2. Auxiliary circuit (on ): A tiny circuit that checks one claim: "given , , and , the output is correct." Because 's scalar field is , the curve arithmetic on points is native here. This circuit is roughly 10,000 constraints, compared to 100,000+ for emulating the same operation non-natively in the main circuit.

  3. Two parallel folding loops. The main accumulator on folds each step's computation claim, just as in standard Nova. The auxiliary accumulator on folds each step's commitment-update claim. Both accumulators grow in parallel: one step of the IVC loop produces one fold on each curve.

  4. Two final SNARKs. At the end, the prover produces one SNARK on (proving the accumulated computation is correct) and one SNARK on (proving all commitment updates were correct). The verifier checks both. Each SNARK operates over its native field, so neither requires emulation.

The two accumulators are coupled through shared public inputs: the commitment values that the main circuit assumes correct and the auxiliary circuit verifies. Soundness holds because a cheating prover who fakes a commitment update on the main side will fail the auxiliary accumulator's check at the end.

After steps:

  • Main accumulator: one final SNARK on proves " was applied correctly times"
  • Auxiliary accumulator: one final SNARK on proves "all commitment updates were computed correctly"

Both SNARKs are over their native fields. No cross-field emulation anywhere.

The cost breakdown:

  • Per step: ~10,000 constraints on the cycle curve (the scalar multiplication circuit)
  • Final proof: two SNARKs, one on each curve
  • Total overhead: roughly constraints across all steps, versus without CycleFold

For long computations, this is a 10× reduction in the curve-cycle overhead.

CycleFold only works for folding, not traditional recursion. Traditional recursion embeds the entire verifier in circuit at each step, including pairings, hash evaluations, and complex checks that are all entangled with the soundness argument. You cannot split these across two curves because the verifier's logic is monolithic. Folding's per-step verifier, by contrast, is just a few scalar multiplications on commitments, cleanly separable from the computation proof. This modularity is what lets CycleFold put the two concerns on different curves.

CycleFold applies to Nova, HyperNova, and ProtoStar, making all of them practical over curve cycles like Pasta (Pallas/Vesta) or BN254/Grumpkin.

The full compatibility landscape

With Nova, HyperNova, ProtoStar/ProtoGalaxy, and Mira all developed, we can now map the full landscape of which proof-system components pair naturally. Chapter 22 observed that "one choice determines the rest": picking univariate polynomials implies roots of unity, FFT, and KZG or FRI; picking multilinear polynomials implies the hypercube, the halving trick, and IPA or Dory. The same principle extends to the recursion layer, though the boundaries have softened as folding generalizes:

LineageConstraintsPIOP mechanismPCSRecursion
Sum-check nativeR1CSSum-check (multilinear)IPA, Dory, HyraxNova (relaxed R1CS folding)
Sum-check + CCSCCS (captures R1CS, Plonkish, AIR)Sum-check (multilinear)IPA, Dory, HyraxHyperNova (CCS folding via sum-check)
Quotienting nativePlonkishQuotienting (univariate)KZGMira (pairing-based folding); or curve cycles
STARKAIRQuotienting (univariate)FRIFull verification (recursion via hash-based PCS)
HybridMixedSTARK inner, Groth16 outerFRI inner, KZG outerComposition (STARK→Groth16 wrap)

ProtoStar and ProtoGalaxy do not appear in a single row because they are protocol-agnostic: they accumulate any special-sound interactive protocol, whether it comes from the sum-check or quotienting tradition. A ProtoStar-based system can accumulate sum-check rounds (landing in the sum-check rows) or Plonkish quotient checks (landing in the quotienting row). They sit orthogonally to the lineage distinction, operating at the level of the interactive protocol's algebraic structure rather than the constraint system or PCS.

HyperNova also crosses boundaries: it uses sum-check as its PIOP mechanism but folds CCS instances, which can express Plonkish and AIR constraints. The constraint system and the PIOP mechanism are not always from the same lineage.

Within each pure lineage (sum-check native, quotienting native), each component exists because the previous one requires it. In the sum-check lineage, Nova's folding produces a linear combination of witness vectors (). The folded witness is a multilinear polynomial's evaluation table, because R1CS witnesses are evaluation tables over the Boolean hypercube (Chapter 19). Sum-check is the natural PIOP for multilinear polynomials because it reduces claims about exponentially many hypercube evaluations to a single random evaluation (Chapter 3). The PCS must then open a multilinear polynomial at a random point in , which is exactly what IPA and Dory are built to do (Chapter 22). Each layer's choice is forced by the previous layer's output format.

In the quotienting lineage, the chain starts differently. Plonkish constraints are verified via quotient polynomials over roots of unity, which FFT computes. KZG commits to the same univariate polynomials the FFT produces, reusing the evaluation domain. Mira folds pairing-based arguments directly, staying within the univariate/KZG world. For STARK recursion, FRI replaces KZG; the verifier is hash-based and the recursion proceeds through full in-circuit verification rather than folding.

The hybrid row exists for a different reason: complementary strengths across stacks. STARKs (quotienting + FRI) give fast transparent proving; Groth16 (quotienting + KZG) gives tiny constant-size proofs. Composition bridges them by proving "the STARK verifier accepted" with Groth16, using hash-based transparency for the bulk and pairing-based compactness for on-chain delivery.

The heuristic for choosing a recursion strategy follows from which constraints matter most. For pure sum-check IVC with R1CS (simplest folding, smallest per-step overhead), Nova suffices. For richer constraint languages (custom gates, higher degree) with sum-check-based folding, HyperNova over CCS. For existing Plonkish/KZG infrastructure, Mira for pairing-based folding or ProtoGalaxy for general accumulation. For the smallest final proofs on-chain, fold with whichever machinery suits your stack and compose with Groth16 at the very end.

BlindFold: Folding for Zero-Knowledge

Most deployed "zkVMs" were not, for years, truly zero-knowledge. They were succinct (short proofs, fast verification) but the proofs leaked information about the prover's private inputs. The reason: the sum-check protocol at their core is not zero-knowledge. Each round polynomial is a deterministic function of the witness, and its coefficients are linear combinations of the witness entries. After enough rounds, a verifier accumulates enough constraints to recover the witness entirely. Chapter 18 showed this leakage concretely.

BlindFold (Section 7 of HyperNova, Kothapalli, Setty, Tzialla, 2023) resolves this in three moves:

Move 1: Hide the round polynomials. For a degree- round polynomial , the prover sends Pedersen commitments instead of the field elements . The verifier sees only opaque group elements. This immediately hides the witness, but the verifier can no longer check anything (the consistency check requires knowing the actual coefficients).

Move 2: Encode the verifier as an R1CS. Every check the sum-check verifier would have performed becomes a constraint. The consistency check (verifying equals the previous claim) is one linear constraint. The evaluation check (verifying at the Fiat-Shamir challenge) is another, with the powers baked into the R1CS matrices as constants since both parties derive them from the public transcript. For a sum-check with rounds, this R1CS has roughly constraints, a tiny system.

Move 3: Fold with a random witness, then prove. Proving this R1CS with Spartan directly would reintroduce the problem (Spartan's own sum-check messages would leak the witness). BlindFold breaks the circularity by folding the real witness with a uniformly random witness before running Spartan. The folded witness is uniformly distributed for any , because the map is an affine bijection on . This is the algebraic one-time pad: just as is uniform for uniform key regardless of message , the folded witness is uniform regardless of the real witness. Spartan can now prove the folded R1CS in the clear without being zero-knowledge itself, because the data it operates on reveals nothing about .

The folding uses the same Nova protocol from earlier in this chapter (cross-term computation, commitment, Fiat-Shamir challenge, linear combination of witnesses and error vectors). The only difference from IVC folding is that the second instance is random rather than derived from a computation step. The final SNARK proves the folded instance is satisfiable; the verifier folds commitments homomorphically (Pedersen's additive structure) and checks the Spartan proof without ever seeing any witness value.

The cost is remarkably low. The blinded Phase 1 proof (Pedersen commitments replacing field elements) is often shorter than the original non-ZK proof. The Phase 2 BlindFold proof (Spartan over constraints) adds roughly 3 KB. In multi-stage protocols, BlindFold encodes all stages' verifier checks into a single R1CS and folds once; the marginal cost of adding another sum-check stage is two more constraints. Earlier approaches (masking polynomials per Libra, -protocol wrappers per Hyrax) pay their overhead per stage and scale with the number of sum-check invocations.

Choosing a strategy

The chapter has covered three recursion-related techniques, each at a different level of the proof system stack. The decision tree is:

  1. Composition is for combining two different proof systems. Use it when you need a small final proof from a fast-but-large-proof inner system (the STARK→Groth16 pattern), or when wrapping a non-ZK system in a ZK outer layer. It is a one-time operation, not an iterative one.

  2. IVC (folding or traditional recursion) is for unbounded sequential computation. Nova handles R1CS; HyperNova generalizes to CCS (capturing Plonkish and AIR); ProtoStar/ProtoGalaxy accumulate any special-sound protocol. The choice between them depends on the step function size relative to the verifier overhead and on which constraint system your application uses; the "When to Use What" guide in the Beyond Nova section develops this in detail. The compatibility table above maps which folding scheme pairs with which PCS and PIOP.

  3. Direct proving (no recursion) is appropriate when the computation fits in a single circuit. Every recursive system has a minimum useful circuit size: if , recursion adds overhead without benefit. Traditional recursion has a high threshold (- constraints for in-circuit verification). Folding lowers it dramatically (under 100 group operations per step), which is why folding made recursion practical for small step functions where it was previously absurd.

Key Takeaways

  1. Composition combines complementary proof systems. The outer prover handles only the inner verifier circuit, which is much smaller than the original computation. This is why Groth16 wrapping doesn't reintroduce the slow prover: Groth16 proves a circuit of size , not .

  2. Recursion compresses through self-reference. Each recursive layer proves a smaller circuit (the previous layer's verifier). After layers, verification cost reaches a constant. IVC extends this to unbounded sequential computation, where each step's proof attests to the entire history.

  3. Field mismatch is the main obstacle to recursion. Pairing-based verifiers do arithmetic, but circuits constrain in . Emulation blows up circuit size by 100×. Curve cycles (Pasta, BN254/Grumpkin, BLS12-377/BW6-761) solve this by alternating between matched curve pairs where each step's verifier arithmetic is native.

  4. Deep recursion weakens security proofs but not security. Extraction requires rewinds for depth , degrading provable security by bits per layer. No known attack exploits the depth; the gap is between what we can prove and what we believe.

  5. Folding replaces per-step verification with accumulation. Two relaxed R1CS claims fold into one via random linear combination, with the error vector absorbing the cross-terms. Only the final accumulated claim requires a SNARK. Per-step cost drops from thousands of constraints to a handful of group operations, making IVC practical for small step functions where traditional recursion's overhead dominated.

  6. Folding has generalized beyond R1CS. HyperNova folds CCS (which captures R1CS, Plonkish, and AIR) via asymmetric folding with sum-check. ProtoStar and ProtoGalaxy accumulate any special-sound protocol, sitting orthogonally to the sum-check/quotienting divide. Mira folds pairing-based arguments directly.

  7. The proof-system stack has natural compatibility lanes. Choosing a constraint system, PIOP, PCS, and recursion strategy is not four independent decisions. Sum-check folding pairs with multilinear polynomials and IPA; quotienting pairs with univariate polynomials and KZG/FRI. HyperNova crosses the boundary by accepting quotienting-style constraints (via CCS) while using sum-check internally.

  8. Pedersen-based folding is not post-quantum safe. Nova, HyperNova, and ProtoStar all use Pedersen commitments (discrete-log binding). The STARK→Groth16 wrapper reintroduces pairing assumptions at the final step. Lattice-based folding (LatticeFold, Neo, SuperNeo) and hash-based full STARK recursion are the emerging PQ-safe alternatives.

  9. BlindFold adds zero-knowledge via the algebraic one-time pad. Commit to sum-check round polynomials, encode the verifier as a tiny R1CS, fold the real witness with a random one ( is uniform for uniform ), and prove the folded instance with Spartan. Spartan need not be ZK because the data it sees is already masked. Cost: KB.

  10. The decision tree has three levels. Composition for combining complementary systems (one-time wrapping). Folding or traditional recursion for unbounded sequential computation (the ratio determines which). Direct proving when the computation fits in a single circuit. Every recursive system has a minimum useful circuit size; folding lowered this threshold by orders of magnitude.

Chapter 24: Choosing a SNARK

In 2016, Zcash launched with Groth16. The choice seemed obvious: smallest proofs, fastest verification, mature implementation. But Groth16 required a trusted setup ceremony. Six participants generated randomness, then destroyed their computers. The protocol was secure only if at least one participant was honest. If all six had colluded or been compromised, they could reconstruct the secret, mint unlimited currency, and no one would ever know.

Three years later, the Zcash team switched to Halo 2. No trusted setup. The proofs were larger. The proving was slower. But the existential risk evaporated.

This is the nature of SNARK selection: every choice trades one virtue for another. There is no universal optimum, no "best" system. There is only the right system for your constraints, your threat model, your willingness to accept which category of failure.

The preceding chapters developed a complete toolkit: sum-check protocols, polynomial commitments, arithmetization schemes, zero-knowledge techniques, composition and recursion. Each admits multiple instantiations. The combinations number in the dozens. Each combination produces a system with different properties: proof sizes ranging from 128 bytes to 100 kilobytes, proving times from milliseconds to hours, trust assumptions from ceremony-dependent to fully transparent.

This chapter provides a framework for navigating that landscape. Not a prescription (the field moves too fast for prescriptions) but a map of the territory and a compass for orientation.

The Five Axes of Trade-off

Every SNARK balances five properties. Improve one, and another suffers. The physics of cryptography permits no free lunch.

Proof Size

How many bytes cross the wire? For on-chain verification, proof size translates directly to gas costs (the blockchain section below gives concrete numbers). The spectrum spans three orders of magnitude:

  • Constant-size (~100-300 bytes): Groth16, PLONK with KZG
  • Logarithmic (~1-10 KB): Bulletproofs, Spartan
  • Polylogarithmic (~10-100+ KB): STARKs, FRI-based systems

For on-chain verification, proof size is often the binding constraint. Everything else is negotiable.

Verification Time

How fast can the verifier check the proof?

On-chain, verification time translates directly to gas costs. A pairing operation costs roughly 45,000 gas. Groth16 needs 3 pairings. PLONK needs about 10. STARKs replace pairings with hashes, but require many of them.

The hierarchy:

  • Constant-time (~3 pairings): Groth16
  • Logarithmic (~10-20 pairings): PLONK, IPA-based systems
  • Polylogarithmic (hash-dominated): STARKs

Groth16's 3-pairing verification is hard to beat. Everything else is playing catch-up. But pairings rely on discrete log, which Shor's algorithm breaks, so this advantage may not survive the quantum transition.

Prover Time

How fast can an honest prover generate a proof?

For small circuits, this barely matters. For zkVMs processing real programs, it's everything.

Consider a billion-constraint proof. At , with each field operation taking 10 nanoseconds, proving takes about 10 seconds. At , with , the same proof takes 5 minutes. At , it takes 300 years.

The hierarchy:

  • Linear in constraint count: Sum-check-based systems (Spartan, Lasso, Jolt)
  • Quasilinear (): PLONK, Groth16, FFT-dominated systems
  • Superlinear: Some theoretical constructions (impractical at scale)

At billion-constraint scale, the factor (roughly 30) is the difference between a 10-second proof and a 5-minute proof. This is why zkVMs have increasingly moved toward sum-check-based architectures: when proving a million CPU instructions at 50 constraints each, linear time is a requirement, not a luxury.

The gap is wider than the asymptotics suggest. FFT-based provers (Groth16, PLONK) perform butterfly operations that jump across memory at strides of , thrashing caches and stalling on RAM latency (Chapter 20 develops this in detail). Sum-check provers scan data linearly, keeping it streaming through the cache hierarchy. At billion-constraint scale, memory access patterns can dominate wall-clock time even more than the operation count, compounding sum-check's asymptotic advantage with a large constant-factor improvement.

Trust Assumptions

What must you trust for security?

The Zcash ceremony involved six participants on three continents. Each generated randomness, contributed to the parameters, then destroyed their machines. One participant used a Faraday cage. Another broadcast from an airplane. The paranoia was justified: if all six colluded or were compromised, they could mint unlimited currency, and the counterfeits would be cryptographically indistinguishable from real coins.

This is the price of trusted setup.

The spectrum:

  • Circuit-specific trusted setup (Groth16): Each circuit requires its own ceremony. Change the circuit, repeat the ritual.
  • Universal trusted setup (PLONK, Marlin): One ceremony supports all circuits up to a size bound. The trust is amortized, not eliminated.
  • Transparent (STARKs, Bulletproofs): No trusted setup. Security derives entirely from public-coin randomness and standard assumptions.

Transparency eliminates an entire category of catastrophic failure, at the cost of larger proofs, sometimes by two orders of magnitude.

Post-Quantum Security

Will the system survive Shor's algorithm?

Shor's algorithm solves discrete logarithm and factoring in polynomial time on a quantum computer. The day a cryptographically relevant quantum computer boots, every pairing-based SNARK becomes insecure. Groth16 proofs could be forged. KZG commitments could be opened to false values. The entire security model collapses.

The threatened systems:

  • All pairing-based SNARKs (Groth16, KZG-based PLONK)
  • All discrete-log commitments (Pedersen, Bulletproofs)

The resistant systems form a growing family:

  • Hash-based constructions (STARKs with FRI, WHIR-based systems)
  • Sum-check + hash-based PCS (Whirlaway combines SuperSpartan with WHIR, achieving both multilinear proving and post-quantum security with proofs smaller than FRI at the same security level)
  • Lattice-based commitments (LatticeFold, Neo; under active research, not yet production-ready)

The sum-check tradition is no longer tied to discrete-log commitments. WHIR (EUROCRYPT 2025) provides a hash-based multilinear PCS with faster verification than FRI, enabling sum-check-based provers to achieve post-quantum security without switching to the univariate/STARK paradigm. This closes a gap that previously forced sum-check systems to rely on IPA or KZG, both quantum-vulnerable.

When will quantum computers arrive? Estimates as of 2026 range from 5 to 20 years for cryptographically relevant machines, with the timeline compressing as investment accelerates. For a private transaction, the uncertainty is tolerable. For infrastructure meant to last decades (identity systems, legal records, financial settlements), the Ethereum Foundation's response is instructive: provable 128-bit security by end of 2026, with proof-size caps that push the ecosystem toward hash-based schemes.

The System Landscape

Each major proof system occupies a different position in the trade-off space. None dominates all others. The choice depends on which constraints bind tightest.

Groth16: The Incumbent

Groth16 has the smallest proofs in the business: 128 bytes, three group elements. Verification requires three pairings. Implementations exist in every language, optimized for every platform, battle-tested across billions of dollars in transactions.

The cost is trust. Every circuit needs its own ceremony. Change one constraint, and the parameters are worthless. The ceremony participants must be trusted absolutely, or the "toxic waste" (the secret randomness) must never be reconstructed.

This combination (minimal proofs, maximal trust) made Groth16 the default for years. It remains dominant for on-chain verification where proof size is the binding constraint and the application can absorb a one-time ceremony.

PLONK: The Flexible Middle Ground

PLONK solved Groth16's upgrade problem. A single ceremony generates parameters that work for any circuit up to a size bound. Modify the circuit, keep the same parameters. The trust is amortized across an ecosystem rather than concentrated on a single application.

Proofs grow to 500-2000 bytes. Verification requires more pairings. But the flexibility is transformative: zkEVMs can upgrade their circuits without coordinating new ceremonies. Application developers can iterate without security theater.

Custom gates push PLONK further. Where Groth16 accepts only R1CS, PLONK's constraint system accommodates specialized operations. A hash function that requires 10,000 R1CS constraints might need only 100 Plonkish constraints with a custom gate.

Variants proliferated: UltraPLONK, TurboPLONK, HyperPLONK, each optimizing a different axis (proof size, custom gates, multilinear polynomials). PLONK became the platform on which much of the industry standardized for general-purpose proving.

STARKs: The Transparent Option

STARKs eliminate trust entirely. No ceremony. No toxic waste. No existential risk from compromised participants. Security rests on collision-resistant hashing, nothing more.

The price is size. STARK proofs run 50-100+ KB, sometimes larger. Verification is polylogarithmic rather than constant. For on-chain deployment, this can be prohibitive.

But STARKs offer compensations. Provers approach linear time (Chapter 20 develops how FRI folding and small-field techniques achieve this). Hash-based constructions are believed to be post-quantum secure, since the best known quantum attack (Grover's algorithm) provides only a quadratic speedup, manageable by doubling the hash output size. And there's a philosophical clarity: the proof stands alone, answerable only to mathematics.

StarkWare built a company on this trade-off. For rollups processing millions of transactions, the amortized proof cost per transaction becomes negligible. The prover speed matters; the verifier runs once.

Bulletproofs: The Pairing-Free Path

Bulletproofs occupy a specific niche: transparency without the STARK size explosion. Proofs grow logarithmically (typically 600-700 bytes for range proofs). No trusted setup. No pairings required.

The tradeoff is that verification takes linear time in the circuit size. For small circuits (range proofs, confidential transactions), this is acceptable. For large computations, it becomes prohibitive.

Monero adopted Bulletproofs for confidential amounts. The proofs are small enough to fit in transactions, transparent enough to satisfy decentralization purists, and specialized enough for the specific task of range proofs.

But Bulletproofs aren't post-quantum. They rely on discrete log hardness. The same quantum computer that breaks Groth16 breaks Bulletproofs.

Sum-check-based systems

Spartan, Lasso, Jolt, HyperPlonk, Binius, and the Whirlaway stack all belong to the sum-check tradition described in Chapters 19-21. Their shared characteristic is linear-time proving, which at billion-constraint scale is the difference between a 10-second proof and a 5-minute one.

Virtual polynomials minimize commitment costs (Chapter 21). Sparse sum-check handles irregular constraint structures naturally. The apparatus is optimized for general-purpose computation, which is why zkVMs have increasingly adopted sum-check architectures.

Sum-check systems produce larger proofs (logarithmic, not constant), have newer implementations (less battle-tested), and historically depended on discrete-log-based PCS (IPA, KZG) that made them quantum-vulnerable. This last limitation is dissolving from two directions. WHIR (EUROCRYPT 2025) provides a hash-based multilinear PCS with faster verification than FRI; Hachi (eprint 2026/156) provides a lattice-based multilinear PCS under Module-SIS with KB proofs and 12.5× faster verification than prior lattice schemes. Whirlaway (SuperSpartan + WHIR) demonstrates that sum-check-based systems can achieve post-quantum security without switching to the univariate/STARK paradigm. The Ethereum Foundation's Lean Ethereum project is building a minimal zkVM on this stack (KoalaBear field, WHIR PCS, sum-check proving), targeting post-quantum on-chain verification.

The converging zkVM landscape

The boundaries between the categories above are blurring in production zkVMs. The major systems as of 2026:

  • SP1 (Succinct): migrated from STARK-based (SP1 Turbo, FRI over BabyBear) to sum-check-based (SP1 Hypercube, multilinear polynomials with a jagged PCS from Chapter 21 and Logup-GKR). Proves over 93% of Ethereum blocks in under 12 seconds (average 10.3s) on a cluster of 160 RTX 4090 GPUs ($300-400K in hardware).
  • RISC Zero: STARK-based with FRI over BabyBear, Groth16 wrapper for on-chain verification. Proves Ethereum blocks in under 45 seconds.
  • Jolt (a16z): pure sum-check with Lasso lookups (Chapter 21) and Twist/Shout memory checking. Over 1 million RISC-V cycles per second on a 32-core CPU.
  • ZKsync Airbender: STARK-based over Mersenne31 with a custom DEEP-ALI implementation.
  • Zisk (Polygon spinoff): RISC-V 64 with a 1.5 GHz execution engine, optimized for low-latency distributed proving.
  • Lean Ethereum (Ethereum Foundation): minimal zkVM using Whirlaway (SuperSpartan + WHIR) over KoalaBear, targeting provable 128-bit post-quantum security.

All of these use small fields (BabyBear or Mersenne31), AIR or CCS constraints, and Logup-style bus arguments for cross-table consistency. The convergence on shared primitives (Chapter 20) is striking even as the architectural choices diverge.

Application-Specific Guidance

Theory meets practice at the application boundary. The abstract trade-offs crystallize into concrete decisions.

Blockchain Verification (On-Chain)

The verifier runs on Ethereum, paying gas for every operation. Two costs dominate: calldata (bytes shipped to the chain) and computation (opcodes executed on-chain).

At current gas prices, a 128-byte Groth16 proof costs about 20,000 gas in calldata. Verification adds roughly 150,000 gas for the pairing checks. Total: under 200,000 gas. A simple ETH transfer costs 21,000 gas. The proof verification is economically viable.

A 50 KB STARK costs 800,000 gas in calldata alone. Verification adds another 300,000-500,000 gas. Total: over a million gas. For individual transactions, this is often prohibitive.

Composition (Chapter 23) bridges the gap. Generate a STARK proof (transparent, fast prover), then prove "the STARK verifier accepted" with Groth16 (small proof, cheap verification). The inner STARK provides transparency; the outer Groth16 provides on-chain efficiency. The trust assumption applies only to the wrapper. The economics favor large computations: wrapping a million-constraint STARK in Groth16 adds $\approx 50{,}000$ constraints for the STARK verifier (5% overhead), while wrapping a thousand-constraint STARK adds 50× overhead.

zkRollups

Rollups amortize proof costs across thousands of transactions. A proof that costs 200,000 gas becomes 20 gas per transaction when it covers 10,000 transactions. The economics invert. Larger proofs become tolerable when they aggregate more computation.

StarkNet uses STARKs directly. The proofs are large (100+ KB), but the amortization across massive batches makes the per-transaction cost negligible. The transparency is a feature, not a compromise.

zkSync and Scroll use Groth16 wrappers around internal proving systems. The outer proof is tiny. The inner system can be whatever works best for their EVM implementation.

Prover efficiency matters most (the prover runs for every batch), while proof size matters less (it amortizes across all transactions in the batch).

zkVMs

Proving correct execution of arbitrary programs requires billions of constraints. The system landscape section above lists the major zkVMs; the choosing question is which architectural pattern fits your deployment.

The binding constraint is prover speed. A 10-second proof is a feature. A 10-minute proof is a bug. Virtual polynomials (Chapter 21) minimize commitment costs; lookup arguments (Chapter 14) replace expensive constraint checks with table lookups; small fields (Chapter 20) cut per-operation cost by 10×. Everything is oriented toward making the prover faster.

On-chain verification still demands small proofs, so zkVMs follow the same composition pattern described in the blockchain section above (STARK or sum-check inner proof, Groth16 wrapper for Ethereum). Eliminating this wrapper, via STARK verification precompiles on Ethereum or efficient hash-based on-chain verification via WHIR, is an active area of work.

Privacy-Preserving Applications

When zero-knowledge is the point (not just a bonus), implementation quality matters as much as theoretical properties.

Groth16 and PLONK produce ZK proofs with modest overhead. The masking techniques are well-understood. But implementation errors can leak information through timing side channels, error messages, or malformed proof handling.

STARKs require more care. The execution trace is exposed during proving, then masked. The masking must be done correctly. A bug here doesn't crash the system; it silently leaks witnesses. You might never know until the damage is done.

Tornado Cash used Groth16. Zcash used Groth16, then Halo 2. Aztec uses UltraPlonk and Honk (PLONK variants co-developed by the Aztec team). All chose mature implementations with extensive auditing, because privacy failures are catastrophic and silent.

Beyond the choice of proof system, privacy applications face a second decision that further constrains the options: where the prover runs. Server-side proving (zkRollups, zkVMs) runs provers on powerful infrastructure; the witness data reaches the server, which generates proofs and posts them on-chain. Privacy comes from the proof hiding witness details from the chain, not from the prover. Client-side proving (Aztec, Zcash) runs provers on user devices, so sensitive data never leaves the machine and only the proof and minimal public inputs reach the network.

Client-side proving constrains system choice dramatically. A browser or mobile device can't match datacenter hardware. Aztec's architecture is instructive: private functions execute locally, requiring proof systems efficient enough for consumer hardware. This rules out anything demanding server-grade resources for reasonable latency.

Post-quantum applications

The "Post-Quantum Security" axis above lists the resistant systems (STARKs, WHIR-based, lattice-based). For application guidance, the critical distinction is between integrity and privacy. For integrity-only applications (proving a computation was correct, no sensitive data in the witness), a dual-proof strategy works: generate both a classical proof (for efficiency today) and a post-quantum proof (for survival tomorrow), and migrate when quantum threatens. For applications involving private data, the dual strategy fails. A "harvest now, decrypt later" adversary records classical proofs today and breaks them with a future quantum computer, retroactively extracting the witness. Private data needs post-quantum security from day one.

The Trade-Off Triangle

Project managers know the Iron Triangle: Fast, Good, Cheap. Pick two. SNARKs have their own version: Succinct, Transparent, Fast Proving. The physics of cryptography enforces the same brutal constraint.

Three properties stand in tension: proof size, prover time, and trust assumptions.

SystemProof SizeProver TimeTrust
Groth16Minimal (128 B)QuasilinearMaximal (circuit-specific)
PLONKSmall (500 B)QuasilinearModerate (universal)
STARKsLarge (50+ KB)LinearNone

Pick any two vertices. The third suffers.

This is not a failure of engineering. It's a reflection of information-theoretic and complexity-theoretic constraints. Small proofs require structured commitments. Structured commitments require trusted setup or expensive verification. Fast provers require simple commitment schemes. Simple commitment schemes produce large proofs.

Every production system that appears to break this triangle does so through composition (Chapter 23). Halo 2 wraps a transparent IPA-based inner proof in a succinct accumulation scheme. RISC Zero and SP1 wrap transparent STARKs in Groth16. Folding-based systems defer all verification to a single final SNARK. In each case, the "escape" is architectural complexity: two or more proof systems cooperating, each contributing the vertex it handles best.

Implementation Realities

The best algorithm with a buggy implementation is worse than a mediocre algorithm implemented correctly.

Audit status

ZK bugs are silent: a soundness error lets attackers forge proofs, a witness leak exposes private data, and neither produces error messages. Zcash's Sprout had a soundness bug for years, discovered by a researcher rather than an attacker. Use audited implementations; multiple recent audits matter more than theoretical elegance.

Hardware acceleration

GPU proving is now standard for production zkVMs, with 10-100× speedups over CPU for NTT and MSM operations. SP1 Hypercube achieves real-time Ethereum proving on 16 GPUs. The choice of proof system constrains which hardware optimizations are available: NTT-heavy systems (STARKs, PLONK) benefit most from GPU parallelism, while sum-check provers with linear memory access patterns also parallelize well across CPU cores via SIMD (Chapter 20).

Tooling

The choice of proof system often follows from the available tooling rather than the other way around. Circom targets Groth16 and PLONK circuits. Cairo is StarkWare's language for STARK-based programs. Noir (Aztec) compiles to multiple backends. At the library level, Arkworks provides modular Rust primitives for field arithmetic, curves, and SNARK components, and Plonky3 (Polygon) is the shared proving framework underlying SP1, OpenVM, and several other production zkVMs, with pluggable field backends (BabyBear, Mersenne31) and a modular AIR interface. Mature tooling compounds over time; switching frameworks mid-project is expensive.

Quick Reference

SystemProof SizeVerify TimeProve TimeSetupPost-Quantum
Groth16~128 B3 pairings$O(n \log n)O(n \log n)O(\log^2 n)O(n)O(n)O(n)O(\log n)O(n)O(\log^2 n)O(n)$TransparentYes

Key Takeaways

  1. Every application has a binding constraint; the system choice follows from it. On-chain verification binds on proof size (Groth16/PLONK). zkVMs bind on prover speed (sum-check/STARKs). Privacy binds on implementation quality and client-side efficiency. Long-lived infrastructure binds on quantum resistance (hash-based systems only). Identify which constraint binds tightest; the rest is negotiable.

  2. The trade-off triangle is inescapable within a single system. Small proofs + fast provers requires trusted setup. Small proofs + transparent requires slow verification. Fast provers + transparent requires large proofs. Composition (Chapter 23) breaks the triangle by combining systems, at the cost of architectural complexity.

  3. Sum-check systems are no longer quantum-vulnerable. WHIR and Hachi provide hash-based and lattice-based multilinear PCS respectively, closing the gap that previously forced sum-check provers onto discrete-log commitments. For private data, post-quantum security is needed from day one (harvest-now-decrypt-later attacks make deferred migration dangerous).

  4. The zkVM landscape is converging on shared primitives. Small fields, AIR or CCS constraints, Logup bus arguments, and STARK→Groth16 composition appear across SP1, RISC Zero, Jolt, ZKsync Airbender, and Lean Ethereum, even as their architectural choices diverge. Plonky3 and Arkworks provide the shared infrastructure.

  5. Tooling and audit status constrain choices as much as theory. ZK bugs are silent (Zcash's Sprout had a soundness bug for years), so multiple recent audits matter more than theoretical elegance. Mature tooling compounds; switching frameworks mid-project is expensive.

Chapter 25: MPC and ZK parallel paths

In 1982, Andrew Yao posed a puzzle that sounded like a parlor game. Two millionaires meet at a party. Each wants to know who is richer, but neither wants to reveal their actual wealth. Is there a protocol that determines who has more money without either party learning anything else?

The question seems impossible. To compare two numbers, someone must see both numbers. A trusted third party could collect the figures, announce the winner, burn the evidence. But what if there is no trusted party? What if the millionaires trust no one, not even each other?

The same tension appears wherever private data meets joint computation. Satellite operators want to check if their orbits will collide, but their trajectories are classified. Banks want to detect money laundering across institutions without opening their books to each other. Nuclear inspectors want to verify warhead counts without learning weapon designs. The underlying problem is always the same: the computation requires inputs that no single party should see.

Yao proved the comparison can be done. Not by clever social arrangements or legal contracts, but by cryptography alone. The protocol he constructed, now called garbled circuits, allows two parties to jointly compute any function on their private inputs while revealing nothing but the output. Neither party sees the other's input. The trusted third party dissolves into mathematics.

This was the birth of Secure Multiparty Computation (MPC). The field expanded rapidly. In 1988, Ben-Or, Goldwasser, and Wigderson showed that with an honest majority of participants, MPC could achieve information-theoretic security with no computational assumption required, just the mathematics of secret sharing. The same year, Chaum, Crépeau, and Damgård proved that with dishonest majorities, MPC remained possible under cryptographic assumptions. By the early 1990s, the core theoretical question was settled. Any function computable by a circuit could be computed securely by mutually distrustful parties.

Computation, it turns out, does not require a single trusted processor. It can be distributed across adversaries who share nothing but a communication channel and a willingness to follow a protocol. The output emerges from the collaboration, but the inputs remain private.

Why MPC belongs in this book

Throughout this book, we've focused on trust between prover and verifier. The verifier need not believe the prover is honest; the proof itself carries the evidence. But there's another trust relationship we've quietly assumed: the prover has access to the witness. What if the witness is too sensitive to give to any single party?

Consider a company that wants to prove its financial reserves exceed its liabilities without revealing the actual figures to the auditor, the proving service, or anyone else. The company holds the witness (the books), but generating a ZK proof requires computation. If the company lacks the infrastructure to prove locally, it faces a dilemma. Outsource the proving and expose the witness, or don't prove at all.

MPC offers an escape. The company secret-shares its witness among multiple proving servers. Each server sees only meaningless fragments. Together, they compute the proof without any single server learning the books. The witness never exists in one place. Trust is distributed rather than concentrated.

This is one of several approaches to the "who runs the prover?" problem:

Prove locally. Keep the witness on your own hardware. No trust required, but you need sufficient compute. For lightweight proofs this works; for zkVM-scale computation it may not.

Distribute via MPC. The approach just described. Requires the servers not to collude (honest majority or computational assumptions). This chapter develops the techniques.

Hardware enclaves (TEEs). Run the prover inside a Trusted Execution Environment like Intel SGX or ARM TrustZone. The enclave attests that it ran the correct code on hidden inputs. Trust shifts from the server operator to the hardware manufacturer, not trustless but a different trust assumption.

(Chapter 27 discusses a fourth approach, computing on encrypted data via FHE, as part of the broader programmable cryptography landscape.)

MPC and ZK also connect at a deeper level. MPC techniques directly yield ZK constructions through the "MPC-in-the-head" paradigm, where the prover simulates an MPC protocol inside their own mind, commits to the simulated parties' views, then lets the verifier audit a subset. The parallel paths converge into a single construction.

The MPC problem

The intuition from Yao's millionaires is clear enough, but building protocols requires a precise target. What exactly does it mean to compute "securely"?

The formal setting has parties holding private inputs . They want to learn for some agreed-upon function , but nothing else. A trusted third party could collect everything, compute, then announce the result. MPC must achieve the same outcome without the trusted party. The question is what "nothing else" means, and against whom.

The answer uses the same simulation paradigm that defines zero-knowledge (Chapter 17). There, a proof is zero-knowledge if a simulator can produce a transcript indistinguishable from a real one without access to the witness. Here, an MPC protocol is secure if a simulator, given only the corrupt parties' inputs and the output, can produce a view indistinguishable from what those parties actually observed during the protocol. If such a simulator exists, the protocol leaks nothing beyond what the function itself reveals. The corrupt parties could have generated everything they saw on their own.

Two parameters shape what kind of security is achievable: the adversary's behavior and the number of corrupt parties.

Adversary models

A semi-honest (or passive) adversary follows the protocol faithfully but tries to extract information from the transcript. Think of a curious employee who logs every packet but never forges one. A malicious (or active) adversary can deviate arbitrarily by sending wrong values, aborting early, or colluding with others. Think of a compromised machine running modified software.

Most efficient protocols assume semi-honest adversaries. Malicious security is achievable at higher cost, as we'll see later in this chapter.

Collusion thresholds

How many parties can be corrupt before security breaks? Protocols specify a threshold so that security holds as long as at most of the parties are corrupt. The dividing line is .

With an honest majority (), protocols can achieve information-theoretic security. No computational assumption, no cryptographic hardness. Even an unbounded adversary learns nothing. The mathematics of secret sharing suffices.

With a dishonest majority (, potentially ), information-theoretic security becomes impossible. If all but one party collude, they hold enough information to reconstruct any secret shared among the group. Cryptographic assumptions become necessary because the adversary could break the scheme given infinite time, but doing so requires solving hard problems.

With the adversary model and threshold specified, the problem is precise. The question that remains is how to actually build such a protocol.

Secret-sharing MPC

The most natural approach is to keep data distributed throughout the entire computation. The BGW protocol, named after Ben-Or, Goldwasser, and Wigderson, does exactly this. Secret-share each input, compute on the shares, reconstruct only the output. To understand how this works, we need to understand what secret sharing actually does.

Shamir's secret sharing

Shamir's scheme (Appendix A covers the full details, including reconstruction formulas and security properties) distributes a secret among parties with threshold by constructing a random univariate polynomial of degree that passes through the point :

The coefficients are chosen uniformly at random. The secret is the constant term, recoverable as .

Each party receives the share , the polynomial evaluated at their index. Any parties can pool their shares and use Lagrange interpolation to recover the polynomial, hence the secret. But shares reveal nothing. A degree polynomial is determined by points, so with only points, every possible secret is equally consistent with the observed shares.

Concrete example. Share the secret among 3 parties with threshold . Choose a random linear polynomial passing through , say . The shares are:

  • Party 1:
  • Party 2:
  • Party 3:

Any two parties can reconstruct. Parties 1 and 3, holding and , interpolate to find the unique line through these points: , so . But party 1 alone, holding only , knows nothing. Any line through could have any -intercept. The secret could be anything.

Setup

Each party secret-shares their input by constructing a random polynomial with then sending share to party . After this initial exchange, party holds one share of every input: . No single party can reconstruct any input, but the distributed shares encode everything needed to compute.

Linear operations

Shamir sharing is linear, which makes addition and scalar multiplication free. If parties hold shares of secrets and encoded by polynomials and , then adding the shares gives valid shares of .

Party holds and . When they compute , this equals , the evaluation of the sum polynomial at . The sum polynomial has constant term . So the parties now hold valid Shamir shares of , without any communication.

The same holds for scalar multiplication. If party holds share and multiplies it by a public constant , the result is the evaluation of the polynomial at . This polynomial has constant term . Each party scales locally; no messages needed.

What this means in practice is that two parties can add their secrets without ever revealing them. Return to the earlier example: we shared via , giving shares . Now a second party shares their private input by constructing and distributing:

  • Party 1:
  • Party 2:
  • Party 3:

After this exchange, each party holds two shares: party 1 holds , party 2 holds , party 3 holds . Nobody knows or except the original owners.

To compute shares of , each party adds their shares locally: party 1 computes , party 2 computes , party 3 computes . These are evaluations of at points . Interpolating any two recovers . The sum was computed without anyone learning the inputs.

Addition and scalar multiplication are free. The cost of MPC concentrates entirely on multiplication.

Multiplication

Multiplication breaks the easy pattern. The product of two shares is not a valid share of the product. Shamir sharing uses polynomials of degree . If parties locally multiply their shares , they get evaluations of the product polynomial , which has degree . This polynomial does encode at zero, but the threshold has effectively doubled so that parties are now needed to reconstruct, not . Repeated multiplications would make the degree explode.

Donald Beaver's solution resolves this through preprocessed randomness. Before the computation begins, distribute shares of random triples satisfying . Nobody knows , , or individually, but everyone holds valid shares of all three.

To describe the protocol, we use bracket notation: means "the parties collectively hold Shamir shares of ," with each party holding one evaluation . To multiply by using a triple:

  1. Parties compute and locally (subtraction is linear, so each party subtracts their shares)
  2. Parties reconstruct and publicly by pooling shares (these values are masked by the random and , so they reveal nothing about or )
  3. Parties compute locally (each party uses their shares of , , plus the now-public , )

The algebra works because . Since , , and are now public scalars, party can compute their share of locally as . This is a linear combination of valid Shamir shares, so the result is itself a valid Shamir share of . No single party learns , but together the parties hold shares of a polynomial whose constant term is , ready to feed into subsequent gates.

Intermediate values are never reconstructed. Each triple enables exactly one multiplication because and are now public; reusing the same triple with different inputs would leak information. A fresh triple is needed for every multiplication gate, generated during a preprocessing phase before inputs are known.

Circuit evaluation

With these building blocks, any arithmetic circuit can be evaluated. Share the inputs, process gates in topological order so that addition gates require no communication while multiplication gates consume one Beaver triple each, then reconstruct only the final output.

The reconstruction step works like the earlier Shamir example, but now all parties contribute shares of the same value. Suppose the circuit's output wire carries the shared value , with party holding share for some degree polynomial with . Each party broadcasts their share. Given any shares, Lagrange interpolation recovers . Before this moment, no party knew ; after it, everyone does. This is the only point in the entire protocol where a shared value becomes public.

The communication cost is field elements per multiplication (each party sends one message to each other party). Round complexity equals the circuit's multiplicative depth, since multiplications at the same depth can proceed in parallel.

Garbled circuits

Secret-sharing MPC generalizes naturally to parties, but requires rounds proportional to circuit depth. Each multiplication forces a round of communication. For deep circuits or high-latency networks, this cost compounds quickly. Yao's garbled circuits take a completely different approach, designed specifically for the two-party case. There are no thresholds, no secret sharing, no multiple rounds of interaction. Instead, one round of communication suffices regardless of circuit depth.

The setting is two parties, say Alice and Bob, each holding a private input. Neither trusts the other. They agree on a function and want to learn without revealing their inputs to each other. The protocol assigns asymmetric roles: Alice becomes the garbler, who encrypts the entire circuit before sending it, and Bob becomes the evaluator, who runs the encrypted circuit blindly.

The evaluator needs one label per input wire, but the two parties' inputs arrive through different channels. For the garbler's own input wires, the garbler knows their bits, so they simply send the corresponding labels directly. For the evaluator's input wires, the garbler holds both labels but must not learn which bit the evaluator has. A primitive called oblivious transfer (developed later in this chapter) lets the evaluator receive the label matching their bit without the garbler learning which one was chosen. The evaluator learns nothing beyond the final output; the garbler learns nothing about the evaluator's input.

Labels as passwords

If the evaluator must compute on the garbler's circuit without learning what the wires carry, something must replace the raw bits. The idea is to use passwords. Each wire in the circuit carries not a 0 or 1, but a random cryptographic label. For each wire, the garbler creates two labels: one that "means 0" and one that "means 1." The evaluator receives exactly one label per wire, the one corresponding to the actual value, but cannot tell which meaning it carries.

This separation between holding a value and knowing a value is what makes garbled circuits work. The evaluator holds passwords that encode the computation, but a random 128-bit string looks the same whether it means 0 or 1.

Garbling a single gate

Each gate computes on passwords instead of bits by having the garbler precompute all possible outputs and encrypt them so only the correct one can be recovered.

Consider an AND gate with input wires (left) and (right) and output wire . Suppose Alice (the garbler) holds the left input and Bob (the evaluator) holds the right input. Alice generates all six labels herself, two per wire, each a 128-bit string that doubles as a symmetric encryption key:

  • Wire : labels and (meaning "left input is 0" and "left input is 1")
  • Wire : labels and
  • Wire : labels and

Alice knows which label corresponds to which bit; the subscript in is her private bookkeeping. Bob will eventually receive exactly one label per wire: for wire , Alice sends the label matching her own bit; for wire , Bob obtains the label matching his bit via oblivious transfer (the primitive introduced above, detailed in its own section below). He ends up holding two labels (one per input wire) but has no way to tell which bit either one represents. He never learns the other label for either wire.

The plain truth table for AND is:

LeftRightOutput
000
010
100
111

Alice now uses all her labels to build the garbled table, covering every possible input combination. She can do this because she created all six labels. The table encodes what the correct output label would be for each pair of inputs, encrypted so that only someone holding the right pair can recover it:

Encrypted Entry

The encryption is a symmetric-key operation (AES in practice) that uses both input labels as the key. Only someone who knows both and can decrypt the corresponding row.

This table has a flaw in its current form. If the rows stay in this order, the evaluator learns which row they decrypted and hence learns the input bits. The fix is to randomly shuffle the rows. After shuffling, the garbled table might look like:

Shuffled Encrypted Entry

Now Bob holds one label for each input wire. He tries to decrypt each of the four rows using his two labels as the key. Recall that each row was encrypted under a specific pair of labels via AES. AES decryption with the wrong key doesn't fail gracefully; it produces random-looking bytes. To tell valid from garbage, each row includes a small authentication tag (a known padding pattern or checksum) alongside the output label. When Bob decrypts with the correct pair, the tag checks out and he recovers the output label. When he decrypts with the wrong pair, the tag is garbled and he knows to discard the result. Exactly one row matches his labels, so he recovers exactly one output label.

This doesn't leak which inputs were used. Bob knows a row succeeded, but the rows are shuffled and he doesn't know what bit his labels represent. The position of the successful row tells him nothing about Alice's input or his own in the context of the truth table.

Hash-indexed tables

Random shuffling forces the evaluator to try all four rows per gate. A more efficient approach uses the hash of the input labels as a row index:

Row IndexEncrypted Entry

The evaluator, holding labels and , computes and looks up that row directly. No trial decryptions needed. The hash reveals nothing about which row was accessed since the evaluator doesn't know the other labels to compute their hashes.

This structure scales better. Instead of trying all rows, the evaluator does one hash and one decryption per gate. For circuits with millions of gates, the difference matters.

Chaining gates together

A single gate is not a computation. With either table approach (shuffled or hash-indexed), the evaluator decrypts one entry per gate and obtains an output label. That output label becomes input to the next gate. Labels propagate through the circuit because the garbler ensures consistency: the output labels of one gate are the same labels used as inputs in the next. The evaluator, holding one label per wire, evaluates gate after gate, each time recovering exactly one output label to feed forward.

Example: A tiny circuit. Consider computing , which requires an AND gate followed by an OR gate.

       a ──┐
            ├── AND ──┬── t
       b ──┘          │
                      ├── OR ── output
       c ─────────────┘

The intermediate wire connects AND's output to OR's input. The garbler:

  1. Generates labels for wires , , , , and (two labels per wire)
  2. Creates a garbled table for AND using 's and 's labels as input keys, encrypting 's labels as outputs
  3. Creates a garbled table for OR using 's and 's labels as input keys, encrypting 's labels as outputs
  4. Sends both garbled tables to the evaluator

The consistency between gates requires no "enforcement" since the garbler controls construction. The labels and are created once, then used in two places: as the encrypted outputs of the AND table, and as the decryption keys indexed in the OR table. When the evaluator decrypts the AND gate and obtains (say) , that exact string appears as an index in the OR table. The garbler wired them together at construction time.

The evaluator:

  1. Receives labels for , , (via oblivious transfer for their inputs, directly for the garbler's inputs)
  2. Evaluates the AND gate, obtaining a label for
  3. Uses the label plus the label to evaluate the OR gate
  4. Obtains a label for the output wire

At the final output, the garbler reveals the mapping: "If your output label is , the result is 0; if it's , the result is 1." Only now does the evaluator learn the actual output bit. This isn't a security breach since the whole point is for both parties to learn . The protection is that intermediate wire mappings stay hidden, so the evaluator learns only the final answer, not the computation path that produced it.

A concrete walkthrough

Let's trace a complete example to see how Alice (garbler) and Bob (evaluator) actually interact. They want to compute where Alice holds and Bob holds .

Step 1: Alice creates the garbled circuit (offline, before any communication). Alice generates all labels for all wires, including Bob's input wire. She doesn't know Bob's input, so she creates labels for both possibilities:

  • Wire (Alice's input): ,
  • Wire (Bob's input): ,
  • Wire : ,

She builds the garbled table, encrypting each output label under the pair of input labels that would produce it:

Input LabelsOutput LabelCiphertext

She randomly shuffles the rows and sends the four ciphertexts to Bob. At this point Bob has the encrypted circuit but no labels. He cannot decrypt anything yet.

Step 2: Bob receives his input labels. Two things happen through different channels:

  • Alice's input: Alice knows her bit is , so she sends to Bob. She does not send . Bob receives this label but has no way to tell it corresponds to the bit 1 rather than 0.
  • Bob's input: Alice holds both and but must not learn Bob's bit. Bob knows he wants (his bit is 0) but cannot tell Alice which one. Via oblivious transfer, Bob receives without Alice learning he chose it, and without Bob learning .

Bob now holds exactly one label per input wire: for wire and for wire .

Step 3: Bob evaluates. Bob tries to decrypt each of the four shuffled ciphertexts using his two labels as the key. Each row was encrypted under a specific pair of labels. Bob's pair is . Only the row that was encrypted under exactly this pair, the row corresponding to , decrypts successfully, yielding . The other three rows, encrypted under different label pairs, produce garbage when Bob tries them. He cannot decrypt them because he doesn't hold or .

Step 4: Output. Alice reveals the output mapping: "Label means 0, label means 1." Bob sees he holds , so the result is .

What did Bob learn? Only the output. He never learned that "meant 1" or that Alice's input was 1. He never saw , , or . What did Alice learn? Nothing about Bob's input, because oblivious transfer hid his choice. She knows the result (Bob can share it) but not which of Bob's bits produced it.

Complexity

The basic protocol requires four encryptions per gate (one per truth-table row). An optimization called Free-XOR eliminates the garbled table entirely for XOR gates by constraining all label pairs to differ by a global secret ; the evaluator simply XORs input labels to obtain the output label with no encryption needed. Since XOR is the most common gate in many circuits, this significantly reduces communication in practice.

Communication is , proportional to the circuit size. Computation uses only symmetric-key operations (AES). The protocol runs in constant rounds regardless of circuit depth: one round to send the garbled circuit, one for oblivious transfers.

Oblivious transfer

The garbled circuits walkthrough relied on a primitive we haven't yet built: a way for Bob to receive one of Alice's two labels without Alice learning which one he chose. This is oblivious transfer (OT). In its general form, a sender holds two messages and , a receiver holds a choice bit , and after the protocol the receiver learns and nothing else while the sender learns nothing about .

The requirement sounds contradictory. Several constructions make it possible.

Construction from commutative encryption

Imagine an encryption scheme where the order of encryption and decryption doesn't matter:

Exponentiation in a finite group provides exactly this. Encrypt message with key by computing . Decrypt by taking an -th root. The order of encryption doesn't matter since , so either party can decrypt their layer without needing the other to go first.

The OT protocol. Alice has messages . Bob wants without Alice learning .

  1. Alice encrypts all messages with her key and sends them in order:
  2. Bob knows he wants the -th message, so he takes the -th ciphertext from the list (he can't read it, but he knows its position). He encrypts it with his own key and sends back
  3. Alice decrypts with her key, obtaining , and sends it to Bob
  4. Bob decrypts with his key to recover

Bob is protected because Alice sees only a doubly-encrypted blob. She doesn't know Bob's key , so she can't decrypt it to see which message he chose.

Alice is protected because Bob receives only one singly-encrypted message ( in step 3). The other messages remain encrypted under Alice's key, which Bob doesn't have.

Construction from Diffie-Hellman

The commutative encryption approach requires three rounds of communication between Alice and Bob. A construction based on Diffie-Hellman key exchange reduces this to two rounds by exploiting the fact that the receiver's choice bit can be hidden inside a group element.

Work in a group of prime order with generator . The sender chooses random and sends . The receiver embeds their choice bit into their response: if , choose random and send ; if , send for random . Either way, the sender sees a random-looking group element and cannot tell which case applies.

The sender computes two keys: and . Then the sender encrypts both messages, and , and sends both ciphertexts.

The receiver can compute only one key. If , the receiver knows and can compute , which equals since . But requires knowing the discrete log of , which the receiver doesn't have. The receiver decrypts and learns . If , the situation reverses: the receiver can compute but not .

The sender sees only , a random group element that reveals nothing about whether the receiver chose or .

Both constructions require public-key operations (exponentiations), which is fine for a handful of OTs but problematic when garbled circuits need one OT per input bit. OT extension (the IKNP protocol) solves this by using a small number of base OTs (typically 128) to bootstrap an unlimited number of extended OTs using only symmetric-key operations. The amortized cost drops to a few AES calls per OT, making garbled circuits practical even for million-bit inputs.

Mixing protocols

Real computations rarely fit neatly into one paradigm. A machine learning inference might need field arithmetic for the linear layers (where secret-sharing MPC excels) but comparisons for activation functions (where garbled circuits handle more efficiently). The most practical approach switches representations mid-computation, using each paradigm where it performs best.

Modern MPC frameworks formalize this by supporting three representations: arithmetic sharing for field operations, Boolean sharing for bitwise operations and comparisons, and Yao's garbled circuits for complex Boolean functions. Conversion protocols translate between them. Arithmetic-to-Boolean (A2B) converts additive shares of a field element into XOR-shares of its bit representation. Boolean-to-Arithmetic (B2A) reverses the process, using oblivious transfer to handle the carry bits that arise when interpreting binary as an integer.

The design problem becomes partitioning a computation so that each segment uses its most efficient representation. Deep multiplicative chains favor arithmetic sharing. Complex comparisons favor Boolean or Yao representations. The optimal decomposition is often hand-tuned for applications where performance matters.

MPC-in-the-head

Everything so far has developed MPC as a tool for private computation among real parties. But this is a book about proof systems, and the MPC machinery we've built turns out to produce zero-knowledge proofs through an unexpected route, one that bypasses polynomial commitments, pairings, and algebraic IOPs entirely.

The transformation is called "MPC-in-the-head," and it rests on a symmetry between MPC security and zero-knowledge. In a real MPC protocol, multiple parties compute on secret-shared inputs with the guarantee that no coalition learns more than the output. MPC-in-the-head takes this guarantee and repurposes it: the prover secret-shares the witness among imaginary parties, then simulates the MPC protocol that would compute entirely inside their own mind, playing all roles. Each simulated party accumulates a "view" consisting of the messages it sent and received, its random tape, and its share of the witness. The prover commits to all views. What was privacy against colluding parties becomes zero-knowledge against the verifier.

Think of a one-person theater troupe performing a three-character scene. The prover writes out the full script: what Alice said to Bob, what Bob said to Charlie, what Charlie said to Alice. Then they seal each character's script in a separate envelope.

The verifier picks two envelopes at random and checks whether the scripts agree. Do the messages that party claims to have sent match what party claims to have received? Did both follow the protocol correctly? Does the output equal 1? If Alice's script says she sent "7" to Bob but Bob's script says he received "9," the inconsistency is caught. By checking different random pairs across repetitions, the verifier catches any forged execution with high probability.

Soundness holds because a cheating prover cannot forge consistency across all pairs of views. If the witness is invalid, the honest MPC would output 0. To fake acceptance, the prover must manufacture views where the protocol appears to output 1, but any inconsistency between a pair of views (mismatched messages, or a party that deviated from the protocol rules) gets caught when the verifier opens that pair. A cheating prover can make some pairs consistent, but not all. Each random challenge catches an inconsistent pair with constant probability; repetition amplifies.

Zero-knowledge follows directly from MPC privacy. The number of views the verifier opens must stay below the reconstruction threshold of the underlying secret sharing scheme. In a -threshold scheme, any shares are consistent with every possible secret, so opening views reveals nothing about the witness. The choice of controls the tradeoff: higher thresholds allow opening more views (better soundness per repetition) while still preserving zero-knowledge. In the simplest case, 3-party additive sharing requires all 3 shares to reconstruct (), so opening 2 views is safe. Those 2 views suffice to check one pair for consistency, giving soundness error per round, driven down by repetition.

Instantiations

ZKBoo and ZKB++ use 3-party secret sharing. The verifier opens 2 of 3 parties, giving a soundness error of per repetition. These schemes excel at proving knowledge of hash preimages, where the circuit structure is fixed and well-optimized.

Ligero combines MPC-in-the-head with Reed-Solomon codes, achieving proof size for circuits with gates. This is sublinear, better than naive approaches though not as succinct as polynomial-based SNARKs.

Limbo and subsequent work push practical performance further, targeting real-world deployment for specific statement classes.

MPC-in-the-head shows that MPC techniques can build proof systems. But MPC also has direct applications of its own, and the most widely deployed is threshold cryptography.

Threshold cryptography

MPC computes arbitrary functions on distributed inputs, but some functions appear so frequently that they deserve specialized protocols. The most important special case is cryptographic key operations. A single signing key or decryption key creates a single point of failure, and the compromise of that one key invalidates all the security built on top of it. MPC provides a way to eliminate that single point by distributing the key itself.

Threshold cryptography applies this idea directly. Instead of a single party holding a signing or decryption key, parties each hold a share. Any of them can cooperate to sign or decrypt, but no coalition of fewer than learns anything about the key. The secret never exists in one place.

Threshold key operations

A cryptocurrency exchange holding billions in assets cannot afford to store a signing key on a single machine. The traditional defense is multisig, where the blockchain verifies -of- separate signatures. But multisig reveals the signing structure on-chain and requires protocol-level support. Threshold signatures take a different approach: the parties hold shares of a single signing key , and when cooperate they produce a single signature indistinguishable from one generated by a solo signer. The blockchain sees nothing unusual. The distribution is invisible.

The reason Schnorr signatures lend themselves to this is linearity. A Schnorr signature has the form where is a nonce, is the challenge hash, and is the signing key. If parties hold Shamir shares and , they compute partial signatures . Lagrange interpolation reconstructs exactly, the same reconstruction used throughout this chapter.

FROST builds a complete threshold Schnorr protocol around this observation. In the first phase, parties jointly generate shares of the nonce using Feldman's verifiable secret sharing (Appendix A), so that each party contributes randomness without anyone learning the full nonce. In the second phase, each party computes their partial signature and the results combine via interpolation. Feldman's verifiability lets parties detect malformed shares during nonce generation, catching cheaters before they can disrupt signing.

FROST requires synchronous coordination during nonce generation: all participating signers must be online simultaneously to exchange commitments. If a signer drops offline, the protocol stalls. ROAST wraps FROST in an asynchronous coordinator that adaptively selects responsive signers, maintains concurrent sessions, and starts fresh with a different subset when someone times out. The first session to complete produces the signature. ROAST doesn't modify FROST's cryptography; it adds a session management layer that makes threshold signing practical across time zones and unreliable networks.

Threshold ECDSA is harder. ECDSA signatures involve a modular inversion step, , and inversion is not linear. Computing it on shared values requires a full MPC protocol for the inversion, adding rounds and computational overhead. Protocols like GG18 and GG20 solve this but at higher cost than FROST.

The same distribution principle applies beyond signing. Threshold decryption (used in e-voting systems like Helios and Belenios via threshold ElGamal) splits a decryption key so that encrypted ballots can only be opened after polls close and only if enough trustees cooperate. The pattern generalizes: any cryptographic operation that depends on a secret key can, in principle, be distributed so that the key never exists in one place.

Practical considerations

The protocols developed in this chapter are theoretically complete. Given enough time and bandwidth, any function can be computed securely. But deploying MPC in practice introduces constraints that the theory abstracts away.

Communication is the bottleneck

MPC and ZK have opposite performance profiles. A ZK prover performs heavy local computation (MSMs, FFTs, hashes) but sends a small proof. An MPC protocol does lightweight computation at each party but exchanges massive amounts of data between them. A ZK prover might spend 10 seconds computing and 10 milliseconds sending; an MPC protocol might spend 10 milliseconds computing and 10 seconds sending. You can run a ZK prover on a single powerful machine, but you can't run high-speed MPC over a slow network.

Within MPC, the binding constraint is usually either bandwidth or latency, and which one dominates determines the protocol choice. If bandwidth is cheap but latency is high (parties on different continents), garbled circuits win because they run in constant rounds despite sending more data. If bandwidth is limited but latency is low (parties in the same data center), secret-sharing MPC wins because each round sends less. The network, not the cryptography, is usually what makes MPC slow.

Preprocessing vs. online

MPC protocols are slow when inputs arrive because every operation pays a cryptographic cost. For latency-sensitive applications like sealed-bid auctions, where parties submit bids that must be processed immediately, this cost is unacceptable. The solution is to separate the computation into two phases. The preprocessing phase generates correlated randomness before the actual inputs are known. Beaver triples for multiplication, OT correlations for garbled circuits, random sharings for masking all fall into this category. The online phase consumes this preprocessed material to compute on the real inputs.

Because none of this preprocessed material depends on the actual inputs, it can be generated during idle time, spreading the heavy cryptographic work across hours or days. When inputs finally arrive, the online phase consumes the stockpiled randomness and runs fast, achieving sub-second latency despite the underlying complexity.

Where does the preprocessing come from? In production systems (cryptocurrency custody platforms, private computation services built on SPDZ), the parties typically generate it themselves via OT extensions or homomorphic encryption, paying the full cost upfront but requiring no trusted party. A simpler alternative is a trusted dealer who generates and distributes the correlated randomness, though this reintroduces a single point of trust that MPC was designed to eliminate. Hybrid approaches using trusted execution environments as hardware-backed dealers are emerging in the wallet and custody space but remain less established.

Malicious security

Everything so far assumes semi-honest adversaries who follow the protocol faithfully but try to extract information from what they observe. Real deployments often face adversaries who can deviate arbitrarily, sending malformed messages or aborting at strategic moments. Adding security against such adversaries requires mechanisms to detect cheating.

For secret-sharing MPC, the main tool is authentication. The SPDZ protocol attaches a Message Authentication Code (MAC) to each shared value. When shares are combined or reconstructed, the MACs are verified. A cheating party who modifies a share will fail the MAC check with overwhelming probability. The SPDZ preprocessing includes authenticated Beaver triples so that the online phase can verify multiplications respect the triple structure. Recent work has brought the communication cost of malicious SPDZ close to the semi-honest baseline, narrowing a gap that was once a factor of two.

For garbled circuits, the problem is different. The semi-honest protocol assumes the garbler constructs the circuit correctly, but a malicious garbler could create a circuit that computes the wrong function, leaking information about the evaluator's input. Early solutions used cut-and-choose, where the garbler creates dozens of independent garbled circuits and the evaluator randomly selects some to verify and the rest to evaluate. This works but is expensive. Modern protocols use authenticated garbling, which achieves malicious security with a single garbled circuit by attaching authentication tags to each wire label, reducing the overhead substantially.

In practice, malicious security is standard for high-value operations. Cryptocurrency custody platforms (Fireblocks, Coinbase) use malicious-secure threshold signature protocols, since a compromised signing ceremony could mean direct financial loss. General-purpose malicious-secure MPC remains more expensive and is less common in production, though the cost gap continues to shrink.

Key takeaways

  1. MPC eliminates trusted third parties. Any function computable by a circuit can be computed jointly by mutually distrustful parties, revealing only the output. Security is defined through simulation: whatever a corrupt coalition observes, it could have generated from its own inputs and the output alone.

  2. MPC solves the "who runs the prover?" problem. When a witness is too sensitive for any single party, secret-sharing it among multiple proving servers lets them jointly compute a ZK proof without any server learning the witness.

  3. Two paradigms with different tradeoffs. Secret-sharing MPC (BGW) handles parties with free linear operations but rounds proportional to multiplicative depth. Garbled circuits achieve constant rounds for two parties but communicate proportional to circuit size. The network, not the cryptography, usually determines which wins.

  4. MPC-in-the-head bridges MPC and ZK. Simulate an MPC protocol inside the prover's mind, commit to all party views, let the verifier audit a random subset. MPC privacy becomes zero-knowledge; MPC correctness becomes soundness. This yields proof systems (ZKBoo, Ligero) that bypass polynomial machinery entirely.

  5. Threshold cryptography distributes key operations. Secret-share a signing or decryption key among parties so that any can operate but fewer than learn nothing. FROST makes threshold Schnorr practical; ROAST adds asynchrony. This is the most widely deployed application of MPC.

  6. Malicious security is production-ready for high-value operations. SPDZ authenticates shares with MACs; authenticated garbling verifies circuit construction. Cryptocurrency custody platforms use malicious-secure threshold signatures as standard, while general-purpose malicious MPC continues to close the cost gap with semi-honest protocols.

Chapter 26: Frontiers and Open Problems

In 1900, Lord Kelvin told the British Association for the Advancement of Science that physics was essentially complete. Only "two small clouds" remained on the horizon: the failure of the Michelson-Morley experiment to detect the luminiferous ether, and the inability of classical theory to predict the spectrum of blackbody radiation. Those two clouds became special relativity and quantum mechanics. Kelvin had mistaken a plateau for a summit.

In 2020, SNARKs felt similarly settled. Groth16 for minimal proofs, PLONK for universal setups. The trade-offs seemed fixed, the design space mapped. Then came lookups (2020), folding schemes (2021), and binary field techniques (2023). Each opened territory that the previous framework couldn't reach.

Small fields and Binius

Every proof system in this book operates over large prime fields, typically 254-bit or 256-bit elements. But most real-world data is small: booleans, bytes, 32-bit integers. Representing a single bit as a 256-bit field element wastes 255 bits of capacity. The waste is expensive. Field multiplications dominate prover time, and each multiplication operates on the full 256 bits even when the meaningful data is a single bit. For bit-level operations like hashing, AES, or bitwise logic, the overhead approaches 256×.

Binius attacks this problem directly by working over binary fields where field elements are actual -bit strings. A boolean is a 1-bit field element. A byte is an 8-bit field element. No padding, no waste.

The arithmetic of binary fields differs from prime fields. Addition is XOR (free in hardware). Multiplication uses polynomial arithmetic over . There are no "negative" elements; the field characteristic is 2. Binary fields lack the convenient structure of prime-order groups, but Binius recovers efficiency through protocol design that exploits the tower structure of binary extensions.

Tower architecture

Rather than a single large field, Binius organizes computation in a tower of nested extensions: up to for cryptographic security. Each level doubles the extension degree, and every element of a smaller field is automatically an element of every larger field above it. A bit in is a valid element of ; it's just a very special one.

This nesting enables a natural optimization. Witness data lives in the smallest field that fits: bits stay in , bytes in , 32-bit integers in . Arithmetic happens at the appropriate level. Only when the verifier's random challenges enter does computation lift to the full tower height. The 256× overhead vanishes.

(This "tower" is unrelated to the "tower of proofs" in Chapter 23's recursion discussion. There, "tower" refers to recursive proof composition . Here, it refers to nested field extensions.)

Protocol components

GKR-based multiplication (multilinear). Binary field multiplication is polynomial multiplication modulo an irreducible polynomial. Rather than encoding this as constraints, Binius uses the GKR protocol (Chapter 7) to verify multiplications via sum-check over multilinear extensions. The prover commits only to inputs and outputs; intermediate multiplication steps are checked interactively.

FRI over binary fields (univariate). For polynomial commitments, Binius adapts FRI to binary domains. The standard FRI folding doesn't directly transfer since the squaring map is not 2-to-1 on binary fields as it is over roots of unity. FRI-Binius instead uses subspace vanishing polynomials with an additive NTT to achieve the necessary folding structure, enabling commitment to polynomials over tiny fields like with no embedding overhead.

Binius thus straddles both PIOP paradigms from Chapter 22: sum-check-based (multilinear) for the computation layer, FRI-based (univariate) for the commitment layer.

Where it stands

For bit-intensive computations, Binius achieves order-of-magnitude improvements:

OperationTraditional (256-bit field)Binius
SHA-256 hash~25,000 constraints~5,000 constraints
AES block~10,000 constraints~1,000 constraints
Bitwise AND1 constraint + range check1 native operation

Fewer constraints mean smaller polynomials, faster transforms, smaller proofs. But the path from theory to deployment has been instructive about the real tradeoffs.

Irreducible (the primary Binius implementation team) archived the original Binius codebase in September 2025 and replaced it with Binius64, a simplified design that operates natively over 64-bit words. The pivot reflected lessons from production experience: the original tower architecture was too general for practical use. Binius64 retains the core ideas (binary field towers, GKR multiplication, FRI-Binius commitments) but targets CPU-based client-side proving rather than competing as a general-purpose zkVM. Early benchmarks show Binius64 on multi-threaded CPUs outperforming SP1 and R0VM on GPUs by roughly 5× for hash-based signature aggregation.

The tradeoffs that motivated the pivot remain relevant for any binary-field system. Binius achieves faster proving at the cost of larger proofs and slower verification than prime-field FRI. Recursion is harder because verifying a binary-field proof inside another binary-field proof requires embedding the arithmetic, and the algebraic structure that makes Binius fast for computation makes it awkward for recursive self-verification. Zero knowledge itself was not yet implemented as of the Binius64 launch, listed as the top priority for subsequent releases.

The benefits are also workload-dependent. Binius shines for bit-intensive operations (hashing, AES, bitwise logic) but the advantage shrinks for 32/64-bit arithmetic, memory operations, or control flow. The Binius64 team's focus on signature aggregation and client-side proving suggests binary fields may find their niche in specialized components rather than full VM execution, composed with prime-field provers via the techniques from Chapter 23.

The broader principle holds regardless of Binius's specific trajectory: matching the proof system's field to the computation's natural representation eliminates artificial overhead.

Field representation is only one axis of adaptation. Another looms larger: the cryptographic assumptions themselves.

Post-quantum SNARKs

Every system in Part IV of this book (Groth16, PLONK, KZG-based constructions) rests on the hardness of discrete logarithm or elliptic curve problems. These will break once cryptographically relevant quantum computers exist, because they all share hidden periodic structure in abelian groups that Shor's algorithm exploits via the quantum Fourier transform.

Timeline estimates have compressed sharply. Recent results reducing the qubit requirements for breaking elliptic curve cryptography (from millions to hundreds of thousands under newer architectures) have moved several expert assessments into the 5-10 year range. Google targets 2029 for full post-quantum migration; the Global Risk Institute rates a cryptographically relevant quantum computer as "quite possible" within 10 years. Even conservative government planning horizons have shortened to 15-25 years. For infrastructure with long lifespans (financial systems, identity, archival signatures) the question is no longer whether to prepare but how fast.

Paths forward

Hash-based systems. STARKs and FRI rely only on collision-resistant hashing. Hash functions resist Shor (no hidden periodic structure). The best known quantum attack on hashes is Grover's algorithm, which searches an unstructured space of elements in steps instead of . This is a quadratic speedup, not an exponential one, so doubling the hash output (e.g., from 128-bit to 256-bit security) neutralizes it entirely. Beyond FRI, WHIR (EUROCRYPT 2025) provides a hash-based multilinear PCS with faster verification, giving sum-check-based provers (Chapter 22) a post-quantum commitment scheme without switching to the univariate/STARK paradigm. The Ethereum Foundation's Whirlaway stack (Chapter 24) combines WHIR with SuperSpartan for exactly this purpose. Hash-based systems are the current practical choice for post-quantum proofs. Their proof sizes are larger than pairing-based alternatives, but they work today.

Lattice-based commitments. Replace Pedersen commitments with schemes based on Module-LWE or similar lattice problems. Lattices resist quantum attacks because the problem of finding short vectors in high-dimensional lattices has no known abelian group structure for the QFT to extract. A polynomial commitment encodes coefficients as a lattice point, with the hardness of finding short vectors ensuring binding and noise flooding or rejection sampling providing hiding. The algebraic structure is richer than hashes, enabling homomorphic operations on commitments that support sum-check-style protocols. The tradeoff is noise growth: LWE noise accumulates with operations, eventually overwhelming the signal unless parameters grow. Recent work is closing the efficiency gap. Hachi (eprint 2026/156) achieves a multilinear PCS under Module-SIS with ~55KB proofs and verification 12.5× faster than prior lattice constructions, bringing lattice-based commitments closer to practical use in sum-check-based proof systems (Chapter 24 discusses the implications for SNARK selection).

Symmetric-key SNARKs. The MPC-in-the-head paradigm (Chapter 25) builds proofs entirely from hash-based commitments, with no algebraic assumptions at all. Ligero improved this with linear-time proving via interleaved Reed-Solomon codes, but constants remain large (10-100× slower than algebraic SNARKs). Security reduces to collision resistance of the hash function.

Open problems

Three interrelated challenges define this frontier. Lattice-based polynomial commitments remain 10-100× slower than hash-based alternatives; closing this gap while maintaining rigorous security is an active research problem. Security reductions are often loose, so the concrete security is much worse than asymptotic claims suggest. Tighter reductions would either increase confidence or reveal that larger parameters are needed. The transition period creates its own problem: building hybrid systems secure against both classical and quantum adversaries without paying twice the cost.

The post-quantum transition will reshape the SNARK landscape, but it operates on a timescale of years to decades. A different revolution is already underway.

zkVMs

Every proof system we've studied requires translating the computation into a constraint system: R1CS, AIR, PLONKish gates. This translation is a specialized craft. Experts hand-optimize circuits for months; a single bug invalidates the work. The barrier to entry is enormous.

zkVMs invert this relationship. Instead of adapting computations to proof systems, adapt proof systems to computations. Compile any program to a standard virtual machine (RISC-V, EVM, WASM) and prove correct execution. The zkVM handles memory, branching, loops, function calls. Write your logic in Rust. Compile to the target ISA. Prove execution. No circuit engineering required.

The current race

The zkVM landscape has stratified into distinct architectural approaches.

SP1 (Succinct). The most widely adopted zkVM, powering OP Succinct rollups, Polygon's Agglayer, and Celestia's bridge to Ethereum. Cross-table lookup architecture with a precompile system that accelerates common operations (signature verification, hashing) by 5-10× over raw RISC-V. SP1 Hypercube (2025) moved from STARKs to multilinear polynomials, achieving near-real-time Ethereum proving: over 93% of L1 blocks proven in under 12 seconds (average 10.3s) on a cluster of 160 RTX 4090 GPUs (0.0001 per transfer.

RISC Zero. STARK-based with FRI commitments over BabyBear, targeting RISC-V. Uses continuations to split large computations into bounded segments (~ cycles), proves each independently, then aggregates via recursion. Final proofs wrap in Groth16 for cheap on-chain verification. R0VM 2.0 (April 2025) reduced Ethereum block proving from 35 minutes to 44 seconds. The Boundless network provides a decentralized proof marketplace.

Note on continuations: Instead of proving the entire computation history at each step, continuations prove only the current segment plus a commitment to the previous segment's final state. This lets you pause and resume computation at arbitrary points, bounding peak prover memory regardless of total computation length.

Jolt (a16z). Built entirely on multilinear polynomials and the Lasso lookup argument (Chapter 21). Implements CPU instructions via lookups into structured tables rather than hand-crafted constraints. Achieves over 1 million RISC-V cycles per second on a 32-core CPU with ~50KB proofs, an order of magnitude smaller than STARK-based alternatives with roughly 10× lower prover overhead per cycle. A streaming prover is under development for arbitrarily long executions in under 2GB RAM. Jolt does not yet support recursion or continuation, which limits direct comparison with SP1 and RISC Zero on long computations.

Zisk (Polygon spinoff). Spun out of Polygon's zkEVM team (led by co-founder Jordi Baylina) in June 2025, with all Polygon zkEVM IP transferred. Built on RISC-V 64, designed for low-latency distributed proving with a 1.5 GHz zkVM execution engine, GPU-optimized code, and advanced aggregation circuits.

The convergence across these systems is notable: multilinear polynomials displacing univariate STARKs, real-time Ethereum proving as the benchmark target, precompiles for common cryptographic operations. Techniques developed for one system transfer rapidly to others.

Design patterns from production

Beyond the specific systems, several design patterns have emerged that generalize across implementations.

Physical CPUs distinguish registers (fast, few) from memory (slow, large). In ZK circuits this distinction vanishes because both register access and memory access are polynomial lookups with identical cost. Valida exploited this by eliminating general-purpose registers entirely in favor of a stack machine, reducing per-cycle constraint count. The deeper lesson is that zkVM architectures should not inherit assumptions from physical hardware that have no analogue in the proving system.

Long computations face a memory wall: proving cycles requires holding intermediate state for steps. Segment-based proving (pioneered by RISC Zero) splits execution into bounded segments of roughly cycles, proves each independently, then aggregates via recursive composition. Peak prover memory stays bounded regardless of total computation length.

Memory consistency can be verified through Merkle trees (hashing inside the circuit, expensive) or through algebraic challenges that accumulate memory operations into fingerprint polynomials and verify consistency via Schwartz-Zippel. The challenge approach, formalized in Twist-and-Shout (Chapter 21) and used in SP1 and Jolt, relies only on field arithmetic and is 10× faster or more for memory-heavy workloads.

Finally, zkVMs expose precompiles for operations that appear frequently and have specialized efficient circuits (SHA256, Keccak, ECDSA, pairings). These run 10-100× faster than interpreted execution at the cost of additional engineering complexity per precompile. The ECDSA verification bottleneck has also driven adoption of EdDSA over "embedded" curves like BabyJubJub, whose base field matches the scalar field of the outer proving curve so that signature verification becomes native field arithmetic.

Open problems

The overhead gap is the defining challenge. Current zkVMs run 100-1000× slower than native computation; the near-term target is 10×. Where does this overhead come from, and which parts are compressible?

Part of it is inherent: every operation must produce a cryptographic trace, and that trace must be committed and checked. But much of the overhead is structural. Memory is one source. A 4GB address space means potential cells, far too many to commit individually. Virtual polynomial techniques (Chapter 21) help, but scaling to gigabytes of working memory remains open. Precompile selection is another. Current systems hand-pick which operations get dedicated circuits based on blockchain workloads. General-purpose proving may need different choices, and automating precompile discovery (profiling hot operations and generating specialized circuits) would change the economics of zkVM design. Sequentiality is a third source. Most zkVMs execute instructions one at a time, each depending on the previous state. Proving parallel programs efficiently, or even exploiting prover-side parallelism for sequential programs, remains largely unexplored.

These problems are connected. Memory overhead limits the computations you can prove. Precompile overhead limits the operations worth proving. Sequential execution limits the hardware you can exploit. Solving any one of them shifts the bottleneck to the others.

But speed means nothing if the proofs are wrong.

Formal verification

A soundness bug in a ZK system is unlike most software bugs. A crash announces itself; a soundness bug operates in silence. An attacker forges proofs, the verifier accepts, the system behaves as though everything is fine. By the time the compromise is discovered, the damage is done. High-profile vulnerabilities have been found in deployed systems: missing constraint checks that allowed invalid witnesses to pass, incorrect range assumptions that permitted overflow attacks, field confusion bugs where values were interpreted in the wrong field.

Several defenses are gaining traction. Verified compilers prove that compilation from a high-level circuit language to low-level constraints preserves semantics. Machine-checked soundness proofs (in Coq, Lean, Isabelle) establish that the protocol is sound by construction. OpenVM's RV32IM extension was formally verified in Lean by Nethermind Research in early 2026, and SP1 Hypercube's core RISC-V chips have been verified in Lean as well. Static analysis tools detect common vulnerability patterns before deployment: unconstrained variables, degree violations, missing range checks.

The persistent challenge is the gaps between verified components. You might verify the compiler but not the runtime, the protocol but not the implementation, the circuit but not the witness generator. Bugs hide at the boundaries. End-to-end verification, from source code to final proof, remains open. So does verification of optimized implementations: the fastest provers use hand-tuned assembly and GPU kernels that are inherently hard to reason about formally.

Formal verification addresses correctness. The remaining frontiers are systems-level problems: making proofs faster to generate, cheaper to verify at scale, and applicable to demanding workloads.

Deployment frontiers

Several bottlenecks sit between correct proof systems and practical deployment. They are less glamorous than new cryptographic constructions but increasingly determine what is actually buildable.

Hardware and memory. Prover computation (MSMs, NTTs, hashing) is massively parallel, making GPUs 10-100× faster than CPUs for these workloads. But the binding constraint is increasingly memory bandwidth rather than arithmetic throughput. Large circuits require gigabytes of data, and memory transfer between CPU and GPU often exceeds computation time. Proof systems designed around GPU memory hierarchy rather than adapted to it after the fact would look very different from what we have.

Witness generation. Academic benchmarks report "prover time" as the cryptographic work (commitments, sum-check, polynomial evaluations), but witness generation (computing all intermediate values for an -gate circuit) often takes longer. A paper might report "proving takes 10 seconds" while silently omitting that witness generation took 60 seconds. The two scale differently: proving parallelizes across GPUs while witness generation is often sequential and memory-bound. For zkVMs, the execution trace already exists; translating it into the format the prover needs is the expensive step.

Aggregation. A rollup processing millions of transactions generates millions of proofs. Verifying them individually costs time. Recursive aggregation (Chapter 23) collapses proofs into one but adds prover overhead. Proof compression (wrapping a STARK in a Groth16 proof) is already standard. The open targets are incremental aggregation (adding proof without recomputing the aggregate) and cross-system aggregation (combining proofs from different proof systems into a single attestation).

Privacy-preserving ML. The most demanding application on the horizon. Proof of inference for small neural networks (thousands of parameters) is tractable but carries 100×+ overhead. Proof of training at GPT scale (billions of parameters, trillions of operations) remains far out of reach. Non-linearities (ReLU, softmax) are expensive in arithmetic circuits; "ZK-friendly" model architectures with amenable activation functions could help but remain speculative. FHE offers a complementary path where the server computes on encrypted data without seeing the inputs (Chapter 27), with hybrid ZK+FHE approaches under active research.

Theoretical foundations

The engineering frontiers above rest on theoretical questions that remain open.

We lack tight lower bounds on proof size for a given soundness error. We have constructions, but no matching impossibility results. Perhaps dramatically better systems are possible; perhaps we're close to optimal. The answer determines whether to keep searching or focus on engineering.

Deep recursion may degrade knowledge soundness. Current security reductions lose tightness with each recursive layer. Whether this is inherent or an artifact of our proof techniques matters directly for the recursive composition that underpins modern zkVMs.

The assumptions underlying SNARKs (knowledge assumptions, generic group model) are stronger than standard cryptographic assumptions. Whether they hold is a matter of ongoing debate. Resolving this either validates the foundations or forces a rethinking of what we build on.

SNARK techniques also have implications beyond cryptography. Progress on proof compression connects to circuit lower bounds, algebraic computation, and the structure of NP. These are among the deepest problems in theoretical computer science.

The field is young enough that systems considered optimal five years ago have already been superseded. Some patterns are visible: post-quantum concerns driving hash-based systems, zkVMs becoming the default abstraction, multilinear polynomials displacing univariate encodings. But ZK proofs are part of a larger landscape that includes fully homomorphic encryption, program obfuscation, and the convergence of programmable cryptography. The next chapter steps back to see where ZK fits in that broader picture.

Key takeaways

  1. Binary fields eliminate representation overhead. Binius and its successor Binius64 prove that matching the field to the data (bits as bits, bytes as bytes) removes the 256× penalty of encoding small values in large prime fields. The tower architecture enables this without sacrificing cryptographic security.

  2. Post-quantum migration is accelerating. Hash-based systems (STARKs, WHIR) work today. Lattice-based commitments (Hachi) are closing the efficiency gap. The Ethereum Foundation targets 128-bit provable security by end of 2026. The question is no longer whether to prepare but how fast.

  3. zkVMs are converging on multilinear proofs. SP1, Jolt, and the Ethereum Foundation's Whirlaway stack have moved to multilinear polynomials and sum-check, while Airbender and RISC Zero push STARK-based approaches to their limits. Real-time Ethereum block proving is now achieved by multiple teams.

  4. Formal verification is catching up to deployment. Machine-checked proofs in Lean now cover core zkVM components (OpenVM, SP1 Hypercube). The persistent gap is end-to-end verification from source code to final proof, especially for optimized GPU implementations.

  5. The bottleneck is shifting from cryptography to systems engineering. Witness generation, memory bandwidth, precompile selection, and proof aggregation increasingly determine real-world performance more than the choice of proof system.

  6. Tight lower bounds remain unknown. We lack matching impossibility results for proof size, and deep recursion may degrade knowledge soundness in ways we cannot yet quantify. The theoretical foundations are solid but incomplete.

Chapter 27: ZK in the cryptographic landscape

In 1943, a resistance fighter in occupied France needs to send a message to London. She writes it in cipher, slips it into a dead letter drop, and waits. A courier retrieves it, carries it across the Channel, and a cryptographer at Bletchley Park decrypts it. The message travels safely because no one who intercepts it can read it.

For the next fifty years, this was cryptography's entire mission: move secrets from A to B without anyone in between learning them. Telegraph, radio, internet. The medium changed; the problem stayed the same. Encrypt, transmit, decrypt. A message sealed or opened, a secret stored or revealed.

Then computers stopped being message carriers and became thinkers. The question changed. It was no longer enough to ask "can I send a secret?" Now we needed to ask: "can I use a secret without exposing it?"

This is the dream of programmable cryptography: secure computation on secrets. The dream took many forms. "Can I prove I know a secret without revealing it?" led to zero-knowledge proofs. "Can we compute together while keeping our inputs private?" led to secure multiparty computation. "Can I encrypt data so someone else can compute on it?" led to fully homomorphic encryption. "Can I publish a program that reveals nothing about how it works?" led to program obfuscation.

These are different philosophies about who computes, who learns, and what trust means. For decades they developed in parallel, each with its own community, its own breakthroughs, its own brick walls.

This book taught you the path that arrived first: zero-knowledge proofs. ZK reached general practicality before the others. MPC is deployed for high-value operations like threshold signing (Chapter 25). FHE works for narrow applications and is improving rapidly. Program obfuscation remains theoretical. Understanding why ZK progressed fastest illuminates both the landscape and the road ahead.

Why ZK arrived first

The most important asymmetry is structural: the prover works in the clear. In ZK, the expensive cryptographic operations happen after the computation, not during it. The prover computes at native speed, then invests work in generating a proof. In fully homomorphic encryption (developed in the next section), every arithmetic operation carries cryptographic overhead because the data stays encrypted throughout. In program obfuscation, the program itself becomes the cryptographic object. This difference compounds across millions of operations.

ZK also benefited from mathematical serendipity. SNARKs exploit polynomial arithmetic over finite fields, exactly what elliptic curves, pairings, and FFTs handle efficiently. The tools developed for other purposes (error-correcting codes, number theory, algebraic geometry) turned out to fit the ZK problem well. FHE and obfuscation involve noise management and lattice arithmetic that fight against efficient computation rather than harmonizing with it.

The theory developed steadily over thirty years. The path from GMR (1985) to PCPs (1992) to IOPs (2016) to practical SNARKs (2016-2020) was long but each step built on the previous. The sum-check protocol from 1991 became the heart of modern systems. Polynomial commitments from 2010 enabled succinctness. The pieces accumulated until they clicked together.

Finally, blockchain created urgent demand. Scalability, privacy, trustless verification: billions of dollars flowed into ZK research. The ecosystem grew rapidly. FHE has applications but no comparable catalyst. Program obfuscation has no applications that couldn't wait until it works, a chicken-and-egg problem that starves it of engineering investment.

MPC also reached practicality, though with different trade-offs. Chapter 25 covers MPC in depth. This chapter focuses on the two dreams that remain partially unfulfilled: computing on encrypted data, and making programs incomprehensible.

Computing on ciphertexts

In 1978, Rivest, Adleman, and Dertouzos asked whether an encryption scheme could support computation on ciphertexts. They called it a "privacy homomorphism": encrypt data, compute on the ciphertexts, decrypt and get the correct result, all without the server ever seeing the plaintext. The question was whether this could work for arbitrary computations, not just a narrow class.

For thirty years, the answer was partial. RSA turned out to be multiplicatively homomorphic (the product of ciphertexts decrypts to the product of plaintexts) but couldn't do addition. Paillier (1999) achieved additive homomorphism but couldn't do multiplication. ElGamal was multiplicative too. Every scheme could do one operation or the other, never both. Since addition and multiplication together are enough to compute any function, the gap between "partially homomorphic" and "fully homomorphic" was the gap between a curiosity and a revolution.

Craig Gentry's 2009 thesis closed that gap.

Learning with errors

Modern FHE rests on the Learning With Errors (LWE) problem, which admits two readings. Algebraically, LWE says that solving a system of linear equations becomes intractable when each equation carries a small random error. This is the view that matters for building encryption: the noise is what makes ciphertexts indistinguishable.

Geometrically, LWE is a lattice problem. A lattice is a regular grid of points in high-dimensional space (integer combinations of basis vectors), and recovering the secret from noisy equations amounts to finding a close lattice point through the noise. This is the view that matters for analyzing security: hardness reductions from worst-case lattice problems (finding shortest vectors, closest vectors) are what give us confidence that LWE resists both classical and quantum attacks. No quantum algorithm is known to solve these lattice problems efficiently.

LWE and its structured variants (Ring-LWE, Module-LWE) underlie the NIST post-quantum encryption standard ML-KEM. As Chapter 26 notes, recent constructions like Hachi are bringing lattice-based polynomial commitments into the ZK landscape as well.

LWE enables encryption by encoding a message bit as a large shift in a noisy inner product. The secret key is a vector . To encrypt a bit , pick a random vector , pick small noise , and compute . The ciphertext is . The bit creates a large gap (adding or nothing); the noise obscures the exact value but not which half of the range we're in. Decryption subtracts the mask and rounds. An attacker who doesn't know faces the LWE problem.

The noise problem

The difficulty of FHE lies in how operations affect the error. A concrete example makes this vivid.

Say our modulus is . We encode bit as values near , bit as values near (that's ). Fresh ciphertexts have noise around . Decryption asks: "Is this value closer to or to ?"

Encrypt two bits, both equal to . Ciphertext decrypts to (the is noise). Ciphertext decrypts to . Both decrypt correctly since and are closer to than to .

Addition is safe. The noises add: . Noise is , still small. But multiplication is where trouble starts. Multiplying ciphertexts multiplies the noises: after one multiplication, noise ; after two, ; after three, . The safety margin is only (values must stay closer to their target than to the alternative). After a few multiplications, noise overwhelms signal and decryption returns garbage.

This is the noise budget: every FHE scheme has a limit on how much computation can be performed before the ciphertext becomes useless. Addition is cheap (noise grows linearly). Multiplication is expensive (noise grows multiplicatively, becoming exponential in circuit depth).

Bootstrapping

The noise budget imposes a depth limit on computation. Gentry's breakthrough was bootstrapping: a way to reset the noise without ever decrypting in the clear.

Return to the example. The ciphertext encoding bit has accumulated noise of . One more multiplication and decryption fails. The noise must come down while the message stays intact, and the plaintext must never be exposed.

The naive fix would be to decrypt the ciphertext and re-encrypt it fresh. But that exposes the plaintext, defeating the purpose. Bootstrapping achieves the same effect without ever leaving ciphertext space, by exploiting the fact that decryption is itself a computation (subtract the mask, round, output the bit). If we run this computation homomorphically on an encrypted copy of the secret key, the rounding step absorbs the old noise internally and the output emerges as a fresh ciphertext.

Concretely, publish an encryption of the secret key, , as a public parameter. Given the noisy ciphertext , treat it as public data (it is already encrypted) and evaluate the decryption circuit homomorphically with as the key input. Inside the homomorphic evaluation, the rounding step absorbs the old noise ( rounds to , giving correctly). Since the key was encrypted, the output is also encrypted: with fresh noise of perhaps instead of the accumulated . The plaintext was never exposed.

This only works if the decryption circuit is shallow enough that running it homomorphically doesn't exhaust the noise budget it is trying to restore. Gentry's construction designs decryption to be "bootstrappable," but the cost is significant; early implementations took minutes per bootstrap. The payoff is that there is no longer a depth limit. Compute until noise grows dangerous, bootstrap to refresh, continue. Any computation becomes possible, one refresh at a time.

Current state

The fifteen years since Gentry's thesis have produced both real improvements and real deployments. FHE is no longer a research curiosity. Apple ships it on a billion phones (Live Caller ID Lookup, iOS 18) for private database queries. Microsoft uses it in Edge's Password Monitor for private set intersection against breach databases. Google's Private Join and Compute handles encrypted advertising attribution. CryptoLab partners with Samsung for encrypted health analytics on Galaxy devices. These deployments share a pattern: the computations are shallow (one or two multiplication depths), the data is structured, and the workloads are embarrassingly parallel.

Three scheme families cover different workload shapes. TFHE optimizes for Boolean circuits through "programmable bootstrapping," where the bootstrap itself computes a function. On modern CPUs, gate evaluation takes roughly 10ms; on GPUs (Zama's TFHE-rs on H100 hardware), bootstrapping has dropped below 1ms. BGV/BFV optimize for integer arithmetic by batching thousands of values into a single ciphertext with SIMD-style parallelism. CKKS accepts approximate arithmetic on fixed-point real numbers, trading small errors for efficiency in workloads like ML inference where exact precision isn't needed.

For the shallow, parallel workloads that current deployments target, FHE overhead is manageable. For general computation the overhead remains to times native, depending on the workload and scheme. Early implementations were a million times slower; the trajectory is encouraging. Hardware acceleration through custom FPGAs and ASICs (programs like DARPA's DPRIVE) could deliver another 100-1000× improvement. But the overhead may have irreducible components: noise management and ciphertext expansion impose costs that shrink with better engineering but may never vanish entirely.

Libraries like Microsoft SEAL, OpenFHE, and Zama's Concrete have made FHE accessible to developers. NIST has begun a standardization process for FHE through its Multi-Party Threshold Schemes call, signaling institutional readiness for wider adoption.

Program obfuscation

Program obfuscation is the most ambitious dream of programmable cryptography. Not just computing on secrets, but making programs themselves into secrets.

Virtual black-box obfuscation

The strongest notion is virtual black-box (VBB) obfuscation: transform a program's source code into a form that still runs correctly but reveals nothing about how it works. A password checker would still accept the correct password and reject all others, but someone reading the obfuscated code could not figure out what the secret password is.

Formally, an obfuscator satisfies VBB if for any program :

  1. Functionality: for all inputs
  2. Black-box security: Anything efficiently computable from is also efficiently computable given only oracle access to

Having the obfuscated code gives you no advantage over having a locked box that runs the program. The code is in front of you, but it's as opaque as a black box.

The impossibility result

In 2001, Barak, Goldreich, Impagliazzo, Rudich, Sahai, Vadhan, and Yang proved that VBB obfuscation is impossible in general. Some programs are inherently "unobfuscatable." The proof constructs a pair of programs and that have identical input-output behavior on almost all inputs but can be distinguished by examining their code. The construction exploits self-reference: program behaves normally on most inputs, but if given its own code as input, it outputs .

Any obfuscation of must output when fed itself, revealing which program it came from. No amount of code transformation can hide this.

Indistinguishability obfuscation

A weaker notion survived. Indistinguishability obfuscation (iO) guarantees only that if programs and compute the same function (identical outputs on all inputs), then their obfuscations are computationally indistinguishable:

This seems weak. You're only hiding implementation details, not the function itself. The power comes from what you can hide inside equivalent programs.

Consider two programs that both output "Hello, World!":

Program A: print("Hello, World!")

Program B:
    secret_key = 0x7a3f...  # 256-bit key, embedded in the code
    if sha256(input) == target:
        return decrypt(secret_key, ciphertext)
    print("Hello, World!")

Program B has a secret key hidden inside it. On every normal input, it behaves identically to Program A. But if you find an input whose hash matches target, it decrypts and returns a hidden message. These programs compute the same function (assuming finding the hash preimage is computationally infeasible), so by iO their obfuscations are indistinguishable. The secret key is in the code, but no one can extract it. The obfuscated program is indistinguishable from an obfuscation of the trivial Program A, which contains no secrets at all.

With efficient iO, you could build almost any cryptographic primitive. The most striking is witness encryption: encrypt a message so that only someone who knows a solution to a puzzle can decrypt it. Not a specific person with a specific key, but anyone who can solve the puzzle.

Witness encryption has a precise duality with zero-knowledge. A ZK proof says "I know a witness for statement " without revealing it. Witness encryption says "only someone who knows a witness can read this" without specifying who. Both are parameterized by an NP statement. ZK proves about the statement; WE encrypts to it.

Beyond witness encryption, iO enables functional encryption (keys that compute from encrypted without learning itself), deniable encryption (produce fake randomness that makes a ciphertext look like it encrypts a different message), and many other primitives. iO acts as a universal building block: given iO, you can construct almost any cryptographic tool. The constraint is not imagination but efficiency.

Construction and costs

In 2021, Jain, Lin, and Sahai constructed iO from well-founded assumptions (variants of LWE and related problems). The theoretical question was settled: iO exists. The construction uses branching programs as the computational model, encoding state transitions in matrix operations obscured by algebraic noise.

For years, all known constructions were exponentially slow: obfuscating a circuit of size required operations scaling as , making anything beyond toy circuits infeasible. That began to change in 2025 with Diamond iO (eprint 2025/236), a lattice-based construction that replaces the costly recursive functional-encryption bootstrapping of earlier schemes with direct matrix operations. Diamond iO is simple enough to implement, and the Machina iO team produced the first end-to-end benchmarks: obfuscating the simplest possible circuit (depth 0, a single input bit, no multiplications) already takes about 16 minutes and produces a 6.3 GB program; at depth 10, obfuscation takes two hours and produces 20 GB. Evaluation of the obfuscated program takes minutes. These numbers are far from practical for general use, but they are finite rather than cosmological. The same could be said of early FHE implementations circa 2011.

One way to sidestep the overhead is to obfuscate as little as possible. The Diamond iO team suggests obfuscating only a small constant-depth circuit whose job is to verify a ZK proof and decrypt a ciphertext, then letting FHE handle the actual application logic. The obfuscated piece stays tiny; iO contributes only what it uniquely provides (hiding the decryption key). The trajectory from "impossible to implement" to "first benchmarks" took four years. How far the next four take us is an open question.

A separate line of work has extended obfuscation to quantum programs. At FOCS 2025, Er-Cheng Tang received the Machtey Award for the first quantum state obfuscation scheme for unitary quantum programs, opening a direction that classical iO cannot address.

Convergence

The boundaries between ZK, MPC, FHE, and obfuscation are dissolving as researchers combine techniques.

The most natural combination is zkFHE. A server computes on encrypted data using FHE, but how does the client know the server computed correctly? The server generates a ZK proof of correct FHE evaluation. The client verifies without decrypting intermediate results, getting both privacy and verifiability in one protocol.

MPC and ZK compose similarly. Multiple parties compute together (Chapter 25) while ZK proves they followed the protocol honestly without revealing individual contributions. Threshold signatures, distributed key management, collaborative computation with verification: the primitives compose naturally.

Even the line between proving and computing is blurring. The folding and accumulation techniques from Chapter 23 let incrementally verifiable computation fold claims together, deferring expensive proof work. ZK handles verification without revelation. MPC enables joint computation. FHE supports outsourced computation on secrets. Each occupies a niche; together they cover territory no single approach could reach.

Trusted Execution Environments (TEEs) like Intel SGX and ARM TrustZone offer a non-cryptographic alternative: hardware isolation at near-native speed, but requiring trust in the hardware manufacturer. Side-channel attacks have repeatedly compromised their guarantees. The cryptographic approaches avoid this trust assumption at the cost of computational overhead.

The landscape at a glance

ApproachWho computes?Who learns result?Trust assumptionStatus
ZKProverVerifierSoundness of proofsPractical
MPCAll parties jointlyAll partiesThreshold honestyPractical (threshold signing, custody)
FHEUntrusted serverClient onlyEncryption securityDeployed for narrow workloads (~1000× general overhead)
iOAnyoneAnyoneObfuscation securityFirst implementations (far from practical)

Key takeaways

  1. ZK's structural advantage is that the prover works in the clear. The cryptographic cost comes after computation, not during it. FHE pays per operation; iO pays per program gate. This asymmetry, combined with algebraic serendipity and blockchain funding, explains why ZK reached general practicality first.

  2. FHE is deployed for shallow, parallel workloads. Apple, Microsoft, and Google ship FHE in production. Bootstrapping enables arbitrary-depth computation by homomorphically evaluating decryption. The overhead (~1000× for general computation, sub-millisecond per gate on GPU for TFHE) continues to shrink but may have irreducible components.

  3. iO moved from theory to first implementation. VBB obfuscation is impossible (Barak et al. 2001), but iO exists (Jain-Lin-Sahai 2021). Diamond iO (2025) produced the first benchmarks. The programs are gigabytes and take hours to obfuscate, but the gap between "impossible" and "merely impractical" is where progress begins.

  4. Trust models determine tool selection. ZK: the prover sees data, the verifier learns only validity. MPC: parties jointly compute, no one sees others' inputs. FHE: the server computes blindly, the client holds the decryption key. The choice depends on who you trust and what you're hiding from whom.

  5. The primitives compose. zkFHE gives encrypted computation with verifiable correctness. MPC + ZK proves honest protocol execution. iO + FHE lets you obfuscate a tiny verifier and outsource computation. No single approach covers the full landscape; together they do.

Appendix A: Cryptographic primitives

This appendix collects cryptographic building blocks used throughout the book but not central to the SNARK narrative. These primitives appear in trusted setups, commitment schemes, and protocol constructions.

Mathematical background

Finite fields

A finite field (for prime ) is the set with addition and multiplication modulo . Every nonzero element has a multiplicative inverse.

  • The multiplicative group has order
  • Fermat's Little Theorem: For , . Thus .
  • Primitive roots: There exists such that

Extension fields arise by adjoining roots of irreducible polynomials. Elements are degree- polynomials over , with multiplication modulo the irreducible polynomial. SNARK-friendly fields often have for 128-bit security.

Roots of unity: If , there exist -th roots of unity satisfying . These enable FFT-based polynomial multiplication.

Elliptic curves

An elliptic curve over is the set of points satisfying plus a "point at infinity" serving as identity.

Points form an abelian group under a geometric addition rule. For distinct points and :

The group order is approximately (Hasse's theorem: ).

Given and , finding is the discrete log problem, believed hard for well-chosen curves. Computing for scalar uses double-and-add, taking group operations.

The Weierstrass form is standard, but other forms offer advantages. Montgomery curves () enable constant-time scalar multiplication via the Montgomery ladder. Twisted Edwards curves () have unified addition formulas (the same formula works for doubling), making them efficient and resistant to side-channel attacks. BabyJubjub and Jubjub are twisted Edwards curves.

Bilinear pairings

A pairing is a map between elliptic curve groups satisfying:

  • Bilinearity:
  • Non-degeneracy: If and are generators, generates
  • Efficiency: Computable in polynomial time

Pairings enable "multiplication in the exponent": given and , you can't compute directly, but moves the product to a different group. KZG commitments use pairings to verify polynomial evaluations: the verifier checks without knowing .

Not all curves support efficient pairings. BN254 and BLS12-381 are specifically designed for this purpose.

Discrete log assumptions

The security of elliptic curve cryptography rests on a hierarchy of assumptions:

  • Discrete Log Problem (DLP): Given and , find .
  • Computational Diffie-Hellman (CDH): Given , , and , compute .
  • Decisional Diffie-Hellman (DDH): Distinguish from for random .

In pairing groups, DDH is easy (check via pairing), but CDH is still believed hard. This is the gap Diffie-Hellman setting that KZG exploits.

Secure random sampling

Many protocols require random field elements sampled uniformly from .

Modulo bias

A common implementation generates random bytes, interprets them as an integer, and takes the result modulo .

x = random_bytes(32)  # 256 bits
r = int(x) mod p

This introduces bias. If , some residues are more likely than others. To sample from using a random byte (0-255): values 0-5 appear with probability (26 preimages each) while values 6-9 appear with probability (25 preimages each). The bias is small but potentially exploitable over many samples.

Rejection sampling

Generate candidates and reject those outside an unbiased range.

repeat:
    x = random_bytes(32)
    if x < p * floor(2^256 / p):
        return x mod p

This ensures each residue has equal probability. Expected iterations: when is close to a power of 2.

Hashing to field elements

When deriving field elements from structured data (Fiat-Shamir challenges, randomness beacons):

  1. Hash the input:
  2. Interpret as integer and reduce modulo
  3. Or use a domain-specific "hash-to-field" function (RFC 9380)

The hash output should be larger than (e.g., 512 bits for a 256-bit field) to minimize bias.

Nothing-up-my-sleeve (NUMS) constructions

Sometimes protocols require public constants that "couldn't have been chosen maliciously." If a constant is needed (e.g., a generator, a hash input), how do we convince others it wasn't chosen to create a trapdoor?

The NUMS technique derives the constant from a public, unpredictable source: digits of , , or ; hashes of fixed strings like ; or sequential integers ("Point number 1", "Point number 2", etc.).

In a Powers of Tau ceremony, the initial toxic waste should be derived via NUMS:

Each participant then randomizes: where is their secret randomness.

Shamir's secret sharing

Distribute a secret among parties such that any can reconstruct but learn nothing.

Construction

Work over a finite field with .

Sharing (by dealer):

  1. Choose random polynomial
  2. The secret is
  3. Give party the share

Reconstruction (by any parties):

  1. Collect shares:
  2. Use Lagrange interpolation to find :

Security

Any shares are consistent with every possible secret. The polynomial through points can have any value at 0. This is information-theoretic: even computationally unbounded adversaries learn nothing.

The threshold exhibits a sharp discontinuity. With shares, the entropy of the secret is bits (maximum uncertainty). With shares, the entropy drops to zero (the secret is uniquely determined). There is no intermediate state where information leaks gradually as shares accumulate.

Worked example

Secret , threshold , parties , field .

Polynomial: (random coefficient ).

Shares:

  • Party 1:
  • Party 2:
  • Party 3:

Reconstruction from parties 1 and 3:

In : , .

Feldman's verifiable secret sharing

Standard Shamir assumes an honest dealer. A malicious dealer could distribute inconsistent shares that don't reconstruct to any secret, or that reconstruct to different secrets for different groups. Feldman's VSS solves this by broadcasting commitments to the polynomial coefficients.

Setup: Group of prime order , generator .

Sharing:

  1. Dealer chooses
  2. Dealer broadcasts commitments:
  3. Dealer sends share to party

Verification: Party checks:

This holds because:

If verification fails, party broadcasts a complaint. Honest parties can detect malicious dealers.

Feldman VSS reveals (the "encrypted" secret). This may leak partial information (e.g., equality with other secrets). Pedersen VSS adds blinding for perfect hiding.

Hash functions in zero-knowledge

SNARKs use hash functions for Fiat-Shamir challenges, Merkle tree commitments (FRI, STARKs), and random oracle instantiation.

The circuit cost problem

Standard hashes (SHA-256, BLAKE3) are expensive in circuits. SHA-256 uses operations that CPUs handle efficiently (32-bit XOR, bit rotations, boolean operations), but these are catastrophic inside arithmetic circuits over prime fields.

A single XOR in an arithmetic circuit requires decomposing each input into bits (one constraint per bit to enforce booleanity: ), then computing the XOR bit-by-bit as . A 256-bit XOR that takes one CPU cycle becomes hundreds of constraints. SHA-256 costs roughly 25,000-30,000 constraints per invocation. A depth-20 Merkle tree (about 1 million leaves) requires 20 hashes, totaling 500,000-600,000 constraints just for hashing.

Algebraic hashes

Algebraically-friendly hashes use only native field operations: addition and multiplication. No bit operations at all.

Poseidon is the dominant choice. It uses a sponge construction with a permutation built from three layers per round:

  1. Add round constants: Breaks symmetry. Cost: 0 constraints (additions are linear).
  2. S-box: Apply (typically ) for nonlinearity. Cost: 2 constraints per S-box.
  3. MDS matrix: Multiply state by a maximum-distance-separable matrix for diffusion. Cost: 0 constraints (linear operations absorbed into next nonlinear step).

The HADES design uses full rounds (S-box on all state elements) at the beginning and end for statistical security, and partial rounds (S-box on only one element) in the middle for algebraic security. A typical configuration of 8 full rounds and 56 partial rounds totals ~160 constraints per hash, compared to ~25,000 for SHA-256.

Other algebraic hashes include MiMC (2016, simpler but higher multiplicative depth, largely superseded), Rescue (alternates S-box and inverse S-box), and Poseidon2 (2023, same constraints as Poseidon but 3× faster witness generation).

Security considerations

Algebraic hashes have less cryptanalytic history than SHA-256. Poseidon has received sustained analysis (Grassi et al. 2019, subsequent Gröbner basis attacks), and current parameters include security margins. Conservative applications may use more rounds than the minimum recommended or fall back to SHA-256 for security-critical operations outside circuits.

Poseidon is not for general-purpose hashing. For files, passwords, or data at rest, use SHA-256 or BLAKE3. Poseidon is a specialized tool for proving hash computations inside ZK circuits.

Modular arithmetic implementation

SNARK provers spend most time in modular arithmetic. Implementation details matter enormously.

Montgomery multiplication

Standard modular multiplication computes , then divides by and takes the remainder. Montgomery representation avoids the expensive division by storing where for convenient . The Montgomery product replaces division by with division by , which is a bit shift (essentially free in hardware). The conversion overhead is amortized over many operations.

SIMD and parallelism

Modern CPUs have vector instructions (AVX-256, AVX-512) that parallelize field arithmetic: four 64-bit multiplications simultaneously, or eight 32-bit multiplications simultaneously. GPU arithmetic parallelizes across thousands of threads. SNARK provers achieve 10-100× speedup from GPU acceleration.

Random beacons

Some applications require public randomness that cannot be predicted before a deadline, cannot be biased by any party, and is verifiable by all.

Blockchain-based beacons use the hash of a future block as randomness. The block hash is unpredictable until mined, but miners can withhold blocks to manipulate the beacon (at cost of block rewards).

VDF-based beacons use a Verifiable Delay Function that requires sequential time to compute but is fast to verify. A beacon seeds a VDF; by the time the output is known, manipulation is impossible.

Multi-party beacons have multiple parties contribute randomness. If any one is honest, the result is unbiased. The simple protocol has each party commit to a random value, then all reveal; the beacon is the hash of all revealed values. The risk is that the last revealer sees the beacon before revealing; commit-then-reveal with timeouts mitigates this.

Elliptic curves in zero-knowledge

Not all elliptic curves work for SNARKs. Pairing-based systems (Groth16, KZG commitments) require curves with efficiently computable bilinear pairings. The choice of curve determines the scalar field, which in turn determines what field elements your circuit operates over.

BN254 (alt_bn128)

A Barreto-Naehrig curve with embedding degree 12 and the workhorse of practical SNARKs.

  • Scalar field: (254 bits)
  • Security: Originally claimed ~128 bits, now estimated at ~100 bits due to advances in discrete log attacks on extension fields
  • Status: Still widely used (Ethereum precompiles, most zkEVMs, Groth16 deployments)

BN254's scalar field prime:

Ethereum has native precompiles for BN254 operations (ecAdd, ecMul, ecPairing), making it the default for on-chain verification.

BLS12-381

A Barreto-Lynn-Scott curve with embedding degree 12. Designed to provide ~128-bit security even with improved attacks.

  • Scalar field: (255 bits)
  • Security: Solid 128-bit security margin
  • Status: Used in newer systems (Zcash Sapling, Ethereum 2.0 signatures, PLONK implementations)

BLS12-381 is larger than BN254 (larger field, more expensive operations) but future-proof against known attack improvements.

Embedded curves

Pairing curves have large coordinates. Computing BN254 point addition inside a BN254 circuit is expensive because the base field is ~254 bits, requiring big-integer arithmetic in constraints. The solution is to use a different curve whose base field matches the SNARK's scalar field.

BabyJubjub is a twisted Edwards curve defined over BN254's scalar field. Points on BabyJubjub have coordinates in where is BN254's scalar field order. BabyJubjub operations are native arithmetic in BN254 circuits, with point addition costing ~6 constraints instead of thousands. EdDSA signature verification becomes practical inside circuits.

Jubjub plays the same role for BLS12-381: a twisted Edwards curve over BLS12-381's scalar field.

The pattern: an "embedded" or "inner" curve lives over the outer curve's scalar field, enabling efficient in-circuit elliptic curve operations.

Curve cycles

For recursive SNARKs, you need to verify a proof inside a circuit. If both the proof system and the circuit use the same field, the verifier does arithmetic in the scalar field while the proof's group operations are over the base field.

A curve cycle pairs two curves where each curve's base field equals the other's scalar field. Pasta curves (Pallas and Vesta) form such a cycle, enabling efficient recursion in systems like Halo 2.

CurveBase FieldScalar Field
Pallas
Vesta

Prove over Pallas, verify in a Vesta circuit; prove over Vesta, verify in a Pallas circuit. The cycle enables indefinite recursion. The BN254/Grumpkin cycle matters for Ethereum developers: since BN254 is precompiled on Ethereum, systems like Aztec use this cycle to verify recursive proofs on-chain cheaply.

Group operations

Elliptic curve SNARKs rely on fast group operations.

Point addition (affine)

Given points and on curve :

Affine coordinates require field inversion (expensive).

Projective coordinates

Represent as where , . Point addition and doubling use only multiplication, avoiding inversion until final conversion back to affine. Jacobian coordinates with , are optimized for repeated doubling.

Multi-scalar multiplication (MSM)

Compute for scalars and points .

Pippenger's algorithm groups scalars by their bit patterns, reducing work from to .

MSM dominates KZG commitment time. Parallelization and GPU implementation are necessary for practical SNARKs.

Appendix B: Historical Timeline

The development of zero-knowledge proofs and succinct arguments spans four decades. This timeline traces the key theoretical breakthroughs and practical systems that shaped the field.

Theoretical foundations (1985-1992)

1985: GMR (Interactive Proofs and Zero-Knowledge) Goldwasser, Micali, and Rackoff introduce interactive proofs and define zero-knowledge. The paper "The Knowledge Complexity of Interactive Proof Systems" establishes the foundational concepts: completeness, soundness, and the simulation paradigm for zero-knowledge. A conceptual revolution: proving something is true without revealing why it's true.

1986: Fiat-Shamir Transform Fiat and Shamir show how to eliminate interaction by replacing verifier randomness with hash function outputs. The prover computes challenges as hashes of the transcript, producing a non-interactive proof. The random oracle model provides the security analysis.

1986-1987: GMW (Zero-Knowledge for All of NP) Goldreich, Micali, and Wigderson prove that every NP language has a zero-knowledge proof, assuming one-way functions exist. The graph 3-coloring construction is theoretical (impractical for real use) but establishes the surprising generality of zero-knowledge.

1990: LFKN (Algebraic Interactive Proofs) Lund, Fortnow, Karloff, and Nisan develop the sum-check protocol for proving claims about polynomial sums. This algebraic technique becomes the cornerstone of later efficient protocols. The paper shows #P IP.

1991: MIP = NEXP (Babai, Fortnow, Lund) Multi-prover interactive proofs, where the verifier interrogates two non-communicating provers, can verify nondeterministic exponential time computations. The result establishes the surprising power of multiple provers and connects to PCP theory.

1992: IP = PSPACE (Shamir) Shamir proves that interactive proofs can verify exactly the problems solvable in polynomial space. The result uses multilinear extensions and sum-check, establishing the power of interaction + randomness.

1992: The PCP Theorem (AS, ALMSS) Arora and Safra (AS) prove NP PCP[log n, polylog n]; Arora, Lund, Motwani, Sudan, and Szegedy (ALMSS) strengthen this to NP = PCP[log n, O(1)]. Every NP statement has a proof where the verifier reads only a constant number of bits. The theoretical foundation for succinct arguments.

1992: Kilian's Succinct Arguments Kilian shows how to compile PCPs using Merkle trees and collision-resistant hashing. The prover commits to the PCP, the verifier queries random bits, and the prover opens with authentication paths. This is the first succinct argument for NP, with proof size polylogarithmic in the computation.

The ZK winter (1992-2008)

For sixteen years, zero-knowledge proofs remained impractical. The PCP theorem promised succinct proofs, but the constructions had astronomical overhead ( blowup in early versions). Researchers refined PCP constructions, developed new proof composition techniques, and explored connections to coding theory, but there were no implementations and no urgency.

Two developments changed that. In 2008, Goldwasser, Kalai, and Rothblum published GKR, showing that sum-check could verify arithmetic circuits with manageable overhead. In 2009, Bitcoin launched, creating a financial ecosystem with urgent demand for privacy, scalability, and trustless verification. The tool and the demand arrived at roughly the same time.

Path to practical systems (2008-2016)

2008: GKR (Efficient Verification of Arithmetic Circuits) Goldwasser, Kalai, and Rothblum develop a protocol for verifying layered arithmetic circuits using sum-check. The prover does polynomial work; the verifier does polylogarithmic work. Later refinements by Cormode, Mitzenmacher, and Thaler make it truly practical.

2010: Groth10 (First Practical Pairing-Based SNARK) Groth introduces succinct arguments using pairings, building on ideas from linear PCPs. The construction enables constant-size proofs verified with a constant number of pairings.

2010: Kate-Zaverucha-Goldberg (KZG) Commitments The KZG paper formalizes polynomial commitments using pairings. Commit to a polynomial with one group element; prove evaluations with one group element. This becomes the cryptographic engine for most practical SNARKs.

2013: Pinocchio Parno, Howell, Gentry, and Raykova build the first complete, implemented SNARK for general computation. C programs compile to circuits; circuits compile to proofs. Real-world verification becomes possible.

2014: Zcash Begins Development The Zerocoin team, building on Pinocchio, starts developing what becomes Zcash, the first major deployment of zkSNARKs for cryptocurrency privacy.

2016: Groth16 (The Speed King) Groth publishes an optimized SNARK with the smallest known proofs (3 group elements) and fastest verification (3 pairings). Despite requiring per-circuit trusted setup, Groth16 becomes the de facto standard for production systems.

2016: ZKBoo (MPC-in-the-Head) Giacomelli, Madsen, and Orlandi publish ZKBoo, the first practical implementation of "MPC-in-the-head." The prover simulates a multiparty computation internally, then lets the verifier audit random subsets. ZKBoo proves that zero-knowledge could be built entirely from symmetric primitives (hashes), offering a third path distinct from pairings (Groth16) and polynomial commitments (STARKs).

The scaling era (2017-2020)

2017: STARKs (Transparent Scalable Arguments) Ben-Sasson, Bentov, Horesh, and Riabzev introduce STARKs (Scalable Transparent ARguments of Knowledge). Based on FRI and hash functions, STARKs require no trusted setup and resist quantum attacks. Proofs are larger but prover time is quasi-linear.

2018: Bulletproofs (Logarithmic Range Proofs) Bünz, Bootle, Boneh, Poelstra, Wuille, and Maxwell develop Bulletproofs using inner-product arguments. Logarithmic proof size for range proofs without trusted setup. Adopted by Monero for confidential transactions.

2018: Zcash Sapling Upgrade Zcash launches Sapling with improved Groth16-based proofs. Proving time drops from ~40 seconds to ~7 seconds on mobile devices.

2019: PLONK (Universal Setup) Gabizon, Williamson, and Ciobotaru introduce PLONK (Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge). One trusted setup ceremony supports all circuits up to a size bound. The permutation argument elegantly handles copy constraints.

2019: Halo (Recursive Proofs Without Pairings) Bowe, Grigg, and Hopwood demonstrate recursion using inner-product arguments over elliptic curves, avoiding the pairing bottleneck. Proofs verify proofs verify proofs, with unlimited depth.

2019-2020: zk-Rollups Emerge Teams including Loopring, zkSync, and StarkWare deploy zk-rollups on Ethereum. Transaction data lives on-chain; execution validity is proven off-chain. Throughput increases 100-1000×.

Three lineages

By the end of this era, three distinct lineages of zero-knowledge proofs had emerged from a common ancestor:

                    Interactive Proofs (1985)
                              │
            ┌─────────────────┼─────────────────┐
            │                 │                 │
            ▼                 ▼                 ▼
    PAIRING LINEAGE     HASH LINEAGE     SUM-CHECK LINEAGE
            │                 │                 │
            ▼                 ▼                 ▼
     Pinocchio (2013)    FRI (2017)        GKR (2008)
            │                 │                 │
            ▼                 ▼                 ▼
     Groth16 (2016)    STARKs (2017)    Spartan (2019)
            │                 │                 │
            ▼                 ▼                 ▼
      PLONK (2019)    Circle STARKs      Jolt (2023)

Three lineages of zero-knowledge proofs, each with distinct cryptographic foundations: pairings, hashes, and sum-check.

The modern era (2020-present)

2020-2022: Lookup Arguments Mature Plookup (Gabizon, Williamson, and Maller, 2020), cq, and other lookup protocols become standard. Table-based constraint checking replaces expensive algebraic encoding for range checks, bitwise operations, and memory access.

2021-2022: Nova and Folding Schemes Kothapalli, Setty, and Tzialla introduce Nova, which replaces expensive recursive SNARK verification with cheap algebraic "folding." Per-step overhead drops from thousands of constraints to a handful of group operations.

2022: Plonky2 (PLONK + FRI) Polygon Zero combines PLONK's flexible arithmetization with FRI's transparent polynomial commitments over a small Goldilocks field. Fast recursion (under 300ms on a laptop) enables practical proofs of Ethereum execution.

2023: Lasso and Jolt Setty, Thaler, and colleagues develop Lasso (efficient lookups for sum-check-based systems) and Jolt (a RISC-V zkVM using these techniques). The sum-check renaissance: proving returns to its interactive-proof roots.

2023: zkEVMs Launch Multiple teams (Polygon, Scroll, zkSync Era, Linea) deploy zkEVMs that prove Ethereum Virtual Machine execution. Arbitrary smart contracts gain ZK privacy or scalability.

2023: SP1 and Competitive zkVMs Succinct Labs releases SP1, a RISC-V zkVM emphasizing developer experience. Competition intensifies: RISC Zero, Jolt, Valida, and others push proving speed and flexibility.

2024: Circle STARKs and Small Fields StarkWare and others explore STARKs over small fields (Mersenne primes, binary towers), trading field size for faster arithmetic. Proof sizes shrink; prover speeds increase.

2024-Present: Folding and IVC Proliferate Nova variants (SuperNova, HyperNova, ProtoStar) extend folding to handle complex constraint types. Incrementally verifiable computation becomes practical for long-running programs.

Convergence

Modern zkVMs are the confluence of three decades of distinct research streams:

    SUM-CHECK              LOOKUPS              FOLDING
    (1990)                 (2020)               (2021)
        │                     │                    │
        │   LFKN, GKR         │   Plookup, Lasso   │   Nova, HyperNova
        │   Spartan           │   cq, Jolt         │   ProtoStar
        │                     │                    │
        └─────────────────────┼────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │     zkVMs       │
                    │                 │
                    │  Jolt, SP1,     │
                    │  RISC Zero,     │
                    │  Zisk           │
                    └─────────────────┘

Modern systems like Jolt and SP1 combine sum-check's linear proving, lookup arguments' efficient table access, and folding's cheap recursion. The zkVM is where three rivers meet.

Visual timeline

1985 ─────── GMR: Interactive Proofs, Zero-Knowledge
1986 ─────── Fiat-Shamir Transform, GMW begins
1990 ─────── LFKN: Sum-Check Protocol
1991 ─────── MIP = NEXP (Multi-Prover Proofs)
1992 ─────── IP = PSPACE, PCP Theorem, Kilian
      │
2008 ─────── GKR Protocol
2010 ─────── Groth's First Pairing-Based SNARK
2010 ─────── KZG Polynomial Commitments
2013 ─────── Pinocchio (First Practical SNARK)
2016 ─────── Groth16 (Optimal Proof Size)
      │
2017 ─────── STARKs (Transparency)
2018 ─────── Bulletproofs (Range Proofs)
2019 ─────── PLONK (Universal Setup)
2019 ─────── Halo (Recursive Without Pairings)
2020 ─────── zk-Rollups Deploy, Plookup
      │
2022 ─────── Plonky2 (PLONK + FRI), Nova (Folding)
2023 ─────── Lasso/Jolt (Sum-Check Renaissance)
2023 ─────── zkEVMs Launch
2024 ─────── Circle STARKs, Small Fields
      │
      ▼
    NOW ─── Folding proliferates, zkVMs compete

Key themes

Theory to practice (1985-2016): Early work established that zero-knowledge proofs exist for all of NP, but constructions were impractical. The path from GMR to Groth16 took 31 years.

The trusted setup debate (2016-2019): Groth16's efficiency came with per-circuit trusted setup. PLONK's universal setup and STARKs' transparency offered alternatives. The field fragmented into camps, each valid for different applications.

The zkVM vision (2020-present): Rather than hand-crafting circuits for each application, prove correct execution of arbitrary programs. RISC-V emerges as the dominant target ISA.

The sum-check renaissance (2022-present): After years of PCP-inspired constructions, the field rediscovers sum-check's elegance. Linear-time proving, virtual polynomials, and folding schemes push efficiency toward theoretical limits.

Chapter 26 surveys the active frontiers in detail.

Appendix C: Field equations cheat sheet

A quick reference for the core equations in zero-knowledge proof systems.

Schwartz-Zippel lemma

For a non-zero polynomial of total degree over a field :

Consequence: Random evaluation catches cheating with probability .

Multilinear extensions

Lagrange basis polynomial

For :

Property: and for .

Multilinear extension formula

For :

Equality polynomial

Property: if , else (on hypercube).

Sum-check protocol

Dimensions: Vector size ; protocol runs rounds.

The claim

Prove:

Round polynomial

Prover sends:

Verifier checks

  • Round 1:
  • Round :
  • Final: query oracle at to check

Soundness

where is the number of variables and is the maximum individual degree. (More precisely: where is the degree in variable .)

Vanishing polynomials

Over roots of unity

For domain where :

Property: for all , and for .

Over boolean hypercube

For proving a polynomial vanishes on , use the univariate identity:

applied variable by variable in multilinear settings.

R1CS and QAP

R1CS constraint

Dimensions: Matrices are ; witness is ; result is .

For witness vector :

where is entry-wise multiplication.

QAP polynomial identity

Define polynomials by interpolating constraint matrices.

The constraint system is satisfied iff:

where is the vanishing polynomial.

KZG polynomial commitments

Dimensions: Polynomial degree ; SRS size elements.

Structured reference string (SRS)

Secret ; public:

Commitment

For :

Evaluation proof

To prove : (Prover knows ; Verifier knows , , )

  1. Compute quotient:
  2. Proof:

Verification (pairing check)

Equivalently:

FRI folding

Split polynomial

For :

  • : even coefficients
  • : odd coefficients

Folding with challenge

Property:

Consistency check

At query point (where is its conjugate on the same coset), verify:

This uses: and .

AIR (Algebraic Intermediate Representation)

Trace polynomials

For a trace matrix with registers and timesteps, interpolate each column over domain :

Transition constraints

For a constraint "register 0 at next step equals of current registers":

The shift accesses "next row" values. Define constraint polynomial:

Quotient check

Valid trace iff vanishes on transition domain :

is a polynomial (not rational function).

Boundary constraints

Pin inputs/outputs. For :

must be a polynomial.

PLONK

Gate equation

on domain .

Permutation grand product

Accumulator satisfies:

Property: The product telescopes, so iff all copy constraints hold.

Quotient check

All constraints satisfied iff there exists with:

Groth16

Public input combination

Given public inputs where :

where are verification key elements.

Verification equation

Given proof :

Verification cost: One MSM (size ) + 3-4 pairings, independent of circuit size.

Proof size

3 group elements: 128 bytes over BN254 (32 + 64 + 32 for , , ).

Lookup arguments

Plookup identity

For lookups and table , let .

Property: Equality holds iff .

LogUp identity

For lookups into table with multiplicities :

Property: Equality holds iff each and counts occurrences correctly.

Soundness: By Schwartz-Zippel, equality holds with probability over random .

Advantage: No sorting required; additive structure enables multi-table batching.

GKR protocol

Dimensions: Layer has gates; layer (inputs) has gates; .

Layer reduction

For layered circuit with values at layer :

Sum-check reduction

A claim about reduces via sum-check to claims about and for random .

Soundness: Compound over layers, each with sum-check rounds.

Inner product argument (IPA)

The claim

Prove for committed .

Folding step

Given challenge :

Property:

where and .

Proof size

group elements after rounds.

Nova folding

Relaxed R1CS

Standard R1CS:

Relaxed R1CS with scalar and error :

A satisfying instance has and .

Folding two instances

Given instances and , with challenge :

where is the "cross-term" computed by the prover.

Property: If both inputs satisfy relaxed R1CS, so does the folded instance.

Fiat-Shamir transform

Challenge derivation

Security requirement

The hash must include:

  • The public statement
  • All previous commitments
  • All previous challenges

Complexity summary

SystemProof SizeVerificationProverSetup
Groth16Per-circuit
PLONK+KZGUniversal
STARK/FRITransparent
BulletproofsTransparent
Sum-check IPNone

Field sizes (common choices)

FieldSizeSecurityUse Case
BN254 scalar~100 bitsEthereum, Groth16, PLONK
BLS12-381 scalar~128 bitsZcash, many SNARKs
Goldilocks~100 bits*Plonky2, fast arithmetic
Baby Bear~100 bits*RISC Zero
KoalaBear~100 bits*Lean Ethereum (Whirlaway)
Mersenne-31~100 bits*Circle STARKs, Airbender

*Small fields require extension fields for cryptographic security; base field security refers to the overall system design.

Quick reference

Proving a sum over hypercube: Sum-check protocol

Encoding data as polynomial: Multilinear extension (hypercube) or Lagrange interpolation (roots of unity)

Binding prover to polynomial: KZG (trusted setup, constant size), FRI (transparent, log² size), IPA (no pairings, log size)

Checking polynomial identity on a domain: Quotient by for roots of unity

Checking table membership: Lookup argument (Plookup with sorting, LogUp without)

Verifying circuit layer-by-layer: GKR protocol with sum-check at each layer

Incremental computation: Nova folding (amortize SNARK cost across steps)

Eliminating interaction: Fiat-Shamir with complete transcript hashing

Appendix D: Advanced polynomial commitment schemes

This appendix covers polynomial commitment schemes that achieve specialized trade-offs beyond the KZG and IPA schemes of Chapter 9.

Hyrax

Chapter 9's IPA scheme has linear verification time: the verifier must compute the folded generators, doing work for a polynomial with coefficients. Hyrax (Wahby et al., 2018) reduces verification to by exploiting the tensor structure of multilinear polynomials. Polynomial evaluation can be written as a vector-matrix-vector product, and this matrix structure enables a commitment scheme where the prover commits to rows separately.

A multilinear polynomial over variables has coefficients. The naive approach stores these as a flat vector and commits with a single Pedersen commitment using generators. Evaluation then requires work.

Hyrax reshapes the flat vector into a matrix :

The entry stores the coefficient , which corresponds to the evaluation at the Boolean point whose binary representation concatenates and .

Polynomial evaluation then decomposes into a vector-matrix-vector product, and the prover can commit to rows separately, reducing verification from to .

Tensor structure of multilinear evaluation

Recall from Chapter 5 that multilinear evaluation uses the equality polynomial:

where .

The key observation: factors across the split. If we partition the evaluation point where and , then:

Define the Lagrange coefficient vectors:

Then evaluation becomes a bilinear form. Starting from the MLE definition:

Split each index where indexes rows and indexes columns:

Factor the equality polynomial:

Substitute the Lagrange vectors and :

This is a rank-2 tensor contraction: two vectors contracting with a matrix. The factorization of separates "row selection" () from "column selection" (), which is what makes the matrix reshaping useful. A flat vector evaluation requires touching all terms, but can be computed in two steps: first (a length- vector), then (a single dot product). Each step involves only operations.

The Hyrax commitment scheme

Public parameters

Random generators and for blinding.

Commitment

Instead of committing to all coefficients at once (which would require generators), commit to each row separately:

where is a blinding factor for row . The full commitment is the tuple of row commitments:

This requires group elements, not one. The trade-off: larger commitment size for cheaper verification.

The opening protocol

To prove where :

Step 1: Both parties compute Lagrange vectors

From the evaluation point , both prover and verifier compute:

  • : row Lagrange coefficients from
  • : column Lagrange coefficients from

Step 2: Prover computes the projection vector

The prover computes , the weighted column sums:

Each is the -weighted sum of column .

Step 3: Verifier computes combined commitment (MSM #1)

The verifier combines the original row commitments (from the commitment phase) using :

This is computed by the verifier during opening, not as part of the initial commitment. The verifier doesn't have access to the matrix , but they don't need it. By Pedersen's homomorphism, a linear combination of commitments is a commitment to the linear combination of the underlying vectors:

The inner sum is exactly the projection vector , where each is defined in Step 2. So would equal if the prover computed correctly. The verifier doesn't know yet, but they have computed what a commitment to the correct should be.

Step 4: Verify consistency (MSM #2)

The prover sends . The verifier computes a commitment to the claimed :

Check:

If , the prover's is consistent with the committed matrix. The verifier derived from the row commitments (which bind the prover to ), so equality means the prover computed correctly.

Step 5: Verify the dot product

Check:

Why this proves evaluation

The tensor contraction gives:

So the dot product check verifies that the claimed value equals the polynomial evaluation.

Zero-knowledge variant

The prover doesn't send directly (which would leak information about ). Instead, both checks are combined into a ZK dot product protocol that proves consistency without revealing .

Zero-knowledge dot product protocol

Hyrax uses a Schnorr-style protocol for proving where is committed (with blinding) and is public.

Setup

Prover holds with Pedersen commitment and blinding factor .

Protocol

  1. Prover picks random masking vector and blinding
  2. Prover sends commitment and masked dot product
  3. Verifier sends random challenge
  4. Prover responds with and
  5. Verifier checks:
    • (commitment consistency)
    • (dot product relation)

The first check ensures opens the linear combination . The second check verifies that , which holds only if .

Communication cost: field elements (the response vector ).

Worked example

Let's trace through Hyrax for variables, so evaluations arranged as a matrix.

Setup

Polynomial evaluations on arranged as matrix (row index = first 2 bits, column index = last 2 bits):

Generators: and blinding generator . Evaluation point: (working over reals for clarity).

Step 1: Commitment phase

Prover commits to each row (omitting blinding for clarity):

The commitment is : four group elements.

Step 2: Compute Lagrange vectors

Split where and .

Similarly . Both prover and verifier compute these from the evaluation point.

Step 3: Compute projection vector

The prover computes . Each is the -weighted sum of column :

So . The prover sends (in the non-ZK variant).

Step 4: Two MSMs

MSM #1: Combine row commitments with :

Expanding:

MSM #2: Commit to using generators:

Check: ✓ (The projection vector is consistent with the committed matrix.)

Step 5: Dot product check

Check:

Verification cost

The verifier performed two MSMs of size 4 (not 16), plus field arithmetic for the dot product. Total: group operations.

Using Bulletproofs for logarithmic proof size

The basic Hyrax protocol has communication because the prover sends (length ) in the Schnorr-style dot product proof. This can be reduced to by replacing Schnorr with Bulletproofs' inner product argument.

Bulletproofs (Bünz et al., 2018) proves with proof size but verifier time (linear in vector length). When applied to Hyrax's dot product step (vectors of length ):

  • Proof size: (row commitments dominate)
  • Verifier time: (MSM for plus Bulletproofs verification on length- vectors)

The parameter

The Hyrax paper introduces a generalization parameter that controls a communication vs. computation trade-off. Instead of a square matrix, arrange the coefficients as :

  • (square-root): matrix, commitment, verification
  • : matrix, commitment, verification
  • General: commitment size, verification time

Higher reduces commitment size (fewer row commitments) at the cost of higher verification time (longer dot product vectors). Since the commitment is sent once but may be opened many times, the square-root case () typically offers the best balance.

Properties and trade-offs

PropertyHyrax (square-root, )
Trusted setupNone (Transparent)
Commitment size group elements
Proof size with Bulletproofs
Verification time group operations
Prover time for commitment, per opening
AssumptionDiscrete log only
Quantum-safeNo

Comparison with IPA:

IPAHyrax
Commitment size
Verification time
Proof size

Both IPA and Hyrax (with Bulletproofs) achieve logarithmic proof size, but Hyrax trades larger commitments for faster verification. This trade-off is worthwhile when:

  • The same polynomial is opened at multiple points (amortizes commitment cost)
  • Verification speed matters more than proof/commitment size
  • You want transparency without paying IPA's linear verification cost

Connection to Dory

Hyrax's square-root verification is an improvement over IPA's linear verification, but can we do better? Dory answers yes by combining Hyrax's matrix structure with pairings.

The key observation: Hyrax's verifier bottleneck is the MSM . This is group operations. Dory eliminates this by:

  1. Tier 2 commitment: Instead of storing row commitments directly, Dory combines them into a single element using pairings
  2. Lazy verification: The verifier never computes explicitly; instead, they track commitments in and verify everything with a single final pairing check

Where Hyrax achieves verification, Dory achieves . The cost is more complex cryptographic machinery (pairings, two-tier structure, SXDH assumption instead of plain discrete log).


Dory

Hyrax reduces IPA's verification to by exploiting tensor structure. Dory (Lee, 2021) pushes further to by combining Hyrax's matrix arrangement with pairings.

In IPA, the verifier recalculates the folded generators at each step, doing work. Dory's verifier instead accumulates commitments in and defers all verification to a single final pairing check. The algebraic structure of pairings makes this possible: the verifier "absorbs" folding challenges into target group elements without touching the original generators directly.

Two-tier commitment structure

Dory commits to polynomials using AFGHO commitments (Abe et al.'s structure-preserving commitments) combined with Pedersen commitments.

Public parameters (SRS): Generated transparently by sampling random group elements (the notation means "sampled uniformly at random from"):

  • : commitment key for row commitments
  • : commitment key for final commitment
  • , : blinding generators (for hiding/zero-knowledge)
  • : derived blinding generator in

All parameters are public. The prover's secrets are the blinding factors .

Tier 1: Row Commitments ()

Treat the polynomial coefficients as a matrix . For each row , compute a Pedersen commitment:

where is a secret blinding factor. This produces elements in .

Tier 2: Final Commitment ()

Combine row commitments via pairing with generators :

where is a final blinding factor. This produces one element (the commitment).

Why two tiers?

TierPurpose
Tier 1 (rows)Enables streaming: process row-by-row with memory
Row commitments serve as "hints" for efficient batch opening
Tier 2 ()Provides succinctness: one element regardless of polynomial size
Binding under SXDH assumption in Type III pairings

The AFGHO commitment is hiding because is uniformly random in . Both tiers are additively homomorphic, which the evaluation protocol relies on.

From coefficients to matrix form

Dory uses the same tensor decomposition as Hyrax. The evaluation point splits into row coordinates and column coordinates . Each half determines a vector of Lagrange coefficients and via the equality polynomial (see the Hyrax derivation above). The evaluation becomes a bilinear form:

Dory uses for row (left) and for column (right) coefficients, distinct from the evaluation point .

The opening protocol (Dory-Innerproduct)

The key reduction: Polynomial evaluation becomes an inner product. Define two vectors:

  • , the matrix times the column Lagrange vector. Each entry is row evaluated at the column coordinates.
  • , the row Lagrange vector.

Then . The inner product of these two vectors is the polynomial evaluation.

Goal: Prove for committed vectors, which proves for the polynomial.

Dory proves membership in the language:

In words: commits to (using ), commits to (using ), and commits to their inner product. The protocol proves these three commitments are consistent, that the same vectors appear in all three.

How verification works

The prover knows , , and . The verifier can compute and from the evaluation point but doesn't know . The verifier never needs directly. Instead:

Step 1: The verifier has the commitment (which encodes cryptographically) and the claimed evaluation .

Step 2: The prover sends a VMV message where:

  • (row commitments combined with row Lagrange coefficients)

Recall from earlier. This is the non-hiding variant; the row commitments already contain blinding from tier 1.

Step 3: First verification check. The verifier checks:

Why this works: By Pedersen linearity:

Note that is a row vector, while is a column vector. However, both represent "partial evaluations" of the matrix. The key point: is determined by the row commitments and Lagrange coefficients. The check verifies that the prover's is consistent with the row commitments . This binds the prover's intermediate computation to the committed polynomial.

Step 4: The verifier computes (not from the prover).

The verifier computes this themselves from the claimed evaluation . This is how the claimed value enters the protocol: it's bound to the blinding generator . If the prover lied about , then won't match the prover's internal computation, and the final check will fail.

Step 5: Initialize verifier state.

  • (from VMV message)
  • the polynomial commitment (the tier-2 commitment the verifier already has)
  • from VMV message
  • as computed above

What remains to prove: The prover must demonstrate that . That is, the intermediate vector (committed implicitly via the consistency check) inner-producted with yields the claimed evaluation. This is where Dory-Reduce takes over.

The folding protocol

Each round halves the problem size. Given vectors of length , the round uses two challenges (, then ) and two prover messages:

First message (before any challenge):

  • , (cross-pairings of halves with generator halves)
  • , (cross-pairings of halves with generator halves)

Verifier sends first challenge

Prover updates vectors:

Second message (computed with -modified vectors):

  • , (cross inner products of modified vectors)

Verifier sends second challenge

Prover folds vectors:

Verifier updates accumulators (no pairing checks, just arithmetic):

where is a precomputed SRS value (the pairing of generator prefixes at round ).

Recurse with vectors of length .

After rounds, vectors have length 1.

Final pairing check: After all rounds:

where primes denote folded values, and is a final challenge.

The invariant: Throughout folding, satisfy:

  • (inner product commitment)
  • , (commitments to each vector)

The verifier does no per-round pairing checks, only accumulator updates. Soundness comes from the final check verifying this invariant for the length-1 vectors.

Why binding works

The prover provides row commitments alongside the tier-2 commitment. Why can't the prover cheat by providing fake rows?

  1. Tier 2 constrains Tier 1: The tier-2 commitment is a deterministic function of the row commitments. Changing any changes .

  2. Tier 1 constrains the data: Each is a Pedersen commitment. Under discrete log hardness, the prover cannot find two different row vectors that produce the same .

  3. No trapdoor: The SRS generators are sampled randomly. Without their discrete logs, the prover is computationally bound to the original coefficients.

If the Dory proof verifies, then with overwhelming probability (under SXDH), the prover knew valid openings for all original commitments.

Properties and trade-offs

PropertyDory
Trusted setupNone (Transparent)
Commitment size (one element)
Proof size group elements
Verification time (the key improvement!)
Prover time for commitment, per opening
AssumptionSXDH (on Type III pairings)
Quantum-safeNo (uses pairings)

Dory uses pairings (like KZG) but achieves transparency (like IPA). It gets logarithmic verification (better than IPA's linear) at the cost of more complex pairing machinery. This makes Dory particularly attractive for systems with many polynomial openings that can be batched (like Jolt's zkVM), where the amortized cost per opening becomes very small.

Implementations like Jolt store row commitments as "opening hints." This increases proof size by per polynomial but enables efficient batch opening without recomputing expensive MSMs. For Jolt's ~26 committed polynomials with , this means ~26 KB of hints instead of ~800 bytes, but saves massive computation during batch verification.

Batching multiple polynomials exploits Pedersen's homomorphism. When batching polynomials with random linear combination coefficient , we combine corresponding rows across all polynomials:

Row of has coefficients . By linearity of Pedersen commitments, . The joint row commitments feed directly into Dory-Reduce, avoiding expensive MSM recomputations.

Why Dory achieves logarithmic verification

Why does Dory achieve logarithmic verification while IPA requires linear time? IPA's linear cost comes from computing folded generators. Dory sidesteps this entirely: the verifier works with commitments in , updating accumulators each round without touching generators. The algebraic structure of pairings () lets the verifier "absorb" folding challenges into commitments. The precomputed values handle the generator contributions.

HyperKZG and Zeromorph: KZG for multilinear polynomials

Hyrax and Dory build new commitment schemes from scratch. A different strategy reuses the existing univariate KZG infrastructure (trusted setup, pairing verification, batching) and adds a reduction layer that converts multilinear evaluation claims into univariate ones. HyperKZG (Setty, 2023) and Zeromorph (Kohrita and Towa, 2023) both take this approach, with different trade-offs.

The shared problem

A multilinear polynomial over variables has coefficients (its evaluations on the Boolean hypercube). The prover commits to by interpreting these values as coefficients of a univariate polynomial and computing a standard KZG commitment using a powers-of-tau SRS.

Commitment is straightforward. The difficulty is opening: given a multilinear evaluation point , proving that using only the univariate commitment . The multilinear evaluation is not the same as the univariate evaluation at some single point. A reduction is needed.

HyperKZG

HyperKZG (an adaptation of Gemini by Setty) reduces the multilinear evaluation to univariate claims via a protocol resembling sum-check. The prover sends auxiliary univariate polynomials, one per variable, that represent the intermediate "partial bindings" as each is fixed to . The verifier checks consistency between consecutive polynomials using KZG openings.

The protocol proceeds in rounds. In round , the prover has a polynomial of degree (starting from ). The prover splits into even and odd coefficients, commits to the odd-coefficient polynomial, and the verifier sends challenge . The prover computes , halving the degree. After rounds, is a constant equal to .

Verification requires group operations and 3 pairings (via batching). Proof size is group elements plus field elements. The prover runs in field operations.

Zeromorph

Zeromorph takes a more algebraic route. It uses the identity that for any multilinear and evaluation point :

where each is a quotient polynomial. This is the multilinear analogue of the univariate fact that divides . Zeromorph maps this identity to the univariate setting: the prover commits to univariate encodings of the and proves the divisibility relation holds under the encoding.

The result is a proof with group elements (slightly smaller than HyperKZG) and verification with group operations plus 3 pairings. Zeromorph also achieves the zero-knowledge property more cheaply: adding ZK costs only extra group operations, compared to an extra -size MSM in naive approaches.

Comparison and practical relevance

PropertyHyperKZGZeromorphDoryHyrax
SetupTrusted (SRS)Trusted (SRS)TransparentTransparent
Commitment size
Proof size
Verification + 3 pairings + 3 pairings pairings
Prover
ZK overheadModerateLow ( ops)LowLow

HyperKZG and Zeromorph occupy the same niche: constant-size commitments with logarithmic proofs, leveraging existing KZG infrastructure. Their main advantage over Dory and Hyrax is compatibility with the powers-of-tau ceremonies already deployed for Groth16 and PLONK. Systems that already have a trusted setup (Ethereum's KZG ceremony, for example) can adopt HyperKZG or Zeromorph with no additional setup cost.

In practice, Jolt uses HyperKZG as its default PCS (with Zeromorph as an alternative). Nova's implementation also supports HyperKZG. For systems that require transparency, Dory or hash-based schemes (Basefold, FRI) are preferred.

Recent work (Mercury, Samaritan) pushes further toward proof size while maintaining prover time, representing the current frontier of KZG-based multilinear commitments.