From 65e4220a487f57b23e551ab31cabe147f711a628 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Mon, 28 Aug 2023 15:56:36 -0400 Subject: [PATCH 01/40] Duplicate Memory trace into BytePacking one --- evm/src/all_stark.rs | 27 +- evm/src/byte_packing/byte_packing_stark.rs | 470 +++++++++++++++++++++ evm/src/byte_packing/columns.rs | 38 ++ evm/src/byte_packing/mod.rs | 7 + evm/src/byte_packing/segments.rs | 193 +++++++++ evm/src/cross_table_lookup.rs | 14 +- evm/src/fixed_recursive_verifier.rs | 17 +- evm/src/lib.rs | 1 + evm/src/memory/memory_stark.rs | 2 +- evm/src/prover.rs | 14 + evm/src/verifier.rs | 25 +- evm/src/witness/traces.rs | 12 +- evm/tests/empty_txn_list.rs | 2 +- 13 files changed, 801 insertions(+), 21 deletions(-) create mode 100644 evm/src/byte_packing/byte_packing_stark.rs create mode 100644 evm/src/byte_packing/columns.rs create mode 100644 evm/src/byte_packing/mod.rs create mode 100644 evm/src/byte_packing/segments.rs diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index b0f3056cf6..8d94b561d7 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -6,11 +6,12 @@ use plonky2::hash::hash_types::RichField; use crate::arithmetic::arithmetic_stark; use crate::arithmetic::arithmetic_stark::ArithmeticStark; +use crate::byte_packing::byte_packing_stark::{self, BytePackingStark}; use crate::config::StarkConfig; use crate::cpu::cpu_stark; use crate::cpu::cpu_stark::CpuStark; use crate::cpu::membus::NUM_GP_CHANNELS; -use crate::cross_table_lookup::{CrossTableLookup, TableWithColumns}; +use crate::cross_table_lookup::{Column, CrossTableLookup, TableWithColumns}; use crate::keccak::keccak_stark; use crate::keccak::keccak_stark::KeccakStark; use crate::keccak_sponge::columns::KECCAK_RATE_BYTES; @@ -30,6 +31,7 @@ pub struct AllStark, const D: usize> { pub keccak_sponge_stark: KeccakSpongeStark, pub logic_stark: LogicStark, pub memory_stark: MemoryStark, + pub byte_packing_stark: BytePackingStark, pub cross_table_lookups: Vec>, } @@ -42,6 +44,7 @@ impl, const D: usize> Default for AllStark { keccak_sponge_stark: KeccakSpongeStark::default(), logic_stark: LogicStark::default(), memory_stark: MemoryStark::default(), + byte_packing_stark: BytePackingStark::default(), cross_table_lookups: all_cross_table_lookups(), } } @@ -56,6 +59,7 @@ impl, const D: usize> AllStark { self.keccak_sponge_stark.num_permutation_batches(config), self.logic_stark.num_permutation_batches(config), self.memory_stark.num_permutation_batches(config), + self.byte_packing_stark.num_permutation_batches(config), ] } @@ -67,6 +71,7 @@ impl, const D: usize> AllStark { self.keccak_sponge_stark.permutation_batch_size(), self.logic_stark.permutation_batch_size(), self.memory_stark.permutation_batch_size(), + self.byte_packing_stark.permutation_batch_size(), ] } } @@ -79,9 +84,10 @@ pub enum Table { KeccakSponge = 3, Logic = 4, Memory = 5, + BytePacking = 6, } -pub(crate) const NUM_TABLES: usize = Table::Memory as usize + 1; +pub(crate) const NUM_TABLES: usize = Table::BytePacking as usize + 1; impl Table { pub(crate) fn all() -> [Self; NUM_TABLES] { @@ -92,6 +98,7 @@ impl Table { Self::KeccakSponge, Self::Logic, Self::Memory, + Self::BytePacking, ] } } @@ -103,6 +110,7 @@ pub(crate) fn all_cross_table_lookups() -> Vec> { ctl_keccak(), ctl_logic(), ctl_memory(), + ctl_byte_packing(), ] } @@ -195,3 +203,18 @@ fn ctl_memory() -> CrossTableLookup { ); CrossTableLookup::new(all_lookers, memory_looked) } + +// TODO: update this +fn ctl_byte_packing() -> CrossTableLookup { + let dummy_read = TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_code_memory(), + Some(Column::zero()), + ); + let byte_packing_looked = TableWithColumns::new( + Table::BytePacking, + byte_packing_stark::ctl_data(), + Some(Column::zero()), + ); + CrossTableLookup::new(vec![dummy_read], byte_packing_looked) +} diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs new file mode 100644 index 0000000000..57361e1507 --- /dev/null +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -0,0 +1,470 @@ +use std::marker::PhantomData; + +use ethereum_types::U256; +use itertools::Itertools; +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 plonky2::util::transpose; +use plonky2_maybe_rayon::*; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cross_table_lookup::Column; +use crate::lookup::{eval_lookups, eval_lookups_circuit, permuted_cols}; +use crate::memory::columns::{ + value_limb, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, CONTEXT_FIRST_CHANGE, COUNTER, + COUNTER_PERMUTED, FILTER, IS_READ, NUM_COLUMNS, RANGE_CHECK, RANGE_CHECK_PERMUTED, + SEGMENT_FIRST_CHANGE, TIMESTAMP, VIRTUAL_FIRST_CHANGE, +}; +use crate::permutation::PermutationPair; +use crate::stark::Stark; +use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; +use crate::witness::memory::MemoryOp; +use crate::witness::memory::MemoryOpKind::Read; + +pub fn ctl_data() -> Vec> { + let mut res = + Column::singles([IS_READ, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL]).collect_vec(); + res.extend(Column::singles((0..8).map(value_limb))); + res.push(Column::single(TIMESTAMP)); + res +} + +pub fn ctl_filter() -> Column { + Column::single(FILTER) +} + +#[derive(Copy, Clone, Default)] +pub struct BytePackingStark { + pub(crate) f: PhantomData, +} + +/// Generates the `_FIRST_CHANGE` columns and the `RANGE_CHECK` column in the trace. +pub fn generate_first_change_flags_and_rc(trace_rows: &mut [[F; NUM_COLUMNS]]) { + let num_ops = trace_rows.len(); + for idx in 0..num_ops - 1 { + let row = trace_rows[idx].as_slice(); + let next_row = trace_rows[idx + 1].as_slice(); + + let context = row[ADDR_CONTEXT]; + let segment = row[ADDR_SEGMENT]; + let virt = row[ADDR_VIRTUAL]; + let timestamp = row[TIMESTAMP]; + let next_context = next_row[ADDR_CONTEXT]; + let next_segment = next_row[ADDR_SEGMENT]; + let next_virt = next_row[ADDR_VIRTUAL]; + let next_timestamp = next_row[TIMESTAMP]; + + let context_changed = context != next_context; + let segment_changed = segment != next_segment; + let virtual_changed = virt != next_virt; + + let context_first_change = context_changed; + let segment_first_change = segment_changed && !context_first_change; + let virtual_first_change = + virtual_changed && !segment_first_change && !context_first_change; + + let row = trace_rows[idx].as_mut_slice(); + row[CONTEXT_FIRST_CHANGE] = F::from_bool(context_first_change); + row[SEGMENT_FIRST_CHANGE] = F::from_bool(segment_first_change); + row[VIRTUAL_FIRST_CHANGE] = F::from_bool(virtual_first_change); + + row[RANGE_CHECK] = if context_first_change { + next_context - context - F::ONE + } else if segment_first_change { + next_segment - segment - F::ONE + } else if virtual_first_change { + next_virt - virt - F::ONE + } else { + next_timestamp - timestamp + }; + + assert!( + row[RANGE_CHECK].to_canonical_u64() < num_ops as u64, + "Range check of {} is too large. Bug in fill_gaps?", + row[RANGE_CHECK] + ); + } +} + +impl, const D: usize> BytePackingStark { + /// Generate most of the trace rows. Excludes a few columns like `COUNTER`, which are generated + /// later, after transposing to column-major form. + fn generate_trace_row_major(&self, mut memory_ops: Vec) -> Vec<[F; NUM_COLUMNS]> { + // fill_gaps expects an ordered list of operations. + memory_ops.sort_by_key(MemoryOp::sorting_key); + Self::fill_gaps(&mut memory_ops); + + Self::pad_memory_ops(&mut memory_ops); + + // fill_gaps may have added operations at the end which break the order, so sort again. + memory_ops.sort_by_key(MemoryOp::sorting_key); + + let mut trace_rows = memory_ops + .into_par_iter() + .map(|op| op.into_row()) + .collect::>(); + generate_first_change_flags_and_rc(trace_rows.as_mut_slice()); + trace_rows + } + + /// Generates the `COUNTER`, `RANGE_CHECK_PERMUTED` and `COUNTER_PERMUTED` columns, given a + /// trace in column-major form. + fn generate_trace_col_major(trace_col_vecs: &mut [Vec]) { + let height = trace_col_vecs[0].len(); + trace_col_vecs[COUNTER] = (0..height).map(|i| F::from_canonical_usize(i)).collect(); + + let (permuted_inputs, permuted_table) = + permuted_cols(&trace_col_vecs[RANGE_CHECK], &trace_col_vecs[COUNTER]); + trace_col_vecs[RANGE_CHECK_PERMUTED] = permuted_inputs; + trace_col_vecs[COUNTER_PERMUTED] = permuted_table; + } + + /// This memory STARK orders rows by `(context, segment, virt, timestamp)`. To enforce the + /// ordering, it range checks the delta of the first field that changed. + /// + /// This method adds some dummy operations to ensure that none of these range checks will be too + /// large, i.e. that they will all be smaller than the number of rows, allowing them to be + /// checked easily with a single lookup. + /// + /// For example, say there are 32 memory operations, and a particular address is accessed at + /// timestamps 20 and 100. 80 would fail the range check, so this method would add two dummy + /// reads to the same address, say at timestamps 50 and 80. + fn fill_gaps(memory_ops: &mut Vec) { + let max_rc = memory_ops.len().next_power_of_two() - 1; + for (mut curr, next) in memory_ops.clone().into_iter().tuple_windows() { + if curr.address.context != next.address.context + || curr.address.segment != next.address.segment + { + // We won't bother to check if there's a large context gap, because there can't be + // more than 500 contexts or so, as explained here: + // https://notes.ethereum.org/@vbuterin/proposals_to_adjust_memory_gas_costs + // Similarly, the number of possible segments is a small constant, so any gap must + // be small. max_rc will always be much larger, as just bootloading the kernel will + // trigger thousands of memory operations. + } else if curr.address.virt != next.address.virt { + while next.address.virt - curr.address.virt - 1 > max_rc { + let mut dummy_address = curr.address; + dummy_address.virt += max_rc + 1; + let dummy_read = MemoryOp::new_dummy_read(dummy_address, 0, U256::zero()); + memory_ops.push(dummy_read); + curr = dummy_read; + } + } else { + while next.timestamp - curr.timestamp > max_rc { + let dummy_read = + MemoryOp::new_dummy_read(curr.address, curr.timestamp + max_rc, curr.value); + memory_ops.push(dummy_read); + curr = dummy_read; + } + } + } + } + + fn pad_memory_ops(memory_ops: &mut Vec) { + let last_op = *memory_ops.last().expect("No memory ops?"); + + // We essentially repeat the last operation until our operation list has the desired size, + // with a few changes: + // - We change its filter to 0 to indicate that this is a dummy operation. + // - We make sure it's a read, since dummy operations must be reads. + let padding_op = MemoryOp { + filter: false, + kind: Read, + ..last_op + }; + + let num_ops = memory_ops.len(); + let num_ops_padded = num_ops.next_power_of_two(); + for _ in num_ops..num_ops_padded { + memory_ops.push(padding_op); + } + } + + pub(crate) fn generate_trace( + &self, + memory_ops: Vec, + timing: &mut TimingTree, + ) -> Vec> { + // Generate most of the trace in row-major form. + let trace_rows = timed!( + timing, + "generate trace rows", + self.generate_trace_row_major(memory_ops) + ); + let trace_row_vecs: Vec<_> = trace_rows.into_iter().map(|row| row.to_vec()).collect(); + + // Transpose to column-major form. + let mut trace_col_vecs = transpose(&trace_row_vecs); + + // A few final generation steps, which work better in column-major form. + Self::generate_trace_col_major(&mut trace_col_vecs); + + trace_col_vecs + .into_iter() + .map(|column| PolynomialValues::new(column)) + .collect() + } +} + +impl, const D: usize> Stark for BytePackingStark { + const COLUMNS: usize = NUM_COLUMNS; + + fn eval_packed_generic( + &self, + vars: StarkEvaluationVars, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + let one = P::from(FE::ONE); + + let timestamp = vars.local_values[TIMESTAMP]; + let addr_context = vars.local_values[ADDR_CONTEXT]; + let addr_segment = vars.local_values[ADDR_SEGMENT]; + let addr_virtual = vars.local_values[ADDR_VIRTUAL]; + let values: Vec<_> = (0..8).map(|i| vars.local_values[value_limb(i)]).collect(); + + let next_timestamp = vars.next_values[TIMESTAMP]; + let next_is_read = vars.next_values[IS_READ]; + let next_addr_context = vars.next_values[ADDR_CONTEXT]; + let next_addr_segment = vars.next_values[ADDR_SEGMENT]; + let next_addr_virtual = vars.next_values[ADDR_VIRTUAL]; + let next_values: Vec<_> = (0..8).map(|i| vars.next_values[value_limb(i)]).collect(); + + // The filter must be 0 or 1. + let filter = vars.local_values[FILTER]; + yield_constr.constraint(filter * (filter - P::ONES)); + + // If this is a dummy row (filter is off), it must be a read. This means the prover can + // insert reads which never appear in the CPU trace (which are harmless), but not writes. + let is_dummy = P::ONES - filter; + let is_write = P::ONES - vars.local_values[IS_READ]; + yield_constr.constraint(is_dummy * is_write); + + let context_first_change = vars.local_values[CONTEXT_FIRST_CHANGE]; + let segment_first_change = vars.local_values[SEGMENT_FIRST_CHANGE]; + let virtual_first_change = vars.local_values[VIRTUAL_FIRST_CHANGE]; + let address_unchanged = + one - context_first_change - segment_first_change - virtual_first_change; + + let range_check = vars.local_values[RANGE_CHECK]; + + 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_address_unchanged = one - address_unchanged; + + // 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(address_unchanged * not_address_unchanged); + + // Second set of ordering constraints: no change before the column corresponding to the nonzero first_change flag. + yield_constr + .constraint_transition(segment_first_change * (next_addr_context - addr_context)); + yield_constr + .constraint_transition(virtual_first_change * (next_addr_context - addr_context)); + yield_constr + .constraint_transition(virtual_first_change * (next_addr_segment - addr_segment)); + yield_constr.constraint_transition(address_unchanged * (next_addr_context - addr_context)); + yield_constr.constraint_transition(address_unchanged * (next_addr_segment - addr_segment)); + yield_constr.constraint_transition(address_unchanged * (next_addr_virtual - addr_virtual)); + + // Third set of ordering constraints: range-check difference in the column that should be increasing. + let computed_range_check = 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) + + address_unchanged * (next_timestamp - timestamp); + yield_constr.constraint_transition(range_check - computed_range_check); + + // Enumerate purportedly-ordered log. + for i in 0..8 { + yield_constr.constraint_transition( + next_is_read * address_unchanged * (next_values[i] - values[i]), + ); + } + + eval_lookups(vars, yield_constr, RANGE_CHECK_PERMUTED, COUNTER_PERMUTED) + } + + fn eval_ext_circuit( + &self, + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + vars: StarkEvaluationTargets, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + let one = builder.one_extension(); + + let addr_context = vars.local_values[ADDR_CONTEXT]; + let addr_segment = vars.local_values[ADDR_SEGMENT]; + let addr_virtual = vars.local_values[ADDR_VIRTUAL]; + let values: Vec<_> = (0..8).map(|i| vars.local_values[value_limb(i)]).collect(); + let timestamp = vars.local_values[TIMESTAMP]; + + let next_addr_context = vars.next_values[ADDR_CONTEXT]; + let next_addr_segment = vars.next_values[ADDR_SEGMENT]; + let next_addr_virtual = vars.next_values[ADDR_VIRTUAL]; + let next_values: Vec<_> = (0..8).map(|i| vars.next_values[value_limb(i)]).collect(); + let next_is_read = vars.next_values[IS_READ]; + let next_timestamp = vars.next_values[TIMESTAMP]; + + // The filter must be 0 or 1. + let filter = vars.local_values[FILTER]; + let constraint = builder.mul_sub_extension(filter, filter, filter); + yield_constr.constraint(builder, constraint); + + // If this is a dummy row (filter is off), it must be a read. This means the prover can + // insert reads which never appear in the CPU trace (which are harmless), but not writes. + let is_dummy = builder.sub_extension(one, filter); + let is_write = builder.sub_extension(one, vars.local_values[IS_READ]); + let is_dummy_write = builder.mul_extension(is_dummy, is_write); + yield_constr.constraint(builder, is_dummy_write); + + let context_first_change = vars.local_values[CONTEXT_FIRST_CHANGE]; + let segment_first_change = vars.local_values[SEGMENT_FIRST_CHANGE]; + let virtual_first_change = vars.local_values[VIRTUAL_FIRST_CHANGE]; + let address_unchanged = { + 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[RANGE_CHECK]; + + 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_address_unchanged = builder.sub_extension(one, address_unchanged); + 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 address_unchanged_bool = + builder.mul_extension(address_unchanged, not_address_unchanged); + yield_constr.constraint(builder, address_unchanged_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_transition(builder, segment_first_change_check); + let virtual_first_change_check_1 = + builder.mul_extension(virtual_first_change, addr_context_diff); + yield_constr.constraint_transition(builder, virtual_first_change_check_1); + let virtual_first_change_check_2 = + builder.mul_extension(virtual_first_change, addr_segment_diff); + yield_constr.constraint_transition(builder, virtual_first_change_check_2); + let address_unchanged_check_1 = builder.mul_extension(address_unchanged, addr_context_diff); + yield_constr.constraint_transition(builder, address_unchanged_check_1); + let address_unchanged_check_2 = builder.mul_extension(address_unchanged, addr_segment_diff); + yield_constr.constraint_transition(builder, address_unchanged_check_2); + let address_unchanged_check_3 = builder.mul_extension(address_unchanged, addr_virtual_diff); + yield_constr.constraint_transition(builder, address_unchanged_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 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 = builder.sub_extension(next_timestamp, timestamp); + let timestamp_range_check = builder.mul_extension(address_unchanged, timestamp_diff); + + let computed_range_check = { + // context_range_check = context_first_change * context_diff + let mut sum = + builder.mul_add_extension(context_first_change, context_diff, 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, computed_range_check); + yield_constr.constraint_transition(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(address_unchanged, value_diff); + let read_constraint = builder.mul_extension(next_is_read, zero_if_read); + yield_constr.constraint_transition(builder, read_constraint); + } + + eval_lookups_circuit( + builder, + vars, + yield_constr, + RANGE_CHECK_PERMUTED, + COUNTER_PERMUTED, + ) + } + + fn constraint_degree(&self) -> usize { + 3 + } + + fn permutation_pairs(&self) -> Vec { + vec![ + PermutationPair::singletons(RANGE_CHECK, RANGE_CHECK_PERMUTED), + PermutationPair::singletons(COUNTER, COUNTER_PERMUTED), + ] + } +} + +#[cfg(test)] +pub(crate) mod tests { + use anyhow::Result; + use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + use crate::byte_packing::byte_packing_stark::BytePackingStark; + 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 = BytePackingStark; + + 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 = BytePackingStark; + + let stark = S { + f: Default::default(), + }; + test_stark_circuit_constraints::(stark) + } +} diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs new file mode 100644 index 0000000000..cb99c190b1 --- /dev/null +++ b/evm/src/byte_packing/columns.rs @@ -0,0 +1,38 @@ +//! Byte packing registers. + +use crate::byte_packing::VALUE_LIMBS; + +// Columns for memory operations, ordered by (addr, timestamp). +/// 1 if this is an actual memory operation, or 0 if it's a padding row. +pub(crate) const FILTER: usize = 0; +pub(crate) const TIMESTAMP: usize = FILTER + 1; +pub(crate) const IS_READ: usize = TIMESTAMP + 1; +pub(crate) const ADDR_CONTEXT: usize = IS_READ + 1; +pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; +pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; + +// Eight 32-bit limbs hold a total of 256 bits. +// If a value represents an integer, it is little-endian encoded. +const VALUE_START: usize = ADDR_VIRTUAL + 1; +pub(crate) const fn value_limb(i: usize) -> usize { + debug_assert!(i < VALUE_LIMBS); + VALUE_START + i +} + +// Flags to indicate whether this part of the address differs from the next row, +// and the previous parts do not differ. +// That is, e.g., `SEGMENT_FIRST_CHANGE` is `F::ONE` iff `ADDR_CONTEXT` is the same in this +// row and the next, but `ADDR_SEGMENT` is not. +pub(crate) const CONTEXT_FIRST_CHANGE: usize = VALUE_START + VALUE_LIMBS; +pub(crate) const SEGMENT_FIRST_CHANGE: usize = CONTEXT_FIRST_CHANGE + 1; +pub(crate) const VIRTUAL_FIRST_CHANGE: usize = SEGMENT_FIRST_CHANGE + 1; + +// We use a range check to enforce the ordering. +pub(crate) const RANGE_CHECK: usize = VIRTUAL_FIRST_CHANGE + 1; +// The counter column (used for the range check) starts from 0 and increments. +pub(crate) const COUNTER: usize = RANGE_CHECK + 1; +// Helper columns for the permutation argument used to enforce the range check. +pub(crate) const RANGE_CHECK_PERMUTED: usize = COUNTER + 1; +pub(crate) const COUNTER_PERMUTED: usize = RANGE_CHECK_PERMUTED + 1; + +pub(crate) const NUM_COLUMNS: usize = COUNTER_PERMUTED + 1; diff --git a/evm/src/byte_packing/mod.rs b/evm/src/byte_packing/mod.rs new file mode 100644 index 0000000000..695ade1f38 --- /dev/null +++ b/evm/src/byte_packing/mod.rs @@ -0,0 +1,7 @@ +pub mod byte_packing_stark; +pub mod columns; +pub mod segments; + +// TODO: Move to CPU module, now that channels have been removed from the memory table. +pub(crate) const NUM_CHANNELS: usize = crate::cpu::membus::NUM_CHANNELS; +pub(crate) const VALUE_LIMBS: usize = 8; diff --git a/evm/src/byte_packing/segments.rs b/evm/src/byte_packing/segments.rs new file mode 100644 index 0000000000..e56d635d63 --- /dev/null +++ b/evm/src/byte_packing/segments.rs @@ -0,0 +1,193 @@ +#[allow(dead_code)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] +pub enum Segment { + /// Contains EVM bytecode. + Code = 0, + /// The program stack. + Stack = 1, + /// Main memory, owned by the contract code. + MainMemory = 2, + /// Data passed to the current context by its caller. + Calldata = 3, + /// Data returned to the current context by its latest callee. + Returndata = 4, + /// A segment which contains a few fixed-size metadata fields, such as the caller's context, or the + /// size of `CALLDATA` and `RETURNDATA`. + GlobalMetadata = 5, + ContextMetadata = 6, + /// General purpose kernel memory, used by various kernel functions. + /// In general, calling a helper function can result in this memory being clobbered. + KernelGeneral = 7, + /// Another segment for general purpose kernel use. + KernelGeneral2 = 8, + /// Segment to hold account code for opcodes like `CODESIZE, CODECOPY,...`. + KernelAccountCode = 9, + /// Contains normalized transaction fields; see `NormalizedTxnField`. + TxnFields = 10, + /// Contains the data field of a transaction. + TxnData = 11, + /// A buffer used to hold raw RLP data. + RlpRaw = 12, + /// Contains all trie data. It is owned by the kernel, so it only lives on context 0. + TrieData = 13, + /// A buffer used to store the encodings of a branch node's children. + TrieEncodedChild = 14, + /// A buffer used to store the lengths of the encodings of a branch node's children. + TrieEncodedChildLen = 15, + /// A table of values 2^i for i=0..255 for use with shift + /// instructions; initialised by `kernel/asm/shift.asm::init_shift_table()`. + ShiftTable = 16, + JumpdestBits = 17, + EcdsaTable = 18, + BnWnafA = 19, + BnWnafB = 20, + BnTableQ = 21, + BnPairing = 22, + /// List of addresses that have been accessed in the current transaction. + AccessedAddresses = 23, + /// List of storage keys that have been accessed in the current transaction. + AccessedStorageKeys = 24, + /// List of addresses that have called SELFDESTRUCT in the current transaction. + SelfDestructList = 25, + /// Contains the bloom filter of a transaction. + TxnBloom = 26, + /// Contains the bloom filter of a block. + BlockBloom = 27, + /// List of log pointers pointing to the LogsData segment. + Logs = 28, + LogsData = 29, + /// Journal of state changes. List of pointers to `JournalData`. Length in `GlobalMetadata`. + Journal = 30, + JournalData = 31, + JournalCheckpoints = 32, + /// List of addresses that have been touched in the current transaction. + TouchedAddresses = 33, + /// List of checkpoints for the current context. Length in `ContextMetadata`. + ContextCheckpoints = 34, +} + +impl Segment { + pub(crate) const COUNT: usize = 35; + + pub(crate) fn all() -> [Self; Self::COUNT] { + [ + Self::Code, + Self::Stack, + Self::MainMemory, + Self::Calldata, + Self::Returndata, + Self::GlobalMetadata, + Self::ContextMetadata, + Self::KernelGeneral, + Self::KernelGeneral2, + Self::KernelAccountCode, + Self::TxnFields, + Self::TxnData, + Self::RlpRaw, + Self::TrieData, + Self::TrieEncodedChild, + Self::TrieEncodedChildLen, + Self::ShiftTable, + Self::JumpdestBits, + Self::EcdsaTable, + Self::BnWnafA, + Self::BnWnafB, + Self::BnTableQ, + Self::BnPairing, + Self::AccessedAddresses, + Self::AccessedStorageKeys, + Self::SelfDestructList, + Self::TxnBloom, + Self::BlockBloom, + Self::Logs, + Self::LogsData, + Self::Journal, + Self::JournalData, + Self::JournalCheckpoints, + Self::TouchedAddresses, + Self::ContextCheckpoints, + ] + } + + /// The variable name that gets passed into kernel assembly code. + pub(crate) fn var_name(&self) -> &'static str { + match self { + Segment::Code => "SEGMENT_CODE", + Segment::Stack => "SEGMENT_STACK", + Segment::MainMemory => "SEGMENT_MAIN_MEMORY", + Segment::Calldata => "SEGMENT_CALLDATA", + Segment::Returndata => "SEGMENT_RETURNDATA", + Segment::GlobalMetadata => "SEGMENT_GLOBAL_METADATA", + Segment::ContextMetadata => "SEGMENT_CONTEXT_METADATA", + Segment::KernelGeneral => "SEGMENT_KERNEL_GENERAL", + Segment::KernelGeneral2 => "SEGMENT_KERNEL_GENERAL_2", + Segment::KernelAccountCode => "SEGMENT_KERNEL_ACCOUNT_CODE", + Segment::TxnFields => "SEGMENT_NORMALIZED_TXN", + Segment::TxnData => "SEGMENT_TXN_DATA", + Segment::RlpRaw => "SEGMENT_RLP_RAW", + Segment::TrieData => "SEGMENT_TRIE_DATA", + Segment::TrieEncodedChild => "SEGMENT_TRIE_ENCODED_CHILD", + Segment::TrieEncodedChildLen => "SEGMENT_TRIE_ENCODED_CHILD_LEN", + Segment::ShiftTable => "SEGMENT_SHIFT_TABLE", + Segment::JumpdestBits => "SEGMENT_JUMPDEST_BITS", + Segment::EcdsaTable => "SEGMENT_KERNEL_ECDSA_TABLE", + Segment::BnWnafA => "SEGMENT_KERNEL_BN_WNAF_A", + Segment::BnWnafB => "SEGMENT_KERNEL_BN_WNAF_B", + Segment::BnTableQ => "SEGMENT_KERNEL_BN_TABLE_Q", + Segment::BnPairing => "SEGMENT_KERNEL_BN_PAIRING", + Segment::AccessedAddresses => "SEGMENT_ACCESSED_ADDRESSES", + Segment::AccessedStorageKeys => "SEGMENT_ACCESSED_STORAGE_KEYS", + Segment::SelfDestructList => "SEGMENT_SELFDESTRUCT_LIST", + Segment::TxnBloom => "SEGMENT_TXN_BLOOM", + Segment::BlockBloom => "SEGMENT_BLOCK_BLOOM", + Segment::Logs => "SEGMENT_LOGS", + Segment::LogsData => "SEGMENT_LOGS_DATA", + Segment::Journal => "SEGMENT_JOURNAL", + Segment::JournalData => "SEGMENT_JOURNAL_DATA", + Segment::JournalCheckpoints => "SEGMENT_JOURNAL_CHECKPOINTS", + Segment::TouchedAddresses => "SEGMENT_TOUCHED_ADDRESSES", + Segment::ContextCheckpoints => "SEGMENT_CONTEXT_CHECKPOINTS", + } + } + + #[allow(dead_code)] + pub(crate) fn bit_range(&self) -> usize { + match self { + Segment::Code => 8, + Segment::Stack => 256, + Segment::MainMemory => 8, + Segment::Calldata => 8, + Segment::Returndata => 8, + Segment::GlobalMetadata => 256, + Segment::ContextMetadata => 256, + Segment::KernelGeneral => 256, + Segment::KernelGeneral2 => 256, + Segment::KernelAccountCode => 8, + Segment::TxnFields => 256, + Segment::TxnData => 8, + Segment::RlpRaw => 8, + Segment::TrieData => 256, + Segment::TrieEncodedChild => 256, + Segment::TrieEncodedChildLen => 6, + Segment::ShiftTable => 256, + Segment::JumpdestBits => 1, + Segment::EcdsaTable => 256, + Segment::BnWnafA => 8, + Segment::BnWnafB => 8, + Segment::BnTableQ => 256, + Segment::BnPairing => 256, + Segment::AccessedAddresses => 256, + Segment::AccessedStorageKeys => 256, + Segment::SelfDestructList => 256, + Segment::TxnBloom => 8, + Segment::BlockBloom => 8, + Segment::Logs => 256, + Segment::LogsData => 256, + Segment::Journal => 256, + Segment::JournalData => 256, + Segment::JournalCheckpoints => 256, + Segment::TouchedAddresses => 256, + Segment::ContextCheckpoints => 256, + } + } +} diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 8f481325c6..a9b90428ca 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -536,10 +536,13 @@ pub(crate) fn verify_cross_table_lookups, const D: config: &StarkConfig, ) -> Result<()> { let mut ctl_zs_openings = ctl_zs_lasts.iter().map(|v| v.iter()).collect::>(); - for CrossTableLookup { - looking_tables, - looked_table, - } in cross_table_lookups.iter() + for ( + index, + CrossTableLookup { + looking_tables, + looked_table, + }, + ) in cross_table_lookups.iter().enumerate() { let extra_product_vec = &ctl_extra_looking_products[looked_table.table as usize]; for c in 0..config.num_challenges { @@ -552,7 +555,8 @@ pub(crate) fn verify_cross_table_lookups, const D: let looked_z = *ctl_zs_openings[looked_table.table as usize].next().unwrap(); ensure!( looking_zs_prod == looked_z, - "Cross-table lookup verification failed." + "Cross-table lookup {:?} verification failed.", + index ); } } diff --git a/evm/src/fixed_recursive_verifier.rs b/evm/src/fixed_recursive_verifier.rs index 59b6026379..12b5e717b5 100644 --- a/evm/src/fixed_recursive_verifier.rs +++ b/evm/src/fixed_recursive_verifier.rs @@ -413,8 +413,23 @@ where &all_stark.cross_table_lookups, stark_config, ); + let byte_packing = RecursiveCircuitsForTable::new( + Table::BytePacking, + &all_stark.byte_packing_stark, + degree_bits_ranges[6].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); - let by_table = [arithmetic, cpu, keccak, keccak_sponge, logic, memory]; + let by_table = [ + arithmetic, + cpu, + keccak, + keccak_sponge, + logic, + memory, + byte_packing, + ]; let root = Self::create_root_circuit(&by_table, stark_config); let aggregation = Self::create_aggregation_circuit(&root); let block = Self::create_block_circuit(&aggregation); diff --git a/evm/src/lib.rs b/evm/src/lib.rs index 29ad6738fc..ab48cda04f 100644 --- a/evm/src/lib.rs +++ b/evm/src/lib.rs @@ -8,6 +8,7 @@ pub mod all_stark; pub mod arithmetic; +pub mod byte_packing; pub mod config; pub mod constraint_consumer; pub mod cpu; diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index 36f7566543..22802ae09d 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -49,7 +49,7 @@ impl MemoryOp { /// depend on the next operation, such as `CONTEXT_FIRST_CHANGE`; those are generated later. /// It also does not generate columns such as `COUNTER`, which are generated later, after the /// trace has been transposed into column-major form. - fn into_row(self) -> [F; NUM_COLUMNS] { + pub(crate) fn into_row(self) -> [F; NUM_COLUMNS] { let mut row = [F::ZERO; NUM_COLUMNS]; row[FILTER] = F::from_bool(self.filter); row[TIMESTAMP] = F::from_canonical_usize(self.timestamp); diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 31be89e701..e611b9dc00 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -277,6 +277,19 @@ where timing, )? ); + let byte_packing_proof = timed!( + timing, + "prove byte packing STARK", + prove_single_table( + &all_stark.byte_packing_stark, + config, + &trace_poly_values[Table::BytePacking as usize], + &trace_commitments[Table::BytePacking as usize], + &ctl_data_per_table[Table::BytePacking as usize], + challenger, + timing, + )? + ); Ok([ arithmetic_proof, cpu_proof, @@ -284,6 +297,7 @@ where keccak_sponge_proof, logic_proof, memory_proof, + byte_packing_proof, ]) } diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index 218f41ad07..f20c07b459 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -2,6 +2,7 @@ use std::any::type_name; use anyhow::{ensure, Result}; use ethereum_types::U256; +use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::types::Field; use plonky2::fri::verifier::verify_fri_proof; @@ -58,6 +59,7 @@ where keccak_sponge_stark, logic_stark, memory_stark, + byte_packing_stark, cross_table_lookups, } = all_stark; @@ -103,6 +105,13 @@ where &ctl_vars_per_table[Table::Memory as usize], config, )?; + verify_stark_proof_with_challenges( + byte_packing_stark, + &all_proof.stark_proofs[Table::BytePacking as usize].proof, + &stark_challenges[Table::BytePacking as usize], + &ctl_vars_per_table[Table::BytePacking as usize], + config, + )?; verify_stark_proof_with_challenges( logic_stark, &all_proof.stark_proofs[Table::Logic as usize].proof, @@ -113,18 +122,14 @@ where let public_values = all_proof.public_values; - // Extra products to add to the looked last value - // Arithmetic, KeccakSponge, Keccak, Logic - let mut extra_looking_products = vec![vec![F::ONE; config.num_challenges]; NUM_TABLES - 1]; + // Extra products to add to the looked last value. + // Only necessary for the Memory values. + let mut extra_looking_products = vec![vec![F::ONE; config.num_challenges]; NUM_TABLES]; // Memory - extra_looking_products.push(Vec::new()); - for c in 0..config.num_challenges { - extra_looking_products[Table::Memory as usize].push(get_memory_extra_looking_products( - &public_values, - ctl_challenges.challenges[c], - )); - } + extra_looking_products[Table::Memory as usize] = (0..config.num_challenges) + .map(|i| get_memory_extra_looking_products(&public_values, ctl_challenges.challenges[i])) + .collect_vec(); verify_cross_table_lookups::( cross_table_lookups, diff --git a/evm/src/witness/traces.rs b/evm/src/witness/traces.rs index 4a1c8d8514..e6a6b9823e 100644 --- a/evm/src/witness/traces.rs +++ b/evm/src/witness/traces.rs @@ -162,7 +162,16 @@ impl Traces { let memory_trace = timed!( timing, "generate memory trace", - all_stark.memory_stark.generate_trace(memory_ops, timing) + all_stark + .memory_stark + .generate_trace(memory_ops.clone(), timing) + ); + let byte_packing_trace = timed!( + timing, + "generate byte packing trace", + all_stark + .byte_packing_stark + .generate_trace(memory_ops, timing) ); [ @@ -172,6 +181,7 @@ impl Traces { keccak_sponge_trace, logic_trace, memory_trace, + byte_packing_trace, ] } } diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs index 7ad04bdc83..717d056905 100644 --- a/evm/tests/empty_txn_list.rs +++ b/evm/tests/empty_txn_list.rs @@ -67,7 +67,7 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 15..16, 14..15, 9..10, 12..13, 18..19], // Minimal ranges to prove an empty list + &[16..17, 15..16, 14..15, 9..10, 12..13, 18..19, 18..19], // Minimal ranges to prove an empty list &config, ); From 78c1333c58b762f0ebdc2a047cbf072b4a60d386 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Mon, 28 Aug 2023 16:32:40 -0400 Subject: [PATCH 02/40] Add mload_32bytes instruction --- evm/src/cpu/columns/ops.rs | 1 + evm/src/cpu/decode.rs | 3 ++- evm/src/cpu/gas.rs | 1 + evm/src/cpu/kernel/interpreter.rs | 2 ++ evm/src/cpu/kernel/opcodes.rs | 1 + evm/src/cpu/stack.rs | 5 +++++ evm/src/witness/gas.rs | 1 + evm/src/witness/operation.rs | 27 +++++++++++++++++++++++++++ evm/src/witness/transition.rs | 3 +++ 9 files changed, 43 insertions(+), 1 deletion(-) diff --git a/evm/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs index 81d8414af6..bd2273b842 100644 --- a/evm/src/cpu/columns/ops.rs +++ b/evm/src/cpu/columns/ops.rs @@ -41,6 +41,7 @@ pub struct OpsColumnsView { pub dup: T, pub swap: T, pub context_op: T, + pub mload_32bytes: T, pub exit_kernel: T, // TODO: combine MLOAD_GENERAL and MSTORE_GENERAL into one flag pub mload_general: T, diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index e21000f8e2..6eb29acd40 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -22,7 +22,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; /// behavior. /// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to /// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid. -const OPCODES: [(u8, usize, bool, usize); 31] = [ +const OPCODES: [(u8, usize, bool, usize); 32] = [ // (start index of block, number of top bits to check (log2), kernel-only, flag column) (0x01, 0, false, COL_MAP.op.add), (0x02, 0, false, COL_MAP.op.mul), @@ -53,6 +53,7 @@ const OPCODES: [(u8, usize, bool, usize); 31] = [ (0x80, 4, false, COL_MAP.op.dup), // 0x80-0x8f (0x90, 4, false, COL_MAP.op.swap), // 0x90-0x9f (0xf6, 1, true, COL_MAP.op.context_op), // 0xf6-0xf7 + (0xf8, 0, true, COL_MAP.op.mload_32bytes), (0xf9, 0, true, COL_MAP.op.exit_kernel), (0xfb, 0, true, COL_MAP.op.mload_general), (0xfc, 0, true, COL_MAP.op.mstore_general), diff --git a/evm/src/cpu/gas.rs b/evm/src/cpu/gas.rs index 616900052f..d32dfbcda4 100644 --- a/evm/src/cpu/gas.rs +++ b/evm/src/cpu/gas.rs @@ -49,6 +49,7 @@ const SIMPLE_OPCODES: OpsColumnsView> = OpsColumnsView { dup: G_VERYLOW, swap: G_VERYLOW, context_op: KERNEL_ONLY_INSTR, + mload_32bytes: KERNEL_ONLY_INSTR, exit_kernel: None, mload_general: KERNEL_ONLY_INSTR, mstore_general: KERNEL_ONLY_INSTR, diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 98ea3cc217..9980fed0d9 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -399,6 +399,7 @@ impl<'a> Interpreter<'a> { 0xf5 => todo!(), // "CREATE2", 0xf6 => self.run_get_context(), // "GET_CONTEXT", 0xf7 => self.run_set_context(), // "SET_CONTEXT", + 0xf8 => todo!(), // "MLOAD_32BYTES", 0xf9 => todo!(), // "EXIT_KERNEL", 0xfa => todo!(), // "STATICCALL", 0xfb => self.run_mload_general(), // "MLOAD_GENERAL", @@ -1278,6 +1279,7 @@ fn get_mnemonic(opcode: u8) -> &'static str { 0xf5 => "CREATE2", 0xf6 => "GET_CONTEXT", 0xf7 => "SET_CONTEXT", + 0xf8 => "MLOAD_32BYTES", 0xf9 => "EXIT_KERNEL", 0xfa => "STATICCALL", 0xfb => "MLOAD_GENERAL", diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index 09c493e04f..4caa46a67a 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -121,6 +121,7 @@ pub fn get_opcode(mnemonic: &str) -> u8 { "CREATE2" => 0xf5, "GET_CONTEXT" => 0xf6, "SET_CONTEXT" => 0xf7, + "MLOAD_32BYTES" => 0xf8, "EXIT_KERNEL" => 0xf9, "STATICCALL" => 0xfa, "MLOAD_GENERAL" => 0xfb, diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index 8ffc152d4c..ffe93aba53 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -108,6 +108,11 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { dup: None, swap: None, context_op: None, // SET_CONTEXT is special since it involves the old and the new stack. + mload_32bytes: Some(StackBehavior { + num_pops: 3, + pushes: true, + disable_other_channels: false, + }), exit_kernel: Some(StackBehavior { num_pops: 1, pushes: false, diff --git a/evm/src/witness/gas.rs b/evm/src/witness/gas.rs index 4c7947bb6b..63db0d7c64 100644 --- a/evm/src/witness/gas.rs +++ b/evm/src/witness/gas.rs @@ -44,6 +44,7 @@ pub(crate) fn gas_to_charge(op: Operation) -> u64 { Swap(_) => G_VERYLOW, GetContext => KERNEL_ONLY_INSTR, SetContext => KERNEL_ONLY_INSTR, + Mload32Bytes => KERNEL_ONLY_INSTR, ExitKernel => KERNEL_ONLY_INSTR, MloadGeneral => KERNEL_ONLY_INSTR, MstoreGeneral => KERNEL_ONLY_INSTR, diff --git a/evm/src/witness/operation.rs b/evm/src/witness/operation.rs index 13619b96a7..96ed74bf13 100644 --- a/evm/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -47,6 +47,7 @@ pub(crate) enum Operation { Swap(u8), GetContext, SetContext, + Mload32Bytes, ExitKernel, MloadGeneral, MstoreGeneral, @@ -686,6 +687,32 @@ pub(crate) fn generate_mload_general( Ok(()) } +// TODO: Update this +pub(crate) fn generate_mload_32bytes( + state: &mut GenerationState, + mut row: CpuColumnsView, +) -> Result<(), ProgramError> { + let [(context, log_in0), (segment, log_in1), (virt, log_in2)] = + stack_pop_with_log_and_fill::<3, _>(state, &mut row)?; + + let (val, log_read) = mem_read_gp_with_log_and_fill( + 3, + MemoryAddress::new_u256s(context, segment, virt)?, + state, + &mut row, + ); + + let log_out = stack_push_log_and_fill(state, &mut row, val)?; + + state.traces.push_memory(log_in0); + state.traces.push_memory(log_in1); + state.traces.push_memory(log_in2); + state.traces.push_memory(log_read); + state.traces.push_memory(log_out); + state.traces.push_cpu(row); + Ok(()) +} + pub(crate) fn generate_mstore_general( state: &mut GenerationState, mut row: CpuColumnsView, diff --git a/evm/src/witness/transition.rs b/evm/src/witness/transition.rs index e9b8c09b69..6f2b1e9cbc 100644 --- a/evm/src/witness/transition.rs +++ b/evm/src/witness/transition.rs @@ -136,6 +136,7 @@ fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 4, false)), // CREATE2 (0xf6, true) => Ok(Operation::GetContext), (0xf7, true) => Ok(Operation::SetContext), + (0xf8, true) => Ok(Operation::Mload32Bytes), (0xf9, true) => Ok(Operation::ExitKernel), (0xfa, _) => Ok(Operation::Syscall(opcode, 6, false)), // STATICCALL (0xfb, true) => Ok(Operation::MloadGeneral), @@ -183,6 +184,7 @@ fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) { Operation::Pc => &mut flags.pc, Operation::Jumpdest => &mut flags.jumpdest, Operation::GetContext | Operation::SetContext => &mut flags.context_op, + Operation::Mload32Bytes => &mut flags.mload_32bytes, Operation::ExitKernel => &mut flags.exit_kernel, Operation::MloadGeneral => &mut flags.mload_general, Operation::MstoreGeneral => &mut flags.mstore_general, @@ -220,6 +222,7 @@ fn perform_op( Operation::Jumpdest => generate_jumpdest(state, row)?, Operation::GetContext => generate_get_context(state, row)?, Operation::SetContext => generate_set_context(state, row)?, + Operation::Mload32Bytes => generate_mload_32bytes(state, row)?, Operation::ExitKernel => generate_exit_kernel(state, row)?, Operation::MloadGeneral => generate_mload_general(state, row)?, Operation::MstoreGeneral => generate_mstore_general(state, row)?, From bba19988a3b2cb0c782513f5e54e911a5932dac1 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Mon, 28 Aug 2023 16:50:09 -0400 Subject: [PATCH 03/40] Use dedicated ops for byte packing trace --- evm/src/byte_packing/byte_packing_stark.rs | 13 ++++++++++--- evm/src/witness/operation.rs | 5 +++++ evm/src/witness/traces.rs | 12 +++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 57361e1507..ec5fb95218 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -23,8 +23,8 @@ use crate::memory::columns::{ use crate::permutation::PermutationPair; use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; -use crate::witness::memory::MemoryOp; use crate::witness::memory::MemoryOpKind::Read; +use crate::witness::memory::{MemoryAddress, MemoryOp}; pub fn ctl_data() -> Vec> { let mut res = @@ -166,7 +166,14 @@ impl, const D: usize> BytePackingStark { } fn pad_memory_ops(memory_ops: &mut Vec) { - let last_op = *memory_ops.last().expect("No memory ops?"); + // TODO: change + let last_op = *memory_ops.last().unwrap_or(&MemoryOp { + filter: false, + timestamp: 0, + address: MemoryAddress::new(0, crate::memory::segments::Segment::AccessedAddresses, 0), + kind: Read, + value: U256::zero(), + }); // We essentially repeat the last operation until our operation list has the desired size, // with a few changes: @@ -179,7 +186,7 @@ impl, const D: usize> BytePackingStark { }; let num_ops = memory_ops.len(); - let num_ops_padded = num_ops.next_power_of_two(); + let num_ops_padded = core::cmp::max(16, num_ops).next_power_of_two(); for _ in num_ops..num_ops_padded { memory_ops.push(padding_op); } diff --git a/evm/src/witness/operation.rs b/evm/src/witness/operation.rs index 96ed74bf13..813446b3b8 100644 --- a/evm/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -709,6 +709,11 @@ pub(crate) fn generate_mload_32bytes( state.traces.push_memory(log_in2); state.traces.push_memory(log_read); state.traces.push_memory(log_out); + state.traces.push_byte_packing(log_in0); + state.traces.push_byte_packing(log_in1); + state.traces.push_byte_packing(log_in2); + state.traces.push_byte_packing(log_read); + state.traces.push_byte_packing(log_out); state.traces.push_cpu(row); Ok(()) } diff --git a/evm/src/witness/traces.rs b/evm/src/witness/traces.rs index e6a6b9823e..9bd801c4db 100644 --- a/evm/src/witness/traces.rs +++ b/evm/src/witness/traces.rs @@ -19,6 +19,7 @@ use crate::{arithmetic, keccak, logic}; #[derive(Clone, Copy, Debug)] pub struct TraceCheckpoint { pub(self) arithmetic_len: usize, + pub(self) byte_packing_len: usize, pub(self) cpu_len: usize, pub(self) keccak_len: usize, pub(self) keccak_sponge_len: usize, @@ -29,6 +30,7 @@ pub struct TraceCheckpoint { #[derive(Debug)] pub(crate) struct Traces { pub(crate) arithmetic_ops: Vec, + pub(crate) byte_packing_ops: Vec, pub(crate) cpu: Vec>, pub(crate) logic_ops: Vec, pub(crate) memory_ops: Vec, @@ -40,6 +42,7 @@ impl Traces { pub fn new() -> Self { Traces { arithmetic_ops: vec![], + byte_packing_ops: vec![], cpu: vec![], logic_ops: vec![], memory_ops: vec![], @@ -51,6 +54,7 @@ impl Traces { pub fn checkpoint(&self) -> TraceCheckpoint { TraceCheckpoint { arithmetic_len: self.arithmetic_ops.len(), + byte_packing_len: self.byte_packing_ops.len(), cpu_len: self.cpu.len(), keccak_len: self.keccak_inputs.len(), keccak_sponge_len: self.keccak_sponge_ops.len(), @@ -61,6 +65,7 @@ impl Traces { pub fn rollback(&mut self, checkpoint: TraceCheckpoint) { self.arithmetic_ops.truncate(checkpoint.arithmetic_len); + self.byte_packing_ops.truncate(checkpoint.byte_packing_len); self.cpu.truncate(checkpoint.cpu_len); self.keccak_inputs.truncate(checkpoint.keccak_len); self.keccak_sponge_ops @@ -89,6 +94,10 @@ impl Traces { self.memory_ops.push(op); } + pub fn push_byte_packing(&mut self, op: MemoryOp) { + self.byte_packing_ops.push(op); + } + pub fn push_keccak(&mut self, input: [u64; keccak::keccak_stark::NUM_INPUTS]) { self.keccak_inputs.push(input); } @@ -123,6 +132,7 @@ impl Traces { let cap_elements = config.fri_config.num_cap_elements(); let Traces { arithmetic_ops, + byte_packing_ops, cpu, logic_ops, memory_ops, @@ -171,7 +181,7 @@ impl Traces { "generate byte packing trace", all_stark .byte_packing_stark - .generate_trace(memory_ops, timing) + .generate_trace(byte_packing_ops, timing) ); [ From eb153614f065d1e5e371070db106611d7942453b Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 29 Aug 2023 12:45:36 -0400 Subject: [PATCH 04/40] Change witness generation to reduce memory reads for MLOAD_32BYTES --- evm/src/byte_packing/byte_packing_stark.rs | 423 +++------------------ evm/src/byte_packing/columns.rs | 41 +- evm/src/byte_packing/mod.rs | 3 +- evm/src/cpu/kernel/asm/memory/packing.asm | 38 +- evm/src/fixed_recursive_verifier.rs | 2 + evm/src/prover.rs | 5 + evm/src/verifier.rs | 2 + evm/src/witness/operation.rs | 39 +- evm/src/witness/traces.rs | 8 +- evm/src/witness/util.rs | 22 ++ 10 files changed, 133 insertions(+), 450 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index ec5fb95218..97e94a07f8 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -1,6 +1,8 @@ +// TODO: Remove +#![allow(unused)] + use std::marker::PhantomData; -use ethereum_types::U256; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::packed::PackedField; @@ -9,28 +11,19 @@ use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; -use plonky2::util::transpose; -use plonky2_maybe_rayon::*; +use crate::byte_packing::columns::{value_bytes, value_limb, FILTER, NUM_COLUMNS, REMAINING_LEN}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; -use crate::lookup::{eval_lookups, eval_lookups_circuit, permuted_cols}; -use crate::memory::columns::{ - value_limb, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, CONTEXT_FIRST_CHANGE, COUNTER, - COUNTER_PERMUTED, FILTER, IS_READ, NUM_COLUMNS, RANGE_CHECK, RANGE_CHECK_PERMUTED, - SEGMENT_FIRST_CHANGE, TIMESTAMP, VIRTUAL_FIRST_CHANGE, -}; -use crate::permutation::PermutationPair; use crate::stark::Stark; +use crate::util::trace_rows_to_poly_values; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; -use crate::witness::memory::MemoryOpKind::Read; -use crate::witness::memory::{MemoryAddress, MemoryOp}; +// TODO: change pub fn ctl_data() -> Vec> { - let mut res = - Column::singles([IS_READ, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL]).collect_vec(); + let mut res = Column::singles([FILTER, FILTER, FILTER, FILTER]).collect_vec(); res.extend(Column::singles((0..8).map(value_limb))); - res.push(Column::single(TIMESTAMP)); + res.push(Column::single(FILTER)); res } @@ -43,178 +36,65 @@ pub struct BytePackingStark { pub(crate) f: PhantomData, } -/// Generates the `_FIRST_CHANGE` columns and the `RANGE_CHECK` column in the trace. -pub fn generate_first_change_flags_and_rc(trace_rows: &mut [[F; NUM_COLUMNS]]) { - let num_ops = trace_rows.len(); - for idx in 0..num_ops - 1 { - let row = trace_rows[idx].as_slice(); - let next_row = trace_rows[idx + 1].as_slice(); - - let context = row[ADDR_CONTEXT]; - let segment = row[ADDR_SEGMENT]; - let virt = row[ADDR_VIRTUAL]; - let timestamp = row[TIMESTAMP]; - let next_context = next_row[ADDR_CONTEXT]; - let next_segment = next_row[ADDR_SEGMENT]; - let next_virt = next_row[ADDR_VIRTUAL]; - let next_timestamp = next_row[TIMESTAMP]; - - let context_changed = context != next_context; - let segment_changed = segment != next_segment; - let virtual_changed = virt != next_virt; - - let context_first_change = context_changed; - let segment_first_change = segment_changed && !context_first_change; - let virtual_first_change = - virtual_changed && !segment_first_change && !context_first_change; - - let row = trace_rows[idx].as_mut_slice(); - row[CONTEXT_FIRST_CHANGE] = F::from_bool(context_first_change); - row[SEGMENT_FIRST_CHANGE] = F::from_bool(segment_first_change); - row[VIRTUAL_FIRST_CHANGE] = F::from_bool(virtual_first_change); - - row[RANGE_CHECK] = if context_first_change { - next_context - context - F::ONE - } else if segment_first_change { - next_segment - segment - F::ONE - } else if virtual_first_change { - next_virt - virt - F::ONE - } else { - next_timestamp - timestamp - }; - - assert!( - row[RANGE_CHECK].to_canonical_u64() < num_ops as u64, - "Range check of {} is too large. Bug in fill_gaps?", - row[RANGE_CHECK] - ); - } -} - impl, const D: usize> BytePackingStark { - /// Generate most of the trace rows. Excludes a few columns like `COUNTER`, which are generated - /// later, after transposing to column-major form. - fn generate_trace_row_major(&self, mut memory_ops: Vec) -> Vec<[F; NUM_COLUMNS]> { - // fill_gaps expects an ordered list of operations. - memory_ops.sort_by_key(MemoryOp::sorting_key); - Self::fill_gaps(&mut memory_ops); - - Self::pad_memory_ops(&mut memory_ops); + pub(crate) fn generate_trace( + &self, + grouped_bytes: Vec>, + min_rows: usize, + timing: &mut TimingTree, + ) -> Vec> { + // Generate most of the trace in row-major form. + let trace_rows = timed!( + timing, + "generate trace rows", + self.generate_trace_rows(grouped_bytes, min_rows) + ); - // fill_gaps may have added operations at the end which break the order, so sort again. - memory_ops.sort_by_key(MemoryOp::sorting_key); + let trace_polys = timed!( + timing, + "convert to PolynomialValues", + trace_rows_to_poly_values(trace_rows) + ); - let mut trace_rows = memory_ops - .into_par_iter() - .map(|op| op.into_row()) - .collect::>(); - generate_first_change_flags_and_rc(trace_rows.as_mut_slice()); - trace_rows + trace_polys } - /// Generates the `COUNTER`, `RANGE_CHECK_PERMUTED` and `COUNTER_PERMUTED` columns, given a - /// trace in column-major form. - fn generate_trace_col_major(trace_col_vecs: &mut [Vec]) { - let height = trace_col_vecs[0].len(); - trace_col_vecs[COUNTER] = (0..height).map(|i| F::from_canonical_usize(i)).collect(); - - let (permuted_inputs, permuted_table) = - permuted_cols(&trace_col_vecs[RANGE_CHECK], &trace_col_vecs[COUNTER]); - trace_col_vecs[RANGE_CHECK_PERMUTED] = permuted_inputs; - trace_col_vecs[COUNTER_PERMUTED] = permuted_table; - } + fn generate_trace_rows( + &self, + grouped_bytes: Vec>, + min_rows: usize, + ) -> Vec<[F; NUM_COLUMNS]> { + let base_len: usize = grouped_bytes.iter().map(|bytes| bytes.len()).sum(); + let mut rows = Vec::with_capacity(base_len.max(min_rows).next_power_of_two()); + + for bytes in grouped_bytes { + rows.extend(self.generate_rows_for_bytes(bytes)); + } - /// This memory STARK orders rows by `(context, segment, virt, timestamp)`. To enforce the - /// ordering, it range checks the delta of the first field that changed. - /// - /// This method adds some dummy operations to ensure that none of these range checks will be too - /// large, i.e. that they will all be smaller than the number of rows, allowing them to be - /// checked easily with a single lookup. - /// - /// For example, say there are 32 memory operations, and a particular address is accessed at - /// timestamps 20 and 100. 80 would fail the range check, so this method would add two dummy - /// reads to the same address, say at timestamps 50 and 80. - fn fill_gaps(memory_ops: &mut Vec) { - let max_rc = memory_ops.len().next_power_of_two() - 1; - for (mut curr, next) in memory_ops.clone().into_iter().tuple_windows() { - if curr.address.context != next.address.context - || curr.address.segment != next.address.segment - { - // We won't bother to check if there's a large context gap, because there can't be - // more than 500 contexts or so, as explained here: - // https://notes.ethereum.org/@vbuterin/proposals_to_adjust_memory_gas_costs - // Similarly, the number of possible segments is a small constant, so any gap must - // be small. max_rc will always be much larger, as just bootloading the kernel will - // trigger thousands of memory operations. - } else if curr.address.virt != next.address.virt { - while next.address.virt - curr.address.virt - 1 > max_rc { - let mut dummy_address = curr.address; - dummy_address.virt += max_rc + 1; - let dummy_read = MemoryOp::new_dummy_read(dummy_address, 0, U256::zero()); - memory_ops.push(dummy_read); - curr = dummy_read; - } - } else { - while next.timestamp - curr.timestamp > max_rc { - let dummy_read = - MemoryOp::new_dummy_read(curr.address, curr.timestamp + max_rc, curr.value); - memory_ops.push(dummy_read); - curr = dummy_read; - } - } + let padded_rows = rows.len().max(min_rows).next_power_of_two(); + for _ in rows.len()..padded_rows { + rows.push(self.generate_padding_row()); } + rows } - fn pad_memory_ops(memory_ops: &mut Vec) { - // TODO: change - let last_op = *memory_ops.last().unwrap_or(&MemoryOp { - filter: false, - timestamp: 0, - address: MemoryAddress::new(0, crate::memory::segments::Segment::AccessedAddresses, 0), - kind: Read, - value: U256::zero(), - }); + fn generate_rows_for_bytes(&self, bytes: Vec) -> Vec<[F; NUM_COLUMNS]> { + let mut rows = Vec::with_capacity(bytes.len()); + let mut row = [F::ZERO; NUM_COLUMNS]; + row[FILTER] = F::ONE; - // We essentially repeat the last operation until our operation list has the desired size, - // with a few changes: - // - We change its filter to 0 to indicate that this is a dummy operation. - // - We make sure it's a read, since dummy operations must be reads. - let padding_op = MemoryOp { - filter: false, - kind: Read, - ..last_op - }; + for (i, &byte) in bytes.iter().enumerate() { + row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1); + row[value_bytes(i)] = F::from_canonical_u8(byte); - let num_ops = memory_ops.len(); - let num_ops_padded = core::cmp::max(16, num_ops).next_power_of_two(); - for _ in num_ops..num_ops_padded { - memory_ops.push(padding_op); + rows.push(row.into()); } - } - - pub(crate) fn generate_trace( - &self, - memory_ops: Vec, - timing: &mut TimingTree, - ) -> Vec> { - // Generate most of the trace in row-major form. - let trace_rows = timed!( - timing, - "generate trace rows", - self.generate_trace_row_major(memory_ops) - ); - let trace_row_vecs: Vec<_> = trace_rows.into_iter().map(|row| row.to_vec()).collect(); - - // Transpose to column-major form. - let mut trace_col_vecs = transpose(&trace_row_vecs); - // A few final generation steps, which work better in column-major form. - Self::generate_trace_col_major(&mut trace_col_vecs); + rows + } - trace_col_vecs - .into_iter() - .map(|column| PolynomialValues::new(column)) - .collect() + fn generate_padding_row(&self) -> [F; NUM_COLUMNS] { + [F::ZERO; NUM_COLUMNS] } } @@ -230,75 +110,7 @@ impl, const D: usize> Stark for BytePackingSt P: PackedField, { let one = P::from(FE::ONE); - - let timestamp = vars.local_values[TIMESTAMP]; - let addr_context = vars.local_values[ADDR_CONTEXT]; - let addr_segment = vars.local_values[ADDR_SEGMENT]; - let addr_virtual = vars.local_values[ADDR_VIRTUAL]; - let values: Vec<_> = (0..8).map(|i| vars.local_values[value_limb(i)]).collect(); - - let next_timestamp = vars.next_values[TIMESTAMP]; - let next_is_read = vars.next_values[IS_READ]; - let next_addr_context = vars.next_values[ADDR_CONTEXT]; - let next_addr_segment = vars.next_values[ADDR_SEGMENT]; - let next_addr_virtual = vars.next_values[ADDR_VIRTUAL]; - let next_values: Vec<_> = (0..8).map(|i| vars.next_values[value_limb(i)]).collect(); - - // The filter must be 0 or 1. - let filter = vars.local_values[FILTER]; - yield_constr.constraint(filter * (filter - P::ONES)); - - // If this is a dummy row (filter is off), it must be a read. This means the prover can - // insert reads which never appear in the CPU trace (which are harmless), but not writes. - let is_dummy = P::ONES - filter; - let is_write = P::ONES - vars.local_values[IS_READ]; - yield_constr.constraint(is_dummy * is_write); - - let context_first_change = vars.local_values[CONTEXT_FIRST_CHANGE]; - let segment_first_change = vars.local_values[SEGMENT_FIRST_CHANGE]; - let virtual_first_change = vars.local_values[VIRTUAL_FIRST_CHANGE]; - let address_unchanged = - one - context_first_change - segment_first_change - virtual_first_change; - - let range_check = vars.local_values[RANGE_CHECK]; - - 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_address_unchanged = one - address_unchanged; - - // 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(address_unchanged * not_address_unchanged); - - // Second set of ordering constraints: no change before the column corresponding to the nonzero first_change flag. - yield_constr - .constraint_transition(segment_first_change * (next_addr_context - addr_context)); - yield_constr - .constraint_transition(virtual_first_change * (next_addr_context - addr_context)); - yield_constr - .constraint_transition(virtual_first_change * (next_addr_segment - addr_segment)); - yield_constr.constraint_transition(address_unchanged * (next_addr_context - addr_context)); - yield_constr.constraint_transition(address_unchanged * (next_addr_segment - addr_segment)); - yield_constr.constraint_transition(address_unchanged * (next_addr_virtual - addr_virtual)); - - // Third set of ordering constraints: range-check difference in the column that should be increasing. - let computed_range_check = 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) - + address_unchanged * (next_timestamp - timestamp); - yield_constr.constraint_transition(range_check - computed_range_check); - - // Enumerate purportedly-ordered log. - for i in 0..8 { - yield_constr.constraint_transition( - next_is_read * address_unchanged * (next_values[i] - values[i]), - ); - } - - eval_lookups(vars, yield_constr, RANGE_CHECK_PERMUTED, COUNTER_PERMUTED) + // TODO } fn eval_ext_circuit( @@ -308,137 +120,12 @@ impl, const D: usize> Stark for BytePackingSt yield_constr: &mut RecursiveConstraintConsumer, ) { let one = builder.one_extension(); - - let addr_context = vars.local_values[ADDR_CONTEXT]; - let addr_segment = vars.local_values[ADDR_SEGMENT]; - let addr_virtual = vars.local_values[ADDR_VIRTUAL]; - let values: Vec<_> = (0..8).map(|i| vars.local_values[value_limb(i)]).collect(); - let timestamp = vars.local_values[TIMESTAMP]; - - let next_addr_context = vars.next_values[ADDR_CONTEXT]; - let next_addr_segment = vars.next_values[ADDR_SEGMENT]; - let next_addr_virtual = vars.next_values[ADDR_VIRTUAL]; - let next_values: Vec<_> = (0..8).map(|i| vars.next_values[value_limb(i)]).collect(); - let next_is_read = vars.next_values[IS_READ]; - let next_timestamp = vars.next_values[TIMESTAMP]; - - // The filter must be 0 or 1. - let filter = vars.local_values[FILTER]; - let constraint = builder.mul_sub_extension(filter, filter, filter); - yield_constr.constraint(builder, constraint); - - // If this is a dummy row (filter is off), it must be a read. This means the prover can - // insert reads which never appear in the CPU trace (which are harmless), but not writes. - let is_dummy = builder.sub_extension(one, filter); - let is_write = builder.sub_extension(one, vars.local_values[IS_READ]); - let is_dummy_write = builder.mul_extension(is_dummy, is_write); - yield_constr.constraint(builder, is_dummy_write); - - let context_first_change = vars.local_values[CONTEXT_FIRST_CHANGE]; - let segment_first_change = vars.local_values[SEGMENT_FIRST_CHANGE]; - let virtual_first_change = vars.local_values[VIRTUAL_FIRST_CHANGE]; - let address_unchanged = { - 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[RANGE_CHECK]; - - 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_address_unchanged = builder.sub_extension(one, address_unchanged); - 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 address_unchanged_bool = - builder.mul_extension(address_unchanged, not_address_unchanged); - yield_constr.constraint(builder, address_unchanged_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_transition(builder, segment_first_change_check); - let virtual_first_change_check_1 = - builder.mul_extension(virtual_first_change, addr_context_diff); - yield_constr.constraint_transition(builder, virtual_first_change_check_1); - let virtual_first_change_check_2 = - builder.mul_extension(virtual_first_change, addr_segment_diff); - yield_constr.constraint_transition(builder, virtual_first_change_check_2); - let address_unchanged_check_1 = builder.mul_extension(address_unchanged, addr_context_diff); - yield_constr.constraint_transition(builder, address_unchanged_check_1); - let address_unchanged_check_2 = builder.mul_extension(address_unchanged, addr_segment_diff); - yield_constr.constraint_transition(builder, address_unchanged_check_2); - let address_unchanged_check_3 = builder.mul_extension(address_unchanged, addr_virtual_diff); - yield_constr.constraint_transition(builder, address_unchanged_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 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 = builder.sub_extension(next_timestamp, timestamp); - let timestamp_range_check = builder.mul_extension(address_unchanged, timestamp_diff); - - let computed_range_check = { - // context_range_check = context_first_change * context_diff - let mut sum = - builder.mul_add_extension(context_first_change, context_diff, 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, computed_range_check); - yield_constr.constraint_transition(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(address_unchanged, value_diff); - let read_constraint = builder.mul_extension(next_is_read, zero_if_read); - yield_constr.constraint_transition(builder, read_constraint); - } - - eval_lookups_circuit( - builder, - vars, - yield_constr, - RANGE_CHECK_PERMUTED, - COUNTER_PERMUTED, - ) + // TODO } fn constraint_degree(&self) -> usize { 3 } - - fn permutation_pairs(&self) -> Vec { - vec![ - PermutationPair::singletons(RANGE_CHECK, RANGE_CHECK_PERMUTED), - PermutationPair::singletons(COUNTER, COUNTER_PERMUTED), - ] - } } #[cfg(test)] diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index cb99c190b1..bbd9d20514 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -1,38 +1,27 @@ //! Byte packing registers. -use crate::byte_packing::VALUE_LIMBS; +use crate::byte_packing::{VALUE_BYTES, VALUE_LIMBS}; // Columns for memory operations, ordered by (addr, timestamp). /// 1 if this is an actual memory operation, or 0 if it's a padding row. pub(crate) const FILTER: usize = 0; -pub(crate) const TIMESTAMP: usize = FILTER + 1; -pub(crate) const IS_READ: usize = TIMESTAMP + 1; -pub(crate) const ADDR_CONTEXT: usize = IS_READ + 1; -pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; -pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; +/// The remaining length of this pack of bytes. +/// Expected to not be greater than 32. +pub(crate) const REMAINING_LEN: usize = FILTER + 1; -// Eight 32-bit limbs hold a total of 256 bits. -// If a value represents an integer, it is little-endian encoded. -const VALUE_START: usize = ADDR_VIRTUAL + 1; +// 32 byte limbs hold a total of 256 bits. +const BYTES_START: usize = REMAINING_LEN + 1; +pub(crate) const fn value_bytes(i: usize) -> usize { + debug_assert!(i < VALUE_BYTES); + VALUE_START + i +} + +// Eight 32-bit limbs hold a total of 256 bits, representing the big-endian +// encoding of the previous byte sequence. +const VALUE_START: usize = BYTES_START + VALUE_BYTES; pub(crate) const fn value_limb(i: usize) -> usize { debug_assert!(i < VALUE_LIMBS); VALUE_START + i } -// Flags to indicate whether this part of the address differs from the next row, -// and the previous parts do not differ. -// That is, e.g., `SEGMENT_FIRST_CHANGE` is `F::ONE` iff `ADDR_CONTEXT` is the same in this -// row and the next, but `ADDR_SEGMENT` is not. -pub(crate) const CONTEXT_FIRST_CHANGE: usize = VALUE_START + VALUE_LIMBS; -pub(crate) const SEGMENT_FIRST_CHANGE: usize = CONTEXT_FIRST_CHANGE + 1; -pub(crate) const VIRTUAL_FIRST_CHANGE: usize = SEGMENT_FIRST_CHANGE + 1; - -// We use a range check to enforce the ordering. -pub(crate) const RANGE_CHECK: usize = VIRTUAL_FIRST_CHANGE + 1; -// The counter column (used for the range check) starts from 0 and increments. -pub(crate) const COUNTER: usize = RANGE_CHECK + 1; -// Helper columns for the permutation argument used to enforce the range check. -pub(crate) const RANGE_CHECK_PERMUTED: usize = COUNTER + 1; -pub(crate) const COUNTER_PERMUTED: usize = RANGE_CHECK_PERMUTED + 1; - -pub(crate) const NUM_COLUMNS: usize = COUNTER_PERMUTED + 1; +pub(crate) const NUM_COLUMNS: usize = VALUE_START + VALUE_LIMBS; diff --git a/evm/src/byte_packing/mod.rs b/evm/src/byte_packing/mod.rs index 695ade1f38..d1afa98c74 100644 --- a/evm/src/byte_packing/mod.rs +++ b/evm/src/byte_packing/mod.rs @@ -2,6 +2,5 @@ pub mod byte_packing_stark; pub mod columns; pub mod segments; -// TODO: Move to CPU module, now that channels have been removed from the memory table. -pub(crate) const NUM_CHANNELS: usize = crate::cpu::membus::NUM_CHANNELS; +pub(crate) const VALUE_BYTES: usize = 32; pub(crate) const VALUE_LIMBS: usize = 8; diff --git a/evm/src/cpu/kernel/asm/memory/packing.asm b/evm/src/cpu/kernel/asm/memory/packing.asm index 0f8023352c..691d3fe580 100644 --- a/evm/src/cpu/kernel/asm/memory/packing.asm +++ b/evm/src/cpu/kernel/asm/memory/packing.asm @@ -7,40 +7,10 @@ // NOTE: addr: 3 denotes a (context, segment, virtual) tuple global mload_packing: // stack: addr: 3, len, retdest - DUP3 DUP3 DUP3 MLOAD_GENERAL DUP5 %eq_const(1) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(1) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(2) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(2) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(3) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(3) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(4) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(4) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(5) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(5) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(6) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(6) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(7) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(7) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(8) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(8) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(9) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(9) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(10) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(10) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(11) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(11) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(12) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(12) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(13) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(13) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(14) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(14) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(15) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(15) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(16) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(16) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(17) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(17) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(18) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(18) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(19) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(19) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(20) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(20) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(21) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(21) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(22) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(22) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(23) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(23) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(24) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(24) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(25) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(25) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(26) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(26) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(27) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(27) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(28) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(28) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(29) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(29) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(30) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(30) DUP4 DUP4 MLOAD_GENERAL ADD DUP5 %eq_const(31) %jumpi(mload_packing_return) %shl_const(8) - DUP4 %add_const(31) DUP4 DUP4 MLOAD_GENERAL ADD -mload_packing_return: - %stack (packed_value, addr: 3, len, retdest) -> (retdest, packed_value) + MLOAD_32BYTES + // stack: packed_value, retdest + SWAP1 + // stack: retdest, packed_value JUMP %macro mload_packing diff --git a/evm/src/fixed_recursive_verifier.rs b/evm/src/fixed_recursive_verifier.rs index 12b5e717b5..fd4c84ca24 100644 --- a/evm/src/fixed_recursive_verifier.rs +++ b/evm/src/fixed_recursive_verifier.rs @@ -29,6 +29,7 @@ use plonky2_util::log2_ceil; use crate::all_stark::{all_cross_table_lookups, AllStark, Table, NUM_TABLES}; use crate::arithmetic::arithmetic_stark::ArithmeticStark; +use crate::byte_packing::byte_packing_stark::BytePackingStark; use crate::config::StarkConfig; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::{verify_cross_table_lookups_circuit, CrossTableLookup}; @@ -303,6 +304,7 @@ where [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, { pub fn to_bytes( &self, diff --git a/evm/src/prover.rs b/evm/src/prover.rs index e611b9dc00..90e7653906 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -21,6 +21,7 @@ use plonky2_util::{log2_ceil, log2_strict}; use crate::all_stark::{AllStark, Table, NUM_TABLES}; use crate::arithmetic::arithmetic_stark::ArithmeticStark; +use crate::byte_packing::byte_packing_stark::BytePackingStark; use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; @@ -58,6 +59,7 @@ where [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, { let (proof, _outputs) = prove_with_outputs(all_stark, config, inputs, timing)?; Ok(proof) @@ -80,6 +82,7 @@ where [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, { timed!(timing, "build kernel", Lazy::force(&KERNEL)); let (traces, public_values, outputs) = timed!( @@ -108,6 +111,7 @@ where [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, { let rate_bits = config.fri_config.rate_bits; let cap_height = config.fri_config.cap_height; @@ -198,6 +202,7 @@ where [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, { let arithmetic_proof = timed!( timing, diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index f20c07b459..17ddc7a364 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -12,6 +12,7 @@ use plonky2::plonk::plonk_common::reduce_with_powers; use crate::all_stark::{AllStark, Table, NUM_TABLES}; use crate::arithmetic::arithmetic_stark::ArithmeticStark; +use crate::byte_packing::byte_packing_stark::BytePackingStark; use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; @@ -44,6 +45,7 @@ where [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, { let AllProofChallenges { stark_challenges, diff --git a/evm/src/witness/operation.rs b/evm/src/witness/operation.rs index 813446b3b8..bbc1f3377f 100644 --- a/evm/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -3,6 +3,7 @@ use itertools::Itertools; use keccak_hash::keccak; use plonky2::field::types::Field; +use super::util::byte_packing_log; use crate::arithmetic::BinaryOperator; use crate::cpu::columns::CpuColumnsView; use crate::cpu::kernel::aggregator::KERNEL; @@ -687,33 +688,39 @@ pub(crate) fn generate_mload_general( Ok(()) } -// TODO: Update this +// Note: This should be used within `perform_mload_32bytes` only, +// as spanning over several rows. +// It returns the remaining byte length to be read to construct a packed value. pub(crate) fn generate_mload_32bytes( state: &mut GenerationState, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { - let [(context, log_in0), (segment, log_in1), (virt, log_in2)] = - stack_pop_with_log_and_fill::<3, _>(state, &mut row)?; + let [(context, log_in0), (segment, log_in1), (base_virt, log_in2), (len, log_in3)] = + stack_pop_with_log_and_fill::<4, _>(state, &mut row)?; + let len = len.as_usize(); - let (val, log_read) = mem_read_gp_with_log_and_fill( - 3, - MemoryAddress::new_u256s(context, segment, virt)?, - state, - &mut row, - ); + let base_address = MemoryAddress::new_u256s(context, segment, base_virt)?; + let bytes = (0..len) + .map(|i| { + let address = MemoryAddress { + virt: base_address.virt.saturating_add(i), + ..base_address + }; + let val = state.memory.get(address); + val.as_u32() as u8 + }) + .collect_vec(); - let log_out = stack_push_log_and_fill(state, &mut row, val)?; + let packed_int = U256::from_big_endian(&bytes); + let log_out = stack_push_log_and_fill(state, &mut row, packed_int)?; + + byte_packing_log(state, base_address, bytes); state.traces.push_memory(log_in0); state.traces.push_memory(log_in1); state.traces.push_memory(log_in2); - state.traces.push_memory(log_read); + state.traces.push_memory(log_in3); state.traces.push_memory(log_out); - state.traces.push_byte_packing(log_in0); - state.traces.push_byte_packing(log_in1); - state.traces.push_byte_packing(log_in2); - state.traces.push_byte_packing(log_read); - state.traces.push_byte_packing(log_out); state.traces.push_cpu(row); Ok(()) } diff --git a/evm/src/witness/traces.rs b/evm/src/witness/traces.rs index 9bd801c4db..d73be7f4bd 100644 --- a/evm/src/witness/traces.rs +++ b/evm/src/witness/traces.rs @@ -30,7 +30,7 @@ pub struct TraceCheckpoint { #[derive(Debug)] pub(crate) struct Traces { pub(crate) arithmetic_ops: Vec, - pub(crate) byte_packing_ops: Vec, + pub(crate) byte_packing_ops: Vec>, pub(crate) cpu: Vec>, pub(crate) logic_ops: Vec, pub(crate) memory_ops: Vec, @@ -94,8 +94,8 @@ impl Traces { self.memory_ops.push(op); } - pub fn push_byte_packing(&mut self, op: MemoryOp) { - self.byte_packing_ops.push(op); + pub fn push_byte_packing(&mut self, bytes: Vec) { + self.byte_packing_ops.push(bytes); } pub fn push_keccak(&mut self, input: [u64; keccak::keccak_stark::NUM_INPUTS]) { @@ -181,7 +181,7 @@ impl Traces { "generate byte packing trace", all_stark .byte_packing_stark - .generate_trace(byte_packing_ops, timing) + .generate_trace(byte_packing_ops, cap_elements, timing) ); [ diff --git a/evm/src/witness/util.rs b/evm/src/witness/util.rs index 755981e5ae..98bf6c3d8c 100644 --- a/evm/src/witness/util.rs +++ b/evm/src/witness/util.rs @@ -258,3 +258,25 @@ pub(crate) fn keccak_sponge_log( input, }); } + +pub(crate) fn byte_packing_log( + state: &mut GenerationState, + base_address: MemoryAddress, + inputs: Vec, +) { + let clock = state.traces.clock(); + + let mut address = base_address; + for &byte in &inputs { + state.traces.push_memory(MemoryOp::new( + MemoryChannel::Code, + clock, + address, + MemoryOpKind::Read, + byte.into(), + )); + address.increment(); + } + + state.traces.push_byte_packing(inputs); +} From 6844573505bd004613dfdcc7b8e6a1cb35c7643a Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 29 Aug 2023 16:28:49 -0400 Subject: [PATCH 05/40] Remove segments --- evm/src/byte_packing/mod.rs | 1 - evm/src/byte_packing/segments.rs | 193 ------------------------------- 2 files changed, 194 deletions(-) delete mode 100644 evm/src/byte_packing/segments.rs diff --git a/evm/src/byte_packing/mod.rs b/evm/src/byte_packing/mod.rs index d1afa98c74..d61f9f252b 100644 --- a/evm/src/byte_packing/mod.rs +++ b/evm/src/byte_packing/mod.rs @@ -1,6 +1,5 @@ pub mod byte_packing_stark; pub mod columns; -pub mod segments; pub(crate) const VALUE_BYTES: usize = 32; pub(crate) const VALUE_LIMBS: usize = 8; diff --git a/evm/src/byte_packing/segments.rs b/evm/src/byte_packing/segments.rs deleted file mode 100644 index e56d635d63..0000000000 --- a/evm/src/byte_packing/segments.rs +++ /dev/null @@ -1,193 +0,0 @@ -#[allow(dead_code)] -#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] -pub enum Segment { - /// Contains EVM bytecode. - Code = 0, - /// The program stack. - Stack = 1, - /// Main memory, owned by the contract code. - MainMemory = 2, - /// Data passed to the current context by its caller. - Calldata = 3, - /// Data returned to the current context by its latest callee. - Returndata = 4, - /// A segment which contains a few fixed-size metadata fields, such as the caller's context, or the - /// size of `CALLDATA` and `RETURNDATA`. - GlobalMetadata = 5, - ContextMetadata = 6, - /// General purpose kernel memory, used by various kernel functions. - /// In general, calling a helper function can result in this memory being clobbered. - KernelGeneral = 7, - /// Another segment for general purpose kernel use. - KernelGeneral2 = 8, - /// Segment to hold account code for opcodes like `CODESIZE, CODECOPY,...`. - KernelAccountCode = 9, - /// Contains normalized transaction fields; see `NormalizedTxnField`. - TxnFields = 10, - /// Contains the data field of a transaction. - TxnData = 11, - /// A buffer used to hold raw RLP data. - RlpRaw = 12, - /// Contains all trie data. It is owned by the kernel, so it only lives on context 0. - TrieData = 13, - /// A buffer used to store the encodings of a branch node's children. - TrieEncodedChild = 14, - /// A buffer used to store the lengths of the encodings of a branch node's children. - TrieEncodedChildLen = 15, - /// A table of values 2^i for i=0..255 for use with shift - /// instructions; initialised by `kernel/asm/shift.asm::init_shift_table()`. - ShiftTable = 16, - JumpdestBits = 17, - EcdsaTable = 18, - BnWnafA = 19, - BnWnafB = 20, - BnTableQ = 21, - BnPairing = 22, - /// List of addresses that have been accessed in the current transaction. - AccessedAddresses = 23, - /// List of storage keys that have been accessed in the current transaction. - AccessedStorageKeys = 24, - /// List of addresses that have called SELFDESTRUCT in the current transaction. - SelfDestructList = 25, - /// Contains the bloom filter of a transaction. - TxnBloom = 26, - /// Contains the bloom filter of a block. - BlockBloom = 27, - /// List of log pointers pointing to the LogsData segment. - Logs = 28, - LogsData = 29, - /// Journal of state changes. List of pointers to `JournalData`. Length in `GlobalMetadata`. - Journal = 30, - JournalData = 31, - JournalCheckpoints = 32, - /// List of addresses that have been touched in the current transaction. - TouchedAddresses = 33, - /// List of checkpoints for the current context. Length in `ContextMetadata`. - ContextCheckpoints = 34, -} - -impl Segment { - pub(crate) const COUNT: usize = 35; - - pub(crate) fn all() -> [Self; Self::COUNT] { - [ - Self::Code, - Self::Stack, - Self::MainMemory, - Self::Calldata, - Self::Returndata, - Self::GlobalMetadata, - Self::ContextMetadata, - Self::KernelGeneral, - Self::KernelGeneral2, - Self::KernelAccountCode, - Self::TxnFields, - Self::TxnData, - Self::RlpRaw, - Self::TrieData, - Self::TrieEncodedChild, - Self::TrieEncodedChildLen, - Self::ShiftTable, - Self::JumpdestBits, - Self::EcdsaTable, - Self::BnWnafA, - Self::BnWnafB, - Self::BnTableQ, - Self::BnPairing, - Self::AccessedAddresses, - Self::AccessedStorageKeys, - Self::SelfDestructList, - Self::TxnBloom, - Self::BlockBloom, - Self::Logs, - Self::LogsData, - Self::Journal, - Self::JournalData, - Self::JournalCheckpoints, - Self::TouchedAddresses, - Self::ContextCheckpoints, - ] - } - - /// The variable name that gets passed into kernel assembly code. - pub(crate) fn var_name(&self) -> &'static str { - match self { - Segment::Code => "SEGMENT_CODE", - Segment::Stack => "SEGMENT_STACK", - Segment::MainMemory => "SEGMENT_MAIN_MEMORY", - Segment::Calldata => "SEGMENT_CALLDATA", - Segment::Returndata => "SEGMENT_RETURNDATA", - Segment::GlobalMetadata => "SEGMENT_GLOBAL_METADATA", - Segment::ContextMetadata => "SEGMENT_CONTEXT_METADATA", - Segment::KernelGeneral => "SEGMENT_KERNEL_GENERAL", - Segment::KernelGeneral2 => "SEGMENT_KERNEL_GENERAL_2", - Segment::KernelAccountCode => "SEGMENT_KERNEL_ACCOUNT_CODE", - Segment::TxnFields => "SEGMENT_NORMALIZED_TXN", - Segment::TxnData => "SEGMENT_TXN_DATA", - Segment::RlpRaw => "SEGMENT_RLP_RAW", - Segment::TrieData => "SEGMENT_TRIE_DATA", - Segment::TrieEncodedChild => "SEGMENT_TRIE_ENCODED_CHILD", - Segment::TrieEncodedChildLen => "SEGMENT_TRIE_ENCODED_CHILD_LEN", - Segment::ShiftTable => "SEGMENT_SHIFT_TABLE", - Segment::JumpdestBits => "SEGMENT_JUMPDEST_BITS", - Segment::EcdsaTable => "SEGMENT_KERNEL_ECDSA_TABLE", - Segment::BnWnafA => "SEGMENT_KERNEL_BN_WNAF_A", - Segment::BnWnafB => "SEGMENT_KERNEL_BN_WNAF_B", - Segment::BnTableQ => "SEGMENT_KERNEL_BN_TABLE_Q", - Segment::BnPairing => "SEGMENT_KERNEL_BN_PAIRING", - Segment::AccessedAddresses => "SEGMENT_ACCESSED_ADDRESSES", - Segment::AccessedStorageKeys => "SEGMENT_ACCESSED_STORAGE_KEYS", - Segment::SelfDestructList => "SEGMENT_SELFDESTRUCT_LIST", - Segment::TxnBloom => "SEGMENT_TXN_BLOOM", - Segment::BlockBloom => "SEGMENT_BLOCK_BLOOM", - Segment::Logs => "SEGMENT_LOGS", - Segment::LogsData => "SEGMENT_LOGS_DATA", - Segment::Journal => "SEGMENT_JOURNAL", - Segment::JournalData => "SEGMENT_JOURNAL_DATA", - Segment::JournalCheckpoints => "SEGMENT_JOURNAL_CHECKPOINTS", - Segment::TouchedAddresses => "SEGMENT_TOUCHED_ADDRESSES", - Segment::ContextCheckpoints => "SEGMENT_CONTEXT_CHECKPOINTS", - } - } - - #[allow(dead_code)] - pub(crate) fn bit_range(&self) -> usize { - match self { - Segment::Code => 8, - Segment::Stack => 256, - Segment::MainMemory => 8, - Segment::Calldata => 8, - Segment::Returndata => 8, - Segment::GlobalMetadata => 256, - Segment::ContextMetadata => 256, - Segment::KernelGeneral => 256, - Segment::KernelGeneral2 => 256, - Segment::KernelAccountCode => 8, - Segment::TxnFields => 256, - Segment::TxnData => 8, - Segment::RlpRaw => 8, - Segment::TrieData => 256, - Segment::TrieEncodedChild => 256, - Segment::TrieEncodedChildLen => 6, - Segment::ShiftTable => 256, - Segment::JumpdestBits => 1, - Segment::EcdsaTable => 256, - Segment::BnWnafA => 8, - Segment::BnWnafB => 8, - Segment::BnTableQ => 256, - Segment::BnPairing => 256, - Segment::AccessedAddresses => 256, - Segment::AccessedStorageKeys => 256, - Segment::SelfDestructList => 256, - Segment::TxnBloom => 8, - Segment::BlockBloom => 8, - Segment::Logs => 256, - Segment::LogsData => 256, - Segment::Journal => 256, - Segment::JournalData => 256, - Segment::JournalCheckpoints => 256, - Segment::TouchedAddresses => 256, - Segment::ContextCheckpoints => 256, - } - } -} From d82066ed2007dce21214af039006661423b012f4 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 29 Aug 2023 17:16:23 -0400 Subject: [PATCH 06/40] Fix stack --- evm/src/cpu/stack.rs | 2 +- evm/src/witness/operation.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index ffe93aba53..adeafb7723 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -109,7 +109,7 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { swap: None, context_op: None, // SET_CONTEXT is special since it involves the old and the new stack. mload_32bytes: Some(StackBehavior { - num_pops: 3, + num_pops: 4, pushes: true, disable_other_channels: false, }), diff --git a/evm/src/witness/operation.rs b/evm/src/witness/operation.rs index bbc1f3377f..2104b53e24 100644 --- a/evm/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -688,9 +688,6 @@ pub(crate) fn generate_mload_general( Ok(()) } -// Note: This should be used within `perform_mload_32bytes` only, -// as spanning over several rows. -// It returns the remaining byte length to be read to construct a packed value. pub(crate) fn generate_mload_32bytes( state: &mut GenerationState, mut row: CpuColumnsView, From 0bb094f503f0d0bcdf19ef75077905d41e41b2d5 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 29 Aug 2023 18:29:39 -0400 Subject: [PATCH 07/40] Fix extra product when fixing CTL for byte_packing --- evm/src/cross_table_lookup.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index a9b90428ca..fff17bc16e 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -542,7 +542,8 @@ pub(crate) fn verify_cross_table_lookups, const D: looking_tables, looked_table, }, - ) in cross_table_lookups.iter().enumerate() + // TODO: Fix extra product with new table + ) in cross_table_lookups.iter().enumerate().take(NUM_TABLES - 3) { let extra_product_vec = &ctl_extra_looking_products[looked_table.table as usize]; for c in 0..config.num_challenges { From bb2b04b231f6594c6f02037f383079e1875a8708 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 29 Aug 2023 18:42:26 -0400 Subject: [PATCH 08/40] Write output value in trace --- evm/src/byte_packing/byte_packing_stark.rs | 1 + evm/src/byte_packing/columns.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 97e94a07f8..1a003ed3cd 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -86,6 +86,7 @@ impl, const D: usize> BytePackingStark { for (i, &byte) in bytes.iter().enumerate() { row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1); row[value_bytes(i)] = F::from_canonical_u8(byte); + row[value_limb(i / 4)] += F::from_canonical_u32((byte as u32) << (8 * (i % 4))); rows.push(row.into()); } diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index bbd9d20514..46ea739bad 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -13,7 +13,7 @@ pub(crate) const REMAINING_LEN: usize = FILTER + 1; const BYTES_START: usize = REMAINING_LEN + 1; pub(crate) const fn value_bytes(i: usize) -> usize { debug_assert!(i < VALUE_BYTES); - VALUE_START + i + BYTES_START + i } // Eight 32-bit limbs hold a total of 256 bits, representing the big-endian From c1ce80dc80f6f3692b0bde2d7786ac7b485c85cb Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 29 Aug 2023 19:26:14 -0400 Subject: [PATCH 09/40] Add constraints for BYTE_PACKING table --- evm/src/byte_packing/byte_packing_stark.rs | 35 +++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 1a003ed3cd..ed26f23130 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -12,6 +12,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; +use super::{VALUE_BYTES, VALUE_LIMBS}; use crate::byte_packing::columns::{value_bytes, value_limb, FILTER, NUM_COLUMNS, REMAINING_LEN}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; @@ -111,7 +112,39 @@ impl, const D: usize> Stark for BytePackingSt P: PackedField, { let one = P::from(FE::ONE); - // TODO + + // The filter must be boolean. + let filter = vars.local_values[FILTER]; + yield_constr.constraint(filter * (filter - P::ONES)); + + // The remaining length of a byte sequence must decrease by one or be zero. + let current_remaining_length = vars.local_values[REMAINING_LEN]; + let next_remaining_length = vars.local_values[REMAINING_LEN]; + yield_constr.constraint_transition( + current_remaining_length * (current_remaining_length - next_remaining_length - one), + ); + + // The remaining length on the last row must be zero. + let final_remaining_length = vars.local_values[REMAINING_LEN]; + yield_constr.constraint_last_row(final_remaining_length); + + // Each byte must be zero or equal to the previous one when reading through a sequence. + for i in 0..VALUE_BYTES { + let current_byte = vars.local_values[value_bytes(i)]; + let next_byte = vars.next_values[value_bytes(i)]; + yield_constr.constraint_transition(next_byte * (next_byte - current_byte)); + } + + // Each limb must correspond to the big-endian u32 value of each chunk of 4 bytes. + for i in 0..VALUE_LIMBS { + let current_limb = vars.local_values[value_limb(i)]; + let value = vars.local_values[value_bytes(4 * i)..value_bytes(4 * i + 4)] + .iter() + .enumerate() + .map(|(i, &v)| v * P::Scalar::from_canonical_usize(1 << (8 * (i % 4)))) + .sum::

