1use std::{iter::zip, marker::PhantomData};
2
3use itertools::Itertools;
4use openvm_circuit::arch::instructions::program::Program;
5use openvm_native_compiler::{
6 conversion::CompilerOptions,
7 ir::{Array, ArrayLike, Builder, Config, DslIr, Ext, ExtConst, Felt, SymbolicExt, Usize, Var},
8 prelude::RVar,
9};
10use openvm_native_compiler_derive::iter_zip;
11use openvm_stark_backend::{
12 air_builders::symbolic::SymbolicExpressionDag,
13 p3_commit::LagrangeSelectors,
14 p3_field::{BasedVectorSpace, PrimeCharacteristicRing, TwoAdicField},
15 p3_matrix::{dense::RowMajorMatrixView, stack::VerticalPair},
16 proof::{AdjacentOpenedValues, Proof},
17 verifier::GenericVerifierConstraintFolder,
18};
19use openvm_stark_sdk::{
20 config::{baby_bear_poseidon2::BabyBearPoseidon2Config, FriParameters},
21 p3_baby_bear::BabyBear,
22};
23
24use crate::{
25 challenger::{duplex::DuplexChallengerVariable, ChallengerVariable},
26 commit::{PcsVariable, PolynomialSpaceVariable},
27 folder::RecursiveVerifierConstraintFolder,
28 fri::{
29 types::{TwoAdicPcsMatsVariable, TwoAdicPcsRoundVariable},
30 TwoAdicFriPcsVariable, TwoAdicMultiplicativeCosetVariable, MAX_TWO_ADICITY,
31 },
32 hints::Hintable,
33 types::{InnerConfig, MultiStarkVerificationAdvice, StarkVerificationAdvice},
34 utils::const_fri_config,
35 vars::{
36 AdjacentOpenedValuesVariable, AirProofDataVariable, CommitmentsVariable,
37 StarkProofVariable, TraceHeightConstraintSystem,
38 },
39 view::get_advice_per_air,
40};
41
42#[cfg(feature = "static-verifier")]
43pub mod outer;
44
45#[derive(Debug, Clone, Copy)]
46pub struct VerifierProgram<C: Config> {
47 _phantom: PhantomData<C>,
48}
49
50impl VerifierProgram<InnerConfig> {
51 pub fn build(
54 constants: MultiStarkVerificationAdvice<InnerConfig>,
55 fri_params: &FriParameters,
56 ) -> Program<BabyBear> {
57 let options = CompilerOptions {
58 enable_cycle_tracker: true,
59 ..Default::default()
60 };
61 Self::build_with_options(constants, fri_params, options)
62 }
63
64 pub fn build_with_options(
67 constants: MultiStarkVerificationAdvice<InnerConfig>,
68 fri_params: &FriParameters,
69 options: CompilerOptions,
70 ) -> Program<BabyBear> {
71 let mut builder = Builder::<InnerConfig>::default();
72
73 builder.cycle_tracker_start("VerifierProgram");
74 builder.cycle_tracker_start("ReadingProofFromInput");
75 let input: StarkProofVariable<_> = builder.uninit();
76 Proof::<BabyBearPoseidon2Config>::witness(&input, &mut builder);
77 builder.cycle_tracker_end("ReadingProofFromInput");
78
79 builder.cycle_tracker_start("InitializePcsConst");
80 let pcs = TwoAdicFriPcsVariable {
81 config: const_fri_config(&mut builder, fri_params),
82 };
83 builder.cycle_tracker_end("InitializePcsConst");
84 StarkVerifier::verify::<DuplexChallengerVariable<_>>(
85 &mut builder,
86 &pcs,
87 &constants,
88 &input,
89 );
90
91 builder.cycle_tracker_end("VerifierProgram");
92 builder.halt();
93
94 builder.compile_isa_with_options(options)
95 }
96}
97
98#[derive(Debug, Clone, Copy)]
99pub struct StarkVerifier<C: Config> {
100 _phantom: PhantomData<C>,
101}
102
103impl<C: Config> StarkVerifier<C>
104where
105 C::F: TwoAdicField,
106{
107 pub fn verify<CH: ChallengerVariable<C>>(
109 builder: &mut Builder<C>,
110 pcs: &TwoAdicFriPcsVariable<C>,
111 m_advice: &MultiStarkVerificationAdvice<C>,
112 proof: &StarkProofVariable<C>,
113 ) {
114 if builder.flags.static_only {
115 let mut challenger = CH::new(builder);
116 Self::verify_raps(builder, pcs, m_advice, &mut challenger, proof);
117 } else {
118 let mut tmp_builder = builder.create_sub_builder();
120 let old_heap_ptr = tmp_builder.load_heap_ptr();
122 let mut challenger = CH::new(&mut tmp_builder);
123 Self::verify_raps(&mut tmp_builder, pcs, m_advice, &mut challenger, proof);
124 tmp_builder.store_heap_ptr(old_heap_ptr);
125 builder.operations.extend(tmp_builder.operations);
126 }
127 }
128
129 pub fn verify_raps(
131 builder: &mut Builder<C>,
132 pcs: &TwoAdicFriPcsVariable<C>,
133 m_advice: &MultiStarkVerificationAdvice<C>,
134 challenger: &mut impl ChallengerVariable<C>,
135 proof: &StarkProofVariable<C>,
136 ) where
137 C::F: TwoAdicField,
138 C::EF: TwoAdicField,
139 {
140 let pre_hash = builder.constant(m_advice.pre_hash.clone());
141 challenger.observe_digest(builder, pre_hash);
142 let air_ids = proof.get_air_ids(builder);
143 let num_airs = cast_usize_to_felt(builder, air_ids.len());
144 challenger.observe(builder, num_airs);
145 iter_zip!(builder, air_ids).for_each(|ptr_vec, builder| {
146 let air_id = builder.iter_ptr_get(&air_ids, ptr_vec[0]);
147 let air_id = cast_usize_to_felt(builder, air_id);
148 challenger.observe(builder, air_id);
149 });
150 let m_advice_var = get_advice_per_air(builder, m_advice, &air_ids);
151 let StarkProofVariable::<C> {
154 commitments,
155 opening,
156 per_air: air_proofs,
157 air_perm_by_height,
158 log_up_pow_witness,
159 } = proof;
160
161 if m_advice.num_challenges_to_sample.len() > 1 {
162 panic!("Only support 0 or 1 phase is supported");
163 }
164
165 let num_airs = RVar::from(air_proofs.len());
166 let num_challenges_to_sample = m_advice_var.num_challenges_to_sample(builder);
167 let num_phases = RVar::from(num_challenges_to_sample.len());
171 builder.range(0, num_airs).for_each(|i_vec, builder| {
174 let i = i_vec[0];
175 let air_proof_data = builder.get(air_proofs, i);
176 let air_advice = builder.get(&m_advice_var.per_air, i);
177 builder.assert_usize_eq(
178 air_proof_data.exposed_values_after_challenge.len(),
179 air_advice.num_exposed_values_after_challenge.len(),
180 );
181 });
182
183 assert_cumulative_sums(builder, air_proofs, &num_challenges_to_sample);
184
185 let air_perm_by_height = if builder.flags.static_only {
186 builder.assert_usize_eq(num_airs, RVar::from(m_advice.per_air.len()));
187 let num_airs = num_airs.value();
188 let perm = (0..num_airs).map(|i| builder.eval(RVar::from(i))).collect();
189 &builder.vec(perm)
190 } else {
191 builder.assert_usize_eq(air_perm_by_height.len(), num_airs);
192 let mask: Array<_, Usize<_>> = builder.dyn_array(num_airs);
194 let zero: Usize<_> = builder.eval(C::N::ZERO);
195 builder.range(0, num_airs).for_each(|i_vec, builder| {
196 builder.set(&mask, i_vec[0], zero.clone());
197 });
198 let one: Usize<_> = builder.eval(C::N::ONE);
199 iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
200 let perm_i = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
201 builder.assert_less_than_slow_small_rhs(perm_i.clone(), num_airs);
202 builder.set_value(&mask, perm_i.clone(), one.clone());
203 });
204 let prev_log_height_plus_one: Usize<_> =
207 builder.eval(RVar::from(MAX_TWO_ADICITY - pcs.config.log_blowup + 1));
208 iter_zip!(builder, air_perm_by_height, mask).for_each(|ptr_vec, builder| {
210 let perm_i = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
211 let mask_i = builder.iter_ptr_get(&mask, ptr_vec[1]);
212 builder.assert_usize_eq(mask_i, one.clone());
213
214 let air_proof = builder.get(air_proofs, perm_i.clone());
215 builder.assert_less_than_slow_small_rhs(
216 air_proof.log_degree.clone(),
217 prev_log_height_plus_one.clone(),
218 );
219 builder.assign(
220 &prev_log_height_plus_one,
221 air_proof.log_degree.clone() + RVar::one(),
222 );
223 });
224 air_perm_by_height
225 };
226 builder.assert_nonzero(&air_proofs.len());
231 let log_max_height = {
232 let index = builder.get(air_perm_by_height, RVar::zero());
233 let air_proof = builder.get(air_proofs, index);
236 RVar::from(air_proof.log_degree.clone())
237 };
238
239 builder.cycle_tracker_start("CheckTraceHeightConstraints");
242 Self::check_trace_height_constraints(
243 builder,
244 &m_advice_var.trace_height_constraint_system,
245 air_proofs,
246 );
247 builder.cycle_tracker_end("CheckTraceHeightConstraints");
248
249 builder.range(0, num_airs).for_each(|i_vec, builder| {
250 let i = i_vec[0];
251 let air_proof_data = builder.get(air_proofs, i);
252 let pvs = air_proof_data.public_values;
253 let air_advice = builder.get(&m_advice_var.per_air, i);
254 builder.assert_usize_eq(air_advice.num_public_values, pvs.len());
255 challenger.observe_slice(builder, pvs);
256 });
257 builder.cycle_tracker_start("stage-c-build-rounds");
260
261 let num_cached_mains: Usize<_> = builder.eval(RVar::zero());
263 let num_common_main_traces: Usize<_> = builder.eval(RVar::zero());
264 let num_after_challenge_traces: Usize<_> = builder.eval(RVar::zero());
265 iter_zip!(builder, m_advice_var.per_air).for_each(|ptr_vec, builder| {
266 let air_advice = builder.iter_ptr_get(&m_advice_var.per_air, ptr_vec[0]);
267 builder
268 .if_eq(air_advice.preprocessed_data.len(), RVar::one())
269 .then(|builder| {
270 let commit = builder.get(&air_advice.preprocessed_data, RVar::zero());
271 challenger.observe_digest(builder, commit);
272 });
273
274 builder.assign(
275 &num_cached_mains,
276 num_cached_mains.clone() + air_advice.width.cached_mains.len(),
277 );
278 builder
279 .if_ne(air_advice.width.common_main, RVar::zero())
280 .then(|builder| {
281 builder.assign(
282 &num_common_main_traces,
283 num_common_main_traces.clone() + RVar::one(),
284 );
285 });
286 builder
287 .if_ne(air_advice.width.after_challenge.len(), RVar::zero())
288 .then(|builder| {
289 builder.assign(
290 &num_after_challenge_traces,
291 num_after_challenge_traces.clone() + RVar::one(),
292 );
293 });
294 });
295 let num_cached_mains = RVar::from(num_cached_mains);
296 let num_common_main_traces = RVar::from(num_common_main_traces);
297 let num_after_challenge_traces = RVar::from(num_after_challenge_traces);
298
299 let num_main_commits: Usize<_> = builder.eval(num_cached_mains + RVar::one());
300 let num_main_commits = RVar::from(num_main_commits);
301
302 let CommitmentsVariable {
303 main_trace: main_trace_commits,
304 after_challenge: after_challenge_commits,
305 quotient: quotient_commit,
306 } = commitments;
307
308 builder.assert_usize_eq(main_trace_commits.len(), num_main_commits);
310 iter_zip!(builder, main_trace_commits).for_each(|ptr_vec, builder| {
312 let main_commit = builder.iter_ptr_get(main_trace_commits, ptr_vec[0]);
313 challenger.observe_digest(builder, main_commit);
314 });
315
316 iter_zip!(builder, air_proofs).for_each(|ptr_vec, builder| {
317 let air_proof = builder.iter_ptr_get(air_proofs, ptr_vec[0]);
318 let log_degree = cast_usize_to_felt(builder, air_proof.log_degree);
319 challenger.observe(builder, log_degree);
320 });
321
322 let challenges_per_phase = builder.array(num_phases);
323
324 builder.if_eq(num_phases, RVar::one()).then(|builder| {
326 challenger.check_witness(builder, m_advice.log_up_pow_bits, *log_up_pow_witness);
328
329 let phase_idx = RVar::zero();
330 let num_to_sample = RVar::from(2);
331 let provided_num_to_sample = builder.get(&num_challenges_to_sample, phase_idx);
332 builder.assert_usize_eq(provided_num_to_sample, num_to_sample);
333
334 let challenges: Array<C, Ext<C::F, C::EF>> = builder.array(num_to_sample);
335 builder.range(0, num_to_sample).for_each(|i_vec, builder| {
337 let challenge = challenger.sample_ext(builder);
338 builder.set_value(&challenges, i_vec[0], challenge);
339 });
340 builder.set_value(&challenges_per_phase, phase_idx, challenges);
341
342 builder.range(0, num_airs).for_each(|j_vec, builder| {
343 let j = j_vec[0];
344 let air_advice = builder.get(&m_advice_var.per_air, j);
345 builder
346 .if_ne(
347 air_advice.num_exposed_values_after_challenge.len(),
348 RVar::zero(),
349 )
350 .then(|builder| {
351 builder.assert_usize_eq(
353 air_advice.num_exposed_values_after_challenge.len(),
354 RVar::one(),
355 );
356 let air_proof_data = builder.get(&proof.per_air, j);
357 let exposed_values = air_proof_data.exposed_values_after_challenge;
358 builder.assert_usize_eq(exposed_values.len(), RVar::one());
359 let values = builder.get(&exposed_values, phase_idx);
360 let values_len =
361 builder.get(&air_advice.num_exposed_values_after_challenge, phase_idx);
362 builder.assert_usize_eq(values_len, values.len());
364
365 iter_zip!(builder, values).for_each(|ptr_vec, builder| {
366 let value = builder.iter_ptr_get(&values, ptr_vec[0]);
367 let felts = builder.ext2felt(value);
368 challenger.observe_slice(builder, felts);
369 });
370 });
371 });
372
373 builder.assert_usize_eq(after_challenge_commits.len(), RVar::one());
375 let commit = builder.get(after_challenge_commits, phase_idx);
376 challenger.observe_digest(builder, commit);
377 });
378
379 let alpha = challenger.sample_ext(builder);
380
381 challenger.observe_digest(builder, quotient_commit.clone());
382
383 challenger.check_witness(builder, m_advice.deep_pow_bits, opening.deep_pow_witness);
384 let zeta = challenger.sample_ext(builder);
385
386 let num_prep_rounds: Usize<_> = builder.eval(RVar::zero());
387
388 let domains = builder.array(num_airs);
390 let quotient_domains = builder.array(num_airs);
391 let trace_points_per_domain = builder.array(num_airs);
392 let quotient_chunk_domains = builder.array(num_airs);
393 let num_quotient_mats: Usize<_> = builder.eval(RVar::zero());
394 builder.range(0, num_airs).for_each(|i_vec, builder| {
395 let i = i_vec[0];
396 let air_proof = builder.get(air_proofs, i);
397 let log_degree: RVar<_> = air_proof.log_degree.clone().into();
398 let advice = builder.get(&m_advice_var.per_air, i);
399
400 let domain = pcs.natural_domain_for_log_degree(builder, log_degree);
402
403 let trace_points = builder.array::<Ext<_, _>>(2);
404 let zeta_next = domain.next_point(builder, zeta);
405 builder.set_value(&trace_points, RVar::zero(), zeta);
406 builder.set_value(&trace_points, RVar::one(), zeta_next);
407
408 let log_quotient_degree = RVar::from(advice.log_quotient_degree);
409 let quotient_degree =
410 RVar::from(builder.sll::<Usize<_>>(RVar::one(), log_quotient_degree));
411 let log_quotient_size = builder.eval_expr(log_degree + log_quotient_degree);
412 let quotient_domain =
416 domain.create_disjoint_domain(builder, log_quotient_size, Some(pcs.config.clone()));
417 builder.set_value("ient_domains, i, quotient_domain.clone());
418
419 let qc_domains =
420 quotient_domain.split_domains(builder, log_quotient_degree, quotient_degree);
421 builder.assign(
422 &num_quotient_mats,
423 num_quotient_mats.clone() + quotient_degree,
424 );
425
426 builder.set_value(&domains, i, domain);
427 builder.set_value(&trace_points_per_domain, i, trace_points);
428 builder.set_value("ient_chunk_domains, i, qc_domains);
429
430 builder
431 .if_eq(advice.preprocessed_data.len(), RVar::one())
432 .then(|builder| {
433 builder.assign(&num_prep_rounds, num_prep_rounds.clone() + RVar::one());
434 });
435 });
436 let num_quotient_mats = RVar::from(num_quotient_mats);
437
438 let num_main_rounds = builder.eval_expr(num_cached_mains + RVar::one());
443 let num_challenge_rounds: RVar<_> = num_challenges_to_sample.len().into();
444 let num_quotient_rounds = RVar::one();
445
446 builder.assert_usize_eq(opening.values.preprocessed.len(), num_prep_rounds.clone());
447 let total_rounds = builder.eval_expr(
450 num_prep_rounds + num_main_rounds + num_challenge_rounds + num_quotient_rounds,
451 );
452
453 let rounds = builder.array::<TwoAdicPcsRoundVariable<_>>(total_rounds);
454 let null_perm = builder.array(0);
456
457 let round_idx: Usize<_> = builder.eval(RVar::zero());
459 builder.range(0, num_airs).for_each(|i_vec, builder| {
460 let i = i_vec[0];
461 let advice = builder.get(&m_advice_var.per_air, i);
462 builder
463 .if_eq(advice.preprocessed_data.len(), RVar::one())
464 .then(|builder| {
465 let prep = builder.get(&opening.values.preprocessed, round_idx.clone());
467 let batch_commit = builder.get(&advice.preprocessed_data, RVar::zero());
468 let prep_width =
469 RVar::from(builder.get(&advice.width.preprocessed, RVar::zero()));
470
471 let domain = builder.get(&domains, i);
472 let trace_points = builder.get(&trace_points_per_domain, i);
473
474 builder.assert_usize_eq(prep_width, prep.local.len());
475 builder.assert_usize_eq(prep_width, prep.next.len());
476 let values = builder.array::<Array<C, _>>(2);
479 builder.set_value(&values, 0, prep.local);
480 builder.set_value(&values, 1, prep.next);
481 let prep_mat = TwoAdicPcsMatsVariable::<C> {
482 domain,
483 values,
484 points: trace_points.clone(),
485 };
486
487 let mats: Array<_, TwoAdicPcsMatsVariable<_>> = builder.array(1);
488 builder.set_value(&mats, 0, prep_mat);
489
490 builder.set_value(
491 &rounds,
492 round_idx.clone(),
493 TwoAdicPcsRoundVariable {
494 batch_commit,
495 mats,
496 permutation: null_perm.clone(),
497 },
498 );
499 builder.assign(&round_idx, round_idx.clone() + RVar::one());
500 });
501 });
502
503 let main_commit_idx: Usize<_> = builder.eval(RVar::zero());
505 builder.assert_usize_eq(opening.values.main.len(), num_main_commits);
506 let common_main_values_per_mat = builder.get(&opening.values.main, num_cached_mains);
507 builder.assert_usize_eq(common_main_values_per_mat.len(), num_common_main_traces);
508 builder.assert_usize_eq(num_common_main_traces, num_airs);
509 let common_main_mats = builder.array(num_common_main_traces);
510 let common_main_matrix_idx: Usize<_> = builder.eval(RVar::zero());
511 builder.range(0, num_airs).for_each(|i_vec, builder| {
512 let i = i_vec[0];
513 let advice = builder.get(&m_advice_var.per_air, i);
514 let cached_main_widths = &advice.width.cached_mains;
515
516 let domain = builder.get(&domains, i);
517 let trace_points = builder.get(&trace_points_per_domain, i);
518
519 iter_zip!(builder, cached_main_widths).for_each(|ptr_vec, builder| {
520 let cached_main_width = builder.iter_ptr_get(cached_main_widths, ptr_vec[0]);
521 let values_per_mat = builder.get(&opening.values.main, main_commit_idx.clone());
522 let batch_commit = builder.get(main_trace_commits, main_commit_idx.clone());
523 builder.assign(&main_commit_idx, main_commit_idx.clone() + RVar::one());
524
525 builder.assert_usize_eq(values_per_mat.len(), RVar::one());
526 let main = builder.get(&values_per_mat, RVar::zero());
527 let values = builder.array::<Array<C, _>>(2);
528 builder.assert_usize_eq(main.local.len(), cached_main_width.clone());
529 builder.assert_usize_eq(main.next.len(), cached_main_width);
530 builder.set_value(&values, 0, main.local);
531 builder.set_value(&values, 1, main.next);
532 let main_mat = TwoAdicPcsMatsVariable::<C> {
533 domain: domain.clone(),
534 values,
535 points: trace_points.clone(),
536 };
537 let mats = builder.array(1);
538 builder.set_value(&mats, 0, main_mat);
539
540 builder.set_value(
541 &rounds,
542 round_idx.clone(),
543 TwoAdicPcsRoundVariable {
544 batch_commit,
545 mats,
546 permutation: null_perm.clone(),
547 },
548 );
549 builder.assign(&round_idx, round_idx.clone() + RVar::one());
550 });
551
552 let common_main_width = RVar::from(advice.width.common_main);
553 builder
554 .if_ne(common_main_width, RVar::zero())
555 .then(|builder| {
556 let main =
561 builder.get(&common_main_values_per_mat, common_main_matrix_idx.clone());
562
563 let values = builder.array::<Array<C, _>>(2);
564 builder.assert_usize_eq(main.local.len(), common_main_width);
565 builder.assert_usize_eq(main.next.len(), common_main_width);
566 builder.set_value(&values, 0, main.local);
567 builder.set_value(&values, 1, main.next);
568 let main_mat = TwoAdicPcsMatsVariable::<C> {
569 domain: domain.clone(),
570 values,
571 points: trace_points.clone(),
572 };
573 builder.set_value(&common_main_mats, common_main_matrix_idx.clone(), main_mat);
574 builder.assign(
575 &common_main_matrix_idx,
576 common_main_matrix_idx.clone() + RVar::one(),
577 );
578 });
579 });
580 {
582 let batch_commit = builder.get(main_trace_commits, main_commit_idx.clone());
583 builder.set_value(
584 &rounds,
585 round_idx.clone(),
586 TwoAdicPcsRoundVariable {
587 batch_commit,
588 mats: common_main_mats,
589 permutation: air_perm_by_height.clone(),
590 },
591 );
592 builder.assign(&round_idx, round_idx.clone() + RVar::one());
593 }
594
595 builder.assert_usize_eq(opening.values.after_challenge.len(), num_phases);
597 builder.if_eq(num_phases, RVar::one()).then(|builder| {
598 builder.assert_usize_eq(num_after_challenge_traces, num_airs);
599 });
600 builder
601 .range(0, num_phases)
602 .for_each(|phase_idx_vec, builder| {
603 let phase_idx = phase_idx_vec[0];
604 let values_per_mat = builder.get(&opening.values.after_challenge, phase_idx);
605 builder.assert_usize_eq(values_per_mat.len(), num_after_challenge_traces);
606 let batch_commit = builder.get(after_challenge_commits, phase_idx);
607
608 let mat_idx: Usize<_> = builder.eval(RVar::zero());
609 let mats: Array<_, TwoAdicPcsMatsVariable<_>> =
610 builder.array(num_after_challenge_traces);
611 builder.range(0, num_airs).for_each(|i_vec, builder| {
612 let i = i_vec[0];
613 let advice = builder.get(&m_advice_var.per_air, i);
614 builder
615 .if_ne(advice.width.after_challenge.len(), RVar::zero())
616 .then(|builder| {
617 builder
619 .assert_usize_eq(advice.width.after_challenge.len(), RVar::one());
620 let after_challenge_width = RVar::from(
621 builder.get(&advice.width.after_challenge, RVar::zero()),
622 );
623 let domain = builder.get(&domains, i);
624 let trace_points = builder.get(&trace_points_per_domain, i);
625
626 let after_challenge = builder.get(&values_per_mat, mat_idx.clone());
627
628 let values = builder.array::<Array<C, _>>(2);
629 builder.assert_usize_eq(
630 after_challenge.local.len(),
631 after_challenge_width * RVar::from(C::EF::DIMENSION),
632 );
633 builder.assert_usize_eq(
634 after_challenge.next.len(),
635 after_challenge.local.len(),
636 );
637 builder.set_value(&values, 0, after_challenge.local);
638 builder.set_value(&values, 1, after_challenge.next);
639 let after_challenge_mat = TwoAdicPcsMatsVariable::<C> {
640 domain,
641 values,
642 points: trace_points,
643 };
644 builder.set_value(&mats, mat_idx.clone(), after_challenge_mat);
645 builder.inc(&mat_idx);
646 });
647 });
648 builder.assert_usize_eq(mat_idx, num_after_challenge_traces);
649
650 builder.set_value(
651 &rounds,
652 round_idx.clone(),
653 TwoAdicPcsRoundVariable {
654 batch_commit,
655 mats,
656 permutation: air_perm_by_height.clone(),
657 },
658 );
659 builder.assign(&round_idx, round_idx.clone() + RVar::one());
660 });
661 let quotient_perm = builder.array(num_quotient_mats);
674 let perm_offset_per_air = builder.array::<Usize<_>>(num_airs);
675 let offset: Usize<_> = builder.eval(RVar::zero());
676 iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
677 let air_index = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
678 builder.set(&perm_offset_per_air, air_index.clone(), offset.clone());
679 let qc_domains = builder.get("ient_chunk_domains, air_index);
680 builder.assign(&offset, offset.clone() + qc_domains.len());
681 });
682
683 let quotient_mats: Array<_, TwoAdicPcsMatsVariable<_>> = builder.array(num_quotient_mats);
684 let qc_points = builder.array::<Ext<_, _>>(1);
685 builder.set_value(&qc_points, 0, zeta);
686
687 let qc_index: Usize<_> = builder.eval(RVar::zero());
688 builder.assert_usize_eq(opening.values.quotient.len(), num_airs);
689 builder.range(0, num_airs).for_each(|i_vec, builder| {
690 let i = i_vec[0];
691 let opened_quotient = builder.get(&opening.values.quotient, i);
692 let qc_domains = builder.get("ient_chunk_domains, i);
693 let air_offset = builder.get(&perm_offset_per_air, i);
694
695 builder.assert_usize_eq(opened_quotient.len(), qc_domains.len());
696 let quotient_degree = qc_domains.len();
697 builder
698 .range(0, quotient_degree)
699 .for_each(|j_vec, builder| {
700 let j = j_vec[0];
701 let qc_dom = builder.get(&qc_domains, j);
702 let qc_vals_array = builder.get(&opened_quotient, j);
703 builder.assert_usize_eq(qc_vals_array.len(), RVar::from(4));
704 let qc_values = builder.array::<Array<C, _>>(1);
705 builder.set_value(&qc_values, 0, qc_vals_array);
706 let qc_mat = TwoAdicPcsMatsVariable::<C> {
707 domain: qc_dom,
708 values: qc_values,
709 points: qc_points.clone(),
710 };
711 let qc_offset = builder.eval_expr(air_offset.clone() + j);
712 builder.set_value("ient_mats, qc_index.clone(), qc_mat);
713 builder.set("ient_perm, qc_offset, RVar::from(qc_index.clone()));
714 builder.assign(&qc_index, qc_index.clone() + RVar::one());
715 });
716 });
717 let quotient_round = TwoAdicPcsRoundVariable {
718 batch_commit: quotient_commit.clone(),
719 mats: quotient_mats,
720 permutation: quotient_perm,
721 };
722 builder.set_value(&rounds, round_idx.clone(), quotient_round);
723 builder.assign(&round_idx, round_idx.clone() + RVar::one());
724
725 builder.assert_usize_eq(round_idx, total_rounds);
727
728 builder.cycle_tracker_end("stage-c-build-rounds");
729
730 builder.cycle_tracker_start("stage-d-verify-pcs");
732 pcs.verify(
733 builder,
734 rounds,
735 opening.proof.clone(),
736 log_max_height,
737 challenger,
738 );
739 builder.cycle_tracker_end("stage-d-verify-pcs");
740
741 builder.cycle_tracker_start("stage-e-verify-constraints");
742 let after_challenge_idx: Usize<C::N> = builder.eval(C::N::ZERO);
743 let preprocessed_idx: Usize<_> = builder.eval(C::N::ZERO);
744 let cached_main_commit_idx: Usize<_> = builder.eval(C::N::ZERO);
745 let common_main_matrix_idx: Usize<_> = builder.eval(C::N::ZERO);
746 let air_idx: Usize<_> = builder.eval(C::N::ZERO);
747 let common_main_openings = builder.get(&opening.values.main, num_cached_mains);
748
749 let challenges = m_advice
751 .num_challenges_to_sample
752 .iter()
753 .enumerate()
754 .map(|(phase, &num_challenges_to_sample)| {
755 (0..num_challenges_to_sample)
756 .map(|i| {
757 let challenge: Ext<_, _> = builder.constant(C::EF::ZERO);
758 builder
759 .if_eq(
760 m_advice_var.num_challenges_to_sample_mask[phase][i].clone(),
761 RVar::one(),
762 )
763 .then(|builder| {
764 let chs = builder.get(&challenges_per_phase, phase);
765 let v = builder.get(&chs, i);
766 builder.assign(&challenge, v);
767 });
768 challenge
769 })
770 .collect_vec()
771 })
772 .collect_vec();
773
774 for (i, air_const) in m_advice.per_air.iter().enumerate() {
775 builder
776 .if_ne(air_idx.clone(), air_ids.len())
777 .then(|builder| {
778 let abs_air_idx = builder.get(&air_ids, air_idx.clone());
779 builder.if_eq(abs_air_idx, RVar::from(i)).then(|builder| {
780 let preprocessed_values = if air_const.preprocessed_data.is_some() {
781 let ret = Some(
782 builder.get(&opening.values.preprocessed, preprocessed_idx.clone()),
783 );
784 builder.inc(&preprocessed_idx);
785 ret
786 } else {
787 None
788 };
789 let mut partitioned_main_values = (0..air_const.width.cached_mains.len())
790 .map(|_| {
791 let ret = builder
792 .get(&opening.values.main, cached_main_commit_idx.clone());
793 builder.inc(&cached_main_commit_idx);
794 builder.get(&ret, 0)
795 })
796 .collect_vec();
797 if air_const.width.common_main > 0 {
798 let common_main =
802 builder.get(&common_main_openings, common_main_matrix_idx.clone());
803 builder.inc(&common_main_matrix_idx);
804 partitioned_main_values.push(common_main);
805 }
806
807 let after_challenge_values = if air_const.width.after_challenge.is_empty() {
808 AdjacentOpenedValuesVariable {
809 local: builder.vec(vec![]),
810 next: builder.vec(vec![]),
811 }
812 } else {
813 let after_challenge_values =
815 builder.get(&opening.values.after_challenge, 0);
816 let after_challenge_values =
817 builder.get(&after_challenge_values, after_challenge_idx.clone());
818 builder.inc(&after_challenge_idx);
819 after_challenge_values
820 };
821 let trace_domain = builder.get(&domains, air_idx.clone());
822 let quotient_domain: TwoAdicMultiplicativeCosetVariable<_> =
823 builder.get("ient_domains, air_idx.clone());
824 let log_quotient_degree = air_const.log_quotient_degree();
826 let quotient_chunks =
827 builder.get(&opening.values.quotient, air_idx.clone());
828
829 let qc_domains =
831 quotient_domain.split_domains_const(builder, log_quotient_degree);
832 let air_proof = builder.get(air_proofs, air_idx.clone());
833 let pvs = (0..air_const.num_public_values)
834 .map(|x| builder.get(&air_proof.public_values, x))
835 .collect_vec();
836
837 let exposed_values = air_const
838 .num_exposed_values_after_challenge
839 .iter()
840 .enumerate()
841 .map(|(phase, &num_exposed)| {
842 let exposed_values =
843 builder.get(&air_proof.exposed_values_after_challenge, phase);
844 (0..num_exposed)
845 .map(|j| builder.get(&exposed_values, j))
846 .collect_vec()
847 })
848 .collect_vec();
849
850 Self::verify_single_rap_constraints(
851 builder,
852 air_const,
853 preprocessed_values,
854 &partitioned_main_values,
855 quotient_chunks,
856 &pvs,
857 trace_domain,
858 qc_domains,
859 zeta,
860 alpha,
861 after_challenge_values,
862 &challenges,
863 &exposed_values,
864 );
865
866 builder.inc(&air_idx);
867 });
868 });
869 }
870 builder.assert_usize_eq(air_idx, air_ids.len());
872
873 builder.cycle_tracker_end("stage-e-verify-constraints");
874 }
875
876 fn check_trace_height_constraints(
887 builder: &mut Builder<C>,
888 constraint_system: &TraceHeightConstraintSystem<C>,
889 air_proofs: &Array<C, AirProofDataVariable<C>>,
890 ) {
891 let values: Vec<Var<C::N>> = (0..constraint_system.height_constraints.len())
893 .map(|_| builder.eval(C::N::ZERO))
894 .collect();
895
896 let assert_less_than = |builder: &mut Builder<C>, a, b| {
897 if builder.flags.static_only {
898 builder.push(DslIr::CircuitLessThan(a, b));
899 } else {
900 builder.assert_less_than_slow_bit_decomp(a, b);
901 }
902 };
903
904 let pows_of_two: Array<C, Var<C::N>> = builder.array(MAX_TWO_ADICITY);
907 let cur: Var<C::N> = builder.constant(C::N::ONE);
908 iter_zip!(builder, pows_of_two).for_each(|ptr_vec, builder| {
909 builder.iter_ptr_set(&pows_of_two, ptr_vec[0], cur);
910 builder.assign(&cur, cur + cur);
911 });
912
913 iter_zip!(builder, air_proofs).for_each(|ptr_vec, builder| {
914 let air_proof_data = builder.iter_ptr_get(air_proofs, ptr_vec[0]);
915
916 let height = builder.get(&pows_of_two, air_proof_data.log_degree);
917 let height_max = builder.get(
918 &constraint_system.height_maxes,
919 air_proof_data.air_id.clone(),
920 );
921 builder
922 .if_eq(height_max.is_some, C::N::ONE)
923 .then(|builder| {
924 assert_less_than(builder, height, height_max.value);
925 });
926
927 for (i, constraint) in constraint_system.height_constraints.iter().enumerate() {
928 let coeff = builder.get(&constraint.coefficients, air_proof_data.air_id.clone());
929
930 let new_value: Var<C::N> = builder.eval(values[i] + coeff * height);
931 let new_value_plus_one: Var<C::N> = builder.eval(new_value + C::N::ONE);
932 assert_less_than(builder, values[i], new_value_plus_one);
933 builder.assign(&values[i], new_value);
934 }
935 });
936 for (value, constraint) in zip(values, &constraint_system.height_constraints) {
937 if builder.flags.static_only || !constraint.is_threshold_at_p {
938 assert_less_than(builder, value, constraint.threshold);
939 }
940 }
941 }
942
943 #[allow(clippy::too_many_arguments)]
945 #[allow(clippy::type_complexity)]
946 pub fn verify_single_rap_constraints(
947 builder: &mut Builder<C>,
948 constants: &StarkVerificationAdvice<C>,
949 preprocessed_values: Option<AdjacentOpenedValuesVariable<C>>,
950 partitioned_main_values: &[AdjacentOpenedValuesVariable<C>],
951 quotient_chunks: Array<C, Array<C, Ext<C::F, C::EF>>>,
952 public_values: &[Felt<C::F>],
953 trace_domain: TwoAdicMultiplicativeCosetVariable<C>,
954 qc_domains: Vec<TwoAdicMultiplicativeCosetVariable<C>>,
955 zeta: Ext<C::F, C::EF>,
956 alpha: Ext<C::F, C::EF>,
957 after_challenge_values: AdjacentOpenedValuesVariable<C>,
958 challenges: &[Vec<Ext<C::F, C::EF>>],
959 exposed_values_after_challenge: &[Vec<Ext<C::F, C::EF>>],
960 ) {
961 let sels = trace_domain.selectors_at_point(builder, zeta);
962
963 let mut preprocessed = AdjacentOpenedValues {
964 local: vec![],
965 next: vec![],
966 };
967 if let Some(preprocessed_values) = preprocessed_values {
968 for i in 0..constants.width.preprocessed.unwrap() {
969 preprocessed
970 .local
971 .push(builder.get(&preprocessed_values.local, i));
972 preprocessed
973 .next
974 .push(builder.get(&preprocessed_values.next, i));
975 }
976 }
977
978 let main_widths = constants.width.main_widths();
979 assert_eq!(partitioned_main_values.len(), main_widths.len());
980 let partitioned_main_values = partitioned_main_values
981 .iter()
982 .zip_eq(main_widths.iter())
983 .map(|(main_values, &width)| {
984 builder.assert_usize_eq(main_values.local.len(), RVar::from(width));
985 builder.assert_usize_eq(main_values.next.len(), RVar::from(width));
986 let mut main = AdjacentOpenedValues {
987 local: vec![],
988 next: vec![],
989 };
990 for i in 0..width {
991 main.local.push(builder.get(&main_values.local, i));
992 main.next.push(builder.get(&main_values.next, i));
993 }
994 main
995 })
996 .collect_vec();
997
998 let mut after_challenge = AdjacentOpenedValues {
999 local: vec![],
1000 next: vec![],
1001 };
1002
1003 let after_challenge_width = if constants.width.after_challenge.is_empty() {
1004 0
1005 } else {
1006 C::EF::DIMENSION * constants.width.after_challenge[0]
1007 };
1008 builder.assert_usize_eq(
1009 after_challenge_values.local.len(),
1010 RVar::from(after_challenge_width),
1011 );
1012 builder.assert_usize_eq(
1013 after_challenge_values.next.len(),
1014 RVar::from(after_challenge_width),
1015 );
1016 for i in 0..after_challenge_width {
1017 after_challenge
1018 .local
1019 .push(builder.get(&after_challenge_values.local, i));
1020 after_challenge
1021 .next
1022 .push(builder.get(&after_challenge_values.next, i));
1023 }
1024
1025 let folded_constraints = Self::eval_constraints(
1026 builder,
1027 &constants.symbolic_constraints,
1028 preprocessed,
1029 &partitioned_main_values,
1030 public_values,
1031 &sels,
1032 alpha,
1033 after_challenge,
1034 challenges,
1035 exposed_values_after_challenge,
1036 );
1037
1038 let num_quotient_chunks = 1 << constants.log_quotient_degree();
1039 let mut quotient = vec![];
1040 builder.assert_usize_eq(quotient_chunks.len(), RVar::from(num_quotient_chunks));
1042 for i in 0..num_quotient_chunks {
1044 let chunk = builder.get("ient_chunks, i);
1045 builder.assert_usize_eq(RVar::from(C::EF::DIMENSION), RVar::from(chunk.len()));
1047 let mut quotient_vals = vec![];
1049 for j in 0..C::EF::DIMENSION {
1050 let value = builder.get(&chunk, j);
1051 quotient_vals.push(value);
1052 }
1053 quotient.push(quotient_vals);
1054 }
1055
1056 let quotient: Ext<_, _> = Self::recompute_quotient(builder, "ient, qc_domains, zeta);
1057
1058 builder.assert_ext_eq(folded_constraints * sels.inv_vanishing, quotient);
1060 }
1061
1062 #[allow(clippy::too_many_arguments)]
1063 fn eval_constraints(
1064 builder: &mut Builder<C>,
1065 constraints: &SymbolicExpressionDag<C::F>,
1066 preprocessed_values: AdjacentOpenedValues<Ext<C::F, C::EF>>,
1067 partitioned_main_values: &[AdjacentOpenedValues<Ext<C::F, C::EF>>],
1068 public_values: &[Felt<C::F>],
1069 selectors: &LagrangeSelectors<Ext<C::F, C::EF>>,
1070 alpha: Ext<C::F, C::EF>,
1071 after_challenge: AdjacentOpenedValues<Ext<C::F, C::EF>>,
1072 challenges: &[Vec<Ext<C::F, C::EF>>],
1073 exposed_values_after_challenge: &[Vec<Ext<C::F, C::EF>>],
1074 ) -> Ext<C::F, C::EF> {
1075 let mut unflatten = |v: &[Ext<C::F, C::EF>]| {
1076 v.chunks_exact(C::EF::DIMENSION)
1077 .map(|chunk| {
1078 builder.eval(
1079 chunk
1080 .iter()
1081 .enumerate()
1082 .map(|(e_i, &x)| {
1083 x * C::EF::ith_basis_element(e_i)
1084 .expect("basis element index out of bounds")
1085 .cons()
1086 })
1087 .sum::<SymbolicExt<_, _>>(),
1088 )
1089 })
1090 .collect::<Vec<Ext<_, _>>>()
1091 };
1092
1093 let after_challenge_values = AdjacentOpenedValues {
1094 local: unflatten(&after_challenge.local),
1095 next: unflatten(&after_challenge.next),
1096 };
1097
1098 let mut folder: RecursiveVerifierConstraintFolder<C> = GenericVerifierConstraintFolder {
1099 preprocessed: VerticalPair::new(
1100 RowMajorMatrixView::new_row(&preprocessed_values.local),
1101 RowMajorMatrixView::new_row(&preprocessed_values.next),
1102 ),
1103 partitioned_main: partitioned_main_values
1104 .iter()
1105 .map(|main_values| {
1106 VerticalPair::new(
1107 RowMajorMatrixView::new_row(&main_values.local),
1108 RowMajorMatrixView::new_row(&main_values.next),
1109 )
1110 })
1111 .collect(),
1112 after_challenge: vec![VerticalPair::new(
1113 RowMajorMatrixView::new_row(&after_challenge_values.local),
1114 RowMajorMatrixView::new_row(&after_challenge_values.next),
1115 )],
1116 challenges,
1117 is_first_row: selectors.is_first_row,
1118 is_last_row: selectors.is_last_row,
1119 is_transition: selectors.is_transition,
1120 alpha,
1121 accumulator: SymbolicExt::ZERO,
1122 public_values,
1123 exposed_values_after_challenge,
1124 _marker: PhantomData,
1125 };
1126 folder.eval_constraints(constraints);
1127
1128 builder.eval(folder.accumulator)
1129 }
1130
1131 fn recompute_quotient(
1132 builder: &mut Builder<C>,
1133 quotient_chunks: &[Vec<Ext<C::F, C::EF>>],
1134 qc_domains: Vec<TwoAdicMultiplicativeCosetVariable<C>>,
1135 zeta: Ext<C::F, C::EF>,
1136 ) -> Ext<C::F, C::EF> {
1137 let zps = qc_domains
1138 .iter()
1139 .enumerate()
1140 .map(|(i, domain)| {
1141 qc_domains
1142 .iter()
1143 .enumerate()
1144 .filter(|(j, _)| *j != i)
1145 .map(|(_, other_domain)| {
1146 let first_point: Ext<_, _> = builder.eval(domain.first_point());
1147 other_domain.vanishing_poly_at_point(builder, zeta)
1148 * other_domain
1149 .vanishing_poly_at_point(builder, first_point)
1150 .inverse()
1151 })
1152 .product::<SymbolicExt<_, _>>()
1153 })
1154 .collect::<Vec<SymbolicExt<_, _>>>()
1155 .into_iter()
1156 .map(|x| builder.eval(x))
1157 .collect::<Vec<Ext<_, _>>>();
1158
1159 builder.eval(
1160 quotient_chunks
1161 .iter()
1162 .enumerate()
1163 .map(|(ch_i, ch)| {
1164 assert_eq!(ch.len(), C::EF::DIMENSION);
1165 ch.iter()
1166 .enumerate()
1167 .map(|(e_i, &c)| {
1168 zps[ch_i]
1169 * C::EF::ith_basis_element(e_i)
1170 .expect("basis element index out of bounds")
1171 * c
1172 })
1173 .sum::<SymbolicExt<_, _>>()
1174 })
1175 .sum::<SymbolicExt<_, _>>(),
1176 )
1177 }
1178}
1179
1180fn assert_cumulative_sums<C: Config>(
1181 builder: &mut Builder<C>,
1182 air_proofs: &Array<C, AirProofDataVariable<C>>,
1183 num_challenges_to_sample: &Array<C, Usize<C::N>>,
1184) {
1185 let num_phase = num_challenges_to_sample.len();
1186 builder.if_eq(num_phase, RVar::one()).then(|builder| {
1188 let cumulative_sum: Ext<C::F, C::EF> = builder.eval(C::F::ZERO);
1189 builder
1190 .range(0, air_proofs.len())
1191 .for_each(|i_vec, builder| {
1192 let i = i_vec[0];
1193 let air_proof_input = builder.get(air_proofs, i);
1194 let exposed_values = air_proof_input.exposed_values_after_challenge;
1195
1196 builder
1197 .if_ne(exposed_values.len(), RVar::zero())
1198 .then(|builder| {
1199 builder.assert_usize_eq(exposed_values.len(), RVar::one());
1201 let values = builder.get(&exposed_values, RVar::zero());
1202 builder.assert_usize_eq(values.len(), RVar::one());
1204
1205 let summand = builder.get(&values, RVar::zero());
1206 builder.assign(&cumulative_sum, cumulative_sum + summand);
1207 });
1208 });
1209 builder.assert_ext_eq(cumulative_sum, C::EF::ZERO.cons());
1210 });
1211}
1212
1213fn cast_usize_to_felt<C: Config>(builder: &mut Builder<C>, val: Usize<C::N>) -> Felt<C::F>
1216where
1217 C::F: TwoAdicField,
1218{
1219 if builder.flags.static_only {
1220 builder.eval(C::F::from_usize(val.value()))
1221 } else {
1222 builder.unsafe_cast_var_to_felt(val.get_var())
1223 }
1224}