openvm_native_recursion/stark/
mod.rs

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    /// Create a new instance of the program for the
52    /// [`openvm_stark_sdk::config::baby_bear_poseidon2`]
53    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    /// Create a new instance of the program for the
65    /// [`openvm_stark_sdk::config::baby_bear_poseidon2`]
66    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    /// Reference: [openvm_stark_backend::verifier::MultiTraceStarkVerifier::verify].
108    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            // Recycle stack space after verifying
119            let mut tmp_builder = builder.create_sub_builder();
120            // Recycle heap space after verifying by resetting the heap pointer.
121            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    /// Reference: [openvm_stark_backend::verifier::MultiTraceStarkVerifier::verify_raps].
130    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        // (T01a): `air_ids` is a subsequence of `0..(m_advice.per_air.len())`.
152
153        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        // Currently only support 0 or 1 phase is supported.
168        // (T01b): `num_challenges_to_sample.len() < 2`.
169
170        let num_phases = RVar::from(num_challenges_to_sample.len());
171        // Outer shape: number of challenge phases with exposed values must match VK. (T01c) then
172        // checks per-phase lengths and observes values for interacting AIRs only.
173        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            // Assert that each index in `air_perm_by_height` is unique and in range [0, num_airs).
193            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            // Check that each index of mask was set, i.e., that `air_perm_by_height` is a
205            // permutation. Also check that permutation is decreasing by height.
206            let prev_log_height_plus_one: Usize<_> =
207                builder.eval(RVar::from(MAX_TWO_ADICITY - pcs.config.log_blowup + 1));
208            // air_perm_by_height and mask both have length num_airs
209            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        // (T02a): `air_perm_by_height` is a valid permutation of `0..num_airs`.
227        // (T02b): For all `i`, `air_proofs[i].log_degree <= MAX_TWO_ADICITY - log_blowup`.
228        // (T02c): For all `0<=i<num_air-1`, `air_proofs[air_perm_by_height[i]].log_degree >=
229        // air_proofs[air_perm_by_height[i+1]].log_degree`.
230        builder.assert_nonzero(&air_proofs.len());
231        let log_max_height = {
232            let index = builder.get(air_perm_by_height, RVar::zero());
233            // SAFETY: `air_proofs.len() != 0` and `air_perm_by_height` is a permutation of
234            // `0..air_proofs.len()`, so `index < air_proofs.len()`.
235            let air_proof = builder.get(air_proofs, index);
236            RVar::from(air_proof.log_degree.clone())
237        };
238
239        // OK: trace_height_constraint_system comes from vkey so requirements of
240        // `check_trace_height_constraints` are met.
241        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        // (T03a): shapes of public values in `air_proofs` have been validated.
258
259        builder.cycle_tracker_start("stage-c-build-rounds");
260
261        // Count the number of main trace commitments together to save a loop.
262        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        // Observe main trace commitments
309        builder.assert_usize_eq(main_trace_commits.len(), num_main_commits);
310        // (T04a): shapes of `main_trace_commits` have been validated.
311        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        // Assumption: (T01b) `num_phases` is 0 or 1.
325        builder.if_eq(num_phases, RVar::one()).then(|builder| {
326            // Proof of work phase.
327            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            // Sample challenges needed in this phase.
336            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                        // Only support 1 challenge phase
352                        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                        // (T01c): shape of `exposed_values_after_challenge` is verified
363                        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            // Observe single commitment to all trace matrices in this phase.
374            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        // Build domains
389        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            // Assumption: (T02b) `log_degree < MAX_TWO_ADICITY`
401            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            // Assumption: (T02b) `log_degree <= MAX_TWO_ADICITY - low_blowup`
413            // Because the VK ensures `log_quotient_degree <= log_blowup`, this won't access an out
414            // of bound index.
415            let quotient_domain =
416                domain.create_disjoint_domain(builder, log_quotient_size, Some(pcs.config.clone()));
417            builder.set_value(&quotient_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(&quotient_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        // Build the opening rounds
439
440        // <Number of main trace commitments> = <number of cached main traces> + 1
441        // All common main traces are committed together.
442        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        // T05a: The shape of `opening.values.preprocessed` has been validated.
448
449        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        // For rounds which don't need permutation
455        let null_perm = builder.array(0);
456
457        // 1. First the preprocessed trace openings: one round per AIR with preprocessing.
458        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                    // Assumption: (T05a) `opening.values.preprocessed` has been validated.
466                    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                    // Assumption: each AIR with preprocessed trace has its own commitment and
477                    // opening values
478                    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        // 2. Then the main trace openings.
504        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                    // common_main_mats
557                    // SAFETY: `common_main_values_per_mat.len()` was asserted to equal
558                    // `num_airs`, so every selected AIR contributes one common-main entry.
559                    // `common_main_matrix_idx` counts prior AIRs in this same AIR order.
560                    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        // T05b: the shape of `opening.values.main` has been validated
581        {
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        // 3. After challenge: one per phase
596        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                            // Only 1 phase is supported.
618                            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        // T05c: the shape of `opening.values.main` has been validated
662
663        // 4. Quotient domains and openings
664
665        // The permutation array for the quotient chunks.
666        // For example:
667        // There are 2 AIRs, X and Y. X has 2 quotient chunks, X_1 and X_2. Y has 3
668        // quotient chunks, Y_1, Y_2, and Y_3.
669        // `air_perm_by_height` is [1, 0].
670        // Because quotient chunks have the same height as the trace of its AIR. So the permutation
671        // array is [Y_1, Y_2, Y_3, X_1, X_2] = [2, 3, 4, 0, 1].
672        // AIR index -> its offset in the permutation array.
673        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(&quotient_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(&quotient_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(&quotient_mats, qc_index.clone(), qc_mat);
713                    builder.set(&quotient_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        // Sanity check: the number of rounds matches.
726        builder.assert_usize_eq(round_idx, total_rounds);
727
728        builder.cycle_tracker_end("stage-c-build-rounds");
729
730        // Verify the pcs proof
731        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        // Convert challenges into a fixed-shape array.
750        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                            // SAFETY: `common_main_values_per_mat.len()` was asserted to equal
799                            // `num_airs`, so every selected AIR contributes one common-main entry.
800                            // `common_main_matrix_idx` counts prior AIRs in this same AIR order.
801                            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                            // One phase for now
814                            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(&quotient_domains, air_idx.clone());
824                        // Check that the quotient data matches the chip's data.
825                        let log_quotient_degree = air_const.log_quotient_degree();
826                        let quotient_chunks =
827                            builder.get(&opening.values.quotient, air_idx.clone());
828
829                        // Get the domains from the chip itself.
830                        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        // Assert that all provided AIRs were verified.
871        builder.assert_usize_eq(air_idx, air_ids.len());
872
873        builder.cycle_tracker_end("stage-e-verify-constraints");
874    }
875
876    /// For constraint `i` in `trace_height_constraints` with coefficients a_i1, ..., a_ik and
877    /// threshold b_i, checks that
878    ///    a_i1 * x_1 + ... + a_ik * x_k < b_i,
879    /// where `x_i` is the height of the trace given in `air_proofs[j]` with `j` such that
880    /// `air_proofs[j].air_id = i`.
881    ///
882    /// The caller must ensure the following is met:
883    /// * `constraint_system.constraints.len() == num_airs`
884    /// * `constraint_system.constraints[air_proofs[i].air_id]` is valid for all `i`
885    /// * For all `i`, `air_proofs[i].log_degree < MAX_TWO_ADICITY`
886    fn check_trace_height_constraints(
887        builder: &mut Builder<C>,
888        constraint_system: &TraceHeightConstraintSystem<C>,
889        air_proofs: &Array<C, AirProofDataVariable<C>>,
890    ) {
891        // We compute and store `a_i1 * x_1 + ... + a_ik * x_k` in `values[i]`.
892        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        // Will index by log_air_height. Max value is assumed to be MAX_TWO_ADICITY - 1 because
905        // `log_blowup >= 1`.
906        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    /// Reference: [openvm_stark_backend::verifier::constraints::verify_single_rap_constraints]
944    #[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        // Assert that the length of the quotient chunk arrays match the expected length.
1041        builder.assert_usize_eq(quotient_chunks.len(), RVar::from(num_quotient_chunks));
1042        // Collect the quotient values into vectors.
1043        for i in 0..num_quotient_chunks {
1044            let chunk = builder.get(&quotient_chunks, i);
1045            // Assert that the chunk length matches the expected length.
1046            builder.assert_usize_eq(RVar::from(C::EF::DIMENSION), RVar::from(chunk.len()));
1047            // Collect the quotient values into vectors.
1048            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, &quotient, qc_domains, zeta);
1057
1058        // Assert that the quotient times the zerofier is equal to the folded constraints.
1059        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    // Currently only support 0 or 1 phase is supported.
1187    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                        // Verifier does not support more than 1 challenge phase
1200                        builder.assert_usize_eq(exposed_values.len(), RVar::one());
1201                        let values = builder.get(&exposed_values, RVar::zero());
1202                        // Only exposed value should be cumulative sum
1203                        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
1213/// Conversion used for challenger to observe usize. The `val` must come
1214/// from compile time constant in static mode.
1215fn 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}