diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index ee779531..4ef79314 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -56,10 +56,10 @@ mod tests { use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use plonky2::util::timing::TimingTree; - use rand::{thread_rng, Rng}; use crate::all_stark::{AllStark, Table}; use crate::config::StarkConfig; + use crate::cpu; use crate::cpu::cpu_stark::CpuStark; use crate::cross_table_lookup::CrossTableLookup; use crate::keccak::keccak_stark::KeccakStark; @@ -68,6 +68,8 @@ mod tests { use crate::recursive_verifier::{ add_virtual_all_proof, set_all_proof_target, verify_proof_circuit, }; + use crate::stark::Stark; + use crate::util::trace_rows_to_poly_values; use crate::verifier::verify_proof; const D: usize = 2; @@ -78,39 +80,34 @@ mod tests { let cpu_stark = CpuStark:: { f: Default::default(), }; - let cpu_rows = 1 << 4; + let cpu_rows = 256; let keccak_stark = KeccakStark:: { f: Default::default(), }; - let keccak_rows = 1 << 3; + let keccak_rows = 256; + let keccak_looked_col = 3; - let mut cpu_trace = vec![PolynomialValues::zero(cpu_rows); 10]; - let mut keccak_trace = vec![PolynomialValues::zero(keccak_rows); 7]; + let mut cpu_trace_rows = vec![]; + for i in 0..cpu_rows { + let mut cpu_trace_row = [F::ZERO; CpuStark::::COLUMNS]; + cpu_trace_row[cpu::columns::IS_CPU_CYCLE] = F::ONE; + cpu_trace_row[cpu::columns::OPCODE] = F::from_canonical_usize(i); + cpu_stark.generate(&mut cpu_trace_row); + cpu_trace_rows.push(cpu_trace_row); + } + let cpu_trace = trace_rows_to_poly_values(cpu_trace_rows); - let vs0 = (0..keccak_rows) - .map(F::from_canonical_usize) - .collect::>(); - let vs1 = (1..=keccak_rows) - .map(F::from_canonical_usize) - .collect::>(); - let start = thread_rng().gen_range(0..cpu_rows - keccak_rows); - - let default = vec![F::ONE; 2]; - - cpu_trace[2].values = vec![default[0]; cpu_rows]; - cpu_trace[2].values[start..start + keccak_rows].copy_from_slice(&vs0); - cpu_trace[4].values = vec![default[1]; cpu_rows]; - cpu_trace[4].values[start..start + keccak_rows].copy_from_slice(&vs1); - - keccak_trace[3].values[..].copy_from_slice(&vs0); - keccak_trace[5].values[..].copy_from_slice(&vs1); + let mut keccak_trace = + vec![PolynomialValues::zero(keccak_rows); KeccakStark::::COLUMNS]; + keccak_trace[keccak_looked_col] = cpu_trace[cpu::columns::OPCODE].clone(); + let default = vec![F::ZERO; 2]; let cross_table_lookups = vec![CrossTableLookup { looking_tables: vec![Table::Cpu], - looking_columns: vec![vec![2, 4]], + looking_columns: vec![vec![cpu::columns::OPCODE]], looked_table: Table::Keccak, - looked_columns: vec![3, 5], + looked_columns: vec![keccak_looked_col], default, }]; diff --git a/evm/src/cpu/columns.rs b/evm/src/cpu/columns.rs new file mode 100644 index 00000000..d2b2e4bf --- /dev/null +++ b/evm/src/cpu/columns.rs @@ -0,0 +1,135 @@ +// Filter. 1 if the row corresponds to a cycle of execution and 0 otherwise. +// Lets us re-use decode columns in non-cycle rows. +pub const IS_CPU_CYCLE: usize = 0; + +// If CPU cycle: The opcode being decoded, in {0, ..., 255}. +pub const OPCODE: usize = IS_CPU_CYCLE + 1; + +// If CPU cycle: flags for EVM instructions. PUSHn, DUPn, and SWAPn only get one flag each. Invalid +// opcodes are split between a number of flags for practical reasons. Exactly one of these flags +// must be 1. +pub const IS_STOP: usize = OPCODE + 1; +pub const IS_ADD: usize = IS_STOP + 1; +pub const IS_MUL: usize = IS_ADD + 1; +pub const IS_SUB: usize = IS_MUL + 1; +pub const IS_DIV: usize = IS_SUB + 1; +pub const IS_SDIV: usize = IS_DIV + 1; +pub const IS_MOD: usize = IS_SDIV + 1; +pub const IS_SMOD: usize = IS_MOD + 1; +pub const IS_ADDMOD: usize = IS_SMOD + 1; +pub const IS_MULMOD: usize = IS_ADDMOD + 1; +pub const IS_EXP: usize = IS_MULMOD + 1; +pub const IS_SIGNEXTEND: usize = IS_EXP + 1; +pub const IS_LT: usize = IS_SIGNEXTEND + 1; +pub const IS_GT: usize = IS_LT + 1; +pub const IS_SLT: usize = IS_GT + 1; +pub const IS_SGT: usize = IS_SLT + 1; +pub const IS_EQ: usize = IS_SGT + 1; +pub const IS_ISZERO: usize = IS_EQ + 1; +pub const IS_AND: usize = IS_ISZERO + 1; +pub const IS_OR: usize = IS_AND + 1; +pub const IS_XOR: usize = IS_OR + 1; +pub const IS_NOT: usize = IS_XOR + 1; +pub const IS_BYTE: usize = IS_NOT + 1; +pub const IS_SHL: usize = IS_BYTE + 1; +pub const IS_SHR: usize = IS_SHL + 1; +pub const IS_SAR: usize = IS_SHR + 1; +pub const IS_SHA3: usize = IS_SAR + 1; +pub const IS_ADDRESS: usize = IS_SHA3 + 1; +pub const IS_BALANCE: usize = IS_ADDRESS + 1; +pub const IS_ORIGIN: usize = IS_BALANCE + 1; +pub const IS_CALLER: usize = IS_ORIGIN + 1; +pub const IS_CALLVALUE: usize = IS_CALLER + 1; +pub const IS_CALLDATALOAD: usize = IS_CALLVALUE + 1; +pub const IS_CALLDATASIZE: usize = IS_CALLDATALOAD + 1; +pub const IS_CALLDATACOPY: usize = IS_CALLDATASIZE + 1; +pub const IS_CODESIZE: usize = IS_CALLDATACOPY + 1; +pub const IS_CODECOPY: usize = IS_CODESIZE + 1; +pub const IS_GASPRICE: usize = IS_CODECOPY + 1; +pub const IS_EXTCODESIZE: usize = IS_GASPRICE + 1; +pub const IS_EXTCODECOPY: usize = IS_EXTCODESIZE + 1; +pub const IS_RETURNDATASIZE: usize = IS_EXTCODECOPY + 1; +pub const IS_RETURNDATACOPY: usize = IS_RETURNDATASIZE + 1; +pub const IS_EXTCODEHASH: usize = IS_RETURNDATACOPY + 1; +pub const IS_BLOCKHASH: usize = IS_EXTCODEHASH + 1; +pub const IS_COINBASE: usize = IS_BLOCKHASH + 1; +pub const IS_TIMESTAMP: usize = IS_COINBASE + 1; +pub const IS_NUMBER: usize = IS_TIMESTAMP + 1; +pub const IS_DIFFICULTY: usize = IS_NUMBER + 1; +pub const IS_GASLIMIT: usize = IS_DIFFICULTY + 1; +pub const IS_CHAINID: usize = IS_GASLIMIT + 1; +pub const IS_SELFBALANCE: usize = IS_CHAINID + 1; +pub const IS_BASEFEE: usize = IS_SELFBALANCE + 1; +pub const IS_POP: usize = IS_BASEFEE + 1; +pub const IS_MLOAD: usize = IS_POP + 1; +pub const IS_MSTORE: usize = IS_MLOAD + 1; +pub const IS_MSTORE8: usize = IS_MSTORE + 1; +pub const IS_SLOAD: usize = IS_MSTORE8 + 1; +pub const IS_SSTORE: usize = IS_SLOAD + 1; +pub const IS_JUMP: usize = IS_SSTORE + 1; +pub const IS_JUMPI: usize = IS_JUMP + 1; +pub const IS_PC: usize = IS_JUMPI + 1; +pub const IS_MSIZE: usize = IS_PC + 1; +pub const IS_GAS: usize = IS_MSIZE + 1; +pub const IS_JUMPDEST: usize = IS_GAS + 1; +// Find the number of bytes to push by reading the bottom 5 bits of the opcode. +pub const IS_PUSH: usize = IS_JUMPDEST + 1; +// Find the stack offset to duplicate by reading the bottom 4 bits of the opcode. +pub const IS_DUP: usize = IS_PUSH + 1; +// Find the stack offset to swap with by reading the bottom 4 bits of the opcode. +pub const IS_SWAP: usize = IS_DUP + 1; +pub const IS_LOG0: usize = IS_SWAP + 1; +pub const IS_LOG1: usize = IS_LOG0 + 1; +pub const IS_LOG2: usize = IS_LOG1 + 1; +pub const IS_LOG3: usize = IS_LOG2 + 1; +pub const IS_LOG4: usize = IS_LOG3 + 1; +pub const IS_CREATE: usize = IS_LOG4 + 1; +pub const IS_CALL: usize = IS_CREATE + 1; +pub const IS_CALLCODE: usize = IS_CALL + 1; +pub const IS_RETURN: usize = IS_CALLCODE + 1; +pub const IS_DELEGATECALL: usize = IS_RETURN + 1; +pub const IS_CREATE2: usize = IS_DELEGATECALL + 1; +pub const IS_STATICCALL: usize = IS_CREATE2 + 1; +pub const IS_REVERT: usize = IS_STATICCALL + 1; +pub const IS_SELFDESTRUCT: usize = IS_REVERT + 1; + +pub const IS_INVALID_0: usize = IS_SELFDESTRUCT + 1; +pub const IS_INVALID_1: usize = IS_INVALID_0 + 1; +pub const IS_INVALID_2: usize = IS_INVALID_1 + 1; +pub const IS_INVALID_3: usize = IS_INVALID_2 + 1; +pub const IS_INVALID_4: usize = IS_INVALID_3 + 1; +pub const IS_INVALID_5: usize = IS_INVALID_4 + 1; +pub const IS_INVALID_6: usize = IS_INVALID_5 + 1; +pub const IS_INVALID_7: usize = IS_INVALID_6 + 1; +pub const IS_INVALID_8: usize = IS_INVALID_7 + 1; +pub const IS_INVALID_9: usize = IS_INVALID_8 + 1; +pub const IS_INVALID_10: usize = IS_INVALID_9 + 1; +pub const IS_INVALID_11: usize = IS_INVALID_10 + 1; +pub const IS_INVALID_12: usize = IS_INVALID_11 + 1; +pub const IS_INVALID_13: usize = IS_INVALID_12 + 1; +pub const IS_INVALID_14: usize = IS_INVALID_13 + 1; +pub const IS_INVALID_15: usize = IS_INVALID_14 + 1; +pub const IS_INVALID_16: usize = IS_INVALID_15 + 1; +pub const IS_INVALID_17: usize = IS_INVALID_16 + 1; +pub const IS_INVALID_18: usize = IS_INVALID_17 + 1; +pub const IS_INVALID_19: usize = IS_INVALID_18 + 1; +pub const IS_INVALID_20: usize = IS_INVALID_19 + 1; +// An instruction is invalid if _any_ of the above flags is 1. + +pub const START_INSTRUCTION_FLAGS: usize = IS_STOP; +pub const END_INSTRUCTION_FLAGS: usize = IS_INVALID_20 + 1; + +// If CPU cycle: the opcode, broken up into bits. +// **big-endian** order +pub const OPCODE_BITS: [usize; 8] = [ + END_INSTRUCTION_FLAGS, + END_INSTRUCTION_FLAGS + 1, + END_INSTRUCTION_FLAGS + 2, + END_INSTRUCTION_FLAGS + 3, + END_INSTRUCTION_FLAGS + 4, + END_INSTRUCTION_FLAGS + 5, + END_INSTRUCTION_FLAGS + 6, + END_INSTRUCTION_FLAGS + 7, +]; + +pub const NUM_CPU_COLUMNS: usize = OPCODE_BITS[OPCODE_BITS.len() - 1] + 1; diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 00c03ec6..a3ca826a 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -5,6 +5,8 @@ use plonky2::field::packed_field::PackedField; use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cpu::columns; +use crate::cpu::decode; use crate::permutation::PermutationPair; use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; @@ -14,26 +16,34 @@ pub struct CpuStark { pub f: PhantomData, } +impl CpuStark { + pub fn generate(&self, local_values: &mut [F; columns::NUM_CPU_COLUMNS]) { + decode::generate(local_values); + } +} + impl, const D: usize> Stark for CpuStark { - const COLUMNS: usize = 10; + const COLUMNS: usize = columns::NUM_CPU_COLUMNS; const PUBLIC_INPUTS: usize = 0; fn eval_packed_generic( &self, - _vars: StarkEvaluationVars, - _yield_constr: &mut ConstraintConsumer

, + vars: StarkEvaluationVars, + yield_constr: &mut ConstraintConsumer

, ) where FE: FieldExtension, P: PackedField, { + decode::eval_packed_generic(vars.local_values, yield_constr); } fn eval_ext_circuit( &self, - _builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, - _vars: StarkEvaluationTargets, - _yield_constr: &mut RecursiveConstraintConsumer, + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + vars: StarkEvaluationTargets, + yield_constr: &mut RecursiveConstraintConsumer, ) { + decode::eval_ext_circuit(builder, vars.local_values, yield_constr); } fn constraint_degree(&self) -> usize { @@ -41,7 +51,7 @@ impl, const D: usize> Stark for CpuStark Vec { - vec![PermutationPair::singletons(8, 9)] + vec![] } } @@ -54,7 +64,6 @@ mod tests { use crate::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree}; #[test] - #[ignore] // TODO: remove this when constraints are no longer all 0. fn test_stark_degree() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs new file mode 100644 index 00000000..523a8350 --- /dev/null +++ b/evm/src/cpu/decode.rs @@ -0,0 +1,264 @@ +use plonky2::field::extension_field::Extendable; +use plonky2::field::field_types::Field; +use plonky2::field::packed_field::PackedField; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cpu::columns; + +// List of opcode blocks +// Each block corresponds to exactly one flag, and each flag corresponds to exactly one block. +// Each block of opcodes: +// - is contiguous +// - has a length that is a power of 2 +// - its start index is a multiple of its length (it is aligned) +// These properties permit us to check if an opcode belongs to a block of length 2^n by checking its +// top 8-n bits. +const OPCODES: [(u64, usize, usize); 102] = [ + // (start index of block, number of top bits to check (log2), flag column) + (0x00, 0, columns::IS_STOP), + (0x01, 0, columns::IS_ADD), + (0x02, 0, columns::IS_MUL), + (0x03, 0, columns::IS_SUB), + (0x04, 0, columns::IS_DIV), + (0x05, 0, columns::IS_SDIV), + (0x06, 0, columns::IS_MOD), + (0x07, 0, columns::IS_SMOD), + (0x08, 0, columns::IS_ADDMOD), + (0x09, 0, columns::IS_MULMOD), + (0x0a, 0, columns::IS_EXP), + (0x0b, 0, columns::IS_SIGNEXTEND), + (0x0c, 2, columns::IS_INVALID_0), // 0x0c-0x0f + (0x10, 0, columns::IS_LT), + (0x11, 0, columns::IS_GT), + (0x12, 0, columns::IS_SLT), + (0x13, 0, columns::IS_SGT), + (0x14, 0, columns::IS_EQ), + (0x15, 0, columns::IS_ISZERO), + (0x16, 0, columns::IS_AND), + (0x17, 0, columns::IS_OR), + (0x18, 0, columns::IS_XOR), + (0x19, 0, columns::IS_NOT), + (0x1a, 0, columns::IS_BYTE), + (0x1b, 0, columns::IS_SHL), + (0x1c, 0, columns::IS_SHR), + (0x1d, 0, columns::IS_SAR), + (0x1e, 1, columns::IS_INVALID_1), // 0x1e-0x1f + (0x20, 0, columns::IS_SHA3), + (0x21, 0, columns::IS_INVALID_2), + (0x22, 1, columns::IS_INVALID_3), // 0x22-0x23 + (0x24, 2, columns::IS_INVALID_4), // 0x24-0x27 + (0x28, 3, columns::IS_INVALID_5), // 0x28-0x2f + (0x30, 0, columns::IS_ADDRESS), + (0x31, 0, columns::IS_BALANCE), + (0x32, 0, columns::IS_ORIGIN), + (0x33, 0, columns::IS_CALLER), + (0x34, 0, columns::IS_CALLVALUE), + (0x35, 0, columns::IS_CALLDATALOAD), + (0x36, 0, columns::IS_CALLDATASIZE), + (0x37, 0, columns::IS_CALLDATACOPY), + (0x38, 0, columns::IS_CODESIZE), + (0x39, 0, columns::IS_CODECOPY), + (0x3a, 0, columns::IS_GASPRICE), + (0x3b, 0, columns::IS_EXTCODESIZE), + (0x3c, 0, columns::IS_EXTCODECOPY), + (0x3d, 0, columns::IS_RETURNDATASIZE), + (0x3e, 0, columns::IS_RETURNDATACOPY), + (0x3f, 0, columns::IS_EXTCODEHASH), + (0x40, 0, columns::IS_BLOCKHASH), + (0x41, 0, columns::IS_COINBASE), + (0x42, 0, columns::IS_TIMESTAMP), + (0x43, 0, columns::IS_NUMBER), + (0x44, 0, columns::IS_DIFFICULTY), + (0x45, 0, columns::IS_GASLIMIT), + (0x46, 0, columns::IS_CHAINID), + (0x47, 0, columns::IS_SELFBALANCE), + (0x48, 0, columns::IS_BASEFEE), + (0x49, 0, columns::IS_INVALID_6), + (0x4a, 1, columns::IS_INVALID_7), // 0x4a-0x4b + (0x4c, 2, columns::IS_INVALID_8), // 0x4c-0x4f + (0x50, 0, columns::IS_POP), + (0x51, 0, columns::IS_MLOAD), + (0x52, 0, columns::IS_MSTORE), + (0x53, 0, columns::IS_MSTORE8), + (0x54, 0, columns::IS_SLOAD), + (0x55, 0, columns::IS_SSTORE), + (0x56, 0, columns::IS_JUMP), + (0x57, 0, columns::IS_JUMPI), + (0x58, 0, columns::IS_PC), + (0x59, 0, columns::IS_MSIZE), + (0x5a, 0, columns::IS_GAS), + (0x5b, 0, columns::IS_JUMPDEST), + (0x5c, 2, columns::IS_INVALID_9), // 0x5c-0x5f + (0x60, 5, columns::IS_PUSH), // 0x60-0x7f + (0x80, 4, columns::IS_DUP), // 0x80-0x8f + (0x90, 4, columns::IS_SWAP), // 0x90-0x9f + (0xa0, 0, columns::IS_LOG0), + (0xa1, 0, columns::IS_LOG1), + (0xa2, 0, columns::IS_LOG2), + (0xa3, 0, columns::IS_LOG3), + (0xa4, 0, columns::IS_LOG4), + (0xa5, 0, columns::IS_INVALID_10), + (0xa6, 1, columns::IS_INVALID_11), // 0xa6-0xa7 + (0xa8, 3, columns::IS_INVALID_12), // 0xa8-0xaf + (0xb0, 4, columns::IS_INVALID_13), // 0xb0-0xbf + (0xc0, 5, columns::IS_INVALID_14), // 0xc0-0xdf + (0xe0, 4, columns::IS_INVALID_15), // 0xe0-0xef + (0xf0, 0, columns::IS_CREATE), + (0xf1, 0, columns::IS_CALL), + (0xf2, 0, columns::IS_CALLCODE), + (0xf3, 0, columns::IS_RETURN), + (0xf4, 0, columns::IS_DELEGATECALL), + (0xf5, 0, columns::IS_CREATE2), + (0xf6, 1, columns::IS_INVALID_16), // 0xf6-0xf7 + (0xf8, 1, columns::IS_INVALID_17), // 0xf8-0xf9 + (0xfa, 0, columns::IS_STATICCALL), + (0xfb, 0, columns::IS_INVALID_18), + (0xfc, 0, columns::IS_INVALID_19), + (0xfd, 0, columns::IS_REVERT), + (0xfe, 0, columns::IS_INVALID_20), + (0xff, 0, columns::IS_SELFDESTRUCT), +]; + +pub fn generate(lv: &mut [F; columns::NUM_CPU_COLUMNS]) { + let cycle_filter = lv[columns::IS_CPU_CYCLE]; + if cycle_filter == F::ZERO { + return; + } + // This assert is not _strictly_ necessary, but I include it as a sanity check. + assert_eq!(cycle_filter, F::ONE, "cycle_filter should be 0 or 1"); + + let opcode = lv[columns::OPCODE].to_canonical_u64(); + assert!(opcode < 256, "opcode should be in {{0, ..., 255}}"); + + for (i, &col) in columns::OPCODE_BITS.iter().enumerate() { + let bit = (opcode >> (7 - i)) & 1; + lv[col] = F::from_canonical_u64(bit); + } + + let top_bits: [u64; 9] = [ + 0, + opcode & 0x80, + opcode & 0xc0, + opcode & 0xe0, + opcode & 0xf0, + opcode & 0xf8, + opcode & 0xfc, + opcode & 0xfe, + opcode, + ]; + + for (oc, block_length, col) in OPCODES { + lv[col] = F::from_bool(top_bits[8 - block_length] == oc); + } +} + +pub fn eval_packed_generic( + lv: &[P; columns::NUM_CPU_COLUMNS], + yield_constr: &mut ConstraintConsumer

, +) { + let cycle_filter = lv[columns::IS_CPU_CYCLE]; + + // Ensure that the opcode bits are valid: each has to be either 0 or 1, and they must match + // the opcode. Note that this also validates that this implicitly range-checks the opcode. + let bits = columns::OPCODE_BITS.map(|i| lv[i]); + // First check that the bits are either 0 or 1. + for bit in bits { + yield_constr.constraint(cycle_filter * bit * (bit - P::ONES)); + } + + // top_bits[i] is the opcode with all but the top i bits cleared. + let top_bits = { + let mut top_bits = [P::ZEROS; 9]; + for i in 0..8 { + top_bits[i + 1] = top_bits[i] + bits[i] * P::Scalar::from_canonical_u64(1 << (7 - i)); + } + top_bits + }; + + // Now check that they match the opcode. + let opcode = lv[columns::OPCODE]; + yield_constr.constraint(cycle_filter * (opcode - top_bits[8])); + + // Check that the instruction flags are valid. + // First, check that they are all either 0 or 1. + for &flag in &lv[columns::START_INSTRUCTION_FLAGS..columns::END_INSTRUCTION_FLAGS] { + yield_constr.constraint(cycle_filter * flag * (flag - P::ONES)); + } + // Now check that exactly one is 1. + let flag_sum: P = (columns::START_INSTRUCTION_FLAGS..columns::END_INSTRUCTION_FLAGS) + .into_iter() + .map(|i| lv[i]) + .sum(); + yield_constr.constraint(cycle_filter * (P::ONES - flag_sum)); + + // Finally, classify all opcodes into blocks + for (oc, block_length, col) in OPCODES { + let constr = lv[col] * (top_bits[8 - block_length] - P::Scalar::from_canonical_u64(oc)); + yield_constr.constraint(cycle_filter * constr); + } +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &[ExtensionTarget; columns::NUM_CPU_COLUMNS], + yield_constr: &mut RecursiveConstraintConsumer, +) { + let cycle_filter = lv[columns::IS_CPU_CYCLE]; + + // Ensure that the opcode bits are valid: each has to be either 0 or 1, and they must match + // the opcode. Note that this also validates that this implicitly range-checks the opcode. + let bits = columns::OPCODE_BITS.map(|i| lv[i]); + // First check that the bits are either 0 or 1. + for bit in bits { + let constr = builder.mul_sub_extension(bit, bit, bit); + let constr = builder.mul_extension(cycle_filter, constr); + yield_constr.constraint(builder, constr); + } + + let top_bits = { + let mut top_bits = [builder.zero_extension(); 9]; + for i in 0..8 { + top_bits[i + 1] = builder.mul_const_add_extension( + F::from_canonical_u64(1 << (7 - i)), + bits[i], + top_bits[i], + ); + } + top_bits + }; + + // Now check that the bits match the opcode. + { + let constr = builder.sub_extension(lv[columns::OPCODE], top_bits[8]); + let constr = builder.mul_extension(cycle_filter, constr); + yield_constr.constraint(builder, constr); + }; + + // Check that the instruction flags are valid. + // First, check that they are all either 0 or 1. + for &flag in &lv[columns::START_INSTRUCTION_FLAGS..columns::END_INSTRUCTION_FLAGS] { + let constr = builder.mul_sub_extension(flag, flag, flag); + let constr = builder.mul_extension(cycle_filter, constr); + yield_constr.constraint(builder, constr); + } + // Now check that they sum to 1. + { + let mut constr = builder.one_extension(); + for &flag in &lv[columns::START_INSTRUCTION_FLAGS..columns::END_INSTRUCTION_FLAGS] { + constr = builder.sub_extension(constr, flag); + } + constr = builder.mul_extension(cycle_filter, constr); + yield_constr.constraint(builder, constr); + } + + for (oc, block_length, col) in OPCODES { + let flag = lv[col]; + let constr = builder.constant_extension(F::from_canonical_u64(oc).into()); + let constr = builder.sub_extension(top_bits[8 - block_length], constr); + let constr = builder.mul_extension(flag, constr); + let constr = builder.mul_extension(cycle_filter, constr); + yield_constr.constraint(builder, constr); + } +} diff --git a/evm/src/cpu/mod.rs b/evm/src/cpu/mod.rs index fe991a17..5a2905bc 100644 --- a/evm/src/cpu/mod.rs +++ b/evm/src/cpu/mod.rs @@ -1 +1,3 @@ +pub(crate) mod columns; pub mod cpu_stark; +pub(crate) mod decode;