What problem are we solving?
If you’ve followed hash-based SNARGs over the last few years — FRI, STIR, BaseFold, Brakedown — you’ve watched the field push hard on prover time, proof size, and post-quantum security. The one cost that didn’t move much was verifier time. Verifying a hash-based SNARG typically takes a few milliseconds, with most of that going to field arithmetic against the verifier’s challenges. A few milliseconds sounds fine until you remember the two settings that matter most:
- On-chain verifiers pay gas per field operation. Every millisecond is dollars.
- Recursive provers verify a SNARG inside their next SNARG. The verifier cost becomes part of the next prover’s circuit — and shrinking it shrinks the next proof too.
WHIR (Arnon, Chiesa, Fenzi, Yogev — 2024) is a new IOP of proximity that drops hash-based SNARG verification into the few-hundred-microseconds regime. The paper’s abstract example, at coefficients, rate , and 100 bits of security, reports commit + open in 1.2 seconds, proof size 63 KiB, and verification in 360 µs.
To compare against prior work on a setting where everyone has data — , , 100 bits of security — WHIR-CB verifies in 0.61 ms vs BaseFold’s 24 ms (a gap). At 128 bits of security on a 192-bit prime field, WHIR-CB verifies in 1.0 ms vs STIR’s 3.8 ms and FRI’s 3.9 ms.
Underneath that headline are two ideas worth understanding:
-
Constrained Reed–Solomon codes (CRS). A small generalization of the Reed–Solomon code that bundles a polynomial code with a sumcheck-style constraint. CRS turns out to be the right object to test against — one IOPP for CRS gives you a low-degree test, a polynomial commitment opening, and a sumcheck query checker, all in one protocol.
-
Sumcheck-inside-the-IOPP. Each WHIR iteration runs a few rounds of sumcheck on , then folds , then samples the folded function out-of-domain, then queries it at a few shift points — and packs all of that into the constraint of a smaller CRS instance. The recursion drives (number of variables) down by each iteration while halving the domain.
The verifier-cost shape, in plain words: roughly queries, each touching a small fold neighborhood and updating one constraint. Same query count as STIR, but each query is much cheaper to handle — concrete formulas in the table below.
The article walks through the protocol, the design choices that make it fast, and how the same machinery yields a polynomial commitment for free.
Prerequisites. Comfort with multilinear polynomials, the sumcheck protocol, Reed–Solomon codes, and the rough shape of FRI-style IOPPs. We’ll set up the rest from scratch.
Headline numbers and the comparison table
Before touching the protocol, here is the headline comparison from the paper (Table 1, paraphrased). The dependence on rate is suppressed:
| Queries | Verifier time | Alphabet | |
|---|---|---|---|
| BaseFold | |||
| FRI | |||
| STIR | |||
| WHIR |
Two things to notice:
- WHIR matches STIR on query count. Both have the same logarithmic dependence on , both crush BaseFold’s linear-in- count.
- WHIR’s verifier time is smaller than STIR’s. Compare the two formulas: STIR pays plus an additive overhead from its quotient bookkeeping. WHIR pays — no quotient term. The inside the per-query cost is the sumcheck-verification-and-
eq-evaluation work, which is cheap.
That second row is the entire reason WHIR’s verifier breaks into the microsecond regime.
Background
Three concepts do most of the lifting: smooth Reed–Solomon codes (and their multilinear view), folding, and the IOP-of-proximity abstraction. One quick notation reminder before we dig in:
| Symbol | Meaning |
|---|---|
| Number of variables in the multilinear ; equivalently of the message size | |
| Message size — number of coefficients of | |
| Codeword size — number of evaluation points | |
| Rate — fraction of the codeword that’s “real data” vs parity |
So is a code where messages have coefficients and codewords have field elements. Sumcheck operates on the message side (sums over the boolean hypercube , size ), not on the codeword side. The codeword on is a redundant encoding of the message — you don’t sum over the encoding, you sum over the message it encodes.
Smooth Reed–Solomon codes, two ways
WHIR uses smooth Reed–Solomon codes. Reed–Solomon itself is a prereq — if it’s rusty, Thaler’s PAZK book chapter on low-degree testing is a clean refresher. This section focuses on what smoothness adds and why WHIR needs it.
is smooth when it’s a multiplicative coset of whose order is a power of two. The interesting consequence — and the reason WHIR needs it — is that smoothness lets squaring halve the domain cleanly, repeatedly, all the way down. Three properties of a smooth chain together to make this work:
- . A cyclic group of even order over a field of characteristic contains a unique element of order 2 — that’s .
- closed under negation. Once , multiplying any by stays inside (cosets are closed under multiplication by subgroup elements). So elements pair up under .
- Squaring collapses each pair. , so the squaring map sends both members of every pair to the same image. The image set has exactly half the elements.
A concrete example makes the chain easy to see. Pick . Its multiplicative group has order , so it’s smooth. Take the order-8 subgroup:
Property 1 lands: ‘s order is 8 (even), and indeed is in . Property 2 follows: pairs under negation are — every pair sits inside . And property 3 collapses them under squaring:
| 1 | 2 | 4 | 8 | 9 | 13 | 15 | 16 | |
|---|---|---|---|---|---|---|---|---|
| 1 | 4 | 16 | 13 | 13 | 16 | 4 | 1 |
Distinct images: — half the size, as promised. The same three properties hold for (still a power-of-two-order coset), so squaring halves again: , then . After squarings the set is exhausted.
This iterated halving is what powers WHIR’s recursion: each iteration squares once and shrinks the multilinear by binding several of its variables to verifier randomness.
Why smoothness matters: two readings of the same vector.
A Reed–Solomon codeword is just a vector of field values. Once is smooth, that exact vector admits two completely different polynomial interpretations — and WHIR uses both, switching lenses mid-protocol.
Reading 1 — univariate. The vector is the evaluations of a single univariate polynomial of degree , one value per point of . This is the FRI-world reading: low-degree testing and folding live here.
Reading 2 — multilinear. The same vector is the evaluations of a multilinear polynomial in variables. To evaluate, you don’t plug in one number — you plug in numbers. Specifically, for each , plug . So one univariate input gets stretched into an -tuple by squaring. This is the BaseFold/Spartan reading: sumcheck lives here.
The two readings encode the same coefficient data. The clearest way to see why is to work out the smallest non-trivial case, .
A degree-3 univariate has 4 coefficients:
A multilinear in 2 variables also has 4 coefficients — one per subset of :
Now substitute and — that’s the smoothness curve at :
Match coefficients with : , , , . Same numbers, just relabeled. And there’s a clean rule for the relabeling: the coefficient of in the univariate matches the multilinear coefficient whose variable subset is given by the binary expansion of . Index 3 in binary is , picking , giving the monomial . Each bit of tells you whether to include the corresponding variable.
This generalizes: for any , plugging into the multilinear and expanding reassembles the univariate, term by term. Smoothness ensures the substitution evaluates correctly at every point of :
Why we care. WHIR uses folding from the univariate reading and sumcheck from the multilinear reading. The protocol runs sumcheck on (Reading 2), then folds the result (Reading 1) — and the smoothness curve is the bridge that lets a single object switch lenses mid-protocol. The two operations are gear-meshed by the curve: the verifier randomness generated by sumcheck (binding the multilinear’s variables) is the same used to fold the univariate vector. Claim 4.15 of the paper makes this exact: the univariate fold equals the multilinear restriction .
One naming convention for the road: when you have a univariate point and the multilinear reading needs an -dimensional evaluation point, the lift hands it to you — just run through the smoothness curve. Univariate-at- equals multilinear-at-.
This dual view is the bridge between FRI-world (univariate, low-degree testing) and BaseFold-world (multilinear, sumcheck).
Folding
Folding is FRI’s signature move. Picture it as shrinking the code in half with verifier randomness as the lever.
You start with , a vector of values. After folding with a verifier-supplied randomness , you get , a new vector of values laid out on the squared domain . The crucial property: if was an RS codeword for some degree- polynomial, the folded vector is itself an RS codeword — for some new degree- polynomial. Folding doesn’t just shrink the vector; it shrinks the underlying polynomial too.
The recipe: each entry of the folded vector is a randomized mixture of two paired entries of the original. For each there are exactly two values in that square to — the pair and from the previous section. Take the values of at this pair, mix them with :
Why this particular formula? Split the underlying univariate polynomial into even and odd parts: . Then (the odd part cancels) and (the even part cancels). So sum-of-pair extracts the even coefficients; scaled-difference-of-pair extracts the odd ones. Mixing them with gives a new polynomial in the squared variable — a polynomial of half the original degree, evaluated on the half-sized domain .
There’s a clean polynomial identity that makes this exact. Write split by its first variable: . Folding with is the same as plugging in for :
In one sentence: folding with binds the first variable of to . The code goes from to — the domain is halved and the polynomial loses one variable.
We’ll iterate folding times with a vector of randomness , written . To compute one entry of the -fold result, the verifier needs the values of at the -th roots of a single point — field operations, using a coefficient-encoding trick the paper exploits.
Distance preservation. Folding’s soundness property: if is -far from (i.e., disagrees with every codeword on a -fraction of positions), then stays -far from with high probability over . Verifier randomness keeps the distance from collapsing — without it, a sneaky prover could fold a non-codeword into something that looks like a codeword. This soundness fact is correlated agreement for Reed–Solomon, the [BCIKS20] result that powers FRI/STIR/WHIR’s analyses.
WHIR needs a slightly stronger version — mutual correlated agreement, which insists not just that the components agree with codewords somewhere but on the same set. We unpack why that sameness matters in §Idea 2.
IOPs of proximity
When we describe WHIR in detail, there’s a lot of mechanical plumbing — Merkle trees, hashes, Fiat–Shamir. We abstract that away. An IOP of proximity (IOPP) for a code is a protocol where:
- The prover sends an oracle (think: a long vector).
- The verifier issues some random challenges and reads a small number of entries of (point queries) plus possibly more oracles.
- The verifier accepts if is close to , rejects if it’s far.
The compilation to a real hash-based protocol is mechanical (BCS transform): commit each oracle via a Merkle tree, send Merkle paths instead of point answers, hash everything for Fiat–Shamir. The number of point queries equals the number of Merkle paths in the final SNARG, so query count is a real cost.
Idea 1 — Constrained Reed–Solomon codes
The move. Take a Reed–Solomon code and also require that some weighted sum of the codeword’s values over the boolean cube equals a target . The sum-shaped extra slot is rich enough to encode “this codeword evaluates to at point ” or “this codeword satisfies a sumcheck-style query” as a single membership question — so checking proximity to a CRS code is the PCS opening, is the sumcheck residue check, is the Σ-IOP query.
Formally, a constrained Reed–Solomon code is a Reed–Solomon code plus an extra sumcheck-style constraint on the underlying multilinear polynomial:
The weight polynomial is a polynomial in variables. The target is the value the sum has to hit. A codeword has to (a) be a Reed–Solomon codeword in the usual sense, and (b) satisfy the sum constraint when you expand it as a multilinear .
That’s the whole definition. The trick is what you can encode in :
-
Point evaluation. Set for a target point . Here is the multilinear extension of boolean equality — picture it as a selector that’s when on the cube and on every other cube point. Concretely, . Then:
So . The CRS code carves out, from all RS codewords, those whose multilinear evaluates to at . This is exactly a polynomial commitment opening. The IOPP for this CRS code, compiled, is a multilinear PCS.
-
Univariate evaluation. A univariate polynomial of degree evaluated at corresponds, via the smoothness identity, to evaluating its multilinear at . So univariate point evaluations are CRS too.
-
Arbitrary sumcheck-like queries. Any polynomial that’s cheap to evaluate gives a valid query. This is what makes WHIR usable as the proximity-test backbone of a Σ-IOP compiler — a poly-IOP variant where the verifier asks weighted-sum queries instead of single-point evaluations, covered later in this article.
The punchline: CRS unifies “test that is a Reed–Solomon codeword” with “test that takes a specific value at a specific place.” Both are CRS proximity tests. Build one good IOPP for CRS, and you get the proximity test, the PCS, and the Σ-IOP query mechanism in one shot.
What the verifier actually sees
Before the protocol walk, anchor on what’s actually on the wire. The verifier never inspects in full — it only sees a few Merkle roots, sumcheck messages, and a handful of opened entries.
Commit phase. The prover takes the multilinear , encodes it as via the smoothness curve, Merkle-commits the entries of , and sends one root hash to the verifier. That hash is the entire commitment.
Open phase, per WHIR iteration. For an opening claim like , each iteration puts the following on the wire:
- sumcheck messages — small univariate polynomials.
- A new Merkle root for the folded oracle .
- One OOD answer .
- shift-query answers — for each , the values of at the -th roots of , plus a Merkle path proving they came from the committed root.
After iterations, the prover sends the final small multilinear in the clear (constant size). That’s the only point where the verifier sees a real polynomial.
How binding works. The verifier never directly checks “is the prover using the same in sumcheck as in the codeword?” Instead, it runs a chain of independent gates. To cheat, the prover has to break every gate simultaneously:
| Cheat attempt | Caught by |
|---|---|
| isn’t close to any RS codeword | Shift queries (proximity test) |
| Sumcheck messages don’t match the codeword’s multilinear | Sumcheck round-by-round equation |
| The folded isn’t really the fold of | Shift queries (cross-check vs ) |
| Multiple codewords match in the list-decoding regime | OOD pinning |
If every gate passes, the soundness analysis says: with overwhelming probability there exists a single multilinear such that the committed encodes it AND the original opening claim holds. The verifier doesn’t need to see — it just needs the prover to be unable to lie consistently across all the gates at once.
The WHIR iteration
Now the protocol. We describe one iteration of WHIR. The iteration reduces testing
to testing
for some new weight polynomial and target that the protocol constructs along the way. The full protocol applies iterations until has only a few variables left and the prover sends in the clear.
For this overview, assume is multilinear (the paper handles a more general setting).
Step 1 — rounds of sumcheck
The move: burn down of the variables in the CRS constraint via sumcheck. After rounds, the first variables of are pinned to verifier randomness , and the leftover sum is over variables. The new target — the one the recursive instance has to hit — is whatever the sumcheck protocol leaves on the table.
Concretely, prover and verifier run sumcheck on the constraint built into . After rounds, the prover has sent univariate polynomials (one per round) and the verifier has sampled . The remaining claim is the partially-bound sum:
The verifier knows — they checked the round-by-round sumcheck equations. What they don’t yet know is whether the prover’s sumcheck polynomials are consistent with the actual . That’s the verifier’s eventual job, and the rest of the iteration is structured to feed exactly that check into the recursive instance.
Step 2 — Send the folded function
The purpose: hand the verifier a smaller object to recurse on. After Step 1’s sumcheck rounds, the multilinear has free variables left; the prover commits to its evaluations on the half-sized domain . That’s the new RS codeword the recursive instance will check.
How: the prover sends as a fresh Merkle-committed oracle. Honestly, is the evaluation on of the multilinear — the same multilinear as the iterated fold , but encoded on the bigger domain instead of (where the iterated fold would naturally land). For these coincide; for , is a more redundant encoding of the same polynomial.
Note the asymmetry. Step 1 bound variables of on the multilinear side, but Step 2 only halves the domain once on the codeword side. The new codeword has length , not — bigger than the minimum needed. The polynomial it encodes ( with variables, coefficients) fits comfortably in this larger domain, with room to spare for parity. This is deliberate: the new code becomes lower-rate (more redundant), and lower rate buys query savings on the recursive instance. We make this precise in §“Why the rate decreases”.
The verifier doesn’t trust yet. The next steps are what tie it back to .
Step 3 — Out-of-domain round
The purpose: pin down a single codeword as “the” , even when list decoding leaves many candidates. Beyond the unique-decoding radius, several distinct codewords can sit within distance of the prover’s claimed — that’s “the list.” Without OOD, the soundness analysis would have to track every codeword in the list, which is a mess. OOD forces the prover to commit to a single multilinear by binding its evaluation at a fresh random point outside .
How: the verifier samples (out-of-domain — that’s the “OOD”) and sends it. Set — the smoothness lift of . The prover replies with , which honestly is .
Why one codeword wins. At most one codeword in the list can satisfy , because two distinct multilinear polynomials disagree at a random point with probability close to 1 (Schwartz–Zippel). So OOD picks out a single codeword to call “the” — same trick DEEP-FRI [BGKS20] introduced for FRI; WHIR reuses it inside every iteration.
Deferred verification. The verifier doesn’t actually check at this step. The value flows into in Step 5; the recursive CRS check is what enforces later, together with everything else. Step 3 gathers the OOD claim; it doesn’t verify it.
Step 4 — Shift queries
The purpose: force the prover’s claimed to actually agree with the fold of the committed on random spot checks. Without these, a cheating prover could send any that satisfies the OOD claim and walk away. The shift queries are the only place in the entire iteration where the verifier directly reads from — every other check operates on the fresh oracle or on small messages.
The mechanics. Verifier samples , lifts each to , and samples a combination randomness . For each , the verifier needs to compute — the value of the iterated -fold of at the point . The verifier only has a Merkle commitment to , not the full vector. How does it compute the fold from queries alone?
The -th roots and the local fold. The “-th roots of ” are the -many such that . Two facts about smoothness make this exact count work:
- Squaring is 2-to-1 on a smooth domain (each has two preimages in ). Iterating squarings is -to-1 — every point of has exactly preimages back in the original domain .
- Smoothness guarantees these preimages all live inside the committed domain (no need to extend). They’re the leaves the verifier can ask for via Merkle paths.
The verifier reads only those values of — typically with one Merkle path, since the prover lays out ‘s tree so that all preimages of any point sit in adjacent leaves (a single path opens the whole leaf cluster). Plug them into the iterated fold formula (the recipe from §Folding, applied times via ) and you get one value — computed locally, in field operations. The verifier never folds the entire ; it folds only at the sampled points.
Total cost: Merkle paths into , one per , each opening adjacent positions.
Deferred verification. The values aren’t compared to directly at this step. They flow into in Step 5; the recursive CRS check is what enforces ” for every .” If wasn’t actually close to the fold of , at least one likely lands where the recursive check fails — but that failure surfaces during recursion, not here.
Step 5 — Recursive claim
The trick. The verifier has accumulated three open obligations: the sumcheck residue from Step 1, the OOD claim from Step 3, and the shift-query claims from Step 4. Rather than discharge them externally — quotient polynomials, side checks, extended-domain bookkeeping — WHIR welds all three into the constraint of a smaller CRS instance and recurses on that single object. The CRS slot is rich enough to absorb every accumulated check, so the protocol moves on with one obligation, not three.
Concretely, define a new weight polynomial and new target :
The new constraint packs three things into one CRS instance:
- The sumcheck residue from Step 1 ( with target ).
- The OOD constraint from Step 3 (the term: , contributing to ).
- The shift-query constraints from Step 4 (the terms).
All three are evaluation-style sumcheck queries against , combined into a single random linear combination via . The protocol then recurses: prover and verifier check .
This is where the checks actually happen. Steps 3 and 4 only gathered obligations — they didn’t verify anything. Step 5 bundles them into and that the next iteration’s CRS check has to satisfy. Recurse all the way down to the base case (where is sent in clear), and the verifier directly evaluates the final at , summed over the boolean cube, against . If anything was inconsistent at any layer — sumcheck residue, OOD value, shift-query agreement — the base-case check fails. Verification is essentially “lazy”: collect all obligations across all iterations, fuse them into one constraint, evaluate at the bottom.
Why the rate decreases
The new code’s rate is . For typical , that’s — the rate decreases by a factor of 8 each iteration. This is the STIR-style trick: lower rate means more redundancy, which means fewer queries are needed in subsequent iterations to maintain soundness.
Why we want lower rate. The point of redundancy is to make tampering detectable. The relevant quantity is distance — the minimum fraction of positions on which two distinct codewords differ. For Reed–Solomon, distance is roughly . If is -far from every codeword, a single random query catches the discrepancy with probability , and random queries catch with probability . To hit bits of soundness, you need
queries. Lower rate → larger distance → fewer queries:
| Rate | Queries for | |
|---|---|---|
| 1/2 | 1 | |
| 1/4 | 2 | |
| 1/8 | 3 | |
| 1/16 | 4 |
(This is a simplified flavor of the analysis — the actual WHIR bound uses Johnson and list-decoding refinements that sharpen the constants. The qualitative trend is the same.)
So the per-iteration query budget shrinks geometrically as we recurse: each iteration’s code is more redundant than the last, demanding fewer spot checks for the same soundness. That’s STIR’s amortization trick — pay larger oracles up front to buy query savings on every subsequent iteration. Total queries collapse to across all iterations rather than .
The cost. The prover sends a of size (half the original domain) at each iteration, so total proof length is . The geometric series gives a free pass on proof size.
The recursion’s base case
After iterations, the code has a constant number of variables. The prover sends in the clear (constant size), and the verifier checks the final CRS constraint by directly evaluating — a constant-time check.
Why the verifier is so fast
Now we can answer “where does come from, and why does STIR carry an extra factor?”
Per query: for fold + for the constraint update
For each shift query in iteration :
-
Fold computation. The verifier reads adjacent entries of from one Merkle path, then computes in field operations using the coefficient-encoding optimization (Section 2.1.4 of the paper). With constant across iterations, that’s per query.
-
Constraint update. Updating to include the new shift point requires evaluating at the recursive instance’s variables — field operations.
Summed over all queries, that’s . No quotients, no division, no extra structure on top.
Why STIR pays the overhead
STIR’s verifier time is — a per-query plus an additive term independent of the query count. The additive term comes from STIR’s use of quotient polynomials to enforce the OOD consistency check: the prover sends a quotient of the form that the verifier checks against an extended evaluation domain. The bookkeeping scales with the OOD claims accumulated across rounds — that’s where comes from.
WHIR avoids the quotient entirely by encoding the OOD claim directly inside the recursive constraint ( inside ). The OOD obligation becomes part of the next round’s sumcheck — the sumcheck residue absorbs it. No extra quotient polynomial, no extended-domain bookkeeping, no term.
This is the structural payoff of the CRS abstraction: the constraint slot is rich enough to hold all the protocol’s accumulating obligations, so they ride along inside the recursion instead of being enforced from outside.
Total verifier time
With and typical settings, — query-optimal. The total verifier time is:
That’s linear in the number of field elements the verifier reads. It can’t get asymptotically smaller without reading less, and at queries we’re already at the information-theoretic floor.
Idea 2 — Mutual correlated agreement
An aside on decoding. Before diving in: WHIR’s protocol never decodes anything. The verifier reads queried entries, runs round-by-round equation checks, and accepts/rejects — pure spot-checking. The “list of codewords close to ” you keep hearing about (in OOD discussions, in correlated-agreement statements) is a mathematical object the soundness proof reasons about, not a runtime subroutine. Nobody runs a list decoder. The proof’s job is to argue that no codeword in the abstract list satisfies WHIR’s checks unless the prover was honest. List decoding is a soundness-analysis tool; OOD is the protocol mechanism that lets the proof shrink the list to one element analytically.
Why this matters: when we say “Reed–Solomon codes have -correlated agreement at the Johnson bound,” that’s a property of the code the proof assumes — not something the protocol invokes. The three WHIR variants (UD/JB/CB) share the same protocol algorithm; they differ in which list-size bound the soundness analysis assumes, which in turn drives different proximity parameters and shift-query counts . Same algorithm, different parameter choices, hence different argument sizes (visible in the headline numbers — WHIR-UD vs WHIR-CB at the same produce different KiB).
Why we need an upgrade. Folding decomposes into two pieces: , where and . For soundness, every codeword close to the folded function must come from a codeword close to the original — list-preserving folding. That holds only if and are close to codewords on the same set, not on possibly-disjoint sets. Standard correlated agreement promises closeness on some set; mutual correlated agreement promises that set is the same. That sameness is the ingredient WHIR’s soundness analysis needs.
Standard vs. mutual
Picture vectors that each might be close to a codeword. Take a random linear combination . Standard correlated agreement says: if is close to some codeword on a large set , then each individual is close to some codeword too — but possibly on different sets , with no relationship to . For most uses, you don’t care. Mutual correlated agreement strengthens this by demanding the same set: every is close to a codeword on itself.
WHIR’s analysis cashes this in via the list-preservation property: with high probability over , every codeword close to is the folding of some codeword close to . Mutual agreement is what lets us paste and back into a single — they have to agree with codewords on the same set for the pasting to recover a single near-codeword for .
Conjecture 1
The paper proves mutual correlated agreement for Reed–Solomon codes within the unique-decoding radius. Beyond it, into the Johnson bound and the capacity bound regimes (where there can be many candidate codewords within distance ), they conjecture:
Conjecture 1 (informal). Mutual correlated agreement holds at essentially the same parameters where standard correlated agreement does — same regime, same trust assumptions.
This is in the same family as the conjectures used by FRI and STIR analyses [BGKS20; ACFY24], so it’s not a new leap of faith. The WHIR experiments report numbers in three configurations: WHIR-UD (provable, unique decoding), WHIR-JB (assuming correlated-agreement holds at Johnson), and WHIR-CB (assuming it holds at capacity). The capacity-bound numbers are the prettiest and what most of the headline figures use.
From WHIR to a multilinear PCS
The punchline. WHIR-as-PCS is a one-liner. Pick the CRS whose constraint is a point opening — that’s the point-evaluation row from Idea 1, — and run WHIR on it. Commit phase: encode the multilinear, Merkle-commit. Open phase: run WHIR on the eq-shaped CRS. The whole opening protocol falls out of the proximity test.
The PCS construction
To commit to a multilinear with variables: the prover encodes as a Reed–Solomon codeword (evaluating along the smoothness curve) and Merkle-commits .
To open at with claimed value : prover and verifier run WHIR on the constrained code . Acceptance means encodes some satisfying , which is exactly the PCS opening.
That’s it. The Merkle root is the commitment; the WHIR transcript is the opening proof.
The same construction works for univariate polynomials of degree : lift to and run the same WHIR opening.
The Σ-IOP compiler
The trick. Any poly-IOP whose verifier asks sumcheck-style queries can be turned into a hash-based SNARG by treating each query as an additional CRS constraint. Collect every query the verifier asks, stack them into a multi-constrained CRS instance, and run one WHIR proximity test for the whole batch. Picture the multi-constrained version as a list of pairs that the same codeword has to satisfy simultaneously — linear-combine them with verifier randomness and you’re back to a single-constraint CRS, so the IOPP doesn’t change.
Concretely, the compiler turns a Σ-IOP (the poly-IOP variant where the verifier asks weighted-sum queries instead of point evaluations) into a real IOP by:
- Having the prover commit to each via its Reed–Solomon encoding .
- Sampling out-of-domain points to pin down a single codeword from each list-decoding list (same trick as inside WHIR itself).
- Collecting all the verifier’s sumcheck queries and their claimed answers into a multi-constrained Reed–Solomon code .
- Running a single multi-constraint WHIR proximity test that handles all queries at once.
The paper applies this compiler to a Σ-IOP for generalized R1CS (Section A), yielding a hash-based SNARK with WHIR’s verifier-time advantages. The point is that any future Σ-IOP — for AIR, CCS, lookup arguments — automatically gets a WHIR-backed SNARK.
Concrete results
The paper’s experiments are on an AWS r6a.24xlarge (96 vCPU, 768 GiB RAM). A few snapshots:
WHIR vs. BaseFold (constrained Reed–Solomon proximity test, Goldilocks, 100-bit security, , ):
| Argument size | Verifier time | Prover time | |
|---|---|---|---|
| BaseFold | 7.95 MiB | 24 ms | 8.0 s |
| WHIR-UD | 390 KiB | 2.39 ms | 3.5 s |
| WHIR-CB | 101 KiB | 0.61 ms | 3.5 s |
WHIR-CB is smaller and faster on the verifier than BaseFold.
WHIR vs. STIR vs. FRI (Reed–Solomon proximity test, 192-bit prime, 128-bit security, , ):
| Argument size | Verifier time | Verifier hashes | Prover time | |
|---|---|---|---|---|
| FRI | 306 KiB | 3.9 ms | 5.6k | 28 s |
| STIR | 160 KiB | 3.8 ms | 2.7k | 36 s |
| WHIR-CB | 157 KiB | 1.0 ms | 2.7k | 34 s |
Argument size matches STIR (both rely on rate decrease). Verifier time is faster than STIR — that’s the saving from cutting quotients.
WHIR-as-PCS vs. KZG (100-bit security, ). KZG only supports univariate polynomials; WHIR supports both. WHIR-CB verifier runs in 0.61 ms at and 0.29 ms at ; KZG verifier runs in 2.42 ms. The paper reports a speedup of WHIR-CB over KZG (Section 6.3.3) — and KZG needs a trusted setup.
What we didn’t cover
A few things skipped for the sake of velocity:
- Round-by-round soundness analysis. Section 5.1 of the paper gives an explicit state function for the round-by-round error breakdown. It’s the technical core of the soundness proof and the reason WHIR is BCS-compatible (Fiat–Shamir-safe).
- The d-Σ-IOP to linear-Σ-IOP reduction. Section 7.3. A higher-degree-in- Σ-IOP can be reduced to a degree-1 (linear) one via an extra sumcheck, so the compiler’s restriction to linear Σ-IOPs is without loss of generality.
- The proximity-gap proof for mutual correlated agreement. Section 4.2 carries this out for the unique-decoding regime; the beyond-UD case is the conjecture.
- Proof-of-work soundness boost. A standard hash-based SNARG trick the implementation uses to push a few more bits of security per round.
Conclusion
WHIR’s contribution is structural more than computational. The standard low-degree-test-plus-PCS-plus-sumcheck stack is a tower of separate primitives, each enforcing constraints from outside the others (quotients, openings, opening-of-opening). WHIR collapses the tower into a single object — constrained Reed–Solomon codes — where the constraints live inside the protocol’s recursive state instead of being enforced from outside.
The benefits are concrete:
- Verifier time drops by an order of magnitude vs. STIR/FRI, two orders vs. BaseFold. Few-hundred-µs verification puts hash-based SNARKs in striking distance of group-based schemes for on-chain use.
- Argument size matches STIR (state of the art for rate-decrease schemes).
- Plug-and-play PCS for both multilinear and univariate polynomials, with no protocol changes — just pick the right .
- Σ-IOP compatibility means future poly-IOPs (R1CS, lookup arguments, custom constraint systems) automatically inherit WHIR’s verifier-time wins.
The mutual-correlated-agreement upgrade also feels generally useful: the same list-preservation issue shows up wherever folding-and-checking is the pattern, which is most modern hash-based protocols.
If you want to go deeper:
- The paper is well-written and the technical overview (Section 2) is enough to implement against.
- The reference implementation in Rust is being upstreamed into arkworks.
- For background, Thaler’s PAZK book Chapters 4 and 9 cover sumcheck and FRI-style folding.
References
- WHIR: Arnon, G., Chiesa, A., Fenzi, G., Yogev, E. (2024). “WHIR: Reed–Solomon Proximity Testing with Super-Fast Verification.” https://eprint.iacr.org/2024/1586.pdf
- FRI: Ben-Sasson, E., Bentov, I., Horesh, Y., Riabzev, M. (2018). “Fast Reed–Solomon Interactive Oracle Proofs of Proximity.”
- STIR: Arnon, G., Chiesa, A., Fenzi, G., Yogev, E. (2024). “STIR: Reed–Solomon Proximity Testing with Fewer Queries.”
- BaseFold: Zeilberger, H., Chen, B., Fisch, B. (2024). “BaseFold: Efficient Field-Agnostic Polynomial Commitment Schemes from Foldable Codes.” https://eprint.iacr.org/2023/1705.pdf
- DEEP-FRI (out-of-domain sampling): Ben-Sasson, E., Goldberg, L., Kopparty, S., Saraf, S. (2019). “DEEP-FRI: Sampling Outside the Box Improves Soundness.” https://eprint.iacr.org/2019/336.pdf
- Correlated agreement [BCIKS20]: Ben-Sasson, E., Carmon, D., Ishai, Y., Kopparty, S., Saraf, S. (2020). “Proximity Gaps for Reed–Solomon Codes.”
- BCS transformation: Ben-Sasson, E., Chiesa, A., Spooner, N. (2016). “Interactive Oracle Proofs.”
- Implementation: WHIR Rust reference implementation. https://github.com/WizardOfMenlo/whir