openvm_stark_backend/verifier/
whir.rs1use core::iter::zip;
2
3use itertools::{izip, Itertools};
4use p3_field::{BasedVectorSpace, ExtensionField, PrimeCharacteristicRing, TwoAdicField};
5use thiserror::Error;
6use tracing::instrument;
7
8use crate::{
9 hasher::MerkleHasher,
10 poly_common::{
11 eval_eq_mle, eval_mle_evals_at_point, eval_mobius_eq_mle, horner_eval,
12 interpolate_quadratic_at_012, Squarable,
13 },
14 proof::WhirProof,
15 FiatShamirTranscript, StarkProtocolConfig,
16};
17
18#[inline]
19fn ensure(cond: bool, err: VerifyWhirError) -> Result<(), VerifyWhirError> {
20 if cond {
21 Ok(())
22 } else {
23 Err(err)
24 }
25}
26
27#[instrument(level = "debug", skip_all)]
31pub fn verify_whir<SC: StarkProtocolConfig, TS: FiatShamirTranscript<SC>>(
32 transcript: &mut TS,
33 config: &SC,
34 whir_proof: &WhirProof<SC>,
35 stacking_openings: &[Vec<SC::EF>],
36 commitments: &[SC::Digest],
37 u: &[SC::EF],
38) -> Result<(), VerifyWhirError> {
39 let params = config.params();
40 let widths = stacking_openings
43 .iter()
44 .map(|v| v.len())
45 .collect::<Vec<_>>();
46
47 if !transcript.check_witness(params.whir.mu_pow_bits, whir_proof.mu_pow_witness) {
49 return Err(VerifyWhirError::MuPoWInvalid);
50 }
51
52 let mu = transcript.sample_ext();
53
54 let WhirProof {
55 mu_pow_witness: _, whir_sumcheck_polys,
57 codeword_commits,
58 ood_values,
59 initial_round_opened_rows,
60 initial_round_merkle_proofs,
61 codeword_opened_values,
62 codeword_merkle_proofs,
63 folding_pow_witnesses,
64 query_phase_pow_witnesses,
65 final_poly,
66 } = whir_proof;
67
68 let m = params.l_skip + params.n_stack;
69 let k_whir = params.k_whir();
70 debug_assert_eq!((m - params.log_final_poly_len()) % k_whir, 0);
71 let num_whir_rounds = params.num_whir_rounds();
72 let mut log_rs_domain_size = m + params.log_blowup;
73 debug_assert!(params.num_whir_sumcheck_rounds() <= m);
74 debug_assert_eq!(
76 folding_pow_witnesses.len(),
77 params.num_whir_sumcheck_rounds()
78 );
79
80 let mut sumcheck_poly_iter = whir_sumcheck_polys.iter();
83 let mut folding_pow_iter = folding_pow_witnesses.iter();
84 let mu_pows: Vec<_> = mu.powers().take(widths.iter().sum::<usize>()).collect();
85 let mut claim = stacking_openings
86 .iter()
87 .flatten()
88 .zip(mu_pows.iter())
89 .fold(SC::EF::ZERO, |acc, (&opening, &mu_pow)| {
90 acc + mu_pow * opening
91 });
92
93 let mut gammas = Vec::with_capacity(num_whir_rounds);
94 let mut zs = Vec::with_capacity(num_whir_rounds);
95 let mut z0s = Vec::with_capacity(num_whir_rounds);
96 let mut alphas = Vec::with_capacity(m);
97
98 debug_assert_eq!(query_phase_pow_witnesses.len(), num_whir_rounds);
100 for (whir_round, (query_phase_pow_witness, round_params)) in
102 zip(query_phase_pow_witnesses, ¶ms.whir.rounds).enumerate()
103 {
104 let is_initial_round = whir_round == 0;
115 let is_final_round = whir_round == num_whir_rounds - 1;
116
117 let mut alphas_round = Vec::with_capacity(k_whir);
118
119 for _ in 0..k_whir {
120 if let Some(evals) = sumcheck_poly_iter.next() {
122 let &[ev1, ev2] = evals;
123
124 transcript.observe_ext(ev1);
125 transcript.observe_ext(ev2);
126
127 let pow_witness = *folding_pow_iter.next().unwrap();
128 if !transcript.check_witness(params.whir.folding_pow_bits, pow_witness) {
129 return Err(VerifyWhirError::FoldingPoWInvalid);
130 }
131 let alpha = transcript.sample_ext();
132 alphas_round.push(alpha);
133
134 let ev0 = claim - ev1;
135 claim = interpolate_quadratic_at_012(&[ev0, ev1, ev2], alpha);
136 }
137 }
138
139 let y0 = if is_final_round {
140 for coeff in final_poly {
143 transcript.observe_ext(*coeff);
144 }
145 None
146 } else {
147 let commit = codeword_commits[whir_round];
149 transcript.observe_commit(commit);
150
151 let z0 = transcript.sample_ext();
152 z0s.push(z0);
153
154 let y0 = ood_values[whir_round];
156 transcript.observe_ext(y0);
157 Some(y0)
158 };
159
160 if !transcript.check_witness(params.whir.query_phase_pow_bits, *query_phase_pow_witness) {
161 return Err(VerifyWhirError::QueryPhasePoWInvalid);
162 }
163
164 let num_queries = round_params.num_queries;
165 let query_indices =
166 (0..num_queries).map(|_| transcript.sample_bits(log_rs_domain_size - k_whir));
167
168 let mut zs_round = Vec::with_capacity(num_queries);
169 let mut ys_round = Vec::with_capacity(num_queries);
170
171 let hasher = config.hasher();
172 let omega = SC::F::two_adic_generator(log_rs_domain_size);
173 for (query_idx, index) in query_indices.into_iter().enumerate() {
174 let zi_root = omega.exp_u64(index);
175 let zi = zi_root.exp_power_of_2(k_whir);
176
177 let yi = if is_initial_round {
178 let mut codeword_vals = vec![SC::EF::ZERO; 1 << k_whir];
179 let mut mu_pow_iter = mu_pows.iter();
180 for (&commit, &width, opened_rows_per_query, merkle_proofs) in izip!(
182 commitments,
183 &widths,
184 initial_round_opened_rows,
185 initial_round_merkle_proofs
186 ) {
187 let opened_rows = &opened_rows_per_query[query_idx];
191 let leaf_hashes = opened_rows
192 .iter()
193 .map(|opened_row| hasher.hash_slice(opened_row))
194 .collect_vec();
195 let query_digest = hasher.tree_compress(leaf_hashes);
196 let merkle_proof = &merkle_proofs[query_idx];
201 merkle_verify(hasher, commit, index as u32, query_digest, merkle_proof)?;
202
203 for c in 0..width {
204 let mu_pow = mu_pow_iter.next().unwrap(); for j in 0..(1 << k_whir) {
206 codeword_vals[j] += *mu_pow * opened_rows[j][c];
207 }
208 }
209 }
210 binary_k_fold::<SC::F, SC::EF>(codeword_vals, &alphas_round, zi_root)
211 } else {
212 let opened_values = codeword_opened_values[whir_round - 1][query_idx].clone();
220 let merkle_proof = &codeword_merkle_proofs[whir_round - 1][query_idx];
221 let leaf_hashes = opened_values
222 .iter()
223 .map(|opened_value| {
224 hasher.hash_slice(opened_value.as_basis_coefficients_slice())
225 })
226 .collect_vec();
227 let query_digest = hasher.tree_compress(leaf_hashes);
228 merkle_verify(
231 hasher,
232 codeword_commits[whir_round - 1],
233 index as u32,
234 query_digest,
235 merkle_proof,
236 )?;
237 binary_k_fold::<SC::F, SC::EF>(opened_values, &alphas_round, zi_root)
238 };
239 zs_round.push(zi);
240 ys_round.push(yi);
241 }
242 let gamma = transcript.sample_ext();
248 if let Some(y0) = y0 {
249 claim += y0 * gamma;
250 }
251 for (yi, gamma_pow) in ys_round.iter().zip(gamma.powers().skip(2)) {
252 claim += *yi * gamma_pow;
253 }
254 gammas.push(gamma);
255 zs.push(zs_round);
256 alphas.extend(alphas_round);
257
258 log_rs_domain_size -= 1;
259 }
260 debug_assert!(sumcheck_poly_iter.next().is_none());
261
262 ensure(
263 final_poly.len() == 1 << params.log_final_poly_len(),
264 VerifyWhirError::FinalPolyDegree,
265 )?;
266
267 debug_assert_eq!(alphas.len(), k_whir * num_whir_rounds);
268 debug_assert_eq!(z0s.len(), num_whir_rounds - 1);
269 debug_assert_eq!(zs.len(), num_whir_rounds);
270 debug_assert_eq!(gammas.len(), num_whir_rounds);
271
272 let t = k_whir * num_whir_rounds;
292 let prefix = eval_mobius_eq_mle(&u[..t], &alphas[..t]);
293 let suffix_sum = eval_mle_evals_at_point(&mut final_poly.clone(), &u[t..]);
294 let mut acc = prefix * suffix_sum;
295 let mut j = k_whir;
296 for i in 0..num_whir_rounds {
297 let zis = &zs[i];
298 let gamma = gammas[i];
299 let alpha_slc = &alphas[j..t];
300 let slc_len = (t - j) + 1;
301
302 if i != num_whir_rounds - 1 {
303 let z0_pow = z0s[i].exp_powers_of_2().take(slc_len).collect_vec();
304 let (z0_pow_max, z0_pow_left) = z0_pow.split_last().unwrap();
305 acc += gamma
306 * eval_eq_mle(alpha_slc, z0_pow_left)
307 * horner_eval::<SC::EF, SC::EF, SC::EF>(final_poly, *z0_pow_max);
308 }
309
310 debug_assert_eq!(zis.len(), params.whir.rounds[i].num_queries);
311 for (zi, gamma_pow) in zip(zis, gamma.powers().skip(2)) {
312 let zi_pow = zi.exp_powers_of_2().take(slc_len).collect_vec();
313 let (zi_pow_max, zi_pow_left) = zi_pow.split_last().unwrap();
314 acc += gamma_pow
315 * eval_eq_mle(alpha_slc, zi_pow_left)
316 * horner_eval::<SC::EF, SC::F, SC::EF>(final_poly, *zi_pow_max);
317 }
318 j += k_whir;
319 }
320 ensure(acc == claim, VerifyWhirError::FinalPolyConstraint)
321}
322
323#[derive(Debug, Error, PartialEq, Eq)]
324pub enum VerifyWhirError {
325 #[error("final polynomial has wrong degree")]
326 FinalPolyDegree,
327 #[error("μ batching proof-of-work witness check failed")]
328 MuPoWInvalid,
329 #[error("folding proof-of-work witness check failed")]
330 FoldingPoWInvalid,
331 #[error("query phase proof-of-work witness check failed")]
332 QueryPhasePoWInvalid,
333 #[error("final polynomial doesn't explain queries")]
334 FinalPolyQueryMismatch,
335 #[error("final poly is not in the final constrained RS code")]
336 FinalPolyConstraint,
337 #[error("merkle verification failed")]
338 MerkleVerify,
339}
340
341pub fn binary_k_fold<F: TwoAdicField, EF: ExtensionField<F>>(
355 mut values: Vec<EF>,
356 alphas: &[EF],
357 x: F,
358) -> EF {
359 let n = values.len();
360 let k = alphas.len();
361 debug_assert_eq!(n, 1 << k);
362
363 let omega_k = F::two_adic_generator(k);
364 let omega_k_inv = omega_k.inverse();
365
366 let tw = omega_k.powers().take(1 << (k - 1)).collect_vec();
367 let inv_tw = omega_k_inv.powers().take(1 << (k - 1)).collect_vec();
368
369 for (j, (&alpha, x_pow, x_inv_pow)) in izip!(
370 alphas.iter(),
371 x.exp_powers_of_2(),
372 x.inverse().exp_powers_of_2()
373 )
374 .enumerate()
375 {
376 let m = n >> (j + 1);
377 let (lo, hi) = values.split_at_mut(m);
378
379 for i in 0..m {
380 let t = tw[i << j] * x_pow;
381 let t_inv = inv_tw[i << j] * x_inv_pow;
382 lo[i] += (alpha - t) * (lo[i] - hi[i]) * t_inv.halve();
383 }
384 }
385 values[0]
386}
387
388pub fn merkle_verify<H: MerkleHasher>(
389 hasher: &H,
390 root: H::Digest,
391 mut idx: u32,
392 leaf_hash: H::Digest,
393 merkle_proof: &[H::Digest],
394) -> Result<(), VerifyWhirError>
395where
396 H::Digest: Eq,
397{
398 let mut cur = leaf_hash;
399 for &sibling in merkle_proof {
400 cur = if idx & 1 == 0 {
401 hasher.compress(cur, sibling)
402 } else {
403 hasher.compress(sibling, cur)
404 };
405 idx >>= 1;
406 }
407 if root != cur {
408 Err(VerifyWhirError::MerkleVerify)
409 } else {
410 Ok(())
411 }
412}