diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index 0cc42d30..af4981af 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -1,3 +1,19 @@ +use std::marker::PhantomData; + +use itertools::{izip, multiunzip}; +use plonky2::{field::{field_types::PrimeField64, extension_field::Extendable}, hash::hash_types::RichField, plonk::circuit_builder::CircuitBuilder}; + +use crate::memory::registers::{ + memory_value_limb, sorted_memory_value_limb, MEMORY_ADDR_CONTEXT, MEMORY_ADDR_SEGMENT, + MEMORY_ADDR_VIRTUAL, MEMORY_IS_READ, MEMORY_TIMESTAMP, SORTED_MEMORY_ADDR_CONTEXT, + SORTED_MEMORY_ADDR_SEGMENT, SORTED_MEMORY_ADDR_VIRTUAL, SORTED_MEMORY_TIMESTAMP, SORTED_MEMORY_IS_READ, + MEMORY_CONTEXT_FIRST_CHANGE, MEMORY_SEGMENT_FIRST_CHANGE, MEMORY_VIRTUAL_FIRST_CHANGE, +}; + +use crate::vars::{StarkEvaluationVars, StarkEvaluationTargets}; +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::stark::Stark; + #[derive(Default)] pub struct TransactionMemory { pub calls: Vec, @@ -14,3 +30,355 @@ pub struct ContractMemory { pub struct MemorySegment { pub content: Vec, } + + +#[derive(Copy, Clone)] +pub struct MemoryStark { + pub(crate) f: PhantomData, +} + +pub fn sort_memory_ops( + context: &[F], + segment: &[F], + virtuals: &[F], + values: &[Vec], + is_read: &[F], + timestamp: &[F], +) -> (Vec, Vec, Vec, Vec>, Vec, Vec) { + let mut ops: Vec<(F, F, F, Vec, F, F)> = izip!( + context.iter().cloned(), + segment.iter().cloned(), + virtuals.iter().cloned(), + values.iter().cloned(), + is_read.iter().cloned(), + timestamp.iter().cloned() + ) + .collect(); + + ops.sort_by(|&(c1, s1, v1, _, _, t1), &(c2, s2, v2, _, _, t2)| { + ( + c1.to_noncanonical_u64(), + s1.to_noncanonical_u64(), + v1.to_noncanonical_u64(), + t1.to_noncanonical_u64(), + ) + .cmp(&( + c2.to_noncanonical_u64(), + s2.to_noncanonical_u64(), + v2.to_noncanonical_u64(), + t2.to_noncanonical_u64(), + )) + }); + + multiunzip(ops) +} + +pub fn generate_first_change_flags( + context: &Vec, + segment: &Vec, + virtuals: &Vec, +) -> (Vec, Vec, Vec) { + let num_ops = context.len(); + let mut context_first_change = Vec::new(); + let mut segment_first_change = Vec::new(); + let mut virtual_first_change = Vec::new(); + for idx in 0..num_ops - 1 { + let this_context_first_change = if context[idx] != context[idx + 1] { + F::ONE + } else { + F::ZERO + }; + let this_segment_first_change = if segment[idx] != segment[idx + 1] { + F::ONE * (F::ONE - this_context_first_change) + } else { + F::ZERO + }; + let this_virtual_first_change = if virtuals[idx] != virtuals[idx + 1] { + F::ONE * (F::ONE - this_context_first_change) * (F::ONE - this_segment_first_change) + } else { + F::ZERO + }; + + context_first_change.push(this_context_first_change); + segment_first_change.push(this_segment_first_change); + virtual_first_change.push(this_virtual_first_change); + } + + context_first_change.push(F::ZERO); + segment_first_change.push(F::ZERO); + virtual_first_change.push(F::ZERO); + + ( + context_first_change, + segment_first_change, + virtual_first_change, + ) +} + +pub fn generate_range_check_value( + context: &Vec, + segment: &Vec, + virtuals: &Vec, + timestamp: &Vec, + context_first_change: &Vec, + segment_first_change: &Vec, + virtual_first_change: &Vec, +) -> Vec { + let num_ops = context.len(); + let mut range_check = Vec::new(); + + for idx in 0..num_ops - 1 { + let this_timestamp_first_change = F::ONE + - context_first_change[idx] + - segment_first_change[idx] + - virtual_first_change[idx]; + + range_check.push( + context_first_change[idx] * (context[idx + 1] - context[idx] - F::ONE) + + segment_first_change[idx] * (segment[idx + 1] - segment[idx] - F::ONE) + + virtual_first_change[idx] * (virtuals[idx + 1] - virtuals[idx] - F::ONE) + + this_timestamp_first_change * (timestamp[idx + 1] - timestamp[idx] - F::ONE), + ); + } + + range_check.push(F::ZERO); + + range_check +} + +impl, const D: usize> MemoryStark { + pub(crate) fn generate_memory(trace_cols: &mut [Vec]) { + let context = &trace_cols[MEMORY_ADDR_CONTEXT]; + let segment = &trace_cols[MEMORY_ADDR_SEGMENT]; + let virtuals = &trace_cols[MEMORY_ADDR_VIRTUAL]; + let values: Vec> = (0..8) + .map(|i| &trace_cols[memory_value_limb(i)]) + .cloned() + .collect(); + let is_read = &trace_cols[MEMORY_IS_READ]; + let timestamp = &trace_cols[MEMORY_TIMESTAMP]; + + let ( + sorted_context, + sorted_segment, + sorted_virtual, + sorted_values, + sorted_is_read, + sorted_timestamp, + ) = sort_memory_ops(context, segment, virtuals, &values, is_read, timestamp); + + let (context_first_change, segment_first_change, virtual_first_change) = + generate_first_change_flags(&sorted_context, &sorted_segment, &sorted_virtual); + + let range_check_value = generate_range_check_value( + &sorted_context, + &sorted_segment, + &sorted_virtual, + &sorted_timestamp, + &context_first_change, + &segment_first_change, + &virtual_first_change, + ); + + trace_cols[SORTED_MEMORY_ADDR_CONTEXT] = sorted_context; + trace_cols[SORTED_MEMORY_ADDR_SEGMENT] = sorted_segment; + trace_cols[SORTED_MEMORY_ADDR_VIRTUAL] = sorted_virtual; + for i in 0..8 { + trace_cols[sorted_memory_value_limb(i)] = sorted_values[i].clone(); + } + trace_cols[SORTED_MEMORY_IS_READ] = sorted_is_read; + trace_cols[SORTED_MEMORY_TIMESTAMP] = sorted_timestamp; + + trace_cols[MEMORY_CONTEXT_FIRST_CHANGE] = context_first_change; + trace_cols[MEMORY_SEGMENT_FIRST_CHANGE] = segment_first_change; + trace_cols[MEMORY_VIRTUAL_FIRST_CHANGE] = virtual_first_change; + + trace_cols[crate::registers::range_check_degree::col_rc_degree_input(0)] = range_check_value; + } +} + +impl, const D: usize> Stark for MemoryStark { + const COLUMNS: usize = NUM_REGISTERS; + const PUBLIC_INPUTS: usize = NUM_PUBLIC_INPUTS; + + fn eval_packed_generic( + vars: StarkEvaluationVars, + yield_constr: &mut ConstraintConsumer

