diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 37fee118..f19d4997 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -152,12 +152,12 @@ mod tests { vec![TableWithColumns::new( Table::Cpu, Column::singles(cpu_keccak_input_output), - Column::single(cpu::columns::IS_KECCAK), + Some(Column::single(cpu::columns::IS_KECCAK)), )], TableWithColumns::new( Table::Keccak, keccak_keccak_input_output, - Column::single(keccak::registers::reg_step(NUM_ROUNDS - 1)), + Some(Column::single(keccak::registers::reg_step(NUM_ROUNDS - 1))), ), None, )]; diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 8a1722cc..bc9f93c2 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -23,24 +23,29 @@ use crate::proof::{StarkProofWithPublicInputs, StarkProofWithPublicInputsTarget} use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; -/// Represent a column or a linear combination of columns. +/// Represent a linear combination of columns. #[derive(Clone)] -pub enum Column { - Single(usize), - LinearCombination(Vec<(usize, F)>), - Empty, +pub struct Column { + linear_combination: Vec<(usize, F)>, + constant: F, } impl Column { pub fn single(c: usize) -> Self { - Self::Single(c) + Self { + linear_combination: vec![(c, F::ONE)], + constant: F::ZERO, + } } pub fn singles(cs: Vec) -> Vec { cs.into_iter().map(Self::single).collect() } - pub fn linear_combination>(iter: I) -> Self { + pub fn linear_combination_with_constant>( + iter: I, + constant: F, + ) -> Self { let v = iter.into_iter().collect::>(); assert!(!v.is_empty()); debug_assert_eq!( @@ -48,7 +53,14 @@ impl Column { v.len(), "Duplicate columns." ); - Self::LinearCombination(v) + Self { + linear_combination: v, + constant, + } + } + + pub fn linear_combination>(iter: I) -> Self { + Self::linear_combination_with_constant(iter, F::ZERO) } pub fn le_bits>(cs: I) -> Self { @@ -59,33 +71,25 @@ impl Column { Self::linear_combination(cs.iter().copied().zip(repeat(F::ONE))) } - pub fn is_empty(&self) -> bool { - matches!(self, Self::Empty) - } - pub fn eval(&self, v: &[P]) -> P where FE: FieldExtension, P: PackedField, { - match self { - Column::Single(c) => v[*c], - Column::LinearCombination(cs) => { - cs.iter().map(|&(c, f)| v[c] * FE::from_basefield(f)).sum() - } - Column::Empty => panic!("Cannot eval with empty column."), - } + self.linear_combination + .iter() + .map(|&(c, f)| v[c] * FE::from_basefield(f)) + .sum::

