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 assert_cumulative_sums(builder, air_proofs, &num_challenges_to_sample);
174
175 let air_perm_by_height = if builder.flags.static_only {
176 builder.assert_usize_eq(num_airs, RVar::from(m_advice.per_air.len()));
177 let num_airs = num_airs.value();
178 let perm = (0..num_airs).map(|i| builder.eval(RVar::from(i))).collect();
179 &builder.vec(perm)
180 } else {
181 builder.assert_usize_eq(air_perm_by_height.len(), num_airs);
182 let mask: Array<_, Usize<_>> = builder.dyn_array(num_airs);
184 let one: Usize<_> = builder.eval(C::N::ONE);
185 iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
186 let perm_i = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
187 builder.assert_less_than_slow_small_rhs(perm_i.clone(), num_airs);
188 builder.set_value(&mask, perm_i.clone(), one.clone());
189 });
190 let prev_log_height_plus_one: Usize<_> =
193 builder.eval(RVar::from(MAX_TWO_ADICITY - pcs.config.log_blowup + 1));
194 iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
195 let perm_i = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
196 let mask_i = builder.get(&mask, perm_i.clone());
197 builder.assert_usize_eq(mask_i, one.clone());
198
199 let air_proof = builder.get(air_proofs, perm_i.clone());
200 builder.assert_less_than_slow_small_rhs(
201 air_proof.log_degree.clone(),
202 prev_log_height_plus_one.clone(),
203 );
204 builder.assign(
205 &prev_log_height_plus_one,
206 air_proof.log_degree.clone() + RVar::one(),
207 );
208 });
209 air_perm_by_height
210 };
211 let log_max_height = {
216 let index = builder.get(air_perm_by_height, RVar::zero());
217 let air_proof = builder.get(air_proofs, index);
218 RVar::from(air_proof.log_degree.clone())
219 };
220
221 builder.cycle_tracker_start("CheckTraceHeightConstraints");
224 Self::check_trace_height_constraints(
225 builder,
226 &m_advice_var.trace_height_constraint_system,
227 air_proofs,
228 );
229 builder.cycle_tracker_end("CheckTraceHeightConstraints");
230
231 builder.range(0, num_airs).for_each(|i_vec, builder| {
232 let i = i_vec[0];
233 let air_proof_data = builder.get(air_proofs, i);
234 let pvs = air_proof_data.public_values;
235 let air_advice = builder.get(&m_advice_var.per_air, i);
236 builder.assert_usize_eq(air_advice.num_public_values, pvs.len());
237 challenger.observe_slice(builder, pvs);
238 });
239 builder.cycle_tracker_start("stage-c-build-rounds");
242
243 let num_cached_mains: Usize<_> = builder.eval(RVar::zero());
245 let num_common_main_traces: Usize<_> = builder.eval(RVar::zero());
246 let num_after_challenge_traces: Usize<_> = builder.eval(RVar::zero());
247 iter_zip!(builder, m_advice_var.per_air).for_each(|ptr_vec, builder| {
248 let air_advice = builder.iter_ptr_get(&m_advice_var.per_air, ptr_vec[0]);
249 builder
250 .if_eq(air_advice.preprocessed_data.len(), RVar::one())
251 .then(|builder| {
252 let commit = builder.get(&air_advice.preprocessed_data, RVar::zero());
253 challenger.observe_digest(builder, commit);
254 });
255
256 builder.assign(
257 &num_cached_mains,
258 num_cached_mains.clone() + air_advice.width.cached_mains.len(),
259 );
260 builder
261 .if_ne(air_advice.width.common_main, RVar::zero())
262 .then(|builder| {
263 builder.assign(
264 &num_common_main_traces,
265 num_common_main_traces.clone() + RVar::one(),
266 );
267 });
268 builder
269 .if_ne(air_advice.width.after_challenge.len(), RVar::zero())
270 .then(|builder| {
271 builder.assign(
272 &num_after_challenge_traces,
273 num_after_challenge_traces.clone() + RVar::one(),
274 );
275 });
276 });
277 let num_cached_mains = RVar::from(num_cached_mains);
278 let num_common_main_traces = RVar::from(num_common_main_traces);
279 let num_after_challenge_traces = RVar::from(num_after_challenge_traces);
280
281 let num_main_commits: Usize<_> = builder.eval(num_cached_mains + RVar::one());
282 let num_main_commits = RVar::from(num_main_commits);
283
284 let CommitmentsVariable {
285 main_trace: main_trace_commits,
286 after_challenge: after_challenge_commits,
287 quotient: quotient_commit,
288 } = commitments;
289
290 builder.assert_usize_eq(main_trace_commits.len(), num_main_commits);
292 iter_zip!(builder, main_trace_commits).for_each(|ptr_vec, builder| {
294 let main_commit = builder.iter_ptr_get(main_trace_commits, ptr_vec[0]);
295 challenger.observe_digest(builder, main_commit);
296 });
297
298 iter_zip!(builder, air_proofs).for_each(|ptr_vec, builder| {
299 let air_proof = builder.iter_ptr_get(air_proofs, ptr_vec[0]);
300 let log_degree = cast_usize_to_felt(builder, air_proof.log_degree);
301 challenger.observe(builder, log_degree);
302 });
303
304 let challenges_per_phase = builder.array(num_phases);
305
306 builder.if_eq(num_phases, RVar::one()).then(|builder| {
308 challenger.check_witness(builder, m_advice.log_up_pow_bits, *log_up_pow_witness);
310
311 let phase_idx = RVar::zero();
312 let num_to_sample = RVar::from(2);
313 let provided_num_to_sample = builder.get(&num_challenges_to_sample, phase_idx);
314 builder.assert_usize_eq(provided_num_to_sample, num_to_sample);
315
316 let challenges: Array<C, Ext<C::F, C::EF>> = builder.array(num_to_sample);
317 builder.range(0, num_to_sample).for_each(|i_vec, builder| {
319 let challenge = challenger.sample_ext(builder);
320 builder.set_value(&challenges, i_vec[0], challenge);
321 });
322 builder.set_value(&challenges_per_phase, phase_idx, challenges);
323
324 builder.range(0, num_airs).for_each(|j_vec, builder| {
325 let j = j_vec[0];
326 let air_advice = builder.get(&m_advice_var.per_air, j);
327 builder
328 .if_ne(
329 air_advice.num_exposed_values_after_challenge.len(),
330 RVar::zero(),
331 )
332 .then(|builder| {
333 builder.assert_usize_eq(
335 air_advice.num_exposed_values_after_challenge.len(),
336 RVar::one(),
337 );
338 let air_proof_data = builder.get(&proof.per_air, j);
339 let exposed_values = air_proof_data.exposed_values_after_challenge;
340 let values = builder.get(&exposed_values, phase_idx);
341 let values_len =
342 builder.get(&air_advice.num_exposed_values_after_challenge, phase_idx);
343 builder.assert_usize_eq(values_len, values.len());
345
346 iter_zip!(builder, values).for_each(|ptr_vec, builder| {
347 let value = builder.iter_ptr_get(&values, ptr_vec[0]);
348 let felts = builder.ext2felt(value);
349 challenger.observe_slice(builder, felts);
350 });
351 });
352 });
353
354 builder.assert_usize_eq(after_challenge_commits.len(), RVar::one());
356 let commit = builder.get(after_challenge_commits, phase_idx);
357 challenger.observe_digest(builder, commit);
358 });
359
360 let alpha = challenger.sample_ext(builder);
361
362 challenger.observe_digest(builder, quotient_commit.clone());
363
364 challenger.check_witness(builder, m_advice.deep_pow_bits, opening.deep_pow_witness);
365 let zeta = challenger.sample_ext(builder);
366
367 let num_prep_rounds: Usize<_> = builder.eval(RVar::zero());
368
369 let domains = builder.array(num_airs);
371 let quotient_domains = builder.array(num_airs);
372 let trace_points_per_domain = builder.array(num_airs);
373 let quotient_chunk_domains = builder.array(num_airs);
374 let num_quotient_mats: Usize<_> = builder.eval(RVar::zero());
375 builder.range(0, num_airs).for_each(|i_vec, builder| {
376 let i = i_vec[0];
377 let air_proof = builder.get(air_proofs, i);
378 let log_degree: RVar<_> = air_proof.log_degree.clone().into();
379 let advice = builder.get(&m_advice_var.per_air, i);
380
381 let domain = pcs.natural_domain_for_log_degree(builder, log_degree);
383
384 let trace_points = builder.array::<Ext<_, _>>(2);
385 let zeta_next = domain.next_point(builder, zeta);
386 builder.set_value(&trace_points, RVar::zero(), zeta);
387 builder.set_value(&trace_points, RVar::one(), zeta_next);
388
389 let log_quotient_degree = RVar::from(advice.log_quotient_degree);
390 let quotient_degree =
391 RVar::from(builder.sll::<Usize<_>>(RVar::one(), log_quotient_degree));
392 let log_quotient_size = builder.eval_expr(log_degree + log_quotient_degree);
393 let quotient_domain =
397 domain.create_disjoint_domain(builder, log_quotient_size, Some(pcs.config.clone()));
398 builder.set_value("ient_domains, i, quotient_domain.clone());
399
400 let qc_domains =
401 quotient_domain.split_domains(builder, log_quotient_degree, quotient_degree);
402 builder.assign(
403 &num_quotient_mats,
404 num_quotient_mats.clone() + quotient_degree,
405 );
406
407 builder.set_value(&domains, i, domain);
408 builder.set_value(&trace_points_per_domain, i, trace_points);
409 builder.set_value("ient_chunk_domains, i, qc_domains);
410
411 builder
412 .if_eq(advice.preprocessed_data.len(), RVar::one())
413 .then(|builder| {
414 builder.assign(&num_prep_rounds, num_prep_rounds.clone() + RVar::one());
415 });
416 });
417 let num_quotient_mats = RVar::from(num_quotient_mats);
418
419 let num_main_rounds = builder.eval_expr(num_cached_mains + RVar::one());
424 let num_challenge_rounds: RVar<_> = num_challenges_to_sample.len().into();
425 let num_quotient_rounds = RVar::one();
426
427 builder.assert_usize_eq(opening.values.preprocessed.len(), num_prep_rounds.clone());
428 let total_rounds = builder.eval_expr(
431 num_prep_rounds + num_main_rounds + num_challenge_rounds + num_quotient_rounds,
432 );
433
434 let rounds = builder.array::<TwoAdicPcsRoundVariable<_>>(total_rounds);
435 let null_perm = builder.array(0);
437
438 let round_idx: Usize<_> = builder.eval(RVar::zero());
440 builder.range(0, num_airs).for_each(|i_vec, builder| {
441 let i = i_vec[0];
442 let advice = builder.get(&m_advice_var.per_air, i);
443 builder
444 .if_eq(advice.preprocessed_data.len(), RVar::one())
445 .then(|builder| {
446 let prep = builder.get(&opening.values.preprocessed, round_idx.clone());
448 let batch_commit = builder.get(&advice.preprocessed_data, RVar::zero());
449 let prep_width =
450 RVar::from(builder.get(&advice.width.preprocessed, RVar::zero()));
451
452 let domain = builder.get(&domains, i);
453 let trace_points = builder.get(&trace_points_per_domain, i);
454
455 builder.assert_usize_eq(prep_width, prep.local.len());
456 builder.assert_usize_eq(prep_width, prep.next.len());
457 let values = builder.array::<Array<C, _>>(2);
460 builder.set_value(&values, 0, prep.local);
461 builder.set_value(&values, 1, prep.next);
462 let prep_mat = TwoAdicPcsMatsVariable::<C> {
463 domain,
464 values,
465 points: trace_points.clone(),
466 };
467
468 let mats: Array<_, TwoAdicPcsMatsVariable<_>> = builder.array(1);
469 builder.set_value(&mats, 0, prep_mat);
470
471 builder.set_value(
472 &rounds,
473 round_idx.clone(),
474 TwoAdicPcsRoundVariable {
475 batch_commit,
476 mats,
477 permutation: null_perm.clone(),
478 },
479 );
480 builder.assign(&round_idx, round_idx.clone() + RVar::one());
481 });
482 });
483
484 let main_commit_idx: Usize<_> = builder.eval(RVar::zero());
486 builder.assert_usize_eq(opening.values.main.len(), num_main_commits);
487 let common_main_values_per_mat = builder.get(&opening.values.main, num_cached_mains);
488 let common_main_mats = builder.array(num_common_main_traces);
489 let common_main_matrix_idx: Usize<_> = builder.eval(RVar::zero());
490 builder.range(0, num_airs).for_each(|i_vec, builder| {
491 let i = i_vec[0];
492 let advice = builder.get(&m_advice_var.per_air, i);
493 let cached_main_widths = &advice.width.cached_mains;
494
495 let domain = builder.get(&domains, i);
496 let trace_points = builder.get(&trace_points_per_domain, i);
497
498 iter_zip!(builder, cached_main_widths).for_each(|ptr_vec, builder| {
499 let cached_main_width = builder.iter_ptr_get(cached_main_widths, ptr_vec[0]);
500 let values_per_mat = builder.get(&opening.values.main, main_commit_idx.clone());
501 let batch_commit = builder.get(main_trace_commits, main_commit_idx.clone());
502 builder.assign(&main_commit_idx, main_commit_idx.clone() + RVar::one());
503
504 builder.assert_usize_eq(values_per_mat.len(), RVar::one());
505 let main = builder.get(&values_per_mat, RVar::zero());
506 let values = builder.array::<Array<C, _>>(2);
507 builder.assert_usize_eq(main.local.len(), cached_main_width.clone());
508 builder.assert_usize_eq(main.next.len(), cached_main_width);
509 builder.set_value(&values, 0, main.local);
510 builder.set_value(&values, 1, main.next);
511 let main_mat = TwoAdicPcsMatsVariable::<C> {
512 domain: domain.clone(),
513 values,
514 points: trace_points.clone(),
515 };
516 let mats = builder.array(1);
517 builder.set_value(&mats, 0, main_mat);
518
519 builder.set_value(
520 &rounds,
521 round_idx.clone(),
522 TwoAdicPcsRoundVariable {
523 batch_commit,
524 mats,
525 permutation: null_perm.clone(),
526 },
527 );
528 builder.assign(&round_idx, round_idx.clone() + RVar::one());
529 });
530
531 let common_main_width = RVar::from(advice.width.common_main);
532 builder
533 .if_ne(common_main_width, RVar::zero())
534 .then(|builder| {
535 let main =
537 builder.get(&common_main_values_per_mat, common_main_matrix_idx.clone());
538
539 let values = builder.array::<Array<C, _>>(2);
540 builder.assert_usize_eq(main.local.len(), common_main_width);
541 builder.assert_usize_eq(main.next.len(), common_main_width);
542 builder.set_value(&values, 0, main.local);
543 builder.set_value(&values, 1, main.next);
544 let main_mat = TwoAdicPcsMatsVariable::<C> {
545 domain: domain.clone(),
546 values,
547 points: trace_points.clone(),
548 };
549 builder.set_value(&common_main_mats, common_main_matrix_idx.clone(), main_mat);
550 builder.assign(
551 &common_main_matrix_idx,
552 common_main_matrix_idx.clone() + RVar::one(),
553 );
554 });
555 });
556 {
558 let batch_commit = builder.get(main_trace_commits, main_commit_idx.clone());
559 builder.set_value(
560 &rounds,
561 round_idx.clone(),
562 TwoAdicPcsRoundVariable {
563 batch_commit,
564 mats: common_main_mats,
565 permutation: air_perm_by_height.clone(),
566 },
567 );
568 builder.assign(&round_idx, round_idx.clone() + RVar::one());
569 }
570
571 builder.assert_usize_eq(opening.values.after_challenge.len(), num_phases);
573 builder
574 .range(0, num_phases)
575 .for_each(|phase_idx_vec, builder| {
576 let phase_idx = phase_idx_vec[0];
577 let values_per_mat = builder.get(&opening.values.after_challenge, phase_idx);
578 builder.assert_usize_eq(values_per_mat.len(), num_after_challenge_traces);
579 let batch_commit = builder.get(after_challenge_commits, phase_idx);
580
581 let mat_idx: Usize<_> = builder.eval(RVar::zero());
582 let mats: Array<_, TwoAdicPcsMatsVariable<_>> =
583 builder.array(num_after_challenge_traces);
584 builder.range(0, num_airs).for_each(|i_vec, builder| {
585 let i = i_vec[0];
586 let advice = builder.get(&m_advice_var.per_air, i);
587 builder
588 .if_ne(advice.width.after_challenge.len(), RVar::zero())
589 .then(|builder| {
590 builder
592 .assert_usize_eq(advice.width.after_challenge.len(), RVar::one());
593 let after_challenge_width = RVar::from(
594 builder.get(&advice.width.after_challenge, RVar::zero()),
595 );
596 let domain = builder.get(&domains, i);
597 let trace_points = builder.get(&trace_points_per_domain, i);
598
599 let after_challenge = builder.get(&values_per_mat, mat_idx.clone());
600
601 let values = builder.array::<Array<C, _>>(2);
602 builder.assert_usize_eq(
603 after_challenge.local.len(),
604 after_challenge_width * RVar::from(C::EF::DIMENSION),
605 );
606 builder.assert_usize_eq(
607 after_challenge.next.len(),
608 after_challenge.local.len(),
609 );
610 builder.set_value(&values, 0, after_challenge.local);
611 builder.set_value(&values, 1, after_challenge.next);
612 let after_challenge_mat = TwoAdicPcsMatsVariable::<C> {
613 domain,
614 values,
615 points: trace_points,
616 };
617 builder.set_value(&mats, mat_idx.clone(), after_challenge_mat);
618 builder.inc(&mat_idx);
619 });
620 });
621 builder.assert_usize_eq(mat_idx, num_after_challenge_traces);
622
623 builder.set_value(
624 &rounds,
625 round_idx.clone(),
626 TwoAdicPcsRoundVariable {
627 batch_commit,
628 mats,
629 permutation: air_perm_by_height.clone(),
630 },
631 );
632 builder.assign(&round_idx, round_idx.clone() + RVar::one());
633 });
634 let quotient_perm = builder.array(num_quotient_mats);
647 let perm_offset_per_air = builder.array::<Usize<_>>(num_airs);
648 let offset: Usize<_> = builder.eval(RVar::zero());
649 iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
650 let air_index = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
651 builder.set(&perm_offset_per_air, air_index.clone(), offset.clone());
652 let qc_domains = builder.get("ient_chunk_domains, air_index);
653 builder.assign(&offset, offset.clone() + qc_domains.len());
654 });
655
656 let quotient_mats: Array<_, TwoAdicPcsMatsVariable<_>> = builder.array(num_quotient_mats);
657 let qc_points = builder.array::<Ext<_, _>>(1);
658 builder.set_value(&qc_points, 0, zeta);
659
660 let qc_index: Usize<_> = builder.eval(RVar::zero());
661 builder.assert_usize_eq(opening.values.quotient.len(), num_airs);
662 builder.range(0, num_airs).for_each(|i_vec, builder| {
663 let i = i_vec[0];
664 let opened_quotient = builder.get(&opening.values.quotient, i);
665 let qc_domains = builder.get("ient_chunk_domains, i);
666 let air_offset = builder.get(&perm_offset_per_air, i);
667
668 builder.assert_usize_eq(opened_quotient.len(), qc_domains.len());
669 let quotient_degree = qc_domains.len();
670 builder
671 .range(0, quotient_degree)
672 .for_each(|j_vec, builder| {
673 let j = j_vec[0];
674 let qc_dom = builder.get(&qc_domains, j);
675 let qc_vals_array = builder.get(&opened_quotient, j);
676 builder.assert_usize_eq(qc_vals_array.len(), RVar::from(4));
677 let qc_values = builder.array::<Array<C, _>>(1);
678 builder.set_value(&qc_values, 0, qc_vals_array);
679 let qc_mat = TwoAdicPcsMatsVariable::<C> {
680 domain: qc_dom,
681 values: qc_values,
682 points: qc_points.clone(),
683 };
684 let qc_offset = builder.eval_expr(air_offset.clone() + j);
685 builder.set_value("ient_mats, qc_index.clone(), qc_mat);
686 builder.set("ient_perm, qc_offset, RVar::from(qc_index.clone()));
687 builder.assign(&qc_index, qc_index.clone() + RVar::one());
688 });
689 });
690 let quotient_round = TwoAdicPcsRoundVariable {
691 batch_commit: quotient_commit.clone(),
692 mats: quotient_mats,
693 permutation: quotient_perm,
694 };
695 builder.set_value(&rounds, round_idx.clone(), quotient_round);
696 builder.assign(&round_idx, round_idx.clone() + RVar::one());
697
698 builder.assert_usize_eq(round_idx, total_rounds);
700
701 builder.cycle_tracker_end("stage-c-build-rounds");
702
703 builder.cycle_tracker_start("stage-d-verify-pcs");
705 pcs.verify(
706 builder,
707 rounds,
708 opening.proof.clone(),
709 log_max_height,
710 challenger,
711 );
712 builder.cycle_tracker_end("stage-d-verify-pcs");
713
714 builder.cycle_tracker_start("stage-e-verify-constraints");
715 let after_challenge_idx: Usize<C::N> = builder.eval(C::N::ZERO);
716 let preprocessed_idx: Usize<_> = builder.eval(C::N::ZERO);
717 let cached_main_commit_idx: Usize<_> = builder.eval(C::N::ZERO);
718 let common_main_matrix_idx: Usize<_> = builder.eval(C::N::ZERO);
719 let air_idx: Usize<_> = builder.eval(C::N::ZERO);
720 let common_main_openings = builder.get(&opening.values.main, num_cached_mains);
721
722 let challenges = m_advice
724 .num_challenges_to_sample
725 .iter()
726 .enumerate()
727 .map(|(phase, &num_challenges_to_sample)| {
728 (0..num_challenges_to_sample)
729 .map(|i| {
730 let challenge: Ext<_, _> = builder.constant(C::EF::ZERO);
731 builder
732 .if_eq(
733 m_advice_var.num_challenges_to_sample_mask[phase][i].clone(),
734 RVar::one(),
735 )
736 .then(|builder| {
737 let chs = builder.get(&challenges_per_phase, phase);
738 let v = builder.get(&chs, i);
739 builder.assign(&challenge, v);
740 });
741 challenge
742 })
743 .collect_vec()
744 })
745 .collect_vec();
746
747 for (i, air_const) in m_advice.per_air.iter().enumerate() {
748 let abs_air_idx = builder.get(&air_ids, air_idx.clone());
749 builder.if_eq(abs_air_idx, RVar::from(i)).then(|builder| {
750 let preprocessed_values = if air_const.preprocessed_data.is_some() {
751 let ret =
752 Some(builder.get(&opening.values.preprocessed, preprocessed_idx.clone()));
753 builder.inc(&preprocessed_idx);
754 ret
755 } else {
756 None
757 };
758 let mut partitioned_main_values = (0..air_const.width.cached_mains.len())
759 .map(|_| {
760 let ret = builder.get(&opening.values.main, cached_main_commit_idx.clone());
761 builder.inc(&cached_main_commit_idx);
762 builder.get(&ret, 0)
763 })
764 .collect_vec();
765 if air_const.width.common_main > 0 {
766 let common_main =
767 builder.get(&common_main_openings, common_main_matrix_idx.clone());
768 builder.inc(&common_main_matrix_idx);
769 partitioned_main_values.push(common_main);
770 }
771
772 let after_challenge_values = if air_const.width.after_challenge.is_empty() {
773 AdjacentOpenedValuesVariable {
774 local: builder.vec(vec![]),
775 next: builder.vec(vec![]),
776 }
777 } else {
778 let after_challenge_values = builder.get(&opening.values.after_challenge, 0);
780 let after_challenge_values =
781 builder.get(&after_challenge_values, after_challenge_idx.clone());
782 builder.inc(&after_challenge_idx);
783 after_challenge_values
784 };
785 let trace_domain = builder.get(&domains, air_idx.clone());
786 let quotient_domain: TwoAdicMultiplicativeCosetVariable<_> =
787 builder.get("ient_domains, air_idx.clone());
788 let log_quotient_degree = air_const.log_quotient_degree();
790 let quotient_chunks = builder.get(&opening.values.quotient, air_idx.clone());
791
792 let qc_domains = quotient_domain.split_domains_const(builder, log_quotient_degree);
794 let air_proof = builder.get(air_proofs, air_idx.clone());
795 let pvs = (0..air_const.num_public_values)
796 .map(|x| builder.get(&air_proof.public_values, x))
797 .collect_vec();
798
799 let exposed_values = air_const
800 .num_exposed_values_after_challenge
801 .iter()
802 .enumerate()
803 .map(|(phase, &num_exposed)| {
804 let exposed_values =
805 builder.get(&air_proof.exposed_values_after_challenge, phase);
806 (0..num_exposed)
807 .map(|j| builder.get(&exposed_values, j))
808 .collect_vec()
809 })
810 .collect_vec();
811
812 Self::verify_single_rap_constraints(
813 builder,
814 air_const,
815 preprocessed_values,
816 &partitioned_main_values,
817 quotient_chunks,
818 &pvs,
819 trace_domain,
820 qc_domains,
821 zeta,
822 alpha,
823 after_challenge_values,
824 &challenges,
825 &exposed_values,
826 );
827
828 builder.inc(&air_idx);
829 });
830 }
831 builder.assert_usize_eq(air_idx, air_ids.len());
833
834 builder.cycle_tracker_end("stage-e-verify-constraints");
835 }
836
837 fn check_trace_height_constraints(
848 builder: &mut Builder<C>,
849 constraint_system: &TraceHeightConstraintSystem<C>,
850 air_proofs: &Array<C, AirProofDataVariable<C>>,
851 ) {
852 let values: Vec<Var<C::N>> = (0..constraint_system.height_constraints.len())
854 .map(|_| builder.eval(C::N::ZERO))
855 .collect();
856
857 let assert_less_than = |builder: &mut Builder<C>, a, b| {
858 if builder.flags.static_only {
859 builder.push(DslIr::CircuitLessThan(a, b));
860 } else {
861 builder.assert_less_than_slow_bit_decomp(a, b);
862 }
863 };
864
865 let pows_of_two: Array<C, Var<C::N>> = builder.array(MAX_TWO_ADICITY);
868 let cur: Var<C::N> = builder.constant(C::N::ONE);
869 iter_zip!(builder, pows_of_two).for_each(|ptr_vec, builder| {
870 builder.iter_ptr_set(&pows_of_two, ptr_vec[0], cur);
871 builder.assign(&cur, cur + cur);
872 });
873
874 iter_zip!(builder, air_proofs).for_each(|ptr_vec, builder| {
875 let air_proof_data = builder.iter_ptr_get(air_proofs, ptr_vec[0]);
876
877 let height = builder.get(&pows_of_two, air_proof_data.log_degree);
878 let height_max = builder.get(
879 &constraint_system.height_maxes,
880 air_proof_data.air_id.clone(),
881 );
882 builder
883 .if_eq(height_max.is_some, C::N::ONE)
884 .then(|builder| {
885 assert_less_than(builder, height, height_max.value);
886 });
887
888 for (i, constraint) in constraint_system.height_constraints.iter().enumerate() {
889 let coeff = builder.get(&constraint.coefficients, air_proof_data.air_id.clone());
890
891 let new_value: Var<C::N> = builder.eval(values[i] + coeff * height);
892 let new_value_plus_one: Var<C::N> = builder.eval(new_value + C::N::ONE);
893 assert_less_than(builder, values[i], new_value_plus_one);
894 builder.assign(&values[i], new_value);
895 }
896 });
897 for (value, constraint) in zip(values, &constraint_system.height_constraints) {
898 if builder.flags.static_only || !constraint.is_threshold_at_p {
899 assert_less_than(builder, value, constraint.threshold);
900 }
901 }
902 }
903
904 #[allow(clippy::too_many_arguments)]
906 #[allow(clippy::type_complexity)]
907 pub fn verify_single_rap_constraints(
908 builder: &mut Builder<C>,
909 constants: &StarkVerificationAdvice<C>,
910 preprocessed_values: Option<AdjacentOpenedValuesVariable<C>>,
911 partitioned_main_values: &[AdjacentOpenedValuesVariable<C>],
912 quotient_chunks: Array<C, Array<C, Ext<C::F, C::EF>>>,
913 public_values: &[Felt<C::F>],
914 trace_domain: TwoAdicMultiplicativeCosetVariable<C>,
915 qc_domains: Vec<TwoAdicMultiplicativeCosetVariable<C>>,
916 zeta: Ext<C::F, C::EF>,
917 alpha: Ext<C::F, C::EF>,
918 after_challenge_values: AdjacentOpenedValuesVariable<C>,
919 challenges: &[Vec<Ext<C::F, C::EF>>],
920 exposed_values_after_challenge: &[Vec<Ext<C::F, C::EF>>],
921 ) {
922 let sels = trace_domain.selectors_at_point(builder, zeta);
923
924 let mut preprocessed = AdjacentOpenedValues {
925 local: vec![],
926 next: vec![],
927 };
928 if let Some(preprocessed_values) = preprocessed_values {
929 for i in 0..constants.width.preprocessed.unwrap() {
930 preprocessed
931 .local
932 .push(builder.get(&preprocessed_values.local, i));
933 preprocessed
934 .next
935 .push(builder.get(&preprocessed_values.next, i));
936 }
937 }
938
939 let main_widths = constants.width.main_widths();
940 assert_eq!(partitioned_main_values.len(), main_widths.len());
941 let partitioned_main_values = partitioned_main_values
942 .iter()
943 .zip_eq(main_widths.iter())
944 .map(|(main_values, &width)| {
945 builder.assert_usize_eq(main_values.local.len(), RVar::from(width));
946 builder.assert_usize_eq(main_values.next.len(), RVar::from(width));
947 let mut main = AdjacentOpenedValues {
948 local: vec![],
949 next: vec![],
950 };
951 for i in 0..width {
952 main.local.push(builder.get(&main_values.local, i));
953 main.next.push(builder.get(&main_values.next, i));
954 }
955 main
956 })
957 .collect_vec();
958
959 let mut after_challenge = AdjacentOpenedValues {
960 local: vec![],
961 next: vec![],
962 };
963
964 let after_challenge_width = if constants.width.after_challenge.is_empty() {
965 0
966 } else {
967 C::EF::DIMENSION * constants.width.after_challenge[0]
968 };
969 builder.assert_usize_eq(
970 after_challenge_values.local.len(),
971 RVar::from(after_challenge_width),
972 );
973 builder.assert_usize_eq(
974 after_challenge_values.next.len(),
975 RVar::from(after_challenge_width),
976 );
977 for i in 0..after_challenge_width {
978 after_challenge
979 .local
980 .push(builder.get(&after_challenge_values.local, i));
981 after_challenge
982 .next
983 .push(builder.get(&after_challenge_values.next, i));
984 }
985
986 let folded_constraints = Self::eval_constraints(
987 builder,
988 &constants.symbolic_constraints,
989 preprocessed,
990 &partitioned_main_values,
991 public_values,
992 &sels,
993 alpha,
994 after_challenge,
995 challenges,
996 exposed_values_after_challenge,
997 );
998
999 let num_quotient_chunks = 1 << constants.log_quotient_degree();
1000 let mut quotient = vec![];
1001 builder.assert_usize_eq(quotient_chunks.len(), RVar::from(num_quotient_chunks));
1003 for i in 0..num_quotient_chunks {
1005 let chunk = builder.get("ient_chunks, i);
1006 builder.assert_usize_eq(RVar::from(C::EF::DIMENSION), RVar::from(chunk.len()));
1008 let mut quotient_vals = vec![];
1010 for j in 0..C::EF::DIMENSION {
1011 let value = builder.get(&chunk, j);
1012 quotient_vals.push(value);
1013 }
1014 quotient.push(quotient_vals);
1015 }
1016
1017 let quotient: Ext<_, _> = Self::recompute_quotient(builder, "ient, qc_domains, zeta);
1018
1019 builder.assert_ext_eq(folded_constraints * sels.inv_vanishing, quotient);
1021 }
1022
1023 #[allow(clippy::too_many_arguments)]
1024 fn eval_constraints(
1025 builder: &mut Builder<C>,
1026 constraints: &SymbolicExpressionDag<C::F>,
1027 preprocessed_values: AdjacentOpenedValues<Ext<C::F, C::EF>>,
1028 partitioned_main_values: &[AdjacentOpenedValues<Ext<C::F, C::EF>>],
1029 public_values: &[Felt<C::F>],
1030 selectors: &LagrangeSelectors<Ext<C::F, C::EF>>,
1031 alpha: Ext<C::F, C::EF>,
1032 after_challenge: AdjacentOpenedValues<Ext<C::F, C::EF>>,
1033 challenges: &[Vec<Ext<C::F, C::EF>>],
1034 exposed_values_after_challenge: &[Vec<Ext<C::F, C::EF>>],
1035 ) -> Ext<C::F, C::EF> {
1036 let mut unflatten = |v: &[Ext<C::F, C::EF>]| {
1037 v.chunks_exact(C::EF::DIMENSION)
1038 .map(|chunk| {
1039 builder.eval(
1040 chunk
1041 .iter()
1042 .enumerate()
1043 .map(|(e_i, &x)| {
1044 x * C::EF::ith_basis_element(e_i)
1045 .expect("basis element index out of bounds")
1046 .cons()
1047 })
1048 .sum::<SymbolicExt<_, _>>(),
1049 )
1050 })
1051 .collect::<Vec<Ext<_, _>>>()
1052 };
1053
1054 let after_challenge_values = AdjacentOpenedValues {
1055 local: unflatten(&after_challenge.local),
1056 next: unflatten(&after_challenge.next),
1057 };
1058
1059 let mut folder: RecursiveVerifierConstraintFolder<C> = GenericVerifierConstraintFolder {
1060 preprocessed: VerticalPair::new(
1061 RowMajorMatrixView::new_row(&preprocessed_values.local),
1062 RowMajorMatrixView::new_row(&preprocessed_values.next),
1063 ),
1064 partitioned_main: partitioned_main_values
1065 .iter()
1066 .map(|main_values| {
1067 VerticalPair::new(
1068 RowMajorMatrixView::new_row(&main_values.local),
1069 RowMajorMatrixView::new_row(&main_values.next),
1070 )
1071 })
1072 .collect(),
1073 after_challenge: vec![VerticalPair::new(
1074 RowMajorMatrixView::new_row(&after_challenge_values.local),
1075 RowMajorMatrixView::new_row(&after_challenge_values.next),
1076 )],
1077 challenges,
1078 is_first_row: selectors.is_first_row,
1079 is_last_row: selectors.is_last_row,
1080 is_transition: selectors.is_transition,
1081 alpha,
1082 accumulator: SymbolicExt::ZERO,
1083 public_values,
1084 exposed_values_after_challenge,
1085 _marker: PhantomData,
1086 };
1087 folder.eval_constraints(constraints);
1088
1089 builder.eval(folder.accumulator)
1090 }
1091
1092 fn recompute_quotient(
1093 builder: &mut Builder<C>,
1094 quotient_chunks: &[Vec<Ext<C::F, C::EF>>],
1095 qc_domains: Vec<TwoAdicMultiplicativeCosetVariable<C>>,
1096 zeta: Ext<C::F, C::EF>,
1097 ) -> Ext<C::F, C::EF> {
1098 let zps = qc_domains
1099 .iter()
1100 .enumerate()
1101 .map(|(i, domain)| {
1102 qc_domains
1103 .iter()
1104 .enumerate()
1105 .filter(|(j, _)| *j != i)
1106 .map(|(_, other_domain)| {
1107 let first_point: Ext<_, _> = builder.eval(domain.first_point());
1108 other_domain.vanishing_poly_at_point(builder, zeta)
1109 * other_domain
1110 .vanishing_poly_at_point(builder, first_point)
1111 .inverse()
1112 })
1113 .product::<SymbolicExt<_, _>>()
1114 })
1115 .collect::<Vec<SymbolicExt<_, _>>>()
1116 .into_iter()
1117 .map(|x| builder.eval(x))
1118 .collect::<Vec<Ext<_, _>>>();
1119
1120 builder.eval(
1121 quotient_chunks
1122 .iter()
1123 .enumerate()
1124 .map(|(ch_i, ch)| {
1125 assert_eq!(ch.len(), C::EF::DIMENSION);
1126 ch.iter()
1127 .enumerate()
1128 .map(|(e_i, &c)| {
1129 zps[ch_i]
1130 * C::EF::ith_basis_element(e_i)
1131 .expect("basis element index out of bounds")
1132 * c
1133 })
1134 .sum::<SymbolicExt<_, _>>()
1135 })
1136 .sum::<SymbolicExt<_, _>>(),
1137 )
1138 }
1139}
1140
1141fn assert_cumulative_sums<C: Config>(
1142 builder: &mut Builder<C>,
1143 air_proofs: &Array<C, AirProofDataVariable<C>>,
1144 num_challenges_to_sample: &Array<C, Usize<C::N>>,
1145) {
1146 let num_phase = num_challenges_to_sample.len();
1147 builder.if_eq(num_phase, RVar::one()).then(|builder| {
1149 let cumulative_sum: Ext<C::F, C::EF> = builder.eval(C::F::ZERO);
1150 builder
1151 .range(0, air_proofs.len())
1152 .for_each(|i_vec, builder| {
1153 let i = i_vec[0];
1154 let air_proof_input = builder.get(air_proofs, i);
1155 let exposed_values = air_proof_input.exposed_values_after_challenge;
1156
1157 builder
1158 .if_ne(exposed_values.len(), RVar::zero())
1159 .then(|builder| {
1160 builder.assert_usize_eq(exposed_values.len(), RVar::one());
1162 let values = builder.get(&exposed_values, RVar::zero());
1163 builder.assert_usize_eq(values.len(), RVar::one());
1165
1166 let summand = builder.get(&values, RVar::zero());
1167 builder.assign(&cumulative_sum, cumulative_sum + summand);
1168 });
1169 });
1170 builder.assert_ext_eq(cumulative_sum, C::EF::ZERO.cons());
1171 });
1172}
1173
1174fn cast_usize_to_felt<C: Config>(builder: &mut Builder<C>, val: Usize<C::N>) -> Felt<C::F>
1177where
1178 C::F: TwoAdicField,
1179{
1180 if builder.flags.static_only {
1181 builder.eval(C::F::from_usize(val.value()))
1182 } else {
1183 builder.unsafe_cast_var_to_felt(val.get_var())
1184 }
1185}