, + ) { + let one = P::from(F::ONE); + + let addr_context = vars.local_values[SORTED_MEMORY_ADDR_CONTEXT]; + let addr_segment = vars.local_values[SORTED_MEMORY_ADDR_SEGMENT]; + let addr_virtual = vars.local_values[SORTED_MEMORY_ADDR_VIRTUAL]; + let values: Vec<_> = (0..8) + .map(|i| vars.local_values[sorted_memory_value_limb(i)]) + .collect(); + let timestamp = vars.local_values[SORTED_MEMORY_TIMESTAMP]; + + let next_addr_context = vars.next_values[SORTED_MEMORY_ADDR_CONTEXT]; + let next_addr_segment = vars.next_values[SORTED_MEMORY_ADDR_SEGMENT]; + let next_addr_virtual = vars.next_values[SORTED_MEMORY_ADDR_VIRTUAL]; + let next_values: Vec<_> = (0..8) + .map(|i| vars.next_values[sorted_memory_value_limb(i)]) + .collect(); + let next_is_read = vars.next_values[SORTED_MEMORY_IS_READ]; + let next_timestamp = vars.next_values[SORTED_MEMORY_TIMESTAMP]; + + let context_first_change = vars.local_values[MEMORY_CONTEXT_FIRST_CHANGE]; + let segment_first_change = vars.local_values[MEMORY_SEGMENT_FIRST_CHANGE]; + let virtual_first_change = vars.local_values[MEMORY_VIRTUAL_FIRST_CHANGE]; + let timestamp_first_change = + one - context_first_change - segment_first_change - virtual_first_change; + + let range_check = + vars.local_values[crate::registers::range_check_degree::col_rc_degree_input(0)]; + + let not_context_first_change = one - context_first_change; + let not_segment_first_change = one - segment_first_change; + let not_virtual_first_change = one - virtual_first_change; + let not_timestamp_first_change = one - timestamp_first_change; + + // First set of ordering constraint: first_change flags are boolean. + yield_constr.constraint(context_first_change * not_context_first_change); + yield_constr.constraint(segment_first_change * not_segment_first_change); + yield_constr.constraint(virtual_first_change * not_virtual_first_change); + yield_constr.constraint(timestamp_first_change * not_timestamp_first_change); + + // Second set of ordering constraints: no change before the column corresponding to the nonzero first_change flag. + yield_constr.constraint(segment_first_change * (next_addr_context - addr_context)); + yield_constr.constraint(virtual_first_change * (next_addr_context - addr_context)); + yield_constr.constraint(virtual_first_change * (next_addr_segment - addr_segment)); + yield_constr.constraint(timestamp_first_change * (next_addr_context - addr_context)); + yield_constr.constraint(timestamp_first_change * (next_addr_segment - addr_segment)); + yield_constr.constraint(timestamp_first_change * (next_addr_virtual - addr_virtual)); + + // Third set of ordering constraints: range-check difference in the column that should be increasing. + let range_check_value = context_first_change * (next_addr_context - addr_context - one) + + segment_first_change * (next_addr_segment - addr_segment - one) + + virtual_first_change * (next_addr_virtual - addr_virtual - one) + + timestamp_first_change * (next_timestamp - timestamp - one); + yield_constr.constraint(range_check - range_check_value); + + // Enumerate purportedly-ordered log. + for i in 0..8 { + yield_constr + .constraint(next_is_read * timestamp_first_change * (next_values[i] - values[i])); + } + } + + fn eval_memory_recursively( + builder: &mut CircuitBuilder, + vars: StarkEvaluationTargets, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + let one = builder.one_extension(); + + let addr_context = vars.local_values[SORTED_MEMORY_ADDR_CONTEXT]; + let addr_segment = vars.local_values[SORTED_MEMORY_ADDR_SEGMENT]; + let addr_virtual = vars.local_values[SORTED_MEMORY_ADDR_VIRTUAL]; + let values: Vec<_> = (0..8) + .map(|i| vars.local_values[sorted_memory_value_limb(i)]) + .collect(); + let timestamp = vars.local_values[SORTED_MEMORY_TIMESTAMP]; + + let next_addr_context = vars.next_values[SORTED_MEMORY_ADDR_CONTEXT]; + let next_addr_segment = vars.next_values[SORTED_MEMORY_ADDR_SEGMENT]; + let next_addr_virtual = vars.next_values[SORTED_MEMORY_ADDR_VIRTUAL]; + let next_values: Vec<_> = (0..8) + .map(|i| vars.next_values[sorted_memory_value_limb(i)]) + .collect(); + let next_is_read = vars.next_values[SORTED_MEMORY_IS_READ]; + let next_timestamp = vars.next_values[SORTED_MEMORY_TIMESTAMP]; + + let context_first_change = vars.local_values[MEMORY_CONTEXT_FIRST_CHANGE]; + let segment_first_change = vars.local_values[MEMORY_SEGMENT_FIRST_CHANGE]; + let virtual_first_change = vars.local_values[MEMORY_VIRTUAL_FIRST_CHANGE]; + let timestamp_first_change = { + let mut cur = builder.sub_extension(one, context_first_change); + cur = builder.sub_extension(cur, segment_first_change); + builder.sub_extension(cur, virtual_first_change) + }; + + let range_check = + vars.local_values[crate::registers::range_check_degree::col_rc_degree_input(0)]; + + let not_context_first_change = builder.sub_extension(one, context_first_change); + let not_segment_first_change = builder.sub_extension(one, segment_first_change); + let not_virtual_first_change = builder.sub_extension(one, virtual_first_change); + let not_timestamp_first_change = builder.sub_extension(one, timestamp_first_change); + let addr_context_diff = builder.sub_extension(next_addr_context, addr_context); + let addr_segment_diff = builder.sub_extension(next_addr_segment, addr_segment); + let addr_virtual_diff = builder.sub_extension(next_addr_virtual, addr_virtual); + + // First set of ordering constraint: traces are boolean. + let context_first_change_bool = + builder.mul_extension(context_first_change, not_context_first_change); + yield_constr.constraint(builder, context_first_change_bool); + let segment_first_change_bool = + builder.mul_extension(segment_first_change, not_segment_first_change); + yield_constr.constraint(builder, segment_first_change_bool); + let virtual_first_change_bool = + builder.mul_extension(virtual_first_change, not_virtual_first_change); + yield_constr.constraint(builder, virtual_first_change_bool); + let timestamp_first_change_bool = + builder.mul_extension(timestamp_first_change, not_timestamp_first_change); + yield_constr.constraint(builder, timestamp_first_change_bool); + + // Second set of ordering constraints: no change before the column corresponding to the nonzero first_change flag. + let segment_first_change_check = builder.mul_extension(segment_first_change, addr_context_diff); + yield_constr.constraint(builder, segment_first_change_check); + let virtual_first_change_check_1 = + builder.mul_extension(virtual_first_change, addr_context_diff); + yield_constr.constraint(builder, virtual_first_change_check_1); + let virtual_first_change_check_2 = + builder.mul_extension(virtual_first_change, addr_segment_diff); + yield_constr.constraint(builder, virtual_first_change_check_2); + let timestamp_first_change_check_1 = + builder.mul_extension(timestamp_first_change, addr_context_diff); + yield_constr.constraint(builder, timestamp_first_change_check_1); + let timestamp_first_change_check_2 = + builder.mul_extension(timestamp_first_change, addr_segment_diff); + yield_constr.constraint(builder, timestamp_first_change_check_2); + let timestamp_first_change_check_3 = + builder.mul_extension(timestamp_first_change, addr_virtual_diff); + yield_constr.constraint(builder, timestamp_first_change_check_3); + + // Third set of ordering constraints: range-check difference in the column that should be increasing. + let context_diff = { + let diff = builder.sub_extension(next_addr_context, addr_context); + builder.sub_extension(diff, one) + }; + let context_range_check = builder.mul_extension(context_first_change, context_diff); + let segment_diff = { + let diff = builder.sub_extension(next_addr_segment, addr_segment); + builder.sub_extension(diff, one) + }; + let segment_range_check = builder.mul_extension(segment_first_change, segment_diff); + let virtual_diff = { + let diff = builder.sub_extension(next_addr_virtual, addr_virtual); + builder.sub_extension(diff, one) + }; + let virtual_range_check = builder.mul_extension(virtual_first_change, virtual_diff); + let timestamp_diff = { + let diff = builder.sub_extension(next_timestamp, timestamp); + builder.sub_extension(diff, one) + }; + let timestamp_range_check = builder.mul_extension(timestamp_first_change, timestamp_diff); + + let range_check_value = { + let mut sum = builder.add_extension(context_range_check, segment_range_check); + sum = builder.add_extension(sum, virtual_range_check); + builder.add_extension(sum, timestamp_range_check) + }; + let range_check_diff = builder.sub_extension(range_check, range_check_value); + yield_constr.constraint(builder, range_check_diff); + + // Enumerate purportedly-ordered log. + for i in 0..8 { + let value_diff = builder.sub_extension(next_values[i], values[i]); + let zero_if_read = builder.mul_extension(timestamp_first_change, value_diff); + let read_constraint = builder.mul_extension(next_is_read, zero_if_read); + yield_constr.constraint(builder, read_constraint); + } + } +} diff --git a/evm/src/memory/mod.rs b/evm/src/memory/mod.rs index 53108262..51adaf72 100644 --- a/evm/src/memory/mod.rs +++ b/evm/src/memory/mod.rs @@ -1,2 +1,2 @@ pub mod memory_stark; -pub mod registers; \ No newline at end of file +pub mod registers; diff --git a/evm/src/memory/registers.rs b/evm/src/memory/registers.rs index 1373d0d8..2ab4619f 100644 --- a/evm/src/memory/registers.rs +++ b/evm/src/memory/registers.rs @@ -1,3 +1,31 @@ //! Memory unit. -pub(super) const END: usize = super::START_MEMORY; +pub(crate) const MEMORY_ADDR_CONTEXT: usize = 0; +pub(crate) const MEMORY_ADDR_SEGMENT: usize = MEMORY_ADDR_CONTEXT + 1; +pub(crate) const MEMORY_ADDR_VIRTUAL: usize = MEMORY_ADDR_SEGMENT + 1; +pub(crate) const MEMORY_VALUE_START: usize = MEMORY_ADDR_VIRTUAL + 1; + +pub const fn memory_value_limb(i: usize) -> usize { + MEMORY_VALUE_START + i +} + +pub(crate) const MEMORY_IS_READ: usize = MEMORY_VALUE_START + 8; +pub(crate) const MEMORY_TIMESTAMP: usize = MEMORY_IS_READ + 1; + +pub(crate) const SORTED_MEMORY_ADDR_CONTEXT: usize = MEMORY_TIMESTAMP + 1; +pub(crate) const SORTED_MEMORY_ADDR_SEGMENT: usize = SORTED_MEMORY_ADDR_CONTEXT + 1; +pub(crate) const SORTED_MEMORY_ADDR_VIRTUAL: usize = SORTED_MEMORY_ADDR_SEGMENT + 1; +pub(crate) const SORTED_MEMORY_VALUE_START: usize = SORTED_MEMORY_ADDR_VIRTUAL + 1; + +pub const fn sorted_memory_value_limb(i: usize) -> usize { + SORTED_MEMORY_VALUE_START + i +} + +pub(crate) const SORTED_MEMORY_IS_READ: usize = SORTED_MEMORY_VALUE_START + 8; +pub(crate) const SORTED_MEMORY_TIMESTAMP: usize = SORTED_MEMORY_IS_READ + 1; + +pub(crate) const MEMORY_CONTEXT_FIRST_CHANGE: usize = SORTED_MEMORY_TIMESTAMP + 1; +pub(crate) const MEMORY_SEGMENT_FIRST_CHANGE: usize = MEMORY_CONTEXT_FIRST_CHANGE + 1; +pub(crate) const MEMORY_VIRTUAL_FIRST_CHANGE: usize = MEMORY_SEGMENT_FIRST_CHANGE + 1; + +pub(crate) const NUM_REGISTERS: usize = MEMORY_VIRTUAL_FIRST_CHANGE + 1;