() + + FE::from_basefield(self.constant) } /// Evaluate on an row of a table given in column-major form. pub fn eval_table(&self, table: &[PolynomialValues], row: usize) -> F { - match self { - Column::Single(c) => table[*c].values[row], - Column::LinearCombination(cs) => { - cs.iter().map(|&(c, f)| table[c].values[row] * f).sum() - } - Column::Empty => panic!("Cannot eval with empty column."), - } + self.linear_combination + .iter() + .map(|&(c, f)| table[c].values[row] * f) + .sum::() + + self.constant } pub fn eval_circuit( @@ -96,23 +100,18 @@ impl Column { where F: RichField + Extendable, { - match self { - Column::Single(c) => v[*c], - Column::LinearCombination(cs) => { - let pairs = cs - .iter() - .map(|&(c, f)| { - ( - v[c], - builder.constant_extension(F::Extension::from_basefield(f)), - ) - }) - .collect::>(); - let zero = builder.zero_extension(); - builder.inner_product_extension(F::ONE, zero, pairs) - } - Column::Empty => panic!("Cannot eval with empty column."), - } + let pairs = self + .linear_combination + .iter() + .map(|&(c, f)| { + ( + v[c], + builder.constant_extension(F::Extension::from_basefield(f)), + ) + }) + .collect::>(); + let constant = builder.constant_extension(F::Extension::from_basefield(self.constant)); + builder.inner_product_extension(F::ONE, constant, pairs) } } @@ -120,12 +119,11 @@ impl Column { pub struct TableWithColumns { table: Table, columns: Vec>, - filter_column: Column, + filter_column: Option>, } impl TableWithColumns { - pub fn new(table: Table, columns: Vec>, filter_column: Column) -> Self { - assert!(columns.iter().all(|c| !c.is_empty())); + pub fn new(table: Table, columns: Vec>, filter_column: Option>) -> Self { Self { table, columns, @@ -154,8 +152,8 @@ impl CrossTableLookup { assert!( looking_tables .iter() - .all(|twc| twc.filter_column.is_empty() == default.is_some()) - && default.is_some() == looked_table.filter_column.is_empty(), + .all(|twc| twc.filter_column.is_none() == default.is_some()) + && default.is_some() == looked_table.filter_column.is_none(), "Default values should be provided iff there are no filter columns." ); if let Some(default) = &default { @@ -176,7 +174,7 @@ pub struct CtlData { pub(crate) challenges: GrandProductChallengeSet, /// Vector of `(Z, columns, filter_columns)` where `Z` is a Z-polynomial for a lookup /// on columns `columns` with filter columns `filter_columns`. - pub zs_columns: Vec<(PolynomialValues, Vec>, Column)>, + pub zs_columns: Vec<(PolynomialValues, Vec>, Option>)>, } impl CtlData { @@ -275,17 +273,17 @@ pub fn cross_table_lookup_data, const D fn partial_products( trace: &[PolynomialValues], columns: &[Column], - filter_column: &Column, + filter_column: &Option>, challenge: GrandProductChallenge, ) -> PolynomialValues { let mut partial_prod = F::ONE; let degree = trace[0].len(); let mut res = Vec::with_capacity(degree); for i in 0..degree { - let filter = if filter_column.is_empty() { - F::ONE + let filter = if let Some(column) = filter_column { + column.eval_table(trace, i) } else { - filter_column.eval_table(trace, i) + F::ONE }; if filter.is_one() { let evals = columns @@ -312,7 +310,7 @@ where pub(crate) next_z: P, pub(crate) challenges: GrandProductChallenge, pub(crate) columns: &'a [Column], - pub(crate) filter_column: &'a Column, + pub(crate) filter_column: &'a Option>, } impl<'a, F: RichField + Extendable, const D: usize> @@ -393,10 +391,10 @@ pub(crate) fn eval_cross_table_lookup_checks P { - if filter_column.is_empty() { - P::ONES + if let Some(column) = filter_column { + column.eval(v) } else { - filter_column.eval(v) + P::ONES } }; let local_filter = filter(vars.local_values); @@ -418,7 +416,7 @@ pub struct CtlCheckVarsTarget<'a, F: Field, const D: usize> { pub(crate) next_z: ExtensionTarget, pub(crate) challenges: GrandProductChallenge, pub(crate) columns: &'a [Column], - pub(crate) filter_column: &'a Column, + pub(crate) filter_column: &'a Option>, } impl<'a, F: Field, const D: usize> CtlCheckVarsTarget<'a, F, D> { @@ -493,15 +491,15 @@ pub(crate) fn eval_cross_table_lookup_checks_circuit< } = lookup_vars; let one = builder.one_extension(); - let local_filter = if filter_column.is_empty() { - one + let local_filter = if let Some(column) = filter_column { + column.eval_circuit(builder, vars.local_values) } else { - filter_column.eval_circuit(builder, vars.local_values) + one }; - let next_filter = if filter_column.is_empty() { - one + let next_filter = if let Some(column) = filter_column { + column.eval_circuit(builder, vars.next_values) } else { - filter_column.eval_circuit(builder, vars.next_values) + one }; fn select, const D: usize>( builder: &mut CircuitBuilder,