From 2c77247d4314aa658c66316063c6179751fbf71b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 29 Aug 2022 18:10:51 -0700 Subject: [PATCH] Keccak sponge STARK It contains a row for each absorb step of the sponge. --- evm/src/cpu/columns/mod.rs | 18 +- evm/src/cross_table_lookup.rs | 23 +- evm/src/keccak_sponge/columns.rs | 118 +++++ evm/src/keccak_sponge/keccak_sponge_stark.rs | 441 +++++++++++++++++++ evm/src/keccak_sponge/mod.rs | 6 + evm/src/lib.rs | 1 + evm/src/stark_testing.rs | 23 +- evm/src/util.rs | 34 ++ field/src/polynomial/mod.rs | 4 + 9 files changed, 635 insertions(+), 33 deletions(-) create mode 100644 evm/src/keccak_sponge/columns.rs create mode 100644 evm/src/keccak_sponge/keccak_sponge_stark.rs create mode 100644 evm/src/keccak_sponge/mod.rs diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index ad0f6a95..567c5a97 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -3,11 +3,12 @@ use std::borrow::{Borrow, BorrowMut}; use std::fmt::Debug; -use std::mem::{size_of, transmute, transmute_copy, ManuallyDrop}; +use std::mem::{size_of, transmute}; use std::ops::{Index, IndexMut}; use crate::cpu::columns::general::CpuGeneralColumnsView; use crate::memory; +use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; mod general; @@ -157,14 +158,6 @@ pub struct CpuColumnsView { // `u8` is guaranteed to have a `size_of` of 1. pub const NUM_CPU_COLUMNS: usize = size_of::>(); -unsafe fn transmute_no_compile_time_size_checks(value: T) -> U { - debug_assert_eq!(size_of::(), size_of::()); - // Need ManuallyDrop so that `value` is not dropped by this function. - let value = ManuallyDrop::new(value); - // Copy the bit pattern. The original value is no longer safe to use. - transmute_copy(&value) -} - impl From<[T; NUM_CPU_COLUMNS]> for CpuColumnsView { fn from(value: [T; NUM_CPU_COLUMNS]) -> Self { unsafe { transmute_no_compile_time_size_checks(value) } @@ -224,12 +217,7 @@ where } const fn make_col_map() -> CpuColumnsView { - let mut indices_arr = [0; NUM_CPU_COLUMNS]; - let mut i = 0; - while i < NUM_CPU_COLUMNS { - indices_arr[i] = i; - i += 1; - } + let indices_arr = indices_arr::(); unsafe { transmute::<[usize; NUM_CPU_COLUMNS], CpuColumnsView>(indices_arr) } } diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 37a8b5a6..3d67dd58 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::iter::repeat; use anyhow::{ensure, Result}; @@ -38,8 +39,10 @@ impl Column { } } - pub fn singles>(cs: I) -> impl Iterator { - cs.into_iter().map(Self::single) + pub fn singles>>( + cs: I, + ) -> impl Iterator { + cs.into_iter().map(|c| Self::single(*c.borrow())) } pub fn constant(constant: F) -> Self { @@ -74,16 +77,20 @@ impl Column { Self::linear_combination_with_constant(iter, F::ZERO) } - pub fn le_bits>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(F::TWO.powers())) + pub fn le_bits>>(cs: I) -> Self { + Self::linear_combination(cs.into_iter().map(|c| *c.borrow()).zip(F::TWO.powers())) } - pub fn le_bytes>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(F::from_canonical_u16(256).powers())) + pub fn le_bytes>>(cs: I) -> Self { + Self::linear_combination( + cs.into_iter() + .map(|c| *c.borrow()) + .zip(F::from_canonical_u16(256).powers()), + ) } - pub fn sum>(cs: I) -> Self { - Self::linear_combination(cs.into_iter().zip(repeat(F::ONE))) + pub fn sum>>(cs: I) -> Self { + Self::linear_combination(cs.into_iter().map(|c| *c.borrow()).zip(repeat(F::ONE))) } pub fn eval(&self, v: &[P]) -> P diff --git a/evm/src/keccak_sponge/columns.rs b/evm/src/keccak_sponge/columns.rs new file mode 100644 index 00000000..e564de93 --- /dev/null +++ b/evm/src/keccak_sponge/columns.rs @@ -0,0 +1,118 @@ +use std::borrow::{Borrow, BorrowMut}; +use std::mem::{size_of, transmute}; + +use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; + +pub(crate) const KECCAK_WIDTH_BYTES: usize = 200; +pub(crate) const KECCAK_WIDTH_U32S: usize = KECCAK_WIDTH_BYTES / 4; +pub(crate) const KECCAK_RATE_BITS: usize = 1088; +pub(crate) const KECCAK_RATE_BYTES: usize = KECCAK_RATE_BITS / 8; +pub(crate) const KECCAK_RATE_U32S: usize = KECCAK_RATE_BYTES / 4; +pub(crate) const KECCAK_CAPACITY_BYTES: usize = 64; +pub(crate) const KECCAK_CAPACITY_U32S: usize = KECCAK_CAPACITY_BYTES / 4; + +#[repr(C)] +#[derive(Eq, PartialEq, Debug)] +pub(crate) struct KeccakSpongeColumnsView { + /// 1 if this row represents a dummy operation (for padding the table); 0 otherwise. + pub is_dummy: T, + + /// 1 if this row represents a full input block, i.e. one in which each byte is an input byte, + /// not a padding byte; 0 otherwise. + pub is_full_input_block: T, + + /// 1 if this row represents the final block of a sponge, in which case some or all of the bytes + /// in the block will be padding bytes; 0 otherwise. + pub is_final_block: T, + + // The address at which we will read the input block. + pub context: T, + pub segment: T, + pub virt: T, + + /// The timestamp at which inputs should be read from memory. + pub timestamp: T, + + /// The length of the original input, in bytes. + pub len: T, + + /// The number of input bytes that have already been absorbed prior to this block. + pub already_absorbed_bytes: T, + + /// If this row represents a final block row, the `i`th entry should be 1 if the final chunk of + /// input has length `i` (in other words if `len - already_absorbed == i`), otherwise 0. + /// + /// If this row represents a full input block, this should contain all 0s. + pub is_final_input_len: [T; KECCAK_RATE_BYTES], + + /// The initial rate bits of the sponge, at the start of this step. + pub original_rate_bits: [T; KECCAK_RATE_BITS], + + /// The capacity bits of the sponge, encoded as 32-bit chunks, at the start of this step. + pub original_capacity_u32s: [T; KECCAK_CAPACITY_U32S], + + /// The block being absorbed, which may contain input bytes and/or padding bytes. + pub block_bits: [T; KECCAK_RATE_BITS], + + /// The rate bits of the sponge, after the current block is xor'd in, but before the permutation + /// is applied. + pub xored_rate_u32s: [T; KECCAK_RATE_U32S], + + /// The entire state (rate + capacity) of the sponge, encoded as 32-bit chunks, after the + /// permutation is applied. + pub updated_state_u32s: [T; KECCAK_WIDTH_U32S], +} + +// `u8` is guaranteed to have a `size_of` of 1. +pub const NUM_KECCAK_SPONGE_COLUMNS: usize = size_of::>(); + +impl From<[T; NUM_KECCAK_SPONGE_COLUMNS]> for KeccakSpongeColumnsView { + fn from(value: [T; NUM_KECCAK_SPONGE_COLUMNS]) -> Self { + unsafe { transmute_no_compile_time_size_checks(value) } + } +} + +impl From> for [T; NUM_KECCAK_SPONGE_COLUMNS] { + fn from(value: KeccakSpongeColumnsView) -> Self { + unsafe { transmute_no_compile_time_size_checks(value) } + } +} + +impl Borrow> for [T; NUM_KECCAK_SPONGE_COLUMNS] { + fn borrow(&self) -> &KeccakSpongeColumnsView { + unsafe { transmute(self) } + } +} + +impl BorrowMut> for [T; NUM_KECCAK_SPONGE_COLUMNS] { + fn borrow_mut(&mut self) -> &mut KeccakSpongeColumnsView { + unsafe { transmute(self) } + } +} + +impl Borrow<[T; NUM_KECCAK_SPONGE_COLUMNS]> for KeccakSpongeColumnsView { + fn borrow(&self) -> &[T; NUM_KECCAK_SPONGE_COLUMNS] { + unsafe { transmute(self) } + } +} + +impl BorrowMut<[T; NUM_KECCAK_SPONGE_COLUMNS]> for KeccakSpongeColumnsView { + fn borrow_mut(&mut self) -> &mut [T; NUM_KECCAK_SPONGE_COLUMNS] { + unsafe { transmute(self) } + } +} + +impl Default for KeccakSpongeColumnsView { + fn default() -> Self { + [T::default(); NUM_KECCAK_SPONGE_COLUMNS].into() + } +} + +const fn make_col_map() -> KeccakSpongeColumnsView { + let indices_arr = indices_arr::(); + unsafe { + transmute::<[usize; NUM_KECCAK_SPONGE_COLUMNS], KeccakSpongeColumnsView>(indices_arr) + } +} + +pub(crate) const KECCAK_SPONGE_COL_MAP: KeccakSpongeColumnsView = make_col_map(); diff --git a/evm/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs new file mode 100644 index 00000000..32edded5 --- /dev/null +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -0,0 +1,441 @@ +use std::borrow::Borrow; +use std::iter; +use std::marker::PhantomData; +use std::mem::size_of; + +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::iop::ext_target::ExtensionTarget; +use plonky2::timed; +use plonky2::util::timing::TimingTree; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cpu::kernel::keccak_util::keccakf_u32s; +use crate::cross_table_lookup::Column; +use crate::keccak_sponge::columns::*; +use crate::memory::segments::Segment; +use crate::stark::Stark; +use crate::util::{trace_rows_to_poly_values, u32_from_le_bits, u32_to_le_bits, u8_to_le_bits}; +use crate::vars::StarkEvaluationTargets; +use crate::vars::StarkEvaluationVars; + +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looked_data() -> Vec> { + let cols = KECCAK_SPONGE_COL_MAP; + let outputs = Column::singles(&cols.updated_state_u32s[..KECCAK_RATE_U32S]); + Column::singles([ + cols.context, + cols.segment, + cols.virt, + cols.timestamp, + cols.len, + ]) + .chain(outputs) + .collect() +} + +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looking_keccak() -> Vec> { + let input_rate_cols = (0..KECCAK_WIDTH_U32S) + .map(|i| Column::le_bits(&KECCAK_SPONGE_COL_MAP.original_rate_bits[i * 32..(i + 1) * 32])); + let input_capacity_cols = Column::singles( + (0..KECCAK_WIDTH_U32S).map(|i| KECCAK_SPONGE_COL_MAP.original_capacity_u32s[i]), + ); + let output_cols = Column::singles( + (0..KECCAK_WIDTH_U32S).map(|i| KECCAK_SPONGE_COL_MAP.updated_state_u32s[i]), + ); + input_rate_cols + .chain(input_capacity_cols) + .chain(output_cols) + .collect() +} + +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { + let cols = KECCAK_SPONGE_COL_MAP; + + let mut res = vec![Column::constant(F::ONE)]; // is_read + + res.extend(Column::singles([cols.context, cols.segment, cols.virt])); + + // The i'th input byte being read. + res.push(Column::le_bits(&cols.block_bits[i * 8..(i + 1) * 8])); + + // Since we're reading a single byte, the higher limbs must be zero. + res.extend((1..8).map(|_| Column::zero())); + + res.push(Column::single(cols.timestamp)); + + assert_eq!( + res.len(), + crate::memory::memory_stark::ctl_data::().len() + ); + res +} + +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looked_filter() -> Column { + // The CPU table is only interested in our final-block rows, since those contain the final + // sponge output. + Column::single(KECCAK_SPONGE_COL_MAP.is_final_block) +} + +#[allow(unused)] // TODO: Should be used soon. +/// CTL filter for reading the `i`th byte of input from memory. +pub(crate) fn ctl_looking_memory_filter(i: usize) -> Column { + // We perform the `i`th read if either + // - this is a full input block, or + // - this is a final block of length `i` or greater + let cols = KECCAK_SPONGE_COL_MAP; + Column::sum(iter::once(&cols.is_full_input_block).chain(&cols.is_final_input_len[i..])) +} + +/// Information about a Keccak sponge operation needed for witness generation. +#[derive(Debug)] +pub(crate) struct KeccakSpongeOp { + // The address at which inputs are read. + pub(crate) context: usize, + pub(crate) segment: Segment, + pub(crate) virt: usize, + + /// The timestamp at which inputs are read. + pub(crate) timestamp: usize, + + /// The length of the input, in bytes. + pub(crate) len: usize, + + /// The input that was read. + pub(crate) input: Vec, +} + +#[derive(Copy, Clone, Default)] +pub(crate) struct KeccakSpongeStark { + f: PhantomData, +} + +impl, const D: usize> KeccakSpongeStark { + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn generate_trace( + &self, + operations: Vec, + min_rows: usize, + ) -> Vec> { + let mut timing = TimingTree::new("generate trace", log::Level::Debug); + + // Generate the witness row-wise. + let trace_rows = timed!( + &mut timing, + "generate trace rows", + self.generate_trace_rows(operations, min_rows) + ); + + let trace_polys = timed!( + &mut timing, + "convert to PolynomialValues", + trace_rows_to_poly_values(trace_rows) + ); + + timing.print(); + trace_polys + } + + fn generate_trace_rows( + &self, + operations: Vec, + min_rows: usize, + ) -> Vec<[F; NUM_KECCAK_SPONGE_COLUMNS]> { + let mut rows = vec![]; + for op in operations { + rows.extend(self.generate_rows_for_op(op)); + } + + let num_rows = rows.len().max(min_rows).next_power_of_two(); + let padding_row = self.generate_padding_row(); + for _ in rows.len()..num_rows { + rows.push(padding_row); + } + rows + } + + fn generate_rows_for_op(&self, op: KeccakSpongeOp) -> Vec<[F; NUM_KECCAK_SPONGE_COLUMNS]> { + let mut rows = vec![]; + + let mut sponge_state = [0u32; KECCAK_WIDTH_U32S]; + + let mut input_blocks = op.input.chunks_exact(KECCAK_RATE_BYTES); + let mut already_absorbed_bytes = 0; + for block in input_blocks.by_ref() { + let row = self.generate_full_input_row( + &op, + already_absorbed_bytes, + sponge_state, + block.try_into().unwrap(), + ); + + // xor block into sponge_state's rate elements. + let block_u32s = block + .chunks(size_of::()) + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())); + for (state_u32, block_u32) in sponge_state.iter_mut().zip(block_u32s) { + *state_u32 ^= block_u32; + } + + sponge_state = row.updated_state_u32s.map(|f| f.to_canonical_u64() as u32); + + rows.push(row.into()); + already_absorbed_bytes += KECCAK_RATE_BYTES; + } + + rows.push( + self.generate_final_row( + &op, + already_absorbed_bytes, + sponge_state, + input_blocks.remainder(), + ) + .into(), + ); + + rows + } + + fn generate_full_input_row( + &self, + op: &KeccakSpongeOp, + already_absorbed_bytes: usize, + sponge_state: [u32; KECCAK_WIDTH_U32S], + block: [u8; KECCAK_RATE_BYTES], + ) -> KeccakSpongeColumnsView { + let mut row = KeccakSpongeColumnsView { + is_full_input_block: F::ONE, + ..Default::default() + }; + + row.block_bits = block + .into_iter() + .flat_map(u8_to_le_bits) + .map(F::from_bool) + .collect_vec() + .try_into() + .unwrap(); + + Self::generate_common_fields(&mut row, op, already_absorbed_bytes, sponge_state); + row + } + + fn generate_final_row( + &self, + op: &KeccakSpongeOp, + already_absorbed_bytes: usize, + sponge_state: [u32; KECCAK_WIDTH_U32S], + final_inputs: &[u8], + ) -> KeccakSpongeColumnsView { + assert_eq!(already_absorbed_bytes + final_inputs.len(), op.len); + + let mut row = KeccakSpongeColumnsView { + is_final_block: F::ONE, + ..Default::default() + }; + + let final_input_bits = final_inputs + .iter() + .flat_map(|x| u8_to_le_bits(*x)) + .map(F::from_bool); + for (block_bit, input_bit) in row.block_bits.iter_mut().zip(final_input_bits) { + *block_bit = input_bit; + } + // pad10*1 rule + row.block_bits[final_inputs.len() * 8] = F::ONE; + row.block_bits[KECCAK_RATE_BITS - 1] = F::ONE; + + row.is_final_input_len[final_inputs.len()] = F::ONE; + + Self::generate_common_fields(&mut row, op, already_absorbed_bytes, sponge_state); + row + } + + /// Generate fields that are common to both full-input-block rows and final-block rows. + fn generate_common_fields( + row: &mut KeccakSpongeColumnsView, + op: &KeccakSpongeOp, + already_absorbed_bytes: usize, + mut sponge_state: [u32; KECCAK_WIDTH_U32S], + ) { + row.context = F::from_canonical_usize(op.context); + row.segment = F::from_canonical_usize(op.segment as usize); + row.virt = F::from_canonical_usize(op.virt); + row.timestamp = F::from_canonical_usize(op.timestamp); + row.len = F::from_canonical_usize(op.len); + row.already_absorbed_bytes = F::from_canonical_usize(already_absorbed_bytes); + + row.original_rate_bits = sponge_state[..KECCAK_RATE_U32S] + .iter() + .flat_map(|x| u32_to_le_bits(*x)) + .map(F::from_bool) + .collect_vec() + .try_into() + .unwrap(); + + row.original_capacity_u32s = sponge_state[KECCAK_RATE_U32S..] + .iter() + .map(|x| F::from_canonical_u32(*x)) + .collect_vec() + .try_into() + .unwrap(); + + let block_u32s = (0..KECCAK_RATE_U32S).map(|i| { + u32_from_le_bits( + row.block_bits[i * 32..(i + 1) * 32] + .iter() + .map(|f| *f == F::ONE) + .collect_vec() + .try_into() + .unwrap(), + ) + }); + + // xor in the block + for (state_i, block_i) in sponge_state.iter_mut().zip(block_u32s) { + *state_i ^= block_i; + } + let xored_rate_u32s: [u32; KECCAK_RATE_U32S] = sponge_state[..KECCAK_RATE_U32S] + .to_vec() + .try_into() + .unwrap(); + row.xored_rate_u32s = xored_rate_u32s.map(F::from_canonical_u32); + + keccakf_u32s(&mut sponge_state); + row.updated_state_u32s = sponge_state.map(F::from_canonical_u32); + } + + fn generate_padding_row(&self) -> [F; NUM_KECCAK_SPONGE_COLUMNS] { + // We just need is_dummy = 1; the other fields will have no effect. + KeccakSpongeColumnsView { + is_dummy: F::ONE, + ..Default::default() + } + .into() + } +} + +impl, const D: usize> Stark for KeccakSpongeStark { + const COLUMNS: usize = NUM_KECCAK_SPONGE_COLUMNS; + + fn eval_packed_generic( + &self, + vars: StarkEvaluationVars, + _yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + let _local_values: &KeccakSpongeColumnsView

= vars.local_values.borrow(); + + // TODO: Each flag (full-input block, final block or dummy row) must be boolean. + // TODO: before_rate_bits, block_bits and is_final_input_len must contain booleans. + + // TODO: Sum of row type flags (full-input block, final block or dummy row) should equal 1. + + // TODO: Sum of is_final_input_len should equal is_final_block (which will be 0 or 1). + + // TODO: If this is the first row, the original sponge state should be 0 and already_absorbed_bytes = 0. + // TODO: If this is a final block, the next row's original sponge state should be 0 and already_absorbed_bytes = 0. + + // TODO: If this is a full-input block, the next row's address, time and len must match. + // TODO: If this is a full-input block, the next row's "before" should match our "after" state. + // TODO: If this is a full-input block, the next row's already_absorbed_bytes should be ours plus 136. + + // TODO: A dummy row is always followed by another dummy row, so the prover can't put dummy rows "in between" to avoid the above checks. + + // TODO: is_final_input_len implies `len - already_absorbed == i`. + } + + fn eval_ext_circuit( + &self, + _builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + vars: StarkEvaluationTargets, + _yield_constr: &mut RecursiveConstraintConsumer, + ) { + let _local_values: &KeccakSpongeColumnsView> = + vars.local_values.borrow(); + + // TODO + } + + fn constraint_degree(&self) -> usize { + 3 + } +} + +#[cfg(test)] +mod tests { + use std::borrow::Borrow; + + use anyhow::Result; + use itertools::Itertools; + use keccak_hash::keccak; + use plonky2::field::goldilocks_field::GoldilocksField; + use plonky2::field::types::PrimeField64; + use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + use crate::keccak_sponge::columns::KeccakSpongeColumnsView; + use crate::keccak_sponge::keccak_sponge_stark::{KeccakSpongeOp, KeccakSpongeStark}; + use crate::memory::segments::Segment; + 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 = KeccakSpongeStark; + + let stark = S::default(); + test_stark_low_degree(stark) + } + + #[test] + fn test_stark_circuit() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = KeccakSpongeStark; + + let stark = S::default(); + test_stark_circuit_constraints::(stark) + } + + #[test] + fn test_generation() -> Result<()> { + const D: usize = 2; + type F = GoldilocksField; + type S = KeccakSpongeStark; + + let input = vec![1, 2, 3]; + let expected_output = keccak(&input); + + let op = KeccakSpongeOp { + context: 0, + segment: Segment::Code, + virt: 0, + timestamp: 0, + len: input.len(), + input, + }; + let stark = S::default(); + let rows = stark.generate_rows_for_op(op); + assert_eq!(rows.len(), 1); + let last_row: &KeccakSpongeColumnsView = rows.last().unwrap().borrow(); + let output = last_row.updated_state_u32s[..8] + .iter() + .flat_map(|x| (x.to_canonical_u64() as u32).to_le_bytes()) + .collect_vec(); + + assert_eq!(output, expected_output.0); + Ok(()) + } +} diff --git a/evm/src/keccak_sponge/mod.rs b/evm/src/keccak_sponge/mod.rs new file mode 100644 index 00000000..92b7f0c1 --- /dev/null +++ b/evm/src/keccak_sponge/mod.rs @@ -0,0 +1,6 @@ +//! The Keccak sponge STARK is used to hash a variable amount of data which is read from memory. +//! It connects to the memory STARK to read input data, and to the Keccak-f STARK to evaluate the +//! permutation at each absorption step. + +pub mod columns; +pub mod keccak_sponge_stark; diff --git a/evm/src/lib.rs b/evm/src/lib.rs index 4b3fc6b9..6f332b59 100644 --- a/evm/src/lib.rs +++ b/evm/src/lib.rs @@ -15,6 +15,7 @@ pub mod generation; mod get_challenges; pub mod keccak; pub mod keccak_memory; +pub mod keccak_sponge; pub mod logic; pub mod lookup; pub mod memory; diff --git a/evm/src/stark_testing.rs b/evm/src/stark_testing.rs index 5cd83e41..81b0f68f 100644 --- a/evm/src/stark_testing.rs +++ b/evm/src/stark_testing.rs @@ -60,17 +60,20 @@ where }) .collect::>(); - let constraint_eval_degree = PolynomialValues::new(constraint_evals).degree(); - let maximum_degree = WITNESS_SIZE * stark.constraint_degree() - 1; + let constraint_poly_values = PolynomialValues::new(constraint_evals); + if !constraint_poly_values.is_zero() { + let constraint_eval_degree = constraint_poly_values.degree(); + let maximum_degree = WITNESS_SIZE * stark.constraint_degree() - 1; - ensure!( - constraint_eval_degree <= maximum_degree, - "Expected degrees at most {} * {} - 1 = {}, actual {:?}", - WITNESS_SIZE, - stark.constraint_degree(), - maximum_degree, - constraint_eval_degree - ); + ensure!( + constraint_eval_degree <= maximum_degree, + "Expected degrees at most {} * {} - 1 = {}, actual {:?}", + WITNESS_SIZE, + stark.constraint_degree(), + maximum_degree, + constraint_eval_degree + ); + } Ok(()) } diff --git a/evm/src/util.rs b/evm/src/util.rs index ae5281db..89a763e0 100644 --- a/evm/src/util.rs +++ b/evm/src/util.rs @@ -1,3 +1,5 @@ +use std::mem::{size_of, transmute_copy, ManuallyDrop}; + use ethereum_types::{H160, U256}; use itertools::Itertools; use plonky2::field::extension::Extendable; @@ -67,3 +69,35 @@ pub(crate) fn h160_limbs(h160: H160) -> [F; 5] { .try_into() .unwrap() } + +pub(crate) fn u8_to_le_bits(x: u8) -> [bool; 8] { + std::array::from_fn(|i| ((x >> i) & 1) != 0) +} + +pub(crate) fn u32_to_le_bits(x: u32) -> [bool; 32] { + std::array::from_fn(|i| ((x >> i) & 1) != 0) +} + +pub(crate) fn u32_from_le_bits(bits: [bool; 32]) -> u32 { + bits.into_iter() + .rev() + .fold(0, |acc, b| (acc << 1) | b as u32) +} + +pub(crate) const fn indices_arr() -> [usize; N] { + let mut indices_arr = [0; N]; + let mut i = 0; + while i < N { + indices_arr[i] = i; + i += 1; + } + indices_arr +} + +pub(crate) unsafe fn transmute_no_compile_time_size_checks(value: T) -> U { + debug_assert_eq!(size_of::(), size_of::()); + // Need ManuallyDrop so that `value` is not dropped by this function. + let value = ManuallyDrop::new(value); + // Copy the bit pattern. The original value is no longer safe to use. + transmute_copy(&value) +} diff --git a/field/src/polynomial/mod.rs b/field/src/polynomial/mod.rs index 20f1c318..09ed69c7 100644 --- a/field/src/polynomial/mod.rs +++ b/field/src/polynomial/mod.rs @@ -37,6 +37,10 @@ impl PolynomialValues { Self::constant(F::ZERO, len) } + pub fn is_zero(&self) -> bool { + self.values.iter().all(|x| x.is_zero()) + } + /// Returns the polynomial whole value is one at the given index, and zero elsewhere. pub fn selector(len: usize, index: usize) -> Self { let mut result = Self::zero(len);