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:
- 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.
- 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:
| Case | Committed | Virtual |
|---|---|---|
| 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.
| Data | Changes? | Technique | Committed | Virtual |
|---|---|---|---|---|
| Instruction tables | No | Read-only | chunks | , table |
| Bytecode | No | Read-only | chunks | , table |
| Registers | Yes | Read-write | , chunks, | , state |
| RAM | Yes | Read-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:
| Table | Data | Height |
|---|---|---|
| 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
-
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.
-
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.
-
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 .
-
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).
-
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.
-
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.
-
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.
-
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.