From 39809b27324e40890ef066d805eeeb1d6b5aa563 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 27 Dec 2022 01:41:31 +0100 Subject: [PATCH 01/53] =?UTF-8?q?add=20U32=20Table=20=E2=80=93=20scaffoldi?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- constraint-evaluation-generator/src/main.rs | 14 +- triton-vm/src/stark.rs | 42 ++++++ triton-vm/src/table.rs | 1 + triton-vm/src/table/challenges.rs | 20 ++- triton-vm/src/table/constraints.rs | 1 + .../constraints/u32_table_constraints.rs | 10 ++ triton-vm/src/table/master_table.rs | 84 ++++++++++- triton-vm/src/table/processor_table.rs | 6 + triton-vm/src/table/table_column.rs | 94 +++++++++++++ triton-vm/src/table/u32_table.rs | 130 ++++++++++++++++++ 10 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 triton-vm/src/table/constraints/u32_table_constraints.rs create mode 100644 triton-vm/src/table/u32_table.rs diff --git a/constraint-evaluation-generator/src/main.rs b/constraint-evaluation-generator/src/main.rs index 30802c2c..e704169e 100644 --- a/constraint-evaluation-generator/src/main.rs +++ b/constraint-evaluation-generator/src/main.rs @@ -1,7 +1,7 @@ -use itertools::Itertools; use std::collections::HashSet; use std::process::Command; +use itertools::Itertools; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::x_field_element::XFieldElement; @@ -16,6 +16,7 @@ use triton_vm::table::op_stack_table::ExtOpStackTable; use triton_vm::table::processor_table::ExtProcessorTable; use triton_vm::table::program_table::ExtProgramTable; use triton_vm::table::ram_table::ExtRamTable; +use triton_vm::table::u32_table::ExtU32Table; fn main() { println!("Generate those constraint evaluators!"); @@ -98,6 +99,17 @@ fn main() { ); write(&table_name_snake, source_code); + let (table_name_snake, table_name_camel) = construct_needed_table_identifiers(&["u32"]); + let source_code = gen( + &table_name_snake, + &table_name_camel, + &mut ExtU32Table::ext_initial_constraints_as_circuits(), + &mut ExtU32Table::ext_consistency_constraints_as_circuits(), + &mut ExtU32Table::ext_transition_constraints_as_circuits(), + &mut ExtU32Table::ext_terminal_constraints_as_circuits(), + ); + write(&table_name_snake, source_code); + if let Err(fmt_failed) = Command::new("cargo").arg("fmt").output() { println!("cargo fmt failed: {}", fmt_failed); } diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index f2f91fae..e152f767 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -903,6 +903,7 @@ pub(crate) mod triton_stark_tests { use crate::table::table_column::ProcessorExtTableColumn::InputTableEvalArg; use crate::table::table_column::ProcessorExtTableColumn::OutputTableEvalArg; use crate::table::table_column::RamBaseTableColumn; + use crate::table::u32_table::ExtU32Table; use crate::vm::simulate; use crate::vm::triton_vm_tests::bigger_tasm_test_programs; use crate::vm::triton_vm_tests::property_based_test_programs; @@ -1211,6 +1212,11 @@ pub(crate) mod triton_stark_tests { ExtHashTable::evaluate_consistency_constraints(br, er, &challenges); ExtHashTable::evaluate_transition_constraints(br, er, br, er, &challenges); ExtHashTable::evaluate_terminal_constraints(br, er, &challenges); + + ExtU32Table::evaluate_initial_constraints(br, er, &challenges); + ExtU32Table::evaluate_consistency_constraints(br, er, &challenges); + ExtU32Table::evaluate_transition_constraints(br, er, br, er, &challenges); + ExtU32Table::evaluate_terminal_constraints(br, er, &challenges); } #[test] @@ -1233,6 +1239,7 @@ pub(crate) mod triton_stark_tests { ExtRamTable::num_initial_quotients(), ExtJumpStackTable::num_initial_quotients(), ExtHashTable::num_initial_quotients(), + ExtU32Table::num_initial_quotients(), GrandCrossTableArg::num_initial_quotients(), ]; let all_cons = [ @@ -1243,6 +1250,7 @@ pub(crate) mod triton_stark_tests { ExtRamTable::num_consistency_quotients(), ExtJumpStackTable::num_consistency_quotients(), ExtHashTable::num_consistency_quotients(), + ExtU32Table::num_consistency_quotients(), GrandCrossTableArg::num_consistency_quotients(), ]; let all_trans = [ @@ -1253,6 +1261,7 @@ pub(crate) mod triton_stark_tests { ExtRamTable::num_transition_quotients(), ExtJumpStackTable::num_transition_quotients(), ExtHashTable::num_transition_quotients(), + ExtU32Table::num_transition_quotients(), GrandCrossTableArg::num_transition_quotients(), ]; let all_term = [ @@ -1263,6 +1272,7 @@ pub(crate) mod triton_stark_tests { ExtRamTable::num_terminal_quotients(), ExtJumpStackTable::num_terminal_quotients(), ExtHashTable::num_terminal_quotients(), + ExtU32Table::num_terminal_quotients(), GrandCrossTableArg::num_terminal_quotients(), ]; @@ -1360,6 +1370,14 @@ pub(crate) mod triton_stark_tests { ExtHashTable::num_initial_quotients(), ExtHashTable::initial_quotient_degree_bounds(id).len() ); + assert_eq!( + ExtU32Table::num_initial_quotients(), + ExtU32Table::evaluate_initial_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtU32Table::num_initial_quotients(), + ExtU32Table::initial_quotient_degree_bounds(id).len() + ); assert_eq!( ExtProgramTable::num_consistency_quotients(), @@ -1417,6 +1435,14 @@ pub(crate) mod triton_stark_tests { ExtHashTable::num_consistency_quotients(), ExtHashTable::consistency_quotient_degree_bounds(id, ph).len() ); + assert_eq!( + ExtU32Table::num_consistency_quotients(), + ExtU32Table::evaluate_consistency_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtU32Table::num_consistency_quotients(), + ExtU32Table::consistency_quotient_degree_bounds(id, ph).len() + ); assert_eq!( ExtProgramTable::num_transition_quotients(), @@ -1474,6 +1500,14 @@ pub(crate) mod triton_stark_tests { ExtHashTable::num_transition_quotients(), ExtHashTable::transition_quotient_degree_bounds(id, ph).len() ); + assert_eq!( + ExtU32Table::num_transition_quotients(), + ExtU32Table::evaluate_transition_constraints(br, er, br, er, &challenges).len(), + ); + assert_eq!( + ExtU32Table::num_transition_quotients(), + ExtU32Table::transition_quotient_degree_bounds(id, ph).len() + ); assert_eq!( ExtProgramTable::num_terminal_quotients(), @@ -1531,6 +1565,14 @@ pub(crate) mod triton_stark_tests { ExtHashTable::num_terminal_quotients(), ExtHashTable::terminal_quotient_degree_bounds(id).len() ); + assert_eq!( + ExtU32Table::num_terminal_quotients(), + ExtU32Table::evaluate_terminal_constraints(br, er, &challenges).len(), + ); + assert_eq!( + ExtU32Table::num_terminal_quotients(), + ExtU32Table::terminal_quotient_degree_bounds(id).len() + ); } #[test] diff --git a/triton-vm/src/table.rs b/triton-vm/src/table.rs index 94a13fa2..124675aa 100644 --- a/triton-vm/src/table.rs +++ b/triton-vm/src/table.rs @@ -12,3 +12,4 @@ pub mod processor_table; pub mod program_table; pub mod ram_table; pub mod table_column; +pub mod u32_table; diff --git a/triton-vm/src/table/challenges.rs b/triton-vm/src/table/challenges.rs index e66076c0..9cedf832 100644 --- a/triton-vm/src/table/challenges.rs +++ b/triton-vm/src/table/challenges.rs @@ -20,6 +20,7 @@ use crate::table::processor_table::IOChallenges; use crate::table::processor_table::ProcessorTableChallenges; use crate::table::program_table::ProgramTableChallenges; use crate::table::ram_table::RamTableChallenges; +use crate::table::u32_table::U32TableChallenges; pub trait TableChallenges: Clone + Debug { type Id: Display @@ -55,11 +56,12 @@ pub struct AllChallenges { pub ram_table_challenges: RamTableChallenges, pub jump_stack_table_challenges: JumpStackTableChallenges, pub hash_table_challenges: HashTableChallenges, + pub u32_table_challenges: U32TableChallenges, pub cross_table_challenges: CrossTableChallenges, } impl AllChallenges { - pub const TOTAL_CHALLENGES: usize = 46 + NUM_CROSS_TABLE_WEIGHTS; + pub const TOTAL_CHALLENGES: usize = 51 + NUM_CROSS_TABLE_WEIGHTS; pub fn create_challenges( mut weights: Vec, @@ -115,6 +117,13 @@ impl AllChallenges { hash_table_digest_output_weight2: weights.pop().unwrap(), hash_table_digest_output_weight3: weights.pop().unwrap(), hash_table_digest_output_weight4: weights.pop().unwrap(), + + u32_table_lhs_weight: weights.pop().unwrap(), + u32_table_rhs_weight: weights.pop().unwrap(), + u32_table_ci_weight: weights.pop().unwrap(), + u32_table_result_weight: weights.pop().unwrap(), + + u32_table_perm_indeterminate: weights.pop().unwrap(), }; let program_table_challenges = ProgramTableChallenges { @@ -203,6 +212,14 @@ impl AllChallenges { digest_output_weight4: processor_table_challenges.hash_table_digest_output_weight4, }; + let u32_table_challenges = U32TableChallenges { + lhs_weight: processor_table_challenges.u32_table_lhs_weight, + rhs_weight: processor_table_challenges.u32_table_rhs_weight, + ci_weight: processor_table_challenges.u32_table_ci_weight, + result_weight: processor_table_challenges.u32_table_result_weight, + processor_perm_indeterminate: processor_table_challenges.u32_table_perm_indeterminate, + }; + let input_terminal = EvalArg::compute_terminal( claimed_input, EvalArg::default_initial(), @@ -241,6 +258,7 @@ impl AllChallenges { ram_table_challenges, jump_stack_table_challenges, hash_table_challenges, + u32_table_challenges, cross_table_challenges, } } diff --git a/triton-vm/src/table/constraints.rs b/triton-vm/src/table/constraints.rs index 69edfdfb..cdb8b881 100644 --- a/triton-vm/src/table/constraints.rs +++ b/triton-vm/src/table/constraints.rs @@ -5,3 +5,4 @@ pub mod op_stack_table_constraints; pub mod processor_table_constraints; pub mod program_table_constraints; pub mod ram_table_constraints; +pub mod u32_table_constraints; diff --git a/triton-vm/src/table/constraints/u32_table_constraints.rs b/triton-vm/src/table/constraints/u32_table_constraints.rs new file mode 100644 index 00000000..f29983ab --- /dev/null +++ b/triton-vm/src/table/constraints/u32_table_constraints.rs @@ -0,0 +1,10 @@ +use crate::table::extension_table::Evaluable; +use crate::table::extension_table::Quotientable; +use crate::table::u32_table::ExtU32Table; + +// This file is a placeholder for auto-generated code +// Run `cargo run --bin constraint-evaluation-generator` +// to fill in this file with optimized constraints. +impl Evaluable for ExtU32Table {} + +impl Quotientable for ExtU32Table {} diff --git a/triton-vm/src/table/master_table.rs b/triton-vm/src/table/master_table.rs index 36317ef0..728e991c 100644 --- a/triton-vm/src/table/master_table.rs +++ b/triton-vm/src/table/master_table.rs @@ -55,6 +55,8 @@ 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::u32_table::ExtU32Table; +use crate::table::u32_table::U32Table; use crate::table::*; use crate::vm::AlgebraicExecutionTrace; @@ -90,6 +92,8 @@ 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 U32_TABLE_START: usize = HASH_TABLE_END; +pub const U32_TABLE_END: usize = U32_TABLE_START + u32_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; @@ -107,6 +111,8 @@ 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; +pub const EXT_U32_TABLE_START: usize = EXT_HASH_TABLE_END; +pub const EXT_U32_TABLE_END: usize = EXT_U32_TABLE_START + u32_table::EXT_WIDTH; /// A `TableId` uniquely determines one of Triton VM's tables. #[derive(Debug, Copy, Clone, Display, EnumCountMacro, EnumIter, PartialEq, Eq, Hash)] @@ -118,6 +124,7 @@ pub enum TableId { RamTable, JumpStackTable, HashTable, + U32Table, } /// A Master Table is, in some sense, a top-level table of Triton VM. It contains all the data @@ -352,6 +359,8 @@ impl MasterBaseTable { 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); + let u32_table = &mut master_base_table.table_mut(TableId::U32Table); + U32Table::fill_trace(u32_table, &aet); // memory-like tables must be filled in before clock jump differences are known, hence // the break from the usual order @@ -385,6 +394,8 @@ impl MasterBaseTable { JumpStackTable::pad_trace(jump_stack_table, main_execution_len); let hash_table = &mut self.table_mut(TableId::HashTable); HashTable::pad_trace(hash_table); + let u32_table = &mut self.table_mut(TableId::U32Table); + U32Table::pad_trace(u32_table, main_execution_len); } pub fn to_fri_domain_table(&self) -> Self { @@ -465,6 +476,11 @@ impl MasterBaseTable { master_ext_table.table_mut(TableId::HashTable), &challenges.hash_table_challenges, ); + U32Table::extend( + self.table(TableId::U32Table), + master_ext_table.table_mut(TableId::U32Table), + &challenges.u32_table_challenges, + ); master_ext_table } @@ -479,6 +495,7 @@ impl MasterBaseTable { RamTable => (RAM_TABLE_START, RAM_TABLE_END), JumpStackTable => (JUMP_STACK_TABLE_START, JUMP_STACK_TABLE_END), HashTable => (HASH_TABLE_START, HASH_TABLE_END), + U32Table => (U32_TABLE_START, U32_TABLE_END), } } @@ -541,6 +558,7 @@ impl MasterExtTable { 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), + U32Table => (EXT_U32_TABLE_START, EXT_U32_TABLE_END), } } @@ -573,6 +591,7 @@ pub fn all_degrees_with_origin( 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), + ExtU32Table::all_degrees_with_origin("u32 table", id, ph), ] .concat() } @@ -602,6 +621,7 @@ pub fn num_all_initial_quotients() -> usize { + ExtRamTable::num_initial_quotients() + ExtJumpStackTable::num_initial_quotients() + ExtHashTable::num_initial_quotients() + + ExtU32Table::num_initial_quotients() } pub fn num_all_consistency_quotients() -> usize { @@ -612,6 +632,7 @@ pub fn num_all_consistency_quotients() -> usize { + ExtRamTable::num_consistency_quotients() + ExtJumpStackTable::num_consistency_quotients() + ExtHashTable::num_consistency_quotients() + + ExtU32Table::num_consistency_quotients() } pub fn num_all_transition_quotients() -> usize { @@ -622,6 +643,7 @@ pub fn num_all_transition_quotients() -> usize { + ExtRamTable::num_transition_quotients() + ExtJumpStackTable::num_transition_quotients() + ExtHashTable::num_transition_quotients() + + ExtU32Table::num_transition_quotients() } pub fn num_all_terminal_quotients() -> usize { @@ -632,6 +654,7 @@ pub fn num_all_terminal_quotients() -> usize { + ExtRamTable::num_terminal_quotients() + ExtJumpStackTable::num_terminal_quotients() + ExtHashTable::num_terminal_quotients() + + ExtU32Table::num_terminal_quotients() + GrandCrossTableArg::num_terminal_quotients() } @@ -644,6 +667,7 @@ pub fn all_initial_quotient_degree_bounds(interpolant_degree: Degree) -> Vec Vec= RHS, + /// - 1 means LHS < RHS, and + /// - 2 means the result is unknown in the current row. + /// The value 2 can never occur in the first row of a section, _i.e._, when `CopyFlag` is 1. + LT, + + /// The result (or intermediate result) of LHS & RHS, _i.e._, bitwise and. + AND, + + /// The result (or intermediate result) of LHS ^ RHS, _i.e._, bitwise xor. + XOR, + + /// The floor of log₂(LHS), _i.e._, the number of bits in LHS minus 1. + Log2Floor, + + /// A copy of LHS in the first row in the current, independent section, _i.e._, when `CopyFlag` + /// was last 1. Needed for the computation of `Pow`. + LhsCopy, + + /// The result (or intermediate result) of LHS ** RHS, _i.e._, LHS to the power of RHS. + Pow, + + /// The inverse-or-zero of LHS. Needed to check whether `LHS` is unequal to 0. + LhsInv, + + /// The inverse-or-zero of RHS. Needed to check whether `RHS` is unequal to 0. + RhsInv, +} + +#[repr(usize)] +#[derive(Display, Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCountMacro, Hash)] +pub enum U32ExtTableColumn { + ProcessorPermArg, +} + // -------------------------------------------------------------------- pub trait BaseTableColumn { @@ -308,6 +374,13 @@ impl BaseTableColumn for HashBaseTableColumn { } } +impl BaseTableColumn for U32BaseTableColumn { + #[inline] + fn base_table_index(&self) -> usize { + (*self) as usize + } +} + // -------------------------------------------------------------------- pub trait ExtTableColumn { @@ -363,6 +436,13 @@ impl ExtTableColumn for HashExtTableColumn { } } +impl ExtTableColumn for U32ExtTableColumn { + #[inline] + fn ext_table_index(&self) -> usize { + (*self) as usize + } +} + // -------------------------------------------------------------------- pub trait MasterBaseTableColumn: BaseTableColumn { @@ -418,6 +498,13 @@ impl MasterBaseTableColumn for HashBaseTableColumn { } } +impl MasterBaseTableColumn for U32BaseTableColumn { + #[inline] + fn master_base_table_index(&self) -> usize { + U32_TABLE_START + self.base_table_index() + } +} + // -------------------------------------------------------------------- pub trait MasterExtTableColumn: ExtTableColumn { @@ -473,6 +560,13 @@ impl MasterExtTableColumn for HashExtTableColumn { } } +impl MasterExtTableColumn for U32ExtTableColumn { + #[inline] + fn master_ext_table_index(&self) -> usize { + EXT_U32_TABLE_START + self.ext_table_index() + } +} + // -------------------------------------------------------------------- #[cfg(test)] diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs new file mode 100644 index 00000000..693cdce9 --- /dev/null +++ b/triton-vm/src/table/u32_table.rs @@ -0,0 +1,130 @@ +use ndarray::ArrayView2; +use ndarray::ArrayViewMut2; +use strum::EnumCount; +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 U32TableChallengeId::*; + +use crate::table::challenges::TableChallenges; +use crate::table::constraint_circuit::ConstraintCircuit; +use crate::table::constraint_circuit::DualRowIndicator; +use crate::table::constraint_circuit::SingleRowIndicator; +use crate::table::master_table::NUM_BASE_COLUMNS; +use crate::table::master_table::NUM_EXT_COLUMNS; +use crate::table::table_column::U32BaseTableColumn; +use crate::table::table_column::U32ExtTableColumn; +use crate::vm::AlgebraicExecutionTrace; + +pub const U32_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 2; +pub const U32_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 0; +pub const U32_TABLE_NUM_EXTENSION_CHALLENGES: usize = U32TableChallengeId::COUNT; + +pub const BASE_WIDTH: usize = U32BaseTableColumn::COUNT; +pub const EXT_WIDTH: usize = U32ExtTableColumn::COUNT; +pub const FULL_WIDTH: usize = BASE_WIDTH + EXT_WIDTH; + +pub struct U32Table {} + +pub struct ExtU32Table {} + +impl ExtU32Table { + pub fn ext_initial_constraints_as_circuits() -> Vec< + ConstraintCircuit< + U32TableChallenges, + SingleRowIndicator, + >, + > { + todo!() + } + + pub fn ext_consistency_constraints_as_circuits() -> Vec< + ConstraintCircuit< + U32TableChallenges, + SingleRowIndicator, + >, + > { + todo!() + } + + pub fn ext_transition_constraints_as_circuits() -> Vec< + ConstraintCircuit>, + > { + todo!() + } + + pub fn ext_terminal_constraints_as_circuits() -> Vec< + ConstraintCircuit< + U32TableChallenges, + SingleRowIndicator, + >, + > { + todo!() + } +} + +impl U32Table { + pub fn fill_trace( + _u32_table: &mut ArrayViewMut2, + _aet: &AlgebraicExecutionTrace, + ) { + todo!() + } + + pub fn pad_trace(_u32_table: &mut ArrayViewMut2, _u32_table_len: usize) { + todo!() + } + + pub fn extend( + base_table: ArrayView2, + ext_table: ArrayViewMut2, + _challenges: &U32TableChallenges, + ) { + assert_eq!(BASE_WIDTH, base_table.ncols()); + assert_eq!(EXT_WIDTH, ext_table.ncols()); + assert_eq!(base_table.nrows(), ext_table.nrows()); + todo!() + } +} + +#[repr(usize)] +#[derive(Debug, Copy, Clone, Display, EnumCountMacro, EnumIter, PartialEq, Eq, Hash)] +pub enum U32TableChallengeId { + LhsWeight, + RhsWeight, + CIWeight, + ResultWeight, + ProcessorPermIndeterminate, +} + +impl From for usize { + fn from(val: U32TableChallengeId) -> Self { + val as usize + } +} + +impl TableChallenges for U32TableChallenges { + type Id = U32TableChallengeId; + + fn get_challenge(&self, id: Self::Id) -> XFieldElement { + match id { + LhsWeight => self.lhs_weight, + RhsWeight => self.rhs_weight, + CIWeight => self.ci_weight, + ResultWeight => self.result_weight, + ProcessorPermIndeterminate => self.processor_perm_indeterminate, + } + } +} + +#[derive(Debug, Clone)] +pub struct U32TableChallenges { + pub lhs_weight: XFieldElement, + pub rhs_weight: XFieldElement, + pub ci_weight: XFieldElement, + pub result_weight: XFieldElement, + pub processor_perm_indeterminate: XFieldElement, +} From a75cfac5f62e29d8ebf402b5a53154823e179660 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 28 Dec 2022 12:40:04 +0100 Subject: [PATCH 02/53] U32 Table specification --- specification/src/SUMMARY.md | 1 + specification/src/u32-table.md | 260 +++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 specification/src/u32-table.md diff --git a/specification/src/SUMMARY.md b/specification/src/SUMMARY.md index d8791ce8..e1283156 100644 --- a/specification/src/SUMMARY.md +++ b/specification/src/SUMMARY.md @@ -16,6 +16,7 @@ + [Random Access Memory Table](random-access-memory-table.md) + [Jump Stack Table](jump-stack-table.md) + [Hash Table](hash-table.md) + + [U32 Table](u32-table.md) + [Memory-Consistency](memory-consistency.md) * [Contiguity of Memory-Pointer Regions](contiguity-of-memory-pointer-regions.md) * [Clock Jump Differences and Inner Sorting](clock-jump-differences-and-inner-sorting.md) diff --git a/specification/src/u32-table.md b/specification/src/u32-table.md new file mode 100644 index 00000000..05fbadc5 --- /dev/null +++ b/specification/src/u32-table.md @@ -0,0 +1,260 @@ +# U32 Table + +The U32 Operations Table arithmetizes the coprocessor for “difficult” 32-bit unsigned integer operations. +The two inputs to the U32 Operations Table are left-hand side (LHS) and right-hand side (RHS). +To allow efficient arithmetization, a [u32 instruction](instructions.md)'s result is constructed using multiple rows. +Crucially, the rows of the U32 table are independent of the processor's clock. +Hence, the result of the instruction can be transferred into the processor within one clock cycle. + +In the U32 Table, the results for many different instructions are computed in parallel, each in their own column. +The processor's current instruction `CI` determines which of columns is copied to the processor as the result. + +## Base Columns + +| name | description | +|:-----------------|:--------------------------------------------------------------------------------------------------------------| +| `CopyFlag` | Marks the beginning of an independent section within the U32 table. | +| `Bits` | The number of bits that LHS and RHS have already been shifted by. | +| `BitsMinus33Inv` | The inverse-or-zero of the difference between 33 and `Bits`. | +| `CI` | Current Instruction, the instruction the processor is currently executing. | +| `LHS` (`st0`) | Left-hand side of the operation. | +| `RHS` (`st1`) | Right-hand side of the operation. | +| `LT` | The result (or intermediate result) of LHS < RHS. | +| `AND` | The result (or intermediate result) of LHS & RHS, _i.e._, bitwise and. | +| `XOR` | The result (or intermediate result) of LHS ^ RHS, _i.e._, bitwise xor. | +| `Log2Floor` | The number of bits in LHS minus 1, which is usually equivalent to the floor of log₂(LHS), except for LHS = 0. | +| `LhsCopy` | A copy of LHS in the first row in the current, independent section, _i.e._, when `CopyFlag` is 1. | +| `Pow` | The result (or intermediate result) of $\texttt{LHS}^\texttt{RHS}$. Might overflow – care advised! | +| `LhsInv` | The inverse-or-zero of LHS. Needed to check whether `LHS` is unequal to 0. | +| `RhsInv` | The inverse-or-zero of RHS. Needed to check whether `RHS` is unequal to 0. | + +`LT` can take on the values 0, 1, or 2, where +- 0 means `LHS` >= `RHS` is definitely known in the current row, +- 1 means `LHS` < `RHS` is definitely known in the current row, and +- 2 means the result is unknown in the current row. +This value can never occur in the first row of a section, _i.e._, when `CopyFlag` is 1. + +An example U32 Table follows. +Some columns are omitted for presentation reasons. +All cells in the U32 Table contain elements of the B-field. +For clearer illustration of the mechanics, the columns marked “$_2$” are presented in base 2, whereas columns marked “$_{10}$” are in the usual base 10. + +| CopyFlag$_2$ | Bits$_{10}$ | LHS$_2$ | RHS$_2$ | LT$_{10}$ | AND$_2$ | XOR$_2$ | Log2Floor$_{10}$ | Pow$_{10}$ | +|-------------:|------------:|:----------|:----------|----------:|:----------|:----------|-----------------:|---------------------:| +| 1 | 0 | 001**0** | 101**0** | 1 | 001**0** | 100**0** | 1 | 1024 | +| 0 | 1 | 00**1** | 10**1** | 1 | 00**1** | 10**0** | 1 | 32 | +| 0 | 2 | 0**0** | 1**0** | 1 | 0**0** | 1**0** | -1 | 4 | +| 0 | 3 | **0** | **1** | 1 | **0** | **1** | -1 | 2 | +| 0 | 4 | **0** | **0** | 2 | **0** | **0** | -1 | 1 | +| 1 | 0 | 1100**0** | 1101**0** | 1 | 1100**0** | 0001**0** | 4 | 11527596562258709312 | +| 0 | 1 | 110**0** | 110**1** | 1 | 110**0** | 000**1** | 4 | 876488338465357824 | +| 0 | 2 | 11**0** | 11**0** | 2 | 11**0** | 00**0** | 4 | 191102976 | +| 0 | 3 | 1**1** | 1**1** | 2 | 1**1** | 0**0** | 4 | 13824 | +| 0 | 4 | **1** | **1** | 2 | **1** | **0** | 4 | 24 | +| 0 | 5 | **0** | **0** | 2 | **0** | **0** | -1 | 1 | + +The AIR verifies the correct update of each consecutive pair of rows. +In every row one bit – the current least significant bit of both LHS and RHS – is eliminated. +Only when both `LHS` and `RHS` are 0 can a new row with `CopyFlag = 1` be inserted. +Inserting a row with `Bits` equal to 32 and `LHS` or `RHS` not 0, as well as +inserting a row with `Bits` equal to 33 makes it impossible to generate a proof of correct execution of Triton VM. + +## Extension Columns + +The U32 Table has 1 extension column, `RunningProductProcessor`. +It corresponds to the Permutation Argument with the [Processor Table](processor-table.md), establishing that whenever the processor executes a u32 instruction, the following holds: + +- the processor's stack register `st0` is copied into `LHS`, +- the processor's stack register `st1` is copied into `RHS`, +- the processor's `CI` register is copied into `CI`, and +- the result, condition to `CI`, is copied to the processor. + +More concretely, the result to be copied to the processor is + +- `LT` if `CI` is opcode(`lt`), +- `AND` if `CI` is opcode(`and`), +- `XOR` if `CI` is opcode(`xor`), +- `Log2Floor` if `CI` is opcode(`log_2_floor`), +- `Pow` if `CI` is opcode(`pow`), +- `LT` if `CI` is opcode(`div`), and +- 0 if `CI` is 0. + +The instruction `div` uses the U32 Table to ensure that the remainder `r` is smaller than the divisor `d`. +Hence, the result of `LT` is used. +The instruction `div` _also_ uses the U32 Table to ensure that the numerator `n` and the quotient `q` are u32. +For this range check, happening in its independent section, no result is required. +While the processor accumulates two factors into the running product in between two consecutive rows, the U32 Table +The instruction `split` also uses the U32 Table for range checking only, _i.e._, to ensure that the instruction's resulting “high bits” and “low bits” each fit in a u32. + +To conditionally copy the required result to the processor, instruction de-selectors like in the Processor Table are used. +Concretely, with `u32_instructions = {lt, and, xor, log_2_floor, pow, div}`, the following aliases are used: + +- `lt_div_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \not\in \{\texttt{lt}, \texttt{div}\}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ +- `and_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{and}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ +- `xor_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{xor}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ +- `log_2_floor_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{log\_2\_floor}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ +- `pow_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{pow}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ + +Throughout the next sections, the alias `Result` corresponds to the polynomial +`LT·lt_div_deselector + AND·and_deselector + XOR·xor_deselector + Log2Floor·log_2_floor_deselector + Pow·pow_deselector`. + +## Padding + +Each padding row is the all-zero row with the exception of +- `BitsMinus33Inv`, which is $-33^{-1}$, +- `LT`, which is 2, +- `Log2Floor`, which is -1, and +- `Pow`, which is 1. + +# Arithmetic Intermediate Representation + +Let all household items (🪥, 🛁, etc.) be challenges, concretely evaluation points, supplied by the verifier. +Let all fruit & vegetables (🥝, 🥥, etc.) be challenges, concretely weights to compress rows, supplied by the verifier. +Both types of challenges are X-field elements, _i.e._, elements of $\mathbb{F}_{p^3}$. + +## Initial Constraints + +1. If the `CopyFlag` is 0, then `RunningProductProcessor` is 1. +1. If the `CopyFlag` is 1, then `RunningProductProcessor` has absorbed the first row with respect to challenges 🥜, 🌰, 🥑, and 🥕, and indeterminate 🧷. + +### Initial Constraints as Polynomials + +1. `CopyFlag·(RunningProductProcessor - 1)` +1. `(CopyFlag - 1)·(RunningProductProcessor - (🧷 - 🥜·LHS - 🌰·RHS - 🥑·CI - 🥕·Result))` + +## Consistency Constraints + +1. The `CopyFlag` is 0 or 1. +1. If `CopyFlag` is 1, then `Bits` is 0. +1. `BitsMinus33Inv` is the inverse of (`Bits` - 33). +1. `LhsInv` is 0 or the inverse of `LHS`. +1. `Lhs` is 0 or `LhsInv` is the inverse of `LHS`. +1. `RhsInv` is 0 or the inverse of `RHS`. +1. `Rhs` is 0 or `RhsInv` is the inverse of `RHS`. +1. If `CopyFlag` is 1, then `LhsCopy` is `LHS`. +1. If `CopyFlag` is 0 and `LHS` is 0 and `RHS` is 0, then `LT` is 2. +1. If `CopyFlag` is 0 and `LHS` is 0 and `RHS` is 0, then `AND` is 0. +1. If `CopyFlag` is 0 and `LHS` is 0 and `RHS` is 0, then `XOR` is 0. +1. If `CopyFlag` is 0 and `LHS` is 0 and `RHS` is 0, then `Pow` is 1. +1. If `LHS` is 0, then `Log2Floor` is -1. + +Written in Disjunctive Normal Form, the same constraints can be expressed as: + +1. The `CopyFlag` is 0 or 1. +1. `CopyFlag` is 0 or `Bits` is 0. +1. `BitsMinus33Inv` the inverse of (`Bits` - 33). +1. `LhsInv` is 0 or the inverse of `LHS`. +1. `Lhs` is 0 or `LhsInv` is the inverse of `LHS`. +1. `RhsInv` is 0 or the inverse of `RHS`. +1. `Rhs` is 0 or `RhsInv` is the inverse of `RHS`. +1. `CopyFlag` is 0 or `LhsCopy` is `LHS`. +1. `CopyFlag` is 1 or `LHS` is not 0 or `RHS` is not 0 or `LT` is 2. +1. `CopyFlag` is 1 or `LHS` is not 0 or `RHS` is not 0 or `AND` is 0. +1. `CopyFlag` is 1 or `LHS` is not 0 or `RHS` is not 0 or `XOR` is 0. +1. `CopyFlag` is 1 or `LHS` is not 0 or `RHS` is not 0 or `Pow` is 1. +1. `LHS` is not 0 or `Log2Floor` is -1. + +### Consistency Constraints as Polynomials + +1. `CopyFlag·(CopyFlag - 1)` +1. `CopyFlag·Bits` +1. `1 - BitsMinus33Inv·(Bits - 33)` +1. `LhsInv·(1 - LHS·LhsInv)` +1. `LHS·(1 - LHS·LhsInv)` +1. `RhsInv·(1 - RHS·RhsInv)` +1. `RHS·(1 - RHS·RhsInv)` +1. `CopyFlag·(LHS - LhsCopy)` +1. `(CopyFlag - 1)·(1 - LHS·LhsInv)·(1 - RHS·RhsInv)·(LT - 2)` +1. `(CopyFlag - 1)·(1 - LHS·LhsInv)·(1 - RHS·RhsInv)·AND` +1. `(CopyFlag - 1)·(1 - LHS·LhsInv)·(1 - RHS·RhsInv)·XOR` +1. `(CopyFlag - 1)·(1 - LHS·LhsInv)·(1 - RHS·RhsInv)·(Pow - 1)` +1. `(1 - LHS·LhsInv)·(Log2Floor + 1)` + +## Transition Constraints + +Even though they are never explicitly represented, it is useful to alias the `LHS`'s and `RHS`'s _least-significant bit_, or “lsb.” +Given two consecutive rows for `LHS`, the (current) least significant bit can be computed by subtracting twice the next row's `LHS` from the current row's `LHS`. +These aliases, _i.e._, `LhsLsb` = 2·`LHS`' - `LHS` and `RhsLsb` = 2·`RHS`' - `RHS`, are used throughout the following. + +1. If the `CopyFlag` in the next row is 1, then `LHS` in the current row is 0. +1. If the `CopyFlag` in the next row is 1, then `RHS` in the current row is 0. +1. If the `CopyFlag` in the next row is 0, then `CI` in the next row is `CI` in the current row. +1. If the `CopyFlag` in the next row is 0, then `LhsCopy` in the next row is `LhsCopy` in the current row. +1. If the `CopyFlag` in the next row is 0 and `LHS` in the current row is unequal to 0, then `Bits` in the next row is `Bits` in the current row plus 1. +1. If the `CopyFlag` in the next row is 0 and `RHS` in the current row is unequal to 0, then `Bits` in the next row is `Bits` in the current row plus 1. +1. If the `CopyFlag` in the next row is 0, then `LhsLsb` is either 0 or 1. +1. If the `CopyFlag` in the next row is 0, then `RhsLsb` is either 0 or 1. +1. If the `CopyFlag` in the next row is 0 and `LT` in the next row is 0, then `LT` in the current row is 0. +1. If the `CopyFlag` in the next row is 0 and `LT` in the next row is 1, then `LT` in the current row is 1. +1. If the `CopyFlag` in the next row is 0 and `LT` in the next row is 2 and `LhsLsb` is 0 and `RhsLsb` is 1, then `LT` in the current row is 1. +1. If the `CopyFlag` in the next row is 0 and `LT` in the next row is 2 and `LhsLsb` is 1 and `RhsLsb` is 0, then `LT` in the current row is 0. +1. If the `CopyFlag` in the next row is 0 and `LT` in the next row is 2 and `LhsLsb` is `RhsLsb` and the `CopyFlag` in the current row is 0, then `LT` in the current row is 2. +1. If the `CopyFlag` in the next row is 0 and `LT` in the next row is 2 and `LhsLsb` is `RhsLsb` and the `CopyFlag` in the current row is 1, then `LT` in the current row is 0. +1. If the `CopyFlag` in the next row is 0, then `AND` in the current row is twice `AND` in the next row plus the product of `LhsLsb` and `RhsLsb`. +1. If the `CopyFlag` in the next row is 0, then `XOR` in the current row is twice `XOR` in the next row plus `LhsLsb` plus `RhsLsb` minus twice the product of `LhsLsb` and `RhsLsb`. +1. If the `CopyFlag` in the next row is 0 and `LHS` in the next row is 0 and `LHS` in the current row is not 0, then `Log2Floor` in the current row is `Bits`. +1. If the `CopyFlag` in the next row is 0 and `LHS` in the next row is not 0, then `Log2Floor` in the current row is `Log2Floor` in the next row. +1. If the `CopyFlag` in the next row is 0 and `RhsLsb` in the current row is 0, then `Pow` in the current row is `Pow` in the next row squared. +1. If the `CopyFlag` in the next row is 0 and `RhsLsb` in the current row is 1, then `Pow` in the current row is `Pow` in the next row squared times `LhsCopy` in the next row. +1. If the `CopyFlag` in the next row is 0, then `RunningProductProcessor` in the next row is `RunningProductProcessor` in the current row. +1. If the `CopyFlag` in the next row is 1, then `RunningProductProcessor` in the next row has absorbed the next row with respect to challenges 🥜, 🌰, 🥑, and 🥕, and indeterminate 🧷. + +Written in Disjunctive Normal Form, the same constraints can be expressed as: + +1. `CopyFlag`' is 0 or `LHS` is 0. +1. `CopyFlag`' is 0 or `RHS` is 0. +1. `CopyFlag`' is 1 or `CI`' is `CI`. +1. `CopyFlag`' is 1 or `LhsCopy`' is `LhsCopy`. +1. `CopyFlag`' is 1 or `LHS` is 0 or `Bits`' is `Bits` + 1. +1. `CopyFlag`' is 1 or `RHS` is 0 or `Bits`' is `Bits` + 1. +1. `CopyFlag`' is 1 or `LhsLsb` is 0 or `LhsLsb` is 1. +1. `CopyFlag`' is 1 or `RhsLsb` is 0 or `RhsLsb` is 1. +1. `CopyFlag`' is 1 or (`LT`' is 1 or 2) or `LT` is 0. +1. `CopyFlag`' is 1 or (`LT`' is 0 or 2) or `LT` is 1. +1. `CopyFlag`' is 1 or (`LT`' is 0 or 1) or `LhsLsb` is 1 or `RhsLsb` is 0 or `LT` is 1. +1. `CopyFlag`' is 1 or (`LT`' is 0 or 1) or `LhsLsb` is 0 or `RhsLsb` is 1 or `LT` is 0. +1. `CopyFlag`' is 1 or (`LT`' is 0 or 1) or `LhsLsb` is unequal to `RhsLsb` or `CopyFlag` is 1 or `LT` is 2. +1. `CopyFlag`' is 1 or (`LT`' is 0 or 1) or `LhsLsb` is unequal to `RhsLsb` or `CopyFlag` is 0 or `LT` is 0. +1. `CopyFlag`' is 1 or `AND` is twice `AND`' plus the product of `LhsLsb` and `RhsLsb`. +1. `CopyFlag`' is 1 or `XOR` is twice `XOR`' plus `LhsLsb` plus `RhsLsb` minus twice the product of `LhsLsb` and `RhsLsb`. +1. `CopyFlag`' is 1 or `LHS`' is not 0 or `LHS` is 0 or `Log2Floor` is `Bits`. +1. `CopyFlag`' is 1 or `LHS`' is 0 or `Log2Floor` is `Log2Floor`'. +1. `CopyFlag`' is 1 or `RhsLsb` is 1 or `Pow` is `Pow`' times `Pow`'. +1. `CopyFlag`' is 1 or `RhsLsb` is 0 or `Pow` is `Pow`' times `Pow`' times `LhsCopy`'. +1. `CopyFlag`' is 1 or `RunningProductProcessor`' is `RunningProductProcessor`. +1. `CopyFlag`' is 0 or `RunningProductProcessor`' is `RunningProductProcessor` times `(🧷 - 🥜·LHS' - 🌰·RHS' - 🥑·CI' - 🥕·Result')`. + +### Transition Constraints as Polynomials + +1. `CopyFlag'·LHS` +1. `CopyFlag'·RHS` +1. `(CopyFlag' - 1)·(CI' - CI)` +1. `(CopyFlag' - 1)·(LhsCopy' - LhsCopy)` +1. `(CopyFlag' - 1)·LHS·(Bits' - Bits - 1)` +1. `(CopyFlag' - 1)·RHS·(Bits' - Bits - 1)` +1. `(CopyFlag' - 1)·LhsLsb·(LhsLsb - 1)` +1. `(CopyFlag' - 1)·RhsLsb·(RhsLsb - 1)` +1. `(CopyFlag' - 1)·(LT' - 1)·(LT' - 2)·LT` +1. `(CopyFlag' - 1)·(LT' - 0)·(LT' - 2)·(LT - 1)` +1. `(CopyFlag' - 1)·(LT' - 0)·(LT' - 1)·(LhsLsb - 1)·RhsLsb·(LT - 1)` +1. `(CopyFlag' - 1)·(LT' - 0)·(LT' - 1)·LhsLsb·(RhsLsb - 1)·LT` +1. `(CopyFlag' - 1)·(LT' - 0)·(LT' - 1)·(1 - LhsLsb - RhsLsb + 2·LhsLsb·RhsLsb)·(CopyFlag - 1)·(LT - 2)` +1. `(CopyFlag' - 1)·(LT' - 0)·(LT' - 1)·(1 - LhsLsb - RhsLsb + 2·LhsLsb·RhsLsb)·CopyFlag·LT` +1. `(CopyFlag' - 1)·(AND - 2·AND' + LhsLsb·RhsLsb)` +1. `(CopyFlag' - 1)·(XOR - 2·XOR' + LhsLsb + RhsLsb - 2·LhsLsb·RhsLsb)` +1. `(CopyFlag' - 1)·(1 - LHS'·LhsInv')·LHS·(Log2Floor - Bits)` +1. `(CopyFlag' - 1)·LHS'·(Log2Floor' - Log2Floor)` +1. `(CopyFlag' - 1)·(RhsLsb - 1)·(Pow - Pow'·Pow')` +1. `(CopyFlag' - 1)·RhsLsb·(Pow - Pow'·Pow'·LhsCopy)` +1. `(CopyFlag' - 1)·(RunningProductProcessor' - RunningProductProcessor)` +1. `CopyFlag'·(RunningProductProcessor' - RunningProductProcessor·(🧷 - 🥜·LHS - 🌰·RHS - 🥑·CI - 🥕·Result))` + +## Terminal Constraints + +1. `LHS` is 0. +1. `RHS` is 0. + +### Terminal Constraints as Polynomials + +1. `LHS` +1. `RHS` From 42ae538066d3c096d86a7ef93be2c27fc1d874d2 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 28 Dec 2022 23:50:58 +0100 Subject: [PATCH 03/53] spec new u32 instructions --- opcodes.py | 20 +++++-- specification/src/instructions.md | 89 +++++++++++++++++++------------ specification/src/registers.md | 4 +- 3 files changed, 73 insertions(+), 40 deletions(-) diff --git a/opcodes.py b/opcodes.py index a1e2e64b..0a199a47 100644 --- a/opcodes.py +++ b/opcodes.py @@ -22,9 +22,15 @@ class Instruction(IntEnum): Add = auto() Mul = auto() Invert = auto() - Split = auto() Eq = auto() Lsb = auto() + Split = auto() + Lt = auto() + And = auto() + Xor = auto() + Log2Floor = auto() + Pow = auto() + Div = auto() XxAdd = auto() XxMul = auto() XInvert = auto() @@ -35,6 +41,7 @@ class Instruction(IntEnum): class InstructionBucket(IntFlag): HasArg = auto() ShrinkStack = auto() + U32 = auto() # === @@ -44,6 +51,9 @@ def in_bucket(instruction_bucket, instruction): if instruction_bucket == InstructionBucket.ShrinkStack: return instruction in [Instruction.Pop, Instruction.Skiz, Instruction.Assert, Instruction.WriteIo, Instruction.Add, Instruction.Mul, Instruction.Eq, Instruction.XbMul] + if instruction_bucket == InstructionBucket.U32: + return instruction in [Instruction.Lt, Instruction.And, Instruction.Xor, Instruction.Log2Floor, + Instruction.Pow, Instruction.Div, Instruction.Split] return False def flag_set(instruction): @@ -60,10 +70,14 @@ def opcode(instruction): def print_all_opcodes(): for instruction in Instruction: - print(f"{opcode(instruction):>02} {str(instruction)}") + opc = opcode(instruction) + print(f"{opc:> 3} {opc:>07b} {str(instruction)}") def print_max_opcode(): - print(f"highest opcode: {max([opcode(instruction) for instruction in Instruction])}") + max_opc = max([opcode(instruction) for instruction in Instruction]) + print(f"highest opcode: {max_opc}") + print(f"#ibs: {len(bin(max_opc)[2:])}") + # === diff --git a/specification/src/instructions.md b/specification/src/instructions.md index a050a3e1..b3a722f6 100644 --- a/specification/src/instructions.md +++ b/specification/src/instructions.md @@ -12,10 +12,13 @@ For reasons of efficient [arithmetization](arithmetization.md), certain properti Concretely: - all double-word instructions have an odd opcode, _i.e._, the least significant bit is 1. - all instructions shrinking the operational stack are 2 mod 4, _i.e._, the second-to-least significant bit is 1. +- all [u32 instructions ](u32-table.md) are 4 mod 8, _i.e._, the third-to-least significant bit is 1. +Furthermore, given that an instruction is a u32 instruction, its next three least-significant bits (indices 3 through 5) identify the instruction. -The former property is used by instruction [skiz](instruction-specific-transition-constraints.md#instruction-skiz). -The latter property helps guarantee that operational stack underflow cannot happen. +The first property is used by instruction [skiz](instruction-specific-transition-constraints.md#instruction-skiz). +The second property helps guarantee that operational stack underflow cannot happen. It is used by several instructions through instruction group [`stack_shrinks_and_top_3_unconstrained`](instruction-groups.md#group-stack_shrinks_and_top_3_unconstrained). +The third property allows efficient arithmetization of the running product for the Permutation Argument between [Processor Table](processor-table.md) and [U32 Table](u32-table.md). ## OpStack Manipulation @@ -23,9 +26,9 @@ It is used by several instructions through instruction group [`stack_shrinks_and |:-------------|-------:|:--------------------|:----------------------|:---------------------------------------------------------------------------------| | `pop` | 2 | `_ a` | `_` | Pops top element from stack. | | `push` + `a` | 1 | `_` | `_ a` | Pushes `a` onto the stack. | -| `divine` | 4 | `_` | `_ a` | Pushes a non-deterministic element `a` to the stack. Interface for secret input. | -| `dup` + `i` | 5 | e.g., `_ e d c b a` | e.g., `_ e d c b a d` | Duplicates the element `i` positions away from the top, assuming `0 <= i < 16`. | -| `swap` + `i` | 9 | e.g., `_ e d c b a` | e.g., `_ e a c b d` | Swaps the `i`th stack element with the top of the stack, assuming `0 < i < 16`. | +| `divine` | g | `_` | `_ a` | Pushes a non-deterministic element `a` to the stack. Interface for secret input. | +| `dup` + `i` | 9 | e.g., `_ e d c b a` | e.g., `_ e d c b a d` | Duplicates the element `i` positions away from the top, assuming `0 <= i < 16`. | +| `swap` + `i` | 17 | e.g., `_ e d c b a` | e.g., `_ e a c b d` | Swaps the `i`th stack element with the top of the stack, assuming `0 < i < 16`. | Instruction `divine` (together with [`divine_sibling`](#hashing)) make TritonVM a virtual machine that can execute non-deterministic programs. As programs go, this concept is somewhat unusual and benefits from additional explanation. @@ -40,30 +43,30 @@ the value `a` was supplied as a secret input. ## Control Flow -| Instruction | Opcode | old OpStack | new OpStack | old `ip` | new `ip` | old JumpStack | new JumpStack | Description | -|:-------------|-------:|:------------|:------------|:---------|:-------------|:--------------|:--------------|:----------------------------------------------------------------------------------------------------------------------------| -| `nop` | 8 | `_` | `_` | `_` | `_ + 1` | `_` | `_` | Do nothing | -| `skiz` | 6 | `_ a` | `_` | `_` | `_ + s` | `_` | `_` | Skip next instruction if `a` is zero. `s` ∈ {1, 2, 3} depends on `a` and whether or not next instruction takes an argument. | -| `call` + `d` | 13 | `_` | `_` | `o` | `d` | `_` | `_ (o+2, d)` | Push `(o+2,d)` to the jump stack, and jump to absolute address `d` | -| `return` | 12 | `_` | `_` | `_` | `o` | `_ (o, d)` | `_` | Pop one pair off the jump stack and jump to that pair's return address (which is the first element). | -| `recurse` | 16 | `_` | `_` | `_` | `d` | `_ (o, d)` | `_ (o, d)` | Peek at the top pair of the jump stack and jump to that pair's destination address (which is the second element). | -| `assert` | 10 | `_ a` | `_` | `_` | `_ + 1` or 💥 | `_` | `_` | Pops `a` if `a == 1`, else crashes the virtual machine. | -| `halt` | 0 | `_` | `_` | `_` | `_ + 1` | `_` | `_` | Solves the halting problem (if the instruction is reached). Indicates graceful shutdown of the VM. | +| Instruction | Opcode | old OpStack | new OpStack | old `ip` | new `ip` | old JumpStack | new JumpStack | Description | +|:-------------|-------:|:------------|:------------|:---------|:---------|:--------------|:--------------|:----------------------------------------------------------------------------------------------------------------------------| +| `nop` | 16 | `_` | `_` | `_` | `_ + 1` | `_` | `_` | Do nothing | +| `skiz` | 10 | `_ a` | `_` | `_` | `_ + s` | `_` | `_` | Skip next instruction if `a` is zero. `s` ∈ {1, 2, 3} depends on `a` and whether or not next instruction takes an argument. | +| `call` + `d` | 25 | `_` | `_` | `o` | `d` | `_` | `_ (o+2, d)` | Push `(o+2,d)` to the jump stack, and jump to absolute address `d` | +| `return` | 24 | `_` | `_` | `_` | `o` | `_ (o, d)` | `_` | Pop one pair off the jump stack and jump to that pair's return address (which is the first element). | +| `recurse` | 32 | `_` | `_` | `_` | `d` | `_ (o, d)` | `_ (o, d)` | Peek at the top pair of the jump stack and jump to that pair's destination address (which is the second element). | +| `assert` | 18 | `_ a` | `_` | `_` | `_ + 1` | `_` | `_` | Pops `a` if `a == 1`, else crashes the virtual machine. | +| `halt` | 0 | `_` | `_` | `_` | `_ + 1` | `_` | `_` | Solves the halting problem (if the instruction is reached). Indicates graceful shutdown of the VM. | ## Memory Access | Instruction | Opcode | old OpStack | new OpStack | old `ramv` | new `ramv` | Description | |:------------|-------:|:------------|:------------|:-----------|:-----------|:----------------------------------------------------------------------------------------| -| `read_mem` | 20 | `_ p a` | `_ p v` | `v` | `v` | Reads value `v` from RAM at address `p` and overwrites the top of the OpStack with `v`. | -| `write_mem` | 24 | `_ p v` | `_ p v` | `_` | `v` | Writes OpStack's top-most value `v` to RAM at the address `p`. | +| `read_mem` | 40 | `_ p a` | `_ p v` | `v` | `v` | Reads value `v` from RAM at address `p` and overwrites the top of the OpStack with `v`. | +| `write_mem` | 48 | `_ p v` | `_ p v` | `_` | `v` | Writes OpStack's top-most value `v` to RAM at the address `p`. | ## Hashing | Instruction | Opcode | old OpStack | new OpStack | Description | |:-----------------|-------:|:----------------|:------------------------------|:--------------------------------------------------------------------------------------------------------| -| `hash` | 28 | `_jihgfedcba` | `_yxwvu00000` | Overwrites the stack's 10 top-most elements with their hash digest (length 5) and 5 zeros. | -| `divine_sibling` | 32 | `_ i*****edcba` | e.g., `_ (i div 2)edcbazyxwv` | Helps traversing a Merkle tree during authentication path verification. See extended description below. | -| `assert_vector` | 36 | `_` | `_` | Assert equality of `st(i)` to `st(i+5)` for `0 <= i < 4`. Crashes the VM if any pair is unequal. | +| `hash` | 56 | `_jihgfedcba` | `_yxwvu00000` | Overwrites the stack's 10 top-most elements with their hash digest (length 5) and 5 zeros. | +| `divine_sibling` | 64 | `_ i*****edcba` | e.g., `_ (i div 2)edcbazyxwv` | Helps traversing a Merkle tree during authentication path verification. See extended description below. | +| `assert_vector` | 72 | `_` | `_` | Assert equality of `st(i)` to `st(i+5)` for `0 <= i < 4`. Crashes the VM if any pair is unequal. | The instruction `hash` works as follows. The stack's 10 top-most elements (`jihgfedcba`) are reversed and concatenated with six zeros, resulting in `abcdefghij000000`. @@ -83,24 +86,40 @@ Depending on this least significant bit of `i`, `divine_sibling` either The 11th element of the operational stack `i` is shifted by 1 bit to the right, _i.e._, the least-significant bit is dropped. In conjunction with instruction `hash` and `assert_vector`, the instruction `divine_sibling` allows to efficiently verify a Merkle authentication path. -## Arithmetic on Stack - -| Instruction | Opcode | old OpStack | new OpStack | Description | -|:------------|-------:|:----------------|:---------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `add` | 14 | `_ b a` | `_ c` | Computes the sum (`c`) of the top two elements of the stack (`b` and `a`) over the field. | -| `mul` | 18 | `_ b a` | `_ c` | Computes the product (`c`) of the top two elements of the stack (`b` and `a`) over the field. | -| `invert` | 40 | `_ a` | `_ b` | Computes the multiplicative inverse (over the field) of the top of the stack. Crashes the VM if the top of the stack is 0. | -| `split` | 44 | `_ a` | `_ lo hi` | Decomposes the top of the stack into the lower 32 bits and the upper 32 bits. Use with care, preferably through [pseudo instructions](pseudo-instructions.md). | -| `eq` | 22 | `_ b a` | `_ (a == b)` | Tests the top two stack elements for equality. | -| `lsb` | 48 | `_ a` | `_ (a >> 1) (a % 2)` | Bit-shifts `a` to the right by 1 bit and pushes the least significant bit of `a` to the stack. Use with care, preferably through [pseudo instructions](pseudo-instructions.md). | -| `xxadd` | 52 | `_ z y x b c a` | `_ z y x w v u` | Adds the two extension field elements encoded by field elements `z y x` and `b c a`, overwriting the top-most extension field element with the result. | -| `xxmul` | 56 | `_ z y x b c a` | `_ z y x w v u` | Multiplies the two extension field elements encoded by field elements `z y x` and `b c a`, overwriting the top-most extension field element with the result. | -| `xinvert` | 60 | `_ z y x` | `_ w v u` | Inverts the extension field element encoded by field elements `z y x` in-place. Crashes the VM if the extension field element is 0. | -| `xbmul` | 26 | `_ z y x a` | `_ w v u` | Scalar multiplication of the extension field element encoded by field elements `z y x` with field element `a`. Overwrites `z y x` with the result. | +## Base Field Arithmetic on Stack + +| Instruction | Opcode | old OpStack | new OpStack | Description | +|:------------|-------:|:------------|:-------------|:---------------------------------------------------------------------------------------------------------------------------| +| `add` | 26 | `_ b a` | `_ c` | Computes the sum (`c`) of the top two elements of the stack (`b` and `a`) over the field. | +| `mul` | 34 | `_ b a` | `_ c` | Computes the product (`c`) of the top two elements of the stack (`b` and `a`) over the field. | +| `invert` | 80 | `_ a` | `_ b` | Computes the multiplicative inverse (over the field) of the top of the stack. Crashes the VM if the top of the stack is 0. | +| `eq` | 42 | `_ b a` | `_ (a == b)` | Tests the top two stack elements for equality. | + +## Bitwise Arithmetic on Stack + +| Instruction | Opcode | old OpStack | new OpStack | Description | +|:------------|-------:|:------------|:---------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `lsb` | 88 | `_ a` | `_ (a >> 1) (a % 2)` | Bit-shifts `a` to the right by 1 bit and pushes the least significant bit of `a` to the stack. Use with care, preferably through [pseudo instructions](pseudo-instructions.md). | +| `split` | 4 | `_ a` | `_ lo hi` | Decomposes the top of the stack into the lower 32 bits and the upper 32 bits. | +| `lt` | 12 | `_ b a` | `_ a Date: Thu, 29 Dec 2022 00:03:39 +0100 Subject: [PATCH 04/53] remove unnecessary pseudo instruction, improve some existing ones --- specification/src/pseudo-instructions.md | 179 ++--------------------- 1 file changed, 14 insertions(+), 165 deletions(-) diff --git a/specification/src/pseudo-instructions.md b/specification/src/pseudo-instructions.md index 933cb409..65222bd2 100644 --- a/specification/src/pseudo-instructions.md +++ b/specification/src/pseudo-instructions.md @@ -4,20 +4,11 @@ Keeping the instruction set small results in a faster overall proving time due t Some convenient-to-have instructions are not implemented natively, but are instead simulated using existing instructions. The following list is a comprehensive overview, including their expansion. - -| Instruction | old OpStack | new OpStack | Description | -|:---------------|:------------|:--------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `neg` | `_ a` | `_ -a` | Replaces the top of the stack with the field element corresponding to its additively inverse element. | -| `sub` | `_ b a` | `_ a-b` | Subtracts the stack's one-from top element from the stack's topmost element. | -| `is_u32` | `_ a` | `_ a` | Crashes the VM if `a` cannot be represented as an unsigned 32-bit integer. | -| `split_assert` | `_ a` | `_ lo hi` | Like instruction `split`, but additionally asserts that the results `lo` and `hi` are, indeed, 32-bit integers. Should be used over `split`. | -| `lte` | `_ b a` | `_ (a <= b)` | Tests if the top element on the stack is less than or equal to the one-from top element. Crashes the VM if `a` or `b` is not a 32-bit integer. | -| `lt` | `_ b a` | `_ (a < b)` | Tests if the top element on the stack is less than the one-from top element. Crashes the VM if `a` or `b` is not a 32-bit integer. | -| `and` | `_ b a` | `_ (a and b)` | Computes the bitwise-and of the top two stack elements. Crashes the VM if `a` or `b` is not a 32-bit integer. | -| `xor` | `_ b a` | `_ (a xor b)` | Computes the bitwise-xor of the top two stack elements. Crashes the VM if `a` or `b` is not a 32-bit integer. | -| `reverse` | `_ a` | `_ b` | Reverses the bit expansion of the top stack element. Crashes the VM if `a` is not a 32-bit integer. | -| `div` | `_ d n` | `_ q r` | Computes division with remainder of the top two stack elements, assuming both arguments are unsigned 32-bit integers. The result satisfies `n == d·q + r`and `r < d`. | - +| Instruction | old OpStack | new OpStack | Description | +|:------------|:------------|:------------|:------------------------------------------------------------------------------------------------------| +| `neg` | `_ a` | `_ -a` | Replaces the top of the stack with the field element corresponding to its additively inverse element. | +| `sub` | `_ b a` | `_ a-b` | Subtracts the stack's one-from top element from the stack's topmost element. | +| `is_u32` | `_ a` | `_ {0,1}` | Tests if the top of the stack is a u32, replacing it with the result. | ## Pseudo instruction `neg` @@ -45,158 +36,16 @@ add ## Pseudo instruction `is_u32` -Program length: 70. - -Execution cycle count: 68. - -``` -dup 0 -for _ in 0..32 { - lsb - pop -} -push 0 -eq -assert -``` - -## Pseudo instruction `split_assert` - -Program length: 145. - -Execution cycle count: 139. - -``` -split -is_u32 -swap 1 -is_u32 -swap 1 -``` - -## Pseudo instruction `lte` - -Program length: 160. +Program length: 10. -Execution cycle count: 146. +Execution cycle count: 7. ``` -push -1 -mul -add -split_assert -push 0 -eq -swap 1 -pop -``` - -## Pseudo instruction `lt` - -Program length: 163. - -Execution cycle count: 148. - -``` -push 1 -add -lte -``` - -## Pseudo instruction `and` - -Program length: 426. - -Execution cycle count: 295. - -``` -for _ in 0..32 { - lsb - swap 2 - lsb - swap 2 -} - -for _ in 0..2 { - push 0 - eq - assert -} - -push 0 // accumulator - -for i in (0..32).rev() { - swap 2 - mul - push 1< Date: Thu, 29 Dec 2022 13:04:42 +0100 Subject: [PATCH 05/53] spec Processor Table's Permutation Argument with U32 Table --- specification/src/processor-table.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/specification/src/processor-table.md b/specification/src/processor-table.md index 09c1a170..bd2df65b 100644 --- a/specification/src/processor-table.md +++ b/specification/src/processor-table.md @@ -7,7 +7,7 @@ Each register is assigned a column in the processor table. ## Extension Colums -The Instruction Table has 11 extension columns, corresponding to Evaluation Arguments and Permutation Arguments. +The Instruction Table has 12 extension columns, corresponding to Evaluation Arguments and Permutation Arguments. Namely: 1. `RunningEvaluationStandardInput` for the Evaluation Argument with the input symbols. 1. `RunningEvaluationStandardOutput` for the Evaluation Argument with the output symbols. @@ -16,6 +16,7 @@ Namely: 1. `RunningProductRamTable` for the Permutation Argument with the [RAM Table](random-access-memory-table.md). 1. `RunningProductJumpStackTable` for the Permutation Argument with the [Jump Stack Table](jump-stack-table.md). 1. `RunningEvaluationToHashTable` for the Evaluation Argument with the [Hash Table](hash-table.md) for copying the input to the hash function from the Processor to the Hash Coprocessor. +1. `RunningProductU32Table` for the Permutation Argument with the [U32 Table](u32-table.md). 1. `RunningEvaluationFromHashTable` for the Evaluation Argument with the [Hash Table](hash-table.md) for copying the hash digest from the Hash Coprocessor to the Processor. 1. `RunningProductAllClockJumpDifferences` for the [Multi-Table Set Equality argument](memory-consistency.md#clock-jump-differences-with-multiplicities-in-the-processor-table) with the [RAM Table](random-access-memory-table.md), the [JumpStack Table](jump-stack-table.md), and the [OpStack Table](operational-stack-table.md). @@ -98,6 +99,7 @@ However, in order to verify the correctness of `RunningEvaluationFromHashTable`, 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. +1. `RunningProductU32Table` is 1. 1. The running evaluation of relevant clock cycles is 1. 1. The running evaluation of unique clock jump differences starts off having applied one evaluation step with the clock jump difference with respect to indeterminate 🛒, if the `cjd` column does not start with zero. 1. The running product of all clock jump differences starts starts off having accumulated the first factor with respect to indeterminate 🚿, but only if the `cjd` column does not start with zero. @@ -140,19 +142,20 @@ However, in order to verify the correctness of `RunningEvaluationFromHashTable`, 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` +1. `RunningProductU32Table - 1` 1. `rer - 1` 1. `cjd · (reu - 🛒 - cjd)) + (1 - cjd · invm) · (reu - 1)` 1. `cjd · (rpm - (🚿 - cjd)) + (1 - cjd · invm) · (rpm - 1)` ## Consistency Constraints -1. The composition of instruction buckets `ib0`-`ib5` corresponds the current instruction `ci`. +1. The composition of instruction buckets `ib0` through `ib6` corresponds to the current instruction `ci`. 1. The inverse of clock jump difference with multiplicity `invm` is the inverse-or-zero of the the clock jump difference `cjd`. (Results in 2 polynomials.) 1. The padding indicator `IsPadding` is either 0 or 1. ### Consistency Constraints as Polynomials -1. `ci - (2^5·ib5 + 2^4·ib4 + 2^3·ib3 + 2^2·ib2 + 2^1·ib1 + 2^0·ib0)` +1. `ci - (2^6·ib6 + 2^5·ib5 + 2^4·ib4 + 2^3·ib3 + 2^2·ib2 + 2^1·ib1 + 2^0·ib0)` 1. `invm·(invm·cjd - 1)` 1. `cjd·(invm·cjd - 1)` 1. `IsPadding·(IsPadding - 1)` @@ -174,6 +177,13 @@ The following constraints apply to every pair of rows. 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. +1. 1. If the current instruction is `split`, then the running product with the U32 Table absorbs `st0` and `st1` in the next row with respect to challenges 🥜 and 🌰 and indeterminate 🧷. + 1. If the current instruction is `lt`, `and`, `xor`, or `pow`, then the running product with the U32 Table absorbs `st0`, `st1`, and `ci` in the current row and `st0` in the next row with respect to challenges 🥜, 🌰, 🥑, and 🥕, and indeterminate 🧷. + 1. If the current instruction is `log2floor`, then the running product with the U32 Table absorbs `st0` and `ci` in the current row and `st0` in the next row with respect to challenges 🥜, 🥑, and 🥕, and indeterminate 🧷. + 1. If the current instruction is `div`, then the running product with the U32 Table absorbs both + 1. `st0` in the next row and `st1` and `ci` in the current row as well as the constant `1` with respect to challenges 🥜, 🌰, 🥑, and 🥕, and indeterminate 🧷. + 1. `st0` in the current row and `st1` in the next row with respect to challenges 🥜 and 🌰, and indeterminate 🧷. + 1. Else, _i.e._, if the current instruction is not a u32 instruction, the running product with the U32 Table remains unchanged. 1. The unique inverse column `invu'` holds the inverse-or-zero of the difference of consecutive `cjd`'s, if `cjd'` is nonzero. (Results in 2 constraint polynomials.) 1. The running evaluation `reu` of unique `cjd`'s is updated relative to indeterminate 🛒 whenever the difference of `cjd`'s is nonzero *and* the next `cjd` is nonzero. @@ -193,6 +203,14 @@ The following constraints apply to every pair of rows. 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')` +1. 1. `split_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0' - 🌰·st1'))` + 1. `+ lt_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🌰·st1 - 🥑·ci - 🥕·st0'))` + 1. `+ and_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🌰·st1 - 🥑·ci - 🥕·st0'))` + 1. `+ xor_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🌰·st1 - 🥑·ci - 🥕·st0'))` + 1. `+ pow_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🌰·st1 - 🥑·ci - 🥕·st0'))` + 1. `+ log2floor_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🥑·ci - 🥕·st0'))` + 1. `+ div_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0' - 🌰·st1 - 🥑·ci - 🥕)·(🧷 - 🥜·st0 - 🌰·st1'))` + 1. `+ (1 - ib2)·(RunningProductU32Table' - RunningProductU32Table)` 1. `invu'·(invu'·(cjd' - cjd) - 1)·cjd'` 1. `(cjd' - cjd)·(invu'·(cjd' - cjd) - 1)·cjd'` 1. `(1 - (cjd' - cjd)·invu)·(reu' - reu) + (1 - cjd'·invm)·(reu' - reu) + cjd'·(cjd' - cjd)·(reu' - 🛒·reu - cjd')` From 8b4bf065a5569065aa137f8ad9537caae41a50cb Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Thu, 29 Dec 2022 13:45:15 +0100 Subject: [PATCH 06/53] instruction specific transition constraints for new u32 instructions --- specification/src/instruction-groups.md | 8 +- ...ruction-specific-transition-constraints.md | 89 +++++++++++++++---- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/specification/src/instruction-groups.md b/specification/src/instruction-groups.md index bebede56..55107a30 100644 --- a/specification/src/instruction-groups.md +++ b/specification/src/instruction-groups.md @@ -55,9 +55,15 @@ A summary of all instructions and which groups they are part of is given in the | `add` | | x | | x | | | | | | | | | | x | | | `mul` | | x | | x | | | | | | | | | | x | | | `invert` | | x | | x | | | | | | | x | | | | | -| `split` | | x | | x | | x | | | | | | | | | | | `eq` | | x | | x | | | | | | | | | | x | | | `lsb` | | x | | x | | x | | | | | | | | | | +| `split` | | x | | x | | x | | | | | | | | | | +| `lt` | | x | | x | | | | | | | | | | x | | +| `and` | | x | | x | | | | | | | | | | x | | +| `xor` | | x | | x | | | | | | | | | | x | | +| `log2floor` | | x | | x | | | | | | | x | | | | | +| `pow` | | x | | x | | | | | | | | | | x | | +| `div` | | x | | x | | | | | | x | | | | | | | `xxadd` | | x | | x | | | | | | x | | | | | | | `xxmul` | | x | | x | | | | | | x | | | | | | | `xinvert` | | x | | x | | | | | | x | | | | | | diff --git a/specification/src/instruction-specific-transition-constraints.md b/specification/src/instruction-specific-transition-constraints.md index 202e22b0..e88588ac 100644 --- a/specification/src/instruction-specific-transition-constraints.md +++ b/specification/src/instruction-specific-transition-constraints.md @@ -467,10 +467,48 @@ Additionally, it defines the following transition constraints. 1. `st0'·st0 - 1` +## Instruction `eq` + +This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `binary_operation`, and `keep_ram`. +Additionally, it defines the following transition constraints. + +### Description + +1. Helper variable `hv0` is the inverse of the difference of the stack's two top-most elements or 0. +1. Helper variable `hv0` is the inverse of the difference of the stack's two top-most elements or the difference is 0. +1. The new top of the stack is 1 if the difference between the stack's two top-most elements is not invertible, 0 otherwise. + +### Polynomials + +1. `hv0·(hv0·(st1 - st0) - 1)` +1. `(st1 - st0)·(hv0·(st1 - st0) - 1)` +1. `st0' - (1 - hv0·(st1 - st0))` + +### Helper variable definitions for `eq` + +1. `hv0 = inverse(rhs - lhs)` if `rhs - lhs ≠ 0`. +1. `hv0 = 0` if `rhs - lhs = 0`. + +## Instruction `lsb` + +This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `stack_grows_and_top_2_unconstrained`, and `keep_ram`. +Additionally, it defines the following transition constraints. + +### Description + +1. The least significant bit is a bit +1. The operand decomposes into right-shifted operand and the least significant bit + +### Polynomials + +1. `st0'·(st0' - 1)` +1. `st0 - (2·st1' + st0')` + ## Instruction `split` This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `stack_grows_and_top_2_unconstrained`, and `keep_ram`. Additionally, it defines the following transition constraints. +Part of the correct transition, namely the range check on the instruction's result, is guaranteed by the [U32 Table](u32-table.md). ### Description @@ -489,42 +527,55 @@ Given the high 32 bits of `st0` as `hi = st0 >> 32` and the low 32 bits of `st0` 1. `hv0 = (hi - (2^32 - 1))` if `lo ≠ 0`. 1. `hv0 = 0` if `lo = 0`. -## Instruction `eq` +## Instruction `lt` This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `binary_operation`, and `keep_ram`. -Additionally, it defines the following transition constraints. +Beyond that, this instruction has no transition constraints. +Instead, correct transition is guaranteed by the [U32 Table](u32-table.md). -### Description +## Instruction `and` -1. Helper variable `hv0` is the inverse of the difference of the stack's two top-most elements or 0. -1. Helper variable `hv0` is the inverse of the difference of the stack's two top-most elements or the difference is 0. -1. The new top of the stack is 1 if the difference between the stack's two top-most elements is not invertible, 0 otherwise. +This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `binary_operation`, and `keep_ram`. +Beyond that, this instruction has no transition constraints. +Instead, correct transition is guaranteed by the [U32 Table](u32-table.md). -### Polynomials +## Instruction `xor` -1. `hv0·(hv0·(st1 - st0) - 1)` -1. `(st1 - st0)·(hv0·(st1 - st0) - 1)` -1. `st0' - (1 - hv0·(st1 - st0))` +This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `binary_operation`, and `keep_ram`. +Beyond that, this instruction has no transition constraints. +Instead, correct transition is guaranteed by the [U32 Table](u32-table.md). -### Helper variable definitions for `eq` +## Instruction `log2floor` -1. `hv0 = inverse(rhs - lhs)` if `rhs - lhs ≠ 0`. -1. `hv0 = 0` if `rhs - lhs = 0`. +This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `unary_operation`, and `keep_ram`. +Beyond that, this instruction has no transition constraints. +Instead, correct transition is guaranteed by the [U32 Table](u32-table.md). -## Instruction `lsb` +## Instruction `pow` -This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `stack_grows_and_top_2_unconstrained`, and `keep_ram`. +This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `binary_operation`, and `keep_ram`. +Beyond that, this instruction has no transition constraints. +Instead, correct transition is guaranteed by the [U32 Table](u32-table.md). + +## Instruction `div` + +This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `stack_remains_and_top_3_unconstrained`, and `keep_ram`. Additionally, it defines the following transition constraints. +Recall that instruction `div` takes stack `_ d n` and computes `_ q r` where `n` is the numerator, `d` is the denominator, `r` is the remainder, and `q` is the quotient. +The following two properties are guaranteed by the [U32 Table](u32-table.md): +1. The remainder `r` is smaller than the denominator `d`, and +1. all four of `n`, `d`, `q`, and `r` are u32s. + ### Description -1. The least significant bit is a bit -1. The operand decomposes into right-shifted operand and the least significant bit +1. Numerator is quotient times denominator plus remainder: `n == q·d + r`. +1. Stack element `st2` does not change. ### Polynomials -1. `st0'·(st0' - 1)` -1. `st0 - (2·st1' + st0')` +1. `st0 - st1·st1' - st0'` +1. `st2' - st2` ## Instruction `xxadd` From 703d7b2b688b742b3f06a9cce1b07516fd0b822d Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Thu, 29 Dec 2022 14:09:59 +0100 Subject: [PATCH 07/53] add u32 table to AET relation diagram --- specification/src/img/aet-relations.ipe | 70 +++++++++++++++--------- specification/src/img/aet-relations.pdf | Bin 27055 -> 28000 bytes specification/src/img/aet-relations.png | Bin 24997 -> 18617 bytes 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/specification/src/img/aet-relations.ipe b/specification/src/img/aet-relations.ipe index a76e9010..95c9a99e 100644 --- a/specification/src/img/aet-relations.ipe +++ b/specification/src/img/aet-relations.ipe @@ -1,7 +1,7 @@ - + \usepackage{lmodern} \renewcommand*\familydefault{\sfdefault} \usepackage[T1]{fontenc} @@ -320,14 +320,18 @@ h - - -108 204 m -108 32 l -280 32 l -280 204 l + + +80 216 m +80 44 l +280 44 l +280 216 l h + +172 124 m +172 112 l + 192 138 m 192 154 l @@ -340,7 +344,7 @@ h jumpStack opStack RAM -hash +hash 128 184 m 128 132 l @@ -392,29 +396,29 @@ h 1.20123 0 0 1.20123 176 176 e - -176 120 m -176 116 l -176 112 -172 112 c -136 112 l -132 112 -132 108 c -132 104 l + +176 124 m +176 108 l +176 104 +172 104 c +102 104 l +98 104 +98 100 c +98 92 l -124 108 m -124 116 l -124 120 -128 120 c -160 120 l -164 120 -168 124 c - - +94 92 m +94 104 l +94 108 +98 108 c +168 108 l +172 108 +172 112 c + + 1.20123 0 0 1.20123 176 176 e - + 1.20123 0 0 1.20123 176 176 e @@ -451,5 +455,17 @@ h 160 100 m 8 0 0 -8 160 92 152 92 a +u32 + +96 124 m +96 120 l +96 116 +100 116 c +156 116 l +160 116 +160 120 c +160 124 +169.127 126.735 c + diff --git a/specification/src/img/aet-relations.pdf b/specification/src/img/aet-relations.pdf index d6771743a4af8f8010bb9c475358e10eb13645dc..ca3d8b31ed217e5b4faaefa21e383750fe73b142 100644 GIT binary patch delta 21057 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 diff --git a/specification/src/img/aet-relations.png b/specification/src/img/aet-relations.png index 4a3dfd9e9a86eac69da9585e3fdbf03fd311bc84..18a2b1e55dfe28a381a93b4b5b5abfb1dc65e072 100644 GIT binary patch literal 18617 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 VHa5*a;cpS4hzdyw=J9KN{$H}5Vmbf- literal 24997 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%0A_CCA5x2A=J$ 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 ZVCVg>KhS4A?1~Sp(a}7qk*RJK@E>g;17QFF From daaf5836d4b91d5a5bab4548c736b542eb133970 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Thu, 29 Dec 2022 14:13:40 +0100 Subject: [PATCH 08/53] spec: increase table counter from 7 to 8 --- specification/src/arithmetization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specification/src/arithmetization.md b/specification/src/arithmetization.md index 3a0187f8..b45ceda0 100644 --- a/specification/src/arithmetization.md +++ b/specification/src/arithmetization.md @@ -11,7 +11,7 @@ In the nomenclature of this note, a trace is a special kind of table that tracks ## Algebraic Execution Tables -There are 7 Arithmetic Execution Tables in TritonVM. +There are 8 Arithmetic Execution Tables in TritonVM. Their relation is described by below figure. A red arrow indicates an Evaluation Argument, a blue arrow indicates a Permutation Argument, and the green arrow is the Bézout Argument. @@ -34,7 +34,7 @@ Together, these columns are referred to as table's _base_ columns, and make up t The entries of a table's columns corresponding to Permutation, Evaluation, and Bézout Arguments are elements from the _X-field_ $\mathbb{F}_{p^3}$. These columns are referred to as a table's _extension_ columns, both because the entries are elements of the X-field and because the entries can only be computed using the base tables, through an _extension_ process. -Collectively, a table's base columns with its entries interpreted as elements of the X-field and the table's extension columns make up the _extension table_. +Together, these columns are referred to as a table's _extension_ columns, and make up the _extension table_. ### Padding @@ -44,7 +44,7 @@ The height $h$ of the longest AET determines the padded height for all tables, w ## Arithmetic Intermediate Representation -For each table, up to three lists containing constraints of different type are given: +For each table, up to four lists containing constraints of different type are given: 1. Initial Constraints, defining values in a table's first row, 1. Consistency Constraints, establishing consistency within any given row, 1. Transition Constraints, establishing the consistency of two consecutive rows in relation to each other, and From 74894bd22f681d3a5dbb46fdc61b20de1f876ce3 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Fri, 30 Dec 2022 20:38:02 +0100 Subject: [PATCH 09/53] add new instructions --- triton-opcodes/src/instruction.rs | 333 ++++++++----------------- triton-vm/Cargo.toml | 2 +- triton-vm/src/state.rs | 44 +++- triton-vm/src/table/processor_table.rs | 93 ++++++- 4 files changed, 233 insertions(+), 239 deletions(-) diff --git a/triton-opcodes/src/instruction.rs b/triton-opcodes/src/instruction.rs index aaa2837e..aa621f70 100644 --- a/triton-opcodes/src/instruction.rs +++ b/triton-opcodes/src/instruction.rs @@ -10,6 +10,7 @@ use anyhow::bail; use anyhow::Result; use itertools::Itertools; use num_traits::One; +use num_traits::Zero; use regex::Regex; use strum::EnumCount; use strum::IntoEnumIterator; @@ -82,19 +83,28 @@ pub enum AnInstruction { ReadMem, WriteMem, - // Hashing-related instructions + // Hashing-related Hash, DivineSibling, AssertVector, - // Arithmetic on stack instructions + // Base field arithmetic on stack Add, Mul, Invert, - Split, Eq, - Lsb, + // Bitwise arithmetic on stack + Lsb, + Split, + Lt, + And, + Xor, + Log2Floor, + Pow, + Div, + + // Extension field arithmetic on stack XxAdd, XxMul, XInvert, @@ -115,6 +125,7 @@ impl Display for AnInstruction { Divine(None) => write!(f, "divine"), Dup(arg) => write!(f, "dup{}", arg), Swap(arg) => write!(f, "swap{}", arg), + // Control flow Nop => write!(f, "nop"), Skiz => write!(f, "skiz"), @@ -128,19 +139,28 @@ impl Display for AnInstruction { ReadMem => write!(f, "read_mem"), WriteMem => write!(f, "write_mem"), - // Hash instructions + // Hashing-related Hash => write!(f, "hash"), DivineSibling => write!(f, "divine_sibling"), AssertVector => write!(f, "assert_vector"), - // Arithmetic on stack instructions + // Base field arithmetic on stack Add => write!(f, "add"), Mul => write!(f, "mul"), Invert => write!(f, "invert"), - Split => write!(f, "split"), Eq => write!(f, "eq"), - Lsb => write!(f, "lsb"), + // Bitwise arithmetic on stack + Lsb => write!(f, "lsb"), + Split => write!(f, "split"), + Lt => write!(f, "lt"), + And => write!(f, "and"), + Xor => write!(f, "xor"), + Log2Floor => write!(f, "log_2_floor"), + Pow => write!(f, "pow"), + Div => write!(f, "div"), + + // Extension field arithmetic on stack XxAdd => write!(f, "xxadd"), XxMul => write!(f, "xxmul"), XInvert => write!(f, "xinvert"), @@ -177,9 +197,15 @@ impl AnInstruction { Add => Add, Mul => Mul, Invert => Invert, - Split => Split, Eq => Eq, Lsb => Lsb, + Split => Split, + Lt => Lt, + And => And, + Xor => Xor, + Log2Floor => Log2Floor, + Pow => Pow, + Div => Div, XxAdd => XxAdd, XxMul => XxMul, XInvert => XInvert, @@ -194,33 +220,39 @@ impl AnInstruction { match self { Pop => 2, Push(_) => 1, - Divine(_) => 4, - Dup(_) => 5, - Swap(_) => 9, - Nop => 8, - Skiz => 6, - Call(_) => 13, - Return => 12, - Recurse => 16, - Assert => 10, + Divine(_) => 8, + Dup(_) => 9, + Swap(_) => 17, + Nop => 16, + Skiz => 10, + Call(_) => 25, + Return => 24, + Recurse => 32, + Assert => 18, Halt => 0, - ReadMem => 20, - WriteMem => 24, - Hash => 28, - DivineSibling => 32, - AssertVector => 36, - Add => 14, - Mul => 18, - Invert => 40, - Split => 44, - Eq => 22, - Lsb => 48, - XxAdd => 52, - XxMul => 56, - XInvert => 60, - XbMul => 26, - ReadIo => 64, - WriteIo => 30, + ReadMem => 40, + WriteMem => 48, + Hash => 56, + DivineSibling => 64, + AssertVector => 72, + Add => 26, + Mul => 34, + Invert => 80, + Eq => 42, + Lsb => 88, + Split => 4, + Lt => 12, + And => 20, + Xor => 28, + Log2Floor => 36, + Pow => 44, + Div => 52, + XxAdd => 96, + XxMul => 104, + XInvert => 112, + XbMul => 50, + ReadIo => 120, + WriteIo => 58, } } @@ -279,9 +311,15 @@ impl AnInstruction { Add => Add, Mul => Mul, Invert => Invert, - Split => Split, Eq => Eq, Lsb => Lsb, + Split => Split, + Lt => Lt, + And => And, + Xor => Xor, + Log2Floor => Log2Floor, + Pow => Pow, + Div => Div, XxAdd => XxAdd, XxMul => XxMul, XInvert => XInvert, @@ -491,41 +529,42 @@ fn parse_token(token: &str, tokens: &mut SplitWhitespace) -> Result vec![ReadMem], "write_mem" => vec![WriteMem], - // Hashing-related instructions + // Hashing-related "hash" => vec![Hash], "divine_sibling" => vec![DivineSibling], "assert_vector" => vec![AssertVector], - // Arithmetic on stack instructions + // Base field arithmetic on stack "add" => vec![Add], "mul" => vec![Mul], "invert" => vec![Invert], - "split" => vec![Split], "eq" => vec![Eq], + + // Bitwise arithmetic on stack "lsb" => vec![Lsb], + "split" => vec![Split], + "lt" => vec![Lt], + "and" => vec![And], + "xor" => vec![Xor], + "log_2_floor" => vec![Log2Floor], + "pow" => vec![Pow], + "div" => vec![Div], + + // Extension field arithmetic on stack "xxadd" => vec![XxAdd], "xxmul" => vec![XxMul], "xinvert" => vec![XInvert], "xbmul" => vec![XbMul], - // Pseudo-instructions - "neg" => vec![Push(BFieldElement::one().neg()), Mul], - "sub" => vec![Swap(ST1), Push(BFieldElement::one().neg()), Mul, Add], - - "lte" => pseudo_instruction_lte(), - "lt" => pseudo_instruction_lt(), - "and" => pseudo_instruction_and(), - "xor" => pseudo_instruction_xor(), - "reverse" => pseudo_instruction_reverse(), - "div" => pseudo_instruction_div(), - - "is_u32" => pseudo_instruction_is_u32(), - "split_assert" => pseudo_instruction_split_assert(), - // Read/write "read_io" => vec![ReadIo], "write_io" => vec![WriteIo], + // pseudo instructions + "neg" => vec![Push(BFieldElement::one().neg()), Mul], + "sub" => vec![Swap(ST1), Push(BFieldElement::one().neg()), Mul, Add], + "is_u32" => pseudo_instruction_is_u32(), + _ => return Err(anyhow::Error::new(UnknownInstruction(token.to_string()))), }; @@ -538,175 +577,16 @@ fn parse_token(token: &str, tokens: &mut SplitWhitespace) -> Result Vec> { - // _ a - let mut instructions = vec![Dup(ST0)]; - // _ a a - for _ in 0..32 { - instructions.push(Lsb); - // _ a (a>>i) b - instructions.push(Pop); - // _ a (a>>i) - } - instructions.push(Push(0_u64.into())); - // _ a (a>>32) 0 - instructions.push(Eq); - // _ a (a>>32)==0 - instructions.push(Assert); - // _ a - instructions -} - -fn pseudo_instruction_split_assert() -> Vec> { - vec![ - vec![Split], - pseudo_instruction_is_u32(), - vec![Swap(ST1)], - pseudo_instruction_is_u32(), - vec![Swap(ST1)], - ] - .concat() -} - -fn pseudo_instruction_lte() -> Vec> { + // input stack: _ a vec![ - vec![Push(-BFieldElement::new(1)), Mul, Add], - pseudo_instruction_split_assert(), - vec![Push(0_u64.into()), Eq, Swap(ST1), Pop], + Dup(ST0), // _ a a + Split, // _ a lo hi + Push(BFieldElement::zero()), // _ a lo hi 0 + Eq, // _ a lo {0,1} + Swap(ST2), // _ {0,1} lo a + Eq, // _ {0,1} {0,1} + Mul, // _ {0,1} ] - .concat() -} - -fn pseudo_instruction_lt() -> Vec> { - vec![vec![Push(1_u64.into()), Add], pseudo_instruction_lte()].concat() -} - -fn pseudo_instruction_div() -> Vec> { - vec![ - vec![ - // _ d n - Divine(Some(Quotient)), - // _ d n q - ], - pseudo_instruction_is_u32(), - vec![ - // _ d n q - Dup(ST2), - // _ d n q d - Dup(ST1), - // _ d n q d q - Mul, - // _ d n q d·q - Dup(ST2), - // _ d n q d·q n - Swap(ST1), - // _ d n q n d·q - Push(-BFieldElement::new(1)), - // _ d n q n d·q -1 - Mul, - // _ d n q n -d·q - Add, - // _ d n q r - Dup(ST3), - // _ d n q r d - Dup(ST1), - // _ d n q r d r - ], - pseudo_instruction_lt(), - vec![ - // _ d n q r r Vec> { - let mut instructions = vec![]; - - // decompose into bits, interleaved - for _ in 0..32 { - // _ A||a B||b - instructions.push(Lsb); - // _ A||a B b - instructions.push(Swap(ST2)); - // _ b B A||a - instructions.push(Lsb); - // _ b B A a - instructions.push(Swap(ST2)); - // _ b a A B - } - - // assert u32-ness of A & B - instructions.push(Push(0_u64.into())); - instructions.push(Eq); - instructions.push(Assert); - // _ (b a)^32 A - instructions.push(Push(0_u64.into())); - instructions.push(Eq); - instructions.push(Assert); - // _ (b a)^32 - - // start accumulating - instructions.push(Push(0_u64.into())); - - for i in (0..32).rev() { - // _ (b a)^i b a acc - instructions.push(Swap(ST2)); - // _ (b a)^i acc a b - instructions.push(Mul); - // _ (b a)^i acc a&b - instructions.push(Push((1_u64 << i).into())); - // _ (b a)^i acc (a&b) 2^i - instructions.push(Mul); - // _ (b a)^i acc (a&b)·2^i - instructions.push(Add); - // _ (b a)^i acc' - } - - instructions -} - -fn pseudo_instruction_xor() -> Vec> { - // a+b = a^b + (a&b)<<1 => a^b = a+b - 2·(a&b) - // Credit: Daniel Lubarov - vec![ - vec![Dup(ST1), Dup(ST1)], - pseudo_instruction_and(), - vec![Push(-BFieldElement::new(2)), Mul, Add, Add], - ] - .concat() -} - -fn pseudo_instruction_reverse() -> Vec> { - let mut instructions = vec![]; - - // decompose into bits - for _ in 0..32 { - instructions.push(Lsb); - instructions.push(Swap(ST1)); - } - instructions.push(Push(0_u64.into())); - instructions.push(Eq); - instructions.push(Assert); - - // start accumulating - instructions.push(Push(0_u64.into())); - for i in 0..32 { - instructions.push(Swap(ST1)); - instructions.push(Push((1_u64 << i).into())); - instructions.push(Mul); - instructions.push(Add); - } - instructions } fn parse_elem(tokens: &mut SplitWhitespace) -> Result { @@ -732,7 +612,7 @@ fn parse_label(tokens: &mut SplitWhitespace) -> Result { } pub fn all_instructions_without_args() -> Vec { - let all_instructions = vec![ + let all_instructions: [_; Instruction::COUNT] = [ Pop, Push(Default::default()), Divine(None), @@ -753,9 +633,15 @@ pub fn all_instructions_without_args() -> Vec { Add, Mul, Invert, - Split, Eq, Lsb, + Split, + Lt, + And, + Xor, + Log2Floor, + Pow, + Div, XxAdd, XxMul, XInvert, @@ -763,8 +649,7 @@ pub fn all_instructions_without_args() -> Vec { ReadIo, WriteIo, ]; - assert_eq!(Instruction::COUNT, all_instructions.len()); - all_instructions + all_instructions.to_vec() } pub fn all_labelled_instructions_with_args() -> Vec { diff --git a/triton-vm/Cargo.toml b/triton-vm/Cargo.toml index 5c837848..749f769f 100644 --- a/triton-vm/Cargo.toml +++ b/triton-vm/Cargo.toml @@ -31,7 +31,7 @@ default-features = false [dependencies] twenty-first = "0.10" -triton-opcodes = "0.11" +triton-opcodes = {path = "../triton-opcodes"} triton-profiler = "0.11" anyhow = "1.0" bincode = "1.3" diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 45d53ece..f0116c03 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -365,16 +365,6 @@ impl<'pgm> VMState<'pgm> { self.instruction_pointer += 1; } - Split => { - let elem = self.op_stack.pop()?; - let n: u64 = elem.value(); - let lo = BFieldElement::new(n & 0xffff_ffff); - let hi = BFieldElement::new(n >> 32); - self.op_stack.push(lo); - self.op_stack.push(hi); - self.instruction_pointer += 1; - } - Eq => { let lhs = self.op_stack.pop()?; let rhs = self.op_stack.pop()?; @@ -390,6 +380,40 @@ impl<'pgm> VMState<'pgm> { self.instruction_pointer += 1; } + Split => { + let elem = self.op_stack.pop()?; + let n: u64 = elem.value(); + let lo = BFieldElement::new(n & 0xffff_ffff); + let hi = BFieldElement::new(n >> 32); + self.op_stack.push(lo); + self.op_stack.push(hi); + self.instruction_pointer += 1; + } + + Lt => { + todo!() + } + + And => { + todo!() + } + + Xor => { + todo!() + } + + Log2Floor => { + todo!() + } + + Pow => { + todo!() + } + + Div => { + todo!() + } + XxAdd => { let lhs: XFieldElement = self.op_stack.pop_x()?; let rhs: XFieldElement = self.op_stack.safe_peek_x(); diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 4e4fd44d..fdf9e60f 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -19,7 +19,8 @@ use strum_macros::Display; use strum_macros::EnumCount as EnumCountMacro; use strum_macros::EnumIter; use triton_opcodes::instruction::all_instructions_without_args; -use triton_opcodes::instruction::{AnInstruction::*, Instruction}; +use triton_opcodes::instruction::AnInstruction::*; +use triton_opcodes::instruction::Instruction; use triton_opcodes::ord_n::Ord7; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::traits::Inverse; @@ -954,9 +955,15 @@ impl ExtProcessorTable { (Add, factory.instruction_add()), (Mul, factory.instruction_mul()), (Invert, factory.instruction_invert()), - (Split, factory.instruction_split()), (Eq, factory.instruction_eq()), (Lsb, factory.instruction_lsb()), + (Split, factory.instruction_split()), + (Lt, factory.instruction_lt()), + (And, factory.instruction_and()), + (Xor, factory.instruction_xor()), + (Log2Floor, factory.instruction_log_2_floor()), + (Pow, factory.instruction_pow()), + (Div, factory.instruction_div()), (XxAdd, factory.instruction_xxadd()), (XxMul, factory.instruction_xxmul()), (XInvert, factory.instruction_xinv()), @@ -2382,6 +2389,72 @@ impl DualRowConstraints { .concat() } + pub fn instruction_lt( + &self, + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { + todo!() + } + + pub fn instruction_and( + &self, + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { + todo!() + } + + pub fn instruction_xor( + &self, + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { + todo!() + } + + pub fn instruction_log_2_floor( + &self, + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { + todo!() + } + + pub fn instruction_pow( + &self, + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { + todo!() + } + + pub fn instruction_div( + &self, + ) -> Vec< + ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + >, + > { + todo!() + } + pub fn instruction_xxadd( &self, ) -> Vec< @@ -4467,9 +4540,15 @@ mod constraint_polynomial_tests { Add => tc.instruction_add(), Mul => tc.instruction_mul(), Invert => tc.instruction_invert(), - Split => tc.instruction_split(), Eq => tc.instruction_eq(), Lsb => tc.instruction_lsb(), + Split => tc.instruction_split(), + Lt => tc.instruction_lt(), + And => tc.instruction_and(), + Xor => tc.instruction_xor(), + Log2Floor => tc.instruction_log_2_floor(), + Pow => tc.instruction_pow(), + Div => tc.instruction_div(), XxAdd => tc.instruction_xxadd(), XxMul => tc.instruction_xxmul(), XInvert => tc.instruction_xinv(), @@ -4821,9 +4900,15 @@ mod constraint_polynomial_tests { (Add, factory.instruction_add()), (Mul, factory.instruction_mul()), (Invert, factory.instruction_invert()), - (Split, factory.instruction_split()), (Eq, factory.instruction_eq()), (Lsb, factory.instruction_lsb()), + (Split, factory.instruction_split()), + (Lt, factory.instruction_lt()), + (And, factory.instruction_and()), + (Xor, factory.instruction_xor()), + (Log2Floor, factory.instruction_log_2_floor()), + (Pow, factory.instruction_pow()), + (Div, factory.instruction_div()), (XxAdd, factory.instruction_xxadd()), (XxMul, factory.instruction_xxmul()), (XInvert, factory.instruction_xinv()), From 2523f25184587eb2f44fb81b83c9949822bbea83 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Fri, 30 Dec 2022 21:54:20 +0100 Subject: [PATCH 10/53] implement step_mut for new instructions --- triton-vm/src/state.rs | 67 +++++++++++++++++++++++++++++++++++------- triton-vm/src/vm.rs | 10 +++++-- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index f0116c03..5824068f 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -7,11 +7,15 @@ use ndarray::Array1; use num_traits::One; use num_traits::Zero; +use triton_opcodes::instruction::AnInstruction::*; use triton_opcodes::instruction::DivinationHint; -use triton_opcodes::instruction::{AnInstruction::*, Instruction}; -use triton_opcodes::ord_n::{Ord16, Ord16::*, Ord7}; +use triton_opcodes::instruction::Instruction; +use triton_opcodes::ord_n::Ord16; +use triton_opcodes::ord_n::Ord16::*; +use triton_opcodes::ord_n::Ord7; use triton_opcodes::program::Program; use twenty_first::shared_math::b_field_element::BFieldElement; +use twenty_first::shared_math::other::log_2_floor; 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; @@ -79,6 +83,9 @@ pub enum VMOutput { /// /// One row per round in the XLIX permutation XlixTrace(Box<[[BFieldElement; STATE_SIZE]; 1 + NUM_ROUNDS]>), + + /// Executed u32 instruction as well as its left-hand side and right-hand side + U32TableEntry(Instruction, BFieldElement, BFieldElement), } #[allow(clippy::needless_range_loop)] @@ -382,36 +389,74 @@ impl<'pgm> VMState<'pgm> { Split => { let elem = self.op_stack.pop()?; - let n: u64 = elem.value(); - let lo = BFieldElement::new(n & 0xffff_ffff); - let hi = BFieldElement::new(n >> 32); + let lo = BFieldElement::new(elem.value() & 0xffff_ffff); + let hi = BFieldElement::new(elem.value() >> 32); self.op_stack.push(lo); self.op_stack.push(hi); self.instruction_pointer += 1; + vm_output = Some(VMOutput::U32TableEntry(Instruction::Split, hi, lo)); } Lt => { - todo!() + let lhs = self.op_stack.pop()?; + let rhs = self.op_stack.pop()?; + let lt = match lhs.value() < rhs.value() { + true => BFieldElement::one(), + false => BFieldElement::zero(), + }; + self.op_stack.push(lt); + self.instruction_pointer += 1; + vm_output = Some(VMOutput::U32TableEntry(Instruction::Lt, lhs, rhs)); } And => { - todo!() + let lhs = self.op_stack.pop()?; + let rhs = self.op_stack.pop()?; + let and = BFieldElement::new(lhs.value() & rhs.value()); + self.op_stack.push(and); + self.instruction_pointer += 1; + vm_output = Some(VMOutput::U32TableEntry(Instruction::And, lhs, rhs)); } Xor => { - todo!() + let lhs = self.op_stack.pop()?; + let rhs = self.op_stack.pop()?; + let xor = BFieldElement::new(lhs.value() ^ rhs.value()); + self.op_stack.push(xor); + self.instruction_pointer += 1; + vm_output = Some(VMOutput::U32TableEntry(Instruction::Xor, lhs, rhs)); } Log2Floor => { - todo!() + let lhs = self.op_stack.pop()?; + let l2f = BFieldElement::new(log_2_floor(lhs.value() as u128)); + self.op_stack.push(l2f); + self.instruction_pointer += 1; + let rhs = BFieldElement::zero(); + vm_output = Some(VMOutput::U32TableEntry(Instruction::Log2Floor, lhs, rhs)); } Pow => { - todo!() + let lhs = self.op_stack.pop()?; + let rhs = self.op_stack.pop()?; + let pow = BFieldElement::new(lhs.value().pow(rhs.value() as u32)); + self.op_stack.push(pow); + self.instruction_pointer += 1; + vm_output = Some(VMOutput::U32TableEntry(Instruction::Pow, lhs, rhs)); } Div => { - todo!() + let numer = self.op_stack.pop()?; + let denom = self.op_stack.pop()?; + if denom.is_zero() { + return vm_err(InverseOfZero); + } + let quot = BFieldElement::new(numer.value() / denom.value()); + let rem = BFieldElement::new(numer.value() % denom.value()); + self.op_stack.push(quot); + self.op_stack.push(rem); + self.instruction_pointer += 1; + vm_output = Some(VMOutput::U32TableEntry(Instruction::Div, numer, denom)); } XxAdd => { diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 0c480f43..4d60a56b 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -1,13 +1,14 @@ use ndarray::Array2; use ndarray::Axis; - -use triton_opcodes::program::Program; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::b_field_element::BFIELD_ZERO; 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 triton_opcodes::instruction::Instruction; +use triton_opcodes::program::Program; + use crate::state::VMOutput; use crate::state::VMState; use crate::table::hash_table; @@ -49,6 +50,9 @@ pub fn simulate( match vm_output { Some(VMOutput::XlixTrace(hash_trace)) => aet.append_hash_trace(*hash_trace), + Some(VMOutput::U32TableEntry(instr, lhs, rhs)) => { + aet.u32_entries.push((instr, lhs, rhs)) + } Some(VMOutput::WriteOutputSymbol(written_word)) => stdout.push(written_word), None => (), } @@ -109,6 +113,7 @@ pub fn run( pub struct AlgebraicExecutionTrace { pub processor_matrix: Array2, pub hash_matrix: Array2, + pub u32_entries: Vec<(Instruction, BFieldElement, BFieldElement)>, } impl Default for AlgebraicExecutionTrace { @@ -116,6 +121,7 @@ impl Default for AlgebraicExecutionTrace { Self { processor_matrix: Array2::default([0, processor_table::BASE_WIDTH]), hash_matrix: Array2::default([0, hash_table::BASE_WIDTH]), + u32_entries: vec![], } } } From 44e8ec073da282613f3b33dd0fd68a19eabe84e1 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Fri, 30 Dec 2022 22:06:22 +0100 Subject: [PATCH 11/53] add instruction-specific constraints for new instructions --- triton-vm/src/table/processor_table.rs | 156 +++++++++++++++++-------- 1 file changed, 106 insertions(+), 50 deletions(-) diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index fdf9e60f..eb86cb49 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -2278,50 +2278,6 @@ impl DualRowConstraints { .concat() } - pub fn instruction_split( - &self, - ) -> 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. - // - // $st0 - (2^32·st0' + st1') = 0$ - let st0_decomposes_to_two_32_bit_chunks = - self.st0() - (two_pow_32.clone() * self.st0_next() + self.st1_next()); - - // Helper variable `hv0` = 0 if either - // 1. `hv0` is the difference between (2^32 - 1) and the high 32 bits (`st0'`), or - // 1. the low 32 bits (`st1'`) are 0. - // - // st1'·(hv0·(st0' - (2^32 - 1)) - 1) - // lo·(hv0·(hi - 0xffff_ffff)) - 1) - let hv0_holds_inverse_of_chunk_difference_or_low_bits_are_0 = { - let hv0 = self.hv0(); - let hi = self.st0_next(); - let lo = self.st1_next(); - let ffff_ffff = two_pow_32 - self.one(); - - lo * (hv0 * (hi - ffff_ffff) - self.one()) - }; - - let specific_constraints = vec![ - st0_decomposes_to_two_32_bit_chunks, - hv0_holds_inverse_of_chunk_difference_or_low_bits_are_0, - ]; - [ - specific_constraints, - self.grow_stack_and_top_two_elements_unconstrained(), - self.step_1(), - self.keep_ram(), - ] - .concat() - } - pub fn instruction_eq( &self, ) -> Vec< @@ -2389,6 +2345,50 @@ impl DualRowConstraints { .concat() } + pub fn instruction_split( + &self, + ) -> 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. + // + // $st0 - (2^32·st0' + st1') = 0$ + let st0_decomposes_to_two_32_bit_chunks = + self.st0() - (two_pow_32.clone() * self.st0_next() + self.st1_next()); + + // Helper variable `hv0` = 0 if either + // 1. `hv0` is the difference between (2^32 - 1) and the high 32 bits (`st0'`), or + // 1. the low 32 bits (`st1'`) are 0. + // + // st1'·(hv0·(st0' - (2^32 - 1)) - 1) + // lo·(hv0·(hi - 0xffff_ffff)) - 1) + let hv0_holds_inverse_of_chunk_difference_or_low_bits_are_0 = { + let hv0 = self.hv0(); + let hi = self.st0_next(); + let lo = self.st1_next(); + let ffff_ffff = two_pow_32 - self.one(); + + lo * (hv0 * (hi - ffff_ffff) - self.one()) + }; + + let specific_constraints = vec![ + st0_decomposes_to_two_32_bit_chunks, + hv0_holds_inverse_of_chunk_difference_or_low_bits_are_0, + ]; + [ + specific_constraints, + self.grow_stack_and_top_two_elements_unconstrained(), + self.step_1(), + self.keep_ram(), + ] + .concat() + } + pub fn instruction_lt( &self, ) -> Vec< @@ -2397,7 +2397,15 @@ impl DualRowConstraints { DualRowIndicator, >, > { - todo!() + // no further constraints + let specific_constraints = vec![]; + [ + specific_constraints, + self.step_1(), + self.binop(), + self.keep_ram(), + ] + .concat() } pub fn instruction_and( @@ -2408,7 +2416,15 @@ impl DualRowConstraints { DualRowIndicator, >, > { - todo!() + // no further constraints + let specific_constraints = vec![]; + [ + specific_constraints, + self.step_1(), + self.binop(), + self.keep_ram(), + ] + .concat() } pub fn instruction_xor( @@ -2419,7 +2435,15 @@ impl DualRowConstraints { DualRowIndicator, >, > { - todo!() + // no further constraints + let specific_constraints = vec![]; + [ + specific_constraints, + self.step_1(), + self.binop(), + self.keep_ram(), + ] + .concat() } pub fn instruction_log_2_floor( @@ -2430,7 +2454,15 @@ impl DualRowConstraints { DualRowIndicator, >, > { - todo!() + // no further constraints + let specific_constraints = vec![]; + [ + specific_constraints, + self.step_1(), + self.unop(), + self.keep_ram(), + ] + .concat() } pub fn instruction_pow( @@ -2441,7 +2473,15 @@ impl DualRowConstraints { DualRowIndicator, >, > { - todo!() + // no further constraints + let specific_constraints = vec![]; + [ + specific_constraints, + self.step_1(), + self.binop(), + self.keep_ram(), + ] + .concat() } pub fn instruction_div( @@ -2452,7 +2492,23 @@ impl DualRowConstraints { DualRowIndicator, >, > { - todo!() + // `n == d·q + r` means `st0 - st1·st1' - st0'` + let numerator_is_quotient_times_denominator_plus_remainder = + self.st0() - self.st1() * self.st1_next() - self.st0_next(); + + let st2_does_not_change = self.st2_next() - self.st2(); + + let specific_constraints = vec![ + numerator_is_quotient_times_denominator_plus_remainder, + st2_does_not_change, + ]; + [ + specific_constraints, + self.step_1(), + self.stack_remains_and_top_three_elements_unconstrained(), + self.keep_ram(), + ] + .concat() } pub fn instruction_xxadd( From 1babe486730292a9e841392340de9cb0cadc87fe Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sat, 31 Dec 2022 08:29:11 +0100 Subject: [PATCH 12/53] add terminal constraint for U32 Table's Permutation Argument --- triton-vm/src/table/challenges.rs | 1 + triton-vm/src/table/cross_table_argument.rs | 8 ++++++++ triton-vm/src/table/processor_table.rs | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/triton-vm/src/table/challenges.rs b/triton-vm/src/table/challenges.rs index 9cedf832..3e235da8 100644 --- a/triton-vm/src/table/challenges.rs +++ b/triton-vm/src/table/challenges.rs @@ -241,6 +241,7 @@ impl AllChallenges { processor_to_jump_stack_weight: weights.pop().unwrap(), processor_to_hash_weight: weights.pop().unwrap(), hash_to_processor_weight: weights.pop().unwrap(), + processor_to_u32_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(), diff --git a/triton-vm/src/table/cross_table_argument.rs b/triton-vm/src/table/cross_table_argument.rs index a43c6dcc..15a9669d 100644 --- a/triton-vm/src/table/cross_table_argument.rs +++ b/triton-vm/src/table/cross_table_argument.rs @@ -24,6 +24,7 @@ use crate::table::table_column::OpStackExtTableColumn; use crate::table::table_column::ProcessorExtTableColumn; use crate::table::table_column::ProgramExtTableColumn; use crate::table::table_column::RamExtTableColumn; +use crate::table::table_column::U32ExtTableColumn; pub const NUM_PRIVATE_PERM_ARGS: usize = PROCESSOR_TABLE_NUM_PERMUTATION_ARGUMENTS; pub const NUM_PRIVATE_EVAL_ARGS: usize = 3; @@ -107,6 +108,7 @@ pub struct CrossTableChallenges { pub processor_to_jump_stack_weight: XFieldElement, pub processor_to_hash_weight: XFieldElement, pub hash_to_processor_weight: XFieldElement, + pub processor_to_u32_weight: XFieldElement, pub all_clock_jump_differences_weight: XFieldElement, pub input_to_processor_weight: XFieldElement, pub processor_to_output_weight: XFieldElement, @@ -124,6 +126,7 @@ pub enum CrossTableChallengeId { ProcessorToJumpStackWeight, ProcessorToHashWeight, HashToProcessorWeight, + ProcessorToU32Weight, AllClockJumpDifferencesWeight, InputToProcessorWeight, ProcessorToOutputWeight, @@ -150,6 +153,7 @@ impl TableChallenges for CrossTableChallenges { ProcessorToJumpStackWeight => self.processor_to_jump_stack_weight, ProcessorToHashWeight => self.processor_to_hash_weight, HashToProcessorWeight => self.hash_to_processor_weight, + ProcessorToU32Weight => self.processor_to_u32_weight, AllClockJumpDifferencesWeight => self.all_clock_jump_differences_weight, InputToProcessorWeight => self.input_to_processor_weight, ProcessorToOutputWeight => self.processor_to_output_weight, @@ -218,6 +222,9 @@ impl Evaluable for GrandCrossTableArg { let hash_to_processor = ext_row [HashExtTableColumn::ToProcessorRunningEvaluation.master_ext_table_index()] - ext_row[ProcessorExtTableColumn::FromHashTableEvalArg.master_ext_table_index()]; + let processor_to_u32 = ext_row + [ProcessorExtTableColumn::U32TablePermArg.master_ext_table_index()] + - ext_row[U32ExtTableColumn::ProcessorPermArg.master_ext_table_index()]; let all_clock_jump_differences = ext_row [ProcessorExtTableColumn::AllClockJumpDifferencesPermArg.master_ext_table_index()] - ext_row @@ -236,6 +243,7 @@ impl Evaluable for GrandCrossTableArg { + 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(ProcessorToU32Weight) * processor_to_u32 + challenges.get_challenge(AllClockJumpDifferencesWeight) * all_clock_jump_differences; vec![non_linear_sum] } diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index eb86cb49..a336ae9f 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -50,7 +50,7 @@ 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_PERMUTATION_ARGUMENTS: usize = 6; pub const PROCESSOR_TABLE_NUM_EVALUATION_ARGUMENTS: usize = 5; pub const PROCESSOR_TABLE_NUM_EXTENSION_CHALLENGES: usize = ProcessorTableChallengeId::COUNT; From 144a6492193700a92f5e31e4eca3fe260b8d3a38 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sat, 31 Dec 2022 09:56:47 +0100 Subject: [PATCH 13/53] add transition constraint for U32 Table's Perm Arg in Processor Table --- triton-vm/src/table/processor_table.rs | 96 +++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index a336ae9f..dca96d83 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -512,6 +512,7 @@ pub enum ProcessorTableChallengeId { OpStackPermIndeterminate, RamPermIndeterminate, JumpStackPermIndeterminate, + U32PermIndeterminate, /// Weights for condensing part of a row into a single column. (Related to processor table.) InstructionTableIpWeight, @@ -555,6 +556,11 @@ pub enum ProcessorTableChallengeId { HashTableDigestOutputWeight2, HashTableDigestOutputWeight3, HashTableDigestOutputWeight4, + + U32TableLhsWeight, + U32TableRhsWeight, + U32TableCiWeight, + U32TableResultWeight, } impl From for usize { @@ -576,6 +582,7 @@ pub struct ProcessorTableChallenges { pub op_stack_perm_indeterminate: XFieldElement, pub ram_perm_indeterminate: XFieldElement, pub jump_stack_perm_indeterminate: XFieldElement, + pub u32_table_perm_indeterminate: XFieldElement, /// Weights for condensing part of a row into a single column. (Related to processor table.) pub instruction_table_ip_weight: XFieldElement, @@ -624,7 +631,6 @@ pub struct ProcessorTableChallenges { pub u32_table_rhs_weight: XFieldElement, pub u32_table_ci_weight: XFieldElement, pub u32_table_result_weight: XFieldElement, - pub u32_table_perm_indeterminate: XFieldElement, } impl TableChallenges for ProcessorTableChallenges { @@ -641,6 +647,7 @@ impl TableChallenges for ProcessorTableChallenges { OpStackPermIndeterminate => self.op_stack_perm_indeterminate, RamPermIndeterminate => self.ram_perm_indeterminate, JumpStackPermIndeterminate => self.jump_stack_perm_indeterminate, + U32PermIndeterminate => self.u32_table_perm_indeterminate, InstructionTableIpWeight => self.instruction_table_ip_weight, InstructionTableCiProcessorWeight => self.instruction_table_ci_processor_weight, InstructionTableNiaWeight => self.instruction_table_nia_weight, @@ -678,6 +685,10 @@ impl TableChallenges for ProcessorTableChallenges { HashTableDigestOutputWeight2 => self.hash_table_digest_output_weight2, HashTableDigestOutputWeight3 => self.hash_table_digest_output_weight3, HashTableDigestOutputWeight4 => self.hash_table_digest_output_weight4, + U32TableLhsWeight => self.u32_table_lhs_weight, + U32TableRhsWeight => self.u32_table_rhs_weight, + U32TableCiWeight => self.u32_table_ci_weight, + U32TableResultWeight => self.u32_table_result_weight, } } } @@ -1059,6 +1070,7 @@ impl ExtProcessorTable { .push(factory.running_product_for_jump_stack_table_updates_correctly()); transition_constraints.push(factory.running_evaluation_to_hash_table_updates_correctly()); transition_constraints.push(factory.running_evaluation_from_hash_table_updates_correctly()); + transition_constraints.push(factory.running_product_to_u32_table_updates_correctly()); let mut built_transition_constraints = transition_constraints .into_iter() @@ -3228,6 +3240,14 @@ impl DualRowConstraints { > { self.current_ext_row_variables[FromHashTableEvalArg.master_ext_table_index()].clone() } + pub fn running_product_u32_table( + &self, + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.current_ext_row_variables[U32TablePermArg.master_ext_table_index()].clone() + } // Property: All polynomial variables that contain '_next' have the same // variable position / value as the one without '_next', +/- NUM_COLUMNS. @@ -3669,6 +3689,14 @@ impl DualRowConstraints { > { self.next_ext_row_variables[FromHashTableEvalArg.master_ext_table_index()].clone() } + pub fn running_product_u32_table_next( + &self, + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + self.next_ext_row_variables[U32TablePermArg.master_ext_table_index()].clone() + } pub fn decompose_arg( &self, @@ -4189,6 +4217,72 @@ impl DualRowConstraints { hash_selector * running_evaluation_remains + hash_deselector * running_evaluation_updates } + + pub fn running_product_to_u32_table_updates_correctly( + &self, + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + DualRowIndicator, + > { + let indeterminate = self.circuit_builder.challenge(U32PermIndeterminate); + let lhs_weight = self.circuit_builder.challenge(U32TableLhsWeight); + let rhs_weight = self.circuit_builder.challenge(U32TableRhsWeight); + let ci_weight = self.circuit_builder.challenge(U32TableCiWeight); + let result_weight = self.circuit_builder.challenge(U32TableResultWeight); + + let split_deselector = + InstructionDeselectors::instruction_deselector(self, Instruction::Split); + let lt_deselector = InstructionDeselectors::instruction_deselector(self, Instruction::Lt); + let and_delector = InstructionDeselectors::instruction_deselector(self, Instruction::And); + let xor_deselector = InstructionDeselectors::instruction_deselector(self, Instruction::Xor); + let pow_deselector = InstructionDeselectors::instruction_deselector(self, Instruction::Pow); + let log_2_floor_deselector = + InstructionDeselectors::instruction_deselector(self, Instruction::Log2Floor); + let div_deselector = InstructionDeselectors::instruction_deselector(self, Instruction::Div); + + let rp = self.running_product_u32_table(); + let rp_next = self.running_product_u32_table_next(); + + let split_factor = indeterminate.clone() + - lhs_weight.clone() * self.st0_next() + - rhs_weight.clone() * self.st1_next(); + let binop_factor = indeterminate.clone() + - lhs_weight.clone() * self.st0() + - rhs_weight.clone() * self.st1() + - ci_weight.clone() * self.ci() + - result_weight.clone() * self.st0_next(); + let unop_factor = indeterminate.clone() + - lhs_weight.clone() * self.st0() + - ci_weight.clone() * self.ci() + - result_weight.clone() * self.st0_next(); + let div_factor_for_lt = indeterminate.clone() + - lhs_weight.clone() * self.st0_next() + - rhs_weight.clone() * self.st1() + - ci_weight * self.ci() + - result_weight; + let div_factor_for_range_check = + indeterminate - lhs_weight * self.st0() - rhs_weight * self.st1_next(); + + let split_summand = split_deselector * (rp_next.clone() - rp.clone() * split_factor); + let lt_summand = lt_deselector * (rp_next.clone() - rp.clone() * binop_factor.clone()); + let and_summand = and_delector * (rp_next.clone() - rp.clone() * binop_factor.clone()); + let xor_summand = xor_deselector * (rp_next.clone() - rp.clone() * binop_factor.clone()); + let pow_summand = pow_deselector * (rp_next.clone() - rp.clone() * binop_factor); + let log_2_floor_summand = + log_2_floor_deselector * (rp_next.clone() - rp.clone() * unop_factor); + let div_summand = div_deselector + * (rp_next.clone() - rp.clone() * div_factor_for_lt * div_factor_for_range_check); + let no_update_summand = (self.one() - self.ib2()) * (rp_next - rp); + + split_summand + + lt_summand + + and_summand + + xor_summand + + pow_summand + + log_2_floor_summand + + div_summand + + no_update_summand + } } #[derive(Debug, Clone)] From d72eb4034eed945c08b7f38edfa3a42a7c8847bf Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sat, 31 Dec 2022 10:05:29 +0100 Subject: [PATCH 14/53] remove divination hint for quotients --- triton-opcodes/src/instruction.rs | 10 ++-------- triton-vm/src/state.rs | 27 ++------------------------- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/triton-opcodes/src/instruction.rs b/triton-opcodes/src/instruction.rs index aa621f70..40fceea7 100644 --- a/triton-opcodes/src/instruction.rs +++ b/triton-opcodes/src/instruction.rs @@ -23,7 +23,6 @@ use twenty_first::shared_math::b_field_element::BFieldElement; use AnInstruction::*; use TokenError::*; -use crate::instruction::DivinationHint::Quotient; use crate::ord_n::Ord16; use crate::ord_n::Ord16::*; use crate::ord_n::Ord7; @@ -50,9 +49,7 @@ impl Display for LabelledInstruction { } #[derive(Debug, DisplayMacro, Clone, Copy, PartialEq, Eq, Hash, EnumCountMacro)] -pub enum DivinationHint { - Quotient, -} +pub enum DivinationHint {} /// A Triton VM instruction /// @@ -483,7 +480,6 @@ fn parse_token(token: &str, tokens: &mut SplitWhitespace) -> Result vec![Pop], "push" => vec![Push(parse_elem(tokens)?)], "divine" => vec![Divine(None)], - "divine_quotient" => vec![Divine(Some(Quotient))], "dup0" => vec![Dup(ST0)], "dup1" => vec![Dup(ST1)], "dup2" => vec![Dup(ST2)], @@ -657,7 +653,6 @@ pub fn all_labelled_instructions_with_args() -> Vec { Pop, Push(BFieldElement::new(42)), Divine(None), - Divine(Some(Quotient)), Dup(ST0), Dup(ST1), Dup(ST2), @@ -760,7 +755,7 @@ pub mod sample_programs { pub const ALL_INSTRUCTIONS: &str = " pop push 42 - divine divine_quotient + divine dup0 dup1 dup2 dup3 dup4 dup5 dup6 dup7 dup8 dup9 dup10 dup11 dup12 dup13 dup14 dup15 swap1 swap2 swap3 swap4 swap5 swap6 swap7 swap8 swap9 swap10 swap11 swap12 swap13 swap14 swap15 @@ -780,7 +775,6 @@ pub mod sample_programs { "pop", "push 42", "divine", - "divine_quotient", "dup0", "dup1", "dup2", diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 5824068f..45f6a6fe 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -8,7 +8,6 @@ use num_traits::One; use num_traits::Zero; use triton_opcodes::instruction::AnInstruction::*; -use triton_opcodes::instruction::DivinationHint; use triton_opcodes::instruction::Instruction; use triton_opcodes::ord_n::Ord16; use triton_opcodes::ord_n::Ord16::*; @@ -210,30 +209,8 @@ impl<'pgm> VMState<'pgm> { self.instruction_pointer += 2; } - Divine(hint) => { - use DivinationHint::*; - - let elem = if let Some(context) = hint { - match context { - Quotient => { - let numerator: u32 = self - .op_stack - .safe_peek(ST0) - .value() - .try_into() - .expect("Numerator uses more than 32 bits."); - let denominator: u32 = self - .op_stack - .safe_peek(ST1) - .value() - .try_into() - .expect("Denominator uses more than 32 bits."); - BFieldElement::new((numerator / denominator) as u64) - } - } - } else { - secret_in.remove(0) - }; + Divine(_) => { + let elem = secret_in.remove(0); self.op_stack.push(elem); self.instruction_pointer += 1; } From c82ec7393ad0dd9859fba8d8639299c1a5ec6196 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sat, 31 Dec 2022 11:22:16 +0100 Subject: [PATCH 15/53] remove confusing and unfinished line in specification --- specification/src/u32-table.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/specification/src/u32-table.md b/specification/src/u32-table.md index 05fbadc5..fa880d40 100644 --- a/specification/src/u32-table.md +++ b/specification/src/u32-table.md @@ -83,7 +83,6 @@ The instruction `div` uses the U32 Table to ensure that the remainder `r` is sma Hence, the result of `LT` is used. The instruction `div` _also_ uses the U32 Table to ensure that the numerator `n` and the quotient `q` are u32. For this range check, happening in its independent section, no result is required. -While the processor accumulates two factors into the running product in between two consecutive rows, the U32 Table The instruction `split` also uses the U32 Table for range checking only, _i.e._, to ensure that the instruction's resulting “high bits” and “low bits” each fit in a u32. To conditionally copy the required result to the processor, instruction de-selectors like in the Processor Table are used. @@ -119,8 +118,8 @@ Both types of challenges are X-field elements, _i.e._, elements of $\mathbb{F}_{ ### Initial Constraints as Polynomials -1. `CopyFlag·(RunningProductProcessor - 1)` -1. `(CopyFlag - 1)·(RunningProductProcessor - (🧷 - 🥜·LHS - 🌰·RHS - 🥑·CI - 🥕·Result))` +1. `(CopyFlag - 1)·(RunningProductProcessor - 1)` +1. `CopyFlag·(RunningProductProcessor - (🧷 - 🥜·LHS - 🌰·RHS - 🥑·CI - 🥕·Result))` ## Consistency Constraints From 3a42df5a194fd5ca225071970cfe2bc3b586d9c3 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sat, 31 Dec 2022 16:02:40 +0100 Subject: [PATCH 16/53] u32 table: add initial constraints --- triton-vm/src/table/u32_table.rs | 89 +++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 693cdce9..e592bebc 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -1,5 +1,6 @@ use ndarray::ArrayView2; use ndarray::ArrayViewMut2; +use std::ops::Mul; use strum::EnumCount; use strum_macros::Display; use strum_macros::EnumCount as EnumCountMacro; @@ -7,16 +8,26 @@ use strum_macros::EnumIter; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::x_field_element::XFieldElement; +use triton_opcodes::instruction::Instruction; use U32TableChallengeId::*; 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::SingleRowIndicator; +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::MasterBaseTableColumn; +use crate::table::table_column::MasterExtTableColumn; use crate::table::table_column::U32BaseTableColumn; +use crate::table::table_column::U32BaseTableColumn::*; use crate::table::table_column::U32ExtTableColumn; +use crate::table::table_column::U32ExtTableColumn::*; use crate::vm::AlgebraicExecutionTrace; pub const U32_TABLE_NUM_PERMUTATION_ARGUMENTS: usize = 2; @@ -38,7 +49,83 @@ impl ExtU32Table { SingleRowIndicator, >, > { - todo!() + let circuit_builder = ConstraintCircuitBuilder::new(); + let challenge = |c| circuit_builder.challenge(c); + let one = circuit_builder.b_constant(1_u32.into()); + + let copy_flag = circuit_builder.input(BaseRow(CopyFlag.master_base_table_index())); + let lhs = circuit_builder.input(BaseRow(LHS.master_base_table_index())); + let rhs = circuit_builder.input(BaseRow(RHS.master_base_table_index())); + let ci = circuit_builder.input(BaseRow(CI.master_base_table_index())); + + let rp = circuit_builder.input(ExtRow(ProcessorPermArg.master_ext_table_index())); + + let deselect_instructions = |instructions: &[Instruction]| { + instructions + .iter() + .map(|&instr| ci.clone() - circuit_builder.b_constant(instr.opcode_b())) + .fold(one.clone(), ConstraintCircuitMonad::mul) + * ci.clone() + }; + let lt_div_deselector = deselect_instructions(&[ + Instruction::And, + Instruction::Xor, + Instruction::Log2Floor, + Instruction::Pow, + ]); + let and_deselector = deselect_instructions(&[ + Instruction::Lt, + Instruction::Div, + Instruction::Xor, + Instruction::Log2Floor, + Instruction::Pow, + ]); + let xor_deselector = deselect_instructions(&[ + Instruction::Lt, + Instruction::Div, + Instruction::And, + Instruction::Log2Floor, + Instruction::Pow, + ]); + let log2floor_deselector = deselect_instructions(&[ + Instruction::Lt, + Instruction::Div, + Instruction::And, + Instruction::Xor, + Instruction::Pow, + ]); + let pow_deselector = deselect_instructions(&[ + Instruction::Lt, + Instruction::Div, + Instruction::And, + Instruction::Xor, + Instruction::Log2Floor, + ]); + let result = lt_div_deselector + * circuit_builder.input(BaseRow(LT.master_base_table_index())) + + and_deselector * circuit_builder.input(BaseRow(AND.master_base_table_index())) + + xor_deselector * circuit_builder.input(BaseRow(XOR.master_base_table_index())) + + log2floor_deselector + * circuit_builder.input(BaseRow(Log2Floor.master_base_table_index())) + + pow_deselector * circuit_builder.input(BaseRow(Pow.master_base_table_index())); + + let initial_factor = challenge(ProcessorPermIndeterminate) + - challenge(LhsWeight) * lhs + - challenge(RhsWeight) * rhs + - challenge(CIWeight) * ci + - challenge(ResultWeight) * result; + let copy_flag_is_0_or_rp_has_accumulated_first_row = + copy_flag.clone() * (rp.clone() - initial_factor); + + let default_initial = circuit_builder.x_constant(PermArg::default_initial()); + let copy_flag_is_1_or_rp_is_default_initial = (one - copy_flag) * (default_initial - rp); + + [ + copy_flag_is_1_or_rp_is_default_initial, + copy_flag_is_0_or_rp_has_accumulated_first_row, + ] + .map(|circuit| circuit.consume()) + .to_vec() } pub fn ext_consistency_constraints_as_circuits() -> Vec< From 24157cd76c890a327c020704cf29d508a0645a26 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sat, 31 Dec 2022 16:25:45 +0100 Subject: [PATCH 17/53] u32 table: add consistency constraints --- triton-vm/src/table/u32_table.rs | 60 +++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index e592bebc..3b6226e5 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -134,7 +134,65 @@ impl ExtU32Table { SingleRowIndicator, >, > { - todo!() + let circuit_builder = ConstraintCircuitBuilder::new(); + let one = circuit_builder.b_constant(1_u32.into()); + + let copy_flag = circuit_builder.input(BaseRow(CopyFlag.master_base_table_index())); + let bits = circuit_builder.input(BaseRow(Bits.master_base_table_index())); + let bits_minus_33_inv = + circuit_builder.input(BaseRow(BitsMinus33Inv.master_base_table_index())); + let lhs = circuit_builder.input(BaseRow(LHS.master_base_table_index())); + let rhs = circuit_builder.input(BaseRow(RHS.master_base_table_index())); + let lt = circuit_builder.input(BaseRow(LT.master_base_table_index())); + let and = circuit_builder.input(BaseRow(AND.master_base_table_index())); + let xor = circuit_builder.input(BaseRow(XOR.master_base_table_index())); + let log2floor = circuit_builder.input(BaseRow(Log2Floor.master_base_table_index())); + let lhs_copy = circuit_builder.input(BaseRow(LhsCopy.master_base_table_index())); + let pow = circuit_builder.input(BaseRow(Pow.master_base_table_index())); + let lhs_inv = circuit_builder.input(BaseRow(LhsInv.master_base_table_index())); + let rhs_inv = circuit_builder.input(BaseRow(RhsInv.master_base_table_index())); + + let copy_flag_is_bit = copy_flag.clone() * (one.clone() - copy_flag.clone()); + let copy_flag_is_0_or_bits_is_0 = copy_flag.clone() * bits.clone(); + let bits_minus_33_inv_is_inverse_of_bits_minus_33 = one.clone() + - bits_minus_33_inv * (bits - circuit_builder.b_constant(BFieldElement::new(33))); + let lhs_inv_is_0_or_the_inverse_of_lhs = + lhs_inv.clone() * (one.clone() - lhs.clone() * lhs_inv.clone()); + let lhs_is_0_or_lhs_inverse_is_the_inverse_of_lhs = + lhs.clone() * (one.clone() - lhs.clone() * lhs_inv.clone()); + let rhs_inv_is_0_or_the_inverse_of_rhs = + rhs_inv.clone() * (one.clone() - rhs.clone() * rhs_inv.clone()); + let rhs_is_0_or_rhs_inverse_is_the_inverse_of_rhs = + rhs.clone() * (one.clone() - rhs.clone() * rhs_inv.clone()); + let copy_flag_is_0_or_lhs_copy_is_lhs = copy_flag.clone() * (lhs_copy - lhs.clone()); + let padding_row = (one.clone() - copy_flag) + * (one.clone() - lhs.clone() * lhs_inv.clone()) + * (one.clone() - rhs * rhs_inv); + let padding_row_or_lt_is_2 = + padding_row.clone() * (lt - circuit_builder.b_constant(BFieldElement::new(2))); + let padding_row_or_and_is_0 = padding_row.clone() * and; + let padding_row_or_xor_is_0 = padding_row.clone() * xor; + let padding_row_or_pow_is_1 = padding_row * (one.clone() - pow); + let lhs_is_not_zero_or_log2floor_is_negative_1 = + (one.clone() - lhs * lhs_inv) * (log2floor + one); + + [ + copy_flag_is_bit, + copy_flag_is_0_or_bits_is_0, + bits_minus_33_inv_is_inverse_of_bits_minus_33, + lhs_inv_is_0_or_the_inverse_of_lhs, + lhs_is_0_or_lhs_inverse_is_the_inverse_of_lhs, + rhs_inv_is_0_or_the_inverse_of_rhs, + rhs_is_0_or_rhs_inverse_is_the_inverse_of_rhs, + copy_flag_is_0_or_lhs_copy_is_lhs, + padding_row_or_lt_is_2, + padding_row_or_and_is_0, + padding_row_or_xor_is_0, + padding_row_or_pow_is_1, + lhs_is_not_zero_or_log2floor_is_negative_1, + ] + .map(|circuit| circuit.consume()) + .to_vec() } pub fn ext_transition_constraints_as_circuits() -> Vec< From 72896cb5a384f62837948d671f3ae0ddc109affb Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sat, 31 Dec 2022 23:00:06 +0100 Subject: [PATCH 18/53] u32 table: add transition constraints --- specification/src/u32-table.md | 4 +- triton-vm/src/table/u32_table.rs | 198 ++++++++++++++++++++++++++++++- 2 files changed, 199 insertions(+), 3 deletions(-) diff --git a/specification/src/u32-table.md b/specification/src/u32-table.md index fa880d40..32813bf0 100644 --- a/specification/src/u32-table.md +++ b/specification/src/u32-table.md @@ -239,8 +239,8 @@ Written in Disjunctive Normal Form, the same constraints can be expressed as: 1. `(CopyFlag' - 1)·(LT' - 0)·(LT' - 1)·LhsLsb·(RhsLsb - 1)·LT` 1. `(CopyFlag' - 1)·(LT' - 0)·(LT' - 1)·(1 - LhsLsb - RhsLsb + 2·LhsLsb·RhsLsb)·(CopyFlag - 1)·(LT - 2)` 1. `(CopyFlag' - 1)·(LT' - 0)·(LT' - 1)·(1 - LhsLsb - RhsLsb + 2·LhsLsb·RhsLsb)·CopyFlag·LT` -1. `(CopyFlag' - 1)·(AND - 2·AND' + LhsLsb·RhsLsb)` -1. `(CopyFlag' - 1)·(XOR - 2·XOR' + LhsLsb + RhsLsb - 2·LhsLsb·RhsLsb)` +1. `(CopyFlag' - 1)·(AND - 2·AND' - LhsLsb·RhsLsb)` +1. `(CopyFlag' - 1)·(XOR - 2·XOR' - LhsLsb - RhsLsb + 2·LhsLsb·RhsLsb)` 1. `(CopyFlag' - 1)·(1 - LHS'·LhsInv')·LHS·(Log2Floor - Bits)` 1. `(CopyFlag' - 1)·LHS'·(Log2Floor' - Log2Floor)` 1. `(CopyFlag' - 1)·(RhsLsb - 1)·(Pow - Pow'·Pow')` diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 3b6226e5..755725ec 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -16,6 +16,7 @@ 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; @@ -198,7 +199,202 @@ impl ExtU32Table { pub fn ext_transition_constraints_as_circuits() -> Vec< ConstraintCircuit>, > { - todo!() + let circuit_builder = ConstraintCircuitBuilder::new(); + let challenge = |c| circuit_builder.challenge(c); + let one = circuit_builder.b_constant(1_u32.into()); + let two = circuit_builder.b_constant(2_u32.into()); + + let copy_flag = circuit_builder.input(CurrentBaseRow(CopyFlag.master_base_table_index())); + let bits = circuit_builder.input(CurrentBaseRow(Bits.master_base_table_index())); + let ci = circuit_builder.input(CurrentBaseRow(CI.master_base_table_index())); + let lhs = circuit_builder.input(CurrentBaseRow(LHS.master_base_table_index())); + let rhs = circuit_builder.input(CurrentBaseRow(RHS.master_base_table_index())); + let lt = circuit_builder.input(CurrentBaseRow(LT.master_base_table_index())); + let and = circuit_builder.input(CurrentBaseRow(AND.master_base_table_index())); + let xor = circuit_builder.input(CurrentBaseRow(XOR.master_base_table_index())); + let log2floor = circuit_builder.input(CurrentBaseRow(Log2Floor.master_base_table_index())); + let lhs_copy = circuit_builder.input(CurrentBaseRow(LhsCopy.master_base_table_index())); + let pow = circuit_builder.input(CurrentBaseRow(Pow.master_base_table_index())); + let rp = circuit_builder.input(CurrentExtRow(ProcessorPermArg.master_ext_table_index())); + + let copy_flag_next = circuit_builder.input(NextBaseRow(CopyFlag.master_base_table_index())); + let bits_next = circuit_builder.input(NextBaseRow(Bits.master_base_table_index())); + let ci_next = circuit_builder.input(NextBaseRow(CI.master_base_table_index())); + let lhs_next = circuit_builder.input(NextBaseRow(LHS.master_base_table_index())); + let rhs_next = circuit_builder.input(NextBaseRow(RHS.master_base_table_index())); + let lt_next = circuit_builder.input(NextBaseRow(LT.master_base_table_index())); + let and_next = circuit_builder.input(NextBaseRow(AND.master_base_table_index())); + let xor_next = circuit_builder.input(NextBaseRow(XOR.master_base_table_index())); + let log2floor_next = + circuit_builder.input(NextBaseRow(Log2Floor.master_base_table_index())); + let lhs_copy_next = circuit_builder.input(NextBaseRow(LhsCopy.master_base_table_index())); + let pow_next = circuit_builder.input(NextBaseRow(Pow.master_base_table_index())); + let lhs_inv_next = circuit_builder.input(NextBaseRow(LhsInv.master_base_table_index())); + let rp_next = circuit_builder.input(NextExtRow(ProcessorPermArg.master_ext_table_index())); + + let deselect_instructions = |instructions: &[Instruction]| { + instructions + .iter() + .map(|&instr| ci.clone() - circuit_builder.b_constant(instr.opcode_b())) + .fold(one.clone(), ConstraintCircuitMonad::mul) + * ci.clone() + }; + let lt_div_deselector = deselect_instructions(&[ + Instruction::And, + Instruction::Xor, + Instruction::Log2Floor, + Instruction::Pow, + ]); + let and_deselector = deselect_instructions(&[ + Instruction::Lt, + Instruction::Div, + Instruction::Xor, + Instruction::Log2Floor, + Instruction::Pow, + ]); + let xor_deselector = deselect_instructions(&[ + Instruction::Lt, + Instruction::Div, + Instruction::And, + Instruction::Log2Floor, + Instruction::Pow, + ]); + let log2floor_deselector = deselect_instructions(&[ + Instruction::Lt, + Instruction::Div, + Instruction::And, + Instruction::Xor, + Instruction::Pow, + ]); + let pow_deselector = deselect_instructions(&[ + Instruction::Lt, + Instruction::Div, + Instruction::And, + Instruction::Xor, + Instruction::Log2Floor, + ]); + let result = lt_div_deselector + * circuit_builder.input(CurrentBaseRow(LT.master_base_table_index())) + + and_deselector * circuit_builder.input(CurrentBaseRow(AND.master_base_table_index())) + + xor_deselector * circuit_builder.input(CurrentBaseRow(XOR.master_base_table_index())) + + log2floor_deselector + * circuit_builder.input(CurrentBaseRow(Log2Floor.master_base_table_index())) + + pow_deselector * circuit_builder.input(CurrentBaseRow(Pow.master_base_table_index())); + + let if_copy_flag_next_is_1_then_lhs_is_0 = copy_flag_next.clone() * lhs.clone(); + let if_copy_flag_next_is_1_then_rhs_is_0 = copy_flag_next.clone() * rhs.clone(); + let if_copy_flag_next_is_0_then_ci_stays = + (copy_flag_next.clone() - one.clone()) * (ci_next - ci.clone()); + let if_copy_flag_next_is_0_then_lhs_copy_stays = + (copy_flag_next.clone() - one.clone()) * (lhs_copy_next - lhs_copy.clone()); + let if_copy_flag_next_is_0_and_lhs_next_is_nonzero_then_bits_increases_by_1 = + (copy_flag_next.clone() - one.clone()) + * lhs.clone() + * (bits_next.clone() - bits.clone() - one.clone()); + let if_copy_flag_next_is_0_and_rhs_next_is_nonzero_then_bits_increases_by_1 = + (copy_flag_next.clone() - one.clone()) + * rhs.clone() + * (bits_next - bits.clone() - one.clone()); + let lhs_lsb = two.clone() * lhs_next.clone() - lhs.clone(); + let rhs_lsb = two.clone() * rhs_next - rhs.clone(); + let lhs_lsb_is_a_bit = lhs_lsb.clone() * (lhs_lsb.clone() - one.clone()); + let rhs_lsb_is_a_bit = rhs_lsb.clone() * (rhs_lsb.clone() - one.clone()); + let if_copy_flag_next_is_0_and_lt_next_is_0_then_lt_is_0 = (copy_flag_next.clone() + - one.clone()) + * (lt_next.clone() - one.clone()) + * (lt_next.clone() - two.clone()) + * lt.clone(); + let if_copy_flag_next_is_0_and_lt_next_is_1_then_lt_is_1 = (copy_flag_next.clone() + - one.clone()) + * lt_next.clone() + * (lt_next.clone() - two.clone()) + * (lt.clone() - one.clone()); + let if_copy_flag_next_is_0_and_lt_next_is_2_and_lt_known_then_lt_is_1 = + (copy_flag_next.clone() - one.clone()) + * lt_next.clone() + * (lt_next.clone() - one.clone()) + * (lhs_lsb.clone() - one.clone()) + * rhs_lsb.clone() + * (lt.clone() - one.clone()); + let if_copy_flag_next_is_0_and_lt_next_is_2_and_gte_known_then_lt_is_0 = + (copy_flag_next.clone() - one.clone()) + * lt_next.clone() + * (lt_next.clone() - one.clone()) + * lhs_lsb.clone() + * (rhs_lsb.clone() - one.clone()) + * lt.clone(); + let lt_result_unclear = (copy_flag_next.clone() - one.clone()) + * lt_next.clone() + * (lt_next - one.clone()) + * (one.clone() + - lhs_lsb.clone() + - rhs_lsb.clone() + - two.clone() * lhs_lsb.clone() * rhs_lsb.clone()); + let if_copy_flag_next_is_0_and_lt_next_is_2_and_lsbs_equal_then_lt_is_2 = lt_result_unclear + .clone() + * (copy_flag.clone() - one.clone()) + * (lt.clone() - two.clone()); + let if_copy_flag_next_is_0_and_lt_next_is_2_and_lsbs_equal_in_top_row_then_lt_is_0 = + lt_result_unclear * copy_flag.clone() * lt; + let if_copy_flag_next_is_0_then_and_updates_correctly = (copy_flag_next.clone() + - one.clone()) + * (and - two.clone() * and_next - lhs_lsb.clone() * rhs_lsb.clone()); + let if_copy_flag_next_is_0_then_xor_updates_correctly = (copy_flag_next.clone() + - one.clone()) + * (xor - two.clone() * xor_next - lhs_lsb.clone() - rhs_lsb.clone() + + two * lhs_lsb * rhs_lsb.clone()); + let if_copy_flag_next_is_0_and_lhs_next_is_0_and_lhs_is_nonzero_then_log2floor_is_bits = + (copy_flag_next.clone() - one.clone()) + * (one.clone() - lhs_next.clone() * lhs_inv_next) + * lhs.clone() + * (log2floor.clone() - bits); + let if_copy_flag_next_is_0_and_lhs_next_is_nonzero_then_log2floor_stays = + (copy_flag_next.clone() - one.clone()) * lhs_next * (log2floor_next - log2floor); + let if_copy_flag_next_is_0_and_rhs_lsb_is_0_then_pow_squares = (copy_flag_next.clone() + - one.clone()) + * (rhs_lsb.clone() - one.clone()) + * (pow.clone() - pow_next.clone() * pow_next.clone()); + let if_copy_flag_next_is_0_and_rhs_lsb_is_0_then_pow_squares_times_lhs_copy = + (copy_flag_next.clone() - one.clone()) + * rhs_lsb + * (pow - pow_next.clone() * pow_next * lhs_copy); + + let compressed_row = challenge(ProcessorPermIndeterminate) + - challenge(LhsWeight) * lhs + - challenge(RhsWeight) * rhs + - challenge(CIWeight) * ci + - challenge(ResultWeight) * result; + let if_copy_flag_next_is_0_then_running_product_stays = + (copy_flag_next - one.clone()) * (rp_next.clone() - rp.clone()); + let if_copy_flag_next_is_1_then_running_product_absorbs_row = + copy_flag * (rp_next - rp * compressed_row); + + [ + if_copy_flag_next_is_1_then_lhs_is_0, + if_copy_flag_next_is_1_then_rhs_is_0, + if_copy_flag_next_is_0_then_ci_stays, + if_copy_flag_next_is_0_then_lhs_copy_stays, + if_copy_flag_next_is_0_and_lhs_next_is_nonzero_then_bits_increases_by_1, + if_copy_flag_next_is_0_and_rhs_next_is_nonzero_then_bits_increases_by_1, + lhs_lsb_is_a_bit, + rhs_lsb_is_a_bit, + if_copy_flag_next_is_0_and_lt_next_is_0_then_lt_is_0, + if_copy_flag_next_is_0_and_lt_next_is_1_then_lt_is_1, + if_copy_flag_next_is_0_and_lt_next_is_2_and_lt_known_then_lt_is_1, + if_copy_flag_next_is_0_and_lt_next_is_2_and_gte_known_then_lt_is_0, + if_copy_flag_next_is_0_and_lt_next_is_2_and_lsbs_equal_then_lt_is_2, + if_copy_flag_next_is_0_and_lt_next_is_2_and_lsbs_equal_in_top_row_then_lt_is_0, + if_copy_flag_next_is_0_then_and_updates_correctly, + if_copy_flag_next_is_0_then_xor_updates_correctly, + if_copy_flag_next_is_0_and_lhs_next_is_0_and_lhs_is_nonzero_then_log2floor_is_bits, + if_copy_flag_next_is_0_and_lhs_next_is_nonzero_then_log2floor_stays, + if_copy_flag_next_is_0_and_rhs_lsb_is_0_then_pow_squares, + if_copy_flag_next_is_0_and_rhs_lsb_is_0_then_pow_squares_times_lhs_copy, + if_copy_flag_next_is_0_then_running_product_stays, + if_copy_flag_next_is_1_then_running_product_absorbs_row, + ] + .map(|circuit| circuit.consume()) + .to_vec() } pub fn ext_terminal_constraints_as_circuits() -> Vec< From 87b09fde415545f0cac212792d80db087472af53 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sat, 31 Dec 2022 23:02:26 +0100 Subject: [PATCH 19/53] u32 table: add terminal constraints --- triton-vm/src/table/u32_table.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 755725ec..ec301b1a 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -403,7 +403,12 @@ impl ExtU32Table { SingleRowIndicator, >, > { - todo!() + let circuit_builder = ConstraintCircuitBuilder::new(); + + let lhs = circuit_builder.input(BaseRow(LHS.master_base_table_index())); + let rhs = circuit_builder.input(BaseRow(RHS.master_base_table_index())); + + [lhs, rhs].map(|circuit| circuit.consume()).to_vec() } } From 6b0f7e28857b5a0bfc23a58772af3f7a7db71841 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 1 Jan 2023 21:49:56 +0100 Subject: [PATCH 20/53] padded_height takes U32 Table into account --- triton-vm/src/table/master_table.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/triton-vm/src/table/master_table.rs b/triton-vm/src/table/master_table.rs index 728e991c..38b2ec80 100644 --- a/triton-vm/src/table/master_table.rs +++ b/triton-vm/src/table/master_table.rs @@ -10,6 +10,7 @@ use ndarray::ArrayView2; use ndarray::ArrayViewMut2; use ndarray::Zip; use num_traits::One; +use num_traits::Zero; use rand::distributions::Standard; use rand::prelude::Distribution; use rand::random; @@ -23,6 +24,7 @@ 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::log_2_floor; use twenty_first::shared_math::other::roundup_npo2; use twenty_first::shared_math::traits::FiniteField; use twenty_first::shared_math::traits::Inverse; @@ -313,7 +315,19 @@ 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); + let mut u32_table_len = 0; + for (_, lhs, rhs) in aet.u32_entries.iter() { + let lhs_contribution = match lhs.is_zero() { + true => 1, + false => log_2_floor(lhs.value() as u128) + 2, + }; + let rhs_contribution = match rhs.is_zero() { + true => 1, + false => log_2_floor(rhs.value() as u128) + 2, + }; + u32_table_len += max(lhs_contribution, rhs_contribution) as usize; + } + let max_height = max(max(instruction_table_len, hash_table_len), u32_table_len); roundup_npo2(max_height as u64) as usize } From 52be945ffee8f8dd4d26a94131e40a890d807bf1 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 1 Jan 2023 22:01:02 +0100 Subject: [PATCH 21/53] instruction div produces two u32 sections in the AET --- triton-vm/src/state.rs | 27 +++++++++++++++++++-------- triton-vm/src/vm.rs | 4 ++-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 45f6a6fe..3e770f26 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -84,7 +84,7 @@ pub enum VMOutput { XlixTrace(Box<[[BFieldElement; STATE_SIZE]; 1 + NUM_ROUNDS]>), /// Executed u32 instruction as well as its left-hand side and right-hand side - U32TableEntry(Instruction, BFieldElement, BFieldElement), + U32TableEntries(Vec<(Instruction, BFieldElement, BFieldElement)>), } #[allow(clippy::needless_range_loop)] @@ -371,7 +371,8 @@ impl<'pgm> VMState<'pgm> { self.op_stack.push(lo); self.op_stack.push(hi); self.instruction_pointer += 1; - vm_output = Some(VMOutput::U32TableEntry(Instruction::Split, hi, lo)); + let u32_table_entry = (Instruction::Halt, hi, lo); + vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Lt => { @@ -383,7 +384,8 @@ impl<'pgm> VMState<'pgm> { }; self.op_stack.push(lt); self.instruction_pointer += 1; - vm_output = Some(VMOutput::U32TableEntry(Instruction::Lt, lhs, rhs)); + let u32_table_entry = (Instruction::Lt, lhs, rhs); + vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } And => { @@ -392,7 +394,8 @@ impl<'pgm> VMState<'pgm> { let and = BFieldElement::new(lhs.value() & rhs.value()); self.op_stack.push(and); self.instruction_pointer += 1; - vm_output = Some(VMOutput::U32TableEntry(Instruction::And, lhs, rhs)); + let u32_table_entry = (Instruction::And, lhs, rhs); + vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Xor => { @@ -401,7 +404,8 @@ impl<'pgm> VMState<'pgm> { let xor = BFieldElement::new(lhs.value() ^ rhs.value()); self.op_stack.push(xor); self.instruction_pointer += 1; - vm_output = Some(VMOutput::U32TableEntry(Instruction::Xor, lhs, rhs)); + let u32_table_entry = (Instruction::Xor, lhs, rhs); + vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Log2Floor => { @@ -410,7 +414,8 @@ impl<'pgm> VMState<'pgm> { self.op_stack.push(l2f); self.instruction_pointer += 1; let rhs = BFieldElement::zero(); - vm_output = Some(VMOutput::U32TableEntry(Instruction::Log2Floor, lhs, rhs)); + let u32_table_entry = (Instruction::Log2Floor, lhs, rhs); + vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Pow => { @@ -419,7 +424,8 @@ impl<'pgm> VMState<'pgm> { let pow = BFieldElement::new(lhs.value().pow(rhs.value() as u32)); self.op_stack.push(pow); self.instruction_pointer += 1; - vm_output = Some(VMOutput::U32TableEntry(Instruction::Pow, lhs, rhs)); + let u32_table_entry = (Instruction::Pow, lhs, rhs); + vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Div => { @@ -433,7 +439,12 @@ impl<'pgm> VMState<'pgm> { self.op_stack.push(quot); self.op_stack.push(rem); self.instruction_pointer += 1; - vm_output = Some(VMOutput::U32TableEntry(Instruction::Div, numer, denom)); + let u32_table_entry_0 = (Instruction::Div, rem, denom); + let u32_table_entry_1 = (Instruction::Halt, numer, quot); + vm_output = Some(VMOutput::U32TableEntries(vec![ + u32_table_entry_0, + u32_table_entry_1, + ])); } XxAdd => { diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 4d60a56b..3389f9ce 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -50,8 +50,8 @@ pub fn simulate( match vm_output { Some(VMOutput::XlixTrace(hash_trace)) => aet.append_hash_trace(*hash_trace), - Some(VMOutput::U32TableEntry(instr, lhs, rhs)) => { - aet.u32_entries.push((instr, lhs, rhs)) + Some(VMOutput::U32TableEntries(mut entries)) => { + aet.u32_entries.append(&mut entries); } Some(VMOutput::WriteOutputSymbol(written_word)) => stdout.push(written_word), None => (), From 6e8ddaee9bb25bbbd6996a3683d6f8bdbe92aa60 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 1 Jan 2023 22:47:02 +0100 Subject: [PATCH 22/53] fill trace of U32 Table --- triton-vm/src/table/u32_table.rs | 108 +++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 5 deletions(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index ec301b1a..2a4f38cd 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -1,11 +1,16 @@ +use ndarray::s; +use ndarray::Array2; use ndarray::ArrayView2; use ndarray::ArrayViewMut2; +use num_traits::One; +use num_traits::Zero; use std::ops::Mul; use strum::EnumCount; 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 triton_opcodes::instruction::Instruction; @@ -23,6 +28,7 @@ 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::MasterBaseTableColumn; use crate::table::table_column::MasterExtTableColumn; use crate::table::table_column::U32BaseTableColumn; @@ -413,11 +419,103 @@ impl ExtU32Table { } impl U32Table { - pub fn fill_trace( - _u32_table: &mut ArrayViewMut2, - _aet: &AlgebraicExecutionTrace, - ) { - todo!() + pub fn fill_trace(u32_table: &mut ArrayViewMut2, aet: &AlgebraicExecutionTrace) { + let mut next_section_start = 0; + for &(instruction, lhs, rhs) in aet.u32_entries.iter() { + let mut first_row = Array2::zeros([1, BASE_WIDTH]); + first_row[[0, CopyFlag.base_table_index()]] = BFieldElement::one(); + first_row[[0, Bits.base_table_index()]] = BFieldElement::zero(); + first_row[[0, BitsMinus33Inv.base_table_index()]] = (-BFieldElement::new(33)).inverse(); + first_row[[0, CI.base_table_index()]] = instruction.opcode_b(); + first_row[[0, LHS.base_table_index()]] = lhs; + first_row[[0, RHS.base_table_index()]] = rhs; + first_row[[0, LhsCopy.base_table_index()]] = lhs; + let u32_section = Self::u32_section_next_row(first_row); + + let next_section_end = next_section_start + u32_section.nrows(); + u32_table + .slice_mut(s![next_section_start..next_section_end, ..]) + .assign(&u32_section); + next_section_start = next_section_end; + } + } + + fn u32_section_next_row(mut section: Array2) -> Array2 { + let zero = BFieldElement::zero(); + let one = BFieldElement::one(); + let two = BFieldElement::new(2); + let thirty_three = BFieldElement::new(33); + + let row_idx = section.nrows() - 1; + if section[[row_idx, LHS.base_table_index()]].is_zero() + && section[[row_idx, RHS.base_table_index()]].is_zero() + { + section[[row_idx, LT.base_table_index()]] = two; + section[[row_idx, AND.base_table_index()]] = zero; + section[[row_idx, XOR.base_table_index()]] = zero; + section[[row_idx, Log2Floor.base_table_index()]] = -one; + section[[row_idx, Pow.base_table_index()]] = one; + return section; + } + + let lhs_lsb = BFieldElement::new(section[[row_idx, LHS.base_table_index()]].value() % 2); + let rhs_lsb = BFieldElement::new(section[[row_idx, RHS.base_table_index()]].value() % 2); + let mut next_row = section.row(row_idx).to_owned(); + next_row[CopyFlag.base_table_index()] = zero; + next_row[Bits.base_table_index()] += one; + next_row[BitsMinus33Inv.base_table_index()] = + (next_row[Bits.base_table_index()] - thirty_three).inverse(); + next_row[LHS.base_table_index()] = + (section[[row_idx, LHS.base_table_index()]] - lhs_lsb) / two; + next_row[RHS.base_table_index()] = + (section[[row_idx, RHS.base_table_index()]] - rhs_lsb) / two; + + section.push_row(next_row.view()).unwrap(); + section = Self::u32_section_next_row(section); + let (mut row, next_row) = section.multi_slice_mut((s![row_idx, ..], s![row_idx + 1, ..])); + + row[LT.base_table_index()] = if next_row[LT.base_table_index()].is_zero() { + zero + } else if next_row[LT.base_table_index()].is_one() { + one + } else { + // LT == 2 + if lhs_lsb.is_zero() && rhs_lsb.is_one() { + one + } else if lhs_lsb.is_one() && rhs_lsb.is_zero() { + zero + } else { + // lhs_lsb == rhs_lsb + if row[CopyFlag.base_table_index()].is_zero() { + two + } else { + zero + } + } + }; + + row[AND.base_table_index()] = two * next_row[AND.base_table_index()] + lhs_lsb * rhs_lsb; + row[XOR.base_table_index()] = + two * next_row[XOR.base_table_index()] + lhs_lsb + rhs_lsb - two * lhs_lsb * rhs_lsb; + + row[Log2Floor.base_table_index()] = if row[LHS.base_table_index()].is_zero() { + -one + } else if !next_row[LHS.base_table_index()].is_zero() { + next_row[Log2Floor.base_table_index()] + } else { + // next_row[LHS.base_table_index()].is_zero() && !row[LHS.base_table_index()].is_zero() + row[Bits.base_table_index()] + }; + + row[Pow.base_table_index()] = if row[RHS.base_table_index()].is_zero() { + next_row[Pow.base_table_index()] * next_row[Pow.base_table_index()] + } else { + next_row[Pow.base_table_index()] + * next_row[Pow.base_table_index()] + * next_row[LhsCopy.base_table_index()] + }; + + section } pub fn pad_trace(_u32_table: &mut ArrayViewMut2, _u32_table_len: usize) { From 48151108641cca0efa3a4e6e3a1b0dd606b0d0b0 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 1 Jan 2023 22:53:15 +0100 Subject: [PATCH 23/53] pad trace of U32 Table --- triton-vm/src/table/u32_table.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 2a4f38cd..5cc5bed2 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -1,10 +1,14 @@ +use std::ops::Mul; + +use ndarray::parallel::prelude::*; use ndarray::s; +use ndarray::Array1; use ndarray::Array2; use ndarray::ArrayView2; use ndarray::ArrayViewMut2; +use ndarray::Axis; use num_traits::One; use num_traits::Zero; -use std::ops::Mul; use strum::EnumCount; use strum_macros::Display; use strum_macros::EnumCount as EnumCountMacro; @@ -518,8 +522,18 @@ impl U32Table { section } - pub fn pad_trace(_u32_table: &mut ArrayViewMut2, _u32_table_len: usize) { - todo!() + pub fn pad_trace(u32_table: &mut ArrayViewMut2, u32_table_len: usize) { + let mut padding_row = Array1::zeros([BASE_WIDTH]); + padding_row[[BitsMinus33Inv.base_table_index()]] = (-BFieldElement::new(33)).inverse(); + padding_row[[LT.base_table_index()]] = BFieldElement::new(2); + padding_row[[Log2Floor.base_table_index()]] = -BFieldElement::one(); + padding_row[[Pow.base_table_index()]] = BFieldElement::one(); + + u32_table + .slice_mut(s![u32_table_len.., ..]) + .axis_iter_mut(Axis(0)) + .into_par_iter() + .for_each(|mut row| row.assign(&padding_row)); } pub fn extend( From 09a8ceb2e17ac15cdafdacf8c894bc1a2ebbefe4 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 1 Jan 2023 23:11:30 +0100 Subject: [PATCH 24/53] extend U32 Table --- triton-vm/src/table/u32_table.rs | 38 +++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 5cc5bed2..d878c150 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -33,6 +33,7 @@ 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::U32BaseTableColumn; @@ -538,13 +539,44 @@ impl U32Table { pub fn extend( base_table: ArrayView2, - ext_table: ArrayViewMut2, - _challenges: &U32TableChallenges, + mut ext_table: ArrayViewMut2, + challenges: &U32TableChallenges, ) { assert_eq!(BASE_WIDTH, base_table.ncols()); assert_eq!(EXT_WIDTH, ext_table.ncols()); assert_eq!(base_table.nrows(), ext_table.nrows()); - todo!() + + let mut running_product = PermArg::default_initial(); + for row_idx in 0..base_table.nrows() { + let current_row = base_table.row(row_idx); + if current_row[CopyFlag.base_table_index()].is_one() { + let ci_opcode = current_row[CI.base_table_index()].value(); + let result = if ci_opcode == Instruction::Lt.opcode() as u64 { + current_row[LT.base_table_index()] + } else if ci_opcode == Instruction::And.opcode() as u64 { + current_row[AND.base_table_index()] + } else if ci_opcode == Instruction::Xor.opcode() as u64 { + current_row[XOR.base_table_index()] + } else if ci_opcode == Instruction::Log2Floor.opcode() as u64 { + current_row[Log2Floor.base_table_index()] + } else if ci_opcode == Instruction::Pow.opcode() as u64 { + current_row[Pow.base_table_index()] + } else if ci_opcode == Instruction::Div.opcode() as u64 { + current_row[LT.base_table_index()] + } else { + BFieldElement::zero() + }; + + let compressed_row = challenges.ci_weight * current_row[CI.base_table_index()] + + challenges.lhs_weight * current_row[LHS.base_table_index()] + + challenges.rhs_weight * current_row[RHS.base_table_index()] + + challenges.result_weight * result; + running_product *= challenges.processor_perm_indeterminate - compressed_row; + } + + let mut extension_row = ext_table.row_mut(row_idx); + extension_row[ProcessorPermArg.ext_table_index()] = running_product; + } } } From 955f9932afa8c63dbec1baf1c0a6045f222bf201 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 1 Jan 2023 23:22:37 +0100 Subject: [PATCH 25/53] use correct number of columns in master tables --- triton-vm/src/table/master_table.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/triton-vm/src/table/master_table.rs b/triton-vm/src/table/master_table.rs index 38b2ec80..0fc46389 100644 --- a/triton-vm/src/table/master_table.rs +++ b/triton-vm/src/table/master_table.rs @@ -70,14 +70,16 @@ pub const NUM_BASE_COLUMNS: usize = program_table::BASE_WIDTH + op_stack_table::BASE_WIDTH + ram_table::BASE_WIDTH + jump_stack_table::BASE_WIDTH - + hash_table::BASE_WIDTH; + + hash_table::BASE_WIDTH + + u32_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; + + hash_table::EXT_WIDTH + + u32_table::EXT_WIDTH; pub const NUM_COLUMNS: usize = NUM_BASE_COLUMNS + NUM_EXT_COLUMNS; pub const PROGRAM_TABLE_START: usize = 0; From 44832429c0173b69a37b6644876eafec1b5ac03f Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 8 Jan 2023 11:10:48 +0100 Subject: [PATCH 26/53] update test for checking number of randomizer polynomials --- triton-vm/src/table/master_table.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/triton-vm/src/table/master_table.rs b/triton-vm/src/table/master_table.rs index 0fc46389..ce4d2795 100644 --- a/triton-vm/src/table/master_table.rs +++ b/triton-vm/src/table/master_table.rs @@ -1486,7 +1486,7 @@ mod master_table_tests { 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::EXT_U32_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; @@ -1591,7 +1591,7 @@ mod master_table_tests { stark.parameters.num_randomizer_polynomials, master_ext_table .master_ext_matrix - .slice(s![.., EXT_HASH_TABLE_END..]) + .slice(s![.., EXT_U32_TABLE_END..]) .ncols() ); } From 63a4ce3ccc53cebcb2ffaccdd80c8f5658353b5c Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 8 Jan 2023 11:15:25 +0100 Subject: [PATCH 27/53] improve debug output for failing constraints: print table & index within --- triton-vm/src/stark.rs | 155 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index e152f767..585ef52e 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -1642,11 +1642,14 @@ pub(crate) mod triton_stark_tests { &challenges, ); let num_initial_constraints = evaluated_initial_constraints.len(); + assert_eq!(num_all_initial_quotients(), num_initial_constraints); for (constraint_idx, ebc) in evaluated_initial_constraints.into_iter().enumerate() { + let (table_idx, table_name) = initial_constraint_table_idx_and_name(constraint_idx); assert_eq!( zero, ebc, "Failed initial constraint with global index {constraint_idx}. \ - Total number of initial constraints: {num_initial_constraints}.", + Total number of initial constraints: {num_initial_constraints}. \ + Table: {table_name}. Index within table: {table_idx}", ); } @@ -1657,11 +1660,15 @@ pub(crate) mod triton_stark_tests { let evaluated_consistency_constraints = evaluate_all_consistency_constraints(base_row, ext_row, &challenges); let num_consistency_constraints = evaluated_consistency_constraints.len(); + assert_eq!(num_all_consistency_quotients(), num_consistency_constraints); for (constraint_idx, ecc) in evaluated_consistency_constraints.into_iter().enumerate() { + let (table_idx, table_name) = + consistency_constraint_table_idx_and_name(constraint_idx); assert_eq!( zero, ecc, "Failed consistency constraint with global index {constraint_idx}. \ Total number of consistency constraints: {num_consistency_constraints}. \ + Table: {table_name}. Index within table: {table_idx} \ Row index: {row_idx}. \ Total rows: {num_rows}", ); @@ -1681,6 +1688,7 @@ pub(crate) mod triton_stark_tests { &challenges, ); let num_transition_constraints = evaluated_transition_constraints.len(); + assert_eq!(num_all_transition_quotients(), num_transition_constraints); for (constraint_idx, etc) in evaluated_transition_constraints.into_iter().enumerate() { if zero != etc { let pi_idx = @@ -1697,9 +1705,12 @@ pub(crate) mod triton_stark_tests { Ok(instr) => format!("{instr:?}"), Err(_) => "not an instruction".to_string(), }; + let (table_idx, table_name) = + transition_constraint_table_idx_and_name(constraint_idx); panic!( "Failed transition constraint with global index {constraint_idx}. \ Total number of transition constraints: {num_transition_constraints}. \ + Table: {table_name}. Index within table: {table_idx} \ Row index: {row_idx}. \ Total rows: {num_rows}\n\ Previous Instruction: {previous_instruction:?} – opcode: {pi}\n\ @@ -1716,15 +1727,155 @@ pub(crate) mod triton_stark_tests { &challenges, ); let num_terminal_constraints = evaluated_terminal_constraints.len(); + assert_eq!(num_all_terminal_quotients(), num_terminal_constraints); for (constraint_idx, etermc) in evaluated_terminal_constraints.into_iter().enumerate() { + let (table_idx, table_name) = terminal_constraint_table_idx_and_name(constraint_idx); assert_eq!( zero, etermc, "Failed terminal constraint with global index {constraint_idx}. \ - Total number of terminal constraints: {num_terminal_constraints}.", + Total number of terminal constraints: {num_terminal_constraints}. \ + Table: {table_name}. Index within table: {table_idx}", ); } } + /// Given the global index of some initial constraint, returns 1) the index within the specific + /// table for that constraint, and 2) the name of that table. + fn initial_constraint_table_idx_and_name(constraint_idx: usize) -> (usize, &'static str) { + let program_start = 0; + let program_end = program_start + ExtProgramTable::num_initial_quotients(); + let instruct_start = program_end; + let instruct_end = instruct_start + ExtInstructionTable::num_initial_quotients(); + let processor_start = instruct_end; + let processor_end = processor_start + ExtProcessorTable::num_initial_quotients(); + let op_stack_start = processor_end; + let op_stack_end = op_stack_start + ExtOpStackTable::num_initial_quotients(); + let ram_start = op_stack_end; + let ram_end = ram_start + ExtRamTable::num_initial_quotients(); + let jump_stack_start = ram_end; + let jump_stack_end = jump_stack_start + ExtJumpStackTable::num_initial_quotients(); + let hash_start = jump_stack_end; + let hash_end = hash_start + ExtHashTable::num_initial_quotients(); + let u32_start = hash_end; + let u32_end = u32_start + ExtU32Table::num_initial_quotients(); + assert_eq!(num_all_initial_quotients(), u32_end); + match constraint_idx { + i if program_start <= i && i < program_end => (i - program_start, "Program"), + i if instruct_start <= i && i < instruct_end => (i - instruct_start, "Instruction"), + i if processor_start <= i && i < processor_end => (i - processor_start, "Processor"), + i if op_stack_start <= i && i < op_stack_end => (i - op_stack_start, "OpStack"), + i if ram_start <= i && i < ram_end => (i - ram_start, "Ram"), + i if jump_stack_start <= i && i < jump_stack_end => (i - jump_stack_start, "JumpStack"), + i if hash_start <= i && i < hash_end => (i - hash_start, "Hash"), + i if u32_start <= i && i < u32_end => (i - u32_start, "U32"), + _ => (0, "Unknown"), + } + } + + /// Given the global index of some consistency constraint, returns 1) the index within the + /// specific table for that constraint, and 2) the name of that table. + fn consistency_constraint_table_idx_and_name(constraint_idx: usize) -> (usize, &'static str) { + let program_start = 0; + let program_end = program_start + ExtProgramTable::num_consistency_quotients(); + let instruct_start = program_end; + let instruct_end = instruct_start + ExtInstructionTable::num_consistency_quotients(); + let processor_start = instruct_end; + let processor_end = processor_start + ExtProcessorTable::num_consistency_quotients(); + let op_stack_start = processor_end; + let op_stack_end = op_stack_start + ExtOpStackTable::num_consistency_quotients(); + let ram_start = op_stack_end; + let ram_end = ram_start + ExtRamTable::num_consistency_quotients(); + let jump_stack_start = ram_end; + let jump_stack_end = jump_stack_start + ExtJumpStackTable::num_consistency_quotients(); + let hash_start = jump_stack_end; + let hash_end = hash_start + ExtHashTable::num_consistency_quotients(); + let u32_start = hash_end; + let u32_end = u32_start + ExtU32Table::num_consistency_quotients(); + assert_eq!(num_all_consistency_quotients(), u32_end); + match constraint_idx { + i if program_start <= i && i < program_end => (i - program_start, "Program"), + i if instruct_start <= i && i < instruct_end => (i - instruct_start, "Instruction"), + i if processor_start <= i && i < processor_end => (i - processor_start, "Processor"), + i if op_stack_start <= i && i < op_stack_end => (i - op_stack_start, "OpStack"), + i if ram_start <= i && i < ram_end => (i - ram_start, "Ram"), + i if jump_stack_start <= i && i < jump_stack_end => (i - jump_stack_start, "JumpStack"), + i if hash_start <= i && i < hash_end => (i - hash_start, "Hash"), + i if u32_start <= i && i < u32_end => (i - u32_start, "U32"), + _ => (0, "Unknown"), + } + } + + /// Given the global index of some transition constraint, returns 1) the index within the + /// specific table for that constraint, and 2) the name of that table. + fn transition_constraint_table_idx_and_name(constraint_idx: usize) -> (usize, &'static str) { + let program_start = 0; + let program_end = program_start + ExtProgramTable::num_transition_quotients(); + let instruct_start = program_end; + let instruct_end = instruct_start + ExtInstructionTable::num_transition_quotients(); + let processor_start = instruct_end; + let processor_end = processor_start + ExtProcessorTable::num_transition_quotients(); + let op_stack_start = processor_end; + let op_stack_end = op_stack_start + ExtOpStackTable::num_transition_quotients(); + let ram_start = op_stack_end; + let ram_end = ram_start + ExtRamTable::num_transition_quotients(); + let jump_stack_start = ram_end; + let jump_stack_end = jump_stack_start + ExtJumpStackTable::num_transition_quotients(); + let hash_start = jump_stack_end; + let hash_end = hash_start + ExtHashTable::num_transition_quotients(); + let u32_start = hash_end; + let u32_end = u32_start + ExtU32Table::num_transition_quotients(); + assert_eq!(num_all_transition_quotients(), u32_end); + match constraint_idx { + i if program_start <= i && i < program_end => (i - program_start, "Program"), + i if instruct_start <= i && i < instruct_end => (i - instruct_start, "Instruction"), + i if processor_start <= i && i < processor_end => (i - processor_start, "Processor"), + i if op_stack_start <= i && i < op_stack_end => (i - op_stack_start, "OpStack"), + i if ram_start <= i && i < ram_end => (i - ram_start, "Ram"), + i if jump_stack_start <= i && i < jump_stack_end => (i - jump_stack_start, "JumpStack"), + i if hash_start <= i && i < hash_end => (i - hash_start, "Hash"), + i if u32_start <= i && i < u32_end => (i - u32_start, "U32"), + _ => (0, "Unknown"), + } + } + + /// Given the global index of some terminal constraint, returns 1) the index within the + /// specific table for that constraint, and 2) the name of that table. + fn terminal_constraint_table_idx_and_name(constraint_idx: usize) -> (usize, &'static str) { + let program_start = 0; + let program_end = program_start + ExtProgramTable::num_terminal_quotients(); + let instruct_start = program_end; + let instruct_end = instruct_start + ExtInstructionTable::num_terminal_quotients(); + let processor_start = instruct_end; + let processor_end = processor_start + ExtProcessorTable::num_terminal_quotients(); + let op_stack_start = processor_end; + let op_stack_end = op_stack_start + ExtOpStackTable::num_terminal_quotients(); + let ram_start = op_stack_end; + let ram_end = ram_start + ExtRamTable::num_terminal_quotients(); + let jump_stack_start = ram_end; + let jump_stack_end = jump_stack_start + ExtJumpStackTable::num_terminal_quotients(); + let hash_start = jump_stack_end; + let hash_end = hash_start + ExtHashTable::num_terminal_quotients(); + let u32_start = hash_end; + let u32_end = u32_start + ExtU32Table::num_terminal_quotients(); + let cross_table_start = u32_end; + let cross_table_end = cross_table_start + GrandCrossTableArg::num_terminal_quotients(); + assert_eq!(num_all_terminal_quotients(), cross_table_end); + match constraint_idx { + i if program_start <= i && i < program_end => (i - program_start, "Program"), + i if instruct_start <= i && i < instruct_end => (i - instruct_start, "Instruction"), + i if processor_start <= i && i < processor_end => (i - processor_start, "Processor"), + i if op_stack_start <= i && i < op_stack_end => (i - op_stack_start, "OpStack"), + i if ram_start <= i && i < ram_end => (i - ram_start, "Ram"), + i if jump_stack_start <= i && i < jump_stack_end => (i - jump_stack_start, "JumpStack"), + i if hash_start <= i && i < hash_end => (i - hash_start, "Hash"), + i if u32_start <= i && i < u32_end => (i - u32_start, "U32"), + i if cross_table_start <= i && i < cross_table_end => { + (i - cross_table_start, "GrandCrossTableArgument") + } + _ => (0, "Unknown"), + } + } + #[test] fn triton_prove_verify_simple_program_test() { let code_with_input = test_hash_nop_nop_lt(); From e44901870a6a04b9036164e51eef87eddd4dca49 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Sun, 8 Jan 2023 11:51:42 +0100 Subject: [PATCH 28/53] use correct length for U32 Table when adding padding rows --- triton-vm/src/table/master_table.rs | 31 ++++++++++++++++------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/triton-vm/src/table/master_table.rs b/triton-vm/src/table/master_table.rs index ce4d2795..3c0651fd 100644 --- a/triton-vm/src/table/master_table.rs +++ b/triton-vm/src/table/master_table.rs @@ -10,7 +10,6 @@ use ndarray::ArrayView2; use ndarray::ArrayViewMut2; use ndarray::Zip; use num_traits::One; -use num_traits::Zero; use rand::distributions::Standard; use rand::prelude::Distribution; use rand::random; @@ -233,6 +232,7 @@ pub struct MasterBaseTable { pub program_len: usize, pub main_execution_len: usize, pub hash_coprocessor_execution_len: usize, + pub u32_coprocesor_execution_len: usize, pub randomized_padded_trace_len: usize, @@ -317,22 +317,22 @@ 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 mut u32_table_len = 0; - for (_, lhs, rhs) in aet.u32_entries.iter() { - let lhs_contribution = match lhs.is_zero() { - true => 1, - false => log_2_floor(lhs.value() as u128) + 2, - }; - let rhs_contribution = match rhs.is_zero() { - true => 1, - false => log_2_floor(rhs.value() as u128) + 2, - }; - u32_table_len += max(lhs_contribution, rhs_contribution) as usize; - } + let u32_table_len = Self::u32_table_length(aet); let max_height = max(max(instruction_table_len, hash_table_len), u32_table_len); roundup_npo2(max_height as u64) as usize } + fn u32_table_length(aet: &AlgebraicExecutionTrace) -> usize { + aet.u32_entries + .iter() + .map(|(_, lhs, rhs)| max(lhs.value(), rhs.value())) + .map(|bigger_value| match bigger_value == 0 { + true => 1, + false => 2 + log_2_floor(bigger_value as u128) as usize, + }) + .sum() + } + pub fn new( aet: AlgebraicExecutionTrace, program: &[BFieldElement], @@ -346,6 +346,7 @@ impl MasterBaseTable { let program_len = program.len(); let main_execution_len = aet.processor_matrix.nrows(); let hash_coprocessor_execution_len = aet.hash_matrix.nrows(); + let u32_coprocesor_execution_len = Self::u32_table_length(&aet); let num_rows = randomized_padded_trace_len; let num_columns = NUM_BASE_COLUMNS; @@ -357,6 +358,7 @@ impl MasterBaseTable { program_len, main_execution_len, hash_coprocessor_execution_len, + u32_coprocesor_execution_len, randomized_padded_trace_len, rand_trace_to_padded_trace_unit_distance: unit_distance, fri_domain, @@ -395,6 +397,7 @@ impl MasterBaseTable { pub fn pad(&mut self) { let program_len = self.program_len; let main_execution_len = self.main_execution_len; + let u32_table_len = self.u32_coprocesor_execution_len; let program_table = &mut self.table_mut(TableId::ProgramTable); ProgramTable::pad_trace(program_table, program_len); @@ -411,7 +414,7 @@ impl MasterBaseTable { let hash_table = &mut self.table_mut(TableId::HashTable); HashTable::pad_trace(hash_table); let u32_table = &mut self.table_mut(TableId::U32Table); - U32Table::pad_trace(u32_table, main_execution_len); + U32Table::pad_trace(u32_table, u32_table_len); } pub fn to_fri_domain_table(&self) -> Self { From b28495746ffc045cdfe758c96f6c4533a145a006 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Mon, 9 Jan 2023 10:57:04 +0100 Subject: [PATCH 29/53] in the Processor Table, update the running product with the U32 Table --- triton-vm/src/table/processor_table.rs | 57 +++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index dca96d83..a7de7e6f 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -148,6 +148,7 @@ impl ProcessorTable { 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 u32_table_running_product = PermArg::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(); @@ -225,6 +226,7 @@ impl ProcessorTable { jump_stack_running_product *= challenges.jump_stack_perm_indeterminate - compressed_row_for_jump_stack_table; + // Hash Table – Hash's input from Processor to Hash Coprocessor if current_row[CI.base_table_index()] == Instruction::Hash.opcode_b() { let st_0_through_9 = [ current_row[ST0.base_table_index()], @@ -288,6 +290,43 @@ impl ProcessorTable { } } + // U32 Table + if let Some(prev_row) = previous_row { + let previously_current_instruction = prev_row[CI.base_table_index()]; + if previously_current_instruction == Instruction::Split.opcode_b() { + u32_table_running_product *= challenges.u32_table_perm_indeterminate + - current_row[ST0.base_table_index()] * challenges.u32_table_lhs_weight + - current_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight; + } + if previously_current_instruction == Instruction::Lt.opcode_b() + || previously_current_instruction == Instruction::And.opcode_b() + || previously_current_instruction == Instruction::Xor.opcode_b() + || previously_current_instruction == Instruction::Pow.opcode_b() + { + u32_table_running_product *= challenges.u32_table_perm_indeterminate + - prev_row[ST0.base_table_index()] * challenges.u32_table_lhs_weight + - prev_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight + - prev_row[CI.base_table_index()] * challenges.u32_table_ci_weight + - current_row[ST0.base_table_index()] * challenges.u32_table_result_weight; + } + if previously_current_instruction == Instruction::Log2Floor.opcode_b() { + u32_table_running_product *= challenges.u32_table_perm_indeterminate + - prev_row[ST0.base_table_index()] * challenges.u32_table_lhs_weight + - prev_row[CI.base_table_index()] * challenges.u32_table_ci_weight + - current_row[ST0.base_table_index()] * challenges.u32_table_result_weight; + } + if previously_current_instruction == Instruction::Div.opcode_b() { + u32_table_running_product *= challenges.u32_table_perm_indeterminate + - current_row[ST0.base_table_index()] * challenges.u32_table_lhs_weight + - prev_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight + - prev_row[CI.base_table_index()] * challenges.u32_table_ci_weight + - BFieldElement::one() * challenges.u32_table_result_weight; + u32_table_running_product *= challenges.u32_table_perm_indeterminate + - prev_row[ST0.base_table_index()] * challenges.u32_table_lhs_weight + - current_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight; + } + } + // Clock Jump Difference let current_clock_jump_difference = current_row[ClockJumpDifference.base_table_index()]; if !current_clock_jump_difference.is_zero() { @@ -328,6 +367,7 @@ impl ProcessorTable { extension_row[ToHashTableEvalArg.ext_table_index()] = to_hash_table_running_evaluation; extension_row[FromHashTableEvalArg.ext_table_index()] = from_hash_table_running_evaluation; + extension_row[U32TablePermArg.ext_table_index()] = u32_table_running_product; extension_row[AllClockJumpDifferencesPermArg.ext_table_index()] = all_clock_jump_differences_running_product; extension_row[UniqueClockJumpDifferencesEvalArg.ext_table_index()] = @@ -741,7 +781,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(); + let previous_instruction_is_0 = 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()); @@ -841,6 +881,10 @@ impl ExtProcessorTable { let running_evaluation_from_hash_table_is_initialized_correctly = factory.running_evaluation_from_hash_table() - constant_x(EvalArg::default_initial()); + // u32 table + let running_product_for_u32_table_is_initialized_correctly = + factory.running_product_u32_table() - constant_x(PermArg::default_initial()); + [ clk_is_0, ip_is_0, @@ -867,7 +911,7 @@ impl ExtProcessorTable { osv_is_0, ramv_is_0, ramp_is_0, - previous_instruction, + previous_instruction_is_0, rer_starts_correctly, reu_starts_correctly, rpm_starts_correctly, @@ -879,6 +923,7 @@ impl ExtProcessorTable { running_product_for_jump_stack_table_is_initialized_correctly, running_evaluation_to_hash_table_is_initialized_correctly, running_evaluation_from_hash_table_is_initialized_correctly, + running_product_for_u32_table_is_initialized_correctly, ] .map(|circuit| circuit.consume()) .to_vec() @@ -1633,6 +1678,14 @@ impl SingleRowConstraints { > { self.ext_row_variables[FromHashTableEvalArg.master_ext_table_index()].clone() } + pub fn running_product_u32_table( + &self, + ) -> ConstraintCircuitMonad< + ProcessorTableChallenges, + SingleRowIndicator, + > { + self.ext_row_variables[U32TablePermArg.master_ext_table_index()].clone() + } } #[derive(Debug, Clone)] From b9411b787e8ff8f294cac299d08d613b0fe219b6 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Mon, 9 Jan 2023 11:09:29 +0100 Subject: [PATCH 30/53] remove instruction test snippets containing deleted pseudo instructions --- triton-vm/src/stark.rs | 1 + triton-vm/src/vm.rs | 58 ------------------------------------------ 2 files changed, 1 insertion(+), 58 deletions(-) diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index 585ef52e..c654b576 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -1129,6 +1129,7 @@ pub(crate) mod triton_stark_tests { code_collection.append(&mut property_based_test_programs()); for (code_idx, code_with_input) in code_collection.into_iter().enumerate() { + println!("Checking Grand Cross-Table Argument for TASM snippet {code_idx}."); let code = code_with_input.source_code; let input = code_with_input.input; let secret_input = code_with_input.secret_input.clone(); diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 3389f9ce..9d67bc16 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -552,47 +552,6 @@ pub mod triton_vm_tests { } } - pub fn test_program_for_reverse() -> SourceCodeAndInput { - SourceCodeAndInput::without_input("push 2147483648 reverse assert halt") - } - - pub fn property_based_test_program_for_reverse() -> SourceCodeAndInput { - let mut rng = ThreadRng::default(); - let st0 = rng.next_u32(); - let st0_rev = st0.reverse_bits().into(); - - let source_code = format!("push {} reverse read_io eq assert halt", st0); - - SourceCodeAndInput { - source_code, - input: vec![st0_rev], - secret_input: vec![], - } - } - - pub fn test_program_for_lte() -> SourceCodeAndInput { - SourceCodeAndInput::without_input("push 5 push 2 lte assert halt") - } - - pub fn property_based_test_program_for_lte() -> SourceCodeAndInput { - let mut rng = ThreadRng::default(); - let st1 = rng.next_u32(); - let st0 = rng.next_u32(); - let result = if st0 <= st1 { - 1_u64.into() - } else { - 0_u64.into() - }; - - let source_code = format!("push {} push {} lte read_io eq assert halt", st1, st0); - - SourceCodeAndInput { - source_code, - input: vec![result], - secret_input: vec![], - } - } - pub fn test_program_for_div() -> SourceCodeAndInput { SourceCodeAndInput::without_input("push 2 push 3 div assert assert halt") } @@ -729,18 +688,6 @@ pub mod triton_vm_tests { ) } - pub fn test_program_for_split_assert() -> SourceCodeAndInput { - SourceCodeAndInput::without_input( - "push -2 split_assert push 4294967294 eq assert push 4294967295 eq assert \ - push -1 split_assert push 4294967295 eq assert push 0 eq assert \ - push 0 split_assert push 0 eq assert push 0 eq assert \ - push 1 split_assert push 0 eq assert push 1 eq assert \ - push 2 split_assert push 0 eq assert push 2 eq assert \ - push 4294967297 split_assert assert assert \ - halt", - ) - } - pub fn test_program_for_xxadd() -> SourceCodeAndInput { SourceCodeAndInput::without_input("push 5 push 6 push 7 push 8 push 9 push 10 xxadd halt") } @@ -799,8 +746,6 @@ pub mod triton_vm_tests { property_based_test_program_for_lt(), property_based_test_program_for_and(), property_based_test_program_for_xor(), - property_based_test_program_for_reverse(), - 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(), @@ -815,10 +760,7 @@ pub mod triton_vm_tests { test_program_for_lt(), test_program_for_and(), test_program_for_xor(), - test_program_for_reverse(), - test_program_for_lte(), test_program_for_div(), - test_program_for_split_assert(), ] } From 08f6486930f10cf26afeb15d4b45d0ed9f070a1a Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Mon, 9 Jan 2023 11:22:21 +0100 Subject: [PATCH 31/53] =?UTF-8?q?modify=20TASM:=20=E2=80=9Cassert=E2=80=9D?= =?UTF-8?q?=20after=20now-boolean-returning=20instruction=20=E2=80=9Cis=5F?= =?UTF-8?q?u32=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also improve the comments for pseudo instruction “is_u32.” --- triton-opcodes/src/instruction.rs | 8 ++++---- triton-vm/src/vm.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/triton-opcodes/src/instruction.rs b/triton-opcodes/src/instruction.rs index 40fceea7..2e2646f3 100644 --- a/triton-opcodes/src/instruction.rs +++ b/triton-opcodes/src/instruction.rs @@ -578,10 +578,10 @@ fn pseudo_instruction_is_u32() -> Vec> { Dup(ST0), // _ a a Split, // _ a lo hi Push(BFieldElement::zero()), // _ a lo hi 0 - Eq, // _ a lo {0,1} - Swap(ST2), // _ {0,1} lo a - Eq, // _ {0,1} {0,1} - Mul, // _ {0,1} + Eq, // _ a lo (hi==0) + Swap(ST2), // _ (hi==0) lo a + Eq, // _ (hi==0) (lo==a) + Mul, // _ (hi==0 & lo==a) ] } diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 9d67bc16..f37bbf32 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -579,7 +579,7 @@ pub mod triton_vm_tests { let mut rng = ThreadRng::default(); let st0 = rng.next_u32(); - let source_code = format!("push {} is_u32 halt", st0); + let source_code = format!("push {} is_u32 assert halt", st0); SourceCodeAndInput::without_input(&source_code) } @@ -671,7 +671,7 @@ pub mod triton_vm_tests { let mut rng = ThreadRng::default(); let st0 = (rng.next_u32() as u64) << 32; - let source_code = format!("push {} is_u32 halt", st0); + let source_code = format!("push {} is_u32 assert halt", st0); let program = SourceCodeAndInput::without_input(&source_code); let _ = program.run(); } From 913363b7a0115d1d9b6ada03ba4fa4da4174e9a7 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Mon, 9 Jan 2023 14:11:56 +0100 Subject: [PATCH 32/53] =?UTF-8?q?print=20master=20table=20column=20indices?= =?UTF-8?q?=20for=20U32=20Table=20in=20corresponding=20=E2=80=9Ctest?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- triton-vm/src/stark.rs | 3 --- triton-vm/src/table/master_table.rs | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index c654b576..7aaffe98 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -1596,7 +1596,6 @@ pub(crate) mod triton_stark_tests { 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!(); } } @@ -1605,7 +1604,6 @@ pub(crate) mod triton_stark_tests { 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!(); } } @@ -1614,7 +1612,6 @@ pub(crate) mod triton_stark_tests { 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!(); } } diff --git a/triton-vm/src/table/master_table.rs b/triton-vm/src/table/master_table.rs index 3c0651fd..d3c7a00a 100644 --- a/triton-vm/src/table/master_table.rs +++ b/triton-vm/src/table/master_table.rs @@ -1513,6 +1513,8 @@ mod master_table_tests { use crate::table::table_column::ProgramExtTableColumn; use crate::table::table_column::RamBaseTableColumn; use crate::table::table_column::RamExtTableColumn; + use crate::table::table_column::U32BaseTableColumn; + use crate::table::table_column::U32ExtTableColumn; use crate::table::u32_table; #[test] @@ -1760,6 +1762,12 @@ mod master_table_tests { column.master_base_table_index() ); } + for column in U32BaseTableColumn::iter() { + println!( + "{:>3} | u32 | {column}", + column.master_base_table_index() + ); + } println!(); println!("idx | table | extension column"); println!("---:|:------------|:----------------"); @@ -1805,5 +1813,11 @@ mod master_table_tests { column.master_ext_table_index() ); } + for column in U32ExtTableColumn::iter() { + println!( + "{:>3} | u32 | {column}", + column.master_ext_table_index() + ); + } } } From c9c523911571f90a06248b490e16b592ddf0fbba Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Mon, 9 Jan 2023 14:29:34 +0100 Subject: [PATCH 33/53] U32 Table: fill the inverse-or-zero of LHS and RHS --- triton-vm/src/table/u32_table.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index d878c150..d602e804 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -479,6 +479,9 @@ impl U32Table { section = Self::u32_section_next_row(section); let (mut row, next_row) = section.multi_slice_mut((s![row_idx, ..], s![row_idx + 1, ..])); + row[LhsInv.base_table_index()] = row[LHS.base_table_index()].inverse_or_zero(); + row[RhsInv.base_table_index()] = row[RHS.base_table_index()].inverse_or_zero(); + row[LT.base_table_index()] = if next_row[LT.base_table_index()].is_zero() { zero } else if next_row[LT.base_table_index()].is_one() { From 2c8bb559c1fbc3fe097d92b35613244ae4d45cf1 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Mon, 9 Jan 2023 14:40:04 +0100 Subject: [PATCH 34/53] U32 Table: check the least-significant bit, not its additive inverse --- specification/src/u32-table.md | 2 +- triton-vm/src/table/u32_table.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/specification/src/u32-table.md b/specification/src/u32-table.md index 32813bf0..0ab8bc3c 100644 --- a/specification/src/u32-table.md +++ b/specification/src/u32-table.md @@ -173,7 +173,7 @@ Written in Disjunctive Normal Form, the same constraints can be expressed as: Even though they are never explicitly represented, it is useful to alias the `LHS`'s and `RHS`'s _least-significant bit_, or “lsb.” Given two consecutive rows for `LHS`, the (current) least significant bit can be computed by subtracting twice the next row's `LHS` from the current row's `LHS`. -These aliases, _i.e._, `LhsLsb` = 2·`LHS`' - `LHS` and `RhsLsb` = 2·`RHS`' - `RHS`, are used throughout the following. +These aliases, _i.e._, `LhsLsb` = `LHS` - 2·`LHS`' and `RhsLsb` = `RHS` - 2·`RHS`', are used throughout the following. 1. If the `CopyFlag` in the next row is 1, then `LHS` in the current row is 0. 1. If the `CopyFlag` in the next row is 1, then `RHS` in the current row is 0. diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index d602e804..affd0d47 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -306,8 +306,8 @@ impl ExtU32Table { (copy_flag_next.clone() - one.clone()) * rhs.clone() * (bits_next - bits.clone() - one.clone()); - let lhs_lsb = two.clone() * lhs_next.clone() - lhs.clone(); - let rhs_lsb = two.clone() * rhs_next - rhs.clone(); + let lhs_lsb = lhs.clone() - two.clone() * lhs_next.clone(); + let rhs_lsb = rhs.clone() - two.clone() * rhs_next; let lhs_lsb_is_a_bit = lhs_lsb.clone() * (lhs_lsb.clone() - one.clone()); let rhs_lsb_is_a_bit = rhs_lsb.clone() * (rhs_lsb.clone() - one.clone()); let if_copy_flag_next_is_0_and_lt_next_is_0_then_lt_is_0 = (copy_flag_next.clone() From 2d44dca4e8d176ae3a5c4f168ca59b2adc780c3f Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Mon, 9 Jan 2023 15:00:45 +0100 Subject: [PATCH 35/53] check for correct absorption of _next_ row in U32 Table's Perm Arg --- triton-vm/src/table/u32_table.rs | 46 +++++++++++++++++--------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index affd0d47..9b9e9175 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -246,9 +246,9 @@ impl ExtU32Table { let deselect_instructions = |instructions: &[Instruction]| { instructions .iter() - .map(|&instr| ci.clone() - circuit_builder.b_constant(instr.opcode_b())) + .map(|&instr| ci_next.clone() - circuit_builder.b_constant(instr.opcode_b())) .fold(one.clone(), ConstraintCircuitMonad::mul) - * ci.clone() + * ci_next.clone() }; let lt_div_deselector = deselect_instructions(&[ Instruction::And, @@ -284,18 +284,18 @@ impl ExtU32Table { Instruction::Xor, Instruction::Log2Floor, ]); - let result = lt_div_deselector - * circuit_builder.input(CurrentBaseRow(LT.master_base_table_index())) - + and_deselector * circuit_builder.input(CurrentBaseRow(AND.master_base_table_index())) - + xor_deselector * circuit_builder.input(CurrentBaseRow(XOR.master_base_table_index())) + let result_next = lt_div_deselector + * circuit_builder.input(NextBaseRow(LT.master_base_table_index())) + + and_deselector * circuit_builder.input(NextBaseRow(AND.master_base_table_index())) + + xor_deselector * circuit_builder.input(NextBaseRow(XOR.master_base_table_index())) + log2floor_deselector - * circuit_builder.input(CurrentBaseRow(Log2Floor.master_base_table_index())) - + pow_deselector * circuit_builder.input(CurrentBaseRow(Pow.master_base_table_index())); + * circuit_builder.input(NextBaseRow(Log2Floor.master_base_table_index())) + + pow_deselector * circuit_builder.input(NextBaseRow(Pow.master_base_table_index())); let if_copy_flag_next_is_1_then_lhs_is_0 = copy_flag_next.clone() * lhs.clone(); let if_copy_flag_next_is_1_then_rhs_is_0 = copy_flag_next.clone() * rhs.clone(); let if_copy_flag_next_is_0_then_ci_stays = - (copy_flag_next.clone() - one.clone()) * (ci_next - ci.clone()); + (copy_flag_next.clone() - one.clone()) * (ci_next.clone() - ci); let if_copy_flag_next_is_0_then_lhs_copy_stays = (copy_flag_next.clone() - one.clone()) * (lhs_copy_next - lhs_copy.clone()); let if_copy_flag_next_is_0_and_lhs_next_is_nonzero_then_bits_increases_by_1 = @@ -307,7 +307,7 @@ impl ExtU32Table { * rhs.clone() * (bits_next - bits.clone() - one.clone()); let lhs_lsb = lhs.clone() - two.clone() * lhs_next.clone(); - let rhs_lsb = rhs.clone() - two.clone() * rhs_next; + let rhs_lsb = rhs - two.clone() * rhs_next.clone(); let lhs_lsb_is_a_bit = lhs_lsb.clone() * (lhs_lsb.clone() - one.clone()); let rhs_lsb_is_a_bit = rhs_lsb.clone() * (rhs_lsb.clone() - one.clone()); let if_copy_flag_next_is_0_and_lt_next_is_0_then_lt_is_0 = (copy_flag_next.clone() @@ -346,7 +346,7 @@ impl ExtU32Table { * (copy_flag.clone() - one.clone()) * (lt.clone() - two.clone()); let if_copy_flag_next_is_0_and_lt_next_is_2_and_lsbs_equal_in_top_row_then_lt_is_0 = - lt_result_unclear * copy_flag.clone() * lt; + lt_result_unclear * copy_flag * lt; let if_copy_flag_next_is_0_then_and_updates_correctly = (copy_flag_next.clone() - one.clone()) * (and - two.clone() * and_next - lhs_lsb.clone() * rhs_lsb.clone()); @@ -357,10 +357,12 @@ impl ExtU32Table { let if_copy_flag_next_is_0_and_lhs_next_is_0_and_lhs_is_nonzero_then_log2floor_is_bits = (copy_flag_next.clone() - one.clone()) * (one.clone() - lhs_next.clone() * lhs_inv_next) - * lhs.clone() + * lhs * (log2floor.clone() - bits); let if_copy_flag_next_is_0_and_lhs_next_is_nonzero_then_log2floor_stays = - (copy_flag_next.clone() - one.clone()) * lhs_next * (log2floor_next - log2floor); + (copy_flag_next.clone() - one.clone()) + * lhs_next.clone() + * (log2floor_next - log2floor); let if_copy_flag_next_is_0_and_rhs_lsb_is_0_then_pow_squares = (copy_flag_next.clone() - one.clone()) * (rhs_lsb.clone() - one.clone()) @@ -370,15 +372,15 @@ impl ExtU32Table { * rhs_lsb * (pow - pow_next.clone() * pow_next * lhs_copy); - let compressed_row = challenge(ProcessorPermIndeterminate) - - challenge(LhsWeight) * lhs - - challenge(RhsWeight) * rhs - - challenge(CIWeight) * ci - - challenge(ResultWeight) * result; let if_copy_flag_next_is_0_then_running_product_stays = - (copy_flag_next - one.clone()) * (rp_next.clone() - rp.clone()); - let if_copy_flag_next_is_1_then_running_product_absorbs_row = - copy_flag * (rp_next - rp * compressed_row); + (copy_flag_next.clone() - one.clone()) * (rp_next.clone() - rp.clone()); + + let compressed_row_next = challenge(CIWeight) * ci_next + + challenge(LhsWeight) * lhs_next + + challenge(RhsWeight) * rhs_next + + challenge(ResultWeight) * result_next; + let if_copy_flag_next_is_1_then_running_product_absorbs_next_row = copy_flag_next + * (rp_next - rp * (challenge(ProcessorPermIndeterminate) - compressed_row_next)); [ if_copy_flag_next_is_1_then_lhs_is_0, @@ -402,7 +404,7 @@ impl ExtU32Table { if_copy_flag_next_is_0_and_rhs_lsb_is_0_then_pow_squares, if_copy_flag_next_is_0_and_rhs_lsb_is_0_then_pow_squares_times_lhs_copy, if_copy_flag_next_is_0_then_running_product_stays, - if_copy_flag_next_is_1_then_running_product_absorbs_row, + if_copy_flag_next_is_1_then_running_product_absorbs_next_row, ] .map(|circuit| circuit.consume()) .to_vec() From 4191b805cdcd616e11751dc01785c2ffdd05186b Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Mon, 9 Jan 2023 15:09:02 +0100 Subject: [PATCH 36/53] U32 Table: check least-significant bits being bits only within a section --- triton-vm/src/table/u32_table.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 9b9e9175..ee14203e 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -308,8 +308,12 @@ impl ExtU32Table { * (bits_next - bits.clone() - one.clone()); let lhs_lsb = lhs.clone() - two.clone() * lhs_next.clone(); let rhs_lsb = rhs - two.clone() * rhs_next.clone(); - let lhs_lsb_is_a_bit = lhs_lsb.clone() * (lhs_lsb.clone() - one.clone()); - let rhs_lsb_is_a_bit = rhs_lsb.clone() * (rhs_lsb.clone() - one.clone()); + let if_copy_flag_next_is_0_then_lhs_lsb_is_a_bit = (copy_flag_next.clone() - one.clone()) + * lhs_lsb.clone() + * (lhs_lsb.clone() - one.clone()); + let if_copy_flag_next_is_0_then_rhs_lsb_is_a_bit = (copy_flag_next.clone() - one.clone()) + * rhs_lsb.clone() + * (rhs_lsb.clone() - one.clone()); let if_copy_flag_next_is_0_and_lt_next_is_0_then_lt_is_0 = (copy_flag_next.clone() - one.clone()) * (lt_next.clone() - one.clone()) @@ -389,8 +393,8 @@ impl ExtU32Table { if_copy_flag_next_is_0_then_lhs_copy_stays, if_copy_flag_next_is_0_and_lhs_next_is_nonzero_then_bits_increases_by_1, if_copy_flag_next_is_0_and_rhs_next_is_nonzero_then_bits_increases_by_1, - lhs_lsb_is_a_bit, - rhs_lsb_is_a_bit, + if_copy_flag_next_is_0_then_lhs_lsb_is_a_bit, + if_copy_flag_next_is_0_then_rhs_lsb_is_a_bit, if_copy_flag_next_is_0_and_lt_next_is_0_then_lt_is_0, if_copy_flag_next_is_0_and_lt_next_is_1_then_lt_is_1, if_copy_flag_next_is_0_and_lt_next_is_2_and_lt_known_then_lt_is_1, From 150f334238e7593e5495f94452f992d5c56ba04f Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 10 Jan 2023 06:59:52 +0100 Subject: [PATCH 37/53] =?UTF-8?q?U32=20Table:=20change=20padding=20rules?= =?UTF-8?q?=20slightly=20=E2=80=93=20copy=20`CI`=20and=20`LhsCopy`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also increase consistency of the use of `LhsCopy` for `Pow` across specification and code. --- specification/src/u32-table.md | 6 ++++-- triton-vm/src/table/u32_table.rs | 8 +++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/specification/src/u32-table.md b/specification/src/u32-table.md index 0ab8bc3c..b0054774 100644 --- a/specification/src/u32-table.md +++ b/specification/src/u32-table.md @@ -105,6 +105,8 @@ Each padding row is the all-zero row with the exception of - `Log2Floor`, which is -1, and - `Pow`, which is 1. +Additionally, if the U32 Table is non-empty before applying padding, the padding row's two columns `CI` and `LhsCopy` are taken from the U32 Table's last row. + # Arithmetic Intermediate Representation Let all household items (🪥, 🛁, etc.) be challenges, concretely evaluation points, supplied by the verifier. @@ -194,7 +196,7 @@ These aliases, _i.e._, `LhsLsb` = `LHS` - 2·`LHS`' and `RhsLsb` = `RHS` - 2·`R 1. If the `CopyFlag` in the next row is 0 and `LHS` in the next row is 0 and `LHS` in the current row is not 0, then `Log2Floor` in the current row is `Bits`. 1. If the `CopyFlag` in the next row is 0 and `LHS` in the next row is not 0, then `Log2Floor` in the current row is `Log2Floor` in the next row. 1. If the `CopyFlag` in the next row is 0 and `RhsLsb` in the current row is 0, then `Pow` in the current row is `Pow` in the next row squared. -1. If the `CopyFlag` in the next row is 0 and `RhsLsb` in the current row is 1, then `Pow` in the current row is `Pow` in the next row squared times `LhsCopy` in the next row. +1. If the `CopyFlag` in the next row is 0 and `RhsLsb` in the current row is 1, then `Pow` in the current row is `Pow` in the next row squared times `LhsCopy` in the current row. 1. If the `CopyFlag` in the next row is 0, then `RunningProductProcessor` in the next row is `RunningProductProcessor` in the current row. 1. If the `CopyFlag` in the next row is 1, then `RunningProductProcessor` in the next row has absorbed the next row with respect to challenges 🥜, 🌰, 🥑, and 🥕, and indeterminate 🧷. @@ -219,7 +221,7 @@ Written in Disjunctive Normal Form, the same constraints can be expressed as: 1. `CopyFlag`' is 1 or `LHS`' is not 0 or `LHS` is 0 or `Log2Floor` is `Bits`. 1. `CopyFlag`' is 1 or `LHS`' is 0 or `Log2Floor` is `Log2Floor`'. 1. `CopyFlag`' is 1 or `RhsLsb` is 1 or `Pow` is `Pow`' times `Pow`'. -1. `CopyFlag`' is 1 or `RhsLsb` is 0 or `Pow` is `Pow`' times `Pow`' times `LhsCopy`'. +1. `CopyFlag`' is 1 or `RhsLsb` is 0 or `Pow` is `Pow`' times `Pow`' times `LhsCopy`. 1. `CopyFlag`' is 1 or `RunningProductProcessor`' is `RunningProductProcessor`. 1. `CopyFlag`' is 0 or `RunningProductProcessor`' is `RunningProductProcessor` times `(🧷 - 🥜·LHS' - 🌰·RHS' - 🥑·CI' - 🥕·Result')`. diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index ee14203e..5aa058f1 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -526,7 +526,7 @@ impl U32Table { } else { next_row[Pow.base_table_index()] * next_row[Pow.base_table_index()] - * next_row[LhsCopy.base_table_index()] + * row[LhsCopy.base_table_index()] }; section @@ -539,6 +539,12 @@ impl U32Table { padding_row[[Log2Floor.base_table_index()]] = -BFieldElement::one(); padding_row[[Pow.base_table_index()]] = BFieldElement::one(); + if u32_table_len > 0 { + let last_row = u32_table.row(u32_table_len - 1); + padding_row[[CI.base_table_index()]] = last_row[CI.base_table_index()]; + padding_row[[LhsCopy.base_table_index()]] = last_row[LhsCopy.base_table_index()]; + } + u32_table .slice_mut(s![u32_table_len.., ..]) .axis_iter_mut(Axis(0)) From 2f0c607a50cc7b14d2d9adff423add1d7c4b43d9 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 10 Jan 2023 09:55:39 +0100 Subject: [PATCH 38/53] properly test transition constraints of new instructions, fix some bugs --- triton-vm/src/state.rs | 7 +- triton-vm/src/table/processor_table.rs | 107 +++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 3e770f26..4b7e526d 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -134,7 +134,7 @@ impl<'pgm> VMState<'pgm> { // if current instruction shrinks the stack if matches!( current_instruction, - Pop | Skiz | Assert | WriteIo | Add | Mul | Eq | XbMul + Pop | Skiz | Assert | WriteIo | Add | Mul | Eq | XbMul | Lt | And | Xor | Pow ) { hvs[3] = (self.op_stack.osp() - BFieldElement::new(16)).inverse_or_zero(); } @@ -410,7 +410,10 @@ impl<'pgm> VMState<'pgm> { Log2Floor => { let lhs = self.op_stack.pop()?; - let l2f = BFieldElement::new(log_2_floor(lhs.value() as u128)); + let l2f = match lhs.is_zero() { + true => -BFieldElement::one(), + false => BFieldElement::new(log_2_floor(lhs.value() as u128)), + }; self.op_stack.push(l2f); self.instruction_pointer += 1; let rhs = BFieldElement::zero(); diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index a7de7e6f..98287a2b 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -4684,6 +4684,7 @@ impl<'a> Display for ExtProcessorMatrixRow<'a> { mod constraint_polynomial_tests { use ndarray::Array2; + use crate::shared_tests::SourceCodeAndInput; use crate::stark::triton_stark_tests::parse_simulate_pad; use crate::table::challenges::AllChallenges; use crate::table::master_table::MasterTable; @@ -4945,6 +4946,112 @@ mod constraint_polynomial_tests { test_constraints_for_rows_with_debug_info(Lsb, &test_rows, &[ST0], &[ST0, ST1]); } + #[test] + fn transition_constraints_for_instruction_lt_test() { + let test_rows = [ + get_test_row_from_source_code("push 3 push 3 lt push 0 eq assert halt", 2), + get_test_row_from_source_code("push 3 push 2 lt push 1 eq assert halt", 2), + get_test_row_from_source_code("push 2 push 3 lt push 0 eq assert halt", 2), + get_test_row_from_source_code("push 512 push 513 lt push 0 eq assert halt", 2), + ]; + test_constraints_for_rows_with_debug_info(Lt, &test_rows, &[ST0, ST1], &[ST0]); + } + + #[test] + fn transition_constraints_for_instruction_and_test() { + let test_rows = [get_test_row_from_source_code( + "push 5 push 12 and push 4 eq assert halt", + 2, + )]; + test_constraints_for_rows_with_debug_info(And, &test_rows, &[ST0, ST1], &[ST0]); + } + + #[test] + fn transition_constraints_for_instruction_xor_test() { + let test_rows = [get_test_row_from_source_code( + "push 5 push 12 xor push 9 eq assert halt", + 2, + )]; + test_constraints_for_rows_with_debug_info(Xor, &test_rows, &[ST0, ST1], &[ST0]); + } + + #[test] + fn transition_constraints_for_instruction_log2floor_test() { + let test_rows = [ + get_test_row_from_source_code("push 0 log_2_floor push -1 eq assert halt", 1), + get_test_row_from_source_code("push 1 log_2_floor push 0 eq assert halt", 1), + get_test_row_from_source_code("push 2 log_2_floor push 1 eq assert halt", 1), + get_test_row_from_source_code("push 3 log_2_floor push 1 eq assert halt", 1), + get_test_row_from_source_code("push 4 log_2_floor push 2 eq assert halt", 1), + get_test_row_from_source_code("push 5 log_2_floor push 2 eq assert halt", 1), + get_test_row_from_source_code("push 6 log_2_floor push 2 eq assert halt", 1), + get_test_row_from_source_code("push 7 log_2_floor push 2 eq assert halt", 1), + get_test_row_from_source_code("push 8 log_2_floor push 3 eq assert halt", 1), + get_test_row_from_source_code("push 9 log_2_floor push 3 eq assert halt", 1), + get_test_row_from_source_code("push 10 log_2_floor push 3 eq assert halt", 1), + get_test_row_from_source_code("push 11 log_2_floor push 3 eq assert halt", 1), + get_test_row_from_source_code("push 12 log_2_floor push 3 eq assert halt", 1), + get_test_row_from_source_code("push 13 log_2_floor push 3 eq assert halt", 1), + get_test_row_from_source_code("push 14 log_2_floor push 3 eq assert halt", 1), + get_test_row_from_source_code("push 15 log_2_floor push 3 eq assert halt", 1), + get_test_row_from_source_code("push 16 log_2_floor push 4 eq assert halt", 1), + get_test_row_from_source_code("push 17 log_2_floor push 4 eq assert halt", 1), + ]; + test_constraints_for_rows_with_debug_info(Log2Floor, &test_rows, &[ST0, ST1], &[ST0]); + } + + #[test] + fn transition_constraints_for_instruction_pow_test() { + let test_rows = [ + get_test_row_from_source_code("push 0 push 0 pow push 1 eq assert halt", 2), + get_test_row_from_source_code("push 1 push 0 pow push 0 eq assert halt", 2), + get_test_row_from_source_code("push 2 push 0 pow push 0 eq assert halt", 2), + get_test_row_from_source_code("push 0 push 1 pow push 1 eq assert halt", 2), + get_test_row_from_source_code("push 1 push 1 pow push 1 eq assert halt", 2), + get_test_row_from_source_code("push 2 push 1 pow push 1 eq assert halt", 2), + get_test_row_from_source_code("push 0 push 2 pow push 1 eq assert halt", 2), + get_test_row_from_source_code("push 1 push 2 pow push 2 eq assert halt", 2), + get_test_row_from_source_code("push 2 push 2 pow push 4 eq assert halt", 2), + get_test_row_from_source_code("push 3 push 2 pow push 8 eq assert halt", 2), + get_test_row_from_source_code("push 4 push 2 pow push 16 eq assert halt", 2), + get_test_row_from_source_code("push 5 push 2 pow push 32 eq assert halt", 2), + get_test_row_from_source_code("push 0 push 3 pow push 1 eq assert halt", 2), + get_test_row_from_source_code("push 1 push 3 pow push 3 eq assert halt", 2), + get_test_row_from_source_code("push 2 push 3 pow push 9 eq assert halt", 2), + get_test_row_from_source_code("push 3 push 3 pow push 27 eq assert halt", 2), + get_test_row_from_source_code("push 4 push 3 pow push 81 eq assert halt", 2), + get_test_row_from_source_code("push 0 push 17 pow push 1 eq assert halt", 2), + get_test_row_from_source_code("push 1 push 17 pow push 17 eq assert halt", 2), + get_test_row_from_source_code("push 2 push 17 pow push 289 eq assert halt", 2), + ]; + test_constraints_for_rows_with_debug_info(Pow, &test_rows, &[ST0, ST1], &[ST0]); + } + + #[test] + fn transition_constraints_for_instruction_div_test() { + let test_rows = [ + get_test_row_from_source_code( + "push 2 push 3 div push 1 eq assert push 1 eq assert halt", + 2, + ), + get_test_row_from_source_code( + "push 3 push 7 div push 1 eq assert push 2 eq assert halt", + 2, + ), + get_test_row_from_source_code( + "push 4 push 7 div push 3 eq assert push 1 eq assert halt", + 2, + ), + ]; + test_constraints_for_rows_with_debug_info(Div, &test_rows, &[ST0, ST1], &[ST0, ST1]); + } + + #[test] + #[should_panic(expected = "0 does not have a multiplicative inverse")] + fn division_by_zero_is_impossible_test() { + SourceCodeAndInput::without_input("div").run(); + } + #[test] fn transition_constraints_for_instruction_xxadd_test() { let test_rows = [ From 3e67345225585c6a64d27f53266aeb7354b429ff Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 10 Jan 2023 12:50:09 +0100 Subject: [PATCH 39/53] normalize U32 Table's instruction de-selectors to correctly copy result --- specification/src/u32-table.md | 15 ++- triton-vm/src/table/u32_table.rs | 200 ++++++++++++++----------------- 2 files changed, 99 insertions(+), 116 deletions(-) diff --git a/specification/src/u32-table.md b/specification/src/u32-table.md index b0054774..2c7ea5ec 100644 --- a/specification/src/u32-table.md +++ b/specification/src/u32-table.md @@ -85,17 +85,16 @@ The instruction `div` _also_ uses the U32 Table to ensure that the numerator `n` For this range check, happening in its independent section, no result is required. The instruction `split` also uses the U32 Table for range checking only, _i.e._, to ensure that the instruction's resulting “high bits” and “low bits” each fit in a u32. -To conditionally copy the required result to the processor, instruction de-selectors like in the Processor Table are used. -Concretely, with `u32_instructions = {lt, and, xor, log_2_floor, pow, div}`, the following aliases are used: +To conditionally copy the required result to the processor, instruction de-selectors (comparable to the ones in the Processor Table) are used. +Concretely, with `u32_instructions = {lt, and, xor, log_2_floor, pow, div}`, the following (normalized) deselector for instruction `lt` is defined as: -- `lt_div_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \not\in \{\texttt{lt}, \texttt{div}\}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ -- `and_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{and}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ -- `xor_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{xor}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ -- `log_2_floor_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{log\_2\_floor}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ -- `pow_deselector` = $\texttt{CI}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{pow}}} \texttt{CI} - \texttt{opcode}(\texttt{i})$ +$$ +\frac{\texttt{CI}}{\texttt{opcode}(\texttt{lt})}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{lt}}} \frac{\texttt{CI} - \texttt{opcode}(\texttt{i})}{\texttt{opcode}(\texttt{lt}) - \texttt{opcode}(\texttt{i})} +$$ +The deselectors `and_deselector`, `xor_deselector`, `log_2_floor_deselector`, `pow_deselector`, and `div_deselector` are defined accordingly. Throughout the next sections, the alias `Result` corresponds to the polynomial -`LT·lt_div_deselector + AND·and_deselector + XOR·xor_deselector + Log2Floor·log_2_floor_deselector + Pow·pow_deselector`. +`LT·lt_deselector + AND·and_deselector + XOR·xor_deselector + Log2Floor·log_2_floor_deselector + Pow·pow_deselector + LT·div_deselector`. ## Padding diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 5aa058f1..53082211 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use std::ops::Mul; use ndarray::parallel::prelude::*; @@ -72,69 +73,62 @@ impl ExtU32Table { let rp = circuit_builder.input(ExtRow(ProcessorPermArg.master_ext_table_index())); - let deselect_instructions = |instructions: &[Instruction]| { - instructions + let normalized_instruction_deselector = |instruction_to_select: Instruction| { + let instructions_to_deselect = [ + Instruction::Lt, + Instruction::And, + Instruction::Xor, + Instruction::Log2Floor, + Instruction::Pow, + Instruction::Div, + ] + .into_iter() + .filter(|&instruction| instruction != instruction_to_select) + .collect_vec(); + + let deselect_0 = ci.clone(); + let deselector = instructions_to_deselect .iter() - .map(|&instr| ci.clone() - circuit_builder.b_constant(instr.opcode_b())) - .fold(one.clone(), ConstraintCircuitMonad::mul) - * ci.clone() + .map(|&instruction| ci.clone() - circuit_builder.b_constant(instruction.opcode_b())) + .fold(deselect_0, ConstraintCircuitMonad::mul); + let normalize_deselected_0 = instruction_to_select.opcode_b(); + let normalizing_factor = instructions_to_deselect + .iter() + .fold(normalize_deselected_0, |acc, instr| { + acc * (instruction_to_select.opcode_b() - instr.opcode_b()) + }) + .inverse(); + circuit_builder.b_constant(normalizing_factor) * deselector }; - let lt_div_deselector = deselect_instructions(&[ - Instruction::And, - Instruction::Xor, - Instruction::Log2Floor, - Instruction::Pow, - ]); - let and_deselector = deselect_instructions(&[ - Instruction::Lt, - Instruction::Div, - Instruction::Xor, - Instruction::Log2Floor, - Instruction::Pow, - ]); - let xor_deselector = deselect_instructions(&[ - Instruction::Lt, - Instruction::Div, - Instruction::And, - Instruction::Log2Floor, - Instruction::Pow, - ]); - let log2floor_deselector = deselect_instructions(&[ - Instruction::Lt, - Instruction::Div, - Instruction::And, - Instruction::Xor, - Instruction::Pow, - ]); - let pow_deselector = deselect_instructions(&[ - Instruction::Lt, - Instruction::Div, - Instruction::And, - Instruction::Xor, - Instruction::Log2Floor, - ]); - let result = lt_div_deselector + + let result = normalized_instruction_deselector(Instruction::Lt) * circuit_builder.input(BaseRow(LT.master_base_table_index())) - + and_deselector * circuit_builder.input(BaseRow(AND.master_base_table_index())) - + xor_deselector * circuit_builder.input(BaseRow(XOR.master_base_table_index())) - + log2floor_deselector + + normalized_instruction_deselector(Instruction::And) + * circuit_builder.input(BaseRow(AND.master_base_table_index())) + + normalized_instruction_deselector(Instruction::Xor) + * circuit_builder.input(BaseRow(XOR.master_base_table_index())) + + normalized_instruction_deselector(Instruction::Log2Floor) * circuit_builder.input(BaseRow(Log2Floor.master_base_table_index())) - + pow_deselector * circuit_builder.input(BaseRow(Pow.master_base_table_index())); + + normalized_instruction_deselector(Instruction::Pow) + * circuit_builder.input(BaseRow(Pow.master_base_table_index())) + + normalized_instruction_deselector(Instruction::Div) + * circuit_builder.input(BaseRow(LT.master_base_table_index())); let initial_factor = challenge(ProcessorPermIndeterminate) - challenge(LhsWeight) * lhs - challenge(RhsWeight) * rhs - challenge(CIWeight) * ci - challenge(ResultWeight) * result; - let copy_flag_is_0_or_rp_has_accumulated_first_row = + let if_copy_flag_is_1_then_rp_has_accumulated_first_row = copy_flag.clone() * (rp.clone() - initial_factor); let default_initial = circuit_builder.x_constant(PermArg::default_initial()); - let copy_flag_is_1_or_rp_is_default_initial = (one - copy_flag) * (default_initial - rp); + let if_copy_flag_is_0_then_rp_is_default_initial = + (one - copy_flag) * (default_initial - rp); [ - copy_flag_is_1_or_rp_is_default_initial, - copy_flag_is_0_or_rp_has_accumulated_first_row, + if_copy_flag_is_0_then_rp_is_default_initial, + if_copy_flag_is_1_then_rp_has_accumulated_first_row, ] .map(|circuit| circuit.consume()) .to_vec() @@ -243,54 +237,48 @@ impl ExtU32Table { let lhs_inv_next = circuit_builder.input(NextBaseRow(LhsInv.master_base_table_index())); let rp_next = circuit_builder.input(NextExtRow(ProcessorPermArg.master_ext_table_index())); - let deselect_instructions = |instructions: &[Instruction]| { - instructions + let normalized_instruction_deselector = |instruction_to_select: Instruction| { + let instructions_to_deselect = [ + Instruction::Lt, + Instruction::And, + Instruction::Xor, + Instruction::Log2Floor, + Instruction::Pow, + Instruction::Div, + ] + .into_iter() + .filter(|&instruction| instruction != instruction_to_select) + .collect_vec(); + + let deselect_0 = ci_next.clone(); + let deselector = instructions_to_deselect + .iter() + .map(|&instruction| { + ci_next.clone() - circuit_builder.b_constant(instruction.opcode_b()) + }) + .fold(deselect_0, ConstraintCircuitMonad::mul); + let normalize_deselected_0 = instruction_to_select.opcode_b(); + let normalizing_factor = instructions_to_deselect .iter() - .map(|&instr| ci_next.clone() - circuit_builder.b_constant(instr.opcode_b())) - .fold(one.clone(), ConstraintCircuitMonad::mul) - * ci_next.clone() + .fold(normalize_deselected_0, |acc, instr| { + acc * (instruction_to_select.opcode_b() - instr.opcode_b()) + }) + .inverse(); + circuit_builder.b_constant(normalizing_factor) * deselector }; - let lt_div_deselector = deselect_instructions(&[ - Instruction::And, - Instruction::Xor, - Instruction::Log2Floor, - Instruction::Pow, - ]); - let and_deselector = deselect_instructions(&[ - Instruction::Lt, - Instruction::Div, - Instruction::Xor, - Instruction::Log2Floor, - Instruction::Pow, - ]); - let xor_deselector = deselect_instructions(&[ - Instruction::Lt, - Instruction::Div, - Instruction::And, - Instruction::Log2Floor, - Instruction::Pow, - ]); - let log2floor_deselector = deselect_instructions(&[ - Instruction::Lt, - Instruction::Div, - Instruction::And, - Instruction::Xor, - Instruction::Pow, - ]); - let pow_deselector = deselect_instructions(&[ - Instruction::Lt, - Instruction::Div, - Instruction::And, - Instruction::Xor, - Instruction::Log2Floor, - ]); - let result_next = lt_div_deselector + + let result_next = normalized_instruction_deselector(Instruction::Lt) * circuit_builder.input(NextBaseRow(LT.master_base_table_index())) - + and_deselector * circuit_builder.input(NextBaseRow(AND.master_base_table_index())) - + xor_deselector * circuit_builder.input(NextBaseRow(XOR.master_base_table_index())) - + log2floor_deselector + + normalized_instruction_deselector(Instruction::Div) + * circuit_builder.input(NextBaseRow(LT.master_base_table_index())) + + normalized_instruction_deselector(Instruction::And) + * circuit_builder.input(NextBaseRow(AND.master_base_table_index())) + + normalized_instruction_deselector(Instruction::Xor) + * circuit_builder.input(NextBaseRow(XOR.master_base_table_index())) + + normalized_instruction_deselector(Instruction::Log2Floor) * circuit_builder.input(NextBaseRow(Log2Floor.master_base_table_index())) - + pow_deselector * circuit_builder.input(NextBaseRow(Pow.master_base_table_index())); + + normalized_instruction_deselector(Instruction::Pow) + * circuit_builder.input(NextBaseRow(Pow.master_base_table_index())); let if_copy_flag_next_is_1_then_lhs_is_0 = copy_flag_next.clone() * lhs.clone(); let if_copy_flag_next_is_1_then_rhs_is_0 = copy_flag_next.clone() * rhs.clone(); @@ -377,7 +365,7 @@ impl ExtU32Table { * (pow - pow_next.clone() * pow_next * lhs_copy); let if_copy_flag_next_is_0_then_running_product_stays = - (copy_flag_next.clone() - one.clone()) * (rp_next.clone() - rp.clone()); + (copy_flag_next.clone() - one) * (rp_next.clone() - rp.clone()); let compressed_row_next = challenge(CIWeight) * ci_next + challenge(LhsWeight) * lhs_next @@ -565,21 +553,17 @@ impl U32Table { for row_idx in 0..base_table.nrows() { let current_row = base_table.row(row_idx); if current_row[CopyFlag.base_table_index()].is_one() { - let ci_opcode = current_row[CI.base_table_index()].value(); - let result = if ci_opcode == Instruction::Lt.opcode() as u64 { - current_row[LT.base_table_index()] - } else if ci_opcode == Instruction::And.opcode() as u64 { - current_row[AND.base_table_index()] - } else if ci_opcode == Instruction::Xor.opcode() as u64 { - current_row[XOR.base_table_index()] - } else if ci_opcode == Instruction::Log2Floor.opcode() as u64 { - current_row[Log2Floor.base_table_index()] - } else if ci_opcode == Instruction::Pow.opcode() as u64 { - current_row[Pow.base_table_index()] - } else if ci_opcode == Instruction::Div.opcode() as u64 { - current_row[LT.base_table_index()] - } else { - BFieldElement::zero() + let current_instruction = current_row[CI.base_table_index()] + .value() + .try_into() + .unwrap_or(Instruction::Split); + let result = match current_instruction { + Instruction::Lt | Instruction::Div => current_row[LT.base_table_index()], + Instruction::And => current_row[AND.base_table_index()], + Instruction::Xor => current_row[XOR.base_table_index()], + Instruction::Log2Floor => current_row[Log2Floor.base_table_index()], + Instruction::Pow => current_row[Pow.base_table_index()], + _ => BFieldElement::zero(), }; let compressed_row = challenges.ci_weight * current_row[CI.base_table_index()] From f0a6cc4594643096b784245ff01603e8cb2ac712 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 10 Jan 2023 12:51:47 +0100 Subject: [PATCH 40/53] fix bug when computing `Pow` in U32 Table --- triton-vm/src/table/u32_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 53082211..cd3d2344 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -509,7 +509,7 @@ impl U32Table { row[Bits.base_table_index()] }; - row[Pow.base_table_index()] = if row[RHS.base_table_index()].is_zero() { + row[Pow.base_table_index()] = if rhs_lsb.is_zero() { next_row[Pow.base_table_index()] * next_row[Pow.base_table_index()] } else { next_row[Pow.base_table_index()] From 1f79a8323640af3f36c7346846dac1c54bf51ca5 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 10 Jan 2023 12:52:12 +0100 Subject: [PATCH 41/53] improve formatting of some tests --- triton-vm/src/vm.rs | 59 +++++++++++++++------------------------------ 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index f37bbf32..3301771d 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -329,8 +329,8 @@ pub mod triton_vm_tests { } pub fn test_program_for_hash() -> SourceCodeAndInput { - let source_code = - "push 0 push 0 push 0 push 1 push 2 push 3 hash pop pop pop pop pop read_io eq assert halt"; + let source_code = "push 0 push 0 push 0 push 1 push 2 push 3 hash \ + pop pop pop pop pop read_io eq assert halt"; let mut hash_input = [BFieldElement::zero(); 10]; hash_input[0] = BFieldElement::new(3); hash_input[1] = BFieldElement::new(2); @@ -404,10 +404,8 @@ pub mod triton_vm_tests { let st4 = rng.gen_range(0..BFieldElement::QUOTIENT); let source_code = format!( - "push {} push {} push {} push {} push {} \ - read_io read_io read_io read_io read_io \ - assert_vector halt", - st4, st3, st2, st1, st0, + "push {st4} push {st3} push {st2} push {st1} push {st0} \ + read_io read_io read_io read_io read_io assert_vector halt", ); SourceCodeAndInput { @@ -436,11 +434,7 @@ pub mod triton_vm_tests { let hi = st0 >> 32; let lo = st0 & 0xffff_ffff; - let source_code = format!( - "push {} split read_io eq assert read_io eq assert halt", - st0 - ); - + let source_code = format!("push {st0} split read_io eq assert read_io eq assert halt"); SourceCodeAndInput { source_code, input: vec![hi.into(), lo.into()], @@ -460,11 +454,7 @@ pub mod triton_vm_tests { let mut rng = ThreadRng::default(); let st0 = rng.next_u64() % BFieldElement::QUOTIENT; - let source_code = format!( - "push {} dup0 read_io eq assert dup0 divine eq assert halt", - st0 - ); - + let source_code = format!("push {st0} dup0 read_io eq assert dup0 divine eq assert halt"); SourceCodeAndInput { source_code, input: vec![st0.into()], @@ -482,8 +472,7 @@ pub mod triton_vm_tests { let lsb = st0 % 2; let st0_shift_right = st0 >> 1; - let source_code = format!("push {} lsb read_io eq assert read_io eq assert halt", st0); - + let source_code = format!("push {st0} lsb read_io eq assert read_io eq assert halt"); SourceCodeAndInput { source_code, input: vec![lsb.into(), st0_shift_right.into()], @@ -492,21 +481,21 @@ pub mod triton_vm_tests { } pub fn test_program_for_lt() -> SourceCodeAndInput { - SourceCodeAndInput::without_input("push 5 push 2 lt assert halt") + SourceCodeAndInput::without_input( + "push 5 push 2 lt assert push 2 push 5 lt push 0 eq assert halt", + ) } pub fn property_based_test_program_for_lt() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); let st1 = rng.next_u32(); let st0 = rng.next_u32(); - let result = if st0 < st1 { - 1_u64.into() - } else { - 0_u64.into() + let result = match st0 < st1 { + true => 1_u64.into(), + false => 0_u64.into(), }; - let source_code = format!("push {} push {} lt read_io eq assert halt", st1, st0); - + let source_code = format!("push {st1} push {st0} lt read_io eq assert halt"); SourceCodeAndInput { source_code, input: vec![result], @@ -524,8 +513,7 @@ pub mod triton_vm_tests { let st0 = rng.next_u32(); let result = st0.bitand(st1); - let source_code = format!("push {} push {} and read_io eq assert halt", st1, st0); - + let source_code = format!("push {st1} push {st0} and read_io eq assert halt"); SourceCodeAndInput { source_code, input: vec![result.into()], @@ -543,8 +531,7 @@ pub mod triton_vm_tests { let st0 = rng.next_u32(); let result = st0.bitxor(st1); - let source_code = format!("push {} push {} xor read_io eq assert halt", st1, st0); - + let source_code = format!("push {st1} push {st0} xor read_io eq assert halt"); SourceCodeAndInput { source_code, input: vec![result.into()], @@ -564,10 +551,8 @@ pub mod triton_vm_tests { let remainder = numerator % denominator; let source_code = format!( - "push {} push {} div read_io eq assert read_io eq assert halt", - denominator, numerator + "push {denominator} push {numerator} div read_io eq assert read_io eq assert halt" ); - SourceCodeAndInput { source_code, input: vec![remainder.into(), quotient.into()], @@ -578,10 +563,7 @@ pub mod triton_vm_tests { pub fn property_based_test_program_for_is_u32() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); let st0 = rng.next_u32(); - - let source_code = format!("push {} is_u32 assert halt", st0); - - SourceCodeAndInput::without_input(&source_code) + SourceCodeAndInput::without_input(&format!("push {st0} is_u32 assert halt")) } pub fn property_based_test_program_for_random_ram_access() -> SourceCodeAndInput { @@ -609,8 +591,6 @@ pub mod triton_vm_tests { } // 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); @@ -671,8 +651,7 @@ pub mod triton_vm_tests { let mut rng = ThreadRng::default(); let st0 = (rng.next_u32() as u64) << 32; - let source_code = format!("push {} is_u32 assert halt", st0); - let program = SourceCodeAndInput::without_input(&source_code); + let program = SourceCodeAndInput::without_input(&format!("push {st0} is_u32 assert halt")); let _ = program.run(); } From 8422ecaf215ac951bf2a9daa2754278ccf208fd1 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 10 Jan 2023 13:57:54 +0100 Subject: [PATCH 42/53] add static & property tests for new instructions `log2floor` and `pow` --- triton-vm/src/stark.rs | 10 --- triton-vm/src/vm.rs | 178 +++++++++++++++++++++++++++++++++-------- 2 files changed, 143 insertions(+), 45 deletions(-) diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index 7aaffe98..bbde0a60 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -905,7 +905,6 @@ pub(crate) mod triton_stark_tests { use crate::table::table_column::RamBaseTableColumn; use crate::table::u32_table::ExtU32Table; use crate::vm::simulate; - 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; @@ -1125,7 +1124,6 @@ pub(crate) mod triton_stark_tests { #[test] 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()); for (code_idx, code_with_input) in code_collection.into_iter().enumerate() { @@ -1607,14 +1605,6 @@ pub(crate) mod triton_stark_tests { } } - #[test] - 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); - } - } - pub fn triton_table_constraints_evaluate_to_zero(source_code_and_input: SourceCodeAndInput) { let zero = XFieldElement::zero(); let (_, _, master_base_table, master_ext_table, challenges) = parse_simulate_pad_extend( diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 3301771d..93e49267 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -177,6 +177,7 @@ pub mod triton_vm_tests { use rand::rngs::ThreadRng; use rand::Rng; use rand::RngCore; + use twenty_first::shared_math::other::log_2_floor; use twenty_first::shared_math::other::random_elements; use twenty_first::shared_math::rescue_prime_regular::RescuePrimeRegular; use twenty_first::shared_math::traits::FiniteField; @@ -424,10 +425,6 @@ pub mod triton_vm_tests { ) } - pub fn test_program_for_instruction_split() -> SourceCodeAndInput { - SourceCodeAndInput::without_input("push -1 split swap1 lt assert halt ") - } - pub fn property_based_test_program_for_split() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); let st0 = rng.next_u64() % BFieldElement::QUOTIENT; @@ -488,53 +485,162 @@ pub mod triton_vm_tests { pub fn property_based_test_program_for_lt() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); - let st1 = rng.next_u32(); - let st0 = rng.next_u32(); - let result = match st0 < st1 { + + let st1_0 = rng.next_u32(); + let st0_0 = rng.next_u32(); + let result_0 = match st0_0 < st1_0 { true => 1_u64.into(), false => 0_u64.into(), }; - let source_code = format!("push {st1} push {st0} lt read_io eq assert halt"); + let st1_1 = rng.next_u32(); + let st0_1 = rng.next_u32(); + let result_1 = match st0_1 < st1_1 { + true => 1_u64.into(), + false => 0_u64.into(), + }; + + let source_code = format!( + "push {st1_0} push {st0_0} lt read_io eq assert \ + push {st1_1} push {st0_1} lt read_io eq assert halt" + ); SourceCodeAndInput { source_code, - input: vec![result], + input: vec![result_0, result_1], secret_input: vec![], } } pub fn test_program_for_and() -> SourceCodeAndInput { - SourceCodeAndInput::without_input("push 5 push 3 and assert halt") + SourceCodeAndInput::without_input( + "push 5 push 3 and assert push 12 push 5 and push 4 eq assert halt", + ) } pub fn property_based_test_program_for_and() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); - let st1 = rng.next_u32(); - let st0 = rng.next_u32(); - let result = st0.bitand(st1); - let source_code = format!("push {st1} push {st0} and read_io eq assert halt"); + let st1_0 = rng.next_u32(); + let st0_0 = rng.next_u32(); + let result_0 = st0_0.bitand(st1_0); + + let st1_1 = rng.next_u32(); + let st0_1 = rng.next_u32(); + let result_1 = st0_1.bitand(st1_1); + + let source_code = format!( + "push {st1_0} push {st0_0} and read_io eq assert \ + push {st1_1} push {st0_1} and read_io eq assert halt" + ); SourceCodeAndInput { source_code, - input: vec![result.into()], + input: vec![result_0.into(), result_1.into()], secret_input: vec![], } } pub fn test_program_for_xor() -> SourceCodeAndInput { - SourceCodeAndInput::without_input("push 7 push 6 xor assert halt") + SourceCodeAndInput::without_input( + "push 7 push 6 xor assert push 5 push 12 xor push 9 eq assert halt", + ) } pub fn property_based_test_program_for_xor() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); - let st1 = rng.next_u32(); - let st0 = rng.next_u32(); - let result = st0.bitxor(st1); - let source_code = format!("push {st1} push {st0} xor read_io eq assert halt"); + let st1_0 = rng.next_u32(); + let st0_0 = rng.next_u32(); + let result_0 = st0_0.bitxor(st1_0); + + let st1_1 = rng.next_u32(); + let st0_1 = rng.next_u32(); + let result_1 = st0_1.bitxor(st1_1); + + let source_code = format!( + "push {st1_0} push {st0_0} xor read_io eq assert \ + push {st1_1} push {st0_1} xor read_io eq assert halt" + ); + SourceCodeAndInput { + source_code, + input: vec![result_0.into(), result_1.into()], + secret_input: vec![], + } + } + + pub fn test_program_for_log2floor() -> SourceCodeAndInput { + SourceCodeAndInput::without_input( + "push 0 log_2_floor push -1 eq assert \ + push 1 log_2_floor push 0 eq assert \ + push 2 log_2_floor push 1 eq assert \ + push 3 log_2_floor push 1 eq assert \ + push 4 log_2_floor push 2 eq assert \ + push 7 log_2_floor push 2 eq assert \ + push 8 log_2_floor push 3 eq assert \ + push 15 log_2_floor push 3 eq assert \ + push 16 log_2_floor push 4 eq assert \ + push 31 log_2_floor push 4 eq assert \ + push 32 log_2_floor push 5 eq assert \ + push 33 log_2_floor push 5 eq assert \ + push 4294967295 log_2_floor push 31 eq assert halt", + ) + } + + pub fn property_based_test_program_for_log2floor() -> SourceCodeAndInput { + let mut rng = ThreadRng::default(); + + let st0_0 = rng.next_u32(); + let l2f_0 = log_2_floor(st0_0 as u128) as u32; + + let st0_1 = rng.next_u32(); + let l2f_1 = log_2_floor(st0_1 as u128) as u32; + + let source_code = format!( + "push {st0_0} log_2_floor read_io eq assert \ + push {st0_1} log_2_floor read_io eq assert halt" + ); + SourceCodeAndInput { + source_code, + input: vec![l2f_0.into(), l2f_1.into()], + secret_input: vec![], + } + } + + pub fn test_program_for_pow() -> SourceCodeAndInput { + SourceCodeAndInput::without_input( + "push 0 push 0 pow push 1 eq assert \ + push 0 push 1 pow push 1 eq assert \ + push 0 push 2 pow push 1 eq assert \ + push 1 push 0 pow push 0 eq assert \ + push 2 push 0 pow push 0 eq assert \ + push 2 push 5 pow push 25 eq assert \ + push 5 push 2 pow push 32 eq assert \ + push 10 push 2 pow push 1024 eq assert \ + push 3 push 3 pow push 27 eq assert \ + push 3 push 5 pow push 125 eq assert \ + push 9 push 7 pow push 40353607 eq assert halt", + ) + } + + pub fn property_based_test_program_for_pow() -> SourceCodeAndInput { + let mut rng = ThreadRng::default(); + + let base_0 = rng.next_u32(); + let max_exponent = (32_f64 * f64::ln(2_f64) / f64::ln(base_0 as f64)).floor() as u32; + let exp_0 = rng.gen_range(0..max_exponent); + let result_0 = base_0.pow(exp_0); + + let base_1 = rng.next_u32(); + let max_exponent = (32_f64 * f64::ln(2_f64) / f64::ln(base_1 as f64)).floor() as u32; + let exp_1 = rng.gen_range(0..max_exponent); + let result_1 = base_0.pow(exp_1); + + let source_code = format!( + "push {exp_0} push {base_0} pow read_io eq assert \ + push {exp_1} push {base_1} pow read_io eq assert halt" + ); SourceCodeAndInput { source_code, - input: vec![result.into()], + input: vec![result_0.into(), result_1.into()], secret_input: vec![], } } @@ -545,6 +651,7 @@ pub mod triton_vm_tests { pub fn property_based_test_program_for_div() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); + let denominator = rng.next_u32(); let numerator = rng.next_u32(); let quotient = numerator / denominator; @@ -562,8 +669,12 @@ pub mod triton_vm_tests { pub fn property_based_test_program_for_is_u32() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); - let st0 = rng.next_u32(); - SourceCodeAndInput::without_input(&format!("push {st0} is_u32 assert halt")) + let st0_u32 = rng.next_u32(); + let st0_not_u32 = (rng.next_u32() as u64) << 32; + SourceCodeAndInput::without_input(&format!( + "push {st0_u32} is_u32 assert \ + push {st0_not_u32} is_u32 push 0 eq assert halt" + )) } pub fn property_based_test_program_for_random_ram_access() -> SourceCodeAndInput { @@ -695,6 +806,7 @@ pub mod triton_vm_tests { pub fn small_tasm_test_programs() -> Vec { vec![ test_program_for_halt(), + test_hash_nop_nop_lt(), test_program_for_push_pop_dup_swap_nop(), test_program_for_divine(), test_program_for_skiz(), @@ -708,6 +820,12 @@ pub mod triton_vm_tests { test_program_for_eq(), test_program_for_lsb(), test_program_for_split(), + test_program_for_lt(), + test_program_for_and(), + test_program_for_xor(), + test_program_for_log2floor(), + test_program_for_pow(), + test_program_for_div(), test_program_for_xxadd(), test_program_for_xxmul(), test_program_for_xinvert(), @@ -725,24 +843,14 @@ pub mod triton_vm_tests { property_based_test_program_for_lt(), property_based_test_program_for_and(), property_based_test_program_for_xor(), + property_based_test_program_for_log2floor(), + property_based_test_program_for_pow(), property_based_test_program_for_div(), property_based_test_program_for_is_u32(), property_based_test_program_for_random_ram_access(), ] } - /// programs with a cycle count of 150 and upwards - pub fn bigger_tasm_test_programs() -> Vec { - vec![ - test_hash_nop_nop_lt(), - test_program_for_instruction_split(), - test_program_for_lt(), - test_program_for_and(), - test_program_for_xor(), - test_program_for_div(), - ] - } - #[test] fn xxadd_test() { let stdin_words = vec![ From a30c7026ec9b862c96dd156585266e8eb882280c Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 10 Jan 2023 16:00:56 +0100 Subject: [PATCH 43/53] turn `lsb` into a pseudo instruction, making it deterministic --- opcodes.py | 1 - specification/src/instruction-groups.md | 1 - ...ruction-specific-transition-constraints.md | 15 ------ specification/src/instructions.md | 19 ++++--- specification/src/pseudo-instructions.md | 49 +++++++++++++------ triton-opcodes/src/instruction.rs | 29 +++++------ triton-vm/src/state.rs | 8 --- triton-vm/src/table/processor_table.rs | 39 --------------- 8 files changed, 57 insertions(+), 104 deletions(-) diff --git a/opcodes.py b/opcodes.py index 0a199a47..cffa8941 100644 --- a/opcodes.py +++ b/opcodes.py @@ -23,7 +23,6 @@ class Instruction(IntEnum): Mul = auto() Invert = auto() Eq = auto() - Lsb = auto() Split = auto() Lt = auto() And = auto() diff --git a/specification/src/instruction-groups.md b/specification/src/instruction-groups.md index 55107a30..9fe81fd8 100644 --- a/specification/src/instruction-groups.md +++ b/specification/src/instruction-groups.md @@ -56,7 +56,6 @@ A summary of all instructions and which groups they are part of is given in the | `mul` | | x | | x | | | | | | | | | | x | | | `invert` | | x | | x | | | | | | | x | | | | | | `eq` | | x | | x | | | | | | | | | | x | | -| `lsb` | | x | | x | | x | | | | | | | | | | | `split` | | x | | x | | x | | | | | | | | | | | `lt` | | x | | x | | | | | | | | | | x | | | `and` | | x | | x | | | | | | | | | | x | | diff --git a/specification/src/instruction-specific-transition-constraints.md b/specification/src/instruction-specific-transition-constraints.md index e88588ac..be738d78 100644 --- a/specification/src/instruction-specific-transition-constraints.md +++ b/specification/src/instruction-specific-transition-constraints.md @@ -489,21 +489,6 @@ Additionally, it defines the following transition constraints. 1. `hv0 = inverse(rhs - lhs)` if `rhs - lhs ≠ 0`. 1. `hv0 = 0` if `rhs - lhs = 0`. -## Instruction `lsb` - -This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `stack_grows_and_top_2_unconstrained`, and `keep_ram`. -Additionally, it defines the following transition constraints. - -### Description - -1. The least significant bit is a bit -1. The operand decomposes into right-shifted operand and the least significant bit - -### Polynomials - -1. `st0'·(st0' - 1)` -1. `st0 - (2·st1' + st0')` - ## Instruction `split` This instruction uses all constraints defined by [instruction groups](instruction-groups.md) `step_1`, `stack_grows_and_top_2_unconstrained`, and `keep_ram`. diff --git a/specification/src/instructions.md b/specification/src/instructions.md index b3a722f6..64839504 100644 --- a/specification/src/instructions.md +++ b/specification/src/instructions.md @@ -97,16 +97,15 @@ In conjunction with instruction `hash` and `assert_vector`, the instruction `div ## Bitwise Arithmetic on Stack -| Instruction | Opcode | old OpStack | new OpStack | Description | -|:------------|-------:|:------------|:---------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `lsb` | 88 | `_ a` | `_ (a >> 1) (a % 2)` | Bit-shifts `a` to the right by 1 bit and pushes the least significant bit of `a` to the stack. Use with care, preferably through [pseudo instructions](pseudo-instructions.md). | -| `split` | 4 | `_ a` | `_ lo hi` | Decomposes the top of the stack into the lower 32 bits and the upper 32 bits. | -| `lt` | 12 | `_ b a` | `_ a>1 a%2` | Bit-shifts `a` to the right by 1 bit and pushes the least significant bit (_lsb_) of `a` to the stack. Crashes the VM if `a` is not a u32. | ## Pseudo instruction `neg` @@ -17,8 +18,9 @@ Program length: 3. Execution cycle count: 2. ``` -push -1 -mul + // _ a +push -1 // _ a -1 +mul // _ -a ``` ## Pseudo instruction `sub` @@ -28,12 +30,12 @@ Program length: 6. Execution cycle count: 4. ``` -swap 1 -push -1 -mul -add + // _ b a +swap 1 // _ a b +push -1 // _ a b -1 +mul // _ a -b +add // _ a-b ``` - ## Pseudo instruction `is_u32` Program length: 10. @@ -41,11 +43,26 @@ Program length: 10. Execution cycle count: 7. ``` + // _ a dup 0 // _ a a split // _ a lo hi push 0 // _ a lo hi 0 -eq // _ a lo {0,1} -swap 2 // _ {0,1} lo a -eq // _ {0,1} {0,1} -mul // _ {0,1} +eq // _ a lo (hi==0) +swap 2 // _ (hi==0) lo a +eq // _ (hi==0) (lo==a) +mul // _ (hi==0 & lo==a) +``` + +## Pseudo instruction `lsb` + +Program length: 5. + +Execution cycle count: 3. + ``` + // _ a +push 2 // _ a 2 +swap 1 // _ 2 a +div // _ a/2 a%2 +``` + diff --git a/triton-opcodes/src/instruction.rs b/triton-opcodes/src/instruction.rs index 2e2646f3..63412377 100644 --- a/triton-opcodes/src/instruction.rs +++ b/triton-opcodes/src/instruction.rs @@ -92,7 +92,6 @@ pub enum AnInstruction { Eq, // Bitwise arithmetic on stack - Lsb, Split, Lt, And, @@ -148,7 +147,6 @@ impl Display for AnInstruction { Eq => write!(f, "eq"), // Bitwise arithmetic on stack - Lsb => write!(f, "lsb"), Split => write!(f, "split"), Lt => write!(f, "lt"), And => write!(f, "and"), @@ -195,7 +193,6 @@ impl AnInstruction { Mul => Mul, Invert => Invert, Eq => Eq, - Lsb => Lsb, Split => Split, Lt => Lt, And => And, @@ -236,7 +233,6 @@ impl AnInstruction { Mul => 34, Invert => 80, Eq => 42, - Lsb => 88, Split => 4, Lt => 12, And => 20, @@ -244,11 +240,11 @@ impl AnInstruction { Log2Floor => 36, Pow => 44, Div => 52, - XxAdd => 96, - XxMul => 104, - XInvert => 112, + XxAdd => 88, + XxMul => 96, + XInvert => 104, XbMul => 50, - ReadIo => 120, + ReadIo => 112, WriteIo => 58, } } @@ -309,7 +305,6 @@ impl AnInstruction { Mul => Mul, Invert => Invert, Eq => Eq, - Lsb => Lsb, Split => Split, Lt => Lt, And => And, @@ -537,7 +532,6 @@ fn parse_token(token: &str, tokens: &mut SplitWhitespace) -> Result vec![Eq], // Bitwise arithmetic on stack - "lsb" => vec![Lsb], "split" => vec![Split], "lt" => vec![Lt], "and" => vec![And], @@ -559,6 +553,7 @@ fn parse_token(token: &str, tokens: &mut SplitWhitespace) -> Result vec![Push(BFieldElement::one().neg()), Mul], "sub" => vec![Swap(ST1), Push(BFieldElement::one().neg()), Mul, Add], + "lsb" => pseudo_instruction_lsb(), "is_u32" => pseudo_instruction_is_u32(), _ => return Err(anyhow::Error::new(UnknownInstruction(token.to_string()))), @@ -572,6 +567,15 @@ fn parse_token(token: &str, tokens: &mut SplitWhitespace) -> Result Vec> { + // input stack: _ a + vec![ + Push(BFieldElement::new(2)), // _ a 2 + Swap(ST1), // _ 2 a + Div, // _ a/2 a%2 + ] +} + fn pseudo_instruction_is_u32() -> Vec> { // input stack: _ a vec![ @@ -630,7 +634,6 @@ pub fn all_instructions_without_args() -> Vec { Mul, Invert, Eq, - Lsb, Split, Lt, And, @@ -701,7 +704,6 @@ pub fn all_labelled_instructions_with_args() -> Vec { Invert, Split, Eq, - Lsb, XxAdd, XxMul, XInvert, @@ -765,7 +767,7 @@ pub mod sample_programs { call foo return recurse assert halt read_mem write_mem hash divine_sibling assert_vector - add mul invert split eq lsb xxadd xxmul xinvert xbmul + add mul invert split eq xxadd xxmul xinvert xbmul read_io write_io "; @@ -823,7 +825,6 @@ pub mod sample_programs { "invert", "split", "eq", - "lsb", "xxadd", "xxmul", "xinvert", diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 4b7e526d..9adf61ad 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -356,14 +356,6 @@ impl<'pgm> VMState<'pgm> { self.instruction_pointer += 1; } - Lsb => { - let top = self.op_stack.pop()?; - let lsb = BFieldElement::new(top.value() & 1); - self.op_stack.push(BFieldElement::new(top.value() >> 1)); - self.op_stack.push(lsb); - self.instruction_pointer += 1; - } - Split => { let elem = self.op_stack.pop()?; let lo = BFieldElement::new(elem.value() & 0xffff_ffff); diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 98287a2b..d8d37c4e 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -1012,7 +1012,6 @@ impl ExtProcessorTable { (Mul, factory.instruction_mul()), (Invert, factory.instruction_invert()), (Eq, factory.instruction_eq()), - (Lsb, factory.instruction_lsb()), (Split, factory.instruction_split()), (Lt, factory.instruction_lt()), (And, factory.instruction_and()), @@ -2383,33 +2382,6 @@ impl DualRowConstraints { .concat() } - /// 1. The lsb is a bit - /// 2. The operand decomposes into right-shifted operand and the lsb - pub fn instruction_lsb( - &self, - ) -> 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; - - let specific_constraints = vec![lsb_is_a_bit, correct_decomposition]; - [ - specific_constraints, - self.step_1(), - self.grow_stack_and_top_two_elements_unconstrained(), - self.keep_ram(), - ] - .concat() - } - pub fn instruction_split( &self, ) -> Vec< @@ -4745,7 +4717,6 @@ mod constraint_polynomial_tests { Mul => tc.instruction_mul(), Invert => tc.instruction_invert(), Eq => tc.instruction_eq(), - Lsb => tc.instruction_lsb(), Split => tc.instruction_split(), Lt => tc.instruction_lt(), And => tc.instruction_and(), @@ -4937,15 +4908,6 @@ mod constraint_polynomial_tests { ); } - #[test] - fn transition_constraints_for_instruction_lsb_test() { - let test_rows = [get_test_row_from_source_code( - "push 3 lsb assert assert halt", - 1, - )]; - test_constraints_for_rows_with_debug_info(Lsb, &test_rows, &[ST0], &[ST0, ST1]); - } - #[test] fn transition_constraints_for_instruction_lt_test() { let test_rows = [ @@ -5211,7 +5173,6 @@ mod constraint_polynomial_tests { (Mul, factory.instruction_mul()), (Invert, factory.instruction_invert()), (Eq, factory.instruction_eq()), - (Lsb, factory.instruction_lsb()), (Split, factory.instruction_split()), (Lt, factory.instruction_lt()), (And, factory.instruction_and()), From 0a057f846ef9bf47ec5412d67254a9012084165a Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Tue, 10 Jan 2023 23:52:17 +0100 Subject: [PATCH 44/53] U32 Table: improve de-selecting of instructions --- specification/src/processor-table.md | 10 +++--- specification/src/u32-table.md | 43 ++++++++++++++------------ triton-vm/src/state.rs | 6 ++-- triton-vm/src/table/processor_table.rs | 19 +++++++----- triton-vm/src/table/u32_table.rs | 31 ++++++++----------- 5 files changed, 56 insertions(+), 53 deletions(-) diff --git a/specification/src/processor-table.md b/specification/src/processor-table.md index bd2df65b..a915e55a 100644 --- a/specification/src/processor-table.md +++ b/specification/src/processor-table.md @@ -177,12 +177,12 @@ The following constraints apply to every pair of rows. 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. -1. 1. If the current instruction is `split`, then the running product with the U32 Table absorbs `st0` and `st1` in the next row with respect to challenges 🥜 and 🌰 and indeterminate 🧷. +1. 1. If the current instruction is `split`, then the running product with the U32 Table absorbs `st0` and `st1` in the next row and `ci` in the current row with respect to challenges 🥜, 🌰, and 🥑, and indeterminate 🧷. 1. If the current instruction is `lt`, `and`, `xor`, or `pow`, then the running product with the U32 Table absorbs `st0`, `st1`, and `ci` in the current row and `st0` in the next row with respect to challenges 🥜, 🌰, 🥑, and 🥕, and indeterminate 🧷. 1. If the current instruction is `log2floor`, then the running product with the U32 Table absorbs `st0` and `ci` in the current row and `st0` in the next row with respect to challenges 🥜, 🥑, and 🥕, and indeterminate 🧷. 1. If the current instruction is `div`, then the running product with the U32 Table absorbs both - 1. `st0` in the next row and `st1` and `ci` in the current row as well as the constant `1` with respect to challenges 🥜, 🌰, 🥑, and 🥕, and indeterminate 🧷. - 1. `st0` in the current row and `st1` in the next row with respect to challenges 🥜 and 🌰, and indeterminate 🧷. + 1. `st0` in the next row and `st1` in the current row as well as the constants `opcode(lt)` and `1` with respect to challenges 🥜, 🌰, 🥑, and 🥕, and indeterminate 🧷. + 1. `st0` in the current row and `st1` in the next row as well as `opcode(split)` with respect to challenges 🥜, 🌰, and 🥑, and indeterminate 🧷. 1. Else, _i.e._, if the current instruction is not a u32 instruction, the running product with the U32 Table remains unchanged. 1. The unique inverse column `invu'` holds the inverse-or-zero of the difference of consecutive `cjd`'s, if `cjd'` is nonzero. (Results in 2 constraint polynomials.) @@ -203,13 +203,13 @@ The following constraints apply to every pair of rows. 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')` -1. 1. `split_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0' - 🌰·st1'))` +1. 1. `split_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0' - 🌰·st1' - 🥑·ci))` 1. `+ lt_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🌰·st1 - 🥑·ci - 🥕·st0'))` 1. `+ and_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🌰·st1 - 🥑·ci - 🥕·st0'))` 1. `+ xor_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🌰·st1 - 🥑·ci - 🥕·st0'))` 1. `+ pow_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🌰·st1 - 🥑·ci - 🥕·st0'))` 1. `+ log2floor_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0 - 🥑·ci - 🥕·st0'))` - 1. `+ div_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0' - 🌰·st1 - 🥑·ci - 🥕)·(🧷 - 🥜·st0 - 🌰·st1'))` + 1. `+ div_deselector·(RunningProductU32Table' - RunningProductU32Table·(🧷 - 🥜·st0' - 🌰·st1 - 🥑·opcode(lt) - 🥕)·(🧷 - 🥜·st0 - 🌰·st1' - 🥑·opcode(split)))` 1. `+ (1 - ib2)·(RunningProductU32Table' - RunningProductU32Table)` 1. `invu'·(invu'·(cjd' - cjd) - 1)·cjd'` 1. `(cjd' - cjd)·(invu'·(cjd' - cjd) - 1)·cjd'` diff --git a/specification/src/u32-table.md b/specification/src/u32-table.md index 2c7ea5ec..dd57e89e 100644 --- a/specification/src/u32-table.md +++ b/specification/src/u32-table.md @@ -64,37 +64,40 @@ inserting a row with `Bits` equal to 33 makes it impossible to generate a proof The U32 Table has 1 extension column, `RunningProductProcessor`. It corresponds to the Permutation Argument with the [Processor Table](processor-table.md), establishing that whenever the processor executes a u32 instruction, the following holds: -- the processor's stack register `st0` is copied into `LHS`, -- the processor's stack register `st1` is copied into `RHS`, -- the processor's `CI` register is copied into `CI`, and +- the processor's requested left-hand side is copied into `LHS`, +- the processor's requested right-hand side is copied into `RHS`, +- the processor's requested u32 instruction is copied into `CI`, and - the result, condition to `CI`, is copied to the processor. More concretely, the result to be copied to the processor is -- `LT` if `CI` is opcode(`lt`), -- `AND` if `CI` is opcode(`and`), -- `XOR` if `CI` is opcode(`xor`), -- `Log2Floor` if `CI` is opcode(`log_2_floor`), -- `Pow` if `CI` is opcode(`pow`), -- `LT` if `CI` is opcode(`div`), and -- 0 if `CI` is 0. - -The instruction `div` uses the U32 Table to ensure that the remainder `r` is smaller than the divisor `d`. -Hence, the result of `LT` is used. -The instruction `div` _also_ uses the U32 Table to ensure that the numerator `n` and the quotient `q` are u32. -For this range check, happening in its independent section, no result is required. -The instruction `split` also uses the U32 Table for range checking only, _i.e._, to ensure that the instruction's resulting “high bits” and “low bits” each fit in a u32. +- 0 if `CI` is the opcode of `split`, and +- the corresponding, equally-named column if `CI` is the opcode of `lt`, `and`, `xor`, `log_2_floor`, or `pow`. To conditionally copy the required result to the processor, instruction de-selectors (comparable to the ones in the Processor Table) are used. -Concretely, with `u32_instructions = {lt, and, xor, log_2_floor, pow, div}`, the following (normalized) deselector for instruction `lt` is defined as: +Concretely, with `u32_instructions = {split, lt, and, xor, log_2_floor, pow}`, the following (normalized) deselector for instruction `lt` is defined as: $$ -\frac{\texttt{CI}}{\texttt{opcode}(\texttt{lt})}\cdot\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{lt}}} \frac{\texttt{CI} - \texttt{opcode}(\texttt{i})}{\texttt{opcode}(\texttt{lt}) - \texttt{opcode}(\texttt{i})} +\prod_{\substack{\texttt{i} \in \texttt{u32\_instructions}\\\texttt{i} \neq \texttt{lt}}} \frac{\texttt{CI} - \texttt{opcode}(\texttt{i})}{\texttt{opcode}(\texttt{lt}) - \texttt{opcode}(\texttt{i})} $$ -The deselectors `and_deselector`, `xor_deselector`, `log_2_floor_deselector`, `pow_deselector`, and `div_deselector` are defined accordingly. +The deselectors `and_deselector`, `xor_deselector`, `log_2_floor_deselector`, and `pow_deselector` are defined accordingly. Throughout the next sections, the alias `Result` corresponds to the polynomial -`LT·lt_deselector + AND·and_deselector + XOR·xor_deselector + Log2Floor·log_2_floor_deselector + Pow·pow_deselector + LT·div_deselector`. +`LT·lt_deselector + AND·and_deselector + XOR·xor_deselector + Log2Floor·log_2_floor_deselector + Pow·pow_deselector`. + +Note that no `split_deselector` is used. +The instruction `split` uses the U32 Table for range checking only. +Concretely, the U32 Table ensures that the instruction `split`'s resulting “high bits” and “low bits” each fit in a u32. + +Instruction `div` also uses the U32 Table, even though the U32 Table is not “aware” of instruction `div`. +Instead, executing `div` creates 2 independent sections in the U32 Table: + +- One section to ensure that the remainder `r` is smaller than the divisor `d`. +The processor requests the result of `lt` by setting the U32 Table's `CI` register to the opcode of `lt`. +This also guarantees that `r` and `d` each fit in a u32. +- One section to ensure that the quotient `q` and the numerator `n` each fit in a u32. +The processor needs no result, only the range checking capabilities, like for instruction `split`. +Consequently, the processor sets the U32 Table's `CI` register to the opcode of `split`. ## Padding diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 9adf61ad..06d42c33 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -363,7 +363,7 @@ impl<'pgm> VMState<'pgm> { self.op_stack.push(lo); self.op_stack.push(hi); self.instruction_pointer += 1; - let u32_table_entry = (Instruction::Halt, hi, lo); + let u32_table_entry = (Instruction::Split, hi, lo); vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } @@ -434,8 +434,8 @@ impl<'pgm> VMState<'pgm> { self.op_stack.push(quot); self.op_stack.push(rem); self.instruction_pointer += 1; - let u32_table_entry_0 = (Instruction::Div, rem, denom); - let u32_table_entry_1 = (Instruction::Halt, numer, quot); + let u32_table_entry_0 = (Instruction::Lt, rem, denom); + let u32_table_entry_1 = (Instruction::Split, numer, quot); vm_output = Some(VMOutput::U32TableEntries(vec![ u32_table_entry_0, u32_table_entry_1, diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index d8d37c4e..21f21674 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -296,7 +296,8 @@ impl ProcessorTable { if previously_current_instruction == Instruction::Split.opcode_b() { u32_table_running_product *= challenges.u32_table_perm_indeterminate - current_row[ST0.base_table_index()] * challenges.u32_table_lhs_weight - - current_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight; + - current_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight + - prev_row[CI.base_table_index()] * challenges.u32_table_ci_weight; } if previously_current_instruction == Instruction::Lt.opcode_b() || previously_current_instruction == Instruction::And.opcode_b() @@ -319,11 +320,12 @@ impl ProcessorTable { u32_table_running_product *= challenges.u32_table_perm_indeterminate - current_row[ST0.base_table_index()] * challenges.u32_table_lhs_weight - prev_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight - - prev_row[CI.base_table_index()] * challenges.u32_table_ci_weight + - Instruction::Lt.opcode_b() * challenges.u32_table_ci_weight - BFieldElement::one() * challenges.u32_table_result_weight; u32_table_running_product *= challenges.u32_table_perm_indeterminate - prev_row[ST0.base_table_index()] * challenges.u32_table_lhs_weight - - current_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight; + - current_row[ST1.base_table_index()] * challenges.u32_table_rhs_weight + - Instruction::Split.opcode_b() * challenges.u32_table_ci_weight; } } @@ -4270,7 +4272,8 @@ impl DualRowConstraints { let split_factor = indeterminate.clone() - lhs_weight.clone() * self.st0_next() - - rhs_weight.clone() * self.st1_next(); + - rhs_weight.clone() * self.st1_next() + - ci_weight.clone() * self.ci(); let binop_factor = indeterminate.clone() - lhs_weight.clone() * self.st0() - rhs_weight.clone() * self.st1() @@ -4283,10 +4286,12 @@ impl DualRowConstraints { let div_factor_for_lt = indeterminate.clone() - lhs_weight.clone() * self.st0_next() - rhs_weight.clone() * self.st1() - - ci_weight * self.ci() + - ci_weight.clone() * self.constant_b(Instruction::Lt.opcode_b()) - result_weight; - let div_factor_for_range_check = - indeterminate - lhs_weight * self.st0() - rhs_weight * self.st1_next(); + let div_factor_for_range_check = indeterminate + - lhs_weight * self.st0() + - rhs_weight * self.st1_next() + - ci_weight * self.constant_b(Instruction::Split.opcode_b()); let split_summand = split_deselector * (rp_next.clone() - rp.clone() * split_factor); let lt_summand = lt_deselector * (rp_next.clone() - rp.clone() * binop_factor.clone()); diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index cd3d2344..083cf468 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -75,26 +75,24 @@ impl ExtU32Table { let normalized_instruction_deselector = |instruction_to_select: Instruction| { let instructions_to_deselect = [ + Instruction::Split, Instruction::Lt, Instruction::And, Instruction::Xor, Instruction::Log2Floor, Instruction::Pow, - Instruction::Div, ] .into_iter() .filter(|&instruction| instruction != instruction_to_select) .collect_vec(); - let deselect_0 = ci.clone(); let deselector = instructions_to_deselect .iter() .map(|&instruction| ci.clone() - circuit_builder.b_constant(instruction.opcode_b())) - .fold(deselect_0, ConstraintCircuitMonad::mul); - let normalize_deselected_0 = instruction_to_select.opcode_b(); + .fold(one.clone(), ConstraintCircuitMonad::mul); let normalizing_factor = instructions_to_deselect .iter() - .fold(normalize_deselected_0, |acc, instr| { + .fold(BFieldElement::one(), |acc, instr| { acc * (instruction_to_select.opcode_b() - instr.opcode_b()) }) .inverse(); @@ -110,9 +108,8 @@ impl ExtU32Table { + normalized_instruction_deselector(Instruction::Log2Floor) * circuit_builder.input(BaseRow(Log2Floor.master_base_table_index())) + normalized_instruction_deselector(Instruction::Pow) - * circuit_builder.input(BaseRow(Pow.master_base_table_index())) - + normalized_instruction_deselector(Instruction::Div) - * circuit_builder.input(BaseRow(LT.master_base_table_index())); + * circuit_builder.input(BaseRow(Pow.master_base_table_index())); + // + normalized_instruction_deselector(Instruction::Split) * 0, which is superfluous let initial_factor = challenge(ProcessorPermIndeterminate) - challenge(LhsWeight) * lhs @@ -239,28 +236,26 @@ impl ExtU32Table { let normalized_instruction_deselector = |instruction_to_select: Instruction| { let instructions_to_deselect = [ + Instruction::Split, Instruction::Lt, Instruction::And, Instruction::Xor, Instruction::Log2Floor, Instruction::Pow, - Instruction::Div, ] .into_iter() .filter(|&instruction| instruction != instruction_to_select) .collect_vec(); - let deselect_0 = ci_next.clone(); let deselector = instructions_to_deselect .iter() .map(|&instruction| { ci_next.clone() - circuit_builder.b_constant(instruction.opcode_b()) }) - .fold(deselect_0, ConstraintCircuitMonad::mul); - let normalize_deselected_0 = instruction_to_select.opcode_b(); + .fold(one.clone(), ConstraintCircuitMonad::mul); let normalizing_factor = instructions_to_deselect .iter() - .fold(normalize_deselected_0, |acc, instr| { + .fold(BFieldElement::one(), |acc, instr| { acc * (instruction_to_select.opcode_b() - instr.opcode_b()) }) .inverse(); @@ -269,8 +264,6 @@ impl ExtU32Table { let result_next = normalized_instruction_deselector(Instruction::Lt) * circuit_builder.input(NextBaseRow(LT.master_base_table_index())) - + normalized_instruction_deselector(Instruction::Div) - * circuit_builder.input(NextBaseRow(LT.master_base_table_index())) + normalized_instruction_deselector(Instruction::And) * circuit_builder.input(NextBaseRow(AND.master_base_table_index())) + normalized_instruction_deselector(Instruction::Xor) @@ -279,6 +272,7 @@ impl ExtU32Table { * circuit_builder.input(NextBaseRow(Log2Floor.master_base_table_index())) + normalized_instruction_deselector(Instruction::Pow) * circuit_builder.input(NextBaseRow(Pow.master_base_table_index())); + // + normalized_instruction_deselector(Instruction::Split) * 0, which is superfluous let if_copy_flag_next_is_1_then_lhs_is_0 = copy_flag_next.clone() * lhs.clone(); let if_copy_flag_next_is_1_then_rhs_is_0 = copy_flag_next.clone() * rhs.clone(); @@ -556,14 +550,15 @@ impl U32Table { let current_instruction = current_row[CI.base_table_index()] .value() .try_into() - .unwrap_or(Instruction::Split); + .expect("Unknown instruction"); let result = match current_instruction { - Instruction::Lt | Instruction::Div => current_row[LT.base_table_index()], + Instruction::Split => BFieldElement::zero(), + Instruction::Lt => current_row[LT.base_table_index()], Instruction::And => current_row[AND.base_table_index()], Instruction::Xor => current_row[XOR.base_table_index()], Instruction::Log2Floor => current_row[Log2Floor.base_table_index()], Instruction::Pow => current_row[Pow.base_table_index()], - _ => BFieldElement::zero(), + ci => panic!("Instruction in U32 Table must be a u32 instruction. Got {ci}"), }; let compressed_row = challenges.ci_weight * current_row[CI.base_table_index()] From 11dfb2536f41de463d7d111acb4d90c9a04374ed Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 11 Jan 2023 00:35:24 +0100 Subject: [PATCH 45/53] update cheatsheet: instructions --- specification/cheatsheet.pdf | Bin 120476 -> 120668 bytes specification/cheatsheet.tex | 82 +++++++++++------------- specification/src/instruction-groups.md | 2 +- specification/src/instructions.md | 26 ++++---- 4 files changed, 52 insertions(+), 58 deletions(-) diff --git a/specification/cheatsheet.pdf b/specification/cheatsheet.pdf index 408a5a8dfdd33bb4375f188c87a8ca4ca48910b8..983a06a2631922ce5704f58ca71d4ff2e68c275e 100644 GIT binary patch delta 53832 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

m&JmbZUR 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 QzkEw-3FP8J=&Az$3kidY$p8QV diff --git a/specification/cheatsheet.tex b/specification/cheatsheet.tex index 2d41bb1c..cdf047a1 100644 --- a/specification/cheatsheet.tex +++ b/specification/cheatsheet.tex @@ -13,16 +13,6 @@ \usetikzlibrary{tikzmark} -% https://tex.stackexchange.com/questions/198658/ -\makeatletter -\newcommand\incircbin -{\mathpalette\@incircbin} -\newcommand\@incircbin[2] -{\mathbin{\ooalign{\hidewidth$#1#2$\hidewidth\crcr$#1\ovoid$}}} -\newcommand{\ocol}{\incircbin{\raisebox{0.4pt}{:}}} -\newcommand{\shrinkstack}[1]{\tikzmarknode[fill=instr-shrink-stack,circle,inner sep=-1pt]{circ}{#1}} -\makeatother - \geometry{a4paper, total={170mm, 257mm}, left=20mm} \linespread{1.9} @@ -42,47 +32,51 @@ \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{instr-u32}{teal!20} \colorlet{row1}{white} \colorlet{row2}{gray!8} -\newcommand{\ssominus}{ - \shrinkstack{\ensuremath{\ominus}} -} - \begin{document} \pagestyle{empty} +{ +\renewcommand{\arraystretch}{0.93} \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{\_} + \texttt{ 2} & $\ominus$ & \texttt{pop} & \texttt{\_ st$_0$} & \texttt{\_} \\ + \texttt{ 1} & $\oplus$ & \tcbox[colback=instr-arg]{\texttt{push + a}} & \texttt{\_} & \texttt{\_ a} \\ + \texttt{ 8} & $\oplus$ & \texttt{divine} & \texttt{\_} & \texttt{\_ a} \\ + \texttt{ 9} & $\oplus$ & \tcbox[colback=instr-arg]{\texttt{dup + i}} & \texttt{\_ st$_{15}$ $\dots$ st$_0$} & \texttt{\_ st$_{15}$ $\dots$ st$_0$ st$_i$} \\ + \texttt{ 17} & $\ovoid^{16}$ & \tcbox[colback=instr-arg]{\texttt{swap + i}} & \texttt{\_ $\dots$ st$_i$ $\dots$ st$_0$} & \texttt{\_ $\dots$ st$_0$ $\dots$ st$_i$} \\ + \texttt{ 16} & $\ovoid$ & \texttt{nop} & \texttt{\_} & \texttt{\_} \\ + \texttt{ 10} & $\ominus$ & \tcbox[colback=instr-jsp]{\texttt{skiz}} & \texttt{\_ st$_0$} & \texttt{\_} \\ + \texttt{ 25} & $\ovoid$ & \splitbox{instr-jsp}{instr-arg}{\texttt{call + d}} & \texttt{\_} & \texttt{\_} \\ + \texttt{ 24} & $\ovoid$ & \tcbox[colback=instr-jsp]{\texttt{return}} & \texttt{\_} & \texttt{\_} \\ + \texttt{ 32} & $\ovoid$ & \tcbox[colback=instr-jsp]{\texttt{recurse}} & \texttt{\_} & \texttt{\_} \\ + \texttt{ 18} & $\ominus$ & \texttt{assert} & \texttt{\_ st$_0$} & \texttt{\_} \\ + \texttt{ 0} & $\ovoid$ & \texttt{halt} & \texttt{\_} & \texttt{\_} \\ + \texttt{ 40} & $\ovoid^1$ & \tcbox[colback=instr-mem]{\texttt{read\_mem}} & \texttt{\_ addr st$_0$} & \texttt{\_ addr val} \\ + \texttt{ 48} & $\ovoid$ & \tcbox[colback=instr-mem]{\texttt{write\_mem}} & \texttt{\_ addr val} & \texttt{\_ addr val} \\ + \texttt{ 56} & $\ovoid^{10}$ & \texttt{hash} & \texttt{\_ st$_9$ $\!\!\dots\!\!$ st$_0$} & \texttt{\_ d$_4$ $\!\!\dots\!\!$ d$_0$ 0 $\!\!\dots\!\!$ 0} \\ + \texttt{ 64} & $\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{ 72} & $\ovoid$ & \texttt{assert\_vector} & \texttt{\_} & \texttt{\_} \\ + \texttt{ 26} & $\ominus^1$ & \texttt{add} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ sum} \\ + \texttt{ 34} & $\ominus^1$ & \texttt{mul} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ prod} \\ + \texttt{ 80} & $\ovoid^1$ & \texttt{invert} & \texttt{\_ st$_0$} & \texttt{\_ st$_0^{-1}$} \\ + \texttt{ 42} & $\ominus^1$ & \texttt{eq} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ (st$_0$==st$_1$)} \\ + \texttt{ 4} & $\oplus^2$ & \tcbox[colback=instr-u32]{\texttt{split}} & \texttt{\_ st$_0$} & \texttt{\_ lo hi} \\ + \texttt{ 12} & $\ominus$ & \tcbox[colback=instr-u32]{\texttt{lt}} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ (st$_0$ Date: Wed, 11 Jan 2023 10:29:23 +0100 Subject: [PATCH 46/53] update cheatsheet: table widths, number of constraints, etc. --- specification/cheatsheet.pdf | Bin 120668 -> 121590 bytes specification/cheatsheet.tex | 70 +++++++++++++--------------- triton-vm/src/stark.rs | 1 + triton-vm/src/table/master_table.rs | 7 +++ 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/specification/cheatsheet.pdf b/specification/cheatsheet.pdf index 983a06a2631922ce5704f58ca71d4ff2e68c275e..50874bbab2d793355d77194665c6692964e4672a 100644 GIT binary patch delta 49234 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

YTdrR-IpXm6w#SMO)&k=BgXjzR*cIcb3Imxbx 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 z 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 | {:>9} | {:>10} |", + "U32Table", + u32_table::BASE_WIDTH, + u32_table::EXT_WIDTH, + u32_table::FULL_WIDTH + ); println!("| | | | |"); println!( "| Sum | {:>10} | {:>9} | {:>10} |", From ba7b75aafec1ac4d55cae9b8b89c258cb24d3a2b Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 11 Jan 2023 15:56:19 +0100 Subject: [PATCH 47/53] add test for executing many u32 instructions Also fix the bug it unveiled. --- triton-vm/src/shared_tests.rs | 21 +++++++++++++++++++++ triton-vm/src/stark.rs | 30 ++++++++++++++++++++++++++++++ triton-vm/src/state.rs | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/triton-vm/src/shared_tests.rs b/triton-vm/src/shared_tests.rs index 12d82d32..192b95b8 100644 --- a/triton-vm/src/shared_tests.rs +++ b/triton-vm/src/shared_tests.rs @@ -231,6 +231,27 @@ pub const FIB_SHOOTOUT: &str = " return "; +pub const MANY_U32_INSTRUCTIONS: &str = " + push 1311768464867721216 split + push 13387 push 78810 lt + push 5 push 7 pow + push 69584 push 6796 xor + push 64972 push 3915 and + push 98668 push 15787 div + push 15787 push 98668 div + push 98141 push 7397 and + push 67749 push 60797 lt + push 49528 split + push 53483 lsb + push 79655 is_u32 + push 60615 log_2_floor + push 13 push 5 pow + push 86323 push 37607 xor + push 32374 push 20636 pow + push 97416 log_2_floor + push 14392 push 31589 div + halt"; + pub const FIB_FIXED_7_LT: &str = " push 0 push 1 diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index 16e8d676..c5da84dc 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -2018,6 +2018,36 @@ pub(crate) mod triton_stark_tests { } } + #[test] + fn constraints_evaluate_to_zero_on_many_u32_operations_test() { + let many_u32_instructions = SourceCodeAndInput::without_input(MANY_U32_INSTRUCTIONS); + triton_table_constraints_evaluate_to_zero(many_u32_instructions); + } + + #[test] + fn triton_prove_verify_many_u32_operations_test() { + let mut profiler = Some(TritonProfiler::new("Prove Many U32 Ops")); + let (stark, proof) = + parse_simulate_prove(MANY_U32_INSTRUCTIONS, vec![], vec![], &mut profiler); + let mut profiler = profiler.unwrap(); + profiler.finish(); + + let result = stark.verify(proof, &mut None); + if let Err(e) = result { + panic!("The Verifier is unhappy! {}", e); + } + assert!(result.unwrap()); + + println!( + "{}", + profiler.report( + None, + Some(stark.claim.padded_height), + Some(stark.fri.domain.length) + ) + ); + } + #[test] #[ignore = "stress test"] fn prove_fib_successively_larger() { diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 06d42c33..45b0f6ef 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -416,7 +416,7 @@ impl<'pgm> VMState<'pgm> { Pow => { let lhs = self.op_stack.pop()?; let rhs = self.op_stack.pop()?; - let pow = BFieldElement::new(lhs.value().pow(rhs.value() as u32)); + let pow = lhs.mod_pow(rhs.value()); self.op_stack.push(pow); self.instruction_pointer += 1; let u32_table_entry = (Instruction::Pow, lhs, rhs); From f081447e8f34de4222cfdb72d807569b145e10f5 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 11 Jan 2023 16:58:09 +0100 Subject: [PATCH 48/53] add negative test for executing u32 instruction on non-u32 --- triton-vm/src/stark.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index c5da84dc..6805b048 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -876,6 +876,8 @@ pub(crate) mod triton_stark_tests { use itertools::izip; use ndarray::Array1; use num_traits::Zero; + use rand::prelude::ThreadRng; + use rand_core::RngCore; use triton_opcodes::instruction::AnInstruction; use triton_opcodes::program::Program; @@ -2070,4 +2072,17 @@ pub(crate) mod triton_stark_tests { } } } + + #[test] + #[should_panic(expected = "multiplicative inverse of zero")] + pub fn negative_log_2_floor_test() { + let mut rng = ThreadRng::default(); + let st0 = (rng.next_u32() as u64) << 32; + + let source_code = format!("push {} log_2_floor halt", st0); + let (stark, proof) = parse_simulate_prove(&source_code, vec![], vec![], &mut None); + let result = stark.verify(proof, &mut None); + assert!(result.is_ok()); + assert!(result.unwrap()); + } } From bd46a654f33dc5a503595847633b02bbed14cadd Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 11 Jan 2023 19:16:56 +0100 Subject: [PATCH 49/53] make VM crash and proving impossible when executing `log_2_floor` on 0 --- specification/src/instructions.md | 2 +- specification/src/u32-table.md | 35 ++++++++++++++------------ triton-vm/src/error.rs | 5 ++++ triton-vm/src/state.rs | 8 +++--- triton-vm/src/table/processor_table.rs | 1 - triton-vm/src/table/u32_table.rs | 11 +++++++- triton-vm/src/vm.rs | 3 +-- 7 files changed, 40 insertions(+), 25 deletions(-) diff --git a/specification/src/instructions.md b/specification/src/instructions.md index 11c2546e..2a183919 100644 --- a/specification/src/instructions.md +++ b/specification/src/instructions.md @@ -103,7 +103,7 @@ In conjunction with instruction `hash` and `assert_vector`, the instruction `div | `lt` | 12 | `_ b a` | `_ a= `RHS` is definitely known in the current row, @@ -139,6 +139,7 @@ Both types of challenges are X-field elements, _i.e._, elements of $\mathbb{F}_{ 1. If `CopyFlag` is 0 and `LHS` is 0 and `RHS` is 0, then `AND` is 0. 1. If `CopyFlag` is 0 and `LHS` is 0 and `RHS` is 0, then `XOR` is 0. 1. If `CopyFlag` is 0 and `LHS` is 0 and `RHS` is 0, then `Pow` is 1. +1. If `CopyFlag` is 1 and `LHS` is 0 and `CI` is the opcode of `log_2_floor`, the VM crashes. 1. If `LHS` is 0, then `Log2Floor` is -1. Written in Disjunctive Normal Form, the same constraints can be expressed as: @@ -155,6 +156,7 @@ Written in Disjunctive Normal Form, the same constraints can be expressed as: 1. `CopyFlag` is 1 or `LHS` is not 0 or `RHS` is not 0 or `AND` is 0. 1. `CopyFlag` is 1 or `LHS` is not 0 or `RHS` is not 0 or `XOR` is 0. 1. `CopyFlag` is 1 or `LHS` is not 0 or `RHS` is not 0 or `Pow` is 1. +1. `CopyFlag` is 0 or `LHS` is not 0 or `CI` is the opcode of `split`, `lt`, `and`, `xor`, or `pow`. 1. `LHS` is not 0 or `Log2Floor` is -1. ### Consistency Constraints as Polynomials @@ -171,6 +173,7 @@ Written in Disjunctive Normal Form, the same constraints can be expressed as: 1. `(CopyFlag - 1)·(1 - LHS·LhsInv)·(1 - RHS·RhsInv)·AND` 1. `(CopyFlag - 1)·(1 - LHS·LhsInv)·(1 - RHS·RhsInv)·XOR` 1. `(CopyFlag - 1)·(1 - LHS·LhsInv)·(1 - RHS·RhsInv)·(Pow - 1)` +1. `CopyFlag·(1 - LHS·LhsInv)·(CI - opcode(split))·(CI - opcode(lt))·(CI - opcode(and))·(CI - opcode(xor))·(CI - opcode(pow))` 1. `(1 - LHS·LhsInv)·(Log2Floor + 1)` ## Transition Constraints diff --git a/triton-vm/src/error.rs b/triton-vm/src/error.rs index db7f562b..126b44b2 100644 --- a/triton-vm/src/error.rs +++ b/triton-vm/src/error.rs @@ -15,6 +15,7 @@ pub enum InstructionError { JumpStackTooShallow, AssertionFailed(usize, u32, BFieldElement), InverseOfZero, + LogarithmOfZero, RunawayInstructionArg, UngracefulTermination, FailedU32Conversion(BFieldElement), @@ -51,6 +52,10 @@ impl Display for InstructionError { write!(f, "0 does not have a multiplicative inverse") } + LogarithmOfZero => { + write!(f, "The logarithm of 0 does not exist") + } + RunawayInstructionArg => { write!( f, diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 45b0f6ef..c2b3cf03 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -402,10 +402,10 @@ impl<'pgm> VMState<'pgm> { Log2Floor => { let lhs = self.op_stack.pop()?; - let l2f = match lhs.is_zero() { - true => -BFieldElement::one(), - false => BFieldElement::new(log_2_floor(lhs.value() as u128)), - }; + if lhs.is_zero() { + return vm_err(LogarithmOfZero); + } + let l2f = BFieldElement::new(log_2_floor(lhs.value() as u128)); self.op_stack.push(l2f); self.instruction_pointer += 1; let rhs = BFieldElement::zero(); diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 21f21674..3c60fd86 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -4945,7 +4945,6 @@ mod constraint_polynomial_tests { #[test] fn transition_constraints_for_instruction_log2floor_test() { let test_rows = [ - get_test_row_from_source_code("push 0 log_2_floor push -1 eq assert halt", 1), get_test_row_from_source_code("push 1 log_2_floor push 0 eq assert halt", 1), get_test_row_from_source_code("push 2 log_2_floor push 1 eq assert halt", 1), get_test_row_from_source_code("push 3 log_2_floor push 1 eq assert halt", 1), diff --git a/triton-vm/src/table/u32_table.rs b/triton-vm/src/table/u32_table.rs index 083cf468..7bb023b0 100644 --- a/triton-vm/src/table/u32_table.rs +++ b/triton-vm/src/table/u32_table.rs @@ -144,6 +144,7 @@ impl ExtU32Table { let bits = circuit_builder.input(BaseRow(Bits.master_base_table_index())); let bits_minus_33_inv = circuit_builder.input(BaseRow(BitsMinus33Inv.master_base_table_index())); + let ci = circuit_builder.input(BaseRow(CI.master_base_table_index())); let lhs = circuit_builder.input(BaseRow(LHS.master_base_table_index())); let rhs = circuit_builder.input(BaseRow(RHS.master_base_table_index())); let lt = circuit_builder.input(BaseRow(LT.master_base_table_index())); @@ -168,7 +169,7 @@ impl ExtU32Table { let rhs_is_0_or_rhs_inverse_is_the_inverse_of_rhs = rhs.clone() * (one.clone() - rhs.clone() * rhs_inv.clone()); let copy_flag_is_0_or_lhs_copy_is_lhs = copy_flag.clone() * (lhs_copy - lhs.clone()); - let padding_row = (one.clone() - copy_flag) + let padding_row = (one.clone() - copy_flag.clone()) * (one.clone() - lhs.clone() * lhs_inv.clone()) * (one.clone() - rhs * rhs_inv); let padding_row_or_lt_is_2 = @@ -176,6 +177,13 @@ impl ExtU32Table { let padding_row_or_and_is_0 = padding_row.clone() * and; let padding_row_or_xor_is_0 = padding_row.clone() * xor; let padding_row_or_pow_is_1 = padding_row * (one.clone() - pow); + let copy_flag_is_0_or_lhs_is_not_0_or_ci_is_not_log2floor = copy_flag + * (one.clone() - lhs.clone() * lhs_inv.clone()) + * (ci.clone() - circuit_builder.b_constant(Instruction::Split.opcode_b())) + * (ci.clone() - circuit_builder.b_constant(Instruction::Lt.opcode_b())) + * (ci.clone() - circuit_builder.b_constant(Instruction::And.opcode_b())) + * (ci.clone() - circuit_builder.b_constant(Instruction::Xor.opcode_b())) + * (ci - circuit_builder.b_constant(Instruction::Pow.opcode_b())); let lhs_is_not_zero_or_log2floor_is_negative_1 = (one.clone() - lhs * lhs_inv) * (log2floor + one); @@ -192,6 +200,7 @@ impl ExtU32Table { padding_row_or_and_is_0, padding_row_or_xor_is_0, padding_row_or_pow_is_1, + copy_flag_is_0_or_lhs_is_not_0_or_ci_is_not_log2floor, lhs_is_not_zero_or_log2floor_is_negative_1, ] .map(|circuit| circuit.consume()) diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index 93e49267..f1b8b6c4 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -569,8 +569,7 @@ pub mod triton_vm_tests { pub fn test_program_for_log2floor() -> SourceCodeAndInput { SourceCodeAndInput::without_input( - "push 0 log_2_floor push -1 eq assert \ - push 1 log_2_floor push 0 eq assert \ + "push 1 log_2_floor push 0 eq assert \ push 2 log_2_floor push 1 eq assert \ push 3 log_2_floor push 1 eq assert \ push 4 log_2_floor push 2 eq assert \ From 2d2a58fae7a344d489cd0858594d7a3c912fe5d4 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Wed, 11 Jan 2023 20:36:52 +0100 Subject: [PATCH 50/53] improve error handling and align the VM runtime to the STARK prover Concretely, make it impossible to run a program for which correct execution cannot be proven. --- triton-vm/src/error.rs | 5 ++ triton-vm/src/stark.rs | 12 +++- triton-vm/src/state.rs | 77 ++++++++++++++++---------- triton-vm/src/table/processor_table.rs | 2 +- 4 files changed, 64 insertions(+), 32 deletions(-) diff --git a/triton-vm/src/error.rs b/triton-vm/src/error.rs index 126b44b2..781f6bf6 100644 --- a/triton-vm/src/error.rs +++ b/triton-vm/src/error.rs @@ -15,6 +15,7 @@ pub enum InstructionError { JumpStackTooShallow, AssertionFailed(usize, u32, BFieldElement), InverseOfZero, + DivisionByZero, LogarithmOfZero, RunawayInstructionArg, UngracefulTermination, @@ -52,6 +53,10 @@ impl Display for InstructionError { write!(f, "0 does not have a multiplicative inverse") } + DivisionByZero => { + write!(f, "Division by 0 is impossible") + } + LogarithmOfZero => { write!(f, "The logarithm of 0 does not exist") } diff --git a/triton-vm/src/stark.rs b/triton-vm/src/stark.rs index 6805b048..856e6ac4 100644 --- a/triton-vm/src/stark.rs +++ b/triton-vm/src/stark.rs @@ -2074,7 +2074,7 @@ pub(crate) mod triton_stark_tests { } #[test] - #[should_panic(expected = "multiplicative inverse of zero")] + #[should_panic(expected = "Failed to convert BFieldElement")] pub fn negative_log_2_floor_test() { let mut rng = ThreadRng::default(); let st0 = (rng.next_u32() as u64) << 32; @@ -2085,4 +2085,14 @@ pub(crate) mod triton_stark_tests { assert!(result.is_ok()); assert!(result.unwrap()); } + + #[test] + #[should_panic(expected = "The logarithm of 0 does not exist")] + pub fn negative_log_2_floor_of_0_test() { + let source_code = "push 0 log_2_floor halt"; + let (stark, proof) = parse_simulate_prove(source_code, vec![], vec![], &mut None); + let result = stark.verify(proof, &mut None); + assert!(result.is_ok()); + assert!(result.unwrap()); + } } diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index c2b3cf03..2b4e9adc 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -368,74 +368,91 @@ impl<'pgm> VMState<'pgm> { } Lt => { - let lhs = self.op_stack.pop()?; - let rhs = self.op_stack.pop()?; - let lt = match lhs.value() < rhs.value() { - true => BFieldElement::one(), - false => BFieldElement::zero(), - }; + let lhs = self.op_stack.pop_u32()?; + let rhs = self.op_stack.pop_u32()?; + let lt = BFieldElement::new((lhs < rhs) as u64); self.op_stack.push(lt); self.instruction_pointer += 1; - let u32_table_entry = (Instruction::Lt, lhs, rhs); + let u32_table_entry = ( + Instruction::Lt, + BFieldElement::new(lhs as u64), + BFieldElement::new(rhs as u64), + ); vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } And => { - let lhs = self.op_stack.pop()?; - let rhs = self.op_stack.pop()?; - let and = BFieldElement::new(lhs.value() & rhs.value()); + let lhs = self.op_stack.pop_u32()?; + let rhs = self.op_stack.pop_u32()?; + let and = BFieldElement::new((lhs & rhs) as u64); self.op_stack.push(and); self.instruction_pointer += 1; - let u32_table_entry = (Instruction::And, lhs, rhs); + let u32_table_entry = ( + Instruction::And, + BFieldElement::new(lhs as u64), + BFieldElement::new(rhs as u64), + ); vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Xor => { - let lhs = self.op_stack.pop()?; - let rhs = self.op_stack.pop()?; - let xor = BFieldElement::new(lhs.value() ^ rhs.value()); + let lhs = self.op_stack.pop_u32()?; + let rhs = self.op_stack.pop_u32()?; + let xor = BFieldElement::new((lhs ^ rhs) as u64); self.op_stack.push(xor); self.instruction_pointer += 1; - let u32_table_entry = (Instruction::Xor, lhs, rhs); + let u32_table_entry = ( + Instruction::Xor, + BFieldElement::new(lhs as u64), + BFieldElement::new(rhs as u64), + ); vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Log2Floor => { - let lhs = self.op_stack.pop()?; + let lhs = self.op_stack.pop_u32()?; if lhs.is_zero() { return vm_err(LogarithmOfZero); } - let l2f = BFieldElement::new(log_2_floor(lhs.value() as u128)); + let l2f = BFieldElement::new(log_2_floor(lhs as u128)); self.op_stack.push(l2f); self.instruction_pointer += 1; - let rhs = BFieldElement::zero(); - let u32_table_entry = (Instruction::Log2Floor, lhs, rhs); + let u32_table_entry = ( + Instruction::Log2Floor, + BFieldElement::new(lhs as u64), + BFieldElement::zero(), + ); vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Pow => { - let lhs = self.op_stack.pop()?; - let rhs = self.op_stack.pop()?; - let pow = lhs.mod_pow(rhs.value()); + let lhs = self.op_stack.pop_u32()?; + let rhs = self.op_stack.pop_u32()?; + let pow = BFieldElement::new(lhs as u64).mod_pow(rhs as u64); self.op_stack.push(pow); self.instruction_pointer += 1; - let u32_table_entry = (Instruction::Pow, lhs, rhs); + let u32_table_entry = ( + Instruction::Pow, + BFieldElement::new(lhs as u64), + BFieldElement::new(rhs as u64), + ); vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } Div => { - let numer = self.op_stack.pop()?; - let denom = self.op_stack.pop()?; + let numer = self.op_stack.pop_u32()?; + let denom = self.op_stack.pop_u32()?; if denom.is_zero() { - return vm_err(InverseOfZero); + return vm_err(DivisionByZero); } - let quot = BFieldElement::new(numer.value() / denom.value()); - let rem = BFieldElement::new(numer.value() % denom.value()); + let quot = BFieldElement::new((numer / denom) as u64); + let rem = BFieldElement::new((numer % denom) as u64); self.op_stack.push(quot); self.op_stack.push(rem); self.instruction_pointer += 1; - let u32_table_entry_0 = (Instruction::Lt, rem, denom); - let u32_table_entry_1 = (Instruction::Split, numer, quot); + let u32_table_entry_0 = (Instruction::Lt, rem, BFieldElement::new(denom as u64)); + let u32_table_entry_1 = + (Instruction::Split, BFieldElement::new(numer as u64), quot); vm_output = Some(VMOutput::U32TableEntries(vec![ u32_table_entry_0, u32_table_entry_1, diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 3c60fd86..689d90d6 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -5013,7 +5013,7 @@ mod constraint_polynomial_tests { } #[test] - #[should_panic(expected = "0 does not have a multiplicative inverse")] + #[should_panic(expected = "Division by 0 is impossible")] fn division_by_zero_is_impossible_test() { SourceCodeAndInput::without_input("div").run(); } From 4d7af28ed9364d9442a02b11d6ce0255148da85d Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Thu, 12 Jan 2023 09:20:54 +0100 Subject: [PATCH 51/53] switch order of u32 chunks produced by instruction `split` fix #160 --- specification/cheatsheet.tex | 2 +- .../instruction-specific-transition-constraints.md | 4 ++-- specification/src/instructions.md | 2 +- specification/src/pseudo-instructions.md | 7 ++++--- triton-opcodes/src/instruction.rs | 3 ++- triton-vm/src/state.rs | 4 ++-- triton-vm/src/table/processor_table.rs | 6 +++--- triton-vm/src/vm.rs | 12 ++++++------ 8 files changed, 21 insertions(+), 19 deletions(-) diff --git a/specification/cheatsheet.tex b/specification/cheatsheet.tex index ead2d53e..b4229791 100644 --- a/specification/cheatsheet.tex +++ b/specification/cheatsheet.tex @@ -62,7 +62,7 @@ \texttt{ 34} & $\ominus^1$ & \texttt{mul} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ prod} \\ \texttt{ 80} & $\ovoid^1$ & \texttt{invert} & \texttt{\_ st$_0$} & \texttt{\_ st$_0^{-1}$} \\ \texttt{ 42} & $\ominus^1$ & \texttt{eq} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ (st$_0$==st$_1$)} \\ - \texttt{ 4} & $\oplus^2$ & \tcbox[colback=instr-u32]{\texttt{split}} & \texttt{\_ st$_0$} & \texttt{\_ lo hi} \\ + \texttt{ 4} & $\oplus^2$ & \tcbox[colback=instr-u32]{\texttt{split}} & \texttt{\_ st$_0$} & \texttt{\_ hi lo} \\ \texttt{ 12} & $\ominus^1$ & \tcbox[colback=instr-u32]{\texttt{lt}} & \texttt{\_ st$_1$ st$_0$} & \texttt{\_ (st$_0$ Vec> { // input stack: _ a vec![ Dup(ST0), // _ a a - Split, // _ a lo hi + Split, // _ a hi lo + Swap(ST1), // _ a lo hi Push(BFieldElement::zero()), // _ a lo hi 0 Eq, // _ a lo (hi==0) Swap(ST2), // _ (hi==0) lo a diff --git a/triton-vm/src/state.rs b/triton-vm/src/state.rs index 2b4e9adc..ac6a3689 100644 --- a/triton-vm/src/state.rs +++ b/triton-vm/src/state.rs @@ -360,10 +360,10 @@ impl<'pgm> VMState<'pgm> { let elem = self.op_stack.pop()?; let lo = BFieldElement::new(elem.value() & 0xffff_ffff); let hi = BFieldElement::new(elem.value() >> 32); - self.op_stack.push(lo); self.op_stack.push(hi); + self.op_stack.push(lo); self.instruction_pointer += 1; - let u32_table_entry = (Instruction::Split, hi, lo); + let u32_table_entry = (Instruction::Split, lo, hi); vm_output = Some(VMOutput::U32TableEntries(vec![u32_table_entry])); } diff --git a/triton-vm/src/table/processor_table.rs b/triton-vm/src/table/processor_table.rs index 689d90d6..d93e5ba0 100644 --- a/triton-vm/src/table/processor_table.rs +++ b/triton-vm/src/table/processor_table.rs @@ -2398,7 +2398,7 @@ impl DualRowConstraints { // // $st0 - (2^32·st0' + st1') = 0$ let st0_decomposes_to_two_32_bit_chunks = - self.st0() - (two_pow_32.clone() * self.st0_next() + self.st1_next()); + self.st0() - (two_pow_32.clone() * self.st1_next() + self.st0_next()); // Helper variable `hv0` = 0 if either // 1. `hv0` is the difference between (2^32 - 1) and the high 32 bits (`st0'`), or @@ -2408,8 +2408,8 @@ impl DualRowConstraints { // lo·(hv0·(hi - 0xffff_ffff)) - 1) let hv0_holds_inverse_of_chunk_difference_or_low_bits_are_0 = { let hv0 = self.hv0(); - let hi = self.st0_next(); - let lo = self.st1_next(); + let hi = self.st1_next(); + let lo = self.st0_next(); let ffff_ffff = two_pow_32 - self.one(); lo * (hv0 * (hi - ffff_ffff) - self.one()) diff --git a/triton-vm/src/vm.rs b/triton-vm/src/vm.rs index f1b8b6c4..a349b59a 100644 --- a/triton-vm/src/vm.rs +++ b/triton-vm/src/vm.rs @@ -434,7 +434,7 @@ pub mod triton_vm_tests { let source_code = format!("push {st0} split read_io eq assert read_io eq assert halt"); SourceCodeAndInput { source_code, - input: vec![hi.into(), lo.into()], + input: vec![lo.into(), hi.into()], secret_input: vec![], } } @@ -669,7 +669,7 @@ pub mod triton_vm_tests { pub fn property_based_test_program_for_is_u32() -> SourceCodeAndInput { let mut rng = ThreadRng::default(); let st0_u32 = rng.next_u32(); - let st0_not_u32 = (rng.next_u32() as u64) << 32; + let st0_not_u32 = ((rng.next_u32() as u64) << 32) + (rng.next_u32() as u64); SourceCodeAndInput::without_input(&format!( "push {st0_u32} is_u32 assert \ push {st0_not_u32} is_u32 push 0 eq assert halt" @@ -767,11 +767,11 @@ pub mod triton_vm_tests { pub fn test_program_for_split() -> SourceCodeAndInput { SourceCodeAndInput::without_input( - "push -2 split push 4294967294 eq assert push 4294967295 eq assert \ - push -1 split push 4294967295 eq assert push 0 eq assert \ + "push -2 split push 4294967295 eq assert push 4294967294 eq assert \ + push -1 split push 0 eq assert push 4294967295 eq assert \ push 0 split push 0 eq assert push 0 eq assert \ - push 1 split push 0 eq assert push 1 eq assert \ - push 2 split push 0 eq assert push 2 eq assert \ + push 1 split push 1 eq assert push 0 eq assert \ + push 2 split push 2 eq assert push 0 eq assert \ push 4294967297 split assert assert \ halt", ) From 6e0c4b96ae0b095bd92d314ece9f8fc9d6263e93 Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Thu, 12 Jan 2023 11:29:02 +0100 Subject: [PATCH 52/53] release triton-opcodes v0.12.0 --- triton-opcodes/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/triton-opcodes/Cargo.toml b/triton-opcodes/Cargo.toml index cfdd74ef..35133988 100644 --- a/triton-opcodes/Cargo.toml +++ b/triton-opcodes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "triton-opcodes" -version = "0.11.0" +version = "0.12.0" edition = "2021" authors = ["Triton Software AG"] From d68e5510c28263be3b17dfaeaaa5d01cff330b8a Mon Sep 17 00:00:00 2001 From: Jan Ferdinand Sauer Date: Thu, 12 Jan 2023 12:44:34 +0100 Subject: [PATCH 53/53] release Triton VM v0.12.0 --- triton-vm/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/triton-vm/Cargo.toml b/triton-vm/Cargo.toml index 749f769f..b3517ee9 100644 --- a/triton-vm/Cargo.toml +++ b/triton-vm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "triton-vm" -version = "0.11.0" +version = "0.12.0" edition = "2021" authors = ["Triton Software AG"] @@ -31,7 +31,7 @@ default-features = false [dependencies] twenty-first = "0.10" -triton-opcodes = {path = "../triton-opcodes"} +triton-opcodes = "0.12" triton-profiler = "0.11" anyhow = "1.0" bincode = "1.3"