(); + yield_constr.constraint(current_limb - value); + } } fn eval_ext_circuit( From 862c1dfda401bfb973da50ca8a996c697aeb1a44 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 29 Aug 2023 19:37:30 -0400 Subject: [PATCH 10/40] Add recursive constraints for BYTE_PACKING table --- evm/src/byte_packing/byte_packing_stark.rs | 46 +++++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index ed26f23130..c7e3208836 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -111,7 +111,7 @@ impl, const D: usize> Stark for BytePackingSt FE: FieldExtension, P: PackedField, { - let one = P::from(FE::ONE); + let one = P::ONES; // The filter must be boolean. let filter = vars.local_values[FILTER]; @@ -154,7 +154,49 @@ impl, const D: usize> Stark for BytePackingSt yield_constr: &mut RecursiveConstraintConsumer, ) { let one = builder.one_extension(); - // TODO + + // The filter must be boolean. + let filter = vars.local_values[FILTER]; + let constraint = builder.mul_sub_extension(filter, filter, filter); + yield_constr.constraint(builder, constraint); + + // The remaining length of a byte sequence must decrease by one or be zero. + let current_remaining_length = vars.local_values[REMAINING_LEN]; + let next_remaining_length = vars.local_values[REMAINING_LEN]; + let length_diff = builder.sub_extension(current_remaining_length, next_remaining_length); + let length_diff_minus_one = builder.add_const_extension(length_diff, F::NEG_ONE); + let constraint = builder.mul_extension(current_remaining_length, length_diff_minus_one); + yield_constr.constraint(builder, constraint); + + // The remaining length on the last row must be zero. + let final_remaining_length = vars.local_values[REMAINING_LEN]; + yield_constr.constraint_last_row(builder, final_remaining_length); + + // Each byte must be zero or equal to the previous one when reading through a sequence. + for i in 0..VALUE_BYTES { + let current_byte = vars.local_values[value_bytes(i)]; + let next_byte = vars.next_values[value_bytes(i)]; + let byte_diff = builder.sub_extension(current_byte, next_byte); + let constraint = builder.mul_extension(next_byte, byte_diff); + yield_constr.constraint(builder, constraint); + } + + // Each limb must correspond to the big-endian u32 value of each chunk of 4 bytes. + for i in 0..VALUE_LIMBS { + let current_limb = vars.local_values[value_limb(i)]; + let mut value = vars.local_values[value_bytes(4 * i)]; + for (i, &v) in vars.local_values[value_bytes(4 * i)..value_bytes(4 * i + 4)] + .iter() + .enumerate() + .skip(1) + { + let scaled_v = + builder.mul_const_extension(F::from_canonical_usize(1 << (8 * (i % 4))), v); + value = builder.add_extension(value, scaled_v); + } + let byte_diff = builder.sub_extension(current_limb, value); + yield_constr.constraint(builder, constraint); + } } fn constraint_degree(&self) -> usize { From 99ed5f047ebdb0ed624dea0d3019c3e36b09b6d0 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 29 Aug 2023 20:13:17 -0400 Subject: [PATCH 11/40] Fix constraints --- evm/src/byte_packing/byte_packing_stark.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index c7e3208836..bc62df0d1d 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -128,11 +128,13 @@ impl, const D: usize> Stark for BytePackingSt let final_remaining_length = vars.local_values[REMAINING_LEN]; yield_constr.constraint_last_row(final_remaining_length); - // Each byte must be zero or equal to the previous one when reading through a sequence. + // Each next byte must equal the current one when reading through a sequence, + // or the current remaining length must be zero. for i in 0..VALUE_BYTES { let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; - yield_constr.constraint_transition(next_byte * (next_byte - current_byte)); + yield_constr + .constraint_transition(current_remaining_length * (next_byte - current_byte)); } // Each limb must correspond to the big-endian u32 value of each chunk of 4 bytes. @@ -172,12 +174,13 @@ impl, const D: usize> Stark for BytePackingSt let final_remaining_length = vars.local_values[REMAINING_LEN]; yield_constr.constraint_last_row(builder, final_remaining_length); - // Each byte must be zero or equal to the previous one when reading through a sequence. + // Each next byte must equal the current one when reading through a sequence, + // or the current remaining length must be zero. for i in 0..VALUE_BYTES { let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; let byte_diff = builder.sub_extension(current_byte, next_byte); - let constraint = builder.mul_extension(next_byte, byte_diff); + let constraint = builder.mul_extension(current_remaining_length, byte_diff); yield_constr.constraint(builder, constraint); } From f72f9010b778c10f6eccb375fa14ad8e4750b0f6 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 30 Aug 2023 12:02:25 -0400 Subject: [PATCH 12/40] Add address in trace and constraints --- evm/src/byte_packing/byte_packing_stark.rs | 128 +++++++++++++++++++-- evm/src/byte_packing/columns.rs | 9 +- evm/src/witness/traces.rs | 7 +- evm/src/witness/util.rs | 2 +- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index bc62df0d1d..58faa5c1a8 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -12,6 +12,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; +use super::columns::{ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_START}; use super::{VALUE_BYTES, VALUE_LIMBS}; use crate::byte_packing::columns::{value_bytes, value_limb, FILTER, NUM_COLUMNS, REMAINING_LEN}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; @@ -19,6 +20,7 @@ use crate::cross_table_lookup::Column; use crate::stark::Stark; use crate::util::trace_rows_to_poly_values; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; +use crate::witness::memory::MemoryAddress; // TODO: change pub fn ctl_data() -> Vec> { @@ -40,7 +42,7 @@ pub struct BytePackingStark { impl, const D: usize> BytePackingStark { pub(crate) fn generate_trace( &self, - grouped_bytes: Vec>, + addr_and_bytes: Vec<(MemoryAddress, Vec)>, min_rows: usize, timing: &mut TimingTree, ) -> Vec> { @@ -48,7 +50,7 @@ impl, const D: usize> BytePackingStark { let trace_rows = timed!( timing, "generate trace rows", - self.generate_trace_rows(grouped_bytes, min_rows) + self.generate_trace_rows(addr_and_bytes, min_rows) ); let trace_polys = timed!( @@ -62,27 +64,41 @@ impl, const D: usize> BytePackingStark { fn generate_trace_rows( &self, - grouped_bytes: Vec>, + addr_and_bytes: Vec<(MemoryAddress, Vec)>, min_rows: usize, ) -> Vec<[F; NUM_COLUMNS]> { - let base_len: usize = grouped_bytes.iter().map(|bytes| bytes.len()).sum(); + let base_len: usize = addr_and_bytes.iter().map(|(_, bytes)| bytes.len()).sum(); let mut rows = Vec::with_capacity(base_len.max(min_rows).next_power_of_two()); - for bytes in grouped_bytes { - rows.extend(self.generate_rows_for_bytes(bytes)); + for (start_addr, bytes) in addr_and_bytes { + rows.extend(self.generate_rows_for_bytes(start_addr, bytes)); } let padded_rows = rows.len().max(min_rows).next_power_of_two(); for _ in rows.len()..padded_rows { rows.push(self.generate_padding_row()); } + rows } - fn generate_rows_for_bytes(&self, bytes: Vec) -> Vec<[F; NUM_COLUMNS]> { + fn generate_rows_for_bytes( + &self, + start_addr: MemoryAddress, + bytes: Vec, + ) -> Vec<[F; NUM_COLUMNS]> { let mut rows = Vec::with_capacity(bytes.len()); let mut row = [F::ZERO; NUM_COLUMNS]; row[FILTER] = F::ONE; + row[SEQUENCE_START] = F::ONE; + let MemoryAddress { + context, + segment, + virt, + } = start_addr; + row[ADDR_CONTEXT] = F::from_canonical_usize(context); + row[ADDR_SEGMENT] = F::from_canonical_usize(segment); + row[ADDR_VIRTUAL] = F::from_canonical_usize(virt); for (i, &byte) in bytes.iter().enumerate() { row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1); @@ -90,6 +106,11 @@ impl, const D: usize> BytePackingStark { row[value_limb(i / 4)] += F::from_canonical_u32((byte as u32) << (8 * (i % 4))); rows.push(row.into()); + row[ADDR_VIRTUAL] += F::ONE; + + if i == 0 { + row[SEQUENCE_START] = F::ZERO; + } } rows @@ -115,7 +136,21 @@ impl, const D: usize> Stark for BytePackingSt // The filter must be boolean. let filter = vars.local_values[FILTER]; - yield_constr.constraint(filter * (filter - P::ONES)); + yield_constr.constraint(filter * (filter - one)); + + // The filter column must start by one. + yield_constr.constraint_first_row(filter - one); + + // Only padding rows have their filter turned off. + let next_filter = vars.next_values[FILTER]; + yield_constr.constraint_transition(next_filter * (next_filter - filter)); + + // The sequence start flag must be boolean + let sequence_start = vars.local_values[SEQUENCE_START]; + yield_constr.constraint(sequence_start * (sequence_start - one)); + + // The sequence start flag column must start by one. + yield_constr.constraint_first_row(sequence_start - one); // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; @@ -128,6 +163,29 @@ impl, const D: usize> Stark for BytePackingSt let final_remaining_length = vars.local_values[REMAINING_LEN]; yield_constr.constraint_last_row(final_remaining_length); + // If the current remaining length is zero, the next sequence start flag must be one. + let next_sequence_start = vars.next_values[SEQUENCE_START]; + yield_constr.constraint_transition(current_remaining_length * (next_sequence_start - one)); + + // The context and segment fields must remain unchanged throughout a byte sequence. + // The virtual address must increment by one at each step of a sequence. + let current_context = vars.local_values[ADDR_CONTEXT]; + let next_context = vars.next_values[ADDR_CONTEXT]; + let current_segment = vars.local_values[ADDR_SEGMENT]; + let next_segment = vars.next_values[ADDR_SEGMENT]; + let current_virtual = vars.local_values[ADDR_VIRTUAL]; + let next_virtual = vars.next_values[ADDR_VIRTUAL]; + let next_filter = vars.next_values[FILTER]; + yield_constr.constraint_transition( + next_filter * (next_sequence_start - one) * (next_context - current_context), + ); + yield_constr.constraint_transition( + next_filter * (next_sequence_start - one) * (next_segment - current_segment), + ); + yield_constr.constraint_transition( + next_filter * (next_sequence_start - one) * (next_virtual - current_virtual - one), + ); + // Each next byte must equal the current one when reading through a sequence, // or the current remaining length must be zero. for i in 0..VALUE_BYTES { @@ -162,6 +220,25 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_sub_extension(filter, filter, filter); yield_constr.constraint(builder, constraint); + // The filter column must start by one. + let constraint = builder.add_const_extension(filter, F::NEG_ONE); + yield_constr.constraint_first_row(builder, filter); + + // Only padding rows have their filter turned off. + let next_filter = vars.next_values[FILTER]; + let constraint = builder.sub_extension(next_filter, filter); + let constraint = builder.mul_extension(next_filter, constraint); + yield_constr.constraint_transition(builder, constraint); + + // The sequence start flag must be boolean + let sequence_start = vars.local_values[SEQUENCE_START]; + let constraint = builder.mul_sub_extension(sequence_start, sequence_start, sequence_start); + yield_constr.constraint(builder, constraint); + + // The sequence start flag column must start by one. + let constraint = builder.add_const_extension(filter, F::NEG_ONE); + yield_constr.constraint_first_row(builder, filter); + // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; let next_remaining_length = vars.local_values[REMAINING_LEN]; @@ -174,6 +251,41 @@ impl, const D: usize> Stark for BytePackingSt let final_remaining_length = vars.local_values[REMAINING_LEN]; yield_constr.constraint_last_row(builder, final_remaining_length); + // If the current remaining length is zero, the next sequence start flag must be one. + let next_sequence_start = vars.next_values[SEQUENCE_START]; + let constraint = builder.mul_sub_extension( + current_remaining_length, + next_sequence_start, + current_remaining_length, + ); + yield_constr.constraint_transition(builder, constraint); + + // The context and segment fields must remain unchanged throughout a byte sequence. + // The virtual address must increment by one at each step of a sequence. + let current_context = vars.local_values[ADDR_CONTEXT]; + let next_context = vars.next_values[ADDR_CONTEXT]; + let current_segment = vars.local_values[ADDR_SEGMENT]; + let next_segment = vars.next_values[ADDR_SEGMENT]; + let current_virtual = vars.local_values[ADDR_VIRTUAL]; + let next_virtual = vars.next_values[ADDR_VIRTUAL]; + let next_filter = vars.next_values[FILTER]; + let addr_filter = builder.mul_sub_extension(next_filter, next_sequence_start, next_filter); + { + let constraint = builder.sub_extension(next_context, current_context); + let constraint = builder.mul_extension(addr_filter, constraint); + yield_constr.constraint_transition(builder, constraint); + } + { + let constraint = builder.sub_extension(next_segment, current_segment); + let constraint = builder.mul_extension(addr_filter, constraint); + yield_constr.constraint_transition(builder, constraint); + } + { + let constraint = builder.sub_extension(next_virtual, current_virtual); + let constraint = builder.mul_sub_extension(addr_filter, constraint, addr_filter); + yield_constr.constraint_transition(builder, constraint); + } + // Each next byte must equal the current one when reading through a sequence, // or the current remaining length must be zero. for i in 0..VALUE_BYTES { diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 46ea739bad..5dda8e7690 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -5,9 +5,16 @@ use crate::byte_packing::{VALUE_BYTES, VALUE_LIMBS}; // Columns for memory operations, ordered by (addr, timestamp). /// 1 if this is an actual memory operation, or 0 if it's a padding row. pub(crate) const FILTER: usize = 0; +/// 1 if this is the beginning of a new sequence of bytes. +pub(crate) const SEQUENCE_START: usize = FILTER + 1; + +pub(crate) const ADDR_CONTEXT: usize = SEQUENCE_START + 1; +pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; +pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; + /// The remaining length of this pack of bytes. /// Expected to not be greater than 32. -pub(crate) const REMAINING_LEN: usize = FILTER + 1; +pub(crate) const REMAINING_LEN: usize = ADDR_VIRTUAL + 1; // 32 byte limbs hold a total of 256 bits. const BYTES_START: usize = REMAINING_LEN + 1; diff --git a/evm/src/witness/traces.rs b/evm/src/witness/traces.rs index d73be7f4bd..72d37da556 100644 --- a/evm/src/witness/traces.rs +++ b/evm/src/witness/traces.rs @@ -7,6 +7,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; +use super::memory::MemoryAddress; use crate::all_stark::{AllStark, NUM_TABLES}; use crate::config::StarkConfig; use crate::cpu::columns::CpuColumnsView; @@ -30,7 +31,7 @@ pub struct TraceCheckpoint { #[derive(Debug)] pub(crate) struct Traces { pub(crate) arithmetic_ops: Vec, - pub(crate) byte_packing_ops: Vec>, + pub(crate) byte_packing_ops: Vec<(MemoryAddress, Vec)>, pub(crate) cpu: Vec>, pub(crate) logic_ops: Vec, pub(crate) memory_ops: Vec, @@ -94,8 +95,8 @@ impl Traces { self.memory_ops.push(op); } - pub fn push_byte_packing(&mut self, bytes: Vec) { - self.byte_packing_ops.push(bytes); + pub fn push_byte_packing(&mut self, address: MemoryAddress, bytes: Vec) { + self.byte_packing_ops.push((address, bytes)); } pub fn push_keccak(&mut self, input: [u64; keccak::keccak_stark::NUM_INPUTS]) { diff --git a/evm/src/witness/util.rs b/evm/src/witness/util.rs index 98bf6c3d8c..b809943077 100644 --- a/evm/src/witness/util.rs +++ b/evm/src/witness/util.rs @@ -278,5 +278,5 @@ pub(crate) fn byte_packing_log( address.increment(); } - state.traces.push_byte_packing(inputs); + state.traces.push_byte_packing(base_address, inputs); } From 06eaf717accca33ff95caeef12549f7ffb0ea6ba Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 30 Aug 2023 15:38:06 -0400 Subject: [PATCH 13/40] Add timestamp and batch inputs into BytePackingOp struct --- evm/src/byte_packing/byte_packing_stark.rs | 99 ++++++++++++++++------ evm/src/byte_packing/columns.rs | 8 +- evm/src/witness/traces.rs | 8 +- evm/src/witness/util.rs | 11 ++- 4 files changed, 91 insertions(+), 35 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 58faa5c1a8..16eecab14a 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -1,6 +1,3 @@ -// TODO: Remove -#![allow(unused)] - use std::marker::PhantomData; use itertools::Itertools; @@ -12,7 +9,9 @@ use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; -use super::columns::{ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_START}; +use super::columns::{ + ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_END, SEQUENCE_START, TIMESTAMP, +}; use super::{VALUE_BYTES, VALUE_LIMBS}; use crate::byte_packing::columns::{value_bytes, value_limb, FILTER, NUM_COLUMNS, REMAINING_LEN}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; @@ -34,6 +33,20 @@ pub fn ctl_filter() -> Column { Column::single(FILTER) } +/// Information about a byte packing operation needed for witness generation. +#[derive(Clone, Debug)] +pub(crate) struct BytePackingOp { + /// The base address at which inputs are read. + pub(crate) base_address: MemoryAddress, + + /// The timestamp at which inputs are read. + pub(crate) timestamp: usize, + + /// The byte sequence that was read and has to be packed. + /// Its length is expected to be at most 32. + pub(crate) bytes: Vec, +} + #[derive(Copy, Clone, Default)] pub struct BytePackingStark { pub(crate) f: PhantomData, @@ -42,7 +55,7 @@ pub struct BytePackingStark { impl, const D: usize> BytePackingStark { pub(crate) fn generate_trace( &self, - addr_and_bytes: Vec<(MemoryAddress, Vec)>, + ops: Vec, min_rows: usize, timing: &mut TimingTree, ) -> Vec> { @@ -50,7 +63,7 @@ impl, const D: usize> BytePackingStark { let trace_rows = timed!( timing, "generate trace rows", - self.generate_trace_rows(addr_and_bytes, min_rows) + self.generate_trace_rows(ops, min_rows) ); let trace_polys = timed!( @@ -64,14 +77,14 @@ impl, const D: usize> BytePackingStark { fn generate_trace_rows( &self, - addr_and_bytes: Vec<(MemoryAddress, Vec)>, + ops: Vec, min_rows: usize, ) -> Vec<[F; NUM_COLUMNS]> { - let base_len: usize = addr_and_bytes.iter().map(|(_, bytes)| bytes.len()).sum(); + let base_len: usize = ops.iter().map(|op| op.bytes.len()).sum(); let mut rows = Vec::with_capacity(base_len.max(min_rows).next_power_of_two()); - for (start_addr, bytes) in addr_and_bytes { - rows.extend(self.generate_rows_for_bytes(start_addr, bytes)); + for op in ops { + rows.extend(self.generate_rows_for_op(op)); } let padded_rows = rows.len().max(min_rows).next_power_of_two(); @@ -82,11 +95,13 @@ impl, const D: usize> BytePackingStark { rows } - fn generate_rows_for_bytes( - &self, - start_addr: MemoryAddress, - bytes: Vec, - ) -> Vec<[F; NUM_COLUMNS]> { + fn generate_rows_for_op(&self, op: BytePackingOp) -> Vec<[F; NUM_COLUMNS]> { + let BytePackingOp { + base_address, + timestamp, + bytes, + } = op; + let mut rows = Vec::with_capacity(bytes.len()); let mut row = [F::ZERO; NUM_COLUMNS]; row[FILTER] = F::ONE; @@ -95,13 +110,15 @@ impl, const D: usize> BytePackingStark { context, segment, virt, - } = start_addr; + } = base_address; row[ADDR_CONTEXT] = F::from_canonical_usize(context); row[ADDR_SEGMENT] = F::from_canonical_usize(segment); row[ADDR_VIRTUAL] = F::from_canonical_usize(virt); + row[TIMESTAMP] = F::from_canonical_usize(timestamp); for (i, &byte) in bytes.iter().enumerate() { row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1); + row[SEQUENCE_END] = F::from_bool(bytes.len() == 1); row[value_bytes(i)] = F::from_canonical_u8(byte); row[value_limb(i / 4)] += F::from_canonical_u32((byte as u32) << (8 * (i % 4))); @@ -152,6 +169,15 @@ impl, const D: usize> Stark for BytePackingSt // The sequence start flag column must start by one. yield_constr.constraint_first_row(sequence_start - one); + // The sequence end flag must be boolean + let sequence_end = vars.local_values[SEQUENCE_END]; + yield_constr.constraint(sequence_end * (sequence_end - one)); + + // If the sequence end flag is activated, the next row must be a new sequence or filter must be off. + let next_sequence_start = vars.next_values[SEQUENCE_START]; + yield_constr + .constraint_transition(sequence_end * next_filter * (next_sequence_start - one)); + // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; let next_remaining_length = vars.local_values[REMAINING_LEN]; @@ -167,21 +193,26 @@ impl, const D: usize> Stark for BytePackingSt let next_sequence_start = vars.next_values[SEQUENCE_START]; yield_constr.constraint_transition(current_remaining_length * (next_sequence_start - one)); - // The context and segment fields must remain unchanged throughout a byte sequence. + // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. // The virtual address must increment by one at each step of a sequence. + let next_filter = vars.next_values[FILTER]; let current_context = vars.local_values[ADDR_CONTEXT]; let next_context = vars.next_values[ADDR_CONTEXT]; let current_segment = vars.local_values[ADDR_SEGMENT]; let next_segment = vars.next_values[ADDR_SEGMENT]; let current_virtual = vars.local_values[ADDR_VIRTUAL]; let next_virtual = vars.next_values[ADDR_VIRTUAL]; - let next_filter = vars.next_values[FILTER]; + let current_timestamp = vars.local_values[TIMESTAMP]; + let next_timestamp = vars.next_values[TIMESTAMP]; yield_constr.constraint_transition( next_filter * (next_sequence_start - one) * (next_context - current_context), ); yield_constr.constraint_transition( next_filter * (next_sequence_start - one) * (next_segment - current_segment), ); + yield_constr.constraint_transition( + next_filter * (next_sequence_start - one) * (next_timestamp - current_timestamp), + ); yield_constr.constraint_transition( next_filter * (next_sequence_start - one) * (next_virtual - current_virtual - one), ); @@ -213,8 +244,6 @@ impl, const D: usize> Stark for BytePackingSt vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { - let one = builder.one_extension(); - // The filter must be boolean. let filter = vars.local_values[FILTER]; let constraint = builder.mul_sub_extension(filter, filter, filter); @@ -222,7 +251,7 @@ impl, const D: usize> Stark for BytePackingSt // The filter column must start by one. let constraint = builder.add_const_extension(filter, F::NEG_ONE); - yield_constr.constraint_first_row(builder, filter); + yield_constr.constraint_first_row(builder, constraint); // Only padding rows have their filter turned off. let next_filter = vars.next_values[FILTER]; @@ -236,8 +265,19 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(builder, constraint); // The sequence start flag column must start by one. - let constraint = builder.add_const_extension(filter, F::NEG_ONE); - yield_constr.constraint_first_row(builder, filter); + let constraint = builder.add_const_extension(sequence_start, F::NEG_ONE); + yield_constr.constraint_first_row(builder, constraint); + + // The sequence end flag must be boolean + let sequence_end = vars.local_values[SEQUENCE_END]; + let constraint = builder.mul_sub_extension(sequence_end, sequence_end, sequence_end); + yield_constr.constraint(builder, constraint); + + // If the sequence end flag is activated, the next row must be a new sequence or filter must be off. + let next_sequence_start = vars.local_values[SEQUENCE_START]; + let constraint = builder.mul_sub_extension(sequence_end, next_sequence_start, sequence_end); + let constraint = builder.mul_extension(next_filter, constraint); + yield_constr.constraint(builder, constraint); // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; @@ -260,15 +300,17 @@ impl, const D: usize> Stark for BytePackingSt ); yield_constr.constraint_transition(builder, constraint); - // The context and segment fields must remain unchanged throughout a byte sequence. + // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. // The virtual address must increment by one at each step of a sequence. + let next_filter = vars.next_values[FILTER]; let current_context = vars.local_values[ADDR_CONTEXT]; let next_context = vars.next_values[ADDR_CONTEXT]; let current_segment = vars.local_values[ADDR_SEGMENT]; let next_segment = vars.next_values[ADDR_SEGMENT]; let current_virtual = vars.local_values[ADDR_VIRTUAL]; let next_virtual = vars.next_values[ADDR_VIRTUAL]; - let next_filter = vars.next_values[FILTER]; + let current_timestamp = vars.local_values[TIMESTAMP]; + let next_timestamp = vars.next_values[TIMESTAMP]; let addr_filter = builder.mul_sub_extension(next_filter, next_sequence_start, next_filter); { let constraint = builder.sub_extension(next_context, current_context); @@ -280,6 +322,11 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_extension(addr_filter, constraint); yield_constr.constraint_transition(builder, constraint); } + { + let constraint = builder.sub_extension(next_timestamp, current_timestamp); + let constraint = builder.mul_extension(addr_filter, constraint); + yield_constr.constraint_transition(builder, constraint); + } { let constraint = builder.sub_extension(next_virtual, current_virtual); let constraint = builder.mul_sub_extension(addr_filter, constraint, addr_filter); @@ -309,7 +356,7 @@ impl, const D: usize> Stark for BytePackingSt builder.mul_const_extension(F::from_canonical_usize(1 << (8 * (i % 4))), v); value = builder.add_extension(value, scaled_v); } - let byte_diff = builder.sub_extension(current_limb, value); + let constraint = builder.sub_extension(current_limb, value); yield_constr.constraint(builder, constraint); } } diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 5dda8e7690..62c85289e8 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -7,14 +7,18 @@ use crate::byte_packing::{VALUE_BYTES, VALUE_LIMBS}; pub(crate) const FILTER: usize = 0; /// 1 if this is the beginning of a new sequence of bytes. pub(crate) const SEQUENCE_START: usize = FILTER + 1; +/// 1 if this is the end of a sequence of bytes. +/// This is also used as filter for the CTL. +pub(crate) const SEQUENCE_END: usize = SEQUENCE_START + 1; -pub(crate) const ADDR_CONTEXT: usize = SEQUENCE_START + 1; +pub(crate) const ADDR_CONTEXT: usize = SEQUENCE_END + 1; pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; +pub(crate) const TIMESTAMP: usize = ADDR_VIRTUAL + 1; /// The remaining length of this pack of bytes. /// Expected to not be greater than 32. -pub(crate) const REMAINING_LEN: usize = ADDR_VIRTUAL + 1; +pub(crate) const REMAINING_LEN: usize = TIMESTAMP + 1; // 32 byte limbs hold a total of 256 bits. const BYTES_START: usize = REMAINING_LEN + 1; diff --git a/evm/src/witness/traces.rs b/evm/src/witness/traces.rs index 72d37da556..42b5da18cb 100644 --- a/evm/src/witness/traces.rs +++ b/evm/src/witness/traces.rs @@ -7,8 +7,8 @@ use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; -use super::memory::MemoryAddress; use crate::all_stark::{AllStark, NUM_TABLES}; +use crate::byte_packing::byte_packing_stark::BytePackingOp; use crate::config::StarkConfig; use crate::cpu::columns::CpuColumnsView; use crate::keccak_sponge::columns::KECCAK_WIDTH_BYTES; @@ -31,7 +31,7 @@ pub struct TraceCheckpoint { #[derive(Debug)] pub(crate) struct Traces { pub(crate) arithmetic_ops: Vec, - pub(crate) byte_packing_ops: Vec<(MemoryAddress, Vec)>, + pub(crate) byte_packing_ops: Vec, pub(crate) cpu: Vec>, pub(crate) logic_ops: Vec, pub(crate) memory_ops: Vec, @@ -95,8 +95,8 @@ impl Traces { self.memory_ops.push(op); } - pub fn push_byte_packing(&mut self, address: MemoryAddress, bytes: Vec) { - self.byte_packing_ops.push((address, bytes)); + pub fn push_byte_packing(&mut self, op: BytePackingOp) { + self.byte_packing_ops.push(op); } pub fn push_keccak(&mut self, input: [u64; keccak::keccak_stark::NUM_INPUTS]) { diff --git a/evm/src/witness/util.rs b/evm/src/witness/util.rs index b809943077..6f7f3f7518 100644 --- a/evm/src/witness/util.rs +++ b/evm/src/witness/util.rs @@ -1,6 +1,7 @@ use ethereum_types::U256; use plonky2::field::types::Field; +use crate::byte_packing::byte_packing_stark::BytePackingOp; use crate::cpu::columns::CpuColumnsView; use crate::cpu::kernel::keccak_util::keccakf_u8s; use crate::cpu::membus::{NUM_CHANNELS, NUM_GP_CHANNELS}; @@ -262,12 +263,12 @@ pub(crate) fn keccak_sponge_log( pub(crate) fn byte_packing_log( state: &mut GenerationState, base_address: MemoryAddress, - inputs: Vec, + bytes: Vec, ) { let clock = state.traces.clock(); let mut address = base_address; - for &byte in &inputs { + for &byte in &bytes { state.traces.push_memory(MemoryOp::new( MemoryChannel::Code, clock, @@ -278,5 +279,9 @@ pub(crate) fn byte_packing_log( address.increment(); } - state.traces.push_byte_packing(base_address, inputs); + state.traces.push_byte_packing(BytePackingOp { + base_address, + timestamp: clock * NUM_CHANNELS, + bytes, + }); } From 5c410ef17a70c669840caed5a70b82a1faf00807 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 30 Aug 2023 20:18:29 -0400 Subject: [PATCH 14/40] Add extra column --- evm/src/byte_packing/byte_packing_stark.rs | 12 ++++++++++++ evm/src/byte_packing/columns.rs | 3 +++ 2 files changed, 15 insertions(+) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 16eecab14a..6b3d1b27a4 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -115,6 +115,7 @@ impl, const D: usize> BytePackingStark { row[ADDR_SEGMENT] = F::from_canonical_usize(segment); row[ADDR_VIRTUAL] = F::from_canonical_usize(virt); row[TIMESTAMP] = F::from_canonical_usize(timestamp); + row[SEQUENCE_LEN] = F::from_canonical_usize(bytes.len()); for (i, &byte) in bytes.iter().enumerate() { row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1); @@ -185,6 +186,11 @@ impl, const D: usize> Stark for BytePackingSt current_remaining_length * (current_remaining_length - next_remaining_length - one), ); + // At the start of a sequence, the remaining length must be equal to the starting length minus one + let sequence_length = vars.local_values[SEQUENCE_LEN]; + yield_constr + .constraint(sequence_start * (sequence_length - current_remaining_length - one)); + // The remaining length on the last row must be zero. let final_remaining_length = vars.local_values[REMAINING_LEN]; yield_constr.constraint_last_row(final_remaining_length); @@ -285,6 +291,12 @@ impl, const D: usize> Stark for BytePackingSt let length_diff = builder.sub_extension(current_remaining_length, next_remaining_length); let length_diff_minus_one = builder.add_const_extension(length_diff, F::NEG_ONE); let constraint = builder.mul_extension(current_remaining_length, length_diff_minus_one); + yield_constr.constraint_transition(builder, constraint); + + // At the start of a sequence, the remaining length must be equal to the starting length minus one + let sequence_length = vars.local_values[SEQUENCE_LEN]; + let length_diff = builder.sub_extension(sequence_length, current_remaining_length); + let constraint = builder.mul_sub_extension(sequence_start, length_diff, sequence_start); yield_constr.constraint(builder, constraint); // The remaining length on the last row must be zero. diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 62c85289e8..f858f34183 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -16,6 +16,9 @@ pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; pub(crate) const TIMESTAMP: usize = ADDR_VIRTUAL + 1; +/// The total length of this pack of bytes. +/// Expected to not be greater than 32. +pub(crate) const SEQUENCE_LEN: usize = TIMESTAMP + 1; /// The remaining length of this pack of bytes. /// Expected to not be greater than 32. pub(crate) const REMAINING_LEN: usize = TIMESTAMP + 1; From f7fe9ef25a18763fa74ecac99be578d44c06b3a6 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Thu, 31 Aug 2023 12:21:49 -0400 Subject: [PATCH 15/40] Fix BytePackingStark CTL --- evm/src/all_stark.rs | 25 ++++++---- evm/src/byte_packing/byte_packing_stark.rs | 58 +++++++++++++++++++--- evm/src/byte_packing/columns.rs | 2 +- evm/src/cpu/cpu_stark.rs | 8 +++ evm/src/cross_table_lookup.rs | 3 +- 5 files changed, 76 insertions(+), 20 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 8d94b561d7..6164fa75ba 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -11,7 +11,7 @@ use crate::config::StarkConfig; use crate::cpu::cpu_stark; use crate::cpu::cpu_stark::CpuStark; use crate::cpu::membus::NUM_GP_CHANNELS; -use crate::cross_table_lookup::{Column, CrossTableLookup, TableWithColumns}; +use crate::cross_table_lookup::{CrossTableLookup, TableWithColumns}; use crate::keccak::keccak_stark; use crate::keccak::keccak_stark::KeccakStark; use crate::keccak_sponge::columns::KECCAK_RATE_BYTES; @@ -106,11 +106,11 @@ impl Table { pub(crate) fn all_cross_table_lookups() -> Vec> { vec![ ctl_arithmetic(), + ctl_byte_packing(), ctl_keccak_sponge(), ctl_keccak(), ctl_logic(), ctl_memory(), - ctl_byte_packing(), ] } @@ -192,9 +192,17 @@ fn ctl_memory() -> CrossTableLookup { Some(keccak_sponge_stark::ctl_looking_memory_filter(i)), ) }); + let byte_packing_reads = (0..32).map(|i| { + TableWithColumns::new( + Table::BytePacking, + byte_packing_stark::ctl_looking_memory(i), + Some(byte_packing_stark::ctl_looking_memory_filter()), + ) + }); let all_lookers = iter::once(cpu_memory_code_read) .chain(cpu_memory_gp_ops) .chain(keccak_sponge_reads) + .chain(byte_packing_reads) .collect(); let memory_looked = TableWithColumns::new( Table::Memory, @@ -204,17 +212,16 @@ fn ctl_memory() -> CrossTableLookup { CrossTableLookup::new(all_lookers, memory_looked) } -// TODO: update this fn ctl_byte_packing() -> CrossTableLookup { - let dummy_read = TableWithColumns::new( + let cpu_looking = TableWithColumns::new( Table::Cpu, - cpu_stark::ctl_data_code_memory(), - Some(Column::zero()), + cpu_stark::ctl_data_byte_packing(), + Some(cpu_stark::ctl_filter_byte_packing()), ); let byte_packing_looked = TableWithColumns::new( Table::BytePacking, - byte_packing_stark::ctl_data(), - Some(Column::zero()), + byte_packing_stark::ctl_looked_data(), + Some(byte_packing_stark::ctl_looked_filter()), ); - CrossTableLookup::new(vec![dummy_read], byte_packing_looked) + CrossTableLookup::new(vec![cpu_looking], byte_packing_looked) } diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 6b3d1b27a4..2f1efd650c 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -1,6 +1,5 @@ use std::marker::PhantomData; -use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::packed::PackedField; use plonky2::field::polynomial::PolynomialValues; @@ -13,7 +12,9 @@ use super::columns::{ ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_END, SEQUENCE_START, TIMESTAMP, }; use super::{VALUE_BYTES, VALUE_LIMBS}; -use crate::byte_packing::columns::{value_bytes, value_limb, FILTER, NUM_COLUMNS, REMAINING_LEN}; +use crate::byte_packing::columns::{ + value_bytes, value_limb, FILTER, NUM_COLUMNS, REMAINING_LEN, SEQUENCE_LEN, +}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; use crate::stark::Stark; @@ -21,15 +22,56 @@ use crate::util::trace_rows_to_poly_values; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; use crate::witness::memory::MemoryAddress; -// TODO: change -pub fn ctl_data() -> Vec> { - let mut res = Column::singles([FILTER, FILTER, FILTER, FILTER]).collect_vec(); - res.extend(Column::singles((0..8).map(value_limb))); - res.push(Column::single(FILTER)); +pub(crate) fn ctl_looked_data() -> Vec> { + let outputs = Column::singles((0..8).map(|i| value_limb(i))); + + // We recover the initial ADDR_VIRTUAL from the sequence length. + let virt_initial = Column::linear_combination_with_constant( + [(ADDR_VIRTUAL, F::ONE), (SEQUENCE_LEN, F::NEG_ONE)], + F::ONE, + ); + + Column::singles([ADDR_CONTEXT, ADDR_SEGMENT]) + .chain([virt_initial]) + .chain(Column::singles([SEQUENCE_LEN, TIMESTAMP])) + .chain(outputs) + .collect() +} + +pub fn ctl_looked_filter() -> Column { + // The CPU table is only interested in our sequence end rows, + // since those contain the final limbs of our packed int. + Column::single(SEQUENCE_END) +} + +pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { + let mut res = vec![Column::constant(F::ONE)]; // is_read + + res.extend(Column::singles([ADDR_CONTEXT, ADDR_SEGMENT])); + + // The address of the byte being read is `virt + total_len - remaining_len - 1 + i`. + res.push(Column::linear_combination_with_constant( + [ + (ADDR_VIRTUAL, F::ONE), + (SEQUENCE_LEN, F::ONE), + (REMAINING_LEN, F::NEG_ONE), + ], + F::from_canonical_usize(i + 1), + )); + + // The i'th input byte being read. + res.push(Column::single(value_bytes(i))); + + // Since we're reading a single byte, the higher limbs must be zero. + res.extend((1..8).map(|_| Column::zero())); + + res.push(Column::single(TIMESTAMP)); + res } -pub fn ctl_filter() -> Column { +/// CTL filter for reading the `i`th byte of the byte sequence from memory. +pub(crate) fn ctl_looking_memory_filter() -> Column { Column::single(FILTER) } diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index f858f34183..c789474d94 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -21,7 +21,7 @@ pub(crate) const TIMESTAMP: usize = ADDR_VIRTUAL + 1; pub(crate) const SEQUENCE_LEN: usize = TIMESTAMP + 1; /// The remaining length of this pack of bytes. /// Expected to not be greater than 32. -pub(crate) const REMAINING_LEN: usize = TIMESTAMP + 1; +pub(crate) const REMAINING_LEN: usize = SEQUENCE_LEN + 1; // 32 byte limbs hold a total of 256 bits. const BYTES_START: usize = REMAINING_LEN + 1; diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 7fd0c76fcc..6c97e54ffa 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -153,6 +153,14 @@ pub fn ctl_arithmetic_shift_rows() -> TableWithColumns { ) } +pub fn ctl_data_byte_packing() -> Vec> { + ctl_data_keccak_sponge() +} + +pub fn ctl_filter_byte_packing() -> Column { + Column::single(COL_MAP.op.mload_32bytes) +} + pub const MEM_CODE_CHANNEL_IDX: usize = 0; pub const MEM_GP_CHANNELS_IDX_START: usize = MEM_CODE_CHANNEL_IDX + 1; diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index fff17bc16e..a9b90428ca 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -542,8 +542,7 @@ pub(crate) fn verify_cross_table_lookups, const D: looking_tables, looked_table, }, - // TODO: Fix extra product with new table - ) in cross_table_lookups.iter().enumerate().take(NUM_TABLES - 3) + ) in cross_table_lookups.iter().enumerate() { let extra_product_vec = &ctl_extra_looking_products[looked_table.table as usize]; for c in 0..config.num_challenges { From bbabe550c3991c1f55f2c6598b3b3ab52e89740b Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Fri, 1 Sep 2023 13:15:47 -0400 Subject: [PATCH 16/40] Tiny fix in witness generation --- evm/src/byte_packing/byte_packing_stark.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 2f1efd650c..79684a1ca2 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -160,8 +160,8 @@ impl, const D: usize> BytePackingStark { row[SEQUENCE_LEN] = F::from_canonical_usize(bytes.len()); for (i, &byte) in bytes.iter().enumerate() { - row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1); - row[SEQUENCE_END] = F::from_bool(bytes.len() == 1); + row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1 - i); + row[SEQUENCE_END] = F::from_bool(bytes.len() == i + 1); row[value_bytes(i)] = F::from_canonical_u8(byte); row[value_limb(i / 4)] += F::from_canonical_u32((byte as u32) << (8 * (i % 4))); From fcb6920abf3d67cf6f0da94f2fc605834a635d83 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Fri, 1 Sep 2023 16:30:04 -0400 Subject: [PATCH 17/40] Fix the Memory CTL --- evm/src/all_stark.rs | 2 +- evm/src/byte_packing/byte_packing_stark.rs | 10 ++++++---- evm/src/byte_packing/columns.rs | 8 +++++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 6164fa75ba..ed33aa2888 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -196,7 +196,7 @@ fn ctl_memory() -> CrossTableLookup { TableWithColumns::new( Table::BytePacking, byte_packing_stark::ctl_looking_memory(i), - Some(byte_packing_stark::ctl_looking_memory_filter()), + Some(byte_packing_stark::ctl_looking_memory_filter(i)), ) }); let all_lookers = iter::once(cpu_memory_code_read) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 79684a1ca2..e630d1cce6 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -9,7 +9,7 @@ use plonky2::timed; use plonky2::util::timing::TimingTree; use super::columns::{ - ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_END, SEQUENCE_START, TIMESTAMP, + index_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_END, SEQUENCE_START, TIMESTAMP, }; use super::{VALUE_BYTES, VALUE_LIMBS}; use crate::byte_packing::columns::{ @@ -56,7 +56,7 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { (SEQUENCE_LEN, F::ONE), (REMAINING_LEN, F::NEG_ONE), ], - F::from_canonical_usize(i + 1), + F::NEG_ONE, )); // The i'th input byte being read. @@ -71,8 +71,8 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { } /// CTL filter for reading the `i`th byte of the byte sequence from memory. -pub(crate) fn ctl_looking_memory_filter() -> Column { - Column::single(FILTER) +pub(crate) fn ctl_looking_memory_filter(i: usize) -> Column { + Column::single(index_bytes(i)) } /// Information about a byte packing operation needed for witness generation. @@ -163,9 +163,11 @@ impl, const D: usize> BytePackingStark { row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1 - i); row[SEQUENCE_END] = F::from_bool(bytes.len() == i + 1); row[value_bytes(i)] = F::from_canonical_u8(byte); + row[index_bytes(i)] = F::ONE; row[value_limb(i / 4)] += F::from_canonical_u32((byte as u32) << (8 * (i % 4))); rows.push(row.into()); + row[index_bytes(i)] = F::ZERO; row[ADDR_VIRTUAL] += F::ONE; if i == 0 { diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index c789474d94..b7dfb1dd84 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -11,7 +11,13 @@ pub(crate) const SEQUENCE_START: usize = FILTER + 1; /// This is also used as filter for the CTL. pub(crate) const SEQUENCE_END: usize = SEQUENCE_START + 1; -pub(crate) const ADDR_CONTEXT: usize = SEQUENCE_END + 1; +pub(super) const BYTES_INDICES_START: usize = SEQUENCE_END + 1; +pub(crate) const fn index_bytes(i: usize) -> usize { + debug_assert!(i < VALUE_BYTES); + BYTES_INDICES_START + i +} + +pub(crate) const ADDR_CONTEXT: usize = BYTES_INDICES_START + VALUE_BYTES; pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; pub(crate) const TIMESTAMP: usize = ADDR_VIRTUAL + 1; From e88744ad16196bed69264e972edda6016bceec61 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Fri, 1 Sep 2023 16:38:35 -0400 Subject: [PATCH 18/40] Add constraints for the new columns --- evm/src/byte_packing/byte_packing_stark.rs | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index e630d1cce6..35b2650c1c 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -223,6 +223,19 @@ impl, const D: usize> Stark for BytePackingSt yield_constr .constraint_transition(sequence_end * next_filter * (next_sequence_start - one)); + // Each byte index must be boolean. + for i in 0..VALUE_BYTES { + let idx_i = vars.local_values[index_bytes(i)]; + yield_constr.constraint(idx_i * (idx_i - one)); + } + + // There must be only one byte index set to 1 per active row. + let sum_indices = vars.local_values[index_bytes(0)..index_bytes(0) + VALUE_BYTES] + .iter() + .copied() + .sum::

(); + yield_constr.constraint(filter * (sum_indices - P::ONES)); + // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; let next_remaining_length = vars.local_values[REMAINING_LEN]; @@ -329,6 +342,20 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_extension(next_filter, constraint); yield_constr.constraint(builder, constraint); + // Each byte index must be boolean. + for i in 0..VALUE_BYTES { + let idx_i = vars.local_values[index_bytes(i)]; + let constraint = builder.mul_sub_extension(idx_i, idx_i, idx_i); + yield_constr.constraint(builder, constraint); + } + + // There must be only one byte index set to 1 per active row. + let sum_indices = builder.add_many_extension( + vars.local_values[index_bytes(0)..index_bytes(0) + VALUE_BYTES].into_iter(), + ); + let constraint = builder.mul_sub_extension(filter, sum_indices, filter); + yield_constr.constraint(builder, constraint); + // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; let next_remaining_length = vars.local_values[REMAINING_LEN]; From 3d9be527b376fdddf0e69e3b3f71eb3f911c7b63 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Fri, 1 Sep 2023 16:48:22 -0400 Subject: [PATCH 19/40] Remove 1 column --- evm/src/byte_packing/byte_packing_stark.rs | 36 ++++++---------------- evm/src/byte_packing/columns.rs | 4 +-- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 35b2650c1c..8f24221614 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -9,7 +9,7 @@ use plonky2::timed; use plonky2::util::timing::TimingTree; use super::columns::{ - index_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_END, SEQUENCE_START, TIMESTAMP, + index_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_END, TIMESTAMP, }; use super::{VALUE_BYTES, VALUE_LIMBS}; use crate::byte_packing::columns::{ @@ -147,7 +147,6 @@ impl, const D: usize> BytePackingStark { let mut rows = Vec::with_capacity(bytes.len()); let mut row = [F::ZERO; NUM_COLUMNS]; row[FILTER] = F::ONE; - row[SEQUENCE_START] = F::ONE; let MemoryAddress { context, segment, @@ -169,10 +168,6 @@ impl, const D: usize> BytePackingStark { rows.push(row.into()); row[index_bytes(i)] = F::ZERO; row[ADDR_VIRTUAL] += F::ONE; - - if i == 0 { - row[SEQUENCE_START] = F::ZERO; - } } rows @@ -207,11 +202,8 @@ impl, const D: usize> Stark for BytePackingSt let next_filter = vars.next_values[FILTER]; yield_constr.constraint_transition(next_filter * (next_filter - filter)); - // The sequence start flag must be boolean - let sequence_start = vars.local_values[SEQUENCE_START]; - yield_constr.constraint(sequence_start * (sequence_start - one)); - // The sequence start flag column must start by one. + let sequence_start = vars.local_values[index_bytes(0)]; yield_constr.constraint_first_row(sequence_start - one); // The sequence end flag must be boolean @@ -219,7 +211,7 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(sequence_end * (sequence_end - one)); // If the sequence end flag is activated, the next row must be a new sequence or filter must be off. - let next_sequence_start = vars.next_values[SEQUENCE_START]; + let next_sequence_start = vars.next_values[index_bytes(0)]; yield_constr .constraint_transition(sequence_end * next_filter * (next_sequence_start - one)); @@ -252,9 +244,8 @@ impl, const D: usize> Stark for BytePackingSt let final_remaining_length = vars.local_values[REMAINING_LEN]; yield_constr.constraint_last_row(final_remaining_length); - // If the current remaining length is zero, the next sequence start flag must be one. - let next_sequence_start = vars.next_values[SEQUENCE_START]; - yield_constr.constraint_transition(current_remaining_length * (next_sequence_start - one)); + // If the current remaining length is zero, the end flag must be one. + yield_constr.constraint_transition(current_remaining_length * sequence_end); // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. // The virtual address must increment by one at each step of a sequence. @@ -322,12 +313,8 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_extension(next_filter, constraint); yield_constr.constraint_transition(builder, constraint); - // The sequence start flag must be boolean - let sequence_start = vars.local_values[SEQUENCE_START]; - let constraint = builder.mul_sub_extension(sequence_start, sequence_start, sequence_start); - yield_constr.constraint(builder, constraint); - // The sequence start flag column must start by one. + let sequence_start = vars.local_values[index_bytes(0)]; let constraint = builder.add_const_extension(sequence_start, F::NEG_ONE); yield_constr.constraint_first_row(builder, constraint); @@ -337,7 +324,7 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(builder, constraint); // If the sequence end flag is activated, the next row must be a new sequence or filter must be off. - let next_sequence_start = vars.local_values[SEQUENCE_START]; + let next_sequence_start = vars.next_values[index_bytes(0)]; let constraint = builder.mul_sub_extension(sequence_end, next_sequence_start, sequence_end); let constraint = builder.mul_extension(next_filter, constraint); yield_constr.constraint(builder, constraint); @@ -374,13 +361,8 @@ impl, const D: usize> Stark for BytePackingSt let final_remaining_length = vars.local_values[REMAINING_LEN]; yield_constr.constraint_last_row(builder, final_remaining_length); - // If the current remaining length is zero, the next sequence start flag must be one. - let next_sequence_start = vars.next_values[SEQUENCE_START]; - let constraint = builder.mul_sub_extension( - current_remaining_length, - next_sequence_start, - current_remaining_length, - ); + // If the current remaining length is zero, the end flag must be one. + let constraint = builder.mul_extension(current_remaining_length, sequence_end); yield_constr.constraint_transition(builder, constraint); // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index b7dfb1dd84..82cf2acc7a 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -5,11 +5,9 @@ use crate::byte_packing::{VALUE_BYTES, VALUE_LIMBS}; // Columns for memory operations, ordered by (addr, timestamp). /// 1 if this is an actual memory operation, or 0 if it's a padding row. pub(crate) const FILTER: usize = 0; -/// 1 if this is the beginning of a new sequence of bytes. -pub(crate) const SEQUENCE_START: usize = FILTER + 1; /// 1 if this is the end of a sequence of bytes. /// This is also used as filter for the CTL. -pub(crate) const SEQUENCE_END: usize = SEQUENCE_START + 1; +pub(crate) const SEQUENCE_END: usize = FILTER + 1; pub(super) const BYTES_INDICES_START: usize = SEQUENCE_END + 1; pub(crate) const fn index_bytes(i: usize) -> usize { From 017c29fee4ab428c87e734bd55e45eb61830963a Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Fri, 1 Sep 2023 19:04:12 -0400 Subject: [PATCH 20/40] Remove limb columns --- evm/src/byte_packing/byte_packing_stark.rs | 60 ++++++++-------------- evm/src/byte_packing/columns.rs | 18 ++----- evm/src/byte_packing/mod.rs | 3 +- 3 files changed, 26 insertions(+), 55 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 8f24221614..b52838741a 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -1,5 +1,6 @@ use std::marker::PhantomData; +use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::packed::PackedField; use plonky2::field::polynomial::PolynomialValues; @@ -11,10 +12,8 @@ use plonky2::util::timing::TimingTree; use super::columns::{ index_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_END, TIMESTAMP, }; -use super::{VALUE_BYTES, VALUE_LIMBS}; -use crate::byte_packing::columns::{ - value_bytes, value_limb, FILTER, NUM_COLUMNS, REMAINING_LEN, SEQUENCE_LEN, -}; +use super::NUM_BYTES; +use crate::byte_packing::columns::{value_bytes, FILTER, NUM_COLUMNS, REMAINING_LEN, SEQUENCE_LEN}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; use crate::stark::Stark; @@ -23,7 +22,17 @@ use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; use crate::witness::memory::MemoryAddress; pub(crate) fn ctl_looked_data() -> Vec> { - let outputs = Column::singles((0..8).map(|i| value_limb(i))); + let outputs: Vec> = (0..8) + .map(|i| { + let range = (value_bytes(i * 4)..value_bytes((i + 1) * 4)).collect_vec(); + Column::linear_combination( + range + .iter() + .enumerate() + .map(|(j, &c)| (c, F::from_canonical_u64(1 << 8 * j))), + ) + }) + .collect(); // We recover the initial ADDR_VIRTUAL from the sequence length. let virt_initial = Column::linear_combination_with_constant( @@ -163,7 +172,6 @@ impl, const D: usize> BytePackingStark { row[SEQUENCE_END] = F::from_bool(bytes.len() == i + 1); row[value_bytes(i)] = F::from_canonical_u8(byte); row[index_bytes(i)] = F::ONE; - row[value_limb(i / 4)] += F::from_canonical_u32((byte as u32) << (8 * (i % 4))); rows.push(row.into()); row[index_bytes(i)] = F::ZERO; @@ -216,13 +224,13 @@ impl, const D: usize> Stark for BytePackingSt .constraint_transition(sequence_end * next_filter * (next_sequence_start - one)); // Each byte index must be boolean. - for i in 0..VALUE_BYTES { + for i in 0..NUM_BYTES { let idx_i = vars.local_values[index_bytes(i)]; yield_constr.constraint(idx_i * (idx_i - one)); } // There must be only one byte index set to 1 per active row. - let sum_indices = vars.local_values[index_bytes(0)..index_bytes(0) + VALUE_BYTES] + let sum_indices = vars.local_values[index_bytes(0)..index_bytes(0) + NUM_BYTES] .iter() .copied() .sum::

(); @@ -273,23 +281,12 @@ impl, const D: usize> Stark for BytePackingSt // Each next byte must equal the current one when reading through a sequence, // or the current remaining length must be zero. - for i in 0..VALUE_BYTES { + for i in 0..NUM_BYTES { let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; yield_constr .constraint_transition(current_remaining_length * (next_byte - current_byte)); } - - // Each limb must correspond to the big-endian u32 value of each chunk of 4 bytes. - for i in 0..VALUE_LIMBS { - let current_limb = vars.local_values[value_limb(i)]; - let value = vars.local_values[value_bytes(4 * i)..value_bytes(4 * i + 4)] - .iter() - .enumerate() - .map(|(i, &v)| v * P::Scalar::from_canonical_usize(1 << (8 * (i % 4)))) - .sum::

(); - yield_constr.constraint(current_limb - value); - } } fn eval_ext_circuit( @@ -330,7 +327,7 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(builder, constraint); // Each byte index must be boolean. - for i in 0..VALUE_BYTES { + for i in 0..NUM_BYTES { let idx_i = vars.local_values[index_bytes(i)]; let constraint = builder.mul_sub_extension(idx_i, idx_i, idx_i); yield_constr.constraint(builder, constraint); @@ -338,7 +335,7 @@ impl, const D: usize> Stark for BytePackingSt // There must be only one byte index set to 1 per active row. let sum_indices = builder.add_many_extension( - vars.local_values[index_bytes(0)..index_bytes(0) + VALUE_BYTES].into_iter(), + vars.local_values[index_bytes(0)..index_bytes(0) + NUM_BYTES].into_iter(), ); let constraint = builder.mul_sub_extension(filter, sum_indices, filter); yield_constr.constraint(builder, constraint); @@ -400,30 +397,13 @@ impl, const D: usize> Stark for BytePackingSt // Each next byte must equal the current one when reading through a sequence, // or the current remaining length must be zero. - for i in 0..VALUE_BYTES { + for i in 0..NUM_BYTES { let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; let byte_diff = builder.sub_extension(current_byte, next_byte); let constraint = builder.mul_extension(current_remaining_length, byte_diff); yield_constr.constraint(builder, constraint); } - - // Each limb must correspond to the big-endian u32 value of each chunk of 4 bytes. - for i in 0..VALUE_LIMBS { - let current_limb = vars.local_values[value_limb(i)]; - let mut value = vars.local_values[value_bytes(4 * i)]; - for (i, &v) in vars.local_values[value_bytes(4 * i)..value_bytes(4 * i + 4)] - .iter() - .enumerate() - .skip(1) - { - let scaled_v = - builder.mul_const_extension(F::from_canonical_usize(1 << (8 * (i % 4))), v); - value = builder.add_extension(value, scaled_v); - } - let constraint = builder.sub_extension(current_limb, value); - yield_constr.constraint(builder, constraint); - } } fn constraint_degree(&self) -> usize { diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 82cf2acc7a..c8ef816b8f 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -1,6 +1,6 @@ //! Byte packing registers. -use crate::byte_packing::{VALUE_BYTES, VALUE_LIMBS}; +use crate::byte_packing::NUM_BYTES; // Columns for memory operations, ordered by (addr, timestamp). /// 1 if this is an actual memory operation, or 0 if it's a padding row. @@ -11,11 +11,11 @@ pub(crate) const SEQUENCE_END: usize = FILTER + 1; pub(super) const BYTES_INDICES_START: usize = SEQUENCE_END + 1; pub(crate) const fn index_bytes(i: usize) -> usize { - debug_assert!(i < VALUE_BYTES); + debug_assert!(i < NUM_BYTES); BYTES_INDICES_START + i } -pub(crate) const ADDR_CONTEXT: usize = BYTES_INDICES_START + VALUE_BYTES; +pub(crate) const ADDR_CONTEXT: usize = BYTES_INDICES_START + NUM_BYTES; pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; pub(crate) const TIMESTAMP: usize = ADDR_VIRTUAL + 1; @@ -30,16 +30,8 @@ pub(crate) const REMAINING_LEN: usize = SEQUENCE_LEN + 1; // 32 byte limbs hold a total of 256 bits. const BYTES_START: usize = REMAINING_LEN + 1; pub(crate) const fn value_bytes(i: usize) -> usize { - debug_assert!(i < VALUE_BYTES); + debug_assert!(i < NUM_BYTES); BYTES_START + i } -// Eight 32-bit limbs hold a total of 256 bits, representing the big-endian -// encoding of the previous byte sequence. -const VALUE_START: usize = BYTES_START + VALUE_BYTES; -pub(crate) const fn value_limb(i: usize) -> usize { - debug_assert!(i < VALUE_LIMBS); - VALUE_START + i -} - -pub(crate) const NUM_COLUMNS: usize = VALUE_START + VALUE_LIMBS; +pub(crate) const NUM_COLUMNS: usize = BYTES_START + NUM_BYTES; diff --git a/evm/src/byte_packing/mod.rs b/evm/src/byte_packing/mod.rs index d61f9f252b..bbe9257e1a 100644 --- a/evm/src/byte_packing/mod.rs +++ b/evm/src/byte_packing/mod.rs @@ -1,5 +1,4 @@ pub mod byte_packing_stark; pub mod columns; -pub(crate) const VALUE_BYTES: usize = 32; -pub(crate) const VALUE_LIMBS: usize = 8; +pub(crate) const NUM_BYTES: usize = 32; From 0193626b9fb747f3209c0f7bf24041115279a3f8 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Fri, 1 Sep 2023 19:48:55 -0400 Subject: [PATCH 21/40] Fix --- evm/src/byte_packing/byte_packing_stark.rs | 21 ++++++++++----------- evm/src/byte_packing/columns.rs | 5 +++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index b52838741a..f23962624d 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -249,11 +249,10 @@ impl, const D: usize> Stark for BytePackingSt .constraint(sequence_start * (sequence_length - current_remaining_length - one)); // The remaining length on the last row must be zero. - let final_remaining_length = vars.local_values[REMAINING_LEN]; - yield_constr.constraint_last_row(final_remaining_length); + yield_constr.constraint_last_row(current_remaining_length); // If the current remaining length is zero, the end flag must be one. - yield_constr.constraint_transition(current_remaining_length * sequence_end); + yield_constr.constraint(current_remaining_length * sequence_end); // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. // The virtual address must increment by one at each step of a sequence. @@ -280,12 +279,12 @@ impl, const D: usize> Stark for BytePackingSt ); // Each next byte must equal the current one when reading through a sequence, - // or the current remaining length must be zero. + // or the next byte index must be one. for i in 0..NUM_BYTES { let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; - yield_constr - .constraint_transition(current_remaining_length * (next_byte - current_byte)); + let next_byte_index = vars.next_values[index_bytes(i)]; + yield_constr.constraint_transition(next_byte_index * (next_byte - current_byte)); } } @@ -355,12 +354,11 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(builder, constraint); // The remaining length on the last row must be zero. - let final_remaining_length = vars.local_values[REMAINING_LEN]; - yield_constr.constraint_last_row(builder, final_remaining_length); + yield_constr.constraint_last_row(builder, current_remaining_length); // If the current remaining length is zero, the end flag must be one. let constraint = builder.mul_extension(current_remaining_length, sequence_end); - yield_constr.constraint_transition(builder, constraint); + yield_constr.constraint(builder, constraint); // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. // The virtual address must increment by one at each step of a sequence. @@ -396,12 +394,13 @@ impl, const D: usize> Stark for BytePackingSt } // Each next byte must equal the current one when reading through a sequence, - // or the current remaining length must be zero. + // or the next byte index must be one. for i in 0..NUM_BYTES { let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; + let next_byte_index = vars.next_values[index_bytes(i)]; let byte_diff = builder.sub_extension(current_byte, next_byte); - let constraint = builder.mul_extension(current_remaining_length, byte_diff); + let constraint = builder.mul_extension(next_byte_index, byte_diff); yield_constr.constraint(builder, constraint); } } diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index c8ef816b8f..8cebf14249 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -2,8 +2,7 @@ use crate::byte_packing::NUM_BYTES; -// Columns for memory operations, ordered by (addr, timestamp). -/// 1 if this is an actual memory operation, or 0 if it's a padding row. +/// 1 if this is an actual byte packing operation, or 0 if it's a padding row. pub(crate) const FILTER: usize = 0; /// 1 if this is the end of a sequence of bytes. /// This is also used as filter for the CTL. @@ -25,6 +24,8 @@ pub(crate) const TIMESTAMP: usize = ADDR_VIRTUAL + 1; pub(crate) const SEQUENCE_LEN: usize = TIMESTAMP + 1; /// The remaining length of this pack of bytes. /// Expected to not be greater than 32. +// TODO: We should be able to remove this by leveraging `SEQUENCE_LEN` and the +// byte indices. pub(crate) const REMAINING_LEN: usize = SEQUENCE_LEN + 1; // 32 byte limbs hold a total of 256 bits. From 3e0a58e4b8643c868de29d67df702470fda0ee39 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Sat, 2 Sep 2023 10:38:43 -0400 Subject: [PATCH 22/40] Fix recursive circuit of BytePackingTable --- evm/src/byte_packing/byte_packing_stark.rs | 6 +++--- evm/src/byte_packing/columns.rs | 2 ++ evm/src/fixed_recursive_verifier.rs | 9 ++++----- evm/tests/empty_txn_list.rs | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index f23962624d..4436980ccf 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -323,7 +323,7 @@ impl, const D: usize> Stark for BytePackingSt let next_sequence_start = vars.next_values[index_bytes(0)]; let constraint = builder.mul_sub_extension(sequence_end, next_sequence_start, sequence_end); let constraint = builder.mul_extension(next_filter, constraint); - yield_constr.constraint(builder, constraint); + yield_constr.constraint_transition(builder, constraint); // Each byte index must be boolean. for i in 0..NUM_BYTES { @@ -399,9 +399,9 @@ impl, const D: usize> Stark for BytePackingSt let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; let next_byte_index = vars.next_values[index_bytes(i)]; - let byte_diff = builder.sub_extension(current_byte, next_byte); + let byte_diff = builder.sub_extension(next_byte, current_byte); let constraint = builder.mul_extension(next_byte_index, byte_diff); - yield_constr.constraint(builder, constraint); + yield_constr.constraint_transition(builder, constraint); } } diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 8cebf14249..4423b27566 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -6,6 +6,8 @@ use crate::byte_packing::NUM_BYTES; pub(crate) const FILTER: usize = 0; /// 1 if this is the end of a sequence of bytes. /// This is also used as filter for the CTL. +// TODO: We should be able to remove this by leveraging `SEQUENCE_LEN` and the +// byte indices for the CTL filter. pub(crate) const SEQUENCE_END: usize = FILTER + 1; pub(super) const BYTES_INDICES_START: usize = SEQUENCE_END + 1; diff --git a/evm/src/fixed_recursive_verifier.rs b/evm/src/fixed_recursive_verifier.rs index fd4c84ca24..1b544b2d01 100644 --- a/evm/src/fixed_recursive_verifier.rs +++ b/evm/src/fixed_recursive_verifier.rs @@ -506,13 +506,13 @@ where } } - // Extra products to add to the looked last value - // Arithmetic, KeccakSponge, Keccak, Logic + // Extra products to add to the looked last value. + // Only necessary for the Memory values. let mut extra_looking_products = - vec![vec![builder.constant(F::ONE); stark_config.num_challenges]; NUM_TABLES - 1]; + vec![vec![builder.one(); stark_config.num_challenges]; NUM_TABLES]; // Memory - let memory_looking_products = (0..stark_config.num_challenges) + extra_looking_products[Table::Memory as usize] = (0..stark_config.num_challenges) .map(|c| { get_memory_extra_looking_products_circuit( &mut builder, @@ -521,7 +521,6 @@ where ) }) .collect_vec(); - extra_looking_products.push(memory_looking_products); // Verify the CTL checks. verify_cross_table_lookups_circuit::( diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs index 717d056905..ba2717bda9 100644 --- a/evm/tests/empty_txn_list.rs +++ b/evm/tests/empty_txn_list.rs @@ -67,7 +67,7 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 15..16, 14..15, 9..10, 12..13, 18..19, 18..19], // Minimal ranges to prove an empty list + &[16..17, 15..16, 14..15, 9..10, 12..13, 18..19, 4..5], // Minimal ranges to prove an empty list &config, ); From 5827e84d358d1da7010c67200b84474a4a814de5 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Sat, 2 Sep 2023 11:39:25 -0400 Subject: [PATCH 23/40] Fix constraints --- evm/src/byte_packing/byte_packing_stark.rs | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 4436980ccf..931170815f 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -169,7 +169,9 @@ impl, const D: usize> BytePackingStark { for (i, &byte) in bytes.iter().enumerate() { row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1 - i); - row[SEQUENCE_END] = F::from_bool(bytes.len() == i + 1); + if i == bytes.len() - 1 { + row[SEQUENCE_END] = F::ONE; + } row[value_bytes(i)] = F::from_canonical_u8(byte); row[index_bytes(i)] = F::ONE; @@ -238,7 +240,7 @@ impl, const D: usize> Stark for BytePackingSt // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; - let next_remaining_length = vars.local_values[REMAINING_LEN]; + let next_remaining_length = vars.next_values[REMAINING_LEN]; yield_constr.constraint_transition( current_remaining_length * (current_remaining_length - next_remaining_length - one), ); @@ -278,13 +280,15 @@ impl, const D: usize> Stark for BytePackingSt next_filter * (next_sequence_start - one) * (next_virtual - current_virtual - one), ); - // Each next byte must equal the current one when reading through a sequence, - // or the next byte index must be one. + // If not at the end of a sequence, each next byte must equal the current one + // when reading through the sequence, or the next byte index must be one. for i in 0..NUM_BYTES { let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; let next_byte_index = vars.next_values[index_bytes(i)]; - yield_constr.constraint_transition(next_byte_index * (next_byte - current_byte)); + yield_constr.constraint_transition( + (sequence_end - one) * (next_byte_index - one) * (next_byte - current_byte), + ); } } @@ -341,7 +345,7 @@ impl, const D: usize> Stark for BytePackingSt // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; - let next_remaining_length = vars.local_values[REMAINING_LEN]; + let next_remaining_length = vars.next_values[REMAINING_LEN]; let length_diff = builder.sub_extension(current_remaining_length, next_remaining_length); let length_diff_minus_one = builder.add_const_extension(length_diff, F::NEG_ONE); let constraint = builder.mul_extension(current_remaining_length, length_diff_minus_one); @@ -393,14 +397,15 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint_transition(builder, constraint); } - // Each next byte must equal the current one when reading through a sequence, - // or the next byte index must be one. + // If not at the end of a sequence, each next byte must equal the current one + // when reading through the sequence, or the next byte index must be one. for i in 0..NUM_BYTES { let current_byte = vars.local_values[value_bytes(i)]; let next_byte = vars.next_values[value_bytes(i)]; let next_byte_index = vars.next_values[index_bytes(i)]; let byte_diff = builder.sub_extension(next_byte, current_byte); - let constraint = builder.mul_extension(next_byte_index, byte_diff); + let constraint = builder.mul_sub_extension(byte_diff, next_byte_index, byte_diff); + let constraint = builder.mul_sub_extension(constraint, sequence_end, constraint); yield_constr.constraint_transition(builder, constraint); } } From f3e84c10e8c85ec40df84bbd7b5b86192132721d Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Sat, 2 Sep 2023 17:45:47 -0400 Subject: [PATCH 24/40] Fix endianness --- evm/src/byte_packing/byte_packing_stark.rs | 37 +++++++--------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 931170815f..c9bc549e81 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -34,14 +34,7 @@ pub(crate) fn ctl_looked_data() -> Vec> { }) .collect(); - // We recover the initial ADDR_VIRTUAL from the sequence length. - let virt_initial = Column::linear_combination_with_constant( - [(ADDR_VIRTUAL, F::ONE), (SEQUENCE_LEN, F::NEG_ONE)], - F::ONE, - ); - - Column::singles([ADDR_CONTEXT, ADDR_SEGMENT]) - .chain([virt_initial]) + Column::singles([ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL]) .chain(Column::singles([SEQUENCE_LEN, TIMESTAMP])) .chain(outputs) .collect() @@ -56,17 +49,7 @@ pub fn ctl_looked_filter() -> Column { pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { let mut res = vec![Column::constant(F::ONE)]; // is_read - res.extend(Column::singles([ADDR_CONTEXT, ADDR_SEGMENT])); - - // The address of the byte being read is `virt + total_len - remaining_len - 1 + i`. - res.push(Column::linear_combination_with_constant( - [ - (ADDR_VIRTUAL, F::ONE), - (SEQUENCE_LEN, F::ONE), - (REMAINING_LEN, F::NEG_ONE), - ], - F::NEG_ONE, - )); + res.extend(Column::singles([ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL])); // The i'th input byte being read. res.push(Column::single(value_bytes(i))); @@ -163,11 +146,13 @@ impl, const D: usize> BytePackingStark { } = base_address; row[ADDR_CONTEXT] = F::from_canonical_usize(context); row[ADDR_SEGMENT] = F::from_canonical_usize(segment); - row[ADDR_VIRTUAL] = F::from_canonical_usize(virt); + // Because of the endianness, we start by the final virtual address value + // and decrement it at each step. + row[ADDR_VIRTUAL] = F::from_canonical_usize(virt + bytes.len() - 1); row[TIMESTAMP] = F::from_canonical_usize(timestamp); row[SEQUENCE_LEN] = F::from_canonical_usize(bytes.len()); - for (i, &byte) in bytes.iter().enumerate() { + for (i, &byte) in bytes.iter().rev().enumerate() { row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1 - i); if i == bytes.len() - 1 { row[SEQUENCE_END] = F::ONE; @@ -177,7 +162,7 @@ impl, const D: usize> BytePackingStark { rows.push(row.into()); row[index_bytes(i)] = F::ZERO; - row[ADDR_VIRTUAL] += F::ONE; + row[ADDR_VIRTUAL] -= F::ONE; } rows @@ -257,7 +242,7 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(current_remaining_length * sequence_end); // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. - // The virtual address must increment by one at each step of a sequence. + // The virtual address must decrement by one at each step of a sequence. let next_filter = vars.next_values[FILTER]; let current_context = vars.local_values[ADDR_CONTEXT]; let next_context = vars.next_values[ADDR_CONTEXT]; @@ -277,7 +262,7 @@ impl, const D: usize> Stark for BytePackingSt next_filter * (next_sequence_start - one) * (next_timestamp - current_timestamp), ); yield_constr.constraint_transition( - next_filter * (next_sequence_start - one) * (next_virtual - current_virtual - one), + next_filter * (next_sequence_start - one) * (current_virtual - next_virtual - one), ); // If not at the end of a sequence, each next byte must equal the current one @@ -365,7 +350,7 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(builder, constraint); // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. - // The virtual address must increment by one at each step of a sequence. + // The virtual address must decrement by one at each step of a sequence. let next_filter = vars.next_values[FILTER]; let current_context = vars.local_values[ADDR_CONTEXT]; let next_context = vars.next_values[ADDR_CONTEXT]; @@ -392,7 +377,7 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint_transition(builder, constraint); } { - let constraint = builder.sub_extension(next_virtual, current_virtual); + let constraint = builder.sub_extension(current_virtual, next_virtual); let constraint = builder.mul_sub_extension(addr_filter, constraint, addr_filter); yield_constr.constraint_transition(builder, constraint); } From 03d561429d24a5afbe8bde7ef7ad01b391039dbb Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 5 Sep 2023 16:13:27 -0400 Subject: [PATCH 25/40] Add MSTORE_32BYTES instruction and move decomposition to packing table --- evm/src/all_stark.rs | 16 ++++-- evm/src/byte_packing/byte_packing_stark.rs | 57 ++++++++++++++++------ evm/src/byte_packing/columns.rs | 4 +- evm/src/cpu/columns/ops.rs | 1 + evm/src/cpu/cpu_stark.rs | 26 ++++++++++ evm/src/cpu/decode.rs | 9 ++-- evm/src/cpu/gas.rs | 1 + evm/src/cpu/kernel/asm/memory/packing.asm | 40 +++------------ evm/src/cpu/kernel/opcodes.rs | 1 + evm/src/cpu/stack.rs | 5 ++ evm/src/witness/gas.rs | 1 + evm/src/witness/operation.rs | 24 ++++++++- evm/src/witness/transition.rs | 3 ++ evm/src/witness/util.rs | 34 +++++++++++++ 14 files changed, 164 insertions(+), 58 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index ed33aa2888..fa29d5b792 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -192,7 +192,7 @@ fn ctl_memory() -> CrossTableLookup { Some(keccak_sponge_stark::ctl_looking_memory_filter(i)), ) }); - let byte_packing_reads = (0..32).map(|i| { + let byte_packing_ops = (0..32).map(|i| { TableWithColumns::new( Table::BytePacking, byte_packing_stark::ctl_looking_memory(i), @@ -202,7 +202,7 @@ fn ctl_memory() -> CrossTableLookup { let all_lookers = iter::once(cpu_memory_code_read) .chain(cpu_memory_gp_ops) .chain(keccak_sponge_reads) - .chain(byte_packing_reads) + .chain(byte_packing_ops) .collect(); let memory_looked = TableWithColumns::new( Table::Memory, @@ -213,15 +213,23 @@ fn ctl_memory() -> CrossTableLookup { } fn ctl_byte_packing() -> CrossTableLookup { - let cpu_looking = TableWithColumns::new( + let cpu_packing_looking = TableWithColumns::new( Table::Cpu, cpu_stark::ctl_data_byte_packing(), Some(cpu_stark::ctl_filter_byte_packing()), ); + let cpu_unpacking_looking = TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_byte_unpacking(), + Some(cpu_stark::ctl_filter_byte_unpacking()), + ); let byte_packing_looked = TableWithColumns::new( Table::BytePacking, byte_packing_stark::ctl_looked_data(), Some(byte_packing_stark::ctl_looked_filter()), ); - CrossTableLookup::new(vec![cpu_looking], byte_packing_looked) + CrossTableLookup::new( + vec![cpu_packing_looking, cpu_unpacking_looking], + byte_packing_looked, + ) } diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index c9bc549e81..752c64bb56 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -10,7 +10,7 @@ use plonky2::timed; use plonky2::util::timing::TimingTree; use super::columns::{ - index_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, SEQUENCE_END, TIMESTAMP, + index_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, IS_READ, SEQUENCE_END, TIMESTAMP, }; use super::NUM_BYTES; use crate::byte_packing::columns::{value_bytes, FILTER, NUM_COLUMNS, REMAINING_LEN, SEQUENCE_LEN}; @@ -34,10 +34,15 @@ pub(crate) fn ctl_looked_data() -> Vec> { }) .collect(); - Column::singles([ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL]) - .chain(Column::singles([SEQUENCE_LEN, TIMESTAMP])) - .chain(outputs) - .collect() + Column::singles([ + ADDR_CONTEXT, + ADDR_SEGMENT, + ADDR_VIRTUAL, + SEQUENCE_LEN, + TIMESTAMP, + ]) + .chain(outputs) + .collect() } pub fn ctl_looked_filter() -> Column { @@ -47,9 +52,8 @@ pub fn ctl_looked_filter() -> Column { } pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { - let mut res = vec![Column::constant(F::ONE)]; // is_read - - res.extend(Column::singles([ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL])); + let mut res = + Column::singles([IS_READ, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL]).collect_vec(); // The i'th input byte being read. res.push(Column::single(value_bytes(i))); @@ -70,13 +74,16 @@ pub(crate) fn ctl_looking_memory_filter(i: usize) -> Column { /// Information about a byte packing operation needed for witness generation. #[derive(Clone, Debug)] pub(crate) struct BytePackingOp { - /// The base address at which inputs are read. + /// Whether this is a read (packing) or write (unpacking) operation. + pub(crate) is_read: bool, + + /// The base address at which inputs are read/written. pub(crate) base_address: MemoryAddress, - /// The timestamp at which inputs are read. + /// The timestamp at which inputs are read/written. pub(crate) timestamp: usize, - /// The byte sequence that was read and has to be packed. + /// The byte sequence that was read/written. /// Its length is expected to be at most 32. pub(crate) bytes: Vec, } @@ -131,24 +138,29 @@ impl, const D: usize> BytePackingStark { fn generate_rows_for_op(&self, op: BytePackingOp) -> Vec<[F; NUM_COLUMNS]> { let BytePackingOp { + is_read, base_address, timestamp, bytes, } = op; - let mut rows = Vec::with_capacity(bytes.len()); - let mut row = [F::ZERO; NUM_COLUMNS]; - row[FILTER] = F::ONE; let MemoryAddress { context, segment, virt, } = base_address; + + let mut rows = Vec::with_capacity(bytes.len()); + let mut row = [F::ZERO; NUM_COLUMNS]; + row[FILTER] = F::ONE; + row[IS_READ] = F::from_bool(is_read); + row[ADDR_CONTEXT] = F::from_canonical_usize(context); row[ADDR_SEGMENT] = F::from_canonical_usize(segment); // Because of the endianness, we start by the final virtual address value // and decrement it at each step. row[ADDR_VIRTUAL] = F::from_canonical_usize(virt + bytes.len() - 1); + row[TIMESTAMP] = F::from_canonical_usize(timestamp); row[SEQUENCE_LEN] = F::from_canonical_usize(bytes.len()); @@ -193,6 +205,14 @@ impl, const D: usize> Stark for BytePackingSt // The filter column must start by one. yield_constr.constraint_first_row(filter - one); + // The is_read flag must be boolean. + let is_read = vars.local_values[IS_READ]; + yield_constr.constraint(is_read * (is_read - one)); + + // If filter is off, is_read must be off. + let is_read = vars.local_values[IS_READ]; + yield_constr.constraint((filter - one) * is_read); + // Only padding rows have their filter turned off. let next_filter = vars.next_values[FILTER]; yield_constr.constraint_transition(next_filter * (next_filter - filter)); @@ -292,6 +312,15 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.add_const_extension(filter, F::NEG_ONE); yield_constr.constraint_first_row(builder, constraint); + // The is_read flag must be boolean. + let is_read = vars.local_values[IS_READ]; + let constraint = builder.mul_sub_extension(is_read, is_read, is_read); + yield_constr.constraint(builder, constraint); + + // If filter is off, is_read must be off. + let constraint = builder.mul_sub_extension(is_read, filter, is_read); + yield_constr.constraint(builder, constraint); + // Only padding rows have their filter turned off. let next_filter = vars.next_values[FILTER]; let constraint = builder.sub_extension(next_filter, filter); diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 4423b27566..7b8efab3f5 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -4,11 +4,13 @@ use crate::byte_packing::NUM_BYTES; /// 1 if this is an actual byte packing operation, or 0 if it's a padding row. pub(crate) const FILTER: usize = 0; +/// 1 if this is a READ operation, and 0 if this is a WRITE operation. +pub(crate) const IS_READ: usize = FILTER + 1; /// 1 if this is the end of a sequence of bytes. /// This is also used as filter for the CTL. // TODO: We should be able to remove this by leveraging `SEQUENCE_LEN` and the // byte indices for the CTL filter. -pub(crate) const SEQUENCE_END: usize = FILTER + 1; +pub(crate) const SEQUENCE_END: usize = IS_READ + 1; pub(super) const BYTES_INDICES_START: usize = SEQUENCE_END + 1; pub(crate) const fn index_bytes(i: usize) -> usize { diff --git a/evm/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs index bd2273b842..6c68a18305 100644 --- a/evm/src/cpu/columns/ops.rs +++ b/evm/src/cpu/columns/ops.rs @@ -41,6 +41,7 @@ pub struct OpsColumnsView { pub dup: T, pub swap: T, pub context_op: T, + pub mstore_32bytes: T, pub mload_32bytes: T, pub exit_kernel: T, // TODO: combine MLOAD_GENERAL and MSTORE_GENERAL into one flag diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 6c97e54ffa..25e7cc6ba0 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -161,6 +161,32 @@ pub fn ctl_filter_byte_packing() -> Column { Column::single(COL_MAP.op.mload_32bytes) } +pub fn ctl_data_byte_unpacking() -> Vec> { + // When executing MSTORE_32BYTES, the GP memory channels are used as follows: + // GP channel 0: stack[-1] = context + // GP channel 1: stack[-2] = segment + // GP channel 2: stack[-3] = virt + // GP channel 3: stack[-4] = val + // GP channel 4: stack[-5] = len + let context = Column::single(COL_MAP.mem_channels[0].value[0]); + let segment = Column::single(COL_MAP.mem_channels[1].value[0]); + let virt = Column::single(COL_MAP.mem_channels[2].value[0]); + let val = Column::singles(COL_MAP.mem_channels[3].value); + let len = Column::single(COL_MAP.mem_channels[4].value[0]); + + let num_channels = F::from_canonical_usize(NUM_CHANNELS); + let timestamp = Column::linear_combination([(COL_MAP.clock, num_channels)]); + + let mut res = vec![context, segment, virt, len, timestamp]; + res.extend(val); + + res +} + +pub fn ctl_filter_byte_unpacking() -> Column { + Column::single(COL_MAP.op.mstore_32bytes) +} + pub const MEM_CODE_CHANNEL_IDX: usize = 0; pub const MEM_GP_CHANNELS_IDX_START: usize = MEM_CODE_CHANNEL_IDX + 1; diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index 6eb29acd40..9a9c572387 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -22,7 +22,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; /// behavior. /// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to /// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid. -const OPCODES: [(u8, usize, bool, usize); 32] = [ +const OPCODES: [(u8, usize, bool, usize); 33] = [ // (start index of block, number of top bits to check (log2), kernel-only, flag column) (0x01, 0, false, COL_MAP.op.add), (0x02, 0, false, COL_MAP.op.mul), @@ -49,9 +49,10 @@ const OPCODES: [(u8, usize, bool, usize); 32] = [ (0x58, 0, false, COL_MAP.op.pc), (0x5b, 0, false, COL_MAP.op.jumpdest), (0x5f, 0, false, COL_MAP.op.push0), - (0x60, 5, false, COL_MAP.op.push), // 0x60-0x7f - (0x80, 4, false, COL_MAP.op.dup), // 0x80-0x8f - (0x90, 4, false, COL_MAP.op.swap), // 0x90-0x9f + (0x60, 5, false, COL_MAP.op.push), // 0x60-0x7f + (0x80, 4, false, COL_MAP.op.dup), // 0x80-0x8f + (0x90, 4, false, COL_MAP.op.swap), // 0x90-0x9f + (0xee, 0, true, COL_MAP.op.mstore_32bytes), (0xf6, 1, true, COL_MAP.op.context_op), // 0xf6-0xf7 (0xf8, 0, true, COL_MAP.op.mload_32bytes), (0xf9, 0, true, COL_MAP.op.exit_kernel), diff --git a/evm/src/cpu/gas.rs b/evm/src/cpu/gas.rs index d32dfbcda4..e967c07ece 100644 --- a/evm/src/cpu/gas.rs +++ b/evm/src/cpu/gas.rs @@ -49,6 +49,7 @@ const SIMPLE_OPCODES: OpsColumnsView> = OpsColumnsView { dup: G_VERYLOW, swap: G_VERYLOW, context_op: KERNEL_ONLY_INSTR, + mstore_32bytes: KERNEL_ONLY_INSTR, mload_32bytes: KERNEL_ONLY_INSTR, exit_kernel: None, mload_general: KERNEL_ONLY_INSTR, diff --git a/evm/src/cpu/kernel/asm/memory/packing.asm b/evm/src/cpu/kernel/asm/memory/packing.asm index 691d3fe580..81ab31236e 100644 --- a/evm/src/cpu/kernel/asm/memory/packing.asm +++ b/evm/src/cpu/kernel/asm/memory/packing.asm @@ -42,40 +42,12 @@ global mload_packing_u64_LE: // Post stack: offset' global mstore_unpacking: // stack: context, segment, offset, value, len, retdest - // We will enumerate i in (32 - len)..32. - // That way BYTE(i, value) will give us the bytes we want. - DUP5 // len - PUSH 32 - SUB - -mstore_unpacking_loop: - // stack: i, context, segment, offset, value, len, retdest - // If i == 32, finish. - DUP1 - %eq_const(32) - %jumpi(mstore_unpacking_finish) - - // stack: i, context, segment, offset, value, len, retdest - DUP5 // value - DUP2 // i - BYTE - // stack: value[i], i, context, segment, offset, value, len, retdest - DUP5 DUP5 DUP5 // context, segment, offset - // stack: context, segment, offset, value[i], i, context, segment, offset, value, len, retdest - MSTORE_GENERAL - // stack: i, context, segment, offset, value, len, retdest - - // Increment offset. - SWAP3 %increment SWAP3 - // Increment i. - %increment - - %jump(mstore_unpacking_loop) - -mstore_unpacking_finish: - // stack: i, context, segment, offset, value, len, retdest - %pop3 - %stack (offset, value, len, retdest) -> (retdest, offset) + %stack(context, segment, offset, value, len, retdest) -> (context, segment, offset, value, len, len, offset, retdest) + // stack: context, segment, offset, value, len, len, offset, retdest + MSTORE_32BYTES + // stack: len, offset, retdest + ADD SWAP1 + // stack: retdest, offset' JUMP %macro mstore_unpacking diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index 4caa46a67a..2503a92e74 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -113,6 +113,7 @@ pub fn get_opcode(mnemonic: &str) -> u8 { "LOG3" => 0xa3, "LOG4" => 0xa4, "PANIC" => 0xa5, + "MSTORE_32BYTES" => 0xee, "CREATE" => 0xf0, "CALL" => 0xf1, "CALLCODE" => 0xf2, diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index adeafb7723..cfeaa1b0b5 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -108,6 +108,11 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { dup: None, swap: None, context_op: None, // SET_CONTEXT is special since it involves the old and the new stack. + mstore_32bytes: Some(StackBehavior { + num_pops: 5, + pushes: false, + disable_other_channels: false, + }), mload_32bytes: Some(StackBehavior { num_pops: 4, pushes: true, diff --git a/evm/src/witness/gas.rs b/evm/src/witness/gas.rs index 63db0d7c64..3a46c04439 100644 --- a/evm/src/witness/gas.rs +++ b/evm/src/witness/gas.rs @@ -45,6 +45,7 @@ pub(crate) fn gas_to_charge(op: Operation) -> u64 { GetContext => KERNEL_ONLY_INSTR, SetContext => KERNEL_ONLY_INSTR, Mload32Bytes => KERNEL_ONLY_INSTR, + Mstore32Bytes => KERNEL_ONLY_INSTR, ExitKernel => KERNEL_ONLY_INSTR, MloadGeneral => KERNEL_ONLY_INSTR, MstoreGeneral => KERNEL_ONLY_INSTR, diff --git a/evm/src/witness/operation.rs b/evm/src/witness/operation.rs index 2104b53e24..ee0be6d8e0 100644 --- a/evm/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -3,7 +3,7 @@ use itertools::Itertools; use keccak_hash::keccak; use plonky2::field::types::Field; -use super::util::byte_packing_log; +use super::util::{byte_packing_log, byte_unpacking_log}; use crate::arithmetic::BinaryOperator; use crate::cpu::columns::CpuColumnsView; use crate::cpu::kernel::aggregator::KERNEL; @@ -49,6 +49,7 @@ pub(crate) enum Operation { GetContext, SetContext, Mload32Bytes, + Mstore32Bytes, ExitKernel, MloadGeneral, MstoreGeneral, @@ -751,6 +752,27 @@ pub(crate) fn generate_mstore_general( Ok(()) } +pub(crate) fn generate_mstore_32bytes( + state: &mut GenerationState, + mut row: CpuColumnsView, +) -> Result<(), ProgramError> { + let [(context, log_in0), (segment, log_in1), (base_virt, log_in2), (val, log_in3), (len, log_in4)] = + stack_pop_with_log_and_fill::<5, _>(state, &mut row)?; + let len = len.as_usize(); + + let base_address = MemoryAddress::new_u256s(context, segment, base_virt)?; + + byte_unpacking_log(state, base_address, val, len); + + state.traces.push_memory(log_in0); + state.traces.push_memory(log_in1); + state.traces.push_memory(log_in2); + state.traces.push_memory(log_in3); + state.traces.push_memory(log_in4); + state.traces.push_cpu(row); + Ok(()) +} + pub(crate) fn generate_exception( exc_code: u8, state: &mut GenerationState, diff --git a/evm/src/witness/transition.rs b/evm/src/witness/transition.rs index 6f2b1e9cbc..2989f30504 100644 --- a/evm/src/witness/transition.rs +++ b/evm/src/witness/transition.rs @@ -128,6 +128,7 @@ fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Mstore32Bytes), (0xf0, _) => Ok(Operation::Syscall(opcode, 3, false)), // CREATE (0xf1, _) => Ok(Operation::Syscall(opcode, 7, false)), // CALL (0xf2, _) => Ok(Operation::Syscall(opcode, 7, false)), // CALLCODE @@ -185,6 +186,7 @@ fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) { Operation::Jumpdest => &mut flags.jumpdest, Operation::GetContext | Operation::SetContext => &mut flags.context_op, Operation::Mload32Bytes => &mut flags.mload_32bytes, + Operation::Mstore32Bytes => &mut flags.mstore_32bytes, Operation::ExitKernel => &mut flags.exit_kernel, Operation::MloadGeneral => &mut flags.mload_general, Operation::MstoreGeneral => &mut flags.mstore_general, @@ -223,6 +225,7 @@ fn perform_op( Operation::GetContext => generate_get_context(state, row)?, Operation::SetContext => generate_set_context(state, row)?, Operation::Mload32Bytes => generate_mload_32bytes(state, row)?, + Operation::Mstore32Bytes => generate_mstore_32bytes(state, row)?, Operation::ExitKernel => generate_exit_kernel(state, row)?, Operation::MloadGeneral => generate_mload_general(state, row)?, Operation::MstoreGeneral => generate_mstore_general(state, row)?, diff --git a/evm/src/witness/util.rs b/evm/src/witness/util.rs index 6f7f3f7518..0c659f7ac8 100644 --- a/evm/src/witness/util.rs +++ b/evm/src/witness/util.rs @@ -280,6 +280,40 @@ pub(crate) fn byte_packing_log( } state.traces.push_byte_packing(BytePackingOp { + is_read: true, + base_address, + timestamp: clock * NUM_CHANNELS, + bytes, + }); +} + +pub(crate) fn byte_unpacking_log( + state: &mut GenerationState, + base_address: MemoryAddress, + val: U256, + len: usize, +) { + let clock = state.traces.clock(); + + let mut bytes = vec![0; 32]; + val.to_little_endian(&mut bytes); + bytes.resize(len, 0); + bytes.reverse(); + + let mut address = base_address; + for &byte in &bytes { + state.traces.push_memory(MemoryOp::new( + MemoryChannel::Code, + clock, + address, + MemoryOpKind::Write, + byte.into(), + )); + address.increment(); + } + + state.traces.push_byte_packing(BytePackingOp { + is_read: false, base_address, timestamp: clock * NUM_CHANNELS, bytes, From 2aa699a40fb0d4ad1a924aa44765bbdb5ad54714 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 5 Sep 2023 17:04:49 -0400 Subject: [PATCH 26/40] Add missing constraint --- evm/src/byte_packing/byte_packing_stark.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 752c64bb56..6fd45eab09 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -225,6 +225,10 @@ impl, const D: usize> Stark for BytePackingSt let sequence_end = vars.local_values[SEQUENCE_END]; yield_constr.constraint(sequence_end * (sequence_end - one)); + // Unless the current sequence end flag is activated, the is_read filter must remain unchanged. + let next_is_read = vars.next_values[IS_READ]; + yield_constr.constraint_transition((sequence_end - one) * (next_is_read - is_read)); + // If the sequence end flag is activated, the next row must be a new sequence or filter must be off. let next_sequence_start = vars.next_values[index_bytes(0)]; yield_constr @@ -337,6 +341,12 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_sub_extension(sequence_end, sequence_end, sequence_end); yield_constr.constraint(builder, constraint); + // Unless the current sequence end flag is activated, the is_read filter must remain unchanged. + let next_is_read = vars.next_values[IS_READ]; + let diff_is_read = builder.sub_extension(next_is_read, is_read); + let constraint = builder.mul_sub_extension(diff_is_read, sequence_end, diff_is_read); + yield_constr.constraint_transition(builder, constraint); + // If the sequence end flag is activated, the next row must be a new sequence or filter must be off. let next_sequence_start = vars.next_values[index_bytes(0)]; let constraint = builder.mul_sub_extension(sequence_end, next_sequence_start, sequence_end); From 79a7a1c2f1eef903e153361667dc85c4155eb6ab Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 5 Sep 2023 17:51:09 -0400 Subject: [PATCH 27/40] Add range-check for all bytes --- evm/src/byte_packing/byte_packing_stark.rs | 64 +++++++++++++++++----- evm/src/byte_packing/columns.rs | 13 ++++- evm/tests/empty_txn_list.rs | 2 +- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 6fd45eab09..853bea570e 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -8,19 +8,22 @@ use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; +use plonky2::util::transpose; -use super::columns::{ - index_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, IS_READ, SEQUENCE_END, TIMESTAMP, -}; use super::NUM_BYTES; -use crate::byte_packing::columns::{value_bytes, FILTER, NUM_COLUMNS, REMAINING_LEN, SEQUENCE_LEN}; +use crate::byte_packing::columns::{ + index_bytes, value_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, FILTER, IS_READ, + NUM_COLUMNS, RANGE_COUNTER, RC_COLS, REMAINING_LEN, SEQUENCE_END, SEQUENCE_LEN, TIMESTAMP, +}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; +use crate::lookup::{eval_lookups, eval_lookups_circuit, permuted_cols}; use crate::stark::Stark; -use crate::util::trace_rows_to_poly_values; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; use crate::witness::memory::MemoryAddress; +const BYTE_RANGE_MAX: usize = 1usize << 8; + pub(crate) fn ctl_looked_data() -> Vec> { let outputs: Vec> = (0..8) .map(|i| { @@ -106,14 +109,12 @@ impl, const D: usize> BytePackingStark { "generate trace rows", self.generate_trace_rows(ops, min_rows) ); + let trace_row_vecs: Vec<_> = trace_rows.into_iter().map(|row| row.to_vec()).collect(); - let trace_polys = timed!( - timing, - "convert to PolynomialValues", - trace_rows_to_poly_values(trace_rows) - ); + let mut trace_cols = transpose(&trace_row_vecs); + self.generate_range_checks(&mut trace_cols); - trace_polys + trace_cols.into_iter().map(PolynomialValues::new).collect() } fn generate_trace_rows( @@ -122,14 +123,14 @@ impl, const D: usize> BytePackingStark { min_rows: usize, ) -> Vec<[F; NUM_COLUMNS]> { let base_len: usize = ops.iter().map(|op| op.bytes.len()).sum(); - let mut rows = Vec::with_capacity(base_len.max(min_rows).next_power_of_two()); + let num_rows = core::cmp::max(base_len.max(BYTE_RANGE_MAX), min_rows).next_power_of_two(); + let mut rows = Vec::with_capacity(num_rows); for op in ops { rows.extend(self.generate_rows_for_op(op)); } - let padded_rows = rows.len().max(min_rows).next_power_of_two(); - for _ in rows.len()..padded_rows { + for _ in rows.len()..num_rows { rows.push(self.generate_padding_row()); } @@ -183,6 +184,31 @@ impl, const D: usize> BytePackingStark { fn generate_padding_row(&self) -> [F; NUM_COLUMNS] { [F::ZERO; NUM_COLUMNS] } + + /// Expects input in *column*-major layout + fn generate_range_checks(&self, cols: &mut Vec>) { + debug_assert!(cols.len() == NUM_COLUMNS); + + let n_rows = cols[0].len(); + debug_assert!(cols.iter().all(|col| col.len() == n_rows)); + + for i in 0..BYTE_RANGE_MAX { + cols[RANGE_COUNTER][i] = F::from_canonical_usize(i); + } + for i in BYTE_RANGE_MAX..n_rows { + cols[RANGE_COUNTER][i] = F::from_canonical_usize(BYTE_RANGE_MAX - 1); + } + + // For each column c in cols, generate the range-check + // permutations and put them in the corresponding range-check + // columns rc_c and rc_c+1. + for (i, rc_c) in (0..NUM_BYTES).zip(RC_COLS.step_by(2)) { + let c = value_bytes(i); + let (col_perm, table_perm) = permuted_cols(&cols[c], &cols[RANGE_COUNTER]); + cols[rc_c].copy_from_slice(&col_perm); + cols[rc_c + 1].copy_from_slice(&table_perm); + } + } } impl, const D: usize> Stark for BytePackingStark { @@ -196,6 +222,11 @@ impl, const D: usize> Stark for BytePackingSt FE: FieldExtension, P: PackedField, { + // Range check all the columns + for col in RC_COLS.step_by(2) { + eval_lookups(vars, yield_constr, col, col + 1); + } + let one = P::ONES; // The filter must be boolean. @@ -307,6 +338,11 @@ impl, const D: usize> Stark for BytePackingSt vars: StarkEvaluationTargets, yield_constr: &mut RecursiveConstraintConsumer, ) { + // Range check all the columns + for col in RC_COLS.step_by(2) { + eval_lookups_circuit(builder, vars, yield_constr, col, col + 1); + } + // The filter must be boolean. let filter = vars.local_values[FILTER]; let constraint = builder.mul_sub_extension(filter, filter, filter); diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 7b8efab3f5..36cfeb4c06 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -1,5 +1,7 @@ //! Byte packing registers. +use core::ops::Range; + use crate::byte_packing::NUM_BYTES; /// 1 if this is an actual byte packing operation, or 0 if it's a padding row. @@ -39,4 +41,13 @@ pub(crate) const fn value_bytes(i: usize) -> usize { BYTES_START + i } -pub(crate) const NUM_COLUMNS: usize = BYTES_START + NUM_BYTES; +// We need one column for the table, then two columns for every value +// that needs to be range checked in the trace (all written bytes), +// namely the permutation of the column and the permutation of the range. +// The two permutations associated to the byte in column i will be in +// columns RC_COLS[2i] and RC_COLS[2i+1]. +pub(crate) const RANGE_COUNTER: usize = BYTES_START + NUM_BYTES; +pub(crate) const NUM_RANGE_CHECK_COLS: usize = 1 + 2 * NUM_BYTES; +pub(crate) const RC_COLS: Range = RANGE_COUNTER + 1..RANGE_COUNTER + NUM_RANGE_CHECK_COLS; + +pub(crate) const NUM_COLUMNS: usize = RANGE_COUNTER + NUM_RANGE_CHECK_COLS; diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs index ba2717bda9..cb59d96b8b 100644 --- a/evm/tests/empty_txn_list.rs +++ b/evm/tests/empty_txn_list.rs @@ -67,7 +67,7 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 15..16, 14..15, 9..10, 12..13, 18..19, 4..5], // Minimal ranges to prove an empty list + &[16..17, 15..16, 14..15, 9..10, 12..13, 18..19, 8..9], // Minimal ranges to prove an empty list &config, ); From d10b75b571939d54850fd37ea3573d913cf0b36d Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 08:37:37 -0400 Subject: [PATCH 28/40] Add extra constraint --- evm/src/byte_packing/byte_packing_stark.rs | 64 ++++++++++++---------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 853bea570e..37a70354fd 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -240,13 +240,11 @@ impl, const D: usize> Stark for BytePackingSt let is_read = vars.local_values[IS_READ]; yield_constr.constraint(is_read * (is_read - one)); - // If filter is off, is_read must be off. - let is_read = vars.local_values[IS_READ]; - yield_constr.constraint((filter - one) * is_read); - - // Only padding rows have their filter turned off. - let next_filter = vars.next_values[FILTER]; - yield_constr.constraint_transition(next_filter * (next_filter - filter)); + // Each byte index must be boolean. + for i in 0..NUM_BYTES { + let idx_i = vars.local_values[index_bytes(i)]; + yield_constr.constraint(idx_i * (idx_i - one)); + } // The sequence start flag column must start by one. let sequence_start = vars.local_values[index_bytes(0)]; @@ -256,6 +254,16 @@ impl, const D: usize> Stark for BytePackingSt let sequence_end = vars.local_values[SEQUENCE_END]; yield_constr.constraint(sequence_end * (sequence_end - one)); + // If filter is off, all flags and byte indices must be off. + let byte_indices = (0..NUM_BYTES) + .map(|i| vars.local_values[index_bytes(i)]) + .sum::

(); + yield_constr.constraint((filter - one) * (is_read + sequence_end + byte_indices)); + + // Only padding rows have their filter turned off. + let next_filter = vars.next_values[FILTER]; + yield_constr.constraint_transition(next_filter * (next_filter - filter)); + // Unless the current sequence end flag is activated, the is_read filter must remain unchanged. let next_is_read = vars.next_values[IS_READ]; yield_constr.constraint_transition((sequence_end - one) * (next_is_read - is_read)); @@ -265,12 +273,6 @@ impl, const D: usize> Stark for BytePackingSt yield_constr .constraint_transition(sequence_end * next_filter * (next_sequence_start - one)); - // Each byte index must be boolean. - for i in 0..NUM_BYTES { - let idx_i = vars.local_values[index_bytes(i)]; - yield_constr.constraint(idx_i * (idx_i - one)); - } - // There must be only one byte index set to 1 per active row. let sum_indices = vars.local_values[index_bytes(0)..index_bytes(0) + NUM_BYTES] .iter() @@ -357,15 +359,12 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_sub_extension(is_read, is_read, is_read); yield_constr.constraint(builder, constraint); - // If filter is off, is_read must be off. - let constraint = builder.mul_sub_extension(is_read, filter, is_read); - yield_constr.constraint(builder, constraint); - - // Only padding rows have their filter turned off. - let next_filter = vars.next_values[FILTER]; - let constraint = builder.sub_extension(next_filter, filter); - let constraint = builder.mul_extension(next_filter, constraint); - yield_constr.constraint_transition(builder, constraint); + // Each byte index must be boolean. + for i in 0..NUM_BYTES { + let idx_i = vars.local_values[index_bytes(i)]; + let constraint = builder.mul_sub_extension(idx_i, idx_i, idx_i); + yield_constr.constraint(builder, constraint); + } // The sequence start flag column must start by one. let sequence_start = vars.local_values[index_bytes(0)]; @@ -377,6 +376,20 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_sub_extension(sequence_end, sequence_end, sequence_end); yield_constr.constraint(builder, constraint); + // If filter is off, all flags and byte indices must be off. + let byte_indices = + builder.add_many_extension((0..NUM_BYTES).map(|i| vars.local_values[index_bytes(i)])); + let constraint = builder.add_extension(sequence_end, byte_indices); + let constraint = builder.add_extension(constraint, is_read); + let constraint = builder.mul_sub_extension(constraint, filter, constraint); + yield_constr.constraint(builder, constraint); + + // Only padding rows have their filter turned off. + let next_filter = vars.next_values[FILTER]; + let constraint = builder.sub_extension(next_filter, filter); + let constraint = builder.mul_extension(next_filter, constraint); + yield_constr.constraint_transition(builder, constraint); + // Unless the current sequence end flag is activated, the is_read filter must remain unchanged. let next_is_read = vars.next_values[IS_READ]; let diff_is_read = builder.sub_extension(next_is_read, is_read); @@ -389,13 +402,6 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_extension(next_filter, constraint); yield_constr.constraint_transition(builder, constraint); - // Each byte index must be boolean. - for i in 0..NUM_BYTES { - let idx_i = vars.local_values[index_bytes(i)]; - let constraint = builder.mul_sub_extension(idx_i, idx_i, idx_i); - yield_constr.constraint(builder, constraint); - } - // There must be only one byte index set to 1 per active row. let sum_indices = builder.add_many_extension( vars.local_values[index_bytes(0)..index_bytes(0) + NUM_BYTES].into_iter(), From 6c6594905d646258bc7c3fd776b015ec2aa3f7bb Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 08:56:04 -0400 Subject: [PATCH 29/40] Cleanup --- evm/src/byte_packing/byte_packing_stark.rs | 123 ++++++++++++--------- evm/src/byte_packing/columns.rs | 9 +- evm/src/byte_packing/mod.rs | 5 + 3 files changed, 81 insertions(+), 56 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 37a70354fd..ae994fcbb1 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -10,6 +10,7 @@ use plonky2::timed; use plonky2::util::timing::TimingTree; use plonky2::util::transpose; +use super::columns::BYTE_INDICES_COLS; use super::NUM_BYTES; use crate::byte_packing::columns::{ index_bytes, value_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, FILTER, IS_READ, @@ -58,7 +59,7 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { let mut res = Column::singles([IS_READ, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL]).collect_vec(); - // The i'th input byte being read. + // The i'th input byte being read/written. res.push(Column::single(value_bytes(i))); // Since we're reading a single byte, the higher limbs must be zero. @@ -69,7 +70,7 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { res } -/// CTL filter for reading the `i`th byte of the byte sequence from memory. +/// CTL filter for reading/writing the `i`th byte of the byte sequence from/to memory. pub(crate) fn ctl_looking_memory_filter(i: usize) -> Column { Column::single(index_bytes(i)) } @@ -159,7 +160,8 @@ impl, const D: usize> BytePackingStark { row[ADDR_CONTEXT] = F::from_canonical_usize(context); row[ADDR_SEGMENT] = F::from_canonical_usize(segment); // Because of the endianness, we start by the final virtual address value - // and decrement it at each step. + // and decrement it at each step. Similarly, we process the byte sequence + // in reverse order. row[ADDR_VIRTUAL] = F::from_canonical_usize(virt + bytes.len() - 1); row[TIMESTAMP] = F::from_canonical_usize(timestamp); @@ -230,15 +232,15 @@ impl, const D: usize> Stark for BytePackingSt let one = P::ONES; // The filter must be boolean. - let filter = vars.local_values[FILTER]; - yield_constr.constraint(filter * (filter - one)); + let current_filter = vars.local_values[FILTER]; + yield_constr.constraint(current_filter * (current_filter - one)); // The filter column must start by one. - yield_constr.constraint_first_row(filter - one); + yield_constr.constraint_first_row(current_filter - one); // The is_read flag must be boolean. - let is_read = vars.local_values[IS_READ]; - yield_constr.constraint(is_read * (is_read - one)); + let current_is_read = vars.local_values[IS_READ]; + yield_constr.constraint(current_is_read * (current_is_read - one)); // Each byte index must be boolean. for i in 0..NUM_BYTES { @@ -247,38 +249,43 @@ impl, const D: usize> Stark for BytePackingSt } // The sequence start flag column must start by one. - let sequence_start = vars.local_values[index_bytes(0)]; - yield_constr.constraint_first_row(sequence_start - one); + let current_sequence_start = vars.local_values[index_bytes(0)]; + yield_constr.constraint_first_row(current_sequence_start - one); // The sequence end flag must be boolean - let sequence_end = vars.local_values[SEQUENCE_END]; - yield_constr.constraint(sequence_end * (sequence_end - one)); + let current_sequence_end = vars.local_values[SEQUENCE_END]; + yield_constr.constraint(current_sequence_end * (current_sequence_end - one)); // If filter is off, all flags and byte indices must be off. - let byte_indices = (0..NUM_BYTES) - .map(|i| vars.local_values[index_bytes(i)]) + let byte_indices = vars.local_values[BYTE_INDICES_COLS] + .iter() + .copied() .sum::

(); - yield_constr.constraint((filter - one) * (is_read + sequence_end + byte_indices)); + yield_constr.constraint( + (current_filter - one) * (current_is_read + current_sequence_end + byte_indices), + ); // Only padding rows have their filter turned off. let next_filter = vars.next_values[FILTER]; - yield_constr.constraint_transition(next_filter * (next_filter - filter)); + yield_constr.constraint_transition(next_filter * (next_filter - current_filter)); // Unless the current sequence end flag is activated, the is_read filter must remain unchanged. let next_is_read = vars.next_values[IS_READ]; - yield_constr.constraint_transition((sequence_end - one) * (next_is_read - is_read)); + yield_constr + .constraint_transition((current_sequence_end - one) * (next_is_read - current_is_read)); // If the sequence end flag is activated, the next row must be a new sequence or filter must be off. let next_sequence_start = vars.next_values[index_bytes(0)]; - yield_constr - .constraint_transition(sequence_end * next_filter * (next_sequence_start - one)); + yield_constr.constraint_transition( + current_sequence_end * next_filter * (next_sequence_start - one), + ); // There must be only one byte index set to 1 per active row. - let sum_indices = vars.local_values[index_bytes(0)..index_bytes(0) + NUM_BYTES] + let sum_indices = vars.local_values[BYTE_INDICES_COLS] .iter() .copied() .sum::

(); - yield_constr.constraint(filter * (sum_indices - P::ONES)); + yield_constr.constraint(current_filter * (sum_indices - P::ONES)); // The remaining length of a byte sequence must decrease by one or be zero. let current_remaining_length = vars.local_values[REMAINING_LEN]; @@ -288,15 +295,16 @@ impl, const D: usize> Stark for BytePackingSt ); // At the start of a sequence, the remaining length must be equal to the starting length minus one - let sequence_length = vars.local_values[SEQUENCE_LEN]; - yield_constr - .constraint(sequence_start * (sequence_length - current_remaining_length - one)); + let current_sequence_length = vars.local_values[SEQUENCE_LEN]; + yield_constr.constraint( + current_sequence_start * (current_sequence_length - current_remaining_length - one), + ); // The remaining length on the last row must be zero. yield_constr.constraint_last_row(current_remaining_length); // If the current remaining length is zero, the end flag must be one. - yield_constr.constraint(current_remaining_length * sequence_end); + yield_constr.constraint(current_remaining_length * current_sequence_end); // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. // The virtual address must decrement by one at each step of a sequence. @@ -329,7 +337,7 @@ impl, const D: usize> Stark for BytePackingSt let next_byte = vars.next_values[value_bytes(i)]; let next_byte_index = vars.next_values[index_bytes(i)]; yield_constr.constraint_transition( - (sequence_end - one) * (next_byte_index - one) * (next_byte - current_byte), + (current_sequence_end - one) * (next_byte_index - one) * (next_byte - current_byte), ); } } @@ -346,17 +354,18 @@ impl, const D: usize> Stark for BytePackingSt } // The filter must be boolean. - let filter = vars.local_values[FILTER]; - let constraint = builder.mul_sub_extension(filter, filter, filter); + let current_filter = vars.local_values[FILTER]; + let constraint = builder.mul_sub_extension(current_filter, current_filter, current_filter); yield_constr.constraint(builder, constraint); // The filter column must start by one. - let constraint = builder.add_const_extension(filter, F::NEG_ONE); + let constraint = builder.add_const_extension(current_filter, F::NEG_ONE); yield_constr.constraint_first_row(builder, constraint); // The is_read flag must be boolean. - let is_read = vars.local_values[IS_READ]; - let constraint = builder.mul_sub_extension(is_read, is_read, is_read); + let current_is_read = vars.local_values[IS_READ]; + let constraint = + builder.mul_sub_extension(current_is_read, current_is_read, current_is_read); yield_constr.constraint(builder, constraint); // Each byte index must be boolean. @@ -367,46 +376,52 @@ impl, const D: usize> Stark for BytePackingSt } // The sequence start flag column must start by one. - let sequence_start = vars.local_values[index_bytes(0)]; - let constraint = builder.add_const_extension(sequence_start, F::NEG_ONE); + let current_sequence_start = vars.local_values[index_bytes(0)]; + let constraint = builder.add_const_extension(current_sequence_start, F::NEG_ONE); yield_constr.constraint_first_row(builder, constraint); // The sequence end flag must be boolean - let sequence_end = vars.local_values[SEQUENCE_END]; - let constraint = builder.mul_sub_extension(sequence_end, sequence_end, sequence_end); + let current_sequence_end = vars.local_values[SEQUENCE_END]; + let constraint = builder.mul_sub_extension( + current_sequence_end, + current_sequence_end, + current_sequence_end, + ); yield_constr.constraint(builder, constraint); // If filter is off, all flags and byte indices must be off. - let byte_indices = - builder.add_many_extension((0..NUM_BYTES).map(|i| vars.local_values[index_bytes(i)])); - let constraint = builder.add_extension(sequence_end, byte_indices); - let constraint = builder.add_extension(constraint, is_read); - let constraint = builder.mul_sub_extension(constraint, filter, constraint); + let byte_indices = builder.add_many_extension(&vars.local_values[BYTE_INDICES_COLS]); + let constraint = builder.add_extension(current_sequence_end, byte_indices); + let constraint = builder.add_extension(constraint, current_is_read); + let constraint = builder.mul_sub_extension(constraint, current_filter, constraint); yield_constr.constraint(builder, constraint); // Only padding rows have their filter turned off. let next_filter = vars.next_values[FILTER]; - let constraint = builder.sub_extension(next_filter, filter); + let constraint = builder.sub_extension(next_filter, current_filter); let constraint = builder.mul_extension(next_filter, constraint); yield_constr.constraint_transition(builder, constraint); // Unless the current sequence end flag is activated, the is_read filter must remain unchanged. let next_is_read = vars.next_values[IS_READ]; - let diff_is_read = builder.sub_extension(next_is_read, is_read); - let constraint = builder.mul_sub_extension(diff_is_read, sequence_end, diff_is_read); + let diff_is_read = builder.sub_extension(next_is_read, current_is_read); + let constraint = + builder.mul_sub_extension(diff_is_read, current_sequence_end, diff_is_read); yield_constr.constraint_transition(builder, constraint); // If the sequence end flag is activated, the next row must be a new sequence or filter must be off. let next_sequence_start = vars.next_values[index_bytes(0)]; - let constraint = builder.mul_sub_extension(sequence_end, next_sequence_start, sequence_end); + let constraint = builder.mul_sub_extension( + current_sequence_end, + next_sequence_start, + current_sequence_end, + ); let constraint = builder.mul_extension(next_filter, constraint); yield_constr.constraint_transition(builder, constraint); // There must be only one byte index set to 1 per active row. - let sum_indices = builder.add_many_extension( - vars.local_values[index_bytes(0)..index_bytes(0) + NUM_BYTES].into_iter(), - ); - let constraint = builder.mul_sub_extension(filter, sum_indices, filter); + let sum_indices = builder.add_many_extension(&vars.local_values[BYTE_INDICES_COLS]); + let constraint = builder.mul_sub_extension(current_filter, sum_indices, current_filter); yield_constr.constraint(builder, constraint); // The remaining length of a byte sequence must decrease by one or be zero. @@ -418,16 +433,17 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint_transition(builder, constraint); // At the start of a sequence, the remaining length must be equal to the starting length minus one - let sequence_length = vars.local_values[SEQUENCE_LEN]; - let length_diff = builder.sub_extension(sequence_length, current_remaining_length); - let constraint = builder.mul_sub_extension(sequence_start, length_diff, sequence_start); + let current_sequence_length = vars.local_values[SEQUENCE_LEN]; + let length_diff = builder.sub_extension(current_sequence_length, current_remaining_length); + let constraint = + builder.mul_sub_extension(current_sequence_start, length_diff, current_sequence_start); yield_constr.constraint(builder, constraint); // The remaining length on the last row must be zero. yield_constr.constraint_last_row(builder, current_remaining_length); // If the current remaining length is zero, the end flag must be one. - let constraint = builder.mul_extension(current_remaining_length, sequence_end); + let constraint = builder.mul_extension(current_remaining_length, current_sequence_end); yield_constr.constraint(builder, constraint); // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. @@ -471,7 +487,8 @@ impl, const D: usize> Stark for BytePackingSt let next_byte_index = vars.next_values[index_bytes(i)]; let byte_diff = builder.sub_extension(next_byte, current_byte); let constraint = builder.mul_sub_extension(byte_diff, next_byte_index, byte_diff); - let constraint = builder.mul_sub_extension(constraint, sequence_end, constraint); + let constraint = + builder.mul_sub_extension(constraint, current_sequence_end, constraint); yield_constr.constraint_transition(builder, constraint); } } diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 36cfeb4c06..e83c4ae3a6 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -20,6 +20,9 @@ pub(crate) const fn index_bytes(i: usize) -> usize { BYTES_INDICES_START + i } +pub(crate) const BYTE_INDICES_COLS: Range = + BYTES_INDICES_START..BYTES_INDICES_START + NUM_BYTES; + pub(crate) const ADDR_CONTEXT: usize = BYTES_INDICES_START + NUM_BYTES; pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; @@ -35,10 +38,10 @@ pub(crate) const SEQUENCE_LEN: usize = TIMESTAMP + 1; pub(crate) const REMAINING_LEN: usize = SEQUENCE_LEN + 1; // 32 byte limbs hold a total of 256 bits. -const BYTES_START: usize = REMAINING_LEN + 1; +const BYTES_VALUES_START: usize = REMAINING_LEN + 1; pub(crate) const fn value_bytes(i: usize) -> usize { debug_assert!(i < NUM_BYTES); - BYTES_START + i + BYTES_VALUES_START + i } // We need one column for the table, then two columns for every value @@ -46,7 +49,7 @@ pub(crate) const fn value_bytes(i: usize) -> usize { // namely the permutation of the column and the permutation of the range. // The two permutations associated to the byte in column i will be in // columns RC_COLS[2i] and RC_COLS[2i+1]. -pub(crate) const RANGE_COUNTER: usize = BYTES_START + NUM_BYTES; +pub(crate) const RANGE_COUNTER: usize = BYTES_VALUES_START + NUM_BYTES; pub(crate) const NUM_RANGE_CHECK_COLS: usize = 1 + 2 * NUM_BYTES; pub(crate) const RC_COLS: Range = RANGE_COUNTER + 1..RANGE_COUNTER + NUM_RANGE_CHECK_COLS; diff --git a/evm/src/byte_packing/mod.rs b/evm/src/byte_packing/mod.rs index bbe9257e1a..7cc93374ca 100644 --- a/evm/src/byte_packing/mod.rs +++ b/evm/src/byte_packing/mod.rs @@ -1,3 +1,8 @@ +//! Byte packing / unpacking unit for the EVM. +//! +//! This module handles reading / writing to memory byte sequences of +//! length at most 32 in Big-Endian ordering. + pub mod byte_packing_stark; pub mod columns; From 3128c0e947414785f89c9073bd80939efb352c2d Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 09:15:27 -0400 Subject: [PATCH 30/40] Remove REMAINING_LEN column --- evm/src/byte_packing/byte_packing_stark.rs | 41 ++++++++++++++++------ evm/src/byte_packing/columns.rs | 13 ++----- evm/src/witness/traces.rs | 4 +-- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index ae994fcbb1..d550877360 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -10,11 +10,10 @@ use plonky2::timed; use plonky2::util::timing::TimingTree; use plonky2::util::transpose; -use super::columns::BYTE_INDICES_COLS; use super::NUM_BYTES; use crate::byte_packing::columns::{ - index_bytes, value_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, FILTER, IS_READ, - NUM_COLUMNS, RANGE_COUNTER, RC_COLS, REMAINING_LEN, SEQUENCE_END, SEQUENCE_LEN, TIMESTAMP, + index_bytes, value_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, BYTE_INDICES_COLS, FILTER, + IS_READ, NUM_COLUMNS, RANGE_COUNTER, RC_COLS, SEQUENCE_END, SEQUENCE_LEN, TIMESTAMP, }; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; @@ -33,7 +32,7 @@ pub(crate) fn ctl_looked_data() -> Vec> { range .iter() .enumerate() - .map(|(j, &c)| (c, F::from_canonical_u64(1 << 8 * j))), + .map(|(j, &c)| (c, F::from_canonical_u64(1 << (8 * j)))), ) }) .collect(); @@ -168,7 +167,6 @@ impl, const D: usize> BytePackingStark { row[SEQUENCE_LEN] = F::from_canonical_usize(bytes.len()); for (i, &byte) in bytes.iter().rev().enumerate() { - row[REMAINING_LEN] = F::from_canonical_usize(bytes.len() - 1 - i); if i == bytes.len() - 1 { row[SEQUENCE_END] = F::ONE; } @@ -288,14 +286,21 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(current_filter * (sum_indices - P::ONES)); // The remaining length of a byte sequence must decrease by one or be zero. - let current_remaining_length = vars.local_values[REMAINING_LEN]; - let next_remaining_length = vars.next_values[REMAINING_LEN]; + let current_sequence_length = vars.local_values[SEQUENCE_LEN]; + let current_remaining_length = current_sequence_length + - (0..NUM_BYTES) + .map(|i| vars.local_values[index_bytes(i)] * P::Scalar::from_canonical_usize(i + 1)) + .sum::

(); + let next_sequence_length = vars.next_values[SEQUENCE_LEN]; + let next_remaining_length = next_sequence_length + - (0..NUM_BYTES) + .map(|i| vars.next_values[index_bytes(i)] * P::Scalar::from_canonical_usize(i + 1)) + .sum::

(); yield_constr.constraint_transition( current_remaining_length * (current_remaining_length - next_remaining_length - one), ); // At the start of a sequence, the remaining length must be equal to the starting length minus one - let current_sequence_length = vars.local_values[SEQUENCE_LEN]; yield_constr.constraint( current_sequence_start * (current_sequence_length - current_remaining_length - one), ); @@ -425,8 +430,24 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(builder, constraint); // The remaining length of a byte sequence must decrease by one or be zero. - let current_remaining_length = vars.local_values[REMAINING_LEN]; - let next_remaining_length = vars.next_values[REMAINING_LEN]; + let current_sequence_length = vars.local_values[SEQUENCE_LEN]; + let mut current_remaining_length = + builder.sub_extension(current_sequence_length, vars.local_values[index_bytes(0)]); + let next_sequence_length = vars.next_values[SEQUENCE_LEN]; + let mut next_remaining_length = + builder.sub_extension(next_sequence_length, vars.next_values[index_bytes(0)]); + for i in 1..NUM_BYTES { + current_remaining_length = builder.mul_const_add_extension( + F::from_canonical_usize(i + 1), + vars.local_values[index_bytes(i)], + current_remaining_length, + ); + next_remaining_length = builder.mul_const_add_extension( + F::from_canonical_usize(i + 1), + vars.next_values[index_bytes(i)], + next_remaining_length, + ); + } let length_diff = builder.sub_extension(current_remaining_length, next_remaining_length); let length_diff_minus_one = builder.add_const_extension(length_diff, F::NEG_ONE); let constraint = builder.mul_extension(current_remaining_length, length_diff_minus_one); diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index e83c4ae3a6..36a4f78ad7 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -10,8 +10,6 @@ pub(crate) const FILTER: usize = 0; pub(crate) const IS_READ: usize = FILTER + 1; /// 1 if this is the end of a sequence of bytes. /// This is also used as filter for the CTL. -// TODO: We should be able to remove this by leveraging `SEQUENCE_LEN` and the -// byte indices for the CTL filter. pub(crate) const SEQUENCE_END: usize = IS_READ + 1; pub(super) const BYTES_INDICES_START: usize = SEQUENCE_END + 1; @@ -28,17 +26,12 @@ pub(crate) const ADDR_SEGMENT: usize = ADDR_CONTEXT + 1; pub(crate) const ADDR_VIRTUAL: usize = ADDR_SEGMENT + 1; pub(crate) const TIMESTAMP: usize = ADDR_VIRTUAL + 1; -/// The total length of this pack of bytes. -/// Expected to not be greater than 32. +/// The total length of a sequence of bytes. +/// Cannot be greater than 32. pub(crate) const SEQUENCE_LEN: usize = TIMESTAMP + 1; -/// The remaining length of this pack of bytes. -/// Expected to not be greater than 32. -// TODO: We should be able to remove this by leveraging `SEQUENCE_LEN` and the -// byte indices. -pub(crate) const REMAINING_LEN: usize = SEQUENCE_LEN + 1; // 32 byte limbs hold a total of 256 bits. -const BYTES_VALUES_START: usize = REMAINING_LEN + 1; +const BYTES_VALUES_START: usize = SEQUENCE_LEN + 1; pub(crate) const fn value_bytes(i: usize) -> usize { debug_assert!(i < NUM_BYTES); BYTES_VALUES_START + i diff --git a/evm/src/witness/traces.rs b/evm/src/witness/traces.rs index 42b5da18cb..e888c8fd68 100644 --- a/evm/src/witness/traces.rs +++ b/evm/src/witness/traces.rs @@ -173,9 +173,7 @@ impl Traces { let memory_trace = timed!( timing, "generate memory trace", - all_stark - .memory_stark - .generate_trace(memory_ops.clone(), timing) + all_stark.memory_stark.generate_trace(memory_ops, timing) ); let byte_packing_trace = timed!( timing, From ef420a35176af1c2734996e1288d6bc14b27fe38 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 10:56:44 -0400 Subject: [PATCH 31/40] Add corresponding implementations in interpreter --- evm/src/cpu/kernel/interpreter.rs | 43 ++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 9980fed0d9..6039c51505 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -391,6 +391,7 @@ impl<'a> Interpreter<'a> { self.stack(), self.get_kernel_general_memory() ), // "PANIC", + 0xee => self.run_mstore_32bytes(), // "MSTORE_32BYTES", 0xf0 => todo!(), // "CREATE", 0xf1 => todo!(), // "CALL", 0xf2 => todo!(), // "CALLCODE", @@ -399,7 +400,7 @@ impl<'a> Interpreter<'a> { 0xf5 => todo!(), // "CREATE2", 0xf6 => self.run_get_context(), // "GET_CONTEXT", 0xf7 => self.run_set_context(), // "SET_CONTEXT", - 0xf8 => todo!(), // "MLOAD_32BYTES", + 0xf8 => self.run_mload_32bytes(), // "MLOAD_32BYTES", 0xf9 => todo!(), // "EXIT_KERNEL", 0xfa => todo!(), // "STATICCALL", 0xfb => self.run_mload_general(), // "MLOAD_GENERAL", @@ -1025,8 +1026,7 @@ impl<'a> Interpreter<'a> { fn run_mload_general(&mut self) { let context = self.pop().as_usize(); let segment = Segment::all()[self.pop().as_usize()]; - let offset_u256 = self.pop(); - let offset = offset_u256.as_usize(); + let offset = self.pop().as_usize(); let value = self .generation_state .memory @@ -1035,6 +1035,23 @@ impl<'a> Interpreter<'a> { self.push(value); } + fn run_mload_32bytes(&mut self) { + let context = self.pop().as_usize(); + let segment = Segment::all()[self.pop().as_usize()]; + let offset = self.pop().as_usize(); + let len = self.pop().as_usize(); + let bytes: Vec = (0..len) + .map(|i| { + self.generation_state + .memory + .mload_general(context, segment, offset + i) + .as_u32() as u8 + }) + .collect(); + let value = U256::from_big_endian(&bytes); + self.push(value); + } + fn run_mstore_general(&mut self) { let context = self.pop().as_usize(); let segment = Segment::all()[self.pop().as_usize()]; @@ -1045,6 +1062,25 @@ impl<'a> Interpreter<'a> { .mstore_general(context, segment, offset, value); } + fn run_mstore_32bytes(&mut self) { + let context = self.pop().as_usize(); + let segment = Segment::all()[self.pop().as_usize()]; + let offset = self.pop().as_usize(); + let value = self.pop(); + let len = self.pop().as_usize(); + + let mut bytes = vec![0; 32]; + value.to_little_endian(&mut bytes); + bytes.resize(len, 0); + bytes.reverse(); + + for (i, &byte) in bytes.iter().enumerate() { + self.generation_state + .memory + .mstore_general(context, segment, offset + i, byte.into()); + } + } + fn stack_len(&self) -> usize { self.generation_state.registers.stack_len } @@ -1271,6 +1307,7 @@ fn get_mnemonic(opcode: u8) -> &'static str { 0xa3 => "LOG3", 0xa4 => "LOG4", 0xa5 => "PANIC", + 0xee => "MSTORE_32BYTES", 0xf0 => "CREATE", 0xf1 => "CALL", 0xf2 => "CALLCODE", From 41ddd558b0618663b826496c775eaf0523981a77 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 11:28:15 -0400 Subject: [PATCH 32/40] Fix recursive version --- evm/src/byte_packing/byte_packing_stark.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index d550877360..ce4483bd45 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -431,11 +431,9 @@ impl, const D: usize> Stark for BytePackingSt // The remaining length of a byte sequence must decrease by one or be zero. let current_sequence_length = vars.local_values[SEQUENCE_LEN]; - let mut current_remaining_length = - builder.sub_extension(current_sequence_length, vars.local_values[index_bytes(0)]); + let mut current_remaining_length = vars.local_values[index_bytes(0)]; let next_sequence_length = vars.next_values[SEQUENCE_LEN]; - let mut next_remaining_length = - builder.sub_extension(next_sequence_length, vars.next_values[index_bytes(0)]); + let mut next_remaining_length = vars.next_values[index_bytes(0)]; for i in 1..NUM_BYTES { current_remaining_length = builder.mul_const_add_extension( F::from_canonical_usize(i + 1), @@ -448,9 +446,16 @@ impl, const D: usize> Stark for BytePackingSt next_remaining_length, ); } + let current_remaining_length = + builder.sub_extension(current_sequence_length, current_remaining_length); + let next_remaining_length = + builder.sub_extension(next_sequence_length, next_remaining_length); let length_diff = builder.sub_extension(current_remaining_length, next_remaining_length); - let length_diff_minus_one = builder.add_const_extension(length_diff, F::NEG_ONE); - let constraint = builder.mul_extension(current_remaining_length, length_diff_minus_one); + let constraint = builder.mul_sub_extension( + current_remaining_length, + length_diff, + current_remaining_length, + ); yield_constr.constraint_transition(builder, constraint); // At the start of a sequence, the remaining length must be equal to the starting length minus one From f350c22fa9689936d77592a79a4a1a1ddc64031d Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 12:07:03 -0400 Subject: [PATCH 33/40] Remove debug assertion because of CI --- evm/src/byte_packing/columns.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 36a4f78ad7..1ce4b59f20 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -33,7 +33,6 @@ pub(crate) const SEQUENCE_LEN: usize = TIMESTAMP + 1; // 32 byte limbs hold a total of 256 bits. const BYTES_VALUES_START: usize = SEQUENCE_LEN + 1; pub(crate) const fn value_bytes(i: usize) -> usize { - debug_assert!(i < NUM_BYTES); BYTES_VALUES_START + i } From e2ca1828fd492fed6d26f96effab395dd4ddc2ea Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 14:54:17 -0400 Subject: [PATCH 34/40] Remove FILTER column --- evm/src/byte_packing/byte_packing_stark.rs | 39 +++++++++------------- evm/src/byte_packing/columns.rs | 6 ++-- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index ce4483bd45..3741a85f83 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -12,8 +12,8 @@ use plonky2::util::transpose; use super::NUM_BYTES; use crate::byte_packing::columns::{ - index_bytes, value_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, BYTE_INDICES_COLS, FILTER, - IS_READ, NUM_COLUMNS, RANGE_COUNTER, RC_COLS, SEQUENCE_END, SEQUENCE_LEN, TIMESTAMP, + index_bytes, value_bytes, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, BYTE_INDICES_COLS, IS_READ, + NUM_COLUMNS, RANGE_COUNTER, RC_COLS, SEQUENCE_END, SEQUENCE_LEN, TIMESTAMP, }; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; @@ -153,7 +153,6 @@ impl, const D: usize> BytePackingStark { let mut rows = Vec::with_capacity(bytes.len()); let mut row = [F::ZERO; NUM_COLUMNS]; - row[FILTER] = F::ONE; row[IS_READ] = F::from_bool(is_read); row[ADDR_CONTEXT] = F::from_canonical_usize(context); @@ -229,8 +228,12 @@ impl, const D: usize> Stark for BytePackingSt let one = P::ONES; - // The filter must be boolean. - let current_filter = vars.local_values[FILTER]; + // We filter active columns by summing all the byte indices. + // Constraining each of them to be boolean is done later on below. + let current_filter = vars.local_values[BYTE_INDICES_COLS] + .iter() + .copied() + .sum::

(); yield_constr.constraint(current_filter * (current_filter - one)); // The filter column must start by one. @@ -264,7 +267,10 @@ impl, const D: usize> Stark for BytePackingSt ); // Only padding rows have their filter turned off. - let next_filter = vars.next_values[FILTER]; + let next_filter = vars.next_values[BYTE_INDICES_COLS] + .iter() + .copied() + .sum::

(); yield_constr.constraint_transition(next_filter * (next_filter - current_filter)); // Unless the current sequence end flag is activated, the is_read filter must remain unchanged. @@ -278,13 +284,6 @@ impl, const D: usize> Stark for BytePackingSt current_sequence_end * next_filter * (next_sequence_start - one), ); - // There must be only one byte index set to 1 per active row. - let sum_indices = vars.local_values[BYTE_INDICES_COLS] - .iter() - .copied() - .sum::

(); - yield_constr.constraint(current_filter * (sum_indices - P::ONES)); - // The remaining length of a byte sequence must decrease by one or be zero. let current_sequence_length = vars.local_values[SEQUENCE_LEN]; let current_remaining_length = current_sequence_length @@ -313,7 +312,6 @@ impl, const D: usize> Stark for BytePackingSt // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. // The virtual address must decrement by one at each step of a sequence. - let next_filter = vars.next_values[FILTER]; let current_context = vars.local_values[ADDR_CONTEXT]; let next_context = vars.next_values[ADDR_CONTEXT]; let current_segment = vars.local_values[ADDR_SEGMENT]; @@ -358,8 +356,9 @@ impl, const D: usize> Stark for BytePackingSt eval_lookups_circuit(builder, vars, yield_constr, col, col + 1); } - // The filter must be boolean. - let current_filter = vars.local_values[FILTER]; + // We filter active columns by summing all the byte indices. + // Constraining each of them to be boolean is done later on below. + let current_filter = builder.add_many_extension(&vars.local_values[BYTE_INDICES_COLS]); let constraint = builder.mul_sub_extension(current_filter, current_filter, current_filter); yield_constr.constraint(builder, constraint); @@ -402,7 +401,7 @@ impl, const D: usize> Stark for BytePackingSt yield_constr.constraint(builder, constraint); // Only padding rows have their filter turned off. - let next_filter = vars.next_values[FILTER]; + let next_filter = builder.add_many_extension(&vars.next_values[BYTE_INDICES_COLS]); let constraint = builder.sub_extension(next_filter, current_filter); let constraint = builder.mul_extension(next_filter, constraint); yield_constr.constraint_transition(builder, constraint); @@ -424,11 +423,6 @@ impl, const D: usize> Stark for BytePackingSt let constraint = builder.mul_extension(next_filter, constraint); yield_constr.constraint_transition(builder, constraint); - // There must be only one byte index set to 1 per active row. - let sum_indices = builder.add_many_extension(&vars.local_values[BYTE_INDICES_COLS]); - let constraint = builder.mul_sub_extension(current_filter, sum_indices, current_filter); - yield_constr.constraint(builder, constraint); - // The remaining length of a byte sequence must decrease by one or be zero. let current_sequence_length = vars.local_values[SEQUENCE_LEN]; let mut current_remaining_length = vars.local_values[index_bytes(0)]; @@ -474,7 +468,6 @@ impl, const D: usize> Stark for BytePackingSt // The context, segment and timestamp fields must remain unchanged throughout a byte sequence. // The virtual address must decrement by one at each step of a sequence. - let next_filter = vars.next_values[FILTER]; let current_context = vars.local_values[ADDR_CONTEXT]; let next_context = vars.next_values[ADDR_CONTEXT]; let current_segment = vars.local_values[ADDR_SEGMENT]; diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index 1ce4b59f20..a46c50e270 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -4,20 +4,18 @@ use core::ops::Range; use crate::byte_packing::NUM_BYTES; -/// 1 if this is an actual byte packing operation, or 0 if it's a padding row. -pub(crate) const FILTER: usize = 0; /// 1 if this is a READ operation, and 0 if this is a WRITE operation. -pub(crate) const IS_READ: usize = FILTER + 1; +pub(crate) const IS_READ: usize = 0; /// 1 if this is the end of a sequence of bytes. /// This is also used as filter for the CTL. pub(crate) const SEQUENCE_END: usize = IS_READ + 1; pub(super) const BYTES_INDICES_START: usize = SEQUENCE_END + 1; pub(crate) const fn index_bytes(i: usize) -> usize { - debug_assert!(i < NUM_BYTES); BYTES_INDICES_START + i } +// Note: Those are used as filter for distinguishing active vs padding rows. pub(crate) const BYTE_INDICES_COLS: Range = BYTES_INDICES_START..BYTES_INDICES_START + NUM_BYTES; From ce478585b39517c82e41537976865da654c5891b Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 15:22:32 -0400 Subject: [PATCH 35/40] Update new test from rebasing --- evm/tests/log_opcode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/tests/log_opcode.rs b/evm/tests/log_opcode.rs index 4b748cece6..f442421c1a 100644 --- a/evm/tests/log_opcode.rs +++ b/evm/tests/log_opcode.rs @@ -431,7 +431,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { // Preprocess all circuits. let all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 17..19, 14..15, 9..11, 12..13, 20..21], + &[16..17, 17..19, 14..15, 9..11, 12..13, 19..21, 11..13], &config, ); From e526987fbdc115648e26cece925380f95d66d2ef Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 6 Sep 2023 18:59:50 -0400 Subject: [PATCH 36/40] Reorder STARK modules to match TraceCheckPoint ordering --- evm/src/all_stark.rs | 68 ++++++++++++++--------------- evm/src/fixed_recursive_verifier.rs | 26 +++++------ evm/src/prover.rs | 37 ++++++++-------- evm/src/verifier.rs | 32 +++++++------- evm/src/witness/traces.rs | 17 ++++---- evm/tests/empty_txn_list.rs | 2 +- evm/tests/log_opcode.rs | 2 +- 7 files changed, 92 insertions(+), 92 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index fa29d5b792..068b0bcbf9 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -26,12 +26,12 @@ use crate::stark::Stark; #[derive(Clone)] pub struct AllStark, const D: usize> { pub arithmetic_stark: ArithmeticStark, + pub byte_packing_stark: BytePackingStark, pub cpu_stark: CpuStark, pub keccak_stark: KeccakStark, pub keccak_sponge_stark: KeccakSpongeStark, pub logic_stark: LogicStark, pub memory_stark: MemoryStark, - pub byte_packing_stark: BytePackingStark, pub cross_table_lookups: Vec>, } @@ -39,12 +39,12 @@ impl, const D: usize> Default for AllStark { fn default() -> Self { Self { arithmetic_stark: ArithmeticStark::default(), + byte_packing_stark: BytePackingStark::default(), cpu_stark: CpuStark::default(), keccak_stark: KeccakStark::default(), keccak_sponge_stark: KeccakSpongeStark::default(), logic_stark: LogicStark::default(), memory_stark: MemoryStark::default(), - byte_packing_stark: BytePackingStark::default(), cross_table_lookups: all_cross_table_lookups(), } } @@ -54,24 +54,24 @@ impl, const D: usize> AllStark { pub(crate) fn nums_permutation_zs(&self, config: &StarkConfig) -> [usize; NUM_TABLES] { [ self.arithmetic_stark.num_permutation_batches(config), + self.byte_packing_stark.num_permutation_batches(config), self.cpu_stark.num_permutation_batches(config), self.keccak_stark.num_permutation_batches(config), self.keccak_sponge_stark.num_permutation_batches(config), self.logic_stark.num_permutation_batches(config), self.memory_stark.num_permutation_batches(config), - self.byte_packing_stark.num_permutation_batches(config), ] } pub(crate) fn permutation_batch_sizes(&self) -> [usize; NUM_TABLES] { [ self.arithmetic_stark.permutation_batch_size(), + self.byte_packing_stark.permutation_batch_size(), self.cpu_stark.permutation_batch_size(), self.keccak_stark.permutation_batch_size(), self.keccak_sponge_stark.permutation_batch_size(), self.logic_stark.permutation_batch_size(), self.memory_stark.permutation_batch_size(), - self.byte_packing_stark.permutation_batch_size(), ] } } @@ -79,26 +79,26 @@ impl, const D: usize> AllStark { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Table { Arithmetic = 0, - Cpu = 1, - Keccak = 2, - KeccakSponge = 3, - Logic = 4, - Memory = 5, - BytePacking = 6, + BytePacking = 1, + Cpu = 2, + Keccak = 3, + KeccakSponge = 4, + Logic = 5, + Memory = 6, } -pub(crate) const NUM_TABLES: usize = Table::BytePacking as usize + 1; +pub(crate) const NUM_TABLES: usize = Table::Memory as usize + 1; impl Table { pub(crate) fn all() -> [Self; NUM_TABLES] { [ Self::Arithmetic, + Self::BytePacking, Self::Cpu, Self::Keccak, Self::KeccakSponge, Self::Logic, Self::Memory, - Self::BytePacking, ] } } @@ -124,6 +124,28 @@ fn ctl_arithmetic() -> CrossTableLookup { ) } +fn ctl_byte_packing() -> CrossTableLookup { + let cpu_packing_looking = TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_byte_packing(), + Some(cpu_stark::ctl_filter_byte_packing()), + ); + let cpu_unpacking_looking = TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_byte_unpacking(), + Some(cpu_stark::ctl_filter_byte_unpacking()), + ); + let byte_packing_looked = TableWithColumns::new( + Table::BytePacking, + byte_packing_stark::ctl_looked_data(), + Some(byte_packing_stark::ctl_looked_filter()), + ); + CrossTableLookup::new( + vec![cpu_packing_looking, cpu_unpacking_looking], + byte_packing_looked, + ) +} + fn ctl_keccak() -> CrossTableLookup { let keccak_sponge_looking = TableWithColumns::new( Table::KeccakSponge, @@ -211,25 +233,3 @@ fn ctl_memory() -> CrossTableLookup { ); CrossTableLookup::new(all_lookers, memory_looked) } - -fn ctl_byte_packing() -> CrossTableLookup { - let cpu_packing_looking = TableWithColumns::new( - Table::Cpu, - cpu_stark::ctl_data_byte_packing(), - Some(cpu_stark::ctl_filter_byte_packing()), - ); - let cpu_unpacking_looking = TableWithColumns::new( - Table::Cpu, - cpu_stark::ctl_data_byte_unpacking(), - Some(cpu_stark::ctl_filter_byte_unpacking()), - ); - let byte_packing_looked = TableWithColumns::new( - Table::BytePacking, - byte_packing_stark::ctl_looked_data(), - Some(byte_packing_stark::ctl_looked_filter()), - ); - CrossTableLookup::new( - vec![cpu_packing_looking, cpu_unpacking_looking], - byte_packing_looked, - ) -} diff --git a/evm/src/fixed_recursive_verifier.rs b/evm/src/fixed_recursive_verifier.rs index 1b544b2d01..b9eeebf0f2 100644 --- a/evm/src/fixed_recursive_verifier.rs +++ b/evm/src/fixed_recursive_verifier.rs @@ -299,12 +299,12 @@ where C: GenericConfig + 'static, C::Hasher: AlgebraicHasher, [(); ArithmeticStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, [(); CpuStark::::COLUMNS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, - [(); BytePackingStark::::COLUMNS]:, { pub fn to_bytes( &self, @@ -380,44 +380,44 @@ where &all_stark.cross_table_lookups, stark_config, ); + let byte_packing = RecursiveCircuitsForTable::new( + Table::BytePacking, + &all_stark.byte_packing_stark, + degree_bits_ranges[1].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); let cpu = RecursiveCircuitsForTable::new( Table::Cpu, &all_stark.cpu_stark, - degree_bits_ranges[1].clone(), + degree_bits_ranges[2].clone(), &all_stark.cross_table_lookups, stark_config, ); let keccak = RecursiveCircuitsForTable::new( Table::Keccak, &all_stark.keccak_stark, - degree_bits_ranges[2].clone(), + degree_bits_ranges[3].clone(), &all_stark.cross_table_lookups, stark_config, ); let keccak_sponge = RecursiveCircuitsForTable::new( Table::KeccakSponge, &all_stark.keccak_sponge_stark, - degree_bits_ranges[3].clone(), + degree_bits_ranges[4].clone(), &all_stark.cross_table_lookups, stark_config, ); let logic = RecursiveCircuitsForTable::new( Table::Logic, &all_stark.logic_stark, - degree_bits_ranges[4].clone(), + degree_bits_ranges[5].clone(), &all_stark.cross_table_lookups, stark_config, ); let memory = RecursiveCircuitsForTable::new( Table::Memory, &all_stark.memory_stark, - degree_bits_ranges[5].clone(), - &all_stark.cross_table_lookups, - stark_config, - ); - let byte_packing = RecursiveCircuitsForTable::new( - Table::BytePacking, - &all_stark.byte_packing_stark, degree_bits_ranges[6].clone(), &all_stark.cross_table_lookups, stark_config, @@ -425,12 +425,12 @@ where let by_table = [ arithmetic, + byte_packing, cpu, keccak, keccak_sponge, logic, memory, - byte_packing, ]; let root = Self::create_root_circuit(&by_table, stark_config); let aggregation = Self::create_aggregation_circuit(&root); diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 90e7653906..8f5878232b 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -54,12 +54,12 @@ where F: RichField + Extendable, C: GenericConfig, [(); ArithmeticStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, [(); CpuStark::::COLUMNS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, - [(); BytePackingStark::::COLUMNS]:, { let (proof, _outputs) = prove_with_outputs(all_stark, config, inputs, timing)?; Ok(proof) @@ -77,12 +77,12 @@ where F: RichField + Extendable, C: GenericConfig, [(); ArithmeticStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, [(); CpuStark::::COLUMNS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, - [(); BytePackingStark::::COLUMNS]:, { timed!(timing, "build kernel", Lazy::force(&KERNEL)); let (traces, public_values, outputs) = timed!( @@ -106,12 +106,12 @@ where F: RichField + Extendable, C: GenericConfig, [(); ArithmeticStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, [(); CpuStark::::COLUMNS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, - [(); BytePackingStark::::COLUMNS]:, { let rate_bits = config.fri_config.rate_bits; let cap_height = config.fri_config.cap_height; @@ -197,12 +197,12 @@ where F: RichField + Extendable, C: GenericConfig, [(); ArithmeticStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, [(); CpuStark::::COLUMNS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, - [(); BytePackingStark::::COLUMNS]:, { let arithmetic_proof = timed!( timing, @@ -217,6 +217,19 @@ where timing, )? ); + let byte_packing_proof = timed!( + timing, + "prove byte packing STARK", + prove_single_table( + &all_stark.byte_packing_stark, + config, + &trace_poly_values[Table::BytePacking as usize], + &trace_commitments[Table::BytePacking as usize], + &ctl_data_per_table[Table::BytePacking as usize], + challenger, + timing, + )? + ); let cpu_proof = timed!( timing, "prove CPU STARK", @@ -282,27 +295,15 @@ where timing, )? ); - let byte_packing_proof = timed!( - timing, - "prove byte packing STARK", - prove_single_table( - &all_stark.byte_packing_stark, - config, - &trace_poly_values[Table::BytePacking as usize], - &trace_commitments[Table::BytePacking as usize], - &ctl_data_per_table[Table::BytePacking as usize], - challenger, - timing, - )? - ); + Ok([ arithmetic_proof, + byte_packing_proof, cpu_proof, keccak_proof, keccak_sponge_proof, logic_proof, memory_proof, - byte_packing_proof, ]) } diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index 17ddc7a364..a27c72107a 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -40,12 +40,12 @@ pub fn verify_proof, C: GenericConfig, co ) -> Result<()> where [(); ArithmeticStark::::COLUMNS]:, + [(); BytePackingStark::::COLUMNS]:, [(); CpuStark::::COLUMNS]:, [(); KeccakStark::::COLUMNS]:, [(); KeccakSpongeStark::::COLUMNS]:, [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, - [(); BytePackingStark::::COLUMNS]:, { let AllProofChallenges { stark_challenges, @@ -56,12 +56,12 @@ where let AllStark { arithmetic_stark, + byte_packing_stark, cpu_stark, keccak_stark, keccak_sponge_stark, logic_stark, memory_stark, - byte_packing_stark, cross_table_lookups, } = all_stark; @@ -79,6 +79,13 @@ where &ctl_vars_per_table[Table::Arithmetic as usize], config, )?; + verify_stark_proof_with_challenges( + byte_packing_stark, + &all_proof.stark_proofs[Table::BytePacking as usize].proof, + &stark_challenges[Table::BytePacking as usize], + &ctl_vars_per_table[Table::BytePacking as usize], + config, + )?; verify_stark_proof_with_challenges( cpu_stark, &all_proof.stark_proofs[Table::Cpu as usize].proof, @@ -100,20 +107,6 @@ where &ctl_vars_per_table[Table::KeccakSponge as usize], config, )?; - verify_stark_proof_with_challenges( - memory_stark, - &all_proof.stark_proofs[Table::Memory as usize].proof, - &stark_challenges[Table::Memory as usize], - &ctl_vars_per_table[Table::Memory as usize], - config, - )?; - verify_stark_proof_with_challenges( - byte_packing_stark, - &all_proof.stark_proofs[Table::BytePacking as usize].proof, - &stark_challenges[Table::BytePacking as usize], - &ctl_vars_per_table[Table::BytePacking as usize], - config, - )?; verify_stark_proof_with_challenges( logic_stark, &all_proof.stark_proofs[Table::Logic as usize].proof, @@ -121,6 +114,13 @@ where &ctl_vars_per_table[Table::Logic as usize], config, )?; + verify_stark_proof_with_challenges( + memory_stark, + &all_proof.stark_proofs[Table::Memory as usize].proof, + &stark_challenges[Table::Memory as usize], + &ctl_vars_per_table[Table::Memory as usize], + config, + )?; let public_values = all_proof.public_values; diff --git a/evm/src/witness/traces.rs b/evm/src/witness/traces.rs index e888c8fd68..246a114481 100644 --- a/evm/src/witness/traces.rs +++ b/evm/src/witness/traces.rs @@ -146,7 +146,13 @@ impl Traces { "generate arithmetic trace", all_stark.arithmetic_stark.generate_trace(arithmetic_ops) ); - + let byte_packing_trace = timed!( + timing, + "generate byte packing trace", + all_stark + .byte_packing_stark + .generate_trace(byte_packing_ops, cap_elements, timing) + ); let cpu_rows = cpu.into_iter().map(|x| x.into()).collect(); let cpu_trace = trace_rows_to_poly_values(cpu_rows); let keccak_trace = timed!( @@ -175,22 +181,15 @@ impl Traces { "generate memory trace", all_stark.memory_stark.generate_trace(memory_ops, timing) ); - let byte_packing_trace = timed!( - timing, - "generate byte packing trace", - all_stark - .byte_packing_stark - .generate_trace(byte_packing_ops, cap_elements, timing) - ); [ arithmetic_trace, + byte_packing_trace, cpu_trace, keccak_trace, keccak_sponge_trace, logic_trace, memory_trace, - byte_packing_trace, ] } } diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs index cb59d96b8b..9ef86a0aa1 100644 --- a/evm/tests/empty_txn_list.rs +++ b/evm/tests/empty_txn_list.rs @@ -67,7 +67,7 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 15..16, 14..15, 9..10, 12..13, 18..19, 8..9], // Minimal ranges to prove an empty list + &[16..17, 10..11, 15..16, 14..15, 9..10, 12..13, 18..19], // Minimal ranges to prove an empty list &config, ); diff --git a/evm/tests/log_opcode.rs b/evm/tests/log_opcode.rs index f442421c1a..d92c6efb91 100644 --- a/evm/tests/log_opcode.rs +++ b/evm/tests/log_opcode.rs @@ -431,7 +431,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { // Preprocess all circuits. let all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 17..19, 14..15, 9..11, 12..13, 19..21, 11..13], + &[16..17, 11..13, 17..19, 14..15, 9..11, 12..13, 19..21], &config, ); From 1479ee35c4fadeb6b461ffa9eb15201c2c12a220 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 12 Sep 2023 10:36:18 -0400 Subject: [PATCH 37/40] Address comments --- evm/src/byte_packing/byte_packing_stark.rs | 71 ++++++++++++++-------- evm/src/byte_packing/columns.rs | 2 + evm/src/memory/memory_stark.rs | 2 +- evm/src/witness/operation.rs | 7 ++- 4 files changed, 53 insertions(+), 29 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 3741a85f83..064fa5aa42 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -6,6 +6,7 @@ use plonky2::field::packed::PackedField; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; use plonky2::timed; use plonky2::util::timing::TimingTree; use plonky2::util::transpose; @@ -22,12 +23,13 @@ use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; use crate::witness::memory::MemoryAddress; +/// Strict upper bound for the individual bytes range-check. const BYTE_RANGE_MAX: usize = 1usize << 8; pub(crate) fn ctl_looked_data() -> Vec> { let outputs: Vec> = (0..8) .map(|i| { - let range = (value_bytes(i * 4)..value_bytes((i + 1) * 4)).collect_vec(); + let range = (value_bytes(i * 4)..value_bytes(i * 4) + 4).collect_vec(); Column::linear_combination( range .iter() @@ -87,7 +89,7 @@ pub(crate) struct BytePackingOp { pub(crate) timestamp: usize, /// The byte sequence that was read/written. - /// Its length is expected to be at most 32. + /// Its length is required to be at most 32. pub(crate) bytes: Vec, } @@ -208,6 +210,37 @@ impl, const D: usize> BytePackingStark { cols[rc_c + 1].copy_from_slice(&table_perm); } } + + /// There is only one `i` for which `vars.local_values[index_bytes(i)]` is non-zero, + /// and `i+1` is the current position: + fn get_active_position(&self, row: &[P; NUM_COLUMNS]) -> P + where + FE: FieldExtension, + P: PackedField, + { + (0..NUM_BYTES) + .map(|i| row[index_bytes(i)] * P::Scalar::from_canonical_usize(i + 1)) + .sum() + } + + /// Recursive version of `get_active_position`. + fn get_active_position_circuit( + &self, + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + row: &[ExtensionTarget; NUM_COLUMNS], + ) -> ExtensionTarget { + let mut current_position = row[index_bytes(0)]; + + for i in 1..NUM_BYTES { + current_position = builder.mul_const_add_extension( + F::from_canonical_usize(i + 1), + row[index_bytes(i)], + current_position, + ); + } + + current_position + } } impl, const D: usize> Stark for BytePackingStark { @@ -286,15 +319,11 @@ impl, const D: usize> Stark for BytePackingSt // The remaining length of a byte sequence must decrease by one or be zero. let current_sequence_length = vars.local_values[SEQUENCE_LEN]; - let current_remaining_length = current_sequence_length - - (0..NUM_BYTES) - .map(|i| vars.local_values[index_bytes(i)] * P::Scalar::from_canonical_usize(i + 1)) - .sum::

(); + let current_position = self.get_active_position(&vars.local_values); + let next_position = self.get_active_position(&vars.next_values); + let current_remaining_length = current_sequence_length - current_position; let next_sequence_length = vars.next_values[SEQUENCE_LEN]; - let next_remaining_length = next_sequence_length - - (0..NUM_BYTES) - .map(|i| vars.next_values[index_bytes(i)] * P::Scalar::from_canonical_usize(i + 1)) - .sum::

(); + let next_remaining_length = next_sequence_length - next_position; yield_constr.constraint_transition( current_remaining_length * (current_remaining_length - next_remaining_length - one), ); @@ -425,25 +454,13 @@ impl, const D: usize> Stark for BytePackingSt // The remaining length of a byte sequence must decrease by one or be zero. let current_sequence_length = vars.local_values[SEQUENCE_LEN]; - let mut current_remaining_length = vars.local_values[index_bytes(0)]; let next_sequence_length = vars.next_values[SEQUENCE_LEN]; - let mut next_remaining_length = vars.next_values[index_bytes(0)]; - for i in 1..NUM_BYTES { - current_remaining_length = builder.mul_const_add_extension( - F::from_canonical_usize(i + 1), - vars.local_values[index_bytes(i)], - current_remaining_length, - ); - next_remaining_length = builder.mul_const_add_extension( - F::from_canonical_usize(i + 1), - vars.next_values[index_bytes(i)], - next_remaining_length, - ); - } + let current_position = self.get_active_position_circuit(builder, &vars.local_values); + let next_position = self.get_active_position_circuit(builder, &vars.next_values); + let current_remaining_length = - builder.sub_extension(current_sequence_length, current_remaining_length); - let next_remaining_length = - builder.sub_extension(next_sequence_length, next_remaining_length); + builder.sub_extension(current_sequence_length, current_position); + let next_remaining_length = builder.sub_extension(next_sequence_length, next_position); let length_diff = builder.sub_extension(current_remaining_length, next_remaining_length); let constraint = builder.mul_sub_extension( current_remaining_length, diff --git a/evm/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs index a46c50e270..f04f450c51 100644 --- a/evm/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -12,6 +12,7 @@ pub(crate) const SEQUENCE_END: usize = IS_READ + 1; pub(super) const BYTES_INDICES_START: usize = SEQUENCE_END + 1; pub(crate) const fn index_bytes(i: usize) -> usize { + debug_assert!(i < NUM_BYTES); BYTES_INDICES_START + i } @@ -31,6 +32,7 @@ pub(crate) const SEQUENCE_LEN: usize = TIMESTAMP + 1; // 32 byte limbs hold a total of 256 bits. const BYTES_VALUES_START: usize = SEQUENCE_LEN + 1; pub(crate) const fn value_bytes(i: usize) -> usize { + debug_assert!(i < NUM_BYTES); BYTES_VALUES_START + i } diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index 22802ae09d..36f7566543 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -49,7 +49,7 @@ impl MemoryOp { /// depend on the next operation, such as `CONTEXT_FIRST_CHANGE`; those are generated later. /// It also does not generate columns such as `COUNTER`, which are generated later, after the /// trace has been transposed into column-major form. - pub(crate) fn into_row(self) -> [F; NUM_COLUMNS] { + fn into_row(self) -> [F; NUM_COLUMNS] { let mut row = [F::ZERO; NUM_COLUMNS]; row[FILTER] = F::from_bool(self.filter); row[TIMESTAMP] = F::from_canonical_usize(self.timestamp); diff --git a/evm/src/witness/operation.rs b/evm/src/witness/operation.rs index ee0be6d8e0..7d07576d30 100644 --- a/evm/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -698,10 +698,15 @@ pub(crate) fn generate_mload_32bytes( let len = len.as_usize(); let base_address = MemoryAddress::new_u256s(context, segment, base_virt)?; + if usize::MAX - base_address.virt < len { + return Err(ProgramError::MemoryError(VirtTooLarge { + virt: base_address.virt.into(), + })); + } let bytes = (0..len) .map(|i| { let address = MemoryAddress { - virt: base_address.virt.saturating_add(i), + virt: base_address.virt + i, ..base_address }; let val = state.memory.get(address); From a13f5095fa78e00d84021b909734b6323c2d4d08 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 12 Sep 2023 10:39:35 -0400 Subject: [PATCH 38/40] Pacify clippy --- evm/src/byte_packing/byte_packing_stark.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 064fa5aa42..a9fa36ea88 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -319,8 +319,8 @@ impl, const D: usize> Stark for BytePackingSt // The remaining length of a byte sequence must decrease by one or be zero. let current_sequence_length = vars.local_values[SEQUENCE_LEN]; - let current_position = self.get_active_position(&vars.local_values); - let next_position = self.get_active_position(&vars.next_values); + let current_position = self.get_active_position(vars.local_values); + let next_position = self.get_active_position(vars.next_values); let current_remaining_length = current_sequence_length - current_position; let next_sequence_length = vars.next_values[SEQUENCE_LEN]; let next_remaining_length = next_sequence_length - next_position; @@ -455,8 +455,8 @@ impl, const D: usize> Stark for BytePackingSt // The remaining length of a byte sequence must decrease by one or be zero. let current_sequence_length = vars.local_values[SEQUENCE_LEN]; let next_sequence_length = vars.next_values[SEQUENCE_LEN]; - let current_position = self.get_active_position_circuit(builder, &vars.local_values); - let next_position = self.get_active_position_circuit(builder, &vars.next_values); + let current_position = self.get_active_position_circuit(builder, vars.local_values); + let next_position = self.get_active_position_circuit(builder, vars.next_values); let current_remaining_length = builder.sub_extension(current_sequence_length, current_position); From ce09a639d3b4cfe3d433a3fd2356b07b07cfe393 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 12 Sep 2023 11:29:53 -0400 Subject: [PATCH 39/40] Add documentation to the packing module --- evm/src/byte_packing/byte_packing_stark.rs | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index a9fa36ea88..219603242e 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -1,3 +1,36 @@ +//! This crate enforces the correctness of reading and writing sequences +//! of bytes in Big-Endian ordering from and to the memory. +//! +//! The trace layout consists in N consecutive rows for an `N` byte sequence, +//! with the byte values being cumulatively written to the trace as they are +//! being processed. +//! +//! At row `i` of such a group (starting from 0), the `i`-th byte flag will be activated +//! (to indicate which byte we are going to be processing), but all bytes with index +//! 0 to `i` may have non-zero values, as they have already been processed. +//! +//! The length of a sequence is stored within each group of rows corresponding to that +//! sequence in a dedicated `SEQUENCE_LEN` column. At any row `i`, the remaining length +//! of the sequence being processed is retrieved from that column and the active byte flag +//! as: +//! +//! remaining_length = sequence_length - \sum_{i=0}^31 b[i] * i +//! +//! where b[i] is the `i`-th byte flag. +//! +//! Because of the discrepancy in endianness between the different tables, the byte sequences +//! are actually written in the trace in reverse order from the order they are provided. +//! As such, the memory virtual address for a group of rows corresponding to a sequence starts +//! with the final virtual address, corresponding to the final byte being read/written, and +//! is being decremented at each step. +//! +//! Note that, when writing a sequence of bytes to memory, both the `U256` value and the +//! corresponding sequence length are being read from the stack. Because of the endianness +//! discrepancy mentioned above, we first convert the value to a byte sequence in Little-Endian, +//! then resize the sequence to prune unneeded zeros before reverting the sequence order. +//! This means that the higher-order bytes will be thrown away during the process, if the value +//! is greater than 256^length, and as a result a different value will be stored in memory. + use std::marker::PhantomData; use itertools::Itertools; @@ -27,6 +60,10 @@ use crate::witness::memory::MemoryAddress; const BYTE_RANGE_MAX: usize = 1usize << 8; pub(crate) fn ctl_looked_data() -> Vec> { + // Reconstruct the u32 limbs composing the final `U256` word + // being read/written from the underlying byte values. For each, + // we pack 4 consecutive bytes and shift them accordingly to + // obtain the corresponding limb. let outputs: Vec> = (0..8) .map(|i| { let range = (value_bytes(i * 4)..value_bytes(i * 4) + 4).collect_vec(); From e9a837bf361abc8c325348b39fb7b30f05af6303 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Tue, 12 Sep 2023 12:04:09 -0400 Subject: [PATCH 40/40] Fix doctest --- evm/src/byte_packing/byte_packing_stark.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs index 219603242e..f97a2b28ab 100644 --- a/evm/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -14,7 +14,7 @@ //! of the sequence being processed is retrieved from that column and the active byte flag //! as: //! -//! remaining_length = sequence_length - \sum_{i=0}^31 b[i] * i +//! remaining_length = sequence_length - \sum_{i=0}^31 b[i] * i //! //! where b[i] is the `i`-th byte flag. //!