triton_vm/table/
master_table.rs

1//! A Master Table is, in some sense, a top-level table of Triton VM. It
2//! contains all the data but little logic beyond bookkeeping and presenting the
3//! data in useful ways. Conversely, the individual tables contain no data but
4//! all the respective logic. Master Tables are responsible for managing the
5//! individual tables and for presenting the right data to the right
6//! tables, serving as a clean interface between the VM and the individual
7//! tables.
8//!
9//! As a mental model, it is perfectly fine to think of the data for the
10//! individual tables as completely separate from each other. Only the
11//! [cross-table arguments][cross_arg] link all tables together.
12//!
13//! Conceptually, there are two Master Tables: the [`MasterMainTable`], and the
14//! [`MasterAuxiliaryTable`][master_aux_table]. The lifecycle of the Master
15//! Tables is as follows:
16//! 1. The [`MasterMainTable`] is instantiated and filled using the Algebraic
17//!    Execution Trace.
18//! 2. The [`MasterMainTable`] is padded using logic from the individual tables.
19//! 3. The still-empty entries in the [`MasterMainTable`] are filled with random
20//!    elements. This step is also known as “trace randomization.”
21//! 4. If there is enough RAM, then each column of the [`MasterMainTable`] is
22//!    low-degree extended. The results are stored on the [`MasterMainTable`]
23//!    for quick access later. If there is not enough RAM, then the low-degree
24//!    extensions of the trace columns will be computed and sometimes recomputed
25//!    just-in-time, and the memory freed afterward. The caching behavior [can
26//!    be forced][overwrite_cache].
27//! 5. The [`MasterMainTable`] is used to derive the
28//!    [`MasterAuxiliaryTable`][master_aux_table] using logic from the
29//!    individual tables.
30//! 6. The [`MasterAuxiliaryTable`][master_aux_table] is trace-randomized.
31//! 7. Each column of the [`MasterAuxiliaryTable`][master_aux_table] is
32//!    [low-degree extended][lde]. The effects are the same as for the
33//!    [`MasterMainTable`].
34//! 8. Using the [`MasterMainTable`] and the
35//!    [`MasterAuxiliaryTable`][master_aux_table], the [quotient
36//!    codeword][master_quot_table] is derived using the AIR. Each individual
37//!    table defines that part of the AIR that is relevant to it.
38//!
39//! The following points are of note:
40//! - The [`MasterAuxiliaryTable`][master_aux_table]'s rightmost columns are the
41//!   randomizer codewords. These are necessary for zero-knowledge.
42//! - The cross-table argument has zero width for the [`MasterMainTable`] and
43//!   [`MasterAuxiliaryTable`][master_aux_table] but does induce a nonzero
44//!   number of constraints and thus terms in the [quotient
45//!   combination][all_quotients_combined].
46//!
47//! [cross_arg]: air::cross_table_argument::GrandCrossTableArg
48//! [overwrite_cache]: crate::config::overwrite_lde_trace_caching_to
49//! [lde]: ArithmeticDomain::low_degree_extension
50//! [master_aux_table]: MasterAuxTable
51//! [master_quot_table]: all_quotients_combined
52
53use std::borrow::Borrow;
54use std::mem::MaybeUninit;
55use std::ops::Add;
56use std::ops::Mul;
57use std::ops::MulAssign;
58use std::ops::Range;
59
60use air::table::AUX_CASCADE_TABLE_END;
61use air::table::AUX_CASCADE_TABLE_START;
62use air::table::AUX_HASH_TABLE_END;
63use air::table::AUX_HASH_TABLE_START;
64use air::table::AUX_JUMP_STACK_TABLE_END;
65use air::table::AUX_JUMP_STACK_TABLE_START;
66use air::table::AUX_LOOKUP_TABLE_END;
67use air::table::AUX_LOOKUP_TABLE_START;
68use air::table::AUX_OP_STACK_TABLE_END;
69use air::table::AUX_OP_STACK_TABLE_START;
70use air::table::AUX_PROCESSOR_TABLE_END;
71use air::table::AUX_PROCESSOR_TABLE_START;
72use air::table::AUX_PROGRAM_TABLE_END;
73use air::table::AUX_PROGRAM_TABLE_START;
74use air::table::AUX_RAM_TABLE_END;
75use air::table::AUX_RAM_TABLE_START;
76use air::table::AUX_U32_TABLE_END;
77use air::table::AUX_U32_TABLE_START;
78use air::table::CASCADE_TABLE_END;
79use air::table::CASCADE_TABLE_START;
80use air::table::HASH_TABLE_END;
81use air::table::HASH_TABLE_START;
82use air::table::JUMP_STACK_TABLE_END;
83use air::table::JUMP_STACK_TABLE_START;
84use air::table::LOOKUP_TABLE_END;
85use air::table::LOOKUP_TABLE_START;
86use air::table::OP_STACK_TABLE_END;
87use air::table::OP_STACK_TABLE_START;
88use air::table::PROCESSOR_TABLE_END;
89use air::table::PROCESSOR_TABLE_START;
90use air::table::PROGRAM_TABLE_END;
91use air::table::PROGRAM_TABLE_START;
92use air::table::RAM_TABLE_END;
93use air::table::RAM_TABLE_START;
94use air::table::TableId;
95use air::table::U32_TABLE_END;
96use air::table::U32_TABLE_START;
97use air::table::cascade::CascadeTable;
98use air::table::hash::HashTable;
99use air::table::jump_stack::JumpStackTable;
100use air::table::lookup::LookupTable;
101use air::table::op_stack::OpStackTable;
102use air::table::processor::ProcessorTable;
103use air::table::program::ProgramTable;
104use air::table::ram::RamTable;
105use air::table::u32::U32Table;
106use air::table_column::CascadeAuxColumn;
107use air::table_column::CascadeMainColumn;
108use air::table_column::HashAuxColumn;
109use air::table_column::HashMainColumn;
110use air::table_column::JumpStackAuxColumn;
111use air::table_column::JumpStackMainColumn;
112use air::table_column::LookupAuxColumn;
113use air::table_column::LookupMainColumn;
114use air::table_column::OpStackAuxColumn;
115use air::table_column::OpStackMainColumn;
116use air::table_column::ProcessorAuxColumn;
117use air::table_column::ProcessorMainColumn;
118use air::table_column::ProgramAuxColumn;
119use air::table_column::ProgramMainColumn;
120use air::table_column::RamAuxColumn;
121use air::table_column::RamMainColumn;
122use air::table_column::U32AuxColumn;
123use air::table_column::U32MainColumn;
124use itertools::Itertools;
125use itertools::izip;
126use ndarray::Array2;
127use ndarray::ArrayView2;
128use ndarray::ArrayViewMut2;
129use ndarray::Zip;
130use ndarray::parallel::prelude::*;
131use ndarray::prelude::*;
132use ndarray::s;
133use num_traits::ConstZero;
134use num_traits::One;
135use num_traits::ToBytes;
136use num_traits::Zero;
137use rand::distr::StandardUniform;
138use rand::prelude::*;
139use strum::EnumCount;
140use twenty_first::math::traits::FiniteField;
141use twenty_first::prelude::*;
142use twenty_first::tip5::RATE;
143use twenty_first::util_types::sponge;
144
145use crate::aet::AlgebraicExecutionTrace;
146use crate::arithmetic_domain::ArithmeticDomain;
147use crate::challenges::Challenges;
148use crate::config::CacheDecision;
149use crate::error::ProvingError;
150use crate::ndarray_helper;
151use crate::ndarray_helper::COL_AXIS;
152use crate::ndarray_helper::ROW_AXIS;
153use crate::ndarray_helper::horizontal_multi_slice_mut;
154use crate::ndarray_helper::partial_sums;
155use crate::profiler::profiler;
156use crate::stark::NUM_RANDOMIZER_POLYNOMIALS;
157use crate::stark::ProverDomains;
158use crate::table::AuxiliaryRow;
159use crate::table::MainRow;
160use crate::table::TraceTable;
161use crate::table::auxiliary_table::DegreeWithOrigin;
162use crate::table::auxiliary_table::Evaluable;
163use crate::table::auxiliary_table::all_degrees_with_origin;
164use crate::table::degree_lowering::DegreeLoweringTable;
165use crate::table::processor::ClkJumpDiffs;
166
167/// Helper trait to turn a slice of either [`BFieldElement`]s or
168/// [`XFieldElement`]s into a slice of [`BFieldElement`]s.
169///
170/// In particular, this is helpful when writing code that is generic over the
171/// two fields but should also perform well, i.e., where calling
172/// [`BFieldCodec::encode`] (which performs allocations) is not a good idea.
173pub(crate) trait BfeSlice: FiniteField {
174    fn bfe_slice(slice: &[Self]) -> &[BFieldElement];
175}
176
177impl BfeSlice for BFieldElement {
178    fn bfe_slice(slice: &[Self]) -> &[BFieldElement] {
179        slice
180    }
181}
182
183impl BfeSlice for XFieldElement {
184    fn bfe_slice(slice: &[Self]) -> &[BFieldElement] {
185        x_field_element::as_flat_slice(slice)
186    }
187}
188
189pub(crate) trait MasterTable: Sync
190where
191    StandardUniform: Distribution<Self::Field>,
192    XFieldElement: Add<Self::Field, Output = XFieldElement>
193        // _no_ clue why this is necessary
194        + Add<XFieldElement, Output = XFieldElement>,
195{
196    type Field: BfeSlice
197        + Add<BFieldElement, Output = Self::Field>
198        + MulAssign<BFieldElement>
199        + From<BFieldElement>
200        + BFieldCodec
201        + Mul<BFieldElement, Output = Self::Field>
202        + Mul<XFieldElement, Output = XFieldElement>
203        + 'static;
204
205    const NUM_COLUMNS: usize;
206
207    fn domains(&self) -> ProverDomains;
208
209    /// The [`ArithmeticDomain`] to [low-degree extend] into.
210    /// The larger of the [`quotient_domain`](ProverDomains::quotient) and the
211    /// [`fri_domain`](ProverDomains::fri).
212    ///
213    /// [low-degree extend]: Self::maybe_low_degree_extend_all_columns
214    fn evaluation_domain(&self) -> ArithmeticDomain {
215        let domains = self.domains();
216        if domains.quotient.length > domains.fri.length {
217            domains.quotient
218        } else {
219            domains.fri
220        }
221    }
222
223    /// Presents underlying trace data, excluding trace randomizers and
224    /// randomizer polynomials.
225    fn trace_table(&self) -> ArrayView2<'_, Self::Field>;
226
227    /// Mutably presents underlying trace data, excluding trace randomizers and
228    /// randomizer polynomials.
229    fn trace_table_mut(&mut self) -> ArrayViewMut2<'_, Self::Field>;
230
231    /// The quotient-domain view of the cached low-degree-extended table, if
232    /// 1. the table has been [low-degree extended][lde], and
233    /// 2. the low-degree-extended table [has been cached][cache].
234    ///
235    /// [lde]: Self::maybe_low_degree_extend_all_columns
236    /// [cache]: crate::config::overwrite_lde_trace_caching_to
237    //
238    // This cannot be implemented generically on the trait because it returns a
239    // pointer to an array that must live somewhere and cannot live on the
240    // stack. From the trait implementation we cannot access the implementing
241    // object's fields.
242    fn quotient_domain_table(&self) -> Option<ArrayView2<'_, Self::Field>>;
243
244    /// Low-degree extend all columns of the trace table (including randomizers)
245    /// _if_ it can be [cached]. In that case, the resulting low-degree extended
246    /// columns can be accessed using [`quotient_domain_table`][table] and
247    /// [`fri_domain_table`][Self::fri_domain_table].
248    ///
249    /// [table]: Self::quotient_domain_table
250    /// [cached]: crate::config::overwrite_lde_trace_caching_to
251    fn maybe_low_degree_extend_all_columns(&mut self) {
252        let evaluation_domain = self.evaluation_domain();
253        let num_rows = evaluation_domain.length;
254        let num_elements = num_rows * Self::NUM_COLUMNS;
255
256        let mut extended_trace = Vec::with_capacity(0);
257        match crate::config::cache_lde_trace() {
258            Some(CacheDecision::NoCache) => return,
259            Some(CacheDecision::Cache) => extended_trace.reserve_exact(num_elements),
260            None => {
261                let Ok(()) = extended_trace.try_reserve_exact(num_elements) else {
262                    return;
263                };
264            }
265        };
266
267        profiler!(start "LDE" ("LDE"));
268        profiler!(start "polynomial zero-initialization");
269        let mut interpolation_polynomials = Array1::zeros(Self::NUM_COLUMNS);
270        profiler!(stop "polynomial zero-initialization");
271
272        profiler!(start "interpolation");
273        let column_indices = Array1::from_iter(0..Self::NUM_COLUMNS);
274        Zip::from(column_indices.view())
275            .and(interpolation_polynomials.axis_iter_mut(ROW_AXIS))
276            .par_for_each(|&col_idx, poly| {
277                let column_interpolant = self.randomized_column_interpolant(col_idx);
278                Array0::from_elem((), column_interpolant).move_into(poly);
279            });
280        profiler!(stop "interpolation");
281
282        profiler!(start "resize");
283        assert!(extended_trace.capacity() >= num_elements);
284        extended_trace
285            .spare_capacity_mut()
286            .par_iter_mut()
287            .for_each(|e| *e = MaybeUninit::new(Self::Field::ZERO));
288
289        unsafe {
290            // Speed up initialization through parallelization.
291            //
292            // SAFETY:
293            // 1. The capacity is sufficiently large – see above `assert!`.
294            // 2. The length is set to equal (or less than) the capacity.
295            // 3. Each element in the spare capacity is initialized.
296            extended_trace.set_len(num_elements);
297        }
298        let mut extended_columns =
299            Array2::from_shape_vec([num_rows, Self::NUM_COLUMNS], extended_trace).unwrap();
300        profiler!(stop "resize");
301
302        profiler!(start "evaluation");
303        Zip::from(extended_columns.axis_iter_mut(COL_AXIS))
304            .and(interpolation_polynomials.axis_iter(ROW_AXIS))
305            .par_for_each(|lde_column, interpolant| {
306                let lde_codeword = evaluation_domain.evaluate(&interpolant[()]);
307                Array1::from(lde_codeword).move_into(lde_column);
308            });
309        profiler!(stop "evaluation");
310        profiler!(start "memoize");
311        self.memoize_low_degree_extended_table(extended_columns);
312        profiler!(stop "memoize");
313        profiler!(stop "LDE");
314    }
315
316    /// Not intended for direct use, but through
317    /// [`Self::maybe_low_degree_extend_all_columns`].
318    #[doc(hidden)]
319    fn memoize_low_degree_extended_table(
320        &mut self,
321        low_degree_extended_columns: Array2<Self::Field>,
322    );
323
324    fn clear_cache(&mut self);
325
326    /// Return the FRI domain view of the cached low-degree-extended table, if
327    /// any.
328    ///
329    /// This method cannot be implemented generically on the trait because it
330    /// returns a pointer to an array and that array has to live somewhere;
331    /// it cannot live on stack and from the trait implementation we cannot
332    /// access the implementing object's fields.
333    fn fri_domain_table(&self) -> Option<ArrayView2<'_, Self::Field>>;
334
335    /// Get one row of the table at an arbitrary index. Notably, the index does
336    /// not have to be in any of the domains. In other words, can be used to
337    /// compute out-of-domain rows. Does not include randomizer polynomials.
338    fn out_of_domain_row(&self, indeterminate: XFieldElement) -> Array1<XFieldElement> {
339        // The following is a batched version of barycentric Lagrangian
340        // evaluation. Since the method `barycentric_evaluate` is
341        // self-contained, not returning intermediate items necessary for
342        // batching, and since returning and reusing those intermediate items
343        // would produce a challenging interface, the relevant parts are
344        // reimplemented here.
345
346        let domain = self.domains().trace.domain_values();
347        let domain_shift = domain.iter().map(|&d| indeterminate - d).collect();
348        let domain_shift_inverses = XFieldElement::batch_inversion(domain_shift);
349        let domain_over_domain_shift = domain
350            .into_iter()
351            .zip_eq(domain_shift_inverses)
352            .map(|(d, inv)| d * inv);
353        let barycentric_eval_denominator_inverse = domain_over_domain_shift
354            .clone()
355            .sum::<XFieldElement>()
356            .inverse();
357
358        let ood_trace_domain_zerofier: XFieldElement =
359            self.domains().trace.zerofier().evaluate(indeterminate);
360
361        let trace_table = self.trace_table();
362        (0..Self::NUM_COLUMNS)
363            .into_par_iter()
364            .map(|i| {
365                let trace_codeword = trace_table.column(i);
366                let barycentric_eval_numerator = domain_over_domain_shift
367                    .clone()
368                    .zip_eq(trace_codeword)
369                    .map(|(dsi, &abscis)| abscis * dsi)
370                    .sum::<XFieldElement>();
371
372                let ood_trace_randomizer: XFieldElement =
373                    self.trace_randomizer_for_column(i).evaluate(indeterminate);
374
375                barycentric_eval_numerator * barycentric_eval_denominator_inverse
376                    + ood_trace_domain_zerofier * ood_trace_randomizer
377            })
378            .collect::<Vec<XFieldElement>>()
379            .into()
380    }
381
382    fn randomized_column_interpolant(&self, idx: usize) -> Polynomial<'static, Self::Field> {
383        let trace_table = self.trace_table();
384        let column_codeword = trace_table.column(idx);
385        let trace_domain = self.domains().trace;
386        let column_interpolant = trace_domain.interpolate(column_codeword.as_slice().unwrap());
387        let randomizer = trace_domain.mul_zerofier_with(self.trace_randomizer_for_column(idx));
388
389        column_interpolant + randomizer
390    }
391
392    /// Uniquely enables the revelation of up to `num_trace_randomizers` entries
393    /// in the corresponding column without compromising zero-knowledge.
394    ///
395    /// In order for the trace randomizer to not influence the trace on the
396    /// [trace domain][ProverDomains::trace], it must be multiplied with a
397    /// polynomial that evaluates to zero on that domain. The polynomial of
398    /// lowest degree with this property is the corresponding
399    /// [zerofier][ArithmeticDomain::zerofier]. The randomized trace column
400    /// interpolant can then be obtained through:
401    ///
402    /// `column + zerofier·randomizer`
403    ///
404    /// If you want to multiply the trace randomizer with the zerofier, the most
405    /// performant approach is [`ArithmeticDomain::mul_zerofier_with`].
406    ///
407    /// # Panics
408    ///
409    /// Panics if the `idx` is larger than or equal to [`Self::NUM_COLUMNS`].
410    fn trace_randomizer_for_column(&self, idx: usize) -> Polynomial<'static, Self::Field> {
411        // While possible to produce some randomizer for a too-large index, it
412        // does not have any useful application and is almost certainly a logic
413        // error.
414        assert!(idx < Self::NUM_COLUMNS);
415
416        let mut rng = rng_from_offset_seed(self.trace_randomizer_seed(), idx);
417        let coefficients = (0..self.num_trace_randomizers())
418            .map(|_| rng.random())
419            .collect();
420        Polynomial::new(coefficients)
421    }
422
423    fn trace_randomizer_seed(&self) -> <StdRng as SeedableRng>::Seed;
424
425    fn num_trace_randomizers(&self) -> usize;
426
427    /// Compute a Merkle tree of the FRI domain table. Every row gives one leaf
428    /// in the tree.
429    fn merkle_tree(&self) -> MerkleTree {
430        profiler!(start "leafs");
431        let hashed_rows = self.hash_all_fri_domain_rows();
432        profiler!(stop "leafs");
433
434        profiler!(start "Merkle tree" ("hash"));
435        let merkle_tree = MerkleTree::par_new(&hashed_rows).unwrap();
436        profiler!(stop "Merkle tree");
437
438        merkle_tree
439    }
440
441    fn hash_all_fri_domain_rows(&self) -> Vec<Digest> {
442        if let Some(fri_domain_table) = self.fri_domain_table() {
443            profiler!(start "hash rows" ("hash"));
444            let all_digests = fri_domain_table
445                .axis_iter(ROW_AXIS)
446                .into_par_iter()
447                .map(|row| row.to_slice().unwrap())
448                .map(Self::Field::bfe_slice)
449                .map(Tip5::hash_varlen)
450                .collect();
451            profiler!(stop "hash rows");
452
453            return all_digests;
454        }
455
456        // Now knowing that the low-degree extensions are not cached, hash all
457        // FRI domain rows of the table using just-in-time low-degree-extension.
458        let num_threads = rayon::current_num_threads().max(1);
459        let eval_domain = self.evaluation_domain();
460        let mut sponge_states = vec![SpongeWithPendingAbsorb::new(); eval_domain.length];
461
462        let column_indices = Array1::from_iter(0..Self::NUM_COLUMNS);
463        let mut codewords = Array2::zeros([eval_domain.length, num_threads]);
464        for column_indices in column_indices.axis_chunks_iter(ROW_AXIS, num_threads) {
465            profiler!(start "LDE" ("LDE"));
466            let mut codewords = codewords.slice_mut(s![.., 0..column_indices.len()]);
467            Zip::from(column_indices)
468                .and(codewords.axis_iter_mut(COL_AXIS))
469                .par_for_each(|&col_idx, target_column| {
470                    let column_interpolant = self.randomized_column_interpolant(col_idx);
471                    let lde_codeword = eval_domain.evaluate(&column_interpolant);
472                    Array1::from(lde_codeword).move_into(target_column);
473                });
474            profiler!(stop "LDE");
475            profiler!(start "hash rows" ("hash"));
476            sponge_states
477                .par_iter_mut()
478                .zip(codewords.axis_iter(ROW_AXIS))
479                .for_each(|(sponge, row)| {
480                    sponge.absorb(Self::Field::bfe_slice(row.to_slice().unwrap()))
481                });
482            profiler!(stop "hash rows");
483        }
484
485        sponge_states
486            .into_par_iter()
487            .map(|sponge| sponge.finalize())
488            .collect()
489    }
490
491    /// The linear combination of the trace-randomized columns using the given
492    /// weights.
493    ///
494    /// # Panics
495    ///
496    /// Panics if the number of supplied weights is unequal to the
497    /// [number of columns][Self::NUM_COLUMNS].
498    fn weighted_sum_of_columns(
499        &self,
500        weights: Array1<XFieldElement>,
501    ) -> Polynomial<'_, XFieldElement> {
502        assert_eq!(Self::NUM_COLUMNS, weights.len());
503
504        let weighted_sum_of_trace_columns = self
505            .trace_table()
506            .axis_iter(ROW_AXIS)
507            .into_par_iter()
508            .map(|row| row.iter().zip_eq(&weights).map(|(&r, &w)| r * w).sum())
509            .collect::<Vec<_>>();
510        let weighted_sum_of_trace_columns = self
511            .domains()
512            .trace
513            .interpolate(&weighted_sum_of_trace_columns);
514
515        let weighted_sum_of_trace_randomizer_polynomials = weights
516            .as_slice()
517            .unwrap()
518            .par_iter()
519            .enumerate()
520            .map(|(i, &w)| self.trace_randomizer_for_column(i).scalar_mul(w))
521            .reduce(Polynomial::zero, |sum, x| sum + x);
522        let randomizer_contribution = self
523            .domains()
524            .trace
525            .mul_zerofier_with(weighted_sum_of_trace_randomizer_polynomials);
526
527        weighted_sum_of_trace_columns + randomizer_contribution
528    }
529
530    /// # Panics
531    ///
532    /// Panics if any of the requested indices is out of range; that is, larger
533    /// than `min(self.fri_domain().length, u32::MAX)`.
534    fn reveal_rows(&self, row_indices: &[usize]) -> Vec<Vec<Self::Field>> {
535        if let Some(fri_domain_table) = self.fri_domain_table() {
536            // the cache already contains the requested information
537            return row_indices
538                .iter()
539                .map(|&row_idx| fri_domain_table.row(row_idx).to_vec())
540                .collect();
541        }
542
543        profiler!(start "recompute rows");
544        // obtain the evaluation points from the FRI domain
545        let domains = self.domains();
546        let indeterminates = row_indices
547            .par_iter()
548            .map(|&i| domains.fri.domain_value(u32::try_from(i).unwrap()))
549            .map(Self::Field::from)
550            .collect::<Vec<_>>();
551
552        // fast multi-point extrapolate every column
553        let offset = domains.trace.offset;
554        let trace_table = self.trace_table();
555        let columns = trace_table.axis_iter(COL_AXIS).into_par_iter().map(|col| {
556            Polynomial::coset_extrapolate(offset, col.as_slice().unwrap(), &indeterminates)
557        });
558
559        // add trace randomizers to their columns
560        // todo: this could be done using `Polynomial::batch_evaluate` if that
561        //   function had more general trait bounds 🤷
562        let trace_domain_zerofier = domains.trace.zerofier();
563        let zerofier_evals = indeterminates
564            .par_iter()
565            .map(|&i| trace_domain_zerofier.evaluate::<_, Self::Field>(i))
566            .collect::<Vec<_>>();
567
568        let trace_randomizers = (0..Self::NUM_COLUMNS)
569            .into_par_iter()
570            .map(|col_idx| self.trace_randomizer_for_column(col_idx))
571            .map(|trace_randomizer| trace_randomizer.batch_evaluate(&indeterminates));
572
573        let columns = columns
574            .zip_eq(trace_randomizers)
575            .flat_map(|(trace_col, rand)| {
576                debug_assert_eq!(trace_col.len(), rand.len());
577                debug_assert_eq!(trace_col.len(), zerofier_evals.len());
578                izip!(trace_col, rand, &zerofier_evals)
579                    .map(|(t, r, &z)| t + r * z)
580                    .collect::<Vec<_>>()
581            })
582            .collect::<Vec<Self::Field>>();
583
584        // transpose the resulting matrix out-of-place
585        let n = row_indices.len();
586        let mut rows = vec![Vec::with_capacity(Self::NUM_COLUMNS); n];
587        for column in columns.chunks_exact(n) {
588            for (row, &element) in rows.iter_mut().zip(column) {
589                row.push(element)
590            }
591        }
592        profiler!(stop "recompute rows");
593
594        rows
595    }
596}
597
598/// Create a [random-number generator](StdRng) from a seed and an offset.
599fn rng_from_offset_seed<B>(mut seed: <StdRng as SeedableRng>::Seed, offset: B) -> StdRng
600where
601    B: ToBytes,
602    <B as ToBytes>::Bytes: IntoIterator<Item = u8>,
603{
604    let offset_le_bytes = offset.to_le_bytes();
605
606    // entire offset must be used
607    debug_assert!(offset_le_bytes.as_ref().len() <= seed.len());
608
609    // Ensure that the operation is independent of the target pointer:
610    // `to_le_bytes` yields any leading zeros _after_ bits of lesser
611    // significance. Note that this does not guarantee portability across
612    // architectures, as `rand::StdRng` is specifically documented as being not
613    // portable.
614    for (seed_byte, offset_byte) in seed.iter_mut().zip(offset_le_bytes) {
615        *seed_byte = seed_byte.wrapping_add(offset_byte);
616    }
617
618    StdRng::from_seed(seed)
619}
620
621/// Helper struct and function to absorb however many elements are available;
622/// used in the context of hashing rows in a streaming fashion.
623#[derive(Clone)]
624struct SpongeWithPendingAbsorb {
625    sponge: Tip5,
626
627    /// A re-usable buffer of pending input elements.
628    /// Only the first [`Self::num_symbols_pending`] elements are valid.
629    pending_input: [BFieldElement; RATE],
630    num_symbols_pending: usize,
631}
632
633impl SpongeWithPendingAbsorb {
634    pub fn new() -> Self {
635        Self {
636            sponge: Tip5::new(sponge::Domain::VariableLength),
637            pending_input: bfe_array![0; RATE],
638            num_symbols_pending: 0,
639        }
640    }
641
642    /// Similar to [`Tip5::absorb`] but buffers input elements until a full
643    /// block is available.
644    pub fn absorb<I>(&mut self, some_input: I)
645    where
646        I: IntoIterator,
647        I::Item: Borrow<BFieldElement>,
648    {
649        for symbol in some_input {
650            let &symbol = symbol.borrow();
651            self.pending_input[self.num_symbols_pending] = symbol;
652            self.num_symbols_pending += 1;
653            if self.num_symbols_pending == RATE {
654                self.num_symbols_pending = 0;
655                self.sponge.absorb(self.pending_input);
656            }
657        }
658    }
659
660    pub fn finalize(mut self) -> Digest {
661        // apply padding
662        self.pending_input[self.num_symbols_pending] = BFieldElement::one();
663        for i in self.num_symbols_pending + 1..RATE {
664            self.pending_input[i] = BFieldElement::zero();
665        }
666        self.sponge.absorb(self.pending_input);
667        self.num_symbols_pending = 0;
668
669        self.sponge.squeeze()[0..Digest::LEN]
670            .to_vec()
671            .try_into()
672            .unwrap()
673    }
674}
675
676/// The Master Main Table, as described in the [module documentation][self].
677#[derive(Debug, Clone)]
678pub struct MasterMainTable {
679    pub num_trace_randomizers: usize,
680
681    program_table_len: usize,
682    main_execution_len: usize,
683    op_stack_table_len: usize,
684    ram_table_len: usize,
685    hash_coprocessor_execution_len: usize,
686    cascade_table_len: usize,
687    u32_coprocessor_execution_len: usize,
688
689    domains: ProverDomains,
690
691    trace_table: Array2<BFieldElement>,
692    trace_randomizer_seed: <StdRng as SeedableRng>::Seed,
693
694    low_degree_extended_table: Option<Array2<BFieldElement>>,
695}
696
697/// The Master Auxiliary Table, as described in the
698/// [module documentation][self].
699#[derive(Debug, Clone)]
700pub struct MasterAuxTable {
701    pub num_trace_randomizers: usize,
702    domains: ProverDomains,
703
704    trace_table: Array2<XFieldElement>,
705    trace_randomizer_seed: <StdRng as SeedableRng>::Seed,
706
707    low_degree_extended_table: Option<Array2<XFieldElement>>,
708}
709
710impl MasterTable for MasterMainTable {
711    type Field = BFieldElement;
712    const NUM_COLUMNS: usize = air::table::NUM_MAIN_COLUMNS
713        + crate::table::degree_lowering::DegreeLoweringMainColumn::COUNT;
714
715    fn domains(&self) -> ProverDomains {
716        self.domains
717    }
718
719    fn trace_table(&self) -> ArrayView2<'_, BFieldElement> {
720        self.trace_table.view()
721    }
722
723    fn trace_table_mut(&mut self) -> ArrayViewMut2<'_, BFieldElement> {
724        self.trace_table.view_mut()
725    }
726
727    fn quotient_domain_table(&self) -> Option<ArrayView2<'_, BFieldElement>> {
728        let table = &self.low_degree_extended_table.as_ref()?;
729        let nrows = table.nrows();
730
731        if self.domains.quotient.length < nrows {
732            let unit_distance = nrows / self.domains.quotient.length;
733            Some(table.slice(s![0..nrows;unit_distance, ..]))
734        } else {
735            Some(table.view())
736        }
737    }
738
739    fn memoize_low_degree_extended_table(
740        &mut self,
741        low_degree_extended_columns: Array2<BFieldElement>,
742    ) {
743        self.low_degree_extended_table = Some(low_degree_extended_columns);
744    }
745
746    fn clear_cache(&mut self) {
747        drop(self.low_degree_extended_table.take());
748    }
749
750    fn fri_domain_table(&self) -> Option<ArrayView2<'_, BFieldElement>> {
751        let table = self.low_degree_extended_table.as_ref()?;
752        let nrows = table.nrows();
753        if nrows > self.domains.fri.length {
754            let unit_step = nrows / self.domains.fri.length;
755            Some(table.slice(s![0..nrows;unit_step, ..]))
756        } else {
757            Some(table.view())
758        }
759    }
760
761    fn trace_randomizer_seed(&self) -> <StdRng as SeedableRng>::Seed {
762        self.trace_randomizer_seed
763    }
764
765    fn num_trace_randomizers(&self) -> usize {
766        self.num_trace_randomizers
767    }
768}
769
770impl MasterTable for MasterAuxTable {
771    type Field = XFieldElement;
772    const NUM_COLUMNS: usize = air::table::NUM_AUX_COLUMNS
773        + crate::table::degree_lowering::DegreeLoweringAuxColumn::COUNT
774        + NUM_RANDOMIZER_POLYNOMIALS;
775
776    fn domains(&self) -> ProverDomains {
777        self.domains
778    }
779
780    fn trace_table(&self) -> ArrayView2<'_, XFieldElement> {
781        self.trace_table.slice(s![.., ..Self::NUM_COLUMNS])
782    }
783
784    fn trace_table_mut(&mut self) -> ArrayViewMut2<'_, XFieldElement> {
785        self.trace_table.slice_mut(s![.., ..Self::NUM_COLUMNS])
786    }
787
788    fn quotient_domain_table(&self) -> Option<ArrayView2<'_, XFieldElement>> {
789        let table = self.low_degree_extended_table.as_ref()?;
790        let nrows = table.nrows();
791        if nrows > self.domains.quotient.length {
792            let unit_distance = nrows / self.domains.quotient.length;
793            Some(table.slice(s![0..nrows;unit_distance, ..]))
794        } else {
795            Some(table.view())
796        }
797    }
798
799    fn memoize_low_degree_extended_table(
800        &mut self,
801        low_degree_extended_columns: Array2<XFieldElement>,
802    ) {
803        self.low_degree_extended_table = Some(low_degree_extended_columns);
804    }
805
806    fn clear_cache(&mut self) {
807        drop(self.low_degree_extended_table.take());
808    }
809
810    fn fri_domain_table(&self) -> Option<ArrayView2<'_, XFieldElement>> {
811        let table = self.low_degree_extended_table.as_ref()?;
812        let nrows = table.nrows();
813        if nrows > self.domains.fri.length {
814            let unit_step = nrows / self.domains.fri.length;
815            Some(table.slice(s![0..nrows;unit_step, ..]))
816        } else {
817            Some(table.view())
818        }
819    }
820
821    fn trace_randomizer_seed(&self) -> <StdRng as SeedableRng>::Seed {
822        self.trace_randomizer_seed
823    }
824
825    fn num_trace_randomizers(&self) -> usize {
826        self.num_trace_randomizers
827    }
828}
829
830type PadFunction = fn(ArrayViewMut2<BFieldElement>, usize);
831type ExtendFunction = fn(ArrayView2<BFieldElement>, ArrayViewMut2<XFieldElement>, &Challenges);
832
833impl MasterMainTable {
834    /// The number of columns in this table.
835    //
836    // Repeated to make the constant public despite the trait being private.
837    pub const NUM_COLUMNS: usize = <Self as MasterTable>::NUM_COLUMNS;
838
839    pub(crate) fn new(
840        aet: &AlgebraicExecutionTrace,
841        domains: ProverDomains,
842        num_trace_randomizers: usize,
843        trace_randomizer_seed: <StdRng as SeedableRng>::Seed,
844    ) -> Self {
845        // column majority (“`F`”) for contiguous column slices
846        let trace_table = ndarray_helper::par_zeros((domains.trace.length, Self::NUM_COLUMNS).f());
847
848        let mut master_main_table = Self {
849            num_trace_randomizers,
850            program_table_len: aet.height_of_table(TableId::Program),
851            main_execution_len: aet.height_of_table(TableId::Processor),
852            op_stack_table_len: aet.height_of_table(TableId::OpStack),
853            ram_table_len: aet.height_of_table(TableId::Ram),
854            hash_coprocessor_execution_len: aet.height_of_table(TableId::Hash),
855            cascade_table_len: aet.height_of_table(TableId::Cascade),
856            u32_coprocessor_execution_len: aet.height_of_table(TableId::U32),
857            domains,
858            trace_table,
859            trace_randomizer_seed,
860            low_degree_extended_table: None,
861        };
862
863        // memory-like tables must be filled in before clock jump differences
864        // are known, hence the break from the usual order
865        let clk_jump_diffs_op_stack =
866            OpStackTable::fill(master_main_table.table_mut(TableId::OpStack), aet, ());
867        let clk_jump_diffs_ram = RamTable::fill(master_main_table.table_mut(TableId::Ram), aet, ());
868        let clk_jump_diffs_jump_stack =
869            JumpStackTable::fill(master_main_table.table_mut(TableId::JumpStack), aet, ());
870
871        let clk_jump_diffs = ClkJumpDiffs {
872            op_stack: clk_jump_diffs_op_stack,
873            ram: clk_jump_diffs_ram,
874            jump_stack: clk_jump_diffs_jump_stack,
875        };
876        let processor_table = master_main_table.table_mut(TableId::Processor);
877        ProcessorTable::fill(processor_table, aet, clk_jump_diffs);
878
879        ProgramTable::fill(master_main_table.table_mut(TableId::Program), aet, ());
880        HashTable::fill(master_main_table.table_mut(TableId::Hash), aet, ());
881        CascadeTable::fill(master_main_table.table_mut(TableId::Cascade), aet, ());
882        LookupTable::fill(master_main_table.table_mut(TableId::Lookup), aet, ());
883        U32Table::fill(master_main_table.table_mut(TableId::U32), aet, ());
884
885        // Filling the degree-lowering table only makes sense after padding has
886        // happened. Hence, this table is omitted here.
887
888        master_main_table
889    }
890
891    /// Pad the trace to the next power of two using the various, table-specific
892    /// padding rules. All tables must have the same height for reasons of
893    /// verifier efficiency. Furthermore, that height must be a power of two
894    /// for reasons of prover efficiency. Concretely, the Number Theory
895    /// Transform (NTT) performed by the prover is particularly
896    /// efficient over the used base field when the number of rows is a power of
897    /// two.
898    pub fn pad(&mut self) {
899        let table_lengths = self.all_table_lengths();
900
901        let tables: [_; TableId::COUNT] = horizontal_multi_slice_mut(
902            self.trace_table.view_mut(),
903            &partial_sums(&[
904                ProgramMainColumn::COUNT,
905                ProcessorMainColumn::COUNT,
906                OpStackMainColumn::COUNT,
907                RamMainColumn::COUNT,
908                JumpStackMainColumn::COUNT,
909                HashMainColumn::COUNT,
910                CascadeMainColumn::COUNT,
911                LookupMainColumn::COUNT,
912                U32MainColumn::COUNT,
913            ]),
914        )
915        .try_into()
916        .unwrap();
917
918        profiler!(start "pad original tables");
919        let all_pad_functions: [PadFunction; TableId::COUNT] = [
920            ProgramTable::pad,
921            ProcessorTable::pad,
922            OpStackTable::pad,
923            RamTable::pad,
924            JumpStackTable::pad,
925            HashTable::pad,
926            CascadeTable::pad,
927            LookupTable::pad,
928            U32Table::pad,
929        ];
930
931        all_pad_functions
932            .into_par_iter()
933            .zip_eq(tables)
934            .zip_eq(table_lengths)
935            .for_each(|((pad, table), table_length)| pad(table, table_length));
936        profiler!(stop "pad original tables");
937
938        profiler!(start "fill degree-lowering table");
939        DegreeLoweringTable::fill_derived_main_columns(self.trace_table_mut());
940        profiler!(stop "fill degree-lowering table");
941    }
942
943    fn all_table_lengths(&self) -> [usize; TableId::COUNT] {
944        let processor_table_len = self.main_execution_len;
945        let jump_stack_table_len = self.main_execution_len;
946
947        [
948            self.program_table_len,
949            processor_table_len,
950            self.op_stack_table_len,
951            self.ram_table_len,
952            jump_stack_table_len,
953            self.hash_coprocessor_execution_len,
954            self.cascade_table_len,
955            AlgebraicExecutionTrace::LOOKUP_TABLE_HEIGHT,
956            self.u32_coprocessor_execution_len,
957        ]
958    }
959
960    /// Create a `MasterAuxTable` from a `MasterMainTable` by `.extend()`ing
961    /// each individual main table. The `.extend()` for each table is
962    /// specific to that table, but always involves adding some number of
963    /// columns.
964    pub fn extend(&self, challenges: &Challenges) -> MasterAuxTable {
965        // construct a seed that hasn't been used for any main table column's
966        // trace randomizer
967        let mut rng = rng_from_offset_seed(self.trace_randomizer_seed(), Self::NUM_COLUMNS);
968
969        profiler!(start "initialize master table");
970        // column majority (“`F`”) for contiguous column slices
971        let aux_trace_table_shape = (self.trace_table().nrows(), MasterAuxTable::NUM_COLUMNS).f();
972        let mut aux_trace_table = ndarray_helper::par_zeros(aux_trace_table_shape);
973
974        let randomizers_start = MasterAuxTable::NUM_COLUMNS - NUM_RANDOMIZER_POLYNOMIALS;
975        aux_trace_table
976            .slice_mut(s![.., randomizers_start..])
977            .mapv_inplace(|_| rng.random());
978        profiler!(stop "initialize master table");
979
980        let mut master_aux_table = MasterAuxTable {
981            num_trace_randomizers: self.num_trace_randomizers,
982            domains: self.domains,
983            trace_table: aux_trace_table,
984            trace_randomizer_seed: rng.random(),
985            low_degree_extended_table: None,
986        };
987
988        profiler!(start "slice master table");
989        let aux_trace_table = master_aux_table
990            .trace_table
991            .slice_mut(s![.., ..randomizers_start]);
992        let auxiliary_tables: [_; TableId::COUNT] = horizontal_multi_slice_mut(
993            aux_trace_table,
994            &partial_sums(&[
995                ProgramAuxColumn::COUNT,
996                ProcessorAuxColumn::COUNT,
997                OpStackAuxColumn::COUNT,
998                RamAuxColumn::COUNT,
999                JumpStackAuxColumn::COUNT,
1000                HashAuxColumn::COUNT,
1001                CascadeAuxColumn::COUNT,
1002                LookupAuxColumn::COUNT,
1003                U32AuxColumn::COUNT,
1004            ]),
1005        )
1006        .try_into()
1007        .unwrap();
1008        profiler!(stop "slice master table");
1009
1010        profiler!(start "all tables");
1011        Self::all_extend_functions()
1012            .into_par_iter()
1013            .zip_eq(self.main_tables_for_extending())
1014            .zip_eq(auxiliary_tables)
1015            .for_each(|((extend, main_table), aux_table)| {
1016                extend(main_table, aux_table, challenges)
1017            });
1018        profiler!(stop "all tables");
1019
1020        profiler!(start "fill degree lowering table");
1021        DegreeLoweringTable::fill_derived_aux_columns(
1022            self.trace_table(),
1023            master_aux_table.trace_table_mut(),
1024            challenges,
1025        );
1026        profiler!(stop "fill degree lowering table");
1027
1028        master_aux_table
1029    }
1030
1031    fn all_extend_functions() -> [ExtendFunction; TableId::COUNT] {
1032        [
1033            ProgramTable::extend,
1034            ProcessorTable::extend,
1035            OpStackTable::extend,
1036            RamTable::extend,
1037            JumpStackTable::extend,
1038            HashTable::extend,
1039            CascadeTable::extend,
1040            LookupTable::extend,
1041            U32Table::extend,
1042        ]
1043    }
1044
1045    fn main_tables_for_extending(&self) -> [ArrayView2<'_, BFieldElement>; TableId::COUNT] {
1046        [
1047            self.table(TableId::Program),
1048            self.table(TableId::Processor),
1049            self.table(TableId::OpStack),
1050            self.table(TableId::Ram),
1051            self.table(TableId::JumpStack),
1052            self.table(TableId::Hash),
1053            self.table(TableId::Cascade),
1054            self.table(TableId::Lookup),
1055            self.table(TableId::U32),
1056        ]
1057    }
1058
1059    fn column_indices_for_table(id: TableId) -> Range<usize> {
1060        match id {
1061            TableId::Program => PROGRAM_TABLE_START..PROGRAM_TABLE_END,
1062            TableId::Processor => PROCESSOR_TABLE_START..PROCESSOR_TABLE_END,
1063            TableId::OpStack => OP_STACK_TABLE_START..OP_STACK_TABLE_END,
1064            TableId::Ram => RAM_TABLE_START..RAM_TABLE_END,
1065            TableId::JumpStack => JUMP_STACK_TABLE_START..JUMP_STACK_TABLE_END,
1066            TableId::Hash => HASH_TABLE_START..HASH_TABLE_END,
1067            TableId::Cascade => CASCADE_TABLE_START..CASCADE_TABLE_END,
1068            TableId::Lookup => LOOKUP_TABLE_START..LOOKUP_TABLE_END,
1069            TableId::U32 => U32_TABLE_START..U32_TABLE_END,
1070        }
1071    }
1072
1073    /// A view of the specified table, without any randomizers.
1074    pub fn table(&self, table_id: TableId) -> ArrayView2<'_, BFieldElement> {
1075        let column_indices = Self::column_indices_for_table(table_id);
1076        self.trace_table.slice(s![.., column_indices])
1077    }
1078
1079    /// A mutable view of the specified table, without any randomizers.
1080    pub fn table_mut(&mut self, table_id: TableId) -> ArrayViewMut2<'_, BFieldElement> {
1081        let column_indices = Self::column_indices_for_table(table_id);
1082        self.trace_table.slice_mut(s![.., column_indices])
1083    }
1084
1085    pub(crate) fn try_to_main_row<T: FiniteField>(
1086        row: Array1<T>,
1087    ) -> Result<MainRow<T>, ProvingError> {
1088        let err = || ProvingError::TableRowConversionError {
1089            expected_len: Self::NUM_COLUMNS,
1090            actual_len: row.len(),
1091        };
1092        row.to_vec().try_into().map_err(|_| err())
1093    }
1094}
1095
1096impl MasterAuxTable {
1097    /// The number of columns in this table, including the randomizer
1098    /// polynomials.
1099    //
1100    // Repeated to make the constant public despite the trait being private.
1101    pub const NUM_COLUMNS: usize = <Self as MasterTable>::NUM_COLUMNS;
1102
1103    fn column_indices_for_table(id: TableId) -> Range<usize> {
1104        match id {
1105            TableId::Program => AUX_PROGRAM_TABLE_START..AUX_PROGRAM_TABLE_END,
1106            TableId::Processor => AUX_PROCESSOR_TABLE_START..AUX_PROCESSOR_TABLE_END,
1107            TableId::OpStack => AUX_OP_STACK_TABLE_START..AUX_OP_STACK_TABLE_END,
1108            TableId::Ram => AUX_RAM_TABLE_START..AUX_RAM_TABLE_END,
1109            TableId::JumpStack => AUX_JUMP_STACK_TABLE_START..AUX_JUMP_STACK_TABLE_END,
1110            TableId::Hash => AUX_HASH_TABLE_START..AUX_HASH_TABLE_END,
1111            TableId::Cascade => AUX_CASCADE_TABLE_START..AUX_CASCADE_TABLE_END,
1112            TableId::Lookup => AUX_LOOKUP_TABLE_START..AUX_LOOKUP_TABLE_END,
1113            TableId::U32 => AUX_U32_TABLE_START..AUX_U32_TABLE_END,
1114        }
1115    }
1116
1117    /// A view of the specified table, without any randomizers.
1118    pub fn table(&self, table_id: TableId) -> ArrayView2<'_, XFieldElement> {
1119        let column_indices = Self::column_indices_for_table(table_id);
1120        self.trace_table.slice(s![.., column_indices])
1121    }
1122
1123    /// A mutable view of the specified table, without any randomizers.
1124    pub fn table_mut(&mut self, table_id: TableId) -> ArrayViewMut2<'_, XFieldElement> {
1125        let column_indices = Self::column_indices_for_table(table_id);
1126        self.trace_table.slice_mut(s![.., column_indices])
1127    }
1128
1129    pub(crate) fn try_to_aux_row(row: Array1<XFieldElement>) -> Result<AuxiliaryRow, ProvingError> {
1130        let err = || ProvingError::TableRowConversionError {
1131            expected_len: Self::NUM_COLUMNS,
1132            actual_len: row.len(),
1133        };
1134        row.to_vec().try_into().map_err(|_| err())
1135    }
1136}
1137
1138pub(crate) fn max_degree_with_origin(
1139    interpolant_degree: isize,
1140    padded_height: usize,
1141) -> DegreeWithOrigin {
1142    all_degrees_with_origin(interpolant_degree, padded_height)
1143        .into_iter()
1144        .max()
1145        .unwrap()
1146}
1147
1148pub fn initial_quotient_zerofier_inverse(
1149    quotient_domain: ArithmeticDomain,
1150) -> Array1<BFieldElement> {
1151    let zerofier_codeword = quotient_domain
1152        .domain_values()
1153        .into_iter()
1154        .map(|x| x - bfe!(1))
1155        .collect();
1156    BFieldElement::batch_inversion(zerofier_codeword).into()
1157}
1158
1159pub fn consistency_quotient_zerofier_inverse(
1160    trace_domain: ArithmeticDomain,
1161    quotient_domain: ArithmeticDomain,
1162) -> Array1<BFieldElement> {
1163    let zerofier_codeword = quotient_domain
1164        .domain_values()
1165        .iter()
1166        .map(|x| x.mod_pow_u32(trace_domain.length as u32) - bfe!(1))
1167        .collect();
1168    BFieldElement::batch_inversion(zerofier_codeword).into()
1169}
1170
1171pub fn transition_quotient_zerofier_inverse(
1172    trace_domain: ArithmeticDomain,
1173    quotient_domain: ArithmeticDomain,
1174) -> Array1<BFieldElement> {
1175    let trace_domain_generator_inverse = trace_domain.generator.inverse();
1176    let quotient_domain_values = quotient_domain.domain_values();
1177
1178    let subgroup_zerofier: Vec<_> = quotient_domain_values
1179        .par_iter()
1180        .map(|domain_value| domain_value.mod_pow_u32(trace_domain.length as u32) - bfe!(1))
1181        .collect();
1182    let subgroup_zerofier_inverse = BFieldElement::batch_inversion(subgroup_zerofier);
1183    let zerofier_inverse: Vec<_> = quotient_domain_values
1184        .into_par_iter()
1185        .zip_eq(subgroup_zerofier_inverse.into_par_iter())
1186        .map(|(domain_value, sub_z_inv)| {
1187            (domain_value - trace_domain_generator_inverse) * sub_z_inv
1188        })
1189        .collect();
1190    zerofier_inverse.into()
1191}
1192
1193pub fn terminal_quotient_zerofier_inverse(
1194    trace_domain: ArithmeticDomain,
1195    quotient_domain: ArithmeticDomain,
1196) -> Array1<BFieldElement> {
1197    // The zerofier for the terminal quotient has a root in the last
1198    // value in the cyclical group generated from the trace domain's generator.
1199    let trace_domain_generator_inverse = trace_domain.generator.inverse();
1200    let zerofier_codeword = quotient_domain
1201        .domain_values()
1202        .into_iter()
1203        .map(|x| x - trace_domain_generator_inverse)
1204        .collect_vec();
1205    BFieldElement::batch_inversion(zerofier_codeword).into()
1206}
1207
1208/// Computes the quotient codeword, which is the randomized linear combination
1209/// of all individual quotients.
1210///
1211/// About assigning weights to quotients: the quotients are ordered by category
1212/// – initial, consistency, transition, and then terminal. Within each category,
1213/// the quotients follow the canonical order of the tables. The last column
1214/// holds the terminal quotient of the cross-table argument, which is strictly
1215/// speaking not a table. The order of the quotients is not actually important.
1216/// However, it must be consistent between [prover] and [verifier].
1217///
1218/// [prover]: crate::stark::Stark::prove
1219/// [verifier]: crate::stark::Stark::verify
1220pub fn all_quotients_combined(
1221    quotient_domain_master_main_table: ArrayView2<BFieldElement>,
1222    quotient_domain_master_aux_table: ArrayView2<XFieldElement>,
1223    trace_domain: ArithmeticDomain,
1224    quotient_domain: ArithmeticDomain,
1225    challenges: &Challenges,
1226    quotient_weights: &[XFieldElement],
1227) -> Vec<XFieldElement> {
1228    assert_eq!(
1229        quotient_domain.length,
1230        quotient_domain_master_main_table.nrows(),
1231    );
1232    assert_eq!(
1233        quotient_domain.length,
1234        quotient_domain_master_aux_table.nrows()
1235    );
1236    assert_eq!(MasterAuxTable::NUM_CONSTRAINTS, quotient_weights.len());
1237
1238    let init_section_end = MasterAuxTable::NUM_INITIAL_CONSTRAINTS;
1239    let cons_section_end = init_section_end + MasterAuxTable::NUM_CONSISTENCY_CONSTRAINTS;
1240    let tran_section_end = cons_section_end + MasterAuxTable::NUM_TRANSITION_CONSTRAINTS;
1241
1242    profiler!(start "zerofier inverse");
1243    let initial_zerofier_inverse = initial_quotient_zerofier_inverse(quotient_domain);
1244    let consistency_zerofier_inverse =
1245        consistency_quotient_zerofier_inverse(trace_domain, quotient_domain);
1246    let transition_zerofier_inverse =
1247        transition_quotient_zerofier_inverse(trace_domain, quotient_domain);
1248    let terminal_zerofier_inverse =
1249        terminal_quotient_zerofier_inverse(trace_domain, quotient_domain);
1250    profiler!(stop "zerofier inverse");
1251
1252    profiler!(start "evaluate AIR, compute quotient codeword");
1253    let dot_product = |partial_row: Vec<_>, weights: &[_]| -> XFieldElement {
1254        let pairs = partial_row.into_iter().zip_eq(weights.iter());
1255        pairs.map(|(v, &w)| v * w).sum()
1256    };
1257
1258    let quotient_codeword = (0..quotient_domain.length)
1259        .into_par_iter()
1260        .map(|row_index| {
1261            let unit_distance = quotient_domain.length / trace_domain.length;
1262            let next_row_index = (row_index + unit_distance) % quotient_domain.length;
1263            let current_row_main = quotient_domain_master_main_table.row(row_index);
1264            let current_row_aux = quotient_domain_master_aux_table.row(row_index);
1265            let next_row_main = quotient_domain_master_main_table.row(next_row_index);
1266            let next_row_aux = quotient_domain_master_aux_table.row(next_row_index);
1267
1268            let initial_constraint_values = MasterAuxTable::evaluate_initial_constraints(
1269                current_row_main,
1270                current_row_aux,
1271                challenges,
1272            );
1273            let initial_inner_product = dot_product(
1274                initial_constraint_values,
1275                &quotient_weights[..init_section_end],
1276            );
1277            let mut quotient_value = initial_inner_product * initial_zerofier_inverse[row_index];
1278
1279            let consistency_constraint_values = MasterAuxTable::evaluate_consistency_constraints(
1280                current_row_main,
1281                current_row_aux,
1282                challenges,
1283            );
1284            let consistency_inner_product = dot_product(
1285                consistency_constraint_values,
1286                &quotient_weights[init_section_end..cons_section_end],
1287            );
1288            quotient_value += consistency_inner_product * consistency_zerofier_inverse[row_index];
1289
1290            let transition_constraint_values = MasterAuxTable::evaluate_transition_constraints(
1291                current_row_main,
1292                current_row_aux,
1293                next_row_main,
1294                next_row_aux,
1295                challenges,
1296            );
1297            let transition_inner_product = dot_product(
1298                transition_constraint_values,
1299                &quotient_weights[cons_section_end..tran_section_end],
1300            );
1301            quotient_value += transition_inner_product * transition_zerofier_inverse[row_index];
1302
1303            let terminal_constraint_values = MasterAuxTable::evaluate_terminal_constraints(
1304                current_row_main,
1305                current_row_aux,
1306                challenges,
1307            );
1308            let terminal_inner_product = dot_product(
1309                terminal_constraint_values,
1310                &quotient_weights[tran_section_end..],
1311            );
1312            quotient_value += terminal_inner_product * terminal_zerofier_inverse[row_index];
1313            quotient_value
1314        })
1315        .collect();
1316    profiler!(stop "evaluate AIR, compute quotient codeword");
1317
1318    quotient_codeword
1319}
1320
1321#[cfg(test)]
1322#[cfg_attr(coverage_nightly, coverage(off))]
1323mod tests {
1324    use fs_err as fs;
1325    use std::fmt::Debug;
1326    use std::ops::Add;
1327    use std::path::Path;
1328
1329    use air::AIR;
1330    use air::cross_table_argument::GrandCrossTableArg;
1331    use air::table::cascade::CascadeTable;
1332    use air::table::hash::HashTable;
1333    use air::table::jump_stack::JumpStackTable;
1334    use air::table_column::MasterAuxColumn;
1335    use air::table_column::MasterMainColumn;
1336    use constraint_circuit::ConstraintCircuitBuilder;
1337    use constraint_circuit::ConstraintCircuitMonad;
1338    use constraint_circuit::DegreeLoweringInfo;
1339    use constraint_circuit::DualRowIndicator;
1340    use constraint_circuit::SingleRowIndicator;
1341    use isa::instruction::Instruction;
1342    use isa::instruction::InstructionBit;
1343    use ndarray::Array2;
1344    use num_traits::ConstZero;
1345    use proptest::prelude::*;
1346    use proptest_arbitrary_interop::arb;
1347    use strum::EnumCount;
1348    use strum::EnumIter;
1349    use strum::IntoEnumIterator;
1350    use strum::VariantNames;
1351    use test_strategy::proptest;
1352    use twenty_first::math::b_field_element::BFieldElement;
1353    use twenty_first::math::traits::FiniteField;
1354    use twenty_first::prelude::x_field_element::EXTENSION_DEGREE;
1355
1356    use crate::arithmetic_domain::ArithmeticDomain;
1357    use crate::constraints::dynamic_air_constraint_evaluation_tasm;
1358    use crate::constraints::static_air_constraint_evaluation_tasm;
1359    use crate::memory_layout::DynamicTasmConstraintEvaluationMemoryLayout;
1360    use crate::memory_layout::StaticTasmConstraintEvaluationMemoryLayout;
1361    use crate::shared_tests::TestableProgram;
1362    use crate::table::degree_lowering::DegreeLoweringAuxColumn;
1363    use crate::table::degree_lowering::DegreeLoweringMainColumn;
1364    use crate::triton_program;
1365
1366    use super::*;
1367
1368    #[test]
1369    fn main_table_width_is_correct() {
1370        let master_main_table = TestableProgram::new(triton_program!(halt))
1371            .generate_proof_artifacts()
1372            .master_main_table;
1373
1374        assert_eq!(
1375            <ProgramTable as AIR>::MainColumn::COUNT,
1376            master_main_table.table(TableId::Program).ncols()
1377        );
1378        assert_eq!(
1379            <ProcessorTable as AIR>::MainColumn::COUNT,
1380            master_main_table.table(TableId::Processor).ncols()
1381        );
1382        assert_eq!(
1383            <OpStackTable as AIR>::MainColumn::COUNT,
1384            master_main_table.table(TableId::OpStack).ncols()
1385        );
1386        assert_eq!(
1387            <RamTable as AIR>::MainColumn::COUNT,
1388            master_main_table.table(TableId::Ram).ncols()
1389        );
1390        assert_eq!(
1391            <JumpStackTable as AIR>::MainColumn::COUNT,
1392            master_main_table.table(TableId::JumpStack).ncols()
1393        );
1394        assert_eq!(
1395            <HashTable as AIR>::MainColumn::COUNT,
1396            master_main_table.table(TableId::Hash).ncols()
1397        );
1398        assert_eq!(
1399            <CascadeTable as AIR>::MainColumn::COUNT,
1400            master_main_table.table(TableId::Cascade).ncols()
1401        );
1402        assert_eq!(
1403            <LookupTable as AIR>::MainColumn::COUNT,
1404            master_main_table.table(TableId::Lookup).ncols()
1405        );
1406        assert_eq!(
1407            <U32Table as AIR>::MainColumn::COUNT,
1408            master_main_table.table(TableId::U32).ncols()
1409        );
1410    }
1411
1412    #[test]
1413    fn aux_table_width_is_correct() {
1414        let master_aux_table = TestableProgram::new(triton_program!(halt))
1415            .generate_proof_artifacts()
1416            .master_aux_table;
1417
1418        assert_eq!(
1419            <ProgramTable as AIR>::AuxColumn::COUNT,
1420            master_aux_table.table(TableId::Program).ncols()
1421        );
1422        assert_eq!(
1423            <ProcessorTable as AIR>::AuxColumn::COUNT,
1424            master_aux_table.table(TableId::Processor).ncols()
1425        );
1426        assert_eq!(
1427            <OpStackTable as AIR>::AuxColumn::COUNT,
1428            master_aux_table.table(TableId::OpStack).ncols()
1429        );
1430        assert_eq!(
1431            <RamTable as AIR>::AuxColumn::COUNT,
1432            master_aux_table.table(TableId::Ram).ncols()
1433        );
1434        assert_eq!(
1435            <JumpStackTable as AIR>::AuxColumn::COUNT,
1436            master_aux_table.table(TableId::JumpStack).ncols()
1437        );
1438        assert_eq!(
1439            <HashTable as AIR>::AuxColumn::COUNT,
1440            master_aux_table.table(TableId::Hash).ncols()
1441        );
1442        assert_eq!(
1443            <CascadeTable as AIR>::AuxColumn::COUNT,
1444            master_aux_table.table(TableId::Cascade).ncols()
1445        );
1446        assert_eq!(
1447            <LookupTable as AIR>::AuxColumn::COUNT,
1448            master_aux_table.table(TableId::Lookup).ncols()
1449        );
1450        assert_eq!(
1451            <U32Table as AIR>::AuxColumn::COUNT,
1452            master_aux_table.table(TableId::U32).ncols()
1453        );
1454    }
1455
1456    #[test]
1457    fn trace_tables_are_in_column_major_order() {
1458        let artifacts = TestableProgram::new(triton_program!(halt)).generate_proof_artifacts();
1459
1460        let main = artifacts.master_main_table.trace_table();
1461        assert!(main.column(0).as_slice().is_some());
1462
1463        let aux = artifacts.master_aux_table.trace_table();
1464        assert!(aux.column(0).as_slice().is_some());
1465    }
1466
1467    #[test]
1468    fn fri_domain_table_row_hashing_is_independent_of_fri_table_caching() {
1469        fn row_hashes_are_identical<FF>(mut table: impl MasterTable<Field = FF>)
1470        where
1471            StandardUniform: Distribution<FF>,
1472            XFieldElement: Add<FF, Output = XFieldElement>,
1473        {
1474            assert!(table.fri_domain_table().is_none());
1475            let jit_digests = table.hash_all_fri_domain_rows();
1476
1477            assert!(table.fri_domain_table().is_none());
1478            table.maybe_low_degree_extend_all_columns();
1479
1480            assert!(table.fri_domain_table().is_some());
1481            let cache_digests = table.hash_all_fri_domain_rows();
1482
1483            assert_eq!(jit_digests, cache_digests);
1484        }
1485
1486        // ensure caching _can_ happen by overwriting environment variables
1487        crate::config::overwrite_lde_trace_caching_to(CacheDecision::Cache);
1488        let artifacts = TestableProgram::new(triton_program!(halt)).generate_proof_artifacts();
1489        row_hashes_are_identical(artifacts.master_main_table);
1490        row_hashes_are_identical(artifacts.master_aux_table);
1491    }
1492
1493    #[proptest]
1494    fn revealing_rows_is_independent_of_fri_table_caching(
1495        #[filter(!#row_indices.is_empty())] row_indices: Vec<usize>,
1496    ) {
1497        fn revealed_rows_are_identical<FF>(
1498            mut table: impl MasterTable<Field = FF>,
1499            indices: &[usize],
1500        ) where
1501            FF: Debug + PartialEq,
1502            StandardUniform: Distribution<FF>,
1503            XFieldElement: Add<FF, Output = XFieldElement>,
1504        {
1505            assert!(table.fri_domain_table().is_none());
1506            let jit_rows = table.reveal_rows(indices);
1507
1508            assert!(table.fri_domain_table().is_none());
1509            table.maybe_low_degree_extend_all_columns();
1510
1511            assert!(table.fri_domain_table().is_some());
1512            let cache_rows = table.reveal_rows(indices);
1513
1514            assert_eq!(jit_rows, cache_rows);
1515        }
1516
1517        // ensure caching _can_ happen by overwriting environment variables
1518        crate::config::overwrite_lde_trace_caching_to(CacheDecision::Cache);
1519        let artifacts = TestableProgram::new(triton_program!(halt)).generate_proof_artifacts();
1520        let main_table = artifacts.master_main_table;
1521        let aux_table = artifacts.master_aux_table;
1522
1523        let len = main_table.trace_table.nrows();
1524        let row_indices = row_indices.into_iter().map(|idx| idx % len).collect_vec();
1525        revealed_rows_are_identical(main_table, &row_indices);
1526        revealed_rows_are_identical(aux_table, &row_indices);
1527    }
1528
1529    #[test]
1530    fn zerofiers_are_correct() {
1531        let big_order = 16;
1532        let big_offset = BFieldElement::generator();
1533        let big_domain = ArithmeticDomain::of_length(big_order as usize)
1534            .unwrap()
1535            .with_offset(big_offset);
1536
1537        let small_order = 8;
1538        let small_domain = ArithmeticDomain::of_length(small_order).unwrap();
1539
1540        let initial_zerofier_inv = initial_quotient_zerofier_inverse(big_domain);
1541        let initial_zerofier = BFieldElement::batch_inversion(initial_zerofier_inv.to_vec());
1542        let initial_zerofier_poly = big_domain.interpolate(&initial_zerofier);
1543        assert_eq!(big_order as usize, initial_zerofier_inv.len());
1544        assert_eq!(1, initial_zerofier_poly.degree());
1545        let initial_zerofier_eval =
1546            initial_zerofier_poly.evaluate_in_same_field(small_domain.domain_value(0));
1547        assert_eq!(bfe!(0), initial_zerofier_eval);
1548
1549        let consistency_zerofier_inv =
1550            consistency_quotient_zerofier_inverse(small_domain, big_domain);
1551        let consistency_zerofier =
1552            BFieldElement::batch_inversion(consistency_zerofier_inv.to_vec());
1553        let consistency_zerofier_poly = big_domain.interpolate(&consistency_zerofier);
1554        assert_eq!(big_order as usize, consistency_zerofier_inv.len());
1555        assert_eq!(small_order as isize, consistency_zerofier_poly.degree());
1556        for val in small_domain.domain_values() {
1557            let consistency_zerofier_eval = consistency_zerofier_poly.evaluate_in_same_field(val);
1558            assert_eq!(bfe!(0), consistency_zerofier_eval);
1559        }
1560
1561        let transition_zerofier_inv =
1562            transition_quotient_zerofier_inverse(small_domain, big_domain);
1563        let transition_zerofier = BFieldElement::batch_inversion(transition_zerofier_inv.to_vec());
1564        let transition_zerofier_poly = big_domain.interpolate(&transition_zerofier);
1565        assert_eq!(big_order as usize, transition_zerofier_inv.len());
1566        assert_eq!(small_order as isize - 1, transition_zerofier_poly.degree());
1567        for &val in small_domain.domain_values().iter().take(small_order - 1) {
1568            let transition_zerofier_eval = transition_zerofier_poly.evaluate_in_same_field(val);
1569            assert_eq!(bfe!(0), transition_zerofier_eval);
1570        }
1571
1572        let terminal_zerofier_inv = terminal_quotient_zerofier_inverse(small_domain, big_domain);
1573        let terminal_zerofier = BFieldElement::batch_inversion(terminal_zerofier_inv.to_vec());
1574        let terminal_zerofier_poly = big_domain.interpolate(&terminal_zerofier);
1575        assert_eq!(big_order as usize, terminal_zerofier_inv.len());
1576        assert_eq!(1, terminal_zerofier_poly.degree());
1577        let terminal_zerofier_eval = terminal_zerofier_poly
1578            .evaluate_in_same_field(small_domain.domain_value(small_order as u32 - 1));
1579        assert_eq!(bfe!(0), terminal_zerofier_eval);
1580    }
1581
1582    struct SpecSnippet {
1583        pub start_marker: &'static str,
1584        pub stop_marker: &'static str,
1585        pub snippet: String,
1586    }
1587
1588    #[test]
1589    fn update_arithmetization_overview() {
1590        let spec_snippets = [
1591            generate_table_overview(),
1592            generate_constraints_overview(),
1593            generate_tasm_air_evaluation_cost_overview(),
1594            generate_opcode_pressure_overview(),
1595        ];
1596
1597        // current directory is triton-vm/triton-vm/
1598        let spec_path = Path::new("../specification/src/arithmetization-overview.md");
1599        let current_spec = fs::read_to_string(spec_path).unwrap().replace("\r\n", "\n");
1600        let mut new_spec = current_spec.clone();
1601        for snippet in spec_snippets {
1602            let start = new_spec.find(snippet.start_marker).unwrap();
1603            let stop = new_spec.find(snippet.stop_marker).unwrap();
1604            new_spec = format!(
1605                "{}{}\n{}{}",
1606                &new_spec[..start],
1607                snippet.start_marker,
1608                snippet.snippet,
1609                &new_spec[stop..]
1610            );
1611        }
1612
1613        if current_spec != new_spec {
1614            println!("Updated arithmetization overview to be:\n\n{new_spec}");
1615            fs::write(spec_path, new_spec).unwrap();
1616            panic!("The arithmetization overview was updated. Please commit the changes.");
1617        }
1618    }
1619
1620    fn generate_table_overview() -> SpecSnippet {
1621        fn table_widths<A: AIR>() -> (usize, usize) {
1622            (A::MainColumn::COUNT, A::AuxColumn::COUNT)
1623        }
1624
1625        const NUM_DEGREE_LOWERING_TARGETS: usize = 3;
1626        const DEGREE_LOWERING_TARGETS: [Option<isize>; NUM_DEGREE_LOWERING_TARGETS] =
1627            [None, Some(8), Some(4)];
1628        assert!(DEGREE_LOWERING_TARGETS.contains(&Some(air::TARGET_DEGREE)));
1629
1630        let mut all_table_info = [
1631            (
1632                "[ProgramTable](program-table.md)",
1633                table_widths::<ProgramTable>(),
1634            ),
1635            (
1636                "[ProcessorTable](processor-table.md)",
1637                table_widths::<ProcessorTable>(),
1638            ),
1639            (
1640                "[OpStackTable](operational-stack-table.md)",
1641                table_widths::<OpStackTable>(),
1642            ),
1643            (
1644                "[RamTable](random-access-memory-table.md)",
1645                table_widths::<RamTable>(),
1646            ),
1647            (
1648                "[JumpStackTable](jump-stack-table.md)",
1649                table_widths::<JumpStackTable>(),
1650            ),
1651            ("[HashTable](hash-table.md)", table_widths::<HashTable>()),
1652            (
1653                "[CascadeTable](cascade-table.md)",
1654                table_widths::<CascadeTable>(),
1655            ),
1656            (
1657                "[LookupTable](lookup-table.md)",
1658                table_widths::<LookupTable>(),
1659            ),
1660            ("[U32Table](u32-table.md)", table_widths::<U32Table>()),
1661        ]
1662        .map(|(description, (main_width, aux_width))| {
1663            (
1664                description.to_string(),
1665                [main_width; NUM_DEGREE_LOWERING_TARGETS],
1666                [aux_width; NUM_DEGREE_LOWERING_TARGETS],
1667            )
1668        })
1669        .to_vec();
1670
1671        let mut deg_low_main = vec![];
1672        let mut deg_low_aux = vec![];
1673        for maybe_target_degree in DEGREE_LOWERING_TARGETS {
1674            let Some(target_degree) = maybe_target_degree else {
1675                deg_low_main.push(0);
1676                deg_low_aux.push(0);
1677                continue;
1678            };
1679
1680            let degree_lowering_info = DegreeLoweringInfo {
1681                target_degree,
1682                num_main_cols: 0,
1683                num_aux_cols: 0,
1684            };
1685
1686            // generic closures are not possible; define two variants :(
1687            let lower_to_target_degree_single_row = |mut constraints: Vec<_>| {
1688                ConstraintCircuitMonad::lower_to_degree(&mut constraints, degree_lowering_info)
1689            };
1690            let lower_to_target_degree_double_row = |mut constraints: Vec<_>| {
1691                ConstraintCircuitMonad::lower_to_degree(&mut constraints, degree_lowering_info)
1692            };
1693
1694            let constraints = constraint_builder::Constraints::all();
1695            let (init_main, init_aux) = lower_to_target_degree_single_row(constraints.init);
1696            let (cons_main, cons_aux) = lower_to_target_degree_single_row(constraints.cons);
1697            let (tran_main, tran_aux) = lower_to_target_degree_double_row(constraints.tran);
1698            let (term_main, term_aux) = lower_to_target_degree_single_row(constraints.term);
1699
1700            deg_low_main
1701                .push(init_main.len() + cons_main.len() + tran_main.len() + term_main.len());
1702            deg_low_aux.push(init_aux.len() + cons_aux.len() + tran_aux.len() + term_aux.len());
1703        }
1704        let target_degrees = DEGREE_LOWERING_TARGETS
1705            .into_iter()
1706            .map(|target| target.map_or_else(|| "-".to_string(), |t| t.to_string()))
1707            .join("/");
1708        all_table_info.push((
1709            format!("DegreeLowering ({target_degrees})"),
1710            deg_low_main.try_into().unwrap(),
1711            deg_low_aux.try_into().unwrap(),
1712        ));
1713        all_table_info.push((
1714            "Randomizers".to_string(),
1715            [0; NUM_DEGREE_LOWERING_TARGETS],
1716            [NUM_RANDOMIZER_POLYNOMIALS; NUM_DEGREE_LOWERING_TARGETS],
1717        ));
1718        let all_table_info = all_table_info;
1719
1720        // produce table code
1721        let mut ft = format!("| {:<42} ", "table name");
1722        ft = format!("{ft}| {:>15} ", "#main cols");
1723        ft = format!("{ft}| {:>16} ", "#aux cols");
1724        ft = format!("{ft}| {:>15} |\n", "total width");
1725
1726        ft = format!("{ft}|:{:-<42}-", "-");
1727        ft = format!("{ft}|-{:-<15}:", "-");
1728        ft = format!("{ft}|-{:-<16}:", "-");
1729        ft = format!("{ft}|-{:-<15}:|\n", "-");
1730
1731        let format_slice_and_collapse_if_all_entries_equal = |slice: &[usize]| {
1732            if slice.iter().all(|&n| n == slice[0]) {
1733                format!("{}", slice[0])
1734            } else {
1735                slice.iter().join("/").to_string()
1736            }
1737        };
1738        let mut total_main = [0; NUM_DEGREE_LOWERING_TARGETS];
1739        let mut total_aux = [0; NUM_DEGREE_LOWERING_TARGETS];
1740        for (name, num_main, num_aux) in all_table_info {
1741            let num_total = num_main
1742                .into_iter()
1743                .zip(num_aux)
1744                .map(|(m, a)| m + EXTENSION_DEGREE * a)
1745                .collect_vec();
1746            ft = format!(
1747                "{ft}| {name:<42} | {:>15} | {:>16} | {:>15} |\n",
1748                format_slice_and_collapse_if_all_entries_equal(&num_main),
1749                format_slice_and_collapse_if_all_entries_equal(&num_aux),
1750                format_slice_and_collapse_if_all_entries_equal(&num_total),
1751            );
1752            for (t, n) in total_main.iter_mut().zip(num_main) {
1753                *t += n;
1754            }
1755            for (t, n) in total_aux.iter_mut().zip(num_aux) {
1756                *t += n;
1757            }
1758        }
1759        ft = format!(
1760            "{ft}| {:<42} | {:>15} | {:>16} | {:>15} |\n",
1761            "**TOTAL**",
1762            format!("**{}**", total_main.iter().join("/")),
1763            format!("**{}**", total_aux.iter().join("/")),
1764            format!(
1765                "**{}**",
1766                total_main
1767                    .into_iter()
1768                    .zip(total_aux)
1769                    .map(|(m, a)| m + EXTENSION_DEGREE * a)
1770                    .join("/")
1771            )
1772        );
1773
1774        let how_to_update = "<!-- To update, please run `cargo test`. -->";
1775        SpecSnippet {
1776            start_marker: "<!-- auto-gen info start table_overview -->",
1777            stop_marker: "<!-- auto-gen info stop table_overview -->",
1778            snippet: format!("{how_to_update}\n{ft}"),
1779        }
1780    }
1781
1782    fn generate_constraints_overview() -> SpecSnippet {
1783        struct ConstraintsOverviewRow {
1784            pub name: String,
1785            pub initial_constraints: Vec<ConstraintCircuitMonad<SingleRowIndicator>>,
1786            pub consistency_constraints: Vec<ConstraintCircuitMonad<SingleRowIndicator>>,
1787            pub transition_constraints: Vec<ConstraintCircuitMonad<DualRowIndicator>>,
1788            pub terminal_constraints: Vec<ConstraintCircuitMonad<SingleRowIndicator>>,
1789            pub last_main_column_index: usize,
1790            pub last_aux_column_index: usize,
1791        }
1792
1793        macro_rules! constraint_overview_rows {
1794            ($($table:ident ends at $main_end:ident and $aux_end: ident.
1795            Spec: [$spec_name:literal]($spec_file:literal)),* $(,)?) => {{
1796                let single_row_builder = || ConstraintCircuitBuilder::new();
1797                let dual_row_builder = || ConstraintCircuitBuilder::new();
1798                let mut rows = Vec::new();
1799                $(
1800                let name = format!("[{}]({})", $spec_name, $spec_file);
1801                let row = ConstraintsOverviewRow {
1802                    name,
1803                    initial_constraints: $table::initial_constraints(&single_row_builder()),
1804                    consistency_constraints: $table::consistency_constraints(&single_row_builder()),
1805                    transition_constraints: $table::transition_constraints(&dual_row_builder()),
1806                    terminal_constraints: $table::terminal_constraints(&single_row_builder()),
1807                    last_main_column_index: $main_end,
1808                    last_aux_column_index: $aux_end,
1809                };
1810                rows.push(row);
1811                )*
1812                rows
1813            }};
1814        }
1815
1816        // Declarative macro workaround (because I'm bad at them):
1817        // an `expr` cannot be followed up with `and`. Instead, declare this
1818        // `const` to have an `ident`, which _can_ be followed up with `and`.
1819        const ZERO: usize = 0;
1820
1821        let degree_lowering_targets = [None, Some(8), Some(4)];
1822        assert!(degree_lowering_targets.contains(&Some(air::TARGET_DEGREE)));
1823
1824        let mut ft = String::new();
1825        for target_degree in degree_lowering_targets {
1826            let mut total_initial = 0;
1827            let mut total_consistency = 0;
1828            let mut total_transition = 0;
1829            let mut total_terminal = 0;
1830            ft = match target_degree {
1831                None => format!("{ft}\nBefore automatic degree lowering:\n\n"),
1832                Some(target) => format!("{ft}\nAfter lowering degree to {target}:\n\n"),
1833            };
1834            ft = format!("{ft}| {:<46} ", "table name");
1835            ft = format!("{ft}| #initial ");
1836            ft = format!("{ft}| #consistency ");
1837            ft = format!("{ft}| #transition ");
1838            ft = format!("{ft}| #terminal ");
1839            if target_degree.is_none() {
1840                ft = format!("{ft}| max degree ");
1841            }
1842            ft = format!("{ft}|\n");
1843
1844            ft = format!("{ft}|:{:-<46}-", "-");
1845            ft = format!("{ft}|-{:-<8}:", "-");
1846            ft = format!("{ft}|-{:-<12}:", "-");
1847            ft = format!("{ft}|-{:-<11}:", "-");
1848            ft = format!("{ft}|-{:-<9}:", "-");
1849            if target_degree.is_none() {
1850                ft = format!("{ft}|-{:-<10}:", "-");
1851            }
1852            ft = format!("{ft}|\n");
1853
1854            let mut total_max_degree = 0;
1855            let mut tables = constraint_overview_rows!(
1856                ProgramTable ends at PROGRAM_TABLE_END and AUX_PROGRAM_TABLE_END.
1857                    Spec: ["ProgramTable"]("program-table.md"),
1858                ProcessorTable ends at PROCESSOR_TABLE_END and AUX_PROCESSOR_TABLE_END.
1859                    Spec: ["ProcessorTable"]("processor-table.md"),
1860                OpStackTable ends at OP_STACK_TABLE_END and AUX_OP_STACK_TABLE_END.
1861                    Spec: ["OpStackTable"]("operational-stack-table.md"),
1862                RamTable ends at RAM_TABLE_END and AUX_RAM_TABLE_END.
1863                    Spec: ["RamTable"]("random-access-memory-table.md"),
1864                JumpStackTable ends at JUMP_STACK_TABLE_END and AUX_JUMP_STACK_TABLE_END.
1865                    Spec: ["JumpStackTable"]("jump-stack-table.md"),
1866                HashTable ends at HASH_TABLE_END and AUX_HASH_TABLE_END.
1867                    Spec: ["HashTable"]("hash-table.md"),
1868                CascadeTable ends at CASCADE_TABLE_END and AUX_CASCADE_TABLE_END.
1869                    Spec: ["CascadeTable"]("cascade-table.md"),
1870                LookupTable ends at LOOKUP_TABLE_END and AUX_LOOKUP_TABLE_END.
1871                    Spec: ["LookupTable"]("lookup-table.md"),
1872                U32Table ends at U32_TABLE_END and AUX_U32_TABLE_END.
1873                    Spec: ["U32Table"]("u32-table.md"),
1874                GrandCrossTableArg ends at ZERO and ZERO.
1875                    Spec: ["Grand Cross-Table Argument"]("table-linking.md"),
1876            );
1877            let mut all_initial_constraints = vec![];
1878            let mut all_consistency_constraints = vec![];
1879            let mut all_transition_constraints = vec![];
1880            let mut all_terminal_constraints = vec![];
1881            for table in &mut tables {
1882                let mut initial_constraints = table.initial_constraints.clone();
1883                let mut consistency_constraints = table.consistency_constraints.clone();
1884                let mut transition_constraints = table.transition_constraints.clone();
1885                let mut terminal_constraints = table.terminal_constraints.clone();
1886
1887                if let Some(target_degree) = target_degree {
1888                    let info = DegreeLoweringInfo {
1889                        target_degree,
1890                        num_main_cols: table.last_main_column_index,
1891                        num_aux_cols: table.last_aux_column_index,
1892                    };
1893                    let (new_main_init, new_aux_init) = ConstraintCircuitMonad::lower_to_degree(
1894                        &mut table.initial_constraints,
1895                        info,
1896                    );
1897                    let (new_main_cons, new_aux_cons) = ConstraintCircuitMonad::lower_to_degree(
1898                        &mut table.consistency_constraints,
1899                        info,
1900                    );
1901                    let (new_main_tran, new_aux_tran) = ConstraintCircuitMonad::lower_to_degree(
1902                        &mut table.transition_constraints,
1903                        info,
1904                    );
1905                    let (new_main_term, new_aux_term) = ConstraintCircuitMonad::lower_to_degree(
1906                        &mut table.terminal_constraints,
1907                        info,
1908                    );
1909
1910                    initial_constraints.extend(new_main_init);
1911                    consistency_constraints.extend(new_main_cons);
1912                    transition_constraints.extend(new_main_tran);
1913                    terminal_constraints.extend(new_main_term);
1914
1915                    initial_constraints.extend(new_aux_init);
1916                    consistency_constraints.extend(new_aux_cons);
1917                    transition_constraints.extend(new_aux_tran);
1918                    terminal_constraints.extend(new_aux_term);
1919                }
1920
1921                let table_max_degree = [
1922                    ConstraintCircuitMonad::multicircuit_degree(&initial_constraints),
1923                    ConstraintCircuitMonad::multicircuit_degree(&consistency_constraints),
1924                    ConstraintCircuitMonad::multicircuit_degree(&transition_constraints),
1925                    ConstraintCircuitMonad::multicircuit_degree(&terminal_constraints),
1926                ]
1927                .into_iter()
1928                .max()
1929                .unwrap_or(-1);
1930
1931                let num_init = initial_constraints.len();
1932                let num_cons = consistency_constraints.len();
1933                let num_tran = transition_constraints.len();
1934                let num_term = terminal_constraints.len();
1935
1936                all_initial_constraints.extend(initial_constraints);
1937                all_consistency_constraints.extend(consistency_constraints);
1938                all_transition_constraints.extend(transition_constraints);
1939                all_terminal_constraints.extend(terminal_constraints);
1940
1941                ft = format!(
1942                    "{ft}| {:<46} | {:>8} | {:12} | {:>11} | {:>9} |",
1943                    table.name, num_init, num_cons, num_tran, num_term,
1944                );
1945                if target_degree.is_none() {
1946                    ft = format!("{ft} {table_max_degree:>10} |");
1947                }
1948                ft = format!("{ft}\n");
1949                total_initial += num_init;
1950                total_consistency += num_cons;
1951                total_transition += num_tran;
1952                total_terminal += num_term;
1953                total_max_degree = total_max_degree.max(table_max_degree);
1954            }
1955            ft = format!(
1956                "{ft}| {:<46} | {:>8} | {:>12} | {:>11} | {:>9} |",
1957                "**TOTAL**",
1958                format!("**{total_initial}**"),
1959                format!("**{total_consistency}**"),
1960                format!("**{total_transition}**"),
1961                format!("**{total_terminal}**"),
1962            );
1963            if target_degree.is_none() {
1964                ft = format!("{ft} {:>10} |", format!("**{}**", total_max_degree));
1965            }
1966            ft = format!("{ft}\n");
1967
1968            let num_nodes_in_all_initial_constraints =
1969                ConstraintCircuitMonad::num_visible_nodes(&all_initial_constraints);
1970            let num_nodes_in_all_consistency_constraints =
1971                ConstraintCircuitMonad::num_visible_nodes(&all_consistency_constraints);
1972            let num_nodes_in_all_transition_constraints =
1973                ConstraintCircuitMonad::num_visible_nodes(&all_transition_constraints);
1974            let num_nodes_in_all_terminal_constraints =
1975                ConstraintCircuitMonad::num_visible_nodes(&all_terminal_constraints);
1976            ft = format!(
1977                "{ft}| {:<46} | {:>8} | {:>12} | {:>11} | {:>9} |",
1978                "(# nodes)",
1979                format!("({num_nodes_in_all_initial_constraints})"),
1980                format!("({num_nodes_in_all_consistency_constraints})"),
1981                format!("({num_nodes_in_all_transition_constraints})"),
1982                format!("({num_nodes_in_all_terminal_constraints})"),
1983            );
1984            if target_degree.is_none() {
1985                ft = format!("{ft} {:>10} |", "");
1986            }
1987            ft = format!("{ft}\n");
1988        }
1989
1990        let how_to_update = "<!-- To update, please run `cargo test`. -->";
1991        SpecSnippet {
1992            start_marker: "<!-- auto-gen info start constraints_overview -->",
1993            stop_marker: "<!-- auto-gen info stop constraints_overview -->",
1994            snippet: format!("{how_to_update}\n{ft}"),
1995        }
1996    }
1997
1998    fn generate_tasm_air_evaluation_cost_overview() -> SpecSnippet {
1999        let dummy_static_layout = StaticTasmConstraintEvaluationMemoryLayout {
2000            free_mem_page_ptr: BFieldElement::ZERO,
2001            curr_main_row_ptr: BFieldElement::ZERO,
2002            curr_aux_row_ptr: BFieldElement::ZERO,
2003            next_main_row_ptr: BFieldElement::ZERO,
2004            next_aux_row_ptr: BFieldElement::ZERO,
2005            challenges_ptr: BFieldElement::ZERO,
2006        };
2007        let dummy_dynamic_layout = DynamicTasmConstraintEvaluationMemoryLayout {
2008            free_mem_page_ptr: BFieldElement::ZERO,
2009            challenges_ptr: BFieldElement::ZERO,
2010        };
2011
2012        let static_tasm = static_air_constraint_evaluation_tasm(dummy_static_layout);
2013        let dynamic_tasm = dynamic_air_constraint_evaluation_tasm(dummy_dynamic_layout);
2014
2015        let mut snippet = "\
2016        | Type         | Processor | Op Stack |   RAM |\n\
2017        |:-------------|----------:|---------:|------:|\n"
2018            .to_string();
2019
2020        for (label, tasm) in [("static", static_tasm), ("dynamic", dynamic_tasm)] {
2021            let program = triton_program!({ &tasm });
2022
2023            let processor = program.clone().into_iter().count();
2024            let stack = program
2025                .clone()
2026                .into_iter()
2027                .map(|instruction| instruction.op_stack_size_influence().abs())
2028                .sum::<i32>();
2029
2030            let ram_table_influence = |instruction| match instruction {
2031                Instruction::ReadMem(st) | Instruction::WriteMem(st) => st.num_words(),
2032                Instruction::SpongeAbsorbMem => tip5::RATE,
2033                Instruction::XbDotStep => 4,
2034                Instruction::XxDotStep => 6,
2035                _ => 0,
2036            };
2037            let ram = program
2038                .clone()
2039                .into_iter()
2040                .map(ram_table_influence)
2041                .sum::<usize>();
2042
2043            snippet = format!(
2044                "{snippet}\
2045            | {label:<12} | {processor:>9} | {stack:>8} | {ram:>5} |\n\
2046            "
2047            );
2048        }
2049
2050        SpecSnippet {
2051            start_marker: "<!-- auto-gen info start tasm_air_evaluation_cost -->",
2052            stop_marker: "<!-- auto-gen info stop tasm_air_evaluation_cost -->",
2053            snippet,
2054        }
2055    }
2056
2057    fn generate_opcode_pressure_overview() -> SpecSnippet {
2058        // todo: de-duplicate this from `triton_isa::instruction::tests`
2059        #[derive(Debug, Copy, Clone, EnumCount, EnumIter, VariantNames)]
2060        enum InstructionBucket {
2061            HasArg,
2062            ShrinksStack,
2063            IsU32,
2064        }
2065
2066        impl InstructionBucket {
2067            pub fn contains(self, instruction: Instruction) -> bool {
2068                match self {
2069                    InstructionBucket::HasArg => instruction.arg().is_some(),
2070                    InstructionBucket::ShrinksStack => instruction.op_stack_size_influence() < 0,
2071                    InstructionBucket::IsU32 => instruction.is_u32_instruction(),
2072                }
2073            }
2074
2075            pub fn flag(self) -> usize {
2076                match self {
2077                    InstructionBucket::HasArg => 1,
2078                    InstructionBucket::ShrinksStack => 1 << 1,
2079                    InstructionBucket::IsU32 => 1 << 2,
2080                }
2081            }
2082        }
2083
2084        fn flag_set(instruction: Instruction) -> usize {
2085            InstructionBucket::iter()
2086                .map(|bucket| usize::from(bucket.contains(instruction)) * bucket.flag())
2087                .fold(0, |acc, bit_flag| acc | bit_flag)
2088        }
2089        // todo: end of duplication
2090
2091        const NUM_FLAG_SETS: usize = 1 << InstructionBucket::COUNT;
2092        let mut num_opcodes_per_flag_set = [0; NUM_FLAG_SETS];
2093        for instruction in Instruction::iter() {
2094            num_opcodes_per_flag_set[flag_set(instruction)] += 1;
2095        }
2096
2097        let cell_width = InstructionBucket::VARIANTS
2098            .iter()
2099            .map(|s| s.len())
2100            .max()
2101            .unwrap();
2102        let mut snippet = String::new();
2103        for name in InstructionBucket::VARIANTS.iter().rev() {
2104            let cell_title = format!("| {name:>cell_width$} ");
2105            snippet.push_str(&cell_title);
2106        }
2107        let num_opcodes_title = format!("| {:>cell_width$} |\n", "Num Opcodes");
2108        snippet.push_str(&num_opcodes_title);
2109
2110        let dash = "-";
2111        for _ in 0..=InstructionBucket::COUNT {
2112            let separator = format!("|-{dash:->cell_width$}:");
2113            snippet.push_str(&separator);
2114        }
2115        snippet.push_str("|\n");
2116
2117        for (line_no, num_opcodes) in (0..).zip(num_opcodes_per_flag_set) {
2118            for bucket in InstructionBucket::iter().rev() {
2119                let bucket_active_in_flag_set = if line_no & bucket.flag() > 0 {
2120                    "y"
2121                } else {
2122                    "n"
2123                };
2124                let cell = format!("| {bucket_active_in_flag_set:>cell_width$} ");
2125                snippet.push_str(&cell);
2126            }
2127            let num_opcodes = format!("| {num_opcodes:>cell_width$} |\n");
2128            snippet.push_str(&num_opcodes);
2129        }
2130
2131        let max_opcodes = format!(
2132            "\nMaximum number of opcodes per row is {}.\n",
2133            1 << (InstructionBit::COUNT - InstructionBucket::COUNT)
2134        );
2135        snippet.push_str(&max_opcodes);
2136
2137        SpecSnippet {
2138            start_marker: "<!-- auto-gen info start opcode_pressure -->",
2139            stop_marker: "<!-- auto-gen info stop opcode_pressure -->",
2140            snippet,
2141        }
2142    }
2143
2144    /// intended use: `cargo t print_all_master_table_indices -- --nocapture`
2145    #[test]
2146    fn print_all_master_table_indices() {
2147        macro_rules! print_columns {
2148            (main $table:ident for $name:literal) => {{
2149                for column in $table::iter() {
2150                    let idx = column.master_main_index();
2151                    let name = $name;
2152                    println!("| {idx:>3} | {name:<11} | {column}");
2153                }
2154            }};
2155            (aux $table:ident for $name:literal) => {{
2156                for column in $table::iter() {
2157                    let idx = column.master_aux_index();
2158                    let name = $name;
2159                    println!("| {idx:>3} | {name:<11} | {column}");
2160                }
2161            }};
2162        }
2163
2164        println!();
2165        println!("| idx | table       | main column");
2166        println!("|----:|:------------|:-----------");
2167        print_columns!(main ProgramMainColumn        for "program");
2168        print_columns!(main ProcessorMainColumn      for "processor");
2169        print_columns!(main OpStackMainColumn        for "op stack");
2170        print_columns!(main RamMainColumn            for "ram");
2171        print_columns!(main JumpStackMainColumn      for "jump stack");
2172        print_columns!(main HashMainColumn           for "hash");
2173        print_columns!(main CascadeMainColumn        for "cascade");
2174        print_columns!(main LookupMainColumn         for "lookup");
2175        print_columns!(main U32MainColumn            for "u32");
2176        print_columns!(main DegreeLoweringMainColumn for "degree low.");
2177
2178        println!();
2179        println!("| idx | table       | auxiliary column");
2180        println!("|----:|:------------|:----------------");
2181        print_columns!(aux ProgramAuxColumn          for "program");
2182        print_columns!(aux ProcessorAuxColumn        for "processor");
2183        print_columns!(aux OpStackAuxColumn          for "op stack");
2184        print_columns!(aux RamAuxColumn              for "ram");
2185        print_columns!(aux JumpStackAuxColumn        for "jump stack");
2186        print_columns!(aux HashAuxColumn             for "hash");
2187        print_columns!(aux CascadeAuxColumn          for "cascade");
2188        print_columns!(aux LookupAuxColumn           for "lookup");
2189        print_columns!(aux U32AuxColumn              for "u32");
2190        print_columns!(aux DegreeLoweringAuxColumn   for "degree low.");
2191    }
2192
2193    fn dummy_master_aux_table() -> MasterAuxTable {
2194        let domains = ProverDomains {
2195            trace: ArithmeticDomain::of_length(1 << 8).unwrap(),
2196            randomized_trace: ArithmeticDomain::of_length(1 << 9).unwrap(),
2197            quotient: ArithmeticDomain::of_length(1 << 10).unwrap(),
2198            fri: ArithmeticDomain::of_length(1 << 11).unwrap(),
2199        };
2200        let trace_table = Array2::zeros((domains.trace.length, MasterAuxTable::NUM_COLUMNS));
2201
2202        MasterAuxTable {
2203            num_trace_randomizers: 16,
2204            domains,
2205            trace_table,
2206            trace_randomizer_seed: StdRng::seed_from_u64(5323196155778693784).random(),
2207            low_degree_extended_table: None,
2208        }
2209    }
2210
2211    #[test]
2212    fn master_aux_table_mut() {
2213        let mut master_table = dummy_master_aux_table();
2214
2215        let num_rows = master_table.domains().trace.length;
2216        Array2::from_elem((num_rows, ProgramAuxColumn::COUNT), 1.into())
2217            .move_into(&mut master_table.table_mut(TableId::Program));
2218        Array2::from_elem((num_rows, ProcessorAuxColumn::COUNT), 2.into())
2219            .move_into(&mut master_table.table_mut(TableId::Processor));
2220        Array2::from_elem((num_rows, OpStackAuxColumn::COUNT), 3.into())
2221            .move_into(&mut master_table.table_mut(TableId::OpStack));
2222        Array2::from_elem((num_rows, RamAuxColumn::COUNT), 4.into())
2223            .move_into(&mut master_table.table_mut(TableId::Ram));
2224        Array2::from_elem((num_rows, JumpStackAuxColumn::COUNT), 5.into())
2225            .move_into(&mut master_table.table_mut(TableId::JumpStack));
2226        Array2::from_elem((num_rows, HashAuxColumn::COUNT), 6.into())
2227            .move_into(&mut master_table.table_mut(TableId::Hash));
2228        Array2::from_elem((num_rows, CascadeAuxColumn::COUNT), 7.into())
2229            .move_into(&mut master_table.table_mut(TableId::Cascade));
2230        Array2::from_elem((num_rows, LookupAuxColumn::COUNT), 8.into())
2231            .move_into(&mut master_table.table_mut(TableId::Lookup));
2232        Array2::from_elem((num_rows, U32AuxColumn::COUNT), 9.into())
2233            .move_into(&mut master_table.table_mut(TableId::U32));
2234
2235        let trace_domain_element = |column| {
2236            let maybe_element = master_table.trace_table.get((0, column));
2237            let xfe = maybe_element.unwrap().to_owned();
2238            xfe.unlift().unwrap().value()
2239        };
2240
2241        assert_eq!(1, trace_domain_element(AUX_PROGRAM_TABLE_START));
2242        assert_eq!(2, trace_domain_element(AUX_PROCESSOR_TABLE_START));
2243        assert_eq!(3, trace_domain_element(AUX_OP_STACK_TABLE_START));
2244        assert_eq!(4, trace_domain_element(AUX_RAM_TABLE_START));
2245        assert_eq!(5, trace_domain_element(AUX_JUMP_STACK_TABLE_START));
2246        assert_eq!(6, trace_domain_element(AUX_HASH_TABLE_START));
2247        assert_eq!(7, trace_domain_element(AUX_CASCADE_TABLE_START));
2248        assert_eq!(8, trace_domain_element(AUX_LOOKUP_TABLE_START));
2249        assert_eq!(9, trace_domain_element(AUX_U32_TABLE_START));
2250    }
2251
2252    #[proptest]
2253    fn sponge_with_pending_absorb_is_equivalent_to_usual_sponge(
2254        #[strategy(arb())] elements: Vec<BFieldElement>,
2255        #[strategy(0_usize..=#elements.len())] substring_index: usize,
2256    ) {
2257        let (substring_0, substring_1) = elements.split_at(substring_index);
2258        let mut sponge = SpongeWithPendingAbsorb::new();
2259        sponge.absorb(substring_0);
2260        sponge.absorb(substring_1);
2261        let pending_absorb_digest = sponge.finalize();
2262
2263        let expected_digest = Tip5::hash_varlen(&elements);
2264        prop_assert_eq!(expected_digest, pending_absorb_digest);
2265    }
2266
2267    /// Test whether the AIR constraint evaluators are the same between
2268    ///  (a) the time when this test was written or last updated; and
2269    ///  (b) the time when the test is being executed.
2270    ///
2271    /// This test catches (with high probability) unintended changes, whether
2272    /// due to nondeterminisms (on a single machine or across various
2273    /// machines) or due to changes to the definitions of the constraints.
2274    /// If the change to the constraints was intentional, this test should
2275    /// be updated.
2276    ///
2277    /// This test might fail in the course of CI for a pull request, if in the
2278    /// meantime the constraints are modified on master. In this case, rebasing
2279    /// the topic branch on top of master is recommended.
2280    #[test]
2281    fn air_constraints_evaluators_have_not_changed() {
2282        let mut rng = StdRng::seed_from_u64(3508729174085202315);
2283
2284        // pseudorandomly populate circuit inputs
2285        let main_row_current_base = Array1::from(rng.random::<MainRow<BFieldElement>>().to_vec());
2286        let main_row_current_ext = Array1::from(rng.random::<MainRow<XFieldElement>>().to_vec());
2287        let aux_row_current = Array1::from(rng.random::<AuxiliaryRow>().to_vec());
2288        let main_row_next_base = Array1::from(rng.random::<MainRow<BFieldElement>>().to_vec());
2289        let main_row_next_ext = Array1::from(rng.random::<MainRow<XFieldElement>>().to_vec());
2290        let aux_row_next = Array1::from(rng.random::<AuxiliaryRow>().to_vec());
2291        let challenges = Challenges {
2292            challenges: rng.random(),
2293        };
2294
2295        // invoke all possible AIR circuit evaluators
2296        let initial_base = MasterAuxTable::evaluate_initial_constraints(
2297            main_row_current_base.view(),
2298            aux_row_current.view(),
2299            &challenges,
2300        );
2301        let initial_extension = MasterAuxTable::evaluate_initial_constraints(
2302            main_row_current_ext.view(),
2303            aux_row_current.view(),
2304            &challenges,
2305        );
2306        let consistency_base = MasterAuxTable::evaluate_consistency_constraints(
2307            main_row_current_base.view(),
2308            aux_row_current.view(),
2309            &challenges,
2310        );
2311        let consistency_extension = MasterAuxTable::evaluate_consistency_constraints(
2312            main_row_current_ext.view(),
2313            aux_row_current.view(),
2314            &challenges,
2315        );
2316        let transition_base = MasterAuxTable::evaluate_transition_constraints(
2317            main_row_current_base.view(),
2318            aux_row_current.view(),
2319            main_row_next_base.view(),
2320            aux_row_next.view(),
2321            &challenges,
2322        );
2323        let transition_extension = MasterAuxTable::evaluate_transition_constraints(
2324            main_row_current_ext.view(),
2325            aux_row_current.view(),
2326            main_row_next_ext.view(),
2327            aux_row_next.view(),
2328            &challenges,
2329        );
2330        let terminal_base = MasterAuxTable::evaluate_terminal_constraints(
2331            main_row_current_base.view(),
2332            aux_row_current.view(),
2333            &challenges,
2334        );
2335        let terminal_extension = MasterAuxTable::evaluate_terminal_constraints(
2336            main_row_current_ext.view(),
2337            aux_row_current.view(),
2338            &challenges,
2339        );
2340
2341        // interpret result as coefficient vector of polynomial
2342        let coefficients = [
2343            initial_base,
2344            initial_extension,
2345            consistency_base,
2346            consistency_extension,
2347            transition_base,
2348            transition_extension,
2349            terminal_base,
2350            terminal_extension,
2351        ]
2352        .concat();
2353        let polynomial = Polynomial::new(coefficients);
2354
2355        // evaluate polynomial in pseudorandom indeterminate
2356        let value = polynomial.evaluate(rng.random::<XFieldElement>());
2357        let expected = xfe!([
2358            5298563950139081492_u64,
2359            1414892817496408712_u64,
2360            2288680969693784735_u64,
2361        ]);
2362        assert_eq!(
2363            expected, value,
2364            "expected: {expected}\nobserved: {value}\n\
2365            If there was an intentional change to the constraints, don't forget to \
2366            update the value of `expected`."
2367        );
2368    }
2369
2370    /// Verify for a dummy trace table that the trace randomizer for every pair
2371    /// of columns have large Hamming distances. If this test fails, then the
2372    /// random number generator is not cryptographically secure or is misused
2373    /// somehow.
2374    #[test]
2375    fn trace_randomizers_have_large_hamming_distances() {
2376        let aux_table = dummy_master_aux_table();
2377
2378        // It is a priori possible that the first few coefficients are
2379        // correlated but then the latter coefficients are independent. We do
2380        // not want the latter coefficients to mask a far-from-random signal. So
2381        // we look at the first `num_coefficients`-many coefficients only.
2382        // This parameter must lie in 1..=aux_table.num_trace_randomizers.
2383        let num_coefficients = 1;
2384
2385        // Binomial distribution with
2386        // n = total number of bits
2387        // p = q = 1/2
2388        let n = num_coefficients * EXTENSION_DEGREE * BFieldElement::BYTES * 8;
2389        let mean = n / 2;
2390        let variance = n / 4;
2391        let stddev = (variance as f64).sqrt();
2392        // four-sigma rule: four nines certainty
2393        let threshold = (mean as f64) - 4.0 * stddev;
2394
2395        for i in 0..MasterAuxTable::NUM_COLUMNS {
2396            let randomizer_i = aux_table.trace_randomizer_for_column(i);
2397            for j in i + 1..MasterAuxTable::NUM_COLUMNS {
2398                let randomizer_j = aux_table.trace_randomizer_for_column(j);
2399
2400                let first_few_coefficients = |poly: &Polynomial<XFieldElement>| {
2401                    poly.coefficients()
2402                        .iter()
2403                        .take(num_coefficients)
2404                        .flat_map(|xfe| xfe.coefficients)
2405                        .map(|bfe| bfe.value())
2406                        .collect_vec()
2407                        .into_iter()
2408                };
2409
2410                let distance = first_few_coefficients(&randomizer_i)
2411                    .zip_eq(first_few_coefficients(&randomizer_j))
2412                    .map(|(lhs, rhs)| lhs ^ rhs)
2413                    .map(|u| u.count_ones())
2414                    .sum::<u32>();
2415
2416                assert!(
2417                    f64::from(distance) > threshold,
2418                    "distance: {distance}\nthreshold: {threshold}"
2419                );
2420            }
2421        }
2422    }
2423}