diff --git a/constraint-evaluation-generator/src/main.rs b/constraint-evaluation-generator/src/main.rs index c084554a2..30802c2cd 100644 --- a/constraint-evaluation-generator/src/main.rs +++ b/constraint-evaluation-generator/src/main.rs @@ -6,9 +6,9 @@ use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::x_field_element::XFieldElement; use triton_vm::table::challenges::TableChallenges; -use triton_vm::table::constraint_circuit::{ - CircuitExpression, CircuitId, ConstraintCircuit, InputIndicator, -}; +use triton_vm::table::constraint_circuit::CircuitExpression; +use triton_vm::table::constraint_circuit::ConstraintCircuit; +use triton_vm::table::constraint_circuit::InputIndicator; use triton_vm::table::hash_table::ExtHashTable; use triton_vm::table::instruction_table::ExtInstructionTable; use triton_vm::table::jump_stack_table::ExtJumpStackTable; @@ -135,6 +135,11 @@ fn gen( let challenge_enum_name = format!("{table_id_name}ChallengeId"); let table_mod_name = format!("Ext{table_id_name}"); + let num_initial_constraints = initial_constraint_circuits.len(); + let num_consistency_constraints = consistency_constraint_circuits.len(); + let num_transition_constraints = transition_constraint_circuits.len(); + let num_terminal_constraints = terminal_constraint_circuits.len(); + let initial_constraints_degrees = turn_circuits_into_degree_bounds_string(initial_constraint_circuits); let consistency_constraints_degrees = @@ -149,44 +154,17 @@ fn gen( let transition_constraint_strings = turn_circuits_into_string(transition_constraint_circuits); let terminal_constraint_strings = turn_circuits_into_string(terminal_constraint_circuits); - // maybe-prefixes to supress clippy's warnings for unused variables - let initial_challenges_used = if initial_constraint_strings.contains("challenges") { - "" - } else { - "_" - }; - let consistency_challenges_used = if consistency_constraint_strings.contains("challenges") { - "" - } else { - "_" - }; - let terminal_challenges_used = if terminal_constraint_strings.contains("challenges") { - "" - } else { - "_" - }; - let consistency_constraints_exist = if consistency_constraints_degrees.is_empty() { - "_" - } else { - "" - }; - let terminal_constraints_exist = if terminal_constraints_degrees.is_empty() { - "_" - } else { - "" - }; - format!( " +use ndarray::ArrayView1; +use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::mpolynomial::Degree; use twenty_first::shared_math::x_field_element::XFieldElement; -use twenty_first::shared_math::b_field_element::BFieldElement; use crate::table::challenges::AllChallenges; use crate::table::challenges::TableChallenges; use crate::table::extension_table::Evaluable; use crate::table::extension_table::Quotientable; -use crate::table::table_collection::interpolant_degree; use crate::table::{table_name_snake}::{table_mod_name}; use crate::table::{table_name_snake}::{challenge_enum_name}::*; @@ -195,30 +173,34 @@ use crate::table::{table_name_snake}::{challenge_enum_name}::*; // `cargo run --bin constraint-evaluation-generator` impl Evaluable for {table_mod_name} {{ #[inline] + #[allow(unused_variables)] fn evaluate_initial_constraints( - &self, - row: &[XFieldElement], + base_row: ArrayView1, + ext_row: ArrayView1, challenges: &AllChallenges, ) -> Vec {{ - let {initial_challenges_used}challenges = &challenges.{table_name_snake}_challenges; + let challenges = &challenges.{table_name_snake}_challenges; {initial_constraint_strings} }} #[inline] + #[allow(unused_variables)] fn evaluate_consistency_constraints( - &self, - {consistency_constraints_exist}row: &[XFieldElement], + base_row: ArrayView1, + ext_row: ArrayView1, challenges: &AllChallenges, ) -> Vec {{ - let {consistency_challenges_used}challenges = &challenges.{table_name_snake}_challenges; + let challenges = &challenges.{table_name_snake}_challenges; {consistency_constraint_strings} }} #[inline] + #[allow(unused_variables)] fn evaluate_transition_constraints( - &self, - current_row: &[XFieldElement], - next_row: &[XFieldElement], + current_base_row: ArrayView1, + current_ext_row: ArrayView1, + next_base_row: ArrayView1, + next_ext_row: ArrayView1, challenges: &AllChallenges, ) -> Vec {{ let challenges = &challenges.{table_name_snake}_challenges; @@ -226,56 +208,65 @@ impl Evaluable for {table_mod_name} {{ }} #[inline] + #[allow(unused_variables)] fn evaluate_terminal_constraints( - &self, - {terminal_constraints_exist}row: &[XFieldElement], + base_row: ArrayView1, + ext_row: ArrayView1, challenges: &AllChallenges, ) -> Vec {{ - let {terminal_challenges_used}challenges = &challenges.{table_name_snake}_challenges; + let challenges = &challenges.{table_name_snake}_challenges; {terminal_constraint_strings} }} }} impl Quotientable for {table_mod_name} {{ - fn get_initial_quotient_degree_bounds( - &self, - padded_height: usize, - num_trace_randomizers: usize, + fn num_initial_quotients() -> usize {{ + {num_initial_constraints} + }} + + fn num_consistency_quotients() -> usize {{ + {num_consistency_constraints} + }} + + fn num_transition_quotients() -> usize {{ + {num_transition_constraints} + }} + + fn num_terminal_quotients() -> usize {{ + {num_terminal_constraints} + }} + + #[allow(unused_variables)] + fn initial_quotient_degree_bounds( + interpolant_degree: Degree, ) -> Vec {{ let zerofier_degree = 1 as Degree; - let interpolant_degree = interpolant_degree(padded_height, num_trace_randomizers); [{initial_constraints_degrees}].to_vec() }} - fn get_consistency_quotient_degree_bounds( - &self, + #[allow(unused_variables)] + fn consistency_quotient_degree_bounds( + interpolant_degree: Degree, padded_height: usize, - num_trace_randomizers: usize, ) -> Vec {{ - let {consistency_constraints_exist}zerofier_degree = padded_height as Degree; - let {consistency_constraints_exist}interpolant_degree = - interpolant_degree(padded_height, num_trace_randomizers); + let zerofier_degree = padded_height as Degree; [{consistency_constraints_degrees}].to_vec() }} - fn get_transition_quotient_degree_bounds( - &self, + #[allow(unused_variables)] + fn transition_quotient_degree_bounds( + interpolant_degree: Degree, padded_height: usize, - num_trace_randomizers: usize, ) -> Vec {{ let zerofier_degree = padded_height as Degree - 1; - let interpolant_degree = interpolant_degree(padded_height, num_trace_randomizers); [{transition_constraints_degrees}].to_vec() }} - fn get_terminal_quotient_degree_bounds( - &self, - padded_height: usize, - num_trace_randomizers: usize, + #[allow(unused_variables)] + fn terminal_quotient_degree_bounds( + interpolant_degree: Degree, ) -> Vec {{ - let {terminal_constraints_exist}zerofier_degree = 1 as Degree; - let {terminal_constraints_exist}interpolant_degree = - interpolant_degree(padded_height, num_trace_randomizers); + let zerofier_degree = 1 as Degree; [{terminal_constraints_degrees}].to_vec() }} }} @@ -284,9 +275,9 @@ impl Quotientable for {table_mod_name} {{ } fn turn_circuits_into_degree_bounds_string( - transition_constraint_circuits: &[ConstraintCircuit], + constraint_circuits: &[ConstraintCircuit], ) -> String { - transition_constraint_circuits + constraint_circuits .iter() .map(|circuit| circuit.degree()) .map(|degree| format!("interpolant_degree * {degree} as Degree - zerofier_degree")) @@ -332,23 +323,34 @@ fn turn_circuits_into_string( let shared_declarations = shared_evaluations.join(""); - let mut constraint_evaluation_expressions: Vec = vec![]; + let mut base_constraint_evaluation_expressions: Vec = vec![]; + let mut ext_constraint_evaluation_expressions: Vec = vec![]; for constraint in constraint_circuits.iter() { // Build code for expressions that evaluate to the constraints - let mut constraint_evaluation = String::default(); - let _dependent_symbols = evaluate_single_node( - 1, - constraint, - &HashSet::default(), - &mut constraint_evaluation, - ); - - constraint_evaluation_expressions.push(constraint_evaluation); + let (constraint_evaluation, _dependent_symbols) = + evaluate_single_node(1, constraint, &HashSet::default()); + match is_bfield_element(constraint) { + true => base_constraint_evaluation_expressions.push(constraint_evaluation), + false => ext_constraint_evaluation_expressions.push(constraint_evaluation), + } } - let constraint_evaluations_joined = constraint_evaluation_expressions.join(",\n"); + let base_constraint_evaluations_joined = base_constraint_evaluation_expressions.join(",\n"); + let ext_constraint_evaluations_joined = ext_constraint_evaluation_expressions.join(",\n"); - format!("{shared_declarations}\n\nvec![{constraint_evaluations_joined}]") + format!( + "{shared_declarations} + + let base_constraints = [{base_constraint_evaluations_joined}]; + let ext_constraints = [{ext_constraint_evaluations_joined}]; + + base_constraints + .map(|v| BFieldElement::lift(&v)) + .iter() + .chain(ext_constraints.iter()) + .cloned() + .collect()" + ) } /// Produce the code to evaluate code for all nodes that share a value number of @@ -358,7 +360,7 @@ fn declare_nodes_with_visit_count( requested_visited_count: usize, circuits: &[ConstraintCircuit], ) -> String { - let mut in_scope: HashSet = HashSet::new(); + let mut in_scope: HashSet = HashSet::new(); let mut output = String::default(); for circuit in circuits.iter() { @@ -376,7 +378,7 @@ fn declare_nodes_with_visit_count( fn declare_single_node_with_visit_count( requested_visited_count: usize, circuit: &ConstraintCircuit, - in_scope: &mut HashSet, + in_scope: &mut HashSet, output: &mut String, ) { if circuit.visited_counter < requested_visited_count { @@ -405,10 +407,10 @@ fn declare_single_node_with_visit_count( // through the `points` input argument, and we do not declare constants. if circuit.visited_counter > requested_visited_count || in_scope.contains(&circuit.id) - || matches!(circuit.expression, CircuitExpression::BConstant(_)) - || matches!(circuit.expression, CircuitExpression::XConstant(_)) - || matches!(circuit.expression, CircuitExpression::Challenge(_)) - || circuit.get_linear_one_index().is_some() + || !matches!( + circuit.expression, + CircuitExpression::BinaryOperation(_, _, _) + ) { return; } @@ -419,10 +421,11 @@ fn declare_single_node_with_visit_count( if circuit.visited_counter == requested_visited_count && !in_scope.contains(&circuit.id) { let binding_name = get_binding_name(circuit); output.push_str(&format!("let {binding_name} =\n")); - evaluate_single_node(requested_visited_count, circuit, in_scope, output); + let (to_output, _) = evaluate_single_node(requested_visited_count, circuit, in_scope); + output.push_str(&to_output); output.push_str(";\n"); - let new_insertion = in_scope.insert(circuit.id.clone()); + let new_insertion = in_scope.insert(circuit.id); // sanity check: don't declare same node multiple times assert!(new_insertion); } @@ -444,56 +447,66 @@ fn get_binding_name( } } -/// Add to `output` the code for evaluating a single node. -/// Return a list of symbols that this evaluation depends on. +/// Recursively check whether a node is composed of only BFieldElements, i.e., only uses +/// (1) inputs from base rows, (2) constants from the B-field, and (3) binary operations on +/// BFieldElements. +fn is_bfield_element( + circuit: &ConstraintCircuit, +) -> bool { + match &circuit.expression { + CircuitExpression::XConstant(_) => false, + CircuitExpression::BConstant(_) => true, + CircuitExpression::Input(indicator) => indicator.is_base_table_row(), + CircuitExpression::Challenge(_) => false, + CircuitExpression::BinaryOperation(_, lhs, rhs) => { + is_bfield_element(&lhs.as_ref().borrow()) && is_bfield_element(&rhs.as_ref().borrow()) + } + } +} + +/// Return (1) the code for evaluating a single node and (2) a list of symbols that this evaluation +/// depends on. fn evaluate_single_node( requested_visited_count: usize, circuit: &ConstraintCircuit, - in_scope: &HashSet, - output: &mut String, -) -> Vec { + in_scope: &HashSet, +) -> (String, Vec) { + let mut output = String::default(); // If this node has already been declared, or visit counter is higher than requested, // than the node value *must* be in scope, meaning that we can just reference it. if circuit.visited_counter > requested_visited_count || in_scope.contains(&circuit.id) { let binding_name = get_binding_name(circuit); output.push_str(&binding_name); return match &circuit.expression { - CircuitExpression::BinaryOperation(_, _, _) => vec![binding_name], - _ => vec![], + CircuitExpression::BinaryOperation(_, _, _) => (output, vec![binding_name]), + _ => (output, vec![]), }; } - // If variable is not already in scope, then we must generate the expression to - // evaluate it. - let mut ret = vec![]; + // If variable is not already in scope, then we must generate the expression to evaluate it. + let mut dependent_symbols = vec![]; match &circuit.expression { CircuitExpression::BinaryOperation(binop, lhs, rhs) => { output.push('('); - let lhs_symbols = evaluate_single_node( - requested_visited_count, - &lhs.as_ref().borrow(), - in_scope, - output, - ); + let (to_output, lhs_symbols) = + evaluate_single_node(requested_visited_count, &lhs.as_ref().borrow(), in_scope); + output.push_str(&to_output); output.push(')'); output.push_str(&binop.to_string()); output.push('('); - let rhs_symbols = evaluate_single_node( - requested_visited_count, - &rhs.as_ref().borrow(), - in_scope, - output, - ); + let (to_output, rhs_symbols) = + evaluate_single_node(requested_visited_count, &rhs.as_ref().borrow(), in_scope); + output.push_str(&to_output); output.push(')'); let ret_as_vec = vec![lhs_symbols, rhs_symbols].concat(); let ret_as_hash_set: HashSet = ret_as_vec.into_iter().collect(); - ret = ret_as_hash_set.into_iter().collect_vec() + dependent_symbols = ret_as_hash_set.into_iter().collect_vec() } _ => output.push_str(&get_binding_name(circuit)), } - ret + (output, dependent_symbols) } fn print_bfe(bfe: &BFieldElement) -> String { diff --git a/specification/cheatsheet.pdf b/specification/cheatsheet.pdf index 69b953271..408a5a8df 100644 Binary files a/specification/cheatsheet.pdf and b/specification/cheatsheet.pdf differ diff --git a/specification/cheatsheet.tex b/specification/cheatsheet.tex index eb13af49b..2d41bb1cb 100644 --- a/specification/cheatsheet.tex +++ b/specification/cheatsheet.tex @@ -1,5 +1,6 @@ \documentclass{article} \PassOptionsToPackage{table}{xcolor} +\usepackage{amssymb} \usepackage{geometry} \usepackage{tikz} \usepackage[most]{tcolorbox} @@ -8,6 +9,7 @@ \usepackage{tabularx} \usepackage{nicefrac} \usepackage{pdflscape} +\usepackage{fontawesome} \usetikzlibrary{tikzmark} @@ -39,100 +41,48 @@ \colorlet{instr-arg}{red!30!green!20} \colorlet{instr-jsp}{blue!90!green!20} +\colorlet{instr-mem}{red!90!blue!20} \colorlet{instr-shrink-stack}{yellow!50} -\colorlet{hint}{gray} \colorlet{row1}{white} \colorlet{row2}{gray!8} -% declared as commands purely for reasons of latex source code formatting -\newcommand{\hintdivinesib}{ - \textcolor{hint}{\texttt{st12 \% 2 = 0 $\Rightarrow$ left node}} -} -\newcommand{\hintsplit}{ - \textcolor{hint}{\texttt{hi $\rightarrow$ st0'}} -} -\newcommand{\hintlt}{ - \textcolor{hint}{\texttt{st0} $\stackrel{\texttt{?}}{\texttt{<}}$ \texttt{st1}} -} -\newcommand{\hintdiv}{ - \textcolor{hint}{\nicefrac{\texttt{st0}}{\texttt{st1}}} -} -\newcommand{\hintxbmul}{ - \textcolor{hint}{\texttt{st0 $\cdot$ (st1, st2, st3)}} -} - \newcommand{\ssominus}{ \shrinkstack{\ensuremath{\ominus}} } \begin{document} \pagestyle{empty} -\begin{minipage}{0.3\textwidth} -\begin{tabular}{rlll} - \texttt{02} & $\ssominus$ & \texttt{pop} & \\ - \texttt{01} & $\oplus$ & \tcbox[colback=instr-arg]{\texttt{push + a}} & \\ - \texttt{04} & $\oplus$ & \texttt{divine} & \\ - \texttt{05} & $\oplus$ & \tcbox[colback=instr-arg]{\texttt{dup + i}} & \\ - \texttt{09} & $\ovoid^{16}$ & \tcbox[colback=instr-arg]{\texttt{swap + i}} & \\ - \texttt{08} & $\ovoid$ & \texttt{nop} & \\ - \texttt{06} & $\ssominus$ & \tcbox[colback=instr-jsp]{\texttt{skiz}} & \\ - \texttt{13} & $\ovoid$ & \splitbox{instr-jsp}{instr-arg}{\texttt{call + d}} & \\ - \texttt{12} & $\ovoid$ & \tcbox[colback=instr-jsp]{\texttt{return}} & \\ - \texttt{16} & $\ovoid$ & \tcbox[colback=instr-jsp]{\texttt{recurse}} & \\ - \texttt{10} & $\ssominus$ & \texttt{assert} & \\ - \texttt{00} & $\ovoid$ & \texttt{halt} & \\ - \texttt{20} & $\ovoid^1$ & \texttt{read\_mem} & \\ - \texttt{24} & $\ovoid$ & \texttt{write\_mem} & \\ - \texttt{28} & $\ovoid^{10}$ & \texttt{hash} & \\ - \texttt{32} & $\ovoid^{11}$ & \texttt{divine\_sibling} & \hintdivinesib \\ - \texttt{36} & $\ovoid$ & \texttt{assert\_vector} & \\ - \texttt{14} & $\ssominus^1$ & \texttt{add} & \\ - \texttt{18} & $\ssominus^1$ & \texttt{mul} & \\ - \texttt{40} & $\ovoid^1$ & \texttt{invert} & \\ - \texttt{44} & $\oplus^2$ & \texttt{split} & \hintsplit \\ - \texttt{22} & $\ssominus^1$ & \texttt{eq} & \\ - \texttt{48} & $\oplus^2$ & \texttt{lsb} & \\ - \texttt{52} & $\ovoid^3$ & \texttt{xxadd} & \\ - \texttt{56} & $\ovoid^3$ & \texttt{xxmul} & \\ - \texttt{60} & $\ovoid^3$ & \texttt{xinvert} & \\ - \texttt{26} & $\ssominus^3$ & \texttt{xbmul} & \hintxbmul \\ - \texttt{64} & $\oplus$ & \texttt{read\_io} & \\ - \texttt{30} & $\ssominus$ & \texttt{write\_io} & +\begin{tabular}{rllll} + \texttt{02} & $\ssominus$ & \texttt{pop} & \texttt{\_ st$_0$} & \texttt{\_} \\ + \texttt{01} & $\oplus$ & \tcbox[colback=instr-arg]{\texttt{push + a}} & \texttt{\_} & \texttt{\_ a} \\ + \texttt{04} & $\oplus$ & \texttt{divine} & \texttt{\_} & \texttt{\_ a} \\ + \texttt{05} & $\oplus$ & \tcbox[colback=instr-arg]{\texttt{dup + i}} & \texttt{\_ st$_{15}$ $\dots$ st$_0$} & \texttt{\_ st$_{15}$ $\dots$ st$_0$ st$_i$} \\ + \texttt{09} & $\ovoid^{16}$ & \tcbox[colback=instr-arg]{\texttt{swap + i}} & \texttt{\_ $\dots$ st$_i$ $\dots$ st$_0$} & \texttt{\_ $\dots$ st$_0$ $\dots$ st$_i$} \\ + \texttt{08} & $\ovoid$ & \texttt{nop} & \texttt{\_} & \texttt{\_} \\ + \texttt{06} & $\ssominus$ & \tcbox[colback=instr-jsp]{\texttt{skiz}} & \texttt{\_ st$_0$} & \texttt{\_} \\ + \texttt{13} & $\ovoid$ & \splitbox{instr-jsp}{instr-arg}{\texttt{call + d}} & \texttt{\_} & \texttt{\_} \\ + \texttt{12} & $\ovoid$ & \tcbox[colback=instr-jsp]{\texttt{return}} & \texttt{\_} & \texttt{\_} \\ + \texttt{16} & $\ovoid$ & \tcbox[colback=instr-jsp]{\texttt{recurse}} & \texttt{\_} & \texttt{\_} \\ + \texttt{10} & $\ssominus$ & \texttt{assert} & \texttt{\_ st$_0$} & \texttt{\_} \\ + \texttt{00} & $\ovoid$ & \texttt{halt} & \texttt{\_} & \texttt{\_} \\ + \texttt{20} & $\ovoid^1$ & \tcbox[colback=instr-mem]{\texttt{read\_mem}} & \texttt{\_ addr st$_0$} & \texttt{\_ addr val} \\ + \texttt{24} & $\ovoid$ & \tcbox[colback=instr-mem]{\texttt{write\_mem}} & \texttt{\_ addr val} & \texttt{\_ addr val} \\ + \texttt{28} & $\ovoid^{10}$ & \texttt{hash} & \texttt{\_ st$_9$ $\!\!\dots\!\!$ st$_0$} & \texttt{\_ d$_4$ $\!\!\dots\!\!$ d$_0$ 0 $\!\!\dots\!\!$ 0} \\ + \texttt{32} & $\ovoid^{11}$ & \texttt{divine\_sibling} & \texttt{\_ idx st$_9$ $\!\!\dots\!\!$ st$_5$ d$_4$ $\!\!\dots\!\!$ d$_0$} & \texttt{\_ idx>>1 r$_4$ $\!\!\dots\!\!$ r$_0$ l$_4$ $\!\!\dots\!\!$ l$_0$} \\ + \texttt{36} & $\ovoid$ & \texttt{assert\_vector} & \texttt{\_} & \texttt{\_} \\ + \texttt{14} & $\ssominus^1$ & \texttt{add} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ sum} \\ + \texttt{18} & $\ssominus^1$ & \texttt{mul} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ prod} \\ + \texttt{40} & $\ovoid^1$ & \texttt{invert} & \texttt{\_ st$_0$} & \texttt{\_ st$_0^{-1}$} \\ + \texttt{44} & $\oplus^2$ & \texttt{split} & \texttt{\_ st$_0$} & \texttt{\_ lo hi} \quad\faWarning \\ + \texttt{22} & $\ssominus^1$ & \texttt{eq} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ res} \\ + \texttt{48} & $\oplus^2$ & \texttt{lsb} & \texttt{\_ st$_0$} & \texttt{\_ st$_0$>>1 st$_0$\%2} \quad\faWarning \\ + \texttt{52} & $\ovoid^3$ & \texttt{xxadd} & \texttt{\_ y$_2$ y$_1$ y$_0$ x$_2$ x$_1$ x$_0$} & \texttt{\_ y$_2$ y$_1$ y$_0$ z$_2$ z$_1$ z$_0$} \\ + \texttt{56} & $\ovoid^3$ & \texttt{xxmul} & \texttt{\_ y$_2$ y$_1$ y$_0$ x$_2$ x$_1$ x$_0$} & \texttt{\_ y$_2$ y$_1$ y$_0$ z$_2$ z$_1$ z$_0$} \\ + \texttt{60} & $\ovoid^3$ & \texttt{xinvert} & \texttt{\_ x$_2$ x$_1$ x$_0$} & \texttt{\_ y$_2$ y$_1$ y$_0$} \\ + \texttt{26} & $\ssominus^3$ & \texttt{xbmul} & \texttt{\_ x$_2$ x$_1$ x$_0$ b} & \texttt{\_ y$_2$ y$_1$ y$_0$} \\ + \texttt{64} & $\oplus$ & \texttt{read\_io} & \texttt{\_} & \texttt{\_ a} \\ + \texttt{30} & $\ssominus$ & \texttt{write\_io} & \texttt{\_ st$_0$} & \texttt{\_} \end{tabular} -\end{minipage}\hfill% -\begin{minipage}[][0.84\textheight][s]{0.6\textwidth} - \rowcolors{3}{row1}{row2} - \hfill - \includegraphics[keepaspectratio,width=0.9\textwidth]{src/img/aet-relations.pdf} - \vfill - - \hfill - \begin{tabular}{lrr} - \multicolumn{3}{l}{\small $p = 18446744069414584321$} \\ \toprule - $i$ & $\nicefrac{1}{i}$ & $\nicefrac{-1}{i}$ \\ \midrule - 2 & 092\dots\!161 & 922\dots\!160 \\ - 3 & 122\dots\!881 & 614\dots\!440 \\ - 4 & 138\dots\!241 & 461\dots\!080 \\ - 5 & 147\dots\!457 & 368\dots\!864 \\ - 6 & 153\dots\!601 & 307\dots\!720 \\ \bottomrule - \end{tabular} - \vfill - - \hfill - \rowcolors{2}{row2}{row1} - \begin{tabular}{lrrr} - \toprule - & base & ext & $\sum$ \\ \midrule - Program & 3 & 1 & 4 \\ - Instruction & 4 & 2 & 6 \\ - Processor & 42 & 11 & 53 \\ - OpStack & 5 & 2 & 7 \\ - RAM & 7 & 6 & 13 \\ - JumpStack & 6 & 2 & 8 \\ - Hash & 49 & 2 & 51 \\ \bottomrule\bottomrule - $\sum$ & 116 & 26 & 142 - \end{tabular} -\end{minipage} \newpage \hspace*{-4em}% @@ -140,18 +90,22 @@ \rowcolors{2}{row2}{row1} \begin{tabular}{lllllllllllllllllllllll} \toprule - Table & \multicolumn{5}{l}{Base Columns} & & & & & & & & & & & & & & & & & \\ \midrule - Program & \multicolumn{3}{l}{Address} & \multicolumn{2}{l}{Instruction} & \multicolumn{3}{l}{IsPadding} & & & & & & & & & & & & & & \\ - Instruction & \multicolumn{3}{l}{Address} & \texttt{CI} & \texttt{NIA} & \multicolumn{3}{l}{IsPadding} & & & & & & & & & & & & & & \\ - Processor & \texttt{CLK} & IsPadding & \texttt{IP} & \texttt{CI} & \texttt{NIA} & \texttt{IB0} & \dots & \texttt{IB6} & \texttt{JSP} & \texttt{JSO} & \texttt{JSD} & \texttt{ST0} & \dots & \texttt{ST15} & \texttt{OSP} & \texttt{OSV} & \texttt{HV0} & \dots & \texttt{HV3} & \texttt{RAMP} & \texttt{RAMV} & \\ - OpStack & \texttt{CLK} & \texttt{clk\_di} & & & & & \multicolumn{4}{l}{\texttt{IB1} ($\widehat{=}$ shrink stack)} & & & & & \texttt{OSP} & \texttt{OSV} & & & & & & \\ - RAM & \texttt{CLK} & \texttt{clk\_di} & & & \texttt{bcpc0} & \multicolumn{2}{l}{\texttt{bcpc1}} & & & & & & & & & & & & & \texttt{RAMP} & \texttt{RAMV} & \texttt{IORD} \\ - JumpStack & \texttt{CLK} & \texttt{clk\_di} & & \texttt{CI} & & & & & \texttt{JSP} & \texttt{JSO} & \texttt{JSD} & & & & & & & & & & & \\ - Hash & \multicolumn{4}{l}{RoundNumber} & & & & & & & & \texttt{ST0} & \dots & \texttt{ST15} & \multicolumn{3}{l}{\texttt{CONSTANT0A}} & \dots & \multicolumn{3}{l}{\texttt{CONSTANT15B}} & \\ \bottomrule + Table & \multicolumn{5}{l}{Base Columns} & & & & & & & & & & & & & & & & & \\ \midrule + Program & \multicolumn{3}{l}{Address} & & \multicolumn{2}{l}{Instruction} & \multicolumn{3}{l}{IsPadding} & & & & & & & & & & & & & \\ + Instruction & \multicolumn{3}{l}{Address} & & \texttt{CI} & \texttt{NIA} & \multicolumn{3}{l}{IsPadding} & & & & & & & & & & & & & \\ + Processor & \texttt{CLK} & IsPadding & \texttt{IP} & \texttt{PI} & \texttt{CI} & \texttt{NIA} & \texttt{IB0} & \dots & \texttt{IB6} & \texttt{JSP} & \texttt{JSO} & \texttt{JSD} & \texttt{ST0} & \dots & \texttt{ST15} & \texttt{OSP} & \texttt{OSV} & \texttt{HV0} & \dots & \texttt{HV3} & \texttt{RAMP} & \texttt{RAMV} \\ + OpStack & \texttt{CLK} & \texttt{clk\_di} & & & & & & \multicolumn{4}{l}{\texttt{IB1} ($\widehat{=}$ shrink stack)} & & & & & \texttt{OSP} & \texttt{OSV} & & & & & \\ + RAM & \texttt{CLK} & \texttt{clk\_di} & & \texttt{PI} & & \texttt{bcpc0} & \multicolumn{2}{l}{\texttt{bcpc1}} & & & & & & & & & & \multicolumn{3}{l}{\texttt{RAMP}DiffInv} & \texttt{RAMP} & \texttt{RAMV} \\ + JumpStack & \texttt{CLK} & \texttt{clk\_di} & & & \texttt{CI} & & & & & \texttt{JSP} & \texttt{JSO} & \texttt{JSD} & & & & & & & & & & \\ + Hash & \multicolumn{4}{l}{RoundNumber} & & & & & & & & & \texttt{ST0} & \dots & \texttt{ST15} & \multicolumn{3}{r}{\texttt{CONSTANT0A}} & \dots & \multicolumn{3}{l}{\texttt{CONSTANT15B}} \\ \bottomrule \end{tabular} } %end scalebox +\begin{tikzpicture}[remember picture, overlay] + \node[anchor=south west,inner sep=0] at (5,-11.9) {\includegraphics[keepaspectratio,width=0.4\textwidth]{src/img/aet-relations.pdf}}; +\end{tikzpicture} \vfill% -\begin{minipage}{0.3\textwidth} +\begin{minipage}[t][0.6\textheight][s]{0.3\textwidth} + \vfill \rowcolors{2}{row2}{row1} \begin{tabular}{rl} \toprule @@ -167,6 +121,54 @@ 195 & \texttt{reverse} \\ 164 & \texttt{div} \\ \bottomrule \end{tabular} + + \vfill + \rowcolors{2}{row2}{row1} + \begin{tabular}{lrrr} + \toprule + & base & ext & $\sum$ \\ \midrule + Program & 3 & 1 & 4 \\ + Instruction & 4 & 2 & 6 \\ + Processor & 43 & 11 & 54 \\ + OpStack & 5 & 2 & 7 \\ + RAM & 8 & 6 & 14 \\ + JumpStack & 6 & 2 & 8 \\ + Hash & 49 & 2 & 51 \\ \bottomrule\bottomrule + $\sum$ & 118 & 26 & 144 + \end{tabular} +\end{minipage}% +\hfill% +\begin{minipage}[t][0.613\textheight][b]{0.5\textwidth} + \vfill + \vspace*{9em} + \hfill + \rowcolors{3}{row1}{row2} + \begin{tabular}{lrr} + \multicolumn{3}{l}{$p = 18446744069414584321$} \\ \toprule + $i$ & $\mathbb{F}_p(\nicefrac{1}{i})$ & $-\mathbb{F}_p(\nicefrac{1}{i})$ \\ \midrule + 2 & 092\dots\!161 & 922\dots\!160 \\ + 3 & 122\dots\!881 & 614\dots\!440 \\ + 4 & 138\dots\!241 & 461\dots\!080 \\ + 5 & 147\dots\!457 & 368\dots\!864 \\ + 6 & 153\dots\!601 & 307\dots\!720 \\ \bottomrule + \end{tabular} + \vfill + + \hfill + \rowcolors{2}{row2}{row1} + \begin{tabular}{lrrrrr} + \toprule + & init & cons & trans & term & $\sum$ \\ \midrule + Program & 2 & 1 & 3 & & 6 \\ + Instruction & 3 & 1 & 5 & & 9 \\ + Processor & 37 & 11 & 75 & 2 & 125 \\ + OpStack & 5 & & 6 & & 11 \\ + Ram & 8 & & 14 & 1 & 23 \\ + JumpStack & 6 & & 8 & & 14 \\ + Hash & 3 & 38 & 21 & & 62 \\ + Cross-Table & & & & 1 & 1 \\ \bottomrule\bottomrule + $\sum$ & 64 & 51 & 132 & 4 & 251 + \end{tabular} \end{minipage} \end{document} diff --git a/specification/src/instruction-specific-transition-constraints.md b/specification/src/instruction-specific-transition-constraints.md index c1e7f352c..202e22b08 100644 --- a/specification/src/instruction-specific-transition-constraints.md +++ b/specification/src/instruction-specific-transition-constraints.md @@ -390,14 +390,6 @@ Additionally, it defines the following transition constraints. 1. If `hv0` is 1, then `st2` is copied to `st7`. 1. If `hv0` is 1, then `st3` is copied to `st8`. 1. If `hv0` is 1, then `st4` is copied to `st9`. -1. The stack element in `st11` does not change. -1. The stack element in `st12` does not change. -1. The stack element in `st13` does not change. -1. The stack element in `st14` does not change. -1. The stack element in `st15` does not change. -1. The top of the OpStack underflow, i.e., `osv`, does not change. -1. The OpStack pointer does not change. -1. If `hv0` is 0, then the RAM value `ramv` does not change. ### Polynomials @@ -408,14 +400,6 @@ Additionally, it defines the following transition constraints. 1. `(1 - hv0)·(st2' - st2) + hv0·(st7' - st2)` 1. `(1 - hv0)·(st3' - st3) + hv0·(st8' - st3)` 1. `(1 - hv0)·(st4' - st4) + hv0·(st9' - st4)` -1. `st11' - st11` -1. `st12' - st12` -1. `st13' - st13` -1. `st14' - st14` -1. `st15' - st15` -1. `osv' - osv` -1. `osp' - osp` -1. `(1 - hv0)·(ramv' - ramv)` ### Helper variable definitions for `divine_sibling` diff --git a/specification/src/processor-table.md b/specification/src/processor-table.md index adaa1cece..09c1a1709 100644 --- a/specification/src/processor-table.md +++ b/specification/src/processor-table.md @@ -65,6 +65,7 @@ However, in order to verify the correctness of `RunningEvaluationFromHashTable`, ## Initial Constraints 1. The cycle counter `clk` is 0. +1. The previous instruction `previous_instruction` is 0. 1. The instruction pointer `ip` is 0. 1. The jump address stack pointer `jsp` is 0. 1. The jump address origin `jso` is 0. @@ -93,7 +94,7 @@ However, in order to verify the correctness of `RunningEvaluationFromHashTable`, 1. `RunningEvaluationStandardOutput` is 1. 1. `RunningProductInstructionTable` has absorbed the first row with respect to challenges 🍓, 🍒, and 🥭 and indeterminate 🛁. 1. `RunningProductOpStackTable` has absorbed the first row with respect to challenges 🍋, 🍊, 🍉, and 🫒 and indeterminate 🪤. -1. `RunningProductRamTable` has absorbed the first row with respect to challenges 🍍, 🍈, and 🍎 and indeterminate 🛋. +1. `RunningProductRamTable` has absorbed the first row with respect to challenges 🍍, 🍈, 🍎, and 🌽 and indeterminate 🛋. 1. `RunningProductJumpStackTable` has absorbed the first row with respect to challenges 🍇, 🍅, 🍌, 🍏, and 🍐 and indeterminate 🧴. 1. `RunningEvaluationToHashTable` has absorbed the first row with respect to challenges 🧄0 through 🧄9 and indeterminate 🪣 if the current instruction is `hash`. Otherwise, it is 1. 1. `RunningEvaluationFromHashTable` is 1. @@ -106,6 +107,7 @@ However, in order to verify the correctness of `RunningEvaluationFromHashTable`, ### Initial Constraints as Polynomials 1. `clk` +1. `previous_instruction` 1. `ip` 1. `jsp` 1. `jso` @@ -134,7 +136,7 @@ However, in order to verify the correctness of `RunningEvaluationFromHashTable`, 1. `RunningEvaluationStandardOutput - 1` 1. `RunningProductInstructionTable - (🛁 - 🍓·ip - 🍒·ci - 🥭·nia)` 1. `RunningProductOpStackTable - (🪤 - 🍋·clk - 🍊·ib1 - 🍉·osp - 🫒·osv)` -1. `RunningProductRamTable - (🛋 - 🍍·clk - 🍈·ramp - 🍎·ramv)` +1. `RunningProductRamTable - (🛋 - 🍍·clk - 🍈·ramp - 🍎·ramv - 🌽·previous_instruction)` 1. `RunningProductJumpStackTable - (🧴 - 🍇·clk - 🍅·ci - 🍌·jsp - 🍏·jso - 🍐·jsd)` 1. `(ci - opcode(hash))·(RunningEvaluationToHashTable - 1) + hash_deselector·(RunningEvaluationToHashTable - 🪣 - 🧄0·st0 - 🧄1·st1 - 🧄2·st2 - 🧄3·st3 - 🧄4·st4 - 🧄5·st5 - 🧄6·st6 - 🧄7·st7 - 🧄8·st8 - 🧄9·st9)` 1. `RunningEvaluationFromHashTable - 1` @@ -163,11 +165,12 @@ The following constraints apply to every pair of rows. 1. The cycle counter `clk` increases by 1. 1. The padding indicator `IsPadding` is 0 or remains unchanged. +1. The current instruction `ci` in the current row is copied into `previous_instruction` in the next row or the next row is a padding row. 1. The running evaluation for standard input absorbs `st0` of the next row with respect to 🛏 if the current instruction is `read_io`, and remains unchanged otherwise. 1. The running evaluation for standard output absorbs `st0` of the next row with respect to 🧯 if the current instruction in the next row is `write_io`, and remains unchanged otherwise. 1. If the next row is not a padding row, the running product for the Instruction Table absorbs the next row with respect to challenges 🍓, 🍒, and 🥭 and indeterminate 🛁. Otherwise, it remains unchanged. 1. The running product for the OpStack Table absorbs the next row with respect to challenges 🍋, 🍊, 🍉, and 🫒 and indeterminate 🪤. -1. The running product for the RAM Table absorbs the next row with respect to challenges 🍍, 🍈, and 🍎 and indeterminate 🛋. +1. The running product for the RAM Table absorbs the next row with respect to challenges 🍍, 🍈, 🍎, and 🌽 and indeterminate 🛋. 1. The running product for the JumpStack Table absorbs the next row with respect to challenges 🍇, 🍅, 🍌, 🍏, and 🍐 and indeterminate 🧴. 1. If the current instruction in the next row is `hash`, the running evaluation “to Hash Table” absorbs the next row with respect to challenges 🧄0 through 🧄9 and indeterminate 🪣. Otherwise, it remains unchanged. 1. If the current instruction is `hash`, the running evaluation “from Hash Table” absorbs the next row with respect to challenges 🫑0 through 🫑4 and indeterminate 🪟. Otherwise, it remains unchanged. @@ -181,11 +184,12 @@ The following constraints apply to every pair of rows. 1. `clk' - (clk + 1)` 1. `IsPadding·(IsPadding' - IsPadding)` +1. `(1 - IsPadding')·(previous_instruction' - ci)` 1. `(ci - opcode(read_io))·(RunningEvaluationStandardInput' - RunningEvaluationStandardInput) + read_io_deselector·(RunningEvaluationStandardInput' - 🛏·RunningEvaluationStandardInput - st0')` 1. `(ci' - opcode(write_io))·(RunningEvaluationStandardOutput' - RunningEvaluationStandardOutput) + write_io_deselector'·(RunningEvaluationStandardOutput' - 🧯·RunningEvaluationStandardOutput - st0')` 1. `(1 - IsPadding')·(RunningProductInstructionTable' - RunningProductInstructionTable(🛁 - 🍓·ip' - 🍒·ci' - 🥭·nia')) + IsPadding'·(RunningProductInstructionTable' - RunningProductInstructionTable)` 1. `RunningProductOpStackTable' - RunningProductOpStackTable·(🪤 - 🍋·clk' - 🍊·ib1' - 🍉·osp' - 🫒·osv')` -1. `RunningProductRamTable' - RunningProductRamTable·(🛋 - 🍍·clk' - 🍈·ramp' - 🍎·ramv')` +1. `RunningProductRamTable' - RunningProductRamTable·(🛋 - 🍍·clk' - 🍈·ramp' - 🍎·ramv' - 🌽·previous_instruction')` 1. `RunningProductJumpStackTable' - RunningProductJumpStackTable·(🧴 - 🍇·clk' - 🍅·ci' - 🍌·jsp' - 🍏·jso' - 🍐·jsd')` 1. `(ci' - opcode(hash))·(RunningEvaluationToHashTable' - RunningEvaluationToHashTable) + hash_deselector'·(RunningEvaluationToHashTable' - 🪣·RunningEvaluationToHashTable - 🧄0·st0' - 🧄1·st1' - 🧄2·st2' - 🧄3·st3' - 🧄4·st4' - 🧄5·st5' - 🧄6·st6' - 🧄7·st7' - 🧄8·st8' - 🧄9·st9')` 1. `(ci - opcode(hash))·(RunningEvaluationFromHashTable' - RunningEvaluationFromHashTable) + hash_deselector·(RunningEvaluationFromHashTable' - 🪟·RunningEvaluationFromHashTable - 🫑0·st5' - 🫑1·st6' - 🫑2·st7' - 🫑3·st8' - 🫑4·st9')` diff --git a/specification/src/random-access-memory-table.md b/specification/src/random-access-memory-table.md index 47c09fa06..72a6816d5 100644 --- a/specification/src/random-access-memory-table.md +++ b/specification/src/random-access-memory-table.md @@ -4,20 +4,21 @@ The RAM is accessible through `read_mem` and `write_mem` commands. ## Base Columns -The RAM Table has 7 columns: +The RAM Table has 8 columns: 1. the cycle counter `clk`, -1. the inverse-or-zero of clock cycle differences minus 1 `clk_di`. +1. the inverse-or-zero of clock cycle differences minus 1 `clk_di`, +1. the instruction executed in the previous clock cycle `previous_instruction`, 1. RAM address pointer `ramp`, 1. the value of the memory at that address `ramv`, 1. helper variable `iord` ("inverse of `ramp` difference", but elsewhere also "difference inverse" and `di` for short), 1. Bézout coefficient polynomial coefficient 0 `bcpc0`, 1. Bézout coefficient polynomial coefficient 1 `bcpc1`, -| Clock | Inverse of Clock Difference Minus One | RAM Pointer | RAM Value | Inverse of RAM Pointer Difference | Bézout coefficient polynomial's coefficients 0 | Bézout coefficient polynomial's coefficients 1 | -|:------|:--------------------------------------|:------------|:----------|:----------------------------------|:-----------------------------------------------|:-----------------------------------------------| -| - | - | - | - | - | - | - | +| Clock | Inverse of Clock Difference Minus One | Previous Instruction | RAM Pointer | RAM Value | Inverse of RAM Pointer Difference | Bézout coefficient polynomial's coefficients 0 | Bézout coefficient polynomial's coefficients 1 | +|:------|:--------------------------------------|:---------------------|:------------|:----------|:----------------------------------|:-----------------------------------------------|:-----------------------------------------------| +| - | - | - | - | - | - | - | - | -Columns `clk`, `ramv`, and `ramp` correspond to the columns of the same name in the [Processor Table](processor-table.md). +Columns `clk`, `previous_instruction`, `ramp`, and `ramv` correspond to the columns of the same name in the [Processor Table](processor-table.md). A permutation argument with the Processor Table establishes that, selecting the columns with these labels, the two tables' sets of rows are identical. Column `iord` helps with detecting a change of `ramp` across two RAM Table rows. @@ -41,7 +42,12 @@ The RAM Table has 2 extension columns, `rppa` and `rpcjd`. ## Sorting Rows Up to order, the rows of the Hash Table in columns `clk`, `ramp`, `ramv` are identical to the rows in the [Processor Table](processor-table.md) in columns `clk`, `ramp`, and `ramv`. -In the Hash Table, the rows are sorted by memory address first, then by cycle counter. +In the Hash Table, the rows are arranged such that they + +1. form contiguous regions of `ramp`, and +1. are sorted by cycle counter `clk` within each such region. + +One way to achieve this is to sort by `ramp` first, `clk` second. Coming back to `iord`: if the difference between `ramp` in row $i$ and row $i+1$ is 0, then `iord` in row $i$ is 0. @@ -49,82 +55,83 @@ Otherwise, `iord` in row $i$ is the multiplicative inverse of the difference bet In the last row, there being no next row, `iord` is 0. An example of the mechanics can be found below. +For reasons of display width, we abbreviate `previous_instruction` by `pi`. For illustrative purposes only, we use four stack registers `st0` through `st3` in the example. -TritonVM has 16 stack registers, `st0` through `st15`. +Triton VM has 16 stack registers, `st0` through `st15`. Processor Table: -| `clk` | `ci` | `nia` | `st0` | `st1` | `st2` | `st3` | `ramp` | `ramv` | -|------:|:------------|:-------|------:|------:|------:|------:|-------:|-------:| -| 0 | `push` | 5 | 0 | 0 | 0 | 0 | 0 | 0 | -| 1 | `push` | 6 | 5 | 0 | 0 | 0 | 0 | 0 | -| 2 | `write_mem` | `pop` | 6 | 5 | 0 | 0 | 5 | 0 | -| 3 | `pop` | `pop` | 6 | 5 | 0 | 0 | 5 | 6 | -| 4 | `pop` | `push` | 5 | 0 | 0 | 0 | 5 | 0 | -| 5 | `push` | 15 | 0 | 0 | 0 | 0 | 5 | 0 | -| 6 | `push` | 16 | 15 | 0 | 0 | 0 | 5 | 0 | -| 7 | `write_mem` | `pop` | 16 | 15 | 0 | 0 | 15 | 0 | -| 8 | `pop` | `pop` | 16 | 15 | 0 | 0 | 15 | 16 | -| 9 | `pop` | `push` | 15 | 0 | 0 | 0 | 15 | 16 | -| 10 | `push` | 5 | 0 | 0 | 0 | 0 | 15 | 16 | -| 11 | `push` | 0 | 5 | 0 | 0 | 0 | 15 | 16 | -| 12 | `read_mem` | `pop` | 0 | 5 | 0 | 0 | 5 | 6 | -| 13 | `pop` | `pop` | 6 | 5 | 0 | 0 | 5 | 6 | -| 14 | `pop` | `push` | 5 | 0 | 0 | 0 | 5 | 6 | -| 15 | `push` | 15 | 0 | 0 | 0 | 0 | 5 | 6 | -| 16 | `push` | 0 | 15 | 0 | 0 | 0 | 5 | 6 | -| 17 | `read_mem` | `pop` | 0 | 15 | 0 | 0 | 15 | 16 | -| 18 | `pop` | `pop` | 16 | 15 | 0 | 0 | 15 | 16 | -| 19 | `pop` | `push` | 15 | 0 | 0 | 0 | 15 | 16 | -| 20 | `push` | 5 | 0 | 0 | 0 | 0 | 15 | 16 | -| 21 | `push` | 7 | 5 | 0 | 0 | 0 | 15 | 16 | -| 22 | `write_mem` | `pop` | 7 | 5 | 0 | 0 | 5 | 6 | -| 23 | `pop` | `pop` | 7 | 5 | 0 | 0 | 5 | 7 | -| 24 | `pop` | `push` | 5 | 0 | 0 | 0 | 5 | 7 | -| 25 | `push` | 15 | 0 | 0 | 0 | 0 | 5 | 7 | -| 26 | `push` | 0 | 15 | 0 | 0 | 0 | 5 | 7 | -| 27 | `read_mem` | `push` | 0 | 15 | 0 | 0 | 15 | 16 | -| 28 | `push` | 5 | 16 | 15 | 0 | 0 | 15 | 16 | -| 29 | `push` | 0 | 5 | 16 | 15 | 0 | 15 | 16 | -| 30 | `read_mem` | `halt` | 0 | 5 | 16 | 15 | 5 | 7 | -| 31 | `halt` | `halt` | 7 | 5 | 16 | 15 | 5 | 7 | +| clk | pi | ci | nia | st0 | st1 | st2 | st3 | ramp | ramv | +|----:|:----------|:----------|:-----|----:|----:|----:|----:|-----:|-----:| +| 0 | - | push | 5 | 0 | 0 | 0 | 0 | 0 | 0 | +| 1 | push | push | 6 | 5 | 0 | 0 | 0 | 0 | 0 | +| 2 | push | write_mem | pop | 6 | 5 | 0 | 0 | 0 | 0 | +| 3 | write_mem | pop | pop | 6 | 5 | 0 | 0 | 5 | 6 | +| 4 | pop | pop | push | 5 | 0 | 0 | 0 | 5 | 6 | +| 5 | pop | push | 15 | 0 | 0 | 0 | 0 | 5 | 6 | +| 6 | push | push | 16 | 15 | 0 | 0 | 0 | 5 | 6 | +| 7 | push | write_mem | pop | 16 | 15 | 0 | 0 | 5 | 6 | +| 8 | write_mem | pop | pop | 16 | 15 | 0 | 0 | 15 | 16 | +| 9 | pop | pop | push | 15 | 0 | 0 | 0 | 15 | 16 | +| 10 | pop | push | 5 | 0 | 0 | 0 | 0 | 15 | 16 | +| 11 | push | push | 0 | 5 | 0 | 0 | 0 | 15 | 16 | +| 12 | push | read_mem | pop | 0 | 5 | 0 | 0 | 15 | 16 | +| 13 | read_mem | pop | pop | 6 | 5 | 0 | 0 | 5 | 6 | +| 14 | pop | pop | push | 5 | 0 | 0 | 0 | 5 | 6 | +| 15 | pop | push | 15 | 0 | 0 | 0 | 0 | 5 | 6 | +| 16 | push | push | 0 | 15 | 0 | 0 | 0 | 5 | 6 | +| 17 | push | read_mem | pop | 0 | 15 | 0 | 0 | 5 | 6 | +| 18 | read_mem | pop | pop | 16 | 15 | 0 | 0 | 15 | 16 | +| 19 | pop | pop | push | 15 | 0 | 0 | 0 | 15 | 16 | +| 20 | pop | push | 5 | 0 | 0 | 0 | 0 | 15 | 16 | +| 21 | push | push | 7 | 5 | 0 | 0 | 0 | 15 | 16 | +| 22 | push | write_mem | pop | 7 | 5 | 0 | 0 | 15 | 16 | +| 23 | write_mem | pop | pop | 7 | 5 | 0 | 0 | 5 | 7 | +| 24 | pop | pop | push | 5 | 0 | 0 | 0 | 5 | 7 | +| 25 | pop | push | 15 | 0 | 0 | 0 | 0 | 5 | 7 | +| 26 | push | push | 0 | 15 | 0 | 0 | 0 | 5 | 7 | +| 27 | push | read_mem | push | 0 | 15 | 0 | 0 | 5 | 7 | +| 28 | read_mem | push | 5 | 16 | 15 | 0 | 0 | 15 | 16 | +| 29 | push | push | 0 | 5 | 16 | 15 | 0 | 15 | 16 | +| 30 | push | read_mem | halt | 0 | 5 | 16 | 15 | 15 | 16 | +| 31 | read_mem | halt | halt | 7 | 5 | 16 | 15 | 5 | 7 | RAM Table: -| `clk` | `ramp` | `ramv` | `iord` | -|------:|-------:|-------:|------------:| -| 0 | 0 | 0 | | -| 1 | 0 | 0 | 5${}^{-1}$ | -| 2 | 5 | 0 | | -| 3 | 5 | 6 | | -| 4 | 5 | 0 | | -| 5 | 5 | 0 | | -| 6 | 5 | 0 | | -| 12 | 5 | 6 | | -| 13 | 5 | 6 | | -| 14 | 5 | 6 | | -| 15 | 5 | 6 | | -| 16 | 5 | 6 | | -| 22 | 5 | 6 | | -| 23 | 5 | 7 | | -| 24 | 5 | 7 | | -| 25 | 5 | 7 | | -| 26 | 5 | 7 | | -| 30 | 5 | 7 | | -| 31 | 5 | 7 | 10${}^{-1}$ | -| 7 | 15 | 0 | | -| 8 | 15 | 16 | | -| 9 | 15 | 16 | | -| 10 | 15 | 16 | | -| 11 | 15 | 16 | | -| 17 | 15 | 16 | | -| 18 | 15 | 16 | | -| 19 | 15 | 16 | | -| 20 | 15 | 16 | | -| 21 | 15 | 16 | | -| 27 | 15 | 16 | | -| 28 | 15 | 16 | | -| 29 | 15 | 16 | | +| clk | pi | ramp | ramv | iord | +|----:|:----------|-----:|-----:|------------:| +| 3 | write_mem | 5 | 6 | 0 | +| 4 | pop | 5 | 6 | 0 | +| 5 | pop | 5 | 6 | 0 | +| 6 | push | 5 | 6 | 0 | +| 7 | push | 5 | 6 | 0 | +| 13 | read_mem | 5 | 6 | 0 | +| 14 | pop | 5 | 6 | 0 | +| 15 | pop | 5 | 6 | 0 | +| 16 | push | 5 | 6 | 0 | +| 17 | push | 5 | 6 | 0 | +| 23 | write_mem | 5 | 7 | 0 | +| 24 | pop | 5 | 7 | 0 | +| 25 | pop | 5 | 7 | 0 | +| 26 | push | 5 | 7 | 0 | +| 27 | push | 5 | 7 | 0 | +| 31 | read_mem | 5 | 7 | -5${}^{-1}$ | +| 0 | - | 0 | 0 | 0 | +| 1 | push | 0 | 0 | 0 | +| 2 | push | 0 | 0 | 15${}^{-1}$ | +| 8 | write_mem | 15 | 16 | 0 | +| 9 | pop | 15 | 16 | 0 | +| 10 | pop | 15 | 16 | 0 | +| 11 | push | 15 | 16 | 0 | +| 12 | push | 15 | 16 | 0 | +| 18 | read_mem | 15 | 16 | 0 | +| 19 | pop | 15 | 16 | 0 | +| 20 | pop | 15 | 16 | 0 | +| 21 | push | 15 | 16 | 0 | +| 22 | push | 15 | 16 | 0 | +| 28 | read_mem | 15 | 16 | 0 | +| 29 | push | 15 | 16 | 0 | +| 30 | push | 15 | 16 | 0 | ## Padding @@ -170,29 +177,25 @@ Both types of challenges are X-field elements, _i.e._, elements of $\mathbb{F}_{ ## Initial Constraints -1. Cycle count `clk` is 0. -1. RAM pointer `ramp` is 0. -1. RAM value `ramv` is 0. +1. RAM value `ramv` is 0 or `previous_instruction` is `write_mem`. 1. The first coefficient of the Bézout coefficient polynomial 0 `bcpc0` is 0. 1. The Bézout coefficient 0 `bc0` is 0. 1. The Bézout coefficient 1 `bc1` is equal to the first coefficient of the Bézout coefficient polynomial `bcpc1`. 1. The running product polynomial `rpp` starts with `🧼 - ramp`. 1. The formal derivative `fd` starts with 1. 1. The running product for clock jump differences `rpcjd` starts with 1. -1. The running product for the permutation argument with the Processor Table `rppa` has absorbed the first row with respect to challenges 🍍, 🍈, and 🍎 and indeterminate 🛋. +1. The running product for the permutation argument with the Processor Table `rppa` has absorbed the first row with respect to challenges 🍍, 🍈, 🍎, and 🌽 and indeterminate 🛋. ### Initial Constraints as Polynomials -1. `clk` -1. `ramp` -1. `ramv` +1. `ramv·(previous_instruction - opcode(write_mem))` 1. `bcpc0` 1. `bc0` 1. `bc1 - bcpc1` 1. `rpp - 🧼 + ramp` 1. `fd - 1` 1. `rpcjd - 1` -1. `rppa - 🛋 - 🍍·clk - 🍈·ramp - 🍎·ramv` +1. `rppa - 🛋 - 🍍·clk - 🍈·ramp - 🍎·ramv - 🌽·previous_instruction` ## Consistency Constraints @@ -201,19 +204,19 @@ None. ## Transition Constraints 1. If `(ramp - ramp')` is 0, then `iord` is 0, else `iord` is the multiplicative inverse of `(ramp' - ramp)`. -1. If the `ramp` changes, then the new `ramv` must be 0. -1. If the `ramp` does not change and the `ramv` does change, then the cycle counter `clk` must increase by 1. +1. If the `ramp` changes and `previous_instruction` in the next row is not `write_mem`, then the RAM value `ramv` in the next row is 0. +1. If the `ramp` does not change and `previous_instruction` in the next row is not `write_mem`, then the RAM value `ramv` does not change. 1. The Bézout coefficient polynomial coefficients are allowed to change only when the `ramp` changes. 1. The running product polynomial `rpp` accumulates a factor `(🧼 - ramp)` whenever `ramp` changes. 1. The clock difference inverse `clk_di` is the inverse-or-zero of the clock difference minus 1. 1. The running product for clock jump differences `rpcjd` accumulates a factor `(🚿 - clk' + clk)` whenever that difference is greater than one and `ramp` is the same. -1. The running product for the permutation argument with the Processor Table `rppa` absorbs the next row with respect to challenges 🍍, 🍈, and 🍎 and indeterminate 🛋. +1. The running product for the permutation argument with the Processor Table `rppa` absorbs the next row with respect to challenges 🍍, 🍈, 🍎, and 🌽 and indeterminate 🛋. Written as Disjunctive Normal Form, the same constraints can be expressed as: 1. `iord` is 0 or `iord` is the inverse of `(ramp' - ramp)`. 1. `(ramp' - ramp)` is zero or `iord` is the inverse of `(ramp' - ramp)`. -1. The `ramp` does not change or the new `ramv` is 0. -1. The `ramp` does change or the `ramv` does not change or the `clk` increases by 1. +1. `(ramp' - ramp)` is zero or `previous_instruction'` is `opcode(write_mem)` or `ramv'` 0. +1. `(ramp' - ramp)` non-zero or `previous_instruction'` is `opcode(write_mem)` or `ramv'` is `ramv`. 1. `bcpc0' - bcpc0` is zero or `(ramp' - ramp)` is nonzero. 1. `bcpc1' - bcpc1` is zero or `(ramp' - ramp)` is nonzero. 1. `(ramp' - ramp)` is zero and `rpp' = rpp`; or `(ramp' - ramp)` is nonzero and `rpp' = rpp·(ramp'-🧼))` is zero. @@ -225,14 +228,14 @@ Written as Disjunctive Normal Form, the same constraints can be expressed as: 1. `(clk' - clk - 1) ≠ 0` and `rpcjd' = rpcjd`; or `ramp' - ramp ≠ 0` and `rpcjd' = rpcjd`; or `(clk' - clk - 1) = 0` and `ramp' - ramp = 0` and `rpcjd' = rpcjd·(🚿 - clk' + clk)`. -1. `rppa' = rppa·(🛋 - 🍍·clk' + 🍈·ramp' + 🍎·ramv')` +1. `rppa' = rppa·(🛋 - 🍍·clk' - 🍈·ramp' - 🍎·ramv' - 🌽·previous_instruction')` ### Transition Constraints as Polynomials 1. `iord·(iord·(ramp' - ramp) - 1)` 1. `(ramp' - ramp)·(iord·(ramp' - ramp) - 1)` -1. `(ramp' - ramp)·ramv'` -1. `(iord·(ramp' - ramp) - 1)·(ramv' - ramv)·(clk' - (clk + 1))` +1. `(ramp' - ramp)·(previous_instruction - opcode(write_mem))·ramv'` +1. `(1 - iord·(ramp' - ramp))·(previous_instruction - opcode(write_mem))·(ramv' - ramv)` 1. `(iord·(ramp' - ramp) - 1)·(bcpc0' - bcpc0)` 1. `(iord·(ramp' - ramp) - 1)·(bcpc1' - bcpc1)` 1. `(iord·(ramp' - ramp) - 1)·(rpp' - rpp) + (ramp' - ramp)·(rpp' - rpp·(ramp'-🧼))` @@ -242,7 +245,7 @@ Written as Disjunctive Normal Form, the same constraints can be expressed as: 1. `clk_di·(clk_di·(clk' - clk - 1) - 1)` 1. `(clk' - clk - 1)·(clk_di·(clk' - clk - 1) - 1)` 1. `(clk' - clk - 1)·(rpcjd' - rpcjd) + (1 - (ramp' - ramp)·iord)·(rpcjd' - rpcjd) + (1 - (clk' - clk - 1)·clk_di)·ramp·(rpcjd' - rpcjd·(🚿 - clk' + clk))` -1. `rppa' - rppa·(🛋 - 🍍·clk' + 🍈·ramp' + 🍎·ramv')` +1. `rppa' - rppa·(🛋 - 🍍·clk' - 🍈·ramp' - 🍎·ramv' - 🌽·previous_instruction')` ## Terminal Constraints diff --git a/specification/src/registers.md b/specification/src/registers.md index 6bc43fd07..e3fb82faa 100644 --- a/specification/src/registers.md +++ b/specification/src/registers.md @@ -2,25 +2,26 @@ This section covers all columns in the Protocol Table. Only a subset of these registers relate to the instruction set; -the remaining registers exist only to enable an efficient arithmetization and are marked with an asterisk. +the remaining registers exist only to enable an efficient arithmetization and are marked with an asterisk (\*). -| Register | Name | Purpose | -|:---------------------|:-----------------------------|:-------------------------------------------------------------------------------------------------------------------| -| *`clk` | cycle counter | counts the number of cycles the program has been running for | -| *`IsPadding` | padding indicator | indicates whether current state is only recorded to improve on STARK's computational runtime | -| `ip` | instruction pointer | contains the memory address (in Program Memory) of the instruction | -| `ci` | current instruction register | contains the current instruction | -| `nia` | next instruction register | contains either the instruction at the next address in Program Memory, or the argument for the current instruction | -| *`ib0` through `ib?` | instruction bucket | decomposition of the instruction's opcode used to keep the AIR degree low | -| `jsp` | jump stack pointer | contains the memory address (in jump stack memory) of the top of the jump stack | -| `jso` | jump stack origin | contains the value of the instruction pointer of the last `call` | -| `jsd` | jump stack destination | contains the argument of the last `call` | -| `st0` through `st15` | operational stack registers | contain explicit operational stack values | -| *`osp` | operational stack pointer | contains the OpStack address of the top of the operational stack | -| *`osv` | operational stack value | contains the (stack) memory value at the given address | -| *`hv0` through `hv3` | helper variable registers | helper variables for some arithmetic operations | -| *`ramp` | RAM pointer | contains an address pointing into the RAM | -| *`ramv` | RAM value | contains the value of the RAM element at the address currently held in `ramp` | +| Register | Name | Purpose | +|:-----------------------|:-----------------------------|:-------------------------------------------------------------------------------------------------------------------| +| *`clk` | cycle counter | counts the number of cycles the program has been running for | +| *`IsPadding` | padding indicator | indicates whether current state is only recorded to improve on STARK's computational runtime | +| *`PreviousInstruction` | previous instruction | holds the opcode of the instruction executed in the previous clock cycle (or 0 if such cycle exists) | +| `ip` | instruction pointer | contains the memory address (in Program Memory) of the instruction | +| `ci` | current instruction register | contains the current instruction | +| `nia` | next instruction register | contains either the instruction at the next address in Program Memory, or the argument for the current instruction | +| *`ib0` through `ib?` | instruction bucket | decomposition of the instruction's opcode used to keep the AIR degree low | +| `jsp` | jump stack pointer | contains the memory address (in jump stack memory) of the top of the jump stack | +| `jso` | jump stack origin | contains the value of the instruction pointer of the last `call` | +| `jsd` | jump stack destination | contains the argument of the last `call` | +| `st0` through `st15` | operational stack registers | contain explicit operational stack values | +| *`osp` | operational stack pointer | contains the OpStack address of the top of the operational stack | +| *`osv` | operational stack value | contains the (stack) memory value at the given address | +| *`hv0` through `hv3` | helper variable registers | helper variables for some arithmetic operations | +| *`ramp` | RAM pointer | contains an address pointing into the RAM | +| *`ramv` | RAM value | contains the value of the RAM element at the address currently held in `ramp` | ## Instruction diff --git a/triton-vm/Cargo.toml b/triton-vm/Cargo.toml index a847db617..5090b0885 100644 --- a/triton-vm/Cargo.toml +++ b/triton-vm/Cargo.toml @@ -5,7 +5,11 @@ edition = "2021" authors = ["Triton Software AG"] license = "Apache-2.0" -description = "A virtual machine that comes with Algebraic Execution Tables (AET) and Arithmetic Intermediate Representations (AIR) for use in combination with a STARK proof system to allow proving correct execution of arbitrary programs in zero-knowledge." +description = """ +A virtual machine that comes with Algebraic Execution Tables (AET) and Arithmetic Intermediate Representations (AIR) +for use in combination with a STARK proof system to allow proving correct execution of arbitrary programs +in zero-knowledge. +""" homepage = "https://triton-vm.org/" documentation = "https://github.com/TritonVM/triton-vm/tree/master/specification" repository = "https://github.com/TritonVM/triton-vm" @@ -59,7 +63,7 @@ serde_with = "2.1" structopt = { version = "0.3", features = ["paw"] } strum = "0.24" strum_macros = "0.24" - +ndarray = { version = "0.15", features = ["rayon"]} [[bench]] name = "prove_halt" diff --git a/triton-vm/benches/prove_fib_100.rs b/triton-vm/benches/prove_fib_100.rs index 9c99d304f..c0875d38c 100644 --- a/triton-vm/benches/prove_fib_100.rs +++ b/triton-vm/benches/prove_fib_100.rs @@ -1,11 +1,16 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; - +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::BenchmarkId; +use criterion::Criterion; use triton_profiler::prof_start; use triton_profiler::prof_stop; -use triton_profiler::triton_profiler::{Report, TritonProfiler}; +use triton_profiler::triton_profiler::Report; +use triton_profiler::triton_profiler::TritonProfiler; + use triton_vm::instruction::sample_programs; use triton_vm::proof::Claim; use triton_vm::stark::Stark; +use triton_vm::table::master_table::MasterBaseTable; use triton_vm::vm::Program; /// cargo criterion --bench prove_fib_100 @@ -29,11 +34,13 @@ fn prove_fib_100(criterion: &mut Criterion) { panic!("The VM encountered the following problem: {}", error); } + let instructions = program.to_bwords(); + let padded_height = MasterBaseTable::padded_height(&aet, &instructions); let claim = Claim { input, - program: program.to_bwords(), + program: instructions, output, - padded_height: 0, + padded_height, }; let stark = Stark::new(claim, Default::default()); diff --git a/triton-vm/benches/prove_halt.rs b/triton-vm/benches/prove_halt.rs index 9edb87d9a..943e632f9 100644 --- a/triton-vm/benches/prove_halt.rs +++ b/triton-vm/benches/prove_halt.rs @@ -1,11 +1,15 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use triton_profiler::triton_profiler::{Report, TritonProfiler}; -use triton_vm::{ - proof::Claim, - shared_tests::save_proof, - stark::{Stark, StarkParameters}, - vm::Program, -}; +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::Criterion; +use triton_profiler::triton_profiler::Report; +use triton_profiler::triton_profiler::TritonProfiler; + +use triton_vm::proof::Claim; +use triton_vm::shared_tests::save_proof; +use triton_vm::stark::Stark; +use triton_vm::stark::StarkParameters; +use triton_vm::table::master_table::MasterBaseTable; +use triton_vm::vm::Program; /// cargo criterion --bench prove_halt fn prove_halt(_criterion: &mut Criterion) { @@ -17,21 +21,24 @@ fn prove_halt(_criterion: &mut Criterion) { Err(e) => panic!("Cannot compile source code into program: {}", e), Ok(p) => p, }; - let claim = Claim { - input: vec![], - program: program.to_bwords(), - output: vec![], - padded_height: 0, - }; - let parameters = StarkParameters::default(); - let stark = Stark::new(claim, parameters); // witness - let (aet, _, err) = program.simulate_no_input(); + let (aet, output, err) = program.simulate_no_input(); if let Some(error) = err { panic!("The VM encountered the following problem: {}", error); } + let code = program.to_bwords(); + let padded_height = MasterBaseTable::padded_height(&aet, &code); + let claim = Claim { + input: vec![], + program: code, + output, + padded_height, + }; + let parameters = StarkParameters::default(); + let stark = Stark::new(claim, parameters); + let proof = stark.prove(aet, &mut maybe_profiler); if let Some(profiler) = &mut maybe_profiler { diff --git a/triton-vm/benches/verify_halt.rs b/triton-vm/benches/verify_halt.rs index bb086997c..d7fd62bd1 100644 --- a/triton-vm/benches/verify_halt.rs +++ b/triton-vm/benches/verify_halt.rs @@ -1,14 +1,20 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use triton_profiler::{ - prof_start, prof_stop, - triton_profiler::{Report, TritonProfiler}, -}; -use triton_vm::{ - proof::Claim, - shared_tests::{load_proof, proof_file_exists, save_proof}, - stark::{Stark, StarkParameters}, - vm::Program, -}; +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::BenchmarkId; +use criterion::Criterion; +use triton_profiler::prof_start; +use triton_profiler::prof_stop; +use triton_profiler::triton_profiler::Report; +use triton_profiler::triton_profiler::TritonProfiler; + +use triton_vm::proof::Claim; +use triton_vm::shared_tests::load_proof; +use triton_vm::shared_tests::proof_file_exists; +use triton_vm::shared_tests::save_proof; +use triton_vm::stark::Stark; +use triton_vm::stark::StarkParameters; +use triton_vm::table::master_table::MasterBaseTable; +use triton_vm::vm::Program; /// cargo criterion --bench verify_halt fn verify_halt(criterion: &mut Criterion) { @@ -22,32 +28,43 @@ fn verify_halt(criterion: &mut Criterion) { Err(e) => panic!("Cannot compile source code into program: {}", e), Ok(p) => p, }; - let claim = Claim { - input: vec![], - program: program.to_bwords(), - output: vec![], - padded_height: 0, - }; - let parameters = StarkParameters::default(); - let stark = Stark::new(claim, parameters); + let instructions = program.to_bwords(); + let stark_parameters = StarkParameters::default(); let filename = "halt.tsp"; - let proof = if proof_file_exists(filename) { - match load_proof(filename) { + let (proof, stark) = if proof_file_exists(filename) { + let proof = match load_proof(filename) { Ok(p) => p, Err(e) => panic!("Could not load proof from disk: {:?}", e), - } + }; + let padded_height = proof.0[1].value() as usize; // todo: `.padded_height()` once available + let claim = Claim { + input: vec![], + program: instructions, + output: vec![], + padded_height, + }; + let stark = Stark::new(claim, stark_parameters); + (proof, stark) } else { - let (aet, _, err) = program.simulate_no_input(); + let (aet, output, err) = program.simulate_no_input(); if let Some(error) = err { panic!("The VM encountered the following problem: {}", error); } + let padded_height = MasterBaseTable::padded_height(&aet, &instructions); + let claim = Claim { + input: vec![], + program: instructions, + output, + padded_height, + }; + let stark = Stark::new(claim, stark_parameters); let proof = stark.prove(aet, &mut None); if let Err(e) = save_proof(filename, proof.clone()) { panic!("Problem! could not save proof to disk: {:?}", e); } - proof + (proof, stark) }; let result = stark.verify(proof.clone(), &mut None); diff --git a/triton-vm/src/arithmetic_domain.rs b/triton-vm/src/arithmetic_domain.rs index f586f1389..0bd6f0e40 100644 --- a/triton-vm/src/arithmetic_domain.rs +++ b/triton-vm/src/arithmetic_domain.rs @@ -1,11 +1,13 @@ use std::ops::MulAssign; +use crate::table::master_table::derive_domain_generator; use num_traits::One; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::polynomial::Polynomial; -use twenty_first::shared_math::traits::{FiniteField, ModPowU32}; +use twenty_first::shared_math::traits::FiniteField; +use twenty_first::shared_math::traits::ModPowU32; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ArithmeticDomain { pub offset: BFieldElement, pub generator: BFieldElement, @@ -13,7 +15,8 @@ pub struct ArithmeticDomain { } impl ArithmeticDomain { - pub fn new(offset: BFieldElement, generator: BFieldElement, length: usize) -> Self { + pub fn new(offset: BFieldElement, length: usize) -> Self { + let generator = derive_domain_generator(length as u64); Self { offset, generator, @@ -21,6 +24,10 @@ impl ArithmeticDomain { } } + pub fn new_no_offset(length: usize) -> Self { + Self::new(BFieldElement::one(), length) + } + pub fn evaluate(&self, polynomial: &Polynomial) -> Vec where FF: FiniteField + MulAssign, @@ -35,7 +42,7 @@ impl ArithmeticDomain { Polynomial::fast_coset_interpolate(&self.offset, self.generator, values) } - pub fn low_degree_extension(&self, codeword: &[FF], target_domain: &Self) -> Vec + pub fn low_degree_extension(&self, codeword: &[FF], target_domain: Self) -> Vec where FF: FiniteField + MulAssign, { @@ -54,7 +61,10 @@ impl ArithmeticDomain { domain_values.push(accumulator * self.offset); accumulator *= self.generator; } - + assert!( + accumulator.is_one(), + "length must be the order of the generator" + ); domain_values } } @@ -76,7 +86,7 @@ mod domain_tests { for order in [4, 8, 32] { let generator = BFieldElement::primitive_root_of_unity(order).unwrap(); let offset = BFieldElement::generator(); - let b_domain = ArithmeticDomain::new(offset, generator, order as usize); + let b_domain = ArithmeticDomain::new(offset, order as usize); let expected_b_values: Vec = (0..order).map(|i| offset * generator.mod_pow(i)).collect(); diff --git a/triton-vm/src/bfield_codec.rs b/triton-vm/src/bfield_codec.rs index cdb0da00d..ea67419f1 100644 --- a/triton-vm/src/bfield_codec.rs +++ b/triton-vm/src/bfield_codec.rs @@ -1,11 +1,14 @@ -use anyhow::{bail, Result}; +use anyhow::bail; +use anyhow::Result; use itertools::Itertools; -use num_traits::{One, Zero}; +use num_traits::One; +use num_traits::Zero; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::rescue_prime_digest::Digest; use twenty_first::shared_math::rescue_prime_regular::DIGEST_LENGTH; -use twenty_first::shared_math::x_field_element::{XFieldElement, EXTENSION_DEGREE}; +use twenty_first::shared_math::x_field_element::XFieldElement; +use twenty_first::shared_math::x_field_element::EXTENSION_DEGREE; use twenty_first::util_types::algebraic_hasher::Hashable; use twenty_first::util_types::merkle_tree::PartialAuthenticationPath; @@ -377,7 +380,8 @@ impl BFieldCodec for Vec> { #[cfg(test)] mod bfield_codec_tests { use itertools::Itertools; - use rand::{thread_rng, RngCore}; + use rand::thread_rng; + use rand::RngCore; use twenty_first::shared_math::b_field_element::BFieldElement; use super::*; diff --git a/triton-vm/src/cross_table_arguments.rs b/triton-vm/src/cross_table_arguments.rs deleted file mode 100644 index 22fbcbbf2..000000000 --- a/triton-vm/src/cross_table_arguments.rs +++ /dev/null @@ -1,636 +0,0 @@ -use std::cmp::max; -use std::ops::{Add, Mul}; - -use itertools::Itertools; -use num_traits::{One, Zero}; -use twenty_first::shared_math::b_field_element::BFieldElement; -use twenty_first::shared_math::mpolynomial::Degree; -use twenty_first::shared_math::traits::{FiniteField, Inverse}; -use twenty_first::shared_math::x_field_element::XFieldElement; - -use crate::arithmetic_domain::ArithmeticDomain; -use crate::table::processor_table::PROCESSOR_TABLE_NUM_PERMUTATION_ARGUMENTS; -use crate::table::table_collection::TableId::{ - HashTable, InstructionTable, JumpStackTable, OpStackTable, ProcessorTable, ProgramTable, - RamTable, -}; -use crate::table::table_collection::{interpolant_degree, ExtTableCollection, TableId}; -use crate::table::table_column::{ - HashExtTableColumn, InstructionExtTableColumn, JumpStackExtTableColumn, OpStackExtTableColumn, - ProcessorExtTableColumn, ProgramExtTableColumn, RamExtTableColumn, -}; - -pub const NUM_PRIVATE_PERM_ARGS: usize = PROCESSOR_TABLE_NUM_PERMUTATION_ARGUMENTS; -pub const NUM_PRIVATE_EVAL_ARGS: usize = 3; -pub const NUM_CROSS_TABLE_ARGS: usize = NUM_PRIVATE_PERM_ARGS + NUM_PRIVATE_EVAL_ARGS; -pub const NUM_PUBLIC_EVAL_ARGS: usize = 2; - -pub trait CrossTableArg { - fn from(&self) -> Vec<(TableId, usize)>; - fn to(&self) -> Vec<(TableId, usize)>; - - fn default_initial() -> XFieldElement - where - Self: Sized; - - fn compute_terminal( - symbols: &[BFieldElement], - initial: XFieldElement, - challenge: XFieldElement, - ) -> XFieldElement - where - Self: Sized; - - fn terminal_quotient( - &self, - ext_codeword_tables: &ExtTableCollection, - quotient_domain: &ArithmeticDomain, - trace_domain_generator: BFieldElement, - ) -> Vec { - let from_codeword = self.combined_from_codeword(ext_codeword_tables); - let to_codeword = self.combined_to_codeword(ext_codeword_tables); - - let trace_domain_generator_inverse = trace_domain_generator.inverse(); - let zerofier = quotient_domain - .domain_values() - .into_iter() - .map(|x| x - trace_domain_generator_inverse) - .collect(); - let zerofier_inverse = BFieldElement::batch_inversion(zerofier); - - zerofier_inverse - .into_iter() - .zip_eq(from_codeword.iter().zip_eq(to_codeword.iter())) - .map(|(z, (&from, &to))| (from - to) * z) - .collect_vec() - } - - fn combined_from_codeword( - &self, - ext_codeword_tables: &ExtTableCollection, - ) -> Vec { - let from = self.from(); - let &(first_from_table, first_from_col) = from.first().unwrap(); - let codeword_length = ext_codeword_tables.data(first_from_table)[first_from_col].len(); - from.iter() - .map(|&(from_table, from_col)| ext_codeword_tables.data(from_table)[from_col].clone()) - .fold( - vec![XFieldElement::one(); codeword_length], - |accumulator, factor| pointwise_operation(accumulator, factor, XFieldElement::mul), - ) - } - - fn combined_to_codeword(&self, ext_codeword_tables: &ExtTableCollection) -> Vec { - let to = self.to(); - let &(first_to_table, first_to_col) = to.first().unwrap(); - let codeword_length = ext_codeword_tables.data(first_to_table)[first_to_col].len(); - to.iter() - .map(|&(to_table, to_col)| ext_codeword_tables.data(to_table)[to_col].clone()) - .fold( - vec![XFieldElement::one(); codeword_length], - |accumulator, factor| pointwise_operation(accumulator, factor, XFieldElement::mul), - ) - } - - fn quotient_degree_bound( - &self, - ext_codeword_tables: &ExtTableCollection, - num_trace_randomizers: usize, - ) -> Degree { - let column_interpolant_degree = - interpolant_degree(ext_codeword_tables.padded_height, num_trace_randomizers); - let lhs_interpolant_degree = column_interpolant_degree * self.from().len() as Degree; - let rhs_interpolant_degree = column_interpolant_degree * self.to().len() as Degree; - let terminal_zerofier_degree = 1; - max(lhs_interpolant_degree, rhs_interpolant_degree) - terminal_zerofier_degree - } - - fn evaluate_difference(&self, cross_table_slice: &[Vec]) -> XFieldElement { - let lhs = self - .from() - .iter() - .map(|&(from_table, from_col)| cross_table_slice[from_table as usize][from_col]) - .fold(XFieldElement::one(), XFieldElement::mul); - let rhs: XFieldElement = self - .to() - .iter() - .map(|&(to_table, to_col)| cross_table_slice[to_table as usize][to_col]) - .fold(XFieldElement::one(), XFieldElement::mul); - - lhs - rhs - } - - fn verify_with_public_data( - symbols: &[BFieldElement], - challenge: XFieldElement, - expected_terminal: XFieldElement, - ) -> bool - where - Self: Sized, - { - let initial = Self::default_initial(); - expected_terminal == Self::compute_terminal(symbols, initial, challenge) - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct PermArg { - from: Vec<(TableId, usize)>, - to: Vec<(TableId, usize)>, -} - -impl CrossTableArg for PermArg { - fn from(&self) -> Vec<(TableId, usize)> { - self.from.clone() - } - - fn to(&self) -> Vec<(TableId, usize)> { - self.to.clone() - } - - fn default_initial() -> XFieldElement { - XFieldElement::one() - } - - /// Compute the product for a permutation argument using `initial`, `challenge`, and `symbols`. - fn compute_terminal( - symbols: &[BFieldElement], - initial: XFieldElement, - challenge: XFieldElement, - ) -> XFieldElement { - symbols - .iter() - .map(|&symbol| challenge - symbol.lift()) - .fold(initial, XFieldElement::mul) - } -} - -impl PermArg { - /// A Permutation Argument between Processor Table and Instruction Table. - pub fn processor_instruction_perm_arg() -> Self { - let from = vec![( - ProcessorTable, - usize::from(ProcessorExtTableColumn::InstructionTablePermArg), - )]; - let to = vec![( - InstructionTable, - usize::from(InstructionExtTableColumn::RunningProductPermArg), - )]; - Self { from, to } - } - - /// A Permutation Argument between Processor Table and Jump-Stack Table. - pub fn processor_jump_stack_perm_arg() -> Self { - let from = vec![( - ProcessorTable, - usize::from(ProcessorExtTableColumn::JumpStackTablePermArg), - )]; - let to = vec![( - JumpStackTable, - usize::from(JumpStackExtTableColumn::RunningProductPermArg), - )]; - Self { from, to } - } - - /// A Permutation Argument between Processor Table and Op-Stack Table. - pub fn processor_op_stack_perm_arg() -> Self { - let from = vec![( - ProcessorTable, - usize::from(ProcessorExtTableColumn::OpStackTablePermArg), - )]; - let to = vec![( - OpStackTable, - usize::from(OpStackExtTableColumn::RunningProductPermArg), - )]; - Self { from, to } - } - - /// A Permutation Argument between Processor Table and RAM Table. - pub fn processor_ram_perm_arg() -> Self { - let from = vec![( - ProcessorTable, - usize::from(ProcessorExtTableColumn::RamTablePermArg), - )]; - let to = vec![( - RamTable, - usize::from(RamExtTableColumn::RunningProductPermArg), - )]; - Self { from, to } - } - - /// Permutation between {OpStack, JumpStack, RAM} Table and - /// Processor Table. - pub fn clock_jump_difference_multi_table_perm_arg() -> Self { - let from = vec![( - ProcessorTable, - usize::from(ProcessorExtTableColumn::AllClockJumpDifferencesPermArg), - )]; - let to_op_stack = ( - OpStackTable, - usize::from(OpStackExtTableColumn::AllClockJumpDifferencesPermArg), - ); - let to_ram = ( - RamTable, - usize::from(RamExtTableColumn::AllClockJumpDifferencesPermArg), - ); - let to_jump_stack = ( - JumpStackTable, - usize::from(JumpStackExtTableColumn::AllClockJumpDifferencesPermArg), - ); - let to = vec![to_op_stack, to_ram, to_jump_stack]; - Self { from, to } - } - - pub fn all_permutation_arguments() -> [Self; NUM_PRIVATE_PERM_ARGS] { - [ - Self::processor_instruction_perm_arg(), - Self::processor_jump_stack_perm_arg(), - Self::processor_op_stack_perm_arg(), - Self::processor_ram_perm_arg(), - Self::clock_jump_difference_multi_table_perm_arg(), - ] - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct EvalArg { - from: Vec<(TableId, usize)>, - to: Vec<(TableId, usize)>, -} - -impl CrossTableArg for EvalArg { - fn from(&self) -> Vec<(TableId, usize)> { - self.from.clone() - } - - fn to(&self) -> Vec<(TableId, usize)> { - self.to.clone() - } - - fn default_initial() -> XFieldElement { - XFieldElement::one() - } - - /// Compute the running evaluation for an evaluation argument as specified by `initial`, - /// `challenge`, and `symbols`. This amounts to evaluating polynomial - /// `f(x) = initial·x^n + Σ_i symbols[n-i]·x^i` - /// at position challenge, i.e., returns `f(challenge)`. - fn compute_terminal( - symbols: &[BFieldElement], - initial: XFieldElement, - challenge: XFieldElement, - ) -> XFieldElement { - symbols.iter().fold(initial, |running_evaluation, symbol| { - challenge * running_evaluation + symbol.lift() - }) - } -} - -impl EvalArg { - /// The Evaluation Argument between the Program Table and the Instruction Table - pub fn program_instruction_eval_arg() -> Self { - let from = vec![( - ProgramTable, - usize::from(ProgramExtTableColumn::RunningEvaluation), - )]; - let to = vec![( - InstructionTable, - usize::from(InstructionExtTableColumn::RunningEvaluation), - )]; - Self { from, to } - } - - pub fn processor_to_hash_eval_arg() -> Self { - let from = vec![( - ProcessorTable, - usize::from(ProcessorExtTableColumn::ToHashTableEvalArg), - )]; - let to = vec![( - HashTable, - usize::from(HashExtTableColumn::FromProcessorRunningEvaluation), - )]; - Self { from, to } - } - - pub fn hash_to_processor_eval_arg() -> Self { - let from = vec![( - HashTable, - usize::from(HashExtTableColumn::ToProcessorRunningEvaluation), - )]; - let to = vec![( - ProcessorTable, - usize::from(ProcessorExtTableColumn::FromHashTableEvalArg), - )]; - Self { from, to } - } - - pub fn all_private_evaluation_arguments() -> [Self; NUM_PRIVATE_EVAL_ARGS] { - [ - Self::program_instruction_eval_arg(), - Self::processor_to_hash_eval_arg(), - Self::hash_to_processor_eval_arg(), - ] - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct GrandCrossTableArg { - program_to_instruction: EvalArg, - processor_to_instruction: PermArg, - processor_to_op_stack: PermArg, - processor_to_ram: PermArg, - processor_to_jump_stack: PermArg, - processor_to_hash: EvalArg, - hash_to_processor: EvalArg, - - program_to_instruction_weight: XFieldElement, - processor_to_instruction_weight: XFieldElement, - processor_to_op_stack_weight: XFieldElement, - processor_to_ram_weight: XFieldElement, - processor_to_jump_stack_weight: XFieldElement, - processor_to_hash_weight: XFieldElement, - hash_to_processor_weight: XFieldElement, - - all_clock_jump_differences: PermArg, - all_clock_jump_differences_weight: XFieldElement, - - input_terminal: XFieldElement, - input_to_processor: (TableId, usize), - input_to_processor_weight: XFieldElement, - - output_terminal: XFieldElement, - processor_to_output: (TableId, usize), - processor_to_output_weight: XFieldElement, -} - -impl<'a> IntoIterator for &'a GrandCrossTableArg { - type Item = (&'a dyn CrossTableArg, XFieldElement); - - type IntoIter = - std::array::IntoIter<(&'a dyn CrossTableArg, XFieldElement), NUM_CROSS_TABLE_ARGS>; - - fn into_iter(self) -> Self::IntoIter { - [ - ( - &self.program_to_instruction as &'a dyn CrossTableArg, - self.program_to_instruction_weight, - ), - ( - &self.processor_to_instruction as &'a dyn CrossTableArg, - self.processor_to_instruction_weight, - ), - ( - &self.processor_to_op_stack as &'a dyn CrossTableArg, - self.processor_to_op_stack_weight, - ), - ( - &self.processor_to_ram as &'a dyn CrossTableArg, - self.processor_to_ram_weight, - ), - ( - &self.processor_to_jump_stack as &'a dyn CrossTableArg, - self.processor_to_jump_stack_weight, - ), - ( - &self.processor_to_hash as &'a dyn CrossTableArg, - self.processor_to_hash_weight, - ), - ( - &self.hash_to_processor as &'a dyn CrossTableArg, - self.hash_to_processor_weight, - ), - ( - &self.all_clock_jump_differences as &'a dyn CrossTableArg, - self.all_clock_jump_differences_weight, - ), - ] - .into_iter() - } -} - -impl GrandCrossTableArg { - pub fn new( - weights: &[XFieldElement; NUM_CROSS_TABLE_ARGS + NUM_PUBLIC_EVAL_ARGS], - input_terminal: XFieldElement, - output_terminal: XFieldElement, - ) -> Self { - let mut weights_stack = weights.to_vec(); - Self { - program_to_instruction: EvalArg::program_instruction_eval_arg(), - processor_to_instruction: PermArg::processor_instruction_perm_arg(), - processor_to_op_stack: PermArg::processor_op_stack_perm_arg(), - processor_to_ram: PermArg::processor_ram_perm_arg(), - processor_to_jump_stack: PermArg::processor_jump_stack_perm_arg(), - processor_to_hash: EvalArg::processor_to_hash_eval_arg(), - hash_to_processor: EvalArg::hash_to_processor_eval_arg(), - - program_to_instruction_weight: weights_stack.pop().unwrap(), - processor_to_instruction_weight: weights_stack.pop().unwrap(), - processor_to_op_stack_weight: weights_stack.pop().unwrap(), - processor_to_ram_weight: weights_stack.pop().unwrap(), - processor_to_jump_stack_weight: weights_stack.pop().unwrap(), - processor_to_hash_weight: weights_stack.pop().unwrap(), - hash_to_processor_weight: weights_stack.pop().unwrap(), - - all_clock_jump_differences: PermArg::clock_jump_difference_multi_table_perm_arg(), - all_clock_jump_differences_weight: weights_stack.pop().unwrap(), - - input_terminal, - input_to_processor: ( - ProcessorTable, - usize::from(ProcessorExtTableColumn::InputTableEvalArg), - ), - input_to_processor_weight: weights_stack.pop().unwrap(), - - output_terminal, - processor_to_output: ( - ProcessorTable, - usize::from(ProcessorExtTableColumn::OutputTableEvalArg), - ), - processor_to_output_weight: weights_stack.pop().unwrap(), - } - } - - pub fn terminal_quotient_codeword( - &self, - ext_codeword_tables: &ExtTableCollection, - quotient_domain: &ArithmeticDomain, - trace_domain_generator: BFieldElement, - ) -> Vec { - let mut non_linear_sum_codeword = vec![XFieldElement::zero(); quotient_domain.length]; - - // cross-table arguments - for (arg, weight) in self.into_iter() { - let from_codeword = arg.combined_from_codeword(ext_codeword_tables); - let to_codeword = arg.combined_to_codeword(ext_codeword_tables); - let non_linear_summand = - weighted_difference_codeword(&from_codeword, &to_codeword, weight); - non_linear_sum_codeword = pointwise_operation( - non_linear_sum_codeword, - non_linear_summand, - XFieldElement::add, - ); - } - - // standard input - let input_terminal_codeword = vec![self.input_terminal; quotient_domain.length]; - let (to_table, to_column) = self.input_to_processor; - let to_codeword = &ext_codeword_tables.data(to_table)[to_column]; - let weight = self.input_to_processor_weight; - let non_linear_summand = - weighted_difference_codeword(&input_terminal_codeword, to_codeword, weight); - non_linear_sum_codeword = pointwise_operation( - non_linear_sum_codeword, - non_linear_summand, - XFieldElement::add, - ); - - // standard output - let (from_table, from_column) = self.processor_to_output; - let from_codeword = &ext_codeword_tables.data(from_table)[from_column]; - let output_terminal_codeword = vec![self.output_terminal; quotient_domain.length]; - let weight = self.processor_to_output_weight; - let non_linear_summand = - weighted_difference_codeword(from_codeword, &output_terminal_codeword, weight); - non_linear_sum_codeword = pointwise_operation( - non_linear_sum_codeword, - non_linear_summand, - XFieldElement::add, - ); - - let trace_domain_generator_inverse = trace_domain_generator.inverse(); - let zerofier = quotient_domain - .domain_values() - .into_iter() - .map(|x| x - trace_domain_generator_inverse) - .collect(); - let zerofier_inverse = BFieldElement::batch_inversion(zerofier); - - zerofier_inverse - .into_iter() - .zip_eq(non_linear_sum_codeword.into_iter()) - .map(|(z, nls)| nls * z) - .collect_vec() - } - - pub fn quotient_degree_bound( - &self, - ext_codeword_tables: &ExtTableCollection, - num_trace_randomizers: usize, - ) -> Degree { - self.into_iter() - .map(|(arg, _)| arg.quotient_degree_bound(ext_codeword_tables, num_trace_randomizers)) - .max() - .unwrap_or(0) - } - - pub fn evaluate_non_linear_sum_of_differences( - &self, - cross_table_slice: &[Vec], - ) -> XFieldElement { - // cross-table arguments - let mut non_linear_sum = self - .into_iter() - .map(|(arg, weight)| weight * arg.evaluate_difference(cross_table_slice)) - .sum(); - - // input - let (to_table, to_column) = self.input_to_processor; - let processor_in = cross_table_slice[to_table as usize][to_column]; - non_linear_sum += self.input_to_processor_weight * (self.input_terminal - processor_in); - - // output - let (from_table, from_colum) = self.processor_to_output; - let processor_out = cross_table_slice[from_table as usize][from_colum]; - non_linear_sum += self.processor_to_output_weight * (processor_out - self.output_terminal); - - non_linear_sum - } -} - -fn pointwise_operation( - left: Vec, - right: Vec, - operation: F, -) -> Vec -where - F: Fn(XFieldElement, XFieldElement) -> XFieldElement, -{ - left.into_iter() - .zip_eq(right.into_iter()) - .map(|(l, r)| operation(l, r)) - .collect_vec() -} - -fn weighted_difference_codeword( - from_codeword: &[XFieldElement], - to_codeword: &[XFieldElement], - weight: XFieldElement, -) -> Vec { - from_codeword - .iter() - .zip_eq(to_codeword.iter()) - .map(|(&from, &to)| weight * (from - to)) - .collect_vec() -} - -#[cfg(test)] -mod permutation_argument_tests { - use crate::stark::triton_stark_tests::parse_simulate_pad_extend; - use crate::vm::triton_vm_tests::test_hash_nop_nop_lt; - - use super::*; - - #[test] - fn all_permutation_arguments_link_from_processor_table_test() { - for perm_arg in PermArg::all_permutation_arguments() { - assert!(perm_arg - .from() - .iter() - .map(|(table, _)| table) - .contains(&TableId::ProcessorTable)); - } - } - - #[test] - fn almost_all_quotient_degree_bounds_of_grand_cross_table_argument_are_equal_test() { - let num_trace_randomizers = 10; - let code_with_input = test_hash_nop_nop_lt(); - let code = code_with_input.source_code; - let input = code_with_input.input; - let secret_input = code_with_input.secret_input; - let (output, _, _, ext_codeword_tables, all_challenges, _) = - parse_simulate_pad_extend(&code, input.clone(), secret_input); - - let input_terminal = EvalArg::compute_terminal( - &input, - EvalArg::default_initial(), - all_challenges - .processor_table_challenges - .standard_input_eval_indeterminate, - ); - - let output_terminal = EvalArg::compute_terminal( - &output, - EvalArg::default_initial(), - all_challenges - .processor_table_challenges - .standard_output_eval_indeterminate, - ); - - let gxta = GrandCrossTableArg::new( - &[XFieldElement::one(); NUM_CROSS_TABLE_ARGS + 2], - input_terminal, - output_terminal, - ); - let quotient_degree_bound = gxta - .program_to_instruction - .quotient_degree_bound(&ext_codeword_tables, num_trace_randomizers); - for (arg, _) in gxta.into_iter().take(7) { - assert_eq!( - quotient_degree_bound, - arg.quotient_degree_bound(&ext_codeword_tables, num_trace_randomizers) - ); - } - } -} diff --git a/triton-vm/src/error.rs b/triton-vm/src/error.rs index 9c4335ef7..db7f562bb 100644 --- a/triton-vm/src/error.rs +++ b/triton-vm/src/error.rs @@ -1,16 +1,19 @@ -use anyhow::Result; use std::error::Error; -use std::fmt::{Display, Formatter}; +use std::fmt::Display; +use std::fmt::Formatter; + +use anyhow::Result; use twenty_first::shared_math::b_field_element::BFieldElement; + use InstructionError::*; #[derive(Debug, Clone)] pub enum InstructionError { + InstructionPointerUnderflow, InstructionPointerOverflow(usize), OpStackTooShallow, JumpStackTooShallow, AssertionFailed(usize, u32, BFieldElement), - MemoryAddressNotFound, InverseOfZero, RunawayInstructionArg, UngracefulTermination, @@ -20,6 +23,10 @@ pub enum InstructionError { impl Display for InstructionError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + InstructionPointerUnderflow => { + write!(f, "Instruction pointer points to before start of program",) + } + InstructionPointerOverflow(ip) => { write!(f, "Instruction pointer {} points outside of program", ip) } @@ -40,10 +47,6 @@ impl Display for InstructionError { ) } - MemoryAddressNotFound => { - write!(f, "Memory address not found") - } - InverseOfZero => { write!(f, "0 does not have a multiplicative inverse") } diff --git a/triton-vm/src/fri.rs b/triton-vm/src/fri.rs index dc3eb2588..83718198a 100644 --- a/triton-vm/src/fri.rs +++ b/triton-vm/src/fri.rs @@ -1,29 +1,33 @@ -use anyhow::Result; -use itertools::Itertools; -use num_traits::One; -use rayon::iter::{ - IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, -}; use std::error::Error; use std::fmt; use std::marker::PhantomData; +use anyhow::Result; +use itertools::Itertools; +use num_traits::One; +use rayon::iter::*; +use triton_profiler::prof_start; +use triton_profiler::prof_stop; use triton_profiler::triton_profiler::TritonProfiler; -use triton_profiler::{prof_start, prof_stop}; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::ntt::intt; -use twenty_first::shared_math::other::{log_2_ceil, log_2_floor}; +use twenty_first::shared_math::other::log_2_ceil; +use twenty_first::shared_math::other::log_2_floor; use twenty_first::shared_math::polynomial::Polynomial; use twenty_first::shared_math::rescue_prime_digest::Digest; +use twenty_first::shared_math::traits::CyclicGroupGenerator; use twenty_first::shared_math::traits::FiniteField; -use twenty_first::shared_math::traits::{CyclicGroupGenerator, ModPowU32}; +use twenty_first::shared_math::traits::ModPowU32; use twenty_first::shared_math::x_field_element::XFieldElement; -use twenty_first::util_types::algebraic_hasher::{AlgebraicHasher, Hashable}; -use twenty_first::util_types::merkle_tree::{MerkleTree, PartialAuthenticationPath}; +use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; +use twenty_first::util_types::algebraic_hasher::Hashable; +use twenty_first::util_types::merkle_tree::MerkleTree; +use twenty_first::util_types::merkle_tree::PartialAuthenticationPath; use twenty_first::util_types::merkle_tree_maker::MerkleTreeMaker; use crate::arithmetic_domain::ArithmeticDomain; -use crate::proof_item::{FriResponse, ProofItem}; +use crate::proof_item::FriResponse; +use crate::proof_item::ProofItem; use crate::proof_stream::ProofStream; use crate::stark::Maker; @@ -60,12 +64,11 @@ pub struct Fri { impl Fri { pub fn new( offset: BFieldElement, - fri_domain_generator: BFieldElement, domain_length: usize, expansion_factor: usize, colinearity_checks_count: usize, ) -> Self { - let domain = ArithmeticDomain::new(offset, fri_domain_generator, domain_length); + let domain = ArithmeticDomain::new(offset, domain_length); let _hasher = PhantomData; Self { domain, @@ -239,7 +242,6 @@ impl Fri { } // Send the last codeword - // todo! use coefficient form for last codeword? let last_codeword: Vec = codeword_local; proof_stream.enqueue(&ProofItem::FriCodeword(last_codeword)); @@ -280,7 +282,7 @@ impl Fri { indices = indices .into_par_iter() - .zip((counter..counter + self.colinearity_checks_count as usize).into_par_iter()) + .zip((counter..counter + self.colinearity_checks_count).into_par_iter()) .map(|(index, _count)| { let mut seed_local = seed.to_sequence(); seed_local.append(&mut counter.to_sequence()); @@ -490,19 +492,20 @@ impl Fri { #[cfg(test)] mod triton_xfri_tests { - use super::*; use itertools::Itertools; use num_traits::Zero; - use rand::{thread_rng, RngCore}; + use rand::thread_rng; + use rand::RngCore; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::rescue_prime_regular::RescuePrimeRegular; - use twenty_first::shared_math::traits::{ - CyclicGroupGenerator, ModPowU32, PrimitiveRootOfUnity, - }; + use twenty_first::shared_math::traits::CyclicGroupGenerator; + use twenty_first::shared_math::traits::ModPowU32; use twenty_first::shared_math::x_field_element::XFieldElement; use twenty_first::test_shared::corrupt_digest; use twenty_first::utils::has_unique_elements; + use super::*; + #[test] fn sample_indices_test() { type H = RescuePrimeRegular; @@ -683,16 +686,9 @@ mod triton_xfri_tests { expansion_factor: usize, colinearity_checks: usize, ) -> Fri { - let fri_domain_generator = BFieldElement::primitive_root_of_unity(subgroup_order).unwrap(); - - // The following offset was picked arbitrarily by copying the one found in - // `get_b_field_fri_test_object`. It does not generate the full Z_p\{0}, but - // we're not sure it needs to, Alan? - let offset = BFieldElement::new(7); - + let offset = BFieldElement::generator(); let fri: Fri = Fri::new( offset, - fri_domain_generator, subgroup_order as usize, expansion_factor, colinearity_checks, diff --git a/triton-vm/src/instruction.rs b/triton-vm/src/instruction.rs index 2eac1c617..aaf61ce23 100644 --- a/triton-vm/src/instruction.rs +++ b/triton-vm/src/instruction.rs @@ -1,23 +1,29 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; +use std::collections::HashSet; use std::error::Error; use std::fmt::Display; use std::ops::Neg; use std::str::SplitWhitespace; use std::vec; -use anyhow::{bail, Result}; +use anyhow::bail; +use anyhow::Result; use itertools::Itertools; use num_traits::One; -use strum::{EnumCount, IntoEnumIterator}; -use strum_macros::{Display as DisplayMacro, EnumCount as EnumCountMacro, EnumIter}; +use strum::EnumCount; +use strum::IntoEnumIterator; +use strum_macros::Display as DisplayMacro; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; use AnInstruction::*; use TokenError::*; use crate::instruction::DivinationHint::Quotient; - -use super::ord_n::{Ord16, Ord16::*, Ord7}; +use crate::ord_n::Ord16; +use crate::ord_n::Ord16::*; +use crate::ord_n::Ord7; /// An `Instruction` has `call` addresses encoded as absolute integers. pub type Instruction = AnInstruction; @@ -828,7 +834,8 @@ pub mod sample_programs { use twenty_first::shared_math::b_field_element::BFieldElement; use super::super::vm::Program; - use super::{AnInstruction::*, LabelledInstruction}; + use super::AnInstruction::*; + use super::LabelledInstruction; pub const PUSH_PUSH_ADD_POP_S: &str = " push 1 @@ -1274,15 +1281,20 @@ pub mod sample_programs { #[cfg(test)] mod instruction_tests { use itertools::Itertools; - use num_traits::{One, Zero}; - use strum::{EnumCount, IntoEnumIterator}; + use num_traits::One; + use num_traits::Zero; + use strum::EnumCount; + use strum::IntoEnumIterator; use twenty_first::shared_math::b_field_element::BFieldElement; use crate::instruction::all_labelled_instructions_with_args; use crate::ord_n::Ord7; use crate::vm::Program; - use super::{all_instructions_without_args, parse, sample_programs, AnInstruction}; + use super::all_instructions_without_args; + use super::parse; + use super::sample_programs; + use super::AnInstruction; #[test] fn opcode_test() { @@ -1417,7 +1429,7 @@ mod instruction_tests { #[test] fn print_all_instructions_and_opcodes() { for instr in all_instructions_without_args() { - println!("{:>3} {: <10}", instr.opcode(), format_args!("{instr}")); + println!("{:>3} {: <10}", instr.opcode(), format!("{instr}")); } } } diff --git a/triton-vm/src/lib.rs b/triton-vm/src/lib.rs index 516f9002c..0a1059b70 100644 --- a/triton-vm/src/lib.rs +++ b/triton-vm/src/lib.rs @@ -1,6 +1,5 @@ pub mod arithmetic_domain; pub mod bfield_codec; -pub mod cross_table_arguments; pub mod error; pub mod fri; pub mod instruction; diff --git a/triton-vm/src/op_stack.rs b/triton-vm/src/op_stack.rs index 730789ad5..220f7ea5c 100644 --- a/triton-vm/src/op_stack.rs +++ b/triton-vm/src/op_stack.rs @@ -1,10 +1,13 @@ -use super::error::{vm_fail, InstructionError::*}; -use super::ord_n::{Ord16, Ord16::*}; use anyhow::Result; use num_traits::Zero; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::x_field_element::XFieldElement; +use super::error::vm_fail; +use super::error::InstructionError::*; +use super::ord_n::Ord16; +use super::ord_n::Ord16::*; + #[derive(Debug, Clone)] pub struct OpStack { pub stack: Vec, @@ -125,10 +128,10 @@ impl OpStack { #[cfg(test)] mod op_stack_test { - use twenty_first::shared_math::b_field_element::BFieldElement; - use crate::{op_stack::OpStack, ord_n::Ord16}; + use crate::op_stack::OpStack; + use crate::ord_n::Ord16; #[test] fn test_sanity() { diff --git a/triton-vm/src/proof.rs b/triton-vm/src/proof.rs index 062aea0fe..6f6ca1cf0 100644 --- a/triton-vm/src/proof.rs +++ b/triton-vm/src/proof.rs @@ -1,4 +1,5 @@ -use serde::{Deserialize, Serialize}; +use serde::Deserialize; +use serde::Serialize; use twenty_first::shared_math::b_field_element::BFieldElement; #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/triton-vm/src/proof_item.rs b/triton-vm/src/proof_item.rs index 9b6e9edf1..a6cd36a9d 100644 --- a/triton-vm/src/proof_item.rs +++ b/triton-vm/src/proof_item.rs @@ -2,7 +2,8 @@ use anyhow::Result; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::rescue_prime_digest::Digest; use twenty_first::shared_math::rescue_prime_regular::DIGEST_LENGTH; -use twenty_first::shared_math::x_field_element::{XFieldElement, EXTENSION_DEGREE}; +use twenty_first::shared_math::x_field_element::XFieldElement; +use twenty_first::shared_math::x_field_element::EXTENSION_DEGREE; use twenty_first::util_types::merkle_tree::PartialAuthenticationPath; use twenty_first::util_types::proof_stream_typed::ProofStreamError; @@ -103,11 +104,9 @@ pub trait MayBeUncast { #[allow(clippy::large_enum_variant)] pub enum ProofItem { CompressedAuthenticationPaths(AuthenticationStructure), - TransposedBaseElementVectors(Vec>), - TransposedExtensionElementVectors(Vec>), + MasterBaseTableRows(Vec>), + MasterExtTableRows(Vec>), MerkleRoot(Digest), - TransposedBaseElements(Vec), - TransposedExtensionElements(Vec), AuthenticationPath(Vec), RevealedCombinationElements(Vec), FriCodeword(Vec), @@ -158,9 +157,9 @@ where } } - pub fn as_transposed_base_element_vectors(&self) -> Result>> { + pub fn as_master_base_table_rows(&self) -> Result>> { match self { - Self::TransposedBaseElementVectors(bss) => Ok(bss.to_owned()), + Self::MasterBaseTableRows(bss) => Ok(bss.to_owned()), Self::Uncast(str) => match Vec::>::decode(str) { Ok(base_element_vectors) => Ok(*base_element_vectors), Err(_) => Err(anyhow::Error::new(ProofStreamError::new( @@ -168,14 +167,14 @@ where ))), }, _ => Err(anyhow::Error::new(ProofStreamError::new( - "expected transposed base element vectors, but got something else", + "expected master base table rows, but got something else", ))), } } - pub fn as_transposed_extension_element_vectors(&self) -> Result>> { + pub fn as_master_ext_table_rows(&self) -> Result>> { match self { - Self::TransposedExtensionElementVectors(xss) => Ok(xss.to_owned()), + Self::MasterExtTableRows(xss) => Ok(xss.to_owned()), Self::Uncast(str) => match Vec::>::decode(str) { Ok(ext_element_vectors) => Ok(*ext_element_vectors), Err(_) => Err(anyhow::Error::new(ProofStreamError::new( @@ -183,7 +182,7 @@ where ))), }, _ => Err(anyhow::Error::new(ProofStreamError::new( - "expected transposed extension element vectors, but got something else", + "expected master extension table rows, but got something else", ))), } } @@ -203,36 +202,6 @@ where } } - pub fn as_transposed_base_elements(&self) -> Result> { - match self { - Self::TransposedBaseElements(bs) => Ok(bs.to_owned()), - Self::Uncast(str) => match Vec::::decode(str) { - Ok(transposed_base_elements) => Ok(*transposed_base_elements), - Err(_) => Err(anyhow::Error::new(ProofStreamError::new( - "cast to transposed base field elements failed", - ))), - }, - _ => Err(anyhow::Error::new(ProofStreamError::new( - "expected tranposed base elements, but got something else", - ))), - } - } - - pub fn as_transposed_extension_elements(&self) -> Result> { - match self { - Self::TransposedExtensionElements(xs) => Ok(xs.to_owned()), - Self::Uncast(str) => match Vec::::decode(str) { - Ok(transposed_ext_elements) => Ok(*transposed_ext_elements), - Err(_) => Err(anyhow::Error::new(ProofStreamError::new( - "cast to transposed extension field elements failed", - ))), - }, - _ => Err(anyhow::Error::new(ProofStreamError::new( - "expected tranposed extension elements, but got something else", - ))), - } - } - pub fn as_authentication_path(&self) -> Result> { match self { Self::AuthenticationPath(bss) => Ok(bss.to_owned()), @@ -330,11 +299,9 @@ impl BFieldCodec for ProofItem { fn encode(&self) -> Vec { let mut tail = match self { ProofItem::CompressedAuthenticationPaths(something) => something.encode(), - ProofItem::TransposedBaseElementVectors(something) => something.encode(), - ProofItem::TransposedExtensionElementVectors(something) => something.encode(), + ProofItem::MasterBaseTableRows(something) => something.encode(), + ProofItem::MasterExtTableRows(something) => something.encode(), ProofItem::MerkleRoot(something) => something.encode(), - ProofItem::TransposedBaseElements(something) => something.encode(), - ProofItem::TransposedExtensionElements(something) => something.encode(), ProofItem::AuthenticationPath(something) => something.encode(), ProofItem::RevealedCombinationElements(something) => something.encode(), ProofItem::FriCodeword(something) => something.encode(), @@ -351,17 +318,16 @@ impl BFieldCodec for ProofItem { #[cfg(test)] mod proof_item_typed_tests { use itertools::Itertools; - use rand::{thread_rng, RngCore}; + use rand::thread_rng; + use rand::RngCore; + use twenty_first::shared_math::other::random_elements; + use twenty_first::shared_math::rescue_prime_regular::RescuePrimeRegular; + use twenty_first::shared_math::x_field_element::XFieldElement; + use twenty_first::shared_math::x_field_element::EXTENSION_DEGREE; use crate::proof_stream::ProofStream; use super::*; - use twenty_first::shared_math::{ - b_field_element::BFieldElement, - other::random_elements, - rescue_prime_regular::RescuePrimeRegular, - x_field_element::{XFieldElement, EXTENSION_DEGREE}, - }; fn random_bool() -> bool { let mut rng = thread_rng(); @@ -415,9 +381,6 @@ mod proof_item_typed_tests { fn test_serialize_stark_proof_with_fiat_shamir() { type H = RescuePrimeRegular; let mut proof_stream = ProofStream::::new(); - let manyb1: Vec = random_elements(10); - let manyx: Vec = random_elements(13); - let manyb2: Vec = random_elements(11); let map = (0..7).into_iter().map(|_| random_digest()).collect_vec(); let auth_struct = (0..8) .into_iter() @@ -441,12 +404,6 @@ mod proof_item_typed_tests { let mut fs = vec![]; fs.push(proof_stream.prover_fiat_shamir()); - proof_stream.enqueue(&ProofItem::TransposedBaseElements(manyb1.clone())); - fs.push(proof_stream.prover_fiat_shamir()); - proof_stream.enqueue(&ProofItem::TransposedExtensionElements(manyx.clone())); - fs.push(proof_stream.prover_fiat_shamir()); - proof_stream.enqueue(&ProofItem::TransposedBaseElements(manyb2.clone())); - fs.push(proof_stream.prover_fiat_shamir()); proof_stream.enqueue(&ProofItem::AuthenticationPath(map.clone())); fs.push(proof_stream.prover_fiat_shamir()); proof_stream.enqueue(&ProofItem::CompressedAuthenticationPaths( @@ -465,29 +422,6 @@ mod proof_item_typed_tests { let mut fs_ = vec![]; fs_.push(proof_stream_.verifier_fiat_shamir()); - let manyb1_ = proof_stream_ - .dequeue() - .expect("can't dequeue item") - .as_transposed_base_elements() - .expect("cannot parse dequeued item"); - assert_eq!(manyb1, manyb1_); - fs_.push(proof_stream_.verifier_fiat_shamir()); - - let manyx_ = proof_stream_ - .dequeue() - .expect("can't dequeue item") - .as_transposed_extension_elements() - .expect("cannot parse dequeued item"); - assert_eq!(manyx, manyx_); - fs_.push(proof_stream_.verifier_fiat_shamir()); - - let manyb2_ = proof_stream_ - .dequeue() - .expect("can't dequeue item") - .as_transposed_base_elements() - .expect("cannot parse dequeued item"); - assert_eq!(manyb2, manyb2_); - fs_.push(proof_stream_.verifier_fiat_shamir()); let map_ = proof_stream_ .dequeue() diff --git a/triton-vm/src/shared_tests.rs b/triton-vm/src/shared_tests.rs index 89a2dc128..1057657d2 100644 --- a/triton-vm/src/shared_tests.rs +++ b/triton-vm/src/shared_tests.rs @@ -1,18 +1,22 @@ -use std::fs::{create_dir_all, File}; -use std::io::{Read, Write}; +use std::fs::create_dir_all; +use std::fs::File; +use std::io::Read; +use std::io::Write; use std::path::Path; -use anyhow::{Error, Result}; -use twenty_first::shared_math::b_field_element::BFieldElement; - +use anyhow::Error; +use anyhow::Result; +use triton_profiler::prof_start; +use triton_profiler::prof_stop; use triton_profiler::triton_profiler::TritonProfiler; -use triton_profiler::{prof_start, prof_stop}; +use twenty_first::shared_math::b_field_element::BFieldElement; -use crate::proof::{Claim, Proof}; -use crate::stark::{Stark, StarkParameters}; -use crate::table::base_matrix::AlgebraicExecutionTrace; -use crate::table::base_matrix::BaseMatrices; -use crate::table::table_collection::BaseTableCollection; +use crate::proof::Claim; +use crate::proof::Proof; +use crate::stark::Stark; +use crate::stark::StarkParameters; +use crate::table::master_table::MasterBaseTable; +use crate::vm::AlgebraicExecutionTrace; use crate::vm::Program; pub fn parse_setup_simulate( @@ -48,32 +52,20 @@ pub fn parse_simulate_prove( secret_input_symbols, maybe_profiler, ); - let base_matrices = BaseMatrices::new(aet.clone(), &program.to_bwords()); - prof_start!(maybe_profiler, "padding"); - let log_expansion_factor = 2; - let security_level = 32; - let padded_height = BaseTableCollection::padded_height(&base_matrices); - prof_stop!(maybe_profiler, "padding"); - - prof_start!(maybe_profiler, "prove"); - let parameters = StarkParameters::new(security_level, 1 << log_expansion_factor); - let program = Program::from_code(code); - let program = match program { - Ok(p) => p.to_bwords(), - Err(e) => panic!( - "Could not convert program from code to vector of BFieldElements: {}", - e - ), - }; + let padded_height = MasterBaseTable::padded_height(&aet, &program.to_bwords()); let claim = Claim { input: input_symbols, - program, + program: program.to_bwords(), output: output_symbols, padded_height, }; + let log_expansion_factor = 2; + let security_level = 32; + let parameters = StarkParameters::new(security_level, 1 << log_expansion_factor); let stark = Stark::new(claim, parameters); + prof_start!(maybe_profiler, "prove"); let proof = stark.prove(aet, maybe_profiler); prof_stop!(maybe_profiler, "prove"); diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index c02fbb59c..40ad434b1 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -1,54 +1,61 @@ use std::collections::HashMap; use std::error::Error; use std::fmt; +use std::ops::Add; -use anyhow::{bail, Result}; +use anyhow::anyhow; +use anyhow::bail; +use anyhow::Result; use itertools::Itertools; -use num_traits::{One, Zero}; -use rayon::iter::{ - IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, -}; +use ndarray::s; +use ndarray::Array1; +use ndarray::ArrayBase; +use ndarray::ArrayView2; +use ndarray::Zip; +use num_traits::One; +use num_traits::Zero; +use rayon::prelude::*; +use triton_profiler::prof_itr0; +use triton_profiler::prof_start; +use triton_profiler::prof_stop; +use triton_profiler::triton_profiler::TritonProfiler; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::mpolynomial::Degree; -use twenty_first::shared_math::other::{is_power_of_two, random_elements, roundup_npo2, transpose}; -use twenty_first::shared_math::polynomial::Polynomial; +use twenty_first::shared_math::other::is_power_of_two; +use twenty_first::shared_math::other::roundup_npo2; use twenty_first::shared_math::rescue_prime_digest::Digest; use twenty_first::shared_math::rescue_prime_regular::RescuePrimeRegular; -use twenty_first::shared_math::traits::{FiniteField, Inverse, ModPowU32, PrimitiveRootOfUnity}; -use twenty_first::shared_math::x_field_element::{XFieldElement, EXTENSION_DEGREE}; +use twenty_first::shared_math::traits::FiniteField; +use twenty_first::shared_math::traits::Inverse; +use twenty_first::shared_math::traits::ModPowU32; +use twenty_first::shared_math::x_field_element::XFieldElement; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; -use twenty_first::util_types::merkle_tree::{CpuParallel, MerkleTree}; - -use triton_profiler::triton_profiler::TritonProfiler; -use triton_profiler::{prof_itr0, prof_start, prof_stop}; +use twenty_first::util_types::merkle_tree::CpuParallel; +use twenty_first::util_types::merkle_tree::MerkleTree; use twenty_first::util_types::merkle_tree_maker::MerkleTreeMaker; use crate::arithmetic_domain::ArithmeticDomain; -use crate::cross_table_arguments::{ - CrossTableArg, EvalArg, GrandCrossTableArg, NUM_CROSS_TABLE_ARGS, NUM_PUBLIC_EVAL_ARGS, -}; -use crate::fri::{Fri, FriValidationError}; -use crate::proof::{Claim, Proof}; +use crate::fri::Fri; +use crate::fri::FriValidationError; +use crate::proof::Claim; +use crate::proof::Proof; use crate::proof_item::ProofItem; use crate::proof_stream::ProofStream; -use crate::table::base_matrix::AlgebraicExecutionTrace; use crate::table::challenges::AllChallenges; -use crate::table::table_collection::{ - derive_trace_domain_generator, BaseTableCollection, ExtTableCollection, -}; - -use super::table::base_matrix::BaseMatrices; +use crate::table::master_table::*; +use crate::vm::AlgebraicExecutionTrace; pub type StarkHasher = RescuePrimeRegular; pub type Maker = CpuParallel; pub type StarkProofStream = ProofStream; pub struct StarkParameters { - security_level: usize, - fri_expansion_factor: usize, - num_trace_randomizers: usize, - num_randomizer_polynomials: usize, - num_colinearity_checks: usize, + pub security_level: usize, + pub fri_expansion_factor: usize, + pub num_trace_randomizers: usize, + pub num_randomizer_polynomials: usize, + pub num_colinearity_checks: usize, + pub num_non_linear_codeword_checks: usize, } impl StarkParameters { @@ -74,6 +81,7 @@ impl StarkParameters { let num_colinearity_checks = security_level / log2_of_fri_expansion_factor; let num_trace_randomizers = num_colinearity_checks * 2; + let num_non_linear_codeword_checks = security_level; StarkParameters { security_level, @@ -81,6 +89,7 @@ impl StarkParameters { num_trace_randomizers, num_randomizer_polynomials, num_colinearity_checks, + num_non_linear_codeword_checks, } } } @@ -110,25 +119,24 @@ impl fmt::Display for StarkValidationError { } pub struct Stark { - parameters: StarkParameters, - claim: Claim, - max_degree: Degree, - fri: Fri, + pub parameters: StarkParameters, + pub claim: Claim, + pub max_degree: Degree, + pub interpolant_degree: Degree, + pub fri: Fri, } impl Stark { pub fn new(claim: Claim, parameters: StarkParameters) -> Self { - let empty_table_collection = ExtTableCollection::with_padded_height(claim.padded_height); + let interpolant_degree = + interpolant_degree(claim.padded_height, parameters.num_trace_randomizers); let max_degree_with_origin = - empty_table_collection.max_degree_with_origin(parameters.num_trace_randomizers); - let max_degree = (roundup_npo2(max_degree_with_origin.degree as u64) - 1) as i64; + max_degree_with_origin(interpolant_degree, claim.padded_height); + let max_degree = (roundup_npo2(max_degree_with_origin.degree as u64) - 1) as Degree; let fri_domain_length = parameters.fri_expansion_factor * (max_degree as usize + 1); - let fri_domain_generator = - BFieldElement::primitive_root_of_unity(fri_domain_length.try_into().unwrap()).unwrap(); let coset_offset = BFieldElement::generator(); let fri = Fri::new( coset_offset, - fri_domain_generator, fri_domain_length, parameters.fri_expansion_factor, parameters.num_colinearity_checks, @@ -137,6 +145,7 @@ impl Stark { parameters, claim, max_degree, + interpolant_degree, fri, } } @@ -146,208 +155,147 @@ impl Stark { aet: AlgebraicExecutionTrace, maybe_profiler: &mut Option, ) -> Proof { + prof_start!(maybe_profiler, "base tables"); + prof_start!(maybe_profiler, "create"); + let mut master_base_table = MasterBaseTable::new( + aet, + &self.claim.program, + self.parameters.num_trace_randomizers, + self.fri.domain, + ); + prof_stop!(maybe_profiler, "create"); + prof_start!(maybe_profiler, "pad"); - let base_matrices = BaseMatrices::new(aet, &self.claim.program); - let base_trace_tables = self.padded(&base_matrices); + master_base_table.pad(); prof_stop!(maybe_profiler, "pad"); - let (x_rand_codeword, b_rand_codewords) = self.get_randomizer_codewords(); + prof_start!(maybe_profiler, "LDE"); + master_base_table.randomize_trace(); + let fri_domain_master_base_table = master_base_table.to_fri_domain_table(); + prof_stop!(maybe_profiler, "LDE"); - prof_start!(maybe_profiler, "LDE 1"); - let base_fri_domain_tables = base_trace_tables.to_fri_domain_tables( - &self.fri.domain, - self.parameters.num_trace_randomizers, - maybe_profiler, - ); - let base_fri_domain_codewords = base_fri_domain_tables.get_all_base_columns(); - let randomizer_and_base_fri_domain_codewords = - vec![b_rand_codewords, base_fri_domain_codewords.clone()].concat(); - prof_stop!(maybe_profiler, "LDE 1"); + prof_start!(maybe_profiler, "Merkle tree"); + let base_merkle_tree = fri_domain_master_base_table.merkle_tree(); + let base_merkle_tree_root = base_merkle_tree.get_root(); + prof_stop!(maybe_profiler, "Merkle tree"); - prof_start!(maybe_profiler, "Merkle tree 1"); - let transposed_base_codewords = transpose(&randomizer_and_base_fri_domain_codewords); - let base_tree = Self::get_merkle_tree(&transposed_base_codewords); - let base_merkle_tree_root = base_tree.get_root(); - prof_stop!(maybe_profiler, "Merkle tree 1"); - - // send first message - prof_start!(maybe_profiler, "Fiat-Shamir 1"); + prof_start!(maybe_profiler, "Fiat-Shamir"); + let padded_height = BFieldElement::new(master_base_table.padded_height as u64); let mut proof_stream = StarkProofStream::new(); - proof_stream.enqueue(&ProofItem::PaddedHeight(BFieldElement::new( - base_trace_tables.padded_height as u64, - ))); + proof_stream.enqueue(&ProofItem::PaddedHeight(padded_height)); proof_stream.enqueue(&ProofItem::MerkleRoot(base_merkle_tree_root)); - let extension_challenge_seed = proof_stream.prover_fiat_shamir(); - let extension_challenge_weights = - Self::sample_weights(extension_challenge_seed, AllChallenges::TOTAL_CHALLENGES); - let extension_challenges = AllChallenges::create_challenges(extension_challenge_weights); - prof_stop!(maybe_profiler, "Fiat-Shamir 1"); + let extension_weights = Self::sample_weights( + proof_stream.prover_fiat_shamir(), + AllChallenges::TOTAL_CHALLENGES, + ); + let extension_challenges = AllChallenges::create_challenges( + extension_weights, + &self.claim.input, + &self.claim.output, + ); + prof_stop!(maybe_profiler, "Fiat-Shamir"); prof_start!(maybe_profiler, "extend"); - let ext_trace_tables = - ExtTableCollection::extend_tables(&base_trace_tables, &extension_challenges); - prof_stop!(maybe_profiler, "extend"); - - prof_start!(maybe_profiler, "LDE 2"); - let ext_fri_domain_tables = ext_trace_tables.to_fri_domain_tables( - &self.fri.domain, - self.parameters.num_trace_randomizers, - maybe_profiler, + let mut master_ext_table = master_base_table.extend( + &extension_challenges, + self.parameters.num_randomizer_polynomials, ); - let extension_fri_domain_codewords = ext_fri_domain_tables.collect_all_columns(); - prof_stop!(maybe_profiler, "LDE 2"); - - prof_start!(maybe_profiler, "Merkle tree 2"); - let transposed_ext_codewords = transpose(&extension_fri_domain_codewords); - let extension_tree = Self::get_extension_merkle_tree(&transposed_ext_codewords); - prof_stop!(maybe_profiler, "Merkle tree 2"); - - // send root for extension codewords - proof_stream.enqueue(&ProofItem::MerkleRoot(extension_tree.get_root())); - - prof_start!(maybe_profiler, "degree bounds"); - prof_start!(maybe_profiler, "base"); - let base_degree_bounds = - base_fri_domain_tables.get_base_degree_bounds(self.parameters.num_trace_randomizers); - prof_stop!(maybe_profiler, "base"); - - prof_start!(maybe_profiler, "extension"); - let extension_degree_bounds = ext_fri_domain_tables - .get_extension_degree_bounds(self.parameters.num_trace_randomizers); - prof_stop!(maybe_profiler, "extension"); - - prof_start!(maybe_profiler, "quotient"); - let mut quotient_degree_bounds = ext_fri_domain_tables - .get_all_quotient_degree_bounds(self.parameters.num_trace_randomizers); - prof_stop!(maybe_profiler, "quotient"); - prof_stop!(maybe_profiler, "degree bounds"); + prof_stop!(maybe_profiler, "extend"); + prof_stop!(maybe_profiler, "base tables"); + + prof_start!(maybe_profiler, "ext tables"); + prof_start!(maybe_profiler, "LDE"); + master_ext_table.randomize_trace(); + let fri_domain_ext_master_table = master_ext_table.to_fri_domain_table(); + prof_stop!(maybe_profiler, "LDE"); + + prof_start!(maybe_profiler, "Merkle tree"); + let ext_merkle_tree = fri_domain_ext_master_table.merkle_tree(); + let ext_merkle_tree_root = ext_merkle_tree.get_root(); + proof_stream.enqueue(&ProofItem::MerkleRoot(ext_merkle_tree_root)); + prof_stop!(maybe_profiler, "Merkle tree"); + prof_stop!(maybe_profiler, "ext tables"); + + prof_start!(maybe_profiler, "quotient degree bounds"); + let quotient_degree_bounds = + all_quotient_degree_bounds(self.interpolant_degree, master_base_table.padded_height); + prof_stop!(maybe_profiler, "quotient degree bounds"); prof_start!(maybe_profiler, "quotient-domain codewords"); + let trace_domain = ArithmeticDomain::new_no_offset(master_base_table.padded_height); let quotient_domain = self.quotient_domain(); let unit_distance = self.fri.domain.length / quotient_domain.length; - let base_quotient_domain_codewords: Vec<_> = base_fri_domain_codewords - .par_iter() - .map(|c| c.clone().into_iter().step_by(unit_distance).collect_vec()) - .collect(); - let extension_quotient_domain_codewords: Vec<_> = extension_fri_domain_codewords - .par_iter() - .map(|c| c.clone().into_iter().step_by(unit_distance).collect()) - .collect(); - let full_quotient_domain_tables = ExtTableCollection::with_data( - base_trace_tables.padded_height, - base_quotient_domain_codewords.clone(), - extension_quotient_domain_codewords.clone(), - ); + let base_quotient_domain_codewords = fri_domain_master_base_table + .master_base_matrix + .slice(s![..; unit_distance, ..]); + let extension_quotient_domain_codewords = fri_domain_ext_master_table + .master_ext_matrix + .slice(s![..; unit_distance, ..]); prof_stop!(maybe_profiler, "quotient-domain codewords"); prof_start!(maybe_profiler, "quotient codewords"); - let mut quotient_codewords = full_quotient_domain_tables.get_all_quotients( - "ient_domain, + let master_quotient_table = all_quotients( + base_quotient_domain_codewords, + extension_quotient_domain_codewords, + trace_domain, + quotient_domain, &extension_challenges, maybe_profiler, ); prof_stop!(maybe_profiler, "quotient codewords"); - prof_start!(maybe_profiler, "grand cross table"); - let num_grand_cross_table_args = 1; - let num_non_lin_combi_weights = 2 * base_fri_domain_codewords.len() - + 2 * extension_fri_domain_codewords.len() - + 2 * quotient_degree_bounds.len() - + 2 * num_grand_cross_table_args; - let num_grand_cross_table_arg_weights = NUM_CROSS_TABLE_ARGS + NUM_PUBLIC_EVAL_ARGS; - - let grand_cross_table_arg_and_non_lin_combi_weights_seed = - proof_stream.prover_fiat_shamir(); - let grand_cross_table_arg_and_non_lin_combi_weights = Self::sample_weights( - grand_cross_table_arg_and_non_lin_combi_weights_seed, - num_grand_cross_table_arg_weights + num_non_lin_combi_weights, - ); - let (grand_cross_table_argument_weights, non_lin_combi_weights) = - grand_cross_table_arg_and_non_lin_combi_weights - .split_at(num_grand_cross_table_arg_weights); - - // prove equal terminal values for the column tuples pertaining to cross table arguments - let input_terminal = EvalArg::compute_terminal( - &self.claim.input, - EvalArg::default_initial(), - extension_challenges - .processor_table_challenges - .standard_input_eval_indeterminate, - ); - let output_terminal = EvalArg::compute_terminal( - &self.claim.output, - EvalArg::default_initial(), - extension_challenges - .processor_table_challenges - .standard_output_eval_indeterminate, - ); - let grand_cross_table_arg = GrandCrossTableArg::new( - grand_cross_table_argument_weights.try_into().unwrap(), - input_terminal, - output_terminal, - ); - let grand_cross_table_arg_quotient_codeword = grand_cross_table_arg - .terminal_quotient_codeword( - &full_quotient_domain_tables, - "ient_domain, - derive_trace_domain_generator(full_quotient_domain_tables.padded_height as u64), - ); - quotient_codewords.push(grand_cross_table_arg_quotient_codeword); - - let grand_cross_table_arg_quotient_degree_bound = grand_cross_table_arg - .quotient_degree_bound( - &full_quotient_domain_tables, - self.parameters.num_trace_randomizers, - ); - quotient_degree_bounds.push(grand_cross_table_arg_quotient_degree_bound); - prof_stop!(maybe_profiler, "grand cross table"); + // Get weights for nonlinear combination. Concretely, sample 2 weights for each base + // polynomial, each extension polynomial, and each quotient. The factor is 2 because + // transition constraints check 2 rows. + prof_start!(maybe_profiler, "Fiat-Shamir"); + let non_lin_combi_weights_seed = proof_stream.prover_fiat_shamir(); + let num_non_lin_combi_weights = + 2 * (NUM_BASE_COLUMNS + NUM_EXT_COLUMNS + num_all_table_quotients()); + let non_lin_combi_weights = + Self::sample_weights(non_lin_combi_weights_seed, num_non_lin_combi_weights); + prof_stop!(maybe_profiler, "Fiat-Shamir"); prof_start!(maybe_profiler, "nonlinear combination"); + prof_start!(maybe_profiler, "create combination codeword"); let combination_codeword = self.create_combination_codeword( - "ient_domain, + quotient_domain, base_quotient_domain_codewords, - extension_quotient_domain_codewords, - quotient_codewords, - non_lin_combi_weights.to_vec(), - base_degree_bounds, - extension_degree_bounds, + extension_quotient_domain_codewords.slice(s![.., ..NUM_EXT_COLUMNS]), + master_quotient_table.view(), + &non_lin_combi_weights, quotient_degree_bounds, - maybe_profiler, ); + prof_stop!(maybe_profiler, "create combination codeword"); prof_start!(maybe_profiler, "LDE 3"); - let fri_combination_codeword_without_randomizer = - quotient_domain.low_degree_extension(&combination_codeword, &self.fri.domain); + let fri_combination_codeword_without_randomizer = Array1::from( + quotient_domain.low_degree_extension(&combination_codeword, self.fri.domain), + ); prof_stop!(maybe_profiler, "LDE 3"); - let fri_combination_codeword: Vec<_> = fri_combination_codeword_without_randomizer - .into_par_iter() - .zip_eq(x_rand_codeword.into_par_iter()) - .map(|(cc_elem, rand_elem)| cc_elem + rand_elem) - .collect(); + let fri_combination_codeword = fri_domain_ext_master_table + .randomizer_polynomials() + .into_iter() + .fold(fri_combination_codeword_without_randomizer, ArrayBase::add) + .to_vec(); prof_stop!(maybe_profiler, "nonlinear combination"); prof_start!(maybe_profiler, "Merkle tree 3"); - let mut combination_codeword_digests: Vec = - Vec::with_capacity(fri_combination_codeword.len()); - fri_combination_codeword - .clone() - .into_par_iter() - .map(|elem| StarkHasher::hash(&elem)) - .collect_into_vec(&mut combination_codeword_digests); - let combination_tree: MerkleTree = + let combination_codeword_digests = fri_combination_codeword + .par_iter() + .map(StarkHasher::hash) + .collect::>(); + let combination_tree: MerkleTree = Maker::from_digests(&combination_codeword_digests); - let combination_root: Digest = combination_tree.get_root(); - + let combination_root = combination_tree.get_root(); proof_stream.enqueue(&ProofItem::MerkleRoot(combination_root)); - prof_stop!(maybe_profiler, "Merkle tree 3"); - // Get indices of slices that go across codewords to prove nonlinear combination + // Get indices of master table rows to prove nonlinear combination prof_start!(maybe_profiler, "Fiat-Shamir 3"); let indices_seed = proof_stream.prover_fiat_shamir(); - let cross_codeword_slice_indices = StarkHasher::sample_indices( - self.parameters.security_level, + let revealed_current_row_indices = StarkHasher::sample_indices( + self.parameters.num_non_linear_codeword_checks, &indices_seed, self.fri.domain.length, ); @@ -365,43 +313,44 @@ impl Stark { prof_start!(maybe_profiler, "open trace leafs"); // the relation between the FRI domain and the trace domain - let unit_distance = self.fri.domain.length / base_trace_tables.padded_height; + let unit_distance = self.fri.domain.length / master_base_table.padded_height; // Open leafs of zipped codewords at indicated positions - let revealed_indices = - self.get_revealed_indices(unit_distance, &cross_codeword_slice_indices); - - let revealed_base_elems = - Self::get_revealed_elements(&transposed_base_codewords, &revealed_indices); - let auth_paths_base = base_tree.get_authentication_structure(&revealed_indices); - proof_stream.enqueue(&ProofItem::TransposedBaseElementVectors( - revealed_base_elems, - )); + let revealed_current_and_next_row_indices = self + .revealed_current_and_next_row_indices(unit_distance, &revealed_current_row_indices); + + let revealed_base_elems = Self::get_revealed_elements( + fri_domain_master_base_table.master_base_matrix.view(), + &revealed_current_and_next_row_indices, + ); + let auth_paths_base = + base_merkle_tree.get_authentication_structure(&revealed_current_and_next_row_indices); + proof_stream.enqueue(&ProofItem::MasterBaseTableRows(revealed_base_elems)); proof_stream.enqueue(&ProofItem::CompressedAuthenticationPaths(auth_paths_base)); - let revealed_ext_elems = - Self::get_revealed_elements(&transposed_ext_codewords, &revealed_indices); - let auth_paths_ext = extension_tree.get_authentication_structure(&revealed_indices); - proof_stream.enqueue(&ProofItem::TransposedExtensionElementVectors( - revealed_ext_elems, - )); + let revealed_ext_elems = Self::get_revealed_elements( + fri_domain_ext_master_table.master_ext_matrix.view(), + &revealed_current_and_next_row_indices, + ); + let auth_paths_ext = + ext_merkle_tree.get_authentication_structure(&revealed_current_and_next_row_indices); + proof_stream.enqueue(&ProofItem::MasterExtTableRows(revealed_ext_elems)); proof_stream.enqueue(&ProofItem::CompressedAuthenticationPaths(auth_paths_ext)); - // open combination codeword at the same positions - // Notice that we need to loop over `indices` here, not `revealed_indices` - // as the latter includes adjacent table rows relative to the values in `indices` - let revealed_combination_elements: Vec = cross_codeword_slice_indices + // Open combination codeword at the same positions as base & ext codewords. + // Use `revealed_current_row_indices`, not `revealed_current_and_next_row_indices`, as + // the latter is only needed to check transition constraints. + let revealed_combination_elements = revealed_current_row_indices .iter() - .map(|i| fri_combination_codeword[*i]) - .collect(); + .map(|&i| fri_combination_codeword[i]) + .collect_vec(); let revealed_combination_auth_paths = - combination_tree.get_authentication_structure(&cross_codeword_slice_indices); + combination_tree.get_authentication_structure(&revealed_current_row_indices); proof_stream.enqueue(&ProofItem::RevealedCombinationElements( revealed_combination_elements, )); proof_stream.enqueue(&ProofItem::CompressedAuthenticationPaths( revealed_combination_auth_paths, )); - prof_stop!(maybe_profiler, "open trace leafs"); if std::env::var("DEBUG").is_ok() { @@ -415,229 +364,161 @@ impl Stark { } fn quotient_domain(&self) -> ArithmeticDomain { - let offset = self.fri.domain.offset; - let length = roundup_npo2(self.max_degree as u64); - let generator = BFieldElement::primitive_root_of_unity(length).unwrap(); - ArithmeticDomain::new(offset, generator, length as usize) + // When debugging, it is useful to check the degree of some intermediate polynomials. + // The quotient domain is chosen to be _just_ large enough to perform all the necessary + // computations on polynomials. Concretely, the maximal degree of a polynomial over the + // quotient domain is at most only slightly larger than the maximal degree allowed in the + // STARK proof, and could be equal. This makes computation for the prover much faster. + // However, it can also make it impossible to check if some operation (e.g., dividing out + // the zerofier) has (erroneously) increased the polynomial's degree beyond the allowed + // maximum. + if std::env::var("DEBUG").is_ok() { + self.fri.domain + } else { + let offset = self.fri.domain.offset; + let length = roundup_npo2(self.max_degree as u64); + ArithmeticDomain::new(offset, length as usize) + } } - fn get_revealed_indices( + fn revealed_current_and_next_row_indices( &self, unit_distance: usize, - cross_codeword_slice_indices: &[usize], + revealed_current_rows_indices: &[usize], ) -> Vec { - let mut revealed_indices: Vec = vec![]; - for &index in cross_codeword_slice_indices.iter() { - revealed_indices.push(index); - revealed_indices.push((index + unit_distance) % self.fri.domain.length); + let mut indices = vec![]; + for &index in revealed_current_rows_indices.iter() { + indices.push(index); + indices.push((index + unit_distance) % self.fri.domain.length); } - revealed_indices.sort_unstable(); - revealed_indices.dedup(); - revealed_indices + indices.sort_unstable(); + indices.dedup(); + indices } fn get_revealed_elements( - transposed_base_codewords: &[Vec], + master_matrix: ArrayView2, revealed_indices: &[usize], ) -> Vec> { - let revealed_base_elements = revealed_indices + revealed_indices .iter() - .map(|idx| transposed_base_codewords[*idx].clone()) - .collect_vec(); - revealed_base_elements + .map(|&idx| master_matrix.slice(s![idx, ..]).to_vec()) + .collect_vec() } - // TODO try to reduce the number of arguments - #[allow(clippy::too_many_arguments)] fn create_combination_codeword( &self, - quotient_domain: &ArithmeticDomain, - base_codewords: Vec>, - extension_codewords: Vec>, - quotient_codewords: Vec>, - weights: Vec, - base_degree_bounds: Vec, - extension_degree_bounds: Vec, - quotient_degree_bounds: Vec, - maybe_profiler: &mut Option, + quotient_domain: ArithmeticDomain, + base_codewords: ArrayView2, + extension_codewords: ArrayView2, + quotient_codewords: ArrayView2, + weights: &[XFieldElement], + quotient_degree_bounds: Vec, ) -> Vec { - prof_start!(maybe_profiler, "create combination codeword"); - let base_codewords_lifted = base_codewords - .into_iter() - .map(|base_codeword| { - base_codeword - .into_iter() - .map(|bfe| bfe.lift()) - .collect_vec() - }) - .collect_vec(); - let mut weights_iterator = weights.into_iter(); - let mut combination_codeword: Vec = vec![0.into(); quotient_domain.length]; + let (base_weights, remaining_weights) = weights.split_at(2 * NUM_BASE_COLUMNS); + let (ext_weights, quot_weights) = remaining_weights.split_at(2 * NUM_EXT_COLUMNS); + + assert_eq!(base_weights.len(), 2 * base_codewords.ncols()); + assert_eq!(ext_weights.len(), 2 * extension_codewords.ncols()); + assert_eq!(quot_weights.len(), 2 * quotient_codewords.ncols()); + let base_and_ext_col_shift = self.max_degree - self.interpolant_degree; let quotient_domain_values = quotient_domain.domain_values(); + let shifted_domain_values = + Self::degree_shift_domain("ient_domain_values, base_and_ext_col_shift); - for (codewords, bounds, identifier) in [ - (base_codewords_lifted, base_degree_bounds, "base"), - (extension_codewords, extension_degree_bounds, "ext"), - (quotient_codewords, quotient_degree_bounds, "quot"), - ] { - if std::env::var("DEBUG").is_ok() { - println!(" --- next up: {identifier} codewords"); - } - for (idx, (codeword, degree_bound)) in - codewords.into_iter().zip_eq(bounds.iter()).enumerate() - { - let shift = (self.max_degree as Degree - degree_bound) as u32; - let codeword_shifted = - Self::shift_codeword("ient_domain_values, &codeword, shift); - - combination_codeword = Self::non_linearly_add_to_codeword( - &combination_codeword, - &codeword, - &weights_iterator.next().unwrap(), - &codeword_shifted, - &weights_iterator.next().unwrap(), - ); - self.debug_check_degrees( - quotient_domain, - &idx, - degree_bound, - &shift, - &codeword, - &codeword_shifted, - identifier, - ); - } - } + let mut combination_codeword = vec![XFieldElement::zero(); quotient_domain.length]; + for (idx, (codeword, weights)) in base_codewords + .columns() + .into_iter() + .zip_eq(base_weights.chunks_exact(2)) + .enumerate() + { + Zip::from(&mut combination_codeword) + .and(codeword) + .and(shifted_domain_values.view()) + .par_for_each(|acc, &bfe, &shift| { + *acc += weights[0] * bfe + weights[1] * bfe * shift + }); + self.debug_check_degree(idx, &combination_codeword, quotient_domain); + } if std::env::var("DEBUG").is_ok() { - println!( - "The combination codeword corresponds to a polynomial of degree {}", - quotient_domain.interpolate(&combination_codeword).degree() - ); + println!(" --- next up: extension codewords"); + } + for (idx, (codeword, weights)) in extension_codewords + .columns() + .into_iter() + .zip_eq(ext_weights.chunks_exact(2)) + .enumerate() + { + Zip::from(&mut combination_codeword) + .and(codeword) + .and(shifted_domain_values.view()) + .par_for_each(|acc, &xfe, &shift| { + *acc += weights[0] * xfe + weights[1] * xfe * shift + }); + self.debug_check_degree(idx, &combination_codeword, quotient_domain); + } + if std::env::var("DEBUG").is_ok() { + println!(" --- next up: quotient codewords"); + } + for (idx, ((codeword, weights), degree_bound)) in quotient_codewords + .columns() + .into_iter() + .zip_eq(quot_weights.chunks_exact(2)) + .zip_eq(quotient_degree_bounds) + .enumerate() + { + let shifted_domain_values = + Self::degree_shift_domain("ient_domain_values, self.max_degree - degree_bound); + Zip::from(&mut combination_codeword) + .and(codeword) + .and(shifted_domain_values.view()) + .par_for_each(|acc, &xfe, &shift| { + *acc += weights[0] * xfe + weights[1] * xfe * shift + }); + self.debug_check_degree(idx, &combination_codeword, quotient_domain); } - - prof_stop!(maybe_profiler, "create combination codeword"); combination_codeword } - #[allow(clippy::too_many_arguments)] - fn debug_check_degrees( + fn degree_shift_domain( + domain_values: &[BFieldElement], + shift: Degree, + ) -> Array1 { + domain_values + .into_par_iter() + .map(|domain_value| domain_value.mod_pow_u32(shift as u32)) + .collect::>() + .into() + } + + fn debug_check_degree( &self, - domain: &ArithmeticDomain, - idx: &usize, - degree_bound: &Degree, - shift: &u32, - extension_codeword: &[XFieldElement], - extension_codeword_shifted: &[XFieldElement], - poly_type: &str, + index: usize, + combination_codeword: &[XFieldElement], + quotient_domain: ArithmeticDomain, ) { if std::env::var("DEBUG").is_err() { return; } - let interpolated = domain.interpolate(extension_codeword); - let interpolated_shifted = domain.interpolate(extension_codeword_shifted); - let int_shift_deg = interpolated_shifted.degree(); - let maybe_excl_mark = if int_shift_deg > self.max_degree as isize { + let max_degree = self.max_degree; + let degree = quotient_domain.interpolate(combination_codeword).degree(); + let maybe_excl_mark = if degree > max_degree as isize { "!!!" - } else if int_shift_deg != -1 && int_shift_deg != self.max_degree as isize { - " ! " + } else if degree != -1 && degree != max_degree as isize { + "!" } else { - " " + "" }; println!( - "{maybe_excl_mark} The shifted {poly_type} codeword with index {idx:>2} \ - must be of maximal degree {}. Got {}. Predicted degree of unshifted codeword: \ - {degree_bound}. Actual degree of unshifted codeword: {}. Shift = {shift}.", - self.max_degree, - int_shift_deg, - interpolated.degree(), + "{maybe_excl_mark:^3} combination codeword has degree {degree} after absorbing \ + shifted codeword with index {index:>2}. Must be of maximal degree {max_degree}." ); } - fn non_linearly_add_to_codeword( - combination_codeword: &Vec, - summand: &Vec, - weight: &XFieldElement, - summand_shifted: &Vec, - weight_shifted: &XFieldElement, - ) -> Vec { - combination_codeword - .par_iter() - .zip_eq(summand.par_iter()) - .map(|(cc_elem, &summand_elem)| *cc_elem + *weight * summand_elem) - .zip_eq(summand_shifted.par_iter()) - .map(|(cc_elem, &summand_shifted_elem)| { - cc_elem + *weight_shifted * summand_shifted_elem - }) - .collect() - } - - fn shift_codeword( - quotient_domain_values: &[BFieldElement], - codeword: &[XFieldElement], - shift: u32, - ) -> Vec { - quotient_domain_values - .par_iter() - .zip_eq(codeword.par_iter()) - .map(|(x, &codeword_element)| (codeword_element * x.mod_pow_u32(shift))) - .collect() - } - - fn get_extension_merkle_tree( - transposed_extension_codewords: &Vec>, - ) -> MerkleTree { - let mut extension_codeword_digests_by_index = - Vec::with_capacity(transposed_extension_codewords.len()); - - transposed_extension_codewords - .into_par_iter() - .map(|transposed_ext_codeword| { - let transposed_ext_codeword_coeffs: Vec = transposed_ext_codeword - .iter() - .map(|elem| elem.coefficients.to_vec()) - .concat(); - - StarkHasher::hash_slice(&transposed_ext_codeword_coeffs) - }) - .collect_into_vec(&mut extension_codeword_digests_by_index); - - Maker::from_digests(&extension_codeword_digests_by_index) - } - - fn get_merkle_tree( - codewords: &Vec>, - ) -> MerkleTree { - let mut codeword_digests_by_index = Vec::with_capacity(codewords.len()); - codewords - .par_iter() - .map(|values| StarkHasher::hash_slice(values)) - .collect_into_vec(&mut codeword_digests_by_index); - Maker::from_digests(&codeword_digests_by_index) - } - - fn padded(&self, base_matrices: &BaseMatrices) -> BaseTableCollection { - let mut base_tables = BaseTableCollection::from_base_matrices(base_matrices); - base_tables.pad(); - base_tables - } - - fn get_randomizer_codewords(&self) -> (Vec, Vec>) { - let randomizer_coefficients = random_elements(self.max_degree as usize + 1); - let randomizer_polynomial = Polynomial::::new(randomizer_coefficients); - - let x_randomizer_codeword = self.fri.domain.evaluate(&randomizer_polynomial); - let mut b_randomizer_codewords = vec![vec![], vec![], vec![]]; - for x_elem in x_randomizer_codeword.iter() { - b_randomizer_codewords[0].push(x_elem.coefficients[0]); - b_randomizer_codewords[1].push(x_elem.coefficients[1]); - b_randomizer_codewords[2].push(x_elem.coefficients[2]); - } - (x_randomizer_codeword, b_randomizer_codewords) - } - fn sample_weights(seed: Digest, num_weights: usize) -> Vec { StarkHasher::get_n_hash_rounds(&seed, num_weights) .iter() @@ -656,95 +537,43 @@ impl Stark { prof_start!(maybe_profiler, "Fiat-Shamir 1"); let padded_height = proof_stream.dequeue()?.as_padded_heights()?.value() as usize; + if self.claim.padded_height != padded_height { + return Err(anyhow!(StarkValidationError::PaddedHeightInequality)); + } let base_merkle_tree_root = proof_stream.dequeue()?.as_merkle_root()?; let extension_challenge_seed = proof_stream.verifier_fiat_shamir(); - let extension_challenge_weights = Self::sample_weights(extension_challenge_seed, AllChallenges::TOTAL_CHALLENGES); - let extension_challenges = AllChallenges::create_challenges(extension_challenge_weights); - if self.claim.padded_height != padded_height && self.claim.padded_height != 0 { - return Err(anyhow::Error::new( - StarkValidationError::PaddedHeightInequality, - )); - } + let challenges = AllChallenges::create_challenges( + extension_challenge_weights, + &self.claim.input, + &self.claim.output, + ); prof_stop!(maybe_profiler, "Fiat-Shamir 1"); prof_start!(maybe_profiler, "dequeue"); - let extension_tree_merkle_root = proof_stream.dequeue()?.as_merkle_root()?; prof_stop!(maybe_profiler, "dequeue"); - prof_start!(maybe_profiler, "degree bounds"); - prof_start!(maybe_profiler, "generate tables"); - let ext_table_collection = ExtTableCollection::for_verifier(padded_height, maybe_profiler); - prof_stop!(maybe_profiler, "generate tables"); - - prof_start!(maybe_profiler, "base"); - let base_degree_bounds = - ext_table_collection.get_all_base_degree_bounds(self.parameters.num_trace_randomizers); - prof_stop!(maybe_profiler, "base"); - - prof_start!(maybe_profiler, "extension"); - let extension_degree_bounds = - ext_table_collection.get_extension_degree_bounds(self.parameters.num_trace_randomizers); - prof_stop!(maybe_profiler, "extension"); - - prof_start!(maybe_profiler, "quotient"); - let quotient_degree_bounds = ext_table_collection - .get_all_quotient_degree_bounds(self.parameters.num_trace_randomizers); - prof_stop!(maybe_profiler, "quotient"); - prof_stop!(maybe_profiler, "degree bounds"); - - // Get weights for nonlinear combination. Concretely, sample 2 weights for each base, and - // extension polynomial and each quotients, because transition constraints check 2 rows. + // Get weights for nonlinear combination. Concretely, sample 2 weights for each base + // polynomial, each extension polynomial, and each quotient. The factor is 2 because + // transition constraints check 2 rows. prof_start!(maybe_profiler, "Fiat-Shamir 2"); - let num_base_polynomials = base_degree_bounds.len(); - let num_extension_polynomials = extension_degree_bounds.len(); - let num_grand_cross_table_args = 1; - let num_non_lin_combi_weights = 2 * num_base_polynomials - + 2 * num_extension_polynomials - + 2 * quotient_degree_bounds.len() - + 2 * num_grand_cross_table_args; - let num_grand_cross_table_arg_weights = NUM_CROSS_TABLE_ARGS + NUM_PUBLIC_EVAL_ARGS; - - let grand_cross_table_arg_and_non_lin_combi_weights_seed = - proof_stream.verifier_fiat_shamir(); - let grand_cross_table_arg_and_non_lin_combi_weights = Self::sample_weights( - grand_cross_table_arg_and_non_lin_combi_weights_seed, - num_grand_cross_table_arg_weights + num_non_lin_combi_weights, - ); - let (grand_cross_table_argument_weights, non_lin_combi_weights) = - grand_cross_table_arg_and_non_lin_combi_weights - .split_at(num_grand_cross_table_arg_weights); - - let input_terminal = EvalArg::compute_terminal( - &self.claim.input, - EvalArg::default_initial(), - extension_challenges - .processor_table_challenges - .standard_input_eval_indeterminate, - ); - let output_terminal = EvalArg::compute_terminal( - &self.claim.output, - EvalArg::default_initial(), - extension_challenges - .processor_table_challenges - .standard_output_eval_indeterminate, - ); - let grand_cross_table_arg = GrandCrossTableArg::new( - grand_cross_table_argument_weights.try_into().unwrap(), - input_terminal, - output_terminal, - ); + let non_lin_combi_weights_seed = proof_stream.verifier_fiat_shamir(); + let num_non_lin_combi_weights = + 2 * (NUM_BASE_COLUMNS + NUM_EXT_COLUMNS + num_all_table_quotients()); + let non_lin_combi_weights = Array1::from(Self::sample_weights( + non_lin_combi_weights_seed, + num_non_lin_combi_weights, + )); prof_stop!(maybe_profiler, "Fiat-Shamir 2"); prof_start!(maybe_profiler, "Fiat-Shamir 3"); let combination_root = proof_stream.dequeue()?.as_merkle_root()?; - let indices_seed = proof_stream.verifier_fiat_shamir(); - let combination_check_indices = StarkHasher::sample_indices( - self.parameters.security_level, + let revealed_current_row_indices = StarkHasher::sample_indices( + self.parameters.num_non_linear_codeword_checks, &indices_seed, self.fri.domain.length, ); @@ -759,19 +588,17 @@ impl Stark { prof_start!(maybe_profiler, "check leafs"); prof_start!(maybe_profiler, "get indices"); // the relation between the FRI domain and the trace domain - let unit_distance = self.fri.domain.length / ext_table_collection.padded_height; - // Open leafs of zipped codewords at indicated positions - let revealed_indices = self.get_revealed_indices(unit_distance, &combination_check_indices); + let unit_distance = self.fri.domain.length / padded_height; + let revealed_current_and_next_row_indices = self + .revealed_current_and_next_row_indices(unit_distance, &revealed_current_row_indices); prof_stop!(maybe_profiler, "get indices"); prof_start!(maybe_profiler, "dequeue base elements"); - let revealed_base_elems = proof_stream - .dequeue()? - .as_transposed_base_element_vectors()?; - let auth_paths_base = proof_stream + let base_table_rows = proof_stream.dequeue()?.as_master_base_table_rows()?; + let base_auth_paths = proof_stream .dequeue()? .as_compressed_authentication_paths()?; - let leaf_digests_base: Vec<_> = revealed_base_elems + let leaf_digests_base: Vec<_> = base_table_rows .par_iter() .map(|revealed_base_elem| StarkHasher::hash_slice(revealed_base_elem)) .collect(); @@ -780,37 +607,35 @@ impl Stark { prof_start!(maybe_profiler, "Merkle verify (base tree)"); if !MerkleTree::::verify_authentication_structure_from_leaves( base_merkle_tree_root, - &revealed_indices, + &revealed_current_and_next_row_indices, &leaf_digests_base, - &auth_paths_base, + &base_auth_paths, ) { bail!("Failed to verify authentication path for base codeword"); } prof_stop!(maybe_profiler, "Merkle verify (base tree)"); prof_start!(maybe_profiler, "dequeue extension elements"); - let revealed_ext_elems = proof_stream - .dequeue()? - .as_transposed_extension_element_vectors()?; + let ext_table_rows = proof_stream.dequeue()?.as_master_ext_table_rows()?; let auth_paths_ext = proof_stream .dequeue()? .as_compressed_authentication_paths()?; - let leaf_digests_ext: Vec<_> = revealed_ext_elems + let leaf_digests_ext = ext_table_rows .par_iter() .map(|xvalues| { - let bvalues: Vec = xvalues + let bvalues = xvalues .iter() .flat_map(|xfe| xfe.coefficients.to_vec()) - .collect(); + .collect_vec(); StarkHasher::hash_slice(&bvalues) }) - .collect(); + .collect::>(); prof_stop!(maybe_profiler, "dequeue extension elements"); prof_start!(maybe_profiler, "Merkle verify (extension tree)"); if !MerkleTree::::verify_authentication_structure_from_leaves( extension_tree_merkle_root, - &revealed_indices, + &revealed_current_and_next_row_indices, &leaf_digests_ext, &auth_paths_ext, ) { @@ -822,16 +647,16 @@ impl Stark { prof_start!(maybe_profiler, "Merkle verify (combination tree)"); let revealed_combination_leafs = proof_stream.dequeue()?.as_revealed_combination_elements()?; - let revealed_combination_digests: Vec<_> = revealed_combination_leafs + let revealed_combination_digests = revealed_combination_leafs .par_iter() .map(StarkHasher::hash) - .collect(); + .collect::>(); let revealed_combination_auth_paths = proof_stream .dequeue()? .as_compressed_authentication_paths()?; if !MerkleTree::::verify_authentication_structure_from_leaves( combination_root, - &combination_check_indices, + &revealed_current_row_indices, &revealed_combination_digests, &revealed_combination_auth_paths, ) { @@ -840,228 +665,166 @@ impl Stark { prof_stop!(maybe_profiler, "Merkle verify (combination tree)"); prof_stop!(maybe_profiler, "check leafs"); - // TODO: we can store the elements mushed into "index_map_of_revealed_elems" separately, - // like in "cross_slice_by_table" below, to avoid unmushing later prof_start!(maybe_profiler, "nonlinear combination"); - prof_start!(maybe_profiler, "restructure"); - let index_map_of_revealed_elems = Self::get_index_map_of_revealed_elems( - self.parameters.num_randomizer_polynomials, - revealed_indices, - revealed_base_elems, - revealed_ext_elems, - ); - prof_stop!(maybe_profiler, "restructure"); + prof_start!(maybe_profiler, "index"); + let (indexed_base_table_rows, indexed_ext_table_rows, indexed_randomizer_rows) = + Self::index_revealed_rows( + revealed_current_and_next_row_indices, + base_table_rows, + ext_table_rows, + ); + prof_stop!(maybe_profiler, "index"); + + // verify non-linear combination + prof_start!(maybe_profiler, "degree bounds"); + let base_and_ext_col_shift = self.max_degree - self.interpolant_degree; + let initial_quotient_degree_bounds = + all_initial_quotient_degree_bounds(self.interpolant_degree); + let consistency_quotient_degree_bounds = + all_consistency_quotient_degree_bounds(self.interpolant_degree, padded_height); + let transition_quotient_degree_bounds = + all_transition_quotient_degree_bounds(self.interpolant_degree, padded_height); + let terminal_quotient_degree_bounds = + all_terminal_quotient_degree_bounds(self.interpolant_degree); + prof_stop!(maybe_profiler, "degree bounds"); + + prof_start!(maybe_profiler, "pre-compute all shifts"); + let mut all_shifts = vec![self.interpolant_degree] + .iter() + .chain(initial_quotient_degree_bounds.iter()) + .chain(consistency_quotient_degree_bounds.iter()) + .chain(transition_quotient_degree_bounds.iter()) + .chain(terminal_quotient_degree_bounds.iter()) + .map(|degree_bound| self.max_degree - degree_bound) + .collect_vec(); + all_shifts.sort(); + all_shifts.dedup(); + let mut all_shifted_fri_domain_values = HashMap::new(); + prof_stop!(maybe_profiler, "pre-compute all shifts"); - // ======================================= - // ==== verify non-linear combination ==== - // ======================================= prof_start!(maybe_profiler, "main loop"); - let base_offset = self.parameters.num_randomizer_polynomials; - let ext_offset = base_offset + num_base_polynomials; - let final_offset = ext_offset + num_extension_polynomials; - let trace_domain_generator = derive_trace_domain_generator(padded_height as u64); + let trace_domain_generator = derive_domain_generator(padded_height as u64); let trace_domain_generator_inverse = trace_domain_generator.inverse(); - for (combination_check_index, revealed_combination_leaf) in combination_check_indices + for (current_row_idx, revealed_combination_leaf) in revealed_current_row_indices .into_iter() .zip_eq(revealed_combination_leafs) { prof_itr0!(maybe_profiler, "main loop"); - prof_start!(maybe_profiler, "populate"); - let current_fri_domain_value = - self.fri.domain.domain_value(combination_check_index as u32); - let cross_slice = &index_map_of_revealed_elems[&combination_check_index]; - - let mut summands = vec![]; - - // populate summands with a cross-slice of (base,ext) codewords and their shifts - for (index_range, degree_bounds) in [ - (base_offset..ext_offset, base_degree_bounds.iter()), - (ext_offset..final_offset, extension_degree_bounds.iter()), - ] { - for (codeword_index, degree_bound) in index_range.zip_eq(degree_bounds) { - let shift = self.max_degree - degree_bound; - let curr_codeword_elem = cross_slice[codeword_index]; - let curr_codeword_elem_shifted = - curr_codeword_elem * current_fri_domain_value.mod_pow_u32(shift as u32); - summands.push(curr_codeword_elem); - summands.push(curr_codeword_elem_shifted); - } + let next_row_idx = (current_row_idx + unit_distance) % self.fri.domain.length; + let current_base_row = indexed_base_table_rows[¤t_row_idx].view(); + let current_ext_row = indexed_ext_table_rows[¤t_row_idx].view(); + let next_base_row = indexed_base_table_rows[&next_row_idx].view(); + let next_ext_row = indexed_ext_table_rows[&next_row_idx].view(); + + prof_start!(maybe_profiler, "zerofiers"); + let one = BFieldElement::one(); + let current_fri_domain_value = self.fri.domain.domain_value(current_row_idx as u32); + let initial_zerofier_inverse = (current_fri_domain_value - one).inverse(); + let consistency_zerofier_inverse = + (current_fri_domain_value.mod_pow_u32(padded_height as u32) - one).inverse(); + let except_last_row = current_fri_domain_value - trace_domain_generator_inverse; + let transition_zerofier_inverse = except_last_row * consistency_zerofier_inverse; + let terminal_zerofier_inverse = except_last_row.inverse(); // i.e., only last row + prof_stop!(maybe_profiler, "zerofiers"); + + prof_start!(maybe_profiler, "shifted FRI domain values"); + // Minimize the respective exponents and thus work spent exponentiating by using the + // fact that `all_shifts` is sorted. Concretely, use + // 1. `x^curr_shift = x^(prev_shift + shift_diff) = x^prev_shift * x^shift_diff`, + // 2. memoization of `x^prev_shift`, and + // 3. the fact that exponentiation by a smaller exponent is computationally cheaper. + let mut previous_shift = all_shifts[0]; + let mut previously_shifted_fri_domain_value = + current_fri_domain_value.mod_pow_u32(previous_shift as u32); + all_shifted_fri_domain_values + .insert(previous_shift, previously_shifted_fri_domain_value); + for &shift in all_shifts.iter().skip(1) { + let current_shifted_fri_domain_value = previously_shifted_fri_domain_value + * current_fri_domain_value.mod_pow_u32((shift - previous_shift) as u32); + all_shifted_fri_domain_values.insert(shift, current_shifted_fri_domain_value); + previous_shift = shift; + previously_shifted_fri_domain_value = current_shifted_fri_domain_value; } - prof_stop!(maybe_profiler, "populate"); - - prof_start!(maybe_profiler, "unmush"); - // unmush cross-codeword slice: pick (base, ext) columns per table - let mut curr_base_idx = base_offset; - let mut curr_ext_idx = ext_offset; - let mut cross_slice_by_table: Vec> = vec![]; - let mut next_cross_slice_by_table: Vec> = vec![]; - - for table in ext_table_collection.into_iter() { - let num_base_cols = table.base_width(); - let num_ext_cols = table.full_width() - table.base_width(); - - let base_col_slice = - cross_slice[curr_base_idx..curr_base_idx + num_base_cols].to_vec(); - let ext_col_slice = cross_slice[curr_ext_idx..curr_ext_idx + num_ext_cols].to_vec(); - let table_slice = [base_col_slice, ext_col_slice].concat(); - cross_slice_by_table.push(table_slice); - - let next_cross_slice_index = - (combination_check_index + unit_distance) % self.fri.domain.length; - let next_cross_slice = &index_map_of_revealed_elems[&next_cross_slice_index]; - - let next_base_col_slice = - next_cross_slice[curr_base_idx..curr_base_idx + num_base_cols].to_vec(); - let next_ext_col_slice = - next_cross_slice[curr_ext_idx..curr_ext_idx + num_ext_cols].to_vec(); - let next_table_slice = [next_base_col_slice, next_ext_col_slice].concat(); - next_cross_slice_by_table.push(next_table_slice); - - curr_base_idx += num_base_cols; - curr_ext_idx += num_ext_cols; + prof_stop!(maybe_profiler, "shifted FRI domain values"); + + prof_start!(maybe_profiler, "evaluate AIR"); + let evaluated_initial_constraints = + evaluate_all_initial_constraints(current_base_row, current_ext_row, &challenges); + let evaluated_consistency_constraints = evaluate_all_consistency_constraints( + current_base_row, + current_ext_row, + &challenges, + ); + let evaluated_transition_constraints = evaluate_all_transition_constraints( + current_base_row, + current_ext_row, + next_base_row, + next_ext_row, + &challenges, + ); + let evaluated_terminal_constraints = + evaluate_all_terminal_constraints(current_base_row, current_ext_row, &challenges); + prof_stop!(maybe_profiler, "evaluate AIR"); + + prof_start!(maybe_profiler, "populate base & ext elements"); + // populate summands with a the revealed FRI domain master table rows and their shifts + let base_ext_fri_domain_value_shifted = + all_shifted_fri_domain_values[&base_and_ext_col_shift]; + let mut summands = Vec::with_capacity(non_lin_combi_weights.len()); + for &base_row_element in current_base_row.iter() { + let base_row_element_shifted = base_row_element * base_ext_fri_domain_value_shifted; + summands.push(base_row_element.lift()); + summands.push(base_row_element_shifted.lift()); } - assert_eq!(ext_offset, curr_base_idx); - assert_eq!(final_offset, curr_ext_idx); - prof_stop!(maybe_profiler, "unmush"); - - prof_start!(maybe_profiler, "inner loop"); - // use AIR (actually RAP) to get relevant parts of quotient codewords - for ((table_row, next_table_row), table) in cross_slice_by_table - .iter() - .zip_eq(next_cross_slice_by_table.iter()) - .zip_eq(ext_table_collection.into_iter()) - { - prof_itr0!(maybe_profiler, "inner loop"); - prof_start!(maybe_profiler, "degree bounds"); - let initial_quotient_degree_bounds = table.get_initial_quotient_degree_bounds( - padded_height, - self.parameters.num_trace_randomizers, - ); - let consistency_quotient_degree_bounds = table - .get_consistency_quotient_degree_bounds( - padded_height, - self.parameters.num_trace_randomizers, - ); - let transition_quotient_degree_bounds = table - .get_transition_quotient_degree_bounds( - padded_height, - self.parameters.num_trace_randomizers, - ); - let terminal_quotient_degree_bounds = table.get_terminal_quotient_degree_bounds( - padded_height, - self.parameters.num_trace_randomizers, - ); - prof_stop!(maybe_profiler, "degree bounds"); - prof_start!(maybe_profiler, "initial constraint quotient points"); - for (evaluated_bc, degree_bound) in table - .evaluate_initial_constraints(table_row, &extension_challenges) - .into_iter() - .zip_eq(initial_quotient_degree_bounds.iter()) - { - let shift = self.max_degree - degree_bound; - let quotient = - evaluated_bc / (current_fri_domain_value - BFieldElement::one()).lift(); - let quotient_shifted = - quotient * current_fri_domain_value.mod_pow_u32(shift as u32); - summands.push(quotient); - summands.push(quotient_shifted); - } - prof_stop!(maybe_profiler, "initial constraint quotient points"); - - prof_start!(maybe_profiler, "consistency constraint quotient points"); - for (evaluated_cc, degree_bound) in table - .evaluate_consistency_constraints(table_row, &extension_challenges) - .into_iter() - .zip_eq(consistency_quotient_degree_bounds.iter()) - { - let shift = self.max_degree - degree_bound; - let quotient = evaluated_cc - / (current_fri_domain_value.mod_pow_u32(padded_height as u32) - - BFieldElement::one()) - .lift(); - let quotient_shifted = - quotient * current_fri_domain_value.mod_pow_u32(shift as u32); - summands.push(quotient); - summands.push(quotient_shifted); - } - prof_stop!(maybe_profiler, "consistency constraint quotient points"); - - prof_start!(maybe_profiler, "transition constraint quotient points"); - for (evaluated_tc, degree_bound) in table - .evaluate_transition_constraints( - table_row, - next_table_row, - &extension_challenges, - ) - .into_iter() - .zip_eq(transition_quotient_degree_bounds.iter()) - { - let shift = self.max_degree - degree_bound; - let quotient = { - let numerator = current_fri_domain_value - trace_domain_generator_inverse; - let denominator = current_fri_domain_value - .mod_pow_u32(padded_height as u32) - - BFieldElement::one(); - evaluated_tc * numerator / denominator.lift() - }; - let quotient_shifted = - quotient * current_fri_domain_value.mod_pow_u32(shift as u32); - summands.push(quotient); - summands.push(quotient_shifted); - } - prof_stop!(maybe_profiler, "transition constraint quotient points"); - - prof_start!(maybe_profiler, "terminal constraint quotient points"); - for (evaluated_termc, degree_bound) in table - .evaluate_terminal_constraints(table_row, &extension_challenges) - .into_iter() - .zip_eq(terminal_quotient_degree_bounds.iter()) + for &ext_row_element in current_ext_row.iter() { + let ext_row_element_shifted = ext_row_element * base_ext_fri_domain_value_shifted; + summands.push(ext_row_element); + summands.push(ext_row_element_shifted); + } + prof_stop!(maybe_profiler, "populate base & ext elements"); + + prof_start!(maybe_profiler, "populate quotient elements"); + for (degree_bound_category, evaluated_constraints_category, zerofier_inverse) in [ + ( + &initial_quotient_degree_bounds, + evaluated_initial_constraints, + initial_zerofier_inverse, + ), + ( + &consistency_quotient_degree_bounds, + evaluated_consistency_constraints, + consistency_zerofier_inverse, + ), + ( + &transition_quotient_degree_bounds, + evaluated_transition_constraints, + transition_zerofier_inverse, + ), + ( + &terminal_quotient_degree_bounds, + evaluated_terminal_constraints, + terminal_zerofier_inverse, + ), + ] { + for (degree_bound, evaluated_constraint) in degree_bound_category + .iter() + .zip_eq(evaluated_constraints_category.into_iter()) { let shift = self.max_degree - degree_bound; - let quotient = evaluated_termc - / (current_fri_domain_value - trace_domain_generator_inverse).lift(); - let quotient_shifted = - quotient * current_fri_domain_value.mod_pow_u32(shift as u32); + let quotient = evaluated_constraint * zerofier_inverse; + let quotient_shifted = quotient * all_shifted_fri_domain_values[&shift]; summands.push(quotient); summands.push(quotient_shifted); } - - prof_stop!(maybe_profiler, "terminal constraint quotient points"); } - prof_stop!(maybe_profiler, "inner loop"); - - prof_start!(maybe_profiler, "grand cross-table argument"); - - let grand_cross_table_arg_degree_bound = grand_cross_table_arg.quotient_degree_bound( - &ext_table_collection, - self.parameters.num_trace_randomizers, - ); - let shift = self.max_degree - grand_cross_table_arg_degree_bound; - let grand_cross_table_arg_evaluated = - grand_cross_table_arg.evaluate_non_linear_sum_of_differences(&cross_slice_by_table); - let grand_cross_table_arg_quotient = grand_cross_table_arg_evaluated - / (current_fri_domain_value - trace_domain_generator_inverse).lift(); - let grand_cross_table_arg_quotient_shifted = - grand_cross_table_arg_quotient * current_fri_domain_value.mod_pow_u32(shift as u32); - summands.push(grand_cross_table_arg_quotient); - summands.push(grand_cross_table_arg_quotient_shifted); - prof_stop!(maybe_profiler, "grand cross-table argument"); + prof_stop!(maybe_profiler, "populate quotient elements"); prof_start!(maybe_profiler, "compute inner product"); - let inner_product: XFieldElement = non_lin_combi_weights - .par_iter() - .zip_eq(summands.par_iter()) - .map(|(&weight, &summand)| weight * summand) - .sum(); - let randomizer_codewords_contribution = cross_slice[0..base_offset] - .iter() - .fold(XFieldElement::zero(), |acc, &summand| acc + summand); - + let inner_product = (&non_lin_combi_weights * &Array1::from(summands)).sum(); + let randomizer_codewords_contribution = indexed_randomizer_rows[¤t_row_idx].sum(); if revealed_combination_leaf != inner_product + randomizer_codewords_contribution { - return Err(anyhow::Error::new( - StarkValidationError::CombinationLeafInequality, - )); + return Err(anyhow!(StarkValidationError::CombinationLeafInequality)); } prof_stop!(maybe_profiler, "compute inner product"); } @@ -1070,79 +833,85 @@ impl Stark { Ok(true) } - fn get_index_map_of_revealed_elems( - num_randomizer_polynomials: usize, + /// Hash-maps for base, extension, and randomizer rows that allow doing + /// `indexed_revealed_base_rows[revealed_index]` instead of + /// `revealed_base_rows[revealed_indices.iter().position(|&i| i == revealed_index).unwrap()]`. + #[allow(clippy::type_complexity)] + fn index_revealed_rows( revealed_indices: Vec, - revealed_base_elems: Vec>, - revealed_ext_elems: Vec>, - ) -> HashMap> { - let mut index_map: HashMap> = HashMap::new(); - for (i, &idx) in revealed_indices.iter().enumerate() { - let mut rand_elems = vec![]; - for (coeff_0, coeff_1, coeff_2) in revealed_base_elems[i] - .iter() - .take(EXTENSION_DEGREE * num_randomizer_polynomials) - .tuples() - { - rand_elems.push(XFieldElement::new([*coeff_0, *coeff_1, *coeff_2])); - } + revealed_base_rows: Vec>, + revealed_ext_rows: Vec>, + ) -> ( + HashMap>, + HashMap>, + HashMap>, + ) { + assert_eq!(revealed_indices.len(), revealed_base_rows.len()); + assert_eq!(revealed_indices.len(), revealed_ext_rows.len()); - let base_elems = revealed_base_elems[i] - .iter() - .skip(EXTENSION_DEGREE * num_randomizer_polynomials) - .map(|bfe| bfe.lift()) - .collect_vec(); + let mut indexed_revealed_base_rows: HashMap> = HashMap::new(); + let mut indexed_revealed_ext_rows: HashMap> = HashMap::new(); + let mut indexed_revealed_rand_rows: HashMap> = HashMap::new(); - let cross_slice = [rand_elems, base_elems, revealed_ext_elems[i].clone()].concat(); - index_map.insert(idx, cross_slice); + for (i, &idx) in revealed_indices.iter().enumerate() { + let base_row = Array1::from(revealed_base_rows[i].to_vec()); + let ext_row = Array1::from(revealed_ext_rows[i][..NUM_EXT_COLUMNS].to_vec()); + let rand_row = Array1::from(revealed_ext_rows[i][NUM_EXT_COLUMNS..].to_vec()); + + indexed_revealed_base_rows.insert(idx, base_row); + indexed_revealed_ext_rows.insert(idx, ext_row); + indexed_revealed_rand_rows.insert(idx, rand_row); } - index_map + + ( + indexed_revealed_base_rows, + indexed_revealed_ext_rows, + indexed_revealed_rand_rows, + ) } } #[cfg(test)] pub(crate) mod triton_stark_tests { - use std::ops::Mul; - - use num_traits::{One, Zero}; - use twenty_first::shared_math::ntt::ntt; + use itertools::izip; + use ndarray::Array1; + use num_traits::Zero; use twenty_first::shared_math::other::log_2_floor; - use crate::cross_table_arguments::EvalArg; use crate::instruction::sample_programs; + use crate::instruction::AnInstruction; use crate::shared_tests::*; - use crate::table::base_matrix::AlgebraicExecutionTrace; - use crate::table::base_table::InheritsFromTable; - use crate::table::table_collection::TableId::ProcessorTable; - use crate::table::table_column::ProcessorExtTableColumn::{ - InputTableEvalArg, OutputTableEvalArg, - }; - use crate::vm::triton_vm_tests::{ - bigger_tasm_test_programs, property_based_test_programs, small_tasm_test_programs, - test_hash_nop_nop_lt, - }; + use crate::table::cross_table_argument::CrossTableArg; + use crate::table::cross_table_argument::EvalArg; + use crate::table::cross_table_argument::GrandCrossTableArg; + use crate::table::extension_table::Evaluable; + use crate::table::extension_table::Quotientable; + use crate::table::hash_table::ExtHashTable; + use crate::table::instruction_table::ExtInstructionTable; + use crate::table::jump_stack_table::ExtJumpStackTable; + use crate::table::master_table::all_degrees_with_origin; + use crate::table::master_table::MasterExtTable; + use crate::table::master_table::TableId::ProcessorTable; + use crate::table::op_stack_table::ExtOpStackTable; + use crate::table::processor_table::ExtProcessorTable; + use crate::table::program_table::ExtProgramTable; + use crate::table::ram_table::ExtRamTable; + use crate::table::table_column::BaseTableColumn; + use crate::table::table_column::ExtTableColumn; + use crate::table::table_column::MasterBaseTableColumn; + use crate::table::table_column::ProcessorBaseTableColumn; + use crate::table::table_column::ProcessorExtTableColumn::InputTableEvalArg; + use crate::table::table_column::ProcessorExtTableColumn::OutputTableEvalArg; + use crate::table::table_column::RamBaseTableColumn; + use crate::vm::triton_vm_tests::bigger_tasm_test_programs; + use crate::vm::triton_vm_tests::property_based_test_programs; + use crate::vm::triton_vm_tests::small_tasm_test_programs; + use crate::vm::triton_vm_tests::test_hash_nop_nop_lt; + use crate::vm::AlgebraicExecutionTrace; use crate::vm::Program; use super::*; - #[test] - fn all_tables_pad_to_same_height_test() { - let code = "read_io read_io push -1 mul add split push 0 eq swap1 pop "; // simulates LTE - let input_symbols = vec![5_u64.into(), 7_u64.into()]; - let (aet, _, program) = parse_setup_simulate(code, input_symbols, vec![]); - let base_matrices = BaseMatrices::new(aet, &program.to_bwords()); - let mut base_tables = BaseTableCollection::from_base_matrices(&base_matrices); - base_tables.pad(); - let padded_height = base_tables.padded_height; - assert_eq!(padded_height, base_tables.program_table.data().len()); - assert_eq!(padded_height, base_tables.instruction_table.data().len()); - assert_eq!(padded_height, base_tables.processor_table.data().len()); - assert_eq!(padded_height, base_tables.op_stack_table.data().len()); - assert_eq!(padded_height, base_tables.ram_table.data().len()); - assert_eq!(padded_height, base_tables.jump_stack_table.data().len()); - assert_eq!(padded_height, base_tables.hash_table.data().len()); - } - pub fn parse_setup_simulate( code: &str, input_symbols: Vec, @@ -1164,28 +933,33 @@ pub(crate) mod triton_stark_tests { code: &str, stdin: Vec, secret_in: Vec, - ) -> ( - BaseTableCollection, - BaseTableCollection, - usize, - Vec, - ) { - let (aet, stdout, program) = parse_setup_simulate(code, stdin, secret_in); - let base_matrices = BaseMatrices::new(aet, &program.to_bwords()); + ) -> (Stark, MasterBaseTable, MasterBaseTable) { + let (aet, stdout, program) = parse_setup_simulate(code, stdin.clone(), secret_in); - let num_trace_randomizers = 2; - let mut base_tables = BaseTableCollection::from_base_matrices(&base_matrices); + let instructions = program.to_bwords(); + let padded_height = MasterBaseTable::padded_height(&aet, &instructions); + let claim = Claim { + input: stdin, + program: instructions, + output: stdout, + padded_height, + }; + let log_expansion_factor = 2; + let security_level = 32; + let parameters = StarkParameters::new(security_level, 1 << log_expansion_factor); + let stark = Stark::new(claim, parameters); - let unpadded_base_tables = base_tables.clone(); + let mut master_base_table = MasterBaseTable::new( + aet, + &stark.claim.program, + stark.parameters.num_trace_randomizers, + stark.fri.domain, + ); - base_tables.pad(); + let unpadded_master_base_table = master_base_table.clone(); + master_base_table.pad(); - ( - base_tables, - unpadded_base_tables, - num_trace_randomizers, - stdout, - ) + (stark, unpadded_master_base_table, master_base_table) } pub fn parse_simulate_pad_extend( @@ -1193,113 +967,152 @@ pub(crate) mod triton_stark_tests { stdin: Vec, secret_in: Vec, ) -> ( - Vec, - BaseTableCollection, - BaseTableCollection, - ExtTableCollection, + Stark, + MasterBaseTable, + MasterBaseTable, + MasterExtTable, AllChallenges, - usize, ) { - let (base_tables, unpadded_base_tables, num_trace_randomizers, stdout) = + let (stark, unpadded_master_base_table, master_base_table) = parse_simulate_pad(code, stdin, secret_in); - let dummy_challenges = AllChallenges::placeholder(); - let ext_tables = ExtTableCollection::extend_tables(&base_tables, &dummy_challenges); + let dummy_challenges = AllChallenges::placeholder(&stark.claim.input, &stark.claim.output); + let master_ext_table = master_base_table.extend( + &dummy_challenges, + stark.parameters.num_randomizer_polynomials, + ); ( - stdout, - unpadded_base_tables, - base_tables, - ext_tables, + stark, + unpadded_master_base_table, + master_base_table, + master_ext_table, dummy_challenges, - num_trace_randomizers, ) } - /// To be used with `-- --nocapture`. Has mainly informative purpose. #[test] - pub fn print_all_constraint_degrees() { - let (_, _, _, ext_tables, _, num_trace_randomizers) = - parse_simulate_pad_extend("halt", vec![], vec![]); - let padded_height = ext_tables.padded_height; - let all_degrees = ext_tables - .into_iter() - .map(|ext_table| { - ext_table.all_degrees_with_origin(padded_height, num_trace_randomizers) - }) - .concat(); - for deg in all_degrees { - println!("{}", deg); + pub fn print_ram_table_example_for_specification() { + let program = "push 5 push 6 write_mem pop pop push 15 push 16 write_mem pop pop push 5 + push 0 read_mem pop pop push 15 push 0 read_mem pop pop push 5 push 7 write_mem pop pop + push 15 push 0 read_mem push 5 push 0 read_mem halt"; + let (_, master_base_table, _) = parse_simulate_pad(program, vec![], vec![]); + + println!("Processor Table:"); + println!( + "| clk | pi | ci | nia | st0 \ + | st1 | st2 | st3 | ramp | ramv |" + ); + println!( + "|-----------:|:-----------|:-----------|:-----------|-----------:\ + |-----------:|-----------:|-----------:|-----------:|-----------:|" + ); + for row in master_base_table.table(ProcessorTable).rows() { + let clk = row[ProcessorBaseTableColumn::CLK.base_table_index()].to_string(); + let st0 = row[ProcessorBaseTableColumn::ST0.base_table_index()].to_string(); + let st1 = row[ProcessorBaseTableColumn::ST1.base_table_index()].to_string(); + let st2 = row[ProcessorBaseTableColumn::ST2.base_table_index()].to_string(); + let st3 = row[ProcessorBaseTableColumn::ST3.base_table_index()].to_string(); + let ramp = row[ProcessorBaseTableColumn::RAMP.base_table_index()].to_string(); + let ramv = row[ProcessorBaseTableColumn::RAMV.base_table_index()].to_string(); + + let prev_instruction = + row[ProcessorBaseTableColumn::PreviousInstruction.base_table_index()].value(); + let curr_instruction = row[ProcessorBaseTableColumn::CI.base_table_index()].value(); + let next_instruction_or_arg = + row[ProcessorBaseTableColumn::NIA.base_table_index()].value(); + + // sorry about this mess – this is just a test. + let pi = match AnInstruction::::try_from(prev_instruction) { + Ok(AnInstruction::Halt) | Err(_) => "-".to_string(), + Ok(instr) => instr.to_string().split('0').collect_vec()[0].to_owned(), + }; + let ci = AnInstruction::::try_from(curr_instruction).unwrap(); + let nia = if ci.size() == 2 { + next_instruction_or_arg.to_string() + } else { + AnInstruction::::try_from(next_instruction_or_arg) + .unwrap() + .to_string() + .split('0') + .collect_vec()[0] + .to_owned() + }; + let ci_string = if ci.size() == 1 { + ci.to_string() + } else { + ci.to_string().split('0').collect_vec()[0].to_owned() + }; + let interesting_cols = [clk, pi, ci_string, nia, st0, st1, st2, st3, ramp, ramv]; + println!( + "{}", + interesting_cols + .iter() + .map(|ff| format!("{:>10}", format!("{ff}"))) + .collect_vec() + .join(" | ") + ); + } + println!(); + println!("RAM Table:"); + println!("| clk | pi | ramp | ramv | iord |"); + println!("|-----------:|:-----------|-----------:|-----------:|-----:|"); + for row in master_base_table.table(TableId::RamTable).rows() { + let clk = row[RamBaseTableColumn::CLK.base_table_index()].to_string(); + let ramp = row[RamBaseTableColumn::RAMP.base_table_index()].to_string(); + let ramv = row[RamBaseTableColumn::RAMV.base_table_index()].to_string(); + let iord = + row[RamBaseTableColumn::InverseOfRampDifference.base_table_index()].to_string(); + + let prev_instruction = + row[RamBaseTableColumn::PreviousInstruction.base_table_index()].value(); + let pi = match AnInstruction::::try_from(prev_instruction) { + Ok(AnInstruction::Halt) | Err(_) => "-".to_string(), + Ok(instr) => instr.to_string().split('0').collect_vec()[0].to_owned(), + }; + let interersting_cols = [clk, pi, ramp, ramv, iord]; + println!( + "{}", + interersting_cols + .iter() + .map(|ff| format!("{:>10}", format!("{ff}"))) + .collect_vec() + .join(" | ") + ); } } + /// To be used with `-- --nocapture`. Has mainly informative purpose. #[test] - pub fn shift_codeword_test() { - let claim = Claim { - input: vec![], - program: vec![], - output: vec![], - padded_height: 32, - }; - let parameters = StarkParameters::default(); - let stark = Stark::new(claim, parameters); - let fri_x_values = stark.fri.domain.domain_values(); - - let mut test_codeword: Vec = vec![0.into(); stark.fri.domain.length]; - let poly_degree = 4; - test_codeword[0..=poly_degree].copy_from_slice(&[ - 2.into(), - 42.into(), - 1.into(), - 3.into(), - 17.into(), - ]); - - ntt( - &mut test_codeword, - stark.fri.domain.generator, - log_2_floor(stark.fri.domain.length as u128) as u32, - ); - for shift in [0, 1, 5, 17, 63, 121, 128] { - let shifted_codeword = Stark::shift_codeword(&fri_x_values, &test_codeword, shift); - let interpolated_shifted_codeword = stark.fri.domain.interpolate(&shifted_codeword); - assert_eq!( - (poly_degree + shift as usize) as isize, - interpolated_shifted_codeword.degree() - ); + pub fn print_all_constraint_degrees() { + let padded_height = 2; + let num_trace_randomizers = 2; + let interpolant_degree = interpolant_degree(padded_height, num_trace_randomizers); + for deg in all_degrees_with_origin(interpolant_degree, padded_height) { + println!("{}", deg); } } - // 1. simulate(), pad(), extend(), test terminals #[test] pub fn check_io_terminals() { - let read_nop_code = " - read_io read_io read_io - nop nop - write_io push 17 write_io - "; - let input_symbols = vec![ - BFieldElement::new(3), - BFieldElement::new(5), - BFieldElement::new(7), - ]; - let (stdout, _, _, ext_table_collection, all_challenges, _) = - parse_simulate_pad_extend(read_nop_code, input_symbols.clone(), vec![]); - - let ptie = ext_table_collection.data(ProcessorTable).last().unwrap() - [usize::from(InputTableEvalArg)]; + let read_nop_code = "read_io read_io read_io nop nop write_io push 17 write_io halt"; + let input_symbols = [3, 5, 7].map(BFieldElement::new).to_vec(); + let (stark, _, _, master_ext_table, all_challenges) = + parse_simulate_pad_extend(read_nop_code, input_symbols, vec![]); + + let processor_table = master_ext_table.table(ProcessorTable); + let processor_table_last_row = processor_table.slice(s![-1, ..]); + let ptie = processor_table_last_row[InputTableEvalArg.ext_table_index()]; let ine = EvalArg::compute_terminal( - &input_symbols, + &stark.claim.input, EvalArg::default_initial(), all_challenges.input_challenges.processor_eval_indeterminate, ); assert_eq!(ptie, ine, "The input evaluation arguments do not match."); - let ptoe = ext_table_collection.data(ProcessorTable).last().unwrap() - [usize::from(OutputTableEvalArg)]; - + let ptoe = processor_table_last_row[OutputTableEvalArg.ext_table_index()]; let oute = EvalArg::compute_terminal( - &stdout, + &stark.claim.output, EvalArg::default_initial(), all_challenges .output_challenges @@ -1309,7 +1122,7 @@ pub(crate) mod triton_stark_tests { } #[test] - pub fn check_all_cross_table_terminals() { + pub fn check_grand_cross_table_argument() { let mut code_collection = small_tasm_test_programs(); code_collection.append(&mut bigger_tasm_test_programs()); code_collection.append(&mut property_based_test_programs()); @@ -1318,365 +1131,553 @@ pub(crate) mod triton_stark_tests { let code = code_with_input.source_code; let input = code_with_input.input; let secret_input = code_with_input.secret_input.clone(); - let (output, _, _, ext_table_collection, all_challenges, _) = - parse_simulate_pad_extend(&code, input.clone(), secret_input); - - let input_terminal = EvalArg::compute_terminal( - &input, - EvalArg::default_initial(), - all_challenges - .processor_table_challenges - .standard_input_eval_indeterminate, - ); - - let output_terminal = EvalArg::compute_terminal( - &output, - EvalArg::default_initial(), - all_challenges - .processor_table_challenges - .standard_output_eval_indeterminate, - ); + let (_, _, master_base_table, master_ext_table, all_challenges) = + parse_simulate_pad_extend(&code, input, secret_input); - let grand_cross_table_arg = GrandCrossTableArg::new( - &[XFieldElement::one(); NUM_CROSS_TABLE_ARGS + NUM_PUBLIC_EVAL_ARGS], - input_terminal, - output_terminal, + let processor_table = master_ext_table.table(ProcessorTable); + let processor_table_last_row = processor_table.slice(s![-1, ..]); + assert_eq!( + all_challenges.cross_table_challenges.input_terminal, + processor_table_last_row[InputTableEvalArg.ext_table_index()], + "The input terminal must match for TASM snippet #{code_idx}." ); - - for (idx, (arg, _)) in grand_cross_table_arg.into_iter().enumerate() { - let from = arg - .from() - .iter() - .map(|&(from_table, from_column)| { - ext_table_collection.data(from_table).last().unwrap()[from_column] - }) - .fold(XFieldElement::one(), XFieldElement::mul); - let to = arg - .to() - .iter() - .map(|&(to_table, to_column)| { - ext_table_collection.data(to_table).last().unwrap()[to_column] - }) - .fold(XFieldElement::one(), XFieldElement::mul); - assert_eq!( - from, to, - "Cross-table argument #{idx} must match for TASM snipped #{code_idx}." - ); - } - - let ptie = ext_table_collection.data(ProcessorTable).last().unwrap() - [usize::from(InputTableEvalArg)]; assert_eq!( - ptie, input_terminal, - "The input terminal must match for TASM snipped #{code_idx}." + all_challenges.cross_table_challenges.output_terminal, + processor_table_last_row[OutputTableEvalArg.ext_table_index()], + "The output terminal must match for TASM snippet #{code_idx}." ); - let ptoe = ext_table_collection.data(ProcessorTable).last().unwrap() - [usize::from(OutputTableEvalArg)]; + let master_base_trace_table = master_base_table.trace_table(); + let master_ext_trace_table = master_ext_table.trace_table(); + let last_master_base_row = master_base_trace_table.slice(s![-1, ..]); + let last_master_ext_row = master_ext_trace_table.slice(s![-1, ..]); + let evaluated_terminal_constraints = GrandCrossTableArg::evaluate_terminal_constraints( + last_master_base_row, + last_master_ext_row, + &all_challenges, + ); assert_eq!( - ptoe, output_terminal, - "The output terminal must match for TASM snipped #{code_idx}." + 1, + evaluated_terminal_constraints.len(), + "The number of terminal constraints must be 1 – has the design changed?" + ); + assert!( + evaluated_terminal_constraints[0].is_zero(), + "The terminal constraint must evaluate to 0 for TASM snippet #{code_idx}." ); } } #[test] fn constraint_polynomials_use_right_variable_count_test() { - let (_, _, _, ext_tables, challenges, _) = - parse_simulate_pad_extend("halt", vec![], vec![]); - - for table in ext_tables.into_iter() { - let dummy_row = vec![0.into(); table.full_width()]; - - // will panic if the number of variables is wrong - table.evaluate_initial_constraints(&dummy_row, &challenges); - table.evaluate_consistency_constraints(&dummy_row, &challenges); - table.evaluate_transition_constraints(&dummy_row, &dummy_row, &challenges); - table.evaluate_terminal_constraints(&dummy_row, &challenges); - } - } - - #[test] - fn extend_does_not_change_base_table() { - let (base_tables, _, _, _) = - parse_simulate_pad(sample_programs::FIBONACCI_LT, vec![], vec![]); - - let dummy_challenges = AllChallenges::placeholder(); - let ext_tables = ExtTableCollection::extend_tables(&base_tables, &dummy_challenges); - - for (base_table, extension_table) in base_tables.into_iter().zip(ext_tables.into_iter()) { - for column in 0..base_table.base_width() { - for row in 0..base_tables.padded_height { - assert_eq!( - base_table.data()[row][column].lift(), - extension_table.data()[row][column] - ); - } - } - } + let challenges = AllChallenges::placeholder(&[], &[]); + let base_row = Array1::zeros(NUM_BASE_COLUMNS); + let ext_row = Array1::zeros(NUM_EXT_COLUMNS); + + let br = base_row.view(); + let er = ext_row.view(); + + ExtProgramTable::evaluate_initial_constraints(br, er, &challenges); + ExtProgramTable::evaluate_consistency_constraints(br, er, &challenges); + ExtProgramTable::evaluate_transition_constraints(br, er, br, er, &challenges); + ExtProgramTable::evaluate_terminal_constraints(br, er, &challenges); + + ExtInstructionTable::evaluate_initial_constraints(br, er, &challenges); + ExtInstructionTable::evaluate_consistency_constraints(br, er, &challenges); + ExtInstructionTable::evaluate_transition_constraints(br, er, br, er, &challenges); + ExtInstructionTable::evaluate_terminal_constraints(br, er, &challenges); + + ExtProcessorTable::evaluate_initial_constraints(br, er, &challenges); + ExtProcessorTable::evaluate_consistency_constraints(br, er, &challenges); + ExtProcessorTable::evaluate_transition_constraints(br, er, br, er, &challenges); + ExtProcessorTable::evaluate_terminal_constraints(br, er, &challenges); + + ExtOpStackTable::evaluate_initial_constraints(br, er, &challenges); + ExtOpStackTable::evaluate_consistency_constraints(br, er, &challenges); + ExtOpStackTable::evaluate_transition_constraints(br, er, br, er, &challenges); + ExtOpStackTable::evaluate_terminal_constraints(br, er, &challenges); + + ExtRamTable::evaluate_initial_constraints(br, er, &challenges); + ExtRamTable::evaluate_consistency_constraints(br, er, &challenges); + ExtRamTable::evaluate_transition_constraints(br, er, br, er, &challenges); + ExtRamTable::evaluate_terminal_constraints(br, er, &challenges); + + ExtJumpStackTable::evaluate_initial_constraints(br, er, &challenges); + ExtJumpStackTable::evaluate_consistency_constraints(br, er, &challenges); + ExtJumpStackTable::evaluate_transition_constraints(br, er, br, er, &challenges); + ExtJumpStackTable::evaluate_terminal_constraints(br, er, &challenges); + + ExtHashTable::evaluate_initial_constraints(br, er, &challenges); + ExtHashTable::evaluate_consistency_constraints(br, er, &challenges); + ExtHashTable::evaluate_transition_constraints(br, er, br, er, &challenges); + ExtHashTable::evaluate_terminal_constraints(br, er, &challenges); } #[test] fn print_number_of_all_constraints_per_table() { - let (_, _, _, ext_tables, challenges, _) = - parse_simulate_pad_extend(sample_programs::COUNTDOWN_FROM_10, vec![], vec![]); + let table_names = [ + "program table", + "instruction table", + "processor table", + "op stack table", + "ram table", + "jump stack table", + "hash table", + "cross-table arg", + ]; + let all_init = [ + ExtProgramTable::num_initial_quotients(), + ExtInstructionTable::num_initial_quotients(), + ExtProcessorTable::num_initial_quotients(), + ExtOpStackTable::num_initial_quotients(), + ExtRamTable::num_initial_quotients(), + ExtJumpStackTable::num_initial_quotients(), + ExtHashTable::num_initial_quotients(), + GrandCrossTableArg::num_initial_quotients(), + ]; + let all_cons = [ + ExtProgramTable::num_consistency_quotients(), + ExtInstructionTable::num_consistency_quotients(), + ExtProcessorTable::num_consistency_quotients(), + ExtOpStackTable::num_consistency_quotients(), + ExtRamTable::num_consistency_quotients(), + ExtJumpStackTable::num_consistency_quotients(), + ExtHashTable::num_consistency_quotients(), + GrandCrossTableArg::num_consistency_quotients(), + ]; + let all_trans = [ + ExtProgramTable::num_transition_quotients(), + ExtInstructionTable::num_transition_quotients(), + ExtProcessorTable::num_transition_quotients(), + ExtOpStackTable::num_transition_quotients(), + ExtRamTable::num_transition_quotients(), + ExtJumpStackTable::num_transition_quotients(), + ExtHashTable::num_transition_quotients(), + GrandCrossTableArg::num_transition_quotients(), + ]; + let all_term = [ + ExtProgramTable::num_terminal_quotients(), + ExtInstructionTable::num_terminal_quotients(), + ExtProcessorTable::num_terminal_quotients(), + ExtOpStackTable::num_terminal_quotients(), + ExtRamTable::num_terminal_quotients(), + ExtJumpStackTable::num_terminal_quotients(), + ExtHashTable::num_terminal_quotients(), + GrandCrossTableArg::num_terminal_quotients(), + ]; + + let num_total_init: usize = all_init.iter().sum(); + let num_total_cons: usize = all_cons.iter().sum(); + let num_total_trans: usize = all_trans.iter().sum(); + let num_total_term: usize = all_term.iter().sum(); + let num_total = num_total_init + num_total_cons + num_total_trans + num_total_term; println!("| Table | Init | Cons | Trans | Term | Sum |"); println!("|:---------------------|------:|------:|------:|------:|------:|"); - - let mut num_total_initial_constraints = 0; - let mut num_total_consistency_constraints = 0; - let mut num_total_transition_constraints = 0; - let mut num_total_terminal_constraints = 0; - for table in ext_tables.into_iter() { - let num_initial_constraints = table - .evaluate_initial_constraints(&table.data()[0], &challenges) - .len(); - let num_consistency_constraints = table - .evaluate_consistency_constraints(&table.data()[0], &challenges) - .len(); - let num_transition_constraints = table - .evaluate_transition_constraints(&table.data()[0], &table.data()[1], &challenges) - .len(); - let num_terminal_constraints = table - .evaluate_terminal_constraints(table.data().last().unwrap(), &challenges) - .len(); - - let num_total_constraints = num_initial_constraints - + num_consistency_constraints - + num_transition_constraints - + num_terminal_constraints; - num_total_initial_constraints += num_initial_constraints; - num_total_consistency_constraints += num_consistency_constraints; - num_total_transition_constraints += num_transition_constraints; - num_total_terminal_constraints += num_terminal_constraints; + for (name, num_init, num_cons, num_trans, num_term) in + izip!(table_names, all_init, all_cons, all_trans, all_term) + { + let num_total = num_init + num_cons + num_trans + num_term; println!( "| {:<20} | {:>5} | {:>5} | {:>5} | {:>5} | {:>5} |", - table.name().split_whitespace().next().unwrap(), - num_initial_constraints, - num_consistency_constraints, - num_transition_constraints, - num_terminal_constraints, - num_total_constraints, + name, num_init, num_cons, num_trans, num_term, num_total, ); } - - let num_total_constraints = num_total_initial_constraints - + num_total_consistency_constraints - + num_total_transition_constraints - + num_total_terminal_constraints; println!( "| {:<20} | {:>5} | {:>5} | {:>5} | {:>5} | {:>5} |", - "Sum", - num_total_initial_constraints, - num_total_consistency_constraints, - num_total_transition_constraints, - num_total_terminal_constraints, - num_total_constraints + "Sum", num_total_init, num_total_cons, num_total_trans, num_total_term, num_total ); } #[test] - fn number_of_quotient_degree_bound_matches_number_of_constraints_test() { - let (_, _, _, ext_tables, challenges, num_trace_randomizers) = - parse_simulate_pad_extend(sample_programs::FIBONACCI_LT, vec![], vec![]); - let padded_height = ext_tables.padded_height; - - for table in ext_tables.into_iter() { - let num_initial_constraints = table - .evaluate_initial_constraints(&table.data()[0], &challenges) - .len(); - let num_initial_quotient_degree_bounds = table - .get_initial_quotient_degree_bounds(padded_height, num_trace_randomizers) - .len(); - assert_eq!( - num_initial_constraints, - num_initial_quotient_degree_bounds, - "{} has mismatching number of initial constraints and quotient degree bounds.", - table.name() - ); + fn number_of_quotient_degree_bounds_match_number_of_constraints_test() { + let base_row = Array1::zeros(NUM_BASE_COLUMNS); + let ext_row = Array1::zeros(NUM_EXT_COLUMNS); + let challenges = AllChallenges::placeholder(&[], &[]); + let padded_height = 2; + let num_trace_randomizers = 2; + let interpolant_degree = interpolant_degree(padded_height, num_trace_randomizers); - let num_consistency_constraints = table - .evaluate_consistency_constraints(&table.data()[0], &challenges) - .len(); - let num_consistency_quotient_degree_bounds = table - .get_consistency_quotient_degree_bounds(padded_height, num_trace_randomizers) - .len(); - assert_eq!( - num_consistency_constraints, - num_consistency_quotient_degree_bounds, - "{} has mismatching number of consistency constraints and quotient degree bounds.", - table.name() - ); + // Shorten some names for better formatting. This is just a test. + let ph = padded_height; + let id = interpolant_degree; + let br = base_row.view(); + let er = ext_row.view(); - let num_transition_constraints = table - .evaluate_transition_constraints(&table.data()[0], &table.data()[1], &challenges) - .len(); - let num_transition_quotient_degree_bounds = table - .get_transition_quotient_degree_bounds(padded_height, num_trace_randomizers) - .len(); - assert_eq!( - num_transition_constraints, - num_transition_quotient_degree_bounds, - "{} has mismatching number of transition constraints and quotient degree bounds.", - table.name() - ); + assert_eq!( + ExtProgramTable::num_initial_quotients(), + ExtProgramTable::evaluate_initial_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtProgramTable::num_initial_quotients(), + ExtProgramTable::initial_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtInstructionTable::num_initial_quotients(), + ExtInstructionTable::evaluate_initial_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtInstructionTable::num_initial_quotients(), + ExtInstructionTable::initial_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtProcessorTable::num_initial_quotients(), + ExtProcessorTable::evaluate_initial_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtProcessorTable::num_initial_quotients(), + ExtProcessorTable::initial_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtOpStackTable::num_initial_quotients(), + ExtOpStackTable::evaluate_initial_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtOpStackTable::num_initial_quotients(), + ExtOpStackTable::initial_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtRamTable::num_initial_quotients(), + ExtRamTable::evaluate_initial_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtRamTable::num_initial_quotients(), + ExtRamTable::initial_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtJumpStackTable::num_initial_quotients(), + ExtJumpStackTable::evaluate_initial_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtJumpStackTable::num_initial_quotients(), + ExtJumpStackTable::initial_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtHashTable::num_initial_quotients(), + ExtHashTable::evaluate_initial_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtHashTable::num_initial_quotients(), + ExtHashTable::initial_quotient_degree_bounds(id).len() + ); - let num_terminal_constraints = table - .evaluate_terminal_constraints(table.data().last().unwrap(), &challenges) - .len(); - let num_terminal_quotient_degree_bounds = table - .get_terminal_quotient_degree_bounds(padded_height, num_trace_randomizers) - .len(); - assert_eq!( - num_terminal_constraints, - num_terminal_quotient_degree_bounds, - "{} has mismatching number of terminal constraints and quotient degree bounds.", - table.name() - ); - } + assert_eq!( + ExtProgramTable::num_consistency_quotients(), + ExtProgramTable::evaluate_consistency_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtProgramTable::num_consistency_quotients(), + ExtProgramTable::consistency_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtInstructionTable::num_consistency_quotients(), + ExtInstructionTable::evaluate_consistency_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtInstructionTable::num_consistency_quotients(), + ExtInstructionTable::consistency_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtProcessorTable::num_consistency_quotients(), + ExtProcessorTable::evaluate_consistency_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtProcessorTable::num_consistency_quotients(), + ExtProcessorTable::consistency_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtOpStackTable::num_consistency_quotients(), + ExtOpStackTable::evaluate_consistency_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtOpStackTable::num_consistency_quotients(), + ExtOpStackTable::consistency_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtRamTable::num_consistency_quotients(), + ExtRamTable::evaluate_consistency_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtRamTable::num_consistency_quotients(), + ExtRamTable::consistency_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtJumpStackTable::num_consistency_quotients(), + ExtJumpStackTable::evaluate_consistency_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtJumpStackTable::num_consistency_quotients(), + ExtJumpStackTable::consistency_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtHashTable::num_consistency_quotients(), + ExtHashTable::evaluate_consistency_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtHashTable::num_consistency_quotients(), + ExtHashTable::consistency_quotient_degree_bounds(id, ph).len() + ); + + assert_eq!( + ExtProgramTable::num_transition_quotients(), + ExtProgramTable::evaluate_transition_constraints(br, er, br, er, &challenges).len(), + ); + assert_eq!( + ExtProgramTable::num_transition_quotients(), + ExtProgramTable::transition_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtInstructionTable::num_transition_quotients(), + ExtInstructionTable::evaluate_transition_constraints(br, er, br, er, &challenges).len(), + ); + assert_eq!( + ExtInstructionTable::num_transition_quotients(), + ExtInstructionTable::transition_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtProcessorTable::num_transition_quotients(), + ExtProcessorTable::evaluate_transition_constraints(br, er, br, er, &challenges).len(), + ); + assert_eq!( + ExtProcessorTable::num_transition_quotients(), + ExtProcessorTable::transition_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtOpStackTable::num_transition_quotients(), + ExtOpStackTable::evaluate_transition_constraints(br, er, br, er, &challenges).len(), + ); + assert_eq!( + ExtOpStackTable::num_transition_quotients(), + ExtOpStackTable::transition_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtRamTable::num_transition_quotients(), + ExtRamTable::evaluate_transition_constraints(br, er, br, er, &challenges).len(), + ); + assert_eq!( + ExtRamTable::num_transition_quotients(), + ExtRamTable::transition_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtJumpStackTable::num_transition_quotients(), + ExtJumpStackTable::evaluate_transition_constraints(br, er, br, er, &challenges).len(), + ); + assert_eq!( + ExtJumpStackTable::num_transition_quotients(), + ExtJumpStackTable::transition_quotient_degree_bounds(id, ph).len() + ); + assert_eq!( + ExtHashTable::num_transition_quotients(), + ExtHashTable::evaluate_transition_constraints(br, er, br, er, &challenges).len(), + ); + assert_eq!( + ExtHashTable::num_transition_quotients(), + ExtHashTable::transition_quotient_degree_bounds(id, ph).len() + ); + + assert_eq!( + ExtProgramTable::num_terminal_quotients(), + ExtProgramTable::evaluate_terminal_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtProgramTable::num_terminal_quotients(), + ExtProgramTable::terminal_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtInstructionTable::num_terminal_quotients(), + ExtInstructionTable::evaluate_terminal_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtInstructionTable::num_terminal_quotients(), + ExtInstructionTable::terminal_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtProcessorTable::num_terminal_quotients(), + ExtProcessorTable::evaluate_terminal_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtProcessorTable::num_terminal_quotients(), + ExtProcessorTable::terminal_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtOpStackTable::num_terminal_quotients(), + ExtOpStackTable::evaluate_terminal_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtOpStackTable::num_terminal_quotients(), + ExtOpStackTable::terminal_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtRamTable::num_terminal_quotients(), + ExtRamTable::evaluate_terminal_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtRamTable::num_terminal_quotients(), + ExtRamTable::terminal_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtJumpStackTable::num_terminal_quotients(), + ExtJumpStackTable::evaluate_terminal_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtJumpStackTable::num_terminal_quotients(), + ExtJumpStackTable::terminal_quotient_degree_bounds(id).len() + ); + assert_eq!( + ExtHashTable::num_terminal_quotients(), + ExtHashTable::evaluate_terminal_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtHashTable::num_terminal_quotients(), + ExtHashTable::terminal_quotient_degree_bounds(id).len() + ); } #[test] - fn triton_table_constraints_evaluate_to_zero_test_on_halt() { - let zero = XFieldElement::zero(); - let source_code_and_input = test_halt(); - let (_, _, _, ext_tables, challenges, _) = - parse_simulate_pad_extend(&source_code_and_input.source_code, vec![], vec![]); - - for table in (&ext_tables).into_iter() { - if let Some(row) = table.data().get(0) { - let evaluated_bcs = table.evaluate_initial_constraints(row, &challenges); - let num_initial_constraints = evaluated_bcs.len(); - for (constraint_idx, ebc) in evaluated_bcs.into_iter().enumerate() { - assert_eq!( - zero, - ebc, - "Failed initial constraint on {}. Constraint index: {}/{}. Row index: {}/{}", - table.name(), - num_initial_constraints, - constraint_idx, - 0, - table.data().len() - ); - } - } + fn triton_table_constraints_evaluate_to_zero_on_halt_test() { + triton_table_constraints_evaluate_to_zero(test_halt()); + } - for (row_idx, curr_row) in table.data().iter().enumerate() { - let evaluated_ccs = table.evaluate_consistency_constraints(curr_row, &challenges); - for (constraint_idx, ecc) in evaluated_ccs.into_iter().enumerate() { - assert_eq!( - zero, - ecc, - "Failed consistency constraint {}. Constraint index: {}. Row index: {}/{}", - table.name(), - constraint_idx, - row_idx, - table.data().len() - ); - } - } + #[test] + fn triton_table_constraints_evaluate_to_zero_on_fibonacci_test() { + let source_code_and_input = + SourceCodeAndInput::without_input(sample_programs::FIBONACCI_LT); + triton_table_constraints_evaluate_to_zero(source_code_and_input); + } - for (row_idx, (curr_row, next_row)) in table.data().iter().tuple_windows().enumerate() { - let evaluated_tcs = - table.evaluate_transition_constraints(curr_row, next_row, &challenges); - for (constraint_idx, evaluated_tc) in evaluated_tcs.into_iter().enumerate() { - assert_eq!( - zero, - evaluated_tc, - "Failed transition constraint on {}. Constraint index: {}. Row index: {}/{}", - table.name(), - constraint_idx, - row_idx, - table.data().len() - ); - } - } + #[test] + fn triton_table_constraints_evaluate_to_zero_on_small_programs_test() { + for (program_idx, program) in small_tasm_test_programs().into_iter().enumerate() { + println!("Testing program with index {program_idx}."); + triton_table_constraints_evaluate_to_zero(program); + println!(); + } + } - if let Some(row) = table.data().last() { - let evaluated_termcs = table.evaluate_terminal_constraints(row, &challenges); - for (constraint_idx, etermc) in evaluated_termcs.into_iter().enumerate() { - assert_eq!( - zero, - etermc, - "Failed terminal constraint on {}. Constraint index: {}. Row index: {}", - table.name(), - constraint_idx, - table.data().len() - 1, - ); - } - } + #[test] + fn triton_table_constraints_evaluate_to_zero_on_property_based_programs_test() { + for (program_idx, program) in property_based_test_programs().into_iter().enumerate() { + println!("Testing program with index {program_idx}."); + triton_table_constraints_evaluate_to_zero(program); + println!(); } } #[test] - fn triton_table_constraints_evaluate_to_zero_test_on_simple_program() { + fn triton_table_constraints_evaluate_to_zero_on_bigger_programs_test() { + for (program_idx, program) in bigger_tasm_test_programs().into_iter().enumerate() { + println!("Testing program with index {program_idx}."); + triton_table_constraints_evaluate_to_zero(program); + println!(); + } + } + + pub fn triton_table_constraints_evaluate_to_zero(source_code_and_input: SourceCodeAndInput) { let zero = XFieldElement::zero(); - let (_, _, _, ext_tables, challenges, _) = - parse_simulate_pad_extend(sample_programs::FIBONACCI_LT, vec![], vec![]); - - for table in (&ext_tables).into_iter() { - if let Some(row) = table.data().get(0) { - let evaluated_bcs = table.evaluate_initial_constraints(row, &challenges); - for (constraint_idx, ebc) in evaluated_bcs.into_iter().enumerate() { - assert_eq!( - zero, - ebc, - "Failed initial constraint on {}. Constraint index: {}. Row index: {}", - table.name(), - constraint_idx, - 0, - ); - } - } + let (_, _, master_base_table, master_ext_table, challenges) = parse_simulate_pad_extend( + &source_code_and_input.source_code, + source_code_and_input.input, + source_code_and_input.secret_input, + ); - for (row_idx, curr_row) in table.data().iter().enumerate() { - let evaluated_ccs = table.evaluate_consistency_constraints(curr_row, &challenges); - for (constraint_idx, ecc) in evaluated_ccs.into_iter().enumerate() { - assert_eq!( - zero, - ecc, - "Failed consistency constraint {}. Constraint index: {}. Row index: {}/{}", - table.name(), - constraint_idx, - row_idx, - table.data().len() - ); - } - } + assert_eq!( + master_base_table.master_base_matrix.nrows(), + master_ext_table.master_ext_matrix.nrows() + ); + let master_base_trace_table = master_base_table.trace_table(); + let master_ext_trace_table = master_ext_table.trace_table(); + assert_eq!( + master_base_trace_table.nrows(), + master_ext_trace_table.nrows() + ); - for (row_idx, (current_row, next_row)) in - table.data().iter().tuple_windows().enumerate() - { - let evaluated_tcs = - table.evaluate_transition_constraints(current_row, next_row, &challenges); - for (constraint_idx, evaluated_tc) in evaluated_tcs.into_iter().enumerate() { - assert_eq!( - zero, - evaluated_tc, - "Failed transition constraint on {}. Constraint index: {}. Row index: {}/{}", - table.name(), - constraint_idx, - row_idx, - table.data().len() - ); - } + let evaluated_initial_constraints = evaluate_all_initial_constraints( + master_base_trace_table.row(0), + master_ext_trace_table.row(0), + &challenges, + ); + let num_initial_constraints = evaluated_initial_constraints.len(); + for (constraint_idx, ebc) in evaluated_initial_constraints.into_iter().enumerate() { + assert_eq!( + zero, ebc, + "Failed initial constraint with global index {constraint_idx}. \ + Total number of initial constraints: {num_initial_constraints}.", + ); + } + + let num_rows = master_base_trace_table.nrows(); + for row_idx in 0..num_rows { + let base_row = master_base_trace_table.row(row_idx); + let ext_row = master_ext_trace_table.row(row_idx); + let evaluated_consistency_constraints = + evaluate_all_consistency_constraints(base_row, ext_row, &challenges); + let num_consistency_constraints = evaluated_consistency_constraints.len(); + for (constraint_idx, ecc) in evaluated_consistency_constraints.into_iter().enumerate() { + assert_eq!( + zero, ecc, + "Failed consistency constraint with global index {constraint_idx}. \ + Total number of consistency constraints: {num_consistency_constraints}. \ + Row index: {row_idx}. \ + Total rows: {num_rows}", + ); } + } - if let Some(row) = table.data().last() { - let evaluated_termcs = table.evaluate_terminal_constraints(row, &challenges); - for (constraint_idx, etermc) in evaluated_termcs.into_iter().enumerate() { - assert_eq!( - zero, - etermc, - "Failed terminal constraint on {}. Constraint index: {}. Row index: {}", - table.name(), - constraint_idx, - table.data().len() - 1, + for row_idx in 0..num_rows - 1 { + let base_row = master_base_trace_table.row(row_idx); + let ext_row = master_ext_trace_table.row(row_idx); + let next_base_row = master_base_trace_table.row(row_idx + 1); + let next_ext_row = master_ext_trace_table.row(row_idx + 1); + let evaluated_transition_constraints = evaluate_all_transition_constraints( + base_row, + ext_row, + next_base_row, + next_ext_row, + &challenges, + ); + let num_transition_constraints = evaluated_transition_constraints.len(); + for (constraint_idx, etc) in evaluated_transition_constraints.into_iter().enumerate() { + if zero != etc { + let pi_idx = + ProcessorBaseTableColumn::PreviousInstruction.master_base_table_index(); + let ci_idx = ProcessorBaseTableColumn::CI.master_base_table_index(); + let nia_idx = ProcessorBaseTableColumn::NIA.master_base_table_index(); + let pi = base_row[pi_idx].value(); + let ci = base_row[ci_idx].value(); + let nia = base_row[nia_idx].value(); + let previous_instruction = + AnInstruction::::try_from(pi).unwrap(); + let current_instruction = AnInstruction::::try_from(ci).unwrap(); + let next_instruction_str = match AnInstruction::::try_from(nia) { + Ok(instr) => format!("{instr:?}"), + Err(_) => "not an instruction".to_string(), + }; + panic!( + "Failed transition constraint with global index {constraint_idx}. \ + Total number of transition constraints: {num_transition_constraints}. \ + Row index: {row_idx}. \ + Total rows: {num_rows}\n\ + Previous Instruction: {previous_instruction:?} – opcode: {pi}\n\ + Current Instruction: {current_instruction:?} – opcode: {ci}\n\ + Next Instruction: {next_instruction_str} – opcode: {nia}\n" ); } } } + + let evaluated_terminal_constraints = evaluate_all_terminal_constraints( + master_base_trace_table.row(num_rows - 1), + master_ext_trace_table.row(num_rows - 1), + &challenges, + ); + let num_terminal_constraints = evaluated_terminal_constraints.len(); + for (constraint_idx, etermc) in evaluated_terminal_constraints.into_iter().enumerate() { + assert_eq!( + zero, etermc, + "Failed terminal constraint with global index {constraint_idx}. \ + Total number of terminal constraints: {num_terminal_constraints}.", + ); + } } #[test] @@ -1700,12 +1701,13 @@ pub(crate) mod triton_stark_tests { #[test] fn triton_prove_verify_halt_test() { + let mut profiler = Some(TritonProfiler::new("Prove Halt")); let code_with_input = test_halt(); let (stark, proof) = parse_simulate_prove( &code_with_input.source_code, code_with_input.input.clone(), code_with_input.secret_input.clone(), - &mut None, + &mut profiler, ); let result = stark.verify(proof, &mut None); @@ -1713,6 +1715,15 @@ pub(crate) mod triton_stark_tests { panic!("The Verifier is unhappy! {}", e); } assert!(result.unwrap()); + + let log_fri_dom_len = log_2_floor(stark.fri.domain.length as u128); + let fri_dom_len_str = format!("log_2 of FRI domain length: {log_fri_dom_len}"); + prof_start!(profiler, &fri_dom_len_str); + prof_stop!(profiler, &fri_dom_len_str); + if let Some(mut p) = profiler { + p.finish(); + println!("{}", p.report()); + } } #[test] @@ -1769,11 +1780,12 @@ pub(crate) mod triton_stark_tests { #[test] fn prove_verify_fibonacci_100_test() { + let mut profiler = Some(TritonProfiler::new("Prove Fib 100")); let source_code = sample_programs::FIBONACCI_VIT; let stdin = vec![100_u64.into()]; let secret_in = vec![]; - let (stark, proof) = parse_simulate_prove(source_code, stdin, secret_in, &mut None); + let (stark, proof) = parse_simulate_prove(source_code, stdin, secret_in, &mut profiler); println!("between prove and verify"); @@ -1782,6 +1794,15 @@ pub(crate) mod triton_stark_tests { panic!("The Verifier is unhappy! {}", e); } assert!(result.unwrap()); + + let log_fri_dom_len = log_2_floor(stark.fri.domain.length as u128); + let fri_dom_len_str = format!("log_2 of FRI domain length: {log_fri_dom_len}"); + prof_start!(profiler, &fri_dom_len_str); + prof_stop!(profiler, &fri_dom_len_str); + if let Some(mut p) = profiler { + p.finish(); + println!("{}", p.report()); + } } #[test] @@ -1796,7 +1817,7 @@ pub(crate) mod triton_stark_tests { let stdin = vec![BFieldElement::new(fibonacci_number)]; let (stark, _) = parse_simulate_prove(source_code, stdin, vec![], &mut profiler); let log_fri_dom_len = log_2_floor(stark.fri.domain.length as u128); - let fri_dom_len_str = format!("log_2 of FRI domain length: {}", log_fri_dom_len); + let fri_dom_len_str = format!("log_2 of FRI domain length: {log_fri_dom_len}"); prof_start!(profiler, &fri_dom_len_str); prof_stop!(profiler, &fri_dom_len_str); if let Some(mut p) = profiler { diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 81fa28f54..85e9a93a7 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -1,34 +1,34 @@ -use anyhow::Result; -use itertools::Itertools; -use num_traits::{One, Zero}; use std::collections::HashMap; use std::convert::TryInto; use std::fmt::Display; +use anyhow::Result; +use ndarray::Array1; +use num_traits::One; +use num_traits::Zero; use twenty_first::shared_math::b_field_element::BFieldElement; -use twenty_first::shared_math::rescue_prime_regular::{ - RescuePrimeRegular, DIGEST_LENGTH, NUM_ROUNDS, ROUND_CONSTANTS, STATE_SIZE, -}; +use twenty_first::shared_math::rescue_prime_regular::RescuePrimeRegular; +use twenty_first::shared_math::rescue_prime_regular::DIGEST_LENGTH; +use twenty_first::shared_math::rescue_prime_regular::NUM_ROUNDS; +use twenty_first::shared_math::rescue_prime_regular::STATE_SIZE; use twenty_first::shared_math::traits::Inverse; use twenty_first::shared_math::x_field_element::XFieldElement; use crate::error::vm_err; +use crate::error::vm_fail; +use crate::error::InstructionError::*; +use crate::instruction::AnInstruction::*; use crate::instruction::DivinationHint; -use crate::ord_n::{Ord16, Ord7}; -use crate::table::base_matrix::ProcessorMatrixRow; -use crate::table::hash_table::{NUM_ROUND_CONSTANTS, TOTAL_NUM_CONSTANTS}; -use crate::table::table_column::{ - InstructionBaseTableColumn, JumpStackBaseTableColumn, OpStackBaseTableColumn, - ProcessorBaseTableColumn, RamBaseTableColumn, -}; - -use super::error::{vm_fail, InstructionError::*}; -use super::instruction::{AnInstruction::*, Instruction}; -use super::op_stack::OpStack; -use super::ord_n::{Ord16::*, Ord7::*}; -use super::table::{hash_table, instruction_table, jump_stack_table, op_stack_table}; -use super::table::{processor_table, ram_table}; -use super::vm::Program; +use crate::instruction::Instruction; +use crate::op_stack::OpStack; +use crate::ord_n::Ord16; +use crate::ord_n::Ord16::*; +use crate::ord_n::Ord7; +use crate::table::processor_table; +use crate::table::processor_table::ProcessorMatrixRow; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::ProcessorBaseTableColumn; +use crate::vm::Program; /// The number of state registers for hashing-specific instructions. pub const STATE_REGISTER_COUNT: usize = 16; @@ -65,6 +65,9 @@ pub struct VMState<'pgm> { /// Current instruction's address in program memory pub instruction_pointer: usize, + /// The instruction that was executed last + pub previous_instruction: BFieldElement, + /// RAM pointer pub ramp: u64, } @@ -77,7 +80,7 @@ pub enum VMOutput { /// Trace of state registers for hash coprocessor table /// /// One row per round in the XLIX permutation - XlixTrace(Vec<[BFieldElement; hash_table::BASE_WIDTH]>), + XlixTrace(Box<[[BFieldElement; STATE_SIZE]; 1 + NUM_ROUNDS]>), } #[allow(clippy::needless_range_loop)] @@ -153,7 +156,7 @@ impl<'pgm> VMState<'pgm> { DivineSibling => { let node_index = self.op_stack.safe_peek(ST10).value(); // set hv0 register to least significant bit of st10 - hvs[0] = BFieldElement::new(node_index as u64 % 2); + hvs[0] = BFieldElement::new(node_index % 2); } Split => { let elem = self.op_stack.safe_peek(ST0); @@ -185,6 +188,11 @@ impl<'pgm> VMState<'pgm> { // All instructions increase the cycle count self.cycle_count += 1; let mut vm_output = None; + self.previous_instruction = match self.current_instruction() { + Ok(instruction) => instruction.opcode_b(), + // trying to read past the end of the program doesn't change the previous instruction + Err(_) => self.previous_instruction, + }; match self.current_instruction()? { Pop => { @@ -286,7 +294,7 @@ impl<'pgm> VMState<'pgm> { ReadMem => { let ramp = self.op_stack.safe_peek(ST1); - let ramv = self.memory_get(&ramp)?; + let ramv = self.memory_get(&ramp); self.op_stack.pop()?; self.op_stack.push(ramv); self.ramp = ramp.value(); @@ -305,8 +313,7 @@ impl<'pgm> VMState<'pgm> { let hash_input: [BFieldElement; 2 * DIGEST_LENGTH] = self.op_stack.pop_n()?; let hash_trace = RescuePrimeRegular::trace(&hash_input); let hash_output = &hash_trace[hash_trace.len() - 1][0..DIGEST_LENGTH]; - let hash_trace_with_round_constants = Self::inprocess_hash_trace(&hash_trace); - vm_output = Some(VMOutput::XlixTrace(hash_trace_with_round_constants)); + vm_output = Some(VMOutput::XlixTrace(Box::new(hash_trace))); for i in (0..DIGEST_LENGTH).rev() { self.op_stack.push(hash_output[i]); @@ -435,111 +442,53 @@ impl<'pgm> VMState<'pgm> { Ok(vm_output) } - pub fn to_instruction_row( - &self, - current_instruction: Instruction, - ) -> [BFieldElement; instruction_table::BASE_WIDTH] { - use InstructionBaseTableColumn::*; - let mut row = [BFieldElement::zero(); instruction_table::BASE_WIDTH]; - - row[usize::from(Address)] = (self.instruction_pointer as u32).into(); - row[usize::from(CI)] = current_instruction.opcode_b(); - row[usize::from(NIA)] = self.nia(); - - row - } - - pub fn to_processor_row(&self) -> [BFieldElement; processor_table::BASE_WIDTH] { + pub fn to_processor_row(&self) -> Array1 { use ProcessorBaseTableColumn::*; - let mut row = [BFieldElement::zero(); processor_table::BASE_WIDTH]; + let mut row = Array1::zeros(processor_table::BASE_WIDTH); let current_instruction = self.current_instruction().unwrap_or(Nop); let hvs = self.derive_helper_variables(); let ramp = self.ramp.into(); - row[usize::from(CLK)] = BFieldElement::new(self.cycle_count as u64); - row[usize::from(IP)] = (self.instruction_pointer as u32).into(); - row[usize::from(CI)] = current_instruction.opcode_b(); - row[usize::from(NIA)] = self.nia(); - row[usize::from(IB0)] = current_instruction.ib(Ord7::IB0); - row[usize::from(IB1)] = current_instruction.ib(Ord7::IB1); - row[usize::from(IB2)] = current_instruction.ib(Ord7::IB2); - row[usize::from(IB3)] = current_instruction.ib(Ord7::IB3); - row[usize::from(IB4)] = current_instruction.ib(Ord7::IB4); - row[usize::from(IB5)] = current_instruction.ib(Ord7::IB5); - row[usize::from(IB6)] = current_instruction.ib(Ord7::IB6); - row[usize::from(JSP)] = self.jsp(); - row[usize::from(JSO)] = self.jso(); - row[usize::from(JSD)] = self.jsd(); - row[usize::from(ST0)] = self.op_stack.st(Ord16::ST0); - row[usize::from(ST1)] = self.op_stack.st(Ord16::ST1); - row[usize::from(ST2)] = self.op_stack.st(Ord16::ST2); - row[usize::from(ST3)] = self.op_stack.st(Ord16::ST3); - row[usize::from(ST4)] = self.op_stack.st(Ord16::ST4); - row[usize::from(ST5)] = self.op_stack.st(Ord16::ST5); - row[usize::from(ST6)] = self.op_stack.st(Ord16::ST6); - row[usize::from(ST7)] = self.op_stack.st(Ord16::ST7); - row[usize::from(ST8)] = self.op_stack.st(Ord16::ST8); - row[usize::from(ST9)] = self.op_stack.st(Ord16::ST9); - row[usize::from(ST10)] = self.op_stack.st(Ord16::ST10); - row[usize::from(ST11)] = self.op_stack.st(Ord16::ST11); - row[usize::from(ST12)] = self.op_stack.st(Ord16::ST12); - row[usize::from(ST13)] = self.op_stack.st(Ord16::ST13); - row[usize::from(ST14)] = self.op_stack.st(Ord16::ST14); - row[usize::from(ST15)] = self.op_stack.st(Ord16::ST15); - row[usize::from(OSP)] = self.op_stack.osp(); - row[usize::from(OSV)] = self.op_stack.osv(); - row[usize::from(HV0)] = hvs[0]; - row[usize::from(HV1)] = hvs[1]; - row[usize::from(HV2)] = hvs[2]; - row[usize::from(HV3)] = hvs[3]; - row[usize::from(RAMP)] = ramp; - row[usize::from(RAMV)] = *self.ram.get(&ramp).unwrap_or(&BFieldElement::zero()); - - row - } - - pub fn to_op_stack_row( - &self, - current_instruction: Instruction, - ) -> [BFieldElement; op_stack_table::BASE_WIDTH] { - use OpStackBaseTableColumn::*; - let mut row = [BFieldElement::zero(); op_stack_table::BASE_WIDTH]; - - row[usize::from(CLK)] = BFieldElement::new(self.cycle_count as u64); - row[usize::from(IB1ShrinkStack)] = current_instruction.ib(IB1); - row[usize::from(OSP)] = self.op_stack.osp(); - row[usize::from(OSV)] = self.op_stack.osv(); - - row - } - - pub fn to_ram_row(&self) -> [BFieldElement; ram_table::BASE_WIDTH] { - use RamBaseTableColumn::*; - let ramp = self.op_stack.st(ST1); - - let mut row = [BFieldElement::zero(); ram_table::BASE_WIDTH]; - - row[usize::from(CLK)] = BFieldElement::new(self.cycle_count as u64); - row[usize::from(RAMP)] = ramp; - row[usize::from(RAMV)] = *self.ram.get(&ramp).unwrap_or(&BFieldElement::zero()); - // value of InverseOfRampDifference is only known after sorting the RAM Table, thus not set - - row - } - - pub fn to_jump_stack_row( - &self, - current_instruction: Instruction, - ) -> [BFieldElement; jump_stack_table::BASE_WIDTH] { - use JumpStackBaseTableColumn::*; - let mut row = [BFieldElement::zero(); jump_stack_table::BASE_WIDTH]; - - row[usize::from(CLK)] = BFieldElement::new(self.cycle_count as u64); - row[usize::from(CI)] = current_instruction.opcode_b(); - row[usize::from(JSP)] = self.jsp(); - row[usize::from(JSO)] = self.jso(); - row[usize::from(JSD)] = self.jsd(); + row[CLK.base_table_index()] = BFieldElement::new(self.cycle_count as u64); + row[PreviousInstruction.base_table_index()] = self.previous_instruction; + row[IP.base_table_index()] = (self.instruction_pointer as u32).into(); + row[CI.base_table_index()] = current_instruction.opcode_b(); + row[NIA.base_table_index()] = self.nia(); + row[IB0.base_table_index()] = current_instruction.ib(Ord7::IB0); + row[IB1.base_table_index()] = current_instruction.ib(Ord7::IB1); + row[IB2.base_table_index()] = current_instruction.ib(Ord7::IB2); + row[IB3.base_table_index()] = current_instruction.ib(Ord7::IB3); + row[IB4.base_table_index()] = current_instruction.ib(Ord7::IB4); + row[IB5.base_table_index()] = current_instruction.ib(Ord7::IB5); + row[IB6.base_table_index()] = current_instruction.ib(Ord7::IB6); + row[JSP.base_table_index()] = self.jsp(); + row[JSO.base_table_index()] = self.jso(); + row[JSD.base_table_index()] = self.jsd(); + row[ST0.base_table_index()] = self.op_stack.st(Ord16::ST0); + row[ST1.base_table_index()] = self.op_stack.st(Ord16::ST1); + row[ST2.base_table_index()] = self.op_stack.st(Ord16::ST2); + row[ST3.base_table_index()] = self.op_stack.st(Ord16::ST3); + row[ST4.base_table_index()] = self.op_stack.st(Ord16::ST4); + row[ST5.base_table_index()] = self.op_stack.st(Ord16::ST5); + row[ST6.base_table_index()] = self.op_stack.st(Ord16::ST6); + row[ST7.base_table_index()] = self.op_stack.st(Ord16::ST7); + row[ST8.base_table_index()] = self.op_stack.st(Ord16::ST8); + row[ST9.base_table_index()] = self.op_stack.st(Ord16::ST9); + row[ST10.base_table_index()] = self.op_stack.st(Ord16::ST10); + row[ST11.base_table_index()] = self.op_stack.st(Ord16::ST11); + row[ST12.base_table_index()] = self.op_stack.st(Ord16::ST12); + row[ST13.base_table_index()] = self.op_stack.st(Ord16::ST13); + row[ST14.base_table_index()] = self.op_stack.st(Ord16::ST14); + row[ST15.base_table_index()] = self.op_stack.st(Ord16::ST15); + row[OSP.base_table_index()] = self.op_stack.osp(); + row[OSV.base_table_index()] = self.op_stack.osv(); + row[HV0.base_table_index()] = hvs[0]; + row[HV1.base_table_index()] = hvs[1]; + row[HV2.base_table_index()] = hvs[2]; + row[HV3.base_table_index()] = hvs[3]; + row[RAMP.base_table_index()] = ramp; + row[RAMV.base_table_index()] = self.memory_get(&ramp); row } @@ -630,11 +579,11 @@ impl<'pgm> VMState<'pgm> { .ok_or_else(|| vm_fail(JumpStackTooShallow)) } - fn memory_get(&self, mem_addr: &BFieldElement) -> Result { + fn memory_get(&self, mem_addr: &BFieldElement) -> BFieldElement { self.ram .get(mem_addr) .copied() - .ok_or_else(|| vm_fail(MemoryAddressNotFound)) + .unwrap_or_else(BFieldElement::zero) } fn assert_vector(&self) -> bool { @@ -727,52 +676,6 @@ impl<'pgm> VMState<'pgm> { Ok(()) } - - fn inprocess_hash_trace( - hash_trace: &[[BFieldElement; - hash_table::BASE_WIDTH - hash_table::NUM_ROUND_CONSTANTS - 1]], - ) -> Vec<[BFieldElement; hash_table::BASE_WIDTH]> { - let mut hash_trace_with_constants = vec![]; - for (index, trace_row) in hash_trace.iter().enumerate() { - let round_number = index + 1; - let round_constants = Self::rescue_xlix_round_constants_by_round_number(round_number); - let mut new_trace_row = [BFieldElement::zero(); hash_table::BASE_WIDTH]; - let mut offset = 0; - new_trace_row[offset] = BFieldElement::new(round_number as u64); - offset += 1; - new_trace_row[offset..offset + STATE_SIZE].copy_from_slice(trace_row); - offset += STATE_SIZE; - new_trace_row[offset..].copy_from_slice(&round_constants); - hash_trace_with_constants.push(new_trace_row) - } - hash_trace_with_constants - } - - /// rescue_xlix_round_constants_by_round_number - /// returns the 2m round constant for round `round_number`. - /// This counter starts at 1; round number 0 indicates padding; - /// and round number 9 indicates a transition to a new hash so - /// the round constants will be all zeros. - fn rescue_xlix_round_constants_by_round_number( - round_number: usize, - ) -> [BFieldElement; NUM_ROUND_CONSTANTS] { - let round_constants: [BFieldElement; TOTAL_NUM_CONSTANTS] = ROUND_CONSTANTS - .iter() - .map(|&x| BFieldElement::new(x)) - .collect_vec() - .try_into() - .unwrap(); - - match round_number { - 0 => [BFieldElement::zero(); hash_table::NUM_ROUND_CONSTANTS], - i if i <= NUM_ROUNDS => round_constants - [NUM_ROUND_CONSTANTS * (i - 1)..NUM_ROUND_CONSTANTS * i] - .try_into() - .unwrap(), - i if i == NUM_ROUNDS + 1 => [BFieldElement::zero(); hash_table::NUM_ROUND_CONSTANTS], - _ => panic!("Round with number {round_number} does not have round constants."), - } - } } impl<'pgm> Display for VMState<'pgm> { @@ -780,7 +683,7 @@ impl<'pgm> Display for VMState<'pgm> { match self.current_instruction() { Ok(_) => { let row = self.to_processor_row(); - write!(f, "{}", ProcessorMatrixRow { row }) + write!(f, "{}", ProcessorMatrixRow { row: row.view() }) } Err(_) => write!(f, "END-OF-FILE"), } @@ -789,7 +692,7 @@ impl<'pgm> Display for VMState<'pgm> { #[cfg(test)] mod vm_state_tests { - + use itertools::Itertools; use twenty_first::shared_math::other::random_elements_array; use twenty_first::shared_math::rescue_prime_digest::Digest; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; diff --git a/triton-vm/src/table.rs b/triton-vm/src/table.rs index 9377db48f..94a13fa2d 100644 --- a/triton-vm/src/table.rs +++ b/triton-vm/src/table.rs @@ -1,15 +1,14 @@ -pub mod base_matrix; -pub mod base_table; pub mod challenges; pub mod constraint_circuit; pub mod constraints; +pub mod cross_table_argument; pub mod extension_table; pub mod hash_table; pub mod instruction_table; pub mod jump_stack_table; +pub mod master_table; pub mod op_stack_table; pub mod processor_table; pub mod program_table; pub mod ram_table; -pub mod table_collection; pub mod table_column; diff --git a/triton-vm/src/table/base_matrix.rs b/triton-vm/src/table/base_matrix.rs deleted file mode 100644 index e8d15c793..000000000 --- a/triton-vm/src/table/base_matrix.rs +++ /dev/null @@ -1,632 +0,0 @@ -use std::fmt::{Display, Formatter}; -use std::ops::Mul; - -use itertools::Itertools; -use num_traits::{One, Zero}; -use twenty_first::shared_math::b_field_element::BFieldElement; -use twenty_first::shared_math::polynomial::Polynomial; -use twenty_first::shared_math::traits::{FiniteField, Inverse}; -use twenty_first::shared_math::x_field_element::XFieldElement; - -use crate::instruction::AnInstruction::*; -use crate::table::table_column::ProcessorExtTableColumn::*; -use crate::table::table_column::RamBaseTableColumn::{ - BezoutCoefficientPolynomialCoefficient0, BezoutCoefficientPolynomialCoefficient1, -}; -use crate::table::table_column::{ - InstructionBaseTableColumn, OpStackBaseTableColumn, ProcessorBaseTableColumn, - ProgramBaseTableColumn, RamBaseTableColumn, -}; - -use super::table_column::{ - JumpStackBaseTableColumn, ProcessorBaseTableColumn::*, ProcessorExtTableColumn, -}; -use super::{ - hash_table, instruction_table, jump_stack_table, op_stack_table, processor_table, - program_table, ram_table, -}; - -#[derive(Debug, Clone, Default)] -pub struct AlgebraicExecutionTrace { - pub processor_matrix: Vec<[BFieldElement; processor_table::BASE_WIDTH]>, - pub hash_matrix: Vec<[BFieldElement; hash_table::BASE_WIDTH]>, -} - -#[derive(Debug, Clone, Default)] -pub struct BaseMatrices { - pub program_matrix: Vec<[BFieldElement; program_table::BASE_WIDTH]>, - pub instruction_matrix: Vec<[BFieldElement; instruction_table::BASE_WIDTH]>, - pub processor_matrix: Vec<[BFieldElement; processor_table::BASE_WIDTH]>, - pub op_stack_matrix: Vec<[BFieldElement; op_stack_table::BASE_WIDTH]>, - pub ram_matrix: Vec<[BFieldElement; ram_table::BASE_WIDTH]>, - pub jump_stack_matrix: Vec<[BFieldElement; jump_stack_table::BASE_WIDTH]>, - pub hash_matrix: Vec<[BFieldElement; hash_table::BASE_WIDTH]>, -} - -impl BaseMatrices { - pub fn new(aet: AlgebraicExecutionTrace, program: &[BFieldElement]) -> Self { - let program_matrix = Self::derive_program_matrix(program); - let instruction_matrix = Self::derive_instruction_matrix(&aet, program); - let op_stack_matrix = Self::derive_op_stack_matrix(&aet); - let ram_matrix = Self::derive_ram_matrix(&aet); - let jump_stack_matrix = Self::derive_jump_stack_matrix(&aet); - - let processor_matrix = Self::add_clock_jump_differences_to_processor_matrix( - aet.processor_matrix, - &op_stack_matrix, - &ram_matrix, - &jump_stack_matrix, - ); - - Self { - program_matrix, - instruction_matrix, - processor_matrix, - op_stack_matrix, - ram_matrix, - jump_stack_matrix, - hash_matrix: aet.hash_matrix, - } - } - - fn add_clock_jump_differences_to_processor_matrix( - mut processor_matrix: Vec<[BFieldElement; processor_table::BASE_WIDTH]>, - op_stack_matrix: &[[BFieldElement; op_stack_table::BASE_WIDTH]], - ram_matrix: &[[BFieldElement; ram_table::BASE_WIDTH]], - jump_stack_matrix: &[[BFieldElement; jump_stack_table::BASE_WIDTH]], - ) -> Vec<[BFieldElement; processor_table::BASE_WIDTH]> { - let one = BFieldElement::one(); - - // get clock jump differences for all 3 memory-like tables - let op_stack_mp_column = usize::from(OpStackBaseTableColumn::OSP); - let op_stack_clk_column = usize::from(OpStackBaseTableColumn::CLK); - let mut all_clk_jump_differences = vec![]; - for (curr_row, next_row) in op_stack_matrix.iter().tuple_windows() { - if next_row[op_stack_mp_column] == curr_row[op_stack_mp_column] { - let clock_jump_diff = next_row[op_stack_clk_column] - curr_row[op_stack_clk_column]; - if clock_jump_diff != one { - all_clk_jump_differences.push(clock_jump_diff); - } - } - } - - let ramp_mp_column = usize::from(RamBaseTableColumn::RAMP); - let ram_clk_column = usize::from(RamBaseTableColumn::CLK); - for (curr_row, next_row) in ram_matrix.iter().tuple_windows() { - if next_row[ramp_mp_column] == curr_row[ramp_mp_column] { - let clock_jump_diff = next_row[ram_clk_column] - curr_row[ram_clk_column]; - if clock_jump_diff != one { - all_clk_jump_differences.push(clock_jump_diff); - } - } - } - - let jump_stack_mp_column = usize::from(JumpStackBaseTableColumn::JSP); - let jump_stack_clk_column = usize::from(JumpStackBaseTableColumn::CLK); - for (curr_row, next_row) in jump_stack_matrix.iter().tuple_windows() { - if next_row[jump_stack_mp_column] == curr_row[jump_stack_mp_column] { - let clock_jump_diff = - next_row[jump_stack_clk_column] - curr_row[jump_stack_clk_column]; - if clock_jump_diff != one { - all_clk_jump_differences.push(clock_jump_diff); - } - } - } - all_clk_jump_differences.sort_by_key(|bfe| std::cmp::Reverse(bfe.value())); - - // add all clock jump differences and their inverses, as well as inverses of uniques - let zero = BFieldElement::zero(); - let mut previous_row: Option<[BFieldElement; processor_table::BASE_WIDTH]> = None; - for row in processor_matrix.iter_mut() { - let clk_jump_difference = all_clk_jump_differences.pop().unwrap_or(zero); - let clk_jump_difference_inv = if clk_jump_difference.is_zero() { - 0_u64.into() - } else { - clk_jump_difference.inverse() - }; - row[usize::from(ClockJumpDifference)] = clk_jump_difference; - row[usize::from(ClockJumpDifferenceInverse)] = clk_jump_difference_inv; - - if let Some(prow) = previous_row { - let previous_clock_jump_difference = prow[usize::from(ClockJumpDifference)]; - if previous_clock_jump_difference != clk_jump_difference { - row[usize::from(UniqueClockJumpDiffDiffInverse)] = - (clk_jump_difference - previous_clock_jump_difference).inverse(); - } - } - - previous_row = Some(*row); - } - assert!( - all_clk_jump_differences.is_empty(), - "Processor Table must record all clock jump differences, \ - but didn't have enough space for remaining {}.", - all_clk_jump_differences.len() - ); - - processor_matrix - } - - fn derive_program_matrix( - program: &[BFieldElement], - ) -> Vec<[BFieldElement; program_table::BASE_WIDTH]> { - program - .iter() - .enumerate() - .map(|(idx, instruction)| { - let mut derived_row = [BFieldElement::zero(); program_table::BASE_WIDTH]; - derived_row[usize::from(ProgramBaseTableColumn::Address)] = (idx as u32).into(); - derived_row[usize::from(ProgramBaseTableColumn::Instruction)] = *instruction; - derived_row - }) - .collect_vec() - } - - fn derive_instruction_matrix( - aet: &AlgebraicExecutionTrace, - program: &[BFieldElement], - ) -> Vec<[BFieldElement; instruction_table::BASE_WIDTH]> { - use InstructionBaseTableColumn::*; - - let program_append_0 = [program, &[BFieldElement::zero()]].concat(); - let program_part = program_append_0 - .into_iter() - .tuple_windows() - .enumerate() - .map(|(idx, (instruction, next_instruction))| { - let mut derived_row = [BFieldElement::zero(); instruction_table::BASE_WIDTH]; - derived_row[usize::from(Address)] = (idx as u32).into(); - derived_row[usize::from(CI)] = instruction; - derived_row[usize::from(NIA)] = next_instruction; - derived_row - }) - .collect_vec(); - let processor_part = aet - .processor_matrix - .iter() - .map(|&row| { - let mut derived_row = [BFieldElement::zero(); instruction_table::BASE_WIDTH]; - derived_row[usize::from(Address)] = row[usize::from(ProcessorBaseTableColumn::IP)]; - derived_row[usize::from(CI)] = row[usize::from(ProcessorBaseTableColumn::CI)]; - derived_row[usize::from(NIA)] = row[usize::from(ProcessorBaseTableColumn::NIA)]; - derived_row - }) - .collect_vec(); - let mut instruction_matrix = [program_part, processor_part].concat(); - instruction_matrix.sort_by_key(|row| row[usize::from(Address)].value()); - instruction_matrix - } - - fn derive_op_stack_matrix( - aet: &AlgebraicExecutionTrace, - ) -> Vec<[BFieldElement; op_stack_table::BASE_WIDTH]> { - use OpStackBaseTableColumn::*; - - let mut op_stack_matrix = aet - .processor_matrix - .iter() - .map(|&row| { - let mut derived_row = [BFieldElement::zero(); op_stack_table::BASE_WIDTH]; - derived_row[usize::from(CLK)] = row[usize::from(ProcessorBaseTableColumn::CLK)]; - derived_row[usize::from(IB1ShrinkStack)] = - row[usize::from(ProcessorBaseTableColumn::IB1)]; - derived_row[usize::from(OSP)] = row[usize::from(ProcessorBaseTableColumn::OSP)]; - derived_row[usize::from(OSV)] = row[usize::from(ProcessorBaseTableColumn::OSV)]; - derived_row - }) - .collect_vec(); - op_stack_matrix - .sort_by_key(|row| (row[usize::from(OSP)].value(), row[usize::from(CLK)].value())); - - // set inverse of clock difference - 1 - let matrix_len = op_stack_matrix.len(); - let &last_op_stack_matrix_row = op_stack_matrix.last().unwrap(); - let mut new_op_stack_matrix = vec![]; - for (mut current_row, next_row) in op_stack_matrix.into_iter().tuple_windows() { - let clock_jump_difference = next_row[usize::from(OpStackBaseTableColumn::CLK)] - - current_row[usize::from(OpStackBaseTableColumn::CLK)]; - if clock_jump_difference.value() > 1u64 { - current_row[usize::from(OpStackBaseTableColumn::InverseOfClkDiffMinusOne)] = - (clock_jump_difference - BFieldElement::one()).inverse(); - } - new_op_stack_matrix.push(current_row); - } - new_op_stack_matrix.push(last_op_stack_matrix_row); - assert_eq!(matrix_len, new_op_stack_matrix.len()); - - new_op_stack_matrix - } - - fn derive_ram_matrix( - aet: &AlgebraicExecutionTrace, - ) -> Vec<[BFieldElement; ram_table::BASE_WIDTH]> { - let mut ram_matrix = aet - .processor_matrix - .iter() - .map(|&row| { - let mut derived_row = [BFieldElement::zero(); ram_table::BASE_WIDTH]; - derived_row[usize::from(RamBaseTableColumn::CLK)] = row[usize::from(CLK)]; - derived_row[usize::from(RamBaseTableColumn::RAMP)] = row[usize::from(RAMP)]; - derived_row[usize::from(RamBaseTableColumn::RAMV)] = row[usize::from(RAMV)]; - derived_row[usize::from(RamBaseTableColumn::InverseOfRampDifference)] = - BFieldElement::zero(); - derived_row - }) - .collect_vec(); - ram_matrix.sort_by_key(|row| { - ( - row[usize::from(RamBaseTableColumn::RAMP)].value(), - row[usize::from(RamBaseTableColumn::CLK)].value(), - ) - }); - - // calculate inverse of ramp difference - let indexed_non_zero_differences = ram_matrix - .iter() - .tuple_windows() - .enumerate() - .map(|(idx, (curr_row, next_row))| { - ( - idx, - next_row[usize::from(RamBaseTableColumn::RAMP)] - - curr_row[usize::from(RamBaseTableColumn::RAMP)], - ) - }) - .filter(|(_, x)| !BFieldElement::is_zero(x)) - .collect_vec(); - let inverses = BFieldElement::batch_inversion( - indexed_non_zero_differences - .iter() - .map(|&(_, x)| x) - .collect_vec(), - ); - for ((idx, _), inverse) in indexed_non_zero_differences - .into_iter() - .zip_eq(inverses.into_iter()) - { - ram_matrix[idx][usize::from(RamBaseTableColumn::InverseOfRampDifference)] = inverse; - } - - // set inverse of clock difference - 1 - let matrix_len = ram_matrix.len(); - let &last_ram_matrix_row = ram_matrix.last().unwrap(); - let mut new_ram_matrix = vec![]; - for (mut current_row, next_row) in ram_matrix.into_iter().tuple_windows() { - current_row[usize::from(RamBaseTableColumn::InverseOfClkDiffMinusOne)] = next_row - [usize::from(RamBaseTableColumn::CLK)] - - current_row[usize::from(RamBaseTableColumn::CLK)]; - new_ram_matrix.push(current_row); - } - new_ram_matrix.push(last_ram_matrix_row); - let mut ram_matrix = new_ram_matrix; - assert_eq!(matrix_len, ram_matrix.len()); - - // compute Bézout coefficient polynomials - let all_ramps = ram_matrix - .iter() - .map(|row| row[usize::from(RamBaseTableColumn::RAMP)]) - .dedup() - .collect_vec(); - let num_of_different_ramps = all_ramps.len(); - let polynomial_with_ramps_as_roots = all_ramps - .into_iter() - .map(|ramp| Polynomial::new(vec![-ramp, BFieldElement::one()])) - .fold( - Polynomial::from_constant(BFieldElement::one()), - Polynomial::mul, - ); - - let formal_derivative_coefficients = polynomial_with_ramps_as_roots - .coefficients - .iter() - .enumerate() - .map(|(i, &coefficient)| BFieldElement::new(i as u64) * coefficient) - .skip(1) - .collect_vec(); - let formal_derivative = Polynomial::new(formal_derivative_coefficients); - - let (gcd, bezout_0, bezout_1) = - XFieldElement::xgcd(polynomial_with_ramps_as_roots, formal_derivative); - assert!(gcd.is_one()); - assert!( - bezout_0.degree() < num_of_different_ramps as isize, - "The Bézout coefficient must be of degree at most {}.", - num_of_different_ramps - 1 - ); - assert!( - bezout_1.degree() <= num_of_different_ramps as isize, - "The Bézout coefficient must be of degree at most {num_of_different_ramps}." - ); - - let mut bezout_coefficient_polynomial_coefficient_0 = bezout_0.coefficients; - bezout_coefficient_polynomial_coefficient_0 - .resize(num_of_different_ramps, BFieldElement::zero()); - - let mut bezout_coefficient_polynomial_coefficient_1 = bezout_1.coefficients; - bezout_coefficient_polynomial_coefficient_1 - .resize(num_of_different_ramps, BFieldElement::zero()); - - // first row - let mut current_bcpc_0 = bezout_coefficient_polynomial_coefficient_0.pop().unwrap(); - let mut current_bcpc_1 = bezout_coefficient_polynomial_coefficient_1.pop().unwrap(); - ram_matrix.first_mut().unwrap()[usize::from(BezoutCoefficientPolynomialCoefficient0)] = - current_bcpc_0; - ram_matrix.first_mut().unwrap()[usize::from(BezoutCoefficientPolynomialCoefficient1)] = - current_bcpc_1; - - // rest of table - let mut previous_ramp = ram_matrix.first().unwrap()[usize::from(RamBaseTableColumn::RAMP)]; - for row in ram_matrix.iter_mut().skip(1) { - if previous_ramp != row[usize::from(RamBaseTableColumn::RAMP)] { - current_bcpc_0 = bezout_coefficient_polynomial_coefficient_0.pop().unwrap(); - current_bcpc_1 = bezout_coefficient_polynomial_coefficient_1.pop().unwrap(); - previous_ramp = row[usize::from(RamBaseTableColumn::RAMP)] - } - row[usize::from(BezoutCoefficientPolynomialCoefficient0)] = current_bcpc_0; - row[usize::from(BezoutCoefficientPolynomialCoefficient1)] = current_bcpc_1; - } - - assert_eq!(0, bezout_coefficient_polynomial_coefficient_0.len()); - assert_eq!(0, bezout_coefficient_polynomial_coefficient_1.len()); - - ram_matrix - } - - fn derive_jump_stack_matrix( - aet: &AlgebraicExecutionTrace, - ) -> Vec<[BFieldElement; jump_stack_table::BASE_WIDTH]> { - let mut jump_stack_matrix = aet - .processor_matrix - .iter() - .map(|&row| { - let mut derived_row = [BFieldElement::zero(); jump_stack_table::BASE_WIDTH]; - derived_row[usize::from(JumpStackBaseTableColumn::CLK)] = row[usize::from(CLK)]; - derived_row[usize::from(JumpStackBaseTableColumn::CI)] = row[usize::from(CI)]; - derived_row[usize::from(JumpStackBaseTableColumn::JSP)] = row[usize::from(JSP)]; - derived_row[usize::from(JumpStackBaseTableColumn::JSO)] = row[usize::from(JSO)]; - derived_row[usize::from(JumpStackBaseTableColumn::JSD)] = row[usize::from(JSD)]; - derived_row - }) - .collect_vec(); - jump_stack_matrix.sort_by_key(|row| { - ( - row[usize::from(JumpStackBaseTableColumn::JSP)].value(), - row[usize::from(JumpStackBaseTableColumn::CLK)].value(), - ) - }); - - // set inverse of clock difference - 1 - let matrix_len = jump_stack_matrix.len(); - let &last_jump_stack_matrix_row = jump_stack_matrix.last().unwrap(); - let mut new_jump_stack_matrix = vec![]; - for (mut current_row, next_row) in jump_stack_matrix.into_iter().tuple_windows() { - let clock_jump_difference = next_row[usize::from(JumpStackBaseTableColumn::CLK)] - - current_row[usize::from(JumpStackBaseTableColumn::CLK)]; - if clock_jump_difference.value() > 1 { - current_row[usize::from(JumpStackBaseTableColumn::InverseOfClkDiffMinusOne)] = - (clock_jump_difference - BFieldElement::one()).inverse() - } - new_jump_stack_matrix.push(current_row); - } - new_jump_stack_matrix.push(last_jump_stack_matrix_row); - assert_eq!(matrix_len, new_jump_stack_matrix.len()); - - new_jump_stack_matrix - } -} - -pub struct ProcessorMatrixRow { - pub row: [BFieldElement; processor_table::BASE_WIDTH], -} - -impl Display for ProcessorMatrixRow { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fn row(f: &mut std::fmt::Formatter<'_>, s: String) -> std::fmt::Result { - writeln!(f, "│ {: <103} │", s) - } - - fn row_blank(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - row(f, "".into()) - } - - let instruction = self.row[usize::from(CI)].value().try_into().unwrap(); - let instruction_with_arg = match instruction { - Push(_) => Push(self.row[usize::from(NIA)]), - Call(_) => Call(self.row[usize::from(NIA)]), - Dup(_) => Dup((self.row[usize::from(NIA)].value() as u32) - .try_into() - .unwrap()), - Swap(_) => Swap( - (self.row[usize::from(NIA)].value() as u32) - .try_into() - .unwrap(), - ), - _ => instruction, - }; - - writeln!(f, " ╭───────────────────────────╮")?; - writeln!(f, " │ {: <25} │", format!("{}", instruction_with_arg))?; - writeln!( - f, - "╭┴───────────────────────────┴────────────────────────────────────\ - ────────────────────┬───────────────────╮" - )?; - - let width = 20; - row( - f, - format!( - "ip: {:>width$} ╷ ci: {:>width$} ╷ nia: {:>width$} │ {:>17}", - self.row[usize::from(IP)].value(), - self.row[usize::from(CI)].value(), - self.row[usize::from(NIA)].value(), - self.row[usize::from(CLK)].value(), - ), - )?; - - writeln!( - f, - "│ jsp: {:>width$} │ jso: {:>width$} │ jsd: {:>width$} ╰───────────────────┤", - self.row[usize::from(JSP)].value(), - self.row[usize::from(JSO)].value(), - self.row[usize::from(JSD)].value(), - )?; - row( - f, - format!( - "ramp: {:>width$} │ ramv: {:>width$} │", - self.row[usize::from(RAMP)].value(), - self.row[usize::from(RAMV)].value(), - ), - )?; - row( - f, - format!( - "osp: {:>width$} │ osv: {:>width$} ╵", - self.row[usize::from(OSP)].value(), - self.row[usize::from(OSV)].value(), - ), - )?; - - row_blank(f)?; - - row( - f, - format!( - "st0-3: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", - self.row[usize::from(ST0)].value(), - self.row[usize::from(ST1)].value(), - self.row[usize::from(ST2)].value(), - self.row[usize::from(ST3)].value(), - ), - )?; - row( - f, - format!( - "st4-7: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", - self.row[usize::from(ST4)].value(), - self.row[usize::from(ST5)].value(), - self.row[usize::from(ST6)].value(), - self.row[usize::from(ST7)].value(), - ), - )?; - row( - f, - format!( - "st8-11: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", - self.row[usize::from(ST8)].value(), - self.row[usize::from(ST9)].value(), - self.row[usize::from(ST10)].value(), - self.row[usize::from(ST11)].value(), - ), - )?; - row( - f, - format!( - "st12-15: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", - self.row[usize::from(ST12)].value(), - self.row[usize::from(ST13)].value(), - self.row[usize::from(ST14)].value(), - self.row[usize::from(ST15)].value(), - ), - )?; - - row_blank(f)?; - - row( - f, - format!( - "hv0-3: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", - self.row[usize::from(HV0)].value(), - self.row[usize::from(HV1)].value(), - self.row[usize::from(HV2)].value(), - self.row[usize::from(HV3)].value(), - ), - )?; - let w = 2; - row( - f, - format!( - "ib0-6: \ - [ {:>w$} | {:>w$} | {:>w$} | {:>w$} | {:>w$} | {:>w$} | {:>w$} ]", - self.row[usize::from(IB0)].value(), - self.row[usize::from(IB1)].value(), - self.row[usize::from(IB2)].value(), - self.row[usize::from(IB3)].value(), - self.row[usize::from(IB4)].value(), - self.row[usize::from(IB5)].value(), - self.row[usize::from(IB6)].value(), - ), - )?; - write!( - f, - "╰─────────────────────────────────────────────────────────────────\ - ────────────────────────────────────────╯" - ) - } -} - -pub struct ExtProcessorMatrixRow { - pub row: [XFieldElement; processor_table::FULL_WIDTH], -} - -impl Display for ExtProcessorMatrixRow { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let base_row = self.row[0..processor_table::BASE_WIDTH] - .iter() - .map(|xfe| xfe.unlift().unwrap()) - .collect_vec() - .try_into() - .unwrap(); - let base_row = ProcessorMatrixRow { row: base_row }; - - let row = |form: &mut std::fmt::Formatter<'_>, - desc: &str, - col: ProcessorExtTableColumn| - -> std::fmt::Result { - // without the extra `format!()`, alignment in `writeln!()` fails - let formatted_col_elem = format!("{}", self.row[usize::from(col)]); - writeln!(form, " │ {: <18} {:>73} │", desc, formatted_col_elem,) - }; - - writeln!(f, "{}", base_row)?; - writeln!( - f, - " ╭───────────────────────────────────────────────────────\ - ────────────────────────────────────────╮" - )?; - row(f, "input_table_ea", InputTableEvalArg)?; - row(f, "output_table_ea", OutputTableEvalArg)?; - row(f, "instr_table_pa", InstructionTablePermArg)?; - row(f, "opstack_table_pa", OpStackTablePermArg)?; - row(f, "ram_table_pa", RamTablePermArg)?; - row(f, "jumpstack_table_pa", JumpStackTablePermArg)?; - row(f, "to_hash_table_ea", ToHashTableEvalArg)?; - row(f, "from_hash_table_ea", FromHashTableEvalArg)?; - write!( - f, - " ╰───────────────────────────────────────────────────────\ - ────────────────────────────────────────╯" - ) - } -} - -pub struct JumpStackMatrixRow { - pub row: [BFieldElement; jump_stack_table::BASE_WIDTH], -} - -impl Display for JumpStackMatrixRow { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let width = 5; - write!( - f, - "│ CLK: {:>width$} │ CI: {:>width$} │ \ - JSP: {:>width$} │ JSO: {:>width$} │ JSD: {:>width$} │", - self.row[usize::from(JumpStackBaseTableColumn::CLK)].value(), - self.row[usize::from(JumpStackBaseTableColumn::CI)].value(), - self.row[usize::from(JumpStackBaseTableColumn::JSP)].value(), - self.row[usize::from(JumpStackBaseTableColumn::JSO)].value(), - self.row[usize::from(JumpStackBaseTableColumn::JSD)].value(), - ) - } -} diff --git a/triton-vm/src/table/base_table.rs b/triton-vm/src/table/base_table.rs deleted file mode 100644 index 7c75b7fb5..000000000 --- a/triton-vm/src/table/base_table.rs +++ /dev/null @@ -1,166 +0,0 @@ -use super::super::arithmetic_domain::ArithmeticDomain; -use itertools::Itertools; -use rand_distr::{Distribution, Standard}; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use std::ops::{Mul, MulAssign, Range}; -use twenty_first::shared_math::b_field_element::BFieldElement; -use twenty_first::shared_math::other::{random_elements, roundup_npo2}; -use twenty_first::shared_math::traits::{FiniteField, PrimitiveRootOfUnity}; -use twenty_first::shared_math::x_field_element::XFieldElement; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Table { - /// The width of each `data` row in the base version of the table - base_width: usize, - - /// The width of each `data` row in the extended version of the table - full_width: usize, - - /// The table data (trace data). Represents every intermediate - matrix: Vec>, - - /// The name of the table. Mostly for debugging purpose. - pub name: String, -} - -#[allow(clippy::too_many_arguments)] -impl Table { - pub fn new(base_width: usize, full_width: usize, matrix: Vec>, name: String) -> Self { - Table { - base_width, - full_width, - matrix, - name, - } - } - - /// Create a `BaseTable` with the same parameters, but new `matrix` data. - pub fn with_data(&self, matrix: Vec>) -> Self { - Table { - matrix, - name: format!("{} with data", self.name), - ..self.to_owned() - } - } -} - -pub trait InheritsFromTable { - fn inherited_table(&self) -> &Table; - fn mut_inherited_table(&mut self) -> &mut Table; - - fn base_width(&self) -> usize { - self.inherited_table().base_width - } - - fn full_width(&self) -> usize { - self.inherited_table().full_width - } - - fn data(&self) -> &Vec> { - &self.inherited_table().matrix - } - - fn mut_data(&mut self) -> &mut Vec> { - &mut self.mut_inherited_table().matrix - } -} - -pub trait Extendable: TableLike { - // Abstract functions that individual structs implement - - /// Computes some (or all) padding rows and, if appropriate, the index where they are to be - /// inserted. - fn get_padding_rows(&self) -> (Option, Vec>); - - // Generic functions common to all extendable tables - - fn new_from_lifted_matrix(&self, matrix: Vec>) -> Table { - Table::new( - self.base_width(), - self.full_width(), - matrix, - format!("{} with lifted matrix", self.name()), - ) - } - /// Add padding to a table so that its height becomes the same as other tables. Uses - /// table-specific padding via `.get_padding_rows()`, which might specify an insertion index for - /// the padding row(s). - fn pad(&mut self, padded_height: usize) { - while self.data().len() != padded_height { - let (maybe_index, mut rows) = self.get_padding_rows(); - match maybe_index { - Some(idx) => { - let old_tail_length = self.data().len() - idx; - self.mut_data().append(&mut rows); - self.mut_data()[idx..].rotate_left(old_tail_length); - } - None => self.mut_data().append(&mut rows), - } - } - assert_eq!(padded_height, self.data().len()); - } -} - -pub trait TableLike: InheritsFromTable -where - FF: FiniteField - + From - + Mul - + MulAssign, - Standard: Distribution, -{ - // Generic functions common to all tables - - fn name(&self) -> String { - self.inherited_table().name.clone() - } - - fn randomized_low_deg_extension( - &self, - fri_domain: &ArithmeticDomain, - num_trace_randomizers: usize, - columns: Range, - ) -> Table { - let all_trace_columns = self.data(); - let padded_height = all_trace_columns.len(); - - assert_ne!( - 0, - padded_height, - "{}: Low-degree extension must be performed on some codeword, but got nothing.", - self.name() - ); - assert!( - padded_height.is_power_of_two(), - "{}: Table data must be padded before interpolation", - self.name() - ); - - let randomized_trace_domain_len = - roundup_npo2((padded_height + num_trace_randomizers) as u64); - let randomized_trace_domain_gen = - BFieldElement::primitive_root_of_unity(randomized_trace_domain_len).unwrap(); - let randomized_trace_domain = ArithmeticDomain::new( - 1_u32.into(), - randomized_trace_domain_gen, - randomized_trace_domain_len as usize, - ); - - // how many elements to skip in the randomized trace domain to only refer to elements - // in the non-randomized trace domain - let unit_distance = randomized_trace_domain_len as usize / padded_height; - - let fri_domain_codewords = columns - .into_par_iter() - .map(|idx| { - let trace = all_trace_columns.iter().map(|row| row[idx]).collect_vec(); - let mut randomized_trace = random_elements(randomized_trace_domain_len as usize); - for i in 0..padded_height { - randomized_trace[unit_distance * i] = trace[i] - } - randomized_trace_domain.low_degree_extension(&randomized_trace, fri_domain) - }) - .collect(); - self.inherited_table().with_data(fri_domain_codewords) - } -} diff --git a/triton-vm/src/table/challenges.rs b/triton-vm/src/table/challenges.rs index 1eae3bade..e66076c04 100644 --- a/triton-vm/src/table/challenges.rs +++ b/triton-vm/src/table/challenges.rs @@ -1,19 +1,25 @@ use std::fmt::Debug; use std::fmt::Display; use std::hash::Hash; + use strum::EnumCount; use strum::IntoEnumIterator; +use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::other::random_elements; use twenty_first::shared_math::x_field_element::XFieldElement; -use super::hash_table::HashTableChallenges; -use super::instruction_table::InstructionTableChallenges; -use super::jump_stack_table::JumpStackTableChallenges; -use super::op_stack_table::OpStackTableChallenges; -use super::processor_table::IOChallenges; -use super::processor_table::ProcessorTableChallenges; -use super::program_table::ProgramTableChallenges; -use super::ram_table::RamTableChallenges; +use crate::table::cross_table_argument::CrossTableArg; +use crate::table::cross_table_argument::CrossTableChallenges; +use crate::table::cross_table_argument::EvalArg; +use crate::table::cross_table_argument::NUM_CROSS_TABLE_WEIGHTS; +use crate::table::hash_table::HashTableChallenges; +use crate::table::instruction_table::InstructionTableChallenges; +use crate::table::jump_stack_table::JumpStackTableChallenges; +use crate::table::op_stack_table::OpStackTableChallenges; +use crate::table::processor_table::IOChallenges; +use crate::table::processor_table::ProcessorTableChallenges; +use crate::table::program_table::ProgramTableChallenges; +use crate::table::ram_table::RamTableChallenges; pub trait TableChallenges: Clone + Debug { type Id: Display @@ -49,12 +55,17 @@ pub struct AllChallenges { pub ram_table_challenges: RamTableChallenges, pub jump_stack_table_challenges: JumpStackTableChallenges, pub hash_table_challenges: HashTableChallenges, + pub cross_table_challenges: CrossTableChallenges, } impl AllChallenges { - pub const TOTAL_CHALLENGES: usize = 130; + pub const TOTAL_CHALLENGES: usize = 46 + NUM_CROSS_TABLE_WEIGHTS; - pub fn create_challenges(mut weights: Vec) -> Self { + pub fn create_challenges( + mut weights: Vec, + claimed_input: &[BFieldElement], + claimed_output: &[BFieldElement], + ) -> Self { let processor_table_challenges = ProcessorTableChallenges { standard_input_eval_indeterminate: weights.pop().unwrap(), standard_output_eval_indeterminate: weights.pop().unwrap(), @@ -77,6 +88,7 @@ impl AllChallenges { ram_table_clk_weight: weights.pop().unwrap(), ram_table_ramp_weight: weights.pop().unwrap(), ram_table_ramv_weight: weights.pop().unwrap(), + ram_table_previous_instruction_weight: weights.pop().unwrap(), jump_stack_table_clk_weight: weights.pop().unwrap(), jump_stack_table_ci_weight: weights.pop().unwrap(), @@ -148,8 +160,10 @@ impl AllChallenges { bezout_relation_indeterminate: weights.pop().unwrap(), processor_perm_indeterminate: processor_table_challenges.ram_perm_indeterminate, clk_weight: processor_table_challenges.ram_table_clk_weight, - ramv_weight: processor_table_challenges.ram_table_ramv_weight, ramp_weight: processor_table_challenges.ram_table_ramp_weight, + ramv_weight: processor_table_challenges.ram_table_ramv_weight, + previous_instruction_weight: processor_table_challenges + .ram_table_previous_instruction_weight, all_clock_jump_differences_multi_perm_indeterminate: processor_table_challenges .all_clock_jump_differences_multi_perm_indeterminate, }; @@ -189,6 +203,34 @@ impl AllChallenges { digest_output_weight4: processor_table_challenges.hash_table_digest_output_weight4, }; + let input_terminal = EvalArg::compute_terminal( + claimed_input, + EvalArg::default_initial(), + processor_table_challenges.standard_input_eval_indeterminate, + ); + let output_terminal = EvalArg::compute_terminal( + claimed_output, + EvalArg::default_initial(), + processor_table_challenges.standard_output_eval_indeterminate, + ); + + let cross_table_challenges = CrossTableChallenges { + input_terminal, + output_terminal, + program_to_instruction_weight: weights.pop().unwrap(), + processor_to_instruction_weight: weights.pop().unwrap(), + processor_to_op_stack_weight: weights.pop().unwrap(), + processor_to_ram_weight: weights.pop().unwrap(), + processor_to_jump_stack_weight: weights.pop().unwrap(), + processor_to_hash_weight: weights.pop().unwrap(), + hash_to_processor_weight: weights.pop().unwrap(), + all_clock_jump_differences_weight: weights.pop().unwrap(), + input_to_processor_weight: weights.pop().unwrap(), + processor_to_output_weight: weights.pop().unwrap(), + }; + + assert!(weights.is_empty(), "{} weights left unused.", weights.len()); + AllChallenges { program_table_challenges, instruction_table_challenges, @@ -199,14 +241,17 @@ impl AllChallenges { ram_table_challenges, jump_stack_table_challenges, hash_table_challenges, + cross_table_challenges, } } - /// Stand-in challenges. Can be used for deriving degree bounds and in tests. For non- - /// interactive STARKs, use Fiat-Shamir to derive the actual challenges. - pub fn placeholder() -> Self { - let random_challenges = random_elements(Self::TOTAL_CHALLENGES); - - Self::create_challenges(random_challenges) + /// Stand-in challenges. Can be used in tests. For non-interactive STARKs, use Fiat-Shamir to + /// derive the actual challenges. + pub fn placeholder(claimed_input: &[BFieldElement], claimed_output: &[BFieldElement]) -> Self { + Self::create_challenges( + random_elements(Self::TOTAL_CHALLENGES), + claimed_input, + claimed_output, + ) } } diff --git a/triton-vm/src/table/constraint_circuit.rs b/triton-vm/src/table/constraint_circuit.rs index d6d2804ff..5369d07f4 100644 --- a/triton-vm/src/table/constraint_circuit.rs +++ b/triton-vm/src/table/constraint_circuit.rs @@ -1,17 +1,22 @@ +use ndarray::ArrayView2; use std::borrow::BorrowMut; use std::cell::RefCell; -use std::cmp::{self}; +use std::cmp; use std::collections::HashSet; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; +use std::fmt::Display; use std::hash::Hash; use std::iter::Sum; use std::marker::PhantomData; -use std::ops::{Add, Mul, Sub}; +use std::ops::Add; +use std::ops::Mul; +use std::ops::Sub; use std::rc::Rc; -use num_traits::{One, Zero}; +use num_traits::One; +use num_traits::Zero; use twenty_first::shared_math::b_field_element::BFieldElement; -use twenty_first::shared_math::mpolynomial::MPolynomial; +use twenty_first::shared_math::mpolynomial::Degree; use twenty_first::shared_math::x_field_element::XFieldElement; use CircuitExpression::*; @@ -37,18 +42,6 @@ impl Display for BinOp { } } -/// Data structure for uniquely identifying each node -#[derive(Debug, Clone, Hash, PartialEq)] -pub struct CircuitId(usize); - -impl Eq for CircuitId {} - -impl Display for CircuitId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - /// An `InputIndicator` is a type that describes the position of a variable in /// a constraint polynomial in the row layout applicable for a certain kind of /// constraint polynomial. @@ -62,86 +55,143 @@ impl Display for CircuitId { /// `From` and `Into` occur for the purpose of this conversion. /// /// Having `Clone + Copy + Hash + PartialEq + Eq` help put these in containers. -pub trait InputIndicator: - Debug + Clone + Copy + Hash + PartialEq + Eq + Display + From + Into -{ +pub trait InputIndicator: Debug + Clone + Copy + Hash + PartialEq + Eq + Display { + /// `true` iff `self` refers to a column in the base table. + fn is_base_table_row(&self) -> bool; + + fn base_row_index(&self) -> usize; + fn ext_row_index(&self) -> usize; + + fn evaluate( + &self, + base_table: ArrayView2, + ext_table: ArrayView2, + ) -> XFieldElement; } -/// A `SingleRowIndicator` describes the position of a variable in +/// A `SingleRowIndicator` describes the position of a variable in /// a constraint polynomial that operates on a single execution trace table at a /// time. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum SingleRowIndicator { - Row(usize), +pub enum SingleRowIndicator { + BaseRow(usize), + ExtRow(usize), } -impl Display for SingleRowIndicator { +impl Display + for SingleRowIndicator +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let input_indicator: String = match self { - SingleRowIndicator::Row(i) => format!("row[{i}]"), + SingleRowIndicator::BaseRow(i) => format!("base_row[{i}]"), + SingleRowIndicator::ExtRow(i) => format!("ext_row[{i}]"), }; writeln!(f, "{input_indicator}") } } -impl From for SingleRowIndicator { - fn from(val: usize) -> Self { - assert!(val < COLUMN_COUNT, "Cannot index out of width of table"); - SingleRowIndicator::Row(val) +impl InputIndicator + for SingleRowIndicator +{ + fn is_base_table_row(&self) -> bool { + match self { + SingleRowIndicator::BaseRow(_) => true, + SingleRowIndicator::ExtRow(_) => false, + } } -} -impl From> for usize { - fn from(val: SingleRowIndicator) -> usize { - match val { - SingleRowIndicator::Row(i) => i, + fn base_row_index(&self) -> usize { + match self { + SingleRowIndicator::BaseRow(i) => *i, + SingleRowIndicator::ExtRow(_) => panic!("not a base row"), + } + } + + fn ext_row_index(&self) -> usize { + match self { + SingleRowIndicator::BaseRow(_) => panic!("not an ext row"), + SingleRowIndicator::ExtRow(i) => *i, } } -} -impl InputIndicator for SingleRowIndicator {} + fn evaluate( + &self, + base_table: ArrayView2, + ext_table: ArrayView2, + ) -> XFieldElement { + match self { + SingleRowIndicator::BaseRow(i) => base_table[[0, *i]].lift(), + SingleRowIndicator::ExtRow(i) => ext_table[[0, *i]], + } + } +} -/// A `DualRowIndicator` describes the position of a variable in +/// A `DualRowIndicator` describes the position of a variable in /// a constraint polynomial that operates on pairs of rows (current and next). #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum DualRowIndicator { - CurrentRow(usize), - NextRow(usize), +pub enum DualRowIndicator { + CurrentBaseRow(usize), + CurrentExtRow(usize), + NextBaseRow(usize), + NextExtRow(usize), } -impl Display for DualRowIndicator { +impl Display + for DualRowIndicator +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let input_indicator: String = match self { - DualRowIndicator::CurrentRow(i) => format!("current_row[{i}]"), - DualRowIndicator::NextRow(i) => format!("next_row[{i}]"), + DualRowIndicator::CurrentBaseRow(i) => format!("current_base_row[{i}]"), + DualRowIndicator::CurrentExtRow(i) => format!("current_ext_row[{i}]"), + DualRowIndicator::NextBaseRow(i) => format!("next_base_row[{i}]"), + DualRowIndicator::NextExtRow(i) => format!("next_ext_row[{i}]"), }; writeln!(f, "{input_indicator}") } } -impl InputIndicator for DualRowIndicator {} +impl InputIndicator + for DualRowIndicator +{ + fn is_base_table_row(&self) -> bool { + match self { + DualRowIndicator::CurrentBaseRow(_) => true, + DualRowIndicator::CurrentExtRow(_) => false, + DualRowIndicator::NextBaseRow(_) => true, + DualRowIndicator::NextExtRow(_) => false, + } + } -impl From for DualRowIndicator { - fn from(val: usize) -> Self { - assert!( - val < 2 * COLUMN_COUNT, - "Cannot index out of two times the width of the table" - ); - if val < COLUMN_COUNT { - DualRowIndicator::CurrentRow(val) - } else { - DualRowIndicator::NextRow(val - COLUMN_COUNT) + fn base_row_index(&self) -> usize { + match self { + DualRowIndicator::CurrentBaseRow(i) => *i, + DualRowIndicator::CurrentExtRow(_) => panic!("not a base row"), + DualRowIndicator::NextBaseRow(i) => *i, + DualRowIndicator::NextExtRow(_) => panic!("not a base row"), + } + } + + fn ext_row_index(&self) -> usize { + match self { + DualRowIndicator::CurrentBaseRow(_) => panic!("not an ext row"), + DualRowIndicator::CurrentExtRow(i) => *i, + DualRowIndicator::NextBaseRow(_) => panic!("not an ext row"), + DualRowIndicator::NextExtRow(i) => *i, } } -} -impl From> for usize { - fn from(val: DualRowIndicator) -> Self { - match val { - DualRowIndicator::CurrentRow(i) => i, - DualRowIndicator::NextRow(i) => COLUMN_COUNT + i, + fn evaluate( + &self, + base_table: ArrayView2, + ext_table: ArrayView2, + ) -> XFieldElement { + match self { + DualRowIndicator::CurrentBaseRow(i) => base_table[[0, *i]].lift(), + DualRowIndicator::CurrentExtRow(i) => ext_table[[0, *i]], + DualRowIndicator::NextBaseRow(i) => base_table[[1, *i]].lift(), + DualRowIndicator::NextExtRow(i) => ext_table[[1, *i]], } } } @@ -191,10 +241,9 @@ impl Hash for ConstraintCircuitMonad { - pub id: CircuitId, + pub id: usize, pub visited_counter: usize, pub expression: CircuitExpression, - pub var_count: usize, } impl Eq for ConstraintCircuit {} @@ -278,13 +327,13 @@ impl ConstraintCircuit { /// Count how many times each reachable node is reached when traversing from /// the starting points that are given as input. The result is stored in the /// `visited_counter` field in each node. - pub fn traverse_multiple(mpols: &mut [ConstraintCircuit]) { - for mpol in mpols.iter_mut() { + pub fn traverse_multiple(ccs: &mut [ConstraintCircuit]) { + for cc in ccs.iter_mut() { assert!( - mpol.visited_counter.is_zero(), + cc.visited_counter.is_zero(), "visited counter must be zero before starting count" ); - mpol.traverse_single(); + cc.traverse_single(); } } @@ -300,7 +349,7 @@ impl ConstraintCircuit { /// Verify that all IDs in the subtree are unique. Panics otherwise. fn inner_has_unique_ids(&mut self, ids: &mut HashSet) { - let new_value = ids.insert(self.id.0); + let new_value = ids.insert(self.id); assert!( !self.visited_counter.is_zero() || new_value, "ID = {} was repeated", @@ -313,16 +362,16 @@ impl ConstraintCircuit { } } - // Verify that a multitree has unique IDs. Otherwise panic. + /// Verify that a multitree has unique IDs. Panics otherwise. pub fn assert_has_unique_ids(constraints: &mut [ConstraintCircuit]) { let mut ids: HashSet = HashSet::new(); - for mpol in constraints.iter_mut() { - mpol.inner_has_unique_ids(&mut ids); + for circuit in constraints.iter_mut() { + circuit.inner_has_unique_ids(&mut ids); } - for mpol in constraints.iter_mut() { - mpol.reset_visited_counters(); + for circuit in constraints.iter_mut() { + circuit.reset_visited_counters(); } } @@ -425,7 +474,7 @@ impl ConstraintCircuit { } } - /// Reduce size of multitree by simplifying constant expressions such as `1 * MPol(_,_)` + /// Reduce size of multitree by simplifying constant expressions such as `1·X` to `X`. pub fn constant_folding(circuits: &mut [&mut ConstraintCircuit]) { for circuit in circuits.iter_mut() { let mut mutated = circuit.constant_fold_inner(); @@ -436,18 +485,19 @@ impl ConstraintCircuit { } /// Return max degree after evaluating the circuit with an input of specified degree - pub fn symbolic_degree_bound(&self, max_degrees: &[i64]) -> i64 { - assert_eq!( - self.var_count, - max_degrees.len(), - "max_degrees length and var_count must match. Got: {}, {}.", - max_degrees.len(), - self.var_count - ); + pub fn symbolic_degree_bound( + &self, + max_base_degrees: &[Degree], + max_ext_degrees: &[Degree], + ) -> Degree { match &self.expression { BinaryOperation(binop, lhs, rhs) => { - let lhs_degree = lhs.borrow().symbolic_degree_bound(max_degrees); - let rhs_degree = rhs.borrow().symbolic_degree_bound(max_degrees); + let lhs_degree = lhs + .borrow() + .symbolic_degree_bound(max_base_degrees, max_ext_degrees); + let rhs_degree = rhs + .borrow() + .symbolic_degree_bound(max_base_degrees, max_ext_degrees); match binop { BinOp::Add | BinOp::Sub => cmp::max(lhs_degree, rhs_degree), BinOp::Mul => { @@ -460,10 +510,10 @@ impl ConstraintCircuit { } } } - Input(input) => { - let index: usize = (*input).into(); - max_degrees[index] - } + Input(input) => match input.is_base_table_row() { + true => max_base_degrees[input.base_row_index()], + false => max_ext_degrees[input.ext_row_index()], + }, XConstant(xfe) => { if xfe.is_zero() { -1 @@ -483,7 +533,7 @@ impl ConstraintCircuit { } /// Return degree of the multivariate polynomial represented by this circuit - pub fn degree(&self) -> i64 { + pub fn degree(&self) -> Degree { match &self.expression { BinaryOperation(binop, lhs, rhs) => { let lhs_degree = lhs.borrow().degree(); @@ -539,8 +589,8 @@ impl ConstraintCircuit { } } - /// Return true if the contained multivariate polynomial consists of only a single term. This means that it can be - /// pretty-printed without parentheses. + /// Return true if the contained multivariate polynomial consists of only a single term. This + /// means that it can be pretty-printed without parentheses. pub fn print_without_parentheses(&self) -> bool { !matches!(&self.expression, BinaryOperation(_, _, _)) } @@ -565,18 +615,6 @@ impl ConstraintCircuit { } } - /// Return Some(index) iff the circuit node represents a linear function with one - /// term and a coefficient of one. Returns the index in which the multivariate - /// polynomial is linear. Returns None otherwise. - pub fn get_linear_one_index(&self) -> Option { - if let Input(input) = self.expression { - let index: usize = input.into(); - Some(index) - } else { - None - } - } - /// Return true iff the evaluation value of this node depends on a challenge pub fn is_randomized(&self) -> bool { match &self.expression { @@ -588,49 +626,6 @@ impl ConstraintCircuit { } } - /// Return the flattened multivariate polynomial that this node - /// represents, given the challenges. - pub fn partial_evaluate(&self, challenges: &T) -> MPolynomial { - let mut polynomial = self.flatten(challenges); - polynomial.normalize(); - polynomial - } - - /// Return the flat multivariate polynomial that computes the - /// same value as this circuit. Do this by recursively applying - /// the multivariate polynomial binary operations, and by - /// replacing the inputs by variables. - fn flatten(&self, challenges: &T) -> MPolynomial { - match &self.expression { - XConstant(xfe) => MPolynomial::::from_constant(*xfe, self.var_count), - BConstant(bfe) => { - MPolynomial::::from_constant(bfe.lift(), self.var_count) - } - Input(input) => { - let mpol_index: usize = (*input).into(); - MPolynomial::::variables(self.var_count)[mpol_index].clone() - } - Challenge(challenge_id) => MPolynomial::::from_constant( - challenges.get_challenge(*challenge_id), - self.var_count, - ), - BinaryOperation(binop, lhs, rhs) => match binop { - BinOp::Add => { - lhs.as_ref().borrow().flatten(challenges) - + rhs.as_ref().borrow().flatten(challenges) - } - BinOp::Sub => { - lhs.as_ref().borrow().flatten(challenges) - - rhs.as_ref().borrow().flatten(challenges) - } - BinOp::Mul => { - lhs.as_ref().borrow().flatten(challenges) - * rhs.as_ref().borrow().flatten(challenges) - } - }, - } - } - /// Replace all challenges with constants in subtree fn apply_challenges_to_one_root(&mut self, challenges: &T) { match &self.expression { @@ -655,6 +650,39 @@ impl ConstraintCircuit { circuit.apply_challenges_to_one_root(challenges); } } + + fn evaluate_inner( + &self, + base_table: ArrayView2, + ext_table: ArrayView2, + ) -> XFieldElement { + match self.clone().expression { + XConstant(xfe) => xfe, + BConstant(bfe) => bfe.lift(), + Input(input) => input.evaluate(base_table, ext_table), + Challenge(challenge_id) => panic!("Challenge {} not evaluated", challenge_id), + BinaryOperation(binop, lhs, rhs) => { + let lhs_value = lhs.as_ref().borrow().evaluate_inner(base_table, ext_table); + let rhs_value = rhs.as_ref().borrow().evaluate_inner(base_table, ext_table); + match binop { + BinOp::Add => lhs_value + rhs_value, + BinOp::Sub => lhs_value - rhs_value, + BinOp::Mul => lhs_value * rhs_value, + } + } + } + } + + pub fn evaluate( + &self, + base_table: ArrayView2, + ext_table: ArrayView2, + challenges: &T, + ) -> XFieldElement { + let mut self_to_evaluate = self.clone(); + self_to_evaluate.apply_challenges_to_one_root(challenges); + self_to_evaluate.evaluate_inner(base_table, ext_table) + } } #[derive(Clone)] @@ -713,8 +741,7 @@ fn binop( circuit: Rc::new(RefCell::new(ConstraintCircuit { visited_counter: 0, expression: BinaryOperation(binop, Rc::clone(&lhs.circuit), Rc::clone(&rhs.circuit)), - id: CircuitId(new_index), - var_count: lhs.circuit.as_ref().borrow().var_count, + id: new_index, })), id_counter_ref: Rc::clone(&lhs.id_counter_ref), all_nodes: Rc::clone(&lhs.all_nodes), @@ -736,12 +763,11 @@ fn binop( visited_counter: 0, expression: BinaryOperation( binop, - // Switch rhs and lhs for symmetric operators to check for membership in hash set + // Switch rhs and lhs for symmetric operators to check membership in hash set Rc::clone(&rhs.circuit), Rc::clone(&lhs.circuit), ), - id: CircuitId(new_index), - var_count: lhs.circuit.as_ref().borrow().var_count, + id: new_index, })), id_counter_ref: Rc::clone(&lhs.id_counter_ref), all_nodes: Rc::clone(&lhs.all_nodes), @@ -804,11 +830,6 @@ impl Sum for ConstraintCircuitMonad ConstraintCircuitMonad { - /// Flatten a circuit to reveal its equivalent multivariate polynomial - pub fn partial_evaluate(&self, challenges: &T) -> MPolynomial { - self.circuit.as_ref().borrow().partial_evaluate(challenges) - } - /// Unwrap a ConstraintCircuitMonad to reveal its inner ConstraintCircuit pub fn consume(self) -> ConstraintCircuit { self.circuit.try_borrow().unwrap().to_owned() @@ -822,16 +843,20 @@ pub struct ConstraintCircuitBuilder { id_counter: Rc>, all_nodes: Rc>>>, _table_type: PhantomData, - var_count: usize, +} + +impl Default for ConstraintCircuitBuilder { + fn default() -> Self { + Self::new() + } } impl ConstraintCircuitBuilder { - pub fn new(var_count: usize) -> Self { + pub fn new() -> Self { Self { id_counter: Rc::new(RefCell::new(0)), all_nodes: Rc::new(RefCell::new(HashSet::default())), _table_type: PhantomData, - var_count, } } @@ -865,8 +890,7 @@ impl ConstraintCircuitBuilder { circuit: Rc::new(RefCell::new(ConstraintCircuit { visited_counter: 0usize, expression, - id: CircuitId(new_id), - var_count: self.var_count, + id: new_id, })), id_counter_ref: Rc::clone(&self.id_counter), all_nodes: Rc::clone(&self.all_nodes), @@ -897,14 +921,14 @@ mod constraint_circuit_tests { use std::hash::Hasher; use itertools::Itertools; - use rand::{thread_rng, RngCore}; - use twenty_first::shared_math::mpolynomial::MPolynomial; + use rand::thread_rng; + use rand::RngCore; use twenty_first::shared_math::other::random_elements; use crate::table::challenges::AllChallenges; - use crate::table::instruction_table::{ - ExtInstructionTable, InstructionTableChallengeId, InstructionTableChallenges, - }; + use crate::table::instruction_table::ExtInstructionTable; + use crate::table::instruction_table::InstructionTableChallengeId; + use crate::table::instruction_table::InstructionTableChallenges; use crate::table::jump_stack_table::ExtJumpStackTable; use crate::table::op_stack_table::ExtOpStackTable; use crate::table::processor_table::ExtProcessorTable; @@ -945,97 +969,66 @@ mod constraint_circuit_tests { counter } - fn circuit_mpol_builder( - challenges: &InstructionTableChallenges, - ) -> ( - ConstraintCircuitMonad>, - MPolynomial, - ConstraintCircuitBuilder>, + fn random_circuit_builder() -> ( + ConstraintCircuitMonad>, + ConstraintCircuitBuilder>, ) { - let var_count = 100; - let circuit_builder: ConstraintCircuitBuilder< - InstructionTableChallenges, - DualRowIndicator<50>, - > = ConstraintCircuitBuilder::new(var_count); - let mpol_variables = MPolynomial::::variables(var_count); + let mut rng = thread_rng(); + let num_base_columns = 50; + let num_ext_columns = 40; + let var_count = 2 * (num_base_columns + num_ext_columns); + let circuit_builder = ConstraintCircuitBuilder::new(); let b_constants: Vec = random_elements(var_count); let x_constants: Vec = random_elements(var_count); - let zero = MPolynomial::from_constant(XFieldElement::zero(), var_count); - let mut rng = thread_rng(); - let rand: usize = rng.next_u64() as usize; - let mut ret_mpol = mpol_variables[rand % var_count].clone(); - let circuit_input: DualRowIndicator<50> = (rand % var_count).into(); + let circuit_input = + DualRowIndicator::NextBaseRow(rng.next_u64() as usize % num_base_columns); let mut ret_circuit = circuit_builder.input(circuit_input); for _ in 0..100 { - let rand: usize = rng.next_u64() as usize; - let choices = 6; - let (mpol, circuit): ( - MPolynomial, - ConstraintCircuitMonad>, - ) = if rand % choices == 0 { - // p(x, y, z) = x - let mp = mpol_variables[rand % var_count].clone(); - let input_value: DualRowIndicator<50> = (rand % var_count).into(); - (mp.clone(), circuit_builder.input(input_value)) - } else if rand % choices == 1 { - // p(x, y, z) = c - ( - MPolynomial::from_constant(x_constants[rand % var_count], var_count), - circuit_builder.x_constant(x_constants[rand % var_count]), - ) - } else if rand % choices == 2 { - // p(x, y, z) = rand_i - ( - MPolynomial::from_constant(challenges.processor_perm_indeterminate, var_count), - circuit_builder - .challenge(InstructionTableChallengeId::ProcessorPermIndeterminate), - ) - } else if rand % choices == 3 { - // p(x, y, z) = 0 - ( - zero.clone(), - circuit_builder.x_constant(XFieldElement::zero()), - ) - } else if rand % choices == 4 { - // p(x, y, z) = bfe - ( - MPolynomial::from_constant(b_constants[rand % var_count].lift(), var_count), - circuit_builder.b_constant(b_constants[rand % var_count]), - ) - } else { - // p(x, y, z) = rand_i * x - let input_value: DualRowIndicator<50> = (rand % var_count).into(); - ( - mpol_variables[rand % var_count] - .clone() - .scalar_mul(challenges.processor_perm_indeterminate), - circuit_builder.input(input_value) - * circuit_builder - .challenge(InstructionTableChallengeId::ProcessorPermIndeterminate), - ) - }; - let operation_indicator = rand % 3; - match operation_indicator { + let circuit = match rng.next_u64() % 6 { 0 => { - ret_mpol = ret_mpol.clone() * mpol; - ret_circuit = ret_circuit * circuit; + // p(x, y, z) = x + circuit_builder.input(DualRowIndicator::CurrentBaseRow( + rng.next_u64() as usize % num_base_columns, + )) } 1 => { - ret_mpol = ret_mpol.clone() + mpol; - ret_circuit = ret_circuit + circuit; + // p(x, y, z) = xfe + circuit_builder.x_constant(x_constants[rng.next_u64() as usize % var_count]) } 2 => { - ret_mpol = ret_mpol.clone() - mpol; - ret_circuit = ret_circuit - circuit; + // p(x, y, z) = rand_i + circuit_builder + .challenge(InstructionTableChallengeId::ProcessorPermIndeterminate) + } + 3 => { + // p(x, y, z) = 0 + circuit_builder.x_constant(XFieldElement::zero()) + } + 4 => { + // p(x, y, z) = bfe + circuit_builder.b_constant(b_constants[rng.next_u64() as usize % var_count]) } - _ => panic!(), + 5 => { + // p(x, y, z) = rand_i * x + let input_value = + DualRowIndicator::CurrentExtRow(rng.next_u64() as usize % num_ext_columns); + let challenge = InstructionTableChallengeId::ProcessorPermIndeterminate; + circuit_builder.input(input_value) * circuit_builder.challenge(challenge) + } + _ => unreachable!(), + }; + match rng.next_u32() % 3 { + 0 => ret_circuit = ret_circuit * circuit, + 1 => ret_circuit = ret_circuit + circuit, + 2 => ret_circuit = ret_circuit - circuit, + _ => unreachable!(), } } - (ret_circuit, ret_mpol, circuit_builder) + (ret_circuit, circuit_builder) } - // Make a deep copy of a MPolCircuit and return it as a MPolCircuitRef + // Make a deep copy of a Multicircuit and return it as a ConstraintCircuitMonad fn deep_copy_inner( val: &ConstraintCircuit, builder: &mut ConstraintCircuitBuilder, @@ -1056,25 +1049,21 @@ mod constraint_circuit_tests { fn deep_copy( val: &ConstraintCircuit, ) -> ConstraintCircuitMonad { - let mut builder = ConstraintCircuitBuilder::new(val.var_count); + let mut builder = ConstraintCircuitBuilder::new(); deep_copy_inner(val, &mut builder) } #[test] fn equality_and_hash_agree_test() { - // Since the MPolCircuits are put into a hash set, I think it's important - // that `Eq` and `Hash` agree whether two nodes are equal or not. So if - // k1 == k2 => h(k1) == h(k2) + // The Multicircuits are put into a hash set. Hence, it is important that `Eq` and `Hash` + // agree whether two nodes are equal: k1 == k2 => h(k1) == h(k2) for _ in 0..100 { - let challenges = AllChallenges::placeholder(); - let (circuit, _mpol, circuit_builder) = - circuit_mpol_builder(&challenges.instruction_table_challenges); + let (circuit, circuit_builder) = random_circuit_builder(); let mut hasher0 = DefaultHasher::new(); circuit.hash(&mut hasher0); let hash0 = hasher0.finish(); assert_eq!(circuit, circuit); - // let zero = circuit_builder.deterministic_input(MPolynomial::zero(100)); let zero = circuit_builder.x_constant(0.into()); let same_circuit = circuit.clone() + zero; let mut hasher1 = DefaultHasher::new(); @@ -1088,20 +1077,19 @@ mod constraint_circuit_tests { } #[test] - fn mpol_circuit_hash_is_unchanged_by_meta_data_test() { + fn multi_circuit_hash_is_unchanged_by_meta_data_test() { // From https://doc.rust-lang.org/std/collections/struct.HashSet.html - // "It is a logic error for a key to be modified in such a way that the key’s hash, as determined by the Hash - // trait, or its equality, as determined by the Eq trait, changes while it is in the map. This is normally only - // possible through Cell, RefCell, global state, I/O, or unsafe code. The behavior resulting from such a logic - // error is not specified, but will be encapsulated to the HashSet that observed the logic error and not result - // in undefined behavior. This could include panics, incorrect results, aborts, memory leaks, and - // non-termination." + // "It is a logic error for a key to be modified in such a way that the key’s hash, as + // determined by the Hash trait, or its equality, as determined by the Eq trait, changes + // while it is in the map. This is normally only possible through Cell, RefCell, global + // state, I/O, or unsafe code. The behavior resulting from such a logic error is not + // specified, but will be encapsulated to the HashSet that observed the logic error and not + // result in undefined behavior. This could include panics, incorrect results, aborts, + // memory leaks, and non-termination." // This means that the hash of a node may not depend on: `visited_counter`, `counter`, - // `id_counter_ref`, or `all_nodes`. The reason for this constraint is that `all_nodes` contains - // the digest of all nodes in the multi tree. - let challenges = AllChallenges::placeholder(); - let (circuit, _mpol, _circuit_builder) = - circuit_mpol_builder(&challenges.instruction_table_challenges); + // `id_counter_ref`, or `all_nodes`. The reason for this constraint is that `all_nodes` + // contains the digest of all nodes in the multi tree. + let (circuit, _circuit_builder) = random_circuit_builder(); let mut hasher0 = DefaultHasher::new(); circuit.hash(&mut hasher0); let digest_prior = hasher0.finish(); @@ -1127,74 +1115,14 @@ mod constraint_circuit_tests { ); } - #[test] - fn circuit_and_mpol_equivalence_check() { - for i in 0..1000 { - let challenges = AllChallenges::placeholder(); - let (circuit, mpol, circuit_builder) = - circuit_mpol_builder(&challenges.instruction_table_challenges); - assert_eq!( - mpol, - circuit.partial_evaluate(&challenges.instruction_table_challenges), - "Partial evaluate and constructed mpol must agree" - ); - - assert_eq!( - circuit.circuit.as_ref().borrow().degree(), - mpol.degree(), - "circuit degree and equivalent mpol degree must match before constant folding. circuit: {}\n\n mpol: {mpol}.\n iteration {i}", circuit.circuit.as_ref().borrow() - ); - - // Also compare with symbolic evaluation - let rand_degree = (thread_rng().next_u32() % 200) as i64; - let interpolated_degrees = vec![rand_degree; circuit_builder.var_count]; - assert_eq!( - circuit.circuit.as_ref().borrow().symbolic_degree_bound(&interpolated_degrees), - mpol.symbolic_degree_bound(&interpolated_degrees), - "symbolic degree bounds must match before constant folding. circuit: {}\n\n mpol: {mpol}.\n interpolated degree: {rand_degree}\niteration {i}", - circuit.circuit.as_ref().borrow() - ); - - // Also verify equality after constant folding of the circuit - let copied_circuit = deep_copy(&circuit.circuit.as_ref().borrow()); - let mut circuits = vec![circuit.consume()]; - ConstraintCircuit::constant_folding(&mut circuits.iter_mut().collect_vec()); - let partial_evaluated = - circuits[0].partial_evaluate(&challenges.instruction_table_challenges); - assert_eq!( - mpol, - partial_evaluated, "Circuit before and after constant folding must agree after parital evaluate.\n before: {copied_circuit}\nafter: {}", circuits[0] - ); - assert_eq!( - circuits[0].degree(), - mpol.degree(), - "circuit degree and equivalent mpol degree must match after constant folding. circuit: {}\n\n mpol: {mpol}.\n iteration {i}", circuits[0] - ); - assert_eq!( - circuits[0].degree(), - partial_evaluated.degree(), - "circuit degree and the degree of its partial evaluation must agree. circuit: {}\n\n mpol: {mpol}.\n iteration {i}", circuits[0] - ); - - // Also compare with symbolic evaluation - let interpolated_degrees = vec![rand_degree; circuit_builder.var_count]; - assert_eq!( - circuits[0].symbolic_degree_bound(&interpolated_degrees), - partial_evaluated.symbolic_degree_bound(&interpolated_degrees), - "symbolic degree bounds must match before constant folding. circuit: {}\n\n mpol: {mpol}.\n iteration {i}", circuits[0] - ); - } - } - #[test] fn circuit_equality_check_and_constant_folding_test() { - let var_count = 10; let circuit_builder: ConstraintCircuitBuilder< InstructionTableChallenges, - DualRowIndicator<5>, - > = ConstraintCircuitBuilder::new(var_count); - let var_0 = circuit_builder.input(DualRowIndicator::CurrentRow(0)); - let var_4 = circuit_builder.input(DualRowIndicator::NextRow(4)); + DualRowIndicator<5, 3>, + > = ConstraintCircuitBuilder::new(); + let var_0 = circuit_builder.input(DualRowIndicator::CurrentBaseRow(0)); + let var_4 = circuit_builder.input(DualRowIndicator::NextBaseRow(4)); let four = circuit_builder.x_constant(4.into()); let one = circuit_builder.x_constant(1.into()); let zero = circuit_builder.x_constant(0.into()); @@ -1272,9 +1200,7 @@ mod constraint_circuit_tests { #[test] fn constant_folding_pbt() { for _ in 0..1000 { - let challenges = AllChallenges::placeholder(); - let (circuit, _mpol, circuit_builder) = - circuit_mpol_builder(&challenges.instruction_table_challenges); + let (circuit, circuit_builder) = random_circuit_builder(); let one = circuit_builder.x_constant(1.into()); let zero = circuit_builder.x_constant(0.into()); @@ -1382,86 +1308,23 @@ mod constraint_circuit_tests { } } - #[test] - fn mpol_algebra_and_circuit_building_is_equivalent_simple_test() { - let var_count = 10; - let variables = MPolynomial::::variables(10); - let four_mpol = MPolynomial::::from_constant( - XFieldElement::new_const(4u64.into()), - var_count, - ); - - let expr_mpol = (variables[0].clone() + variables[4].clone()) - * (variables[8].clone() - variables[9].clone()) - * four_mpol.clone() - * four_mpol; - - let circuit_builder: ConstraintCircuitBuilder< - InstructionTableChallenges, - DualRowIndicator<5>, - > = ConstraintCircuitBuilder::new(var_count); - let var_0 = circuit_builder.input(DualRowIndicator::CurrentRow(0)); - let var_4 = circuit_builder.input(DualRowIndicator::CurrentRow(4)); - let var_8 = circuit_builder.input(DualRowIndicator::NextRow(3)); - let var_9 = circuit_builder.input(DualRowIndicator::NextRow(4)); - - let four = circuit_builder.x_constant(4.into()); - - let expr_circuit = (var_0 + var_4) * (var_8 - var_9) * four.clone() * four; - - // Verify that IDs are unique - ConstraintCircuit::>::assert_has_unique_ids( - &mut [expr_circuit.clone().consume()], - ); - - // Verify that partial evaluation agrees with the flat polynomial representation - let expr_circuit_partial_evaluated = expr_circuit - .partial_evaluate(&AllChallenges::placeholder().instruction_table_challenges); - assert_eq!(expr_mpol, expr_circuit_partial_evaluated); - } - fn constant_folding_of_table_constraints_test( mut constraints: Vec>, challenges: T, table_name: &str, ) { + ConstraintCircuit::assert_has_unique_ids(&mut constraints); println!( "nodes in {table_name} table constraint multitree prior to constant folding: {}", node_counter(&mut constraints) ); - let mut before_fold: Vec> = vec![]; - - for circuit in constraints.iter() { - let partial_evaluated = circuit.partial_evaluate(&challenges); - assert_eq!( - partial_evaluated.degree(), - circuit.degree(), - "Degree of partial evaluated and circuit must agree before constant folding" - ); - before_fold.push(partial_evaluated); - } - ConstraintCircuit::constant_folding(&mut constraints.iter_mut().collect_vec()); println!( "nodes in {table_name} constraint multitree after constant folding: {}", node_counter(&mut constraints) ); - - let mut after_fold: Vec> = vec![]; - for circuit in constraints.iter() { - let partial_evaluated = circuit.partial_evaluate(&challenges); - assert_eq!( - partial_evaluated.degree(), - circuit.degree(), - "Degree of partial evaluated and circuit must agree after constant folding" - ); - after_fold.push(partial_evaluated); - } - - for (i, (before, after)) in before_fold.iter().zip_eq(after_fold.iter()).enumerate() { - assert_eq!(before, after, "Constant folding must leave partially evaluated constraints unchanged for {table_name} table constraint {i}"); - } + ConstraintCircuit::assert_has_unique_ids(&mut constraints); assert!( constraints @@ -1481,9 +1344,11 @@ mod constraint_circuit_tests { ConstraintCircuit::constant_folding(&mut constraints.iter_mut().collect_vec()); println!( - "nodes in {table_name} constraint multitree after applying challenges and constant folding again: {}", + "nodes in {table_name} constraint multitree after applying challenges and constant \ + folding again: {}", node_counter(&mut constraints) ); + ConstraintCircuit::assert_has_unique_ids(&mut constraints); let circuit_degree = constraints.iter().map(|c| c.degree()).max().unwrap(); println!("Max degree constraint for {table_name} table: {circuit_degree}"); @@ -1491,7 +1356,7 @@ mod constraint_circuit_tests { #[test] fn constant_folding_instruction_table_test() { - let challenges = AllChallenges::placeholder(); + let challenges = AllChallenges::placeholder(&[], &[]); let constraint_circuits = ExtInstructionTable::ext_transition_constraints_as_circuits(); constant_folding_of_table_constraints_test( constraint_circuits, @@ -1502,7 +1367,7 @@ mod constraint_circuit_tests { #[test] fn constant_folding_processor_table_test() { - let challenges = AllChallenges::placeholder(); + let challenges = AllChallenges::placeholder(&[], &[]); let constraint_circuits = ExtProcessorTable::ext_transition_constraints_as_circuits(); constant_folding_of_table_constraints_test( constraint_circuits, @@ -1513,7 +1378,7 @@ mod constraint_circuit_tests { #[test] fn constant_folding_program_table_test() { - let challenges = AllChallenges::placeholder(); + let challenges = AllChallenges::placeholder(&[], &[]); let constraint_circuits = ExtProgramTable::ext_transition_constraints_as_circuits(); constant_folding_of_table_constraints_test( constraint_circuits, @@ -1524,7 +1389,7 @@ mod constraint_circuit_tests { #[test] fn constant_folding_jump_stack_table_test() { - let challenges = AllChallenges::placeholder(); + let challenges = AllChallenges::placeholder(&[], &[]); let constraint_circuits = ExtJumpStackTable::ext_transition_constraints_as_circuits(); constant_folding_of_table_constraints_test( constraint_circuits, @@ -1535,7 +1400,7 @@ mod constraint_circuit_tests { #[test] fn constant_folding_op_stack_table_test() { - let challenges = AllChallenges::placeholder(); + let challenges = AllChallenges::placeholder(&[], &[]); let constraint_circuits = ExtOpStackTable::ext_transition_constraints_as_circuits(); constant_folding_of_table_constraints_test( constraint_circuits, @@ -1546,7 +1411,7 @@ mod constraint_circuit_tests { #[test] fn constant_folding_ram_stack_table_test() { - let challenges = AllChallenges::placeholder(); + let challenges = AllChallenges::placeholder(&[], &[]); let constraint_circuits = ExtRamTable::ext_transition_constraints_as_circuits(); constant_folding_of_table_constraints_test( constraint_circuits, diff --git a/triton-vm/src/table/cross_table_argument.rs b/triton-vm/src/table/cross_table_argument.rs new file mode 100644 index 000000000..a43c6dcca --- /dev/null +++ b/triton-vm/src/table/cross_table_argument.rs @@ -0,0 +1,286 @@ +use std::ops::Mul; + +use ndarray::ArrayView1; +use num_traits::One; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; +use twenty_first::shared_math::b_field_element::BFieldElement; +use twenty_first::shared_math::mpolynomial::Degree; +use twenty_first::shared_math::x_field_element::XFieldElement; + +use CrossTableChallengeId::*; + +use crate::table::challenges::AllChallenges; +use crate::table::challenges::TableChallenges; +use crate::table::extension_table::Evaluable; +use crate::table::extension_table::Quotientable; +use crate::table::processor_table::PROCESSOR_TABLE_NUM_PERMUTATION_ARGUMENTS; +use crate::table::table_column::HashExtTableColumn; +use crate::table::table_column::InstructionExtTableColumn; +use crate::table::table_column::JumpStackExtTableColumn; +use crate::table::table_column::MasterExtTableColumn; +use crate::table::table_column::OpStackExtTableColumn; +use crate::table::table_column::ProcessorExtTableColumn; +use crate::table::table_column::ProgramExtTableColumn; +use crate::table::table_column::RamExtTableColumn; + +pub const NUM_PRIVATE_PERM_ARGS: usize = PROCESSOR_TABLE_NUM_PERMUTATION_ARGUMENTS; +pub const NUM_PRIVATE_EVAL_ARGS: usize = 3; +pub const NUM_CROSS_TABLE_ARGS: usize = NUM_PRIVATE_PERM_ARGS + NUM_PRIVATE_EVAL_ARGS; +pub const NUM_PUBLIC_EVAL_ARGS: usize = 2; +pub const NUM_CROSS_TABLE_WEIGHTS: usize = NUM_CROSS_TABLE_ARGS + NUM_PUBLIC_EVAL_ARGS; + +pub trait CrossTableArg { + fn default_initial() -> XFieldElement + where + Self: Sized; + + fn compute_terminal( + symbols: &[BFieldElement], + initial: XFieldElement, + challenge: XFieldElement, + ) -> XFieldElement + where + Self: Sized; +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct PermArg {} + +impl CrossTableArg for PermArg { + fn default_initial() -> XFieldElement { + XFieldElement::one() + } + + /// Compute the product for a permutation argument as specified by `initial`, `challenge`, + /// and `symbols`. This amounts to evaluating polynomial + /// `f(x) = initial · Π_i (x - symbols[i])` + /// at point `challenge`, i.e., returns `f(challenge)`. + fn compute_terminal( + symbols: &[BFieldElement], + initial: XFieldElement, + challenge: XFieldElement, + ) -> XFieldElement { + symbols + .iter() + .map(|&symbol| challenge - symbol) + .fold(initial, XFieldElement::mul) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct EvalArg {} + +impl CrossTableArg for EvalArg { + fn default_initial() -> XFieldElement { + XFieldElement::one() + } + + /// Compute the evaluation for an evaluation argument as specified by `initial`, `challenge`, + /// and `symbols`. This amounts to evaluating polynomial + /// `f(x) = initial·x^n + Σ_i symbols[n-i]·x^i` + /// at point `challenge`, i.e., returns `f(challenge)`. + fn compute_terminal( + symbols: &[BFieldElement], + initial: XFieldElement, + challenge: XFieldElement, + ) -> XFieldElement { + symbols.iter().fold(initial, |running_evaluation, &symbol| { + challenge * running_evaluation + symbol + }) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct GrandCrossTableArg {} + +#[derive(Clone, Debug)] +pub struct CrossTableChallenges { + pub input_terminal: XFieldElement, + pub output_terminal: XFieldElement, + + pub program_to_instruction_weight: XFieldElement, + pub processor_to_instruction_weight: XFieldElement, + pub processor_to_op_stack_weight: XFieldElement, + pub processor_to_ram_weight: XFieldElement, + pub processor_to_jump_stack_weight: XFieldElement, + pub processor_to_hash_weight: XFieldElement, + pub hash_to_processor_weight: XFieldElement, + pub all_clock_jump_differences_weight: XFieldElement, + pub input_to_processor_weight: XFieldElement, + pub processor_to_output_weight: XFieldElement, +} + +#[derive(Debug, Copy, Clone, Display, EnumCountMacro, EnumIter, PartialEq, Eq, Hash)] +pub enum CrossTableChallengeId { + InputTerminal, + OutputTerminal, + + ProgramToInstructionWeight, + ProcessorToInstructionWeight, + ProcessorToOpStackWeight, + ProcessorToRamWeight, + ProcessorToJumpStackWeight, + ProcessorToHashWeight, + HashToProcessorWeight, + AllClockJumpDifferencesWeight, + InputToProcessorWeight, + ProcessorToOutputWeight, +} + +impl From for usize { + fn from(val: CrossTableChallengeId) -> Self { + val as usize + } +} + +impl TableChallenges for CrossTableChallenges { + type Id = CrossTableChallengeId; + + #[inline] + fn get_challenge(&self, id: Self::Id) -> XFieldElement { + match id { + InputTerminal => self.input_terminal, + OutputTerminal => self.output_terminal, + ProgramToInstructionWeight => self.program_to_instruction_weight, + ProcessorToInstructionWeight => self.processor_to_instruction_weight, + ProcessorToOpStackWeight => self.processor_to_op_stack_weight, + ProcessorToRamWeight => self.processor_to_ram_weight, + ProcessorToJumpStackWeight => self.processor_to_jump_stack_weight, + ProcessorToHashWeight => self.processor_to_hash_weight, + HashToProcessorWeight => self.hash_to_processor_weight, + AllClockJumpDifferencesWeight => self.all_clock_jump_differences_weight, + InputToProcessorWeight => self.input_to_processor_weight, + ProcessorToOutputWeight => self.processor_to_output_weight, + } + } +} + +impl Evaluable for GrandCrossTableArg { + fn evaluate_initial_constraints( + _base_row: ArrayView1, + _ext_row: ArrayView1, + _challenges: &AllChallenges, + ) -> Vec { + vec![] + } + + fn evaluate_consistency_constraints( + _base_row: ArrayView1, + _ext_row: ArrayView1, + _challenges: &AllChallenges, + ) -> Vec { + vec![] + } + + fn evaluate_transition_constraints( + _current_base_row: ArrayView1, + _current_ext_row: ArrayView1, + _next_base_row: ArrayView1, + _next_ext_row: ArrayView1, + _challenges: &AllChallenges, + ) -> Vec { + vec![] + } + + fn evaluate_terminal_constraints( + _base_row: ArrayView1, + ext_row: ArrayView1, + challenges: &AllChallenges, + ) -> Vec { + let challenges = &challenges.cross_table_challenges; + + let input_to_processor = challenges.get_challenge(InputTerminal) + - ext_row[ProcessorExtTableColumn::InputTableEvalArg.master_ext_table_index()]; + let processor_to_output = ext_row + [ProcessorExtTableColumn::OutputTableEvalArg.master_ext_table_index()] + - challenges.get_challenge(OutputTerminal); + + let program_to_instruction = ext_row + [ProgramExtTableColumn::RunningEvaluation.master_ext_table_index()] + - ext_row[InstructionExtTableColumn::RunningEvaluation.master_ext_table_index()]; + let processor_to_instruction = ext_row + [ProcessorExtTableColumn::InstructionTablePermArg.master_ext_table_index()] + - ext_row[InstructionExtTableColumn::RunningProductPermArg.master_ext_table_index()]; + let processor_to_op_stack = ext_row + [ProcessorExtTableColumn::OpStackTablePermArg.master_ext_table_index()] + - ext_row[OpStackExtTableColumn::RunningProductPermArg.master_ext_table_index()]; + let processor_to_ram = ext_row + [ProcessorExtTableColumn::RamTablePermArg.master_ext_table_index()] + - ext_row[RamExtTableColumn::RunningProductPermArg.master_ext_table_index()]; + let processor_to_jump_stack = ext_row + [ProcessorExtTableColumn::JumpStackTablePermArg.master_ext_table_index()] + - ext_row[JumpStackExtTableColumn::RunningProductPermArg.master_ext_table_index()]; + let processor_to_hash = ext_row + [ProcessorExtTableColumn::ToHashTableEvalArg.master_ext_table_index()] + - ext_row[HashExtTableColumn::FromProcessorRunningEvaluation.master_ext_table_index()]; + let hash_to_processor = ext_row + [HashExtTableColumn::ToProcessorRunningEvaluation.master_ext_table_index()] + - ext_row[ProcessorExtTableColumn::FromHashTableEvalArg.master_ext_table_index()]; + let all_clock_jump_differences = ext_row + [ProcessorExtTableColumn::AllClockJumpDifferencesPermArg.master_ext_table_index()] + - ext_row + [OpStackExtTableColumn::AllClockJumpDifferencesPermArg.master_ext_table_index()] + * ext_row + [RamExtTableColumn::AllClockJumpDifferencesPermArg.master_ext_table_index()] + * ext_row[JumpStackExtTableColumn::AllClockJumpDifferencesPermArg + .master_ext_table_index()]; + + let non_linear_sum = challenges.get_challenge(InputToProcessorWeight) * input_to_processor + + challenges.get_challenge(ProcessorToOutputWeight) * processor_to_output + + challenges.get_challenge(ProgramToInstructionWeight) * program_to_instruction + + challenges.get_challenge(ProcessorToInstructionWeight) * processor_to_instruction + + challenges.get_challenge(ProcessorToOpStackWeight) * processor_to_op_stack + + challenges.get_challenge(ProcessorToRamWeight) * processor_to_ram + + challenges.get_challenge(ProcessorToJumpStackWeight) * processor_to_jump_stack + + challenges.get_challenge(ProcessorToHashWeight) * processor_to_hash + + challenges.get_challenge(HashToProcessorWeight) * hash_to_processor + + challenges.get_challenge(AllClockJumpDifferencesWeight) * all_clock_jump_differences; + vec![non_linear_sum] + } +} + +impl Quotientable for GrandCrossTableArg { + fn num_initial_quotients() -> usize { + 0 + } + + fn num_consistency_quotients() -> usize { + 0 + } + + fn num_transition_quotients() -> usize { + 0 + } + + fn num_terminal_quotients() -> usize { + 1 + } + + fn initial_quotient_degree_bounds(_interpolant_degree: Degree) -> Vec { + vec![] + } + + fn consistency_quotient_degree_bounds( + _interpolant_degree: Degree, + _padded_height: usize, + ) -> Vec { + vec![] + } + + fn transition_quotient_degree_bounds( + _interpolant_degree: Degree, + _padded_height: usize, + ) -> Vec { + vec![] + } + + fn terminal_quotient_degree_bounds(interpolant_degree: Degree) -> Vec { + let zerofier_degree = 1 as Degree; + let max_columns_involved_in_one_cross_table_argument = 3; + vec![ + interpolant_degree * max_columns_involved_in_one_cross_table_argument - zerofier_degree, + ] + } +} diff --git a/triton-vm/src/table/extension_table.rs b/triton-vm/src/table/extension_table.rs index 0b0b14937..b3f9872ab 100644 --- a/triton-vm/src/table/extension_table.rs +++ b/triton-vm/src/table/extension_table.rs @@ -1,41 +1,30 @@ use std::fmt::Display; use itertools::Itertools; -use num_traits::One; -use rayon::iter::{ - IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, -}; +use ndarray::parallel::prelude::*; +use ndarray::Array1; +use ndarray::ArrayView1; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use ndarray::Axis; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::mpolynomial::Degree; -use twenty_first::shared_math::other::transpose; -use twenty_first::shared_math::traits::{FiniteField, Inverse, ModPowU32}; use twenty_first::shared_math::x_field_element::XFieldElement; -use triton_profiler::triton_profiler::TritonProfiler; -use triton_profiler::{prof_start, prof_stop}; - use crate::arithmetic_domain::ArithmeticDomain; -use crate::table::extension_table; -use crate::table::table_collection::interpolant_degree; - -use super::base_table::TableLike; -use super::challenges::AllChallenges; - -// Generic methods specifically for tables that have been extended - -pub trait ExtensionTable: TableLike + Sync {} +use crate::table::challenges::AllChallenges; const ERROR_MESSAGE_GENERATE_CONSTRAINTS: &str = "Constraints must be in place. Run: `cargo run --bin constraint-evaluation-generator`"; const ERROR_MESSAGE_GENERATE_DEGREE_BOUNDS: &str = "Degree bounds must be in place. Run: `cargo run --bin constraint-evaluation-generator`"; -pub trait Evaluable: ExtensionTable { +pub trait Evaluable { /// The code for this method must be generated by running /// `cargo run --bin constraint-evaluation-generator` fn evaluate_initial_constraints( - &self, - _evaluation_point: &[XFieldElement], + _base_row: ArrayView1, + _ext_row: ArrayView1, _challenges: &AllChallenges, ) -> Vec { panic!("{ERROR_MESSAGE_GENERATE_CONSTRAINTS}") @@ -44,8 +33,8 @@ pub trait Evaluable: ExtensionTable { /// The code for this method must be generated by running /// `cargo run --bin constraint-evaluation-generator` fn evaluate_consistency_constraints( - &self, - _evaluation_point: &[XFieldElement], + _base_row: ArrayView1, + _ext_row: ArrayView1, _challenges: &AllChallenges, ) -> Vec { panic!("{ERROR_MESSAGE_GENERATE_CONSTRAINTS}") @@ -54,9 +43,10 @@ pub trait Evaluable: ExtensionTable { /// The code for this method must be generated by running /// `cargo run --bin constraint-evaluation-generator` fn evaluate_transition_constraints( - &self, - _current_row: &[XFieldElement], - _next_row: &[XFieldElement], + _current_base_row: ArrayView1, + _current_ext_row: ArrayView1, + _next_base_row: ArrayView1, + _next_ext_row: ArrayView1, _challenges: &AllChallenges, ) -> Vec { panic!("{ERROR_MESSAGE_GENERATE_CONSTRAINTS}") @@ -65,80 +55,79 @@ pub trait Evaluable: ExtensionTable { /// The code for this method must be generated by running /// `cargo run --bin constraint-evaluation-generator` fn evaluate_terminal_constraints( - &self, - _evaluation_point: &[XFieldElement], + _base_row: ArrayView1, + _ext_row: ArrayView1, _challenges: &AllChallenges, ) -> Vec { panic!("{ERROR_MESSAGE_GENERATE_CONSTRAINTS}") } } -pub trait Quotientable: ExtensionTable + Evaluable { +pub trait Quotientable: Evaluable { /// Compute the degrees of the quotients from all AIR constraints that apply to the table. fn all_degrees_with_origin( - &self, + table_name: &str, + interpolant_degree: Degree, padded_height: usize, - num_trace_randomizers: usize, ) -> Vec { - let initial_degrees_with_origin = self - .get_initial_quotient_degree_bounds(padded_height, num_trace_randomizers) + let initial_degrees_with_origin = Self::initial_quotient_degree_bounds(interpolant_degree) .into_iter() .enumerate() .map(|(i, d)| DegreeWithOrigin { degree: d, + interpolant_degree, zerofier_degree: 1, - origin_table_name: self.name(), + origin_table_name: table_name.to_owned(), origin_index: i, origin_table_height: padded_height, - origin_num_trace_randomizers: num_trace_randomizers, origin_constraint_type: "initial constraint".to_string(), }) .collect_vec(); - let consistency_degrees_with_origin = self - .get_consistency_quotient_degree_bounds(padded_height, num_trace_randomizers) - .into_iter() - .enumerate() - .map(|(i, d)| DegreeWithOrigin { - degree: d, - zerofier_degree: padded_height as Degree, - origin_table_name: self.name(), - origin_index: i, - origin_table_height: padded_height, - origin_num_trace_randomizers: num_trace_randomizers, - origin_constraint_type: "consistency constraint".to_string(), - }) - .collect(); - - let transition_degrees_with_origin = self - .get_transition_quotient_degree_bounds(padded_height, num_trace_randomizers) - .into_iter() - .enumerate() - .map(|(i, d)| DegreeWithOrigin { - degree: d, - zerofier_degree: padded_height as Degree - 1, - origin_table_name: self.name(), - origin_index: i, - origin_table_height: padded_height, - origin_num_trace_randomizers: num_trace_randomizers, - origin_constraint_type: "transition constraint".to_string(), - }) - .collect(); - - let terminal_degrees_with_origin = self - .get_terminal_quotient_degree_bounds(padded_height, num_trace_randomizers) - .into_iter() - .enumerate() - .map(|(i, d)| DegreeWithOrigin { - degree: d, - zerofier_degree: 1, - origin_table_name: self.name(), - origin_index: i, - origin_table_height: padded_height, - origin_num_trace_randomizers: num_trace_randomizers, - origin_constraint_type: "terminal constraint".to_string(), - }) - .collect(); + let consistency_degrees_with_origin = + Self::consistency_quotient_degree_bounds(interpolant_degree, padded_height) + .into_iter() + .enumerate() + .map(|(i, d)| DegreeWithOrigin { + degree: d, + interpolant_degree, + zerofier_degree: padded_height as Degree, + origin_table_name: table_name.to_owned(), + origin_index: i, + origin_table_height: padded_height, + origin_constraint_type: "consistency constraint".to_string(), + }) + .collect(); + + let transition_degrees_with_origin = + Self::transition_quotient_degree_bounds(interpolant_degree, padded_height) + .into_iter() + .enumerate() + .map(|(i, d)| DegreeWithOrigin { + degree: d, + interpolant_degree, + zerofier_degree: padded_height as Degree - 1, + origin_table_name: table_name.to_owned(), + origin_index: i, + origin_table_height: padded_height, + origin_constraint_type: "transition constraint".to_string(), + }) + .collect(); + + let terminal_degrees_with_origin = + Self::terminal_quotient_degree_bounds(interpolant_degree) + .into_iter() + .enumerate() + .map(|(i, d)| DegreeWithOrigin { + degree: d, + interpolant_degree, + zerofier_degree: 1, + origin_table_name: table_name.to_owned(), + origin_index: i, + origin_table_height: padded_height, + origin_constraint_type: "terminal constraint".to_string(), + }) + .collect(); [ initial_degrees_with_origin, @@ -149,249 +138,172 @@ pub trait Quotientable: ExtensionTable + Evaluable { .concat() } - fn initial_quotients( - &self, - domain: &ArithmeticDomain, - transposed_codewords: &[Vec], + fn fill_initial_quotients( + master_base_table: ArrayView2, + master_ext_table: ArrayView2, + quot_table: &mut ArrayViewMut2, + zerofier_inverse: ArrayView1, challenges: &AllChallenges, - ) -> Vec> { - debug_assert_eq!(domain.length, transposed_codewords.len()); - - let zerofier_codeword = domain - .domain_values() - .into_iter() - .map(|x| x - BFieldElement::one()) - .collect(); - let zerofier_inverse = BFieldElement::batch_inversion(zerofier_codeword); - - let transposed_quotient_codewords: Vec<_> = zerofier_inverse - .par_iter() + ) { + debug_assert_eq!(zerofier_inverse.len(), master_base_table.nrows()); + debug_assert_eq!(zerofier_inverse.len(), master_ext_table.nrows()); + debug_assert_eq!(zerofier_inverse.len(), quot_table.nrows()); + quot_table + .axis_iter_mut(Axis(0)) + .into_par_iter() .enumerate() - .map(|(domain_index, &z_inv)| { - let row = &transposed_codewords[domain_index]; - let evaluated_bcs = self.evaluate_initial_constraints(row, challenges); - evaluated_bcs.iter().map(|&ebc| ebc * z_inv).collect() - }) - .collect(); - transpose(&transposed_quotient_codewords) + .for_each(|(row_index, quot_row)| { + let mut quotient_table_row = Array1::from(Self::evaluate_initial_constraints( + master_base_table.row(row_index), + master_ext_table.row(row_index), + challenges, + )); + let z_inv = zerofier_inverse[row_index]; + quotient_table_row.mapv_inplace(|a| a * z_inv); + quotient_table_row.move_into(quot_row); + }); } - fn consistency_quotients( - &self, - domain: &ArithmeticDomain, - transposed_codewords: &[Vec], + fn fill_consistency_quotients( + master_base_table: ArrayView2, + master_ext_table: ArrayView2, + quot_table: &mut ArrayViewMut2, + zerofier_inverse: ArrayView1, challenges: &AllChallenges, - padded_height: usize, - ) -> Vec> { - debug_assert_eq!(domain.length, transposed_codewords.len()); - - let zerofier_codeword = domain - .domain_values() - .iter() - .map(|x| x.mod_pow_u32(padded_height as u32) - BFieldElement::one()) - .collect(); - let zerofier_inverse = BFieldElement::batch_inversion(zerofier_codeword); - - let transposed_quotient_codewords: Vec<_> = zerofier_inverse - .par_iter() + ) { + debug_assert_eq!(zerofier_inverse.len(), master_base_table.nrows()); + debug_assert_eq!(zerofier_inverse.len(), master_ext_table.nrows()); + debug_assert_eq!(zerofier_inverse.len(), quot_table.nrows()); + quot_table + .axis_iter_mut(Axis(0)) + .into_par_iter() .enumerate() - .map(|(domain_index, &z_inv)| { - let row = &transposed_codewords[domain_index]; - let evaluated_ccs = self.evaluate_consistency_constraints(row, challenges); - evaluated_ccs.iter().map(|&ecc| ecc * z_inv).collect() - }) - .collect(); - transpose(&transposed_quotient_codewords) + .for_each(|(row_index, quot_row)| { + let mut quotient_table_row = Array1::from(Self::evaluate_consistency_constraints( + master_base_table.row(row_index), + master_ext_table.row(row_index), + challenges, + )); + let z_inv = zerofier_inverse[row_index]; + quotient_table_row.mapv_inplace(|a| a * z_inv); + quotient_table_row.move_into(quot_row); + }); } - fn transition_quotients( - &self, - domain: &ArithmeticDomain, - transposed_codewords: &[Vec], + fn fill_transition_quotients( + master_base_table: ArrayView2, + master_ext_table: ArrayView2, + quot_table: &mut ArrayViewMut2, + zerofier_inverse: ArrayView1, challenges: &AllChallenges, - trace_domain_generator: BFieldElement, - padded_height: usize, - ) -> Vec> { - debug_assert_eq!(domain.length, transposed_codewords.len()); + trace_domain: ArithmeticDomain, + quotient_domain: ArithmeticDomain, + ) { + debug_assert_eq!(zerofier_inverse.len(), master_base_table.nrows()); + debug_assert_eq!(zerofier_inverse.len(), master_ext_table.nrows()); + debug_assert_eq!(zerofier_inverse.len(), quot_table.nrows()); - let one = XFieldElement::one(); - let trace_domain_generator_inverse = trace_domain_generator.inverse(); - let domain_values = domain.domain_values(); - - let subgroup_zerofier: Vec<_> = domain_values - .par_iter() - .map(|domain_value| domain_value.mod_pow_u32(padded_height as u32) - one) - .collect(); - let subgroup_zerofier_inverse = XFieldElement::batch_inversion(subgroup_zerofier); - let zerofier_inverse: Vec<_> = domain_values - .into_par_iter() - .zip_eq(subgroup_zerofier_inverse.into_par_iter()) - .map(|(domain_value, sub_z_inv)| { - (domain_value - trace_domain_generator_inverse) * sub_z_inv - }) - .collect(); // the relation between the quotient domain and the trace domain - let unit_distance = domain.length / padded_height; + let unit_distance = quotient_domain.length / trace_domain.length; + let domain_length_bit_mask = quotient_domain.length - 1; - let domain_length_bit_mask = domain.length - 1; - let transposed_quotient_codewords: Vec<_> = zerofier_inverse - .par_iter() + quot_table + .axis_iter_mut(Axis(0)) + .into_par_iter() .enumerate() - .map(|(current_row_idx, &z_inv)| { - // `&domain_length_bit_mask` performs the modulo operation cheaply: + .for_each(|(current_row_index, quot_row)| { + // bitwise logical and `domain_length_bit_mask` performs the modulo operation: // `domain.length - 1` is a bit-mask with all 1s because `domain.length` is 2^k // for some k. - let next_row_index = (current_row_idx + unit_distance) & domain_length_bit_mask; - let current_row = &transposed_codewords[current_row_idx]; - let next_row = &transposed_codewords[next_row_index]; - - let evaluated_tcs = - self.evaluate_transition_constraints(current_row, next_row, challenges); - evaluated_tcs.iter().map(|&etc| etc * z_inv).collect() - }) - .collect(); - transpose(&transposed_quotient_codewords) + let next_row_index = (current_row_index + unit_distance) & domain_length_bit_mask; + let mut quotient_table_row = Array1::from(Self::evaluate_transition_constraints( + master_base_table.row(current_row_index), + master_ext_table.row(current_row_index), + master_base_table.row(next_row_index), + master_ext_table.row(next_row_index), + challenges, + )); + let z_inv = zerofier_inverse[current_row_index]; + quotient_table_row.mapv_inplace(|a| a * z_inv); + quotient_table_row.move_into(quot_row); + }); } - fn terminal_quotients( - &self, - quotient_domain: &ArithmeticDomain, - transposed_codewords: &[Vec], + fn fill_terminal_quotients( + master_base_table: ArrayView2, + master_ext_table: ArrayView2, + quot_table: &mut ArrayViewMut2, + zerofier_inverse: ArrayView1, challenges: &AllChallenges, - trace_domain_generator: BFieldElement, - ) -> Vec> { - debug_assert_eq!(quotient_domain.length, transposed_codewords.len()); - - // The zerofier for the terminal quotient has a root in the last - // value in the cyclical group generated from the trace domain's generator. - let zerofier_codeword = quotient_domain - .domain_values() - .into_iter() - .map(|x| x - trace_domain_generator.inverse()) - .collect_vec(); - let zerofier_inverse = BFieldElement::batch_inversion(zerofier_codeword); - - let transposed_quotient_codewords: Vec<_> = zerofier_inverse - .par_iter() + ) { + debug_assert_eq!(zerofier_inverse.len(), master_base_table.nrows()); + debug_assert_eq!(zerofier_inverse.len(), master_ext_table.nrows()); + debug_assert_eq!(zerofier_inverse.len(), quot_table.nrows()); + quot_table + .axis_iter_mut(Axis(0)) + .into_par_iter() .enumerate() - .map(|(domain_index, &z_inv)| { - let row = &transposed_codewords[domain_index]; - let evaluated_termcs = self.evaluate_terminal_constraints(row, challenges); - evaluated_termcs.iter().map(|&etc| etc * z_inv).collect() - }) - .collect(); - transpose(&transposed_quotient_codewords) + .for_each(|(row_index, quot_row)| { + let mut quotient_table_row = Array1::from(Self::evaluate_terminal_constraints( + master_base_table.row(row_index), + master_ext_table.row(row_index), + challenges, + )); + let z_inv = zerofier_inverse[row_index]; + quotient_table_row.mapv_inplace(|a| a * z_inv); + quotient_table_row.move_into(quot_row); + }); } - fn all_quotients( - &self, - quotient_domain: &ArithmeticDomain, - transposed_codewords: Vec>, - challenges: &AllChallenges, - trace_domain_generator: BFieldElement, - padded_height: usize, - maybe_profiler: &mut Option, - ) -> Vec> { - prof_start!(maybe_profiler, "initial quotients"); - let initial_quotients = - self.initial_quotients(quotient_domain, &transposed_codewords, challenges); - prof_stop!(maybe_profiler, "initial quotients"); - - prof_start!(maybe_profiler, "consistency quotients"); - let consistency_quotients = self.consistency_quotients( - quotient_domain, - &transposed_codewords, - challenges, - padded_height, - ); - prof_stop!(maybe_profiler, "consistency quotients"); - - prof_start!(maybe_profiler, "transition quotients"); - let transition_quotients = self.transition_quotients( - quotient_domain, - &transposed_codewords, - challenges, - trace_domain_generator, - padded_height, - ); - prof_stop!(maybe_profiler, "transition quotients"); + fn num_initial_quotients() -> usize { + panic!("{ERROR_MESSAGE_GENERATE_CONSTRAINTS}") + } - prof_start!(maybe_profiler, "terminal quotients"); - let terminal_quotients = self.terminal_quotients( - quotient_domain, - &transposed_codewords, - challenges, - trace_domain_generator, - ); - prof_stop!(maybe_profiler, "terminal quotients"); + fn num_consistency_quotients() -> usize { + panic!("{ERROR_MESSAGE_GENERATE_CONSTRAINTS}") + } - vec![ - initial_quotients, - consistency_quotients, - transition_quotients, - terminal_quotients, - ] - .concat() + fn num_transition_quotients() -> usize { + panic!("{ERROR_MESSAGE_GENERATE_CONSTRAINTS}") } - fn get_all_quotient_degree_bounds( - &self, - padded_height: usize, - num_trace_randomizers: usize, - ) -> Vec { - vec![ - self.get_initial_quotient_degree_bounds(padded_height, num_trace_randomizers), - self.get_consistency_quotient_degree_bounds(padded_height, num_trace_randomizers), - self.get_transition_quotient_degree_bounds(padded_height, num_trace_randomizers), - self.get_terminal_quotient_degree_bounds(padded_height, num_trace_randomizers), - ] - .concat() + fn num_terminal_quotients() -> usize { + panic!("{ERROR_MESSAGE_GENERATE_CONSTRAINTS}") } - fn get_initial_quotient_degree_bounds( - &self, - _padded_height: usize, - _num_trace_randomizers: usize, - ) -> Vec { + fn initial_quotient_degree_bounds(_interpolant_degree: Degree) -> Vec { panic!("{ERROR_MESSAGE_GENERATE_DEGREE_BOUNDS}") } - fn get_consistency_quotient_degree_bounds( - &self, + fn consistency_quotient_degree_bounds( + _interpolant_degree: Degree, _padded_height: usize, - _num_trace_randomizers: usize, ) -> Vec { panic!("{ERROR_MESSAGE_GENERATE_DEGREE_BOUNDS}") } - fn get_transition_quotient_degree_bounds( - &self, + fn transition_quotient_degree_bounds( + _interpolant_degree: Degree, _padded_height: usize, - _num_trace_randomizers: usize, ) -> Vec { panic!("{ERROR_MESSAGE_GENERATE_DEGREE_BOUNDS}") } - fn get_terminal_quotient_degree_bounds( - &self, - _padded_height: usize, - _num_trace_randomizers: usize, - ) -> Vec { + fn terminal_quotient_degree_bounds(_interpolant_degree: Degree) -> Vec { panic!("{ERROR_MESSAGE_GENERATE_DEGREE_BOUNDS}") } } -pub trait QuotientableExtensionTable: ExtensionTable + Quotientable {} /// Helps debugging and benchmarking. The maximal degree achieved in any table dictates the length /// of the FRI domain, which in turn is responsible for the main performance bottleneck. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct DegreeWithOrigin { pub degree: Degree, + pub interpolant_degree: Degree, pub zerofier_degree: Degree, pub origin_table_name: String, pub origin_index: usize, pub origin_table_height: usize, - pub origin_num_trace_randomizers: usize, pub origin_constraint_type: String, } @@ -399,11 +311,11 @@ impl Default for DegreeWithOrigin { fn default() -> Self { DegreeWithOrigin { degree: -1, + interpolant_degree: 0, zerofier_degree: -1, origin_table_name: "NoTable".to_string(), origin_index: usize::MAX, origin_table_height: 0, - origin_num_trace_randomizers: 0, origin_constraint_type: "NoType".to_string(), } } @@ -411,13 +323,12 @@ impl Default for DegreeWithOrigin { impl Display for DegreeWithOrigin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let interpolant_degree = extension_table::interpolant_degree( - self.origin_table_height, - self.origin_num_trace_randomizers, - ); let zerofier_corrected_degree = self.degree + self.zerofier_degree; - assert_eq!(0, zerofier_corrected_degree % interpolant_degree); - let degree = zerofier_corrected_degree / interpolant_degree as Degree; + let degree = if self.interpolant_degree != 0 { + zerofier_corrected_degree / self.interpolant_degree + } else { + zerofier_corrected_degree + }; write!( f, "Degree of poly for table {} (index {:02}) of type {} is {}.", diff --git a/triton-vm/src/table/hash_table.rs b/triton-vm/src/table/hash_table.rs index 1b6309c3e..6c3b12a1e 100644 --- a/triton-vm/src/table/hash_table.rs +++ b/triton-vm/src/table/hash_table.rs @@ -1,115 +1,86 @@ use itertools::Itertools; -use num_traits::Zero; +use ndarray::s; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use num_traits::One; use strum::EnumCount; -use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter}; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; +use twenty_first::shared_math::rescue_prime_regular::ALPHA; use twenty_first::shared_math::rescue_prime_regular::DIGEST_LENGTH; -use twenty_first::shared_math::rescue_prime_regular::{ - ALPHA, MDS, MDS_INV, NUM_ROUNDS, ROUND_CONSTANTS, STATE_SIZE, -}; +use twenty_first::shared_math::rescue_prime_regular::MDS; +use twenty_first::shared_math::rescue_prime_regular::MDS_INV; +use twenty_first::shared_math::rescue_prime_regular::NUM_ROUNDS; +use twenty_first::shared_math::rescue_prime_regular::ROUND_CONSTANTS; +use twenty_first::shared_math::rescue_prime_regular::STATE_SIZE; use twenty_first::shared_math::x_field_element::XFieldElement; -use crate::cross_table_arguments::{CrossTableArg, EvalArg}; -use crate::table::base_table::Extendable; use crate::table::challenges::TableChallenges; -use crate::table::constraint_circuit::DualRowIndicator::{CurrentRow, NextRow}; -use crate::table::constraint_circuit::SingleRowIndicator::Row; -use crate::table::constraint_circuit::{ - ConstraintCircuit, ConstraintCircuitBuilder, ConstraintCircuitMonad, DualRowIndicator, - SingleRowIndicator, -}; +use crate::table::constraint_circuit::ConstraintCircuit; +use crate::table::constraint_circuit::ConstraintCircuitBuilder; +use crate::table::constraint_circuit::ConstraintCircuitMonad; +use crate::table::constraint_circuit::DualRowIndicator; +use crate::table::constraint_circuit::DualRowIndicator::*; +use crate::table::constraint_circuit::SingleRowIndicator; +use crate::table::constraint_circuit::SingleRowIndicator::*; +use crate::table::cross_table_argument::CrossTableArg; +use crate::table::cross_table_argument::EvalArg; use crate::table::hash_table::HashTableChallengeId::*; -use crate::table::table_column::HashBaseTableColumn::{self, *}; -use crate::table::table_column::HashExtTableColumn::{self, *}; - -use super::base_table::{InheritsFromTable, Table, TableLike}; -use super::extension_table::{ExtensionTable, QuotientableExtensionTable}; +use crate::table::master_table::NUM_BASE_COLUMNS; +use crate::table::master_table::NUM_EXT_COLUMNS; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::ExtTableColumn; +use crate::table::table_column::HashBaseTableColumn; +use crate::table::table_column::HashBaseTableColumn::*; +use crate::table::table_column::HashExtTableColumn; +use crate::table::table_column::HashExtTableColumn::*; +use crate::table::table_column::MasterBaseTableColumn; +use crate::table::table_column::MasterExtTableColumn; +use crate::vm::AlgebraicExecutionTrace; pub const HASH_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 0; pub const HASH_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 2; - -/// This is 15 because it combines: 10 stack_input_weights and 5 digest_output_weights. -pub const HASH_TABLE_NUM_EXTENSION_CHALLENGES: usize = 15; +pub const HASH_TABLE_NUM_EXTENSION_CHALLENGES: usize = HashTableChallengeId::COUNT; pub const BASE_WIDTH: usize = HashBaseTableColumn::COUNT; -pub const FULL_WIDTH: usize = BASE_WIDTH + HashExtTableColumn::COUNT; +pub const EXT_WIDTH: usize = HashExtTableColumn::COUNT; +pub const FULL_WIDTH: usize = BASE_WIDTH + EXT_WIDTH; pub const NUM_ROUND_CONSTANTS: usize = STATE_SIZE * 2; pub const TOTAL_NUM_CONSTANTS: usize = NUM_ROUND_CONSTANTS * NUM_ROUNDS; #[derive(Debug, Clone)] -pub struct HashTable { - inherited_table: Table, -} - -impl InheritsFromTable for HashTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} +pub struct HashTable {} #[derive(Debug, Clone)] -pub struct ExtHashTable { - pub(crate) inherited_table: Table, -} - -impl Default for ExtHashTable { - fn default() -> Self { - Self { - inherited_table: Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "EmptyExtHashTable".to_string(), - ), - } - } -} - -impl QuotientableExtensionTable for ExtHashTable {} - -impl InheritsFromTable for ExtHashTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} - -impl TableLike for HashTable {} - -impl Extendable for HashTable { - fn get_padding_rows(&self) -> (Option, Vec>) { - (None, vec![vec![BFieldElement::zero(); BASE_WIDTH]]) - } -} - -impl TableLike for ExtHashTable {} +pub struct ExtHashTable {} impl ExtHashTable { - pub fn ext_initial_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_initial_constraints_as_circuits() -> Vec< + ConstraintCircuit< + HashTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let challenge = |c| circuit_builder.challenge(c); let one = circuit_builder.b_constant(1_u32.into()); let running_evaluation_initial = circuit_builder.x_constant(EvalArg::default_initial()); - let round_number = circuit_builder.input(Row(ROUNDNUMBER.into())); - let running_evaluation_from_processor = - circuit_builder.input(Row(FromProcessorRunningEvaluation.into())); - let running_evaluation_to_processor = - circuit_builder.input(Row(ToProcessorRunningEvaluation.into())); + let round_number = circuit_builder.input(BaseRow(ROUNDNUMBER.master_base_table_index())); + let running_evaluation_from_processor = circuit_builder.input(ExtRow( + FromProcessorRunningEvaluation.master_ext_table_index(), + )); + let running_evaluation_to_processor = circuit_builder.input(ExtRow( + ToProcessorRunningEvaluation.master_ext_table_index(), + )); let state = [ STATE0, STATE1, STATE2, STATE3, STATE4, STATE5, STATE6, STATE7, STATE8, STATE9, ] - .map(|st| circuit_builder.input(Row(st.into()))); + .map(|st| circuit_builder.input(BaseRow(st.master_base_table_index()))); let round_number_is_0_or_1 = round_number.clone() * (round_number.clone() - one.clone()); @@ -155,18 +126,22 @@ impl ExtHashTable { .to_vec() } - pub fn ext_consistency_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_consistency_constraints_as_circuits() -> Vec< + ConstraintCircuit< + HashTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let constant = |c: u64| circuit_builder.b_constant(c.into()); - let round_number = circuit_builder.input(Row(ROUNDNUMBER.into())); - let state10 = circuit_builder.input(Row(STATE10.into())); - let state11 = circuit_builder.input(Row(STATE11.into())); - let state12 = circuit_builder.input(Row(STATE12.into())); - let state13 = circuit_builder.input(Row(STATE13.into())); - let state14 = circuit_builder.input(Row(STATE14.into())); - let state15 = circuit_builder.input(Row(STATE15.into())); + let round_number = circuit_builder.input(BaseRow(ROUNDNUMBER.master_base_table_index())); + let state10 = circuit_builder.input(BaseRow(STATE10.master_base_table_index())); + let state11 = circuit_builder.input(BaseRow(STATE11.master_base_table_index())); + let state12 = circuit_builder.input(BaseRow(STATE12.master_base_table_index())); + let state13 = circuit_builder.input(BaseRow(STATE13.master_base_table_index())); + let state14 = circuit_builder.input(BaseRow(STATE14.master_base_table_index())); + let state15 = circuit_builder.input(BaseRow(STATE15.master_base_table_index())); let round_number_deselector = |round_number_to_deselect| { (0..=NUM_ROUNDS + 1) @@ -185,10 +160,10 @@ impl ExtHashTable { round_number_is_not_1_or * state15, ]; - let round_constant_offset: usize = CONSTANT0A.into(); + let round_constant_offset = CONSTANT0A.master_base_table_index(); for round_constant_col_index in 0..NUM_ROUND_CONSTANTS { let round_constant_input = - circuit_builder.input(Row(round_constant_col_index + round_constant_offset)); + circuit_builder.input(BaseRow(round_constant_col_index + round_constant_offset)); let round_constant_constraint_circuit = (1..=NUM_ROUNDS) .map(|i| { let round_constant_idx = @@ -207,9 +182,10 @@ impl ExtHashTable { .collect() } - pub fn ext_transition_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(2 * FULL_WIDTH); + pub fn ext_transition_constraints_as_circuits() -> Vec< + ConstraintCircuit>, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let constant = |c: u64| circuit_builder.b_constant(c.into()); let from_processor_eval_indeterminate = @@ -217,17 +193,23 @@ impl ExtHashTable { let to_processor_eval_indeterminate = circuit_builder.challenge(ToProcessorEvalIndeterminate); - let round_number = circuit_builder.input(CurrentRow(ROUNDNUMBER.into())); - let running_evaluation_from_processor = - circuit_builder.input(CurrentRow(FromProcessorRunningEvaluation.into())); - let running_evaluation_to_processor = - circuit_builder.input(CurrentRow(ToProcessorRunningEvaluation.into())); - - let round_number_next = circuit_builder.input(NextRow(ROUNDNUMBER.into())); - let running_evaluation_from_processor_next = - circuit_builder.input(NextRow(FromProcessorRunningEvaluation.into())); - let running_evaluation_to_processor_next = - circuit_builder.input(NextRow(ToProcessorRunningEvaluation.into())); + let round_number = + circuit_builder.input(CurrentBaseRow(ROUNDNUMBER.master_base_table_index())); + let running_evaluation_from_processor = circuit_builder.input(CurrentExtRow( + FromProcessorRunningEvaluation.master_ext_table_index(), + )); + let running_evaluation_to_processor = circuit_builder.input(CurrentExtRow( + ToProcessorRunningEvaluation.master_ext_table_index(), + )); + + let round_number_next = + circuit_builder.input(NextBaseRow(ROUNDNUMBER.master_base_table_index())); + let running_evaluation_from_processor_next = circuit_builder.input(NextExtRow( + FromProcessorRunningEvaluation.master_ext_table_index(), + )); + let running_evaluation_to_processor_next = circuit_builder.input(NextExtRow( + ToProcessorRunningEvaluation.master_ext_table_index(), + )); // round number // round numbers evolve as @@ -276,7 +258,7 @@ impl ExtHashTable { CONSTANT14A, CONSTANT15A, ] - .map(|c| circuit_builder.input(CurrentRow(c.into()))); + .map(|c| circuit_builder.input(CurrentBaseRow(c.master_base_table_index()))); let round_constants_b: [_; STATE_SIZE] = [ CONSTANT0B, CONSTANT1B, @@ -295,14 +277,16 @@ impl ExtHashTable { CONSTANT14B, CONSTANT15B, ] - .map(|c| circuit_builder.input(CurrentRow(c.into()))); + .map(|c| circuit_builder.input(CurrentBaseRow(c.master_base_table_index()))); let state: [_; STATE_SIZE] = [ STATE0, STATE1, STATE2, STATE3, STATE4, STATE5, STATE6, STATE7, STATE8, STATE9, STATE10, STATE11, STATE12, STATE13, STATE14, STATE15, ]; - let current_state = state.map(|s| circuit_builder.input(CurrentRow(s.into()))); - let next_state = state.map(|s| circuit_builder.input(NextRow(s.into()))); + let current_state = + state.map(|s| circuit_builder.input(CurrentBaseRow(s.master_base_table_index()))); + let next_state = + state.map(|s| circuit_builder.input(NextBaseRow(s.master_base_table_index()))); // left-hand-side, starting at current round and going forward @@ -453,135 +437,116 @@ impl ExtHashTable { .collect() } - pub fn ext_terminal_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_terminal_constraints_as_circuits() -> Vec< + ConstraintCircuit< + HashTableChallenges, + SingleRowIndicator, + >, + > { // no more constraints vec![] } } impl HashTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } + pub fn fill_trace( + hash_table: &mut ArrayViewMut2, + aet: &AlgebraicExecutionTrace, + ) { + let hash_table_to_fill = hash_table.slice_mut(s![0..aet.hash_matrix.nrows(), ..]); + aet.hash_matrix.clone().move_into(hash_table_to_fill); } - pub fn new_prover(matrix: Vec>) -> Self { - let inherited_table = Table::new(BASE_WIDTH, FULL_WIDTH, matrix, "HashTable".to_string()); - Self { inherited_table } + pub fn pad_trace(_hash_table: &mut ArrayViewMut2) { + // Hash Table is padded with all-zero rows. It is also initialized with all-zero rows. + // Hence, no need to do anything. } - pub fn extend(&self, challenges: &HashTableChallenges) -> ExtHashTable { + pub fn extend( + base_table: ArrayView2, + mut ext_table: ArrayViewMut2, + challenges: &HashTableChallenges, + ) { + assert_eq!(BASE_WIDTH, base_table.ncols()); + assert_eq!(EXT_WIDTH, ext_table.ncols()); + assert_eq!(base_table.nrows(), ext_table.nrows()); let mut from_processor_running_evaluation = EvalArg::default_initial(); let mut to_processor_running_evaluation = EvalArg::default_initial(); - let mut extension_matrix: Vec> = Vec::with_capacity(self.data().len()); - for row in self.data().iter() { - let mut extension_row = [0.into(); FULL_WIDTH]; - extension_row[..BASE_WIDTH] - .copy_from_slice(&row.iter().map(|elem| elem.lift()).collect_vec()); + for row_idx in 0..base_table.nrows() { + let current_row = base_table.row(row_idx); // Add compressed input to running evaluation if round index marks beginning of hashing - if row[usize::from(ROUNDNUMBER)].value() == 1 { + if current_row[ROUNDNUMBER.base_table_index()].is_one() { let state_for_input = [ - extension_row[usize::from(STATE0)], - extension_row[usize::from(STATE1)], - extension_row[usize::from(STATE2)], - extension_row[usize::from(STATE3)], - extension_row[usize::from(STATE4)], - extension_row[usize::from(STATE5)], - extension_row[usize::from(STATE6)], - extension_row[usize::from(STATE7)], - extension_row[usize::from(STATE8)], - extension_row[usize::from(STATE9)], + current_row[STATE0.base_table_index()], + current_row[STATE1.base_table_index()], + current_row[STATE2.base_table_index()], + current_row[STATE3.base_table_index()], + current_row[STATE4.base_table_index()], + current_row[STATE5.base_table_index()], + current_row[STATE6.base_table_index()], + current_row[STATE7.base_table_index()], + current_row[STATE8.base_table_index()], + current_row[STATE9.base_table_index()], + ]; + let stack_input_weights = [ + challenges.stack_input_weight0, + challenges.stack_input_weight1, + challenges.stack_input_weight2, + challenges.stack_input_weight3, + challenges.stack_input_weight4, + challenges.stack_input_weight5, + challenges.stack_input_weight6, + challenges.stack_input_weight7, + challenges.stack_input_weight8, + challenges.stack_input_weight9, ]; let compressed_state_for_input: XFieldElement = state_for_input .iter() - .zip_eq( - [ - challenges.stack_input_weight0, - challenges.stack_input_weight1, - challenges.stack_input_weight2, - challenges.stack_input_weight3, - challenges.stack_input_weight4, - challenges.stack_input_weight5, - challenges.stack_input_weight6, - challenges.stack_input_weight7, - challenges.stack_input_weight8, - challenges.stack_input_weight9, - ] - .iter(), - ) + .zip_eq(stack_input_weights.iter()) .map(|(&state, &weight)| weight * state) .sum(); - from_processor_running_evaluation = from_processor_running_evaluation * challenges.from_processor_eval_indeterminate + compressed_state_for_input; } - extension_row[usize::from(FromProcessorRunningEvaluation)] = - from_processor_running_evaluation; // Add compressed digest to running evaluation if round index marks end of hashing - if row[usize::from(ROUNDNUMBER)].value() == NUM_ROUNDS as u64 + 1 { + if current_row[ROUNDNUMBER.base_table_index()].value() == NUM_ROUNDS as u64 + 1 { let state_for_output = [ - extension_row[usize::from(STATE0)], - extension_row[usize::from(STATE1)], - extension_row[usize::from(STATE2)], - extension_row[usize::from(STATE3)], - extension_row[usize::from(STATE4)], + current_row[STATE0.base_table_index()], + current_row[STATE1.base_table_index()], + current_row[STATE2.base_table_index()], + current_row[STATE3.base_table_index()], + current_row[STATE4.base_table_index()], + ]; + let digest_output_weights = [ + challenges.digest_output_weight0, + challenges.digest_output_weight1, + challenges.digest_output_weight2, + challenges.digest_output_weight3, + challenges.digest_output_weight4, ]; let compressed_state_for_output: XFieldElement = state_for_output .iter() - .zip_eq( - [ - challenges.digest_output_weight0, - challenges.digest_output_weight1, - challenges.digest_output_weight2, - challenges.digest_output_weight3, - challenges.digest_output_weight4, - ] - .iter(), - ) + .zip_eq(digest_output_weights.iter()) .map(|(&state, &weight)| weight * state) .sum(); - to_processor_running_evaluation = to_processor_running_evaluation * challenges.to_processor_eval_indeterminate + compressed_state_for_output; } - extension_row[usize::from(ToProcessorRunningEvaluation)] = - to_processor_running_evaluation; - extension_matrix.push(extension_row.to_vec()); - } - - assert_eq!(self.data().len(), extension_matrix.len()); - let extension_table = self.new_from_lifted_matrix(extension_matrix); - - ExtHashTable { - inherited_table: extension_table, - } - } - - pub fn for_verifier() -> ExtHashTable { - let inherited_table = - Table::new(BASE_WIDTH, FULL_WIDTH, vec![], "ExtHashTable".to_string()); - let base_table = Self { inherited_table }; - let empty_matrix: Vec> = vec![]; - let extension_table = base_table.new_from_lifted_matrix(empty_matrix); - - ExtHashTable { - inherited_table: extension_table, + let mut extension_row = ext_table.row_mut(row_idx); + extension_row[FromProcessorRunningEvaluation.ext_table_index()] = + from_processor_running_evaluation; + extension_row[ToProcessorRunningEvaluation.ext_table_index()] = + to_processor_running_evaluation; } } } -impl ExtHashTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } - } -} - #[derive(Debug, Copy, Clone, Display, EnumCountMacro, EnumIter, PartialEq, Eq, Hash)] pub enum HashTableChallengeId { FromProcessorEvalIndeterminate, @@ -666,62 +631,88 @@ impl TableChallenges for HashTableChallenges { } } -impl ExtensionTable for ExtHashTable {} - #[cfg(test)] mod constraint_tests { - use crate::table::challenges::AllChallenges; + use num_traits::Zero; + + use crate::stark::triton_stark_tests::parse_simulate_pad_extend; use crate::table::extension_table::Evaluable; - use crate::vm::Program; + use crate::table::master_table::MasterTable; use super::*; #[test] - fn table_satisfies_constraints_test() { - let program = Program::from_code("hash hash hash halt").unwrap(); - - let (aet, _, maybe_err) = program.simulate_no_input(); - - if let Some(e) = maybe_err { - panic!("Program execution failed: {e}"); - } - - let challenges = AllChallenges::placeholder(); - let ext_hash_table = - HashTable::new_prover(aet.hash_matrix.iter().map(|r| r.to_vec()).collect()) - .extend(&challenges.hash_table_challenges); - - for v in ext_hash_table.evaluate_initial_constraints(&ext_hash_table.data()[0], &challenges) + fn hash_table_satisfies_constraints_test() { + let source_code = "hash hash hash halt"; + let (_, _, master_base_table, master_ext_table, challenges) = + parse_simulate_pad_extend(source_code, vec![], vec![]); + assert_eq!( + master_base_table.master_base_matrix.nrows(), + master_ext_table.master_ext_matrix.nrows() + ); + let master_base_trace_table = master_base_table.trace_table(); + let master_ext_trace_table = master_ext_table.trace_table(); + assert_eq!( + master_base_trace_table.nrows(), + master_ext_trace_table.nrows() + ); + + let num_rows = master_base_trace_table.nrows(); + let first_base_row = master_base_trace_table.row(0); + let first_ext_row = master_ext_trace_table.row(0); + for (idx, v) in + ExtHashTable::evaluate_initial_constraints(first_base_row, first_ext_row, &challenges) + .iter() + .enumerate() { - assert!(v.is_zero()); + assert!(v.is_zero(), "Initial constraint {idx} failed."); } - for (i, row) in ext_hash_table.data().iter().enumerate() { - for (j, v) in ext_hash_table - .evaluate_consistency_constraints(row, &challenges) - .iter() - .enumerate() + for row_idx in 0..num_rows { + let base_row = master_base_trace_table.row(row_idx); + let ext_row = master_ext_trace_table.row(row_idx); + for (constraint_idx, v) in + ExtHashTable::evaluate_consistency_constraints(base_row, ext_row, &challenges) + .iter() + .enumerate() { - assert!(v.is_zero(), "consistency constraint {j} failed in row {i}"); + assert!( + v.is_zero(), + "consistency constraint {constraint_idx} failed in row {row_idx}" + ); } } - for (i, (current_row, next_row)) in ext_hash_table.data().iter().tuple_windows().enumerate() - { - for (j, v) in ext_hash_table - .evaluate_transition_constraints(current_row, next_row, &challenges) - .iter() - .enumerate() + for row_idx in 0..num_rows - 1 { + let base_row = master_base_trace_table.row(row_idx); + let ext_row = master_ext_trace_table.row(row_idx); + let next_base_row = master_base_trace_table.row(row_idx + 1); + let next_ext_row = master_ext_trace_table.row(row_idx + 1); + for (constraint_idx, v) in ExtHashTable::evaluate_transition_constraints( + base_row, + ext_row, + next_base_row, + next_ext_row, + &challenges, + ) + .iter() + .enumerate() { - assert!(v.is_zero(), "transition constraint {j} failed in row {i}",); + assert!( + v.is_zero(), + "transition constraint {constraint_idx} failed in row {row_idx}", + ); } } - for v in ext_hash_table.evaluate_terminal_constraints( - &ext_hash_table.data()[ext_hash_table.data().len() - 1], - &challenges, - ) { - assert!(v.is_zero()); + let last_base_row = master_base_trace_table.row(num_rows - 1); + let last_ext_row = master_ext_trace_table.row(num_rows - 1); + for (idx, v) in + ExtHashTable::evaluate_terminal_constraints(last_base_row, last_ext_row, &challenges) + .iter() + .enumerate() + { + assert!(v.is_zero(), "Terminal constraint {idx} failed."); } } } diff --git a/triton-vm/src/table/instruction_table.rs b/triton-vm/src/table/instruction_table.rs index 5230d790d..3733102fd 100644 --- a/triton-vm/src/table/instruction_table.rs +++ b/triton-vm/src/table/instruction_table.rs @@ -1,37 +1,54 @@ -use itertools::Itertools; -use num_traits::{One, Zero}; +use ndarray::parallel::prelude::*; +use ndarray::s; +use ndarray::ArrayView1; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use ndarray::Axis; +use num_traits::One; +use num_traits::Zero; use strum::EnumCount; -use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter}; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::x_field_element::XFieldElement; use InstructionTableChallengeId::*; -use crate::cross_table_arguments::{CrossTableArg, EvalArg, PermArg}; -use crate::table::base_table::Extendable; +use crate::table::challenges::TableChallenges; +use crate::table::constraint_circuit::ConstraintCircuit; +use crate::table::constraint_circuit::ConstraintCircuitBuilder; +use crate::table::constraint_circuit::ConstraintCircuitMonad; +use crate::table::constraint_circuit::DualRowIndicator; +use crate::table::constraint_circuit::DualRowIndicator::*; use crate::table::constraint_circuit::SingleRowIndicator; -use crate::table::constraint_circuit::SingleRowIndicator::Row; - -use super::base_table::{InheritsFromTable, Table, TableLike}; -use super::challenges::TableChallenges; -use super::constraint_circuit::DualRowIndicator::{self, *}; -use super::constraint_circuit::{ - ConstraintCircuit, ConstraintCircuitBuilder, ConstraintCircuitMonad, -}; -use super::extension_table::{ExtensionTable, QuotientableExtensionTable}; -use super::table_column::InstructionBaseTableColumn::{self, *}; -use super::table_column::InstructionExtTableColumn::{self, *}; +use crate::table::constraint_circuit::SingleRowIndicator::*; +use crate::table::cross_table_argument::CrossTableArg; +use crate::table::cross_table_argument::EvalArg; +use crate::table::cross_table_argument::PermArg; +use crate::table::master_table::NUM_BASE_COLUMNS; +use crate::table::master_table::NUM_EXT_COLUMNS; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::ExtTableColumn; +use crate::table::table_column::InstructionBaseTableColumn; +use crate::table::table_column::InstructionBaseTableColumn::*; +use crate::table::table_column::InstructionExtTableColumn; +use crate::table::table_column::InstructionExtTableColumn::*; +use crate::table::table_column::MasterBaseTableColumn; +use crate::table::table_column::MasterExtTableColumn; +use crate::table::table_column::ProcessorBaseTableColumn; +use crate::vm::AlgebraicExecutionTrace; pub const INSTRUCTION_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 1; pub const INSTRUCTION_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 1; +pub const INSTRUCTION_TABLE_NUM_EXTENSION_CHALLENGES: usize = InstructionTableChallengeId::COUNT; pub const BASE_WIDTH: usize = InstructionBaseTableColumn::COUNT; -pub const FULL_WIDTH: usize = BASE_WIDTH + InstructionExtTableColumn::COUNT; +pub const EXT_WIDTH: usize = InstructionExtTableColumn::COUNT; +pub const FULL_WIDTH: usize = BASE_WIDTH + EXT_WIDTH; #[derive(Debug, Clone)] -pub struct InstructionTable { - inherited_table: Table, -} +pub struct InstructionTable {} #[derive(Debug, Copy, Clone, Display, EnumCountMacro, EnumIter, PartialEq, Hash, Eq)] pub enum InstructionTableChallengeId { @@ -90,81 +107,28 @@ impl TableChallenges for InstructionTableChallenges { } } -impl InheritsFromTable for InstructionTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} - #[derive(Debug, Clone)] -pub struct ExtInstructionTable { - pub(crate) inherited_table: Table, -} - -impl Default for ExtInstructionTable { - fn default() -> Self { - Self { - inherited_table: Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "EmptyExtInstructionTable".to_string(), - ), - } - } -} - -impl QuotientableExtensionTable for ExtInstructionTable {} - -impl InheritsFromTable for ExtInstructionTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} - -impl TableLike for InstructionTable {} - -impl Extendable for InstructionTable { - fn get_padding_rows(&self) -> (Option, Vec>) { - let zero = BFieldElement::zero(); - let one = BFieldElement::one(); - if let Some(row) = self.data().last() { - let mut padding_row = row.clone(); - // address keeps increasing - padding_row[usize::from(Address)] += one; - padding_row[usize::from(IsPadding)] = one; - (None, vec![padding_row]) - } else { - let mut padding_row = [zero; BASE_WIDTH]; - padding_row[usize::from(IsPadding)] = one; - (None, vec![padding_row.to_vec()]) - } - } -} - -impl TableLike for ExtInstructionTable {} +pub struct ExtInstructionTable {} impl ExtInstructionTable { - pub fn ext_initial_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_initial_constraints_as_circuits() -> Vec< + ConstraintCircuit< + InstructionTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let running_evaluation_initial = circuit_builder.x_constant(EvalArg::default_initial()); let running_product_initial = circuit_builder.x_constant(PermArg::default_initial()); - let ip = circuit_builder.input(Row(Address.into())); - let ci = circuit_builder.input(Row(CI.into())); - let nia = circuit_builder.input(Row(NIA.into())); - let running_evaluation = circuit_builder.input(Row(RunningEvaluation.into())); - let running_product = circuit_builder.input(Row(RunningProductPermArg.into())); + let ip = circuit_builder.input(BaseRow(Address.master_base_table_index())); + let ci = circuit_builder.input(BaseRow(CI.master_base_table_index())); + let nia = circuit_builder.input(BaseRow(NIA.master_base_table_index())); + let running_evaluation = + circuit_builder.input(ExtRow(RunningEvaluation.master_ext_table_index())); + let running_product = + circuit_builder.input(ExtRow(RunningProductPermArg.master_ext_table_index())); // Note that “ip = 0” is enforced by a separate constraint. This means we can drop summand // `ip_weight * ip` from the compressed row. @@ -188,34 +152,48 @@ impl ExtInstructionTable { ] } - pub fn ext_consistency_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_consistency_constraints_as_circuits() -> Vec< + ConstraintCircuit< + InstructionTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1u32.into()); - let is_padding = circuit_builder.input(Row(IsPadding.into())); + let is_padding = circuit_builder.input(BaseRow(IsPadding.master_base_table_index())); let is_padding_is_bit = is_padding.clone() * (is_padding - one); vec![is_padding_is_bit.consume()] } - pub fn ext_transition_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_transition_constraints_as_circuits() -> Vec< + ConstraintCircuit< + InstructionTableChallenges, + DualRowIndicator, + >, + > { let circuit_builder: ConstraintCircuitBuilder< InstructionTableChallenges, - DualRowIndicator, - > = ConstraintCircuitBuilder::new(2 * FULL_WIDTH); - let one: ConstraintCircuitMonad> = - circuit_builder.b_constant(1u32.into()); - let addr = circuit_builder.input(CurrentRow(Address.into())); - - let addr_next = circuit_builder.input(NextRow(Address.into())); - let current_instruction = circuit_builder.input(CurrentRow(CI.into())); - let current_instruction_next = circuit_builder.input(NextRow(CI.into())); - let next_instruction = circuit_builder.input(CurrentRow(NIA.into())); - let next_instruction_next = circuit_builder.input(NextRow(NIA.into())); - let is_padding = circuit_builder.input(CurrentRow(IsPadding.into())); - let is_padding_next = circuit_builder.input(NextRow(IsPadding.into())); + DualRowIndicator, + > = ConstraintCircuitBuilder::new(); + let one: ConstraintCircuitMonad< + InstructionTableChallenges, + DualRowIndicator, + > = circuit_builder.b_constant(1u32.into()); + let addr = circuit_builder.input(CurrentBaseRow(Address.master_base_table_index())); + + let addr_next = circuit_builder.input(NextBaseRow(Address.master_base_table_index())); + let current_instruction = + circuit_builder.input(CurrentBaseRow(CI.master_base_table_index())); + let current_instruction_next = + circuit_builder.input(NextBaseRow(CI.master_base_table_index())); + let next_instruction = circuit_builder.input(CurrentBaseRow(NIA.master_base_table_index())); + let next_instruction_next = + circuit_builder.input(NextBaseRow(NIA.master_base_table_index())); + let is_padding = circuit_builder.input(CurrentBaseRow(IsPadding.master_base_table_index())); + let is_padding_next = + circuit_builder.input(NextBaseRow(IsPadding.master_base_table_index())); // Base table constraints let address_increases_by_one = addr_next.clone() - (addr.clone() + one.clone()); @@ -228,11 +206,16 @@ impl ExtInstructionTable { // Extension table constraints let processor_perm_indeterminate = circuit_builder.challenge(ProcessorPermIndeterminate); - let running_evaluation = circuit_builder.input(CurrentRow(RunningEvaluation.into())); - let running_evaluation_next = circuit_builder.input(NextRow(RunningEvaluation.into())); + let running_evaluation = + circuit_builder.input(CurrentExtRow(RunningEvaluation.master_ext_table_index())); + let running_evaluation_next = + circuit_builder.input(NextExtRow(RunningEvaluation.master_ext_table_index())); - let running_product = circuit_builder.input(CurrentRow(RunningProductPermArg.into())); - let running_product_next = circuit_builder.input(NextRow(RunningProductPermArg.into())); + let running_product = circuit_builder.input(CurrentExtRow( + RunningProductPermArg.master_ext_table_index(), + )); + let running_product_next = + circuit_builder.input(NextExtRow(RunningProductPermArg.master_ext_table_index())); // The running evaluation is updated if and only if // 1. the address changes, and @@ -298,113 +281,126 @@ impl ExtInstructionTable { .to_vec() } - pub fn ext_terminal_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_terminal_constraints_as_circuits() -> Vec< + ConstraintCircuit< + InstructionTableChallenges, + SingleRowIndicator, + >, + > { vec![] } } impl InstructionTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } + pub fn fill_trace( + instruction_table: &mut ArrayViewMut2, + aet: &AlgebraicExecutionTrace, + program: &[BFieldElement], + ) { + // Pre-process the AET's processor trace to find the number of occurrences of each unique + // row when only looking at the IP, CI, and NIA columns. Unless the prover is cheating, + // this is equivalent to looking at only the instruction pointer (IP) column, because the + // program is static. + let program_len = program.len(); + let mut processor_trace_row_counts = vec![0; program_len]; + for row in aet.processor_matrix.rows() { + let ip = row[ProcessorBaseTableColumn::IP.base_table_index()].value() as usize; + assert!(ip < program_len, "IP out of bounds – forgot to \"halt\"?"); + processor_trace_row_counts[ip] += 1; + } + + let mut next_row_in_instruction_table: usize = 0; + for (address, &instruction) in program.iter().enumerate() { + // Use zero in the last row. + let &nia = program.get(address + 1).unwrap_or(&BFieldElement::zero()); + // Gets a “+1” to account for the row from the program table. + let number_of_rows_for_this_instruction = processor_trace_row_counts[address] + 1; + let last_row_for_this_instruction = + next_row_in_instruction_table + number_of_rows_for_this_instruction; + let mut instruction_sub_table = instruction_table.slice_mut(s![ + next_row_in_instruction_table..last_row_for_this_instruction, + .. + ]); + instruction_sub_table + .slice_mut(s![.., Address.base_table_index()]) + .fill(BFieldElement::new(address as u64)); + instruction_sub_table + .slice_mut(s![.., CI.base_table_index()]) + .fill(instruction); + instruction_sub_table + .slice_mut(s![.., NIA.base_table_index()]) + .fill(nia); + next_row_in_instruction_table = last_row_for_this_instruction; + } } - pub fn new_prover(matrix: Vec>) -> Self { - let inherited_table = Table::new( - BASE_WIDTH, - FULL_WIDTH, - matrix, - "InstructionTable".to_string(), - ); - Self { inherited_table } + pub fn pad_trace( + instruction_table: &mut ArrayViewMut2, + instruction_table_len: usize, + ) { + let mut last_row = instruction_table + .slice(s![instruction_table_len - 1, ..]) + .to_owned(); + last_row[Address.base_table_index()] = + last_row[Address.base_table_index()] + BFieldElement::one(); + last_row[IsPadding.base_table_index()] = BFieldElement::one(); + + let mut padding_section = instruction_table.slice_mut(s![instruction_table_len.., ..]); + padding_section + .axis_iter_mut(Axis(0)) + .into_par_iter() + .for_each(|padding_row| last_row.clone().move_into(padding_row)); } - pub fn extend(&self, challenges: &InstructionTableChallenges) -> ExtInstructionTable { - let mut extension_matrix: Vec> = Vec::with_capacity(self.data().len()); - let mut processor_table_running_product = PermArg::default_initial(); + pub fn extend( + base_table: ArrayView2, + mut ext_table: ArrayViewMut2, + challenges: &InstructionTableChallenges, + ) { + assert_eq!(BASE_WIDTH, base_table.ncols()); + assert_eq!(EXT_WIDTH, ext_table.ncols()); + assert_eq!(base_table.nrows(), ext_table.nrows()); let mut program_table_running_evaluation = EvalArg::default_initial(); - let mut previous_row: Option> = None; - - for row in self.data().iter() { - let mut extension_row = [0.into(); FULL_WIDTH]; - extension_row[..BASE_WIDTH] - .copy_from_slice(&row.iter().map(|elem| elem.lift()).collect_vec()); - - // Is the current row's address different from the previous row's address? - // Different: update running evaluation of Evaluation Argument with Program Table. - // Not different: update running product of Permutation Argument with Processor Table. - let mut is_duplicate_row = false; - if let Some(prow) = previous_row { - if prow[usize::from(Address)] == row[usize::from(Address)] { - is_duplicate_row = true; - debug_assert_eq!(prow[usize::from(CI)], row[usize::from(CI)]); - debug_assert_eq!(prow[usize::from(NIA)], row[usize::from(NIA)]); - } else { - debug_assert_eq!( - prow[usize::from(Address)] + BFieldElement::one(), - row[usize::from(Address)] - ); - } - } - - // Compress values of current row for Permutation Argument with Processor Table - let ip = row[usize::from(Address)].lift(); - let ci = row[usize::from(CI)].lift(); - let nia = row[usize::from(NIA)].lift(); - let compressed_row_for_permutation_argument = ip * challenges.ip_processor_weight - + ci * challenges.ci_processor_weight - + nia * challenges.nia_processor_weight; - - // Update running product if same row has been seen before and not padding row - if is_duplicate_row && row[usize::from(IsPadding)].is_zero() { - processor_table_running_product *= challenges.processor_perm_indeterminate - - compressed_row_for_permutation_argument; - } - extension_row[usize::from(RunningProductPermArg)] = processor_table_running_product; - - // Compress values of current row for Evaluation Argument with Program Table - let compressed_row_for_evaluation_argument = ip * challenges.address_weight - + ci * challenges.instruction_weight - + nia * challenges.next_instruction_weight; - - // Update running evaluation if same row has _not_ been seen before and not padding row - if !is_duplicate_row && row[usize::from(IsPadding)].is_zero() { + let mut processor_table_running_product = PermArg::default_initial(); + let mut previous_row: Option> = None; + + for row_idx in 0..base_table.nrows() { + let current_row = base_table.row(row_idx); + let ip = current_row[Address.base_table_index()]; + let ci = current_row[CI.base_table_index()]; + let nia = current_row[NIA.base_table_index()]; + + // Is the current row a padding row? + // Padding Row: don't updated anything. + // Not padding row: Is previous row's address different from current row's address? + // Different: update running evaluation of Evaluation Argument with Program Table. + // Not different: update running product of Permutation Argument with Processor Table. + let is_duplicate_row = if let Some(prev_row) = previous_row { + prev_row[Address.base_table_index()] == current_row[Address.base_table_index()] + } else { + false + }; + if !is_duplicate_row && current_row[IsPadding.base_table_index()].is_zero() { + let compressed_row_for_evaluation_argument = ip * challenges.address_weight + + ci * challenges.instruction_weight + + nia * challenges.next_instruction_weight; program_table_running_evaluation = program_table_running_evaluation * challenges.program_eval_indeterminate + compressed_row_for_evaluation_argument; } - extension_row[usize::from(RunningEvaluation)] = program_table_running_evaluation; - - previous_row = Some(row.clone()); - extension_matrix.push(extension_row.to_vec()); - } - - assert_eq!(self.data().len(), extension_matrix.len()); - let inherited_table = self.new_from_lifted_matrix(extension_matrix); - ExtInstructionTable { inherited_table } - } + if is_duplicate_row && current_row[IsPadding.base_table_index()].is_zero() { + let compressed_row_for_permutation_argument = ip * challenges.ip_processor_weight + + ci * challenges.ci_processor_weight + + nia * challenges.nia_processor_weight; + processor_table_running_product *= challenges.processor_perm_indeterminate + - compressed_row_for_permutation_argument; + } - pub fn for_verifier() -> ExtInstructionTable { - let inherited_table = Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "ExtInstructionTable".to_string(), - ); - let base_table = Self { inherited_table }; - let empty_matrix: Vec> = vec![]; - let extension_table = base_table.new_from_lifted_matrix(empty_matrix); - - ExtInstructionTable { - inherited_table: extension_table, + let mut extension_row = ext_table.row_mut(row_idx); + extension_row[RunningEvaluation.ext_table_index()] = program_table_running_evaluation; + extension_row[RunningProductPermArg.ext_table_index()] = + processor_table_running_product; + previous_row = Some(current_row); } } } - -impl ExtInstructionTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } - } -} - -impl ExtensionTable for ExtInstructionTable {} diff --git a/triton-vm/src/table/jump_stack_table.rs b/triton-vm/src/table/jump_stack_table.rs index 50c9b06fc..44d948888 100644 --- a/triton-vm/src/table/jump_stack_table.rs +++ b/triton-vm/src/table/jump_stack_table.rs @@ -1,144 +1,82 @@ -use itertools::Itertools; +use std::cmp::Ordering; + +use ndarray::parallel::prelude::*; +use ndarray::s; +use ndarray::Array1; +use ndarray::ArrayView1; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use ndarray::Axis; use num_traits::One; +use num_traits::Zero; use strum::EnumCount; -use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter}; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::traits::Inverse; use twenty_first::shared_math::x_field_element::XFieldElement; +use std::fmt::Display; +use std::fmt::Formatter; use JumpStackTableChallengeId::*; -use crate::cross_table_arguments::{CrossTableArg, PermArg}; use crate::instruction::Instruction; -use crate::table::base_table::Extendable; +use crate::table::challenges::TableChallenges; +use crate::table::constraint_circuit::ConstraintCircuit; +use crate::table::constraint_circuit::ConstraintCircuitBuilder; +use crate::table::constraint_circuit::DualRowIndicator; +use crate::table::constraint_circuit::DualRowIndicator::*; use crate::table::constraint_circuit::SingleRowIndicator; -use crate::table::constraint_circuit::SingleRowIndicator::Row; -use crate::table::table_column::JumpStackBaseTableColumn::{self, *}; -use crate::table::table_column::JumpStackExtTableColumn::{self, *}; - -use super::base_table::{InheritsFromTable, Table, TableLike}; -use super::challenges::TableChallenges; -use super::constraint_circuit::DualRowIndicator::*; -use super::constraint_circuit::{ConstraintCircuit, ConstraintCircuitBuilder, DualRowIndicator}; -use super::extension_table::{ExtensionTable, QuotientableExtensionTable}; +use crate::table::constraint_circuit::SingleRowIndicator::*; +use crate::table::cross_table_argument::CrossTableArg; +use crate::table::cross_table_argument::PermArg; +use crate::table::master_table::NUM_BASE_COLUMNS; +use crate::table::master_table::NUM_EXT_COLUMNS; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::ExtTableColumn; +use crate::table::table_column::JumpStackBaseTableColumn; +use crate::table::table_column::JumpStackBaseTableColumn::*; +use crate::table::table_column::JumpStackExtTableColumn; +use crate::table::table_column::JumpStackExtTableColumn::*; +use crate::table::table_column::MasterBaseTableColumn; +use crate::table::table_column::MasterExtTableColumn; +use crate::table::table_column::ProcessorBaseTableColumn; +use crate::vm::AlgebraicExecutionTrace; pub const JUMP_STACK_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 1; pub const JUMP_STACK_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 0; - -/// This is 5 because it combines: clk, ci, jsp, jso, jsd, -pub const JUMP_STACK_TABLE_NUM_EXTENSION_CHALLENGES: usize = 5; +pub const JUMP_STACK_TABLE_NUM_EXTENSION_CHALLENGES: usize = JumpStackTableChallengeId::COUNT; pub const BASE_WIDTH: usize = JumpStackBaseTableColumn::COUNT; -pub const FULL_WIDTH: usize = BASE_WIDTH + JumpStackExtTableColumn::COUNT; +pub const EXT_WIDTH: usize = JumpStackExtTableColumn::COUNT; +pub const FULL_WIDTH: usize = BASE_WIDTH + EXT_WIDTH; #[derive(Debug, Clone)] -pub struct JumpStackTable { - inherited_table: Table, -} - -impl InheritsFromTable for JumpStackTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} +pub struct JumpStackTable {} #[derive(Debug, Clone)] -pub struct ExtJumpStackTable { - pub(crate) inherited_table: Table, -} - -impl Default for ExtJumpStackTable { - fn default() -> Self { - Self { - inherited_table: Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "EmptyExtJumpStackTable".to_string(), - ), - } - } -} - -impl QuotientableExtensionTable for ExtJumpStackTable {} - -impl InheritsFromTable for ExtJumpStackTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} - -impl TableLike for JumpStackTable {} - -impl Extendable for JumpStackTable { - fn get_padding_rows(&self) -> (Option, Vec>) { - panic!( - "This function should not be called: the Jump Stack Table implements `.pad` directly." - ) - } - - fn pad(&mut self, padded_height: usize) { - let max_clock = self.data().len() as u64 - 1; - let num_padding_rows = padded_height - self.data().len(); - - let template_index = self - .data() - .iter() - .enumerate() - .find(|(_, row)| row[usize::from(CLK)].value() == max_clock) - .map(|(idx, _)| idx) - .expect("Jump Stack Table must contain row with clock cycle equal to max cycle."); - let insertion_index = template_index + 1; - - let padding_template = &mut self.mut_data()[template_index]; - padding_template[usize::from(InverseOfClkDiffMinusOne)] = 0_u64.into(); - - let mut padding_rows = vec![]; - while padding_rows.len() < num_padding_rows { - let mut padding_row = padding_template.clone(); - padding_row[usize::from(CLK)] += (padding_rows.len() as u32 + 1).into(); - padding_rows.push(padding_row) - } - - if let Some(row) = padding_rows.last_mut() { - if let Some(next_row) = self.data().get(insertion_index) { - let clk_diff = next_row[usize::from(CLK)] - row[usize::from(CLK)]; - row[usize::from(InverseOfClkDiffMinusOne)] = - (clk_diff - BFieldElement::one()).inverse_or_zero(); - } - } - - let old_tail_length = self.data().len() - insertion_index; - self.mut_data().append(&mut padding_rows); - self.mut_data()[insertion_index..].rotate_left(old_tail_length); - - assert_eq!(padded_height, self.data().len()); - } -} - -impl TableLike for ExtJumpStackTable {} +pub struct ExtJumpStackTable {} impl ExtJumpStackTable { - pub fn ext_initial_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_initial_constraints_as_circuits() -> Vec< + ConstraintCircuit< + JumpStackTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1_u32.into()); - let clk = circuit_builder.input(Row(CLK.into())); - let jsp = circuit_builder.input(Row(JSP.into())); - let jso = circuit_builder.input(Row(JSO.into())); - let jsd = circuit_builder.input(Row(JSD.into())); - let ci = circuit_builder.input(Row(CI.into())); - let rppa = circuit_builder.input(Row(RunningProductPermArg.into())); - let rpcjd = circuit_builder.input(Row(AllClockJumpDifferencesPermArg.into())); + let clk = circuit_builder.input(BaseRow(CLK.master_base_table_index())); + let jsp = circuit_builder.input(BaseRow(JSP.master_base_table_index())); + let jso = circuit_builder.input(BaseRow(JSO.master_base_table_index())); + let jsd = circuit_builder.input(BaseRow(JSD.master_base_table_index())); + let ci = circuit_builder.input(BaseRow(CI.master_base_table_index())); + let rppa = circuit_builder.input(ExtRow(RunningProductPermArg.master_ext_table_index())); + let rpcjd = circuit_builder.input(ExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); let processor_perm_indeterminate = circuit_builder.challenge(ProcessorPermRowIndeterminate); // note: `clk`, `jsp`, `jso`, and `jsd` are all constrained to be 0 and can thus be omitted. @@ -159,37 +97,56 @@ impl ExtJumpStackTable { .to_vec() } - pub fn ext_consistency_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_consistency_constraints_as_circuits() -> Vec< + ConstraintCircuit< + JumpStackTableChallenges, + SingleRowIndicator, + >, + > { // no further constraints vec![] } - pub fn ext_transition_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(2 * FULL_WIDTH); + pub fn ext_transition_constraints_as_circuits() -> Vec< + ConstraintCircuit< + JumpStackTableChallenges, + DualRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1u32.into()); let call_opcode = circuit_builder.b_constant(Instruction::Call(Default::default()).opcode_b()); let return_opcode = circuit_builder.b_constant(Instruction::Return.opcode_b()); - let clk = circuit_builder.input(CurrentRow(CLK.into())); - let ci = circuit_builder.input(CurrentRow(CI.into())); - let jsp = circuit_builder.input(CurrentRow(JSP.into())); - let jso = circuit_builder.input(CurrentRow(JSO.into())); - let jsd = circuit_builder.input(CurrentRow(JSD.into())); - let clk_di = circuit_builder.input(CurrentRow(InverseOfClkDiffMinusOne.into())); - let rppa = circuit_builder.input(CurrentRow(RunningProductPermArg.into())); - let rpcjd = circuit_builder.input(CurrentRow(AllClockJumpDifferencesPermArg.into())); - - let clk_next = circuit_builder.input(NextRow(CLK.into())); - let ci_next = circuit_builder.input(NextRow(CI.into())); - let jsp_next = circuit_builder.input(NextRow(JSP.into())); - let jso_next = circuit_builder.input(NextRow(JSO.into())); - let jsd_next = circuit_builder.input(NextRow(JSD.into())); - let clk_di_next = circuit_builder.input(NextRow(InverseOfClkDiffMinusOne.into())); - let rppa_next = circuit_builder.input(NextRow(RunningProductPermArg.into())); - let rpcjd_next = circuit_builder.input(NextRow(AllClockJumpDifferencesPermArg.into())); + let clk = circuit_builder.input(CurrentBaseRow(CLK.master_base_table_index())); + let ci = circuit_builder.input(CurrentBaseRow(CI.master_base_table_index())); + let jsp = circuit_builder.input(CurrentBaseRow(JSP.master_base_table_index())); + let jso = circuit_builder.input(CurrentBaseRow(JSO.master_base_table_index())); + let jsd = circuit_builder.input(CurrentBaseRow(JSD.master_base_table_index())); + let clk_di = circuit_builder.input(CurrentBaseRow( + InverseOfClkDiffMinusOne.master_base_table_index(), + )); + let rppa = circuit_builder.input(CurrentExtRow( + RunningProductPermArg.master_ext_table_index(), + )); + let rpcjd = circuit_builder.input(CurrentExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); + + let clk_next = circuit_builder.input(NextBaseRow(CLK.master_base_table_index())); + let ci_next = circuit_builder.input(NextBaseRow(CI.master_base_table_index())); + let jsp_next = circuit_builder.input(NextBaseRow(JSP.master_base_table_index())); + let jso_next = circuit_builder.input(NextBaseRow(JSO.master_base_table_index())); + let jsd_next = circuit_builder.input(NextBaseRow(JSD.master_base_table_index())); + let clk_di_next = circuit_builder.input(NextBaseRow( + InverseOfClkDiffMinusOne.master_base_table_index(), + )); + let rppa_next = + circuit_builder.input(NextExtRow(RunningProductPermArg.master_ext_table_index())); + let rpcjd_next = circuit_builder.input(NextExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); // 1. The jump stack pointer jsp increases by 1 // or the jump stack pointer jsp does not change @@ -281,106 +238,210 @@ impl ExtJumpStackTable { .to_vec() } - pub fn ext_terminal_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_terminal_constraints_as_circuits() -> Vec< + ConstraintCircuit< + JumpStackTableChallenges, + SingleRowIndicator, + >, + > { vec![] } } impl JumpStackTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } + /// Fills the trace table in-place and returns all clock jump differences greater than 1. + pub fn fill_trace( + jump_stack_table: &mut ArrayViewMut2, + aet: &AlgebraicExecutionTrace, + ) -> Vec { + // Store the registers relevant for the Jump Stack Table, i.e., CLK, CI, JSP, JSO, JSD, + // with JSP as the key. Preserves, thus allows reusing, the order of the processor's + // rows, which are sorted by CLK. + let mut pre_processed_jump_stack_table: Vec> = vec![]; + for processor_row in aet.processor_matrix.rows() { + let clk = processor_row[ProcessorBaseTableColumn::CLK.base_table_index()]; + let ci = processor_row[ProcessorBaseTableColumn::CI.base_table_index()]; + let jsp = processor_row[ProcessorBaseTableColumn::JSP.base_table_index()]; + let jso = processor_row[ProcessorBaseTableColumn::JSO.base_table_index()]; + let jsd = processor_row[ProcessorBaseTableColumn::JSD.base_table_index()]; + // The (honest) prover can only grow the Jump Stack's size by at most 1 per execution + // step. Hence, the following (a) works, and (b) sorts. + let jsp_val = jsp.value() as usize; + let jump_stack_row = (clk, ci, jso, jsd); + match jsp_val.cmp(&pre_processed_jump_stack_table.len()) { + Ordering::Less => pre_processed_jump_stack_table[jsp_val].push(jump_stack_row), + Ordering::Equal => pre_processed_jump_stack_table.push(vec![jump_stack_row]), + Ordering::Greater => panic!("JSP must increase by at most 1 per execution step."), + } + } + + // Move the rows into the Jump Stack Table, sorted by JSP first, CLK second. + let mut jump_stack_table_row = 0; + for (jsp_val, rows_with_this_jsp) in pre_processed_jump_stack_table.into_iter().enumerate() + { + let jsp = BFieldElement::new(jsp_val as u64); + for (clk, ci, jso, jsd) in rows_with_this_jsp { + jump_stack_table[(jump_stack_table_row, CLK.base_table_index())] = clk; + jump_stack_table[(jump_stack_table_row, CI.base_table_index())] = ci; + jump_stack_table[(jump_stack_table_row, JSP.base_table_index())] = jsp; + jump_stack_table[(jump_stack_table_row, JSO.base_table_index())] = jso; + jump_stack_table[(jump_stack_table_row, JSD.base_table_index())] = jsd; + jump_stack_table_row += 1; + } + } + assert_eq!(aet.processor_matrix.nrows(), jump_stack_table_row); + + // Set inverse of (clock difference - 1). Also, collect all clock jump differences + // greater than 1. + // The Jump Stack Table and the Processor Table have the same length. + let mut clock_jump_differences_greater_than_1 = vec![]; + for row_idx in 0..aet.processor_matrix.nrows() - 1 { + let (mut curr_row, next_row) = + jump_stack_table.multi_slice_mut((s![row_idx, ..], s![row_idx + 1, ..])); + let clk_diff = next_row[CLK.base_table_index()] - curr_row[CLK.base_table_index()]; + let clk_diff_minus_1 = clk_diff - BFieldElement::one(); + let clk_diff_minus_1_inverse = clk_diff_minus_1.inverse_or_zero(); + curr_row[InverseOfClkDiffMinusOne.base_table_index()] = clk_diff_minus_1_inverse; + + if curr_row[JSP.base_table_index()] == next_row[JSP.base_table_index()] + && clk_diff.value() > 1 + { + clock_jump_differences_greater_than_1.push(clk_diff); + } + } + clock_jump_differences_greater_than_1 } - pub fn new_prover(matrix: Vec>) -> Self { - let inherited_table = - Table::new(BASE_WIDTH, FULL_WIDTH, matrix, "JumpStackTable".to_string()); - Self { inherited_table } + pub fn pad_trace( + jump_stack_table: &mut ArrayViewMut2, + processor_table_len: usize, + ) { + assert!( + processor_table_len > 0, + "Processor Table must have at least 1 row." + ); + + // Set up indices for relevant sections of the table. + let padded_height = jump_stack_table.nrows(); + let num_padding_rows = padded_height - processor_table_len; + let max_clk_before_padding = processor_table_len - 1; + let max_clk_before_padding_row_idx = jump_stack_table + .rows() + .into_iter() + .enumerate() + .find(|(_, row)| row[CLK.base_table_index()].value() as usize == max_clk_before_padding) + .map(|(idx, _)| idx) + .expect("Jump Stack Table must contain row with clock cycle equal to max cycle."); + let rows_to_move_source_section_start = max_clk_before_padding_row_idx + 1; + let rows_to_move_source_section_end = processor_table_len; + let num_rows_to_move = rows_to_move_source_section_end - rows_to_move_source_section_start; + let rows_to_move_dest_section_start = rows_to_move_source_section_start + num_padding_rows; + let rows_to_move_dest_section_end = rows_to_move_dest_section_start + num_rows_to_move; + let padding_section_start = rows_to_move_source_section_start; + let padding_section_end = padding_section_start + num_padding_rows; + assert_eq!(padded_height, rows_to_move_dest_section_end); + + // Move all rows below the row with highest CLK to the end of the table – if they exist. + if num_rows_to_move > 0 { + let rows_to_move_source_range = + rows_to_move_source_section_start..rows_to_move_source_section_end; + let rows_to_move_dest_range = + rows_to_move_dest_section_start..rows_to_move_dest_section_end; + let rows_to_move = jump_stack_table + .slice(s![rows_to_move_source_range, ..]) + .to_owned(); + rows_to_move + .move_into(&mut jump_stack_table.slice_mut(s![rows_to_move_dest_range, ..])); + } + + // Fill the created gap with padding rows, i.e., with (adjusted) copies of the last row + // before the gap. This is the padding section. + let mut padding_row_template = jump_stack_table + .row(max_clk_before_padding_row_idx) + .to_owned(); + padding_row_template[InverseOfClkDiffMinusOne.base_table_index()] = BFieldElement::zero(); + let mut padding_section = + jump_stack_table.slice_mut(s![padding_section_start..padding_section_end, ..]); + padding_section + .axis_iter_mut(Axis(0)) + .into_par_iter() + .for_each(|padding_row| padding_row_template.clone().move_into(padding_row)); + + // CLK keeps increasing by 1 also in the padding section. + let new_clk_values = Array1::from_iter( + (processor_table_len..padded_height).map(|clk| BFieldElement::new(clk as u64)), + ); + new_clk_values.move_into(padding_section.slice_mut(s![.., CLK.base_table_index()])); + + // InverseOfClkDiffMinusOne must be consistent at the padding section's boundaries. + jump_stack_table[[ + max_clk_before_padding_row_idx, + InverseOfClkDiffMinusOne.base_table_index(), + ]] = BFieldElement::zero(); + if num_rows_to_move > 0 && rows_to_move_dest_section_start > 0 { + let max_clk_after_padding = padded_height - 1; + let clk_diff_minus_one_at_padding_section_lower_boundary = jump_stack_table + [[rows_to_move_dest_section_start, CLK.base_table_index()]] + - BFieldElement::new(max_clk_after_padding as u64) + - BFieldElement::one(); + let last_row_in_padding_section_idx = rows_to_move_dest_section_start - 1; + jump_stack_table[[ + last_row_in_padding_section_idx, + InverseOfClkDiffMinusOne.base_table_index(), + ]] = clk_diff_minus_one_at_padding_section_lower_boundary.inverse_or_zero(); + } } - pub fn extend(&self, challenges: &JumpStackTableChallenges) -> ExtJumpStackTable { - let mut extension_matrix: Vec> = Vec::with_capacity(self.data().len()); + pub fn extend( + base_table: ArrayView2, + mut ext_table: ArrayViewMut2, + challenges: &JumpStackTableChallenges, + ) { + assert_eq!(BASE_WIDTH, base_table.ncols()); + assert_eq!(EXT_WIDTH, ext_table.ncols()); + assert_eq!(base_table.nrows(), ext_table.nrows()); let mut running_product = PermArg::default_initial(); let mut all_clock_jump_differences_running_product = PermArg::default_initial(); - - let mut previous_row: Option> = None; - for row in self.data().iter() { - let mut extension_row = [0.into(); FULL_WIDTH]; - extension_row[..BASE_WIDTH] - .copy_from_slice(&row.iter().map(|elem| elem.lift()).collect_vec()); - - let (clk, ci, jsp, jso, jsd) = ( - extension_row[usize::from(CLK)], - extension_row[usize::from(CI)], - extension_row[usize::from(JSP)], - extension_row[usize::from(JSO)], - extension_row[usize::from(JSD)], - ); - - let (clk_w, ci_w, jsp_w, jso_w, jsd_w) = ( - challenges.clk_weight, - challenges.ci_weight, - challenges.jsp_weight, - challenges.jso_weight, - challenges.jsd_weight, - ); - - // compress multiple values within one row so they become one value - let compressed_row_for_permutation_argument = - clk * clk_w + ci * ci_w + jsp * jsp_w + jso * jso_w + jsd * jsd_w; - - // compute the running *product* of the compressed column (for permutation argument) + let mut previous_row: Option> = None; + + for row_idx in 0..base_table.nrows() { + let current_row = base_table.row(row_idx); + let clk = current_row[CLK.base_table_index()]; + let ci = current_row[CI.base_table_index()]; + let jsp = current_row[JSP.base_table_index()]; + let jso = current_row[JSO.base_table_index()]; + let jsd = current_row[JSD.base_table_index()]; + + let compressed_row_for_permutation_argument = clk * challenges.clk_weight + + ci * challenges.ci_weight + + jsp * challenges.jsp_weight + + jso * challenges.jso_weight + + jsd * challenges.jsd_weight; running_product *= challenges.processor_perm_indeterminate - compressed_row_for_permutation_argument; - extension_row[usize::from(RunningProductPermArg)] = running_product; // clock jump difference - if let Some(prow) = previous_row { - if prow[usize::from(JSP)] == row[usize::from(JSP)] { + if let Some(prev_row) = previous_row { + if prev_row[JSP.base_table_index()] == current_row[JSP.base_table_index()] { let clock_jump_difference = - (row[usize::from(CLK)] - prow[usize::from(CLK)]).lift(); - if clock_jump_difference != XFieldElement::one() { + current_row[CLK.base_table_index()] - prev_row[CLK.base_table_index()]; + if !clock_jump_difference.is_one() { all_clock_jump_differences_running_product *= challenges .all_clock_jump_differences_multi_perm_indeterminate - clock_jump_difference; } } } - extension_row[usize::from(AllClockJumpDifferencesPermArg)] = - all_clock_jump_differences_running_product; - previous_row = Some(row.clone()); - extension_matrix.push(extension_row.to_vec()); - } - - assert_eq!(self.data().len(), extension_matrix.len()); - let inherited_table = self.new_from_lifted_matrix(extension_matrix); - ExtJumpStackTable { inherited_table } - } - - pub fn for_verifier() -> ExtJumpStackTable { - let inherited_table = Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "ExtJumpStackTable".to_string(), - ); - let base_table = Self { inherited_table }; - let empty_matrix: Vec> = vec![]; - let extension_table = base_table.new_from_lifted_matrix(empty_matrix); - - ExtJumpStackTable { - inherited_table: extension_table, + let mut extension_row = ext_table.row_mut(row_idx); + extension_row[RunningProductPermArg.ext_table_index()] = running_product; + extension_row[AllClockJumpDifferencesPermArg.ext_table_index()] = + all_clock_jump_differences_running_product; + previous_row = Some(current_row); } } } -impl ExtJumpStackTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } - } -} - #[derive(Debug, Copy, Clone, Display, EnumCountMacro, EnumIter, PartialEq, Eq, Hash)] pub enum JumpStackTableChallengeId { ProcessorPermRowIndeterminate, @@ -401,7 +462,7 @@ impl From for usize { #[derive(Debug, Clone)] pub struct JumpStackTableChallenges { /// The weight that combines two consecutive rows in the - /// permutation/evaluation column of the op-stack table. + /// permutation/evaluation column of the jump-stack table. pub processor_perm_indeterminate: XFieldElement, /// Weights for condensing part of a row into a single column. (Related to processor table.) @@ -434,4 +495,22 @@ impl TableChallenges for JumpStackTableChallenges { } } -impl ExtensionTable for ExtJumpStackTable {} +pub struct JumpStackMatrixRow { + pub row: [BFieldElement; BASE_WIDTH], +} + +impl Display for JumpStackMatrixRow { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let width = 5; + write!( + f, + "│ CLK: {:>width$} │ CI: {:>width$} │ \ + JSP: {:>width$} │ JSO: {:>width$} │ JSD: {:>width$} │", + self.row[CLK.base_table_index()].value(), + self.row[CI.base_table_index()].value(), + self.row[JSP.base_table_index()].value(), + self.row[JSO.base_table_index()].value(), + self.row[JSD.base_table_index()].value(), + ) + } +} diff --git a/triton-vm/src/table/master_table.rs b/triton-vm/src/table/master_table.rs new file mode 100644 index 000000000..36317ef0e --- /dev/null +++ b/triton-vm/src/table/master_table.rs @@ -0,0 +1,1708 @@ +use std::cmp::max; +use std::ops::MulAssign; + +use itertools::Itertools; +use ndarray::parallel::prelude::*; +use ndarray::prelude::*; +use ndarray::s; +use ndarray::Array2; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use ndarray::Zip; +use num_traits::One; +use rand::distributions::Standard; +use rand::prelude::Distribution; +use rand::random; +use strum::EnumCount; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; +use triton_profiler::prof_start; +use triton_profiler::prof_stop; +use triton_profiler::triton_profiler::TritonProfiler; +use twenty_first::shared_math::b_field_element::BFieldElement; +use twenty_first::shared_math::mpolynomial::Degree; +use twenty_first::shared_math::other::is_power_of_two; +use twenty_first::shared_math::other::roundup_npo2; +use twenty_first::shared_math::traits::FiniteField; +use twenty_first::shared_math::traits::Inverse; +use twenty_first::shared_math::traits::ModPowU32; +use twenty_first::shared_math::traits::PrimitiveRootOfUnity; +use twenty_first::shared_math::x_field_element::XFieldElement; +use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; +use twenty_first::util_types::merkle_tree::CpuParallel; +use twenty_first::util_types::merkle_tree::MerkleTree; +use twenty_first::util_types::merkle_tree_maker::MerkleTreeMaker; + +use crate::arithmetic_domain::ArithmeticDomain; +use crate::stark::StarkHasher; +use crate::table::challenges::AllChallenges; +use crate::table::cross_table_argument::GrandCrossTableArg; +use crate::table::extension_table::DegreeWithOrigin; +use crate::table::extension_table::Evaluable; +use crate::table::extension_table::Quotientable; +use crate::table::hash_table::ExtHashTable; +use crate::table::hash_table::HashTable; +use crate::table::instruction_table::ExtInstructionTable; +use crate::table::instruction_table::InstructionTable; +use crate::table::jump_stack_table::ExtJumpStackTable; +use crate::table::jump_stack_table::JumpStackTable; +use crate::table::op_stack_table::ExtOpStackTable; +use crate::table::op_stack_table::OpStackTable; +use crate::table::processor_table::ExtProcessorTable; +use crate::table::processor_table::ProcessorTable; +use crate::table::program_table::ExtProgramTable; +use crate::table::program_table::ProgramTable; +use crate::table::ram_table::ExtRamTable; +use crate::table::ram_table::RamTable; +use crate::table::*; +use crate::vm::AlgebraicExecutionTrace; + +pub const NUM_TABLES: usize = TableId::COUNT; + +pub const NUM_BASE_COLUMNS: usize = program_table::BASE_WIDTH + + instruction_table::BASE_WIDTH + + processor_table::BASE_WIDTH + + op_stack_table::BASE_WIDTH + + ram_table::BASE_WIDTH + + jump_stack_table::BASE_WIDTH + + hash_table::BASE_WIDTH; +pub const NUM_EXT_COLUMNS: usize = program_table::EXT_WIDTH + + instruction_table::EXT_WIDTH + + processor_table::EXT_WIDTH + + op_stack_table::EXT_WIDTH + + ram_table::EXT_WIDTH + + jump_stack_table::EXT_WIDTH + + hash_table::EXT_WIDTH; +pub const NUM_COLUMNS: usize = NUM_BASE_COLUMNS + NUM_EXT_COLUMNS; + +pub const PROGRAM_TABLE_START: usize = 0; +pub const PROGRAM_TABLE_END: usize = PROGRAM_TABLE_START + program_table::BASE_WIDTH; +pub const INSTRUCTION_TABLE_START: usize = PROGRAM_TABLE_END; +pub const INSTRUCTION_TABLE_END: usize = INSTRUCTION_TABLE_START + instruction_table::BASE_WIDTH; +pub const PROCESSOR_TABLE_START: usize = INSTRUCTION_TABLE_END; +pub const PROCESSOR_TABLE_END: usize = PROCESSOR_TABLE_START + processor_table::BASE_WIDTH; +pub const OP_STACK_TABLE_START: usize = PROCESSOR_TABLE_END; +pub const OP_STACK_TABLE_END: usize = OP_STACK_TABLE_START + op_stack_table::BASE_WIDTH; +pub const RAM_TABLE_START: usize = OP_STACK_TABLE_END; +pub const RAM_TABLE_END: usize = RAM_TABLE_START + ram_table::BASE_WIDTH; +pub const JUMP_STACK_TABLE_START: usize = RAM_TABLE_END; +pub const JUMP_STACK_TABLE_END: usize = JUMP_STACK_TABLE_START + jump_stack_table::BASE_WIDTH; +pub const HASH_TABLE_START: usize = JUMP_STACK_TABLE_END; +pub const HASH_TABLE_END: usize = HASH_TABLE_START + hash_table::BASE_WIDTH; + +pub const EXT_PROGRAM_TABLE_START: usize = 0; +pub const EXT_PROGRAM_TABLE_END: usize = EXT_PROGRAM_TABLE_START + program_table::EXT_WIDTH; +pub const EXT_INSTRUCTION_TABLE_START: usize = EXT_PROGRAM_TABLE_END; +pub const EXT_INSTRUCTION_TABLE_END: usize = + EXT_INSTRUCTION_TABLE_START + instruction_table::EXT_WIDTH; +pub const EXT_PROCESSOR_TABLE_START: usize = EXT_INSTRUCTION_TABLE_END; +pub const EXT_PROCESSOR_TABLE_END: usize = EXT_PROCESSOR_TABLE_START + processor_table::EXT_WIDTH; +pub const EXT_OP_STACK_TABLE_START: usize = EXT_PROCESSOR_TABLE_END; +pub const EXT_OP_STACK_TABLE_END: usize = EXT_OP_STACK_TABLE_START + op_stack_table::EXT_WIDTH; +pub const EXT_RAM_TABLE_START: usize = EXT_OP_STACK_TABLE_END; +pub const EXT_RAM_TABLE_END: usize = EXT_RAM_TABLE_START + ram_table::EXT_WIDTH; +pub const EXT_JUMP_STACK_TABLE_START: usize = EXT_RAM_TABLE_END; +pub const EXT_JUMP_STACK_TABLE_END: usize = + EXT_JUMP_STACK_TABLE_START + jump_stack_table::EXT_WIDTH; +pub const EXT_HASH_TABLE_START: usize = EXT_JUMP_STACK_TABLE_END; +pub const EXT_HASH_TABLE_END: usize = EXT_HASH_TABLE_START + hash_table::EXT_WIDTH; + +/// A `TableId` uniquely determines one of Triton VM's tables. +#[derive(Debug, Copy, Clone, Display, EnumCountMacro, EnumIter, PartialEq, Eq, Hash)] +pub enum TableId { + ProgramTable, + InstructionTable, + ProcessorTable, + OpStackTable, + RamTable, + JumpStackTable, + HashTable, +} + +/// A Master Table is, in some sense, a top-level table of Triton VM. It contains all the data +/// but little logic beyond bookkeeping and presenting the data in a useful way. Conversely, the +/// individual tables contain no data but all of the respective logic. Master Tables are +/// responsible for managing the individual tables and for presenting the right data to the right +/// tables, serving as a clean interface between the VM and the individual tables. +/// +/// As a mental model, it is perfectly fine to think of the data for the individual tables as +/// completely separate from each other. Only the cross-table argument links all tables together. +/// +/// Conceptually, there are three Master Tables: the Master Base Table, the Master Extension +/// Table, and the Master Quotient Table. The lifecycle of the Master Tables is as follows: +/// 1. The Master Base Table is instantiated and filled using the Algebraic Execution Trace. +/// This is the first time a Master Base Table is instantiated. It is in column-major form. +/// 2. The Master Base Table is padded using logic from the individual tables. +/// 3. The still-empty entries in the Master Base Table are filled with random elements. This +/// step is also known as “trace randomization.” +/// 4. Each column of the Master Base Table is low-degree extended. The result is the Master Base +/// Table over the FRI domain. This is the second and last time a Master Base Table is +/// instantiated. It is in row-major form. +/// 5. The Master Base Table and the Master Base Table over the FRI domain are used to derive the +/// Master Extension Table using logic from the individual tables. This is the first time a +/// Master Extension Table is instantiated. It is in column-major form. +/// 6. The Master Extension Table is trace-randomized. +/// 7. Each column of the Master Extension Table is low-degree extended. The result is the Master +/// Extension Table over the FRI domain. This is the second and last time a Master Extension +/// Table is instantiated. It is in row-major form. +/// 8. Using the Master Base Table over the FRI domain and the Master Extension Table over the +/// FRI domain, the Quotient Master Table is derived using the AIR. Each individual table +/// defines that part of the AIR that is relevant to it. +/// +/// The following points are of note: +/// - The Master Extension Table's rightmost columns are the randomizer codewords. These are +/// necessary for zero-knowledge. +/// - The terminal quotient of the cross-table argument, which links the individual tables together, +/// is also stored in the Master Quotient Table. Even though the cross-table argument is not +/// a table, it does define part of the AIR. Hence, the cross-table argument does not contribute +/// to padding or extending the Master Tables, but is incorporated when deriving the Master +/// Qoutient Table. +/// - For better performance, it is possible to derive the Master Quotient Table (step 8) from the +/// Master Base Table and Master Extension Table over a smaller domain than the FRI domain – +/// the “quotient domain.” The quotient domain is a subset of the FRI domain. This +/// performance improvement changes nothing conceptually. +pub trait MasterTable +where + FF: FiniteField + MulAssign, + Standard: Distribution, +{ + fn randomized_padded_trace_len(&self) -> usize; + fn rand_trace_to_padded_trace_unit_distance(&self) -> usize; + + /// Presents underlying trace data, excluding trace randomizers. Makes little sense over the + /// FRI domain. + fn trace_table(&self) -> ArrayView2; + + /// Presents all underlying data. + fn master_matrix(&self) -> ArrayView2; + + /// Presents all underlying data in a mutable manner. + fn master_matrix_mut(&mut self) -> ArrayViewMut2; + fn fri_domain(&self) -> ArithmeticDomain; + + /// set all rows _not_ needed for the (padded) trace to random values + fn randomize_trace(&mut self) { + let randomized_padded_trace_len = self.randomized_padded_trace_len(); + let unit_distance = self.rand_trace_to_padded_trace_unit_distance(); + (1..unit_distance).for_each(|offset| { + self.master_matrix_mut() + .slice_mut(s![offset..randomized_padded_trace_len; unit_distance, ..]) + .par_mapv_inplace(|_| random::()) + }); + } + + /// Result is in row-major order. + fn low_degree_extend_all_columns(&self) -> Array2 + where + Self: Sync, + { + let randomized_trace_domain_len = self.randomized_padded_trace_len(); + let randomized_trace_domain = ArithmeticDomain::new_no_offset(randomized_trace_domain_len); + + let num_rows = self.fri_domain().length; + let num_columns = self.master_matrix().ncols(); + let mut extended_columns = Array2::zeros([num_rows, num_columns]); + Zip::from(extended_columns.axis_iter_mut(Axis(1))) + .and(self.master_matrix().axis_iter(Axis(1))) + .par_for_each(|lde_column, trace_column| { + let fri_codeword = randomized_trace_domain + .low_degree_extension(&trace_column.to_vec(), self.fri_domain()); + Array1::from(fri_codeword).move_into(lde_column); + }); + extended_columns + } +} + +#[derive(Clone)] +pub struct MasterBaseTable { + pub padded_height: usize, + pub num_trace_randomizers: usize, + + pub program_len: usize, + pub main_execution_len: usize, + pub hash_coprocessor_execution_len: usize, + + pub randomized_padded_trace_len: usize, + + /// how many elements to skip in the randomized (padded) trace domain to only refer to + /// elements in the _non_-randomized (padded) trace domain + pub rand_trace_to_padded_trace_unit_distance: usize, + + pub fri_domain: ArithmeticDomain, + pub master_base_matrix: Array2, +} + +pub struct MasterExtTable { + pub padded_height: usize, + pub num_trace_randomizers: usize, + pub num_randomizer_polynomials: usize, + + pub randomized_padded_trace_len: usize, + + /// how many elements to skip in the randomized (padded) trace domain to only refer to + /// elements in the _non_-randomized (padded) trace domain + pub rand_trace_to_padded_trace_unit_distance: usize, + + pub fri_domain: ArithmeticDomain, + pub master_ext_matrix: Array2, +} + +impl MasterTable for MasterBaseTable { + fn randomized_padded_trace_len(&self) -> usize { + self.randomized_padded_trace_len + } + + fn rand_trace_to_padded_trace_unit_distance(&self) -> usize { + self.rand_trace_to_padded_trace_unit_distance + } + + fn trace_table(&self) -> ArrayView2 { + self.master_base_matrix + .slice(s![..; self.rand_trace_to_padded_trace_unit_distance, ..]) + } + + fn master_matrix(&self) -> ArrayView2 { + self.master_base_matrix.view() + } + + fn master_matrix_mut(&mut self) -> ArrayViewMut2 { + self.master_base_matrix.view_mut() + } + + fn fri_domain(&self) -> ArithmeticDomain { + self.fri_domain + } +} + +impl MasterTable for MasterExtTable { + fn randomized_padded_trace_len(&self) -> usize { + self.randomized_padded_trace_len + } + + fn rand_trace_to_padded_trace_unit_distance(&self) -> usize { + self.rand_trace_to_padded_trace_unit_distance + } + + fn trace_table(&self) -> ArrayView2 { + self.master_ext_matrix + .slice(s![..; self.rand_trace_to_padded_trace_unit_distance, ..]) + } + + fn master_matrix(&self) -> ArrayView2 { + self.master_ext_matrix.view() + } + + fn master_matrix_mut(&mut self) -> ArrayViewMut2 { + self.master_ext_matrix.view_mut() + } + + fn fri_domain(&self) -> ArithmeticDomain { + self.fri_domain + } +} + +impl MasterBaseTable { + pub fn padded_height(aet: &AlgebraicExecutionTrace, program: &[BFieldElement]) -> usize { + let instruction_table_len = program.len() + aet.processor_matrix.nrows(); + let hash_table_len = aet.hash_matrix.nrows(); + let max_height = max(instruction_table_len, hash_table_len); + roundup_npo2(max_height as u64) as usize + } + + pub fn new( + aet: AlgebraicExecutionTrace, + program: &[BFieldElement], + num_trace_randomizers: usize, + fri_domain: ArithmeticDomain, + ) -> Self { + let padded_height = Self::padded_height(&aet, program); + let randomized_padded_trace_len = + randomized_padded_trace_len(num_trace_randomizers, padded_height); + let unit_distance = randomized_padded_trace_len / padded_height; + let program_len = program.len(); + let main_execution_len = aet.processor_matrix.nrows(); + let hash_coprocessor_execution_len = aet.hash_matrix.nrows(); + + let num_rows = randomized_padded_trace_len; + let num_columns = NUM_BASE_COLUMNS; + let master_base_matrix = Array2::zeros([num_rows, num_columns].f()); + + let mut master_base_table = Self { + padded_height, + num_trace_randomizers, + program_len, + main_execution_len, + hash_coprocessor_execution_len, + randomized_padded_trace_len, + rand_trace_to_padded_trace_unit_distance: unit_distance, + fri_domain, + master_base_matrix, + }; + + let program_table = &mut master_base_table.table_mut(TableId::ProgramTable); + ProgramTable::fill_trace(program_table, program); + let instruction_table = &mut master_base_table.table_mut(TableId::InstructionTable); + InstructionTable::fill_trace(instruction_table, &aet, program); + let op_stack_table = &mut master_base_table.table_mut(TableId::OpStackTable); + let op_stack_clk_jump_diffs = OpStackTable::fill_trace(op_stack_table, &aet); + let ram_table = &mut master_base_table.table_mut(TableId::RamTable); + let ram_clk_jump_diffs = RamTable::fill_trace(ram_table, &aet); + let jump_stack_table = &mut master_base_table.table_mut(TableId::JumpStackTable); + let jump_stack_clk_jump_diffs = JumpStackTable::fill_trace(jump_stack_table, &aet); + let hash_table = &mut master_base_table.table_mut(TableId::HashTable); + HashTable::fill_trace(hash_table, &aet); + + // memory-like tables must be filled in before clock jump differences are known, hence + // the break from the usual order + let all_clk_jump_diffs = [ + op_stack_clk_jump_diffs, + ram_clk_jump_diffs, + jump_stack_clk_jump_diffs, + ] + .concat(); + let processor_table = &mut master_base_table.table_mut(TableId::ProcessorTable); + ProcessorTable::fill_trace(processor_table, &aet, all_clk_jump_diffs); + + master_base_table + } + + pub fn pad(&mut self) { + let program_len = self.program_len; + let main_execution_len = self.main_execution_len; + + let program_table = &mut self.table_mut(TableId::ProgramTable); + ProgramTable::pad_trace(program_table, program_len); + let instruction_table = &mut self.table_mut(TableId::InstructionTable); + InstructionTable::pad_trace(instruction_table, program_len + main_execution_len); + let processor_table = &mut self.table_mut(TableId::ProcessorTable); + ProcessorTable::pad_trace(processor_table, main_execution_len); + let op_stack_table = &mut self.table_mut(TableId::OpStackTable); + OpStackTable::pad_trace(op_stack_table, main_execution_len); + let ram_table = &mut self.table_mut(TableId::RamTable); + RamTable::pad_trace(ram_table, main_execution_len); + let jump_stack_table = &mut self.table_mut(TableId::JumpStackTable); + JumpStackTable::pad_trace(jump_stack_table, main_execution_len); + let hash_table = &mut self.table_mut(TableId::HashTable); + HashTable::pad_trace(hash_table); + } + + pub fn to_fri_domain_table(&self) -> Self { + Self { + master_base_matrix: self.low_degree_extend_all_columns(), + ..*self + } + } + + pub fn merkle_tree(&self) -> MerkleTree { + let hashed_rows = self + .master_base_matrix + .axis_iter(Axis(0)) + .into_par_iter() + .map(|row| StarkHasher::hash_slice(&row.to_vec())) + .collect::>(); + CpuParallel::from_digests(&hashed_rows) + } + + /// Create a `MasterExtTable` from a `MasterBaseTable` by `.extend()`ing each individual base + /// table. The `.extend()` for each table is specific to that table, but always involves + /// adding some number of columns. + pub fn extend( + &self, + challenges: &AllChallenges, + num_randomizer_polynomials: usize, + ) -> MasterExtTable { + // randomizer polynomials + let num_rows = self.master_base_matrix.nrows(); + let num_columns = NUM_EXT_COLUMNS + num_randomizer_polynomials; + let mut master_ext_matrix = Array2::zeros([num_rows, num_columns].f()); + master_ext_matrix + .slice_mut(s![.., NUM_EXT_COLUMNS..]) + .par_mapv_inplace(|_| random::()); + + let mut master_ext_table = MasterExtTable { + padded_height: self.padded_height, + num_trace_randomizers: self.num_trace_randomizers, + num_randomizer_polynomials, + randomized_padded_trace_len: self.randomized_padded_trace_len, + rand_trace_to_padded_trace_unit_distance: self.rand_trace_to_padded_trace_unit_distance, + fri_domain: self.fri_domain, + master_ext_matrix, + }; + + ProgramTable::extend( + self.table(TableId::ProgramTable), + master_ext_table.table_mut(TableId::ProgramTable), + &challenges.program_table_challenges, + ); + InstructionTable::extend( + self.table(TableId::InstructionTable), + master_ext_table.table_mut(TableId::InstructionTable), + &challenges.instruction_table_challenges, + ); + ProcessorTable::extend( + self.table(TableId::ProcessorTable), + master_ext_table.table_mut(TableId::ProcessorTable), + &challenges.processor_table_challenges, + ); + OpStackTable::extend( + self.table(TableId::OpStackTable), + master_ext_table.table_mut(TableId::OpStackTable), + &challenges.op_stack_table_challenges, + ); + RamTable::extend( + self.table(TableId::RamTable), + master_ext_table.table_mut(TableId::RamTable), + &challenges.ram_table_challenges, + ); + JumpStackTable::extend( + self.table(TableId::JumpStackTable), + master_ext_table.table_mut(TableId::JumpStackTable), + &challenges.jump_stack_table_challenges, + ); + HashTable::extend( + self.table(TableId::HashTable), + master_ext_table.table_mut(TableId::HashTable), + &challenges.hash_table_challenges, + ); + + master_ext_table + } + + fn table_slice_info(id: TableId) -> (usize, usize) { + use TableId::*; + match id { + ProgramTable => (PROGRAM_TABLE_START, PROGRAM_TABLE_END), + InstructionTable => (INSTRUCTION_TABLE_START, INSTRUCTION_TABLE_END), + ProcessorTable => (PROCESSOR_TABLE_START, PROCESSOR_TABLE_END), + OpStackTable => (OP_STACK_TABLE_START, OP_STACK_TABLE_END), + RamTable => (RAM_TABLE_START, RAM_TABLE_END), + JumpStackTable => (JUMP_STACK_TABLE_START, JUMP_STACK_TABLE_END), + HashTable => (HASH_TABLE_START, HASH_TABLE_END), + } + } + + pub fn table(&self, id: TableId) -> ArrayView2 { + let (table_start, table_end) = Self::table_slice_info(id); + let unit_distance = self.rand_trace_to_padded_trace_unit_distance; + self.master_base_matrix + .slice(s![..; unit_distance, table_start..table_end]) + } + + pub fn table_mut(&mut self, id: TableId) -> ArrayViewMut2 { + let (table_start, table_end) = Self::table_slice_info(id); + let unit_distance = self.rand_trace_to_padded_trace_unit_distance; + self.master_base_matrix + .slice_mut(s![..; unit_distance, table_start..table_end]) + } +} + +impl MasterExtTable { + pub fn to_fri_domain_table(&self) -> Self { + Self { + master_ext_matrix: self.low_degree_extend_all_columns(), + ..*self + } + } + + pub fn randomizer_polynomials(&self) -> Vec> { + let mut randomizer_polynomials = Vec::with_capacity(self.num_randomizer_polynomials); + for col_idx in NUM_EXT_COLUMNS..self.master_ext_matrix.ncols() { + let randomizer_polynomial = self.master_ext_matrix.column(col_idx); + randomizer_polynomials.push(randomizer_polynomial.to_owned()); + } + randomizer_polynomials + } + + pub fn merkle_tree(&self) -> MerkleTree { + let hashed_rows = self + .master_ext_matrix + .axis_iter(Axis(0)) + .into_par_iter() + .map(|row| { + let contiguous_row_bfe = row + .to_vec() + .iter() + .map(|xfe| xfe.coefficients.to_vec()) + .concat(); + StarkHasher::hash_slice(&contiguous_row_bfe) + }) + .collect::>(); + CpuParallel::from_digests(&hashed_rows) + } + + fn table_slice_info(id: TableId) -> (usize, usize) { + use TableId::*; + match id { + ProgramTable => (EXT_PROGRAM_TABLE_START, EXT_PROGRAM_TABLE_END), + InstructionTable => (EXT_INSTRUCTION_TABLE_START, EXT_INSTRUCTION_TABLE_END), + ProcessorTable => (EXT_PROCESSOR_TABLE_START, EXT_PROCESSOR_TABLE_END), + OpStackTable => (EXT_OP_STACK_TABLE_START, EXT_OP_STACK_TABLE_END), + RamTable => (EXT_RAM_TABLE_START, EXT_RAM_TABLE_END), + JumpStackTable => (EXT_JUMP_STACK_TABLE_START, EXT_JUMP_STACK_TABLE_END), + HashTable => (EXT_HASH_TABLE_START, EXT_HASH_TABLE_END), + } + } + + pub fn table(&self, id: TableId) -> ArrayView2 { + let unit_distance = self.rand_trace_to_padded_trace_unit_distance; + let (table_start, table_end) = Self::table_slice_info(id); + self.master_ext_matrix + .slice(s![..; unit_distance, table_start..table_end]) + } + + pub fn table_mut(&mut self, id: TableId) -> ArrayViewMut2 { + let unit_distance = self.rand_trace_to_padded_trace_unit_distance; + let (table_start, table_end) = Self::table_slice_info(id); + self.master_ext_matrix + .slice_mut(s![..; unit_distance, table_start..table_end]) + } +} + +pub fn all_degrees_with_origin( + interpolant_degree: Degree, + padded_height: usize, +) -> Vec { + let id = interpolant_degree; + let ph = padded_height; + [ + ExtProgramTable::all_degrees_with_origin("program table", id, ph), + ExtInstructionTable::all_degrees_with_origin("instruction table", id, ph), + ExtProcessorTable::all_degrees_with_origin("processor table", id, ph), + ExtOpStackTable::all_degrees_with_origin("op stack table", id, ph), + ExtRamTable::all_degrees_with_origin("ram table", id, ph), + ExtJumpStackTable::all_degrees_with_origin("jump stack table", id, ph), + ExtHashTable::all_degrees_with_origin("hash table", id, ph), + ] + .concat() +} + +pub fn max_degree_with_origin( + interpolant_degree: Degree, + padded_height: usize, +) -> DegreeWithOrigin { + all_degrees_with_origin(interpolant_degree, padded_height) + .into_iter() + .max() + .unwrap_or_default() +} + +pub fn num_all_table_quotients() -> usize { + num_all_initial_quotients() + + num_all_consistency_quotients() + + num_all_transition_quotients() + + num_all_terminal_quotients() +} + +pub fn num_all_initial_quotients() -> usize { + ExtProgramTable::num_initial_quotients() + + ExtInstructionTable::num_initial_quotients() + + ExtProcessorTable::num_initial_quotients() + + ExtOpStackTable::num_initial_quotients() + + ExtRamTable::num_initial_quotients() + + ExtJumpStackTable::num_initial_quotients() + + ExtHashTable::num_initial_quotients() +} + +pub fn num_all_consistency_quotients() -> usize { + ExtProgramTable::num_consistency_quotients() + + ExtInstructionTable::num_consistency_quotients() + + ExtProcessorTable::num_consistency_quotients() + + ExtOpStackTable::num_consistency_quotients() + + ExtRamTable::num_consistency_quotients() + + ExtJumpStackTable::num_consistency_quotients() + + ExtHashTable::num_consistency_quotients() +} + +pub fn num_all_transition_quotients() -> usize { + ExtProgramTable::num_transition_quotients() + + ExtInstructionTable::num_transition_quotients() + + ExtProcessorTable::num_transition_quotients() + + ExtOpStackTable::num_transition_quotients() + + ExtRamTable::num_transition_quotients() + + ExtJumpStackTable::num_transition_quotients() + + ExtHashTable::num_transition_quotients() +} + +pub fn num_all_terminal_quotients() -> usize { + ExtProgramTable::num_terminal_quotients() + + ExtInstructionTable::num_terminal_quotients() + + ExtProcessorTable::num_terminal_quotients() + + ExtOpStackTable::num_terminal_quotients() + + ExtRamTable::num_terminal_quotients() + + ExtJumpStackTable::num_terminal_quotients() + + ExtHashTable::num_terminal_quotients() + + GrandCrossTableArg::num_terminal_quotients() +} + +pub fn all_initial_quotient_degree_bounds(interpolant_degree: Degree) -> Vec { + [ + ExtProgramTable::initial_quotient_degree_bounds(interpolant_degree), + ExtInstructionTable::initial_quotient_degree_bounds(interpolant_degree), + ExtProcessorTable::initial_quotient_degree_bounds(interpolant_degree), + ExtOpStackTable::initial_quotient_degree_bounds(interpolant_degree), + ExtRamTable::initial_quotient_degree_bounds(interpolant_degree), + ExtJumpStackTable::initial_quotient_degree_bounds(interpolant_degree), + ExtHashTable::initial_quotient_degree_bounds(interpolant_degree), + ] + .concat() +} + +pub fn all_consistency_quotient_degree_bounds( + interpolant_degree: Degree, + padded_height: usize, +) -> Vec { + [ + ExtProgramTable::consistency_quotient_degree_bounds(interpolant_degree, padded_height), + ExtInstructionTable::consistency_quotient_degree_bounds(interpolant_degree, padded_height), + ExtProcessorTable::consistency_quotient_degree_bounds(interpolant_degree, padded_height), + ExtOpStackTable::consistency_quotient_degree_bounds(interpolant_degree, padded_height), + ExtRamTable::consistency_quotient_degree_bounds(interpolant_degree, padded_height), + ExtJumpStackTable::consistency_quotient_degree_bounds(interpolant_degree, padded_height), + ExtHashTable::consistency_quotient_degree_bounds(interpolant_degree, padded_height), + ] + .concat() +} + +pub fn all_transition_quotient_degree_bounds( + interpolant_degree: Degree, + padded_height: usize, +) -> Vec { + [ + ExtProgramTable::transition_quotient_degree_bounds(interpolant_degree, padded_height), + ExtInstructionTable::transition_quotient_degree_bounds(interpolant_degree, padded_height), + ExtProcessorTable::transition_quotient_degree_bounds(interpolant_degree, padded_height), + ExtOpStackTable::transition_quotient_degree_bounds(interpolant_degree, padded_height), + ExtRamTable::transition_quotient_degree_bounds(interpolant_degree, padded_height), + ExtJumpStackTable::transition_quotient_degree_bounds(interpolant_degree, padded_height), + ExtHashTable::transition_quotient_degree_bounds(interpolant_degree, padded_height), + ] + .concat() +} + +pub fn all_terminal_quotient_degree_bounds(interpolant_degree: Degree) -> Vec { + [ + ExtProgramTable::terminal_quotient_degree_bounds(interpolant_degree), + ExtInstructionTable::terminal_quotient_degree_bounds(interpolant_degree), + ExtProcessorTable::terminal_quotient_degree_bounds(interpolant_degree), + ExtOpStackTable::terminal_quotient_degree_bounds(interpolant_degree), + ExtRamTable::terminal_quotient_degree_bounds(interpolant_degree), + ExtJumpStackTable::terminal_quotient_degree_bounds(interpolant_degree), + ExtHashTable::terminal_quotient_degree_bounds(interpolant_degree), + GrandCrossTableArg::terminal_quotient_degree_bounds(interpolant_degree), + ] + .concat() +} + +pub fn all_quotient_degree_bounds(interpolant_degree: Degree, padded_height: usize) -> Vec { + [ + all_initial_quotient_degree_bounds(interpolant_degree), + all_consistency_quotient_degree_bounds(interpolant_degree, padded_height), + all_transition_quotient_degree_bounds(interpolant_degree, padded_height), + all_terminal_quotient_degree_bounds(interpolant_degree), + ] + .concat() +} + +pub fn initial_quotient_zerofier_inverse( + quotient_domain: ArithmeticDomain, +) -> Array1 { + let zerofier_codeword = quotient_domain + .domain_values() + .into_iter() + .map(|x| x - BFieldElement::one()) + .collect(); + BFieldElement::batch_inversion(zerofier_codeword).into() +} + +pub fn consistency_quotient_zerofier_inverse( + trace_domain: ArithmeticDomain, + quotient_domain: ArithmeticDomain, +) -> Array1 { + let zerofier_codeword = quotient_domain + .domain_values() + .iter() + .map(|x| x.mod_pow_u32(trace_domain.length as u32) - BFieldElement::one()) + .collect(); + BFieldElement::batch_inversion(zerofier_codeword).into() +} + +pub fn transition_quotient_zerofier_inverse( + trace_domain: ArithmeticDomain, + quotient_domain: ArithmeticDomain, +) -> Array1 { + let one = BFieldElement::one(); + let trace_domain_generator_inverse = trace_domain.generator.inverse(); + let quotient_domain_values = quotient_domain.domain_values(); + + let subgroup_zerofier: Vec<_> = quotient_domain_values + .par_iter() + .map(|domain_value| domain_value.mod_pow_u32(trace_domain.length as u32) - one) + .collect(); + let subgroup_zerofier_inverse = BFieldElement::batch_inversion(subgroup_zerofier); + let zerofier_inverse: Vec<_> = quotient_domain_values + .into_par_iter() + .zip_eq(subgroup_zerofier_inverse.into_par_iter()) + .map(|(domain_value, sub_z_inv)| { + (domain_value - trace_domain_generator_inverse) * sub_z_inv + }) + .collect(); + zerofier_inverse.into() +} + +pub fn terminal_quotient_zerofier_inverse( + trace_domain: ArithmeticDomain, + quotient_domain: ArithmeticDomain, +) -> Array1 { + // The zerofier for the terminal quotient has a root in the last + // value in the cyclical group generated from the trace domain's generator. + let trace_domain_generator_inverse = trace_domain.generator.inverse(); + let zerofier_codeword = quotient_domain + .domain_values() + .into_iter() + .map(|x| x - trace_domain_generator_inverse) + .collect_vec(); + BFieldElement::batch_inversion(zerofier_codeword).into() +} + +pub fn fill_all_initial_quotients( + master_base_table: ArrayView2, + master_ext_table: ArrayView2, + quot_table: &mut ArrayViewMut2, + zerofier_inverse: ArrayView1, + challenges: &AllChallenges, +) { + // The order of the quotient tables is not actually important. However, it must be consistent + // between prover and verifier, and the shapes must check out. + let program_section_start = 0; + let program_section_end = program_section_start + ExtProgramTable::num_initial_quotients(); + let instruction_section_start = program_section_end; + let instruction_section_end = + instruction_section_start + ExtInstructionTable::num_initial_quotients(); + let processor_section_start = instruction_section_end; + let processor_section_end = + processor_section_start + ExtProcessorTable::num_initial_quotients(); + let op_stack_section_start = processor_section_end; + let op_stack_section_end = op_stack_section_start + ExtOpStackTable::num_initial_quotients(); + let ram_section_start = op_stack_section_end; + let ram_section_end = ram_section_start + ExtRamTable::num_initial_quotients(); + let jump_stack_section_start = ram_section_end; + let jump_stack_section_end = + jump_stack_section_start + ExtJumpStackTable::num_initial_quotients(); + let hash_section_start = jump_stack_section_end; + let hash_section_end = hash_section_start + ExtHashTable::num_initial_quotients(); + + let mut program_quot_table = + quot_table.slice_mut(s![.., program_section_start..program_section_end]); + ExtProgramTable::fill_initial_quotients( + master_base_table, + master_ext_table, + &mut program_quot_table, + zerofier_inverse, + challenges, + ); + let mut instruction_quot_table = + quot_table.slice_mut(s![.., instruction_section_start..instruction_section_end]); + ExtInstructionTable::fill_initial_quotients( + master_base_table, + master_ext_table, + &mut instruction_quot_table, + zerofier_inverse, + challenges, + ); + let mut processor_quot_table = + quot_table.slice_mut(s![.., processor_section_start..processor_section_end]); + ExtProcessorTable::fill_initial_quotients( + master_base_table, + master_ext_table, + &mut processor_quot_table, + zerofier_inverse, + challenges, + ); + let mut op_stack_quot_table = + quot_table.slice_mut(s![.., op_stack_section_start..op_stack_section_end]); + ExtOpStackTable::fill_initial_quotients( + master_base_table, + master_ext_table, + &mut op_stack_quot_table, + zerofier_inverse, + challenges, + ); + let mut ram_quot_table = quot_table.slice_mut(s![.., ram_section_start..ram_section_end]); + ExtRamTable::fill_initial_quotients( + master_base_table, + master_ext_table, + &mut ram_quot_table, + zerofier_inverse, + challenges, + ); + let mut jump_stack_quot_table = + quot_table.slice_mut(s![.., jump_stack_section_start..jump_stack_section_end]); + ExtJumpStackTable::fill_initial_quotients( + master_base_table, + master_ext_table, + &mut jump_stack_quot_table, + zerofier_inverse, + challenges, + ); + let mut hash_quot_table = quot_table.slice_mut(s![.., hash_section_start..hash_section_end]); + ExtHashTable::fill_initial_quotients( + master_base_table, + master_ext_table, + &mut hash_quot_table, + zerofier_inverse, + challenges, + ); +} + +pub fn fill_all_consistency_quotients( + master_base_table: ArrayView2, + master_ext_table: ArrayView2, + quot_table: &mut ArrayViewMut2, + zerofier_inverse: ArrayView1, + challenges: &AllChallenges, +) { + // The order of the quotient tables is not actually important. However, it must be consistent + // between prover and verifier, and the shapes must check out. + let program_section_start = 0; + let program_section_end = program_section_start + ExtProgramTable::num_consistency_quotients(); + let instruction_section_start = program_section_end; + let instruction_section_end = + instruction_section_start + ExtInstructionTable::num_consistency_quotients(); + let processor_section_start = instruction_section_end; + let processor_section_end = + processor_section_start + ExtProcessorTable::num_consistency_quotients(); + let op_stack_section_start = processor_section_end; + let op_stack_section_end = + op_stack_section_start + ExtOpStackTable::num_consistency_quotients(); + let ram_section_start = op_stack_section_end; + let ram_section_end = ram_section_start + ExtRamTable::num_consistency_quotients(); + let jump_stack_section_start = ram_section_end; + let jump_stack_section_end = + jump_stack_section_start + ExtJumpStackTable::num_consistency_quotients(); + let hash_section_start = jump_stack_section_end; + let hash_section_end = hash_section_start + ExtHashTable::num_consistency_quotients(); + + let mut program_quot_table = + quot_table.slice_mut(s![.., program_section_start..program_section_end]); + ExtProgramTable::fill_consistency_quotients( + master_base_table, + master_ext_table, + &mut program_quot_table, + zerofier_inverse, + challenges, + ); + let mut instruction_quot_table = + quot_table.slice_mut(s![.., instruction_section_start..instruction_section_end]); + ExtInstructionTable::fill_consistency_quotients( + master_base_table, + master_ext_table, + &mut instruction_quot_table, + zerofier_inverse, + challenges, + ); + let mut processor_quot_table = + quot_table.slice_mut(s![.., processor_section_start..processor_section_end]); + ExtProcessorTable::fill_consistency_quotients( + master_base_table, + master_ext_table, + &mut processor_quot_table, + zerofier_inverse, + challenges, + ); + let mut op_stack_quot_table = + quot_table.slice_mut(s![.., op_stack_section_start..op_stack_section_end]); + ExtOpStackTable::fill_consistency_quotients( + master_base_table, + master_ext_table, + &mut op_stack_quot_table, + zerofier_inverse, + challenges, + ); + let mut ram_quot_table = quot_table.slice_mut(s![.., ram_section_start..ram_section_end]); + ExtRamTable::fill_consistency_quotients( + master_base_table, + master_ext_table, + &mut ram_quot_table, + zerofier_inverse, + challenges, + ); + let mut jump_stack_quot_table = + quot_table.slice_mut(s![.., jump_stack_section_start..jump_stack_section_end]); + ExtJumpStackTable::fill_consistency_quotients( + master_base_table, + master_ext_table, + &mut jump_stack_quot_table, + zerofier_inverse, + challenges, + ); + let mut hash_quot_table = quot_table.slice_mut(s![.., hash_section_start..hash_section_end]); + ExtHashTable::fill_consistency_quotients( + master_base_table, + master_ext_table, + &mut hash_quot_table, + zerofier_inverse, + challenges, + ); +} + +pub fn fill_all_transition_quotients( + master_base_table: ArrayView2, + master_ext_table: ArrayView2, + quot_table: &mut ArrayViewMut2, + zerofier_inverse: ArrayView1, + challenges: &AllChallenges, + trace_domain: ArithmeticDomain, + quotient_domain: ArithmeticDomain, +) { + // The order of the quotient tables is not actually important. However, it must be consistent + // between prover and verifier, and the shapes must check out. + let program_section_start = 0; + let program_section_end = program_section_start + ExtProgramTable::num_transition_quotients(); + let instruction_section_start = program_section_end; + let instruction_section_end = + instruction_section_start + ExtInstructionTable::num_transition_quotients(); + let processor_section_start = instruction_section_end; + let processor_section_end = + processor_section_start + ExtProcessorTable::num_transition_quotients(); + let op_stack_section_start = processor_section_end; + let op_stack_section_end = op_stack_section_start + ExtOpStackTable::num_transition_quotients(); + let ram_section_start = op_stack_section_end; + let ram_section_end = ram_section_start + ExtRamTable::num_transition_quotients(); + let jump_stack_section_start = ram_section_end; + let jump_stack_section_end = + jump_stack_section_start + ExtJumpStackTable::num_transition_quotients(); + let hash_section_start = jump_stack_section_end; + let hash_section_end = hash_section_start + ExtHashTable::num_transition_quotients(); + + let mut program_quot_table = + quot_table.slice_mut(s![.., program_section_start..program_section_end]); + ExtProgramTable::fill_transition_quotients( + master_base_table, + master_ext_table, + &mut program_quot_table, + zerofier_inverse, + challenges, + trace_domain, + quotient_domain, + ); + let mut instruction_quot_table = + quot_table.slice_mut(s![.., instruction_section_start..instruction_section_end]); + ExtInstructionTable::fill_transition_quotients( + master_base_table, + master_ext_table, + &mut instruction_quot_table, + zerofier_inverse, + challenges, + trace_domain, + quotient_domain, + ); + let mut processor_quot_table = + quot_table.slice_mut(s![.., processor_section_start..processor_section_end]); + ExtProcessorTable::fill_transition_quotients( + master_base_table, + master_ext_table, + &mut processor_quot_table, + zerofier_inverse, + challenges, + trace_domain, + quotient_domain, + ); + let mut op_stack_quot_table = + quot_table.slice_mut(s![.., op_stack_section_start..op_stack_section_end]); + ExtOpStackTable::fill_transition_quotients( + master_base_table, + master_ext_table, + &mut op_stack_quot_table, + zerofier_inverse, + challenges, + trace_domain, + quotient_domain, + ); + let mut ram_quot_table = quot_table.slice_mut(s![.., ram_section_start..ram_section_end]); + ExtRamTable::fill_transition_quotients( + master_base_table, + master_ext_table, + &mut ram_quot_table, + zerofier_inverse, + challenges, + trace_domain, + quotient_domain, + ); + let mut jump_stack_quot_table = + quot_table.slice_mut(s![.., jump_stack_section_start..jump_stack_section_end]); + ExtJumpStackTable::fill_transition_quotients( + master_base_table, + master_ext_table, + &mut jump_stack_quot_table, + zerofier_inverse, + challenges, + trace_domain, + quotient_domain, + ); + let mut hash_quot_table = quot_table.slice_mut(s![.., hash_section_start..hash_section_end]); + ExtHashTable::fill_transition_quotients( + master_base_table, + master_ext_table, + &mut hash_quot_table, + zerofier_inverse, + challenges, + trace_domain, + quotient_domain, + ); +} + +pub fn fill_all_terminal_quotients( + master_base_table: ArrayView2, + master_ext_table: ArrayView2, + quot_table: &mut ArrayViewMut2, + zerofier_inverse: ArrayView1, + challenges: &AllChallenges, +) { + // The order of the quotient tables is not actually important. However, it must be consistent + // between prover and verifier, and the shapes must check out. + let program_section_start = 0; + let program_section_end = program_section_start + ExtProgramTable::num_terminal_quotients(); + let instruction_section_start = program_section_end; + let instruction_section_end = + instruction_section_start + ExtInstructionTable::num_terminal_quotients(); + let processor_section_start = instruction_section_end; + let processor_section_end = + processor_section_start + ExtProcessorTable::num_terminal_quotients(); + let op_stack_section_start = processor_section_end; + let op_stack_section_end = op_stack_section_start + ExtOpStackTable::num_terminal_quotients(); + let ram_section_start = op_stack_section_end; + let ram_section_end = ram_section_start + ExtRamTable::num_terminal_quotients(); + let jump_stack_section_start = ram_section_end; + let jump_stack_section_end = + jump_stack_section_start + ExtJumpStackTable::num_terminal_quotients(); + let hash_section_start = jump_stack_section_end; + let hash_section_end = hash_section_start + ExtHashTable::num_terminal_quotients(); + let cross_table_section_start = hash_section_end; + let cross_table_section_end = + cross_table_section_start + GrandCrossTableArg::num_terminal_quotients(); + + let mut program_quot_table = + quot_table.slice_mut(s![.., program_section_start..program_section_end]); + ExtProgramTable::fill_terminal_quotients( + master_base_table, + master_ext_table, + &mut program_quot_table, + zerofier_inverse, + challenges, + ); + let mut instruction_quot_table = + quot_table.slice_mut(s![.., instruction_section_start..instruction_section_end]); + ExtInstructionTable::fill_terminal_quotients( + master_base_table, + master_ext_table, + &mut instruction_quot_table, + zerofier_inverse, + challenges, + ); + let mut processor_quot_table = + quot_table.slice_mut(s![.., processor_section_start..processor_section_end]); + ExtProcessorTable::fill_terminal_quotients( + master_base_table, + master_ext_table, + &mut processor_quot_table, + zerofier_inverse, + challenges, + ); + let mut op_stack_quot_table = + quot_table.slice_mut(s![.., op_stack_section_start..op_stack_section_end]); + ExtOpStackTable::fill_terminal_quotients( + master_base_table, + master_ext_table, + &mut op_stack_quot_table, + zerofier_inverse, + challenges, + ); + let mut ram_quot_table = quot_table.slice_mut(s![.., ram_section_start..ram_section_end]); + ExtRamTable::fill_terminal_quotients( + master_base_table, + master_ext_table, + &mut ram_quot_table, + zerofier_inverse, + challenges, + ); + let mut jump_stack_quot_table = + quot_table.slice_mut(s![.., jump_stack_section_start..jump_stack_section_end]); + ExtJumpStackTable::fill_terminal_quotients( + master_base_table, + master_ext_table, + &mut jump_stack_quot_table, + zerofier_inverse, + challenges, + ); + let mut hash_quot_table = quot_table.slice_mut(s![.., hash_section_start..hash_section_end]); + ExtHashTable::fill_terminal_quotients( + master_base_table, + master_ext_table, + &mut hash_quot_table, + zerofier_inverse, + challenges, + ); + let mut cross_table_argument_quot_table = + quot_table.slice_mut(s![.., cross_table_section_start..cross_table_section_end]); + GrandCrossTableArg::fill_terminal_quotients( + master_base_table, + master_ext_table, + &mut cross_table_argument_quot_table, + zerofier_inverse, + challenges, + ); +} + +/// Computes an array containing all quotients – the Master Quotient Table. Each column corresponds +/// to a different quotient. The quotients are ordered by category – initial, consistency, +/// transition, and then terminal. Within each category, the quotients follow the canonical order +/// of the tables. The last column holds the terminal quotient of the cross-table argument, which +/// is strictly speaking not a table. +/// The order of the quotients is not actually important. However, it must be consistent between +/// prover and verifier. +/// +/// The returned array is in row-major order. +pub fn all_quotients( + quotient_domain_master_base_table: ArrayView2, + quotient_domain_master_ext_table: ArrayView2, + trace_domain: ArithmeticDomain, + quotient_domain: ArithmeticDomain, + challenges: &AllChallenges, + maybe_profiler: &mut Option, +) -> Array2 { + assert_eq!( + quotient_domain.length, + quotient_domain_master_base_table.nrows(), + ); + assert_eq!( + quotient_domain.length, + quotient_domain_master_ext_table.nrows() + ); + + prof_start!(maybe_profiler, "malloc"); + let num_columns = num_all_table_quotients(); + let mut all_quotients = Array2::zeros([quotient_domain.length, num_columns]); + prof_stop!(maybe_profiler, "malloc"); + + let initial_quotient_section_start = 0; + let initial_quotient_section_end = initial_quotient_section_start + num_all_initial_quotients(); + let consistency_quotient_section_start = initial_quotient_section_end; + let consistency_quotient_section_end = + consistency_quotient_section_start + num_all_consistency_quotients(); + let transition_quotient_section_start = consistency_quotient_section_end; + let transition_quotient_section_end = + transition_quotient_section_start + num_all_transition_quotients(); + let terminal_quotient_section_start = transition_quotient_section_end; + let terminal_quotient_section_end = + terminal_quotient_section_start + num_all_terminal_quotients(); + + prof_start!(maybe_profiler, "initial"); + let mut initial_quot_table = all_quotients.slice_mut(s![ + .., + initial_quotient_section_start..initial_quotient_section_end + ]); + let initial_quotient_zerofier_inverse = initial_quotient_zerofier_inverse(quotient_domain); + fill_all_initial_quotients( + quotient_domain_master_base_table, + quotient_domain_master_ext_table, + &mut initial_quot_table, + initial_quotient_zerofier_inverse.view(), + challenges, + ); + prof_stop!(maybe_profiler, "initial"); + + prof_start!(maybe_profiler, "consistency"); + let mut consistency_quotients = all_quotients.slice_mut(s![ + .., + consistency_quotient_section_start..consistency_quotient_section_end + ]); + let consistency_quotient_zerofier_inverse = + consistency_quotient_zerofier_inverse(trace_domain, quotient_domain); + fill_all_consistency_quotients( + quotient_domain_master_base_table, + quotient_domain_master_ext_table, + &mut consistency_quotients, + consistency_quotient_zerofier_inverse.view(), + challenges, + ); + prof_stop!(maybe_profiler, "consistency"); + + prof_start!(maybe_profiler, "transition"); + let mut transition_quotients = all_quotients.slice_mut(s![ + .., + transition_quotient_section_start..transition_quotient_section_end + ]); + let transition_quotient_zerofier_inverse = + transition_quotient_zerofier_inverse(trace_domain, quotient_domain); + fill_all_transition_quotients( + quotient_domain_master_base_table, + quotient_domain_master_ext_table, + &mut transition_quotients, + transition_quotient_zerofier_inverse.view(), + challenges, + trace_domain, + quotient_domain, + ); + prof_stop!(maybe_profiler, "transition"); + + prof_start!(maybe_profiler, "terminal"); + let mut terminal_quot_table = all_quotients.slice_mut(s![ + .., + terminal_quotient_section_start..terminal_quotient_section_end + ]); + let initial_quotient_zerofier_inverse = + terminal_quotient_zerofier_inverse(trace_domain, quotient_domain); + fill_all_terminal_quotients( + quotient_domain_master_base_table, + quotient_domain_master_ext_table, + &mut terminal_quot_table, + initial_quotient_zerofier_inverse.view(), + challenges, + ); + prof_stop!(maybe_profiler, "terminal"); + + all_quotients +} + +pub fn evaluate_all_initial_constraints( + base_row: ArrayView1, + ext_row: ArrayView1, + challenges: &AllChallenges, +) -> Vec { + [ + ExtProgramTable::evaluate_initial_constraints(base_row, ext_row, challenges), + ExtInstructionTable::evaluate_initial_constraints(base_row, ext_row, challenges), + ExtProcessorTable::evaluate_initial_constraints(base_row, ext_row, challenges), + ExtOpStackTable::evaluate_initial_constraints(base_row, ext_row, challenges), + ExtRamTable::evaluate_initial_constraints(base_row, ext_row, challenges), + ExtJumpStackTable::evaluate_initial_constraints(base_row, ext_row, challenges), + ExtHashTable::evaluate_initial_constraints(base_row, ext_row, challenges), + ] + .concat() +} + +pub fn evaluate_all_consistency_constraints( + base_row: ArrayView1, + ext_row: ArrayView1, + challenges: &AllChallenges, +) -> Vec { + [ + ExtProgramTable::evaluate_consistency_constraints(base_row, ext_row, challenges), + ExtInstructionTable::evaluate_consistency_constraints(base_row, ext_row, challenges), + ExtProcessorTable::evaluate_consistency_constraints(base_row, ext_row, challenges), + ExtOpStackTable::evaluate_consistency_constraints(base_row, ext_row, challenges), + ExtRamTable::evaluate_consistency_constraints(base_row, ext_row, challenges), + ExtJumpStackTable::evaluate_consistency_constraints(base_row, ext_row, challenges), + ExtHashTable::evaluate_consistency_constraints(base_row, ext_row, challenges), + ] + .concat() +} + +pub fn evaluate_all_transition_constraints( + current_base_row: ArrayView1, + current_ext_row: ArrayView1, + next_base_row: ArrayView1, + next_ext_row: ArrayView1, + challenges: &AllChallenges, +) -> Vec { + let cbr = current_base_row; + let cer = current_ext_row; + let nbr = next_base_row; + let ner = next_ext_row; + [ + ExtProgramTable::evaluate_transition_constraints(cbr, cer, nbr, ner, challenges), + ExtInstructionTable::evaluate_transition_constraints(cbr, cer, nbr, ner, challenges), + ExtProcessorTable::evaluate_transition_constraints(cbr, cer, nbr, ner, challenges), + ExtOpStackTable::evaluate_transition_constraints(cbr, cer, nbr, ner, challenges), + ExtRamTable::evaluate_transition_constraints(cbr, cer, nbr, ner, challenges), + ExtJumpStackTable::evaluate_transition_constraints(cbr, cer, nbr, ner, challenges), + ExtHashTable::evaluate_transition_constraints(cbr, cer, nbr, ner, challenges), + ] + .concat() +} + +pub fn evaluate_all_terminal_constraints( + base_row: ArrayView1, + ext_row: ArrayView1, + challenges: &AllChallenges, +) -> Vec { + [ + ExtProgramTable::evaluate_terminal_constraints(base_row, ext_row, challenges), + ExtInstructionTable::evaluate_terminal_constraints(base_row, ext_row, challenges), + ExtProcessorTable::evaluate_terminal_constraints(base_row, ext_row, challenges), + ExtOpStackTable::evaluate_terminal_constraints(base_row, ext_row, challenges), + ExtRamTable::evaluate_terminal_constraints(base_row, ext_row, challenges), + ExtJumpStackTable::evaluate_terminal_constraints(base_row, ext_row, challenges), + ExtHashTable::evaluate_terminal_constraints(base_row, ext_row, challenges), + GrandCrossTableArg::evaluate_terminal_constraints(base_row, ext_row, challenges), + ] + .concat() +} + +pub fn evaluate_all_constraints( + current_base_row: ArrayView1, + current_ext_row: ArrayView1, + next_base_row: ArrayView1, + next_ext_row: ArrayView1, + challenges: &AllChallenges, +) -> Vec { + [ + evaluate_all_initial_constraints(current_base_row, current_ext_row, challenges), + evaluate_all_consistency_constraints(current_base_row, current_ext_row, challenges), + evaluate_all_transition_constraints( + current_base_row, + current_ext_row, + next_base_row, + next_ext_row, + challenges, + ), + evaluate_all_terminal_constraints(current_base_row, current_ext_row, challenges), + ] + .concat() +} + +pub fn randomized_padded_trace_len(num_trace_randomizers: usize, padded_height: usize) -> usize { + roundup_npo2((padded_height + num_trace_randomizers) as u64) as usize +} + +pub fn interpolant_degree(padded_height: usize, num_trace_randomizers: usize) -> Degree { + (randomized_padded_trace_len(padded_height, num_trace_randomizers) - 1) as Degree +} + +pub fn derive_domain_generator(domain_length: u64) -> BFieldElement { + debug_assert!( + 0 == domain_length || is_power_of_two(domain_length), + "The domain length must be a power of 2 but was {domain_length}.", + ); + BFieldElement::primitive_root_of_unity(domain_length).unwrap() +} + +#[cfg(test)] +mod master_table_tests { + use ndarray::s; + use num_traits::Zero; + use strum::IntoEnumIterator; + use twenty_first::shared_math::b_field_element::BFieldElement; + use twenty_first::shared_math::traits::FiniteField; + + use crate::arithmetic_domain::ArithmeticDomain; + use crate::stark::triton_stark_tests::parse_simulate_pad; + use crate::stark::triton_stark_tests::parse_simulate_pad_extend; + use crate::table::hash_table; + use crate::table::instruction_table; + use crate::table::jump_stack_table; + use crate::table::master_table::consistency_quotient_zerofier_inverse; + use crate::table::master_table::initial_quotient_zerofier_inverse; + use crate::table::master_table::terminal_quotient_zerofier_inverse; + use crate::table::master_table::transition_quotient_zerofier_inverse; + use crate::table::master_table::TableId::*; + use crate::table::master_table::EXT_HASH_TABLE_END; + use crate::table::master_table::NUM_BASE_COLUMNS; + use crate::table::master_table::NUM_COLUMNS; + use crate::table::master_table::NUM_EXT_COLUMNS; + use crate::table::op_stack_table; + use crate::table::processor_table; + use crate::table::program_table; + use crate::table::ram_table; + use crate::table::table_column::HashBaseTableColumn; + use crate::table::table_column::HashExtTableColumn; + use crate::table::table_column::InstructionBaseTableColumn; + use crate::table::table_column::InstructionExtTableColumn; + use crate::table::table_column::JumpStackBaseTableColumn; + use crate::table::table_column::JumpStackExtTableColumn; + use crate::table::table_column::MasterBaseTableColumn; + use crate::table::table_column::MasterExtTableColumn; + use crate::table::table_column::OpStackBaseTableColumn; + use crate::table::table_column::OpStackExtTableColumn; + use crate::table::table_column::ProcessorBaseTableColumn; + use crate::table::table_column::ProcessorExtTableColumn; + use crate::table::table_column::ProgramBaseTableColumn; + use crate::table::table_column::ProgramExtTableColumn; + use crate::table::table_column::RamBaseTableColumn; + use crate::table::table_column::RamExtTableColumn; + + #[test] + fn base_table_width_is_correct() { + let (_, _, master_base_table) = parse_simulate_pad("halt", vec![], vec![]); + + assert_eq!( + program_table::BASE_WIDTH, + master_base_table.table(ProgramTable).ncols() + ); + assert_eq!( + instruction_table::BASE_WIDTH, + master_base_table.table(InstructionTable).ncols() + ); + assert_eq!( + processor_table::BASE_WIDTH, + master_base_table.table(ProcessorTable).ncols() + ); + assert_eq!( + op_stack_table::BASE_WIDTH, + master_base_table.table(OpStackTable).ncols() + ); + assert_eq!( + ram_table::BASE_WIDTH, + master_base_table.table(RamTable).ncols() + ); + assert_eq!( + jump_stack_table::BASE_WIDTH, + master_base_table.table(JumpStackTable).ncols() + ); + assert_eq!( + hash_table::BASE_WIDTH, + master_base_table.table(HashTable).ncols() + ); + } + + #[test] + fn ext_table_width_is_correct() { + let (stark, _, _, master_ext_table, _) = parse_simulate_pad_extend("halt", vec![], vec![]); + + assert_eq!( + program_table::EXT_WIDTH, + master_ext_table.table(ProgramTable).ncols() + ); + assert_eq!( + instruction_table::EXT_WIDTH, + master_ext_table.table(InstructionTable).ncols() + ); + assert_eq!( + processor_table::EXT_WIDTH, + master_ext_table.table(ProcessorTable).ncols() + ); + assert_eq!( + op_stack_table::EXT_WIDTH, + master_ext_table.table(OpStackTable).ncols() + ); + assert_eq!( + ram_table::EXT_WIDTH, + master_ext_table.table(RamTable).ncols() + ); + assert_eq!( + jump_stack_table::EXT_WIDTH, + master_ext_table.table(JumpStackTable).ncols() + ); + assert_eq!( + hash_table::EXT_WIDTH, + master_ext_table.table(HashTable).ncols() + ); + // use some domain-specific knowledge to also check for the randomizer columns + assert_eq!( + stark.parameters.num_randomizer_polynomials, + master_ext_table + .master_ext_matrix + .slice(s![.., EXT_HASH_TABLE_END..]) + .ncols() + ); + } + + #[test] + fn zerofiers_are_correct_test() { + let big_order = 16; + let big_offset = BFieldElement::generator(); + let big_domain = ArithmeticDomain::new(big_offset, big_order as usize); + + let small_order = 8; + let small_domain = ArithmeticDomain::new_no_offset(small_order as usize); + + let initial_zerofier_inv = initial_quotient_zerofier_inverse(big_domain); + let initial_zerofier = BFieldElement::batch_inversion(initial_zerofier_inv.to_vec()); + let initial_zerofier_poly = big_domain.interpolate(&initial_zerofier); + assert_eq!(big_order as usize, initial_zerofier_inv.len()); + assert_eq!(1, initial_zerofier_poly.degree()); + assert!(initial_zerofier_poly + .evaluate(&small_domain.domain_value(0)) + .is_zero()); + + let consistency_zerofier_inv = + consistency_quotient_zerofier_inverse(small_domain, big_domain); + let consistency_zerofier = + BFieldElement::batch_inversion(consistency_zerofier_inv.to_vec()); + let consistency_zerofier_poly = big_domain.interpolate(&consistency_zerofier); + assert_eq!(big_order as usize, consistency_zerofier_inv.len()); + assert_eq!(small_order as isize, consistency_zerofier_poly.degree()); + for val in small_domain.domain_values() { + assert!(consistency_zerofier_poly.evaluate(&val).is_zero()); + } + + let transition_zerofier_inv = + transition_quotient_zerofier_inverse(small_domain, big_domain); + let transition_zerofier = BFieldElement::batch_inversion(transition_zerofier_inv.to_vec()); + let transition_zerofier_poly = big_domain.interpolate(&transition_zerofier); + assert_eq!(big_order as usize, transition_zerofier_inv.len()); + assert_eq!(small_order as isize - 1, transition_zerofier_poly.degree()); + for val in small_domain + .domain_values() + .iter() + .take(small_order as usize - 1) + { + assert!(transition_zerofier_poly.evaluate(val).is_zero()); + } + + let terminal_zerofier_inv = terminal_quotient_zerofier_inverse(small_domain, big_domain); + let terminal_zerofier = BFieldElement::batch_inversion(terminal_zerofier_inv.to_vec()); + let terminal_zerofier_poly = big_domain.interpolate(&terminal_zerofier); + assert_eq!(big_order as usize, terminal_zerofier_inv.len()); + assert_eq!(1, terminal_zerofier_poly.degree()); + assert!(terminal_zerofier_poly + .evaluate(&small_domain.domain_value(small_order as u32 - 1)) + .is_zero()); + } + + /// intended use: `cargo t print_all_table_widths -- --nocapture` + #[test] + fn print_all_table_widths() { + println!("| table name | #base cols | #ext cols | full width |"); + println!("|:-------------------|-----------:|----------:|-----------:|"); + println!( + "| {:<18} | {:>10} | {:>9} | {:>10} |", + "ProgramTable", + program_table::BASE_WIDTH, + program_table::EXT_WIDTH, + program_table::FULL_WIDTH + ); + println!( + "| {:<18} | {:>10} | {:>9} | {:>10} |", + "InstructionTable", + instruction_table::BASE_WIDTH, + instruction_table::EXT_WIDTH, + instruction_table::FULL_WIDTH + ); + println!( + "| {:<18} | {:>10} | {:>9} | {:>10} |", + "ProcessorTable", + processor_table::BASE_WIDTH, + processor_table::EXT_WIDTH, + processor_table::FULL_WIDTH + ); + println!( + "| {:<18} | {:>10} | {:>9} | {:>10} |", + "OpStackTable", + op_stack_table::BASE_WIDTH, + op_stack_table::EXT_WIDTH, + op_stack_table::FULL_WIDTH + ); + println!( + "| {:<18} | {:>10} | {:>9} | {:>10} |", + "RamTable", + ram_table::BASE_WIDTH, + ram_table::EXT_WIDTH, + ram_table::FULL_WIDTH + ); + println!( + "| {:<18} | {:>10} | {:>9} | {:>10} |", + "JumpStackTable", + jump_stack_table::BASE_WIDTH, + jump_stack_table::EXT_WIDTH, + jump_stack_table::FULL_WIDTH + ); + println!( + "| {:<18} | {:>10} | {:>9} | {:>10} |", + "HashTable", + hash_table::BASE_WIDTH, + hash_table::EXT_WIDTH, + hash_table::FULL_WIDTH + ); + println!("| | | | |"); + println!( + "| Sum | {:>10} | {:>9} | {:>10} |", + NUM_BASE_COLUMNS, NUM_EXT_COLUMNS, NUM_COLUMNS, + ); + } + + /// intended use: `cargo t print_all_master_table_indices -- --nocapture` + #[test] + fn print_all_master_table_indices() { + println!("idx | table | base column"); + println!("---:|:------------|:-----------"); + for column in ProgramBaseTableColumn::iter() { + println!( + "{:>3} | program | {column}", + column.master_base_table_index() + ); + } + for column in InstructionBaseTableColumn::iter() { + println!( + "{:>3} | instruction | {column}", + column.master_base_table_index() + ); + } + for column in ProcessorBaseTableColumn::iter() { + println!( + "{:>3} | processor | {column}", + column.master_base_table_index() + ); + } + for column in OpStackBaseTableColumn::iter() { + println!( + "{:>3} | op stack | {column}", + column.master_base_table_index() + ); + } + for column in RamBaseTableColumn::iter() { + println!( + "{:>3} | ram | {column}", + column.master_base_table_index() + ); + } + for column in JumpStackBaseTableColumn::iter() { + println!( + "{:>3} | jump stack | {column}", + column.master_base_table_index() + ); + } + for column in HashBaseTableColumn::iter() { + println!( + "{:>3} | hash | {column}", + column.master_base_table_index() + ); + } + println!(); + println!("idx | table | extension column"); + println!("---:|:------------|:----------------"); + for column in ProgramExtTableColumn::iter() { + println!( + "{:>3} | program | {column}", + column.master_ext_table_index() + ); + } + for column in InstructionExtTableColumn::iter() { + println!( + "{:>3} | instruction | {column}", + column.master_ext_table_index() + ); + } + for column in ProcessorExtTableColumn::iter() { + println!( + "{:>3} | processor | {column}", + column.master_ext_table_index() + ); + } + for column in OpStackExtTableColumn::iter() { + println!( + "{:>3} | op stack | {column}", + column.master_ext_table_index() + ); + } + for column in RamExtTableColumn::iter() { + println!( + "{:>3} | ram | {column}", + column.master_ext_table_index() + ); + } + for column in JumpStackExtTableColumn::iter() { + println!( + "{:>3} | jump stack | {column}", + column.master_ext_table_index() + ); + } + for column in HashExtTableColumn::iter() { + println!( + "{:>3} | hash | {column}", + column.master_ext_table_index() + ); + } + } +} diff --git a/triton-vm/src/table/op_stack_table.rs b/triton-vm/src/table/op_stack_table.rs index 812ae42a1..29ffb5efd 100644 --- a/triton-vm/src/table/op_stack_table.rs +++ b/triton-vm/src/table/op_stack_table.rs @@ -1,140 +1,79 @@ -use itertools::Itertools; +use std::cmp::Ordering; + +use ndarray::parallel::prelude::*; +use ndarray::s; +use ndarray::Array1; +use ndarray::ArrayView1; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use ndarray::Axis; use num_traits::One; +use num_traits::Zero; use strum::EnumCount; -use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter}; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::traits::Inverse; use twenty_first::shared_math::x_field_element::XFieldElement; use OpStackTableChallengeId::*; -use crate::cross_table_arguments::{CrossTableArg, PermArg}; -use crate::table::base_table::Extendable; +use crate::op_stack::OP_STACK_REG_COUNT; +use crate::table::challenges::TableChallenges; +use crate::table::constraint_circuit::ConstraintCircuit; +use crate::table::constraint_circuit::ConstraintCircuitBuilder; +use crate::table::constraint_circuit::DualRowIndicator; +use crate::table::constraint_circuit::DualRowIndicator::*; use crate::table::constraint_circuit::SingleRowIndicator; -use crate::table::constraint_circuit::SingleRowIndicator::Row; -use crate::table::table_column::OpStackBaseTableColumn::{self, *}; -use crate::table::table_column::OpStackExtTableColumn::{self, *}; - -use super::base_table::{InheritsFromTable, Table, TableLike}; -use super::challenges::TableChallenges; -use super::constraint_circuit::DualRowIndicator::*; -use super::constraint_circuit::{ConstraintCircuit, ConstraintCircuitBuilder, DualRowIndicator}; -use super::extension_table::{ExtensionTable, QuotientableExtensionTable}; +use crate::table::constraint_circuit::SingleRowIndicator::*; +use crate::table::cross_table_argument::CrossTableArg; +use crate::table::cross_table_argument::PermArg; +use crate::table::master_table::NUM_BASE_COLUMNS; +use crate::table::master_table::NUM_EXT_COLUMNS; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::ExtTableColumn; +use crate::table::table_column::MasterBaseTableColumn; +use crate::table::table_column::MasterExtTableColumn; +use crate::table::table_column::OpStackBaseTableColumn; +use crate::table::table_column::OpStackBaseTableColumn::*; +use crate::table::table_column::OpStackExtTableColumn; +use crate::table::table_column::OpStackExtTableColumn::*; +use crate::table::table_column::ProcessorBaseTableColumn; +use crate::vm::AlgebraicExecutionTrace; pub const OP_STACK_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 1; pub const OP_STACK_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 0; - -/// This is 4 because it combines: clk, ci, osv, osp -pub const OP_STACK_TABLE_NUM_EXTENSION_CHALLENGES: usize = 4; +pub const OP_STACK_TABLE_NUM_EXTENSION_CHALLENGES: usize = OpStackTableChallengeId::COUNT; pub const BASE_WIDTH: usize = OpStackBaseTableColumn::COUNT; -pub const FULL_WIDTH: usize = BASE_WIDTH + OpStackExtTableColumn::COUNT; +pub const EXT_WIDTH: usize = OpStackExtTableColumn::COUNT; +pub const FULL_WIDTH: usize = BASE_WIDTH + EXT_WIDTH; #[derive(Debug, Clone)] -pub struct OpStackTable { - inherited_table: Table, -} - -impl InheritsFromTable for OpStackTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} +pub struct OpStackTable {} #[derive(Debug, Clone)] -pub struct ExtOpStackTable { - pub(crate) inherited_table: Table, -} - -impl Default for ExtOpStackTable { - fn default() -> Self { - Self { - inherited_table: Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "EmptyExtOpStackTable".to_string(), - ), - } - } -} - -impl QuotientableExtensionTable for ExtOpStackTable {} - -impl InheritsFromTable for ExtOpStackTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} - -impl TableLike for OpStackTable {} - -impl Extendable for OpStackTable { - fn get_padding_rows(&self) -> (Option, Vec>) { - panic!("This function should not be called: the Op Stack Table implements `.pad` directly.") - } - - fn pad(&mut self, padded_height: usize) { - let max_clock = self.data().len() as u64 - 1; - let num_padding_rows = padded_height - self.data().len(); - - let template_index = self - .data() - .iter() - .enumerate() - .find(|(_, row)| row[usize::from(CLK)].value() == max_clock) - .map(|(idx, _)| idx) - .expect("Op Stack Table must contain row with clock cycle equal to max cycle."); - let insertion_index = template_index + 1; - - let padding_template = &mut self.mut_data()[template_index]; - padding_template[usize::from(InverseOfClkDiffMinusOne)] = 0_u64.into(); - - let mut padding_rows = vec![]; - while padding_rows.len() < num_padding_rows { - let mut padding_row = padding_template.clone(); - padding_row[usize::from(CLK)] += (padding_rows.len() as u32 + 1).into(); - padding_rows.push(padding_row) - } - - if let Some(row) = padding_rows.last_mut() { - if let Some(next_row) = self.data().get(insertion_index) { - let clk_diff = next_row[usize::from(CLK)] - row[usize::from(CLK)]; - row[usize::from(InverseOfClkDiffMinusOne)] = - (clk_diff - BFieldElement::one()).inverse_or_zero(); - } - } - - let old_tail_length = self.data().len() - insertion_index; - self.mut_data().append(&mut padding_rows); - self.mut_data()[insertion_index..].rotate_left(old_tail_length); - - assert_eq!(padded_height, self.data().len()); - } -} - -impl TableLike for ExtOpStackTable {} +pub struct ExtOpStackTable {} impl ExtOpStackTable { - pub fn ext_initial_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_initial_constraints_as_circuits() -> Vec< + ConstraintCircuit< + OpStackTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1_u32.into()); - let clk = circuit_builder.input(Row(CLK.into())); - let ib1 = circuit_builder.input(Row(IB1ShrinkStack.into())); - let osp = circuit_builder.input(Row(OSP.into())); - let osv = circuit_builder.input(Row(OSV.into())); - let rppa = circuit_builder.input(Row(RunningProductPermArg.into())); - let rpcjd = circuit_builder.input(Row(AllClockJumpDifferencesPermArg.into())); + let clk = circuit_builder.input(BaseRow(CLK.master_base_table_index())); + let ib1 = circuit_builder.input(BaseRow(IB1ShrinkStack.master_base_table_index())); + let osp = circuit_builder.input(BaseRow(OSP.master_base_table_index())); + let osv = circuit_builder.input(BaseRow(OSV.master_base_table_index())); + let rppa = circuit_builder.input(ExtRow(RunningProductPermArg.master_ext_table_index())); + let rpcjd = circuit_builder.input(ExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); let clk_is_0 = clk; let osv_is_0 = osv; @@ -163,34 +102,53 @@ impl ExtOpStackTable { .to_vec() } - pub fn ext_consistency_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_consistency_constraints_as_circuits() -> Vec< + ConstraintCircuit< + OpStackTableChallenges, + SingleRowIndicator, + >, + > { // no further constraints vec![] } - pub fn ext_transition_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_transition_constraints_as_circuits() -> Vec< + ConstraintCircuit< + OpStackTableChallenges, + DualRowIndicator, + >, + > { let circuit_builder = ConstraintCircuitBuilder::< OpStackTableChallenges, - DualRowIndicator, - >::new(2 * FULL_WIDTH); + DualRowIndicator, + >::new(); let one = circuit_builder.b_constant(1u32.into()); - let clk = circuit_builder.input(CurrentRow(CLK.into())); - let ib1_shrink_stack = circuit_builder.input(CurrentRow(IB1ShrinkStack.into())); - let osp = circuit_builder.input(CurrentRow(OSP.into())); - let osv = circuit_builder.input(CurrentRow(OSV.into())); - let clk_di = circuit_builder.input(CurrentRow(InverseOfClkDiffMinusOne.into())); - let rpcjd = circuit_builder.input(CurrentRow(AllClockJumpDifferencesPermArg.into())); - let rppa = circuit_builder.input(CurrentRow(RunningProductPermArg.into())); - - let clk_next = circuit_builder.input(NextRow(CLK.into())); - let ib1_shrink_stack_next = circuit_builder.input(NextRow(IB1ShrinkStack.into())); - let osp_next = circuit_builder.input(NextRow(OSP.into())); - let osv_next = circuit_builder.input(NextRow(OSV.into())); - let rpcjd_next = circuit_builder.input(NextRow(AllClockJumpDifferencesPermArg.into())); - let rppa_next = circuit_builder.input(NextRow(RunningProductPermArg.into())); + let clk = circuit_builder.input(CurrentBaseRow(CLK.master_base_table_index())); + let ib1_shrink_stack = + circuit_builder.input(CurrentBaseRow(IB1ShrinkStack.master_base_table_index())); + let osp = circuit_builder.input(CurrentBaseRow(OSP.master_base_table_index())); + let osv = circuit_builder.input(CurrentBaseRow(OSV.master_base_table_index())); + let clk_di = circuit_builder.input(CurrentBaseRow( + InverseOfClkDiffMinusOne.master_base_table_index(), + )); + let rpcjd = circuit_builder.input(CurrentExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); + let rppa = circuit_builder.input(CurrentExtRow( + RunningProductPermArg.master_ext_table_index(), + )); + + let clk_next = circuit_builder.input(NextBaseRow(CLK.master_base_table_index())); + let ib1_shrink_stack_next = + circuit_builder.input(NextBaseRow(IB1ShrinkStack.master_base_table_index())); + let osp_next = circuit_builder.input(NextBaseRow(OSP.master_base_table_index())); + let osv_next = circuit_builder.input(NextBaseRow(OSV.master_base_table_index())); + let rpcjd_next = circuit_builder.input(NextExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); + let rppa_next = + circuit_builder.input(NextExtRow(RunningProductPermArg.master_ext_table_index())); // the osp increases by 1 or the osp does not change // @@ -256,101 +214,207 @@ impl ExtOpStackTable { .to_vec() } - pub fn ext_terminal_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_terminal_constraints_as_circuits() -> Vec< + ConstraintCircuit< + OpStackTableChallenges, + SingleRowIndicator, + >, + > { // no further constraints vec![] } } impl OpStackTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } - } + /// Fills the trace table in-place and returns all clock jump differences greater than 1. + pub fn fill_trace( + op_stack_table: &mut ArrayViewMut2, + aet: &AlgebraicExecutionTrace, + ) -> Vec { + // Store the registers relevant for the Op Stack Table, i.e., CLK, IB1, OSP, and OSV, + // with OSP as the key. Preserves, thus allows reusing, the order of the processor's + // rows, which are sorted by CLK. + let mut pre_processed_op_stack_table: Vec> = vec![]; + for processor_row in aet.processor_matrix.rows() { + let clk = processor_row[ProcessorBaseTableColumn::CLK.base_table_index()]; + let ib1 = processor_row[ProcessorBaseTableColumn::IB1.base_table_index()]; + let osp = processor_row[ProcessorBaseTableColumn::OSP.base_table_index()]; + let osv = processor_row[ProcessorBaseTableColumn::OSV.base_table_index()]; + // The (honest) prover can only grow the Op Stack's size by at most 1 per execution + // step. Hence, the following (a) works, and (b) sorts. + let osp_minus_16 = osp.value() as usize - OP_STACK_REG_COUNT; + let op_stack_row = (clk, ib1, osv); + match osp_minus_16.cmp(&pre_processed_op_stack_table.len()) { + Ordering::Less => pre_processed_op_stack_table[osp_minus_16].push(op_stack_row), + Ordering::Equal => pre_processed_op_stack_table.push(vec![op_stack_row]), + Ordering::Greater => panic!("OSP must increase by at most 1 per execution step."), + } + } - pub fn new_prover(matrix: Vec>) -> Self { - let inherited_table = - Table::new(BASE_WIDTH, FULL_WIDTH, matrix, "OpStackTable".to_string()); - Self { inherited_table } + // Move the rows into the Op Stack Table, sorted by OSP first, CLK second. + let mut op_stack_table_row = 0; + for (osp_minus_16, rows_with_this_osp) in + pre_processed_op_stack_table.into_iter().enumerate() + { + let osp = BFieldElement::new((osp_minus_16 + OP_STACK_REG_COUNT) as u64); + for (clk, ib1, osv) in rows_with_this_osp { + op_stack_table[[op_stack_table_row, CLK.base_table_index()]] = clk; + op_stack_table[[op_stack_table_row, IB1ShrinkStack.base_table_index()]] = ib1; + op_stack_table[[op_stack_table_row, OSP.base_table_index()]] = osp; + op_stack_table[[op_stack_table_row, OSV.base_table_index()]] = osv; + op_stack_table_row += 1; + } + } + assert_eq!(aet.processor_matrix.nrows(), op_stack_table_row); + + // Set inverse of (clock difference - 1). Also, collect all clock jump differences + // greater than 1. + // The Op Stack Table and the Processor Table have the same length. + let mut clock_jump_differences_greater_than_1 = vec![]; + for row_idx in 0..aet.processor_matrix.nrows() - 1 { + let (mut curr_row, next_row) = + op_stack_table.multi_slice_mut((s![row_idx, ..], s![row_idx + 1, ..])); + let clk_diff = next_row[CLK.base_table_index()] - curr_row[CLK.base_table_index()]; + let clk_diff_minus_1 = clk_diff - BFieldElement::one(); + let clk_diff_minus_1_inverse = clk_diff_minus_1.inverse_or_zero(); + curr_row[InverseOfClkDiffMinusOne.base_table_index()] = clk_diff_minus_1_inverse; + + if curr_row[OSP.base_table_index()] == next_row[OSP.base_table_index()] + && clk_diff.value() > 1 + { + clock_jump_differences_greater_than_1.push(clk_diff); + } + } + clock_jump_differences_greater_than_1 } - pub fn extend(&self, challenges: &OpStackTableChallenges) -> ExtOpStackTable { - let mut extension_matrix: Vec> = Vec::with_capacity(self.data().len()); - let mut running_product = PermArg::default_initial(); - let mut all_clock_jump_differences_running_product = PermArg::default_initial(); - - let mut previous_row: Option> = None; - for row in self.data().iter() { - let mut extension_row = [0.into(); FULL_WIDTH]; - extension_row[..BASE_WIDTH] - .copy_from_slice(&row.iter().map(|elem| elem.lift()).collect_vec()); - - let clk = extension_row[usize::from(CLK)]; - let ib1 = extension_row[usize::from(IB1ShrinkStack)]; - let osp = extension_row[usize::from(OSP)]; - let osv = extension_row[usize::from(OSV)]; + pub fn pad_trace( + op_stack_table: &mut ArrayViewMut2, + processor_table_len: usize, + ) { + assert!( + processor_table_len > 0, + "Processor Table must have at least 1 row." + ); - let clk_w = challenges.clk_weight; - let ib1_w = challenges.ib1_weight; - let osp_w = challenges.osp_weight; - let osv_w = challenges.osv_weight; + // Set up indices for relevant sections of the table. + let padded_height = op_stack_table.nrows(); + let num_padding_rows = padded_height - processor_table_len; + let max_clk_before_padding = processor_table_len - 1; + let max_clk_before_padding_row_idx = op_stack_table + .rows() + .into_iter() + .enumerate() + .find(|(_, row)| row[CLK.base_table_index()].value() as usize == max_clk_before_padding) + .map(|(idx, _)| idx) + .expect("Op Stack Table must contain row with clock cycle equal to max cycle."); + let rows_to_move_source_section_start = max_clk_before_padding_row_idx + 1; + let rows_to_move_source_section_end = processor_table_len; + let num_rows_to_move = rows_to_move_source_section_end - rows_to_move_source_section_start; + let rows_to_move_dest_section_start = rows_to_move_source_section_start + num_padding_rows; + let rows_to_move_dest_section_end = rows_to_move_dest_section_start + num_rows_to_move; + let padding_section_start = rows_to_move_source_section_start; + let padding_section_end = padding_section_start + num_padding_rows; + assert_eq!(padded_height, rows_to_move_dest_section_end); + + // Move all rows below the row with highest CLK to the end of the table – if they exist. + if num_rows_to_move > 0 { + let rows_to_move_source_range = + rows_to_move_source_section_start..rows_to_move_source_section_end; + let rows_to_move_dest_range = + rows_to_move_dest_section_start..rows_to_move_dest_section_end; + let rows_to_move = op_stack_table + .slice(s![rows_to_move_source_range, ..]) + .to_owned(); + rows_to_move.move_into(&mut op_stack_table.slice_mut(s![rows_to_move_dest_range, ..])); + } - // compress multiple values within one row so they become one value - let compressed_row_for_permutation_argument = - clk * clk_w + ib1 * ib1_w + osp * osp_w + osv * osv_w; + // Fill the created gap with padding rows, i.e., with (adjusted) copies of the last row + // before the gap. This is the padding section. + let mut padding_row_template = op_stack_table + .row(max_clk_before_padding_row_idx) + .to_owned(); + padding_row_template[InverseOfClkDiffMinusOne.base_table_index()] = BFieldElement::zero(); + let mut padding_section = + op_stack_table.slice_mut(s![padding_section_start..padding_section_end, ..]); + padding_section + .axis_iter_mut(Axis(0)) + .into_par_iter() + .for_each(|padding_row| padding_row_template.clone().move_into(padding_row)); + + // CLK keeps increasing by 1 also in the padding section. + let new_clk_values = Array1::from_iter( + (processor_table_len..padded_height).map(|clk| BFieldElement::new(clk as u64)), + ); + new_clk_values.move_into(padding_section.slice_mut(s![.., CLK.base_table_index()])); + + // InverseOfClkDiffMinusOne must be consistent at the padding section's boundaries. + op_stack_table[[ + max_clk_before_padding_row_idx, + InverseOfClkDiffMinusOne.base_table_index(), + ]] = BFieldElement::zero(); + if num_rows_to_move > 0 && rows_to_move_dest_section_start > 0 { + let max_clk_after_padding = padded_height - 1; + let clk_diff_minus_one_at_padding_section_lower_boundary = op_stack_table + [[rows_to_move_dest_section_start, CLK.base_table_index()]] + - BFieldElement::new(max_clk_after_padding as u64) + - BFieldElement::one(); + let last_row_in_padding_section_idx = rows_to_move_dest_section_start - 1; + op_stack_table[[ + last_row_in_padding_section_idx, + InverseOfClkDiffMinusOne.base_table_index(), + ]] = clk_diff_minus_one_at_padding_section_lower_boundary.inverse_or_zero(); + } + } - // compute the running *product* of the compressed column (for permutation argument) + pub fn extend( + base_table: ArrayView2, + mut ext_table: ArrayViewMut2, + challenges: &OpStackTableChallenges, + ) { + assert_eq!(BASE_WIDTH, base_table.ncols()); + assert_eq!(EXT_WIDTH, ext_table.ncols()); + assert_eq!(base_table.nrows(), ext_table.nrows()); + let mut running_product = PermArg::default_initial(); + let mut all_clock_jump_differences_running_product = PermArg::default_initial(); + let mut previous_row: Option> = None; + + for row_idx in 0..base_table.nrows() { + let current_row = base_table.row(row_idx); + let clk = current_row[CLK.base_table_index()]; + let ib1 = current_row[IB1ShrinkStack.base_table_index()]; + let osp = current_row[OSP.base_table_index()]; + let osv = current_row[OSV.base_table_index()]; + + let compressed_row_for_permutation_argument = clk * challenges.clk_weight + + ib1 * challenges.ib1_weight + + osp * challenges.osp_weight + + osv * challenges.osv_weight; running_product *= challenges.processor_perm_indeterminate - compressed_row_for_permutation_argument; - extension_row[usize::from(RunningProductPermArg)] = running_product; // clock jump difference - if let Some(prow) = previous_row { - if prow[usize::from(OSP)] == row[usize::from(OSP)] { + if let Some(prev_row) = previous_row { + if prev_row[OSP.base_table_index()] == current_row[OSP.base_table_index()] { let clock_jump_difference = - (row[usize::from(CLK)] - prow[usize::from(CLK)]).lift(); - if clock_jump_difference != XFieldElement::one() { + current_row[CLK.base_table_index()] - prev_row[CLK.base_table_index()]; + if !clock_jump_difference.is_one() { all_clock_jump_differences_running_product *= challenges .all_clock_jump_differences_multi_perm_indeterminate - clock_jump_difference; } } } - extension_row[usize::from(AllClockJumpDifferencesPermArg)] = - all_clock_jump_differences_running_product; - previous_row = Some(row.clone()); - extension_matrix.push(extension_row.to_vec()); - } - - assert_eq!(self.data().len(), extension_matrix.len()); - let inherited_table = self.new_from_lifted_matrix(extension_matrix); - ExtOpStackTable { inherited_table } - } - - pub fn for_verifier() -> ExtOpStackTable { - let inherited_table = Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "ExtOpStackTable".to_string(), - ); - let base_table = Self { inherited_table }; - let empty_matrix: Vec> = vec![]; - let extension_table = base_table.new_from_lifted_matrix(empty_matrix); - - ExtOpStackTable { - inherited_table: extension_table, + let mut extension_row = ext_table.row_mut(row_idx); + extension_row[RunningProductPermArg.ext_table_index()] = running_product; + extension_row[AllClockJumpDifferencesPermArg.ext_table_index()] = + all_clock_jump_differences_running_product; + previous_row = Some(current_row); } } } -impl ExtOpStackTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } - } -} - #[derive(Debug, Copy, Clone, Display, EnumCountMacro, EnumIter, PartialEq, Eq, Hash)] pub enum OpStackTableChallengeId { ProcessorPermIndeterminate, @@ -400,5 +464,3 @@ pub struct OpStackTableChallenges { /// Weight for accumulating all clock jump differences pub all_clock_jump_differences_multi_perm_indeterminate: XFieldElement, } - -impl ExtensionTable for ExtOpStackTable {} diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 781d12560..8c2ae7271 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -1,159 +1,222 @@ use std::cmp::max; use std::cmp::Eq; use std::collections::HashMap; +use std::fmt::Display; use std::ops::Mul; use itertools::Itertools; -use num_traits::{One, Zero}; +use ndarray::parallel::prelude::*; +use ndarray::s; +use ndarray::Array1; +use ndarray::ArrayView1; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use ndarray::Axis; +use num_traits::One; +use num_traits::Zero; use strum::EnumCount; -use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter}; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; -use twenty_first::shared_math::mpolynomial::MPolynomial; +use twenty_first::shared_math::traits::Inverse; use twenty_first::shared_math::x_field_element::XFieldElement; use ProcessorTableChallengeId::*; -use crate::cross_table_arguments::{CrossTableArg, EvalArg, PermArg}; -use crate::instruction::{all_instructions_without_args, AnInstruction::*, Instruction}; +use crate::instruction::all_instructions_without_args; +use crate::instruction::AnInstruction::*; +use crate::instruction::Instruction; use crate::ord_n::Ord7; -use crate::table::base_table::{Extendable, InheritsFromTable, Table, TableLike}; -use crate::table::constraint_circuit::{InputIndicator, SingleRowIndicator}; -use crate::table::extension_table::ExtensionTable; -use crate::table::table_column::ProcessorBaseTableColumn::{self, *}; -use crate::table::table_column::ProcessorExtTableColumn::{self, *}; - -use super::challenges::TableChallenges; -use super::constraint_circuit::{ - ConstraintCircuit, ConstraintCircuitBuilder, ConstraintCircuitMonad, DualRowIndicator, -}; -use super::extension_table::QuotientableExtensionTable; +use crate::table::challenges::TableChallenges; +use crate::table::constraint_circuit::ConstraintCircuit; +use crate::table::constraint_circuit::ConstraintCircuitBuilder; +use crate::table::constraint_circuit::ConstraintCircuitMonad; +use crate::table::constraint_circuit::DualRowIndicator; +use crate::table::constraint_circuit::InputIndicator; +use crate::table::constraint_circuit::SingleRowIndicator; +use crate::table::cross_table_argument::CrossTableArg; +use crate::table::cross_table_argument::EvalArg; +use crate::table::cross_table_argument::PermArg; +use crate::table::master_table::NUM_BASE_COLUMNS; +use crate::table::master_table::NUM_EXT_COLUMNS; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::ExtTableColumn; +use crate::table::table_column::MasterBaseTableColumn; +use crate::table::table_column::MasterExtTableColumn; +use crate::table::table_column::ProcessorBaseTableColumn; +use crate::table::table_column::ProcessorBaseTableColumn::*; +use crate::table::table_column::ProcessorExtTableColumn; +use crate::table::table_column::ProcessorExtTableColumn::*; +use crate::vm::AlgebraicExecutionTrace; pub const PROCESSOR_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 5; pub const PROCESSOR_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 5; - -/// This is 43 because it combines all other tables (except program). -pub const PROCESSOR_TABLE_NUM_EXTENSION_CHALLENGES: usize = 43; +pub const PROCESSOR_TABLE_NUM_EXTENSION_CHALLENGES: usize = ProcessorTableChallengeId::COUNT; pub const BASE_WIDTH: usize = ProcessorBaseTableColumn::COUNT; -pub const FULL_WIDTH: usize = BASE_WIDTH + ProcessorExtTableColumn::COUNT; +pub const EXT_WIDTH: usize = ProcessorExtTableColumn::COUNT; +pub const FULL_WIDTH: usize = BASE_WIDTH + EXT_WIDTH; #[derive(Debug, Clone)] -pub struct ProcessorTable { - inherited_table: Table, -} +pub struct ProcessorTable {} -impl InheritsFromTable for ProcessorTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } +impl ProcessorTable { + pub fn fill_trace( + processor_table: &mut ArrayViewMut2, + aet: &AlgebraicExecutionTrace, + mut all_clk_jump_diffs: Vec, + ) { + // fill the processor table from the AET + let mut processor_table_to_fill = + processor_table.slice_mut(s![0..aet.processor_matrix.nrows(), ..]); + aet.processor_matrix + .clone() + .move_into(&mut processor_table_to_fill); - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} + let zero = BFieldElement::zero(); + all_clk_jump_diffs.sort_by_key(|bfe| std::cmp::Reverse(bfe.value())); -impl ProcessorTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } + let mut previous_row: Option> = None; + for mut row in processor_table_to_fill.rows_mut() { + // add all clock jump differences and their inverses + let clk_jump_difference = all_clk_jump_diffs.pop().unwrap_or(zero); + let clk_jump_difference_inv = clk_jump_difference.inverse_or_zero(); + row[ClockJumpDifference.base_table_index()] = clk_jump_difference; + row[ClockJumpDifferenceInverse.base_table_index()] = clk_jump_difference_inv; + + // add inverses of unique clock jump difference differences + if let Some(prow) = previous_row { + let previous_clk_jump_difference = prow[ClockJumpDifference.base_table_index()]; + if previous_clk_jump_difference != clk_jump_difference { + let clk_diff_diff: BFieldElement = + clk_jump_difference - previous_clk_jump_difference; + let clk_diff_diff_inv = clk_diff_diff.inverse(); + row[UniqueClockJumpDiffDiffInverse.base_table_index()] = clk_diff_diff_inv; + } + } + + previous_row = Some(row.to_owned()); + } + + assert!( + all_clk_jump_diffs.is_empty(), + "Processor Table must record all clock jump differences, but didn't have enough space \ + for the remaining {}.", + all_clk_jump_diffs.len() + ); } - pub fn new_prover(matrix: Vec>) -> Self { - let inherited_table = - Table::new(BASE_WIDTH, FULL_WIDTH, matrix, "ProcessorTable".to_string()); - Self { inherited_table } + pub fn pad_trace( + processor_table: &mut ArrayViewMut2, + processor_table_len: usize, + ) { + assert!( + processor_table_len > 0, + "Processor Table must have at least one row." + ); + let mut padding_template = processor_table.row(processor_table_len - 1).to_owned(); + padding_template[IsPadding.base_table_index()] = BFieldElement::one(); + processor_table + .slice_mut(s![processor_table_len.., ..]) + .axis_iter_mut(Axis(0)) + .into_par_iter() + .for_each(|mut row| row.assign(&padding_template)); + + let clk_range = processor_table_len..processor_table.nrows(); + let clk_col = Array1::from_iter(clk_range.map(|a| BFieldElement::new(a as u64))); + clk_col.move_into( + processor_table.slice_mut(s![processor_table_len.., CLK.base_table_index()]), + ); } - pub fn extend(&self, challenges: &ProcessorTableChallenges) -> ExtProcessorTable { + pub fn extend( + base_table: ArrayView2, + mut ext_table: ArrayViewMut2, + challenges: &ProcessorTableChallenges, + ) { + assert_eq!(BASE_WIDTH, base_table.ncols()); + assert_eq!(EXT_WIDTH, ext_table.ncols()); + assert_eq!(base_table.nrows(), ext_table.nrows()); let mut unique_clock_jump_differences = vec![]; - let mut extension_matrix: Vec> = Vec::with_capacity(self.data().len()); let mut input_table_running_evaluation = EvalArg::default_initial(); let mut output_table_running_evaluation = EvalArg::default_initial(); let mut instruction_table_running_product = PermArg::default_initial(); - let mut opstack_table_running_product = PermArg::default_initial(); + let mut op_stack_table_running_product = PermArg::default_initial(); let mut ram_table_running_product = PermArg::default_initial(); let mut jump_stack_running_product = PermArg::default_initial(); let mut to_hash_table_running_evaluation = EvalArg::default_initial(); let mut from_hash_table_running_evaluation = EvalArg::default_initial(); - let mut selected_clock_cycles_running_evaluation = EvalArg::default_initial(); let mut unique_clock_jump_differences_running_evaluation = EvalArg::default_initial(); let mut all_clock_jump_differences_running_product = PermArg::default_initial() * PermArg::default_initial() * PermArg::default_initial(); - let mut previous_row: Option> = None; - for row in self.data().iter() { - let mut extension_row = [0.into(); FULL_WIDTH]; - extension_row[..BASE_WIDTH] - .copy_from_slice(&row.iter().map(|elem| elem.lift()).collect_vec()); + let mut previous_row: Option> = None; + for row_idx in 0..base_table.nrows() { + let current_row = base_table.row(row_idx); // Input table - if let Some(prow) = previous_row.clone() { - if prow[usize::from(CI)] == Instruction::ReadIo.opcode_b() { - let input_symbol = extension_row[usize::from(ST0)]; + if let Some(prev_row) = previous_row { + if prev_row[CI.base_table_index()] == Instruction::ReadIo.opcode_b() { + let input_symbol = current_row[ST0.base_table_index()]; input_table_running_evaluation = input_table_running_evaluation * challenges.standard_input_eval_indeterminate + input_symbol; } } - extension_row[usize::from(InputTableEvalArg)] = input_table_running_evaluation; // Output table - if row[usize::from(CI)] == Instruction::WriteIo.opcode_b() { - let output_symbol = extension_row[usize::from(ST0)]; + if current_row[CI.base_table_index()] == Instruction::WriteIo.opcode_b() { + let output_symbol = current_row[ST0.base_table_index()]; output_table_running_evaluation = output_table_running_evaluation * challenges.standard_output_eval_indeterminate + output_symbol; } - extension_row[usize::from(OutputTableEvalArg)] = output_table_running_evaluation; // Instruction table - let ip = extension_row[usize::from(IP)]; - let ci = extension_row[usize::from(CI)]; - let nia = extension_row[usize::from(NIA)]; - - let ip_w = challenges.instruction_table_ip_weight; - let ci_w = challenges.instruction_table_ci_processor_weight; - let nia_w = challenges.instruction_table_nia_weight; - - if row[usize::from(IsPadding)].is_zero() { - let compressed_row_for_instruction_table_permutation_argument = - ip * ip_w + ci * ci_w + nia * nia_w; + if current_row[IsPadding.base_table_index()].is_zero() { + let ip = current_row[IP.base_table_index()]; + let ci = current_row[CI.base_table_index()]; + let nia = current_row[NIA.base_table_index()]; + let compressed_row_for_instruction_table_permutation_argument = ip + * challenges.instruction_table_ip_weight + + ci * challenges.instruction_table_ci_processor_weight + + nia * challenges.instruction_table_nia_weight; instruction_table_running_product *= challenges.instruction_perm_indeterminate - compressed_row_for_instruction_table_permutation_argument; } - extension_row[usize::from(InstructionTablePermArg)] = instruction_table_running_product; // OpStack table - let clk = extension_row[usize::from(CLK)]; - let ib1 = extension_row[usize::from(IB1)]; - let osp = extension_row[usize::from(OSP)]; - let osv = extension_row[usize::from(OSV)]; - + let clk = current_row[CLK.base_table_index()]; + let ib1 = current_row[IB1.base_table_index()]; + let osp = current_row[OSP.base_table_index()]; + let osv = current_row[OSV.base_table_index()]; let compressed_row_for_op_stack_table_permutation_argument = clk * challenges.op_stack_table_clk_weight + ib1 * challenges.op_stack_table_ib1_weight + osp * challenges.op_stack_table_osp_weight + osv * challenges.op_stack_table_osv_weight; - opstack_table_running_product *= challenges.op_stack_perm_indeterminate + op_stack_table_running_product *= challenges.op_stack_perm_indeterminate - compressed_row_for_op_stack_table_permutation_argument; - extension_row[usize::from(OpStackTablePermArg)] = opstack_table_running_product; // RAM Table - let ramv = extension_row[usize::from(RAMV)]; - let ramp = extension_row[usize::from(RAMP)]; - + let ramv = current_row[RAMV.base_table_index()]; + let ramp = current_row[RAMP.base_table_index()]; + let previous_instruction = current_row[PreviousInstruction.base_table_index()]; let compressed_row_for_ram_table_permutation_argument = clk * challenges.ram_table_clk_weight + ramv * challenges.ram_table_ramv_weight - + ramp * challenges.ram_table_ramp_weight; + + ramp * challenges.ram_table_ramp_weight + + previous_instruction * challenges.ram_table_previous_instruction_weight; ram_table_running_product *= challenges.ram_perm_indeterminate - compressed_row_for_ram_table_permutation_argument; - extension_row[usize::from(RamTablePermArg)] = ram_table_running_product; // JumpStack Table - let jsp = extension_row[usize::from(JSP)]; - let jso = extension_row[usize::from(JSO)]; - let jsd = extension_row[usize::from(JSD)]; + let ci = current_row[CI.base_table_index()]; + let jsp = current_row[JSP.base_table_index()]; + let jso = current_row[JSO.base_table_index()]; + let jsd = current_row[JSD.base_table_index()]; let compressed_row_for_jump_stack_table = clk * challenges.jump_stack_table_clk_weight + ci * challenges.jump_stack_table_ci_weight + jsp * challenges.jump_stack_table_jsp_weight @@ -161,69 +224,62 @@ impl ProcessorTable { + jsd * challenges.jump_stack_table_jsd_weight; jump_stack_running_product *= challenges.jump_stack_perm_indeterminate - compressed_row_for_jump_stack_table; - extension_row[usize::from(JumpStackTablePermArg)] = jump_stack_running_product; - // Hash Table – Hash's input from Processor to Hash Coprocessor - if row[usize::from(CI)] == Instruction::Hash.opcode_b() { + if current_row[CI.base_table_index()] == Instruction::Hash.opcode_b() { let st_0_through_9 = [ - extension_row[usize::from(ST0)], - extension_row[usize::from(ST1)], - extension_row[usize::from(ST2)], - extension_row[usize::from(ST3)], - extension_row[usize::from(ST4)], - extension_row[usize::from(ST5)], - extension_row[usize::from(ST6)], - extension_row[usize::from(ST7)], - extension_row[usize::from(ST8)], - extension_row[usize::from(ST9)], + current_row[ST0.base_table_index()], + current_row[ST1.base_table_index()], + current_row[ST2.base_table_index()], + current_row[ST3.base_table_index()], + current_row[ST4.base_table_index()], + current_row[ST5.base_table_index()], + current_row[ST6.base_table_index()], + current_row[ST7.base_table_index()], + current_row[ST8.base_table_index()], + current_row[ST9.base_table_index()], + ]; + let hash_table_stack_input_challenges = [ + challenges.hash_table_stack_input_weight0, + challenges.hash_table_stack_input_weight1, + challenges.hash_table_stack_input_weight2, + challenges.hash_table_stack_input_weight3, + challenges.hash_table_stack_input_weight4, + challenges.hash_table_stack_input_weight5, + challenges.hash_table_stack_input_weight6, + challenges.hash_table_stack_input_weight7, + challenges.hash_table_stack_input_weight8, + challenges.hash_table_stack_input_weight9, ]; let compressed_row_for_hash_input: XFieldElement = st_0_through_9 .into_iter() - .zip_eq( - [ - challenges.hash_table_stack_input_weight0, - challenges.hash_table_stack_input_weight1, - challenges.hash_table_stack_input_weight2, - challenges.hash_table_stack_input_weight3, - challenges.hash_table_stack_input_weight4, - challenges.hash_table_stack_input_weight5, - challenges.hash_table_stack_input_weight6, - challenges.hash_table_stack_input_weight7, - challenges.hash_table_stack_input_weight8, - challenges.hash_table_stack_input_weight9, - ] - .into_iter(), - ) + .zip_eq(hash_table_stack_input_challenges.into_iter()) .map(|(st, weight)| weight * st) .sum(); to_hash_table_running_evaluation = to_hash_table_running_evaluation * challenges.to_hash_table_eval_indeterminate + compressed_row_for_hash_input; } - extension_row[usize::from(ToHashTableEvalArg)] = to_hash_table_running_evaluation; // Hash Table – Hash's output from Hash Coprocessor to Processor - if let Some(prow) = previous_row.clone() { - if prow[usize::from(CI)] == Instruction::Hash.opcode_b() { + if let Some(prev_row) = previous_row { + if prev_row[CI.base_table_index()] == Instruction::Hash.opcode_b() { let st_5_through_9 = [ - extension_row[usize::from(ST5)], - extension_row[usize::from(ST6)], - extension_row[usize::from(ST7)], - extension_row[usize::from(ST8)], - extension_row[usize::from(ST9)], + current_row[ST5.base_table_index()], + current_row[ST6.base_table_index()], + current_row[ST7.base_table_index()], + current_row[ST8.base_table_index()], + current_row[ST9.base_table_index()], + ]; + let hash_table_digest_output_challenges = [ + challenges.hash_table_digest_output_weight0, + challenges.hash_table_digest_output_weight1, + challenges.hash_table_digest_output_weight2, + challenges.hash_table_digest_output_weight3, + challenges.hash_table_digest_output_weight4, ]; let compressed_row_for_hash_digest: XFieldElement = st_5_through_9 .into_iter() - .zip_eq( - [ - challenges.hash_table_digest_output_weight0, - challenges.hash_table_digest_output_weight1, - challenges.hash_table_digest_output_weight2, - challenges.hash_table_digest_output_weight3, - challenges.hash_table_digest_output_weight4, - ] - .into_iter(), - ) + .zip_eq(hash_table_digest_output_challenges.into_iter()) .map(|(st, weight)| weight * st) .sum(); from_hash_table_running_evaluation = from_hash_table_running_evaluation @@ -231,45 +287,59 @@ impl ProcessorTable { + compressed_row_for_hash_digest; } } - extension_row[usize::from(FromHashTableEvalArg)] = from_hash_table_running_evaluation; // Clock Jump Difference - let current_clock_jump_difference = row[usize::from(ClockJumpDifference)].lift(); + let current_clock_jump_difference = current_row[ClockJumpDifference.base_table_index()]; if !current_clock_jump_difference.is_zero() { all_clock_jump_differences_running_product *= challenges .all_clock_jump_differences_multi_perm_indeterminate - current_clock_jump_difference; } - extension_row[usize::from(AllClockJumpDifferencesPermArg)] = - all_clock_jump_differences_running_product; - if let Some(prow) = previous_row { - let previous_clock_jump_difference = prow[usize::from(ClockJumpDifference)].lift(); - if previous_clock_jump_difference != current_clock_jump_difference - && !current_clock_jump_difference.is_zero() - { - unique_clock_jump_differences.push(current_clock_jump_difference); - unique_clock_jump_differences_running_evaluation = - unique_clock_jump_differences_running_evaluation - * challenges.unique_clock_jump_differences_eval_indeterminate - + current_clock_jump_difference; - } - } else if !current_clock_jump_difference.is_zero() { + // Update the running evaluation of unique clock jump differences if and only if + // 1. the current clock jump difference is not 0, and either + // 2a. there is no previous row, or + // 2b. the previous clock jump difference is different from the current one. + // We can merge (2a) and (2b) by setting the “previous” clock jump difference to + // anything unequal to the current clock jump difference if no previous row exists. + let previous_clock_jump_difference = if let Some(prev_row) = previous_row { + prev_row[ClockJumpDifference.base_table_index()] + } else { + current_clock_jump_difference - BFieldElement::one() + }; + if !current_clock_jump_difference.is_zero() + && previous_clock_jump_difference != current_clock_jump_difference + { unique_clock_jump_differences.push(current_clock_jump_difference); - unique_clock_jump_differences_running_evaluation = challenges - .unique_clock_jump_differences_eval_indeterminate - + current_clock_jump_difference; + unique_clock_jump_differences_running_evaluation = + unique_clock_jump_differences_running_evaluation + * challenges.unique_clock_jump_differences_eval_indeterminate + + current_clock_jump_difference; } - extension_row[usize::from(UniqueClockJumpDifferencesEvalArg)] = - unique_clock_jump_differences_running_evaluation; - previous_row = Some(row.clone()); - extension_matrix.push(extension_row.to_vec()); + let mut extension_row = ext_table.row_mut(row_idx); + extension_row[InputTableEvalArg.ext_table_index()] = input_table_running_evaluation; + extension_row[OutputTableEvalArg.ext_table_index()] = output_table_running_evaluation; + extension_row[InstructionTablePermArg.ext_table_index()] = + instruction_table_running_product; + extension_row[OpStackTablePermArg.ext_table_index()] = op_stack_table_running_product; + extension_row[RamTablePermArg.ext_table_index()] = ram_table_running_product; + extension_row[JumpStackTablePermArg.ext_table_index()] = jump_stack_running_product; + extension_row[ToHashTableEvalArg.ext_table_index()] = to_hash_table_running_evaluation; + extension_row[FromHashTableEvalArg.ext_table_index()] = + from_hash_table_running_evaluation; + extension_row[AllClockJumpDifferencesPermArg.ext_table_index()] = + all_clock_jump_differences_running_product; + extension_row[UniqueClockJumpDifferencesEvalArg.ext_table_index()] = + unique_clock_jump_differences_running_evaluation; + previous_row = Some(current_row); } + // pre-process the unique clock jump differences for faster accesses later + unique_clock_jump_differences.sort_by_key(|&bfe| bfe.value()); + unique_clock_jump_differences.reverse(); if std::env::var("DEBUG").is_ok() { let mut unique_clock_jump_differences_copy = unique_clock_jump_differences.clone(); - unique_clock_jump_differences_copy.sort_by_key(|xfe| xfe.unlift().unwrap().value()); unique_clock_jump_differences_copy.dedup(); assert_eq!( unique_clock_jump_differences_copy, @@ -277,46 +347,36 @@ impl ProcessorTable { ); } - // second pass over Processor Table to compute evaluation over - // all relevant clock cycles - for extension_row in extension_matrix.iter_mut() { - let current_clk = extension_row[usize::from(CLK)]; - if unique_clock_jump_differences.contains(¤t_clk) { + // second pass over Processor Table to compute evaluation over all relevant clock cycles + let mut selected_clock_cycles_running_evaluation = EvalArg::default_initial(); + for row_idx in 0..base_table.nrows() { + let current_clk = base_table[[row_idx, CLK.base_table_index()]]; + let mut extension_row = ext_table.row_mut(row_idx); + if unique_clock_jump_differences.last() == Some(¤t_clk) { + unique_clock_jump_differences.pop(); selected_clock_cycles_running_evaluation = selected_clock_cycles_running_evaluation * challenges.unique_clock_jump_differences_eval_indeterminate + current_clk; } - extension_row[usize::from(SelectedClockCyclesEvalArg)] = + extension_row[SelectedClockCyclesEvalArg.ext_table_index()] = selected_clock_cycles_running_evaluation; } - assert_eq!(self.data().len(), extension_matrix.len()); - let inherited_table = self.new_from_lifted_matrix(extension_matrix); - ExtProcessorTable { inherited_table } - } - - pub fn for_verifier() -> ExtProcessorTable { - let inherited_table = Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "ExtProcessorTable".to_string(), + assert!( + unique_clock_jump_differences.is_empty(), + "Unhandled unique clock jump differences: {unique_clock_jump_differences:?}" + ); + assert_eq!( + unique_clock_jump_differences_running_evaluation, + selected_clock_cycles_running_evaluation, + "Even though all unique clock jump differences were handled, the running evaluation of \ + unique clock jump differences is not equal to the running evaluation of selected \ + clock cycles." ); - let base_table = Self { inherited_table }; - let empty_matrix: Vec> = vec![]; - let extension_table = base_table.new_from_lifted_matrix(empty_matrix); - - ExtProcessorTable { - inherited_table: extension_table, - } } } impl ExtProcessorTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } - } - /// Instruction-specific transition constraints are combined with deselectors in such a way /// that arbitrary sets of mutually exclusive combinations are summed, i.e., /// @@ -336,9 +396,19 @@ impl ExtProcessorTable { factory: &mut DualRowConstraints, instr_tc_polys_tuples: [( Instruction, - Vec>>, + Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + >, ); Instruction::COUNT], - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let (all_instructions, all_tc_polys_for_all_instructions): (Vec<_>, Vec>) = instr_tc_polys_tuples.into_iter().unzip(); @@ -381,9 +451,17 @@ impl ExtProcessorTable { fn combine_transition_constraints_with_padding_constraints( factory: &DualRowConstraints, instruction_transition_constraints: Vec< - ConstraintCircuitMonad>, + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, >, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let ip_remains = factory.ip_next() - factory.ip(); let ci_remains = factory.ci_next() - factory.ci(); let nia_remains = factory.nia_next() - factory.nia(); @@ -448,6 +526,7 @@ pub enum ProcessorTableChallengeId { RamTableClkWeight, RamTableRamvWeight, RamTableRampWeight, + RamTablePreviousInstructionWeight, JumpStackTableClkWeight, JumpStackTableCiWeight, @@ -511,6 +590,7 @@ pub struct ProcessorTableChallenges { pub ram_table_clk_weight: XFieldElement, pub ram_table_ramp_weight: XFieldElement, pub ram_table_ramv_weight: XFieldElement, + pub ram_table_previous_instruction_weight: XFieldElement, pub jump_stack_table_clk_weight: XFieldElement, pub jump_stack_table_ci_weight: XFieldElement, @@ -565,6 +645,7 @@ impl TableChallenges for ProcessorTableChallenges { RamTableClkWeight => self.ram_table_clk_weight, RamTableRamvWeight => self.ram_table_ramv_weight, RamTableRampWeight => self.ram_table_ramp_weight, + RamTablePreviousInstructionWeight => self.ram_table_previous_instruction_weight, JumpStackTableClkWeight => self.jump_stack_table_clk_weight, JumpStackTableCiWeight => self.jump_stack_table_ci_weight, JumpStackTableJspWeight => self.jump_stack_table_jsp_weight, @@ -602,59 +683,15 @@ pub struct IOChallenges { } #[derive(Debug, Clone)] -pub struct ExtProcessorTable { - pub(crate) inherited_table: Table, -} - -impl QuotientableExtensionTable for ExtProcessorTable {} - -impl InheritsFromTable for ExtProcessorTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} - -impl Default for ExtProcessorTable { - fn default() -> Self { - Self { - inherited_table: Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "EmptyExtProcessorTable".to_string(), - ), - } - } -} - -impl TableLike for ProcessorTable {} - -impl Extendable for ProcessorTable { - fn get_padding_rows(&self) -> (Option, Vec>) { - let zero = BFieldElement::zero(); - let one = BFieldElement::one(); - if let Some(row) = self.data().last() { - let mut padding_row = row.clone(); - padding_row[usize::from(CLK)] += one; - padding_row[usize::from(IsPadding)] = one; - (None, vec![padding_row]) - } else { - let mut padding_row = vec![zero; BASE_WIDTH]; - padding_row[usize::from(IsPadding)] = one; - (None, vec![padding_row]) - } - } -} - -impl TableLike for ExtProcessorTable {} +pub struct ExtProcessorTable {} impl ExtProcessorTable { - pub fn ext_initial_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_initial_constraints_as_circuits() -> Vec< + ConstraintCircuit< + ProcessorTableChallenges, + SingleRowIndicator, + >, + > { use ProcessorTableChallengeId::*; let factory = SingleRowConstraints::default(); @@ -687,6 +724,7 @@ impl ExtProcessorTable { let osv_is_0 = factory.osv(); let ramv_is_0 = factory.ramv(); let ramp_is_0 = factory.ramp(); + let previous_instruction = factory.previous_instruction(); // The running evaluation of relevant clock cycles `rer` starts with the initial. let rer_starts_correctly = factory.rer() - constant_x(EvalArg::default_initial()); @@ -812,6 +850,7 @@ impl ExtProcessorTable { osv_is_0, ramv_is_0, ramp_is_0, + previous_instruction, rer_starts_correctly, reu_starts_correctly, rpm_starts_correctly, @@ -828,8 +867,12 @@ impl ExtProcessorTable { .to_vec() } - pub fn ext_consistency_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_consistency_constraints_as_circuits() -> Vec< + ConstraintCircuit< + ProcessorTableChallenges, + SingleRowIndicator, + >, + > { let factory = SingleRowConstraints::default(); let one = factory.one(); let constant = |c| factory.constant_from_i32(c); @@ -876,8 +919,12 @@ impl ExtProcessorTable { .to_vec() } - pub fn ext_transition_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_transition_constraints_as_circuits() -> Vec< + ConstraintCircuit< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let mut factory = DualRowConstraints::default(); // instruction-specific constraints @@ -927,6 +974,7 @@ impl ExtProcessorTable { // constraints common to all instructions transition_constraints.insert(0, factory.clk_always_increases_by_one()); transition_constraints.insert(1, factory.is_padding_is_zero_or_does_not_change()); + transition_constraints.insert(2, factory.previous_instruction_is_copied_correctly()); // constraints related to clock jump difference argument @@ -1010,8 +1058,12 @@ impl ExtProcessorTable { built_transition_constraints } - pub fn ext_terminal_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_terminal_constraints_as_circuits() -> Vec< + ConstraintCircuit< + ProcessorTableChallenges, + SingleRowIndicator, + >, + > { let factory = SingleRowConstraints::default(); // In the last row, current instruction register ci is 0, corresponding to instruction halt. @@ -1028,33 +1080,48 @@ impl ExtProcessorTable { #[derive(Debug, Clone)] pub struct SingleRowConstraints { - variables: [ConstraintCircuitMonad>; - FULL_WIDTH], - circuit_builder: - ConstraintCircuitBuilder>, - one: ConstraintCircuitMonad>, - two: ConstraintCircuitMonad>, + base_row_variables: [ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + >; NUM_BASE_COLUMNS], + ext_row_variables: [ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + >; NUM_EXT_COLUMNS], + circuit_builder: ConstraintCircuitBuilder< + ProcessorTableChallenges, + SingleRowIndicator, + >, + one: ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + >, + two: ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + >, } impl Default for SingleRowConstraints { fn default() -> Self { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); - let variables_as_circuits = (0..FULL_WIDTH) - .map(|i| { - let input: SingleRowIndicator = i.into(); - circuit_builder.input(input) - }) - .collect_vec(); - - let variables = variables_as_circuits + let circuit_builder = ConstraintCircuitBuilder::new(); + let base_row_variables = (0..NUM_BASE_COLUMNS) + .map(|i| circuit_builder.input(SingleRowIndicator::BaseRow(i))) + .collect_vec() + .try_into() + .expect("Create variables for single base row constraints"); + let ext_row_variables = (0..NUM_EXT_COLUMNS) + .map(|i| circuit_builder.input(SingleRowIndicator::ExtRow(i))) + .collect_vec() .try_into() - .expect("Create variables for single row constraints"); + .expect("Create variables for single ext row constraints"); let one = circuit_builder.b_constant(1u32.into()); let two = circuit_builder.b_constant(2u32.into()); Self { - variables, + base_row_variables, + ext_row_variables, circuit_builder, one, two, @@ -1066,13 +1133,19 @@ impl SingleRowConstraints { pub fn constant_from_xfe( &self, constant: XFieldElement, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { self.circuit_builder.x_constant(constant) } pub fn constant_from_i32( &self, constant: i32, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { let bfe = if constant < 0 { BFieldElement::new(BFieldElement::QUOTIENT - ((-constant) as u64)) } else { @@ -1083,319 +1156,530 @@ impl SingleRowConstraints { pub fn one( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { self.one.clone() } pub fn two( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { self.two.clone() } pub fn clk( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(CLK)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[CLK.master_base_table_index()].clone() } pub fn is_padding( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IsPadding)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IsPadding.master_base_table_index()].clone() } pub fn ip( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IP.master_base_table_index()].clone() } pub fn ci( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(CI)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[CI.master_base_table_index()].clone() } pub fn nia( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(NIA)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[NIA.master_base_table_index()].clone() } pub fn ib0( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB0)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IB0.master_base_table_index()].clone() } pub fn ib1( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB1)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IB1.master_base_table_index()].clone() } pub fn ib2( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB2)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IB2.master_base_table_index()].clone() } pub fn ib3( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB3)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IB3.master_base_table_index()].clone() } pub fn ib4( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB4)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IB4.master_base_table_index()].clone() } pub fn ib5( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB5)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IB5.master_base_table_index()].clone() } pub fn ib6( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB6)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[IB6.master_base_table_index()].clone() } pub fn jsp( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(JSP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[JSP.master_base_table_index()].clone() } pub fn jsd( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(JSD)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[JSD.master_base_table_index()].clone() } pub fn jso( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(JSO)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[JSO.master_base_table_index()].clone() } pub fn st0( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST0)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST0.master_base_table_index()].clone() } pub fn st1( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST1)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST1.master_base_table_index()].clone() } pub fn st2( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST2)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST2.master_base_table_index()].clone() } pub fn st3( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST3)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST3.master_base_table_index()].clone() } pub fn st4( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST4)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST4.master_base_table_index()].clone() } pub fn st5( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST5)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST5.master_base_table_index()].clone() } pub fn st6( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST6)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST6.master_base_table_index()].clone() } pub fn st7( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST7)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST7.master_base_table_index()].clone() } pub fn st8( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST8)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST8.master_base_table_index()].clone() } pub fn st9( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST9)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST9.master_base_table_index()].clone() } pub fn st10( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST10)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST10.master_base_table_index()].clone() } pub fn st11( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST11)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST11.master_base_table_index()].clone() } pub fn st12( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST12)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST12.master_base_table_index()].clone() } pub fn st13( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST13)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST13.master_base_table_index()].clone() } pub fn st14( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST14)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST14.master_base_table_index()].clone() } pub fn st15( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST15)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ST15.master_base_table_index()].clone() } pub fn osp( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(OSP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[OSP.master_base_table_index()].clone() } pub fn osv( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(OSV)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[OSV.master_base_table_index()].clone() } pub fn hv0( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(HV0)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[HV0.master_base_table_index()].clone() } pub fn hv1( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(HV1)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[HV1.master_base_table_index()].clone() } pub fn hv2( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(HV2)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[HV2.master_base_table_index()].clone() } pub fn hv3( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(HV3)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[HV3.master_base_table_index()].clone() } pub fn ramv( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(RAMV)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[RAMV.master_base_table_index()].clone() } pub fn ramp( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(RAMP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[RAMP.master_base_table_index()].clone() + } + pub fn previous_instruction( + &self, + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[PreviousInstruction.master_base_table_index()].clone() } pub fn cjd( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ClockJumpDifference)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ClockJumpDifference.master_base_table_index()].clone() } pub fn invm( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ClockJumpDifferenceInverse)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[ClockJumpDifferenceInverse.master_base_table_index()].clone() } pub fn invu( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(UniqueClockJumpDiffDiffInverse)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.base_row_variables[UniqueClockJumpDiffDiffInverse.master_base_table_index()].clone() } pub fn rer( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(SelectedClockCyclesEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[SelectedClockCyclesEvalArg.master_ext_table_index()].clone() } pub fn reu( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(UniqueClockJumpDifferencesEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[UniqueClockJumpDifferencesEvalArg.master_ext_table_index()].clone() } pub fn rpm( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(AllClockJumpDifferencesPermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[AllClockJumpDifferencesPermArg.master_ext_table_index()].clone() } pub fn running_evaluation_standard_input( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(InputTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[InputTableEvalArg.master_ext_table_index()].clone() } pub fn running_evaluation_standard_output( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(OutputTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[OutputTableEvalArg.master_ext_table_index()].clone() } pub fn running_product_instruction_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(InstructionTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[InstructionTablePermArg.master_ext_table_index()].clone() } pub fn running_product_op_stack_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(OpStackTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[OpStackTablePermArg.master_ext_table_index()].clone() } pub fn running_product_ram_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(RamTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[RamTablePermArg.master_ext_table_index()].clone() } pub fn running_product_jump_stack_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(JumpStackTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[JumpStackTablePermArg.master_ext_table_index()].clone() } pub fn running_evaluation_to_hash_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ToHashTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[ToHashTableEvalArg.master_ext_table_index()].clone() } pub fn running_evaluation_from_hash_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(FromHashTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[FromHashTableEvalArg.master_ext_table_index()].clone() } } #[derive(Debug, Clone)] pub struct DualRowConstraints { - variables: [ConstraintCircuitMonad>; - 2 * FULL_WIDTH], - circuit_builder: - ConstraintCircuitBuilder>, - zero: ConstraintCircuitMonad>, - one: ConstraintCircuitMonad>, - two: ConstraintCircuitMonad>, + current_base_row_variables: [ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >; NUM_BASE_COLUMNS], + current_ext_row_variables: [ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >; NUM_EXT_COLUMNS], + next_base_row_variables: [ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >; NUM_BASE_COLUMNS], + next_ext_row_variables: [ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >; NUM_EXT_COLUMNS], + circuit_builder: ConstraintCircuitBuilder< + ProcessorTableChallenges, + DualRowIndicator, + >, + zero: ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + one: ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + two: ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, } impl Default for DualRowConstraints { fn default() -> Self { - let circuit_builder = ConstraintCircuitBuilder::new(2 * FULL_WIDTH); - let variables_as_circuits = (0..2 * FULL_WIDTH) - .map(|i| { - let input: DualRowIndicator = i.into(); - circuit_builder.input(input) - }) - .collect_vec(); - - let variables = variables_as_circuits + let circuit_builder = ConstraintCircuitBuilder::new(); + let current_base_row_variables = (0..NUM_BASE_COLUMNS) + .map(|i| circuit_builder.input(DualRowIndicator::CurrentBaseRow(i))) + .collect_vec() + .try_into() + .expect("Create variables for dual rows – current base row"); + let current_ext_row_variables = (0..NUM_EXT_COLUMNS) + .map(|i| circuit_builder.input(DualRowIndicator::CurrentExtRow(i))) + .collect_vec() .try_into() - .expect("Create variables for transition constraints"); + .expect("Create variables for dual rows – current ext row"); + let next_base_row_variables = (0..NUM_BASE_COLUMNS) + .map(|i| circuit_builder.input(DualRowIndicator::NextBaseRow(i))) + .collect_vec() + .try_into() + .expect("Create variables for dual rows – next base row"); + let next_ext_row_variables = (0..NUM_EXT_COLUMNS) + .map(|i| circuit_builder.input(DualRowIndicator::NextExtRow(i))) + .collect_vec() + .try_into() + .expect("Create variables for dual rows – next ext row"); let zero = circuit_builder.b_constant(0u32.into()); let one = circuit_builder.b_constant(1u32.into()); let two = circuit_builder.b_constant(2u32.into()); Self { - variables, + current_base_row_variables, + current_ext_row_variables, + next_base_row_variables, + next_ext_row_variables, circuit_builder, zero, one, @@ -1421,7 +1705,10 @@ impl DualRowConstraints { /// when every `clk` register $a$ is one less than `clk` register $a + 1$. pub fn clk_always_increases_by_one( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let one = self.one(); let clk = self.clk(); let clk_next = self.clk_next(); @@ -1431,14 +1718,29 @@ impl DualRowConstraints { pub fn is_padding_is_zero_or_does_not_change( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { self.is_padding() * (self.is_padding_next() - self.is_padding()) } + pub fn previous_instruction_is_copied_correctly( + &self, + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + (self.previous_instruction_next() - self.ci()) * (self.one() - self.is_padding_next()) + } + pub fn indicator_polynomial( &self, i: usize, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let hv0 = self.hv0(); let hv1 = self.hv1(); let hv2 = self.hv2(); @@ -1461,16 +1763,18 @@ impl DualRowConstraints { 13 => hv3 * hv2 * (self.one() - hv1) * hv0, 14 => hv3 * hv2 * hv1 * (self.one() - hv0), 15 => hv3 * hv2 * hv1 * hv0, - _ => panic!( - "No indicator polynomial with index {} exists: there are only 16.", - i - ), + _ => panic!("No indicator polynomial with index {i} exists: there are only 16."), } } pub fn instruction_pop( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { [self.step_1(), self.shrink_stack(), self.keep_ram()].concat() } @@ -1478,7 +1782,12 @@ impl DualRowConstraints { /// $st0_next == nia => st0_next - nia == 0$ pub fn instruction_push( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![self.st0_next() - self.nia()]; [ specific_constraints, @@ -1491,13 +1800,23 @@ impl DualRowConstraints { pub fn instruction_divine( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { [self.step_1(), self.grow_stack(), self.keep_ram()].concat() } pub fn instruction_dup( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![ self.indicator_polynomial(0) * (self.st0_next() - self.st0()), self.indicator_polynomial(1) * (self.st0_next() - self.st1()), @@ -1528,7 +1847,12 @@ impl DualRowConstraints { pub fn instruction_swap( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![ self.indicator_polynomial(0), self.indicator_polynomial(1) * (self.st1_next() - self.st0()), @@ -1590,13 +1914,23 @@ impl DualRowConstraints { pub fn instruction_nop( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { [self.step_1(), self.keep_stack(), self.keep_ram()].concat() } pub fn instruction_skiz( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The next instruction nia is decomposed into helper variables hv. let nia_decomposes_to_hvs = self.nia() - (self.hv0() + self.two() * self.hv1()); @@ -1634,7 +1968,12 @@ impl DualRowConstraints { pub fn instruction_call( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The jump stack pointer jsp is incremented by 1. let jsp_incr_1 = self.jsp_next() - (self.jsp() + self.one()); @@ -1658,7 +1997,12 @@ impl DualRowConstraints { pub fn instruction_return( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The jump stack pointer jsp is decremented by 1. let jsp_incr_1 = self.jsp_next() - (self.jsp() - self.one()); @@ -1671,7 +2015,12 @@ impl DualRowConstraints { pub fn instruction_recurse( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The instruction pointer ip is set to the last jump's destination jsd. let ip_becomes_jsd = self.ip_next() - self.jsd(); let specific_constraints = vec![ip_becomes_jsd]; @@ -1686,7 +2035,12 @@ impl DualRowConstraints { pub fn instruction_assert( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The current top of the stack st0 is 1. let st_0_is_1 = self.st0() - self.one(); @@ -1702,7 +2056,12 @@ impl DualRowConstraints { pub fn instruction_halt( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The instruction executed in the following step is instruction halt. let halt_is_followed_by_halt = self.ci_next() - self.ci(); @@ -1718,12 +2077,17 @@ impl DualRowConstraints { pub fn instruction_read_mem( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // the RAM pointer is overwritten with st1 let update_ramp = self.ramp_next() - self.st1(); // The top of the stack is overwritten with the RAM value. - let st0_becomes_ramv = self.st0_next() - self.ramv(); + let st0_becomes_ramv = self.st0_next() - self.ramv_next(); let specific_constraints = vec![update_ramp, st0_becomes_ramv]; [specific_constraints, self.step_1(), self.unop()].concat() @@ -1731,7 +2095,12 @@ impl DualRowConstraints { pub fn instruction_write_mem( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // the RAM pointer is overwritten with st1 let update_ramp = self.ramp_next() - self.st1(); @@ -1745,7 +2114,12 @@ impl DualRowConstraints { /// Two Evaluation Arguments with the Hash Table guarantee correct transition. pub fn instruction_hash( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { [ self.step_1(), self.stack_remains_and_top_ten_elements_unconstrained(), @@ -1760,7 +2134,12 @@ impl DualRowConstraints { /// st10 mod 2. The second polynomial sets the new value of st10 to st10 div 2. pub fn instruction_divine_sibling( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // Helper variable hv0 is either 0 or 1. let hv0_is_0_or_1 = self.hv0() * (self.hv0() - self.one()); @@ -1799,7 +2178,12 @@ impl DualRowConstraints { pub fn instruction_assert_vector( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![ // Register st0 is equal to st5. self.st5() - self.st0(), @@ -1824,7 +2208,12 @@ impl DualRowConstraints { /// $st0' - (st0 + st1) = 0$ pub fn instruction_add( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![self.st0_next() - (self.st0() + self.st1())]; [ specific_constraints, @@ -1840,7 +2229,12 @@ impl DualRowConstraints { /// $st0' - (st0 * st1) = 0$ pub fn instruction_mul( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![self.st0_next() - (self.st0() * self.st1())]; [ specific_constraints, @@ -1856,7 +2250,12 @@ impl DualRowConstraints { /// $st0'·st0 - 1 = 0$ pub fn instruction_invert( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![self.st0_next() * self.st0() - self.one()]; [ specific_constraints, @@ -1869,7 +2268,12 @@ impl DualRowConstraints { pub fn instruction_split( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let two_pow_32 = self.constant_b(BFieldElement::new(1_u64 << 32)); // The top of the stack is decomposed as 32-bit chunks into the stack's top-most elements. @@ -1908,7 +2312,12 @@ impl DualRowConstraints { pub fn instruction_eq( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // Helper variable hv0 is the inverse of the difference of the stack's two top-most elements or 0. // // $ hv0·(hv0·(st1 - st0) - 1) = 0 $ @@ -1945,10 +2354,15 @@ impl DualRowConstraints { /// 2. The operand decomposes into right-shifted operand and the lsb pub fn instruction_lsb( &self, - ) -> Vec>> { - let operand = self.variables[usize::from(ST0)].clone(); - let shifted_operand = self.variables[FULL_WIDTH + usize::from(ST1)].clone(); - let lsb = self.variables[FULL_WIDTH + usize::from(ST0)].clone(); + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { + let operand = self.current_base_row_variables[ST0.master_base_table_index()].clone(); + let shifted_operand = self.next_base_row_variables[ST1.master_base_table_index()].clone(); + let lsb = self.next_base_row_variables[ST0.master_base_table_index()].clone(); let lsb_is_a_bit = lsb.clone() * (lsb.clone() - self.one()); let correct_decomposition = self.two() * shifted_operand + lsb - operand; @@ -1965,7 +2379,12 @@ impl DualRowConstraints { pub fn instruction_xxadd( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The result of adding st0 to st3 is moved into st0. let st0_becomes_st0_plus_st3 = self.st0_next() - (self.st0() + self.st3()); @@ -1991,7 +2410,12 @@ impl DualRowConstraints { pub fn instruction_xxmul( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The coefficient of x^0 of multiplying the two X-Field elements on the stack is moved into st0. // // $st0' - (st0·st3 - st2·st4 - st1·st5)$ @@ -2031,7 +2455,12 @@ impl DualRowConstraints { pub fn instruction_xinv( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The coefficient of x^0 of multiplying X-Field element on top of the current stack and on top of the next stack is 1. // // $st0·st0' - st2·st1' - st1·st2' - 1 = 0$ @@ -2073,7 +2502,12 @@ impl DualRowConstraints { pub fn instruction_xbmul( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { // The result of multiplying the top of the stack with the X-Field element's coefficient for x^0 is moved into st0. // // st0' - st0·st1 @@ -2108,7 +2542,12 @@ impl DualRowConstraints { /// An Evaluation Argument with the list of input symbols guarantees correct transition. pub fn instruction_read_io( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { [self.step_1(), self.grow_stack(), self.keep_ram()].concat() } @@ -2117,643 +2556,994 @@ impl DualRowConstraints { /// An Evaluation Argument with the list of output symbols guarantees correct transition. pub fn instruction_write_io( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { [self.step_1(), self.shrink_stack(), self.keep_ram()].concat() } pub fn zero( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { self.zero.to_owned() } pub fn one( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { self.one.to_owned() } pub fn two( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { self.two.to_owned() } pub fn constant( &self, constant: u32, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { self.circuit_builder.b_constant(constant.into()) } pub fn constant_b( &self, constant: BFieldElement, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { self.circuit_builder.b_constant(constant) } - pub fn constant_x(&self, constant: XFieldElement) -> MPolynomial { - MPolynomial::from_constant(constant, 2 * FULL_WIDTH) - } - pub fn clk( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(CLK)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[CLK.master_base_table_index()].clone() } pub fn ip( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IP.master_base_table_index()].clone() } pub fn ci( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(CI)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[CI.master_base_table_index()].clone() } pub fn nia( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(NIA)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[NIA.master_base_table_index()].clone() } pub fn ib0( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB0)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IB0.master_base_table_index()].clone() } pub fn ib1( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB1)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IB1.master_base_table_index()].clone() } pub fn ib2( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB2)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IB2.master_base_table_index()].clone() } pub fn ib3( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB3)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IB3.master_base_table_index()].clone() } pub fn ib4( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB4)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IB4.master_base_table_index()].clone() } pub fn ib5( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB5)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IB5.master_base_table_index()].clone() } pub fn ib6( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IB6)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IB6.master_base_table_index()].clone() } pub fn jsp( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(JSP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[JSP.master_base_table_index()].clone() } pub fn jsd( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(JSD)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[JSD.master_base_table_index()].clone() } pub fn jso( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(JSO)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[JSO.master_base_table_index()].clone() } pub fn st0( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST0)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST0.master_base_table_index()].clone() } pub fn st1( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST1)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST1.master_base_table_index()].clone() } pub fn st2( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST2)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST2.master_base_table_index()].clone() } pub fn st3( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST3)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST3.master_base_table_index()].clone() } pub fn st4( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST4)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST4.master_base_table_index()].clone() } pub fn st5( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST5)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST5.master_base_table_index()].clone() } pub fn st6( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST6)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST6.master_base_table_index()].clone() } pub fn st7( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST7)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST7.master_base_table_index()].clone() } pub fn st8( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST8)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST8.master_base_table_index()].clone() } pub fn st9( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST9)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST9.master_base_table_index()].clone() } pub fn st10( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST10)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST10.master_base_table_index()].clone() } pub fn st11( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST11)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST11.master_base_table_index()].clone() } pub fn st12( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST12)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST12.master_base_table_index()].clone() } pub fn st13( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST13)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST13.master_base_table_index()].clone() } pub fn st14( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST14)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST14.master_base_table_index()].clone() } pub fn st15( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ST15)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ST15.master_base_table_index()].clone() } pub fn osp( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(OSP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[OSP.master_base_table_index()].clone() } pub fn osv( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(OSV)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[OSV.master_base_table_index()].clone() } pub fn hv0( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(HV0)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[HV0.master_base_table_index()].clone() } pub fn hv1( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(HV1)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[HV1.master_base_table_index()].clone() } pub fn hv2( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(HV2)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[HV2.master_base_table_index()].clone() } pub fn hv3( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(HV3)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[HV3.master_base_table_index()].clone() } pub fn ramp( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(RAMP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[RAMP.master_base_table_index()].clone() + } + + pub fn previous_instruction( + &self, + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[PreviousInstruction.master_base_table_index()].clone() } pub fn ramv( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(RAMV)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[RAMV.master_base_table_index()].clone() } pub fn is_padding( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(IsPadding)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[IsPadding.master_base_table_index()].clone() } pub fn cjd( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ClockJumpDifference)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ClockJumpDifference.master_base_table_index()].clone() } pub fn invm( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ClockJumpDifferenceInverse)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[ClockJumpDifferenceInverse.master_base_table_index()] + .clone() } pub fn invu( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(UniqueClockJumpDiffDiffInverse)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_base_row_variables[UniqueClockJumpDiffDiffInverse.master_base_table_index()] + .clone() } pub fn rer( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(SelectedClockCyclesEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[SelectedClockCyclesEvalArg.master_ext_table_index()].clone() } pub fn reu( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(UniqueClockJumpDifferencesEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[UniqueClockJumpDifferencesEvalArg.master_ext_table_index()] + .clone() } pub fn rpm( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(AllClockJumpDifferencesPermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[AllClockJumpDifferencesPermArg.master_ext_table_index()] + .clone() } pub fn running_evaluation_standard_input( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(InputTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[InputTableEvalArg.master_ext_table_index()].clone() } pub fn running_evaluation_standard_output( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(OutputTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[OutputTableEvalArg.master_ext_table_index()].clone() } pub fn running_product_instruction_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(InstructionTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[InstructionTablePermArg.master_ext_table_index()].clone() } pub fn running_product_op_stack_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(OpStackTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[OpStackTablePermArg.master_ext_table_index()].clone() } pub fn running_product_ram_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(RamTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[RamTablePermArg.master_ext_table_index()].clone() } pub fn running_product_jump_stack_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(JumpStackTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[JumpStackTablePermArg.master_ext_table_index()].clone() } pub fn running_evaluation_to_hash_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(ToHashTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[ToHashTableEvalArg.master_ext_table_index()].clone() } pub fn running_evaluation_from_hash_table( &self, - ) -> ConstraintCircuitMonad> { - self.variables[usize::from(FromHashTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[FromHashTableEvalArg.master_ext_table_index()].clone() } // Property: All polynomial variables that contain '_next' have the same - // variable position / value as the one without '_next', +/- FULL_WIDTH. + // variable position / value as the one without '_next', +/- NUM_COLUMNS. pub fn clk_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(CLK)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[CLK.master_base_table_index()].clone() } pub fn ip_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IP.master_base_table_index()].clone() } pub fn ci_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(CI)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[CI.master_base_table_index()].clone() } pub fn nia_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(NIA)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[NIA.master_base_table_index()].clone() } pub fn ib0_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IB0)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IB0.master_base_table_index()].clone() } pub fn ib1_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IB1)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IB1.master_base_table_index()].clone() } pub fn ib2_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IB2)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IB2.master_base_table_index()].clone() } pub fn ib3_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IB3)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IB3.master_base_table_index()].clone() } pub fn ib4_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IB4)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IB4.master_base_table_index()].clone() } pub fn ib5_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IB5)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IB5.master_base_table_index()].clone() } pub fn ib6_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IB6)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IB6.master_base_table_index()].clone() } pub fn jsp_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(JSP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[JSP.master_base_table_index()].clone() } pub fn jsd_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(JSD)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[JSD.master_base_table_index()].clone() } pub fn jso_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(JSO)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[JSO.master_base_table_index()].clone() } pub fn st0_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST0)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST0.master_base_table_index()].clone() } pub fn st1_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST1)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST1.master_base_table_index()].clone() } pub fn st2_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST2)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST2.master_base_table_index()].clone() } pub fn st3_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST3)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST3.master_base_table_index()].clone() } pub fn st4_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST4)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST4.master_base_table_index()].clone() } pub fn st5_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST5)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST5.master_base_table_index()].clone() } pub fn st6_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST6)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST6.master_base_table_index()].clone() } pub fn st7_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST7)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST7.master_base_table_index()].clone() } pub fn st8_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST8)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST8.master_base_table_index()].clone() } pub fn st9_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST9)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST9.master_base_table_index()].clone() } pub fn st10_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST10)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST10.master_base_table_index()].clone() } pub fn st11_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST11)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST11.master_base_table_index()].clone() } pub fn st12_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST12)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST12.master_base_table_index()].clone() } pub fn st13_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST13)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST13.master_base_table_index()].clone() } pub fn st14_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST14)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST14.master_base_table_index()].clone() } pub fn st15_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ST15)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ST15.master_base_table_index()].clone() } pub fn osp_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(OSP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[OSP.master_base_table_index()].clone() } pub fn osv_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(OSV)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[OSV.master_base_table_index()].clone() } pub fn ramp_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(RAMP)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[RAMP.master_base_table_index()].clone() + } + + pub fn previous_instruction_next( + &self, + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[PreviousInstruction.master_base_table_index()].clone() } pub fn ramv_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(RAMV)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[RAMV.master_base_table_index()].clone() } pub fn is_padding_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(IsPadding)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[IsPadding.master_base_table_index()].clone() } pub fn cjd_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ClockJumpDifference)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ClockJumpDifference.master_base_table_index()].clone() } pub fn invm_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ClockJumpDifferenceInverse)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[ClockJumpDifferenceInverse.master_base_table_index()].clone() } pub fn invu_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(UniqueClockJumpDiffDiffInverse)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_base_row_variables[UniqueClockJumpDiffDiffInverse.master_base_table_index()] + .clone() } pub fn rer_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(SelectedClockCyclesEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[SelectedClockCyclesEvalArg.master_ext_table_index()].clone() } pub fn reu_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(UniqueClockJumpDifferencesEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[UniqueClockJumpDifferencesEvalArg.master_ext_table_index()] + .clone() } pub fn rpm_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(AllClockJumpDifferencesPermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[AllClockJumpDifferencesPermArg.master_ext_table_index()].clone() } pub fn running_evaluation_standard_input_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(InputTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[InputTableEvalArg.master_ext_table_index()].clone() } pub fn running_evaluation_standard_output_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(OutputTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[OutputTableEvalArg.master_ext_table_index()].clone() } pub fn running_product_instruction_table_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(InstructionTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[InstructionTablePermArg.master_ext_table_index()].clone() } pub fn running_product_op_stack_table_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(OpStackTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[OpStackTablePermArg.master_ext_table_index()].clone() } pub fn running_product_ram_table_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(RamTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[RamTablePermArg.master_ext_table_index()].clone() } pub fn running_product_jump_stack_table_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(JumpStackTablePermArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[JumpStackTablePermArg.master_ext_table_index()].clone() } pub fn running_evaluation_to_hash_table_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(ToHashTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[ToHashTableEvalArg.master_ext_table_index()].clone() } pub fn running_evaluation_from_hash_table_next( &self, - ) -> ConstraintCircuitMonad> { - self.variables[FULL_WIDTH + usize::from(FromHashTableEvalArg)].clone() + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[FromHashTableEvalArg.master_ext_table_index()].clone() } pub fn decompose_arg( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let hv0_is_a_bit = self.hv0() * (self.hv0() - self.one()); let hv1_is_a_bit = self.hv1() * (self.hv1() - self.one()); let hv2_is_a_bit = self.hv2() * (self.hv2() - self.one()); @@ -2774,7 +3564,12 @@ impl DualRowConstraints { pub fn keep_jump_stack( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let jsp_does_not_change = self.jsp_next() - self.jsp(); let jso_does_not_change = self.jso_next() - self.jso(); let jsd_does_not_change = self.jsd_next() - self.jsd(); @@ -2787,7 +3582,12 @@ impl DualRowConstraints { pub fn step_1( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let instruction_pointer_increases_by_one = self.ip_next() - self.ip() - self.one(); let specific_constraints = vec![instruction_pointer_increases_by_one]; [specific_constraints, self.keep_jump_stack()].concat() @@ -2795,7 +3595,12 @@ impl DualRowConstraints { pub fn step_2( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let instruction_pointer_increases_by_two = self.ip_next() - self.ip() - self.two(); let specific_constraints = vec![instruction_pointer_increases_by_two]; [specific_constraints, self.keep_jump_stack()].concat() @@ -2803,7 +3608,12 @@ impl DualRowConstraints { pub fn grow_stack_and_top_two_elements_unconstrained( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { vec![ // The stack element in st1 is moved into st2. self.st2_next() - self.st1(), @@ -2830,7 +3640,12 @@ impl DualRowConstraints { pub fn grow_stack( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![ // The stack element in st0 is moved into st1. self.st1_next() - self.st0(), @@ -2844,7 +3659,12 @@ impl DualRowConstraints { pub fn stack_shrinks_and_top_three_elements_unconstrained( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { vec![ // The stack element in st4 is moved into st3. self.st3_next() - self.st4(), @@ -2872,7 +3692,12 @@ impl DualRowConstraints { pub fn binop( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![ // The stack element in st2 is moved into st1. self.st1_next() - self.st2(), @@ -2888,14 +3713,24 @@ impl DualRowConstraints { pub fn shrink_stack( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constrants = vec![self.st0_next() - self.st1()]; [specific_constrants, self.binop()].concat() } pub fn stack_remains_and_top_eleven_elements_unconstrained( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { vec![ self.st11_next() - self.st11(), self.st12_next() - self.st12(), @@ -2911,7 +3746,12 @@ impl DualRowConstraints { pub fn stack_remains_and_top_ten_elements_unconstrained( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![self.st10_next() - self.st10()]; [ specific_constraints, @@ -2922,7 +3762,12 @@ impl DualRowConstraints { pub fn stack_remains_and_top_three_elements_unconstrained( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![ self.st3_next() - self.st3(), self.st4_next() - self.st4(), @@ -2941,7 +3786,12 @@ impl DualRowConstraints { pub fn unop( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![ // The stack element in st1 does not change. self.st1_next() - self.st1(), @@ -2957,14 +3807,24 @@ impl DualRowConstraints { pub fn keep_stack( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let specific_constraints = vec![self.st0_next() - self.st0()]; [specific_constraints, self.unop()].concat() } pub fn keep_ram( &self, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { vec![ self.ramv_next() - self.ramv(), self.ramp_next() - self.ramp(), @@ -2973,7 +3833,10 @@ impl DualRowConstraints { pub fn running_evaluation_for_standard_input_updates_correctly( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let indeterminate = self .circuit_builder .challenge(StandardInputEvalIndeterminate); @@ -2993,7 +3856,10 @@ impl DualRowConstraints { pub fn running_product_for_instruction_table_updates_correctly( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let indeterminate = self.circuit_builder.challenge(InstructionPermIndeterminate); let ip_weight = self.circuit_builder.challenge(InstructionTableIpWeight); let ci_weight = self @@ -3013,7 +3879,10 @@ impl DualRowConstraints { pub fn running_evaluation_for_standard_output_updates_correctly( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let indeterminate = self .circuit_builder .challenge(StandardOutputEvalIndeterminate); @@ -3033,7 +3902,10 @@ impl DualRowConstraints { pub fn running_product_for_op_stack_table_updates_correctly( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let indeterminate = self.circuit_builder.challenge(OpStackPermIndeterminate); let clk_weight = self.circuit_builder.challenge(OpStackTableClkWeight); let ib1_weight = self.circuit_builder.challenge(OpStackTableIb1Weight); @@ -3050,14 +3922,21 @@ impl DualRowConstraints { pub fn running_product_for_ram_table_updates_correctly( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let indeterminate = self.circuit_builder.challenge(RamPermIndeterminate); let clk_weight = self.circuit_builder.challenge(RamTableClkWeight); let ramp_weight = self.circuit_builder.challenge(RamTableRampWeight); let ramv_weight = self.circuit_builder.challenge(RamTableRamvWeight); + let previous_instruction_weight = self + .circuit_builder + .challenge(RamTablePreviousInstructionWeight); let compressed_row = clk_weight * self.clk_next() + ramp_weight * self.ramp_next() - + ramv_weight * self.ramv_next(); + + ramv_weight * self.ramv_next() + + previous_instruction_weight * self.previous_instruction_next(); self.running_product_ram_table_next() - self.running_product_ram_table() * (indeterminate - compressed_row) @@ -3065,7 +3944,10 @@ impl DualRowConstraints { pub fn running_product_for_jump_stack_table_updates_correctly( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let indeterminate = self.circuit_builder.challenge(JumpStackPermIndeterminate); let clk_weight = self.circuit_builder.challenge(JumpStackTableClkWeight); let ci_weight = self.circuit_builder.challenge(JumpStackTableCiWeight); @@ -3084,7 +3966,10 @@ impl DualRowConstraints { pub fn running_evaluation_to_hash_table_updates_correctly( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let hash_deselector = InstructionDeselectors::instruction_deselector_next(self, Instruction::Hash); let hash_selector = self.ci_next() - self.constant_b(Instruction::Hash.opcode_b()); @@ -3131,7 +4016,10 @@ impl DualRowConstraints { pub fn running_evaluation_from_hash_table_updates_correctly( &self, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let hash_deselector = InstructionDeselectors::instruction_deselector(self, Instruction::Hash); let hash_selector = self.ci() - self.constant_b(Instruction::Hash.opcode_b()); @@ -3173,7 +4061,10 @@ impl DualRowConstraints { pub struct InstructionDeselectors { deselectors: HashMap< Instruction, - ConstraintCircuitMonad>, + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, >, } @@ -3192,7 +4083,10 @@ impl InstructionDeselectors { pub fn get( &self, instruction: Instruction, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { self.deselectors .get(&instruction) .unwrap_or_else(|| panic!("The instruction {} does not exist!", instruction)) @@ -3232,7 +4126,10 @@ impl InstructionDeselectors { pub fn instruction_deselector( factory: &DualRowConstraints, instruction: Instruction, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let instruction_bucket_polynomials = [ factory.ib0(), factory.ib1(), @@ -3254,7 +4151,10 @@ impl InstructionDeselectors { pub fn instruction_deselector_single_row( factory: &SingleRowConstraints, instruction: Instruction, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { let instruction_bucket_polynomials = [ factory.ib0(), factory.ib1(), @@ -3276,7 +4176,10 @@ impl InstructionDeselectors { pub fn instruction_deselector_next( factory: &DualRowConstraints, instruction: Instruction, - ) -> ConstraintCircuitMonad> { + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { let instruction_bucket_polynomials = [ factory.ib0_next(), factory.ib1_next(), @@ -3298,7 +4201,10 @@ impl InstructionDeselectors { factory: &mut DualRowConstraints, ) -> HashMap< Instruction, - ConstraintCircuitMonad>, + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, > { all_instructions_without_args() .into_iter() @@ -3307,13 +4213,201 @@ impl InstructionDeselectors { } } -impl ExtensionTable for ExtProcessorTable {} +pub struct ProcessorMatrixRow<'a> { + pub row: ArrayView1<'a, BFieldElement>, +} + +impl<'a> Display for ProcessorMatrixRow<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn row(f: &mut std::fmt::Formatter<'_>, s: String) -> std::fmt::Result { + writeln!(f, "│ {: <103} │", s) + } + + fn row_blank(f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + row(f, "".into()) + } + + let instruction = self.row[CI.base_table_index()].value().try_into().unwrap(); + let instruction_with_arg = match instruction { + Push(_) => Push(self.row[NIA.base_table_index()]), + Call(_) => Call(self.row[NIA.base_table_index()]), + Dup(_) => Dup((self.row[NIA.base_table_index()].value() as u32) + .try_into() + .unwrap()), + Swap(_) => Swap( + (self.row[NIA.base_table_index()].value() as u32) + .try_into() + .unwrap(), + ), + _ => instruction, + }; + + writeln!(f, " ╭───────────────────────────╮")?; + writeln!(f, " │ {: <25} │", format!("{}", instruction_with_arg))?; + writeln!( + f, + "╭┴───────────────────────────┴────────────────────────────────────\ + ────────────────────┬───────────────────╮" + )?; + + let width = 20; + row( + f, + format!( + "ip: {:>width$} ╷ ci: {:>width$} ╷ nia: {:>width$} │ {:>17}", + self.row[IP.base_table_index()].value(), + self.row[CI.base_table_index()].value(), + self.row[NIA.base_table_index()].value(), + self.row[CLK.base_table_index()].value(), + ), + )?; + + writeln!( + f, + "│ jsp: {:>width$} │ jso: {:>width$} │ jsd: {:>width$} ╰───────────────────┤", + self.row[JSP.base_table_index()].value(), + self.row[JSO.base_table_index()].value(), + self.row[JSD.base_table_index()].value(), + )?; + row( + f, + format!( + "ramp: {:>width$} │ ramv: {:>width$} │", + self.row[RAMP.base_table_index()].value(), + self.row[RAMV.base_table_index()].value(), + ), + )?; + row( + f, + format!( + "osp: {:>width$} │ osv: {:>width$} ╵", + self.row[OSP.base_table_index()].value(), + self.row[OSV.base_table_index()].value(), + ), + )?; + + row_blank(f)?; + + row( + f, + format!( + "st0-3: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", + self.row[ST0.base_table_index()].value(), + self.row[ST1.base_table_index()].value(), + self.row[ST2.base_table_index()].value(), + self.row[ST3.base_table_index()].value(), + ), + )?; + row( + f, + format!( + "st4-7: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", + self.row[ST4.base_table_index()].value(), + self.row[ST5.base_table_index()].value(), + self.row[ST6.base_table_index()].value(), + self.row[ST7.base_table_index()].value(), + ), + )?; + row( + f, + format!( + "st8-11: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", + self.row[ST8.base_table_index()].value(), + self.row[ST9.base_table_index()].value(), + self.row[ST10.base_table_index()].value(), + self.row[ST11.base_table_index()].value(), + ), + )?; + row( + f, + format!( + "st12-15: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", + self.row[ST12.base_table_index()].value(), + self.row[ST13.base_table_index()].value(), + self.row[ST14.base_table_index()].value(), + self.row[ST15.base_table_index()].value(), + ), + )?; + + row_blank(f)?; + + row( + f, + format!( + "hv0-3: [ {:>width$} | {:>width$} | {:>width$} | {:>width$} ]", + self.row[HV0.base_table_index()].value(), + self.row[HV1.base_table_index()].value(), + self.row[HV2.base_table_index()].value(), + self.row[HV3.base_table_index()].value(), + ), + )?; + let w = 2; + row( + f, + format!( + "ib0-6: \ + [ {:>w$} | {:>w$} | {:>w$} | {:>w$} | {:>w$} | {:>w$} | {:>w$} ]", + self.row[IB0.base_table_index()].value(), + self.row[IB1.base_table_index()].value(), + self.row[IB2.base_table_index()].value(), + self.row[IB3.base_table_index()].value(), + self.row[IB4.base_table_index()].value(), + self.row[IB5.base_table_index()].value(), + self.row[IB6.base_table_index()].value(), + ), + )?; + write!( + f, + "╰─────────────────────────────────────────────────────────────────\ + ────────────────────────────────────────╯" + ) + } +} + +pub struct ExtProcessorMatrixRow<'a> { + pub row: ArrayView1<'a, XFieldElement>, +} + +impl<'a> Display for ExtProcessorMatrixRow<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let row = |form: &mut std::fmt::Formatter<'_>, + desc: &str, + col: ProcessorExtTableColumn| + -> std::fmt::Result { + // without the extra `format!()`, alignment in `writeln!()` fails + let formatted_col_elem = format!("{}", self.row[col.ext_table_index()]); + writeln!(form, " │ {: <18} {:>73} │", desc, formatted_col_elem,) + }; + writeln!( + f, + " ╭───────────────────────────────────────────────────────\ + ────────────────────────────────────────╮" + )?; + row(f, "input_table_ea", InputTableEvalArg)?; + row(f, "output_table_ea", OutputTableEvalArg)?; + row(f, "instr_table_pa", InstructionTablePermArg)?; + row(f, "opstack_table_pa", OpStackTablePermArg)?; + row(f, "ram_table_pa", RamTablePermArg)?; + row(f, "jumpstack_table_pa", JumpStackTablePermArg)?; + row(f, "to_hash_table_ea", ToHashTableEvalArg)?; + row(f, "from_hash_table_ea", FromHashTableEvalArg)?; + write!( + f, + " ╰───────────────────────────────────────────────────────\ + ────────────────────────────────────────╯" + ) + } +} #[cfg(test)] mod constraint_polynomial_tests { + use ndarray::Array2; + use crate::ord_n::Ord16; - use crate::table::base_matrix::ProcessorMatrixRow; + use crate::stark::triton_stark_tests::parse_simulate_pad; use crate::table::challenges::AllChallenges; + use crate::table::master_table::MasterTable; + use crate::table::processor_table::ProcessorMatrixRow; use crate::vm::Program; use super::*; @@ -3322,34 +4416,28 @@ mod constraint_polynomial_tests { /// helps identifying whether the printing causes an infinite loop fn print_simple_processor_table_row_test() { let program = Program::from_code("push 2 push -1 add assert halt").unwrap(); - let (base_matrices, _, _) = program.simulate_no_input(); - for row in base_matrices.processor_matrix { + let (aet, _, _) = program.simulate_no_input(); + for row in aet.processor_matrix.rows() { println!("{}", ProcessorMatrixRow { row }); } } - fn get_test_row_from_source_code(source_code: &str, row_num: usize) -> Vec { - let fake_extension_columns = [BFieldElement::zero(); FULL_WIDTH - BASE_WIDTH].to_vec(); - - let program = Program::from_code(source_code).unwrap(); - let (base_matrices, _, err) = program.simulate_no_input(); - if let Some(e) = err { - panic!("The VM crashed because: {}", e); - } - - let test_row = [ - base_matrices.processor_matrix[row_num].to_vec(), - fake_extension_columns.clone(), - base_matrices.processor_matrix[row_num + 1].to_vec(), - fake_extension_columns, - ] - .concat(); - test_row.into_iter().map(|belem| belem.lift()).collect() + fn get_test_row_from_source_code(source_code: &str, row_num: usize) -> Array2 { + let (_, unpadded_master_base_table, _) = parse_simulate_pad(source_code, vec![], vec![]); + unpadded_master_base_table + .trace_table() + .slice(s![row_num..=row_num + 1, ..]) + .to_owned() } fn get_transition_constraints_for_instruction( instruction: Instruction, - ) -> Vec>> { + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { let tc = DualRowConstraints::default(); match instruction { Pop => tc.instruction_pop(), @@ -3386,42 +4474,48 @@ mod constraint_polynomial_tests { fn test_constraints_for_rows_with_debug_info( instruction: Instruction, - test_rows: &[Vec], + master_base_tables: &[Array2], debug_cols_curr_row: &[ProcessorBaseTableColumn], debug_cols_next_row: &[ProcessorBaseTableColumn], ) { - for (case_idx, test_row) in test_rows.iter().enumerate() { + let challenges = AllChallenges::placeholder(&[], &[]); + let fake_ext_table = Array2::zeros([2, NUM_EXT_COLUMNS]); + for (case_idx, test_rows) in master_base_tables.iter().enumerate() { + let curr_row = test_rows.slice(s![0, ..]); + let next_row = test_rows.slice(s![1, ..]); + // Print debug information println!( - "Testing all constraint polynomials of {} for test row with index {}…", - instruction, case_idx + "Testing all constraints of {instruction} for test row with index {case_idx}…" ); - for c in debug_cols_curr_row { - print!("{} = {}, ", c, test_row[usize::from(*c)]); + for &c in debug_cols_curr_row { + print!("{} = {}, ", c, curr_row[c.master_base_table_index()]); } - for c in debug_cols_next_row { - print!("{}' = {}, ", c, test_row[*c as usize + FULL_WIDTH]); + for &c in debug_cols_next_row { + print!("{}' = {}, ", c, next_row[c.master_base_table_index()]); } println!(); - // We need dummy challenges to do partial evaluate. Even though we are - // not looking at extension constraints, only base constraints - let dummy_challenges = AllChallenges::placeholder(); - for (poly_idx, poly) in get_transition_constraints_for_instruction(instruction) - .iter() - .enumerate() + assert_eq!( + instruction.opcode_b(), + curr_row[CI.master_base_table_index()], + "The test is trying to check the wrong transition constraint polynomials." + ); + for (constraint_idx, constraint_circuit) in + get_transition_constraints_for_instruction(instruction) + .into_iter() + .enumerate() { - assert_eq!( - instruction.opcode_b().lift(), - test_row[usize::from(CI)], - "The test is trying to check the wrong transition constraint polynomials." + let evaluation_result = constraint_circuit.consume().evaluate( + test_rows.view(), + fake_ext_table.view(), + &challenges.processor_table_challenges, ); assert_eq!( XFieldElement::zero(), - poly.partial_evaluate(&dummy_challenges.processor_table_challenges).evaluate(test_row), - "For case {}, transition constraint polynomial with index {} must evaluate to zero.", - case_idx, - poly_idx, + evaluation_result, + "For case {case_idx}, transition constraint polynomial with \ + index {constraint_idx} must evaluate to zero. Got {evaluation_result} instead.", ); } } @@ -3635,60 +4729,61 @@ mod constraint_polynomial_tests { let mut factory = DualRowConstraints::default(); let deselectors = InstructionDeselectors::new(&mut factory); - let mut row = vec![0.into(); 2 * FULL_WIDTH]; + let mut master_base_table = Array2::zeros([2, NUM_BASE_COLUMNS]); + let master_ext_table = Array2::zeros([2, NUM_EXT_COLUMNS]); - // We need dummy challenges to do partial evaluate. Even though we are - // not looking at extension constraints, only base constraints - let dummy_challenges = AllChallenges::placeholder(); + // We need dummy challenges to evaluate. + let dummy_challenges = AllChallenges::placeholder(&[], &[]); for instruction in all_instructions_without_args() { use ProcessorBaseTableColumn::*; let deselector = deselectors.get(instruction); - println!( - "\n\nThe Deselector for instruction {} is:\n{}", - instruction, deselector - ); + println!("\n\nThe Deselector for instruction {instruction} is:\n{deselector}",); // Negative tests for other_instruction in all_instructions_without_args() .into_iter() .filter(|other_instruction| *other_instruction != instruction) { - row[usize::from(IB0)] = other_instruction.ib(Ord7::IB0).lift(); - row[usize::from(IB1)] = other_instruction.ib(Ord7::IB1).lift(); - row[usize::from(IB2)] = other_instruction.ib(Ord7::IB2).lift(); - row[usize::from(IB3)] = other_instruction.ib(Ord7::IB3).lift(); - row[usize::from(IB4)] = other_instruction.ib(Ord7::IB4).lift(); - row[usize::from(IB5)] = other_instruction.ib(Ord7::IB5).lift(); - row[usize::from(IB6)] = other_instruction.ib(Ord7::IB6).lift(); - let result = deselector - .partial_evaluate(&dummy_challenges.processor_table_challenges) - .evaluate(&row); + let mut curr_row = master_base_table.slice_mut(s![0, ..]); + curr_row[IB0.master_base_table_index()] = other_instruction.ib(Ord7::IB0); + curr_row[IB1.master_base_table_index()] = other_instruction.ib(Ord7::IB1); + curr_row[IB2.master_base_table_index()] = other_instruction.ib(Ord7::IB2); + curr_row[IB3.master_base_table_index()] = other_instruction.ib(Ord7::IB3); + curr_row[IB4.master_base_table_index()] = other_instruction.ib(Ord7::IB4); + curr_row[IB5.master_base_table_index()] = other_instruction.ib(Ord7::IB5); + curr_row[IB6.master_base_table_index()] = other_instruction.ib(Ord7::IB6); + let result = deselector.clone().consume().evaluate( + master_base_table.view(), + master_ext_table.view(), + &dummy_challenges.processor_table_challenges, + ); assert!( result.is_zero(), - "Deselector for {} should return 0 for all other instructions, including {} whose opcode is {}", - instruction, - other_instruction, + "Deselector for {instruction} should return 0 for all other instructions, \ + including {other_instruction} whose opcode is {}", other_instruction.opcode() ) } // Positive tests - row[usize::from(IB0)] = instruction.ib(Ord7::IB0).lift(); - row[usize::from(IB1)] = instruction.ib(Ord7::IB1).lift(); - row[usize::from(IB2)] = instruction.ib(Ord7::IB2).lift(); - row[usize::from(IB3)] = instruction.ib(Ord7::IB3).lift(); - row[usize::from(IB4)] = instruction.ib(Ord7::IB4).lift(); - row[usize::from(IB5)] = instruction.ib(Ord7::IB5).lift(); - row[usize::from(IB6)] = instruction.ib(Ord7::IB6).lift(); - let result = deselector - .partial_evaluate(&dummy_challenges.processor_table_challenges) - .evaluate(&row); + let mut curr_row = master_base_table.slice_mut(s![0, ..]); + curr_row[IB0.master_base_table_index()] = instruction.ib(Ord7::IB0); + curr_row[IB1.master_base_table_index()] = instruction.ib(Ord7::IB1); + curr_row[IB2.master_base_table_index()] = instruction.ib(Ord7::IB2); + curr_row[IB3.master_base_table_index()] = instruction.ib(Ord7::IB3); + curr_row[IB4.master_base_table_index()] = instruction.ib(Ord7::IB4); + curr_row[IB5.master_base_table_index()] = instruction.ib(Ord7::IB5); + curr_row[IB6.master_base_table_index()] = instruction.ib(Ord7::IB6); + let result = deselector.consume().evaluate( + master_base_table.view(), + master_ext_table.view(), + &dummy_challenges.processor_table_challenges, + ); assert!( !result.is_zero(), - "Deselector for {} should be non-zero when CI is {}", - instruction, + "Deselector for {instruction} should be non-zero when CI is {}", instruction.opcode() ) } @@ -3730,20 +4825,12 @@ mod constraint_polynomial_tests { (WriteIo, factory.instruction_write_io()), ]; - // We need dummy challenges to do partial evaluate. Even though we are - // not looking at extension constraints, only base constraints - let dummy_challenges = AllChallenges::placeholder(); - println!("| Instruction | #polys | max deg | Degrees"); println!("|:----------------|-------:|--------:|:------------"); for (instruction, constraints) in all_instructions_and_their_transition_constraints { let degrees = constraints .iter() - .map(|circuit| { - circuit - .partial_evaluate(&dummy_challenges.processor_table_challenges) - .degree() - }) + .map(|circuit| circuit.clone().consume().degree()) .collect_vec(); let max_degree = degrees.iter().max().unwrap_or(&0); let degrees_str = degrees.iter().map(|d| format!("{d}")).join(", "); diff --git a/triton-vm/src/table/program_table.rs b/triton-vm/src/table/program_table.rs index 33ec6c569..f51a0467f 100644 --- a/triton-vm/src/table/program_table.rs +++ b/triton-vm/src/table/program_table.rs @@ -1,106 +1,65 @@ -use itertools::Itertools; -use num_traits::{One, Zero}; +use ndarray::s; +use ndarray::Array1; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use num_traits::One; +use num_traits::Zero; use strum::EnumCount; -use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter}; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::x_field_element::XFieldElement; use ProgramTableChallengeId::*; -use crate::cross_table_arguments::{CrossTableArg, EvalArg}; -use crate::table::base_table::Extendable; +use crate::table::challenges::TableChallenges; +use crate::table::constraint_circuit::ConstraintCircuit; +use crate::table::constraint_circuit::ConstraintCircuitBuilder; +use crate::table::constraint_circuit::DualRowIndicator; +use crate::table::constraint_circuit::DualRowIndicator::*; use crate::table::constraint_circuit::SingleRowIndicator; -use crate::table::constraint_circuit::SingleRowIndicator::Row; -use crate::table::table_column::ProgramBaseTableColumn::{self, *}; -use crate::table::table_column::ProgramExtTableColumn::{self, *}; - -use super::base_table::{InheritsFromTable, Table, TableLike}; -use super::challenges::TableChallenges; -use super::constraint_circuit::DualRowIndicator::*; -use super::constraint_circuit::{ConstraintCircuit, ConstraintCircuitBuilder, DualRowIndicator}; -use super::extension_table::{ExtensionTable, QuotientableExtensionTable}; +use crate::table::constraint_circuit::SingleRowIndicator::*; +use crate::table::cross_table_argument::CrossTableArg; +use crate::table::cross_table_argument::EvalArg; +use crate::table::master_table::NUM_BASE_COLUMNS; +use crate::table::master_table::NUM_EXT_COLUMNS; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::ExtTableColumn; +use crate::table::table_column::MasterBaseTableColumn; +use crate::table::table_column::MasterExtTableColumn; +use crate::table::table_column::ProgramBaseTableColumn; +use crate::table::table_column::ProgramBaseTableColumn::*; +use crate::table::table_column::ProgramExtTableColumn; +use crate::table::table_column::ProgramExtTableColumn::*; pub const PROGRAM_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 0; pub const PROGRAM_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 1; - -/// This is 3 because it combines: addr, instruction, instruction in next row -pub const PROGRAM_TABLE_EXTENSION_CHALLENGE_COUNT: usize = 3; +pub const PROGRAM_TABLE_NUM_EXTENSION_CHALLENGES: usize = ProgramTableChallengeId::COUNT; pub const BASE_WIDTH: usize = ProgramBaseTableColumn::COUNT; -pub const FULL_WIDTH: usize = BASE_WIDTH + ProgramExtTableColumn::COUNT; +pub const EXT_WIDTH: usize = ProgramExtTableColumn::COUNT; +pub const FULL_WIDTH: usize = BASE_WIDTH + EXT_WIDTH; #[derive(Debug, Clone)] -pub struct ProgramTable { - inherited_table: Table, -} - -impl InheritsFromTable for ProgramTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} +pub struct ProgramTable {} #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExtProgramTable { - pub(crate) inherited_table: Table, -} - -impl Default for ExtProgramTable { - fn default() -> Self { - Self { - inherited_table: Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "EmptyExtProgramTable".to_string(), - ), - } - } -} - -impl QuotientableExtensionTable for ExtProgramTable {} - -impl InheritsFromTable for ExtProgramTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} - -impl TableLike for ProgramTable {} - -impl Extendable for ProgramTable { - fn get_padding_rows(&self) -> (Option, Vec>) { - let zero = BFieldElement::zero(); - let one = BFieldElement::one(); - - let mut padding_row = [zero; BASE_WIDTH]; - if let Some(row) = self.data().last() { - padding_row[usize::from(Address)] = row[usize::from(Address)] + one; - } - padding_row[usize::from(IsPadding)] = one; - - (None, vec![padding_row.to_vec()]) - } -} - -impl TableLike for ExtProgramTable {} +pub struct ExtProgramTable {} impl ExtProgramTable { - pub fn ext_initial_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_initial_constraints_as_circuits() -> Vec< + ConstraintCircuit< + ProgramTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1_u32.into()); - let address = circuit_builder.input(Row(Address.into())); - let running_evaluation = circuit_builder.input(Row(RunningEvaluation.into())); + let address = circuit_builder.input(BaseRow(Address.master_base_table_index())); + let running_evaluation = + circuit_builder.input(ExtRow(RunningEvaluation.master_ext_table_index())); let first_address_is_zero = address; @@ -112,29 +71,42 @@ impl ExtProgramTable { ] } - pub fn ext_consistency_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_consistency_constraints_as_circuits() -> Vec< + ConstraintCircuit< + ProgramTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1_u32.into()); - let is_padding = circuit_builder.input(Row(IsPadding.into())); + let is_padding = circuit_builder.input(BaseRow(IsPadding.master_base_table_index())); let is_padding_is_bit = is_padding.clone() * (is_padding - one); vec![is_padding_is_bit.consume()] } - pub fn ext_transition_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(2 * FULL_WIDTH); - let address = circuit_builder.input(CurrentRow(Address.into())); - let address_next = circuit_builder.input(NextRow(Address.into())); + pub fn ext_transition_constraints_as_circuits() -> Vec< + ConstraintCircuit< + ProgramTableChallenges, + DualRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1u32.into()); - let instruction = circuit_builder.input(CurrentRow(Instruction.into())); - let is_padding = circuit_builder.input(CurrentRow(IsPadding.into())); - let running_evaluation = circuit_builder.input(CurrentRow(RunningEvaluation.into())); - let instruction_next = circuit_builder.input(NextRow(Instruction.into())); - let is_padding_next = circuit_builder.input(NextRow(IsPadding.into())); - let running_evaluation_next = circuit_builder.input(NextRow(RunningEvaluation.into())); + let address = circuit_builder.input(CurrentBaseRow(Address.master_base_table_index())); + let instruction = + circuit_builder.input(CurrentBaseRow(Instruction.master_base_table_index())); + let is_padding = circuit_builder.input(CurrentBaseRow(IsPadding.master_base_table_index())); + let running_evaluation = + circuit_builder.input(CurrentExtRow(RunningEvaluation.master_ext_table_index())); + let address_next = circuit_builder.input(NextBaseRow(Address.master_base_table_index())); + let instruction_next = + circuit_builder.input(NextBaseRow(Instruction.master_base_table_index())); + let is_padding_next = + circuit_builder.input(NextBaseRow(IsPadding.master_base_table_index())); + let running_evaluation_next = + circuit_builder.input(NextExtRow(RunningEvaluation.master_ext_table_index())); let address_increases_by_one = address_next - (address.clone() + one.clone()); let is_padding_is_0_or_remains_unchanged = @@ -162,88 +134,82 @@ impl ExtProgramTable { .to_vec() } - pub fn ext_terminal_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_terminal_constraints_as_circuits() -> Vec< + ConstraintCircuit< + ProgramTableChallenges, + SingleRowIndicator, + >, + > { // no further constraints vec![] } } impl ProgramTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } - } + pub fn fill_trace(program_table: &mut ArrayViewMut2, program: &[BFieldElement]) { + let program_len = program.len(); + let address_column = program_table.slice_mut(s![..program_len, Address.base_table_index()]); + let addresses = Array1::from_iter((0..program_len).map(|a| BFieldElement::new(a as u64))); + addresses.move_into(address_column); - pub fn new_prover(matrix: Vec>) -> Self { - let inherited_table = - Table::new(BASE_WIDTH, FULL_WIDTH, matrix, "ProgramTable".to_string()); - Self { inherited_table } + let instructions = Array1::from(program.to_owned()); + let instruction_column = + program_table.slice_mut(s![..program_len, Instruction.base_table_index()]); + instructions.move_into(instruction_column); } - pub fn extend(&self, challenges: &ProgramTableChallenges) -> ExtProgramTable { - let mut extension_matrix: Vec> = Vec::with_capacity(self.data().len()); - let mut instruction_table_running_evaluation = EvalArg::default_initial(); + pub fn pad_trace(program_table: &mut ArrayViewMut2, program_len: usize) { + let addresses = Array1::from_iter( + (program_len..program_table.nrows()).map(|a| BFieldElement::new(a as u64)), + ); + addresses.move_into(program_table.slice_mut(s![program_len.., Address.base_table_index()])); - let data_with_0 = { - let mut tmp = self.data().clone(); - tmp.push(vec![BFieldElement::zero(); BASE_WIDTH]); - tmp - }; + program_table + .slice_mut(s![program_len.., IsPadding.base_table_index()]) + .fill(BFieldElement::one()); + } - for (row, next_row) in data_with_0.into_iter().tuple_windows() { - let mut extension_row = [0.into(); FULL_WIDTH]; - extension_row[..BASE_WIDTH] - .copy_from_slice(&row.iter().map(|elem| elem.lift()).collect_vec()); + pub fn extend( + base_table: ArrayView2, + mut ext_table: ArrayViewMut2, + challenges: &ProgramTableChallenges, + ) { + assert_eq!(BASE_WIDTH, base_table.ncols()); + assert_eq!(EXT_WIDTH, ext_table.ncols()); + assert_eq!(base_table.nrows(), ext_table.nrows()); + let mut instruction_table_running_evaluation = EvalArg::default_initial(); - let address = row[usize::from(Address)].lift(); - let instruction = row[usize::from(Instruction)].lift(); - let next_instruction = next_row[usize::from(Instruction)].lift(); + for (idx, window) in base_table.windows([2, BASE_WIDTH]).into_iter().enumerate() { + let row = window.slice(s![0, ..]); + let next_row = window.slice(s![1, ..]); + let mut extension_row = ext_table.slice_mut(s![idx, ..]); // The running evaluation linking Program Table and Instruction Table does record the // initial in the first row, contrary to most other running evaluations and products. // The running product's final value, allowing for a meaningful cross-table argument, // is recorded in the first padding row. This row is guaranteed to exist. - extension_row[usize::from(RunningEvaluation)] = instruction_table_running_evaluation; + extension_row[RunningEvaluation.ext_table_index()] = + instruction_table_running_evaluation; // update the running evaluation if not a padding row - if row[usize::from(IsPadding)].is_zero() { - // compress address, instruction, and next instruction (or argument) into one value + if row[IsPadding.base_table_index()].is_zero() { + let address = row[Address.base_table_index()]; + let instruction = row[Instruction.base_table_index()]; + let next_instruction = next_row[Instruction.base_table_index()]; let compressed_row_for_evaluation_argument = address * challenges.address_weight + instruction * challenges.instruction_weight + next_instruction * challenges.next_instruction_weight; - instruction_table_running_evaluation = instruction_table_running_evaluation * challenges.instruction_eval_indeterminate + compressed_row_for_evaluation_argument; } - - extension_matrix.push(extension_row.to_vec()); } - assert_eq!(self.data().len(), extension_matrix.len()); - let inherited_table = self.new_from_lifted_matrix(extension_matrix); - ExtProgramTable { inherited_table } - } - - pub fn for_verifier() -> ExtProgramTable { - let inherited_table = Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "ExtProgramTable".to_string(), - ); - let base_table = Self { inherited_table }; - let empty_matrix: Vec> = vec![]; - let extension_table = base_table.new_from_lifted_matrix(empty_matrix); - - ExtProgramTable { - inherited_table: extension_table, - } - } -} - -impl ExtProgramTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } + let mut last_row = ext_table + .rows_mut() + .into_iter() + .last() + .expect("Program Table must not be empty."); + last_row[RunningEvaluation.ext_table_index()] = instruction_table_running_evaluation; } } @@ -286,5 +252,3 @@ pub struct ProgramTableChallenges { pub instruction_weight: XFieldElement, pub next_instruction_weight: XFieldElement, } - -impl ExtensionTable for ExtProgramTable {} diff --git a/triton-vm/src/table/ram_table.rs b/triton-vm/src/table/ram_table.rs index 998982f0c..39afb8a13 100644 --- a/triton-vm/src/table/ram_table.rs +++ b/triton-vm/src/table/ram_table.rs @@ -1,121 +1,292 @@ -use itertools::Itertools; -use num_traits::{One, Zero}; +use std::collections::HashMap; + +use ndarray::parallel::prelude::*; +use ndarray::s; +use ndarray::Array1; +use ndarray::ArrayView1; +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use ndarray::Axis; +use num_traits::One; +use num_traits::Zero; use strum::EnumCount; -use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter}; +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; +use twenty_first::shared_math::polynomial::Polynomial; use twenty_first::shared_math::traits::Inverse; use twenty_first::shared_math::x_field_element::XFieldElement; use RamTableChallengeId::*; -use crate::cross_table_arguments::{CrossTableArg, PermArg}; -use crate::table::base_table::Extendable; -use crate::table::base_table::{InheritsFromTable, Table, TableLike}; +use crate::instruction::Instruction; use crate::table::challenges::TableChallenges; +use crate::table::constraint_circuit::ConstraintCircuit; +use crate::table::constraint_circuit::ConstraintCircuitBuilder; +use crate::table::constraint_circuit::DualRowIndicator; +use crate::table::constraint_circuit::DualRowIndicator::*; use crate::table::constraint_circuit::SingleRowIndicator; -use crate::table::constraint_circuit::SingleRowIndicator::Row; -use crate::table::constraint_circuit::{ - ConstraintCircuit, ConstraintCircuitBuilder, DualRowIndicator, -}; -use crate::table::extension_table::{ExtensionTable, QuotientableExtensionTable}; -use crate::table::table_column::RamBaseTableColumn::{self, *}; -use crate::table::table_column::RamExtTableColumn::{self, *}; - -use super::constraint_circuit::DualRowIndicator::*; +use crate::table::constraint_circuit::SingleRowIndicator::*; +use crate::table::cross_table_argument::CrossTableArg; +use crate::table::cross_table_argument::PermArg; +use crate::table::master_table::NUM_BASE_COLUMNS; +use crate::table::master_table::NUM_EXT_COLUMNS; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::ExtTableColumn; +use crate::table::table_column::MasterBaseTableColumn; +use crate::table::table_column::MasterExtTableColumn; +use crate::table::table_column::ProcessorBaseTableColumn; +use crate::table::table_column::RamBaseTableColumn; +use crate::table::table_column::RamBaseTableColumn::*; +use crate::table::table_column::RamExtTableColumn; +use crate::table::table_column::RamExtTableColumn::*; +use crate::vm::AlgebraicExecutionTrace; pub const RAM_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 1; pub const RAM_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 0; - -/// This is 3 because it combines: clk, ramv, ramp -pub const RAM_TABLE_NUM_EXTENSION_CHALLENGES: usize = 3; +pub const RAM_TABLE_NUM_EXTENSION_CHALLENGES: usize = RamTableChallengeId::COUNT; pub const BASE_WIDTH: usize = RamBaseTableColumn::COUNT; -pub const FULL_WIDTH: usize = BASE_WIDTH + RamExtTableColumn::COUNT; +pub const EXT_WIDTH: usize = RamExtTableColumn::COUNT; +pub const FULL_WIDTH: usize = BASE_WIDTH + EXT_WIDTH; #[derive(Debug, Clone)] -pub struct RamTable { - inherited_table: Table, -} - -impl InheritsFromTable for RamTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } - - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} +pub struct RamTable {} #[derive(Debug, Clone)] -pub struct ExtRamTable { - pub(crate) inherited_table: Table, -} +pub struct ExtRamTable {} -impl Default for ExtRamTable { - fn default() -> Self { - Self { - inherited_table: Table::new( - BASE_WIDTH, - FULL_WIDTH, - vec![], - "EmptyExtRamTable".to_string(), - ), +impl RamTable { + /// Fills the trace table in-place and returns all clock jump differences greater than 1. + pub fn fill_trace( + ram_table: &mut ArrayViewMut2, + aet: &AlgebraicExecutionTrace, + ) -> Vec { + // Store the registers relevant for the Ram Table, i.e., CLK, RAMP, RAMV, and + // PreviousInstruction, with RAMP as the key. Preserves, thus allows reusing, the order + // of the processor's rows, which are sorted by CLK. Note that the Ram Table must not be + // sorted by RAMP, but must form contiguous regions of RAMP values. + let mut pre_processed_ram_table: HashMap<_, Vec<_>> = HashMap::new(); + for processor_row in aet.processor_matrix.rows() { + let clk = processor_row[ProcessorBaseTableColumn::CLK.base_table_index()]; + let ramp = processor_row[ProcessorBaseTableColumn::RAMP.base_table_index()]; + let ramv = processor_row[ProcessorBaseTableColumn::RAMV.base_table_index()]; + let previous_instruction = + processor_row[ProcessorBaseTableColumn::PreviousInstruction.base_table_index()]; + let ram_row = (clk, previous_instruction, ramv); + pre_processed_ram_table + .entry(ramp) + .and_modify(|v| v.push(ram_row)) + .or_insert_with(|| vec![ram_row]); } - } -} -impl QuotientableExtensionTable for ExtRamTable {} + // Compute Bézout coefficient polynomials. + let num_of_ramps = pre_processed_ram_table.keys().len(); + let polynomial_with_ramps_as_roots = pre_processed_ram_table.keys().fold( + Polynomial::from_constant(BFieldElement::one()), + |acc, &ramp| acc * Polynomial::new(vec![-ramp, BFieldElement::one()]), // acc·(x - ramp) + ); + let formal_derivative = polynomial_with_ramps_as_roots.formal_derivative(); + let (gcd, bezout_0, bezout_1) = + Polynomial::xgcd(polynomial_with_ramps_as_roots, formal_derivative); + assert!(gcd.is_one(), "Each RAMP value must occur at most once."); + assert!( + bezout_0.degree() < num_of_ramps as isize, + "The Bézout coefficient 0 must be of degree at most {}.", + num_of_ramps - 1 + ); + assert!( + bezout_1.degree() <= num_of_ramps as isize, + "The Bézout coefficient 1 must be of degree at most {num_of_ramps}." + ); + let mut bezout_coefficient_polynomial_coefficients_0 = bezout_0.coefficients; + let mut bezout_coefficient_polynomial_coefficients_1 = bezout_1.coefficients; + bezout_coefficient_polynomial_coefficients_0.resize(num_of_ramps, BFieldElement::zero()); + bezout_coefficient_polynomial_coefficients_1.resize(num_of_ramps, BFieldElement::zero()); + let mut current_bcpc_0 = bezout_coefficient_polynomial_coefficients_0.pop().unwrap(); + let mut current_bcpc_1 = bezout_coefficient_polynomial_coefficients_1.pop().unwrap(); + ram_table[[ + 0, + BezoutCoefficientPolynomialCoefficient0.base_table_index(), + ]] = current_bcpc_0; + ram_table[[ + 0, + BezoutCoefficientPolynomialCoefficient1.base_table_index(), + ]] = current_bcpc_1; + + // Move the rows into the Ram Table as contiguous regions of RAMP values. Each such + // contiguous region is sorted by CLK by virtue of the order of the processor's rows. + let mut ram_table_row_idx = 0; + for (ramp, ram_table_rows) in pre_processed_ram_table { + for (clk, previous_instruction, ramv) in ram_table_rows { + let mut ram_table_row = ram_table.row_mut(ram_table_row_idx); + ram_table_row[CLK.base_table_index()] = clk; + ram_table_row[RAMP.base_table_index()] = ramp; + ram_table_row[RAMV.base_table_index()] = ramv; + ram_table_row[PreviousInstruction.base_table_index()] = previous_instruction; + ram_table_row_idx += 1; + } + } + assert_eq!(aet.processor_matrix.nrows(), ram_table_row_idx); + + // - Set inverse of clock difference - 1. + // - Set inverse of RAMP difference. + // - Fill in the Bézout coefficients if the RAMP has changed. + // - Collect all clock jump differences greater than 1. + // The Ram Table and the Processor Table have the same length. + let mut clock_jump_differences_greater_than_1 = vec![]; + for row_idx in 0..aet.processor_matrix.nrows() - 1 { + let (mut curr_row, mut next_row) = + ram_table.multi_slice_mut((s![row_idx, ..], s![row_idx + 1, ..])); + + let clk_diff = next_row[CLK.base_table_index()] - curr_row[CLK.base_table_index()]; + let clk_diff_minus_1 = clk_diff - BFieldElement::one(); + let clk_diff_minus_1_inverse = clk_diff_minus_1.inverse_or_zero(); + curr_row[InverseOfClkDiffMinusOne.base_table_index()] = clk_diff_minus_1_inverse; + + let ramp_diff = next_row[RAMP.base_table_index()] - curr_row[RAMP.base_table_index()]; + let ramp_diff_inverse = ramp_diff.inverse_or_zero(); + curr_row[InverseOfRampDifference.base_table_index()] = ramp_diff_inverse; + + if !ramp_diff.is_zero() { + current_bcpc_0 = bezout_coefficient_polynomial_coefficients_0.pop().unwrap(); + current_bcpc_1 = bezout_coefficient_polynomial_coefficients_1.pop().unwrap(); + } + next_row[BezoutCoefficientPolynomialCoefficient0.base_table_index()] = current_bcpc_0; + next_row[BezoutCoefficientPolynomialCoefficient1.base_table_index()] = current_bcpc_1; -impl InheritsFromTable for ExtRamTable { - fn inherited_table(&self) -> &Table { - &self.inherited_table - } + if ramp_diff.is_zero() && !clk_diff.is_zero() && !clk_diff.is_one() { + clock_jump_differences_greater_than_1.push(clk_diff); + } + } - fn mut_inherited_table(&mut self) -> &mut Table { - &mut self.inherited_table - } -} + assert_eq!(0, bezout_coefficient_polynomial_coefficients_0.len()); + assert_eq!(0, bezout_coefficient_polynomial_coefficients_1.len()); -impl RamTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } + clock_jump_differences_greater_than_1 } - pub fn new_prover(matrix: Vec>) -> Self { - let inherited_table = Table::new(BASE_WIDTH, FULL_WIDTH, matrix, "RamTable".to_string()); - Self { inherited_table } + pub fn pad_trace(ram_table: &mut ArrayViewMut2, processor_table_len: usize) { + assert!( + processor_table_len > 0, + "Processor Table must have at least 1 row." + ); + + // Set up indices for relevant sections of the table. + let padded_height = ram_table.nrows(); + let num_padding_rows = padded_height - processor_table_len; + let max_clk_before_padding = processor_table_len - 1; + let max_clk_before_padding_row_idx = ram_table + .rows() + .into_iter() + .enumerate() + .find(|(_, row)| row[CLK.base_table_index()].value() as usize == max_clk_before_padding) + .map(|(idx, _)| idx) + .expect("Ram Table must contain row with clock cycle equal to max cycle."); + let rows_to_move_source_section_start = max_clk_before_padding_row_idx + 1; + let rows_to_move_source_section_end = processor_table_len; + let num_rows_to_move = rows_to_move_source_section_end - rows_to_move_source_section_start; + let rows_to_move_dest_section_start = rows_to_move_source_section_start + num_padding_rows; + let rows_to_move_dest_section_end = rows_to_move_dest_section_start + num_rows_to_move; + let padding_section_start = rows_to_move_source_section_start; + let padding_section_end = padding_section_start + num_padding_rows; + assert_eq!(padded_height, rows_to_move_dest_section_end); + + // Move all rows below the row with highest CLK to the end of the table – if they exist. + if num_rows_to_move > 0 { + let rows_to_move_source_range = + rows_to_move_source_section_start..rows_to_move_source_section_end; + let rows_to_move_dest_range = + rows_to_move_dest_section_start..rows_to_move_dest_section_end; + let rows_to_move = ram_table + .slice(s![rows_to_move_source_range, ..]) + .to_owned(); + rows_to_move.move_into(&mut ram_table.slice_mut(s![rows_to_move_dest_range, ..])); + } + + // Fill the created gap with padding rows, i.e., with (adjusted) copies of the last row + // before the gap. This is the padding section. + let mut padding_row_template = ram_table.row(max_clk_before_padding_row_idx).to_owned(); + let ramp_difference_inverse = + padding_row_template[InverseOfRampDifference.base_table_index()]; + padding_row_template[InverseOfRampDifference.base_table_index()] = BFieldElement::zero(); + padding_row_template[InverseOfClkDiffMinusOne.base_table_index()] = BFieldElement::zero(); + let mut padding_section = + ram_table.slice_mut(s![padding_section_start..padding_section_end, ..]); + padding_section + .axis_iter_mut(Axis(0)) + .into_par_iter() + .for_each(|padding_row| padding_row_template.clone().move_into(padding_row)); + + // CLK keeps increasing by 1 also in the padding section. + let clk_range = processor_table_len..padded_height; + let clk_col = Array1::from_iter(clk_range.map(|clk| BFieldElement::new(clk as u64))); + clk_col.move_into(padding_section.slice_mut(s![.., CLK.base_table_index()])); + + // InverseOfRampDifference and InverseOfClkDiffMinusOne must be consistent at the padding + // section's boundaries. + ram_table[[ + max_clk_before_padding_row_idx, + InverseOfRampDifference.base_table_index(), + ]] = BFieldElement::zero(); + ram_table[[ + max_clk_before_padding_row_idx, + InverseOfClkDiffMinusOne.base_table_index(), + ]] = BFieldElement::zero(); + if num_rows_to_move > 0 && rows_to_move_dest_section_start > 0 { + let max_clk_after_padding = padded_height - 1; + let clk_diff_minus_one_at_padding_section_lower_boundary = ram_table + [[rows_to_move_dest_section_start, CLK.base_table_index()]] + - BFieldElement::new(max_clk_after_padding as u64) + - BFieldElement::one(); + let last_row_in_padding_section_idx = rows_to_move_dest_section_start - 1; + ram_table[[ + last_row_in_padding_section_idx, + InverseOfRampDifference.base_table_index(), + ]] = ramp_difference_inverse; + ram_table[[ + last_row_in_padding_section_idx, + InverseOfClkDiffMinusOne.base_table_index(), + ]] = clk_diff_minus_one_at_padding_section_lower_boundary.inverse_or_zero(); + } } - pub fn extend(&self, challenges: &RamTableChallenges) -> ExtRamTable { - let mut extension_matrix: Vec> = Vec::with_capacity(self.data().len()); + pub fn extend( + base_table: ArrayView2, + mut ext_table: ArrayViewMut2, + challenges: &RamTableChallenges, + ) { + assert_eq!(BASE_WIDTH, base_table.ncols()); + assert_eq!(EXT_WIDTH, ext_table.ncols()); + assert_eq!(base_table.nrows(), ext_table.nrows()); let mut running_product_for_perm_arg = PermArg::default_initial(); let mut all_clock_jump_differences_running_product = PermArg::default_initial(); // initialize columns establishing Bézout relation - let ramp_first_row = self.data().first().unwrap()[usize::from(RAMP)]; - let mut running_product_of_ramp = challenges.bezout_relation_indeterminate - ramp_first_row; + let mut running_product_of_ramp = + challenges.bezout_relation_indeterminate - base_table.row(0)[RAMP.base_table_index()]; let mut formal_derivative = XFieldElement::one(); - let mut bezout_coefficient_0 = XFieldElement::zero(); - let bcpc_first_row = - self.data().first().unwrap()[usize::from(BezoutCoefficientPolynomialCoefficient1)]; - let mut bezout_coefficient_1 = bcpc_first_row.lift(); - - let mut previous_row: Option> = None; - for row in self.data().iter() { - let mut extension_row = [0.into(); FULL_WIDTH]; - extension_row[..BASE_WIDTH] - .copy_from_slice(&row.iter().map(|elem| elem.lift()).collect_vec()); - - let clk = extension_row[usize::from(CLK)]; - let ramp = extension_row[usize::from(RAMP)]; - let ramv = extension_row[usize::from(RAMV)]; - - if let Some(prow) = previous_row { - if prow[usize::from(RAMP)] != row[usize::from(RAMP)] { + let mut bezout_coefficient_0 = + base_table.row(0)[BezoutCoefficientPolynomialCoefficient0.base_table_index()].lift(); + let mut bezout_coefficient_1 = + base_table.row(0)[BezoutCoefficientPolynomialCoefficient1.base_table_index()].lift(); + + let mut previous_row: Option> = None; + for row_idx in 0..base_table.nrows() { + let current_row = base_table.row(row_idx); + let clk = current_row[CLK.base_table_index()]; + let ramp = current_row[RAMP.base_table_index()]; + let ramv = current_row[RAMV.base_table_index()]; + let previous_instruction = current_row[PreviousInstruction.base_table_index()]; + + if let Some(prev_row) = previous_row { + if prev_row[RAMP.base_table_index()] != current_row[RAMP.base_table_index()] { // accumulate coefficient for Bézout relation, proving new RAMP is unique - let bcpc0 = extension_row[usize::from(BezoutCoefficientPolynomialCoefficient0)]; - let bcpc1 = extension_row[usize::from(BezoutCoefficientPolynomialCoefficient1)]; + let bcpc0 = + current_row[BezoutCoefficientPolynomialCoefficient0.base_table_index()]; + let bcpc1 = + current_row[BezoutCoefficientPolynomialCoefficient1.base_table_index()]; let bezout_challenge = challenges.bezout_relation_indeterminate; formal_derivative = @@ -126,8 +297,8 @@ impl RamTable { } else { // prove that clock jump is directed forward let clock_jump_difference = - (row[usize::from(CLK)] - prow[usize::from(CLK)]).lift(); - if clock_jump_difference != XFieldElement::one() { + current_row[CLK.base_table_index()] - prev_row[CLK.base_table_index()]; + if !clock_jump_difference.is_one() { all_clock_jump_differences_running_product *= challenges .all_clock_jump_differences_multi_perm_indeterminate - clock_jump_difference; @@ -135,175 +306,113 @@ impl RamTable { } } - extension_row[usize::from(RunningProductOfRAMP)] = running_product_of_ramp; - extension_row[usize::from(FormalDerivative)] = formal_derivative; - extension_row[usize::from(BezoutCoefficient0)] = bezout_coefficient_0; - extension_row[usize::from(BezoutCoefficient1)] = bezout_coefficient_1; - extension_row[usize::from(AllClockJumpDifferencesPermArg)] = - all_clock_jump_differences_running_product; - // permutation argument to Processor Table - let clk_w = challenges.clk_weight; - let ramp_w = challenges.ramp_weight; - let ramv_w = challenges.ramv_weight; - - // compress multiple values within one row so they become one value - let compressed_row_for_permutation_argument = - clk * clk_w + ramp * ramp_w + ramv * ramv_w; - - // compute the running product of the compressed column for permutation argument + let compressed_row_for_permutation_argument = clk * challenges.clk_weight + + ramp * challenges.ramp_weight + + ramv * challenges.ramv_weight + + previous_instruction * challenges.previous_instruction_weight; running_product_for_perm_arg *= challenges.processor_perm_indeterminate - compressed_row_for_permutation_argument; - extension_row[usize::from(RunningProductPermArg)] = running_product_for_perm_arg; - - previous_row = Some(row.clone()); - extension_matrix.push(extension_row.to_vec()); - } - assert_eq!(self.data().len(), extension_matrix.len()); - let inherited_table = self.new_from_lifted_matrix(extension_matrix); - ExtRamTable { inherited_table } - } - - pub fn for_verifier() -> ExtRamTable { - let inherited_table = Table::new(BASE_WIDTH, FULL_WIDTH, vec![], "ExtRamTable".to_string()); - let base_table = Self { inherited_table }; - let empty_matrix: Vec> = vec![]; - let extension_table = base_table.new_from_lifted_matrix(empty_matrix); - - ExtRamTable { - inherited_table: extension_table, - } - } -} - -impl ExtRamTable { - pub fn new(inherited_table: Table) -> Self { - Self { inherited_table } - } -} - -impl TableLike for RamTable {} - -impl Extendable for RamTable { - fn get_padding_rows(&self) -> (Option, Vec>) { - panic!("This function should not be called: the Ram Table implements `.pad` directly.") - } - - fn pad(&mut self, padded_height: usize) { - let max_clock = self.data().len() as u64 - 1; - let num_padding_rows = padded_height - self.data().len(); - - if self.data().is_empty() { - let mut all_padding = vec![]; - for i in 0..padded_height { - let mut padding_row = vec![BFieldElement::zero(); BASE_WIDTH]; - padding_row[usize::from(CLK)] = BFieldElement::new(i as u64); - all_padding.push(padding_row); - } - self.mut_data().append(&mut all_padding); - return; - } - - let template_index = self - .data() - .iter() - .enumerate() - .find(|(_, row)| row[usize::from(CLK)].value() == max_clock) - .map(|(idx, _)| idx) - .unwrap(); - let insertion_index = template_index + 1; - - let padding_template = &mut self.mut_data()[template_index]; - let difference_inverse = padding_template[usize::from(InverseOfRampDifference)]; - padding_template[usize::from(InverseOfRampDifference)] = BFieldElement::zero(); - padding_template[usize::from(InverseOfClkDiffMinusOne)] = 0_u64.into(); - - let mut padding_rows = vec![]; - while padding_rows.len() < num_padding_rows { - let mut padding_row = padding_template.clone(); - padding_row[usize::from(CLK)] += (padding_rows.len() as u32 + 1).into(); - padding_rows.push(padding_row) - } - - if let Some(row) = padding_rows.last_mut() { - row[usize::from(InverseOfRampDifference)] = difference_inverse; - - if let Some(next_row) = self.data().get(insertion_index) { - let clk_diff = next_row[usize::from(CLK)] - row[usize::from(CLK)]; - row[usize::from(InverseOfClkDiffMinusOne)] = - (clk_diff - BFieldElement::one()).inverse_or_zero(); - } + let mut extension_row = ext_table.row_mut(row_idx); + extension_row[RunningProductPermArg.ext_table_index()] = running_product_for_perm_arg; + extension_row[RunningProductOfRAMP.ext_table_index()] = running_product_of_ramp; + extension_row[FormalDerivative.ext_table_index()] = formal_derivative; + extension_row[BezoutCoefficient0.ext_table_index()] = bezout_coefficient_0; + extension_row[BezoutCoefficient1.ext_table_index()] = bezout_coefficient_1; + extension_row[AllClockJumpDifferencesPermArg.ext_table_index()] = + all_clock_jump_differences_running_product; + previous_row = Some(current_row); } - - let old_tail_length = self.data().len() - insertion_index; - self.mut_data().append(&mut padding_rows); - self.mut_data()[insertion_index..].rotate_left(old_tail_length); - - assert_eq!(padded_height, self.data().len()); } } -impl TableLike for ExtRamTable {} - impl ExtRamTable { - pub fn ext_initial_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_initial_constraints_as_circuits() -> Vec< + ConstraintCircuit< + RamTableChallenges, + SingleRowIndicator, + >, + > { use RamTableChallengeId::*; - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1_u32.into()); let bezout_challenge = circuit_builder.challenge(BezoutRelationIndeterminate); let rppa_challenge = circuit_builder.challenge(ProcessorPermIndeterminate); - let clk = circuit_builder.input(Row(CLK.into())); - let ramp = circuit_builder.input(Row(RAMP.into())); - let ramv = circuit_builder.input(Row(RAMV.into())); - let bcpc0 = circuit_builder.input(Row(BezoutCoefficientPolynomialCoefficient0.into())); - let bcpc1 = circuit_builder.input(Row(BezoutCoefficientPolynomialCoefficient1.into())); - let rp = circuit_builder.input(Row(RunningProductOfRAMP.into())); - let fd = circuit_builder.input(Row(FormalDerivative.into())); - let bc0 = circuit_builder.input(Row(BezoutCoefficient0.into())); - let bc1 = circuit_builder.input(Row(BezoutCoefficient1.into())); - let rppa = circuit_builder.input(Row(RunningProductPermArg.into())); - - let clk_is_0 = clk; - let ramp_is_0 = ramp; - let ramv_is_0 = ramv; + let clk = circuit_builder.input(BaseRow(CLK.master_base_table_index())); + let ramp = circuit_builder.input(BaseRow(RAMP.master_base_table_index())); + let ramv = circuit_builder.input(BaseRow(RAMV.master_base_table_index())); + let previous_instruction = + circuit_builder.input(BaseRow(PreviousInstruction.master_base_table_index())); + let bcpc0 = circuit_builder.input(BaseRow( + BezoutCoefficientPolynomialCoefficient0.master_base_table_index(), + )); + let bcpc1 = circuit_builder.input(BaseRow( + BezoutCoefficientPolynomialCoefficient1.master_base_table_index(), + )); + let rp = circuit_builder.input(ExtRow(RunningProductOfRAMP.master_ext_table_index())); + let fd = circuit_builder.input(ExtRow(FormalDerivative.master_ext_table_index())); + let bc0 = circuit_builder.input(ExtRow(BezoutCoefficient0.master_ext_table_index())); + let bc1 = circuit_builder.input(ExtRow(BezoutCoefficient1.master_ext_table_index())); + let rpcjd = circuit_builder.input(ExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); + let rppa = circuit_builder.input(ExtRow(RunningProductPermArg.master_ext_table_index())); + + let write_mem_opcode = circuit_builder.b_constant(Instruction::WriteMem.opcode_b()); + let ramv_is_0_or_was_written_to = + ramv.clone() * (write_mem_opcode - previous_instruction.clone()); let bezout_coefficient_polynomial_coefficient_0_is_0 = bcpc0; let bezout_coefficient_0_is_0 = bc0; let bezout_coefficient_1_is_bezout_coefficient_polynomial_coefficient_1 = bc1 - bcpc1; - let formal_derivative_is_1 = fd - one; - // This should be rp - (bezout_challenge - ramp). However, `ramp` is already constrained to - // be 0, and can thus be omitted. - let running_product_polynomial_is_initialized_correctly = rp - bezout_challenge; - let running_product_permutation_argument_is_initialized_correctly = rppa - rppa_challenge; + let formal_derivative_is_1 = fd - one.clone(); + let running_product_polynomial_is_initialized_correctly = + rp - (bezout_challenge - ramp.clone()); + + let running_product_for_clock_jump_differences_is_initialized_to_1 = rpcjd - one; + + let clk_weight = circuit_builder.challenge(ClkWeight); + let ramp_weight = circuit_builder.challenge(RampWeight); + let ramv_weight = circuit_builder.challenge(RamvWeight); + let previous_instruction_weight = circuit_builder.challenge(PreviousInstructionWeight); + let compressed_row_for_permutation_argument = clk * clk_weight + + ramp * ramp_weight + + ramv * ramv_weight + + previous_instruction * previous_instruction_weight; + let running_product_permutation_argument_is_initialized_correctly = + rppa - (rppa_challenge - compressed_row_for_permutation_argument); [ - clk_is_0, - ramp_is_0, - ramv_is_0, + ramv_is_0_or_was_written_to, bezout_coefficient_polynomial_coefficient_0_is_0, bezout_coefficient_0_is_0, bezout_coefficient_1_is_bezout_coefficient_polynomial_coefficient_1, - formal_derivative_is_1, running_product_polynomial_is_initialized_correctly, + formal_derivative_is_1, + running_product_for_clock_jump_differences_is_initialized_to_1, running_product_permutation_argument_is_initialized_correctly, ] .map(|circuit| circuit.consume()) .to_vec() } - pub fn ext_consistency_constraints_as_circuits( - ) -> Vec>> { + pub fn ext_consistency_constraints_as_circuits() -> Vec< + ConstraintCircuit< + RamTableChallenges, + SingleRowIndicator, + >, + > { // no further constraints vec![] } - pub fn ext_transition_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(2 * FULL_WIDTH); + pub fn ext_transition_constraints_as_circuits() -> Vec< + ConstraintCircuit>, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1u32.into()); let bezout_challenge = circuit_builder.challenge(BezoutRelationIndeterminate); @@ -313,56 +422,81 @@ impl ExtRamTable { let clk_weight = circuit_builder.challenge(ClkWeight); let ramp_weight = circuit_builder.challenge(RampWeight); let ramv_weight = circuit_builder.challenge(RamvWeight); - - let clk = circuit_builder.input(CurrentRow(CLK.into())); - let ramp = circuit_builder.input(CurrentRow(RAMP.into())); - let ramv = circuit_builder.input(CurrentRow(RAMV.into())); - let iord = circuit_builder.input(CurrentRow(InverseOfRampDifference.into())); - let bcpc0 = - circuit_builder.input(CurrentRow(BezoutCoefficientPolynomialCoefficient0.into())); - let bcpc1 = - circuit_builder.input(CurrentRow(BezoutCoefficientPolynomialCoefficient1.into())); - let rp = circuit_builder.input(CurrentRow(RunningProductOfRAMP.into())); - let fd = circuit_builder.input(CurrentRow(FormalDerivative.into())); - let bc0 = circuit_builder.input(CurrentRow(BezoutCoefficient0.into())); - let bc1 = circuit_builder.input(CurrentRow(BezoutCoefficient1.into())); - let clk_di = circuit_builder.input(CurrentRow(InverseOfClkDiffMinusOne.into())); - let rpcjd = circuit_builder.input(CurrentRow(AllClockJumpDifferencesPermArg.into())); - let rppa = circuit_builder.input(CurrentRow(RunningProductPermArg.into())); - - let clk_next = circuit_builder.input(NextRow(CLK.into())); - let ramp_next = circuit_builder.input(NextRow(RAMP.into())); - let ramv_next = circuit_builder.input(NextRow(RAMV.into())); - let bcpc0_next = - circuit_builder.input(NextRow(BezoutCoefficientPolynomialCoefficient0.into())); - let bcpc1_next = - circuit_builder.input(NextRow(BezoutCoefficientPolynomialCoefficient1.into())); - let rp_next = circuit_builder.input(NextRow(RunningProductOfRAMP.into())); - let fd_next = circuit_builder.input(NextRow(FormalDerivative.into())); - let bc0_next = circuit_builder.input(NextRow(BezoutCoefficient0.into())); - let bc1_next = circuit_builder.input(NextRow(BezoutCoefficient1.into())); - let rpcjd_next = circuit_builder.input(NextRow(AllClockJumpDifferencesPermArg.into())); - let rppa_next = circuit_builder.input(NextRow(RunningProductPermArg.into())); - - let ramp_diff = ramp_next.clone() - ramp.clone(); + let previous_instruction_weight = circuit_builder.challenge(PreviousInstructionWeight); + + let clk = circuit_builder.input(CurrentBaseRow(CLK.master_base_table_index())); + let ramp = circuit_builder.input(CurrentBaseRow(RAMP.master_base_table_index())); + let ramv = circuit_builder.input(CurrentBaseRow(RAMV.master_base_table_index())); + let iord = circuit_builder.input(CurrentBaseRow( + InverseOfRampDifference.master_base_table_index(), + )); + let bcpc0 = circuit_builder.input(CurrentBaseRow( + BezoutCoefficientPolynomialCoefficient0.master_base_table_index(), + )); + let bcpc1 = circuit_builder.input(CurrentBaseRow( + BezoutCoefficientPolynomialCoefficient1.master_base_table_index(), + )); + let clk_diff_minus_one_inv = circuit_builder.input(CurrentBaseRow( + InverseOfClkDiffMinusOne.master_base_table_index(), + )); + let rp = + circuit_builder.input(CurrentExtRow(RunningProductOfRAMP.master_ext_table_index())); + let fd = circuit_builder.input(CurrentExtRow(FormalDerivative.master_ext_table_index())); + let bc0 = circuit_builder.input(CurrentExtRow(BezoutCoefficient0.master_ext_table_index())); + let bc1 = circuit_builder.input(CurrentExtRow(BezoutCoefficient1.master_ext_table_index())); + let rpcjd = circuit_builder.input(CurrentExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); + let rppa = circuit_builder.input(CurrentExtRow( + RunningProductPermArg.master_ext_table_index(), + )); + + let clk_next = circuit_builder.input(NextBaseRow(CLK.master_base_table_index())); + let ramp_next = circuit_builder.input(NextBaseRow(RAMP.master_base_table_index())); + let ramv_next = circuit_builder.input(NextBaseRow(RAMV.master_base_table_index())); + let previous_instruction_next = + circuit_builder.input(NextBaseRow(PreviousInstruction.master_base_table_index())); + let bcpc0_next = circuit_builder.input(NextBaseRow( + BezoutCoefficientPolynomialCoefficient0.master_base_table_index(), + )); + let bcpc1_next = circuit_builder.input(NextBaseRow( + BezoutCoefficientPolynomialCoefficient1.master_base_table_index(), + )); + let rp_next = + circuit_builder.input(NextExtRow(RunningProductOfRAMP.master_ext_table_index())); + let fd_next = circuit_builder.input(NextExtRow(FormalDerivative.master_ext_table_index())); + let bc0_next = + circuit_builder.input(NextExtRow(BezoutCoefficient0.master_ext_table_index())); + let bc1_next = + circuit_builder.input(NextExtRow(BezoutCoefficient1.master_ext_table_index())); + let rpcjd_next = circuit_builder.input(NextExtRow( + AllClockJumpDifferencesPermArg.master_ext_table_index(), + )); + let rppa_next = + circuit_builder.input(NextExtRow(RunningProductPermArg.master_ext_table_index())); + + let ramp_diff = ramp_next.clone() - ramp; let ramp_changes = ramp_diff.clone() * iord.clone(); // iord is 0 or iord is the inverse of (ramp' - ramp) - let iord_is_0_or_iord_is_inverse_of_ramp_diff = - iord.clone() * (ramp_changes.clone() - one.clone()); + let iord_is_0_or_iord_is_inverse_of_ramp_diff = iord * (ramp_changes.clone() - one.clone()); // (ramp' - ramp) is zero or iord is the inverse of (ramp' - ramp) let ramp_diff_is_0_or_iord_is_inverse_of_ramp_diff = ramp_diff.clone() * (ramp_changes.clone() - one.clone()); - // The ramp does not change or the new ramv is 0 - let ramp_does_not_change_or_ramv_becomes_0 = ramp_diff.clone() * ramv_next.clone(); + // (ramp doesn't change) and (previous instruction is not write_mem) + // implies the ramv doesn't change + let op_code_write_mem = circuit_builder.b_constant(Instruction::WriteMem.opcode_b()); + let ramp_changes_or_write_mem_or_ramv_stays = (one.clone() - ramp_changes.clone()) + * (op_code_write_mem.clone() - previous_instruction_next.clone()) + * (ramv_next.clone() - ramv); - // The ramp does change or the ramv does not change or the clk increases by 1 - let ramp_does_not_change_or_ramv_does_not_change_or_clk_increases_by_1 = - (ramp_changes.clone() - one.clone()) - * (ramv_next.clone() - ramv) - * (clk_next.clone() - (clk.clone() + one.clone())); + // (ramp changes) and (previous instruction is not write_mem) + // implies the next ramv is 0 + let ramp_stays_or_write_mem_or_ramv_next_is_0 = ramp_diff.clone() + * (op_code_write_mem - previous_instruction_next.clone()) + * ramv_next.clone(); let bcbp0_only_changes_if_ramp_changes = (one.clone() - ramp_changes.clone()) * (bcpc0_next.clone() - bcpc0); @@ -382,34 +516,43 @@ impl ExtRamTable { * (bc0_next.clone() - bezout_challenge.clone() * bc0.clone() - bcpc0_next) + (one.clone() - ramp_changes.clone()) * (bc0_next - bc0); - let bezout_coefficient_1_is_constructed_correctly = ramp_diff + let bezout_coefficient_1_is_constructed_correctly = ramp_diff.clone() * (bc1_next.clone() - bezout_challenge * bc1.clone() - bcpc1_next) - + (one.clone() - ramp_changes) * (bc1_next - bc1); + + (one.clone() - ramp_changes.clone()) * (bc1_next - bc1); - let clk_di_is_inverse_of_clkd = - clk_di.clone() * (clk_next.clone() - clk.clone() - one.clone()); - let clk_di_is_zero_or_inverse_of_clkd = clk_di.clone() * clk_di_is_inverse_of_clkd.clone(); + let clk_diff_minus_one = clk_next.clone() - clk.clone() - one.clone(); + let clk_di_is_inverse_of_clk_diff = + clk_diff_minus_one_inv.clone() * clk_diff_minus_one.clone(); + let clk_di_is_zero_or_inverse_of_clkd = + clk_diff_minus_one_inv.clone() * (clk_di_is_inverse_of_clk_diff.clone() - one.clone()); let clkd_is_zero_or_inverse_of_clk_di = - (clk_next.clone() - clk.clone() - one.clone()) * clk_di_is_inverse_of_clkd; - - let rpcjd_updates_correctly = (clk_next.clone() - clk.clone() - one.clone()) - * (rpcjd_next.clone() - rpcjd.clone()) - + (one.clone() - (ramp_next.clone() - ramp.clone()) * iord) - * (rpcjd_next.clone() - rpcjd.clone()) - + (one.clone() - (clk_next.clone() - clk - one) * clk_di) - * ramp.clone() - * (rpcjd_next - rpcjd * (cjd_challenge - ramp)); - - let compressed_row_for_permutation_argument = - clk_next * clk_weight + ramp_next * ramp_weight + ramv_next * ramv_weight; + clk_diff_minus_one.clone() * (clk_di_is_inverse_of_clk_diff - one.clone()); + + // Running product of clock jump differences (“rpcjd”) updates iff + // - the RAMP remains the same, and + // - the clock difference is greater than 1. + let clk_diff = clk_next.clone() - clk; + let clk_diff_eq_one = one.clone() - clk_diff.clone(); + let clk_diff_gt_one = one.clone() - clk_diff_minus_one * clk_diff_minus_one_inv; + let rpcjd_remains = rpcjd_next.clone() - rpcjd.clone(); + let rpcjd_absorbs_clk_diff = rpcjd_next - rpcjd * (cjd_challenge - clk_diff); + let rpcjd_updates_correctly = + (one - ramp_changes) * clk_diff_eq_one * rpcjd_absorbs_clk_diff + + ramp_diff * rpcjd_remains.clone() + + clk_diff_gt_one * rpcjd_remains; + + let compressed_row_for_permutation_argument = clk_next * clk_weight + + ramp_next * ramp_weight + + ramv_next * ramv_weight + + previous_instruction_next * previous_instruction_weight; let rppa_updates_correctly = rppa_next - rppa * (rppa_challenge - compressed_row_for_permutation_argument); [ iord_is_0_or_iord_is_inverse_of_ramp_diff, ramp_diff_is_0_or_iord_is_inverse_of_ramp_diff, - ramp_does_not_change_or_ramv_becomes_0, - ramp_does_not_change_or_ramv_does_not_change_or_clk_increases_by_1, + ramp_changes_or_write_mem_or_ramv_stays, + ramp_stays_or_write_mem_or_ramv_next_is_0, bcbp0_only_changes_if_ramp_changes, bcbp1_only_changes_if_ramp_changes, running_product_ramp_updates_correctly, @@ -425,15 +568,19 @@ impl ExtRamTable { .to_vec() } - pub fn ext_terminal_constraints_as_circuits( - ) -> Vec>> { - let circuit_builder = ConstraintCircuitBuilder::new(FULL_WIDTH); + pub fn ext_terminal_constraints_as_circuits() -> Vec< + ConstraintCircuit< + RamTableChallenges, + SingleRowIndicator, + >, + > { + let circuit_builder = ConstraintCircuitBuilder::new(); let one = circuit_builder.b_constant(1_u32.into()); - let rp = circuit_builder.input(Row(RunningProductOfRAMP.into())); - let fd = circuit_builder.input(Row(FormalDerivative.into())); - let bc0 = circuit_builder.input(Row(BezoutCoefficient0.into())); - let bc1 = circuit_builder.input(Row(BezoutCoefficient1.into())); + let rp = circuit_builder.input(ExtRow(RunningProductOfRAMP.master_ext_table_index())); + let fd = circuit_builder.input(ExtRow(FormalDerivative.master_ext_table_index())); + let bc0 = circuit_builder.input(ExtRow(BezoutCoefficient0.master_ext_table_index())); + let bc1 = circuit_builder.input(ExtRow(BezoutCoefficient1.master_ext_table_index())); let bezout_relation_holds = bc0 * rp + bc1 * fd - one; @@ -448,6 +595,7 @@ pub enum RamTableChallengeId { ClkWeight, RamvWeight, RampWeight, + PreviousInstructionWeight, AllClockJumpDifferencesMultiPermIndeterminate, } @@ -467,8 +615,9 @@ pub struct RamTableChallenges { /// Weights for condensing part of a row into a single column. (Related to processor table.) pub clk_weight: XFieldElement, - pub ramv_weight: XFieldElement, pub ramp_weight: XFieldElement, + pub ramv_weight: XFieldElement, + pub previous_instruction_weight: XFieldElement, /// Point of evaluation for accumulating all clock jump differences into a running product pub all_clock_jump_differences_multi_perm_indeterminate: XFieldElement, @@ -485,11 +634,10 @@ impl TableChallenges for RamTableChallenges { ClkWeight => self.clk_weight, RamvWeight => self.ramv_weight, RampWeight => self.ramp_weight, + PreviousInstructionWeight => self.previous_instruction_weight, AllClockJumpDifferencesMultiPermIndeterminate => { self.all_clock_jump_differences_multi_perm_indeterminate } } } } - -impl ExtensionTable for ExtRamTable {} diff --git a/triton-vm/src/table/table_collection.rs b/triton-vm/src/table/table_collection.rs deleted file mode 100644 index 432afd5d0..000000000 --- a/triton-vm/src/table/table_collection.rs +++ /dev/null @@ -1,914 +0,0 @@ -use itertools::Itertools; -use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; -use strum::EnumCount; -use triton_profiler::triton_profiler::TritonProfiler; -use triton_profiler::{prof_start, prof_stop}; -use twenty_first::shared_math::b_field_element::BFieldElement; -use twenty_first::shared_math::mpolynomial::Degree; -use twenty_first::shared_math::other::{is_power_of_two, roundup_npo2, transpose}; -use twenty_first::shared_math::traits::PrimitiveRootOfUnity; -use twenty_first::shared_math::x_field_element::XFieldElement; - -use crate::arithmetic_domain::ArithmeticDomain; -use crate::table::base_table::{Extendable, InheritsFromTable, Table}; -use crate::table::extension_table::DegreeWithOrigin; -use crate::table::table_column::*; -use crate::table::*; - -use super::base_matrix::BaseMatrices; -use super::base_table::TableLike; -use super::challenges::AllChallenges; -use super::extension_table::QuotientableExtensionTable; -use super::hash_table::{ExtHashTable, HashTable}; -use super::instruction_table::{ExtInstructionTable, InstructionTable}; -use super::jump_stack_table::{ExtJumpStackTable, JumpStackTable}; -use super::op_stack_table::{ExtOpStackTable, OpStackTable}; -use super::processor_table::{ExtProcessorTable, ProcessorTable}; -use super::program_table::{ExtProgramTable, ProgramTable}; -use super::ram_table::{ExtRamTable, RamTable}; - -pub const NUM_TABLES: usize = 7; - -#[derive(Debug, Clone)] -pub struct BaseTableCollection { - /// The number of `data` rows after padding - pub padded_height: usize, - - pub program_table: ProgramTable, - pub instruction_table: InstructionTable, - pub processor_table: ProcessorTable, - pub op_stack_table: OpStackTable, - pub ram_table: RamTable, - pub jump_stack_table: JumpStackTable, - pub hash_table: HashTable, -} - -#[derive(Debug, Clone)] -pub struct ExtTableCollection { - /// The number of `data` rows after padding - pub padded_height: usize, - - pub program_table: ExtProgramTable, - pub instruction_table: ExtInstructionTable, - pub processor_table: ExtProcessorTable, - pub op_stack_table: ExtOpStackTable, - pub ram_table: ExtRamTable, - pub jump_stack_table: ExtJumpStackTable, - pub hash_table: ExtHashTable, -} - -/// A `TableId` uniquely determines one of Triton VM's tables. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum TableId { - ProgramTable, - InstructionTable, - ProcessorTable, - OpStackTable, - RamTable, - JumpStackTable, - HashTable, -} - -/// Convert vector-of-arrays to vector-of-vectors. -fn to_vec_vecs(vector_of_arrays: &[[T; S]]) -> Vec> { - vector_of_arrays - .iter() - .map(|arr| arr.to_vec()) - .collect_vec() -} - -pub fn interpolant_degree(padded_height: usize, num_trace_randomizers: usize) -> Degree { - let randomized_trace_length = roundup_npo2((padded_height + num_trace_randomizers) as u64); - (randomized_trace_length - 1) as Degree -} - -pub fn derive_trace_domain_generator(padded_height: u64) -> BFieldElement { - debug_assert!( - 0 == padded_height || is_power_of_two(padded_height), - "The padded height was: {}", - padded_height - ); - BFieldElement::primitive_root_of_unity(padded_height).unwrap() -} - -impl BaseTableCollection { - pub fn from_base_matrices(base_matrices: &BaseMatrices) -> Self { - let padded_height = Self::padded_height(base_matrices); - - let program_table = ProgramTable::new_prover(to_vec_vecs(&base_matrices.program_matrix)); - let processor_table = - ProcessorTable::new_prover(to_vec_vecs(&base_matrices.processor_matrix)); - let instruction_table = - InstructionTable::new_prover(to_vec_vecs(&base_matrices.instruction_matrix)); - let op_stack_table = OpStackTable::new_prover(to_vec_vecs(&base_matrices.op_stack_matrix)); - let ram_table = RamTable::new_prover(to_vec_vecs(&base_matrices.ram_matrix)); - let jump_stack_table = - JumpStackTable::new_prover(to_vec_vecs(&base_matrices.jump_stack_matrix)); - let hash_table = HashTable::new_prover(to_vec_vecs(&base_matrices.hash_matrix)); - - BaseTableCollection { - padded_height, - program_table, - instruction_table, - processor_table, - op_stack_table, - ram_table, - jump_stack_table, - hash_table, - } - } - - pub fn padded_height(base_matrices: &BaseMatrices) -> usize { - let max_height = [ - 1, // minimum max height - base_matrices.program_matrix.len(), - base_matrices.processor_matrix.len(), - base_matrices.instruction_matrix.len(), - base_matrices.op_stack_matrix.len(), - base_matrices.ram_matrix.len(), - base_matrices.jump_stack_matrix.len(), - base_matrices.hash_matrix.len(), - ] - .into_iter() - .max() - .unwrap(); - - roundup_npo2(max_height as u64) as usize - } - - pub fn to_fri_domain_tables( - &self, - fri_domain: &ArithmeticDomain, - num_trace_randomizers: usize, - maybe_profiler: &mut Option, - ) -> Self { - prof_start!(maybe_profiler, "program table"); - let program_table = ProgramTable::new(self.program_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - 0..program_table::BASE_WIDTH, - )); - prof_stop!(maybe_profiler, "program table"); - prof_start!(maybe_profiler, "instruction table"); - let instruction_table = - InstructionTable::new(self.instruction_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - 0..instruction_table::BASE_WIDTH, - )); - prof_stop!(maybe_profiler, "instruction table"); - prof_start!(maybe_profiler, "processor table"); - let processor_table = - ProcessorTable::new(self.processor_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - 0..processor_table::BASE_WIDTH, - )); - prof_stop!(maybe_profiler, "processor table"); - prof_start!(maybe_profiler, "op stack table"); - let op_stack_table = OpStackTable::new(self.op_stack_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - 0..op_stack_table::BASE_WIDTH, - )); - prof_stop!(maybe_profiler, "op stack table"); - prof_start!(maybe_profiler, "ram table"); - let ram_table = RamTable::new(self.ram_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - 0..ram_table::BASE_WIDTH, - )); - prof_stop!(maybe_profiler, "ram table"); - prof_start!(maybe_profiler, "jump stack table"); - let jump_stack_table = - JumpStackTable::new(self.jump_stack_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - 0..jump_stack_table::BASE_WIDTH, - )); - prof_stop!(maybe_profiler, "jump stack table"); - prof_start!(maybe_profiler, "hash table"); - let hash_table = HashTable::new(self.hash_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - 0..hash_table::BASE_WIDTH, - )); - prof_stop!(maybe_profiler, "hash table"); - - BaseTableCollection { - padded_height: self.padded_height, - program_table, - instruction_table, - processor_table, - op_stack_table, - ram_table, - jump_stack_table, - hash_table, - } - } - - pub fn get_all_base_columns(&self) -> Vec> { - self.into_iter() - .map(|table| table.data().clone()) - .collect_vec() - .concat() - } - - pub fn get_base_degree_bounds(&self, num_trace_randomizers: usize) -> Vec { - let sum_of_base_widths = self.into_iter().map(|table| table.base_width()).sum(); - vec![interpolant_degree(self.padded_height, num_trace_randomizers); sum_of_base_widths] - } - - pub fn pad(&mut self) { - let padded_height = self.padded_height; - self.program_table.pad(padded_height); - self.instruction_table.pad(padded_height); - self.processor_table.pad(padded_height); - self.op_stack_table.pad(padded_height); - self.ram_table.pad(padded_height); - self.jump_stack_table.pad(padded_height); - self.hash_table.pad(padded_height); - } -} - -impl<'a> IntoIterator for &'a BaseTableCollection { - type Item = &'a dyn TableLike; - - type IntoIter = std::array::IntoIter<&'a dyn TableLike, NUM_TABLES>; - - fn into_iter(self) -> Self::IntoIter { - [ - &self.program_table as &'a dyn TableLike, - &self.instruction_table as &'a dyn TableLike, - &self.processor_table as &'a dyn TableLike, - &self.op_stack_table as &'a dyn TableLike, - &self.ram_table as &'a dyn TableLike, - &self.jump_stack_table as &'a dyn TableLike, - &self.hash_table as &'a dyn TableLike, - ] - .into_iter() - } -} - -impl ExtTableCollection { - pub fn with_padded_height(padded_height: usize) -> Self { - ExtTableCollection { - padded_height, - program_table: Default::default(), - instruction_table: Default::default(), - processor_table: Default::default(), - op_stack_table: Default::default(), - ram_table: Default::default(), - jump_stack_table: Default::default(), - hash_table: Default::default(), - } - } - - /// todo: Temporary method, to be replaced in issue #139 - pub fn with_data( - padded_height: usize, - base_codewords: Vec>, - extension_codewords: Vec>, - ) -> Self { - let (base_program_table_data, base_codewords) = - base_codewords.split_at(ProgramBaseTableColumn::COUNT); - let (ext_program_table_data, extension_codewords) = - extension_codewords.split_at(ProgramExtTableColumn::COUNT); - let lifted_base_program_table_data: Vec<_> = base_program_table_data - .into_par_iter() - .map(|codeword| codeword.par_iter().map(|bfe| bfe.lift()).collect()) - .collect(); - let program_table_full_data = vec![ - lifted_base_program_table_data, - ext_program_table_data.to_vec(), - ] - .concat(); - let program_table = ExtProgramTable::new(Table::new( - ProgramBaseTableColumn::COUNT, - ProgramBaseTableColumn::COUNT + ProgramExtTableColumn::COUNT, - program_table_full_data, - "ExtProgramTable over quotient domain".to_string(), - )); - - let (base_instruction_table_data, base_codewords) = - base_codewords.split_at(InstructionBaseTableColumn::COUNT); - let (ext_instruction_table_data, extension_codewords) = - extension_codewords.split_at(InstructionExtTableColumn::COUNT); - let lifted_base_instruction_table_data: Vec<_> = base_instruction_table_data - .into_par_iter() - .map(|codeword| codeword.par_iter().map(|bfe| bfe.lift()).collect()) - .collect(); - let instruction_table_full_data = vec![ - lifted_base_instruction_table_data, - ext_instruction_table_data.to_vec(), - ] - .concat(); - let instruction_table = ExtInstructionTable::new(Table::new( - InstructionBaseTableColumn::COUNT, - InstructionBaseTableColumn::COUNT + InstructionExtTableColumn::COUNT, - instruction_table_full_data, - "ExtInstructionTable over quotient domain".to_string(), - )); - - let (base_processor_table_data, base_codewords) = - base_codewords.split_at(ProcessorBaseTableColumn::COUNT); - let (ext_processor_table_data, extension_codewords) = - extension_codewords.split_at(ProcessorExtTableColumn::COUNT); - let lifted_base_processor_table_data: Vec<_> = base_processor_table_data - .into_par_iter() - .map(|codeword| codeword.par_iter().map(|bfe| bfe.lift()).collect()) - .collect(); - let processor_table_full_data = vec![ - lifted_base_processor_table_data, - ext_processor_table_data.to_vec(), - ] - .concat(); - let processor_table = ExtProcessorTable::new(Table::new( - ProcessorBaseTableColumn::COUNT, - ProcessorBaseTableColumn::COUNT + ProcessorExtTableColumn::COUNT, - processor_table_full_data, - "ExtProcessorTable over quotient domain".to_string(), - )); - - let (base_op_stack_table_data, base_codewords) = - base_codewords.split_at(OpStackBaseTableColumn::COUNT); - let (ext_op_stack_table_data, extension_codewords) = - extension_codewords.split_at(OpStackExtTableColumn::COUNT); - let lifted_base_op_stack_table_data: Vec<_> = base_op_stack_table_data - .into_par_iter() - .map(|codeword| codeword.par_iter().map(|bfe| bfe.lift()).collect()) - .collect(); - let op_stack_table_full_data = vec![ - lifted_base_op_stack_table_data, - ext_op_stack_table_data.to_vec(), - ] - .concat(); - let op_stack_table = ExtOpStackTable::new(Table::new( - OpStackBaseTableColumn::COUNT, - OpStackBaseTableColumn::COUNT + OpStackExtTableColumn::COUNT, - op_stack_table_full_data, - "ExtOpStackTable over quotient domain".to_string(), - )); - - let (base_ram_table_data, base_codewords) = - base_codewords.split_at(RamBaseTableColumn::COUNT); - let (ext_ram_table_data, extension_codewords) = - extension_codewords.split_at(RamExtTableColumn::COUNT); - let lifted_base_ram_table_data: Vec<_> = base_ram_table_data - .into_par_iter() - .map(|codeword| codeword.par_iter().map(|bfe| bfe.lift()).collect()) - .collect(); - let ram_table_full_data = - vec![lifted_base_ram_table_data, ext_ram_table_data.to_vec()].concat(); - let ram_table = ExtRamTable::new(Table::new( - RamBaseTableColumn::COUNT, - RamBaseTableColumn::COUNT + RamExtTableColumn::COUNT, - ram_table_full_data, - "ExtRamTable over quotient domain".to_string(), - )); - - let (base_jump_stack_table_data, base_codewords) = - base_codewords.split_at(JumpStackBaseTableColumn::COUNT); - let (ext_jump_stack_table_data, extension_codewords) = - extension_codewords.split_at(JumpStackExtTableColumn::COUNT); - let lifted_base_jump_stack_table_data: Vec<_> = base_jump_stack_table_data - .into_par_iter() - .map(|codeword| codeword.par_iter().map(|bfe| bfe.lift()).collect()) - .collect(); - let jump_stack_table_full_data = vec![ - lifted_base_jump_stack_table_data, - ext_jump_stack_table_data.to_vec(), - ] - .concat(); - let jump_stack_table = ExtJumpStackTable::new(Table::new( - JumpStackBaseTableColumn::COUNT, - JumpStackBaseTableColumn::COUNT + JumpStackExtTableColumn::COUNT, - jump_stack_table_full_data, - "ExtJumpStackTable over quotient domain".to_string(), - )); - - let (base_hash_table_data, base_codewords) = - base_codewords.split_at(HashBaseTableColumn::COUNT); - let (ext_hash_table_data, extension_codewords) = - extension_codewords.split_at(HashExtTableColumn::COUNT); - let lifted_base_hash_table_data: Vec<_> = base_hash_table_data - .into_par_iter() - .map(|codeword| codeword.par_iter().map(|bfe| bfe.lift()).collect()) - .collect(); - let hash_table_full_data = - vec![lifted_base_hash_table_data, ext_hash_table_data.to_vec()].concat(); - let hash_table = ExtHashTable::new(Table::new( - HashBaseTableColumn::COUNT, - HashBaseTableColumn::COUNT + HashExtTableColumn::COUNT, - hash_table_full_data, - "ExtHashTable over quotient domain".to_string(), - )); - - assert!(base_codewords.is_empty()); - assert!(extension_codewords.is_empty()); - - Self { - padded_height, - program_table, - instruction_table, - processor_table, - op_stack_table, - ram_table, - jump_stack_table, - hash_table, - } - } - - pub fn for_verifier(padded_height: usize, maybe_profiler: &mut Option) -> Self { - prof_start!(maybe_profiler, "program table"); - let ext_program_table = ProgramTable::for_verifier(); - prof_stop!(maybe_profiler, "program table"); - prof_start!(maybe_profiler, "instruction table"); - let ext_instruction_table = InstructionTable::for_verifier(); - prof_stop!(maybe_profiler, "instruction table"); - prof_start!(maybe_profiler, "processor table"); - let ext_processor_table = ProcessorTable::for_verifier(); - prof_stop!(maybe_profiler, "processor table"); - prof_start!(maybe_profiler, "op stack table"); - let ext_op_stack_table = OpStackTable::for_verifier(); - prof_stop!(maybe_profiler, "op stack table"); - prof_start!(maybe_profiler, "ram table"); - let ext_ram_table = RamTable::for_verifier(); - prof_stop!(maybe_profiler, "ram table"); - prof_start!(maybe_profiler, "jump stack table"); - let ext_jump_stack_table = JumpStackTable::for_verifier(); - prof_stop!(maybe_profiler, "jump stack table"); - prof_start!(maybe_profiler, "hash table"); - let ext_hash_table = HashTable::for_verifier(); - prof_stop!(maybe_profiler, "hash table"); - - ExtTableCollection { - padded_height, - program_table: ext_program_table, - instruction_table: ext_instruction_table, - processor_table: ext_processor_table, - op_stack_table: ext_op_stack_table, - ram_table: ext_ram_table, - jump_stack_table: ext_jump_stack_table, - hash_table: ext_hash_table, - } - } - - pub fn max_degree_with_origin(&self, num_trace_randomizers: usize) -> DegreeWithOrigin { - self.into_iter() - .map(|ext_table| { - ext_table.all_degrees_with_origin(self.padded_height, num_trace_randomizers) - }) - .concat() - .into_iter() - .max() - .unwrap_or_default() - } - - /// Create an ExtTableCollection from a BaseTableCollection by `.extend()`ing each base table. - /// The `.extend()` for each table is specific to that table, but always - /// involves adding some number of columns. Each table only needs their - /// own challenges, but `AllChallenges` are passed everywhere to keep each table's `.extend()` - /// homogenous. - pub fn extend_tables( - base_tables: &BaseTableCollection, - all_challenges: &AllChallenges, - ) -> Self { - let padded_height = base_tables.padded_height; - let program_table = base_tables - .program_table - .extend(&all_challenges.program_table_challenges); - let instruction_table = base_tables - .instruction_table - .extend(&all_challenges.instruction_table_challenges); - let processor_table = base_tables - .processor_table - .extend(&all_challenges.processor_table_challenges); - let op_stack_table = base_tables - .op_stack_table - .extend(&all_challenges.op_stack_table_challenges); - let ram_table = base_tables - .ram_table - .extend(&all_challenges.ram_table_challenges); - let jump_stack_table = base_tables - .jump_stack_table - .extend(&all_challenges.jump_stack_table_challenges); - let hash_table = base_tables - .hash_table - .extend(&all_challenges.hash_table_challenges); - - ExtTableCollection { - padded_height, - program_table, - instruction_table, - processor_table, - op_stack_table, - ram_table, - jump_stack_table, - hash_table, - } - } - - /// Heads up: only extension columns are low-degree extended – base columns are already covered. - pub fn to_fri_domain_tables( - &self, - fri_domain: &ArithmeticDomain, - num_trace_randomizers: usize, - maybe_profiler: &mut Option, - ) -> Self { - prof_start!(maybe_profiler, "program table"); - let program_table = ExtProgramTable::new(self.program_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - program_table::BASE_WIDTH..program_table::FULL_WIDTH, - )); - prof_stop!(maybe_profiler, "program table"); - prof_start!(maybe_profiler, "instruction table"); - let instruction_table = - ExtInstructionTable::new(self.instruction_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - instruction_table::BASE_WIDTH..instruction_table::FULL_WIDTH, - )); - prof_stop!(maybe_profiler, "instruction table"); - prof_start!(maybe_profiler, "processor table"); - let processor_table = - ExtProcessorTable::new(self.processor_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - processor_table::BASE_WIDTH..processor_table::FULL_WIDTH, - )); - prof_stop!(maybe_profiler, "processor table"); - prof_start!(maybe_profiler, "op stack table"); - let op_stack_table = - ExtOpStackTable::new(self.op_stack_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - op_stack_table::BASE_WIDTH..op_stack_table::FULL_WIDTH, - )); - prof_stop!(maybe_profiler, "op stack table"); - prof_start!(maybe_profiler, "ram table"); - let ram_table = ExtRamTable::new(self.ram_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - ram_table::BASE_WIDTH..ram_table::FULL_WIDTH, - )); - prof_stop!(maybe_profiler, "ram table"); - prof_start!(maybe_profiler, "jump stack table"); - let jump_stack_table = - ExtJumpStackTable::new(self.jump_stack_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - jump_stack_table::BASE_WIDTH..jump_stack_table::FULL_WIDTH, - )); - prof_stop!(maybe_profiler, "jump stack table"); - prof_start!(maybe_profiler, "hash table"); - let hash_table = ExtHashTable::new(self.hash_table.randomized_low_deg_extension( - fri_domain, - num_trace_randomizers, - hash_table::BASE_WIDTH..hash_table::FULL_WIDTH, - )); - prof_stop!(maybe_profiler, "hash table"); - - ExtTableCollection { - padded_height: self.padded_height, - program_table, - instruction_table, - processor_table, - op_stack_table, - ram_table, - jump_stack_table, - hash_table, - } - } - - pub fn collect_all_columns(&self) -> Vec> { - let mut all_ext_cols = vec![]; - - for table in self.into_iter() { - for col in table.data().iter() { - all_ext_cols.push(col.clone()); - } - } - all_ext_cols - } - - pub fn data(&self, table_id: TableId) -> &Vec> { - use TableId::*; - - match table_id { - ProgramTable => self.program_table.data(), - InstructionTable => self.instruction_table.data(), - ProcessorTable => self.processor_table.data(), - OpStackTable => self.op_stack_table.data(), - RamTable => self.ram_table.data(), - JumpStackTable => self.jump_stack_table.data(), - HashTable => self.hash_table.data(), - } - } - - pub fn get_all_base_degree_bounds(&self, num_trace_randomizers: usize) -> Vec { - let sum_base_widths = self.into_iter().map(|table| table.base_width()).sum(); - vec![interpolant_degree(self.padded_height, num_trace_randomizers); sum_base_widths] - } - - pub fn get_extension_degree_bounds(&self, num_trace_randomizers: usize) -> Vec { - let sum_base_widths: usize = self.into_iter().map(|table| table.base_width()).sum(); - let sum_full_widths: usize = self.into_iter().map(|table| table.full_width()).sum(); - let num_extension_columns = sum_full_widths - sum_base_widths; - vec![interpolant_degree(self.padded_height, num_trace_randomizers); num_extension_columns] - } - - pub fn get_all_quotients( - &self, - domain: &ArithmeticDomain, - challenges: &AllChallenges, - maybe_profiler: &mut Option, - ) -> Vec> { - let padded_height = self.padded_height; - let trace_domain_generator = derive_trace_domain_generator(padded_height as u64); - - self.into_iter() - .map(|ext_codeword_table| { - prof_start!(maybe_profiler, &ext_codeword_table.name()); - // TODO: Consider if we can use `transposed_ext_codewords` from caller, Stark::prove(). - // This would require more complicated indexing, but it would save a lot of allocation. - let transposed_codewords = transpose(ext_codeword_table.data()); - let res = ext_codeword_table.all_quotients( - domain, - transposed_codewords, - challenges, - trace_domain_generator, - padded_height, - maybe_profiler, - ); - prof_stop!(maybe_profiler, &ext_codeword_table.name()); - res - }) - .concat() - } - - pub fn get_all_quotient_degree_bounds(&self, num_trace_randomizers: usize) -> Vec { - self.into_iter() - .map(|ext_table| { - ext_table.get_all_quotient_degree_bounds(self.padded_height, num_trace_randomizers) - }) - .concat() - } - - pub fn join( - base_codeword_tables: BaseTableCollection, - ext_codeword_tables: ExtTableCollection, - ) -> ExtTableCollection { - let padded_height = base_codeword_tables.padded_height; - - let program_base_matrix = base_codeword_tables.program_table.data(); - let lifted_program_base_matrix = program_base_matrix - .iter() - .map(|cdwd| cdwd.iter().map(|bfe| bfe.lift()).collect_vec()) - .collect_vec(); - let program_ext_matrix = ext_codeword_tables.program_table.data(); - let full_program_matrix = - vec![lifted_program_base_matrix, program_ext_matrix.to_vec()].concat(); - let joined_program_table = ext_codeword_tables - .program_table - .inherited_table() - .with_data(full_program_matrix); - let program_table = ExtProgramTable { - inherited_table: joined_program_table, - }; - - let instruction_base_matrix = base_codeword_tables.instruction_table.data(); - let lifted_instruction_base_matrix = instruction_base_matrix - .iter() - .map(|cdwd| cdwd.iter().map(|bfe| bfe.lift()).collect_vec()) - .collect_vec(); - let instruction_ext_matrix = ext_codeword_tables.instruction_table.data(); - let full_instruction_matrix = vec![ - lifted_instruction_base_matrix, - instruction_ext_matrix.to_vec(), - ] - .concat(); - let joined_instruction_table = ext_codeword_tables - .instruction_table - .inherited_table() - .with_data(full_instruction_matrix); - let instruction_table = ExtInstructionTable { - inherited_table: joined_instruction_table, - }; - - let processor_base_matrix = base_codeword_tables.processor_table.data(); - let lifted_processor_base_matrix = processor_base_matrix - .iter() - .map(|cdwd| cdwd.iter().map(|bfe| bfe.lift()).collect_vec()) - .collect_vec(); - let processor_ext_matrix = ext_codeword_tables.processor_table.data(); - let full_processor_matrix = - vec![lifted_processor_base_matrix, processor_ext_matrix.to_vec()].concat(); - let joined_processor_table = ext_codeword_tables - .processor_table - .inherited_table() - .with_data(full_processor_matrix); - let processor_table = ExtProcessorTable { - inherited_table: joined_processor_table, - }; - - let op_stack_base_matrix = base_codeword_tables.op_stack_table.data(); - let lifted_op_stack_base_matrix = op_stack_base_matrix - .iter() - .map(|cdwd| cdwd.iter().map(|bfe| bfe.lift()).collect_vec()) - .collect_vec(); - let op_stack_ext_matrix = ext_codeword_tables.op_stack_table.data(); - let full_op_stack_matrix = - vec![lifted_op_stack_base_matrix, op_stack_ext_matrix.to_vec()].concat(); - let joined_op_stack_table = ext_codeword_tables - .op_stack_table - .inherited_table() - .with_data(full_op_stack_matrix); - let op_stack_table = ExtOpStackTable { - inherited_table: joined_op_stack_table, - }; - - let ram_base_matrix = base_codeword_tables.ram_table.data(); - let lifted_ram_base_matrix = ram_base_matrix - .iter() - .map(|cdwd| cdwd.iter().map(|bfe| bfe.lift()).collect_vec()) - .collect_vec(); - let ram_ext_matrix = ext_codeword_tables.ram_table.data(); - let full_ram_matrix = vec![lifted_ram_base_matrix, ram_ext_matrix.to_vec()].concat(); - let joined_ram_table = ext_codeword_tables - .ram_table - .inherited_table() - .with_data(full_ram_matrix); - let ram_table = ExtRamTable { - inherited_table: joined_ram_table, - }; - - let jump_stack_base_matrix = base_codeword_tables.jump_stack_table.data(); - let lifted_jump_stack_base_matrix = jump_stack_base_matrix - .iter() - .map(|cdwd| cdwd.iter().map(|bfe| bfe.lift()).collect_vec()) - .collect_vec(); - let jump_stack_ext_matrix = ext_codeword_tables.jump_stack_table.data(); - let full_jump_stack_matrix = vec![ - lifted_jump_stack_base_matrix, - jump_stack_ext_matrix.to_vec(), - ] - .concat(); - let joined_jump_stack_table = ext_codeword_tables - .jump_stack_table - .inherited_table() - .with_data(full_jump_stack_matrix); - let jump_stack_table = ExtJumpStackTable { - inherited_table: joined_jump_stack_table, - }; - - let hash_base_matrix = base_codeword_tables.hash_table.data(); - let lifted_hash_base_matrix = hash_base_matrix - .iter() - .map(|cdwd| cdwd.iter().map(|bfe| bfe.lift()).collect_vec()) - .collect_vec(); - let hash_ext_matrix = ext_codeword_tables.hash_table.data(); - let full_hash_matrix = vec![lifted_hash_base_matrix, hash_ext_matrix.to_vec()].concat(); - let joined_hash_table = ext_codeword_tables - .hash_table - .inherited_table() - .with_data(full_hash_matrix); - let hash_table = ExtHashTable { - inherited_table: joined_hash_table, - }; - - ExtTableCollection { - padded_height, - program_table, - instruction_table, - processor_table, - op_stack_table, - ram_table, - jump_stack_table, - hash_table, - } - } -} - -impl<'a> IntoIterator for &'a ExtTableCollection { - type Item = &'a dyn QuotientableExtensionTable; - - type IntoIter = std::array::IntoIter<&'a dyn QuotientableExtensionTable, NUM_TABLES>; - - fn into_iter(self) -> Self::IntoIter { - [ - &self.program_table as &'a dyn QuotientableExtensionTable, - &self.instruction_table as &'a dyn QuotientableExtensionTable, - &self.processor_table as &'a dyn QuotientableExtensionTable, - &self.op_stack_table as &'a dyn QuotientableExtensionTable, - &self.ram_table as &'a dyn QuotientableExtensionTable, - &self.jump_stack_table as &'a dyn QuotientableExtensionTable, - &self.hash_table as &'a dyn QuotientableExtensionTable, - ] - .into_iter() - } -} - -#[cfg(test)] -mod table_collection_tests { - use crate::table::{ - hash_table, instruction_table, jump_stack_table, op_stack_table, processor_table, - program_table, ram_table, - }; - - use super::*; - - fn dummy_ext_table_collection() -> ExtTableCollection { - let max_padded_height = 1; - ExtTableCollection::with_padded_height(max_padded_height) - } - - #[test] - fn base_table_width_is_correct() { - let base_matrices = BaseMatrices::default(); - let base_tables = BaseTableCollection::from_base_matrices(&base_matrices); - - assert_eq!( - program_table::BASE_WIDTH, - base_tables.program_table.base_width() - ); - assert_eq!( - instruction_table::BASE_WIDTH, - base_tables.instruction_table.base_width() - ); - assert_eq!( - processor_table::BASE_WIDTH, - base_tables.processor_table.base_width() - ); - assert_eq!( - op_stack_table::BASE_WIDTH, - base_tables.op_stack_table.base_width() - ); - assert_eq!(ram_table::BASE_WIDTH, base_tables.ram_table.base_width()); - assert_eq!( - jump_stack_table::BASE_WIDTH, - base_tables.jump_stack_table.base_width() - ); - assert_eq!(hash_table::BASE_WIDTH, base_tables.hash_table.base_width()); - } - - #[test] - fn ext_table_width_is_correct() { - let ext_tables = dummy_ext_table_collection(); - - assert_eq!( - program_table::FULL_WIDTH, - ext_tables.program_table.full_width() - ); - assert_eq!( - instruction_table::FULL_WIDTH, - ext_tables.instruction_table.full_width() - ); - assert_eq!( - processor_table::FULL_WIDTH, - ext_tables.processor_table.full_width() - ); - assert_eq!( - op_stack_table::FULL_WIDTH, - ext_tables.op_stack_table.full_width() - ); - assert_eq!(ram_table::FULL_WIDTH, ext_tables.ram_table.full_width()); - assert_eq!( - jump_stack_table::FULL_WIDTH, - ext_tables.jump_stack_table.full_width() - ); - assert_eq!(hash_table::FULL_WIDTH, ext_tables.hash_table.full_width()); - } - - /// intended use: `cargo t print_all_table_widths -- --nocapture` - #[test] - fn print_all_table_widths() { - println!("| table name | #base cols | #ext cols | full width |"); - println!("|:-------------------|-----------:|----------:|-----------:|"); - for table in dummy_ext_table_collection().into_iter() { - println!( - "| {:<18} | {:>10} | {:>9} | {:>10} |", - table.name().split_off(8), - table.base_width(), - table.full_width() - table.base_width(), - table.full_width(), - ); - } - let sum_base_columns: usize = dummy_ext_table_collection() - .into_iter() - .map(|table| table.base_width()) - .sum(); - let sum_full_widths: usize = dummy_ext_table_collection() - .into_iter() - .map(|table| table.full_width()) - .sum(); - println!("| | | | |"); - println!( - "| Sum | {:>10} | {:>9} | {:>10} |", - sum_base_columns, - sum_full_widths - sum_base_columns, - sum_full_widths - ); - } -} diff --git a/triton-vm/src/table/table_column.rs b/triton-vm/src/table/table_column.rs index 555d78069..bab444012 100644 --- a/triton-vm/src/table/table_column.rs +++ b/triton-vm/src/table/table_column.rs @@ -1,17 +1,70 @@ //! Enums that convert table column names into `usize` indices //! -//! These let one address a given column by its name rather than its arbitrary index. +//! Allows addressing columns by name rather than their hard-to-remember index. + +use std::hash::Hash; + +use strum_macros::Display; +use strum_macros::EnumCount as EnumCountMacro; +use strum_macros::EnumIter; + +use crate::table::master_table::EXT_HASH_TABLE_START; +use crate::table::master_table::EXT_INSTRUCTION_TABLE_START; +use crate::table::master_table::EXT_JUMP_STACK_TABLE_START; +use crate::table::master_table::EXT_OP_STACK_TABLE_START; +use crate::table::master_table::EXT_PROCESSOR_TABLE_START; +use crate::table::master_table::EXT_PROGRAM_TABLE_START; +use crate::table::master_table::EXT_RAM_TABLE_START; +use crate::table::master_table::HASH_TABLE_START; +use crate::table::master_table::INSTRUCTION_TABLE_START; +use crate::table::master_table::JUMP_STACK_TABLE_START; +use crate::table::master_table::OP_STACK_TABLE_START; +use crate::table::master_table::PROCESSOR_TABLE_START; +use crate::table::master_table::PROGRAM_TABLE_START; +use crate::table::master_table::RAM_TABLE_START; + +// -------- Program Table -------- + +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] +pub enum ProgramBaseTableColumn { + Address, + Instruction, + IsPadding, +} -// -------------------------------------------------------------------- +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] +pub enum ProgramExtTableColumn { + RunningEvaluation, +} + +// -------- Instruction Table -------- + +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] +pub enum InstructionBaseTableColumn { + Address, + CI, + NIA, + IsPadding, +} + +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] +pub enum InstructionExtTableColumn { + RunningProductPermArg, + RunningEvaluation, +} -use num_traits::Bounded; -use strum::{EnumCount, IntoEnumIterator}; -use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter}; +// -------- Processor Table -------- -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum ProcessorBaseTableColumn { CLK, IsPadding, + PreviousInstruction, IP, CI, NIA, @@ -54,27 +107,8 @@ pub enum ProcessorBaseTableColumn { RAMV, } -impl From for usize { - fn from(column: ProcessorBaseTableColumn) -> Self { - ProcessorBaseTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n) - .unwrap() - } -} - -impl Bounded for ProcessorBaseTableColumn { - fn min_value() -> Self { - ProcessorBaseTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - ProcessorBaseTableColumn::iter().last().unwrap() - } -} - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum ProcessorExtTableColumn { InputTableEvalArg, OutputTableEvalArg, @@ -91,139 +125,10 @@ pub enum ProcessorExtTableColumn { AllClockJumpDifferencesPermArg, } -impl From for usize { - fn from(column: ProcessorExtTableColumn) -> Self { - ProcessorExtTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n + ProcessorBaseTableColumn::COUNT) - .unwrap() - } -} - -impl Bounded for ProcessorExtTableColumn { - fn min_value() -> Self { - ProcessorExtTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - ProcessorExtTableColumn::iter().last().unwrap() - } -} - -// -------------------------------------------------------------------- - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] -pub enum ProgramBaseTableColumn { - Address, - Instruction, - IsPadding, -} - -impl From for usize { - fn from(column: ProgramBaseTableColumn) -> Self { - ProgramBaseTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n) - .unwrap() - } -} - -impl Bounded for ProgramBaseTableColumn { - fn min_value() -> Self { - ProgramBaseTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - ProgramBaseTableColumn::iter().last().unwrap() - } -} - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] -pub enum ProgramExtTableColumn { - RunningEvaluation, -} - -impl From for usize { - fn from(column: ProgramExtTableColumn) -> Self { - ProgramExtTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n + ProgramBaseTableColumn::COUNT) - .unwrap() - } -} - -impl Bounded for ProgramExtTableColumn { - fn min_value() -> Self { - ProgramExtTableColumn::iter().next().unwrap() - } +// -------- OpStack Table -------- - fn max_value() -> Self { - ProgramExtTableColumn::iter().last().unwrap() - } -} - -// -------------------------------------------------------------------- - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] -pub enum InstructionBaseTableColumn { - Address, - CI, - NIA, - IsPadding, -} - -impl From for usize { - fn from(column: InstructionBaseTableColumn) -> Self { - InstructionBaseTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n) - .unwrap() - } -} - -impl Bounded for InstructionBaseTableColumn { - fn min_value() -> Self { - InstructionBaseTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - InstructionBaseTableColumn::iter().last().unwrap() - } -} - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] -pub enum InstructionExtTableColumn { - RunningProductPermArg, - RunningEvaluation, -} - -impl From for usize { - fn from(column: InstructionExtTableColumn) -> Self { - InstructionExtTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n + InstructionBaseTableColumn::COUNT) - .unwrap() - } -} - -impl Bounded for InstructionExtTableColumn { - fn min_value() -> Self { - InstructionExtTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - InstructionExtTableColumn::iter().last().unwrap() - } -} - -// -------------------------------------------------------------------- - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum OpStackBaseTableColumn { CLK, InverseOfClkDiffMinusOne, @@ -232,58 +137,21 @@ pub enum OpStackBaseTableColumn { OSV, } -impl From for usize { - fn from(column: OpStackBaseTableColumn) -> Self { - OpStackBaseTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n) - .unwrap() - } -} - -impl Bounded for OpStackBaseTableColumn { - fn min_value() -> Self { - OpStackBaseTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - OpStackBaseTableColumn::iter().last().unwrap() - } -} - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum OpStackExtTableColumn { RunningProductPermArg, AllClockJumpDifferencesPermArg, } -impl From for usize { - fn from(column: OpStackExtTableColumn) -> Self { - OpStackExtTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n + OpStackBaseTableColumn::COUNT) - .unwrap() - } -} - -impl Bounded for OpStackExtTableColumn { - fn min_value() -> Self { - OpStackExtTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - OpStackExtTableColumn::iter().last().unwrap() - } -} +// -------- RAM Table -------- -// -------------------------------------------------------------------- - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum RamBaseTableColumn { CLK, InverseOfClkDiffMinusOne, + PreviousInstruction, RAMP, RAMV, InverseOfRampDifference, @@ -291,27 +159,8 @@ pub enum RamBaseTableColumn { BezoutCoefficientPolynomialCoefficient1, } -impl From for usize { - fn from(column: RamBaseTableColumn) -> Self { - RamBaseTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n) - .unwrap() - } -} - -impl Bounded for RamBaseTableColumn { - fn min_value() -> Self { - RamBaseTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - RamBaseTableColumn::iter().last().unwrap() - } -} - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum RamExtTableColumn { RunningProductOfRAMP, FormalDerivative, @@ -321,29 +170,10 @@ pub enum RamExtTableColumn { AllClockJumpDifferencesPermArg, } -impl From for usize { - fn from(column: RamExtTableColumn) -> Self { - RamExtTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n + RamBaseTableColumn::COUNT) - .unwrap() - } -} +// -------- JumpStack Table -------- -impl Bounded for RamExtTableColumn { - fn min_value() -> Self { - RamExtTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - RamExtTableColumn::iter().last().unwrap() - } -} - -// -------------------------------------------------------------------- - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum JumpStackBaseTableColumn { CLK, InverseOfClkDiffMinusOne, @@ -353,55 +183,17 @@ pub enum JumpStackBaseTableColumn { JSD, } -impl From for usize { - fn from(column: JumpStackBaseTableColumn) -> Self { - JumpStackBaseTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n) - .unwrap() - } -} - -impl Bounded for JumpStackBaseTableColumn { - fn min_value() -> Self { - JumpStackBaseTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - JumpStackBaseTableColumn::iter().last().unwrap() - } -} - -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum JumpStackExtTableColumn { RunningProductPermArg, AllClockJumpDifferencesPermArg, } -impl From for usize { - fn from(column: JumpStackExtTableColumn) -> Self { - JumpStackExtTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n + JumpStackBaseTableColumn::COUNT) - .unwrap() - } -} - -impl Bounded for JumpStackExtTableColumn { - fn min_value() -> Self { - JumpStackExtTableColumn::iter().next().unwrap() - } - - fn max_value() -> Self { - JumpStackExtTableColumn::iter().last().unwrap() - } -} - -// -------------------------------------------------------------------- +// -------- Hash Table -------- -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] pub enum HashBaseTableColumn { ROUNDNUMBER, STATE0, @@ -454,165 +246,435 @@ pub enum HashBaseTableColumn { CONSTANT15B, } -impl From for usize { - fn from(column: HashBaseTableColumn) -> Self { - HashBaseTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n) - .unwrap() +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] +pub enum HashExtTableColumn { + ToProcessorRunningEvaluation, + FromProcessorRunningEvaluation, +} + +// -------------------------------------------------------------------- + +pub trait BaseTableColumn { + fn base_table_index(&self) -> usize; +} + +impl BaseTableColumn for ProgramBaseTableColumn { + #[inline] + fn base_table_index(&self) -> usize { + (*self) as usize } } -impl TryFrom for HashBaseTableColumn { - type Error = String; +impl BaseTableColumn for InstructionBaseTableColumn { + #[inline] + fn base_table_index(&self) -> usize { + (*self) as usize + } +} - fn try_from(idx: usize) -> Result { - HashBaseTableColumn::iter() - .get(idx) - .ok_or_else(|| format!("Column index {} out of bounds", idx)) +impl BaseTableColumn for ProcessorBaseTableColumn { + #[inline] + fn base_table_index(&self) -> usize { + (*self) as usize } } -impl Bounded for HashBaseTableColumn { - fn min_value() -> Self { - HashBaseTableColumn::iter().next().unwrap() +impl BaseTableColumn for OpStackBaseTableColumn { + #[inline] + fn base_table_index(&self) -> usize { + (*self) as usize } +} - fn max_value() -> Self { - HashBaseTableColumn::iter().last().unwrap() +impl BaseTableColumn for RamBaseTableColumn { + #[inline] + fn base_table_index(&self) -> usize { + (*self) as usize } } -#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro)] -pub enum HashExtTableColumn { - ToProcessorRunningEvaluation, - FromProcessorRunningEvaluation, +impl BaseTableColumn for JumpStackBaseTableColumn { + #[inline] + fn base_table_index(&self) -> usize { + (*self) as usize + } +} + +impl BaseTableColumn for HashBaseTableColumn { + #[inline] + fn base_table_index(&self) -> usize { + (*self) as usize + } +} + +// -------------------------------------------------------------------- + +pub trait ExtTableColumn { + fn ext_table_index(&self) -> usize; +} + +impl ExtTableColumn for ProgramExtTableColumn { + #[inline] + fn ext_table_index(&self) -> usize { + (*self) as usize + } +} + +impl ExtTableColumn for InstructionExtTableColumn { + #[inline] + fn ext_table_index(&self) -> usize { + (*self) as usize + } +} + +impl ExtTableColumn for ProcessorExtTableColumn { + #[inline] + fn ext_table_index(&self) -> usize { + (*self) as usize + } +} + +impl ExtTableColumn for OpStackExtTableColumn { + #[inline] + fn ext_table_index(&self) -> usize { + (*self) as usize + } +} + +impl ExtTableColumn for RamExtTableColumn { + #[inline] + fn ext_table_index(&self) -> usize { + (*self) as usize + } +} + +impl ExtTableColumn for JumpStackExtTableColumn { + #[inline] + fn ext_table_index(&self) -> usize { + (*self) as usize + } +} + +impl ExtTableColumn for HashExtTableColumn { + #[inline] + fn ext_table_index(&self) -> usize { + (*self) as usize + } +} + +// -------------------------------------------------------------------- + +pub trait MasterBaseTableColumn: BaseTableColumn { + fn master_base_table_index(&self) -> usize; +} + +impl MasterBaseTableColumn for ProgramBaseTableColumn { + #[inline] + fn master_base_table_index(&self) -> usize { + PROGRAM_TABLE_START + self.base_table_index() + } +} + +impl MasterBaseTableColumn for InstructionBaseTableColumn { + #[inline] + fn master_base_table_index(&self) -> usize { + INSTRUCTION_TABLE_START + self.base_table_index() + } +} + +impl MasterBaseTableColumn for ProcessorBaseTableColumn { + #[inline] + fn master_base_table_index(&self) -> usize { + PROCESSOR_TABLE_START + self.base_table_index() + } +} + +impl MasterBaseTableColumn for OpStackBaseTableColumn { + #[inline] + fn master_base_table_index(&self) -> usize { + OP_STACK_TABLE_START + self.base_table_index() + } +} + +impl MasterBaseTableColumn for RamBaseTableColumn { + #[inline] + fn master_base_table_index(&self) -> usize { + RAM_TABLE_START + self.base_table_index() + } +} + +impl MasterBaseTableColumn for JumpStackBaseTableColumn { + #[inline] + fn master_base_table_index(&self) -> usize { + JUMP_STACK_TABLE_START + self.base_table_index() + } } -impl From for usize { - fn from(column: HashExtTableColumn) -> Self { - HashExtTableColumn::iter() - .enumerate() - .find(|&(_n, col)| column == col) - .map(|(n, _col)| n + HashBaseTableColumn::COUNT) - .unwrap() +impl MasterBaseTableColumn for HashBaseTableColumn { + #[inline] + fn master_base_table_index(&self) -> usize { + HASH_TABLE_START + self.base_table_index() } } -impl Bounded for HashExtTableColumn { - fn min_value() -> Self { - HashExtTableColumn::iter().next().unwrap() +// -------------------------------------------------------------------- + +pub trait MasterExtTableColumn: ExtTableColumn { + fn master_ext_table_index(&self) -> usize; +} + +impl MasterExtTableColumn for ProgramExtTableColumn { + #[inline] + fn master_ext_table_index(&self) -> usize { + EXT_PROGRAM_TABLE_START + self.ext_table_index() } +} + +impl MasterExtTableColumn for InstructionExtTableColumn { + #[inline] + fn master_ext_table_index(&self) -> usize { + EXT_INSTRUCTION_TABLE_START + self.ext_table_index() + } +} - fn max_value() -> Self { - HashExtTableColumn::iter().last().unwrap() +impl MasterExtTableColumn for ProcessorExtTableColumn { + #[inline] + fn master_ext_table_index(&self) -> usize { + EXT_PROCESSOR_TABLE_START + self.ext_table_index() } } +impl MasterExtTableColumn for OpStackExtTableColumn { + #[inline] + fn master_ext_table_index(&self) -> usize { + EXT_OP_STACK_TABLE_START + self.ext_table_index() + } +} + +impl MasterExtTableColumn for RamExtTableColumn { + #[inline] + fn master_ext_table_index(&self) -> usize { + EXT_RAM_TABLE_START + self.ext_table_index() + } +} + +impl MasterExtTableColumn for JumpStackExtTableColumn { + #[inline] + fn master_ext_table_index(&self) -> usize { + EXT_JUMP_STACK_TABLE_START + self.ext_table_index() + } +} + +impl MasterExtTableColumn for HashExtTableColumn { + #[inline] + fn master_ext_table_index(&self) -> usize { + EXT_HASH_TABLE_START + self.ext_table_index() + } +} + +// -------------------------------------------------------------------- + #[cfg(test)] mod table_column_tests { - use crate::table::{ - hash_table, instruction_table, jump_stack_table, op_stack_table, processor_table, - program_table, ram_table, - }; + use strum::IntoEnumIterator; + + use crate::table::hash_table; + use crate::table::instruction_table; + use crate::table::jump_stack_table; + use crate::table::op_stack_table; + use crate::table::processor_table; + use crate::table::program_table; + use crate::table::ram_table; use super::*; - struct TestCase<'a> { - base_width: usize, - full_width: usize, - max_base_column: usize, - max_ext_column: usize, - table_name: &'a str, + #[test] + fn column_max_bound_matches_table_width() { + assert_eq!( + program_table::BASE_WIDTH, + ProgramBaseTableColumn::iter() + .last() + .unwrap() + .base_table_index() + + 1, + "ProgramTable's BASE_WIDTH is 1 + its max column index", + ); + assert_eq!( + instruction_table::BASE_WIDTH, + InstructionBaseTableColumn::iter() + .last() + .unwrap() + .base_table_index() + + 1, + "InstructionTable's BASE_WIDTH is 1 + its max column index", + ); + assert_eq!( + processor_table::BASE_WIDTH, + ProcessorBaseTableColumn::iter() + .last() + .unwrap() + .base_table_index() + + 1, + "ProcessorTable's BASE_WIDTH is 1 + its max column index", + ); + assert_eq!( + op_stack_table::BASE_WIDTH, + OpStackBaseTableColumn::iter() + .last() + .unwrap() + .base_table_index() + + 1, + "OpStackTable's BASE_WIDTH is 1 + its max column index", + ); + assert_eq!( + ram_table::BASE_WIDTH, + RamBaseTableColumn::iter() + .last() + .unwrap() + .base_table_index() + + 1, + "RamTable's BASE_WIDTH is 1 + its max column index", + ); + assert_eq!( + jump_stack_table::BASE_WIDTH, + JumpStackBaseTableColumn::iter() + .last() + .unwrap() + .base_table_index() + + 1, + "JumpStackTable's BASE_WIDTH is 1 + its max column index", + ); + assert_eq!( + hash_table::BASE_WIDTH, + HashBaseTableColumn::iter() + .last() + .unwrap() + .base_table_index() + + 1, + "HashTable's BASE_WIDTH is 1 + its max column index", + ); + + assert_eq!( + program_table::EXT_WIDTH, + ProgramExtTableColumn::iter() + .last() + .unwrap() + .ext_table_index() + + 1, + "ProgramTable's EXT_WIDTH is 1 + its max column index", + ); + assert_eq!( + instruction_table::EXT_WIDTH, + InstructionExtTableColumn::iter() + .last() + .unwrap() + .ext_table_index() + + 1, + "InstructionTable's EXT_WIDTH is 1 + its max column index", + ); + assert_eq!( + processor_table::EXT_WIDTH, + ProcessorExtTableColumn::iter() + .last() + .unwrap() + .ext_table_index() + + 1, + "ProcessorTable's EXT_WIDTH is 1 + its max column index", + ); + assert_eq!( + op_stack_table::EXT_WIDTH, + OpStackExtTableColumn::iter() + .last() + .unwrap() + .ext_table_index() + + 1, + "OpStack:Table's EXT_WIDTH is 1 + its max column index", + ); + assert_eq!( + ram_table::EXT_WIDTH, + RamExtTableColumn::iter().last().unwrap().ext_table_index() + 1, + "RamTable's EXT_WIDTH is 1 + its max column index", + ); + assert_eq!( + jump_stack_table::EXT_WIDTH, + JumpStackExtTableColumn::iter() + .last() + .unwrap() + .ext_table_index() + + 1, + "JumpStack:Table's EXT_WIDTH is 1 + its max column index", + ); + assert_eq!( + hash_table::EXT_WIDTH, + HashExtTableColumn::iter().last().unwrap().ext_table_index() + 1, + "HashTable's EXT_WIDTH is 1 + its max column index", + ); } - impl<'a> TestCase<'a> { - pub fn new( - base_width: usize, - full_width: usize, - max_base_column: usize, - max_ext_column: usize, - table_name: &'a str, - ) -> Self { - TestCase { - base_width, - full_width, - max_base_column, - max_ext_column, - table_name, - } + #[test] + fn master_base_table_is_contiguous() { + let mut expected_column_index = 0; + for column in ProgramBaseTableColumn::iter() { + assert_eq!(expected_column_index, column.master_base_table_index()); + expected_column_index += 1; + } + for column in InstructionBaseTableColumn::iter() { + assert_eq!(expected_column_index, column.master_base_table_index()); + expected_column_index += 1; + } + for column in ProcessorBaseTableColumn::iter() { + assert_eq!(expected_column_index, column.master_base_table_index()); + expected_column_index += 1; + } + for column in OpStackBaseTableColumn::iter() { + assert_eq!(expected_column_index, column.master_base_table_index()); + expected_column_index += 1; + } + for column in RamBaseTableColumn::iter() { + assert_eq!(expected_column_index, column.master_base_table_index()); + expected_column_index += 1; + } + for column in JumpStackBaseTableColumn::iter() { + assert_eq!(expected_column_index, column.master_base_table_index()); + expected_column_index += 1; + } + for column in HashBaseTableColumn::iter() { + assert_eq!(expected_column_index, column.master_base_table_index()); + expected_column_index += 1; } } #[test] - fn column_max_bound_matches_table_width() { - let cases: Vec = vec![ - TestCase::new( - program_table::BASE_WIDTH, - program_table::FULL_WIDTH, - ProgramBaseTableColumn::max_value().into(), - ProgramExtTableColumn::max_value().into(), - "ProgramTable", - ), - TestCase::new( - instruction_table::BASE_WIDTH, - instruction_table::FULL_WIDTH, - InstructionBaseTableColumn::max_value().into(), - InstructionExtTableColumn::max_value().into(), - "InstructionTable", - ), - TestCase::new( - processor_table::BASE_WIDTH, - processor_table::FULL_WIDTH, - ProcessorBaseTableColumn::max_value().into(), - ProcessorExtTableColumn::max_value().into(), - "ProcessorTable", - ), - TestCase::new( - op_stack_table::BASE_WIDTH, - op_stack_table::FULL_WIDTH, - OpStackBaseTableColumn::max_value().into(), - OpStackExtTableColumn::max_value().into(), - "OpStackTable", - ), - TestCase::new( - ram_table::BASE_WIDTH, - ram_table::FULL_WIDTH, - RamBaseTableColumn::max_value().into(), - RamExtTableColumn::max_value().into(), - "RamTable", - ), - TestCase::new( - jump_stack_table::BASE_WIDTH, - jump_stack_table::FULL_WIDTH, - JumpStackBaseTableColumn::max_value().into(), - JumpStackExtTableColumn::max_value().into(), - "JumpStackTable", - ), - TestCase::new( - hash_table::BASE_WIDTH, - hash_table::FULL_WIDTH, - HashBaseTableColumn::max_value().into(), - HashExtTableColumn::max_value().into(), - "HashTable", - ), - ]; - - for case in cases.iter() { - assert_eq!( - case.base_width, - case.max_base_column + 1, - "{}'s BASE_WIDTH is 1 + its max column index", - case.table_name - ); - - assert_eq!( - case.full_width, - case.max_ext_column + 1, - "Ext{}'s FULL_WIDTH is 1 + its max ext column index", - case.table_name - ); + fn master_ext_table_is_contiguous() { + let mut expected_column_index = 0; + for column in ProgramExtTableColumn::iter() { + assert_eq!(expected_column_index, column.master_ext_table_index()); + expected_column_index += 1; + } + for column in InstructionExtTableColumn::iter() { + assert_eq!(expected_column_index, column.master_ext_table_index()); + expected_column_index += 1; + } + for column in ProcessorExtTableColumn::iter() { + assert_eq!(expected_column_index, column.master_ext_table_index()); + expected_column_index += 1; + } + for column in OpStackExtTableColumn::iter() { + assert_eq!(expected_column_index, column.master_ext_table_index()); + expected_column_index += 1; + } + for column in RamExtTableColumn::iter() { + assert_eq!(expected_column_index, column.master_ext_table_index()); + expected_column_index += 1; + } + for column in JumpStackExtTableColumn::iter() { + assert_eq!(expected_column_index, column.master_ext_table_index()); + expected_column_index += 1; + } + for column in HashExtTableColumn::iter() { + assert_eq!(expected_column_index, column.master_ext_table_index()); + expected_column_index += 1; } } } diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 26ce0a525..573cc6a05 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -3,12 +3,80 @@ use std::io::Cursor; use anyhow::Result; use itertools::Itertools; +use ndarray::Array2; +use ndarray::Axis; use twenty_first::shared_math::b_field_element::BFieldElement; +use twenty_first::shared_math::rescue_prime_regular::NUM_ROUNDS; +use twenty_first::shared_math::rescue_prime_regular::ROUND_CONSTANTS; +use twenty_first::shared_math::rescue_prime_regular::STATE_SIZE; use crate::instruction; -use crate::instruction::{parse, Instruction, LabelledInstruction}; -use crate::state::{VMOutput, VMState}; -use crate::table::base_matrix::AlgebraicExecutionTrace; +use crate::instruction::parse; +use crate::instruction::Instruction; +use crate::instruction::LabelledInstruction; +use crate::state::VMOutput; +use crate::state::VMState; +use crate::table::hash_table; +use crate::table::hash_table::NUM_ROUND_CONSTANTS; +use crate::table::processor_table; +use crate::table::table_column::BaseTableColumn; +use crate::table::table_column::HashBaseTableColumn::CONSTANT0A; +use crate::table::table_column::HashBaseTableColumn::ROUNDNUMBER; +use crate::table::table_column::HashBaseTableColumn::STATE0; + +#[derive(Debug, Clone)] +pub struct AlgebraicExecutionTrace { + pub processor_matrix: Array2, + pub hash_matrix: Array2, +} + +impl Default for AlgebraicExecutionTrace { + fn default() -> Self { + Self { + processor_matrix: Array2::default([0, processor_table::BASE_WIDTH]), + hash_matrix: Array2::default([0, hash_table::BASE_WIDTH]), + } + } +} + +impl AlgebraicExecutionTrace { + pub fn append_hash_trace(&mut self, hash_trace: [[BFieldElement; STATE_SIZE]; NUM_ROUNDS + 1]) { + let mut hash_matrix_addendum = Array2::default([NUM_ROUNDS + 1, hash_table::BASE_WIDTH]); + for (row_idx, mut row) in hash_matrix_addendum.rows_mut().into_iter().enumerate() { + let round_number = row_idx + 1; + let trace_row = hash_trace[row_idx]; + let round_constants = Self::rescue_xlix_round_constants_by_round_number(round_number); + row[ROUNDNUMBER.base_table_index()] = BFieldElement::from(row_idx as u64 + 1); + for st_idx in 0..STATE_SIZE { + row[STATE0.base_table_index() + st_idx] = trace_row[st_idx]; + } + for rc_idx in 0..NUM_ROUND_CONSTANTS { + row[CONSTANT0A.base_table_index() + rc_idx] = round_constants[rc_idx]; + } + } + self.hash_matrix + .append(Axis(0), hash_matrix_addendum.view()) + .expect("shapes must be identical"); + } + + /// The 2·STATE_SIZE (= NUM_ROUND_CONSTANTS) round constants for round `round_number`. + /// Of note: + /// - Round index 0 indicates a padding row – all constants are zero. + /// - Round index 9 indicates an output row – all constants are zero. + pub fn rescue_xlix_round_constants_by_round_number( + round_number: usize, + ) -> [BFieldElement; NUM_ROUND_CONSTANTS] { + match round_number { + i if i == 0 || i == NUM_ROUNDS + 1 => [0_u64; NUM_ROUND_CONSTANTS], + i if i <= NUM_ROUNDS => ROUND_CONSTANTS + [NUM_ROUND_CONSTANTS * (i - 1)..NUM_ROUND_CONSTANTS * i] + .try_into() + .unwrap(), + _ => panic!("Round with number {round_number} does not have round constants."), + } + .map(BFieldElement::new) + } +} #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Program { @@ -124,7 +192,9 @@ impl Program { let mut aet = AlgebraicExecutionTrace::default(); let mut state = VMState::new(self); // record initial state - aet.processor_matrix.push(state.to_processor_row()); + aet.processor_matrix + .push_row(state.to_processor_row().view()) + .expect("shapes must be identical"); let mut stdout = vec![]; while !state.is_complete() { @@ -134,14 +204,14 @@ impl Program { }; match vm_output { - Some(VMOutput::XlixTrace(mut hash_trace)) => { - aet.hash_matrix.append(&mut hash_trace) - } + Some(VMOutput::XlixTrace(hash_trace)) => aet.append_hash_trace(*hash_trace), Some(VMOutput::WriteOutputSymbol(written_word)) => stdout.push(written_word), None => (), } - // Record next, to be executed state. If `Halt`, - aet.processor_matrix.push(state.to_processor_row()); + // Record next, to be executed state. + aet.processor_matrix + .push_row(state.to_processor_row().view()) + .expect("shapes must be identical"); } (aet, stdout, None) @@ -202,28 +272,34 @@ impl Program { #[cfg(test)] pub mod triton_vm_tests { - use std::ops::{BitAnd, BitXor}; + use std::ops::BitAnd; + use std::ops::BitXor; - use num_traits::{One, Zero}; + use ndarray::Array1; + use ndarray::ArrayView1; + use num_traits::One; + use num_traits::Zero; use rand::rngs::ThreadRng; - use rand::{Rng, RngCore}; - use triton_profiler::triton_profiler::TritonProfiler; - use twenty_first::shared_math::mpolynomial::MPolynomial; - use twenty_first::shared_math::other; - use twenty_first::shared_math::other::roundup_npo2; - use twenty_first::shared_math::rescue_prime_regular::{RescuePrimeRegular, NUM_ROUNDS}; - - use crate::instruction::{sample_programs, AnInstruction}; + use rand::Rng; + use rand::RngCore; + use twenty_first::shared_math::other::random_elements; + use twenty_first::shared_math::rescue_prime_regular::RescuePrimeRegular; + use twenty_first::shared_math::traits::FiniteField; + + use crate::instruction::sample_programs; use crate::shared_tests::SourceCodeAndInput; - use crate::table::base_matrix::{BaseMatrices, ProcessorMatrixRow}; - use crate::table::base_table::{Extendable, InheritsFromTable}; - use crate::table::challenges::AllChallenges; - use crate::table::extension_table::Evaluable; - use crate::table::processor_table::ProcessorTable; - use crate::table::table_column::ProcessorBaseTableColumn; + use crate::table::processor_table::ProcessorMatrixRow; use super::*; + fn pretty_print_array_view(array: ArrayView1) -> String { + array + .iter() + .map(|ff| format!("{ff}")) + .collect_vec() + .join(", ") + } + #[test] fn initialise_table_test() { let code = sample_programs::GCD_X_Y; @@ -231,21 +307,17 @@ pub mod triton_vm_tests { let stdin = vec![BFieldElement::new(42), BFieldElement::new(56)]; - let (base_matrices, stdout, err) = program.simulate(stdin, vec![]); + let (aet, stdout, err) = program.simulate(stdin, vec![]); println!( "VM output: [{}]", - stdout - .iter() - .map(|s| format!("{s}")) - .collect_vec() - .join(", ") + pretty_print_array_view(Array1::from(stdout).view()) ); if let Some(e) = err { panic!("Execution failed: {e}"); } - for row in base_matrices.processor_matrix { + for row in aet.processor_matrix.rows() { println!("{}", ProcessorMatrixRow { row }); } } @@ -263,10 +335,10 @@ pub mod triton_vm_tests { println!("{}", program); - let (base_matrices, _, err) = program.simulate_no_input(); + let (aet, _, err) = program.simulate_no_input(); println!("{:?}", err); - for row in base_matrices.processor_matrix { + for row in aet.processor_matrix.rows() { println!("{}", ProcessorMatrixRow { row }); } } @@ -279,14 +351,8 @@ pub mod triton_vm_tests { let stdin = vec![42_u64.into(), 56_u64.into()]; let (_, stdout, err) = program.simulate(stdin, vec![]); - println!( - "VM output: [{}]", - stdout - .iter() - .map(|s| format!("{s}")) - .collect_vec() - .join(", ") - ); + let stdout = Array1::from(stdout); + println!("VM output: [{}]", pretty_print_array_view(stdout.view())); if let Some(e) = err { panic!("Execution failed: {e}"); @@ -298,72 +364,14 @@ pub mod triton_vm_tests { assert_eq!(expected_symbol, computed_symbol); } - #[test] - fn hello_world() { - let code = sample_programs::HELLO_WORLD_1; - let program = Program::from_code(code).unwrap(); - - println!("{}", program); - - let (aet, stdout, err) = program.simulate_no_input(); - let base_matrices = BaseMatrices::new(aet, &program.to_bwords()); - - println!("{:?}", err); - for row in base_matrices.processor_matrix.clone() { - println!("{}", ProcessorMatrixRow { row }); - } - - // check `output_matrix` - let expected_output = vec![ - 10, 33, 100, 108, 114, 111, 87, 32, 44, 111, 108, 108, 101, 72, - ] - .into_iter() - .rev() - .map(BFieldElement::new) - .collect_vec(); - - assert_eq!(expected_output, stdout); - - // each `hash` operation result in 8 rows - let hash_instruction_count = 0; - let prc_rows_count = base_matrices.processor_matrix.len(); - assert!(hash_instruction_count <= 8 * prc_rows_count); - - // noRows(jump_stack_table) == noRows(processor_table) - let jmp_rows_count = base_matrices.jump_stack_matrix.len(); - let prc_rows_count = base_matrices.processor_matrix.len(); - assert_eq!(jmp_rows_count, prc_rows_count); - } - - #[test] - fn hash_hash_hash_test() { - let code = sample_programs::HASH_HASH_HASH_HALT; - let program = Program::from_code(code).unwrap(); - - println!("{}", program); - - let (aet, _, err) = program.simulate_no_input(); - let base_matrices = BaseMatrices::new(aet, &program.to_bwords()); - - // noRows(jump_stack_table) == noRows(processor_table) - assert_eq!( - base_matrices.jump_stack_matrix.len(), - base_matrices.processor_matrix.len() - ); - - for row in base_matrices.processor_matrix { - println!("{}", ProcessorMatrixRow { row }); - } - println!("Errors: {:?}", err); - - // each of three `hash` instructions result in NUM_ROUNDS+1 rows. - assert_eq!(3 * (NUM_ROUNDS + 1), base_matrices.hash_matrix.len()); - } - pub fn test_hash_nop_nop_lt() -> SourceCodeAndInput { SourceCodeAndInput::without_input("hash nop hash nop nop hash push 3 push 2 lt assert halt") } + pub fn test_program_for_halt() -> SourceCodeAndInput { + SourceCodeAndInput::without_input("halt") + } + pub fn test_program_for_push_pop_dup_swap_nop() -> SourceCodeAndInput { SourceCodeAndInput::without_input( "push 1 push 2 pop assert \ @@ -691,6 +699,87 @@ pub mod triton_vm_tests { SourceCodeAndInput::without_input(&source_code) } + pub fn property_based_test_program_for_random_ram_access() -> SourceCodeAndInput { + let mut rng = ThreadRng::default(); + let num_memory_accesses = rng.gen_range(10..50); + let memory_addresses: Vec = random_elements(num_memory_accesses); + let mut memory_values: Vec = random_elements(num_memory_accesses); + let mut source_code = String::new(); + + // Read some memory before first write to ensure that the memory is initialized with 0s. + // Not all addresses are read to have different access patterns: + // - Some addresses are read before written to. + // - Other addresses are written to before read. + for memory_address in memory_addresses.iter().take(num_memory_accesses / 4) { + source_code.push_str(&format!( + "push {memory_address} push 0 read_mem push 0 eq assert pop " + )); + } + + // Write everything to RAM. + for (memory_address, memory_value) in memory_addresses.iter().zip_eq(memory_values.iter()) { + source_code.push_str(&format!( + "push {memory_address} push {memory_value} write_mem pop pop " + )); + } + + // Read back in random order and check that the values did not change. + // For repeated sampling from the same range, better performance can be achieved by using + // `Uniform`. However, this is a test, and not very many samples – it's fine. + let mut reading_permutation = (0..num_memory_accesses).collect_vec(); + for i in 0..num_memory_accesses { + let j = rng.gen_range(0..num_memory_accesses); + reading_permutation.swap(i, j); + } + for idx in reading_permutation { + let memory_address = memory_addresses[idx]; + let memory_value = memory_values[idx]; + source_code.push_str(&format!( + "push {memory_address} push 0 read_mem push {memory_value} eq assert pop " + )); + } + + // Overwrite half the values with new ones. + let mut writing_permutation = (0..num_memory_accesses).collect_vec(); + for i in 0..num_memory_accesses { + let j = rng.gen_range(0..num_memory_accesses); + writing_permutation.swap(i, j); + } + for idx in 0..num_memory_accesses / 2 { + let memory_address = memory_addresses[writing_permutation[idx]]; + let new_memory_value = rng.gen(); + memory_values[writing_permutation[idx]] = new_memory_value; + source_code.push_str(&format!( + "push {memory_address} push {new_memory_value} write_mem pop pop " + )); + } + + // Read back all, i.e., unchanged and overwritten values in (different from before) random + // order and check that the values did not change. + let mut reading_permutation = (0..num_memory_accesses).collect_vec(); + for i in 0..num_memory_accesses { + let j = rng.gen_range(0..num_memory_accesses); + reading_permutation.swap(i, j); + } + for idx in reading_permutation { + let memory_address = memory_addresses[idx]; + let memory_value = memory_values[idx]; + source_code.push_str(&format!( + "push {memory_address} push 0 read_mem push {memory_value} eq assert pop " + )); + } + + source_code.push_str("halt"); + SourceCodeAndInput::without_input(&source_code) + } + + #[test] + // Sanity check for the relatively complex property-based test for random RAM access. + fn run_dont_prove_property_based_test_for_random_ram_access() { + let source_code_and_input = property_based_test_program_for_random_ram_access(); + source_code_and_input.run(); + } + #[test] #[should_panic(expected = "st0 must be 1.")] pub fn negative_property_is_u32_test() { @@ -753,6 +842,7 @@ pub mod triton_vm_tests { pub fn small_tasm_test_programs() -> Vec { vec![ + test_program_for_halt(), test_program_for_push_pop_dup_swap_nop(), test_program_for_divine(), test_program_for_skiz(), @@ -787,6 +877,7 @@ pub mod triton_vm_tests { property_based_test_program_for_lte(), property_based_test_program_for_div(), property_based_test_program_for_is_u32(), + property_based_test_program_for_random_ram_access(), ] } @@ -805,121 +896,6 @@ pub mod triton_vm_tests { ] } - #[test] - fn processor_table_constraints_evaluate_to_zero_for_small_tasm_programs_test() { - processor_table_constraints_evaluate_to_zero(&small_tasm_test_programs()) - } - - #[test] - fn processor_table_constraints_evaluate_to_zero_for_property_based_tasm_programs_test() { - processor_table_constraints_evaluate_to_zero(&property_based_test_programs()) - } - - #[test] - fn processor_table_constraints_evaluate_to_zero_for_bigger_tasm_programs_test() { - processor_table_constraints_evaluate_to_zero(&bigger_tasm_test_programs()) - } - - fn processor_table_constraints_evaluate_to_zero(all_programs: &[SourceCodeAndInput]) { - let mut profiler = TritonProfiler::new("Table Constraints Evaluate to Zero Test"); - for (code_idx, program) in all_programs.iter().enumerate() { - let (aet, output, err) = program.simulate(); - - println!("\nChecking transition constraints for program number {code_idx}"); - println!( - "VM output: [{}]", - output - .iter() - .map(|s| format!("{s}")) - .collect_vec() - .join(", ") - ); - if let Some(e) = err { - panic!("The VM is not happy: {}", e); - } - - let processor_matrix = aet - .processor_matrix - .iter() - .map(|row| row.to_vec()) - .collect_vec(); - let num_cycles = processor_matrix.len(); - - let mut processor_table = ProcessorTable::new_prover(processor_matrix); - let padded_height = roundup_npo2(processor_table.data().len() as u64) as usize; - processor_table.pad(padded_height); - - assert!( - other::is_power_of_two(processor_table.data().len()), - "Matrix length must be power of 2 after padding" - ); - - let challenges = AllChallenges::placeholder(); - let ext_processor_table = - processor_table.extend(&challenges.processor_table_challenges); - - let program_idx_string = format!("Program number {code_idx:>2}"); - profiler.start(&program_idx_string); - for (row_idx, (current_row, next_row)) in ext_processor_table - .data() - .iter() - .tuple_windows() - .enumerate() - { - for (tc_idx, tc_evaluation_result) in ext_processor_table - .evaluate_transition_constraints(current_row, next_row, &challenges) - .iter() - .enumerate() - { - if !tc_evaluation_result.is_zero() { - let ci = current_row[ProcessorBaseTableColumn::CI as usize].coefficients[0] - .value(); - panic!( - "In row {row_idx}, the constraint with index {tc_idx} evaluates to \ - {tc_evaluation_result} but must be 0.\n\ - Instruction: {:?} – opcode: {:?}\n\ - Evaluation Point, current row: [{:?}]\n\ - Evaluation Point, next row: [{:?}]", - AnInstruction::::try_from(ci,).unwrap(), - ci, - current_row - .iter() - .map(|xfe| format!("{xfe}")) - .collect_vec() - .join(", "), - next_row - .iter() - .map(|xfe| format!("{xfe}")) - .collect_vec() - .join(", ") - ); - } - } - } - let num_cycles_string = format!("took {num_cycles:>4} VM cycles"); - profiler.start(&num_cycles_string); - profiler.stop(&num_cycles_string); - profiler.stop(&program_idx_string); - } - profiler.finish(); - println!("{}", profiler.report()); - } - - fn _assert_air_constraints_on_matrix( - table_data: &[Vec], - air_constraints: &[MPolynomial], - ) { - for step in 0..table_data.len() - 1 { - let register: Vec = table_data[step].clone(); - let next_register: Vec = table_data[step + 1].clone(); - let point: Vec = vec![register, next_register].concat(); - - for air_constraint in air_constraints.iter() { - assert!(air_constraint.evaluate(&point).is_zero()); - } - } - } - #[test] fn xxadd_test() { let stdin_words = vec![