diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index b687d48e..e3db96ed 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -8,6 +8,9 @@ use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{CrossTableLookup, TableWithColumns}; use crate::keccak::keccak_stark; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::columns::KECCAK_WIDTH_BYTES; +use crate::keccak_memory::keccak_memory_stark; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic; use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; @@ -18,6 +21,7 @@ use crate::stark::Stark; pub struct AllStark, const D: usize> { pub cpu_stark: CpuStark, pub keccak_stark: KeccakStark, + pub keccak_memory_stark: KeccakMemoryStark, pub logic_stark: LogicStark, pub memory_stark: MemoryStark, pub cross_table_lookups: Vec>, @@ -28,6 +32,7 @@ impl, const D: usize> Default for AllStark { Self { cpu_stark: CpuStark::default(), keccak_stark: KeccakStark::default(), + keccak_memory_stark: KeccakMemoryStark::default(), logic_stark: LogicStark::default(), memory_stark: MemoryStark::default(), cross_table_lookups: all_cross_table_lookups(), @@ -40,6 +45,7 @@ impl, const D: usize> AllStark { let ans = vec![ self.cpu_stark.num_permutation_batches(config), self.keccak_stark.num_permutation_batches(config), + self.keccak_memory_stark.num_permutation_batches(config), self.logic_stark.num_permutation_batches(config), self.memory_stark.num_permutation_batches(config), ]; @@ -51,6 +57,7 @@ impl, const D: usize> AllStark { let ans = vec![ self.cpu_stark.permutation_batch_size(), self.keccak_stark.permutation_batch_size(), + self.keccak_memory_stark.permutation_batch_size(), self.logic_stark.permutation_batch_size(), self.memory_stark.permutation_batch_size(), ]; @@ -63,8 +70,9 @@ impl, const D: usize> AllStark { pub enum Table { Cpu = 0, Keccak = 1, - Logic = 2, - Memory = 3, + KeccakMemory = 2, + Logic = 3, + Memory = 4, } impl Table { @@ -75,16 +83,22 @@ impl Table { #[allow(unused)] // TODO: Should be used soon. pub(crate) fn all_cross_table_lookups() -> Vec> { - vec![ctl_keccak(), ctl_logic(), ctl_memory()] + vec![ctl_keccak(), ctl_logic(), ctl_memory(), ctl_keccak_memory()] } fn ctl_keccak() -> CrossTableLookup { + let cpu_looking = TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_keccak(), + Some(cpu_stark::ctl_filter_keccak()), + ); + let keccak_memory_looking = TableWithColumns::new( + Table::KeccakMemory, + keccak_memory_stark::ctl_looking_keccak(), + Some(keccak_memory_stark::ctl_filter()), + ); CrossTableLookup::new( - vec![TableWithColumns::new( - Table::Cpu, - cpu_stark::ctl_data_keccak(), - Some(cpu_stark::ctl_filter_keccak()), - )], + vec![cpu_looking, keccak_memory_looking], TableWithColumns::new( Table::Keccak, keccak_stark::ctl_data(), @@ -94,6 +108,22 @@ fn ctl_keccak() -> CrossTableLookup { ) } +fn ctl_keccak_memory() -> CrossTableLookup { + CrossTableLookup::new( + vec![TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_keccak_memory(), + Some(cpu_stark::ctl_filter_keccak_memory()), + )], + TableWithColumns::new( + Table::KeccakMemory, + keccak_memory_stark::ctl_looked_data(), + Some(keccak_memory_stark::ctl_filter()), + ), + None, + ) +} + fn ctl_logic() -> CrossTableLookup { CrossTableLookup::new( vec![TableWithColumns::new( @@ -107,16 +137,33 @@ fn ctl_logic() -> CrossTableLookup { } fn ctl_memory() -> CrossTableLookup { + let cpu_memory_ops = (0..NUM_CHANNELS).map(|channel| { + TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_memory(channel), + Some(cpu_stark::ctl_filter_memory(channel)), + ) + }); + let keccak_memory_reads = (0..KECCAK_WIDTH_BYTES).map(|i| { + TableWithColumns::new( + Table::KeccakMemory, + keccak_memory_stark::ctl_looking_memory(i, true), + Some(keccak_memory_stark::ctl_filter()), + ) + }); + let keccak_memory_writes = (0..KECCAK_WIDTH_BYTES).map(|i| { + TableWithColumns::new( + Table::KeccakMemory, + keccak_memory_stark::ctl_looking_memory(i, false), + Some(keccak_memory_stark::ctl_filter()), + ) + }); + let all_lookers = cpu_memory_ops + .chain(keccak_memory_reads) + .chain(keccak_memory_writes) + .collect(); CrossTableLookup::new( - (0..NUM_CHANNELS) - .map(|channel| { - TableWithColumns::new( - Table::Cpu, - cpu_stark::ctl_data_memory(channel), - Some(cpu_stark::ctl_filter_memory(channel)), - ) - }) - .collect(), + all_lookers, TableWithColumns::new( Table::Memory, memory_stark::ctl_data(), @@ -142,12 +189,13 @@ mod tests { use plonky2::util::timing::TimingTree; use rand::{thread_rng, Rng}; - use crate::all_stark::AllStark; + use crate::all_stark::{AllStark, Table}; use crate::config::StarkConfig; use crate::cpu::cpu_stark::CpuStark; use crate::cpu::kernel::aggregator::KERNEL; use crate::cross_table_lookup::testutils::check_ctls; use crate::keccak::keccak_stark::{KeccakStark, NUM_INPUTS, NUM_ROUNDS}; + use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::{self, LogicStark, Operation}; use crate::memory::memory_stark::tests::generate_random_memory_ops; use crate::memory::memory_stark::MemoryStark; @@ -177,6 +225,13 @@ mod tests { keccak_stark.generate_trace(keccak_inputs) } + fn make_keccak_memory_trace( + keccak_memory_stark: &KeccakMemoryStark, + config: &StarkConfig, + ) -> Vec> { + keccak_memory_stark.generate_trace(vec![], 1 << config.fri_config.cap_height) + } + fn make_logic_trace( num_rows: usize, logic_stark: &LogicStark, @@ -625,6 +680,7 @@ mod tests { let num_keccak_perms = 2; let keccak_trace = make_keccak_trace(num_keccak_perms, &all_stark.keccak_stark, &mut rng); + let keccak_memory_trace = make_keccak_memory_trace(&all_stark.keccak_memory_stark, config); let logic_trace = make_logic_trace(num_logic_rows, &all_stark.logic_stark, &mut rng); let mem_trace = make_memory_trace(num_memory_ops, &all_stark.memory_stark, &mut rng); let mut memory_trace = mem_trace.0; @@ -639,14 +695,20 @@ mod tests { &mut memory_trace, ); - let traces = vec![cpu_trace, keccak_trace, logic_trace, memory_trace]; + let traces = vec![ + cpu_trace, + keccak_trace, + keccak_memory_trace, + logic_trace, + memory_trace, + ]; check_ctls(&traces, &all_stark.cross_table_lookups); let proof = prove::( &all_stark, config, traces, - vec![vec![]; 4], + vec![vec![]; Table::num_tables()], &mut TimingTree::default(), )?; diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 8f641db9..564ea246 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -150,9 +150,12 @@ pub struct CpuColumnsView { /// If CPU cycle: the opcode, broken up into bits in little-endian order. pub opcode_bits: [T; 8], - /// Filter. 1 iff a Keccak permutation is computed on this row. + /// Filter. 1 iff a Keccak lookup is performed on this row. pub is_keccak: T, + /// Filter. 1 iff a Keccak memory lookup is performed on this row. + pub is_keccak_memory: T, + pub(crate) general: CpuGeneralColumnsView, pub(crate) clock: T, diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 918f7d9b..0478c609 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -22,10 +22,30 @@ pub fn ctl_data_keccak() -> Vec> { res } +pub fn ctl_data_keccak_memory() -> Vec> { + // When executing KECCAK_GENERAL, the memory channels are used as follows: + // channel 0: instruction + // channel 1: stack[-1] = context + // channel 2: stack[-2] = segment + // channel 3: stack[-3] = virtual + let context = Column::single(COL_MAP.mem_value[1][0]); + let segment = Column::single(COL_MAP.mem_value[2][0]); + let virt = Column::single(COL_MAP.mem_value[3][0]); + + let num_channels = F::from_canonical_usize(NUM_CHANNELS); + let clock = Column::linear_combination([(COL_MAP.clock, num_channels)]); + + vec![context, segment, virt, clock] +} + pub fn ctl_filter_keccak() -> Column { Column::single(COL_MAP.is_keccak) } +pub fn ctl_filter_keccak_memory() -> Column { + Column::single(COL_MAP.is_keccak_memory) +} + pub fn ctl_data_logic() -> Vec> { let mut res = Column::singles([COL_MAP.is_and, COL_MAP.is_or, COL_MAP.is_xor]).collect_vec(); let logic = COL_MAP.general.logic(); @@ -53,7 +73,7 @@ pub fn ctl_data_memory(channel: usize) -> Vec> { let scalar = F::from_canonical_usize(NUM_CHANNELS); let addend = F::from_canonical_usize(channel); cols.push(Column::linear_combination_with_constant( - vec![(COL_MAP.clock, scalar)], + [(COL_MAP.clock, scalar)], addend, )); diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index b4b8d6fb..6071f8ff 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -24,7 +24,7 @@ use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; /// Represent a linear combination of columns. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Column { linear_combination: Vec<(usize, F)>, constant: F, @@ -42,6 +42,17 @@ impl Column { cs.into_iter().map(Self::single) } + pub fn constant(constant: F) -> Self { + Self { + linear_combination: vec![], + constant, + } + } + + pub fn zero() -> Self { + Self::constant(F::ZERO) + } + pub fn linear_combination_with_constant>( iter: I, constant: F, @@ -67,6 +78,10 @@ impl Column { Self::linear_combination(cs.into_iter().zip(F::TWO.powers())) } + pub fn le_bytes>(cs: I) -> Self { + Self::linear_combination(cs.into_iter().zip(F::from_canonical_u16(256).powers())) + } + pub fn sum>(cs: I) -> Self { Self::linear_combination(cs.into_iter().zip(repeat(F::ONE))) } @@ -115,7 +130,7 @@ impl Column { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TableWithColumns { table: Table, columns: Vec>, diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index 1dfaca05..bc84c01c 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -2,11 +2,14 @@ use std::mem; use ethereum_types::U256; use plonky2::field::types::Field; +use tiny_keccak::keccakf; use crate::cpu::columns::{CpuColumnsView, NUM_CPU_COLUMNS}; use crate::generation::memory::MemoryState; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryOp; use crate::memory::memory_stark::MemoryOp; use crate::memory::segments::Segment; +use crate::memory::NUM_CHANNELS; use crate::{keccak, logic}; #[derive(Debug)] @@ -18,6 +21,7 @@ pub(crate) struct GenerationState { pub(crate) memory: MemoryState, pub(crate) keccak_inputs: Vec<[u64; keccak::keccak_stark::NUM_INPUTS]>, + pub(crate) keccak_memory_inputs: Vec, pub(crate) logic_ops: Vec, } @@ -55,10 +59,21 @@ impl GenerationState { channel_index: usize, segment: Segment, virt: usize, + ) -> U256 { + let context = self.current_context; + self.get_mem(channel_index, context, segment, virt) + } + + /// Read some memory, and log the operation. + pub(crate) fn get_mem( + &mut self, + channel_index: usize, + context: usize, + segment: Segment, + virt: usize, ) -> U256 { self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; let timestamp = self.cpu_rows.len(); - let context = self.current_context; let value = self.memory.contexts[context].segments[segment as usize].get(virt); self.memory.log.push(MemoryOp { filter: true, @@ -79,10 +94,23 @@ impl GenerationState { segment: Segment, virt: usize, value: U256, + ) { + let context = self.current_context; + self.set_mem(channel_index, context, segment, virt, value); + } + + /// Write some memory, and log the operation. + pub(crate) fn set_mem( + &mut self, + channel_index: usize, + context: usize, + segment: Segment, + virt: usize, + value: U256, ) { self.current_cpu_row.mem_channel_used[channel_index] = F::ONE; let timestamp = self.cpu_rows.len(); - let context = self.current_context; + let timestamp = timestamp * NUM_CHANNELS + channel_index; self.memory.log.push(MemoryOp { filter: true, timestamp, @@ -95,6 +123,52 @@ impl GenerationState { self.memory.contexts[context].segments[segment as usize].set(virt, value) } + /// Evaluate the Keccak-f permutation in-place on some data in memory, and record the operations + /// for the purpose of witness generation. + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn keccak_memory( + &mut self, + context: usize, + segment: Segment, + virt: usize, + ) -> [u64; keccak::keccak_stark::NUM_INPUTS] { + let read_timestamp = self.cpu_rows.len() * NUM_CHANNELS; + let input = (0..25) + .map(|i| { + let bytes = [0, 1, 2, 3, 4, 5, 6, 7].map(|j| { + let virt = virt + i * 8 + j; + let byte = self.get_mem(0, context, segment, virt); + debug_assert!(byte.bits() <= 8); + byte.as_u32() as u8 + }); + u64::from_le_bytes(bytes) + }) + .collect::>() + .try_into() + .unwrap(); + let output = self.keccak(input); + self.keccak_memory_inputs.push(KeccakMemoryOp { + context, + segment, + virt, + read_timestamp, + input, + output, + }); + output + } + + /// Evaluate the Keccak-f permutation, and record the operation for the purpose of witness + /// generation. + pub(crate) fn keccak( + &mut self, + mut input: [u64; keccak::keccak_stark::NUM_INPUTS], + ) -> [u64; keccak::keccak_stark::NUM_INPUTS] { + self.keccak_inputs.push(input); + keccakf(&mut input); + input + } + pub(crate) fn commit_cpu_row(&mut self) { let mut swapped_row = [F::ZERO; NUM_CPU_COLUMNS].into(); mem::swap(&mut self.current_cpu_row, &mut swapped_row); @@ -112,6 +186,7 @@ impl Default for GenerationState { current_context: 0, memory: MemoryState::default(), keccak_inputs: vec![], + keccak_memory_inputs: vec![], logic_ops: vec![], } } diff --git a/evm/src/keccak_memory/columns.rs b/evm/src/keccak_memory/columns.rs new file mode 100644 index 00000000..92bdbf2b --- /dev/null +++ b/evm/src/keccak_memory/columns.rs @@ -0,0 +1,29 @@ +pub(crate) const KECCAK_WIDTH_BYTES: usize = 200; + +/// 1 if this row represents a real operation; 0 if it's a padding row. +pub(crate) const COL_IS_REAL: usize = 0; + +// The address at which we will read inputs and write outputs. +pub(crate) const COL_CONTEXT: usize = 1; +pub(crate) const COL_SEGMENT: usize = 2; +pub(crate) const COL_VIRTUAL: usize = 3; + +/// The timestamp at which inputs should be read from memory. +/// Outputs will be written at the following timestamp. +pub(crate) const COL_READ_TIMESTAMP: usize = 4; + +const START_INPUT_LIMBS: usize = 5; +/// A byte of the input. +pub(crate) fn col_input_byte(i: usize) -> usize { + debug_assert!(i < KECCAK_WIDTH_BYTES); + START_INPUT_LIMBS + i +} + +const START_OUTPUT_LIMBS: usize = START_INPUT_LIMBS + KECCAK_WIDTH_BYTES; +/// A byte of the output. +pub(crate) fn col_output_byte(i: usize) -> usize { + debug_assert!(i < KECCAK_WIDTH_BYTES); + START_OUTPUT_LIMBS + i +} + +pub const NUM_COLUMNS: usize = START_OUTPUT_LIMBS + KECCAK_WIDTH_BYTES; diff --git a/evm/src/keccak_memory/keccak_memory_stark.rs b/evm/src/keccak_memory/keccak_memory_stark.rs new file mode 100644 index 00000000..5b0f0fd1 --- /dev/null +++ b/evm/src/keccak_memory/keccak_memory_stark.rs @@ -0,0 +1,230 @@ +use std::marker::PhantomData; + +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::packed::PackedField; +use plonky2::field::polynomial::PolynomialValues; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; +use plonky2::timed; +use plonky2::util::timing::TimingTree; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cross_table_lookup::Column; +use crate::keccak::keccak_stark::NUM_INPUTS; +use crate::keccak_memory::columns::*; +use crate::memory::segments::Segment; +use crate::stark::Stark; +use crate::util::trace_rows_to_poly_values; +use crate::vars::StarkEvaluationTargets; +use crate::vars::StarkEvaluationVars; + +const NUM_PUBLIC_INPUTS: usize = 0; + +pub(crate) fn ctl_looked_data() -> Vec> { + Column::singles([COL_CONTEXT, COL_SEGMENT, COL_VIRTUAL, COL_READ_TIMESTAMP]).collect() +} + +pub(crate) fn ctl_looking_keccak() -> Vec> { + let input_cols = (0..50).map(|i| { + Column::le_bytes((0..4).map(|j| { + let byte_index = i * 4 + j; + col_input_byte(byte_index) + })) + }); + let output_cols = (0..50).map(|i| { + Column::le_bytes((0..4).map(|j| { + let byte_index = i * 4 + j; + col_output_byte(byte_index) + })) + }); + input_cols.chain(output_cols).collect() +} + +pub(crate) fn ctl_looking_memory(i: usize, is_read: bool) -> Vec> { + let mut res = vec![Column::constant(F::from_bool(is_read))]; + res.extend(Column::singles([COL_CONTEXT, COL_SEGMENT, COL_VIRTUAL])); + + res.push(Column::single(col_input_byte(i))); + // Since we're reading or writing a single byte, the higher limbs must be zero. + res.extend((1..8).map(|_| Column::zero())); + + // Since COL_READ_TIMESTAMP is the read time, we add 1 if this is a write. + let is_write_f = F::from_bool(!is_read); + res.push(Column::linear_combination_with_constant( + [(COL_READ_TIMESTAMP, F::ONE)], + is_write_f, + )); + + assert_eq!( + res.len(), + crate::memory::memory_stark::ctl_data::().len() + ); + res +} + +/// CTL filter used for both directions (looked and looking). +pub(crate) fn ctl_filter() -> Column { + Column::single(COL_IS_REAL) +} + +/// Information about a Keccak memory operation needed for witness generation. +#[derive(Debug)] +pub(crate) struct KeccakMemoryOp { + // The address at which we will read inputs and write outputs. + pub(crate) context: usize, + pub(crate) segment: Segment, + pub(crate) virt: usize, + + /// The timestamp at which inputs should be read from memory. + /// Outputs will be written at the following timestamp. + pub(crate) read_timestamp: usize, + + /// The input that was read at that address. + pub(crate) input: [u64; NUM_INPUTS], + pub(crate) output: [u64; NUM_INPUTS], +} + +#[derive(Copy, Clone, Default)] +pub struct KeccakMemoryStark { + pub(crate) f: PhantomData, +} + +impl, const D: usize> KeccakMemoryStark { + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn generate_trace( + &self, + operations: Vec, + min_rows: usize, + ) -> Vec> { + let mut timing = TimingTree::new("generate trace", log::Level::Debug); + + // Generate the witness row-wise. + let trace_rows = timed!( + &mut timing, + "generate trace rows", + self.generate_trace_rows(operations, min_rows) + ); + + let trace_polys = timed!( + &mut timing, + "convert to PolynomialValues", + trace_rows_to_poly_values(trace_rows) + ); + + timing.print(); + trace_polys + } + + fn generate_trace_rows( + &self, + operations: Vec, + min_rows: usize, + ) -> Vec<[F; NUM_COLUMNS]> { + let num_rows = operations.len().max(min_rows).next_power_of_two(); + let mut rows = Vec::with_capacity(num_rows); + for op in operations { + rows.push(self.generate_row_for_op(op)); + } + + let padding_row = self.generate_padding_row(); + for _ in rows.len()..num_rows { + rows.push(padding_row); + } + rows + } + + fn generate_row_for_op(&self, op: KeccakMemoryOp) -> [F; NUM_COLUMNS] { + let mut row = [F::ZERO; NUM_COLUMNS]; + row[COL_IS_REAL] = F::ONE; + row[COL_CONTEXT] = F::from_canonical_usize(op.context); + row[COL_SEGMENT] = F::from_canonical_usize(op.segment as usize); + row[COL_VIRTUAL] = F::from_canonical_usize(op.virt); + row[COL_READ_TIMESTAMP] = F::from_canonical_usize(op.read_timestamp); + for i in 0..25 { + let input_u64 = op.input[i]; + let output_u64 = op.output[i]; + for j in 0..8 { + let byte_index = i * 8 + j; + row[col_input_byte(byte_index)] = F::from_canonical_u8(input_u64.to_le_bytes()[j]); + row[col_output_byte(byte_index)] = + F::from_canonical_u8(output_u64.to_le_bytes()[j]); + } + } + row + } + + fn generate_padding_row(&self) -> [F; NUM_COLUMNS] { + // We just need COL_IS_REAL to be zero, which it is by default. + // The other fields will have no effect. + [F::ZERO; NUM_COLUMNS] + } +} + +impl, const D: usize> Stark for KeccakMemoryStark { + const COLUMNS: usize = NUM_COLUMNS; + const PUBLIC_INPUTS: usize = NUM_PUBLIC_INPUTS; + + fn eval_packed_generic( + &self, + vars: StarkEvaluationVars, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + // is_real must be 0 or 1. + let is_real = vars.local_values[COL_IS_REAL]; + yield_constr.constraint(is_real * (is_real - P::ONES)); + } + + fn eval_ext_circuit( + &self, + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + vars: StarkEvaluationTargets, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + // is_real must be 0 or 1. + let is_real = vars.local_values[COL_IS_REAL]; + let constraint = builder.mul_sub_extension(is_real, is_real, is_real); + yield_constr.constraint(builder, constraint); + } + + fn constraint_degree(&self) -> usize { + 2 + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; + use crate::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree}; + + #[test] + fn test_stark_degree() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = KeccakMemoryStark; + + let stark = S { + f: Default::default(), + }; + test_stark_low_degree(stark) + } + + #[test] + fn test_stark_circuit() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = KeccakMemoryStark; + + let stark = S { + f: Default::default(), + }; + test_stark_circuit_constraints::(stark) + } +} diff --git a/evm/src/keccak_memory/mod.rs b/evm/src/keccak_memory/mod.rs new file mode 100644 index 00000000..7b5e3d01 --- /dev/null +++ b/evm/src/keccak_memory/mod.rs @@ -0,0 +1,2 @@ +pub mod columns; +pub mod keccak_memory_stark; diff --git a/evm/src/lib.rs b/evm/src/lib.rs index 47335db2..0a31a7ba 100644 --- a/evm/src/lib.rs +++ b/evm/src/lib.rs @@ -13,6 +13,7 @@ pub mod cross_table_lookup; pub mod generation; mod get_challenges; pub mod keccak; +pub mod keccak_memory; pub mod logic; pub mod lookup; pub mod memory; diff --git a/evm/src/prover.rs b/evm/src/prover.rs index e93ad754..8c33c289 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -23,6 +23,7 @@ use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{cross_table_lookup_data, CtlCheckVars, CtlData}; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::permutation::PermutationCheckVars; @@ -50,6 +51,8 @@ where [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakStark::::PUBLIC_INPUTS]:, + [(); KeccakMemoryStark::::COLUMNS]:, + [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, @@ -124,6 +127,19 @@ where &mut challenger, timing, )?; + let keccak_memory_proof = prove_single_table( + &all_stark.keccak_memory_stark, + config, + &trace_poly_values[Table::KeccakMemory as usize], + &trace_commitments[Table::KeccakMemory as usize], + &ctl_data_per_table[Table::KeccakMemory as usize], + public_inputs[Table::KeccakMemory as usize] + .clone() + .try_into() + .unwrap(), + &mut challenger, + timing, + )?; let logic_proof = prove_single_table( &all_stark.logic_stark, config, @@ -151,7 +167,13 @@ where timing, )?; - let stark_proofs = vec![cpu_proof, keccak_proof, logic_proof, memory_proof]; + let stark_proofs = vec![ + cpu_proof, + keccak_proof, + keccak_memory_proof, + logic_proof, + memory_proof, + ]; debug_assert_eq!(stark_proofs.len(), num_starks); Ok(AllProof { stark_proofs }) diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index b69a5519..6eed4717 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -17,6 +17,7 @@ use crate::constraint_consumer::RecursiveConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{verify_cross_table_lookups_circuit, CtlCheckVarsTarget}; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::permutation::PermutationCheckDataTarget; @@ -43,6 +44,8 @@ pub fn verify_proof_circuit< [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakStark::::PUBLIC_INPUTS]:, + [(); KeccakMemoryStark::::COLUMNS]:, + [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, @@ -59,6 +62,7 @@ pub fn verify_proof_circuit< let AllStark { cpu_stark, keccak_stark, + keccak_memory_stark, logic_stark, memory_stark, cross_table_lookups, @@ -95,6 +99,18 @@ pub fn verify_proof_circuit< inner_config, ) ); + with_context!( + builder, + "verify Keccak memory proof", + verify_stark_proof_with_challenges_circuit::( + builder, + keccak_memory_stark, + &all_proof.stark_proofs[Table::KeccakMemory as usize], + &stark_challenges[Table::KeccakMemory as usize], + &ctl_vars_per_table[Table::KeccakMemory as usize], + inner_config, + ) + ); with_context!( builder, "verify logic proof", @@ -309,6 +325,21 @@ pub fn add_virtual_all_proof, const D: usize>( public_inputs, } }, + { + let proof = add_virtual_stark_proof( + builder, + all_stark.keccak_memory_stark, + config, + degree_bits[Table::KeccakMemory as usize], + nums_ctl_zs[Table::KeccakMemory as usize], + ); + let public_inputs = + builder.add_virtual_targets(KeccakMemoryStark::::PUBLIC_INPUTS); + StarkProofWithPublicInputsTarget { + proof, + public_inputs, + } + }, { let proof = add_virtual_stark_proof( builder, @@ -331,7 +362,7 @@ pub fn add_virtual_all_proof, const D: usize>( degree_bits[Table::Memory as usize], nums_ctl_zs[Table::Memory as usize], ); - let public_inputs = builder.add_virtual_targets(KeccakStark::::PUBLIC_INPUTS); + let public_inputs = builder.add_virtual_targets(MemoryStark::::PUBLIC_INPUTS); StarkProofWithPublicInputsTarget { proof, public_inputs, diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index 1b46dc90..c8e3b8e6 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -12,6 +12,7 @@ use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{verify_cross_table_lookups, CtlCheckVars}; use crate::keccak::keccak_stark::KeccakStark; +use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::permutation::PermutationCheckVars; @@ -32,6 +33,8 @@ where [(); CpuStark::::PUBLIC_INPUTS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakStark::::PUBLIC_INPUTS]:, + [(); KeccakMemoryStark::::COLUMNS]:, + [(); KeccakMemoryStark::::PUBLIC_INPUTS]:, [(); LogicStark::::COLUMNS]:, [(); LogicStark::::PUBLIC_INPUTS]:, [(); MemoryStark::::COLUMNS]:, @@ -48,6 +51,7 @@ where let AllStark { cpu_stark, keccak_stark, + keccak_memory_stark, logic_stark, memory_stark, cross_table_lookups, @@ -74,6 +78,13 @@ where &ctl_vars_per_table[Table::Keccak as usize], config, )?; + verify_stark_proof_with_challenges( + keccak_memory_stark, + &all_proof.stark_proofs[Table::KeccakMemory as usize], + &stark_challenges[Table::KeccakMemory as usize], + &ctl_vars_per_table[Table::KeccakMemory as usize], + config, + )?; verify_stark_proof_with_challenges( memory_stark, &all_proof.stark_proofs[Table::Memory as usize],