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::{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    /// Create a new instance of the program for the [`openvm_stark_sdk::config::baby_bear_poseidon2`]
52    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    /// Create a new instance of the program for the [`openvm_stark_sdk::config::baby_bear_poseidon2`]
64    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    /// Reference: [openvm_stark_backend::verifier::MultiTraceStarkVerifier::verify].
106    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            // Recycle stack space after verifying
117            let mut tmp_builder = builder.create_sub_builder();
118            // Recycle heap space after verifying by resetting the heap pointer.
119            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    /// Reference: [openvm_stark_backend::verifier::MultiTraceStarkVerifier::verify_raps].
128    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        // (T01a): `air_ids` is a subsequence of `0..(m_advice.per_air.len())`.
150
151        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        // Currently only support 0 or 1 phase is supported.
166        // (T01b): `num_challenges_to_sample.len() < 2`.
167
168        let num_phases = RVar::from(num_challenges_to_sample.len());
169        // Here the shape of `exposed_values_after_challenge` is not verified. But it's verified later (T01c).
170        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            // Assert that each index in `air_perm_by_height` is unique and in range [0, num_airs).
180            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            // Check that each index of mask was set, i.e., that `air_perm_by_height` is a permutation.
188            // Also check that permutation is decreasing by height.
189            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        // (T02a): `air_perm_by_height` is a valid permutation of `0..num_airs`.
209        // (T02b): For all `i`, `air_proofs[i].log_degree <= MAX_TWO_ADICITY - log_blowup`.
210        // (T02c): For all `0<=i<num_air-1`, `air_proofs[air_perm_by_height[i]].log_degree >= air_proofs[air_perm_by_height[i+1]].log_degree`.
211        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        // OK: trace_height_constraint_system comes from vkey so requirements of
218        // `check_trace_height_constraints` are met.
219        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        // (T03a): shapes of public values in `air_proofs` have been validated.
236
237        builder.cycle_tracker_start("stage-c-build-rounds");
238
239        // Count the number of main trace commitments together to save a loop.
240        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        // Observe main trace commitments
287        builder.assert_usize_eq(main_trace_commits.len(), num_main_commits);
288        // (T04a): shapes of `main_trace_commits` have been validated.
289        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        // Assumption: (T01b) `num_phases` is 0 or 1.
303        builder.if_eq(num_phases, RVar::one()).then(|builder| {
304            // Proof of work phase.
305            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            // Sample challenges needed in this phase.
314            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                        // Only support 1 challenge phase
330                        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                        // (T01c): shape of `exposed_values_after_challenge` is verified
340                        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            // Observe single commitment to all trace matrices in this phase.
351            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        // Build domains
365        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            // Assumption: (T02b) `log_degree < MAX_TWO_ADICITY`
377            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            // Assumption: (T02b) `log_degree <= MAX_TWO_ADICITY - low_blowup`
389            // Because the VK ensures `log_quotient_degree <= log_blowup`, this won't access an out
390            // of bound index.
391            let quotient_domain =
392                domain.create_disjoint_domain(builder, log_quotient_size, Some(pcs.config.clone()));
393            builder.set_value(&quotient_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(&quotient_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        // Build the opening rounds
415
416        // <Number of main trace commitments> = <number of cached main traces> + 1
417        // All common main traces are committed together.
418        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        // T05a: The shape of `opening.values.preprocessed` has been validated.
424
425        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        // For rounds which don't need permutation
431        let null_perm = builder.array(0);
432
433        // 1. First the preprocessed trace openings: one round per AIR with preprocessing.
434        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                    // Assumption: (T05a) `opening.values.preprocessed` has been validated.
442                    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                    // Assumption: each AIR with preprocessed trace has its own commitment and opening values
453                    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        // 2. Then the main trace openings.
479        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                    // common_main_mats
530                    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        // T05b: the shape of `opening.values.main` has been validated
551        {
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        // 3. After challenge: one per phase
566        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                            // Only 1 phase is supported.
585                            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        // T05c: the shape of `opening.values.main` has been validated
629
630        // 4. Quotient domains and openings
631
632        // The permutation array for the quotient chunks.
633        // For example:
634        // There are 2 AIRs, X and Y. X has 2 quotient chunks, X_1 and X_2. Y has 3
635        // quotient chunks, Y_1, Y_2, and Y_3.
636        // `air_perm_by_height` is [1, 0].
637        // Because quotient chunks have the same height as the trace of its AIR. So the permutation
638        // array is [Y_1, Y_2, Y_3, X_1, X_2] = [2, 3, 4, 0, 1].
639        // AIR index -> its offset in the permutation array.
640        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(&quotient_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(&quotient_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(&quotient_mats, qc_index.clone(), qc_mat);
680                    builder.set(&quotient_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        // Sanity check: the number of rounds matches.
693        builder.assert_usize_eq(round_idx, total_rounds);
694
695        builder.cycle_tracker_end("stage-c-build-rounds");
696
697        // Verify the pcs proof
698        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        // Convert challenges into a fixed-shape array.
717        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                    // One phase for now
773                    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(&quotient_domains, air_idx.clone());
782                // Check that the quotient data matches the chip's data.
783                let log_quotient_degree = air_const.log_quotient_degree();
784                let quotient_chunks = builder.get(&opening.values.quotient, air_idx.clone());
785
786                // Get the domains from the chip itself.
787                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        // Assert that all provided AIRs were verified.
826        builder.assert_usize_eq(air_idx, air_ids.len());
827
828        builder.cycle_tracker_end("stage-e-verify-constraints");
829    }
830
831    /// For constraint `i` in `trace_height_constraints` with coefficients a_i1, ..., a_ik and
832    /// threshold b_i, checks that
833    ///    a_i1 * x_1 + ... + a_ik * x_k < b_i,
834    /// where `x_i` is the height of the trace given in `air_proofs[j]` with `j` such that
835    /// `air_proofs[j].air_id = i`.
836    ///
837    /// The caller must ensure the following is met:
838    /// * `constraint_system.constraints.len() == num_airs`
839    /// * `constraint_system.constraints[air_proofs[i].air_id]` is valid for all `i`
840    /// * For all `i`, `air_proofs[i].log_degree < MAX_TWO_ADICITY`
841    fn check_trace_height_constraints(
842        builder: &mut Builder<C>,
843        constraint_system: &TraceHeightConstraintSystem<C>,
844        air_proofs: &Array<C, AirProofDataVariable<C>>,
845    ) {
846        // We compute and store `a_i1 * x_1 + ... + a_ik * x_k` in `values[i]`.
847        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        // Will index by log_air_height. Max value is assumed to be MAX_TWO_ADICITY - 1 because
860        // `log_blowup >= 1`.
861        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    /// Reference: [openvm_stark_backend::verifier::constraints::verify_single_rap_constraints]
899    #[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        // Assert that the length of the quotient chunk arrays match the expected length.
996        builder.assert_usize_eq(quotient_chunks.len(), RVar::from(num_quotient_chunks));
997        // Collect the quotient values into vectors.
998        for i in 0..num_quotient_chunks {
999            let chunk = builder.get(&quotient_chunks, i);
1000            // Assert that the chunk length matches the expected length.
1001            builder.assert_usize_eq(RVar::from(C::EF::D), RVar::from(chunk.len()));
1002            // Collect the quotient values into vectors.
1003            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, &quotient, qc_domains, zeta);
1012
1013        // Assert that the quotient times the zerofier is equal to the folded constraints.
1014        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    // Currently only support 0 or 1 phase is supported.
1131    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                        // Verifier does not support more than 1 challenge phase
1144                        builder.assert_usize_eq(exposed_values.len(), RVar::one());
1145                        let values = builder.get(&exposed_values, RVar::zero());
1146                        // Only exposed value should be cumulative sum
1147                        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
1157/// Conversion used for challenger to observe usize. The `val` must come
1158/// from compile time constant in static mode.
1159fn 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}