WHIR

Weights Help Improving Rate. an interactive oracle proof of proximity for constrained Reed-Solomon codes that simultaneously serves as a multilinear polynomial commitment scheme. Arnon, Chiesa, Fenzi, Yogev (EUROCRYPT 2025). ePrint 2024/1586.

protocol

WHIR provides three operations:

WHIR_commit(f) → C
  input:  multilinear polynomial f(x₁, ..., x_k) over Goldilocks
  output: commitment C (hemera Merkle root over evaluation table)
  cost:   O(2^k log 2^k) hemera hashes

WHIR_open(f, r) → (v, π)
  input:  polynomial f, evaluation point r ∈ F^k
  output: value v = f(r), proximity proof π
  cost:   O(2^k) field ops + O(log² 2^k) hemera hashes

WHIR_verify(C, r, v, π) → accept/reject
  input:  commitment C, point r, claimed value v, proof π
  output: accept if f(r) = v and f is close to a degree-bounded polynomial
  cost:   O(log² 2^k) hemera hashes + field ops

verification algorithm

WHIR_verify(C, r, v, proof):
  1. init transcript T from C
  2. for each folding round i = 1..log(N):
     a. absorb prover's round message into T
     b. squeeze challenge αᵢ from T
     c. verify Merkle paths for queried positions against round commitment
     d. fold polynomial: combine evaluations using αᵢ as weight
     e. check consistency: folded values match next round's commitment
  3. verify final polynomial is constant (degree 0)
  4. check that the accumulated evaluation equals v at point r

the folding formula per round: given evaluations f(x) on the current domain and challenge α, split into even and odd parts:

f_folded(x) = (f(x) + f(-x)) / 2 + α × (f(x) - f(-x)) / (2x)

this halves the number of evaluation points each round. the even part (f(x) + f(-x))/2 extracts the symmetric component; the odd part (f(x) - f(-x))/(2x) extracts the antisymmetric component. the challenge α linearly combines them into a single folded polynomial of half the degree.

for multilinear polynomials: folding over variable x_j with challenge r_j gives:

f'(x_1, ..., x_{j-1}, x_{j+1}, ..., x_k) = f(..., x_j=0) × (1 - r_j) + f(..., x_j=1) × r_j

each round eliminates one variable, reducing a k-variate multilinear polynomial to a (k-1)-variate one.

Reed-Solomon domain

the evaluation domain is a multiplicative coset of a 2-adic subgroup of the Goldilocks field (p = 2^64 - 2^32 + 1).

rate ρ is the inverse of the blowup factor. ρ = 1/2 means the evaluation domain is 2× the polynomial degree; ρ = 1/4 means 4×. lower rate increases proof size but improves soundness per query.

for a trace with 2^n rows and 2^4 columns (16 columns), the polynomial has 2^{n+4} evaluations. the Reed-Solomon evaluation domain size is 2^{n+4} / ρ. at ρ = 1/2 this gives a domain of size 2^{n+5}.

domain generator: a primitive 2^k-th root of unity ω in Goldilocks. the Goldilocks field has multiplicative order p - 1 = 2^32 × (2^32 - 1), so primitive 2^k-th roots of unity exist for k up to 32. the subgroup ⟨ω⟩ = {ω^0, ω^1, ..., ω^{2^k - 1}} forms the base domain.

coset shift: multiply the entire subgroup by a fixed non-unity element g ∈ F* to obtain the coset g⟨ω⟩ = {g, gω, gω², ...}. this avoids evaluating at zero and ensures the evaluation domain is disjoint from the polynomial's natural domain.

proof structure

a WHIRProof contains:

WHIRProof {
  rounds: [RoundData; log₂(N)]
  final_value: F                          // constant polynomial value after all folds
}

RoundData {
  commitment: hemera Merkle root           // commitment to folded evaluations
  auth_paths: [MerklePath; num_queries]   // authentication paths for queried positions
  leaf_values: [F; num_queries]           // evaluation values at queried positions
}

query positions are derived deterministically from the Fiat-Shamir transcript: after absorbing each round commitment, the verifier squeezes query indices from the transcript. this makes the proof non-interactive.

