diff --git a/evm/src/cross_table_lookup.rs b/evm/src/cross_table_lookup.rs index 3d67dd58..83f2083d 100644 --- a/evm/src/cross_table_lookup.rs +++ b/evm/src/cross_table_lookup.rs @@ -56,6 +56,10 @@ impl Column { Self::constant(F::ZERO) } + pub fn one() -> Self { + Self::constant(F::ONE) + } + pub fn linear_combination_with_constant>( iter: I, constant: F, diff --git a/evm/src/keccak_sponge/columns.rs b/evm/src/keccak_sponge/columns.rs index 1cf39138..3fb944fc 100644 --- a/evm/src/keccak_sponge/columns.rs +++ b/evm/src/keccak_sponge/columns.rs @@ -42,16 +42,16 @@ pub(crate) struct KeccakSpongeColumnsView { /// 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 initial rate part of the sponge, at the start of this step. + pub original_rate_u32s: [T; KECCAK_RATE_U32S], - /// The capacity bits of the sponge, encoded as 32-bit chunks, at the start of this step. + /// The capacity part 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], + pub block_bytes: [T; KECCAK_RATE_BYTES], - /// The rate bits of the sponge, after the current block is xor'd in, but before the permutation + /// The rate part 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], diff --git a/evm/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs index 19c80696..f1bb86df 100644 --- a/evm/src/keccak_sponge/keccak_sponge_stark.rs +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -1,6 +1,7 @@ use std::borrow::Borrow; -use std::iter; +use std::iter::{once, repeat}; use std::marker::PhantomData; +use std::mem::size_of; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; @@ -11,6 +12,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::timed; use plonky2::util::timing::TimingTree; +use plonky2_util::ceil_div_usize; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::kernel::keccak_util::keccakf_u32s; @@ -18,7 +20,7 @@ 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::util::trace_rows_to_poly_values; use crate::vars::StarkEvaluationTargets; use crate::vars::StarkEvaluationVars; @@ -39,18 +41,16 @@ pub(crate) fn ctl_looked_data() -> Vec> { #[allow(unused)] // TODO: Should be used soon. pub(crate) fn ctl_looking_keccak() -> Vec> { - let input_rate_cols = (0..KECCAK_RATE_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_CAPACITY_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() + let cols = KECCAK_SPONGE_COL_MAP; + Column::singles( + [ + cols.original_rate_u32s.as_slice(), + &cols.original_capacity_u32s, + &cols.updated_state_u32s, + ] + .concat(), + ) + .collect() } #[allow(unused)] // TODO: Should be used soon. @@ -68,7 +68,7 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { )); // The i'th input byte being read. - res.push(Column::le_bits(&cols.block_bits[i * 8..(i + 1) * 8])); + res.push(Column::single(cols.block_bytes[i])); // Since we're reading a single byte, the higher limbs must be zero. res.extend((1..8).map(|_| Column::zero())); @@ -82,6 +82,49 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { res } +/// CTL for performing the `i`th logic CTL. Since we need to do 136 byte XORs, and the logic CTL can +/// XOR 32 bytes per CTL, there are 5 such CTLs. +#[allow(unused)] // TODO: Should be used soon. +pub(crate) fn ctl_looking_logic(i: usize) -> Vec> { + const U32S_PER_CTL: usize = 8; + const U8S_PER_CTL: usize = 32; + + debug_assert!(i < ceil_div_usize(KECCAK_RATE_BYTES, U8S_PER_CTL)); + let cols = KECCAK_SPONGE_COL_MAP; + + let mut res = vec![ + Column::zero(), // is_and + Column::zero(), // is_or + Column::one(), // is_xor + ]; + + // Input 0 contains some of the sponge's original rate chunks. If this is the last CTL, we won't + // need to use all of the CTL's inputs, so we will pass some zeros. + res.extend( + Column::singles(&cols.original_rate_u32s[i * U32S_PER_CTL..]) + .chain(repeat(Column::zero())) + .take(U32S_PER_CTL), + ); + + // Input 1 contains some of block's chunks. Again, for the last CTL it will include some zeros. + res.extend( + cols.block_bytes[i * U8S_PER_CTL..] + .chunks(size_of::()) + .map(|chunk| Column::le_bytes(chunk)) + .chain(repeat(Column::zero())) + .take(U8S_PER_CTL), + ); + + // The output contains the XOR'd rate part. + res.extend( + Column::singles(&cols.xored_rate_u32s[i * U32S_PER_CTL..]) + .chain(repeat(Column::zero())) + .take(U32S_PER_CTL), + ); + + 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 @@ -96,7 +139,7 @@ pub(crate) fn ctl_looking_memory_filter(i: usize) -> Column { // - 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..])) + Column::sum(once(&cols.is_full_input_block).chain(&cols.is_final_input_len[i..])) } /// Information about a Keccak sponge operation needed for witness generation. @@ -157,7 +200,7 @@ impl, const D: usize> KeccakSpongeStark { operations .into_iter() .flat_map(|op| self.generate_rows_for_op(op)) - .chain(iter::repeat(self.generate_padding_row())) + .chain(repeat(self.generate_padding_row())) .take(num_rows) .collect() } @@ -208,13 +251,7 @@ impl, const D: usize> KeccakSpongeStark { ..Default::default() }; - row.block_bits = block - .into_iter() - .flat_map(u8_to_le_bits) - .map(F::from_bool) - .collect_vec() - .try_into() - .unwrap(); + row.block_bytes = block.map(F::from_canonical_u8); Self::generate_common_fields(&mut row, op, already_absorbed_bytes, sponge_state); row @@ -234,16 +271,18 @@ impl, const D: usize> KeccakSpongeStark { ..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; + for (block_byte, input_byte) in row.block_bytes.iter_mut().zip(final_inputs) { + *block_byte = F::from_canonical_u8(*input_byte); } + // pad10*1 rule - row.block_bits[final_inputs.len() * 8] = F::ONE; - row.block_bits[KECCAK_RATE_BITS - 1] = F::ONE; + if final_inputs.len() == KECCAK_RATE_BYTES - 1 { + // Both 1s are placed in the same byte. + row.block_bytes[final_inputs.len()] = F::from_canonical_u8(0b10000001); + } else { + row.block_bytes[final_inputs.len()] = F::ONE; + row.block_bytes[KECCAK_RATE_BYTES - 1] = F::ONE; + } row.is_final_input_len[final_inputs.len()] = F::ONE; @@ -252,6 +291,7 @@ impl, const D: usize> KeccakSpongeStark { } /// Generate fields that are common to both full-input-block rows and final-block rows. + /// Also updates the sponge state with a single absorption. fn generate_common_fields( row: &mut KeccakSpongeColumnsView, op: &KeccakSpongeOp, @@ -265,10 +305,9 @@ impl, const D: usize> KeccakSpongeStark { 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] + row.original_rate_u32s = sponge_state[..KECCAK_RATE_U32S] .iter() - .flat_map(|x| u32_to_le_bits(*x)) - .map(F::from_bool) + .map(|x| F::from_canonical_u32(*x)) .collect_vec() .try_into() .unwrap(); @@ -281,10 +320,10 @@ impl, const D: usize> KeccakSpongeStark { .unwrap(); let block_u32s = (0..KECCAK_RATE_U32S).map(|i| { - u32_from_le_bits( - row.block_bits[i * 32..(i + 1) * 32] + u32::from_le_bytes( + row.block_bytes[i * 4..(i + 1) * 4] .iter() - .map(Field::is_one) + .map(|x| x.to_canonical_u64() as u8) .collect_vec() .try_into() .unwrap(), diff --git a/evm/src/util.rs b/evm/src/util.rs index 89a763e0..12aead46 100644 --- a/evm/src/util.rs +++ b/evm/src/util.rs @@ -70,20 +70,6 @@ pub(crate) fn h160_limbs(h160: H160) -> [F; 5] { .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;