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