number of queries per round: security_bits / log₂(1/ρ). at 100-bit security with ρ = 1/2: 100 / log₂(2) = 100 queries per round. at ρ = 1/4: 100 / log₂(4) = 50 queries per round. lower rate reduces queries but increases evaluation domain size.

parameters

parameter 100-bit security 128-bit security
argument size (standalone) ~101 KiB ~157 KiB
verification time ~290 μs ~1.0 ms
verifier hashes ~1,800 ~2,700
folding rounds log₂(N) log₂(N)
queries per round determined by security target determined by security target
field Goldilocks field (p = 2^64 − 2^32 + 1) Goldilocks field
hash hemera (Poseidon2) hemera (Poseidon2)

the standalone WHIR argument size (~101 KiB at 100-bit) is larger than the full whirlaway proof (~60 KiB at 100-bit). the difference: Whirlaway's multilinear encoding and SuperSpartan sumcheck reduce the WHIR opening to ~58 KiB. see whirlaway for the composed proof breakdown.

performance comparison

at d = 2²⁴, 128-bit security:

scheme argument size verifier hashes verification time
FRI 306 KiB 5,600 3.9 ms
STIR 160 KiB 2,600 3.8 ms
WHIR 157 KiB 2,700 1.0 ms

at 100-bit security:

scheme argument size verification time setup
KZG 48 bytes 2.4 ms trusted
PST ~200 bytes 3.6 ms trusted
BaseFold 7.95 MiB 24 ms none
WHIR 101 KiB 290 μs none

properties

property value
trusted setup none (transparent)
post-quantum yes (hash-only security)
dual nature IOPP (proximity test) + multilinear PCS
field any with large multiplicative subgroup
soundness mutual correlated agreement

verification circuit decomposition

inside nox, WHIR_verify decomposes into two jets:

merkle_verify(root, leaf, path, index)
  d × 300 constraints per depth-d path

fri_fold(poly_layer, challenge)        // nox jet
  N/2 constraints per folding round

full WHIR_verify:

1. Fiat-Shamir: derive challenges from transcript
   → hemera hashes (~22 cycles per challenge)

2. per folding round (log(N) rounds):
   a. merkle_verify: check evaluation query paths
   b. fri_fold: fold polynomial with round challenge
   c. sumcheck round: weight polynomial combination (~10 constraints)

3. final check: verify folded polynomial is constant

estimated constraints for N = 2^16, 100-bit security:

component calls constraints
Fiat-Shamir (hemera) ~16 hashes ~19,200
merkle_verify ~50 paths × depth 16 ~240,000
fri_fold 16 rounds, geometric sum ~32,768
sumcheck rounds 16 ~160
total ~292,000

without jets (pure Layer 1): ~2,900,000 constraints (10×).

design notes

WHIR-compressed Merkle proofs

standard hemera Merkle proof for depth 20: 1,280 bytes. WHIR alternative: commit each tree level as a multilinear polynomial, open at queried positions.

encoding: f_ℓ(b₁, ..., b_ℓ) = hash of node reached by path bits b₁...b_ℓ
crossover: k > ~80 leaves for depth 20 (below this, standard proofs are smaller)
hybrid:    prover selects mode; verifier accepts both via mode flag

open questions: evaluation table layout, root finalization domain separation, hybrid mode protocol.

incremental commitment updates

EdgeSet polynomial commitments face re-commitment cost when edges are added. with a fixed evaluation domain:

insert edge k+1:
  1. evaluate P at new point
  2. update one Merkle leaf
  3. rehash path: log₂(D) × 2 permutations

for D = 2^16: 32 permutations per insertion
vs full re-commit: O(N log N) hashes

domain sizing strategy: start at 2^10, double when 75% full. amortized O(1) per insertion.

composed WHIR_VERIFY jet

a single jet combining merkle_verify + fri_fold + Fiat-Shamir could reduce verification to ~100K constraints (from ~292K). trades ISA complexity for recursion depth. current two-jet decomposition suffices for depths up to ~10.

see polynomial-commitment for the commitment abstraction, sumcheck for the weight polynomial mechanism, verifier for the full zheng verification algorithm, whirlaway for the architecture

Dimensions

WHIR

Local Graph