WHIR: Super-Fast Verification for Hash-Based SNARKs

April 29, 2026

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 2222^{22} coefficients, rate 1/41/4, 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 — m=24m = 24, ρ=1/2\rho = 1/2, 100 bits of security — WHIR-CB verifies in 0.61 ms vs BaseFold’s 24 ms (a 39×39\times 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:

  1. 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.

  2. Sumcheck-inside-the-IOPP. Each WHIR iteration runs a few rounds of sumcheck on ff, then folds ff, 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 mm (number of variables) down by kk each iteration while halving the domain.

The verifier-cost shape, in plain words: roughly λ\lambda 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 ρ\rho is suppressed:

Queries qqVerifier timeAlphabet
BaseFoldO(λm)O(\lambda \cdot m)O(q)O(q)F2\mathbb F^2
FRIO(λ+λkm)O(\lambda + \frac{\lambda}{k} \cdot m)O(q2k)O(q \cdot 2^k)F2k\mathbb F^{2^k}
STIRO(λ+λklogmk)O(\lambda + \frac{\lambda}{k} \cdot \log\frac{m}{k})O(q2k+λ2k2k)O(q \cdot 2^k + \frac{\lambda^2}{k} \cdot 2^k)F2k\mathbb F^{2^k}
WHIRO(λ+λklogmk)O(\lambda + \frac{\lambda}{k} \cdot \log\frac{m}{k})O(q(2k+m))O(q \cdot (2^k + m))F2k\mathbb F^{2^k}

Two things to notice:

  • WHIR matches STIR on query count. Both have the same logarithmic dependence on mm, both crush BaseFold’s linear-in-mm count.
  • WHIR’s verifier time is smaller than STIR’s. Compare the two formulas: STIR pays q2kq \cdot 2^k plus an additive λ2k2k\frac{\lambda^2}{k} \cdot 2^k overhead from its quotient bookkeeping. WHIR pays q(2k+m)q \cdot (2^k + m) — no quotient term. The +m+m 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:

SymbolMeaning
mmNumber of variables in the multilinear f^\hat f; equivalently log2\log_2 of the message size
2m2^mMessage size — number of coefficients of f^\hat f
L\lvert L \rvertCodeword size — number of evaluation points
ρ=2m/L\rho = 2^m / \lvert L \rvertRate — fraction of the codeword that’s “real data” vs parity

So RS[F,L,m]\mathsf{RS}[\mathbb F, L, m] is a code where messages have 2m2^m coefficients and codewords have L\lvert L \rvert field elements. Sumcheck operates on the message side (sums over the boolean hypercube {0,1}m\{0,1\}^m, size 2m2^m), not on the codeword side. The codeword on LL 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.

LL is smooth when it’s a multiplicative coset of F\mathbb F^\ast 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 LL chain together to make this work:

  1. 1L-1 \in L. A cyclic group of even order over a field of characteristic 2\neq 2 contains a unique element of order 2 — that’s 1-1.
  2. LL closed under negation. Once 1L-1 \in L, multiplying any xLx \in L by 1-1 stays inside (cosets are closed under multiplication by subgroup elements). So elements pair up under xxx \leftrightarrow -x.
  3. Squaring collapses each pair. x2=(x)2x^2 = (-x)^2, so the squaring map sends both members of every pair to the same image. The image set L(2):={x2:xL}L^{(2)} := \{x^2 : x \in L\} has exactly half the elements.

A concrete example makes the chain easy to see. Pick F17\mathbb F_{17}. Its multiplicative group has order 16=2416 = 2^4, so it’s smooth. Take the order-8 subgroup:

L={1, 2, 4, 8, 9, 13, 15, 16}L = \{1,\ 2,\ 4,\ 8,\ 9,\ 13,\ 15,\ 16\}

Property 1 lands: LL‘s order is 8 (even), and indeed 161(mod17)16 \equiv -1 \pmod{17} is in LL. Property 2 follows: pairs under negation (x,x=17x)(x, -x = 17-x) are (1,16),(2,15),(4,13),(8,9)(1, 16), (2, 15), (4, 13), (8, 9) — every pair sits inside LL. And property 3 collapses them under squaring:

xx12489131516
x2x^2141613131641

Distinct images: L(2)={1,4,13,16}L^{(2)} = \{1, 4, 13, 16\} — half the size, as promised. The same three properties hold for L(2)L^{(2)} (still a power-of-two-order coset), so squaring halves again: L(4)={1,16}L^{(4)} = \{1, 16\}, then L(8)={1}L^{(8)} = \{1\}. After log2L\log_2 |L| squarings the set is exhausted.

Smoothness chain in F₁₇: L = {1, 2, 4, 8, 9, 13, 15, 16} pairs under negation as (1,16), (2,15), (4,13), (8,9). Squaring sends each pair to one image, collapsing L (8 elements) to L^(2) = {1, 4, 13, 16} (4 elements). Iterating squares again: L^(4) = {1, 16}, then L^(8) = {1}. After log₂|L| squarings the set is exhausted — that's WHIR's recursion floor.

This iterated halving is what powers WHIR’s recursion: each iteration squares LL once and shrinks the multilinear f^\hat f 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 L|L| field values. Once LL 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 g^\hat g of degree <2m< 2^m, one value per point of LL. 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 f^\hat f in mm variables. To evaluate, you don’t plug in one number — you plug in mm numbers. Specifically, for each xLx \in L, plug f^(x,x2,x4,,x2m1)\hat f(x, x^2, x^4, \ldots, x^{2^{m-1}}). So one univariate input xx gets stretched into an mm-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, m=2m = 2.

A degree-3 univariate has 4 coefficients:

g^(X)=a0+a1X+a2X2+a3X3\hat g(X) = a_0 + a_1 X + a_2 X^2 + a_3 X^3

A multilinear in 2 variables also has 4 coefficients — one per subset of {X1,X2}\{X_1, X_2\}:

f^(X1,X2)=b0+b1X1+b2X2+b3X1X2\hat f(X_1, X_2) = b_0 + b_1 X_1 + b_2 X_2 + b_3 X_1 X_2

Now substitute X1=xX_1 = x and X2=x2X_2 = x^2 — that’s the smoothness curve at m=2m=2:

f^(x,x2)=b0+b1x+b2x2+b3xx2=b0+b1x+b2x2+b3x3\hat f(x, x^2) = b_0 + b_1 \cdot x + b_2 \cdot x^2 + b_3 \cdot x \cdot x^2 = b_0 + b_1 x + b_2 x^2 + b_3 x^3

Match coefficients with g^\hat g: a0=b0a_0 = b_0, a1=b1a_1 = b_1, a2=b2a_2 = b_2, a3=b3a_3 = b_3. Same numbers, just relabeled. And there’s a clean rule for the relabeling: the coefficient of XkX^k in the univariate matches the multilinear coefficient whose variable subset is given by the binary expansion of kk. Index 3 in binary is 1111, picking {X1,X2}\{X_1, X_2\}, giving the monomial X1X2=xx2=x3X_1 X_2 = x \cdot x^2 = x^3. Each bit of kk tells you whether to include the corresponding variable.

This generalizes: for any mm, plugging Xi:=x2i1X_i := x^{2^{i-1}} into the multilinear and expanding reassembles the univariate, term by term. Smoothness ensures the substitution evaluates correctly at every point of LL:

RS[F,L,m]={f:LF  :  f^F<2[X1,,Xm], f(x)=f^(x,x2,x4,,x2m1)}\mathsf{RS}[\mathbb F, L, m] = \big\{ f : L \to \mathbb F \;:\; \exists\, \hat f \in \mathbb F^{<2}[X_1, \ldots, X_m],\ f(x) = \hat f\big(x, x^2, x^4, \ldots, x^{2^{m-1}}\big) \big\}

Why we care. WHIR uses folding from the univariate reading and sumcheck from the multilinear reading. The protocol runs sumcheck on f^\hat f (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 α\boldsymbol\alpha generated by sumcheck (binding the multilinear’s variables) is the same α\boldsymbol\alpha used to fold the univariate vector. Claim 4.15 of the paper makes this exact: the univariate fold equals the multilinear restriction f^(α,Xk+1,,Xm)\hat f(\boldsymbol\alpha, X_{k+1}, \ldots, X_m).

One naming convention for the road: when you have a univariate point zz and the multilinear reading needs an mm-dimensional evaluation point, the lift z:=(z,z2,z4,,z2m1)\mathbf z := (z, z^2, z^4, \ldots, z^{2^{m-1}}) hands it to you — just zz run through the smoothness curve. Univariate-at-zz equals multilinear-at-z\mathbf z.

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 ff, a vector of L|L| values. After folding with a verifier-supplied randomness α\alpha, you get Fold(f,α)\mathsf{Fold}(f, \alpha), a new vector of L/2|L|/2 values laid out on the squared domain L(2):={x2:xL}L^{(2)} := \{x^2 : x \in L\}. The crucial property: if ff was an RS codeword for some degree-<2m<2^m polynomial, the folded vector is itself an RS codeword — for some new degree-<2m1<2^{m-1} 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 yL(2)y \in L^{(2)} there are exactly two values in LL that square to yy — the pair xx and x-x from the previous section. Take the values of ff at this pair, mix them with α\alpha:

Fold(f,α)(y):=f(x)+f(x)2+αf(x)f(x)2x\mathsf{Fold}(f, \alpha)(y) := \frac{f(x) + f(-x)}{2} + \alpha \cdot \frac{f(x) - f(-x)}{2x} Folding in F₁₇: f on L = {1, 2, 4, 8, 9, 13, 15, 16} groups into four pairs (1,16), (2,15), (4,13), (8,9). Each pair sends both values through the fold formula to produce one output: g(1), g(4), g(16), g(13) on L^(2). Worked example for the first pair: g(1) = ½(f(1) + f(16)) + α · (f(1) − f(16))/2, where the first term is the even part of ĝ at y=1 and the second is the odd part blended with α. 8 values on L collapse to 4 values on L^(2).

Why this particular formula? Split the underlying univariate polynomial g^\hat g into even and odd parts: g^(X)=g^even(X2)+Xg^odd(X2)\hat g(X) = \hat g_{\text{even}}(X^2) + X \cdot \hat g_{\text{odd}}(X^2). Then f(x)+f(x)=2g^even(x2)f(x) + f(-x) = 2 \hat g_{\text{even}}(x^2) (the odd part cancels) and f(x)f(x)=2xg^odd(x2)f(x) - f(-x) = 2x \cdot \hat g_{\text{odd}}(x^2) (the even part cancels). So sum-of-pair extracts the even coefficients; scaled-difference-of-pair extracts the odd ones. Mixing them with α\alpha gives a new polynomial g^even(Y)+αg^odd(Y)\hat g_{\text{even}}(Y) + \alpha \cdot \hat g_{\text{odd}}(Y) in the squared variable Y=x2Y = x^2 — a polynomial of half the original degree, evaluated on the half-sized domain L(2)L^{(2)}.

There’s a clean polynomial identity that makes this exact. Write f^\hat f split by its first variable: f^(X1,X2,,Xm)=f^0(X2,)+X1f^1(X2,)\hat f(X_1, X_2, \ldots, X_m) = \hat f_0(X_2, \ldots) + X_1 \cdot \hat f_1(X_2, \ldots). Folding with α\alpha is the same as plugging α\alpha in for X1X_1:

Fold(f,α)^(X2,,Xm)=f^(α,X2,,Xm)\widehat{\mathsf{Fold}(f, \alpha)}(X_2, \ldots, X_m) = \hat f(\alpha, X_2, \ldots, X_m)

In one sentence: folding with α\alpha binds the first variable of f^\hat f to α\alpha. The code goes from RS[F,L,m]\mathsf{RS}[\mathbb F, L, m] to RS[F,L(2),m1]\mathsf{RS}[\mathbb F, L^{(2)}, m-1] — the domain is halved and the polynomial loses one variable.

We’ll iterate folding kk times with a vector of randomness α=(α1,,αk)Fk\boldsymbol\alpha = (\alpha_1, \ldots, \alpha_k) \in \mathbb F^k, written Fold(f,α):L(2k)F\mathsf{Fold}(f, \boldsymbol\alpha) : L^{(2^k)} \to \mathbb F. To compute one entry of the kk-fold result, the verifier needs the 2k2^k values of ff at the 2k2^k-th roots of a single point — O(2k)O(2^k) field operations, using a coefficient-encoding trick the paper exploits.

Distance preservation. Folding’s soundness property: if ff is δ\delta-far from RS[F,L,m]\mathsf{RS}[\mathbb F, L, m] (i.e., disagrees with every codeword on a δ\delta-fraction of positions), then Fold(f,α)\mathsf{Fold}(f, \alpha) stays δ\delta-far from RS[F,L(2),m1]\mathsf{RS}[\mathbb F, L^{(2)}, m-1] with high probability over α\alpha. 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 CC is a protocol where:

  • The prover sends an oracle f:LFf : L \to \mathbb F (think: a long vector).
  • The verifier issues some random challenges and reads a small number of entries of ff (point queries) plus possibly more oracles.
  • The verifier accepts if ff is close to CC, 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 σ\sigma. The sum-shaped extra slot is rich enough to encode “this codeword evaluates to yy at point z\mathbf z” 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:

CRS[F,L,m,w^,σ]:={fRS[F,L,m]  :  b{0,1}mw^(f^(b),b)=σ}\mathsf{CRS}[\mathbb F, L, m, \hat w, \sigma] := \Big\{ f \in \mathsf{RS}[\mathbb F, L, m] \;:\; \sum_{b \in \{0,1\}^m} \hat w\big(\hat f(b), b\big) = \sigma \Big\}

The weight polynomial w^(Z,X1,,Xm)\hat w(Z, X_1, \ldots, X_m) is a polynomial in m+1m+1 variables. The target σF\sigma \in \mathbb F is the value the sum has to hit. A codeword ff 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 f^\hat f.

That’s the whole definition. The trick is what you can encode in w^\hat w:

  • Point evaluation. Set w^(Z,X):=Zeq(X,z)\hat w(Z, X) := Z \cdot \mathsf{eq}(X, \mathbf z) for a target point zFm\mathbf z \in \mathbb F^m. Here eq\mathsf{eq} is the multilinear extension of boolean equality — picture it as a selector that’s 11 when b=zb = \mathbf z on the cube and 00 on every other cube point. Concretely, eq(b,z)=i(bizi+(1bi)(1zi))\mathsf{eq}(b, \mathbf z) = \prod_i (b_i z_i + (1-b_i)(1-z_i)). Then:

    b{0,1}mw^(f^(b),b)=bf^(b)eq(b,z)=f^(z)\sum_{b \in \{0,1\}^m} \hat w\big(\hat f(b), b\big) = \sum_b \hat f(b) \cdot \mathsf{eq}(b, \mathbf z) = \hat f(\mathbf z)

    So σ=f^(z)\sigma = \hat f(\mathbf z). The CRS code carves out, from all RS codewords, those whose multilinear evaluates to σ\sigma at z\mathbf z. 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 <2m<2^m evaluated at zFz \in \mathbb F corresponds, via the smoothness identity, to evaluating its multilinear at z=(z,z2,z4,,z2m1)\mathbf z = (z, z^2, z^4, \ldots, z^{2^{m-1}}). So univariate point evaluations are CRS too.

  • Arbitrary sumcheck-like queries. Any polynomial w^\hat w 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 ff is a Reed–Solomon codeword” with “test that f^\hat f 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.

CRS code anatomy: a constrained Reed–Solomon code is the intersection of two requirements — being an RS codeword on L, and satisfying a sumcheck-style equation Σ_b ŵ(f̂(b), b) = σ on the underlying multilinear. Three callouts show how different choices of ŵ yield a multilinear PCS opening, a univariate evaluation, and a generic Σ-IOP query — all as the same CRS object.

What the verifier actually sees

Before the protocol walk, anchor on what’s actually on the wire. The verifier never inspects f^\hat f in full — it only sees a few Merkle roots, sumcheck messages, and a handful of opened entries.

Commit phase. The prover takes the multilinear f^\hat f, encodes it as f:LFf : L \to \mathbb F via the smoothness curve, Merkle-commits the L\lvert L \rvert entries of ff, and sends one root hash to the verifier. That hash is the entire commitment.

Open phase, per WHIR iteration. For an opening claim like f^(z)=σ\hat f(\mathbf z) = \sigma, each iteration puts the following on the wire:

  • kk sumcheck messages h^1,,h^k\hat h_1, \ldots, \hat h_k — small univariate polynomials.
  • A new Merkle root for the folded oracle g:L(2)Fg : L^{(2)} \to \mathbb F.
  • One OOD answer y0Fy_0 \in \mathbb F.
  • tt shift-query answers — for each ziz_i, the 2k2^k values of ff at the 2k2^k-th roots of ziz_i, plus a Merkle path proving they came from the committed root.

After M=m/kM = m/k iterations, the prover sends the final small multilinear f^(M)\hat f^{(M)} 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 f^\hat f 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 attemptCaught by
ff isn’t close to any RS codewordShift queries (proximity test)
Sumcheck messages don’t match the codeword’s multilinearSumcheck round-by-round equation h^i(0)+h^i(1)=h^i1(αi1)\hat h_i(0) + \hat h_i(1) = \hat h_{i-1}(\alpha_{i-1})
The folded gg isn’t really the fold of ffShift queries (cross-check gg vs Fold(f,α)\mathsf{Fold}(f, \boldsymbol\alpha))
Multiple codewords match gg in the list-decoding regimeOOD pinning

If every gate passes, the soundness analysis says: with overwhelming probability there exists a single multilinear f^\hat f such that the committed ff encodes it AND the original opening claim f^(z)=σ\hat f(\mathbf z) = \sigma holds. The verifier doesn’t need to see f^\hat f — 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

fC:=CRS[F,L,m,w^,σ]f \in C := \mathsf{CRS}[\mathbb F, L, m, \hat w, \sigma]

to testing

fC:=CRS[F,L(2),mk,w^,σ]f' \in C' := \mathsf{CRS}\big[\mathbb F, L^{(2)}, m-k, \hat w', \sigma'\big]

for some new weight polynomial w^\hat w' and target σ\sigma' that the protocol constructs along the way. The full protocol applies M=m/kM = m/k iterations until C(M)C^{(M)} has only a few variables left and the prover sends f^(M)\hat f^{(M)} in the clear.

For this overview, assume w^\hat w is multilinear (the paper handles a more general setting).

One WHIR iteration: f over (L, m, ŵ, σ) goes through k sumcheck rounds, then folds to g over L^{(2)} with m-k variables, then OOD sample (z₀, y₀) pins down a unique codeword from the list, then t shift queries (zᵢ, yᵢ) cross-check Fold(f, α). All of this gets bundled into the new constraint ŵ', σ' for the recursive instance C'.

Step 1 — kk rounds of sumcheck

The move: burn down kk of the mm variables in the CRS constraint via sumcheck. After kk rounds, the first kk variables of f^\hat f are pinned to verifier randomness α\boldsymbol\alpha, and the leftover sum is over mkm-k 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 CC. After kk rounds, the prover has sent kk univariate polynomials h^1,,h^k\hat h_1, \ldots, \hat h_k (one per round) and the verifier has sampled α=(α1,,αk)\boldsymbol\alpha = (\alpha_1, \ldots, \alpha_k). The remaining claim is the partially-bound sum:

b{0,1}mkw^(f^(α,b),α,b)=h^k(αk)\sum_{b' \in \{0,1\}^{m-k}} \hat w\big(\hat f(\boldsymbol\alpha, b'), \boldsymbol\alpha, b'\big) = \hat h_k(\alpha_k)

The verifier knows h^k(αk)\hat h_k(\alpha_k) — 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 f^\hat f. 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 mkm-k free variables left; the prover commits to its evaluations on the half-sized domain L(2)L^{(2)}. That’s the new RS codeword the recursive instance will check.

How: the prover sends g:L(2)Fg : L^{(2)} \to \mathbb F as a fresh Merkle-committed oracle. Honestly, gg is the evaluation on L(2)L^{(2)} of the multilinear g^(X):=f^(α,X)\hat g(X) := \hat f(\boldsymbol\alpha, X) — the same multilinear as the iterated fold Fold(f,α)\mathsf{Fold}(f, \boldsymbol\alpha), but encoded on the bigger domain L(2)L^{(2)} instead of L(2k)L^{(2^k)} (where the iterated fold would naturally land). For k=1k = 1 these coincide; for k>1k > 1, gg is a more redundant encoding of the same polynomial.

Note the asymmetry. Step 1 bound kk variables of f^\hat f on the multilinear side, but Step 2 only halves the domain once on the codeword side. The new codeword gg has length L/2\lvert L \rvert / 2, not L/2k\lvert L \rvert / 2^k — bigger than the minimum needed. The polynomial it encodes (g^\hat g with mkm-k variables, 2mk2^{m-k} 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 gg yet. The next steps are what tie it back to ff.

Step 3 — Out-of-domain round

The purpose: pin down a single codeword as “the” gg, even when list decoding leaves many candidates. Beyond the unique-decoding radius, several distinct codewords can sit within distance δ\delta of the prover’s claimed gg — 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 L(2)L^{(2)}.

How: the verifier samples z0Fz_0 \leftarrow \mathbb F (out-of-domain — that’s the “OOD”) and sends it. Set z0:=pow(z0,mk)=(z0,z02,z04,)\mathbf z_0 := \mathsf{pow}(z_0, m-k) = (z_0, z_0^2, z_0^4, \ldots) — the smoothness lift of z0z_0. The prover replies with y0Fy_0 \in \mathbb F, which honestly is g^(z0)\hat g(\mathbf z_0).

Why one codeword wins. At most one codeword u^\hat u in the list can satisfy u^(z0)=y0\hat u(\mathbf z_0) = y_0, 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” gg — same trick DEEP-FRI [BGKS20] introduced for FRI; WHIR reuses it inside every iteration.

Deferred verification. The verifier doesn’t actually check y0y_0 at this step. The value y0y_0 flows into σ\sigma' in Step 5; the recursive CRS check is what enforces g^(z0)=y0\hat g(\mathbf z_0) = y_0 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 gg to actually agree with the fold of the committed ff on tt random spot checks. Without these, a cheating prover could send any gg 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 ff — every other check operates on the fresh oracle gg or on small messages.

The mechanics. Verifier samples z1,,ztL(2k)z_1, \ldots, z_t \leftarrow L^{(2^k)}, lifts each to zi:=pow(zi,mk)\mathbf z_i := \mathsf{pow}(z_i, m-k), and samples a combination randomness γF\gamma \leftarrow \mathbb F. For each ziz_i, the verifier needs to compute yi:=Fold(f,α)(zi)y_i := \mathsf{Fold}(f, \boldsymbol\alpha)(z_i) — the value of the iterated kk-fold of ff at the point ziz_i. The verifier only has a Merkle commitment to ff, not the full vector. How does it compute the fold from queries alone?

The 2k2^k-th roots and the local fold. The “2k2^k-th roots of ziz_i” are the 2k2^k-many xLx \in L such that x2k=zix^{2^k} = z_i. Two facts about smoothness make this exact count work:

  • Squaring is 2-to-1 on a smooth domain (each yL(2)y \in L^{(2)} has two preimages ±x\pm x in LL). Iterating kk squarings is 2k2^k-to-1 — every point of L(2k)L^{(2^k)} has exactly 2k2^k preimages back in the original domain LL.
  • Smoothness guarantees these preimages all live inside the committed domain LL (no need to extend). They’re the leaves the verifier can ask for via Merkle paths.

The verifier reads only those 2k2^k values of ff — typically with one Merkle path, since the prover lays out ff‘s tree so that all 2k2^k preimages of any L(2k)L^{(2^k)} 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 kk times via α=(α1,,αk)\boldsymbol\alpha = (\alpha_1, \ldots, \alpha_k)) and you get one value yi=Fold(f,α)(zi)y_i = \mathsf{Fold}(f, \boldsymbol\alpha)(z_i) — computed locally, in O(2k)O(2^k) field operations. The verifier never folds the entire ff; it folds only at the sampled points.

Total cost: tt Merkle paths into ff, one per ziz_i, each opening 2k2^k adjacent positions.

Deferred verification. The yiy_i values aren’t compared to gg directly at this step. They flow into σ\sigma' in Step 5; the recursive CRS check is what enforces ”g^(zi)=yi\hat g(\mathbf z_i) = y_i for every ii.” If gg wasn’t actually close to the fold of ff, at least one zi\mathbf z_i 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 tt 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 w^\hat w' and new target σ\sigma':

w^(Z,X):=w^(Z,α,X)+Zi=0tγi+1eq(zi,X)\hat w'(Z, X) := \hat w(Z, \boldsymbol\alpha, X) + Z \cdot \sum_{i=0}^{t} \gamma^{i+1} \cdot \mathsf{eq}(\mathbf z_i, X) σ:=h^k(αk)+i=0tγi+1yi\sigma' := \hat h_k(\alpha_k) + \sum_{i=0}^{t} \gamma^{i+1} \cdot y_i

The new constraint w^\hat w' packs three things into one CRS instance:

  • The sumcheck residue from Step 1 (w^(Z,α,X)\hat w(Z, \boldsymbol\alpha, X) with target h^k(αk)\hat h_k(\alpha_k)).
  • The OOD constraint from Step 3 (the i=0i = 0 term: Zγeq(z0,X)Z \cdot \gamma \cdot \mathsf{eq}(\mathbf z_0, X), contributing γy0\gamma \cdot y_0 to σ\sigma').
  • The tt shift-query constraints from Step 4 (the i1i \ge 1 terms).

All three are evaluation-style sumcheck queries against g^\hat g, combined into a single random linear combination via γ\gamma. The protocol then recurses: prover and verifier check gC:=CRS[F,L(2),mk,w^,σ]g \in C' := \mathsf{CRS}[\mathbb F, L^{(2)}, m-k, \hat w', \sigma'].

This is where the checks actually happen. Steps 3 and 4 only gathered obligations — they didn’t verify anything. Step 5 bundles them into w^\hat w' and σ\sigma' that the next iteration’s CRS check has to satisfy. Recurse all the way down to the base case (where f^(M)\hat f^{(M)} is sent in clear), and the verifier directly evaluates the final w^(M)\hat w^{(M)} at f^(M)\hat f^{(M)}, summed over the boolean cube, against σ(M)\sigma^{(M)}. 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 ρ=2mk/L(2)=2mk/(L/2)=21kρ\rho' = 2^{m-k}/|L^{(2)}| = 2^{m-k}/(|L|/2) = 2^{1-k} \cdot \rho. For typical k=4k = 4, that’s ρ=ρ/8\rho' = \rho/8 — 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 δ\delta — the minimum fraction of positions on which two distinct codewords differ. For Reed–Solomon, distance is roughly 1ρ1 - \rho. If ff is δ\delta-far from every codeword, a single random query catches the discrepancy with probability δ\geq \delta, and tt random queries catch with probability 1(1δ)t\geq 1 - (1-\delta)^t. To hit λ\lambda bits of soundness, you need

t    λlog2(1/ρ)t \;\gtrsim\; \frac{\lambda}{\log_2(1/\rho)}

queries. Lower rate → larger distance → fewer queries:

Rate ρ\rholog2(1/ρ)\log_2(1/\rho)Queries for λ=128\lambda = 128
1/21128\sim 128
1/4264\sim 64
1/8343\sim 43
1/16432\sim 32

(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 O(λ)O(\lambda) across all iterations rather than O(λlogn)O(\lambda \log n).

The cost. The prover sends a gg of size L/2|L|/2 (half the original domain) at each iteration, so total proof length is L+L/2+L/4+=O(L)|L| + |L|/2 + |L|/4 + \ldots = O(|L|). The geometric series gives a free pass on proof size.

The recursion’s base case

After M=m/kM = m/k iterations, the code has a constant number of variables. The prover sends f^(M)\hat f^{(M)} in the clear (constant size), and the verifier checks the final CRS constraint by directly evaluating w^(M)\hat w^{(M)} — a constant-time check.

WHIR recursion tower: M stacked CRS instances, each iteration shrinking variables m → m-k, halving the domain |L| → |L|/2, and dropping the rate by a factor 2^(k-1). Slab widths shrink with domain size, so the geometric-series total proof length |L| + |L|/2 + ... = O(|L|) is visible. The base case at the bottom is the in-the-clear final polynomial.

Why the verifier is so fast

Now we can answer “where does O(qWHIR(2k+m))O(q_{\text{WHIR}} \cdot (2^k + m)) come from, and why does STIR carry an extra λ2/k\lambda^2/k factor?”

Per query: O(2k)O(2^k) for fold + O(m)O(m) for the constraint update

For each shift query ziz_i in iteration jj:

  • Fold computation. The verifier reads 2kj2^{k_j} adjacent entries of f(j)f^{(j)} from one Merkle path, then computes Fold(f(j),α)(zi)\mathsf{Fold}(f^{(j)}, \boldsymbol\alpha)(z_i) in O(2kj)O(2^{k_j}) field operations using the coefficient-encoding optimization (Section 2.1.4 of the paper). With kj=kk_j = k constant across iterations, that’s O(2k)O(2^k) per query.

  • Constraint update. Updating w^(j+1)\hat w^{(j+1)} to include the new shift point requires evaluating eq(zi,)\mathsf{eq}(\mathbf z_i, \cdot) at the recursive instance’s variables — O(mj)O(m_j) field operations.

Summed over all qWHIRq_{\text{WHIR}} queries, that’s O(qWHIR(2k+m))O(q_{\text{WHIR}} \cdot (2^k + m)). No quotients, no division, no extra structure on top.

Why STIR pays the λ2/k\lambda^2/k overhead

STIR’s verifier time is O(qSTIR2k+λ2k2k)O(q_{\text{STIR}} \cdot 2^k + \frac{\lambda^2}{k} \cdot 2^k) — a per-query 2k2^k plus an additive λ2k2k\frac{\lambda^2}{k} \cdot 2^k 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 (g^(X)y0)/eq(X,ζ)(\hat g(X) - y_0) / \mathsf{eq}(X, \zeta) that the verifier checks against an extended evaluation domain. The bookkeeping scales with the OOD claims accumulated across rounds — that’s where λ2/k\lambda^2 / k comes from.

WHIR avoids the quotient entirely by encoding the OOD claim directly inside the recursive constraint (Zγeq(z0,X)Z \cdot \gamma \cdot \mathsf{eq}(\mathbf z_0, X) inside w^\hat w'). 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 λ2/k\lambda^2/k 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 k=O(logm)k = O(\log m) and typical settings, qWHIR=O(λ)q_{\text{WHIR}} = O(\lambda) — query-optimal. The total verifier time is:

O(qWHIR(2k+m))=O(λm)O\big(q_{\text{WHIR}} \cdot (2^k + m)\big) = O(\lambda \cdot m)

That’s linear in the number of field elements the verifier reads. It can’t get asymptotically smaller without reading less, and at λ\lambda 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 gg” 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 (δ,ε)(\delta, \varepsilon)-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 δ\delta and shift-query counts tit_i. Same algorithm, different parameter choices, hence different argument sizes (visible in the headline numbers — WHIR-UD vs WHIR-CB at the same (m,ρ)(m, \rho) produce different KiB).

Why we need an upgrade. Folding decomposes ff into two pieces: Fold(f,α)=f0+αf1\mathsf{Fold}(f, \alpha) = f_0 + \alpha \cdot f_1, where f0(x2)=(f(x)+f(x))/2f_0(x^2) = (f(x)+f(-x))/2 and f1(x2)=(f(x)f(x))/(2x)f_1(x^2) = (f(x)-f(-x))/(2x). For soundness, every codeword close to the folded function must come from a codeword close to the original ff — list-preserving folding. That holds only if f0f_0 and f1f_1 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 correlated agreement: the left panel shows standard correlated agreement, where the agreement set S for the random combination f_α and the agreement set T for the individual f_i's can be in different parts of L. The right panel shows WHIR's stronger mutual correlated agreement, where T = S — every f_i agrees with the code on the same large set. That sameness lets WHIR paste folded pieces back into a single codeword close to f.

Standard vs. mutual

Picture \ell vectors that each might be close to a codeword. Take a random linear combination fα:=i=1αi1fif_\alpha := \sum_{i=1}^{\ell} \alpha^{i-1} \cdot f_i. Standard correlated agreement says: if fαf_\alpha is close to some codeword on a large set SS, then each individual fif_i is close to some codeword too — but possibly on different sets TiT_i, with no relationship to SS. For most uses, you don’t care. Mutual correlated agreement strengthens this by demanding the same set: every fif_i is close to a codeword on SS itself.

WHIR’s analysis cashes this in via the list-preservation property: with high probability over α\alpha, every codeword uu close to Fold(f,α)\mathsf{Fold}(f, \alpha) is the folding of some codeword ww close to ff. Mutual agreement is what lets us paste f0f_0 and f1f_1 back into a single ww — they have to agree with codewords on the same set SS for the pasting to recover a single near-codeword for ff.

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 δ\delta), 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, w^(Z,X)=Zeq(X,z)\hat w(Z, X) = Z \cdot \mathsf{eq}(X, \mathbf z) — 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 f^\hat f with mm variables: the prover encodes f^\hat f as a Reed–Solomon codeword f:LFf : L \to \mathbb F (evaluating along the smoothness curve) and Merkle-commits ff.

To open at zFm\mathbf z \in \mathbb F^m with claimed value σ\sigma: prover and verifier run WHIR on the constrained code CRS[F,L,m,Zeq(,z),σ]\mathsf{CRS}[\mathbb F, L, m, Z \cdot \mathsf{eq}(\cdot, \mathbf z), \sigma]. Acceptance means ff encodes some f^\hat f satisfying f^(z)=σ\hat f(\mathbf z) = \sigma, 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 <2m< 2^m: lift zFz \in \mathbb F to z=(z,z2,,z2m1)\mathbf z = (z, z^2, \ldots, z^{2^{m-1}}) 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 (w^,σ)(\hat w, \sigma) 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:

  1. Having the prover commit to each f^i\hat f_i via its Reed–Solomon encoding fif_i.
  2. Sampling out-of-domain points ziz_i to pin down a single codeword from each list-decoding list (same trick as inside WHIR itself).
  3. Collecting all the verifier’s sumcheck queries w^i,j\hat w_{i,j} and their claimed answers Ai,jA_{i,j} into a multi-constrained Reed–Solomon code CRS[F,L,m,(w^i,j,Ai,j)i,j]\mathsf{CRS}[\mathbb F, L, m, (\hat w_{i,j}, A_{i,j})_{i,j}].
  4. 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, m=24m = 24, ρ=1/2\rho = 1/2):

Argument sizeVerifier timeProver time
BaseFold7.95 MiB24 ms8.0 s
WHIR-UD390 KiB2.39 ms3.5 s
WHIR-CB101 KiB0.61 ms3.5 s

WHIR-CB is 74×74\times smaller and 39×39\times faster on the verifier than BaseFold.

WHIR vs. STIR vs. FRI (Reed–Solomon proximity test, 192-bit prime, 128-bit security, m=24m = 24, ρ=1/2\rho = 1/2):

Argument sizeVerifier timeVerifier hashesProver time
FRI306 KiB3.9 ms5.6k28 s
STIR160 KiB3.8 ms2.7k36 s
WHIR-CB157 KiB1.0 ms2.7k34 s

Argument size matches STIR (both rely on rate decrease). Verifier time is 3.8×3.8\times faster than STIR — that’s the λ2/k\lambda^2/k saving from cutting quotients.

WHIR-as-PCS vs. KZG (100-bit security, m=24m = 24). KZG only supports univariate polynomials; WHIR supports both. WHIR-CB verifier runs in 0.61 ms at ρ=1/2\rho = 1/2 and 0.29 ms at ρ=1/16\rho = 1/16; KZG verifier runs in 2.42 ms. The paper reports a 4.08.3×4.0\text{–}8.3\times 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-ZZ Σ-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 w^\hat w.
  • Σ-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