Skip to main content

triton_vm/
table.rs

1use air::AIR;
2use ndarray::ArrayView2;
3use ndarray::ArrayViewMut2;
4use strum::Display;
5use strum::EnumCount;
6use strum::EnumIter;
7use twenty_first::prelude::*;
8
9use crate::aet::AlgebraicExecutionTrace;
10use crate::challenges::Challenges;
11pub use crate::stark::NUM_QUOTIENT_SEGMENTS;
12use crate::table::master_table::MasterAuxTable;
13use crate::table::master_table::MasterMainTable;
14
15pub mod auxiliary_table;
16pub mod cascade;
17pub mod degree_lowering;
18pub mod hash;
19pub mod jump_stack;
20pub mod lookup;
21pub mod master_table;
22pub mod op_stack;
23pub mod processor;
24pub mod program;
25pub mod ram;
26pub mod u32;
27
28trait TraceTable: AIR {
29    // a nicer design is in order
30    type FillParam;
31    type FillReturnInfo;
32
33    fn fill(
34        main_table: ArrayViewMut2<BFieldElement>,
35        aet: &AlgebraicExecutionTrace,
36        _: Self::FillParam,
37    ) -> Self::FillReturnInfo;
38
39    fn pad(main_table: ArrayViewMut2<BFieldElement>, table_length: usize);
40
41    fn extend(
42        main_table: ArrayView2<BFieldElement>,
43        aux_table: ArrayViewMut2<XFieldElement>,
44        challenges: &Challenges,
45    );
46}
47
48#[derive(
49    Debug, Display, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, EnumCount, EnumIter,
50)]
51pub enum ConstraintType {
52    /// Pertains only to the first row of the execution trace.
53    Initial,
54
55    /// Pertains to each row of the execution trace.
56    Consistency,
57
58    /// Pertains to each pair of consecutive rows of the execution trace.
59    Transition,
60
61    /// Pertains only to the last row of the execution trace.
62    Terminal,
63}
64
65/// A single row of a [`MasterMainTable`].
66///
67/// Usually, the elements in the table are [`BFieldElement`]s. For out-of-domain
68/// rows, which is relevant for “Domain Extension to Eliminate Pretenders”
69/// (DEEP), the elements are [`XFieldElement`]s.
70pub type MainRow<T> = [T; MasterMainTable::NUM_COLUMNS];
71
72/// A single row of a [`MasterAuxTable`].
73pub type AuxiliaryRow = [XFieldElement; MasterAuxTable::NUM_COLUMNS];
74
75/// An element of the split-up quotient polynomial.
76///
77/// See also [`NUM_QUOTIENT_SEGMENTS`].
78pub type QuotientSegments = [XFieldElement; NUM_QUOTIENT_SEGMENTS];
79
80#[cfg(test)]
81#[cfg_attr(coverage_nightly, coverage(off))]
82mod tests {
83    use std::collections::HashMap;
84
85    use air::table::AUX_CASCADE_TABLE_END;
86    use air::table::AUX_HASH_TABLE_END;
87    use air::table::AUX_JUMP_STACK_TABLE_END;
88    use air::table::AUX_LOOKUP_TABLE_END;
89    use air::table::AUX_OP_STACK_TABLE_END;
90    use air::table::AUX_PROCESSOR_TABLE_END;
91    use air::table::AUX_PROGRAM_TABLE_END;
92    use air::table::AUX_RAM_TABLE_END;
93    use air::table::AUX_U32_TABLE_END;
94    use air::table::CASCADE_TABLE_END;
95    use air::table::HASH_TABLE_END;
96    use air::table::JUMP_STACK_TABLE_END;
97    use air::table::LOOKUP_TABLE_END;
98    use air::table::OP_STACK_TABLE_END;
99    use air::table::PROCESSOR_TABLE_END;
100    use air::table::PROGRAM_TABLE_END;
101    use air::table::RAM_TABLE_END;
102    use air::table::U32_TABLE_END;
103    use air::table::cascade::CascadeTable;
104    use air::table::hash::HashTable;
105    use air::table::jump_stack::JumpStackTable;
106    use air::table::lookup::LookupTable;
107    use air::table::op_stack::OpStackTable;
108    use air::table::processor::ProcessorTable;
109    use air::table::program::ProgramTable;
110    use air::table::ram::RamTable;
111    use air::table::u32::U32Table;
112    use constraint_circuit::BinOp;
113    use constraint_circuit::CircuitExpression;
114    use constraint_circuit::ConstraintCircuit;
115    use constraint_circuit::ConstraintCircuitBuilder;
116    use constraint_circuit::ConstraintCircuitMonad;
117    use constraint_circuit::DegreeLoweringInfo;
118    use constraint_circuit::InputIndicator;
119    use itertools::Itertools;
120    use ndarray::Array2;
121    use ndarray::ArrayView2;
122    use rand::prelude::*;
123    use rand::random;
124
125    use crate::challenges::Challenges;
126    use crate::prelude::Claim;
127    use crate::table::degree_lowering::DegreeLoweringTable;
128    use crate::tests::test;
129
130    use super::*;
131
132    /// Verify that all nodes evaluate to a unique value when given a randomized
133    /// input. If this is not the case two nodes that are not equal evaluate
134    /// to the same value.
135    fn table_constraints_prop<II: InputIndicator>(
136        constraints: &[ConstraintCircuit<II>],
137        table_name: &str,
138    ) {
139        let seed = random();
140        let mut rng = StdRng::seed_from_u64(seed);
141        println!("seed: {seed}");
142
143        let dummy_claim = Claim::default();
144        let challenges: [XFieldElement; Challenges::SAMPLE_COUNT] = rng.random();
145        let challenges = challenges.to_vec();
146        let challenges = Challenges::new(challenges, &dummy_claim);
147        let challenges = &challenges.challenges;
148
149        let num_rows = 2;
150        let main_shape = [num_rows, MasterMainTable::NUM_COLUMNS];
151        let aux_shape = [num_rows, MasterAuxTable::NUM_COLUMNS];
152        let main_rows = Array2::from_shape_simple_fn(main_shape, || rng.random::<BFieldElement>());
153        let aux_rows = Array2::from_shape_simple_fn(aux_shape, || rng.random::<XFieldElement>());
154        let main_rows = main_rows.view();
155        let aux_rows = aux_rows.view();
156
157        let mut values = HashMap::new();
158        for c in constraints {
159            evaluate_assert_unique(c, challenges, main_rows, aux_rows, &mut values);
160        }
161
162        let circuit_degree = constraints.iter().map(|c| c.degree()).max().unwrap_or(-1);
163        println!("Max degree constraint for {table_name} table: {circuit_degree}");
164    }
165
166    /// Recursively evaluates the given constraint circuit and its sub-circuits
167    /// on the given main and auxiliary table, and returns the result of the
168    /// evaluation. At each recursive step, updates the given HashMap with
169    /// the result of the evaluation. If the HashMap already contains the
170    /// result of the evaluation, panics. This function is used to assert
171    /// that the evaluation of a constraint circuit and its sub-circuits is
172    /// unique. It is used to identify redundant constraints or
173    /// sub-circuits. The employed method is the Schwartz-Zippel lemma.
174    fn evaluate_assert_unique<II: InputIndicator>(
175        constraint: &ConstraintCircuit<II>,
176        challenges: &[XFieldElement],
177        main_rows: ArrayView2<BFieldElement>,
178        aux_rows: ArrayView2<XFieldElement>,
179        values: &mut HashMap<XFieldElement, (u64, ConstraintCircuit<II>)>,
180    ) -> XFieldElement {
181        let value = match &constraint.expression {
182            CircuitExpression::BinOp(binop, lhs, rhs) => {
183                let lhs = lhs.borrow();
184                let rhs = rhs.borrow();
185                let lhs = evaluate_assert_unique(&lhs, challenges, main_rows, aux_rows, values);
186                let rhs = evaluate_assert_unique(&rhs, challenges, main_rows, aux_rows, values);
187                binop.operation(lhs, rhs)
188            }
189            _ => constraint.evaluate(main_rows, aux_rows, challenges),
190        };
191
192        let own_id = constraint.id.to_owned();
193        let maybe_entry = values.insert(value, (own_id, constraint.clone()));
194        if let Some((other_id, other_circuit)) = maybe_entry {
195            assert_eq!(
196                own_id, other_id,
197                "Circuit ID {other_id} and circuit ID {own_id} are not unique. \
198                Collision on:\n\
199                ID {other_id} – {other_circuit}\n\
200                ID {own_id} – {constraint}\n\
201                Both evaluate to {value}.",
202            );
203        }
204
205        value
206    }
207
208    #[macro_rules_attr::apply(test)]
209    fn nodes_are_unique_for_all_constraints() {
210        fn build_constraints<II: InputIndicator>(
211            multicircuit_builder: &dyn Fn(
212                &ConstraintCircuitBuilder<II>,
213            ) -> Vec<ConstraintCircuitMonad<II>>,
214        ) -> Vec<ConstraintCircuit<II>> {
215            let circuit_builder = ConstraintCircuitBuilder::new();
216            let multicircuit = multicircuit_builder(&circuit_builder);
217            let mut constraints = multicircuit.into_iter().map(|c| c.consume()).collect_vec();
218            ConstraintCircuit::assert_unique_ids(&mut constraints);
219            constraints
220        }
221
222        macro_rules! assert_constraint_properties {
223            ($table:ident) => {{
224                let init = build_constraints(&$table::initial_constraints);
225                let cons = build_constraints(&$table::consistency_constraints);
226                let tran = build_constraints(&$table::transition_constraints);
227                let term = build_constraints(&$table::terminal_constraints);
228                table_constraints_prop(&init, concat!(stringify!($table), " init"));
229                table_constraints_prop(&cons, concat!(stringify!($table), " cons"));
230                table_constraints_prop(&tran, concat!(stringify!($table), " tran"));
231                table_constraints_prop(&term, concat!(stringify!($table), " term"));
232            }};
233        }
234
235        assert_constraint_properties!(ProcessorTable);
236        assert_constraint_properties!(ProgramTable);
237        assert_constraint_properties!(JumpStackTable);
238        assert_constraint_properties!(OpStackTable);
239        assert_constraint_properties!(RamTable);
240        assert_constraint_properties!(HashTable);
241        assert_constraint_properties!(U32Table);
242        assert_constraint_properties!(CascadeTable);
243        assert_constraint_properties!(LookupTable);
244    }
245
246    /// Like [`ConstraintCircuitMonad::lower_to_degree`] with additional
247    /// assertion of expected properties. Also prints:
248    /// - the given multicircuit prior to degree lowering
249    /// - the multicircuit after degree lowering
250    /// - the new base constraints
251    /// - the new auxiliary constraints
252    /// - the numbers of original and new constraints
253    fn lower_degree_and_assert_properties<II: InputIndicator>(
254        multicircuit: &mut [ConstraintCircuitMonad<II>],
255        info: DegreeLoweringInfo,
256    ) -> (
257        Vec<ConstraintCircuitMonad<II>>,
258        Vec<ConstraintCircuitMonad<II>>,
259    ) {
260        let seed = random();
261        let mut rng = StdRng::seed_from_u64(seed);
262        println!("seed: {seed}");
263
264        let num_constraints = multicircuit.len();
265        println!("original multicircuit:");
266        for circuit in multicircuit.iter() {
267            println!("  {circuit}");
268        }
269
270        let (new_main_constraints, new_aux_constraints) =
271            ConstraintCircuitMonad::lower_to_degree(multicircuit, info);
272
273        assert_eq!(num_constraints, multicircuit.len());
274
275        let target_deg = info.target_degree;
276        assert!(ConstraintCircuitMonad::multicircuit_degree(multicircuit) <= target_deg);
277        assert!(ConstraintCircuitMonad::multicircuit_degree(&new_main_constraints) <= target_deg);
278        assert!(ConstraintCircuitMonad::multicircuit_degree(&new_aux_constraints) <= target_deg);
279
280        // Check that the new constraints are simple substitutions.
281        let mut substitution_rules = vec![];
282        for (constraint_type, constraints) in [
283            ("main", &new_main_constraints),
284            ("aux", &new_aux_constraints),
285        ] {
286            for (i, constraint) in constraints.iter().enumerate() {
287                let expression = constraint.circuit.borrow().expression.clone();
288                let CircuitExpression::BinOp(BinOp::Add, lhs, rhs) = expression else {
289                    panic!("New {constraint_type} constraint {i} must be a subtraction.");
290                };
291                let CircuitExpression::Input(input_indicator) = lhs.borrow().expression.clone()
292                else {
293                    panic!("New {constraint_type} constraint {i} must be a simple substitution.");
294                };
295                let substitution_rule = rhs.borrow().clone();
296                assert_substitution_rule_uses_legal_variables(input_indicator, &substitution_rule);
297                substitution_rules.push(substitution_rule);
298            }
299        }
300
301        // Use the Schwartz-Zippel lemma to check that no two substitution rules
302        // are equal.
303        let dummy_claim = Claim::default();
304        let challenges: [XFieldElement; Challenges::SAMPLE_COUNT] = rng.random();
305        let challenges = challenges.to_vec();
306        let challenges = Challenges::new(challenges, &dummy_claim);
307        let challenges = &challenges.challenges;
308
309        let num_rows = 2;
310        let main_shape = [num_rows, MasterMainTable::NUM_COLUMNS];
311        let aux_shape = [num_rows, MasterAuxTable::NUM_COLUMNS];
312        let main_rows = Array2::from_shape_simple_fn(main_shape, || rng.random::<BFieldElement>());
313        let aux_rows = Array2::from_shape_simple_fn(aux_shape, || rng.random::<XFieldElement>());
314        let main_rows = main_rows.view();
315        let aux_rows = aux_rows.view();
316
317        let evaluated_substitution_rules = substitution_rules
318            .iter()
319            .map(|c| c.evaluate(main_rows, aux_rows, challenges));
320
321        let mut values_to_index = HashMap::new();
322        for (idx, value) in evaluated_substitution_rules.enumerate() {
323            if let Some(index) = values_to_index.get(&value) {
324                panic!("Substitution {idx} must be distinct from substitution {index}.");
325            } else {
326                values_to_index.insert(value, idx);
327            }
328        }
329
330        // Print the multicircuit and new constraints after degree lowering.
331        println!("new multicircuit:");
332        for circuit in multicircuit.iter() {
333            println!("  {circuit}");
334        }
335        println!("new main constraints:");
336        for constraint in &new_main_constraints {
337            println!("  {constraint}");
338        }
339        println!("new aux constraints:");
340        for constraint in &new_aux_constraints {
341            println!("  {constraint}");
342        }
343
344        let num_new_main_constraints = new_main_constraints.len();
345        let num_new_aux_constraints = new_aux_constraints.len();
346        println!(
347            "Started with {num_constraints} constraints. \
348            Derived {num_new_main_constraints} new main, \
349            {num_new_aux_constraints} new auxiliary constraints."
350        );
351
352        (new_main_constraints, new_aux_constraints)
353    }
354
355    /// Panics if the given substitution rule uses variables with an index
356    /// greater than (or equal) to the given index. In practice, this given
357    /// index corresponds to a newly introduced variable.
358    fn assert_substitution_rule_uses_legal_variables<II: InputIndicator>(
359        new_var: II,
360        substitution_rule: &ConstraintCircuit<II>,
361    ) {
362        match substitution_rule.expression.clone() {
363            CircuitExpression::BinOp(_, lhs, rhs) => {
364                let lhs = lhs.borrow();
365                let rhs = rhs.borrow();
366                assert_substitution_rule_uses_legal_variables(new_var, &lhs);
367                assert_substitution_rule_uses_legal_variables(new_var, &rhs);
368            }
369            CircuitExpression::Input(old_var) => {
370                let new_var_is_main = new_var.is_main_table_column();
371                let old_var_is_main = old_var.is_main_table_column();
372                let legal_substitute = match (new_var_is_main, old_var_is_main) {
373                    (true, false) => false,
374                    (false, true) => true,
375                    _ => old_var.column() < new_var.column(),
376                };
377                assert!(legal_substitute, "Cannot replace {old_var} with {new_var}.");
378            }
379            _ => (),
380        };
381    }
382
383    #[macro_rules_attr::apply(test)]
384    fn degree_lowering_works_correctly_for_all_tables() {
385        macro_rules! assert_degree_lowering {
386            ($table:ident ($main_end:ident, $aux_end:ident)) => {{
387                let degree_lowering_info = DegreeLoweringInfo {
388                    target_degree: air::TARGET_DEGREE,
389                    num_main_cols: $main_end,
390                    num_aux_cols: $aux_end,
391                };
392                let circuit_builder = ConstraintCircuitBuilder::new();
393                let mut init = $table::initial_constraints(&circuit_builder);
394                lower_degree_and_assert_properties(&mut init, degree_lowering_info);
395
396                let circuit_builder = ConstraintCircuitBuilder::new();
397                let mut cons = $table::consistency_constraints(&circuit_builder);
398                lower_degree_and_assert_properties(&mut cons, degree_lowering_info);
399
400                let circuit_builder = ConstraintCircuitBuilder::new();
401                let mut tran = $table::transition_constraints(&circuit_builder);
402                lower_degree_and_assert_properties(&mut tran, degree_lowering_info);
403
404                let circuit_builder = ConstraintCircuitBuilder::new();
405                let mut term = $table::terminal_constraints(&circuit_builder);
406                lower_degree_and_assert_properties(&mut term, degree_lowering_info);
407            }};
408        }
409
410        assert_degree_lowering!(ProgramTable(PROGRAM_TABLE_END, AUX_PROGRAM_TABLE_END));
411        assert_degree_lowering!(ProcessorTable(PROCESSOR_TABLE_END, AUX_PROCESSOR_TABLE_END));
412        assert_degree_lowering!(OpStackTable(OP_STACK_TABLE_END, AUX_OP_STACK_TABLE_END));
413        assert_degree_lowering!(RamTable(RAM_TABLE_END, AUX_RAM_TABLE_END));
414        assert_degree_lowering!(JumpStackTable(
415            JUMP_STACK_TABLE_END,
416            AUX_JUMP_STACK_TABLE_END
417        ));
418        assert_degree_lowering!(HashTable(HASH_TABLE_END, AUX_HASH_TABLE_END));
419        assert_degree_lowering!(CascadeTable(CASCADE_TABLE_END, AUX_CASCADE_TABLE_END));
420        assert_degree_lowering!(LookupTable(LOOKUP_TABLE_END, AUX_LOOKUP_TABLE_END));
421        assert_degree_lowering!(U32Table(U32_TABLE_END, AUX_U32_TABLE_END));
422    }
423
424    /// Fills the derived columns of the degree-lowering table using randomly
425    /// generated rows and checks the resulting values for uniqueness. The
426    /// described method corresponds to an application of the
427    /// Schwartz-Zippel lemma to check uniqueness of the substitution rules
428    /// generated during degree lowering.
429    #[macro_rules_attr::apply(test)]
430    #[ignore = "(probably) requires normalization of circuit expressions"]
431    fn substitution_rules_are_unique() {
432        let challenges = Challenges::default();
433        let mut main_table_rows =
434            Array2::from_shape_fn((2, MasterMainTable::NUM_COLUMNS), |_| random());
435        let mut aux_table_rows =
436            Array2::from_shape_fn((2, MasterAuxTable::NUM_COLUMNS), |_| random());
437
438        DegreeLoweringTable::fill_derived_main_columns(main_table_rows.view_mut());
439        DegreeLoweringTable::fill_derived_aux_columns(
440            main_table_rows.view(),
441            aux_table_rows.view_mut(),
442            &challenges,
443        );
444
445        let mut encountered_values = HashMap::new();
446        for col_idx in 0..MasterMainTable::NUM_COLUMNS {
447            let val = main_table_rows[(0, col_idx)].lift();
448            let other_entry = encountered_values.insert(val, col_idx);
449            if let Some(other_idx) = other_entry {
450                panic!("Duplicate value {val} in derived main column {other_idx} and {col_idx}.");
451            }
452        }
453        println!("Now comparing auxiliary columns…");
454        for col_idx in 0..MasterAuxTable::NUM_COLUMNS {
455            let val = aux_table_rows[(0, col_idx)];
456            let other_entry = encountered_values.insert(val, col_idx);
457            if let Some(other_idx) = other_entry {
458                panic!("Duplicate value {val} in derived aux column {other_idx} and {col_idx}.");
459            }
460        }
461    }
462}