diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 513004fe..facf300b 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -18,8 +18,12 @@ pest_derive = "2.1.0" rayon = "1.5.1" rand = "0.8.5" rand_chacha = "0.3.1" +rlp = "0.5.1" keccak-rust = { git = "https://github.com/npwardberkeley/keccak-rust" } +[dev-dependencies] +hex-literal = "0.3.4" + [features] asmtools = ["hex"] diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 820ebd1b..f02e0202 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -23,6 +23,18 @@ pub struct AllStark, const D: usize> { pub cross_table_lookups: Vec>, } +impl, const D: usize> Default for AllStark { + fn default() -> Self { + Self { + cpu_stark: CpuStark::default(), + keccak_stark: KeccakStark::default(), + logic_stark: LogicStark::default(), + memory_stark: MemoryStark::default(), + cross_table_lookups: all_cross_table_lookups(), + } + } +} + impl, const D: usize> AllStark { pub(crate) fn nums_permutation_zs(&self, config: &StarkConfig) -> Vec { let ans = vec![ @@ -117,6 +129,7 @@ mod tests { use std::borrow::BorrowMut; use anyhow::Result; + use ethereum_types::U256; use itertools::{izip, Itertools}; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::{Field, PrimeField64}; @@ -127,12 +140,12 @@ mod tests { use plonky2::util::timing::TimingTree; use rand::{thread_rng, Rng}; - use crate::all_stark::{all_cross_table_lookups, AllStark}; + use crate::all_stark::AllStark; use crate::config::StarkConfig; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::testutils::check_ctls; use crate::keccak::keccak_stark::{KeccakStark, NUM_INPUTS, NUM_ROUNDS}; - use crate::logic::{self, LogicStark}; + use crate::logic::{self, LogicStark, Operation}; use crate::memory::memory_stark::{generate_random_memory_ops, MemoryStark}; use crate::memory::NUM_CHANNELS; use crate::proof::AllProof; @@ -165,32 +178,16 @@ mod tests { logic_stark: &LogicStark, rng: &mut R, ) -> Vec> { - let mut trace_rows = vec![]; - for _ in 0..num_rows { - let mut row = [F::ZERO; logic::columns::NUM_COLUMNS]; - - assert_eq!(logic::PACKED_LIMB_BITS, 16); - for col in logic::columns::INPUT0 { - row[col] = F::from_bool(rng.gen()); - } - for col in logic::columns::INPUT1 { - row[col] = F::from_bool(rng.gen()); - } - let op: usize = rng.gen_range(0..3); - let op_col = [ - logic::columns::IS_AND, - logic::columns::IS_OR, - logic::columns::IS_XOR, - ][op]; - row[op_col] = F::ONE; - logic_stark.generate(&mut row); - trace_rows.push(row); - } - - for _ in num_rows..num_rows.next_power_of_two() { - trace_rows.push([F::ZERO; logic::columns::NUM_COLUMNS]) - } - trace_rows_to_poly_values(trace_rows) + let all_ops = [logic::Op::And, logic::Op::Or, logic::Op::Xor]; + let ops = (0..num_rows) + .map(|_| { + let op = all_ops[rng.gen_range(0..all_ops.len())]; + let input0 = U256(rng.gen()); + let input1 = U256(rng.gen()); + Operation::new(op, input0, input1) + }) + .collect(); + logic_stark.generate_trace(ops) } fn make_memory_trace( @@ -239,6 +236,11 @@ mod tests { .collect(); let mut cpu_trace_rows: Vec<[F; CpuStark::::COLUMNS]> = vec![]; + let mut bootstrap_row: cpu::columns::CpuColumnsView = + [F::ZERO; CpuStark::::COLUMNS].into(); + bootstrap_row.is_bootstrap_kernel = F::ONE; + cpu_trace_rows.push(bootstrap_row.into()); + for i in 0..num_keccak_perms { let mut row: cpu::columns::CpuColumnsView = [F::ZERO; CpuStark::::COLUMNS].into(); @@ -315,54 +317,38 @@ mod tests { } } } + + // Pad to a power of two. + for _ in cpu_trace_rows.len()..cpu_trace_rows.len().next_power_of_two() { + cpu_trace_rows.push([F::ZERO; CpuStark::::COLUMNS]); + } trace_rows_to_poly_values(cpu_trace_rows) } fn get_proof(config: &StarkConfig) -> Result<(AllStark, AllProof)> { - let cpu_stark = CpuStark:: { - f: Default::default(), - }; + let all_stark = AllStark::default(); - let keccak_stark = KeccakStark:: { - f: Default::default(), - }; - - let logic_stark = LogicStark:: { - f: Default::default(), - }; let num_logic_rows = 62; - - let memory_stark = MemoryStark:: { - f: Default::default(), - }; let num_memory_ops = 1 << 5; let mut rng = thread_rng(); let num_keccak_perms = 2; - let keccak_trace = make_keccak_trace(num_keccak_perms, &keccak_stark, &mut rng); - let logic_trace = make_logic_trace(num_logic_rows, &logic_stark, &mut rng); - let mem_trace = make_memory_trace(num_memory_ops, &memory_stark, &mut rng); + let keccak_trace = make_keccak_trace(num_keccak_perms, &all_stark.keccak_stark, &mut rng); + let logic_trace = make_logic_trace(num_logic_rows, &all_stark.logic_stark, &mut rng); + let mem_trace = make_memory_trace(num_memory_ops, &all_stark.memory_stark, &mut rng); let mut memory_trace = mem_trace.0; let num_memory_ops = mem_trace.1; let cpu_trace = make_cpu_trace( num_keccak_perms, num_logic_rows, num_memory_ops, - &cpu_stark, + &all_stark.cpu_stark, &keccak_trace, &logic_trace, &mut memory_trace, ); - let all_stark = AllStark { - cpu_stark, - keccak_stark, - logic_stark, - memory_stark, - cross_table_lookups: all_cross_table_lookups(), - }; - let traces = vec![cpu_trace, keccak_trace, logic_trace, memory_trace]; check_ctls(&traces, &all_stark.cross_table_lookups); diff --git a/evm/src/cpu/bootstrap_kernel.rs b/evm/src/cpu/bootstrap_kernel.rs new file mode 100644 index 00000000..ba10b70e --- /dev/null +++ b/evm/src/cpu/bootstrap_kernel.rs @@ -0,0 +1,76 @@ +//! The initial phase of execution, where the kernel code is hashed while being written to memory. +//! The hash is then checked against a precomputed kernel hash. + +use itertools::Itertools; +use plonky2::field::extension::Extendable; +use plonky2::field::packed::PackedField; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; +use plonky2::plonk::circuit_builder::CircuitBuilder; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cpu::columns::{COL_MAP, NUM_CPU_COLUMNS}; +use crate::cpu::public_inputs::NUM_PUBLIC_INPUTS; +use crate::generation::state::GenerationState; +use crate::memory; +use crate::memory::segments; +use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; + +pub(crate) fn generate_bootstrap_kernel(state: &mut GenerationState) { + for chunk in &state.kernel.code.clone().into_iter().enumerate().chunks(4) { + for (addr, byte) in chunk { + let mut value = [F::ZERO; memory::VALUE_LIMBS]; + value[0] = F::from_canonical_u8(byte); + + let channel = addr % memory::NUM_CHANNELS; + state.set_mem_current(channel, segments::CODE, addr, value); + + // TODO: Set other registers. + + state.commit_cpu_row(); + } + } +} + +pub(crate) fn eval_bootstrap_kernel>( + vars: StarkEvaluationVars, + yield_constr: &mut ConstraintConsumer

, +) { + // IS_BOOTSTRAP_KERNEL must have an init value of 1, a final value of 0, and a delta in {0, -1}. + let local_is_bootstrap = vars.local_values[COL_MAP.is_bootstrap_kernel]; + let next_is_bootstrap = vars.next_values[COL_MAP.is_bootstrap_kernel]; + yield_constr.constraint_first_row(local_is_bootstrap - P::ONES); + yield_constr.constraint_last_row(local_is_bootstrap); + let delta_is_bootstrap = next_is_bootstrap - local_is_bootstrap; + yield_constr.constraint_transition(delta_is_bootstrap * (delta_is_bootstrap + P::ONES)); + + // If IS_BOOTSTRAP_KERNEL changed (from 1 to 0), check that the current kernel hash matches a + // precomputed one. + let hash_diff = F::ZERO; // TODO + yield_constr.constraint_transition(delta_is_bootstrap * hash_diff) +} + +pub(crate) fn eval_bootstrap_kernel_circuit, const D: usize>( + builder: &mut CircuitBuilder, + vars: StarkEvaluationTargets, + yield_constr: &mut RecursiveConstraintConsumer, +) { + let one = builder.one_extension(); + + // IS_BOOTSTRAP_KERNEL must have an init value of 1, a final value of 0, and a delta in {0, -1}. + let local_is_bootstrap = vars.local_values[COL_MAP.is_bootstrap_kernel]; + let next_is_bootstrap = vars.next_values[COL_MAP.is_bootstrap_kernel]; + let constraint = builder.sub_extension(local_is_bootstrap, one); + yield_constr.constraint_first_row(builder, constraint); + yield_constr.constraint_last_row(builder, local_is_bootstrap); + let delta_is_bootstrap = builder.sub_extension(next_is_bootstrap, local_is_bootstrap); + let constraint = + builder.mul_add_extension(delta_is_bootstrap, delta_is_bootstrap, delta_is_bootstrap); + yield_constr.constraint_transition(builder, constraint); + + // If IS_BOOTSTRAP_KERNEL changed (from 1 to 0), check that the current kernel hash matches a + // precomputed one. + let hash_diff = builder.zero_extension(); // TODO + let constraint = builder.mul_extension(delta_is_bootstrap, hash_diff); + yield_constr.constraint_transition(builder, constraint) +} diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index ad32dd98..1e5cc887 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -9,7 +9,7 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; -use crate::cpu::{decode, simple_logic}; +use crate::cpu::{bootstrap_kernel, decode, simple_logic}; use crate::cross_table_lookup::Column; use crate::memory::NUM_CHANNELS; use crate::stark::Stark; @@ -62,7 +62,7 @@ pub fn ctl_filter_memory(channel: usize) -> Column { Column::single(COL_MAP.mem_channel_used[channel]) } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] pub struct CpuStark { pub f: PhantomData, } @@ -88,6 +88,7 @@ impl, const D: usize> Stark for CpuStark, { let local_values = vars.local_values.borrow(); + bootstrap_kernel::eval_bootstrap_kernel(vars, yield_constr); decode::eval_packed_generic(local_values, yield_constr); simple_logic::eval_packed(local_values, yield_constr); } @@ -99,6 +100,7 @@ impl, const D: usize> Stark for CpuStark, ) { let local_values = vars.local_values.borrow(); + bootstrap_kernel::eval_bootstrap_kernel_circuit(builder, vars, yield_constr); decode::eval_ext_circuit(builder, local_values, yield_constr); simple_logic::eval_ext_circuit(builder, local_values, yield_constr); } diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 800f5094..179e9367 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -17,7 +17,7 @@ const BYTES_PER_OFFSET: u8 = 3; #[derive(PartialEq, Eq, Debug)] pub struct Kernel { - pub code: Vec, + pub(crate) code: Vec, pub(crate) global_labels: HashMap, } diff --git a/evm/src/cpu/kernel/mod.rs b/evm/src/cpu/kernel/mod.rs index dd75a68a..ae765b99 100644 --- a/evm/src/cpu/kernel/mod.rs +++ b/evm/src/cpu/kernel/mod.rs @@ -1,5 +1,5 @@ pub mod aggregator; -mod assembler; +pub mod assembler; mod ast; mod opcodes; mod parser; diff --git a/evm/src/cpu/mod.rs b/evm/src/cpu/mod.rs index 14a11b1c..8da8a125 100644 --- a/evm/src/cpu/mod.rs +++ b/evm/src/cpu/mod.rs @@ -1,5 +1,7 @@ +pub(crate) mod bootstrap_kernel; pub(crate) mod columns; pub mod cpu_stark; pub(crate) mod decode; pub mod kernel; +pub mod public_inputs; mod simple_logic; diff --git a/evm/src/cpu/public_inputs.rs b/evm/src/cpu/public_inputs.rs new file mode 100644 index 00000000..0a02e406 --- /dev/null +++ b/evm/src/cpu/public_inputs.rs @@ -0,0 +1 @@ +pub const NUM_PUBLIC_INPUTS: usize = 0; // PIs will be added later. diff --git a/evm/src/generation/memory.rs b/evm/src/generation/memory.rs new file mode 100644 index 00000000..dfff4388 --- /dev/null +++ b/evm/src/generation/memory.rs @@ -0,0 +1,52 @@ +use plonky2::field::types::Field; + +use crate::memory::memory_stark::MemoryOp; +use crate::memory::segments::NUM_SEGMENTS; +use crate::memory::VALUE_LIMBS; + +#[allow(unused)] // TODO: Should be used soon. +#[derive(Debug)] +pub(crate) struct MemoryState { + /// A log of each memory operation, in the order that it occurred. + pub log: Vec>, + + pub contexts: Vec>, +} + +impl Default for MemoryState { + fn default() -> Self { + Self { + log: vec![], + // We start with an initial context for the kernel. + contexts: vec![MemoryContextState::default()], + } + } +} + +#[derive(Default, Debug)] +pub(crate) struct MemoryContextState { + /// The content of each memory segment. + pub segments: [MemorySegmentState; NUM_SEGMENTS], +} + +#[derive(Default, Debug)] +pub(crate) struct MemorySegmentState { + pub content: Vec<[F; VALUE_LIMBS]>, +} + +impl MemorySegmentState { + pub(super) fn get(&self, virtual_addr: usize) -> [F; VALUE_LIMBS] { + self.content + .get(virtual_addr) + .copied() + .unwrap_or([F::ZERO; VALUE_LIMBS]) + } + + pub(super) fn set(&mut self, virtual_addr: usize, value: [F; VALUE_LIMBS]) { + if virtual_addr + 1 > self.content.len() { + self.content + .resize(virtual_addr + 1, [F::ZERO; VALUE_LIMBS]); + } + self.content[virtual_addr] = value; + } +} diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs new file mode 100644 index 00000000..4eb3cbbf --- /dev/null +++ b/evm/src/generation/mod.rs @@ -0,0 +1,60 @@ +use plonky2::field::extension::Extendable; +use plonky2::field::polynomial::PolynomialValues; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; + +use crate::all_stark::AllStark; +use crate::cpu::bootstrap_kernel::generate_bootstrap_kernel; +use crate::cpu::columns::NUM_CPU_COLUMNS; +use crate::generation::state::GenerationState; +use crate::util::trace_rows_to_poly_values; + +mod memory; +pub(crate) mod state; + +pub type RlpBlob = Vec; + +/// Merkle proofs are encoded using an RLP blob for each node in the path. +pub type RlpMerkleProof = Vec; + +#[allow(unused)] // TODO: Should be used soon. +pub struct TransactionData { + pub signed_txn: Vec, + + /// A Merkle proof for each interaction with the state trie, ordered chronologically. + pub trie_proofs: Vec, +} + +#[allow(unused)] // TODO: Should be used soon. +pub fn generate_traces, const D: usize>( + all_stark: &AllStark, + txns: &[TransactionData], +) -> Vec>> { + let mut state = GenerationState::::default(); + + generate_bootstrap_kernel::(&mut state); + + for txn in txns { + generate_txn(&mut state, txn); + } + + let GenerationState { + cpu_rows, + current_cpu_row, + memory, + keccak_inputs, + logic_ops: logic_inputs, + .. + } = state; + assert_eq!(current_cpu_row, [F::ZERO; NUM_CPU_COLUMNS]); + + let cpu_trace = trace_rows_to_poly_values(cpu_rows); + let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs); + let logic_trace = all_stark.logic_stark.generate_trace(logic_inputs); + let memory_trace = all_stark.memory_stark.generate_trace(memory.log); + vec![cpu_trace, keccak_trace, logic_trace, memory_trace] +} + +fn generate_txn(_state: &mut GenerationState, _txn: &TransactionData) { + todo!() +} diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs new file mode 100644 index 00000000..4dbd90fe --- /dev/null +++ b/evm/src/generation/state.rs @@ -0,0 +1,118 @@ +use ethereum_types::U256; +use plonky2::field::types::Field; + +use crate::cpu::columns::NUM_CPU_COLUMNS; +use crate::cpu::kernel::aggregator::combined_kernel; +use crate::cpu::kernel::assembler::Kernel; +use crate::generation::memory::MemoryState; +use crate::logic::{Op, Operation}; +use crate::memory::memory_stark::MemoryOp; +use crate::{keccak, logic}; + +#[derive(Debug)] +pub(crate) struct GenerationState { + pub(crate) kernel: Kernel, + + pub(crate) cpu_rows: Vec<[F; NUM_CPU_COLUMNS]>, + pub(crate) current_cpu_row: [F; NUM_CPU_COLUMNS], + + pub(crate) current_context: usize, + pub(crate) memory: MemoryState, + + pub(crate) keccak_inputs: Vec<[u64; keccak::keccak_stark::NUM_INPUTS]>, + pub(crate) logic_ops: Vec, +} + +impl GenerationState { + /// Compute logical AND, and record the operation to be added in the logic table later. + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn and(&mut self, input0: U256, input1: U256) -> U256 { + self.logic_op(Op::And, input0, input1) + } + + /// Compute logical OR, and record the operation to be added in the logic table later. + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn or(&mut self, input0: U256, input1: U256) -> U256 { + self.logic_op(Op::Or, input0, input1) + } + + /// Compute logical XOR, and record the operation to be added in the logic table later. + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn xor(&mut self, input0: U256, input1: U256) -> U256 { + self.logic_op(Op::Xor, input0, input1) + } + + /// Compute logical AND, and record the operation to be added in the logic table later. + pub(crate) fn logic_op(&mut self, op: Op, input0: U256, input1: U256) -> U256 { + let operation = Operation::new(op, input0, input1); + let result = operation.result; + self.logic_ops.push(operation); + result + } + + /// Read some memory within the current execution context, and log the operation. + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn get_mem_current( + &mut self, + channel_index: usize, + segment: usize, + virt: usize, + ) -> [F; crate::memory::VALUE_LIMBS] { + let timestamp = self.cpu_rows.len(); + let context = self.current_context; + let value = self.memory.contexts[context].segments[segment].get(virt); + self.memory.log.push(MemoryOp { + channel_index, + timestamp, + is_read: true, + context, + segment, + virt, + value, + }); + value + } + + /// Write some memory within the current execution context, and log the operation. + pub(crate) fn set_mem_current( + &mut self, + channel_index: usize, + segment: usize, + virt: usize, + value: [F; crate::memory::VALUE_LIMBS], + ) { + let timestamp = self.cpu_rows.len(); + let context = self.current_context; + self.memory.log.push(MemoryOp { + channel_index, + timestamp, + is_read: false, + context, + segment, + virt, + value, + }); + self.memory.contexts[context].segments[segment].set(virt, value) + } + + pub(crate) fn commit_cpu_row(&mut self) { + self.cpu_rows.push(self.current_cpu_row); + self.current_cpu_row = [F::ZERO; NUM_CPU_COLUMNS]; + } +} + +// `GenerationState` can't `derive(Default)` because `Default` is only implemented for arrays up to +// length 32 :-\. +impl Default for GenerationState { + fn default() -> Self { + Self { + kernel: combined_kernel(), + cpu_rows: vec![], + current_cpu_row: [F::ZERO; NUM_CPU_COLUMNS], + current_context: 0, + memory: MemoryState::default(), + keccak_inputs: vec![], + logic_ops: vec![], + } + } +} diff --git a/evm/src/keccak/keccak_stark.rs b/evm/src/keccak/keccak_stark.rs index f6768eef..53dd66ab 100644 --- a/evm/src/keccak/keccak_stark.rs +++ b/evm/src/keccak/keccak_stark.rs @@ -44,7 +44,7 @@ pub fn ctl_filter() -> Column { Column::single(reg_step(NUM_ROUNDS - 1)) } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] pub struct KeccakStark { pub(crate) f: PhantomData, } diff --git a/evm/src/lib.rs b/evm/src/lib.rs index 1d91e8e0..47335db2 100644 --- a/evm/src/lib.rs +++ b/evm/src/lib.rs @@ -10,6 +10,7 @@ pub mod config; pub mod constraint_consumer; pub mod cpu; pub mod cross_table_lookup; +pub mod generation; mod get_challenges; pub mod keccak; pub mod logic; diff --git a/evm/src/logic.rs b/evm/src/logic.rs index cb8d8604..bde5d645 100644 --- a/evm/src/logic.rs +++ b/evm/src/logic.rs @@ -1,15 +1,17 @@ use std::marker::PhantomData; +use ethereum_types::U256; use itertools::izip; 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 crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; use crate::stark::Stark; -use crate::util::{limb_from_bits_le, limb_from_bits_le_recursive}; +use crate::util::{limb_from_bits_le, limb_from_bits_le_recursive, trace_rows_to_poly_values}; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; // Total number of bits per input/output. @@ -61,71 +63,78 @@ pub fn ctl_filter() -> Column { Column::sum([columns::IS_AND, columns::IS_OR, columns::IS_XOR]) } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] pub struct LogicStark { pub f: PhantomData, } -enum Op { - // The `Zero` op is just for convenience. The all-zero row already satisfies the constraints; - // `Zero` lets us call `generate` on it without crashing. - Zero, +#[derive(Copy, Clone, Debug)] +pub(crate) enum Op { And, Or, Xor, } -fn check_op_flags(lv: &[F; columns::NUM_COLUMNS]) -> Op { - let is_and = lv[columns::IS_AND].to_canonical_u64(); - assert!(is_and <= 1); - let is_or = lv[columns::IS_OR].to_canonical_u64(); - assert!(is_or <= 1); - let is_xor = lv[columns::IS_XOR].to_canonical_u64(); - assert!(is_xor <= 1); - assert!(is_and + is_or + is_xor <= 1); - if is_and == 1 { - Op::And - } else if is_or == 1 { - Op::Or - } else if is_xor == 1 { - Op::Xor - } else { - Op::Zero - } +#[derive(Debug)] +pub(crate) struct Operation { + operator: Op, + input0: U256, + input1: U256, + pub(crate) result: U256, } -fn check_bits(lv: &[F; columns::NUM_COLUMNS]) { - for bit_cols in [columns::INPUT0, columns::INPUT1] { - for bit_col in bit_cols { - let bit = lv[bit_col].to_canonical_u64(); - assert!(bit <= 1); +impl Operation { + pub(crate) fn new(operator: Op, input0: U256, input1: U256) -> Self { + let result = match operator { + Op::And => input0 & input1, + Op::Or => input0 | input1, + Op::Xor => input0 ^ input1, + }; + Operation { + operator, + input0, + input1, + result, } } } -fn make_result(lv: &mut [F; columns::NUM_COLUMNS], op: Op) { - for (res_col, limb_in0_cols, limb_in1_cols) in izip!( - columns::RESULT, - columns::limb_bit_cols_for_input(columns::INPUT0), - columns::limb_bit_cols_for_input(columns::INPUT1), - ) { - let limb_in0: u64 = limb_from_bits_le(limb_in0_cols.map(|col| lv[col])).to_canonical_u64(); - let limb_in1: u64 = limb_from_bits_le(limb_in1_cols.map(|col| lv[col])).to_canonical_u64(); - let res = match op { - Op::Zero => 0, - Op::And => limb_in0 & limb_in1, - Op::Or => limb_in0 | limb_in1, - Op::Xor => limb_in0 ^ limb_in1, - }; - lv[res_col] = F::from_canonical_u64(res); - } -} - impl LogicStark { - pub fn generate(&self, lv: &mut [F; columns::NUM_COLUMNS]) { - let op = check_op_flags(lv); - check_bits(lv); - make_result(lv, op); + pub(crate) fn generate_trace(&self, operations: Vec) -> Vec> { + let len = operations.len(); + let padded_len = len.next_power_of_two(); + + let mut rows = Vec::with_capacity(padded_len); + for op in operations { + rows.push(Self::generate_row(op)); + } + + // Pad to a power of two. + for _ in len..padded_len { + rows.push([F::ZERO; columns::NUM_COLUMNS]); + } + + trace_rows_to_poly_values(rows) + } + + fn generate_row(operation: Operation) -> [F; columns::NUM_COLUMNS] { + let mut row = [F::ZERO; columns::NUM_COLUMNS]; + match operation.operator { + Op::And => row[columns::IS_AND] = F::ONE, + Op::Or => row[columns::IS_OR] = F::ONE, + Op::Xor => row[columns::IS_XOR] = F::ONE, + } + for (i, col) in columns::INPUT0.enumerate() { + row[col] = F::from_bool(operation.input0.bit(i)); + } + for (i, col) in columns::INPUT1.enumerate() { + row[col] = F::from_bool(operation.input1.bit(i)); + } + for (i, col) in columns::RESULT.enumerate() { + let bit_range = i * PACKED_LIMB_BITS..(i + 1) * PACKED_LIMB_BITS; + row[col] = limb_from_bits_le(bit_range.map(|j| F::from_bool(operation.result.bit(j)))); + } + row } } diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index 49ee1ee2..17da621b 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -41,19 +41,20 @@ pub fn ctl_filter(channel: usize) -> Column { Column::single(is_channel(channel)) } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] pub struct MemoryStark { pub(crate) f: PhantomData, } +#[derive(Debug)] pub struct MemoryOp { - channel_index: usize, - timestamp: F, - is_read: F, - context: F, - segment: F, - virt: F, - value: [F; 8], + pub channel_index: usize, + pub timestamp: usize, + pub is_read: bool, + pub context: usize, + pub segment: usize, + pub virt: usize, + pub value: [F; 8], } pub fn generate_random_memory_ops( @@ -62,7 +63,7 @@ pub fn generate_random_memory_ops( ) -> Vec> { let mut memory_ops = Vec::new(); - let mut current_memory_values: HashMap<(F, F, F), [F; 8]> = HashMap::new(); + let mut current_memory_values: HashMap<(usize, usize, usize), [F; 8]> = HashMap::new(); let num_cycles = num_ops / 2; for clock in 0..num_cycles { let mut used_indices = HashSet::new(); @@ -81,7 +82,6 @@ pub fn generate_random_memory_ops( !has_read && rng.gen() }; has_read = has_read || is_read; - let is_read_field = F::from_bool(is_read); let (context, segment, virt, vals) = if is_read { let written: Vec<_> = current_memory_values.keys().collect(); @@ -94,13 +94,13 @@ pub fn generate_random_memory_ops( } else { // TODO: with taller memory table or more padding (to enable range-checking bigger diffs), // test larger address values. - let mut context = F::from_canonical_usize(rng.gen_range(0..40)); - let mut segment = F::from_canonical_usize(rng.gen_range(0..8)); - let mut virt = F::from_canonical_usize(rng.gen_range(0..20)); + let mut context = rng.gen_range(0..40); + let mut segment = rng.gen_range(0..8); + let mut virt = rng.gen_range(0..20); while new_writes_this_cycle.contains_key(&(context, segment, virt)) { - context = F::from_canonical_usize(rng.gen_range(0..40)); - segment = F::from_canonical_usize(rng.gen_range(0..8)); - virt = F::from_canonical_usize(rng.gen_range(0..20)); + context = rng.gen_range(0..40); + segment = rng.gen_range(0..8); + virt = rng.gen_range(0..20); } let val: [u32; 8] = rng.gen(); @@ -111,11 +111,11 @@ pub fn generate_random_memory_ops( (context, segment, virt, vals) }; - let timestamp = F::from_canonical_usize(clock * NUM_CHANNELS + channel_index); + let timestamp = clock * NUM_CHANNELS + channel_index; memory_ops.push(MemoryOp { channel_index, timestamp, - is_read: is_read_field, + is_read, context, segment, virt, @@ -243,11 +243,11 @@ impl, const D: usize> MemoryStark { value, } = memory_ops[i]; trace_cols[is_channel(channel_index)][i] = F::ONE; - trace_cols[TIMESTAMP][i] = timestamp; - trace_cols[IS_READ][i] = is_read; - trace_cols[ADDR_CONTEXT][i] = context; - trace_cols[ADDR_SEGMENT][i] = segment; - trace_cols[ADDR_VIRTUAL][i] = virt; + trace_cols[TIMESTAMP][i] = F::from_canonical_usize(timestamp); + trace_cols[IS_READ][i] = F::from_bool(is_read); + trace_cols[ADDR_CONTEXT][i] = F::from_canonical_usize(context); + trace_cols[ADDR_SEGMENT][i] = F::from_canonical_usize(segment); + trace_cols[ADDR_VIRTUAL][i] = F::from_canonical_usize(virt); for j in 0..8 { trace_cols[value_limb(j)][i] = value[j]; } diff --git a/evm/src/memory/mod.rs b/evm/src/memory/mod.rs index 395eb6fb..83f9c308 100644 --- a/evm/src/memory/mod.rs +++ b/evm/src/memory/mod.rs @@ -1,5 +1,6 @@ pub mod columns; pub mod memory_stark; +pub mod segments; pub(crate) const NUM_CHANNELS: usize = 4; pub(crate) const VALUE_LIMBS: usize = 8; diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs new file mode 100644 index 00000000..d20cb037 --- /dev/null +++ b/evm/src/memory/segments.rs @@ -0,0 +1,22 @@ +/// Contains EVM bytecode. +pub const CODE: usize = 0; + +pub const STACK: usize = 1; + +/// Main memory, owned by the contract code. +pub const MAIN_MEM: usize = 2; + +/// Memory owned by the kernel. +pub const KERNEL_MEM: usize = 3; + +/// Data passed to the current context by its caller. +pub const CALLDATA: usize = 4; + +/// Data returned to the current context by its latest callee. +pub const RETURNDATA: usize = 5; + +/// A segment which contains a few fixed-size metadata fields, such as the caller's context, or the +/// size of `CALLDATA` and `RETURNDATA`. +pub const METADATA: usize = 6; + +pub const NUM_SEGMENTS: usize = 7; diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 438a0be7..2d398d1a 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -1,3 +1,5 @@ +use std::any::type_name; + use anyhow::{ensure, Result}; use plonky2::field::extension::Extendable; use plonky2::field::packable::Packable; @@ -563,6 +565,10 @@ fn check_constraints<'a, F, C, S, const D: usize>( .collect::>(); for v in constraint_values { - assert!(v.iter().all(|x| x.is_zero())); + assert!( + v.iter().all(|x| x.is_zero()), + "Constraint failed in {}", + type_name::() + ); } } diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs new file mode 100644 index 00000000..c30e7b7b --- /dev/null +++ b/evm/tests/transfer_to_new_addr.rs @@ -0,0 +1,46 @@ +use hex_literal::hex; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::plonk::config::PoseidonGoldilocksConfig; +use plonky2::util::timing::TimingTree; +use plonky2_evm::all_stark::AllStark; +use plonky2_evm::config::StarkConfig; +use plonky2_evm::generation::{generate_traces, TransactionData}; +use plonky2_evm::prover::prove; +use plonky2_evm::verifier::verify_proof; + +type F = GoldilocksField; +const D: usize = 2; +type C = PoseidonGoldilocksConfig; + +/// Test a simple token transfer to a new address. +#[test] +#[ignore] // TODO: Won't work until txn parsing, storage, etc. are implemented. +fn test_simple_transfer() -> anyhow::Result<()> { + let all_stark = AllStark::::default(); + + let txn = TransactionData { + signed_txn: hex!("f85f050a82520894000000000000000000000000000000000000000064801ca0fa56df5d988638fad8798e5ef75a1e1125dc7fb55d2ac4bce25776a63f0c2967a02cb47a5579eb5f83a1cabe4662501c0059f1b58e60ef839a1b0da67af6b9fb38").to_vec(), + trie_proofs: vec![ + vec![ + hex!("f874a1202f93d0dfb1562c03c825a33eec4438e468c17fff649ae844c004065985ae2945b850f84e058a152d02c7e14af6800000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").to_vec(), + ], + vec![ + hex!("f8518080a0d36b8b6b60021940d5553689fb33e5d45e649dd8f4f211d26566238a83169da58080a0c62aa627943b70321f89a8b2fea274ecd47116e62042077dcdc0bdca7c1f66738080808080808080808080").to_vec(), + hex!("f873a03f93d0dfb1562c03c825a33eec4438e468c17fff649ae844c004065985ae2945b850f84e068a152d02c7e14af67ccb4ca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").to_vec(), + ], + ] + }; + + let traces = generate_traces(&all_stark, &[txn]); + + let config = StarkConfig::standard_fast_config(); + let proof = prove::( + &all_stark, + &config, + traces, + vec![vec![]; 4], + &mut TimingTree::default(), + )?; + + verify_proof(all_stark, proof, &config) +} diff --git a/field/src/types.rs b/field/src/types.rs index 97c8776b..81945e5a 100644 --- a/field/src/types.rs +++ b/field/src/types.rs @@ -284,6 +284,12 @@ pub trait Field: Self::from_canonical_u64(n as u64) } + /// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < Self::order()`. + // TODO: Should probably be unsafe. + fn from_canonical_u8(n: u8) -> Self { + Self::from_canonical_u64(n as u64) + } + /// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < Self::order()`. // TODO: Should probably be unsafe. fn from_canonical_usize(n: usize) -> Self {