triton_vm/
stark.rs

1use std::ops::Mul;
2
3use arbitrary::Arbitrary;
4use arbitrary::Unstructured;
5use itertools::izip;
6use itertools::Itertools;
7use ndarray::prelude::*;
8use ndarray::Zip;
9use num_traits::ConstOne;
10use num_traits::ConstZero;
11use num_traits::Zero;
12use rand::prelude::*;
13use rand::random;
14use rayon::prelude::*;
15use serde::Deserialize;
16use serde::Serialize;
17use twenty_first::math::ntt::intt;
18use twenty_first::math::ntt::ntt;
19use twenty_first::math::traits::FiniteField;
20use twenty_first::math::traits::PrimitiveRootOfUnity;
21use twenty_first::prelude::*;
22
23use crate::aet::AlgebraicExecutionTrace;
24use crate::arithmetic_domain::ArithmeticDomain;
25use crate::challenges::Challenges;
26use crate::error::ProvingError;
27use crate::error::VerificationError;
28use crate::fri;
29use crate::fri::Fri;
30use crate::ndarray_helper;
31use crate::profiler::profiler;
32use crate::proof::Claim;
33use crate::proof::Proof;
34use crate::proof_item::ProofItem;
35use crate::proof_stream::ProofStream;
36use crate::table::auxiliary_table::Evaluable;
37use crate::table::master_table::all_quotients_combined;
38use crate::table::master_table::interpolant_degree;
39use crate::table::master_table::max_degree_with_origin;
40use crate::table::master_table::randomized_trace_len;
41use crate::table::master_table::MasterAuxTable;
42use crate::table::master_table::MasterMainTable;
43use crate::table::master_table::MasterTable;
44use crate::table::QuotientSegments;
45
46/// The number of segments the quotient polynomial is split into.
47/// Helps keeping the FRI domain small.
48pub const NUM_QUOTIENT_SEGMENTS: usize = air::TARGET_DEGREE as usize;
49
50/// The number of randomizer polynomials over the [extension field](XFieldElement) used in the
51/// [`STARK`](Stark). Integral for achieving zero-knowledge in [FRI](Fri).
52pub const NUM_RANDOMIZER_POLYNOMIALS: usize = 1;
53
54const NUM_DEEP_CODEWORD_COMPONENTS: usize = 3;
55
56/// Parameters for the Zero-Knowledge
57/// [Scalable Transparent ARgument of Knowledge (STARK)][stark] for Triton VM.
58///
59/// The two core methods are [`Stark::prove`] and [`Stark::verify`].
60///
61/// [stark]: https://www.iacr.org/archive/crypto2019/116940201/116940201.pdf
62#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
63pub struct Stark {
64    /// The conjectured security level in bits. Concretely, the system
65    /// - is perfectly complete, and
66    /// - has soundness error 2^(-security_level).
67    pub security_level: usize,
68
69    /// The ratio between the lengths of the randomized trace domain and the
70    /// [FRI domain](Stark::fri). Must be a power of 2 for efficiency reasons.
71    pub fri_expansion_factor: usize,
72
73    /// The number of randomizers for the execution trace. The trace randomizers are
74    /// integral for achieving zero-knowledge. In particular, they achieve ZK for
75    /// the (DEEP) ALI part of the zk-STARK.
76    ///
77    /// See also [`MasterTable::trace_randomizer_for_column`].
78    pub num_trace_randomizers: usize,
79
80    /// The number of collinearity checks to perform in [FRI](Fri).
81    pub num_collinearity_checks: usize,
82}
83
84/// The prover for Triton VM's [zk-STARK](Stark). The core method is
85/// [`prove`](Prover::prove). It is probably more convenient to call
86/// [`Stark::prove`] directly.
87///
88/// It is possible to [set the randomness seed][seed] used for deriving all
89/// prover randomness. To prevent accidental randomness re-use, the [`Prover`]
90/// does not implement [`Clone`].
91///
92/// [seed]: Prover::set_randomness_seed_which_may_break_zero_knowledge
93#[expect(missing_copy_implementations)]
94#[derive(Debug, Eq, PartialEq, Arbitrary)]
95pub struct Prover {
96    parameters: Stark,
97
98    /// The seed for all randomness used while [proving][Stark::prove].
99    ///
100    /// For Triton VM's proofs to be zero knowledge, this seed must be sampled
101    /// uniformly at random, and independently of all other input. No information
102    /// about it must reach the verifier.
103    randomness_seed: <StdRng as SeedableRng>::Seed,
104}
105
106/// The verifier for Triton VM's [zs-STARK](Stark). The core method is
107/// [`verify`](Verifier::verify). It is probably more convenient to call
108/// [`Stark::verify`] directly.
109#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize, Arbitrary)]
110pub struct Verifier {
111    parameters: Stark,
112}
113
114impl Prover {
115    /// A [`Prover`] with a sane [randomness seed][seed].
116    ///
117    /// [seed]: Prover::set_randomness_seed_which_may_break_zero_knowledge
118    pub fn new(parameters: Stark) -> Self {
119        Self {
120            parameters,
121            randomness_seed: random(),
122        }
123    }
124
125    /// Manually set the seed for the randomness used during [proving](Self::prove).
126    /// This makes the generated [proof](Proof) deterministic.
127    ///
128    /// # WARNING!
129    ///
130    /// Careless use of this method can break Zero-Knowledge of Triton VM. In
131    /// particular, the [verifier](Stark::verify) must learn nothing about the
132    /// supplied seed, must be unable to influence the supplied seed, and must be
133    /// unable to guess anything about the supplied seed. The latter implies that
134    /// whatever source of randomness is chosen must have sufficient entropy.
135    ///
136    /// ### If in doubt, don't use this method.
137    // Even though this method can be used to disable or cripple one of the
138    // core promises of Triton VM, it is not marked `unsafe` because it is always
139    // _memory_ safe. See also: https://doc.rust-lang.org/std/keyword.unsafe.html
140    //
141    // The length of the name is intended to discourage use.
142    #[must_use]
143    pub fn set_randomness_seed_which_may_break_zero_knowledge(
144        mut self,
145        seed: <StdRng as SeedableRng>::Seed,
146    ) -> Self {
147        self.randomness_seed = seed;
148        self
149    }
150
151    /// See also [`Stark::prove`].
152    pub fn prove(
153        self,
154        claim: &Claim,
155        aet: &AlgebraicExecutionTrace,
156    ) -> Result<Proof, ProvingError> {
157        profiler!(start "Fiat-Shamir: claim" ("hash"));
158        let mut proof_stream = ProofStream::new();
159        proof_stream.alter_fiat_shamir_state_with(claim);
160        profiler!(stop "Fiat-Shamir: claim");
161
162        profiler!(start "derive additional parameters");
163        let padded_height = aet.padded_height();
164        let max_degree = self.parameters.max_degree(padded_height);
165        let fri = self.parameters.fri(padded_height)?;
166        let quotient_domain = Self::quotient_domain(fri.domain, max_degree)?;
167        proof_stream.enqueue(ProofItem::Log2PaddedHeight(padded_height.ilog2()));
168        profiler!(stop "derive additional parameters");
169
170        profiler!(start "main tables");
171        profiler!(start "create" ("gen"));
172        let mut master_main_table = MasterMainTable::new(
173            aet,
174            quotient_domain,
175            fri.domain,
176            self.parameters.num_trace_randomizers,
177            self.randomness_seed,
178        );
179        profiler!(stop "create");
180
181        profiler!(start "pad" ("gen"));
182        master_main_table.pad();
183        profiler!(stop "pad");
184
185        master_main_table.maybe_low_degree_extend_all_columns();
186
187        profiler!(start "Merkle tree");
188        let main_merkle_tree = master_main_table.merkle_tree();
189        profiler!(stop "Merkle tree");
190
191        profiler!(start "Fiat-Shamir" ("hash"));
192        proof_stream.enqueue(ProofItem::MerkleRoot(main_merkle_tree.root()));
193        let challenges = proof_stream.sample_scalars(Challenges::SAMPLE_COUNT);
194        let challenges = Challenges::new(challenges, claim);
195        profiler!(stop "Fiat-Shamir");
196
197        profiler!(start "extend" ("gen"));
198        let mut master_aux_table = master_main_table.extend(&challenges);
199        profiler!(stop "extend");
200        profiler!(stop "main tables");
201
202        profiler!(start "aux tables");
203        master_aux_table.maybe_low_degree_extend_all_columns();
204
205        profiler!(start "Merkle tree");
206        let aux_merkle_tree = master_aux_table.merkle_tree();
207        profiler!(stop "Merkle tree");
208
209        profiler!(start "Fiat-Shamir" ("hash"));
210        proof_stream.enqueue(ProofItem::MerkleRoot(aux_merkle_tree.root()));
211
212        // Get the weights with which to compress the many quotients into one.
213        let quotient_combination_weights =
214            proof_stream.sample_scalars(MasterAuxTable::NUM_CONSTRAINTS);
215        profiler!(stop "Fiat-Shamir");
216        profiler!(stop "aux tables");
217
218        let (fri_domain_quotient_segment_codewords, quotient_segment_polynomials) =
219            Self::compute_quotient_segments(
220                &mut master_main_table,
221                &mut master_aux_table,
222                quotient_domain,
223                &challenges,
224                &quotient_combination_weights,
225            );
226
227        profiler!(start "hash rows of quotient segments" ("hash"));
228        let interpret_xfe_as_bfes = |xfe: &XFieldElement| xfe.coefficients.to_vec();
229        let hash_row = |row: ArrayView1<_>| {
230            let row_as_bfes = row.iter().map(interpret_xfe_as_bfes).concat();
231            Tip5::hash_varlen(&row_as_bfes)
232        };
233        let quotient_segments_rows = fri_domain_quotient_segment_codewords
234            .axis_iter(Axis(0))
235            .into_par_iter();
236        let fri_domain_quotient_segment_codewords_digests =
237            quotient_segments_rows.map(hash_row).collect::<Vec<_>>();
238        profiler!(stop "hash rows of quotient segments");
239        profiler!(start "Merkle tree" ("hash"));
240        let quot_merkle_tree = MerkleTree::par_new(&fri_domain_quotient_segment_codewords_digests)?;
241        let quot_merkle_tree_root = quot_merkle_tree.root();
242        proof_stream.enqueue(ProofItem::MerkleRoot(quot_merkle_tree_root));
243        profiler!(stop "Merkle tree");
244
245        debug_assert_eq!(fri.domain.length, quot_merkle_tree.num_leafs());
246
247        profiler!(start "out-of-domain rows");
248        let trace_domain_generator = master_main_table.trace_domain().generator;
249        let out_of_domain_point_curr_row = proof_stream.sample_scalars(1)[0];
250        let out_of_domain_point_next_row = trace_domain_generator * out_of_domain_point_curr_row;
251
252        let ood_main_row = master_main_table.out_of_domain_row(out_of_domain_point_curr_row);
253        let ood_main_row = MasterMainTable::try_to_main_row(ood_main_row)?;
254        proof_stream.enqueue(ProofItem::OutOfDomainMainRow(Box::new(ood_main_row)));
255
256        let ood_aux_row = master_aux_table.out_of_domain_row(out_of_domain_point_curr_row);
257        let ood_aux_row = MasterAuxTable::try_to_aux_row(ood_aux_row)?;
258        proof_stream.enqueue(ProofItem::OutOfDomainAuxRow(Box::new(ood_aux_row)));
259
260        let ood_next_main_row = master_main_table.out_of_domain_row(out_of_domain_point_next_row);
261        let ood_next_main_row = MasterMainTable::try_to_main_row(ood_next_main_row)?;
262        proof_stream.enqueue(ProofItem::OutOfDomainMainRow(Box::new(ood_next_main_row)));
263
264        let ood_next_aux_row = master_aux_table.out_of_domain_row(out_of_domain_point_next_row);
265        let ood_next_aux_row = MasterAuxTable::try_to_aux_row(ood_next_aux_row)?;
266        proof_stream.enqueue(ProofItem::OutOfDomainAuxRow(Box::new(ood_next_aux_row)));
267
268        let out_of_domain_point_curr_row_pow_num_segments =
269            out_of_domain_point_curr_row.mod_pow_u32(NUM_QUOTIENT_SEGMENTS as u32);
270        let out_of_domain_curr_row_quot_segments = quotient_segment_polynomials
271            .map(|poly| poly.evaluate(out_of_domain_point_curr_row_pow_num_segments))
272            .to_vec()
273            .try_into()
274            .unwrap();
275        proof_stream.enqueue(ProofItem::OutOfDomainQuotientSegments(
276            out_of_domain_curr_row_quot_segments,
277        ));
278        profiler!(stop "out-of-domain rows");
279
280        profiler!(start "Fiat-Shamir" ("hash"));
281        let weights = LinearCombinationWeights::sample(&mut proof_stream);
282        profiler!(stop "Fiat-Shamir");
283
284        let fri_domain_is_short_domain = fri.domain.length <= quotient_domain.length;
285        let short_domain = if fri_domain_is_short_domain {
286            fri.domain
287        } else {
288            quotient_domain
289        };
290
291        profiler!(start "linear combination");
292        profiler!(start "main" ("CC"));
293        let main_combination_poly = master_main_table.weighted_sum_of_columns(weights.main);
294        profiler!(stop "main");
295
296        profiler!(start "aux" ("CC"));
297        let aux_combination_poly = master_aux_table.weighted_sum_of_columns(weights.aux);
298        profiler!(stop "aux");
299        let main_and_aux_combination_polynomial = main_combination_poly + aux_combination_poly;
300        let main_and_aux_codeword = short_domain.evaluate(&main_and_aux_combination_polynomial);
301
302        profiler!(start "quotient" ("CC"));
303        let quotient_segments_combination_polynomial = quotient_segment_polynomials
304            .into_iter()
305            .zip_eq(weights.quot_segments)
306            .fold(Polynomial::zero(), |acc, (poly, w)| acc + poly * w);
307        let quotient_segments_combination_codeword =
308            short_domain.evaluate(&quotient_segments_combination_polynomial);
309        profiler!(stop "quotient");
310
311        profiler!(stop "linear combination");
312
313        profiler!(start "DEEP");
314        // There are (at least) two possible ways to perform the DEEP update.
315        // 1. The one used here, where main & aux codewords are DEEP'd twice: once with the out-of-
316        //    domain point for the current row (i.e., α) and once using the out-of-domain point for
317        //    the next row (i.e., ω·α). The DEEP update's denominator is a degree-1 polynomial in
318        //    both cases, namely (ω^i - α) and (ω^i - ω·α) respectively.
319        // 2. One where the main & aux codewords are DEEP'd only once, using the degree-2 polynomial
320        //    (ω^i - α)·(ω^i - ω·α) as the denominator. This requires a linear interpolation in the
321        //    numerator: b(ω^i) - i((b(α), α) + (b(ω·α), ω·α))(w^i).
322        //
323        // In either case, the DEEP'd quotient polynomial is an additional summand for the
324        // combination codeword: (q(ω^i) - q(α)) / (ω^i - α).
325        // All (three or two) summands are weighted and summed to form the combination codeword.
326        // The weights are sampled through the Fiat-Shamir heuristic.
327        //
328        // Both approaches are sound. The first approach is more efficient, as it requires fewer
329        // operations.
330        profiler!(start "main&aux curr row");
331        let out_of_domain_curr_row_main_and_aux_value =
332            main_and_aux_combination_polynomial.evaluate(out_of_domain_point_curr_row);
333        let main_and_aux_curr_row_deep_codeword = Self::deep_codeword(
334            &main_and_aux_codeword,
335            short_domain,
336            out_of_domain_point_curr_row,
337            out_of_domain_curr_row_main_and_aux_value,
338        );
339        profiler!(stop "main&aux curr row");
340
341        profiler!(start "main&aux next row");
342        let out_of_domain_next_row_main_and_aux_value =
343            main_and_aux_combination_polynomial.evaluate(out_of_domain_point_next_row);
344        let main_and_aux_next_row_deep_codeword = Self::deep_codeword(
345            &main_and_aux_codeword,
346            short_domain,
347            out_of_domain_point_next_row,
348            out_of_domain_next_row_main_and_aux_value,
349        );
350        profiler!(stop "main&aux next row");
351
352        profiler!(start "segmented quotient");
353        let out_of_domain_curr_row_quot_segments_value = quotient_segments_combination_polynomial
354            .evaluate(out_of_domain_point_curr_row_pow_num_segments);
355        let quotient_segments_curr_row_deep_codeword = Self::deep_codeword(
356            &quotient_segments_combination_codeword,
357            short_domain,
358            out_of_domain_point_curr_row_pow_num_segments,
359            out_of_domain_curr_row_quot_segments_value,
360        );
361        profiler!(stop "segmented quotient");
362        profiler!(stop "DEEP");
363
364        profiler!(start "combined DEEP polynomial");
365        profiler!(start "sum" ("CC"));
366        let deep_codeword = [
367            main_and_aux_curr_row_deep_codeword,
368            main_and_aux_next_row_deep_codeword,
369            quotient_segments_curr_row_deep_codeword,
370        ]
371        .into_par_iter()
372        .zip_eq(weights.deep.as_slice().unwrap())
373        .map(|(codeword, &weight)| codeword.into_par_iter().map(|c| c * weight).collect())
374        .reduce(
375            || vec![XFieldElement::ZERO; short_domain.length],
376            |left, right| left.into_iter().zip(right).map(|(l, r)| l + r).collect(),
377        );
378
379        profiler!(stop "sum");
380        let fri_combination_codeword = if fri_domain_is_short_domain {
381            deep_codeword
382        } else {
383            profiler!(start "LDE" ("LDE"));
384            let deep_codeword = quotient_domain.low_degree_extension(&deep_codeword, fri.domain);
385            profiler!(stop "LDE");
386            deep_codeword
387        };
388        assert_eq!(fri.domain.length, fri_combination_codeword.len());
389        profiler!(stop "combined DEEP polynomial");
390
391        profiler!(start "FRI");
392        let revealed_current_row_indices =
393            fri.prove(&fri_combination_codeword, &mut proof_stream)?;
394        assert_eq!(
395            self.parameters.num_collinearity_checks,
396            revealed_current_row_indices.len()
397        );
398        profiler!(stop "FRI");
399
400        profiler!(start "open trace leafs");
401        // Open leafs of zipped codewords at indicated positions
402        let main_row_err = |row: Vec<_>| ProvingError::TableRowConversionError {
403            expected_len: MasterMainTable::NUM_COLUMNS,
404            actual_len: row.len(),
405        };
406        let revealed_main_elems = master_main_table
407            .reveal_rows(&revealed_current_row_indices)
408            .into_iter()
409            .map(|row| row.try_into().map_err(main_row_err))
410            .try_collect()?;
411        let base_authentication_structure =
412            main_merkle_tree.authentication_structure(&revealed_current_row_indices)?;
413        proof_stream.enqueue(ProofItem::MasterMainTableRows(revealed_main_elems));
414        proof_stream.enqueue(ProofItem::AuthenticationStructure(
415            base_authentication_structure,
416        ));
417
418        let aux_row_err = |row: Vec<_>| ProvingError::TableRowConversionError {
419            expected_len: MasterAuxTable::NUM_COLUMNS,
420            actual_len: row.len(),
421        };
422        let revealed_aux_elems = master_aux_table
423            .reveal_rows(&revealed_current_row_indices)
424            .into_iter()
425            .map(|row| row.try_into().map_err(aux_row_err))
426            .try_collect()?;
427        let aux_authentication_structure =
428            aux_merkle_tree.authentication_structure(&revealed_current_row_indices)?;
429        proof_stream.enqueue(ProofItem::MasterAuxTableRows(revealed_aux_elems));
430        proof_stream.enqueue(ProofItem::AuthenticationStructure(
431            aux_authentication_structure,
432        ));
433
434        // Open quotient & combination codewords at the same positions as main & aux codewords.
435        let into_fixed_width_row =
436            |row: ArrayView1<_>| -> QuotientSegments { row.to_vec().try_into().unwrap() };
437        let revealed_quotient_segments_rows = revealed_current_row_indices
438            .iter()
439            .map(|&i| fri_domain_quotient_segment_codewords.row(i))
440            .map(into_fixed_width_row)
441            .collect_vec();
442        let revealed_quotient_authentication_structure =
443            quot_merkle_tree.authentication_structure(&revealed_current_row_indices)?;
444        proof_stream.enqueue(ProofItem::QuotientSegmentsElements(
445            revealed_quotient_segments_rows,
446        ));
447        proof_stream.enqueue(ProofItem::AuthenticationStructure(
448            revealed_quotient_authentication_structure,
449        ));
450        profiler!(stop "open trace leafs");
451
452        Ok(proof_stream.into())
453    }
454
455    /// An [`ArithmeticDomain`] _just_ large enough to perform all the necessary
456    /// computations on polynomials. Concretely, the maximal degree of a polynomial
457    /// over the quotient domain is at most only slightly larger than the maximal
458    /// degree allowed in the STARK proof, and could be equal. This makes
459    /// computation for the prover much faster.
460    pub(crate) fn quotient_domain(
461        fri_domain: ArithmeticDomain,
462        max_degree: isize,
463    ) -> Result<ArithmeticDomain, ProvingError> {
464        let max_degree = usize::try_from(max_degree).expect("AIR should constrain the VM");
465        let domain_length = max_degree.next_power_of_two();
466        Ok(ArithmeticDomain::of_length(domain_length)?.with_offset(fri_domain.offset))
467    }
468
469    fn compute_quotient_segments(
470        main_table: &mut MasterMainTable,
471        aux_table: &mut MasterAuxTable,
472        quotient_domain: ArithmeticDomain,
473        challenges: &Challenges,
474        quotient_combination_weights: &[XFieldElement],
475    ) -> (
476        Array2<XFieldElement>,
477        Array1<Polynomial<'static, XFieldElement>>,
478    ) {
479        let (Some(main_quotient_domain_codewords), Some(aux_quotient_domain_codewords)) = (
480            main_table.quotient_domain_table(),
481            aux_table.quotient_domain_table(),
482        ) else {
483            // The decision to cache the quotient domain main table can be independent of
484            // the decision to cache the quotient domain auxiliary table. Additionally,
485            // available memory is affected by other running programs. Together, this may
486            // result in one table being cached while the other is not. To compute the
487            // quotients, either both or neither are needed.[^1] For peak memory
488            // consumption, it is beneficial to clear any unused cache.
489            //
490            // Discarding the cache incurs a performance penalty later, when revealing the
491            // rows indicated by FRI. This is an acceptable tradeoff:
492            // Executing this very code path means that in the current environment, peak
493            // memory usage is a concern. Running out of memory results in abnormal
494            // termination of the prover. Slower execution is an acceptable price for normal
495            // termination.
496            //
497            // [^1]: Code using exactly one cache _could_ exist, but oh! the engineering.
498            main_table.clear_cache();
499            aux_table.clear_cache();
500
501            profiler!(start "quotient calculation (just-in-time)");
502            let (fri_domain_quotient_segment_codewords, quotient_segment_polynomials) =
503                Self::compute_quotient_segments_with_jit_lde(
504                    main_table,
505                    aux_table,
506                    challenges,
507                    quotient_combination_weights,
508                );
509            profiler!(stop "quotient calculation (just-in-time)");
510
511            return (
512                fri_domain_quotient_segment_codewords,
513                quotient_segment_polynomials,
514            );
515        };
516
517        profiler!(start "quotient calculation (cached)" ("CC"));
518        let quotient_codeword = all_quotients_combined(
519            main_quotient_domain_codewords,
520            aux_quotient_domain_codewords,
521            main_table.trace_domain(),
522            quotient_domain,
523            challenges,
524            quotient_combination_weights,
525        );
526        let quotient_codeword = Array1::from(quotient_codeword);
527        assert_eq!(quotient_domain.length, quotient_codeword.len());
528        profiler!(stop "quotient calculation (cached)");
529
530        profiler!(start "quotient LDE" ("LDE"));
531        let quotient_segment_polynomials =
532            Self::interpolate_quotient_segments(quotient_codeword, quotient_domain);
533
534        let fri_domain = main_table.fri_domain();
535        let fri_domain_quotient_segment_codewords =
536            Self::fri_domain_segment_polynomials(quotient_segment_polynomials.view(), fri_domain);
537        profiler!(stop "quotient LDE");
538
539        (
540            fri_domain_quotient_segment_codewords,
541            quotient_segment_polynomials,
542        )
543    }
544
545    /// Computes the quotient segments in a memory-friendly way, i.e., without ever
546    /// representing the entire low-degree extended trace. Instead, the trace is
547    /// extrapolated over cosets of the trace domain, and the quotients are computed
548    /// there. The resulting coset-quotients are linearly recombined to produce the
549    /// quotient segment codewords.
550    fn compute_quotient_segments_with_jit_lde(
551        main_table: &mut MasterMainTable,
552        aux_table: &mut MasterAuxTable,
553        challenges: &Challenges,
554        quotient_combination_weights: &[XFieldElement],
555    ) -> (
556        Array2<XFieldElement>,
557        Array1<Polynomial<'static, XFieldElement>>,
558    ) {
559        // This parameter regulates a time-memory tradeoff. Semantically, it is the
560        // ratio of the randomized trace length to the length of the domain used for
561        // calculating the quotient segments (aka “working domain”). When this factor is
562        // larger, there are _more_ cosets of _smaller_ size over which the trace
563        // polynomials are evaluated.
564        // Must be a power of two and lie in 2..=randomized_trace_domain.length.
565        //
566        // The requirement for the working domain to be at most as long as the trace
567        // domain, i.e., at most half the size of the randomized trace domain, is
568        // explained below.
569        const RANDOMIZED_TRACE_LEN_TO_WORKING_DOMAIN_LEN_RATIO: usize = 2;
570        const NUM_COSETS: usize =
571            NUM_QUOTIENT_SEGMENTS * RANDOMIZED_TRACE_LEN_TO_WORKING_DOMAIN_LEN_RATIO;
572
573        debug_assert!(RANDOMIZED_TRACE_LEN_TO_WORKING_DOMAIN_LEN_RATIO.is_power_of_two());
574
575        let mut working_domain = main_table.randomized_trace_domain();
576        for _ in 0..RANDOMIZED_TRACE_LEN_TO_WORKING_DOMAIN_LEN_RATIO.ilog2() {
577            working_domain = working_domain.halve().unwrap();
578        }
579        let working_domain = working_domain;
580        let trace_domain = main_table.trace_domain();
581        let fri_domain = main_table.fri_domain();
582
583        let num_rows = working_domain.length;
584        let coset_root_order = (num_rows * NUM_COSETS).try_into().unwrap();
585
586        // the powers of ι define `NUM_COSETS`-many cosets of the working domain
587        let iota = BFieldElement::primitive_root_of_unity(coset_root_order).unwrap();
588        let psi = fri_domain.offset;
589
590        // for every coset, evaluate constraints
591        profiler!(start "zero-initialization");
592        // column majority (“`F`”) for contiguous column slices
593        let mut quotient_multicoset_evaluations =
594            ndarray_helper::par_zeros((num_rows, NUM_COSETS).f());
595        let mut main_columns =
596            ndarray_helper::par_zeros((num_rows, MasterMainTable::NUM_COLUMNS).f());
597        let mut aux_columns =
598            ndarray_helper::par_zeros((num_rows, MasterAuxTable::NUM_COLUMNS).f());
599        profiler!(stop "zero-initialization");
600
601        profiler!(start "fetch trace randomizers");
602        let main_trace_randomizers = (0..MasterMainTable::NUM_COLUMNS)
603            .into_par_iter()
604            .map(|i| main_table.trace_randomizer_for_column(i))
605            .collect::<Vec<_>>();
606        let aux_trace_randomizers = (0..MasterAuxTable::NUM_COLUMNS)
607            .into_par_iter()
608            .map(|i| aux_table.trace_randomizer_for_column(i))
609            .collect::<Vec<_>>();
610        let main_trace_randomizers = Array1::from(main_trace_randomizers);
611        let aux_trace_randomizers = Array1::from(aux_trace_randomizers);
612        profiler!(stop "fetch trace randomizers");
613
614        profiler!(start "poly interpolate" ("LDE"));
615        main_table
616            .trace_table_mut()
617            .axis_iter_mut(Axis(1))
618            .into_par_iter()
619            .for_each(|mut column| intt(column.as_slice_mut().unwrap()));
620        aux_table
621            .trace_table_mut()
622            .axis_iter_mut(Axis(1))
623            .into_par_iter()
624            .for_each(|mut column| intt(column.as_slice_mut().unwrap()));
625        profiler!(stop "poly interpolate");
626
627        profiler!(start "calculate quotients");
628        for (coset_index, quotient_column) in
629            (0..).zip(quotient_multicoset_evaluations.columns_mut())
630        {
631            // always also offset by fri domain offset to avoid division-by-zero errors
632            let working_domain = working_domain.with_offset(iota.mod_pow(coset_index) * psi);
633            profiler!(start "poly evaluate" ("LDE"));
634            Zip::from(main_table.trace_table().axis_iter(Axis(1)))
635                .and(main_columns.axis_iter_mut(Axis(1)))
636                .par_for_each(|trace_column, target_column| {
637                    let trace_poly = Polynomial::new_borrowed(trace_column.as_slice().unwrap());
638                    Array1::from(working_domain.evaluate(&trace_poly)).move_into(target_column);
639                });
640            Zip::from(aux_table.trace_table().axis_iter(Axis(1)))
641                .and(aux_columns.axis_iter_mut(Axis(1)))
642                .par_for_each(|trace_column, target_column| {
643                    let trace_poly = Polynomial::new_borrowed(trace_column.as_slice().unwrap());
644                    Array1::from(working_domain.evaluate(&trace_poly)).move_into(target_column);
645                });
646            profiler!(stop "poly evaluate");
647
648            // A _randomized_ trace interpolant is:
649            //
650            //    trace_interpolant + trace_zerofier·trace_randomizer
651            //    ╶───────┬───────╴   ╶──────────────┬─────────────╴
652            //            ╵                          │
653            //   was just moved into                 ╵
654            //  `{main, aux}_columns`          still missing
655            //
656            //
657            // Knowing the shape of the trace zerofier (see also `domain.zerofier()`), and
658            // with the length of the trace domain being `n`, this is:
659            //
660            //  trace_zerofier·trace_randomizer = (X^n - 1)·trace_randomizer
661            //
662            // For reasons of efficiency, all three components (interpolant, zerofier, and
663            // randomizer) are evaluated over the `working_domain`, i.e., they are
664            // codewords. The constant `RANDOMIZED_TRACE_LEN_TO_WORKING_DOMAIN_LEN_RATIO`
665            // defines the length of the working domain in relation to the randomized trace
666            // domain. Let the length of the working domain `m`, the generator of the
667            // working domain a primitive mth root of unity ξ, and the working domain's
668            // offset γ.
669            //
670            // If the length of the working domain `m` is less than or equal the length of
671            // the trace domain, i.e., if m <= n or equivalently, if constant
672            //  `RANDOMIZED_TRACE_LEN_TO_WORKING_DOMAIN_LEN_RATIO` >= 2, then evaluating the
673            // trace zerofier (X^n - 1) on the `i`th working domain value gives:
674            //
675            //   (X^n - 1)(ξ^i·γ) = (ξ^i·γ)^n - 1 = 1^i · γ^n - 1 = γ^n - 1
676            //
677            // In other words, the trace_zerofier codeword over the working domain is
678            // [working_domain_offset^n - 1; m].
679            //
680            // Should a future re-design want to consider a working domain of length equal
681            // to the randomized trace domain, or in other words, should it consider a
682            // `RANDOMIZED_TRACE_LEN_TO_WORKING_DOMAIN_LEN_RATIO` of 1, then the
683            // trace_zerofier's contribution below needs to be generalized. On
684            // working_domain's value `i`, the zerofier contribution would then be
685            // (-1)^i·γ^n - 1. In particular, note the term (-1)^i, which is absent from the
686            // trace randomizer when evaluated on working domains at most as long as the
687            // trace domain.
688            assert!(working_domain.length <= trace_domain.length);
689
690            profiler!(start "trace randomizers" ("LDE"));
691            let trace_domain_len = u64::try_from(trace_domain.length).unwrap();
692            let zerofier = working_domain.offset.mod_pow(trace_domain_len) - BFieldElement::ONE;
693
694            Zip::from(main_columns.axis_iter_mut(Axis(1)))
695                .and(main_trace_randomizers.axis_iter(Axis(0)))
696                .par_for_each(|mut column, randomizer_polynomial| {
697                    let randomizer_codeword = working_domain.evaluate(&randomizer_polynomial[[]]);
698                    for (cell, randomizer) in column.iter_mut().zip(randomizer_codeword) {
699                        *cell += zerofier * randomizer;
700                    }
701                });
702            Zip::from(aux_columns.axis_iter_mut(Axis(1)))
703                .and(aux_trace_randomizers.axis_iter(Axis(0)))
704                .par_for_each(|mut column, randomizer_polynomial| {
705                    let randomizer_codeword = working_domain.evaluate(&randomizer_polynomial[[]]);
706                    for (cell, randomizer) in column.iter_mut().zip(randomizer_codeword) {
707                        *cell += zerofier * randomizer;
708                    }
709                });
710            profiler!(stop "trace randomizers");
711
712            profiler!(start "AIR evaluation" ("AIR"));
713            let all_quotients = all_quotients_combined(
714                main_columns.view(),
715                aux_columns.view(),
716                trace_domain,
717                working_domain,
718                challenges,
719                quotient_combination_weights,
720            );
721            Array1::from(all_quotients).move_into(quotient_column);
722            profiler!(stop "AIR evaluation");
723        }
724        profiler!(stop "calculate quotients");
725
726        profiler!(start "segmentify");
727        let segmentification = Self::segmentify::<NUM_QUOTIENT_SEGMENTS>(
728            quotient_multicoset_evaluations,
729            psi,
730            iota,
731            fri_domain,
732        );
733        profiler!(stop "segmentify");
734
735        profiler!(start "restore original trace" ("LDE"));
736        main_table
737            .trace_table_mut()
738            .axis_iter_mut(Axis(1))
739            .into_par_iter()
740            .for_each(|mut column| ntt(column.as_slice_mut().unwrap()));
741        aux_table
742            .trace_table_mut()
743            .axis_iter_mut(Axis(1))
744            .into_par_iter()
745            .for_each(|mut column| ntt(column.as_slice_mut().unwrap()));
746        profiler!(stop "restore original trace");
747
748        segmentification
749    }
750
751    /// Map a matrix whose columns represent the evaluation of a high-degree
752    /// polynomial f on all constituents of a partition of some large domain into
753    /// smaller cosets, to
754    /// 1. a matrix of segment codewords (on the FRI domain), and
755    /// 2. an array of matching segment polynomials,
756    ///
757    /// such that the segment polynomials correspond to the interleaving split of
758    /// the original high-degree polynomial.
759    ///
760    /// For example, let f(X) have degree M·N where N is the chosen domain's length.
761    /// Then the input is an N×M matrix representing the values of f(X) on the
762    /// chosen domain and its cosets:
763    ///
764    /// ```txt
765    /// ⎛  …          …   …           ⎞  ┬
766    /// ⎜ f(coset_0)  …  f(coset_M-1) ⎟ domain length
767    /// ⎝  …          …   …           ⎠  ┴
768    ///
769    /// ├───────── NUM_COSETS ────────┤
770    /// ```
771    ///
772    /// The `NUM_SEGMENTS` (=:`K`) produced segment polynomials are f_i(X) such that
773    /// f(X) = Σ_k x^k · f_k(X^K).
774    /// For example, for `K = 2`, this is f(X) = f_E(X²) + X·f_O(X²).
775    ///
776    /// The produced segment codewords are the segment polynomial's evaluations on
777    /// the FRI domain:
778    ///
779    /// ```txt
780    /// ⎛  …            …   …             ⎞  ┬
781    /// ⎜ f_0(FRI_dom)  …  f_K-1(FRI_dom) ⎟ FRI domain length
782    /// ⎝  …            …   …             ⎠  ┴
783    ///
784    /// ├────────── NUM_SEGMENTS ─────────┤
785    /// ```
786    //
787    // The mechanics of this function are backed by some serious maths originally
788    // derived by Alan Szepieniec, and later generalized by him and Jan Ferdinand
789    // Sauer.
790    //
791    // The main idea is based on the segmentation formula. For K segments, this is
792    //
793    // f(X) = Σ_{k=0}^{K-1} X^k · f_k(X^K)
794    //
795    // where each f_k is one segment. When substituting X for X·ξ^l, where ξ is a
796    // Kth root of unity (i.e., ξ^K = 1), this gives rise to K equations, where
797    // l ∈ { 0, …, K-1 }:
798    //
799    // f(X·ξ^0)     = Σ_{k=0}^{K-1} (X·ξ^0)^k     · f_k(X^K)
800    //              ⋮
801    // f(X·ξ^(K-1)) = Σ_{k=0}^{K-1} (X·ξ^(K-1))^k · f_k(X^K)
802    //
803    // Note how the indeterminates of the f_k are identical for all rows. That is,
804    // the mapping between f's evaluations on (the “right”) cosets and f's segments
805    // is a linear one.
806    //
807    // ⎛  …       ⎞       ⎛     …               ⎞   ⎛  …       ⎞
808    // ⎜ f(X·ξ^l) ⎟   =   ⎜ …  X^k · ξ^(k·l)  … ⎟ · ⎜ f_k(X^K) ⎟                 (1)
809    // ⎝  …       ⎠       ⎝     …               ⎠   ⎝  …       ⎠
810    //
811    // This function works by applying the inverse of the coefficient matrix to the
812    // function's input, i.e., to the left hand side of above equation. To compute
813    // this map efficiently, it is decomposed as follows.
814    // Operator “∘” denotes the Hadamard, i.e., element-wise product.
815    //
816    //                    ⎛     …         ⎞   ⎛ ⎛  …  ⎞   ⎛  …       ⎞ ⎞
817    //                =   ⎜ …  ξ^(k·l)  … ⎟ · ⎜ ⎜ X^k ⎟ ∘ ⎜ f_k(X^K) ⎟ ⎟
818    //                    ⎝     …         ⎠   ⎝ ⎝  …  ⎠   ⎝  …       ⎠ ⎠
819    //
820    // The coefficient matrix has dimensions K×K. Since ξ is a Kth root of unity,
821    // above matrix is an NTT matrix. That means its application can be efficiently
822    // reverted by performing iNTTs.
823    // The final step is elementwise multiplication with the vector (X^(-k)) to
824    // get the segment polynomials.
825    //
826    // For reasons of efficiency, this function does not operate on polynomials in
827    // monomial coefficient form, but on polynomial evaluations on some domain,
828    // i.e., codewords.
829    // Also for reasons of efficiency, the domain length N is a power of two, and
830    // the evaluation points are multiples of an Nth root of unity, ω. In order to
831    // avoid divisions by zero, the domain is offset by Ψ. Furthermore, the offset
832    // of a coset is some power of ι, which itself is a root of unity of order N·M,
833    // where M is the number of cosets. That is, ι^M = ω, and ω^N = 1.
834    // Summarizing, this function's input is a matrix of the following form:
835    //
836    // ⎛     …                  ⎞╷ ┬       ⎛     …                   ⎞
837    // ⎜ …  f(Ψ · ι^j · ω^i)  … ⎟i N   =   ⎜ …  f(Ψ · ι^(j + iM))  … ⎟
838    // ⎝     …                  ⎠↓ ┴       ⎝     …                   ⎠
839    // ╶─────────── j ─────────→
840    // ├─────────── M ──────────┤
841    //
842    // In order to kick off the series of steps derived & outlined above, this
843    // matrix needs to be rearranged. The desired shape can be derived by taking the
844    // left-hand side of the system of equations (1) and substituting the
845    // indeterminate X for the points at which f is evaluated, Ψ · ι^j · ω^i.
846    // Let L such that N·M = L·K. Observe that ξ being a Kth root of unity implies
847    // ξ = ω^(N/K) = ι^(N·M/K) = ι^L.
848    //
849    // ⎛  …       ⎞       ⎛     …                          ⎞ ┬
850    // ⎜ f(X·ξ^l) ⎟   ↦   ⎜ …  f(ψ · ι^(j + i·M + l·L))  … ⎟ L
851    // ⎝  …       ⎠       ⎝     …                          ⎠ ┴
852    //
853    //                    ├────────────── K ──────────────┤
854    //
855    // Helpful insights to understand the matrix re-arrangement are:
856    // - the various powers of ι, i.e., { ι^(j + i·M) | 0 ⩽ i < N, 0 ⩽ j < M },
857    //   sweep the entire input matrix (which has dimensions N×M)
858    // - ι is a (primitive) (N·M)th root of unity and thus, _all_ powers of ι are
859    //   required to index the entirety of the input matrix
860    // - the re-arranged matrix (which has dimensions L×K) has all the same entries
861    //   as the input matrix
862    //
863    // The map that satisfies all of these re-arrangement constraints is
864    // (i, j) ↦ ((j + i·M) % L, (j + i·M) // L)
865    // which has the inverse
866    // (a, b) ↦ ((a + b·L) // M, (a + b·L) % M).
867    //
868    // With all those pieces in place, it's finally time for some code.
869    fn segmentify<const NUM_SEGMENTS: usize>(
870        quotient_multicoset_evaluations: Array2<XFieldElement>,
871        psi: BFieldElement,
872        iota: BFieldElement,
873        fri_domain: ArithmeticDomain,
874    ) -> (
875        Array2<XFieldElement>,
876        Array1<Polynomial<'static, XFieldElement>>,
877    ) {
878        let num_input_rows = quotient_multicoset_evaluations.nrows();
879        let num_cosets = quotient_multicoset_evaluations.ncols();
880        let num_output_rows = num_input_rows * num_cosets / NUM_SEGMENTS;
881
882        assert!(num_input_rows.is_power_of_two());
883        assert!(num_cosets.is_power_of_two());
884        assert!(num_output_rows.is_power_of_two());
885        assert!(NUM_SEGMENTS.is_power_of_two());
886        assert!(
887            num_input_rows >= num_cosets,
888            "working domain length: {num_input_rows} versus num cosets: {num_cosets}",
889        );
890        assert!(
891            num_cosets >= NUM_SEGMENTS,
892            "num cosets: {num_cosets} versus num segments: {NUM_SEGMENTS}",
893        );
894
895        // Re-arrange data in preparation for iNTT:
896        // Move appropriate powers of ξ^(k·l) with the same k into the same row. Change
897        // the matrix's dimensions from N×M to L×K, with row majority (“`C`”) to get
898        // contiguous row slices for iNTT.
899        let mut quotient_segments = ndarray_helper::par_zeros((num_output_rows, NUM_SEGMENTS));
900        quotient_segments
901            .axis_iter_mut(Axis(0))
902            .into_par_iter()
903            .enumerate()
904            .for_each(|(output_row_idx, mut output_row)| {
905                for (output_col_idx, cell) in output_row.iter_mut().enumerate() {
906                    let exponent_of_iota = output_row_idx + output_col_idx * num_output_rows;
907                    let input_row_idx = exponent_of_iota / num_cosets;
908                    let input_col_idx = exponent_of_iota % num_cosets;
909                    *cell = quotient_multicoset_evaluations[[input_row_idx, input_col_idx]];
910                }
911            });
912
913        // apply inverse of Vandermonde matrix for ξ = ι^L to every row
914        quotient_segments
915            .axis_iter_mut(Axis(0))
916            .into_par_iter()
917            .for_each(|mut row| intt(row.as_slice_mut().unwrap()));
918
919        // scale every row by Ψ^-k · ι^(-k(j+i·M))
920        let num_threads = std::thread::available_parallelism()
921            .map(|t| t.get())
922            .unwrap_or(1);
923        let chunk_size = (num_output_rows / num_threads).max(1);
924        let iota_inverse = iota.inverse();
925        let psi_inverse = psi.inverse();
926        quotient_segments
927            .axis_chunks_iter_mut(Axis(0), chunk_size)
928            .into_par_iter()
929            .enumerate()
930            .for_each(|(chunk_idx, mut chunk_of_rows)| {
931                let chunk_start = (chunk_idx * chunk_size).try_into().unwrap();
932                let mut psi_iotajim_inv = psi_inverse * iota_inverse.mod_pow(chunk_start);
933                for mut row in chunk_of_rows.rows_mut() {
934                    let mut psi_iotajim_invk = XFieldElement::ONE;
935                    for cell in &mut row {
936                        *cell *= psi_iotajim_invk;
937                        psi_iotajim_invk *= psi_iotajim_inv;
938                    }
939                    psi_iotajim_inv *= iota_inverse;
940                }
941            });
942
943        // low-degree extend columns from trace to FRI domain
944        let segment_domain_offset = psi.mod_pow(NUM_SEGMENTS.try_into().unwrap());
945        let segment_domain = ArithmeticDomain::of_length(num_output_rows)
946            .unwrap()
947            .with_offset(segment_domain_offset);
948
949        let mut quotient_codewords = Array2::zeros([fri_domain.length, NUM_SEGMENTS]);
950        let mut quotient_polynomials = Array1::zeros([NUM_SEGMENTS]);
951        Zip::from(quotient_segments.axis_iter(Axis(1)))
952            .and(quotient_codewords.axis_iter_mut(Axis(1)))
953            .and(quotient_polynomials.axis_iter_mut(Axis(0)))
954            .par_for_each(|segment, target_codeword, target_polynomial| {
955                // `quotient_segments` is in row-major order, requiring `segment.to_vec()`
956                let interpolant = segment_domain.interpolate(&segment.to_vec());
957                let lde_codeword = fri_domain.evaluate(&interpolant);
958                Array1::from(lde_codeword).move_into(target_codeword);
959                Array0::from_elem((), interpolant).move_into(target_polynomial);
960            });
961
962        (quotient_codewords, quotient_polynomials)
963    }
964
965    fn interpolate_quotient_segments(
966        quotient_codeword: Array1<XFieldElement>,
967        quotient_domain: ArithmeticDomain,
968    ) -> Array1<Polynomial<'static, XFieldElement>> {
969        let quotient_interpolation_poly = quotient_domain.interpolate(&quotient_codeword.to_vec());
970        let quotient_segments: [_; NUM_QUOTIENT_SEGMENTS] =
971            Self::split_polynomial_into_segments(quotient_interpolation_poly);
972        Array1::from(quotient_segments.to_vec())
973    }
974
975    /// Losslessly split the given polynomial `f` into `N` segments of (roughly) equal degree.
976    /// The degree of each segment is at most `f.degree() / N`.
977    /// It holds that `f(x) = Σ_{i=0}^{N-1} x^i·f_i(x^N)`, where the `f_i` are the segments.
978    ///
979    /// For example, let
980    /// - `N = 3`, and
981    /// - `f(x) = 7·x^7 + 6·x^6 + 5·x^5 + 4·x^4 + 3·x^3 + 2·x^2 + 1·x + 0`.
982    ///
983    /// Then, the function returns the array:
984    ///
985    /// ```text
986    /// [f_0(x) = 6·x^2 + 3·x + 0,
987    ///  f_1(x) = 7·x^2 + 4·x + 1,
988    ///  f_2(x) =         5·x + 2]
989    /// ```
990    ///
991    /// The following equality holds: `f(x) == f_0(x^3) + x·f_1(x^3) + x^2·f_2(x^3)`.
992    fn split_polynomial_into_segments<const N: usize, FF: FiniteField>(
993        polynomial: Polynomial<FF>,
994    ) -> [Polynomial<'static, FF>; N] {
995        let mut segments = Vec::with_capacity(N);
996        let coefficients = polynomial.into_coefficients();
997        for segment_index in 0..N {
998            let segment_coefficients = coefficients.iter().skip(segment_index).step_by(N);
999            let segment = Polynomial::new(segment_coefficients.copied().collect());
1000            segments.push(segment);
1001        }
1002        segments.try_into().unwrap()
1003    }
1004
1005    fn fri_domain_segment_polynomials(
1006        quotient_segment_polynomials: ArrayView1<Polynomial<XFieldElement>>,
1007        fri_domain: ArithmeticDomain,
1008    ) -> Array2<XFieldElement> {
1009        let fri_domain_codewords: Vec<_> = quotient_segment_polynomials
1010            .into_par_iter()
1011            .flat_map(|segment| fri_domain.evaluate(segment))
1012            .collect();
1013        Array2::from_shape_vec(
1014            [fri_domain.length, NUM_QUOTIENT_SEGMENTS].f(),
1015            fri_domain_codewords,
1016        )
1017        .unwrap()
1018    }
1019
1020    /// Apply the [DEEP update](Stark::deep_update) to a polynomial in value form,
1021    /// _i.e._, to a codeword.
1022    fn deep_codeword(
1023        codeword: &[XFieldElement],
1024        domain: ArithmeticDomain,
1025        out_of_domain_point: XFieldElement,
1026        out_of_domain_value: XFieldElement,
1027    ) -> Vec<XFieldElement> {
1028        domain
1029            .domain_values()
1030            .par_iter()
1031            .zip_eq(codeword)
1032            .map(|(&in_domain_value, &in_domain_evaluation)| {
1033                Stark::deep_update(
1034                    in_domain_value,
1035                    in_domain_evaluation,
1036                    out_of_domain_point,
1037                    out_of_domain_value,
1038                )
1039            })
1040            .collect()
1041    }
1042}
1043
1044impl Verifier {
1045    pub fn new(parameters: Stark) -> Self {
1046        Self { parameters }
1047    }
1048
1049    /// See also [`Stark::verify`].
1050    pub fn verify(self, claim: &Claim, proof: &Proof) -> Result<(), VerificationError> {
1051        profiler!(start "deserialize");
1052        let mut proof_stream = ProofStream::try_from(proof)?;
1053        profiler!(stop "deserialize");
1054
1055        profiler!(start "Fiat-Shamir: Claim" ("hash"));
1056        proof_stream.alter_fiat_shamir_state_with(claim);
1057        profiler!(stop "Fiat-Shamir: Claim");
1058
1059        profiler!(start "derive additional parameters");
1060        let log_2_padded_height = proof_stream.dequeue()?.try_into_log2_padded_height()?;
1061        let padded_height = 1 << log_2_padded_height;
1062        let fri = self.parameters.fri(padded_height)?;
1063        let merkle_tree_height = fri.domain.length.ilog2() as usize;
1064        profiler!(stop "derive additional parameters");
1065
1066        profiler!(start "Fiat-Shamir 1" ("hash"));
1067        let main_merkle_tree_root = proof_stream.dequeue()?.try_into_merkle_root()?;
1068        let extension_challenge_weights = proof_stream.sample_scalars(Challenges::SAMPLE_COUNT);
1069        let challenges = Challenges::new(extension_challenge_weights, claim);
1070        let auxiliary_tree_merkle_root = proof_stream.dequeue()?.try_into_merkle_root()?;
1071        // Sample weights for quotient codeword, which is a part of the combination codeword.
1072        // See corresponding part in the prover for a more detailed explanation.
1073        let quot_codeword_weights = proof_stream.sample_scalars(MasterAuxTable::NUM_CONSTRAINTS);
1074        let quot_codeword_weights = Array1::from(quot_codeword_weights);
1075        let quotient_codeword_merkle_root = proof_stream.dequeue()?.try_into_merkle_root()?;
1076        profiler!(stop "Fiat-Shamir 1");
1077
1078        profiler!(start "dequeue ood point and rows" ("hash"));
1079        let trace_domain_generator = ArithmeticDomain::generator_for_length(padded_height as u64)?;
1080        let out_of_domain_point_curr_row = proof_stream.sample_scalars(1)[0];
1081        let out_of_domain_point_next_row = trace_domain_generator * out_of_domain_point_curr_row;
1082        let out_of_domain_point_curr_row_pow_num_segments =
1083            out_of_domain_point_curr_row.mod_pow_u32(NUM_QUOTIENT_SEGMENTS as u32);
1084
1085        let out_of_domain_curr_main_row =
1086            proof_stream.dequeue()?.try_into_out_of_domain_main_row()?;
1087        let out_of_domain_curr_aux_row =
1088            proof_stream.dequeue()?.try_into_out_of_domain_aux_row()?;
1089        let out_of_domain_next_main_row =
1090            proof_stream.dequeue()?.try_into_out_of_domain_main_row()?;
1091        let out_of_domain_next_aux_row =
1092            proof_stream.dequeue()?.try_into_out_of_domain_aux_row()?;
1093        let out_of_domain_curr_row_quot_segments = proof_stream
1094            .dequeue()?
1095            .try_into_out_of_domain_quot_segments()?;
1096
1097        let out_of_domain_curr_main_row = Array1::from(out_of_domain_curr_main_row.to_vec());
1098        let out_of_domain_curr_aux_row = Array1::from(out_of_domain_curr_aux_row.to_vec());
1099        let out_of_domain_next_main_row = Array1::from(out_of_domain_next_main_row.to_vec());
1100        let out_of_domain_next_aux_row = Array1::from(out_of_domain_next_aux_row.to_vec());
1101        let out_of_domain_curr_row_quot_segments =
1102            Array1::from(out_of_domain_curr_row_quot_segments.to_vec());
1103        profiler!(stop "dequeue ood point and rows");
1104
1105        profiler!(start "out-of-domain quotient element");
1106        profiler!(start "evaluate AIR" ("AIR"));
1107        let evaluated_initial_constraints = MasterAuxTable::evaluate_initial_constraints(
1108            out_of_domain_curr_main_row.view(),
1109            out_of_domain_curr_aux_row.view(),
1110            &challenges,
1111        );
1112        let evaluated_consistency_constraints = MasterAuxTable::evaluate_consistency_constraints(
1113            out_of_domain_curr_main_row.view(),
1114            out_of_domain_curr_aux_row.view(),
1115            &challenges,
1116        );
1117        let evaluated_transition_constraints = MasterAuxTable::evaluate_transition_constraints(
1118            out_of_domain_curr_main_row.view(),
1119            out_of_domain_curr_aux_row.view(),
1120            out_of_domain_next_main_row.view(),
1121            out_of_domain_next_aux_row.view(),
1122            &challenges,
1123        );
1124        let evaluated_terminal_constraints = MasterAuxTable::evaluate_terminal_constraints(
1125            out_of_domain_curr_main_row.view(),
1126            out_of_domain_curr_aux_row.view(),
1127            &challenges,
1128        );
1129        profiler!(stop "evaluate AIR");
1130
1131        profiler!(start "zerofiers");
1132        let initial_zerofier_inv = (out_of_domain_point_curr_row - bfe!(1)).inverse();
1133        let consistency_zerofier_inv =
1134            (out_of_domain_point_curr_row.mod_pow_u32(padded_height as u32) - bfe!(1)).inverse();
1135        let except_last_row = out_of_domain_point_curr_row - trace_domain_generator.inverse();
1136        let transition_zerofier_inv = except_last_row * consistency_zerofier_inv;
1137        let terminal_zerofier_inv = except_last_row.inverse(); // i.e., only last row
1138        profiler!(stop "zerofiers");
1139
1140        profiler!(start "divide");
1141        let divide = |constraints: Vec<_>, z_inv| constraints.into_iter().map(move |c| c * z_inv);
1142        let initial_quotients = divide(evaluated_initial_constraints, initial_zerofier_inv);
1143        let consistency_quotients =
1144            divide(evaluated_consistency_constraints, consistency_zerofier_inv);
1145        let transition_quotients =
1146            divide(evaluated_transition_constraints, transition_zerofier_inv);
1147        let terminal_quotients = divide(evaluated_terminal_constraints, terminal_zerofier_inv);
1148
1149        let quotient_summands = initial_quotients
1150            .chain(consistency_quotients)
1151            .chain(transition_quotients)
1152            .chain(terminal_quotients)
1153            .collect_vec();
1154        profiler!(stop "divide");
1155
1156        profiler!(start "inner product" ("CC"));
1157        let out_of_domain_quotient_value =
1158            quot_codeword_weights.dot(&Array1::from(quotient_summands));
1159        profiler!(stop "inner product");
1160        profiler!(stop "out-of-domain quotient element");
1161
1162        profiler!(start "verify quotient's segments");
1163        let powers_of_out_of_domain_point_curr_row = (0..NUM_QUOTIENT_SEGMENTS as u32)
1164            .map(|exponent| out_of_domain_point_curr_row.mod_pow_u32(exponent))
1165            .collect::<Array1<_>>();
1166        let sum_of_evaluated_out_of_domain_quotient_segments =
1167            powers_of_out_of_domain_point_curr_row.dot(&out_of_domain_curr_row_quot_segments);
1168        if out_of_domain_quotient_value != sum_of_evaluated_out_of_domain_quotient_segments {
1169            return Err(VerificationError::OutOfDomainQuotientValueMismatch);
1170        };
1171        profiler!(stop "verify quotient's segments");
1172
1173        profiler!(start "Fiat-Shamir 2" ("hash"));
1174        let weights = LinearCombinationWeights::sample(&mut proof_stream);
1175        let main_and_aux_codeword_weights = weights.main_and_aux();
1176        profiler!(stop "Fiat-Shamir 2");
1177
1178        profiler!(start "sum out-of-domain values" ("CC"));
1179        let out_of_domain_curr_row_main_and_aux_value = Self::linearly_sum_main_and_aux_row(
1180            out_of_domain_curr_main_row.view(),
1181            out_of_domain_curr_aux_row.view(),
1182            main_and_aux_codeword_weights.view(),
1183        );
1184        let out_of_domain_next_row_main_and_aux_value = Self::linearly_sum_main_and_aux_row(
1185            out_of_domain_next_main_row.view(),
1186            out_of_domain_next_aux_row.view(),
1187            main_and_aux_codeword_weights.view(),
1188        );
1189        let out_of_domain_curr_row_quotient_segment_value = weights
1190            .quot_segments
1191            .dot(&out_of_domain_curr_row_quot_segments);
1192        profiler!(stop "sum out-of-domain values");
1193
1194        // verify low degree of combination polynomial with FRI
1195        profiler!(start "FRI");
1196        let revealed_fri_indices_and_elements = fri.verify(&mut proof_stream)?;
1197        let (revealed_current_row_indices, revealed_fri_values): (Vec<_>, Vec<_>) =
1198            revealed_fri_indices_and_elements.into_iter().unzip();
1199        profiler!(stop "FRI");
1200
1201        profiler!(start "check leafs");
1202        profiler!(start "dequeue main elements");
1203        let main_table_rows = proof_stream.dequeue()?.try_into_master_main_table_rows()?;
1204        let main_authentication_structure = proof_stream
1205            .dequeue()?
1206            .try_into_authentication_structure()?;
1207        let leaf_digests_main: Vec<_> = main_table_rows
1208            .par_iter()
1209            .map(|revealed_main_elem| Tip5::hash_varlen(revealed_main_elem))
1210            .collect();
1211        profiler!(stop "dequeue main elements");
1212
1213        let index_leaves = |leaves| {
1214            let index_iter = revealed_current_row_indices.iter().copied();
1215            index_iter.zip_eq(leaves).collect()
1216        };
1217        profiler!(start "Merkle verify (main tree)" ("hash"));
1218        let base_merkle_tree_inclusion_proof = MerkleTreeInclusionProof {
1219            tree_height: merkle_tree_height,
1220            indexed_leafs: index_leaves(leaf_digests_main),
1221            authentication_structure: main_authentication_structure,
1222        };
1223        if !base_merkle_tree_inclusion_proof.verify(main_merkle_tree_root) {
1224            return Err(VerificationError::MainCodewordAuthenticationFailure);
1225        }
1226        profiler!(stop "Merkle verify (main tree)");
1227
1228        profiler!(start "dequeue auxiliary elements");
1229        let aux_table_rows = proof_stream.dequeue()?.try_into_master_aux_table_rows()?;
1230        let aux_authentication_structure = proof_stream
1231            .dequeue()?
1232            .try_into_authentication_structure()?;
1233        let leaf_digests_aux = aux_table_rows
1234            .par_iter()
1235            .map(|xvalues| {
1236                let b_values = xvalues.iter().flat_map(|xfe| xfe.coefficients.to_vec());
1237                Tip5::hash_varlen(&b_values.collect_vec())
1238            })
1239            .collect::<Vec<_>>();
1240        profiler!(stop "dequeue auxiliary elements");
1241
1242        profiler!(start "Merkle verify (auxiliary tree)" ("hash"));
1243        let aux_merkle_tree_inclusion_proof = MerkleTreeInclusionProof {
1244            tree_height: merkle_tree_height,
1245            indexed_leafs: index_leaves(leaf_digests_aux),
1246            authentication_structure: aux_authentication_structure,
1247        };
1248        if !aux_merkle_tree_inclusion_proof.verify(auxiliary_tree_merkle_root) {
1249            return Err(VerificationError::AuxiliaryCodewordAuthenticationFailure);
1250        }
1251        profiler!(stop "Merkle verify (auxiliary tree)");
1252
1253        profiler!(start "dequeue quotient segments' elements");
1254        let revealed_quotient_segments_elements =
1255            proof_stream.dequeue()?.try_into_quot_segments_elements()?;
1256        let revealed_quotient_segments_digests =
1257            Self::hash_quotient_segment_elements(&revealed_quotient_segments_elements);
1258        let revealed_quotient_authentication_structure = proof_stream
1259            .dequeue()?
1260            .try_into_authentication_structure()?;
1261        profiler!(stop "dequeue quotient segments' elements");
1262
1263        profiler!(start "Merkle verify (combined quotient)" ("hash"));
1264        let quot_merkle_tree_inclusion_proof = MerkleTreeInclusionProof {
1265            tree_height: merkle_tree_height,
1266            indexed_leafs: index_leaves(revealed_quotient_segments_digests),
1267            authentication_structure: revealed_quotient_authentication_structure,
1268        };
1269        if !quot_merkle_tree_inclusion_proof.verify(quotient_codeword_merkle_root) {
1270            return Err(VerificationError::QuotientCodewordAuthenticationFailure);
1271        }
1272        profiler!(stop "Merkle verify (combined quotient)");
1273        profiler!(stop "check leafs");
1274
1275        profiler!(start "linear combination");
1276        if self.parameters.num_collinearity_checks != revealed_current_row_indices.len() {
1277            return Err(VerificationError::IncorrectNumberOfRowIndices);
1278        };
1279        if self.parameters.num_collinearity_checks != revealed_fri_values.len() {
1280            return Err(VerificationError::IncorrectNumberOfFRIValues);
1281        };
1282        if self.parameters.num_collinearity_checks != revealed_quotient_segments_elements.len() {
1283            return Err(VerificationError::IncorrectNumberOfQuotientSegmentElements);
1284        };
1285        if self.parameters.num_collinearity_checks != main_table_rows.len() {
1286            return Err(VerificationError::IncorrectNumberOfMainTableRows);
1287        };
1288        if self.parameters.num_collinearity_checks != aux_table_rows.len() {
1289            return Err(VerificationError::IncorrectNumberOfAuxTableRows);
1290        };
1291
1292        for (row_idx, main_row, aux_row, quotient_segments_elements, fri_value) in izip!(
1293            revealed_current_row_indices,
1294            main_table_rows,
1295            aux_table_rows,
1296            revealed_quotient_segments_elements,
1297            revealed_fri_values,
1298        ) {
1299            let main_row = Array1::from(main_row.to_vec());
1300            let aux_row = Array1::from(aux_row.to_vec());
1301            let current_fri_domain_value = fri.domain.domain_value(row_idx as u32);
1302
1303            profiler!(start "main & aux elements" ("CC"));
1304            let main_and_aux_curr_row_element = Self::linearly_sum_main_and_aux_row(
1305                main_row.view(),
1306                aux_row.view(),
1307                main_and_aux_codeword_weights.view(),
1308            );
1309            let quotient_segments_curr_row_element = weights
1310                .quot_segments
1311                .dot(&Array1::from(quotient_segments_elements.to_vec()));
1312            profiler!(stop "main & aux elements");
1313
1314            profiler!(start "DEEP update");
1315            let main_and_aux_curr_row_deep_value = Stark::deep_update(
1316                current_fri_domain_value,
1317                main_and_aux_curr_row_element,
1318                out_of_domain_point_curr_row,
1319                out_of_domain_curr_row_main_and_aux_value,
1320            );
1321            let main_and_aux_next_row_deep_value = Stark::deep_update(
1322                current_fri_domain_value,
1323                main_and_aux_curr_row_element,
1324                out_of_domain_point_next_row,
1325                out_of_domain_next_row_main_and_aux_value,
1326            );
1327            let quot_curr_row_deep_value = Stark::deep_update(
1328                current_fri_domain_value,
1329                quotient_segments_curr_row_element,
1330                out_of_domain_point_curr_row_pow_num_segments,
1331                out_of_domain_curr_row_quotient_segment_value,
1332            );
1333            profiler!(stop "DEEP update");
1334
1335            profiler!(start "combination codeword equality");
1336            let deep_value_components = Array1::from(vec![
1337                main_and_aux_curr_row_deep_value,
1338                main_and_aux_next_row_deep_value,
1339                quot_curr_row_deep_value,
1340            ]);
1341            if fri_value != weights.deep.dot(&deep_value_components) {
1342                return Err(VerificationError::CombinationCodewordMismatch);
1343            };
1344            profiler!(stop "combination codeword equality");
1345        }
1346        profiler!(stop "linear combination");
1347        Ok(())
1348    }
1349
1350    fn hash_quotient_segment_elements(quotient_segment_rows: &[QuotientSegments]) -> Vec<Digest> {
1351        let interpret_xfe_as_bfes = |xfe: XFieldElement| xfe.coefficients.to_vec();
1352        let collect_row_as_bfes = |row: &QuotientSegments| row.map(interpret_xfe_as_bfes).concat();
1353        quotient_segment_rows
1354            .iter()
1355            .map(collect_row_as_bfes)
1356            .map(|row| Tip5::hash_varlen(&row))
1357            .collect()
1358    }
1359
1360    fn linearly_sum_main_and_aux_row<FF>(
1361        main_row: ArrayView1<FF>,
1362        aux_row: ArrayView1<XFieldElement>,
1363        weights: ArrayView1<XFieldElement>,
1364    ) -> XFieldElement
1365    where
1366        FF: FiniteField + Into<XFieldElement>,
1367        XFieldElement: Mul<FF, Output = XFieldElement>,
1368    {
1369        profiler!(start "collect");
1370        let mut row = main_row.map(|&element| element.into());
1371        row.append(Axis(0), aux_row).unwrap();
1372        profiler!(stop "collect");
1373        profiler!(start "inner product");
1374        // todo: Try to get rid of this clone. The alternative line
1375        //   `let main_and_aux_element = (&weights * &summands).sum();`
1376        //   without cloning the weights does not compile for a seemingly nonsensical reason.
1377        let weights = weights.to_owned();
1378        let main_and_aux_element = (weights * row).sum();
1379        profiler!(stop "inner product");
1380        main_and_aux_element
1381    }
1382}
1383
1384impl Stark {
1385    /// # Panics
1386    ///
1387    /// Panics if `log2_of_fri_expansion_factor` is zero.
1388    pub fn new(security_level: usize, log2_of_fri_expansion_factor: usize) -> Self {
1389        assert_ne!(
1390            0, log2_of_fri_expansion_factor,
1391            "FRI expansion factor must be greater than one."
1392        );
1393
1394        let fri_expansion_factor = 1 << log2_of_fri_expansion_factor;
1395        let num_collinearity_checks = security_level / log2_of_fri_expansion_factor;
1396
1397        let num_out_of_domain_rows = 2;
1398        let num_trace_randomizers = num_collinearity_checks
1399            + num_out_of_domain_rows * x_field_element::EXTENSION_DEGREE
1400            + NUM_QUOTIENT_SEGMENTS * x_field_element::EXTENSION_DEGREE;
1401
1402        Stark {
1403            security_level,
1404            fri_expansion_factor,
1405            num_trace_randomizers,
1406            num_collinearity_checks,
1407        }
1408    }
1409
1410    /// Prove the correctness of the given [Claim] using the given
1411    /// [witness](AlgebraicExecutionTrace).
1412    ///
1413    /// This method should be the first option to consider for proving.
1414    /// For more control over the proving process, see [`Prover`].
1415    // This pass-through method guarantees fresh prover randomness at each call.
1416    pub fn prove(
1417        &self,
1418        claim: &Claim,
1419        aet: &AlgebraicExecutionTrace,
1420    ) -> Result<Proof, ProvingError> {
1421        Prover::new(*self).prove(claim, aet)
1422    }
1423
1424    /// Verify the accuracy of the given [Claim], supported by the [Proof].
1425    ///
1426    /// See also [`Verifier`].
1427    // This pass-through method achieves symmetry with the [`Prover`].
1428    pub fn verify(&self, claim: &Claim, proof: &Proof) -> Result<(), VerificationError> {
1429        Verifier::new(*self).verify(claim, proof)
1430    }
1431
1432    /// The upper bound to use for the maximum degree the quotients given the length
1433    /// of the trace and the number of trace randomizers. The degree of the
1434    /// quotients depends on the [AIR](air) constraints.
1435    pub fn max_degree(&self, padded_height: usize) -> isize {
1436        let interpolant_degree = interpolant_degree(padded_height, self.num_trace_randomizers);
1437        let max_constraint_degree_with_origin =
1438            max_degree_with_origin(interpolant_degree, padded_height);
1439        let max_constraint_degree = max_constraint_degree_with_origin.degree as u64;
1440        let min_arithmetic_domain_length_supporting_max_constraint_degree =
1441            max_constraint_degree.next_power_of_two();
1442        let max_degree_supported_by_that_smallest_arithmetic_domain =
1443            min_arithmetic_domain_length_supporting_max_constraint_degree - 1;
1444
1445        max_degree_supported_by_that_smallest_arithmetic_domain as isize
1446    }
1447
1448    /// The parameters for [FRI](Fri). The length of the
1449    /// [FRI domain](ArithmeticDomain) has a major influence on
1450    /// [proving](Prover::prove) time. It is influenced by the length of the
1451    /// [execution trace](AlgebraicExecutionTrace) and the FRI expansion factor,
1452    /// a security parameter.
1453    ///
1454    /// In principle, the FRI domain length is also influenced by the AIR's degree
1455    /// (see [`air::TARGET_DEGREE`]). However, by segmenting the quotient polynomial
1456    /// into `TARGET_DEGREE`-many parts, that influence is mitigated.
1457    pub fn fri(&self, padded_height: usize) -> fri::SetupResult<Fri> {
1458        let fri_domain_length = self.fri_expansion_factor
1459            * randomized_trace_len(padded_height, self.num_trace_randomizers);
1460        let coset_offset = BFieldElement::generator();
1461        let domain = ArithmeticDomain::of_length(fri_domain_length)?.with_offset(coset_offset);
1462
1463        Fri::new(
1464            domain,
1465            self.fri_expansion_factor,
1466            self.num_collinearity_checks,
1467        )
1468    }
1469
1470    /// Given `f(x)` (the in-domain evaluation of polynomial `f` in `x`), the domain point `x` at
1471    /// which polynomial `f` was evaluated, the out-of-domain evaluation `f(α)`, and the
1472    /// out-of-domain domain point `α`, apply the DEEP update: `(f(x) - f(α)) / (x - α)`.
1473    #[inline]
1474    fn deep_update(
1475        in_domain_point: BFieldElement,
1476        in_domain_value: XFieldElement,
1477        out_of_domain_point: XFieldElement,
1478        out_of_domain_value: XFieldElement,
1479    ) -> XFieldElement {
1480        (in_domain_value - out_of_domain_value) / (in_domain_point - out_of_domain_point)
1481    }
1482}
1483
1484impl Default for Stark {
1485    fn default() -> Self {
1486        let log_2_of_fri_expansion_factor = 2;
1487        let security_level = 160;
1488
1489        Self::new(security_level, log_2_of_fri_expansion_factor)
1490    }
1491}
1492
1493impl Default for Prover {
1494    fn default() -> Self {
1495        Self::new(Stark::default())
1496    }
1497}
1498
1499impl<'a> Arbitrary<'a> for Stark {
1500    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1501        let security_level = u.int_in_range(1..=640)?;
1502        let log_2_of_fri_expansion_factor = u.int_in_range(1..=8)?;
1503        Ok(Self::new(security_level, log_2_of_fri_expansion_factor))
1504    }
1505}
1506
1507/// Fiat-Shamir-sampled challenges to compress a row into a single
1508/// [extension field element][XFieldElement].
1509struct LinearCombinationWeights {
1510    /// of length [`MasterMainTable::NUM_COLUMNS`]
1511    main: Array1<XFieldElement>,
1512
1513    /// of length [`MasterAuxTable::NUM_COLUMNS`]
1514    aux: Array1<XFieldElement>,
1515
1516    /// of length [`NUM_QUOTIENT_SEGMENTS`]
1517    quot_segments: Array1<XFieldElement>,
1518
1519    /// of length [`NUM_DEEP_CODEWORD_COMPONENTS`]
1520    deep: Array1<XFieldElement>,
1521}
1522
1523impl LinearCombinationWeights {
1524    const NUM: usize = MasterMainTable::NUM_COLUMNS
1525        + MasterAuxTable::NUM_COLUMNS
1526        + NUM_QUOTIENT_SEGMENTS
1527        + NUM_DEEP_CODEWORD_COMPONENTS;
1528
1529    fn sample(proof_stream: &mut ProofStream) -> Self {
1530        const MAIN_END: usize = MasterMainTable::NUM_COLUMNS;
1531        const AUX_END: usize = MAIN_END + MasterAuxTable::NUM_COLUMNS;
1532        const QUOT_END: usize = AUX_END + NUM_QUOTIENT_SEGMENTS;
1533
1534        let weights = proof_stream.sample_scalars(Self::NUM);
1535
1536        Self {
1537            main: weights[..MAIN_END].to_vec().into(),
1538            aux: weights[MAIN_END..AUX_END].to_vec().into(),
1539            quot_segments: weights[AUX_END..QUOT_END].to_vec().into(),
1540            deep: weights[QUOT_END..].to_vec().into(),
1541        }
1542    }
1543
1544    fn main_and_aux(&self) -> Array1<XFieldElement> {
1545        let main = self.main.clone().into_iter();
1546        main.chain(self.aux.clone()).collect()
1547    }
1548}
1549
1550#[cfg(test)]
1551pub(crate) mod tests {
1552    use std::collections::HashMap;
1553    use std::collections::HashSet;
1554
1555    use air::challenge_id::ChallengeId::StandardInputIndeterminate;
1556    use air::challenge_id::ChallengeId::StandardOutputIndeterminate;
1557    use air::cross_table_argument::CrossTableArg;
1558    use air::cross_table_argument::EvalArg;
1559    use air::cross_table_argument::GrandCrossTableArg;
1560    use air::table::cascade::CascadeTable;
1561    use air::table::hash::HashTable;
1562    use air::table::jump_stack::JumpStackTable;
1563    use air::table::lookup::LookupTable;
1564    use air::table::op_stack::OpStackTable;
1565    use air::table::processor::ProcessorTable;
1566    use air::table::program::ProgramTable;
1567    use air::table::ram;
1568    use air::table::ram::RamTable;
1569    use air::table::u32::U32Table;
1570    use air::table::TableId;
1571    use air::table_column::MasterAuxColumn;
1572    use air::table_column::MasterMainColumn;
1573    use air::table_column::OpStackMainColumn;
1574    use air::table_column::ProcessorAuxColumn::InputTableEvalArg;
1575    use air::table_column::ProcessorAuxColumn::OutputTableEvalArg;
1576    use air::table_column::ProcessorMainColumn;
1577    use air::table_column::RamMainColumn;
1578    use air::AIR;
1579    use assert2::assert;
1580    use assert2::check;
1581    use assert2::let_assert;
1582    use constraint_circuit::ConstraintCircuitBuilder;
1583    use isa::error::OpStackError;
1584    use isa::instruction::Instruction;
1585    use isa::op_stack::OpStackElement;
1586    use itertools::izip;
1587    use proptest::collection::vec;
1588    use proptest::prelude::*;
1589    use proptest::test_runner::TestCaseResult;
1590    use proptest_arbitrary_interop::arb;
1591    use rand::prelude::*;
1592    use rand::Rng;
1593    use strum::EnumCount;
1594    use strum::IntoEnumIterator;
1595    use test_strategy::proptest;
1596    use twenty_first::math::other::random_elements;
1597
1598    use super::*;
1599    use crate::config::CacheDecision;
1600    use crate::error::InstructionError;
1601    use crate::shared_tests::construct_master_main_table;
1602    use crate::shared_tests::low_security_stark;
1603    use crate::shared_tests::prove_and_verify;
1604    use crate::shared_tests::ProgramAndInput;
1605    use crate::shared_tests::DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS;
1606    use crate::table::auxiliary_table;
1607    use crate::table::auxiliary_table::Evaluable;
1608    use crate::table::master_table::MasterAuxTable;
1609    use crate::triton_program;
1610    use crate::vm::tests::property_based_test_program_for_and;
1611    use crate::vm::tests::property_based_test_program_for_assert_vector;
1612    use crate::vm::tests::property_based_test_program_for_div_mod;
1613    use crate::vm::tests::property_based_test_program_for_eq;
1614    use crate::vm::tests::property_based_test_program_for_is_u32;
1615    use crate::vm::tests::property_based_test_program_for_log2floor;
1616    use crate::vm::tests::property_based_test_program_for_lsb;
1617    use crate::vm::tests::property_based_test_program_for_lt;
1618    use crate::vm::tests::property_based_test_program_for_pop_count;
1619    use crate::vm::tests::property_based_test_program_for_pow;
1620    use crate::vm::tests::property_based_test_program_for_random_ram_access;
1621    use crate::vm::tests::property_based_test_program_for_split;
1622    use crate::vm::tests::property_based_test_program_for_xb_dot_step;
1623    use crate::vm::tests::property_based_test_program_for_xor;
1624    use crate::vm::tests::property_based_test_program_for_xx_dot_step;
1625    use crate::vm::tests::test_program_0_lt_0;
1626    use crate::vm::tests::test_program_claim_in_ram_corresponds_to_currently_running_program;
1627    use crate::vm::tests::test_program_for_add_mul_invert;
1628    use crate::vm::tests::test_program_for_and;
1629    use crate::vm::tests::test_program_for_assert_vector;
1630    use crate::vm::tests::test_program_for_call_recurse_return;
1631    use crate::vm::tests::test_program_for_div_mod;
1632    use crate::vm::tests::test_program_for_divine;
1633    use crate::vm::tests::test_program_for_eq;
1634    use crate::vm::tests::test_program_for_halt;
1635    use crate::vm::tests::test_program_for_hash;
1636    use crate::vm::tests::test_program_for_log2floor;
1637    use crate::vm::tests::test_program_for_lsb;
1638    use crate::vm::tests::test_program_for_lt;
1639    use crate::vm::tests::test_program_for_many_sponge_instructions;
1640    use crate::vm::tests::test_program_for_merkle_step_left_sibling;
1641    use crate::vm::tests::test_program_for_merkle_step_mem_left_sibling;
1642    use crate::vm::tests::test_program_for_merkle_step_mem_right_sibling;
1643    use crate::vm::tests::test_program_for_merkle_step_right_sibling;
1644    use crate::vm::tests::test_program_for_pop_count;
1645    use crate::vm::tests::test_program_for_pow;
1646    use crate::vm::tests::test_program_for_push_pop_dup_swap_nop;
1647    use crate::vm::tests::test_program_for_read_io_write_io;
1648    use crate::vm::tests::test_program_for_recurse_or_return;
1649    use crate::vm::tests::test_program_for_skiz;
1650    use crate::vm::tests::test_program_for_split;
1651    use crate::vm::tests::test_program_for_sponge_instructions;
1652    use crate::vm::tests::test_program_for_sponge_instructions_2;
1653    use crate::vm::tests::test_program_for_starting_with_pop_count;
1654    use crate::vm::tests::test_program_for_write_mem_read_mem;
1655    use crate::vm::tests::test_program_for_x_invert;
1656    use crate::vm::tests::test_program_for_xb_mul;
1657    use crate::vm::tests::test_program_for_xor;
1658    use crate::vm::tests::test_program_for_xx_add;
1659    use crate::vm::tests::test_program_for_xx_mul;
1660    use crate::vm::tests::test_program_hash_nop_nop_lt;
1661    use crate::vm::tests::ProgramForMerkleTreeUpdate;
1662    use crate::vm::tests::ProgramForRecurseOrReturn;
1663    use crate::vm::tests::ProgramForSpongeAndHashInstructions;
1664    use crate::vm::NonDeterminism;
1665    use crate::vm::VM;
1666    use crate::PublicInput;
1667
1668    pub(crate) fn master_main_table_for_low_security_level(
1669        program_and_input: ProgramAndInput,
1670    ) -> (Stark, Claim, MasterMainTable) {
1671        let ProgramAndInput {
1672            program,
1673            public_input,
1674            non_determinism,
1675        } = program_and_input;
1676
1677        let claim = Claim::about_program(&program).with_input(public_input.clone());
1678        let (aet, stdout) = VM::trace_execution(program, public_input, non_determinism).unwrap();
1679        let claim = claim.with_output(stdout);
1680
1681        let stark = low_security_stark(DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS);
1682        let master_main_table = construct_master_main_table(stark, &aet);
1683
1684        (stark, claim, master_main_table)
1685    }
1686
1687    pub(crate) fn master_tables_for_low_security_level(
1688        program_and_input: ProgramAndInput,
1689    ) -> (Stark, Claim, MasterMainTable, MasterAuxTable, Challenges) {
1690        let (stark, claim, mut master_main_table) =
1691            master_main_table_for_low_security_level(program_and_input);
1692
1693        let challenges = Challenges::placeholder(&claim);
1694        master_main_table.pad();
1695        let master_aux_table = master_main_table.extend(&challenges);
1696
1697        (
1698            stark,
1699            claim,
1700            master_main_table,
1701            master_aux_table,
1702            challenges,
1703        )
1704    }
1705
1706    #[proptest]
1707    fn two_default_provers_have_different_randomness_seeds() {
1708        let seed = || Prover::default().randomness_seed;
1709        prop_assert_ne!(seed(), seed());
1710    }
1711
1712    #[test]
1713    fn quotient_segments_are_independent_of_fri_table_caching() {
1714        // ensure caching _can_ happen by overwriting environment variables
1715        crate::config::overwrite_lde_trace_caching_to(CacheDecision::Cache);
1716
1717        let mut rng = StdRng::seed_from_u64(1632525295622789151);
1718        let weights = rng.random::<[XFieldElement; MasterAuxTable::NUM_CONSTRAINTS]>();
1719
1720        let program = ProgramAndInput::new(triton_program!(halt));
1721        let (stark, _, mut main, mut aux, ch) = master_tables_for_low_security_level(program);
1722        let padded_height = main.trace_table().nrows();
1723        let fri_dom = stark.fri(padded_height).unwrap().domain;
1724        let max_degree = stark.max_degree(padded_height);
1725        let quot_dom = Prover::quotient_domain(fri_dom, max_degree).unwrap();
1726
1727        debug_assert!(main.fri_domain_table().is_none());
1728        debug_assert!(aux.fri_domain_table().is_none());
1729        let jit_segments =
1730            Prover::compute_quotient_segments(&mut main, &mut aux, quot_dom, &ch, &weights);
1731
1732        debug_assert!(main.fri_domain_table().is_none());
1733        main.maybe_low_degree_extend_all_columns();
1734        debug_assert!(main.fri_domain_table().is_some());
1735
1736        debug_assert!(aux.fri_domain_table().is_none());
1737        aux.maybe_low_degree_extend_all_columns();
1738        debug_assert!(aux.fri_domain_table().is_some());
1739
1740        let cache_segments =
1741            Prover::compute_quotient_segments(&mut main, &mut aux, quot_dom, &ch, &weights);
1742
1743        assert_eq!(jit_segments, cache_segments);
1744    }
1745
1746    /// [`Stark::compute_quotient_segments`] takes mutable references to both, the
1747    /// main and the auxiliary tables. It is vital that certain information is
1748    /// _not_ mutated.
1749    #[test]
1750    fn computing_quotient_segments_does_not_change_execution_trace() {
1751        fn assert_no_trace_mutation(cache_decision: CacheDecision) {
1752            crate::config::overwrite_lde_trace_caching_to(cache_decision);
1753
1754            let mut rng = StdRng::seed_from_u64(15157673430940347283);
1755            let weights = rng.random::<[XFieldElement; MasterAuxTable::NUM_CONSTRAINTS]>();
1756
1757            let program = ProgramAndInput::new(triton_program!(halt));
1758            let (stark, _, mut main, mut aux, ch) = master_tables_for_low_security_level(program);
1759
1760            let original_main_trace = main.trace_table().to_owned();
1761            let original_aux_trace = aux.trace_table().to_owned();
1762
1763            let padded_height = main.trace_table().nrows();
1764            let fri_dom = stark.fri(padded_height).unwrap().domain;
1765            let max_degree = stark.max_degree(padded_height);
1766            let quot_dom = Prover::quotient_domain(fri_dom, max_degree).unwrap();
1767
1768            if cache_decision == CacheDecision::Cache {
1769                main.maybe_low_degree_extend_all_columns();
1770                assert!(main.fri_domain_table().is_some());
1771
1772                aux.maybe_low_degree_extend_all_columns();
1773                assert!(aux.fri_domain_table().is_some());
1774            }
1775
1776            let _segments =
1777                Prover::compute_quotient_segments(&mut main, &mut aux, quot_dom, &ch, &weights);
1778
1779            assert_eq!(original_main_trace, main.trace_table());
1780            assert_eq!(original_aux_trace, aux.trace_table());
1781        }
1782
1783        assert_no_trace_mutation(CacheDecision::Cache);
1784        assert_no_trace_mutation(CacheDecision::NoCache);
1785    }
1786
1787    #[test]
1788    fn supplying_prover_randomness_seed_fully_derandomizes_produced_proof() {
1789        let ProgramAndInput {
1790            program,
1791            public_input,
1792            non_determinism,
1793        } = program_executing_every_instruction();
1794
1795        let claim = Claim::about_program(&program).with_input(public_input.clone());
1796        let (aet, output) = VM::trace_execution(program, public_input, non_determinism).unwrap();
1797        let claim = claim.with_output(output);
1798
1799        let stark = low_security_stark(DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS);
1800        let mut rng = StdRng::seed_from_u64(3351975627407608972);
1801        let proof = Prover::new(stark)
1802            .set_randomness_seed_which_may_break_zero_knowledge(rng.random())
1803            .prove(&claim, &aet)
1804            .unwrap();
1805
1806        insta::assert_snapshot!(
1807            Tip5::hash(&proof),
1808            @"17275651906185656762,\
1809              13250937299792022858,\
1810              05731754925513787901,\
1811              05512095638892086027,\
1812              08634562101877660478",
1813        );
1814    }
1815
1816    #[test]
1817    fn print_ram_table_example_for_specification() {
1818        let program = triton_program!(
1819            push 20 push 100 write_mem 1 pop 1  // write 20 to address 100
1820            push 5 push 6 push 7 push 8 push 9
1821            push 42 write_mem 5 pop 1           // write 5..=9 to addresses 42..=46
1822            push 42 read_mem 1 pop 2            // read from address 42
1823            push 45 read_mem 3 pop 4            // read from address 42..=44
1824            push 17 push 18 push 19
1825            push 43 write_mem 3 pop 1           // write 17..=19 to addresses 43..=45
1826            push 46 read_mem 5 pop 1 pop 5      // read from addresses 42..=46
1827            push 42 read_mem 1 pop 2            // read from address 42
1828            push 100 read_mem 1 pop 2           // read from address 100
1829            halt
1830        );
1831        let (_, _, master_main_table, _, _) =
1832            master_tables_for_low_security_level(ProgramAndInput::new(program));
1833
1834        println!();
1835        println!("Processor Table:\n");
1836        println!("| clk | ci  | nia | st0 | st1 | st2 | st3 | st4 | st5 |");
1837        println!("|----:|:----|:----|----:|----:|----:|----:|----:|----:|");
1838        for row in master_main_table
1839            .table(TableId::Processor)
1840            .rows()
1841            .into_iter()
1842            .take(40)
1843        {
1844            let clk = row[ProcessorMainColumn::CLK.main_index()].to_string();
1845            let st0 = row[ProcessorMainColumn::ST0.main_index()].to_string();
1846            let st1 = row[ProcessorMainColumn::ST1.main_index()].to_string();
1847            let st2 = row[ProcessorMainColumn::ST2.main_index()].to_string();
1848            let st3 = row[ProcessorMainColumn::ST3.main_index()].to_string();
1849            let st4 = row[ProcessorMainColumn::ST4.main_index()].to_string();
1850            let st5 = row[ProcessorMainColumn::ST5.main_index()].to_string();
1851
1852            let (ci, nia) = ci_and_nia_from_master_table_row(row);
1853
1854            let interesting_cols = [clk, ci, nia, st0, st1, st2, st3, st4, st5];
1855            let interesting_cols = interesting_cols
1856                .iter()
1857                .map(|ff| format!("{:>10}", format!("{ff}")))
1858                .join(" | ");
1859            println!("| {interesting_cols} |");
1860        }
1861        println!();
1862        println!("RAM Table:\n");
1863        println!("| clk | type | pointer | value | iord |");
1864        println!("|----:|:-----|--------:|------:|-----:|");
1865        for row in master_main_table
1866            .table(TableId::Ram)
1867            .rows()
1868            .into_iter()
1869            .take(25)
1870        {
1871            let clk = row[RamMainColumn::CLK.main_index()].to_string();
1872            let ramp = row[RamMainColumn::RamPointer.main_index()].to_string();
1873            let ramv = row[RamMainColumn::RamValue.main_index()].to_string();
1874            let iord = row[RamMainColumn::InverseOfRampDifference.main_index()].to_string();
1875
1876            let instruction_type = match row[RamMainColumn::InstructionType.main_index()] {
1877                ram::INSTRUCTION_TYPE_READ => "read",
1878                ram::INSTRUCTION_TYPE_WRITE => "write",
1879                ram::PADDING_INDICATOR => "pad",
1880                _ => "-",
1881            }
1882            .to_string();
1883
1884            let interesting_cols = [clk, instruction_type, ramp, ramv, iord];
1885            let interesting_cols = interesting_cols
1886                .iter()
1887                .map(|ff| format!("{:>10}", format!("{ff}")))
1888                .join(" | ");
1889            println!("| {interesting_cols} |");
1890        }
1891    }
1892
1893    #[test]
1894    fn print_op_stack_table_example_for_specification() {
1895        let num_interesting_rows = 30;
1896        let fake_op_stack_size = 4;
1897
1898        let program = triton_program! {
1899            push 42 push 43 push 44 push 45 push 46 push 47 push 48
1900            nop pop 1 pop 2 pop 1
1901            push 77 swap 3 push 78 swap 3 push 79
1902            pop 1 pop 2 pop 3
1903            halt
1904        };
1905        let (_, _, master_main_table) =
1906            master_main_table_for_low_security_level(ProgramAndInput::new(program));
1907
1908        println!();
1909        println!("Processor Table:");
1910        println!("| clk | ci  | nia | st0 | st1 | st2 | st3 | underflow | pointer |");
1911        println!("|----:|:----|----:|----:|----:|----:|----:|:----------|--------:|");
1912        for row in master_main_table
1913            .table(TableId::Processor)
1914            .rows()
1915            .into_iter()
1916            .take(num_interesting_rows)
1917        {
1918            let clk = row[ProcessorMainColumn::CLK.main_index()].to_string();
1919            let st0 = row[ProcessorMainColumn::ST0.main_index()].to_string();
1920            let st1 = row[ProcessorMainColumn::ST1.main_index()].to_string();
1921            let st2 = row[ProcessorMainColumn::ST2.main_index()].to_string();
1922            let st3 = row[ProcessorMainColumn::ST3.main_index()].to_string();
1923            let st4 = row[ProcessorMainColumn::ST4.main_index()].to_string();
1924            let st5 = row[ProcessorMainColumn::ST5.main_index()].to_string();
1925            let st6 = row[ProcessorMainColumn::ST6.main_index()].to_string();
1926            let st7 = row[ProcessorMainColumn::ST7.main_index()].to_string();
1927            let st8 = row[ProcessorMainColumn::ST8.main_index()].to_string();
1928            let st9 = row[ProcessorMainColumn::ST9.main_index()].to_string();
1929
1930            let osp = row[ProcessorMainColumn::OpStackPointer.main_index()];
1931            let osp =
1932                (osp.value() + fake_op_stack_size).saturating_sub(OpStackElement::COUNT as u64);
1933
1934            let underflow_size = osp.saturating_sub(fake_op_stack_size);
1935            let underflow_candidates = [st4, st5, st6, st7, st8, st9];
1936            let underflow = underflow_candidates
1937                .into_iter()
1938                .take(underflow_size as usize);
1939            let underflow = underflow.map(|ff| format!("{:>2}", format!("{ff}")));
1940            let underflow = format!("[{}]", underflow.collect_vec().join(", "));
1941
1942            let osp = osp.to_string();
1943            let (ci, nia) = ci_and_nia_from_master_table_row(row);
1944
1945            let interesting_cols = [clk, ci, nia, st0, st1, st2, st3, underflow, osp];
1946            let interesting_cols = interesting_cols
1947                .map(|ff| format!("{:>10}", format!("{ff}")))
1948                .join(" | ");
1949            println!("| {interesting_cols} |");
1950        }
1951
1952        println!();
1953        println!("Op Stack Table:");
1954        println!("| clk | ib1 | pointer | value |");
1955        println!("|----:|----:|--------:|------:|");
1956        for row in master_main_table
1957            .table(TableId::OpStack)
1958            .rows()
1959            .into_iter()
1960            .take(num_interesting_rows)
1961        {
1962            let clk = row[OpStackMainColumn::CLK.main_index()].to_string();
1963            let ib1 = row[OpStackMainColumn::IB1ShrinkStack.main_index()].to_string();
1964
1965            let osp = row[OpStackMainColumn::StackPointer.main_index()];
1966            let osp =
1967                (osp.value() + fake_op_stack_size).saturating_sub(OpStackElement::COUNT as u64);
1968            let osp = osp.to_string();
1969
1970            let value = row[OpStackMainColumn::FirstUnderflowElement.main_index()].to_string();
1971
1972            let interesting_cols = [clk, ib1, osp, value];
1973            let interesting_cols = interesting_cols
1974                .map(|ff| format!("{:>10}", format!("{ff}")))
1975                .join(" | ");
1976            println!("| {interesting_cols} |");
1977        }
1978    }
1979
1980    fn ci_and_nia_from_master_table_row(row: ArrayView1<BFieldElement>) -> (String, String) {
1981        let curr_instruction = row[ProcessorMainColumn::CI.main_index()].value();
1982        let next_instruction_or_arg = row[ProcessorMainColumn::NIA.main_index()].value();
1983
1984        let curr_instruction = Instruction::try_from(curr_instruction).unwrap();
1985        let nia = curr_instruction
1986            .arg()
1987            .map(|_| next_instruction_or_arg.to_string())
1988            .unwrap_or_default();
1989        (curr_instruction.name().to_string(), nia)
1990    }
1991
1992    /// To be used with `-- --nocapture`. Has mainly informative purpose.
1993    #[test]
1994    fn print_all_constraint_degrees() {
1995        let padded_height = 2;
1996        let num_trace_randomizers = 2;
1997        let interpolant_degree = interpolant_degree(padded_height, num_trace_randomizers);
1998        for deg in auxiliary_table::all_degrees_with_origin(interpolant_degree, padded_height) {
1999            println!("{deg}");
2000        }
2001    }
2002
2003    #[test]
2004    fn check_io_terminals() {
2005        let read_nop_program = triton_program!(
2006            read_io 3 nop nop write_io 2 push 17 write_io 1 halt
2007        );
2008        let mut program_and_input = ProgramAndInput::new(read_nop_program);
2009        program_and_input.public_input = PublicInput::from([3, 5, 7].map(|b| bfe!(b)));
2010        let (_, claim, _, master_aux_table, all_challenges) =
2011            master_tables_for_low_security_level(program_and_input);
2012
2013        let processor_table = master_aux_table.table(TableId::Processor);
2014        let processor_table_last_row = processor_table.slice(s![-1, ..]);
2015        let ptie = processor_table_last_row[InputTableEvalArg.aux_index()];
2016        let ine = EvalArg::compute_terminal(
2017            &claim.input,
2018            EvalArg::default_initial(),
2019            all_challenges[StandardInputIndeterminate],
2020        );
2021        check!(ptie == ine);
2022
2023        let ptoe = processor_table_last_row[OutputTableEvalArg.aux_index()];
2024        let oute = EvalArg::compute_terminal(
2025            &claim.output,
2026            EvalArg::default_initial(),
2027            all_challenges[StandardOutputIndeterminate],
2028        );
2029        check!(ptoe == oute);
2030    }
2031
2032    #[test]
2033    fn constraint_polynomials_use_right_number_of_variables() {
2034        let challenges = Challenges::default();
2035        let main_row = Array1::<BFieldElement>::zeros(MasterMainTable::NUM_COLUMNS);
2036        let aux_row = Array1::zeros(MasterAuxTable::NUM_COLUMNS);
2037
2038        let br = main_row.view();
2039        let er = aux_row.view();
2040
2041        MasterAuxTable::evaluate_initial_constraints(br, er, &challenges);
2042        MasterAuxTable::evaluate_consistency_constraints(br, er, &challenges);
2043        MasterAuxTable::evaluate_transition_constraints(br, er, br, er, &challenges);
2044        MasterAuxTable::evaluate_terminal_constraints(br, er, &challenges);
2045    }
2046
2047    #[test]
2048    fn print_number_of_all_constraints_per_table() {
2049        let table_names = [
2050            "program table",
2051            "processor table",
2052            "op stack table",
2053            "ram table",
2054            "jump stack table",
2055            "hash table",
2056            "cascade table",
2057            "lookup table",
2058            "u32 table",
2059            "cross-table arg",
2060        ];
2061        let circuit_builder = ConstraintCircuitBuilder::new();
2062        let all_init = [
2063            ProgramTable::initial_constraints(&circuit_builder),
2064            ProcessorTable::initial_constraints(&circuit_builder),
2065            OpStackTable::initial_constraints(&circuit_builder),
2066            RamTable::initial_constraints(&circuit_builder),
2067            JumpStackTable::initial_constraints(&circuit_builder),
2068            HashTable::initial_constraints(&circuit_builder),
2069            CascadeTable::initial_constraints(&circuit_builder),
2070            LookupTable::initial_constraints(&circuit_builder),
2071            U32Table::initial_constraints(&circuit_builder),
2072            GrandCrossTableArg::initial_constraints(&circuit_builder),
2073        ]
2074        .map(|vec| vec.len());
2075        let circuit_builder = ConstraintCircuitBuilder::new();
2076        let all_cons = [
2077            ProgramTable::consistency_constraints(&circuit_builder),
2078            ProcessorTable::consistency_constraints(&circuit_builder),
2079            OpStackTable::consistency_constraints(&circuit_builder),
2080            RamTable::consistency_constraints(&circuit_builder),
2081            JumpStackTable::consistency_constraints(&circuit_builder),
2082            HashTable::consistency_constraints(&circuit_builder),
2083            CascadeTable::consistency_constraints(&circuit_builder),
2084            LookupTable::consistency_constraints(&circuit_builder),
2085            U32Table::consistency_constraints(&circuit_builder),
2086            GrandCrossTableArg::consistency_constraints(&circuit_builder),
2087        ]
2088        .map(|vec| vec.len());
2089        let circuit_builder = ConstraintCircuitBuilder::new();
2090        let all_trans = [
2091            ProgramTable::transition_constraints(&circuit_builder),
2092            ProcessorTable::transition_constraints(&circuit_builder),
2093            OpStackTable::transition_constraints(&circuit_builder),
2094            RamTable::transition_constraints(&circuit_builder),
2095            JumpStackTable::transition_constraints(&circuit_builder),
2096            HashTable::transition_constraints(&circuit_builder),
2097            CascadeTable::transition_constraints(&circuit_builder),
2098            LookupTable::transition_constraints(&circuit_builder),
2099            U32Table::transition_constraints(&circuit_builder),
2100            GrandCrossTableArg::transition_constraints(&circuit_builder),
2101        ]
2102        .map(|vec| vec.len());
2103        let circuit_builder = ConstraintCircuitBuilder::new();
2104        let all_term = [
2105            ProgramTable::terminal_constraints(&circuit_builder),
2106            ProcessorTable::terminal_constraints(&circuit_builder),
2107            OpStackTable::terminal_constraints(&circuit_builder),
2108            RamTable::terminal_constraints(&circuit_builder),
2109            JumpStackTable::terminal_constraints(&circuit_builder),
2110            HashTable::terminal_constraints(&circuit_builder),
2111            CascadeTable::terminal_constraints(&circuit_builder),
2112            LookupTable::terminal_constraints(&circuit_builder),
2113            U32Table::terminal_constraints(&circuit_builder),
2114            GrandCrossTableArg::terminal_constraints(&circuit_builder),
2115        ]
2116        .map(|vec| vec.len());
2117
2118        let num_total_init: usize = all_init.iter().sum();
2119        let num_total_cons: usize = all_cons.iter().sum();
2120        let num_total_trans: usize = all_trans.iter().sum();
2121        let num_total_term: usize = all_term.iter().sum();
2122        let num_total = num_total_init + num_total_cons + num_total_trans + num_total_term;
2123
2124        println!();
2125        println!("| Table                |  Init |  Cons | Trans |  Term |   Sum |");
2126        println!("|:---------------------|------:|------:|------:|------:|------:|");
2127        for (name, num_init, num_cons, num_trans, num_term) in
2128            izip!(table_names, all_init, all_cons, all_trans, all_term)
2129        {
2130            let num_total = num_init + num_cons + num_trans + num_term;
2131            println!(
2132                "| {name:<20} | {num_init:>5} | {num_cons:>5} \
2133                 | {num_trans:>5} | {num_term:>5} | {num_total:>5} |",
2134            );
2135        }
2136        println!(
2137            "| {:<20} | {num_total_init:>5} | {num_total_cons:>5} \
2138             | {num_total_trans:>5} | {num_total_term:>5} | {num_total:>5} |",
2139            "Sum",
2140        );
2141    }
2142
2143    #[test]
2144    fn number_of_quotient_degree_bounds_match_number_of_constraints() {
2145        let main_row = Array1::<BFieldElement>::zeros(MasterMainTable::NUM_COLUMNS);
2146        let aux_row = Array1::zeros(MasterAuxTable::NUM_COLUMNS);
2147        let ch = Challenges::default();
2148        let padded_height = 2;
2149        let num_trace_randomizers = 2;
2150        let interpolant_degree = interpolant_degree(padded_height, num_trace_randomizers);
2151
2152        // Shorten some names for better formatting. This is just a test.
2153        let ph = padded_height;
2154        let id = interpolant_degree;
2155        let br = main_row.view();
2156        let er = aux_row.view();
2157
2158        let num_init_quots = MasterAuxTable::NUM_INITIAL_CONSTRAINTS;
2159        let num_cons_quots = MasterAuxTable::NUM_CONSISTENCY_CONSTRAINTS;
2160        let num_tran_quots = MasterAuxTable::NUM_TRANSITION_CONSTRAINTS;
2161        let num_term_quots = MasterAuxTable::NUM_TERMINAL_CONSTRAINTS;
2162
2163        let eval_init_consts = MasterAuxTable::evaluate_initial_constraints(br, er, &ch);
2164        let eval_cons_consts = MasterAuxTable::evaluate_consistency_constraints(br, er, &ch);
2165        let eval_tran_consts = MasterAuxTable::evaluate_transition_constraints(br, er, br, er, &ch);
2166        let eval_term_consts = MasterAuxTable::evaluate_terminal_constraints(br, er, &ch);
2167
2168        assert!(num_init_quots == eval_init_consts.len());
2169        assert!(num_cons_quots == eval_cons_consts.len());
2170        assert!(num_tran_quots == eval_tran_consts.len());
2171        assert!(num_term_quots == eval_term_consts.len());
2172
2173        assert!(num_init_quots == MasterAuxTable::initial_quotient_degree_bounds(id).len());
2174        assert!(num_cons_quots == MasterAuxTable::consistency_quotient_degree_bounds(id, ph).len());
2175        assert!(num_tran_quots == MasterAuxTable::transition_quotient_degree_bounds(id, ph).len());
2176        assert!(num_term_quots == MasterAuxTable::terminal_quotient_degree_bounds(id).len());
2177    }
2178
2179    #[test]
2180    fn constraints_evaluate_to_zero_on_fibonacci() {
2181        let source_code_and_input =
2182            ProgramAndInput::new(crate::example_programs::FIBONACCI_SEQUENCE.clone())
2183                .with_input(bfe_array![100]);
2184        triton_constraints_evaluate_to_zero(source_code_and_input);
2185    }
2186
2187    #[test]
2188    fn constraints_evaluate_to_zero_on_big_mmr_snippet() {
2189        let source_code_and_input = ProgramAndInput::new(
2190            crate::example_programs::CALCULATE_NEW_MMR_PEAKS_FROM_APPEND_WITH_SAFE_LISTS.clone(),
2191        );
2192        triton_constraints_evaluate_to_zero(source_code_and_input);
2193    }
2194
2195    #[test]
2196    fn constraints_evaluate_to_zero_on_program_for_halt() {
2197        triton_constraints_evaluate_to_zero(test_program_for_halt())
2198    }
2199
2200    #[test]
2201    fn constraints_evaluate_to_zero_on_program_hash_nop_nop_lt() {
2202        triton_constraints_evaluate_to_zero(test_program_hash_nop_nop_lt())
2203    }
2204
2205    #[test]
2206    fn constraints_evaluate_to_zero_on_program_for_push_pop_dup_swap_nop() {
2207        triton_constraints_evaluate_to_zero(test_program_for_push_pop_dup_swap_nop())
2208    }
2209
2210    #[test]
2211    fn constraints_evaluate_to_zero_on_program_for_divine() {
2212        triton_constraints_evaluate_to_zero(test_program_for_divine())
2213    }
2214
2215    #[test]
2216    fn constraints_evaluate_to_zero_on_program_for_skiz() {
2217        triton_constraints_evaluate_to_zero(test_program_for_skiz())
2218    }
2219
2220    #[test]
2221    fn constraints_evaluate_to_zero_on_program_for_call_recurse_return() {
2222        triton_constraints_evaluate_to_zero(test_program_for_call_recurse_return())
2223    }
2224
2225    #[test]
2226    fn constraints_evaluate_to_zero_on_program_for_recurse_or_return() {
2227        triton_constraints_evaluate_to_zero(test_program_for_recurse_or_return())
2228    }
2229
2230    #[proptest(cases = 20)]
2231    fn constraints_evaluate_to_zero_on_property_based_test_program_for_recurse_or_return(
2232        program: ProgramForRecurseOrReturn,
2233    ) {
2234        triton_constraints_evaluate_to_zero(program.assemble())
2235    }
2236
2237    #[test]
2238    fn constraints_evaluate_to_zero_on_program_for_write_mem_read_mem() {
2239        triton_constraints_evaluate_to_zero(test_program_for_write_mem_read_mem())
2240    }
2241
2242    #[test]
2243    fn constraints_evaluate_to_zero_on_program_for_hash() {
2244        triton_constraints_evaluate_to_zero(test_program_for_hash())
2245    }
2246
2247    #[test]
2248    fn constraints_evaluate_to_zero_on_program_for_merkle_step_right_sibling() {
2249        triton_constraints_evaluate_to_zero(test_program_for_merkle_step_right_sibling())
2250    }
2251
2252    #[test]
2253    fn constraints_evaluate_to_zero_on_program_for_merkle_step_left_sibling() {
2254        triton_constraints_evaluate_to_zero(test_program_for_merkle_step_left_sibling())
2255    }
2256
2257    #[test]
2258    fn constraints_evaluate_to_zero_on_program_for_merkle_step_mem_right_sibling() {
2259        triton_constraints_evaluate_to_zero(test_program_for_merkle_step_mem_right_sibling())
2260    }
2261
2262    #[test]
2263    fn constraints_evaluate_to_zero_on_program_for_merkle_step_mem_left_sibling() {
2264        triton_constraints_evaluate_to_zero(test_program_for_merkle_step_mem_left_sibling())
2265    }
2266
2267    #[proptest(cases = 20)]
2268    fn constraints_evaluate_to_zero_on_property_based_test_program_for_merkle_tree_update(
2269        program: ProgramForMerkleTreeUpdate,
2270    ) {
2271        triton_constraints_evaluate_to_zero(program.assemble())
2272    }
2273
2274    #[test]
2275    fn constraints_evaluate_to_zero_on_program_for_assert_vector() {
2276        triton_constraints_evaluate_to_zero(test_program_for_assert_vector())
2277    }
2278
2279    #[test]
2280    fn constraints_evaluate_to_zero_on_program_for_sponge_instructions() {
2281        triton_constraints_evaluate_to_zero(test_program_for_sponge_instructions())
2282    }
2283
2284    #[test]
2285    fn constraints_evaluate_to_zero_on_program_for_sponge_instructions_2() {
2286        triton_constraints_evaluate_to_zero(test_program_for_sponge_instructions_2())
2287    }
2288
2289    #[test]
2290    fn constraints_evaluate_to_zero_on_program_for_many_sponge_instructions() {
2291        triton_constraints_evaluate_to_zero(test_program_for_many_sponge_instructions())
2292    }
2293
2294    #[test]
2295    fn constraints_evaluate_to_zero_on_program_for_add_mul_invert() {
2296        triton_constraints_evaluate_to_zero(test_program_for_add_mul_invert())
2297    }
2298
2299    #[test]
2300    fn constraints_evaluate_to_zero_on_program_for_eq() {
2301        triton_constraints_evaluate_to_zero(test_program_for_eq())
2302    }
2303
2304    #[test]
2305    fn constraints_evaluate_to_zero_on_program_for_lsb() {
2306        triton_constraints_evaluate_to_zero(test_program_for_lsb())
2307    }
2308
2309    #[test]
2310    fn constraints_evaluate_to_zero_on_program_for_split() {
2311        triton_constraints_evaluate_to_zero(test_program_for_split())
2312    }
2313
2314    #[test]
2315    fn constraints_evaluate_to_zero_on_program_0_lt_0() {
2316        triton_constraints_evaluate_to_zero(test_program_0_lt_0())
2317    }
2318
2319    #[test]
2320    fn constraints_evaluate_to_zero_on_program_for_lt() {
2321        triton_constraints_evaluate_to_zero(test_program_for_lt())
2322    }
2323
2324    #[test]
2325    fn constraints_evaluate_to_zero_on_program_for_and() {
2326        triton_constraints_evaluate_to_zero(test_program_for_and())
2327    }
2328
2329    #[test]
2330    fn constraints_evaluate_to_zero_on_program_for_xor() {
2331        triton_constraints_evaluate_to_zero(test_program_for_xor())
2332    }
2333
2334    #[test]
2335    fn constraints_evaluate_to_zero_on_program_for_log2floor() {
2336        triton_constraints_evaluate_to_zero(test_program_for_log2floor())
2337    }
2338
2339    #[test]
2340    fn constraints_evaluate_to_zero_on_program_for_pow() {
2341        triton_constraints_evaluate_to_zero(test_program_for_pow())
2342    }
2343
2344    #[test]
2345    fn constraints_evaluate_to_zero_on_program_for_div_mod() {
2346        triton_constraints_evaluate_to_zero(test_program_for_div_mod())
2347    }
2348
2349    #[test]
2350    fn constraints_evaluate_to_zero_on_program_for_starting_with_pop_count() {
2351        triton_constraints_evaluate_to_zero(test_program_for_starting_with_pop_count())
2352    }
2353
2354    #[test]
2355    fn constraints_evaluate_to_zero_on_program_for_pop_count() {
2356        triton_constraints_evaluate_to_zero(test_program_for_pop_count())
2357    }
2358
2359    #[test]
2360    fn constraints_evaluate_to_zero_on_program_for_xx_add() {
2361        triton_constraints_evaluate_to_zero(test_program_for_xx_add())
2362    }
2363
2364    #[test]
2365    fn constraints_evaluate_to_zero_on_program_for_xx_mul() {
2366        triton_constraints_evaluate_to_zero(test_program_for_xx_mul())
2367    }
2368
2369    #[test]
2370    fn constraints_evaluate_to_zero_on_program_for_x_invert() {
2371        triton_constraints_evaluate_to_zero(test_program_for_x_invert())
2372    }
2373
2374    #[test]
2375    fn constraints_evaluate_to_zero_on_program_for_xb_mul() {
2376        triton_constraints_evaluate_to_zero(test_program_for_xb_mul())
2377    }
2378
2379    #[test]
2380    fn constraints_evaluate_to_zero_on_program_for_read_io_write_io() {
2381        triton_constraints_evaluate_to_zero(test_program_for_read_io_write_io())
2382    }
2383
2384    #[test]
2385    fn constraints_evaluate_to_zero_on_property_based_test_program_for_assert_vector() {
2386        triton_constraints_evaluate_to_zero(property_based_test_program_for_assert_vector())
2387    }
2388
2389    #[test]
2390    fn constraints_evaluate_to_zero_on_single_sponge_absorb_mem_instructions() {
2391        let program = triton_program!(sponge_init sponge_absorb_mem halt);
2392        let program = ProgramAndInput::new(program);
2393        triton_constraints_evaluate_to_zero(program);
2394    }
2395
2396    #[proptest(cases = 3)]
2397    fn constraints_evaluate_to_zero_on_property_based_test_program_for_sponge_instructions(
2398        program: ProgramForSpongeAndHashInstructions,
2399    ) {
2400        triton_constraints_evaluate_to_zero(program.assemble());
2401    }
2402
2403    #[test]
2404    fn constraints_evaluate_to_zero_on_property_based_test_program_for_split() {
2405        triton_constraints_evaluate_to_zero(property_based_test_program_for_split())
2406    }
2407
2408    #[test]
2409    fn constraints_evaluate_to_zero_on_property_based_test_program_for_eq() {
2410        triton_constraints_evaluate_to_zero(property_based_test_program_for_eq())
2411    }
2412
2413    #[test]
2414    fn constraints_evaluate_to_zero_on_property_based_test_program_for_lsb() {
2415        triton_constraints_evaluate_to_zero(property_based_test_program_for_lsb())
2416    }
2417
2418    #[test]
2419    fn constraints_evaluate_to_zero_on_property_based_test_program_for_lt() {
2420        triton_constraints_evaluate_to_zero(property_based_test_program_for_lt())
2421    }
2422
2423    #[test]
2424    fn constraints_evaluate_to_zero_on_property_based_test_program_for_and() {
2425        triton_constraints_evaluate_to_zero(property_based_test_program_for_and())
2426    }
2427
2428    #[test]
2429    fn constraints_evaluate_to_zero_on_property_based_test_program_for_xor() {
2430        triton_constraints_evaluate_to_zero(property_based_test_program_for_xor())
2431    }
2432
2433    #[test]
2434    fn constraints_evaluate_to_zero_on_property_based_test_program_for_log2floor() {
2435        triton_constraints_evaluate_to_zero(property_based_test_program_for_log2floor())
2436    }
2437
2438    #[test]
2439    fn constraints_evaluate_to_zero_on_property_based_test_program_for_pow() {
2440        triton_constraints_evaluate_to_zero(property_based_test_program_for_pow())
2441    }
2442
2443    #[test]
2444    fn constraints_evaluate_to_zero_on_property_based_test_program_for_div_mod() {
2445        triton_constraints_evaluate_to_zero(property_based_test_program_for_div_mod())
2446    }
2447
2448    #[test]
2449    fn constraints_evaluate_to_zero_on_property_based_test_program_for_pop_count() {
2450        triton_constraints_evaluate_to_zero(property_based_test_program_for_pop_count())
2451    }
2452
2453    #[test]
2454    fn constraints_evaluate_to_zero_on_property_based_test_program_for_is_u32() {
2455        triton_constraints_evaluate_to_zero(property_based_test_program_for_is_u32())
2456    }
2457
2458    #[test]
2459    fn constraints_evaluate_to_zero_on_property_based_test_program_for_random_ram_access() {
2460        triton_constraints_evaluate_to_zero(property_based_test_program_for_random_ram_access())
2461    }
2462
2463    #[test]
2464    fn constraints_evaluate_to_zero_on_property_based_test_program_for_xx_dot_step() {
2465        triton_constraints_evaluate_to_zero(property_based_test_program_for_xx_dot_step());
2466    }
2467
2468    #[test]
2469    fn constraints_evaluate_to_zero_on_property_based_test_program_for_xb_dot_step() {
2470        triton_constraints_evaluate_to_zero(property_based_test_program_for_xb_dot_step());
2471    }
2472
2473    #[test]
2474    fn can_read_twice_from_same_ram_address_within_one_cycle() {
2475        for i in 0..x_field_element::EXTENSION_DEGREE {
2476            // This program reads from the same address twice, even if the stack
2477            // is not well-initialized.
2478            let program = triton_program! {
2479                dup 0
2480                addi {i}
2481                xx_dot_step
2482                halt
2483            };
2484            let program_and_input = ProgramAndInput::new(program);
2485            debug_assert!(program_and_input.clone().run().is_ok());
2486            triton_constraints_evaluate_to_zero(program_and_input);
2487        }
2488    }
2489
2490    #[test]
2491    fn claim_in_ram_corresponds_to_currently_running_program() {
2492        triton_constraints_evaluate_to_zero(
2493            test_program_claim_in_ram_corresponds_to_currently_running_program(),
2494        );
2495    }
2496
2497    macro_rules! check_constraints_fn {
2498        (fn $fn_name:ident for $table:ident) => {
2499            fn $fn_name(
2500                master_main_trace_table: ArrayView2<BFieldElement>,
2501                master_aux_trace_table: ArrayView2<XFieldElement>,
2502                challenges: &Challenges,
2503            ) {
2504                assert!(master_main_trace_table.nrows() == master_aux_trace_table.nrows());
2505                let challenges = &challenges.challenges;
2506
2507                let builder = ConstraintCircuitBuilder::new();
2508                for (constraint_idx, constraint) in $table::initial_constraints(&builder)
2509                    .into_iter()
2510                    .map(|constraint_monad| constraint_monad.consume())
2511                    .enumerate()
2512                {
2513                    let evaluated_constraint = constraint.evaluate(
2514                        master_main_trace_table.slice(s![..1, ..]),
2515                        master_aux_trace_table.slice(s![..1, ..]),
2516                        challenges,
2517                    );
2518                    check!(
2519                        xfe!(0) == evaluated_constraint,
2520                        "{}: Initial constraint {constraint_idx} failed.",
2521                        stringify!($table),
2522                    );
2523                }
2524
2525                let builder = ConstraintCircuitBuilder::new();
2526                for (constraint_idx, constraint) in $table::consistency_constraints(&builder)
2527                    .into_iter()
2528                    .map(|constraint_monad| constraint_monad.consume())
2529                    .enumerate()
2530                {
2531                    for row_idx in 0..master_main_trace_table.nrows() {
2532                        let evaluated_constraint = constraint.evaluate(
2533                            master_main_trace_table.slice(s![row_idx..=row_idx, ..]),
2534                            master_aux_trace_table.slice(s![row_idx..=row_idx, ..]),
2535                            challenges,
2536                        );
2537                        check!(
2538                            xfe!(0) == evaluated_constraint,
2539                            "{}: Consistency constraint {constraint_idx} failed on row {row_idx}.",
2540                            stringify!($table),
2541                        );
2542                    }
2543                }
2544
2545                let builder = ConstraintCircuitBuilder::new();
2546                for (constraint_idx, constraint) in $table::transition_constraints(&builder)
2547                    .into_iter()
2548                    .map(|constraint_monad| constraint_monad.consume())
2549                    .enumerate()
2550                {
2551                    for row_idx in 0..master_main_trace_table.nrows() - 1 {
2552                        let evaluated_constraint = constraint.evaluate(
2553                            master_main_trace_table.slice(s![row_idx..=row_idx + 1, ..]),
2554                            master_aux_trace_table.slice(s![row_idx..=row_idx + 1, ..]),
2555                            challenges,
2556                        );
2557                        check!(
2558                            xfe!(0) == evaluated_constraint,
2559                            "{}: Transition constraint {constraint_idx} failed on row {row_idx}.",
2560                            stringify!($table),
2561                        );
2562                    }
2563                }
2564
2565                let builder = ConstraintCircuitBuilder::new();
2566                for (constraint_idx, constraint) in $table::terminal_constraints(&builder)
2567                    .into_iter()
2568                    .map(|constraint_monad| constraint_monad.consume())
2569                    .enumerate()
2570                {
2571                    let evaluated_constraint = constraint.evaluate(
2572                        master_main_trace_table.slice(s![-1.., ..]),
2573                        master_aux_trace_table.slice(s![-1.., ..]),
2574                        challenges,
2575                    );
2576                    check!(
2577                        xfe!(0) == evaluated_constraint,
2578                        "{}: Terminal constraint {constraint_idx} failed.",
2579                        stringify!($table),
2580                    );
2581                }
2582            }
2583        };
2584    }
2585
2586    check_constraints_fn!(fn check_program_table_constraints for ProgramTable);
2587    check_constraints_fn!(fn check_processor_table_constraints for ProcessorTable);
2588    check_constraints_fn!(fn check_op_stack_table_constraints for OpStackTable);
2589    check_constraints_fn!(fn check_ram_table_constraints for RamTable);
2590    check_constraints_fn!(fn check_jump_stack_table_constraints for JumpStackTable);
2591    check_constraints_fn!(fn check_hash_table_constraints for HashTable);
2592    check_constraints_fn!(fn check_cascade_table_constraints for CascadeTable);
2593    check_constraints_fn!(fn check_lookup_table_constraints for LookupTable);
2594    check_constraints_fn!(fn check_u32_table_constraints for U32Table);
2595    check_constraints_fn!(fn check_cross_table_constraints for GrandCrossTableArg);
2596
2597    fn triton_constraints_evaluate_to_zero(program_and_input: ProgramAndInput) {
2598        let (_, _, master_main_table, master_aux_table, challenges) =
2599            master_tables_for_low_security_level(program_and_input);
2600
2601        let mbt = master_main_table.trace_table();
2602        let met = master_aux_table.trace_table();
2603        assert!(mbt.nrows() == met.nrows());
2604
2605        check_program_table_constraints(mbt, met, &challenges);
2606        check_processor_table_constraints(mbt, met, &challenges);
2607        check_op_stack_table_constraints(mbt, met, &challenges);
2608        check_ram_table_constraints(mbt, met, &challenges);
2609        check_jump_stack_table_constraints(mbt, met, &challenges);
2610        check_hash_table_constraints(mbt, met, &challenges);
2611        check_cascade_table_constraints(mbt, met, &challenges);
2612        check_lookup_table_constraints(mbt, met, &challenges);
2613        check_u32_table_constraints(mbt, met, &challenges);
2614        check_cross_table_constraints(mbt, met, &challenges);
2615    }
2616
2617    #[test]
2618    fn derived_constraints_evaluate_to_zero_on_halt() {
2619        derived_constraints_evaluate_to_zero(test_program_for_halt());
2620    }
2621
2622    fn derived_constraints_evaluate_to_zero(program_and_input: ProgramAndInput) {
2623        let (_, _, master_main_table, master_aux_table, challenges) =
2624            master_tables_for_low_security_level(program_and_input);
2625
2626        let master_main_trace_table = master_main_table.trace_table();
2627        let master_aux_trace_table = master_aux_table.trace_table();
2628
2629        let evaluated_initial_constraints = MasterAuxTable::evaluate_initial_constraints(
2630            master_main_trace_table.row(0),
2631            master_aux_trace_table.row(0),
2632            &challenges,
2633        );
2634        for (constraint_idx, evaluated_constraint) in
2635            evaluated_initial_constraints.into_iter().enumerate()
2636        {
2637            assert!(
2638                xfe!(0) == evaluated_constraint,
2639                "Initial constraint {constraint_idx} failed.",
2640            );
2641        }
2642
2643        for row_idx in 0..master_main_trace_table.nrows() {
2644            let evaluated_consistency_constraints =
2645                MasterAuxTable::evaluate_consistency_constraints(
2646                    master_main_trace_table.row(row_idx),
2647                    master_aux_trace_table.row(row_idx),
2648                    &challenges,
2649                );
2650            for (constraint_idx, evaluated_constraint) in
2651                evaluated_consistency_constraints.into_iter().enumerate()
2652            {
2653                assert!(
2654                    xfe!(0) == evaluated_constraint,
2655                    "Consistency constraint {constraint_idx} failed in row {row_idx}.",
2656                );
2657            }
2658        }
2659
2660        for curr_row_idx in 0..master_main_trace_table.nrows() - 1 {
2661            let next_row_idx = curr_row_idx + 1;
2662            let evaluated_transition_constraints = MasterAuxTable::evaluate_transition_constraints(
2663                master_main_trace_table.row(curr_row_idx),
2664                master_aux_trace_table.row(curr_row_idx),
2665                master_main_trace_table.row(next_row_idx),
2666                master_aux_trace_table.row(next_row_idx),
2667                &challenges,
2668            );
2669            for (constraint_idx, evaluated_constraint) in
2670                evaluated_transition_constraints.into_iter().enumerate()
2671            {
2672                assert!(
2673                    xfe!(0) == evaluated_constraint,
2674                    "Transition constraint {constraint_idx} failed in row {curr_row_idx}.",
2675                );
2676            }
2677        }
2678
2679        let evaluated_terminal_constraints = MasterAuxTable::evaluate_terminal_constraints(
2680            master_main_trace_table.row(master_main_trace_table.nrows() - 1),
2681            master_aux_trace_table.row(master_aux_trace_table.nrows() - 1),
2682            &challenges,
2683        );
2684        for (constraint_idx, evaluated_constraint) in
2685            evaluated_terminal_constraints.into_iter().enumerate()
2686        {
2687            assert!(
2688                xfe!(0) == evaluated_constraint,
2689                "Terminal constraint {constraint_idx} failed.",
2690            );
2691        }
2692    }
2693
2694    #[test]
2695    fn prove_and_verify_simple_program() {
2696        prove_and_verify(
2697            test_program_hash_nop_nop_lt(),
2698            DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS,
2699        );
2700    }
2701
2702    #[test]
2703    fn prove_and_verify_halt_with_different_fri_expansion_factors() {
2704        for log_2_fri_expansion_factor in 1..5 {
2705            println!("Testing with log2_fri_expansion_factor = {log_2_fri_expansion_factor}");
2706            prove_and_verify(test_program_for_halt(), log_2_fri_expansion_factor);
2707        }
2708    }
2709
2710    #[test]
2711    fn prove_and_verify_fibonacci_100() {
2712        let program_and_input =
2713            ProgramAndInput::new(crate::example_programs::FIBONACCI_SEQUENCE.clone())
2714                .with_input(PublicInput::from(bfe_array![100]));
2715        prove_and_verify(
2716            program_and_input,
2717            DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS,
2718        );
2719    }
2720
2721    #[test]
2722    fn prove_verify_program_using_pick_and_place() {
2723        let input = bfe_vec![6, 3, 7, 5, 1, 2, 4, 4, 7, 3, 6, 1, 5, 2];
2724        let program = triton_program! {       // i: 13 12 11 10  9  8  7  6  5  4  3  2  1  0
2725            read_io 5 read_io 5 read_io 4     //  _  6  3  7  5 ›1‹ 2  4  4  7  3  6 ›1‹ 5  2
2726            pick 2 pick 9 place 13 place 13   //  _  1  1  6  3  7  5 ›2‹ 4  4  7  3  6  5 ›2‹
2727            pick 0 pick 7 place 13 place 13   //  _  2  2  1  1  6 ›3‹ 7  5  4  4  7 ›3‹ 6  5
2728            pick 2 pick 8 place 13 place 13   //  _  3  3  2  2  1  1  6  7  5 ›4‹›4‹ 7  6  5
2729            pick 3 pick 4 place 13 place 13   //  _  4  4  3  3  2  2  1  1  6  7 ›5‹ 7  6 ›5‹
2730            pick 0 pick 3 place 13 place 13   //  _  5  5  4  4  3  3  2  2  1  1 ›6‹ 7  7 ›6‹
2731            pick 0 pick 3 place 13 place 13   //  _  6  6  5  5  4  4  3  3  2  2  1  1 ›7‹›7‹
2732            pick 1 pick 1 place 13 place 13   //  _  7  7  6  6  5  5  4  4  3  3  2  2  1  1
2733            write_io 5 write_io 5 write_io 4  //  _
2734            halt
2735        };
2736
2737        let program_and_input = ProgramAndInput::new(program).with_input(input);
2738        let output = program_and_input.clone().run().unwrap();
2739        let expected_output = bfe_vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7];
2740        assert!(expected_output == output);
2741
2742        prove_and_verify(
2743            program_and_input,
2744            DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS,
2745        );
2746    }
2747
2748    #[test]
2749    fn constraints_evaluate_to_zero_on_many_u32_operations() {
2750        let many_u32_instructions = ProgramAndInput::new(
2751            crate::example_programs::PROGRAM_WITH_MANY_U32_INSTRUCTIONS.clone(),
2752        );
2753        triton_constraints_evaluate_to_zero(many_u32_instructions);
2754    }
2755
2756    #[test]
2757    fn prove_verify_many_u32_operations() {
2758        prove_and_verify(
2759            ProgramAndInput::new(
2760                crate::example_programs::PROGRAM_WITH_MANY_U32_INSTRUCTIONS.clone(),
2761            ),
2762            DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS,
2763        );
2764    }
2765
2766    #[proptest]
2767    fn verifying_arbitrary_proof_does_not_panic(
2768        #[strategy(arb())] stark: Stark,
2769        #[strategy(arb())] claim: Claim,
2770        #[strategy(arb())] proof: Proof,
2771    ) {
2772        let _verdict = stark.verify(&claim, &proof);
2773    }
2774
2775    #[proptest]
2776    fn negative_log_2_floor(
2777        #[strategy(arb())]
2778        #[filter(#st0.value() > u64::from(u32::MAX))]
2779        st0: BFieldElement,
2780    ) {
2781        let program = triton_program!(push {st0} log_2_floor halt);
2782        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
2783        let_assert!(InstructionError::OpStackError(err) = err.source);
2784        let_assert!(OpStackError::FailedU32Conversion(element) = err);
2785        assert!(st0 == element);
2786    }
2787
2788    #[test]
2789    fn negative_log_2_floor_of_0() {
2790        let program = triton_program!(push 0 log_2_floor halt);
2791        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
2792        let_assert!(InstructionError::LogarithmOfZero = err.source);
2793    }
2794
2795    #[test]
2796    fn deep_update() {
2797        let domain_length = 1 << 10;
2798        let domain = ArithmeticDomain::of_length(domain_length).unwrap();
2799
2800        let poly_degree = rand::rng().random_range(2..20);
2801        let low_deg_poly_coeffs: Vec<XFieldElement> = random_elements(poly_degree);
2802        let low_deg_poly = Polynomial::new(low_deg_poly_coeffs.clone());
2803        let low_deg_codeword = domain.evaluate(&low_deg_poly);
2804
2805        let out_of_domain_point: XFieldElement = rand::rng().random();
2806        let out_of_domain_value = low_deg_poly.evaluate(out_of_domain_point);
2807
2808        let deep_poly = Prover::deep_codeword(
2809            &low_deg_codeword,
2810            domain,
2811            out_of_domain_point,
2812            out_of_domain_value,
2813        );
2814        let poly_of_maybe_low_degree = domain.interpolate(&deep_poly);
2815        assert!(poly_degree as isize - 2 == poly_of_maybe_low_degree.degree());
2816
2817        let bogus_out_of_domain_value = rand::rng().random();
2818        let bogus_deep_poly = Prover::deep_codeword(
2819            &low_deg_codeword,
2820            domain,
2821            out_of_domain_point,
2822            bogus_out_of_domain_value,
2823        );
2824        let poly_of_hopefully_high_degree = domain.interpolate(&bogus_deep_poly);
2825        assert!(domain_length as isize - 1 == poly_of_hopefully_high_degree.degree());
2826    }
2827
2828    /// Re-compose the segments of a polynomial and assert that the result is equal to the
2829    /// polynomial itself. Uses the Schwartz-Zippel lemma to test polynomial equality.
2830    fn assert_polynomial_equals_recomposed_segments<const N: usize, FF: FiniteField>(
2831        f: &Polynomial<FF>,
2832        segments: &[Polynomial<FF>; N],
2833        x: FF,
2834    ) {
2835        let x_pow_n = x.mod_pow_u32(N as u32);
2836        let evaluate_segment = |(segment_idx, segment): (_, &Polynomial<_>)| {
2837            segment.evaluate::<_, FF>(x_pow_n) * x.mod_pow_u32(segment_idx as u32)
2838        };
2839        let evaluated_segments = segments.iter().enumerate().map(evaluate_segment);
2840        let sum_of_evaluated_segments = evaluated_segments.fold(FF::zero(), |acc, x| acc + x);
2841        assert!(f.evaluate::<_, FF>(x) == sum_of_evaluated_segments);
2842    }
2843
2844    fn assert_segments_degrees_are_small_enough<const N: usize, FF: FiniteField>(
2845        f: &Polynomial<FF>,
2846        segments: &[Polynomial<FF>; N],
2847    ) {
2848        let max_allowed_degree = f.degree() / (N as isize);
2849        let all_degrees_are_small_enough =
2850            segments.iter().all(|s| s.degree() <= max_allowed_degree);
2851        assert!(all_degrees_are_small_enough);
2852    }
2853
2854    #[test]
2855    fn split_polynomial_into_segments_of_unequal_size() {
2856        let coefficients: [XFieldElement; 211] = rand::rng().random();
2857        let f = Polynomial::new(coefficients.to_vec());
2858
2859        let segments_2 = Prover::split_polynomial_into_segments::<2, _>(f.clone());
2860        let segments_3 = Prover::split_polynomial_into_segments::<3, _>(f.clone());
2861        let segments_4 = Prover::split_polynomial_into_segments::<4, _>(f.clone());
2862        let segments_7 = Prover::split_polynomial_into_segments::<7, _>(f.clone());
2863
2864        assert_segments_degrees_are_small_enough(&f, &segments_2);
2865        assert_segments_degrees_are_small_enough(&f, &segments_3);
2866        assert_segments_degrees_are_small_enough(&f, &segments_4);
2867        assert_segments_degrees_are_small_enough(&f, &segments_7);
2868
2869        let x = rand::rng().random();
2870        assert_polynomial_equals_recomposed_segments(&f, &segments_2, x);
2871        assert_polynomial_equals_recomposed_segments(&f, &segments_3, x);
2872        assert_polynomial_equals_recomposed_segments(&f, &segments_4, x);
2873        assert_polynomial_equals_recomposed_segments(&f, &segments_7, x);
2874    }
2875
2876    #[test]
2877    fn split_polynomial_into_segments_of_equal_size() {
2878        let coefficients: [BFieldElement; 2 * 3 * 4 * 7] = rand::rng().random();
2879        let f = Polynomial::new(coefficients.to_vec());
2880
2881        let segments_2 = Prover::split_polynomial_into_segments::<2, _>(f.clone());
2882        let segments_3 = Prover::split_polynomial_into_segments::<3, _>(f.clone());
2883        let segments_4 = Prover::split_polynomial_into_segments::<4, _>(f.clone());
2884        let segments_7 = Prover::split_polynomial_into_segments::<7, _>(f.clone());
2885
2886        assert_segments_degrees_are_small_enough(&f, &segments_2);
2887        assert_segments_degrees_are_small_enough(&f, &segments_3);
2888        assert_segments_degrees_are_small_enough(&f, &segments_4);
2889        assert_segments_degrees_are_small_enough(&f, &segments_7);
2890
2891        let x = rand::rng().random();
2892        assert_polynomial_equals_recomposed_segments(&f, &segments_2, x);
2893        assert_polynomial_equals_recomposed_segments(&f, &segments_3, x);
2894        assert_polynomial_equals_recomposed_segments(&f, &segments_4, x);
2895        assert_polynomial_equals_recomposed_segments(&f, &segments_7, x);
2896    }
2897
2898    #[derive(Debug, Clone, Eq, PartialEq, Hash, test_strategy::Arbitrary)]
2899    struct SegmentifyProptestData {
2900        #[strategy(2_usize..8)]
2901        log_trace_length: usize,
2902
2903        #[strategy(1_usize..=#log_trace_length.min(4))]
2904        log_num_cosets: usize,
2905
2906        #[strategy(1_usize..6)]
2907        log_expansion_factor: usize,
2908
2909        #[strategy(vec(arb(), (1 << #log_num_cosets) * (1 << #log_trace_length)))]
2910        #[map(Polynomial::new)]
2911        polynomial: Polynomial<'static, XFieldElement>,
2912
2913        #[strategy(arb())]
2914        psi: BFieldElement,
2915
2916        #[strategy(arb())]
2917        random_point: XFieldElement,
2918    }
2919
2920    #[proptest]
2921    fn polynomial_segments_cohere_with_originating_polynomial(test_data: SegmentifyProptestData) {
2922        fn segmentify_and_assert_coherence<const N: usize>(
2923            test_data: &SegmentifyProptestData,
2924        ) -> TestCaseResult {
2925            let num_cosets = 1 << test_data.log_num_cosets;
2926            let trace_length = 1 << test_data.log_trace_length;
2927            let expansion_factor = 1 << test_data.log_expansion_factor;
2928
2929            let iota =
2930                BFieldElement::primitive_root_of_unity((trace_length * num_cosets) as u64).unwrap();
2931            let trace_domain = ArithmeticDomain::of_length(trace_length)?;
2932            let fri_domain = ArithmeticDomain::of_length(trace_length * expansion_factor)?
2933                .with_offset(test_data.psi);
2934
2935            let coset_evaluations = (0..u32::try_from(num_cosets)?)
2936                .flat_map(|i| {
2937                    let coset = trace_domain.with_offset(iota.mod_pow_u32(i) * test_data.psi);
2938                    coset.evaluate(&test_data.polynomial)
2939                })
2940                .collect();
2941            let coset_evaluations =
2942                Array2::from_shape_vec((trace_length, num_cosets).f(), coset_evaluations)?;
2943
2944            let (actual_segment_codewords, segment_polynomials) =
2945                Prover::segmentify::<N>(coset_evaluations, test_data.psi, iota, fri_domain);
2946
2947            prop_assert_eq!(N, actual_segment_codewords.ncols());
2948            prop_assert_eq!(N, segment_polynomials.len());
2949
2950            let segments_evaluated = (0..)
2951                .zip(&segment_polynomials)
2952                .map(|(segment_index, segment_polynomial)| -> XFieldElement {
2953                    let point_to_the_seg_idx = test_data.random_point.mod_pow_u32(segment_index);
2954                    let point_to_the_num_seg = test_data.random_point.mod_pow_u32(N as u32);
2955                    let segment_in_point_to_the_num_seg =
2956                        segment_polynomial.evaluate_in_same_field(point_to_the_num_seg);
2957                    point_to_the_seg_idx * segment_in_point_to_the_num_seg
2958                })
2959                .sum::<XFieldElement>();
2960            let evaluation_in_random_point = test_data
2961                .polynomial
2962                .evaluate_in_same_field(test_data.random_point);
2963            prop_assert_eq!(segments_evaluated, evaluation_in_random_point);
2964
2965            let segments_codewords = segment_polynomials
2966                .iter()
2967                .flat_map(|polynomial| Array1::from(fri_domain.evaluate(polynomial)))
2968                .collect_vec();
2969            let segments_codewords =
2970                Array2::from_shape_vec((fri_domain.length, N).f(), segments_codewords)?;
2971            prop_assert_eq!(segments_codewords, actual_segment_codewords);
2972
2973            Ok(())
2974        }
2975
2976        let num_cosets = 1 << test_data.log_num_cosets;
2977        if num_cosets >= 1 {
2978            segmentify_and_assert_coherence::<1>(&test_data)?;
2979        }
2980        if num_cosets >= 2 {
2981            segmentify_and_assert_coherence::<2>(&test_data)?;
2982        }
2983        if num_cosets >= 4 {
2984            segmentify_and_assert_coherence::<4>(&test_data)?;
2985        }
2986        if num_cosets >= 8 {
2987            segmentify_and_assert_coherence::<8>(&test_data)?;
2988        }
2989        if num_cosets >= 16 {
2990            segmentify_and_assert_coherence::<16>(&test_data)?;
2991        }
2992    }
2993
2994    #[proptest]
2995    fn linear_combination_weights_samples_correct_number_of_elements(
2996        #[strategy(arb())] mut proof_stream: ProofStream,
2997    ) {
2998        let weights = LinearCombinationWeights::sample(&mut proof_stream);
2999
3000        prop_assert_eq!(MasterMainTable::NUM_COLUMNS, weights.main.len());
3001        prop_assert_eq!(MasterAuxTable::NUM_COLUMNS, weights.aux.len());
3002        prop_assert_eq!(NUM_QUOTIENT_SEGMENTS, weights.quot_segments.len());
3003        prop_assert_eq!(NUM_DEEP_CODEWORD_COMPONENTS, weights.deep.len());
3004        prop_assert_eq!(
3005            MasterMainTable::NUM_COLUMNS + MasterAuxTable::NUM_COLUMNS,
3006            weights.main_and_aux().len()
3007        );
3008    }
3009
3010    /// A program that executes every instruction in the instruction set.
3011    pub fn program_executing_every_instruction() -> ProgramAndInput {
3012        let m_step_mem_addr = 100_000;
3013
3014        let program = triton_program! {
3015            // merkle_step using the following fake tree:
3016            //     ─── 1 ───
3017            //    ╱         ╲
3018            //   2           3
3019            //  ╱  ╲
3020            // 4    5
3021            push {m_step_mem_addr}  // _ addr (address for `merkle_step_mem`)
3022            push 0                  // _ addr 0 (spacer)
3023            push 5                  // _ addr 0 5 (node index for `merkle_step`s)
3024            read_io 5               // _ addr 0 5 [digest; 5]
3025            merkle_step             // _ addr 0 2 [digest; 5]
3026            merkle_step_mem         // _ addr 0 1 [digest; 5]
3027            divine 5                // _ addr 0 1 [digest; 5] [digest; 5]
3028            assert_vector           // _ addr 0 1 [digest; 5]
3029            pop 5                   // _ addr 0 1
3030            assert                  // _ addr 0
3031            pop 2                   // _
3032
3033            // stack manipulation
3034            push 1 push 2 push 3    // _  1  2  3
3035            place 2                 // _  3  1  2
3036            pick 1                  // _  3  2  1
3037            swap 2                  // _  1  2  3
3038            dup 2 assert            // _  1  2  3
3039            addi -2 assert          // _  1  2
3040            addi -1 assert          // _  1
3041            assert                  // _
3042
3043            // dot_step
3044            push 0 push 0 push 0    // _ [accumulator; 3]
3045            push 500                // _ [accumulator; 3] addr_0
3046            push 800                // _ [accumulator; 3] addr_0 addr_1
3047            xb_dot_step             // _ [accumulator; 3] addr_0 addr_1
3048            xx_dot_step             // _ [accumulator; 3] addr_0 addr_1
3049            write_io 5              // _
3050
3051            // extension field arithmetic
3052            push 1 push 2 push 3    // _ [xfe_0; 3]
3053            push 7 push 8 push 9    // _ [xfe_0; 3] [xfe_1; 3]
3054            dup 3 dup 3 dup 3       // _ [xfe_0; 3] [xfe_1; 3] [xfe_2; 3]
3055            xx_add                  // _ [xfe_0; 3] [xfe_1; 3]
3056            dup 4 dup 4 dup 4       // _ [xfe_0; 3] [xfe_1; 3] [xfe_2; 3]
3057            xx_mul                  // _ [xfe_0; 3] [xfe_1; 3]
3058            x_invert                // _ [xfe_0; 3] [xfe_1; 3]
3059            push 42                 // _ [xfe_0; 3] [xfe_1; 3] 42
3060            xb_mul                  // _ [xfe_0; 3] [xfe_1; 3]
3061
3062            // base field arithmetic
3063            add mul                 // _ bfe_0 bfe_1 bfe_2 bfe_3
3064            addi 0                  // _ bfe_0 bfe_1 bfe_2 bfe_3
3065            invert                  // _ bfe_0 bfe_1 bfe_2 bfe_3
3066            mul add                 // _ bfe_0 bfe_1
3067            eq                      // _ bfe_0
3068            pop 1                   // _
3069
3070            // bit-wise arithmetic
3071            push 38                 // _ 38
3072            push 2                  // _ 38 2
3073            pow                     // _ big_num
3074            push 1337               // _ big_num 1337
3075            add                     // _ big_num
3076            split                   // _ u32_0 u32_1
3077            dup 1 dup 1 lt pop 1    // _ u32_0 u32_1
3078            dup 1 and               // _ u32_0 u32_1
3079            dup 1 xor               // _ u32_0 u32_1
3080            push 9                  // _ u32_0 u32_1 9
3081            log_2_floor pop 1       // _ u32_0 u32_1
3082            div_mod                 // _ u32_0 u32_1
3083            pop_count               // _ u32_0 u32_1
3084            pop 2                   // _
3085
3086            // Sponge
3087            sponge_init             // _
3088            divine 5 divine 5       // _ [stuff; 10]
3089            sponge_absorb           // _
3090            push 42                 // _ 42
3091            sponge_absorb_mem       // _ 52
3092            pop 1                   // _
3093            sponge_squeeze          // _ [stuff; 10]
3094            hash                    // _ [stuff; 5]
3095            pop 5                   // _
3096
3097            // RAM
3098            push 300                // _ address
3099            read_mem 5              // _ [stuff; 5] address
3100            swap 6                  // _ [stuff; 5] address
3101            write_mem 5             // _ address
3102            pop 1                   // _
3103
3104            // control flow
3105            push 0 skiz nop         // _
3106            push 1 skiz nop         // _
3107            push 0 push 2           // _ 0 2
3108            push 0 push 0 push 0    // _ 0 2 [0; 3]
3109            push 0 push 0           // _ 0 2 [0; 5]
3110            call rec_or_ret         // _ 0 0 [0; 5]
3111            pop 5 pop 2             // _
3112            push 2                  // _ 2
3113            call rec                // _ 0
3114            pop 1
3115            halt
3116
3117            // BEFORE: _ n
3118            // AFTER:  _ 0
3119            rec:
3120                dup 0 push 0 eq     // _ n n==0
3121                skiz return         // _ n
3122                push -1 add         // _ n-1
3123                recurse             // _ n-1
3124
3125            // BEFORE: _ m n [_; 5]
3126            // AFTER:  _ m m [_; 5]
3127            rec_or_ret:
3128                swap 5              // _ m [_; 5] n
3129                push -1 add         // _ m [_; 5] n-1
3130                swap 5              // _ m n-1 [_; 5]
3131                recurse_or_return   // _ m n-1 [_; 5]
3132        };
3133
3134        let tree_node_5 = Digest::new(bfe_array![5; 5]);
3135        let tree_node_4 = Digest::new(bfe_array![4; 5]);
3136        let tree_node_3 = Digest::new(bfe_array![3; 5]);
3137        let tree_node_2 = Tip5::hash_pair(tree_node_4, tree_node_5);
3138        let tree_node_1 = Tip5::hash_pair(tree_node_2, tree_node_3);
3139
3140        let public_input = tree_node_5.values().to_vec();
3141
3142        let secret_input = [tree_node_1.reversed().values().to_vec(), bfe_vec![1337; 10]].concat();
3143        let mut ram = (0..)
3144            .zip(42..)
3145            .take(1_000)
3146            .map(|(l, r)| (bfe!(l), bfe!(r)))
3147            .collect::<HashMap<_, _>>();
3148        for (address, digest_element) in (m_step_mem_addr..).zip(tree_node_3.values()) {
3149            ram.insert(bfe!(address), digest_element);
3150        }
3151        let non_determinism = NonDeterminism::new(secret_input)
3152            .with_digests([tree_node_4])
3153            .with_ram(ram);
3154
3155        ProgramAndInput::new(program)
3156            .with_input(public_input)
3157            .with_non_determinism(non_determinism)
3158    }
3159
3160    #[test]
3161    fn program_executing_every_instruction_actually_executes_every_instruction() {
3162        let ProgramAndInput {
3163            program,
3164            public_input,
3165            non_determinism,
3166        } = program_executing_every_instruction();
3167        let (aet, _) = VM::trace_execution(program, public_input, non_determinism).unwrap();
3168        let opcodes_of_all_executed_instructions = aet
3169            .processor_trace
3170            .column(ProcessorMainColumn::CI.main_index())
3171            .iter()
3172            .copied()
3173            .collect::<HashSet<_>>();
3174
3175        let all_opcodes = Instruction::iter()
3176            .map(|instruction| instruction.opcode_b())
3177            .collect::<HashSet<_>>();
3178
3179        all_opcodes
3180            .difference(&opcodes_of_all_executed_instructions)
3181            .for_each(|&opcode| {
3182                let instruction = Instruction::try_from(opcode).unwrap();
3183                eprintln!("Instruction {instruction} was not executed.");
3184            });
3185        assert_eq!(all_opcodes, opcodes_of_all_executed_instructions);
3186    }
3187
3188    #[test]
3189    fn constraints_evaluate_to_zero_on_program_executing_every_instruction() {
3190        triton_constraints_evaluate_to_zero(program_executing_every_instruction());
3191    }
3192}