1use 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
167pub(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 + 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 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 fn trace_table(&self) -> ArrayView2<'_, Self::Field>;
226
227 fn trace_table_mut(&mut self) -> ArrayViewMut2<'_, Self::Field>;
230
231 fn quotient_domain_table(&self) -> Option<ArrayView2<'_, Self::Field>>;
243
244 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 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 #[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 fn fri_domain_table(&self) -> Option<ArrayView2<'_, Self::Field>>;
334
335 fn out_of_domain_row(&self, indeterminate: XFieldElement) -> Array1<XFieldElement> {
339 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 fn trace_randomizer_for_column(&self, idx: usize) -> Polynomial<'static, Self::Field> {
411 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 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 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 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 fn reveal_rows(&self, row_indices: &[usize]) -> Vec<Vec<Self::Field>> {
535 if let Some(fri_domain_table) = self.fri_domain_table() {
536 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 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 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 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 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
598fn 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 debug_assert!(offset_le_bytes.as_ref().len() <= seed.len());
608
609 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#[derive(Clone)]
624struct SpongeWithPendingAbsorb {
625 sponge: Tip5,
626
627 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 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 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#[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#[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 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 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 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 master_main_table
889 }
890
891 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 pub fn extend(&self, challenges: &Challenges) -> MasterAuxTable {
965 let mut rng = rng_from_offset_seed(self.trace_randomizer_seed(), Self::NUM_COLUMNS);
968
969 profiler!(start "initialize master table");
970 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 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 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 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 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 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 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
1208pub 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 "ient_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 "ient_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 "ient_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 "ient_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 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 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 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 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 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 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 #[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 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 #[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]
2281 fn air_constraints_evaluators_have_not_changed() {
2282 let mut rng = StdRng::seed_from_u64(3508729174085202315);
2283
2284 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 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 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 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 #[test]
2375 fn trace_randomizers_have_large_hamming_distances() {
2376 let aux_table = dummy_master_aux_table();
2377
2378 let num_coefficients = 1;
2384
2385 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 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}