openvm_native_recursion/stark/
mod.rs

1use std::{iter::zip, marker::PhantomData};
2
3use itertools::Itertools;
4use openvm_circuit::arch::instructions::program::Program;
5use openvm_native_compiler::{
6    conversion::CompilerOptions,
7    ir::{Array, ArrayLike, Builder, Config, DslIr, Ext, ExtConst, Felt, SymbolicExt, Usize, Var},
8    prelude::RVar,
9};
10use openvm_native_compiler_derive::iter_zip;
11use openvm_stark_backend::{
12    air_builders::symbolic::SymbolicExpressionDag,
13    p3_commit::LagrangeSelectors,
14    p3_field::{BasedVectorSpace, PrimeCharacteristicRing, TwoAdicField},
15    p3_matrix::{dense::RowMajorMatrixView, stack::VerticalPair},
16    proof::{AdjacentOpenedValues, Proof},
17    verifier::GenericVerifierConstraintFolder,
18};
19use openvm_stark_sdk::{
20    config::{baby_bear_poseidon2::BabyBearPoseidon2Config, FriParameters},
21    p3_baby_bear::BabyBear,
22};
23
24use crate::{
25    challenger::{duplex::DuplexChallengerVariable, ChallengerVariable},
26    commit::{PcsVariable, PolynomialSpaceVariable},
27    folder::RecursiveVerifierConstraintFolder,
28    fri::{
29        types::{TwoAdicPcsMatsVariable, TwoAdicPcsRoundVariable},
30        TwoAdicFriPcsVariable, TwoAdicMultiplicativeCosetVariable, MAX_TWO_ADICITY,
31    },
32    hints::Hintable,
33    types::{InnerConfig, MultiStarkVerificationAdvice, StarkVerificationAdvice},
34    utils::const_fri_config,
35    vars::{
36        AdjacentOpenedValuesVariable, AirProofDataVariable, CommitmentsVariable,
37        StarkProofVariable, TraceHeightConstraintSystem,
38    },
39    view::get_advice_per_air,
40};
41
42#[cfg(feature = "static-verifier")]
43pub mod outer;
44
45#[derive(Debug, Clone, Copy)]
46pub struct VerifierProgram<C: Config> {
47    _phantom: PhantomData<C>,
48}
49
50impl VerifierProgram<InnerConfig> {
51    /// Create a new instance of the program for the
52    /// [`openvm_stark_sdk::config::baby_bear_poseidon2`]
53    pub fn build(
54        constants: MultiStarkVerificationAdvice<InnerConfig>,
55        fri_params: &FriParameters,
56    ) -> Program<BabyBear> {
57        let options = CompilerOptions {
58            enable_cycle_tracker: true,
59            ..Default::default()
60        };
61        Self::build_with_options(constants, fri_params, options)
62    }
63
64    /// Create a new instance of the program for the
65    /// [`openvm_stark_sdk::config::baby_bear_poseidon2`]
66    pub fn build_with_options(
67        constants: MultiStarkVerificationAdvice<InnerConfig>,
68        fri_params: &FriParameters,
69        options: CompilerOptions,
70    ) -> Program<BabyBear> {
71        let mut builder = Builder::<InnerConfig>::default();
72
73        builder.cycle_tracker_start("VerifierProgram");
74        builder.cycle_tracker_start("ReadingProofFromInput");
75        let input: StarkProofVariable<_> = builder.uninit();
76        Proof::<BabyBearPoseidon2Config>::witness(&input, &mut builder);
77        builder.cycle_tracker_end("ReadingProofFromInput");
78
79        builder.cycle_tracker_start("InitializePcsConst");
80        let pcs = TwoAdicFriPcsVariable {
81            config: const_fri_config(&mut builder, fri_params),
82        };
83        builder.cycle_tracker_end("InitializePcsConst");
84        StarkVerifier::verify::<DuplexChallengerVariable<_>>(
85            &mut builder,
86            &pcs,
87            &constants,
88            &input,
89        );
90
91        builder.cycle_tracker_end("VerifierProgram");
92        builder.halt();
93
94        builder.compile_isa_with_options(options)
95    }
96}
97
98#[derive(Debug, Clone, Copy)]
99pub struct StarkVerifier<C: Config> {
100    _phantom: PhantomData<C>,
101}
102
103impl<C: Config> StarkVerifier<C>
104where
105    C::F: TwoAdicField,
106{
107    /// Reference: [openvm_stark_backend::verifier::MultiTraceStarkVerifier::verify].
108    pub fn verify<CH: ChallengerVariable<C>>(
109        builder: &mut Builder<C>,
110        pcs: &TwoAdicFriPcsVariable<C>,
111        m_advice: &MultiStarkVerificationAdvice<C>,
112        proof: &StarkProofVariable<C>,
113    ) {
114        if builder.flags.static_only {
115            let mut challenger = CH::new(builder);
116            Self::verify_raps(builder, pcs, m_advice, &mut challenger, proof);
117        } else {
118            // Recycle stack space after verifying
119            let mut tmp_builder = builder.create_sub_builder();
120            // Recycle heap space after verifying by resetting the heap pointer.
121            let old_heap_ptr = tmp_builder.load_heap_ptr();
122            let mut challenger = CH::new(&mut tmp_builder);
123            Self::verify_raps(&mut tmp_builder, pcs, m_advice, &mut challenger, proof);
124            tmp_builder.store_heap_ptr(old_heap_ptr);
125            builder.operations.extend(tmp_builder.operations);
126        }
127    }
128
129    /// Reference: [openvm_stark_backend::verifier::MultiTraceStarkVerifier::verify_raps].
130    pub fn verify_raps(
131        builder: &mut Builder<C>,
132        pcs: &TwoAdicFriPcsVariable<C>,
133        m_advice: &MultiStarkVerificationAdvice<C>,
134        challenger: &mut impl ChallengerVariable<C>,
135        proof: &StarkProofVariable<C>,
136    ) where
137        C::F: TwoAdicField,
138        C::EF: TwoAdicField,
139    {
140        let pre_hash = builder.constant(m_advice.pre_hash.clone());
141        challenger.observe_digest(builder, pre_hash);
142        let air_ids = proof.get_air_ids(builder);
143        let num_airs = cast_usize_to_felt(builder, air_ids.len());
144        challenger.observe(builder, num_airs);
145        iter_zip!(builder, air_ids).for_each(|ptr_vec, builder| {
146            let air_id = builder.iter_ptr_get(&air_ids, ptr_vec[0]);
147            let air_id = cast_usize_to_felt(builder, air_id);
148            challenger.observe(builder, air_id);
149        });
150        let m_advice_var = get_advice_per_air(builder, m_advice, &air_ids);
151        // (T01a): `air_ids` is a subsequence of `0..(m_advice.per_air.len())`.
152
153        let StarkProofVariable::<C> {
154            commitments,
155            opening,
156            per_air: air_proofs,
157            air_perm_by_height,
158            log_up_pow_witness,
159        } = proof;
160
161        if m_advice.num_challenges_to_sample.len() > 1 {
162            panic!("Only support 0 or 1 phase is supported");
163        }
164
165        let num_airs = RVar::from(air_proofs.len());
166        let num_challenges_to_sample = m_advice_var.num_challenges_to_sample(builder);
167        // Currently only support 0 or 1 phase is supported.
168        // (T01b): `num_challenges_to_sample.len() < 2`.
169
170        let num_phases = RVar::from(num_challenges_to_sample.len());
171        // 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        challenger.check_witness(builder, m_advice.deep_pow_bits, opening.deep_pow_witness);
365        let zeta = challenger.sample_ext(builder);
366
367        let num_prep_rounds: Usize<_> = builder.eval(RVar::zero());
368
369        // Build domains
370        let domains = builder.array(num_airs);
371        let quotient_domains = builder.array(num_airs);
372        let trace_points_per_domain = builder.array(num_airs);
373        let quotient_chunk_domains = builder.array(num_airs);
374        let num_quotient_mats: Usize<_> = builder.eval(RVar::zero());
375        builder.range(0, num_airs).for_each(|i_vec, builder| {
376            let i = i_vec[0];
377            let air_proof = builder.get(air_proofs, i);
378            let log_degree: RVar<_> = air_proof.log_degree.clone().into();
379            let advice = builder.get(&m_advice_var.per_air, i);
380
381            // Assumption: (T02b) `log_degree < MAX_TWO_ADICITY`
382            let domain = pcs.natural_domain_for_log_degree(builder, log_degree);
383
384            let trace_points = builder.array::<Ext<_, _>>(2);
385            let zeta_next = domain.next_point(builder, zeta);
386            builder.set_value(&trace_points, RVar::zero(), zeta);
387            builder.set_value(&trace_points, RVar::one(), zeta_next);
388
389            let log_quotient_degree = RVar::from(advice.log_quotient_degree);
390            let quotient_degree =
391                RVar::from(builder.sll::<Usize<_>>(RVar::one(), log_quotient_degree));
392            let log_quotient_size = builder.eval_expr(log_degree + log_quotient_degree);
393            // Assumption: (T02b) `log_degree <= MAX_TWO_ADICITY - low_blowup`
394            // Because the VK ensures `log_quotient_degree <= log_blowup`, this won't access an out
395            // of bound index.
396            let quotient_domain =
397                domain.create_disjoint_domain(builder, log_quotient_size, Some(pcs.config.clone()));
398            builder.set_value(&quotient_domains, i, quotient_domain.clone());
399
400            let qc_domains =
401                quotient_domain.split_domains(builder, log_quotient_degree, quotient_degree);
402            builder.assign(
403                &num_quotient_mats,
404                num_quotient_mats.clone() + quotient_degree,
405            );
406
407            builder.set_value(&domains, i, domain);
408            builder.set_value(&trace_points_per_domain, i, trace_points);
409            builder.set_value(&quotient_chunk_domains, i, qc_domains);
410
411            builder
412                .if_eq(advice.preprocessed_data.len(), RVar::one())
413                .then(|builder| {
414                    builder.assign(&num_prep_rounds, num_prep_rounds.clone() + RVar::one());
415                });
416        });
417        let num_quotient_mats = RVar::from(num_quotient_mats);
418
419        // Build the opening rounds
420
421        // <Number of main trace commitments> = <number of cached main traces> + 1
422        // All common main traces are committed together.
423        let num_main_rounds = builder.eval_expr(num_cached_mains + RVar::one());
424        let num_challenge_rounds: RVar<_> = num_challenges_to_sample.len().into();
425        let num_quotient_rounds = RVar::one();
426
427        builder.assert_usize_eq(opening.values.preprocessed.len(), num_prep_rounds.clone());
428        // T05a: The shape of `opening.values.preprocessed` has been validated.
429
430        let total_rounds = builder.eval_expr(
431            num_prep_rounds + num_main_rounds + num_challenge_rounds + num_quotient_rounds,
432        );
433
434        let rounds = builder.array::<TwoAdicPcsRoundVariable<_>>(total_rounds);
435        // For rounds which don't need permutation
436        let null_perm = builder.array(0);
437
438        // 1. First the preprocessed trace openings: one round per AIR with preprocessing.
439        let round_idx: Usize<_> = builder.eval(RVar::zero());
440        builder.range(0, num_airs).for_each(|i_vec, builder| {
441            let i = i_vec[0];
442            let advice = builder.get(&m_advice_var.per_air, i);
443            builder
444                .if_eq(advice.preprocessed_data.len(), RVar::one())
445                .then(|builder| {
446                    // Assumption: (T05a) `opening.values.preprocessed` has been validated.
447                    let prep = builder.get(&opening.values.preprocessed, round_idx.clone());
448                    let batch_commit = builder.get(&advice.preprocessed_data, RVar::zero());
449                    let prep_width =
450                        RVar::from(builder.get(&advice.width.preprocessed, RVar::zero()));
451
452                    let domain = builder.get(&domains, i);
453                    let trace_points = builder.get(&trace_points_per_domain, i);
454
455                    builder.assert_usize_eq(prep_width, prep.local.len());
456                    builder.assert_usize_eq(prep_width, prep.next.len());
457                    // Assumption: each AIR with preprocessed trace has its own commitment and
458                    // opening values
459                    let values = builder.array::<Array<C, _>>(2);
460                    builder.set_value(&values, 0, prep.local);
461                    builder.set_value(&values, 1, prep.next);
462                    let prep_mat = TwoAdicPcsMatsVariable::<C> {
463                        domain,
464                        values,
465                        points: trace_points.clone(),
466                    };
467
468                    let mats: Array<_, TwoAdicPcsMatsVariable<_>> = builder.array(1);
469                    builder.set_value(&mats, 0, prep_mat);
470
471                    builder.set_value(
472                        &rounds,
473                        round_idx.clone(),
474                        TwoAdicPcsRoundVariable {
475                            batch_commit,
476                            mats,
477                            permutation: null_perm.clone(),
478                        },
479                    );
480                    builder.assign(&round_idx, round_idx.clone() + RVar::one());
481                });
482        });
483
484        // 2. Then the main trace openings.
485        let main_commit_idx: Usize<_> = builder.eval(RVar::zero());
486        builder.assert_usize_eq(opening.values.main.len(), num_main_commits);
487        let common_main_values_per_mat = builder.get(&opening.values.main, num_cached_mains);
488        let common_main_mats = builder.array(num_common_main_traces);
489        let common_main_matrix_idx: Usize<_> = builder.eval(RVar::zero());
490        builder.range(0, num_airs).for_each(|i_vec, builder| {
491            let i = i_vec[0];
492            let advice = builder.get(&m_advice_var.per_air, i);
493            let cached_main_widths = &advice.width.cached_mains;
494
495            let domain = builder.get(&domains, i);
496            let trace_points = builder.get(&trace_points_per_domain, i);
497
498            iter_zip!(builder, cached_main_widths).for_each(|ptr_vec, builder| {
499                let cached_main_width = builder.iter_ptr_get(cached_main_widths, ptr_vec[0]);
500                let values_per_mat = builder.get(&opening.values.main, main_commit_idx.clone());
501                let batch_commit = builder.get(main_trace_commits, main_commit_idx.clone());
502                builder.assign(&main_commit_idx, main_commit_idx.clone() + RVar::one());
503
504                builder.assert_usize_eq(values_per_mat.len(), RVar::one());
505                let main = builder.get(&values_per_mat, RVar::zero());
506                let values = builder.array::<Array<C, _>>(2);
507                builder.assert_usize_eq(main.local.len(), cached_main_width.clone());
508                builder.assert_usize_eq(main.next.len(), cached_main_width);
509                builder.set_value(&values, 0, main.local);
510                builder.set_value(&values, 1, main.next);
511                let main_mat = TwoAdicPcsMatsVariable::<C> {
512                    domain: domain.clone(),
513                    values,
514                    points: trace_points.clone(),
515                };
516                let mats = builder.array(1);
517                builder.set_value(&mats, 0, main_mat);
518
519                builder.set_value(
520                    &rounds,
521                    round_idx.clone(),
522                    TwoAdicPcsRoundVariable {
523                        batch_commit,
524                        mats,
525                        permutation: null_perm.clone(),
526                    },
527                );
528                builder.assign(&round_idx, round_idx.clone() + RVar::one());
529            });
530
531            let common_main_width = RVar::from(advice.width.common_main);
532            builder
533                .if_ne(common_main_width, RVar::zero())
534                .then(|builder| {
535                    // common_main_mats
536                    let main =
537                        builder.get(&common_main_values_per_mat, common_main_matrix_idx.clone());
538
539                    let values = builder.array::<Array<C, _>>(2);
540                    builder.assert_usize_eq(main.local.len(), common_main_width);
541                    builder.assert_usize_eq(main.next.len(), common_main_width);
542                    builder.set_value(&values, 0, main.local);
543                    builder.set_value(&values, 1, main.next);
544                    let main_mat = TwoAdicPcsMatsVariable::<C> {
545                        domain: domain.clone(),
546                        values,
547                        points: trace_points.clone(),
548                    };
549                    builder.set_value(&common_main_mats, common_main_matrix_idx.clone(), main_mat);
550                    builder.assign(
551                        &common_main_matrix_idx,
552                        common_main_matrix_idx.clone() + RVar::one(),
553                    );
554                });
555        });
556        // T05b: the shape of `opening.values.main` has been validated
557        {
558            let batch_commit = builder.get(main_trace_commits, main_commit_idx.clone());
559            builder.set_value(
560                &rounds,
561                round_idx.clone(),
562                TwoAdicPcsRoundVariable {
563                    batch_commit,
564                    mats: common_main_mats,
565                    permutation: air_perm_by_height.clone(),
566                },
567            );
568            builder.assign(&round_idx, round_idx.clone() + RVar::one());
569        }
570
571        // 3. After challenge: one per phase
572        builder.assert_usize_eq(opening.values.after_challenge.len(), num_phases);
573        builder
574            .range(0, num_phases)
575            .for_each(|phase_idx_vec, builder| {
576                let phase_idx = phase_idx_vec[0];
577                let values_per_mat = builder.get(&opening.values.after_challenge, phase_idx);
578                builder.assert_usize_eq(values_per_mat.len(), num_after_challenge_traces);
579                let batch_commit = builder.get(after_challenge_commits, phase_idx);
580
581                let mat_idx: Usize<_> = builder.eval(RVar::zero());
582                let mats: Array<_, TwoAdicPcsMatsVariable<_>> =
583                    builder.array(num_after_challenge_traces);
584                builder.range(0, num_airs).for_each(|i_vec, builder| {
585                    let i = i_vec[0];
586                    let advice = builder.get(&m_advice_var.per_air, i);
587                    builder
588                        .if_ne(advice.width.after_challenge.len(), RVar::zero())
589                        .then(|builder| {
590                            // Only 1 phase is supported.
591                            builder
592                                .assert_usize_eq(advice.width.after_challenge.len(), RVar::one());
593                            let after_challenge_width = RVar::from(
594                                builder.get(&advice.width.after_challenge, RVar::zero()),
595                            );
596                            let domain = builder.get(&domains, i);
597                            let trace_points = builder.get(&trace_points_per_domain, i);
598
599                            let after_challenge = builder.get(&values_per_mat, mat_idx.clone());
600
601                            let values = builder.array::<Array<C, _>>(2);
602                            builder.assert_usize_eq(
603                                after_challenge.local.len(),
604                                after_challenge_width * RVar::from(C::EF::DIMENSION),
605                            );
606                            builder.assert_usize_eq(
607                                after_challenge.next.len(),
608                                after_challenge.local.len(),
609                            );
610                            builder.set_value(&values, 0, after_challenge.local);
611                            builder.set_value(&values, 1, after_challenge.next);
612                            let after_challenge_mat = TwoAdicPcsMatsVariable::<C> {
613                                domain,
614                                values,
615                                points: trace_points,
616                            };
617                            builder.set_value(&mats, mat_idx.clone(), after_challenge_mat);
618                            builder.inc(&mat_idx);
619                        });
620                });
621                builder.assert_usize_eq(mat_idx, num_after_challenge_traces);
622
623                builder.set_value(
624                    &rounds,
625                    round_idx.clone(),
626                    TwoAdicPcsRoundVariable {
627                        batch_commit,
628                        mats,
629                        permutation: air_perm_by_height.clone(),
630                    },
631                );
632                builder.assign(&round_idx, round_idx.clone() + RVar::one());
633            });
634        // T05c: the shape of `opening.values.main` has been validated
635
636        // 4. Quotient domains and openings
637
638        // The permutation array for the quotient chunks.
639        // For example:
640        // There are 2 AIRs, X and Y. X has 2 quotient chunks, X_1 and X_2. Y has 3
641        // quotient chunks, Y_1, Y_2, and Y_3.
642        // `air_perm_by_height` is [1, 0].
643        // Because quotient chunks have the same height as the trace of its AIR. So the permutation
644        // array is [Y_1, Y_2, Y_3, X_1, X_2] = [2, 3, 4, 0, 1].
645        // AIR index -> its offset in the permutation array.
646        let quotient_perm = builder.array(num_quotient_mats);
647        let perm_offset_per_air = builder.array::<Usize<_>>(num_airs);
648        let offset: Usize<_> = builder.eval(RVar::zero());
649        iter_zip!(builder, air_perm_by_height).for_each(|ptr_vec, builder| {
650            let air_index = builder.iter_ptr_get(air_perm_by_height, ptr_vec[0]);
651            builder.set(&perm_offset_per_air, air_index.clone(), offset.clone());
652            let qc_domains = builder.get(&quotient_chunk_domains, air_index);
653            builder.assign(&offset, offset.clone() + qc_domains.len());
654        });
655
656        let quotient_mats: Array<_, TwoAdicPcsMatsVariable<_>> = builder.array(num_quotient_mats);
657        let qc_points = builder.array::<Ext<_, _>>(1);
658        builder.set_value(&qc_points, 0, zeta);
659
660        let qc_index: Usize<_> = builder.eval(RVar::zero());
661        builder.assert_usize_eq(opening.values.quotient.len(), num_airs);
662        builder.range(0, num_airs).for_each(|i_vec, builder| {
663            let i = i_vec[0];
664            let opened_quotient = builder.get(&opening.values.quotient, i);
665            let qc_domains = builder.get(&quotient_chunk_domains, i);
666            let air_offset = builder.get(&perm_offset_per_air, i);
667
668            builder.assert_usize_eq(opened_quotient.len(), qc_domains.len());
669            let quotient_degree = qc_domains.len();
670            builder
671                .range(0, quotient_degree)
672                .for_each(|j_vec, builder| {
673                    let j = j_vec[0];
674                    let qc_dom = builder.get(&qc_domains, j);
675                    let qc_vals_array = builder.get(&opened_quotient, j);
676                    builder.assert_usize_eq(qc_vals_array.len(), RVar::from(4));
677                    let qc_values = builder.array::<Array<C, _>>(1);
678                    builder.set_value(&qc_values, 0, qc_vals_array);
679                    let qc_mat = TwoAdicPcsMatsVariable::<C> {
680                        domain: qc_dom,
681                        values: qc_values,
682                        points: qc_points.clone(),
683                    };
684                    let qc_offset = builder.eval_expr(air_offset.clone() + j);
685                    builder.set_value(&quotient_mats, qc_index.clone(), qc_mat);
686                    builder.set(&quotient_perm, qc_offset, RVar::from(qc_index.clone()));
687                    builder.assign(&qc_index, qc_index.clone() + RVar::one());
688                });
689        });
690        let quotient_round = TwoAdicPcsRoundVariable {
691            batch_commit: quotient_commit.clone(),
692            mats: quotient_mats,
693            permutation: quotient_perm,
694        };
695        builder.set_value(&rounds, round_idx.clone(), quotient_round);
696        builder.assign(&round_idx, round_idx.clone() + RVar::one());
697
698        // Sanity check: the number of rounds matches.
699        builder.assert_usize_eq(round_idx, total_rounds);
700
701        builder.cycle_tracker_end("stage-c-build-rounds");
702
703        // Verify the pcs proof
704        builder.cycle_tracker_start("stage-d-verify-pcs");
705        pcs.verify(
706            builder,
707            rounds,
708            opening.proof.clone(),
709            log_max_height,
710            challenger,
711        );
712        builder.cycle_tracker_end("stage-d-verify-pcs");
713
714        builder.cycle_tracker_start("stage-e-verify-constraints");
715        let after_challenge_idx: Usize<C::N> = builder.eval(C::N::ZERO);
716        let preprocessed_idx: Usize<_> = builder.eval(C::N::ZERO);
717        let cached_main_commit_idx: Usize<_> = builder.eval(C::N::ZERO);
718        let common_main_matrix_idx: Usize<_> = builder.eval(C::N::ZERO);
719        let air_idx: Usize<_> = builder.eval(C::N::ZERO);
720        let common_main_openings = builder.get(&opening.values.main, num_cached_mains);
721
722        // Convert challenges into a fixed-shape array.
723        let challenges = m_advice
724            .num_challenges_to_sample
725            .iter()
726            .enumerate()
727            .map(|(phase, &num_challenges_to_sample)| {
728                (0..num_challenges_to_sample)
729                    .map(|i| {
730                        let challenge: Ext<_, _> = builder.constant(C::EF::ZERO);
731                        builder
732                            .if_eq(
733                                m_advice_var.num_challenges_to_sample_mask[phase][i].clone(),
734                                RVar::one(),
735                            )
736                            .then(|builder| {
737                                let chs = builder.get(&challenges_per_phase, phase);
738                                let v = builder.get(&chs, i);
739                                builder.assign(&challenge, v);
740                            });
741                        challenge
742                    })
743                    .collect_vec()
744            })
745            .collect_vec();
746
747        for (i, air_const) in m_advice.per_air.iter().enumerate() {
748            let abs_air_idx = builder.get(&air_ids, air_idx.clone());
749            builder.if_eq(abs_air_idx, RVar::from(i)).then(|builder| {
750                let preprocessed_values = if air_const.preprocessed_data.is_some() {
751                    let ret =
752                        Some(builder.get(&opening.values.preprocessed, preprocessed_idx.clone()));
753                    builder.inc(&preprocessed_idx);
754                    ret
755                } else {
756                    None
757                };
758                let mut partitioned_main_values = (0..air_const.width.cached_mains.len())
759                    .map(|_| {
760                        let ret = builder.get(&opening.values.main, cached_main_commit_idx.clone());
761                        builder.inc(&cached_main_commit_idx);
762                        builder.get(&ret, 0)
763                    })
764                    .collect_vec();
765                if air_const.width.common_main > 0 {
766                    let common_main =
767                        builder.get(&common_main_openings, common_main_matrix_idx.clone());
768                    builder.inc(&common_main_matrix_idx);
769                    partitioned_main_values.push(common_main);
770                }
771
772                let after_challenge_values = if air_const.width.after_challenge.is_empty() {
773                    AdjacentOpenedValuesVariable {
774                        local: builder.vec(vec![]),
775                        next: builder.vec(vec![]),
776                    }
777                } else {
778                    // One phase for now
779                    let after_challenge_values = builder.get(&opening.values.after_challenge, 0);
780                    let after_challenge_values =
781                        builder.get(&after_challenge_values, after_challenge_idx.clone());
782                    builder.inc(&after_challenge_idx);
783                    after_challenge_values
784                };
785                let trace_domain = builder.get(&domains, air_idx.clone());
786                let quotient_domain: TwoAdicMultiplicativeCosetVariable<_> =
787                    builder.get(&quotient_domains, air_idx.clone());
788                // Check that the quotient data matches the chip's data.
789                let log_quotient_degree = air_const.log_quotient_degree();
790                let quotient_chunks = builder.get(&opening.values.quotient, air_idx.clone());
791
792                // Get the domains from the chip itself.
793                let qc_domains = quotient_domain.split_domains_const(builder, log_quotient_degree);
794                let air_proof = builder.get(air_proofs, air_idx.clone());
795                let pvs = (0..air_const.num_public_values)
796                    .map(|x| builder.get(&air_proof.public_values, x))
797                    .collect_vec();
798
799                let exposed_values = air_const
800                    .num_exposed_values_after_challenge
801                    .iter()
802                    .enumerate()
803                    .map(|(phase, &num_exposed)| {
804                        let exposed_values =
805                            builder.get(&air_proof.exposed_values_after_challenge, phase);
806                        (0..num_exposed)
807                            .map(|j| builder.get(&exposed_values, j))
808                            .collect_vec()
809                    })
810                    .collect_vec();
811
812                Self::verify_single_rap_constraints(
813                    builder,
814                    air_const,
815                    preprocessed_values,
816                    &partitioned_main_values,
817                    quotient_chunks,
818                    &pvs,
819                    trace_domain,
820                    qc_domains,
821                    zeta,
822                    alpha,
823                    after_challenge_values,
824                    &challenges,
825                    &exposed_values,
826                );
827
828                builder.inc(&air_idx);
829            });
830        }
831        // Assert that all provided AIRs were verified.
832        builder.assert_usize_eq(air_idx, air_ids.len());
833
834        builder.cycle_tracker_end("stage-e-verify-constraints");
835    }
836
837    /// For constraint `i` in `trace_height_constraints` with coefficients a_i1, ..., a_ik and
838    /// threshold b_i, checks that
839    ///    a_i1 * x_1 + ... + a_ik * x_k < b_i,
840    /// where `x_i` is the height of the trace given in `air_proofs[j]` with `j` such that
841    /// `air_proofs[j].air_id = i`.
842    ///
843    /// The caller must ensure the following is met:
844    /// * `constraint_system.constraints.len() == num_airs`
845    /// * `constraint_system.constraints[air_proofs[i].air_id]` is valid for all `i`
846    /// * For all `i`, `air_proofs[i].log_degree < MAX_TWO_ADICITY`
847    fn check_trace_height_constraints(
848        builder: &mut Builder<C>,
849        constraint_system: &TraceHeightConstraintSystem<C>,
850        air_proofs: &Array<C, AirProofDataVariable<C>>,
851    ) {
852        // We compute and store `a_i1 * x_1 + ... + a_ik * x_k` in `values[i]`.
853        let values: Vec<Var<C::N>> = (0..constraint_system.height_constraints.len())
854            .map(|_| builder.eval(C::N::ZERO))
855            .collect();
856
857        let assert_less_than = |builder: &mut Builder<C>, a, b| {
858            if builder.flags.static_only {
859                builder.push(DslIr::CircuitLessThan(a, b));
860            } else {
861                builder.assert_less_than_slow_bit_decomp(a, b);
862            }
863        };
864
865        // Will index by log_air_height. Max value is assumed to be MAX_TWO_ADICITY - 1 because
866        // `log_blowup >= 1`.
867        let pows_of_two: Array<C, Var<C::N>> = builder.array(MAX_TWO_ADICITY);
868        let cur: Var<C::N> = builder.constant(C::N::ONE);
869        iter_zip!(builder, pows_of_two).for_each(|ptr_vec, builder| {
870            builder.iter_ptr_set(&pows_of_two, ptr_vec[0], cur);
871            builder.assign(&cur, cur + cur);
872        });
873
874        iter_zip!(builder, air_proofs).for_each(|ptr_vec, builder| {
875            let air_proof_data = builder.iter_ptr_get(air_proofs, ptr_vec[0]);
876
877            let height = builder.get(&pows_of_two, air_proof_data.log_degree);
878            let height_max = builder.get(
879                &constraint_system.height_maxes,
880                air_proof_data.air_id.clone(),
881            );
882            builder
883                .if_eq(height_max.is_some, C::N::ONE)
884                .then(|builder| {
885                    assert_less_than(builder, height, height_max.value);
886                });
887
888            for (i, constraint) in constraint_system.height_constraints.iter().enumerate() {
889                let coeff = builder.get(&constraint.coefficients, air_proof_data.air_id.clone());
890
891                let new_value: Var<C::N> = builder.eval(values[i] + coeff * height);
892                let new_value_plus_one: Var<C::N> = builder.eval(new_value + C::N::ONE);
893                assert_less_than(builder, values[i], new_value_plus_one);
894                builder.assign(&values[i], new_value);
895            }
896        });
897        for (value, constraint) in zip(values, &constraint_system.height_constraints) {
898            if builder.flags.static_only || !constraint.is_threshold_at_p {
899                assert_less_than(builder, value, constraint.threshold);
900            }
901        }
902    }
903
904    /// Reference: [openvm_stark_backend::verifier::constraints::verify_single_rap_constraints]
905    #[allow(clippy::too_many_arguments)]
906    #[allow(clippy::type_complexity)]
907    pub fn verify_single_rap_constraints(
908        builder: &mut Builder<C>,
909        constants: &StarkVerificationAdvice<C>,
910        preprocessed_values: Option<AdjacentOpenedValuesVariable<C>>,
911        partitioned_main_values: &[AdjacentOpenedValuesVariable<C>],
912        quotient_chunks: Array<C, Array<C, Ext<C::F, C::EF>>>,
913        public_values: &[Felt<C::F>],
914        trace_domain: TwoAdicMultiplicativeCosetVariable<C>,
915        qc_domains: Vec<TwoAdicMultiplicativeCosetVariable<C>>,
916        zeta: Ext<C::F, C::EF>,
917        alpha: Ext<C::F, C::EF>,
918        after_challenge_values: AdjacentOpenedValuesVariable<C>,
919        challenges: &[Vec<Ext<C::F, C::EF>>],
920        exposed_values_after_challenge: &[Vec<Ext<C::F, C::EF>>],
921    ) {
922        let sels = trace_domain.selectors_at_point(builder, zeta);
923
924        let mut preprocessed = AdjacentOpenedValues {
925            local: vec![],
926            next: vec![],
927        };
928        if let Some(preprocessed_values) = preprocessed_values {
929            for i in 0..constants.width.preprocessed.unwrap() {
930                preprocessed
931                    .local
932                    .push(builder.get(&preprocessed_values.local, i));
933                preprocessed
934                    .next
935                    .push(builder.get(&preprocessed_values.next, i));
936            }
937        }
938
939        let main_widths = constants.width.main_widths();
940        assert_eq!(partitioned_main_values.len(), main_widths.len());
941        let partitioned_main_values = partitioned_main_values
942            .iter()
943            .zip_eq(main_widths.iter())
944            .map(|(main_values, &width)| {
945                builder.assert_usize_eq(main_values.local.len(), RVar::from(width));
946                builder.assert_usize_eq(main_values.next.len(), RVar::from(width));
947                let mut main = AdjacentOpenedValues {
948                    local: vec![],
949                    next: vec![],
950                };
951                for i in 0..width {
952                    main.local.push(builder.get(&main_values.local, i));
953                    main.next.push(builder.get(&main_values.next, i));
954                }
955                main
956            })
957            .collect_vec();
958
959        let mut after_challenge = AdjacentOpenedValues {
960            local: vec![],
961            next: vec![],
962        };
963
964        let after_challenge_width = if constants.width.after_challenge.is_empty() {
965            0
966        } else {
967            C::EF::DIMENSION * constants.width.after_challenge[0]
968        };
969        builder.assert_usize_eq(
970            after_challenge_values.local.len(),
971            RVar::from(after_challenge_width),
972        );
973        builder.assert_usize_eq(
974            after_challenge_values.next.len(),
975            RVar::from(after_challenge_width),
976        );
977        for i in 0..after_challenge_width {
978            after_challenge
979                .local
980                .push(builder.get(&after_challenge_values.local, i));
981            after_challenge
982                .next
983                .push(builder.get(&after_challenge_values.next, i));
984        }
985
986        let folded_constraints = Self::eval_constraints(
987            builder,
988            &constants.symbolic_constraints,
989            preprocessed,
990            &partitioned_main_values,
991            public_values,
992            &sels,
993            alpha,
994            after_challenge,
995            challenges,
996            exposed_values_after_challenge,
997        );
998
999        let num_quotient_chunks = 1 << constants.log_quotient_degree();
1000        let mut quotient = vec![];
1001        // Assert that the length of the quotient chunk arrays match the expected length.
1002        builder.assert_usize_eq(quotient_chunks.len(), RVar::from(num_quotient_chunks));
1003        // Collect the quotient values into vectors.
1004        for i in 0..num_quotient_chunks {
1005            let chunk = builder.get(&quotient_chunks, i);
1006            // Assert that the chunk length matches the expected length.
1007            builder.assert_usize_eq(RVar::from(C::EF::DIMENSION), RVar::from(chunk.len()));
1008            // Collect the quotient values into vectors.
1009            let mut quotient_vals = vec![];
1010            for j in 0..C::EF::DIMENSION {
1011                let value = builder.get(&chunk, j);
1012                quotient_vals.push(value);
1013            }
1014            quotient.push(quotient_vals);
1015        }
1016
1017        let quotient: Ext<_, _> = Self::recompute_quotient(builder, &quotient, qc_domains, zeta);
1018
1019        // Assert that the quotient times the zerofier is equal to the folded constraints.
1020        builder.assert_ext_eq(folded_constraints * sels.inv_vanishing, quotient);
1021    }
1022
1023    #[allow(clippy::too_many_arguments)]
1024    fn eval_constraints(
1025        builder: &mut Builder<C>,
1026        constraints: &SymbolicExpressionDag<C::F>,
1027        preprocessed_values: AdjacentOpenedValues<Ext<C::F, C::EF>>,
1028        partitioned_main_values: &[AdjacentOpenedValues<Ext<C::F, C::EF>>],
1029        public_values: &[Felt<C::F>],
1030        selectors: &LagrangeSelectors<Ext<C::F, C::EF>>,
1031        alpha: Ext<C::F, C::EF>,
1032        after_challenge: AdjacentOpenedValues<Ext<C::F, C::EF>>,
1033        challenges: &[Vec<Ext<C::F, C::EF>>],
1034        exposed_values_after_challenge: &[Vec<Ext<C::F, C::EF>>],
1035    ) -> Ext<C::F, C::EF> {
1036        let mut unflatten = |v: &[Ext<C::F, C::EF>]| {
1037            v.chunks_exact(C::EF::DIMENSION)
1038                .map(|chunk| {
1039                    builder.eval(
1040                        chunk
1041                            .iter()
1042                            .enumerate()
1043                            .map(|(e_i, &x)| {
1044                                x * C::EF::ith_basis_element(e_i)
1045                                    .expect("basis element index out of bounds")
1046                                    .cons()
1047                            })
1048                            .sum::<SymbolicExt<_, _>>(),
1049                    )
1050                })
1051                .collect::<Vec<Ext<_, _>>>()
1052        };
1053
1054        let after_challenge_values = AdjacentOpenedValues {
1055            local: unflatten(&after_challenge.local),
1056            next: unflatten(&after_challenge.next),
1057        };
1058
1059        let mut folder: RecursiveVerifierConstraintFolder<C> = GenericVerifierConstraintFolder {
1060            preprocessed: VerticalPair::new(
1061                RowMajorMatrixView::new_row(&preprocessed_values.local),
1062                RowMajorMatrixView::new_row(&preprocessed_values.next),
1063            ),
1064            partitioned_main: partitioned_main_values
1065                .iter()
1066                .map(|main_values| {
1067                    VerticalPair::new(
1068                        RowMajorMatrixView::new_row(&main_values.local),
1069                        RowMajorMatrixView::new_row(&main_values.next),
1070                    )
1071                })
1072                .collect(),
1073            after_challenge: vec![VerticalPair::new(
1074                RowMajorMatrixView::new_row(&after_challenge_values.local),
1075                RowMajorMatrixView::new_row(&after_challenge_values.next),
1076            )],
1077            challenges,
1078            is_first_row: selectors.is_first_row,
1079            is_last_row: selectors.is_last_row,
1080            is_transition: selectors.is_transition,
1081            alpha,
1082            accumulator: SymbolicExt::ZERO,
1083            public_values,
1084            exposed_values_after_challenge,
1085            _marker: PhantomData,
1086        };
1087        folder.eval_constraints(constraints);
1088
1089        builder.eval(folder.accumulator)
1090    }
1091
1092    fn recompute_quotient(
1093        builder: &mut Builder<C>,
1094        quotient_chunks: &[Vec<Ext<C::F, C::EF>>],
1095        qc_domains: Vec<TwoAdicMultiplicativeCosetVariable<C>>,
1096        zeta: Ext<C::F, C::EF>,
1097    ) -> Ext<C::F, C::EF> {
1098        let zps = qc_domains
1099            .iter()
1100            .enumerate()
1101            .map(|(i, domain)| {
1102                qc_domains
1103                    .iter()
1104                    .enumerate()
1105                    .filter(|(j, _)| *j != i)
1106                    .map(|(_, other_domain)| {
1107                        let first_point: Ext<_, _> = builder.eval(domain.first_point());
1108                        other_domain.vanishing_poly_at_point(builder, zeta)
1109                            * other_domain
1110                                .vanishing_poly_at_point(builder, first_point)
1111                                .inverse()
1112                    })
1113                    .product::<SymbolicExt<_, _>>()
1114            })
1115            .collect::<Vec<SymbolicExt<_, _>>>()
1116            .into_iter()
1117            .map(|x| builder.eval(x))
1118            .collect::<Vec<Ext<_, _>>>();
1119
1120        builder.eval(
1121            quotient_chunks
1122                .iter()
1123                .enumerate()
1124                .map(|(ch_i, ch)| {
1125                    assert_eq!(ch.len(), C::EF::DIMENSION);
1126                    ch.iter()
1127                        .enumerate()
1128                        .map(|(e_i, &c)| {
1129                            zps[ch_i]
1130                                * C::EF::ith_basis_element(e_i)
1131                                    .expect("basis element index out of bounds")
1132                                * c
1133                        })
1134                        .sum::<SymbolicExt<_, _>>()
1135                })
1136                .sum::<SymbolicExt<_, _>>(),
1137        )
1138    }
1139}
1140
1141fn assert_cumulative_sums<C: Config>(
1142    builder: &mut Builder<C>,
1143    air_proofs: &Array<C, AirProofDataVariable<C>>,
1144    num_challenges_to_sample: &Array<C, Usize<C::N>>,
1145) {
1146    let num_phase = num_challenges_to_sample.len();
1147    // Currently only support 0 or 1 phase is supported.
1148    builder.if_eq(num_phase, RVar::one()).then(|builder| {
1149        let cumulative_sum: Ext<C::F, C::EF> = builder.eval(C::F::ZERO);
1150        builder
1151            .range(0, air_proofs.len())
1152            .for_each(|i_vec, builder| {
1153                let i = i_vec[0];
1154                let air_proof_input = builder.get(air_proofs, i);
1155                let exposed_values = air_proof_input.exposed_values_after_challenge;
1156
1157                builder
1158                    .if_ne(exposed_values.len(), RVar::zero())
1159                    .then(|builder| {
1160                        // Verifier does not support more than 1 challenge phase
1161                        builder.assert_usize_eq(exposed_values.len(), RVar::one());
1162                        let values = builder.get(&exposed_values, RVar::zero());
1163                        // Only exposed value should be cumulative sum
1164                        builder.assert_usize_eq(values.len(), RVar::one());
1165
1166                        let summand = builder.get(&values, RVar::zero());
1167                        builder.assign(&cumulative_sum, cumulative_sum + summand);
1168                    });
1169            });
1170        builder.assert_ext_eq(cumulative_sum, C::EF::ZERO.cons());
1171    });
1172}
1173
1174/// Conversion used for challenger to observe usize. The `val` must come
1175/// from compile time constant in static mode.
1176fn cast_usize_to_felt<C: Config>(builder: &mut Builder<C>, val: Usize<C::N>) -> Felt<C::F>
1177where
1178    C::F: TwoAdicField,
1179{
1180    if builder.flags.static_only {
1181        builder.eval(C::F::from_usize(val.value()))
1182    } else {
1183        builder.unsafe_cast_var_to_felt(val.get_var())
1184    }
1185}