From c735c541ecdd7aae6068c778baabb82a308fba75 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Thu, 21 Nov 2024 09:48:30 +0100 Subject: [PATCH 01/10] instruction table: implement trace_evaluation --- .../src/components/instruction/table.rs | 243 +++++++++++++++++- 1 file changed, 242 insertions(+), 1 deletion(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 791cc67..72c7d08 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -2,7 +2,16 @@ use brainfuck_vm::{ instruction::VALID_INSTRUCTIONS_BF, machine::ProgramMemory, registers::Registers, }; use num_traits::Zero; -use stwo_prover::core::fields::m31::BaseField; +use stwo_prover::core::{ + backend::{ + simd::{column::BaseColumn, m31::LOG_N_LANES}, + Column, + }, + fields::m31::BaseField, + poly::circle::{CanonicCoset, CircleEvaluation}, +}; + +use crate::components::{memory::component::Claim, TraceError, TraceEval}; /// Represents a single row in the Instruction Table. /// @@ -22,6 +31,23 @@ pub struct InstructionTableRow { ni: BaseField, } +impl InstructionTableRow { + /// Get the instruction pointer. + pub const fn ip(&self) -> BaseField { + self.ip + } + + /// Get the current instruction. + pub const fn ci(&self) -> BaseField { + self.ci + } + + /// Get the next instruction. + pub const fn ni(&self) -> BaseField { + self.ni + } +} + impl From<&Registers> for InstructionTableRow { fn from(registers: &Registers) -> Self { Self { ip: registers.ip, ci: registers.ci, ni: registers.ni } @@ -85,6 +111,68 @@ impl InstructionTable { } } } + + /// Get the instruction table. + pub const fn table(&self) -> &Vec { + &self.table + } + + /// Transforms the [`InstructionTable`] into a [`TraceEval`], to be committed when + /// generating a STARK proof. + /// + /// The [`InstructionTable`] is transformed from an array of rows (one element = one step + /// of all registers) to an array of columns (one element = all steps of one register). + /// It is then evaluated on the circle domain. + /// + /// # Returns + /// A tuple containing the evaluated trace and claim for STARK proof. + /// + /// # Errors + /// Returns [`TraceError::EmptyTrace`] if the table is empty. + pub fn trace_evaluation(&self) -> Result<(TraceEval, Claim), TraceError> { + // Determine the number of rows in the table. + let n_rows = self.table.len() as u32; + // If the table is empty, there is no data to evaluate, so return an error. + if n_rows == 0 { + return Err(TraceError::EmptyTrace); + } + + // Compute `log_n_rows`, the base-2 logarithm of the number of rows. + // This determines the smallest power of two greater than or equal to `n_rows`. + let log_n_rows = n_rows.ilog2(); + + // Add `LOG_N_LANES` to account for SIMD optimization. This ensures that + // the domain size is aligned for parallel processing. + let log_size = log_n_rows + LOG_N_LANES; + + // Initialize a trace with 3 columns (for `ip`, `ci`, and `ni` registers), + // each column containing `2^log_size` entries initialized to zero. + let mut trace = vec![BaseColumn::zeros(1 << log_size); 3]; + + // Populate the columns with data from the table rows. + // We iterate over the table rows and, for each row: + // - Map the `ip` value to the first column. + // - Map the `ci` value to the second column. + // - Map the `ni` value to the third column. + for (index, row) in self.table.iter().enumerate().take(1 << log_n_rows) { + trace[InstructionColumn::Ip.index()].data[index] = row.ip().into(); + trace[InstructionColumn::Ci.index()].data[index] = row.ci().into(); + trace[InstructionColumn::Ni.index()].data[index] = row.ni().into(); + } + + // Create a circle domain using a canonical coset. + // This domain provides the mathematical structure required for FFT-based evaluation. + let domain = CanonicCoset::new(log_size).circle_domain(); + + // Map each column into the circle domain. + // + // This converts the columnar data into polynomial evaluations over the domain, enabling + // constraint verification in STARK proofs. + let trace = trace.into_iter().map(|col| CircleEvaluation::new(domain, col)).collect(); + + // Return the evaluated trace and a claim containing the log size of the domain. + Ok((trace, Claim { log_size })) + } } impl From<(Vec, &ProgramMemory)> for InstructionTable { @@ -125,6 +213,33 @@ impl From<(Vec, &ProgramMemory)> for InstructionTable { } } +/// Enum representing the column indices in the Instruction trace +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InstructionColumn { + /// Index of the `ip` register column in the Instruction trace. + Ip, + /// Index of the `ci` register column in the Instruction trace. + Ci, + /// Index of the `ni` register column in the Instruction trace. + Ni, +} + +impl InstructionColumn { + /// Returns the index of the column in the Instruction table + pub const fn index(self) -> usize { + match self { + Self::Ip => 0, + Self::Ci => 1, + Self::Ni => 2, + } + } + + /// Returns the total number of columns in the Instruction table + pub const fn count() -> usize { + 3 + } +} + #[cfg(test)] mod tests { use super::*; @@ -365,4 +480,130 @@ mod tests { // Verify that the instruction table is correct assert_eq!(instruction_table, expected_instruction_table); } + + #[test] + fn test_trace_evaluation_empty_table() { + let instruction_table = InstructionTable::new(); + let result = instruction_table.trace_evaluation(); + + assert!(matches!(result, Err(TraceError::EmptyTrace))); + } + + #[test] + fn test_trace_evaluation_single_row() { + let mut instruction_table = InstructionTable::new(); + instruction_table.add_row(InstructionTableRow { + ip: BaseField::from(1), + ci: BaseField::from(43), + ni: BaseField::from(91), + }); + + let (trace, claim) = instruction_table.trace_evaluation().unwrap(); + + assert_eq!(claim.log_size, LOG_N_LANES, "Log size should include SIMD lanes."); + assert_eq!( + trace.len(), + InstructionColumn::count(), + "Trace should contain one column per register." + ); + + // Check that each column contains the correct values + assert_eq!(trace[InstructionColumn::Ip.index()].to_cpu().values[0], BaseField::from(1)); + assert_eq!(trace[InstructionColumn::Ci.index()].to_cpu().values[0], BaseField::from(43)); + assert_eq!(trace[InstructionColumn::Ni.index()].to_cpu().values[0], BaseField::from(91)); + } + + #[test] + #[allow(clippy::similar_names)] + fn test_write_instruction_trace() { + let mut instruction_table = InstructionTable::new(); + + // Add rows to the instruction table. + let rows = vec![ + InstructionTableRow { + ip: BaseField::zero(), + ci: BaseField::from(43), + ni: BaseField::from(91), + }, + InstructionTableRow { + ip: BaseField::one(), + ci: BaseField::from(91), + ni: BaseField::from(9), + }, + ]; + instruction_table.add_rows(rows); + + // Perform the trace evaluation. + let (trace, claim) = instruction_table.trace_evaluation().unwrap(); + + // Calculate the expected parameters. + let expected_log_n_rows: u32 = 1; // log2(2 rows) + let expected_log_size = expected_log_n_rows + LOG_N_LANES; + let expected_size = 1 << expected_log_size; + + // Construct the expected trace columns. + let mut ip_column = BaseColumn::zeros(expected_size); + let mut ci_column = BaseColumn::zeros(expected_size); + let mut ni_column = BaseColumn::zeros(expected_size); + + ip_column.data[0] = BaseField::zero().into(); + ip_column.data[1] = BaseField::one().into(); + + ci_column.data[0] = BaseField::from(43).into(); + ci_column.data[1] = BaseField::from(91).into(); + + ni_column.data[0] = BaseField::from(91).into(); + ni_column.data[1] = BaseField::from(9).into(); + + // Create the expected domain for evaluation. + let domain = CanonicCoset::new(expected_log_size).circle_domain(); + + // Transform expected columns into CircleEvaluation. + let expected_trace: TraceEval = vec![ip_column, ci_column, ni_column] + .into_iter() + .map(|col| CircleEvaluation::new(domain, col)) + .collect(); + + // Create the expected claim. + let expected_claim = Claim { log_size: expected_log_size }; + + // Assert equality of the claim. + assert_eq!(claim, expected_claim); + + // Assert equality of the trace. + for col_index in 0..expected_trace.len() { + assert_eq!(trace[col_index].domain, expected_trace[col_index].domain); + assert_eq!(trace[col_index].to_cpu().values, expected_trace[col_index].to_cpu().values); + } + } + + #[test] + fn test_trace_evaluation_circle_domain() { + let mut instruction_table = InstructionTable::new(); + instruction_table.add_rows(vec![ + InstructionTableRow { + ip: BaseField::from(0), + ci: BaseField::from(43), + ni: BaseField::from(91), + }, + InstructionTableRow { + ip: BaseField::from(1), + ci: BaseField::from(91), + ni: BaseField::from(9), + }, + ]); + + let (trace, claim) = instruction_table.trace_evaluation().unwrap(); + + let log_size = claim.log_size; + let domain = CanonicCoset::new(log_size).circle_domain(); + + // Check that each column is evaluated over the correct domain + for column in trace { + assert_eq!( + column.domain, domain, + "Trace column domain should match expected circle domain." + ); + } + } } From e66f397d404cbd6cbf27c9158612d347200a88eb Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Thu, 21 Nov 2024 09:49:17 +0100 Subject: [PATCH 02/10] clean up --- crates/brainfuck_prover/src/components/instruction/table.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 72c7d08..4656d02 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -1,3 +1,4 @@ +use crate::components::{memory::component::Claim, TraceError, TraceEval}; use brainfuck_vm::{ instruction::VALID_INSTRUCTIONS_BF, machine::ProgramMemory, registers::Registers, }; @@ -11,8 +12,6 @@ use stwo_prover::core::{ poly::circle::{CanonicCoset, CircleEvaluation}, }; -use crate::components::{memory::component::Claim, TraceError, TraceEval}; - /// Represents a single row in the Instruction Table. /// /// The Instruction Table stores: From 684c242c20079d19221d2f39b46e2c7f497645b9 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Thu, 21 Nov 2024 09:53:27 +0100 Subject: [PATCH 03/10] clean up --- crates/brainfuck_prover/src/components/instruction/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 4656d02..0c1f99f 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -146,7 +146,7 @@ impl InstructionTable { // Initialize a trace with 3 columns (for `ip`, `ci`, and `ni` registers), // each column containing `2^log_size` entries initialized to zero. - let mut trace = vec![BaseColumn::zeros(1 << log_size); 3]; + let mut trace = vec![BaseColumn::zeros(1 << log_size); InstructionColumn::count()]; // Populate the columns with data from the table rows. // We iterate over the table rows and, for each row: From a25667abc7ce763fc9b84bbfe98d04ea49c20099 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:03:50 +0100 Subject: [PATCH 04/10] Update crates/brainfuck_prover/src/components/instruction/table.rs --- crates/brainfuck_prover/src/components/instruction/table.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 0c1f99f..f771cf3 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -129,7 +129,6 @@ impl InstructionTable { /// # Errors /// Returns [`TraceError::EmptyTrace`] if the table is empty. pub fn trace_evaluation(&self) -> Result<(TraceEval, Claim), TraceError> { - // Determine the number of rows in the table. let n_rows = self.table.len() as u32; // If the table is empty, there is no data to evaluate, so return an error. if n_rows == 0 { From f10fb486579108ceda6c9ff8d88eea23e44454f2 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:04:27 +0100 Subject: [PATCH 05/10] Update crates/brainfuck_prover/src/components/instruction/table.rs --- crates/brainfuck_prover/src/components/instruction/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index f771cf3..297bc06 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -223,7 +223,7 @@ pub enum InstructionColumn { } impl InstructionColumn { - /// Returns the index of the column in the Instruction table + /// Returns the index of the column in the Instruction trace pub const fn index(self) -> usize { match self { Self::Ip => 0, From ef316f28ffbb0f99420dde51c969d7ae29d3059b Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:04:47 +0100 Subject: [PATCH 06/10] Update crates/brainfuck_prover/src/components/instruction/table.rs --- crates/brainfuck_prover/src/components/instruction/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 297bc06..2dc2b4b 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -232,7 +232,7 @@ impl InstructionColumn { } } - /// Returns the total number of columns in the Instruction table + /// Returns the total number of columns in the Instruction trace pub const fn count() -> usize { 3 } From 19014e80dc95def6a53a00d21a11d773fceae629 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:05:14 +0100 Subject: [PATCH 07/10] Update crates/brainfuck_prover/src/components/instruction/table.rs Co-authored-by: malatrax <71888134+zmalatrax@users.noreply.github.com> --- crates/brainfuck_prover/src/components/instruction/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 2dc2b4b..7427c47 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -513,7 +513,7 @@ mod tests { #[test] #[allow(clippy::similar_names)] - fn test_write_instruction_trace() { + fn test_instruction_trace_evaluation() { let mut instruction_table = InstructionTable::new(); // Add rows to the instruction table. From 9730bc82ade1342edd7eb2f61442c00595ed28ad Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Thu, 21 Nov 2024 15:09:07 +0100 Subject: [PATCH 08/10] fix comment --- crates/brainfuck_prover/src/components/instruction/table.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 7427c47..913da19 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -136,7 +136,9 @@ impl InstructionTable { } // Compute `log_n_rows`, the base-2 logarithm of the number of rows. - // This determines the smallest power of two greater than or equal to `n_rows`. + // This determines the smallest power of two greater than or equal to `n_rows` + // + // The result is rounded down (i.e. (17 as u32).ilog2() = 4). let log_n_rows = n_rows.ilog2(); // Add `LOG_N_LANES` to account for SIMD optimization. This ensures that From 9b6a4a98306a1f1f75aeae0c6307cb3bc59375f1 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Thu, 21 Nov 2024 15:16:28 +0100 Subject: [PATCH 09/10] fix --- .../src/components/instruction/table.rs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 913da19..704a3fa 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -507,10 +507,27 @@ mod tests { "Trace should contain one column per register." ); - // Check that each column contains the correct values - assert_eq!(trace[InstructionColumn::Ip.index()].to_cpu().values[0], BaseField::from(1)); - assert_eq!(trace[InstructionColumn::Ci.index()].to_cpu().values[0], BaseField::from(43)); - assert_eq!(trace[InstructionColumn::Ni.index()].to_cpu().values[0], BaseField::from(91)); + // Expected values for the single row + let expected_ip_column = vec![BaseField::from(1); 16]; + let expected_ci_column = vec![BaseField::from(43); 16]; + let expected_ni_column = vec![BaseField::from(91); 16]; + + // Check that the entire column matches expected values + assert_eq!( + trace[InstructionColumn::Ip.index()].to_cpu().values, + expected_ip_column, + "IP column should match expected values." + ); + assert_eq!( + trace[InstructionColumn::Ci.index()].to_cpu().values, + expected_ci_column, + "CI column should match expected values." + ); + assert_eq!( + trace[InstructionColumn::Ni.index()].to_cpu().values, + expected_ni_column, + "NI column should match expected values." + ); } #[test] From e4ec4674a917d1984e78b1a9c1d4f43b21bb15f9 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Thu, 21 Nov 2024 15:19:59 +0100 Subject: [PATCH 10/10] clippy --- crates/brainfuck_prover/src/components/instruction/table.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/brainfuck_prover/src/components/instruction/table.rs b/crates/brainfuck_prover/src/components/instruction/table.rs index 704a3fa..bef1950 100644 --- a/crates/brainfuck_prover/src/components/instruction/table.rs +++ b/crates/brainfuck_prover/src/components/instruction/table.rs @@ -490,6 +490,7 @@ mod tests { } #[test] + #[allow(clippy::similar_names)] fn test_trace_evaluation_single_row() { let mut instruction_table = InstructionTable::new(); instruction_table.add_row(InstructionTableRow {