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::{FieldAlgebra, FieldExtensionAlgebra, 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(
53 constants: MultiStarkVerificationAdvice<InnerConfig>,
54 fri_params: &FriParameters,
55 ) -> Program<BabyBear> {
56 let options = CompilerOptions {
57 enable_cycle_tracker: true,
58 ..Default::default()
59 };
60 Self::build_with_options(constants, fri_params, options)
61 }
62
63 pub fn build_with_options(
65 constants: MultiStarkVerificationAdvice<InnerConfig>,
66 fri_params: &FriParameters,
67 options: CompilerOptions,
68 ) -> Program<BabyBear> {
69 let mut builder = Builder::<InnerConfig>::default();
70
71 builder.cycle_tracker_start("VerifierProgram");
72 builder.cycle_tracker_start("ReadingProofFromInput");
73 let input: StarkProofVariable<_> = builder.uninit();
74 Proof::<BabyBearPoseidon2Config>::witness(&input, &mut builder);
75 builder.cycle_tracker_end("ReadingProofFromInput");
76
77 builder.cycle_tracker_start("InitializePcsConst");
78 let pcs = TwoAdicFriPcsVariable {
79 config: const_fri_config(&mut builder, fri_params),
80 };
81 builder.cycle_tracker_end("InitializePcsConst");
82 StarkVerifier::verify::<DuplexChallengerVariable<_>>(
83 &mut builder,
84 &pcs,
85 &constants,
86 &input,
87 );
88
89 builder.cycle_tracker_end("VerifierProgram");
90 builder.halt();
91
92 builder.compile_isa_with_options(options)
93 }
94}
95
96#[derive(Debug, Clone, Copy)]
97pub struct StarkVerifier<C: Config> {
98 _phantom: PhantomData<C>,
99}
100
101impl<C: Config> StarkVerifier<C>
102where
103 C::F: TwoAdicField,
104{
105 pub fn verify<CH: ChallengerVariable<C>>(
107 builder: &mut Builder<C>,
108 pcs: &TwoAdicFriPcsVariable<C>,
109 m_advice: &MultiStarkVerificationAdvice<C>,
110 proof: &StarkProofVariable<C>,
111 ) {
112 if builder.flags.static_only {
113 let mut challenger = CH::new(builder);
114 Self::verify_raps(builder, pcs, m_advice, &mut challenger, proof);
115 } else {
116 let mut tmp_builder = builder.create_sub_builder();
118 let old_heap_ptr = tmp_builder.load_heap_ptr();
120 let mut challenger = CH::new(&mut tmp_builder);
121 Self::verify_raps(&mut tmp_builder, pcs, m_advice, &mut challenger, proof);
122 tmp_builder.store_heap_ptr(old_heap_ptr);
123 builder.operations.extend(tmp_builder.operations);
124 }
125 }
126
127 pub fn verify_raps(
129 builder: &mut Builder<C>,
130 pcs: &TwoAdicFriPcsVariable<C>,
131 m_advice: &MultiStarkVerificationAdvice<C>,
132 challenger: &mut impl ChallengerVariable<C>,
133 proof: &StarkProofVariable<C>,
134 ) where
135 C::F: TwoAdicField,
136 C::EF: TwoAdicField,
137 {
138 let pre_hash = builder.constant(m_advice.pre_hash.clone());
139 challenger.observe_digest(builder, pre_hash);
140 let air_ids = proof.get_air_ids(builder);
141 let num_airs = cast_usize_to_felt(builder, air_ids.len());
142 challenger.observe(builder, num_airs);
143 iter_zip!(builder, air_ids).for_each(|ptr_vec, builder| {
144 let air_id = builder.iter_ptr_get(&air_ids, ptr_vec[0]);
145 let air_id = cast_usize_to_felt(builder, air_id);
146 challenger.observe(builder, air_id);
147 });
148 let m_advice_var = get_advice_per_air(builder, m_advice, &air_ids);
149 let StarkProofVariable::<C> {
152 commitments,
153 opening,
154 per_air: air_proofs,
155 air_perm_by_height,
156 log_up_pow_witness,
157 } = proof;
158
159 if m_advice.num_challenges_to_sample.len() > 1 {
160 panic!("Only support 0 or 1 phase is supported");
161 }
162
163 let num_airs = RVar::from(air_proofs.len());
164 let num_challenges_to_sample = m_advice_var.num_challenges_to_sample(builder);
165 let num_phases = RVar::from(num_challenges_to_sample.len());
169 assert_cumulative_sums(builder, air_proofs, &num_challenges_to_sample);
171
172 let air_perm_by_height = if builder.flags.static_only {
173 builder.assert_usize_eq(num_airs, RVar::from(m_advice.per_air.len()));
174 let num_airs = num_airs.value();
175 let perm = (0..num_airs).map(|i| builder.eval(RVar::from(i))).collect();
176 &builder.vec(perm)
177 } else {
178 builder.assert_usize_eq(air_perm_by_height.len(), num_airs);
179 let mask: Array<_, Usize<_>> = builder.dyn_array(num_airs);
181 let one: Usize<_> = builder.eval(C::N::ONE);
182 iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
183 let perm_i = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
184 builder.assert_less_than_slow_small_rhs(perm_i.clone(), num_airs);
185 builder.set_value(&mask, perm_i.clone(), one.clone());
186 });
187 let prev_log_height_plus_one: Usize<_> =
190 builder.eval(RVar::from(MAX_TWO_ADICITY - pcs.config.log_blowup + 1));
191 iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
192 let perm_i = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
193 let mask_i = builder.get(&mask, perm_i.clone());
194 builder.assert_usize_eq(mask_i, one.clone());
195
196 let air_proof = builder.get(air_proofs, perm_i.clone());
197 builder.assert_less_than_slow_small_rhs(
198 air_proof.log_degree.clone(),
199 prev_log_height_plus_one.clone(),
200 );
201 builder.assign(
202 &prev_log_height_plus_one,
203 air_proof.log_degree.clone() + RVar::one(),
204 );
205 });
206 air_perm_by_height
207 };
208 let log_max_height = {
212 let index = builder.get(air_perm_by_height, RVar::zero());
213 let air_proof = builder.get(air_proofs, index);
214 RVar::from(air_proof.log_degree.clone())
215 };
216
217 builder.cycle_tracker_start("CheckTraceHeightConstraints");
220 Self::check_trace_height_constraints(
221 builder,
222 &m_advice_var.trace_height_constraint_system,
223 air_proofs,
224 );
225 builder.cycle_tracker_end("CheckTraceHeightConstraints");
226
227 builder.range(0, num_airs).for_each(|i_vec, builder| {
228 let i = i_vec[0];
229 let air_proof_data = builder.get(air_proofs, i);
230 let pvs = air_proof_data.public_values;
231 let air_advice = builder.get(&m_advice_var.per_air, i);
232 builder.assert_usize_eq(air_advice.num_public_values, pvs.len());
233 challenger.observe_slice(builder, pvs);
234 });
235 builder.cycle_tracker_start("stage-c-build-rounds");
238
239 let num_cached_mains: Usize<_> = builder.eval(RVar::zero());
241 let num_common_main_traces: Usize<_> = builder.eval(RVar::zero());
242 let num_after_challenge_traces: Usize<_> = builder.eval(RVar::zero());
243 iter_zip!(builder, m_advice_var.per_air).for_each(|ptr_vec, builder| {
244 let air_advice = builder.iter_ptr_get(&m_advice_var.per_air, ptr_vec[0]);
245 builder
246 .if_eq(air_advice.preprocessed_data.len(), RVar::one())
247 .then(|builder| {
248 let commit = builder.get(&air_advice.preprocessed_data, RVar::zero());
249 challenger.observe_digest(builder, commit);
250 });
251
252 builder.assign(
253 &num_cached_mains,
254 num_cached_mains.clone() + air_advice.width.cached_mains.len(),
255 );
256 builder
257 .if_ne(air_advice.width.common_main, RVar::zero())
258 .then(|builder| {
259 builder.assign(
260 &num_common_main_traces,
261 num_common_main_traces.clone() + RVar::one(),
262 );
263 });
264 builder
265 .if_ne(air_advice.width.after_challenge.len(), RVar::zero())
266 .then(|builder| {
267 builder.assign(
268 &num_after_challenge_traces,
269 num_after_challenge_traces.clone() + RVar::one(),
270 );
271 });
272 });
273 let num_cached_mains = RVar::from(num_cached_mains);
274 let num_common_main_traces = RVar::from(num_common_main_traces);
275 let num_after_challenge_traces = RVar::from(num_after_challenge_traces);
276
277 let num_main_commits: Usize<_> = builder.eval(num_cached_mains + RVar::one());
278 let num_main_commits = RVar::from(num_main_commits);
279
280 let CommitmentsVariable {
281 main_trace: main_trace_commits,
282 after_challenge: after_challenge_commits,
283 quotient: quotient_commit,
284 } = commitments;
285
286 builder.assert_usize_eq(main_trace_commits.len(), num_main_commits);
288 iter_zip!(builder, main_trace_commits).for_each(|ptr_vec, builder| {
290 let main_commit = builder.iter_ptr_get(main_trace_commits, ptr_vec[0]);
291 challenger.observe_digest(builder, main_commit);
292 });
293
294 iter_zip!(builder, air_proofs).for_each(|ptr_vec, builder| {
295 let air_proof = builder.iter_ptr_get(air_proofs, ptr_vec[0]);
296 let log_degree = cast_usize_to_felt(builder, air_proof.log_degree);
297 challenger.observe(builder, log_degree);
298 });
299
300 let challenges_per_phase = builder.array(num_phases);
301
302 builder.if_eq(num_phases, RVar::one()).then(|builder| {
304 challenger.check_witness(builder, m_advice.log_up_pow_bits, *log_up_pow_witness);
306
307 let phase_idx = RVar::zero();
308 let num_to_sample = RVar::from(2);
309 let provided_num_to_sample = builder.get(&num_challenges_to_sample, phase_idx);
310 builder.assert_usize_eq(provided_num_to_sample, num_to_sample);
311
312 let challenges: Array<C, Ext<C::F, C::EF>> = builder.array(num_to_sample);
313 builder.range(0, num_to_sample).for_each(|i_vec, builder| {
315 let challenge = challenger.sample_ext(builder);
316 builder.set_value(&challenges, i_vec[0], challenge);
317 });
318 builder.set_value(&challenges_per_phase, phase_idx, challenges);
319
320 builder.range(0, num_airs).for_each(|j_vec, builder| {
321 let j = j_vec[0];
322 let air_advice = builder.get(&m_advice_var.per_air, j);
323 builder
324 .if_ne(
325 air_advice.num_exposed_values_after_challenge.len(),
326 RVar::zero(),
327 )
328 .then(|builder| {
329 builder.assert_usize_eq(
331 air_advice.num_exposed_values_after_challenge.len(),
332 RVar::one(),
333 );
334 let air_proof_data = builder.get(&proof.per_air, j);
335 let exposed_values = air_proof_data.exposed_values_after_challenge;
336 let values = builder.get(&exposed_values, phase_idx);
337 let values_len =
338 builder.get(&air_advice.num_exposed_values_after_challenge, phase_idx);
339 builder.assert_usize_eq(values_len, values.len());
341
342 iter_zip!(builder, values).for_each(|ptr_vec, builder| {
343 let value = builder.iter_ptr_get(&values, ptr_vec[0]);
344 let felts = builder.ext2felt(value);
345 challenger.observe_slice(builder, felts);
346 });
347 });
348 });
349
350 builder.assert_usize_eq(after_challenge_commits.len(), RVar::one());
352 let commit = builder.get(after_challenge_commits, phase_idx);
353 challenger.observe_digest(builder, commit);
354 });
355
356 let alpha = challenger.sample_ext(builder);
357
358 challenger.observe_digest(builder, quotient_commit.clone());
359
360 let zeta = challenger.sample_ext(builder);
361
362 let num_prep_rounds: Usize<_> = builder.eval(RVar::zero());
363
364 let domains = builder.array(num_airs);
366 let quotient_domains = builder.array(num_airs);
367 let trace_points_per_domain = builder.array(num_airs);
368 let quotient_chunk_domains = builder.array(num_airs);
369 let num_quotient_mats: Usize<_> = builder.eval(RVar::zero());
370 builder.range(0, num_airs).for_each(|i_vec, builder| {
371 let i = i_vec[0];
372 let air_proof = builder.get(air_proofs, i);
373 let log_degree: RVar<_> = air_proof.log_degree.clone().into();
374 let advice = builder.get(&m_advice_var.per_air, i);
375
376 let domain = pcs.natural_domain_for_log_degree(builder, log_degree);
378
379 let trace_points = builder.array::<Ext<_, _>>(2);
380 let zeta_next = domain.next_point(builder, zeta);
381 builder.set_value(&trace_points, RVar::zero(), zeta);
382 builder.set_value(&trace_points, RVar::one(), zeta_next);
383
384 let log_quotient_degree = RVar::from(advice.log_quotient_degree);
385 let quotient_degree =
386 RVar::from(builder.sll::<Usize<_>>(RVar::one(), log_quotient_degree));
387 let log_quotient_size = builder.eval_expr(log_degree + log_quotient_degree);
388 let quotient_domain =
392 domain.create_disjoint_domain(builder, log_quotient_size, Some(pcs.config.clone()));
393 builder.set_value("ient_domains, i, quotient_domain.clone());
394
395 let qc_domains =
396 quotient_domain.split_domains(builder, log_quotient_degree, quotient_degree);
397 builder.assign(
398 &num_quotient_mats,
399 num_quotient_mats.clone() + quotient_degree,
400 );
401
402 builder.set_value(&domains, i, domain);
403 builder.set_value(&trace_points_per_domain, i, trace_points);
404 builder.set_value("ient_chunk_domains, i, qc_domains);
405
406 builder
407 .if_eq(advice.preprocessed_data.len(), RVar::one())
408 .then(|builder| {
409 builder.assign(&num_prep_rounds, num_prep_rounds.clone() + RVar::one());
410 });
411 });
412 let num_quotient_mats = RVar::from(num_quotient_mats);
413
414 let num_main_rounds = builder.eval_expr(num_cached_mains + RVar::one());
419 let num_challenge_rounds: RVar<_> = num_challenges_to_sample.len().into();
420 let num_quotient_rounds = RVar::one();
421
422 builder.assert_usize_eq(opening.values.preprocessed.len(), num_prep_rounds.clone());
423 let total_rounds = builder.eval_expr(
426 num_prep_rounds + num_main_rounds + num_challenge_rounds + num_quotient_rounds,
427 );
428
429 let rounds = builder.array::<TwoAdicPcsRoundVariable<_>>(total_rounds);
430 let null_perm = builder.array(0);
432
433 let round_idx: Usize<_> = builder.eval(RVar::zero());
435 builder.range(0, num_airs).for_each(|i_vec, builder| {
436 let i = i_vec[0];
437 let advice = builder.get(&m_advice_var.per_air, i);
438 builder
439 .if_eq(advice.preprocessed_data.len(), RVar::one())
440 .then(|builder| {
441 let prep = builder.get(&opening.values.preprocessed, round_idx.clone());
443 let batch_commit = builder.get(&advice.preprocessed_data, RVar::zero());
444 let prep_width =
445 RVar::from(builder.get(&advice.width.preprocessed, RVar::zero()));
446
447 let domain = builder.get(&domains, i);
448 let trace_points = builder.get(&trace_points_per_domain, i);
449
450 builder.assert_usize_eq(prep_width, prep.local.len());
451 builder.assert_usize_eq(prep_width, prep.next.len());
452 let values = builder.array::<Array<C, _>>(2);
454 builder.set_value(&values, 0, prep.local);
455 builder.set_value(&values, 1, prep.next);
456 let prep_mat = TwoAdicPcsMatsVariable::<C> {
457 domain,
458 values,
459 points: trace_points.clone(),
460 };
461
462 let mats: Array<_, TwoAdicPcsMatsVariable<_>> = builder.array(1);
463 builder.set_value(&mats, 0, prep_mat);
464
465 builder.set_value(
466 &rounds,
467 round_idx.clone(),
468 TwoAdicPcsRoundVariable {
469 batch_commit,
470 mats,
471 permutation: null_perm.clone(),
472 },
473 );
474 builder.assign(&round_idx, round_idx.clone() + RVar::one());
475 });
476 });
477
478 let main_commit_idx: Usize<_> = builder.eval(RVar::zero());
480 builder.assert_usize_eq(opening.values.main.len(), num_main_commits);
481 let common_main_values_per_mat = builder.get(&opening.values.main, num_cached_mains);
482 let common_main_mats = builder.array(num_common_main_traces);
483 let common_main_matrix_idx: Usize<_> = builder.eval(RVar::zero());
484 builder.range(0, num_airs).for_each(|i_vec, builder| {
485 let i = i_vec[0];
486 let advice = builder.get(&m_advice_var.per_air, i);
487 let cached_main_widths = &advice.width.cached_mains;
488
489 let domain = builder.get(&domains, i);
490 let trace_points = builder.get(&trace_points_per_domain, i);
491
492 iter_zip!(builder, cached_main_widths).for_each(|ptr_vec, builder| {
493 let cached_main_width = builder.iter_ptr_get(cached_main_widths, ptr_vec[0]);
494 let values_per_mat = builder.get(&opening.values.main, main_commit_idx.clone());
495 let batch_commit = builder.get(main_trace_commits, main_commit_idx.clone());
496 builder.assign(&main_commit_idx, main_commit_idx.clone() + RVar::one());
497
498 builder.assert_usize_eq(values_per_mat.len(), RVar::one());
499 let main = builder.get(&values_per_mat, RVar::zero());
500 let values = builder.array::<Array<C, _>>(2);
501 builder.assert_usize_eq(main.local.len(), cached_main_width.clone());
502 builder.assert_usize_eq(main.next.len(), cached_main_width);
503 builder.set_value(&values, 0, main.local);
504 builder.set_value(&values, 1, main.next);
505 let main_mat = TwoAdicPcsMatsVariable::<C> {
506 domain: domain.clone(),
507 values,
508 points: trace_points.clone(),
509 };
510 let mats = builder.array(1);
511 builder.set_value(&mats, 0, main_mat);
512
513 builder.set_value(
514 &rounds,
515 round_idx.clone(),
516 TwoAdicPcsRoundVariable {
517 batch_commit,
518 mats,
519 permutation: null_perm.clone(),
520 },
521 );
522 builder.assign(&round_idx, round_idx.clone() + RVar::one());
523 });
524
525 let common_main_width = RVar::from(advice.width.common_main);
526 builder
527 .if_ne(common_main_width, RVar::zero())
528 .then(|builder| {
529 let main =
531 builder.get(&common_main_values_per_mat, common_main_matrix_idx.clone());
532
533 let values = builder.array::<Array<C, _>>(2);
534 builder.assert_usize_eq(main.local.len(), common_main_width);
535 builder.assert_usize_eq(main.next.len(), common_main_width);
536 builder.set_value(&values, 0, main.local);
537 builder.set_value(&values, 1, main.next);
538 let main_mat = TwoAdicPcsMatsVariable::<C> {
539 domain: domain.clone(),
540 values,
541 points: trace_points.clone(),
542 };
543 builder.set_value(&common_main_mats, common_main_matrix_idx.clone(), main_mat);
544 builder.assign(
545 &common_main_matrix_idx,
546 common_main_matrix_idx.clone() + RVar::one(),
547 );
548 });
549 });
550 {
552 let batch_commit = builder.get(main_trace_commits, main_commit_idx.clone());
553 builder.set_value(
554 &rounds,
555 round_idx.clone(),
556 TwoAdicPcsRoundVariable {
557 batch_commit,
558 mats: common_main_mats,
559 permutation: air_perm_by_height.clone(),
560 },
561 );
562 builder.assign(&round_idx, round_idx.clone() + RVar::one());
563 }
564
565 builder.assert_usize_eq(opening.values.after_challenge.len(), num_phases);
567 builder
568 .range(0, num_phases)
569 .for_each(|phase_idx_vec, builder| {
570 let phase_idx = phase_idx_vec[0];
571 let values_per_mat = builder.get(&opening.values.after_challenge, phase_idx);
572 builder.assert_usize_eq(values_per_mat.len(), num_after_challenge_traces);
573 let batch_commit = builder.get(after_challenge_commits, phase_idx);
574
575 let mat_idx: Usize<_> = builder.eval(RVar::zero());
576 let mats: Array<_, TwoAdicPcsMatsVariable<_>> =
577 builder.array(num_after_challenge_traces);
578 builder.range(0, num_airs).for_each(|i_vec, builder| {
579 let i = i_vec[0];
580 let advice = builder.get(&m_advice_var.per_air, i);
581 builder
582 .if_ne(advice.width.after_challenge.len(), RVar::zero())
583 .then(|builder| {
584 builder
586 .assert_usize_eq(advice.width.after_challenge.len(), RVar::one());
587 let after_challenge_width = RVar::from(
588 builder.get(&advice.width.after_challenge, RVar::zero()),
589 );
590 let domain = builder.get(&domains, i);
591 let trace_points = builder.get(&trace_points_per_domain, i);
592
593 let after_challenge = builder.get(&values_per_mat, mat_idx.clone());
594
595 let values = builder.array::<Array<C, _>>(2);
596 builder.assert_usize_eq(
597 after_challenge.local.len(),
598 after_challenge_width * RVar::from(C::EF::D),
599 );
600 builder.assert_usize_eq(
601 after_challenge.next.len(),
602 after_challenge.local.len(),
603 );
604 builder.set_value(&values, 0, after_challenge.local);
605 builder.set_value(&values, 1, after_challenge.next);
606 let after_challenge_mat = TwoAdicPcsMatsVariable::<C> {
607 domain,
608 values,
609 points: trace_points,
610 };
611 builder.set_value(&mats, mat_idx.clone(), after_challenge_mat);
612 builder.inc(&mat_idx);
613 });
614 });
615 builder.assert_usize_eq(mat_idx, num_after_challenge_traces);
616
617 builder.set_value(
618 &rounds,
619 round_idx.clone(),
620 TwoAdicPcsRoundVariable {
621 batch_commit,
622 mats,
623 permutation: air_perm_by_height.clone(),
624 },
625 );
626 builder.assign(&round_idx, round_idx.clone() + RVar::one());
627 });
628 let quotient_perm = builder.array(num_quotient_mats);
641 let perm_offset_per_air = builder.array::<Usize<_>>(num_airs);
642 let offset: Usize<_> = builder.eval(RVar::zero());
643 iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
644 let air_index = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
645 builder.set(&perm_offset_per_air, air_index.clone(), offset.clone());
646 let qc_domains = builder.get("ient_chunk_domains, air_index);
647 builder.assign(&offset, offset.clone() + qc_domains.len());
648 });
649
650 let quotient_mats: Array<_, TwoAdicPcsMatsVariable<_>> = builder.array(num_quotient_mats);
651 let qc_points = builder.array::<Ext<_, _>>(1);
652 builder.set_value(&qc_points, 0, zeta);
653
654 let qc_index: Usize<_> = builder.eval(RVar::zero());
655 builder.assert_usize_eq(opening.values.quotient.len(), num_airs);
656 builder.range(0, num_airs).for_each(|i_vec, builder| {
657 let i = i_vec[0];
658 let opened_quotient = builder.get(&opening.values.quotient, i);
659 let qc_domains = builder.get("ient_chunk_domains, i);
660 let air_offset = builder.get(&perm_offset_per_air, i);
661
662 builder.assert_usize_eq(opened_quotient.len(), qc_domains.len());
663 let quotient_degree = qc_domains.len();
664 builder
665 .range(0, quotient_degree)
666 .for_each(|j_vec, builder| {
667 let j = j_vec[0];
668 let qc_dom = builder.get(&qc_domains, j);
669 let qc_vals_array = builder.get(&opened_quotient, j);
670 builder.assert_usize_eq(qc_vals_array.len(), RVar::from(4));
671 let qc_values = builder.array::<Array<C, _>>(1);
672 builder.set_value(&qc_values, 0, qc_vals_array);
673 let qc_mat = TwoAdicPcsMatsVariable::<C> {
674 domain: qc_dom,
675 values: qc_values,
676 points: qc_points.clone(),
677 };
678 let qc_offset = builder.eval_expr(air_offset.clone() + j);
679 builder.set_value("ient_mats, qc_index.clone(), qc_mat);
680 builder.set("ient_perm, qc_offset, RVar::from(qc_index.clone()));
681 builder.assign(&qc_index, qc_index.clone() + RVar::one());
682 });
683 });
684 let quotient_round = TwoAdicPcsRoundVariable {
685 batch_commit: quotient_commit.clone(),
686 mats: quotient_mats,
687 permutation: quotient_perm,
688 };
689 builder.set_value(&rounds, round_idx.clone(), quotient_round);
690 builder.assign(&round_idx, round_idx.clone() + RVar::one());
691
692 builder.assert_usize_eq(round_idx, total_rounds);
694
695 builder.cycle_tracker_end("stage-c-build-rounds");
696
697 builder.cycle_tracker_start("stage-d-verify-pcs");
699 pcs.verify(
700 builder,
701 rounds,
702 opening.proof.clone(),
703 log_max_height,
704 challenger,
705 );
706 builder.cycle_tracker_end("stage-d-verify-pcs");
707
708 builder.cycle_tracker_start("stage-e-verify-constraints");
709 let after_challenge_idx: Usize<C::N> = builder.eval(C::N::ZERO);
710 let preprocessed_idx: Usize<_> = builder.eval(C::N::ZERO);
711 let cached_main_commit_idx: Usize<_> = builder.eval(C::N::ZERO);
712 let common_main_matrix_idx: Usize<_> = builder.eval(C::N::ZERO);
713 let air_idx: Usize<_> = builder.eval(C::N::ZERO);
714 let common_main_openings = builder.get(&opening.values.main, num_cached_mains);
715
716 let challenges = m_advice
718 .num_challenges_to_sample
719 .iter()
720 .enumerate()
721 .map(|(phase, &num_challenges_to_sample)| {
722 (0..num_challenges_to_sample)
723 .map(|i| {
724 let challenge: Ext<_, _> = builder.constant(C::EF::ZERO);
725 builder
726 .if_eq(
727 m_advice_var.num_challenges_to_sample_mask[phase][i].clone(),
728 RVar::one(),
729 )
730 .then(|builder| {
731 let chs = builder.get(&challenges_per_phase, phase);
732 let v = builder.get(&chs, i);
733 builder.assign(&challenge, v);
734 });
735 challenge
736 })
737 .collect_vec()
738 })
739 .collect_vec();
740
741 for (i, air_const) in m_advice.per_air.iter().enumerate() {
742 let abs_air_idx = builder.get(&air_ids, air_idx.clone());
743 builder.if_eq(abs_air_idx, RVar::from(i)).then(|builder| {
744 let preprocessed_values = if air_const.preprocessed_data.is_some() {
745 let ret =
746 Some(builder.get(&opening.values.preprocessed, preprocessed_idx.clone()));
747 builder.inc(&preprocessed_idx);
748 ret
749 } else {
750 None
751 };
752 let mut partitioned_main_values = (0..air_const.width.cached_mains.len())
753 .map(|_| {
754 let ret = builder.get(&opening.values.main, cached_main_commit_idx.clone());
755 builder.inc(&cached_main_commit_idx);
756 builder.get(&ret, 0)
757 })
758 .collect_vec();
759 if air_const.width.common_main > 0 {
760 let common_main =
761 builder.get(&common_main_openings, common_main_matrix_idx.clone());
762 builder.inc(&common_main_matrix_idx);
763 partitioned_main_values.push(common_main);
764 }
765
766 let after_challenge_values = if air_const.width.after_challenge.is_empty() {
767 AdjacentOpenedValuesVariable {
768 local: builder.vec(vec![]),
769 next: builder.vec(vec![]),
770 }
771 } else {
772 let after_challenge_values = builder.get(&opening.values.after_challenge, 0);
774 let after_challenge_values =
775 builder.get(&after_challenge_values, after_challenge_idx.clone());
776 builder.inc(&after_challenge_idx);
777 after_challenge_values
778 };
779 let trace_domain = builder.get(&domains, air_idx.clone());
780 let quotient_domain: TwoAdicMultiplicativeCosetVariable<_> =
781 builder.get("ient_domains, air_idx.clone());
782 let log_quotient_degree = air_const.log_quotient_degree();
784 let quotient_chunks = builder.get(&opening.values.quotient, air_idx.clone());
785
786 let qc_domains = quotient_domain.split_domains_const(builder, log_quotient_degree);
788 let air_proof = builder.get(air_proofs, air_idx.clone());
789 let pvs = (0..air_const.num_public_values)
790 .map(|x| builder.get(&air_proof.public_values, x))
791 .collect_vec();
792
793 let exposed_values = air_const
794 .num_exposed_values_after_challenge
795 .iter()
796 .enumerate()
797 .map(|(phase, &num_exposed)| {
798 let exposed_values =
799 builder.get(&air_proof.exposed_values_after_challenge, phase);
800 (0..num_exposed)
801 .map(|j| builder.get(&exposed_values, j))
802 .collect_vec()
803 })
804 .collect_vec();
805
806 Self::verify_single_rap_constraints(
807 builder,
808 air_const,
809 preprocessed_values,
810 &partitioned_main_values,
811 quotient_chunks,
812 &pvs,
813 trace_domain,
814 qc_domains,
815 zeta,
816 alpha,
817 after_challenge_values,
818 &challenges,
819 &exposed_values,
820 );
821
822 builder.inc(&air_idx);
823 });
824 }
825 builder.assert_usize_eq(air_idx, air_ids.len());
827
828 builder.cycle_tracker_end("stage-e-verify-constraints");
829 }
830
831 fn check_trace_height_constraints(
842 builder: &mut Builder<C>,
843 constraint_system: &TraceHeightConstraintSystem<C>,
844 air_proofs: &Array<C, AirProofDataVariable<C>>,
845 ) {
846 let values: Vec<Var<C::N>> = (0..constraint_system.height_constraints.len())
848 .map(|_| builder.eval(C::N::ZERO))
849 .collect();
850
851 let assert_less_than = |builder: &mut Builder<C>, a, b| {
852 if builder.flags.static_only {
853 builder.push(DslIr::CircuitLessThan(a, b));
854 } else {
855 builder.assert_less_than_slow_bit_decomp(a, b);
856 }
857 };
858
859 let pows_of_two: Array<C, Var<C::N>> = builder.array(MAX_TWO_ADICITY);
862 let cur: Var<C::N> = builder.constant(C::N::ONE);
863 iter_zip!(builder, pows_of_two).for_each(|ptr_vec, builder| {
864 builder.iter_ptr_set(&pows_of_two, ptr_vec[0], cur);
865 builder.assign(&cur, cur + cur);
866 });
867
868 iter_zip!(builder, air_proofs).for_each(|ptr_vec, builder| {
869 let air_proof_data = builder.iter_ptr_get(air_proofs, ptr_vec[0]);
870
871 let height = builder.get(&pows_of_two, air_proof_data.log_degree);
872 let height_max = builder.get(
873 &constraint_system.height_maxes,
874 air_proof_data.air_id.clone(),
875 );
876 builder
877 .if_eq(height_max.is_some, C::N::ONE)
878 .then(|builder| {
879 assert_less_than(builder, height, height_max.value);
880 });
881
882 for (i, constraint) in constraint_system.height_constraints.iter().enumerate() {
883 let coeff = builder.get(&constraint.coefficients, air_proof_data.air_id.clone());
884
885 let new_value: Var<C::N> = builder.eval(values[i] + coeff * height);
886 let new_value_plus_one: Var<C::N> = builder.eval(new_value + C::N::ONE);
887 assert_less_than(builder, values[i], new_value_plus_one);
888 builder.assign(&values[i], new_value);
889 }
890 });
891 for (value, constraint) in zip(values, &constraint_system.height_constraints) {
892 if builder.flags.static_only || !constraint.is_threshold_at_p {
893 assert_less_than(builder, value, constraint.threshold);
894 }
895 }
896 }
897
898 #[allow(clippy::too_many_arguments)]
900 #[allow(clippy::type_complexity)]
901 pub fn verify_single_rap_constraints(
902 builder: &mut Builder<C>,
903 constants: &StarkVerificationAdvice<C>,
904 preprocessed_values: Option<AdjacentOpenedValuesVariable<C>>,
905 partitioned_main_values: &[AdjacentOpenedValuesVariable<C>],
906 quotient_chunks: Array<C, Array<C, Ext<C::F, C::EF>>>,
907 public_values: &[Felt<C::F>],
908 trace_domain: TwoAdicMultiplicativeCosetVariable<C>,
909 qc_domains: Vec<TwoAdicMultiplicativeCosetVariable<C>>,
910 zeta: Ext<C::F, C::EF>,
911 alpha: Ext<C::F, C::EF>,
912 after_challenge_values: AdjacentOpenedValuesVariable<C>,
913 challenges: &[Vec<Ext<C::F, C::EF>>],
914 exposed_values_after_challenge: &[Vec<Ext<C::F, C::EF>>],
915 ) {
916 let sels = trace_domain.selectors_at_point(builder, zeta);
917
918 let mut preprocessed = AdjacentOpenedValues {
919 local: vec![],
920 next: vec![],
921 };
922 if let Some(preprocessed_values) = preprocessed_values {
923 for i in 0..constants.width.preprocessed.unwrap() {
924 preprocessed
925 .local
926 .push(builder.get(&preprocessed_values.local, i));
927 preprocessed
928 .next
929 .push(builder.get(&preprocessed_values.next, i));
930 }
931 }
932
933 let main_widths = constants.width.main_widths();
934 assert_eq!(partitioned_main_values.len(), main_widths.len());
935 let partitioned_main_values = partitioned_main_values
936 .iter()
937 .zip_eq(main_widths.iter())
938 .map(|(main_values, &width)| {
939 builder.assert_usize_eq(main_values.local.len(), RVar::from(width));
940 builder.assert_usize_eq(main_values.next.len(), RVar::from(width));
941 let mut main = AdjacentOpenedValues {
942 local: vec![],
943 next: vec![],
944 };
945 for i in 0..width {
946 main.local.push(builder.get(&main_values.local, i));
947 main.next.push(builder.get(&main_values.next, i));
948 }
949 main
950 })
951 .collect_vec();
952
953 let mut after_challenge = AdjacentOpenedValues {
954 local: vec![],
955 next: vec![],
956 };
957
958 let after_challenge_width = if constants.width.after_challenge.is_empty() {
959 0
960 } else {
961 C::EF::D * constants.width.after_challenge[0]
962 };
963 builder.assert_usize_eq(
964 after_challenge_values.local.len(),
965 RVar::from(after_challenge_width),
966 );
967 builder.assert_usize_eq(
968 after_challenge_values.next.len(),
969 RVar::from(after_challenge_width),
970 );
971 for i in 0..after_challenge_width {
972 after_challenge
973 .local
974 .push(builder.get(&after_challenge_values.local, i));
975 after_challenge
976 .next
977 .push(builder.get(&after_challenge_values.next, i));
978 }
979
980 let folded_constraints = Self::eval_constraints(
981 builder,
982 &constants.symbolic_constraints,
983 preprocessed,
984 &partitioned_main_values,
985 public_values,
986 &sels,
987 alpha,
988 after_challenge,
989 challenges,
990 exposed_values_after_challenge,
991 );
992
993 let num_quotient_chunks = 1 << constants.log_quotient_degree();
994 let mut quotient = vec![];
995 builder.assert_usize_eq(quotient_chunks.len(), RVar::from(num_quotient_chunks));
997 for i in 0..num_quotient_chunks {
999 let chunk = builder.get("ient_chunks, i);
1000 builder.assert_usize_eq(RVar::from(C::EF::D), RVar::from(chunk.len()));
1002 let mut quotient_vals = vec![];
1004 for j in 0..C::EF::D {
1005 let value = builder.get(&chunk, j);
1006 quotient_vals.push(value);
1007 }
1008 quotient.push(quotient_vals);
1009 }
1010
1011 let quotient: Ext<_, _> = Self::recompute_quotient(builder, "ient, qc_domains, zeta);
1012
1013 builder.assert_ext_eq(folded_constraints * sels.inv_zeroifier, quotient);
1015 }
1016
1017 #[allow(clippy::too_many_arguments)]
1018 fn eval_constraints(
1019 builder: &mut Builder<C>,
1020 constraints: &SymbolicExpressionDag<C::F>,
1021 preprocessed_values: AdjacentOpenedValues<Ext<C::F, C::EF>>,
1022 partitioned_main_values: &[AdjacentOpenedValues<Ext<C::F, C::EF>>],
1023 public_values: &[Felt<C::F>],
1024 selectors: &LagrangeSelectors<Ext<C::F, C::EF>>,
1025 alpha: Ext<C::F, C::EF>,
1026 after_challenge: AdjacentOpenedValues<Ext<C::F, C::EF>>,
1027 challenges: &[Vec<Ext<C::F, C::EF>>],
1028 exposed_values_after_challenge: &[Vec<Ext<C::F, C::EF>>],
1029 ) -> Ext<C::F, C::EF> {
1030 let mut unflatten = |v: &[Ext<C::F, C::EF>]| {
1031 v.chunks_exact(C::EF::D)
1032 .map(|chunk| {
1033 builder.eval(
1034 chunk
1035 .iter()
1036 .enumerate()
1037 .map(|(e_i, &x)| x * C::EF::monomial(e_i).cons())
1038 .sum::<SymbolicExt<_, _>>(),
1039 )
1040 })
1041 .collect::<Vec<Ext<_, _>>>()
1042 };
1043
1044 let after_challenge_values = AdjacentOpenedValues {
1045 local: unflatten(&after_challenge.local),
1046 next: unflatten(&after_challenge.next),
1047 };
1048
1049 let mut folder: RecursiveVerifierConstraintFolder<C> = GenericVerifierConstraintFolder {
1050 preprocessed: VerticalPair::new(
1051 RowMajorMatrixView::new_row(&preprocessed_values.local),
1052 RowMajorMatrixView::new_row(&preprocessed_values.next),
1053 ),
1054 partitioned_main: partitioned_main_values
1055 .iter()
1056 .map(|main_values| {
1057 VerticalPair::new(
1058 RowMajorMatrixView::new_row(&main_values.local),
1059 RowMajorMatrixView::new_row(&main_values.next),
1060 )
1061 })
1062 .collect(),
1063 after_challenge: vec![VerticalPair::new(
1064 RowMajorMatrixView::new_row(&after_challenge_values.local),
1065 RowMajorMatrixView::new_row(&after_challenge_values.next),
1066 )],
1067 challenges,
1068 is_first_row: selectors.is_first_row,
1069 is_last_row: selectors.is_last_row,
1070 is_transition: selectors.is_transition,
1071 alpha,
1072 accumulator: SymbolicExt::ZERO,
1073 public_values,
1074 exposed_values_after_challenge,
1075 _marker: PhantomData,
1076 };
1077 folder.eval_constraints(constraints);
1078
1079 builder.eval(folder.accumulator)
1080 }
1081
1082 fn recompute_quotient(
1083 builder: &mut Builder<C>,
1084 quotient_chunks: &[Vec<Ext<C::F, C::EF>>],
1085 qc_domains: Vec<TwoAdicMultiplicativeCosetVariable<C>>,
1086 zeta: Ext<C::F, C::EF>,
1087 ) -> Ext<C::F, C::EF> {
1088 let zps = qc_domains
1089 .iter()
1090 .enumerate()
1091 .map(|(i, domain)| {
1092 qc_domains
1093 .iter()
1094 .enumerate()
1095 .filter(|(j, _)| *j != i)
1096 .map(|(_, other_domain)| {
1097 let first_point: Ext<_, _> = builder.eval(domain.first_point());
1098 other_domain.zp_at_point(builder, zeta)
1099 * other_domain.zp_at_point(builder, first_point).inverse()
1100 })
1101 .product::<SymbolicExt<_, _>>()
1102 })
1103 .collect::<Vec<SymbolicExt<_, _>>>()
1104 .into_iter()
1105 .map(|x| builder.eval(x))
1106 .collect::<Vec<Ext<_, _>>>();
1107
1108 builder.eval(
1109 quotient_chunks
1110 .iter()
1111 .enumerate()
1112 .map(|(ch_i, ch)| {
1113 assert_eq!(ch.len(), C::EF::D);
1114 ch.iter()
1115 .enumerate()
1116 .map(|(e_i, &c)| zps[ch_i] * C::EF::monomial(e_i) * c)
1117 .sum::<SymbolicExt<_, _>>()
1118 })
1119 .sum::<SymbolicExt<_, _>>(),
1120 )
1121 }
1122}
1123
1124fn assert_cumulative_sums<C: Config>(
1125 builder: &mut Builder<C>,
1126 air_proofs: &Array<C, AirProofDataVariable<C>>,
1127 num_challenges_to_sample: &Array<C, Usize<C::N>>,
1128) {
1129 let num_phase = num_challenges_to_sample.len();
1130 builder.if_eq(num_phase, RVar::one()).then(|builder| {
1132 let cumulative_sum: Ext<C::F, C::EF> = builder.eval(C::F::ZERO);
1133 builder
1134 .range(0, air_proofs.len())
1135 .for_each(|i_vec, builder| {
1136 let i = i_vec[0];
1137 let air_proof_input = builder.get(air_proofs, i);
1138 let exposed_values = air_proof_input.exposed_values_after_challenge;
1139
1140 builder
1141 .if_ne(exposed_values.len(), RVar::zero())
1142 .then(|builder| {
1143 builder.assert_usize_eq(exposed_values.len(), RVar::one());
1145 let values = builder.get(&exposed_values, RVar::zero());
1146 builder.assert_usize_eq(values.len(), RVar::one());
1148
1149 let summand = builder.get(&values, RVar::zero());
1150 builder.assign(&cumulative_sum, cumulative_sum + summand);
1151 });
1152 });
1153 builder.assert_ext_eq(cumulative_sum, C::EF::ZERO.cons());
1154 });
1155}
1156
1157fn cast_usize_to_felt<C: Config>(builder: &mut Builder<C>, val: Usize<C::N>) -> Felt<C::F>
1160where
1161 C::F: TwoAdicField,
1162{
1163 if builder.flags.static_only {
1164 builder.eval(C::F::from_canonical_usize(val.value()))
1165 } else {
1166 builder.unsafe_cast_var_to_felt(val.get_var())
1167 }
1168}