From 925483ed1eb0f6724ece0dde50c5b576377607e4 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 17 Jul 2022 15:07:29 -0700 Subject: [PATCH 01/38] Add custom opcodes - `GET_STATE_ROOT` and `SET_STATE_ROOT` deal with the root of the state trie, and will be called from storage routines. Similarly `GET_RECEIPT_ROOT` and `SET_RECEIPT_ROOT` deal with the root of the receipt trie. - `PANIC` enables an unsatisfiable constraint, so no proof can be generated. - `GET_CONTEXT` and `SET_CONTEXT`, used when calling and returning - `CONSUME_GAS` charges the sender gas; useful for cases where gas calculations are nontrivial and best implemented in assembly. - `EXIT_KERNEL` simply clears the CPU flag indicating that we're in kernel mode; it would be used just before a jump to return to the (userspace) caller. - `MLOAD_GENERAL` and `MSTORE_GENERAL` are for reading and writing memory, but they're not limited to the main memory segment of the current context; they can access any context and any segment. I added a couple macros to show how the they would typically be used. There may be more later, but these are the ones I think we need for now. I tried to fill in smaller invalid sections of the decoder's tree, as Jacqui suggested, while keeping related opcodes together. We can fine tune it when the opcode list is more stable. These are all intended to be priviledged, i.e. they will be treated as invalid if used from userspace, for compatibility as well as (in some cases) security reasons. --- evm/src/cpu/columns.rs | 17 +++++++++----- evm/src/cpu/decode.rs | 37 ++++++++++++++++++------------- evm/src/cpu/kernel/asm/memory.asm | 27 ++++++++++++++++++++++ evm/src/cpu/kernel/interpreter.rs | 11 +++++++++ evm/src/cpu/kernel/opcodes.rs | 11 +++++++++ 5 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/memory.asm diff --git a/evm/src/cpu/columns.rs b/evm/src/cpu/columns.rs index f3a400c6..c79f9e9d 100644 --- a/evm/src/cpu/columns.rs +++ b/evm/src/cpu/columns.rs @@ -90,6 +90,10 @@ pub struct CpuColumnsView { pub is_msize: T, pub is_gas: T, pub is_jumpdest: T, + pub is_get_state_root: T, + pub is_set_state_root: T, + pub is_get_receipt_root: T, + pub is_set_receipt_root: T, pub is_push: T, pub is_dup: T, pub is_swap: T, @@ -98,13 +102,20 @@ pub struct CpuColumnsView { pub is_log2: T, pub is_log3: T, pub is_log4: T, + pub is_panic: T, pub is_create: T, pub is_call: T, pub is_callcode: T, pub is_return: T, pub is_delegatecall: T, pub is_create2: T, + pub is_get_context: T, + pub is_set_context: T, + pub is_consume_gas: T, + pub is_exit_kernel: T, pub is_staticcall: T, + pub is_mload_general: T, + pub is_mstore_general: T, pub is_revert: T, pub is_selfdestruct: T, @@ -124,12 +135,6 @@ pub struct CpuColumnsView { pub is_invalid_12: T, pub is_invalid_13: T, pub is_invalid_14: T, - pub is_invalid_15: T, - pub is_invalid_16: T, - pub is_invalid_17: T, - pub is_invalid_18: T, - pub is_invalid_19: T, - pub is_invalid_20: T, /// If CPU cycle: the opcode, broken up into bits in **big-endian** order. pub opcode_bits: [T; 8], diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index 0b091558..22a25dc6 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -15,7 +15,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; // - 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] = [ +const OPCODES: [(u64, usize, usize); 107] = [ // (start index of block, number of top bits to check (log2), flag column) (0x00, 0, COL_MAP.is_stop), (0x01, 0, COL_MAP.is_add), @@ -90,34 +90,39 @@ const OPCODES: [(u64, usize, usize); 102] = [ (0x59, 0, COL_MAP.is_msize), (0x5a, 0, COL_MAP.is_gas), (0x5b, 0, COL_MAP.is_jumpdest), - (0x5c, 2, COL_MAP.is_invalid_9), // 0x5c-0x5f - (0x60, 5, COL_MAP.is_push), // 0x60-0x7f - (0x80, 4, COL_MAP.is_dup), // 0x80-0x8f - (0x90, 4, COL_MAP.is_swap), // 0x90-0x9f + (0x5c, 0, COL_MAP.is_get_state_root), + (0x5d, 0, COL_MAP.is_set_state_root), + (0x5e, 0, COL_MAP.is_get_receipt_root), + (0x5f, 0, COL_MAP.is_set_receipt_root), + (0x60, 5, COL_MAP.is_push), // 0x60-0x7f + (0x80, 4, COL_MAP.is_dup), // 0x80-0x8f + (0x90, 4, COL_MAP.is_swap), // 0x90-0x9f (0xa0, 0, COL_MAP.is_log0), (0xa1, 0, COL_MAP.is_log1), (0xa2, 0, COL_MAP.is_log2), (0xa3, 0, COL_MAP.is_log3), (0xa4, 0, COL_MAP.is_log4), - (0xa5, 0, COL_MAP.is_invalid_10), - (0xa6, 1, COL_MAP.is_invalid_11), // 0xa6-0xa7 - (0xa8, 3, COL_MAP.is_invalid_12), // 0xa8-0xaf - (0xb0, 4, COL_MAP.is_invalid_13), // 0xb0-0xbf - (0xc0, 5, COL_MAP.is_invalid_14), // 0xc0-0xdf - (0xe0, 4, COL_MAP.is_invalid_15), // 0xe0-0xef + (0xa5, 0, COL_MAP.is_panic), + (0xa6, 1, COL_MAP.is_invalid_9), // 0xa6-0xa7 + (0xa8, 3, COL_MAP.is_invalid_10), // 0xa8-0xaf + (0xb0, 4, COL_MAP.is_invalid_11), // 0xb0-0xbf + (0xc0, 5, COL_MAP.is_invalid_12), // 0xc0-0xdf + (0xe0, 4, COL_MAP.is_invalid_13), // 0xe0-0xef (0xf0, 0, COL_MAP.is_create), (0xf1, 0, COL_MAP.is_call), (0xf2, 0, COL_MAP.is_callcode), (0xf3, 0, COL_MAP.is_return), (0xf4, 0, COL_MAP.is_delegatecall), (0xf5, 0, COL_MAP.is_create2), - (0xf6, 1, COL_MAP.is_invalid_16), // 0xf6-0xf7 - (0xf8, 1, COL_MAP.is_invalid_17), // 0xf8-0xf9 + (0xf6, 0, COL_MAP.is_get_context), + (0xf7, 0, COL_MAP.is_set_context), + (0xf8, 0, COL_MAP.is_consume_gas), + (0xf9, 0, COL_MAP.is_exit_kernel), (0xfa, 0, COL_MAP.is_staticcall), - (0xfb, 0, COL_MAP.is_invalid_18), - (0xfc, 0, COL_MAP.is_invalid_19), + (0xfb, 0, COL_MAP.is_mload_general), + (0xfc, 0, COL_MAP.is_mstore_general), (0xfd, 0, COL_MAP.is_revert), - (0xfe, 0, COL_MAP.is_invalid_20), + (0xfe, 0, COL_MAP.is_invalid_14), (0xff, 0, COL_MAP.is_selfdestruct), ]; diff --git a/evm/src/cpu/kernel/asm/memory.asm b/evm/src/cpu/kernel/asm/memory.asm new file mode 100644 index 00000000..c325ed9d --- /dev/null +++ b/evm/src/cpu/kernel/asm/memory.asm @@ -0,0 +1,27 @@ +// Load a byte from the given segment of the current context's memory space. +// Note that main memory values are one byte each, but in general memory values +// can be 256 bits. This macro deals with a single address (unlike MSTORE), so +// if it is used with main memory, it will load a single byte. +%macro mload_current(segment) + // stack: offset + PUSH $segment + // stack: segment, offset + CURRENT_CONTEXT + // stack: context, segment, offset + MLOAD_GENERAL + // stack: value +%endmacro + +// Store a byte to the given segment of the current context's memory space. +// Note that main memory values are one byte each, but in general memory values +// can be 256 bits. This macro deals with a single address (unlike MSTORE), so +// if it is used with main memory, it will store a single byte. +%macro mstore_current(segment) + // stack: offset, value + PUSH $segment + // stack: segment, offset, value + CURRENT_CONTEXT + // stack: context, segment, offset, value + MSTORE_GENERAL + // stack: (empty) +%endmacro diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 09e493b9..d18ef945 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -115,6 +115,10 @@ impl<'a> Interpreter<'a> { 0x59 => todo!(), // "MSIZE", 0x5a => todo!(), // "GAS", 0x5b => (), // "JUMPDEST", + 0x5c => todo!(), // "GET_STATE_ROOT", + 0x5d => todo!(), // "SET_STATE_ROOT", + 0x5e => todo!(), // "GET_RECEIPT_ROOT", + 0x5f => todo!(), // "SET_RECEIPT_ROOT", x if (0x60..0x80).contains(&x) => self.run_push(x - 0x5f), // "PUSH" x if (0x80..0x90).contains(&x) => self.run_dup(x - 0x7f), // "DUP" x if (0x90..0xa0).contains(&x) => self.run_swap(x - 0x8f), // "SWAP" @@ -123,13 +127,20 @@ impl<'a> Interpreter<'a> { 0xa2 => todo!(), // "LOG2", 0xa3 => todo!(), // "LOG3", 0xa4 => todo!(), // "LOG4", + 0xa5 => panic!("Executed PANIC"), // "PANIC", 0xf0 => todo!(), // "CREATE", 0xf1 => todo!(), // "CALL", 0xf2 => todo!(), // "CALLCODE", 0xf3 => todo!(), // "RETURN", 0xf4 => todo!(), // "DELEGATECALL", 0xf5 => todo!(), // "CREATE2", + 0xf6 => todo!(), // "GET_CONTEXT", + 0xf7 => todo!(), // "SET_CONTEXT", + 0xf8 => todo!(), // "CONSUME_GAS", + 0xf9 => todo!(), // "EXIT_KERNEL", 0xfa => todo!(), // "STATICCALL", + 0xfb => todo!(), // "MLOAD_GENERAL", + 0xfc => todo!(), // "MSTORE_GENERAL", 0xfd => todo!(), // "REVERT", 0xfe => todo!(), // "INVALID", 0xff => todo!(), // "SELFDESTRUCT", diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index b8633178..b9ef7d5e 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -71,6 +71,10 @@ pub(crate) fn get_opcode(mnemonic: &str) -> u8 { "MSIZE" => 0x59, "GAS" => 0x5a, "JUMPDEST" => 0x5b, + "GET_STATE_ROOT" => 0x5c, + "SET_STATE_ROOT" => 0x5d, + "GET_RECEIPT_ROOT" => 0x5e, + "SET_RECEIPT_ROOT" => 0x5f, "DUP1" => 0x80, "DUP2" => 0x81, "DUP3" => 0x82, @@ -108,13 +112,20 @@ pub(crate) fn get_opcode(mnemonic: &str) -> u8 { "LOG2" => 0xa2, "LOG3" => 0xa3, "LOG4" => 0xa4, + "PANIC" => 0xa5, "CREATE" => 0xf0, "CALL" => 0xf1, "CALLCODE" => 0xf2, "RETURN" => 0xf3, "DELEGATECALL" => 0xf4, "CREATE2" => 0xf5, + "GET_CONTEXT" => 0xf6, + "SET_CONTEXT" => 0xf7, + "CONSUME_GAS" => 0xf8, + "EXIT_KERNEL" => 0xf9, "STATICCALL" => 0xfa, + "MLOAD_GENERAL" => 0xfb, + "MSTORE_GENERAL" => 0xfc, "REVERT" => 0xfd, "INVALID" => 0xfe, "SELFDESTRUCT" => 0xff, From 14a58439e5a6e153ced0113370d11078ca504fdf Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 18 Jul 2022 16:24:47 +0200 Subject: [PATCH 02/38] SHA3 in interpreter --- evm/src/cpu/kernel/interpreter.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 81ce287b..1839aaf2 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -1,4 +1,5 @@ -use ethereum_types::{U256, U512}; +use ethereum_types::{BigEndianHash, U256, U512}; +use keccak_hash::keccak; /// Halt interpreter execution whenever a jump to this offset is done. const HALT_OFFSET: usize = 0xdeadbeef; @@ -25,6 +26,11 @@ impl EvmMemory { U256::from_big_endian(&self.memory[offset..offset + 32]) } + fn mload8(&mut self, offset: usize) -> u8 { + self.expand(offset + 1); + self.memory[offset] + } + fn mstore(&mut self, offset: usize, value: U256) { self.expand(offset + 32); let value_be = { @@ -119,7 +125,7 @@ impl<'a> Interpreter<'a> { 0x1b => todo!(), // "SHL", 0x1c => todo!(), // "SHR", 0x1d => todo!(), // "SAR", - 0x20 => todo!(), // "KECCAK256", + 0x20 => self.run_sha3(), // "SHA3", 0x30 => todo!(), // "ADDRESS", 0x31 => todo!(), // "BALANCE", 0x32 => todo!(), // "ORIGIN", @@ -286,6 +292,16 @@ impl<'a> Interpreter<'a> { self.push(!x); } + fn run_sha3(&mut self) { + let offset = self.pop().as_usize(); + let size = self.pop().as_usize(); + let bytes = (offset..offset + size) + .map(|i| self.memory.mload8(i)) + .collect::>(); + let hash = keccak(bytes); + self.push(hash.into_uint()); + } + fn run_pop(&mut self) { self.pop(); } From 15ee8917784c75a07fbf89337c46c9228ab5c05e Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 18 Jul 2022 16:36:37 +0200 Subject: [PATCH 03/38] SHA3 in asm --- evm/src/cpu/kernel/asm/ecrecover.asm | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/asm/ecrecover.asm b/evm/src/cpu/kernel/asm/ecrecover.asm index d0994054..2c71d317 100644 --- a/evm/src/cpu/kernel/asm/ecrecover.asm +++ b/evm/src/cpu/kernel/asm/ecrecover.asm @@ -133,7 +133,27 @@ ecrecover_with_first_point: // TODO final_hashing: JUMPDEST - PUSH 0xdeadbeef + // stack: PKx, PKy, retdest + PUSH 0 + // stack: 0, PKx, PKy, retdest + MSTORE + // stack: PKy, retdest + PUSH 0x20 + // stack: 0x20, PKy, retdest + MSTORE + // stack: retdest + PUSH 0x40 + // stack: 0x40, retdest + PUSH 0 + // stack: 0, 0x40, retdest + SHA3 + // stack: hash, retdest + PUSH 0xffffffffffffffffffffffffffffffffffffffff + // stack: 2^160-1, hash, retdest + AND + // stack: address, retdest + SWAP1 + // stack: retdest, address JUMP // Check if v, r, and s are in correct form. From f9ec4e8e7d0f78865ecd0d206fd070c391b2a8c4 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 18 Jul 2022 16:41:17 +0200 Subject: [PATCH 04/38] Modify ecrecover tests --- evm/src/cpu/kernel/opcodes.rs | 1 + evm/src/cpu/kernel/tests/ecrecover.rs | 16 ++++------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index b8633178..6ca359b7 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -35,6 +35,7 @@ pub(crate) fn get_opcode(mnemonic: &str) -> u8 { "SHR" => 0x1c, "SAR" => 0x1d, "KECCAK256" => 0x20, + "SHA3" => 0x20, "ADDRESS" => 0x30, "BALANCE" => 0x31, "ORIGIN" => 0x32, diff --git a/evm/src/cpu/kernel/tests/ecrecover.rs b/evm/src/cpu/kernel/tests/ecrecover.rs index 5077d042..80257222 100644 --- a/evm/src/cpu/kernel/tests/ecrecover.rs +++ b/evm/src/cpu/kernel/tests/ecrecover.rs @@ -1,20 +1,13 @@ +use std::str::FromStr; + use anyhow::Result; use ethereum_types::U256; -use keccak_hash::keccak; use crate::cpu::kernel::aggregator::combined_kernel; use crate::cpu::kernel::assembler::Kernel; use crate::cpu::kernel::interpreter::run; use crate::cpu::kernel::tests::u256ify; -fn pubkey_to_addr(x: U256, y: U256) -> Vec { - let mut buf = [0; 64]; - x.to_big_endian(&mut buf[0..32]); - y.to_big_endian(&mut buf[32..64]); - let hash = keccak(buf); - hash.0[12..].to_vec() -} - fn test_valid_ecrecover( hash: &str, v: &str, @@ -24,10 +17,9 @@ fn test_valid_ecrecover( kernel: &Kernel, ) -> Result<()> { let ecrecover = kernel.global_labels["ecrecover"]; - let initial_stack = u256ify([s, r, v, hash])?; + let initial_stack = u256ify(["0xdeadbeef", s, r, v, hash])?; let stack = run(&kernel.code, ecrecover, initial_stack).stack; - let got = pubkey_to_addr(stack[1], stack[0]); - assert_eq!(got, hex::decode(&expected[2..]).unwrap()); + assert_eq!(stack[0], U256::from_str(expected).unwrap()); Ok(()) } From ea0d081fa86cd5c030ad5523af544be47636cfda Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 18 Jul 2022 16:53:26 +0200 Subject: [PATCH 05/38] Fix comment --- evm/src/cpu/kernel/asm/ecrecover.asm | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/evm/src/cpu/kernel/asm/ecrecover.asm b/evm/src/cpu/kernel/asm/ecrecover.asm index 2c71d317..95efd914 100644 --- a/evm/src/cpu/kernel/asm/ecrecover.asm +++ b/evm/src/cpu/kernel/asm/ecrecover.asm @@ -107,31 +107,31 @@ ecrecover_with_first_point: // stack: u2, Y, X, retdest // Compute u2 * GENERATOR and chain the call to `ec_mul` with a call to `ec_add` to compute PUBKEY = (X,Y) + u2 * GENERATOR, - // and a call to `final_hashing` to get the final result `SHA3(PUBKEY)[-20:]`. - PUSH final_hashing - // stack: final_hashing, u2, Y, X, retdest + // and a call to `pubkey_to_addr` to get the final result `SHA3(PUBKEY)[-20:]`. + PUSH pubkey_to_addr + // stack: pubkey_to_addr, u2, Y, X, retdest SWAP3 - // stack: X, u2, Y, final_hashing, retdest + // stack: X, u2, Y, pubkey_to_addr, retdest PUSH ec_add_valid_points_secp - // stack: ec_add_valid_points_secp, X, u2, Y, final_hashing, retdest + // stack: ec_add_valid_points_secp, X, u2, Y, pubkey_to_addr, retdest SWAP1 - // stack: X, ec_add_valid_points_secp, u2, Y, final_hashing, retdest + // stack: X, ec_add_valid_points_secp, u2, Y, pubkey_to_addr, retdest PUSH 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 // x-coordinate of generator - // stack: Gx, X, ec_add_valid_points_secp, u2, Y, final_hashing, retdest + // stack: Gx, X, ec_add_valid_points_secp, u2, Y, pubkey_to_addr, retdest SWAP1 - // stack: X, Gx, ec_add_valid_points_secp, u2, Y, final_hashing, retdest + // stack: X, Gx, ec_add_valid_points_secp, u2, Y, pubkey_to_addr, retdest PUSH 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 // y-coordinate of generator - // stack: Gy, X, Gx, ec_add_valid_points_secp, u2, Y, final_hashing, retdest + // stack: Gy, X, Gx, ec_add_valid_points_secp, u2, Y, pubkey_to_addr, retdest SWAP1 - // stack: X, Gy, Gx, ec_add_valid_points_secp, u2, Y, final_hashing, retdest + // stack: X, Gy, Gx, ec_add_valid_points_secp, u2, Y, pubkey_to_addr, retdest SWAP4 - // stack: u2, Gy, Gx, ec_add_valid_points_secp, X, Y, final_hashing, retdest + // stack: u2, Gy, Gx, ec_add_valid_points_secp, X, Y, pubkey_to_addr, retdest SWAP2 - // stack: Gx, Gy, u2, ec_add_valid_points_secp, X, Y, final_hashing, retdest + // stack: Gx, Gy, u2, ec_add_valid_points_secp, X, Y, pubkey_to_addr, retdest %jump(ec_mul_valid_point_secp) -// TODO -final_hashing: +// Take a public key (PKx, PKy) and return the associated address SHA3(PKx || PKy)[-20:]. +pubkey_to_addr: JUMPDEST // stack: PKx, PKy, retdest PUSH 0 From 0b7e3eca67e13dabe45351edb240a560ab8bd100 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 18 Jul 2022 08:58:11 -0700 Subject: [PATCH 06/38] PANIC returns error --- evm/src/cpu/kernel/interpreter.rs | 28 +++++++----- evm/src/cpu/kernel/tests/curve_ops.rs | 62 +++++++++++++-------------- evm/src/cpu/kernel/tests/ecrecover.rs | 4 +- evm/src/cpu/kernel/tests/exp.rs | 12 +++--- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 33bc4327..a9507fde 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use ethereum_types::{U256, U512}; /// Halt interpreter execution whenever a jump to this offset is done. @@ -51,7 +52,11 @@ pub(crate) struct Interpreter<'a> { running: bool, } -pub(crate) fn run(code: &[u8], initial_offset: usize, initial_stack: Vec) -> Interpreter { +pub(crate) fn run( + code: &[u8], + initial_offset: usize, + initial_stack: Vec, +) -> anyhow::Result { let mut interpreter = Interpreter { code, jumpdests: find_jumpdests(code), @@ -65,7 +70,7 @@ pub(crate) fn run(code: &[u8], initial_offset: usize, initial_stack: Vec) interpreter.run_opcode(); } - interpreter + Ok(interpreter) } impl<'a> Interpreter<'a> { @@ -89,7 +94,7 @@ impl<'a> Interpreter<'a> { self.stack.pop().expect("Pop on empty stack.") } - fn run_opcode(&mut self) { + fn run_opcode(&mut self) -> anyhow::Result<()> { let opcode = self.code.get(self.offset).copied().unwrap_or_default(); self.incr(1); match opcode { @@ -168,7 +173,7 @@ impl<'a> Interpreter<'a> { 0xa2 => todo!(), // "LOG2", 0xa3 => todo!(), // "LOG3", 0xa4 => todo!(), // "LOG4", - 0xa5 => panic!("Executed PANIC"), // "PANIC", + 0xa5 => bail!("Executed PANIC"), // "PANIC", 0xf0 => todo!(), // "CREATE", 0xf1 => todo!(), // "CALL", 0xf2 => todo!(), // "CALLCODE", @@ -183,10 +188,11 @@ impl<'a> Interpreter<'a> { 0xfb => todo!(), // "MLOAD_GENERAL", 0xfc => todo!(), // "MSTORE_GENERAL", 0xfd => todo!(), // "REVERT", - 0xfe => todo!(), // "INVALID", + 0xfe => bail!("Executed INVALID"), // "INVALID", 0xff => todo!(), // "SELFDESTRUCT", - _ => panic!("Unrecognized opcode {}.", opcode), + _ => bail!("Unrecognized opcode {}.", opcode), }; + Ok(()) } fn run_stop(&mut self) { @@ -381,15 +387,16 @@ mod tests { use crate::cpu::kernel::interpreter::{run, Interpreter}; #[test] - fn test_run() { + fn test_run() -> anyhow::Result<()> { let code = vec![ 0x60, 0x1, 0x60, 0x2, 0x1, 0x63, 0xde, 0xad, 0xbe, 0xef, 0x56, ]; // PUSH1, 1, PUSH1, 2, ADD, PUSH4 deadbeef, JUMP - assert_eq!(run(&code, 0, vec![]).stack, vec![0x3.into()]); + assert_eq!(run(&code, 0, vec![])?.stack, vec![0x3.into()]); + Ok(()) } #[test] - fn test_run_with_memory() { + fn test_run_with_memory() -> anyhow::Result<()> { // PUSH1 0xff // PUSH1 0 // MSTORE @@ -407,9 +414,10 @@ mod tests { 0x60, 0xff, 0x60, 0x0, 0x52, 0x60, 0, 0x51, 0x60, 0x1, 0x51, 0x60, 0x42, 0x60, 0x27, 0x53, ]; - let run = run(&code, 0, vec![]); + let run = run(&code, 0, vec![])?; let Interpreter { stack, memory, .. } = run; assert_eq!(stack, vec![0xff.into(), 0xff00.into()]); assert_eq!(&memory.memory, &hex!("00000000000000000000000000000000000000000000000000000000000000ff0000000000000042000000000000000000000000000000000000000000000000")); + Ok(()) } } diff --git a/evm/src/cpu/kernel/tests/curve_ops.rs b/evm/src/cpu/kernel/tests/curve_ops.rs index 7d7f042a..6d8c6696 100644 --- a/evm/src/cpu/kernel/tests/curve_ops.rs +++ b/evm/src/cpu/kernel/tests/curve_ops.rs @@ -43,76 +43,76 @@ mod bn { // Standard addition #1 let initial_stack = u256ify(["0xdeadbeef", point0.1, point0.0, point1.1, point1.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point2.1, point2.0])?); // Standard addition #2 let initial_stack = u256ify(["0xdeadbeef", point1.1, point1.0, point0.1, point0.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point2.1, point2.0])?); // Standard doubling #1 let initial_stack = u256ify(["0xdeadbeef", point0.1, point0.0, point0.1, point0.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point3.1, point3.0])?); // Standard doubling #2 let initial_stack = u256ify(["0xdeadbeef", point0.1, point0.0])?; - let stack = run(&kernel.code, ec_double, initial_stack).stack; + let stack = run(&kernel.code, ec_double, initial_stack)?.stack; assert_eq!(stack, u256ify([point3.1, point3.0])?); // Standard doubling #3 let initial_stack = u256ify(["0xdeadbeef", "0x2", point0.1, point0.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([point3.1, point3.0])?); // Addition with identity #1 let initial_stack = u256ify(["0xdeadbeef", identity.1, identity.0, point1.1, point1.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point1.1, point1.0])?); // Addition with identity #2 let initial_stack = u256ify(["0xdeadbeef", point1.1, point1.0, identity.1, identity.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point1.1, point1.0])?); // Addition with identity #3 let initial_stack = u256ify(["0xdeadbeef", identity.1, identity.0, identity.1, identity.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([identity.1, identity.0])?); // Addition with invalid point(s) #1 let initial_stack = u256ify(["0xdeadbeef", point0.1, point0.0, invalid.1, invalid.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, vec![U256::MAX, U256::MAX]); // Addition with invalid point(s) #2 let initial_stack = u256ify(["0xdeadbeef", invalid.1, invalid.0, point0.1, point0.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, vec![U256::MAX, U256::MAX]); // Addition with invalid point(s) #3 let initial_stack = u256ify(["0xdeadbeef", invalid.1, invalid.0, identity.1, identity.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, vec![U256::MAX, U256::MAX]); // Addition with invalid point(s) #4 let initial_stack = u256ify(["0xdeadbeef", invalid.1, invalid.0, invalid.1, invalid.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, vec![U256::MAX, U256::MAX]); // Scalar multiplication #1 let initial_stack = u256ify(["0xdeadbeef", s, point0.1, point0.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([point4.1, point4.0])?); // Scalar multiplication #2 let initial_stack = u256ify(["0xdeadbeef", "0x0", point0.1, point0.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([identity.1, identity.0])?); // Scalar multiplication #3 let initial_stack = u256ify(["0xdeadbeef", "0x1", point0.1, point0.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([point0.1, point0.0])?); // Scalar multiplication #4 let initial_stack = u256ify(["0xdeadbeef", s, identity.1, identity.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([identity.1, identity.0])?); // Scalar multiplication #5 let initial_stack = u256ify(["0xdeadbeef", s, invalid.1, invalid.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, vec![U256::MAX, U256::MAX]); // Multiple calls @@ -126,7 +126,7 @@ mod bn { point0.1, point0.0, ])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point4.1, point4.0])?); Ok(()) @@ -176,55 +176,55 @@ mod secp { // Standard addition #1 let initial_stack = u256ify(["0xdeadbeef", point0.1, point0.0, point1.1, point1.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point2.1, point2.0])?); // Standard addition #2 let initial_stack = u256ify(["0xdeadbeef", point1.1, point1.0, point0.1, point0.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point2.1, point2.0])?); // Standard doubling #1 let initial_stack = u256ify(["0xdeadbeef", point0.1, point0.0, point0.1, point0.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point3.1, point3.0])?); // Standard doubling #2 let initial_stack = u256ify(["0xdeadbeef", point0.1, point0.0])?; - let stack = run(&kernel.code, ec_double, initial_stack).stack; + let stack = run(&kernel.code, ec_double, initial_stack)?.stack; assert_eq!(stack, u256ify([point3.1, point3.0])?); // Standard doubling #3 let initial_stack = u256ify(["0xdeadbeef", "0x2", point0.1, point0.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([point3.1, point3.0])?); // Addition with identity #1 let initial_stack = u256ify(["0xdeadbeef", identity.1, identity.0, point1.1, point1.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point1.1, point1.0])?); // Addition with identity #2 let initial_stack = u256ify(["0xdeadbeef", point1.1, point1.0, identity.1, identity.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point1.1, point1.0])?); // Addition with identity #3 let initial_stack = u256ify(["0xdeadbeef", identity.1, identity.0, identity.1, identity.0])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([identity.1, identity.0])?); // Scalar multiplication #1 let initial_stack = u256ify(["0xdeadbeef", s, point0.1, point0.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([point4.1, point4.0])?); // Scalar multiplication #2 let initial_stack = u256ify(["0xdeadbeef", "0x0", point0.1, point0.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([identity.1, identity.0])?); // Scalar multiplication #3 let initial_stack = u256ify(["0xdeadbeef", "0x1", point0.1, point0.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([point0.1, point0.0])?); // Scalar multiplication #4 let initial_stack = u256ify(["0xdeadbeef", s, identity.1, identity.0])?; - let stack = run(&kernel.code, ec_mul, initial_stack).stack; + let stack = run(&kernel.code, ec_mul, initial_stack)?.stack; assert_eq!(stack, u256ify([identity.1, identity.0])?); // Multiple calls @@ -238,7 +238,7 @@ mod secp { point0.1, point0.0, ])?; - let stack = run(&kernel.code, ec_add, initial_stack).stack; + let stack = run(&kernel.code, ec_add, initial_stack)?.stack; assert_eq!(stack, u256ify([point4.1, point4.0])?); Ok(()) diff --git a/evm/src/cpu/kernel/tests/ecrecover.rs b/evm/src/cpu/kernel/tests/ecrecover.rs index 5077d042..d6eac6f7 100644 --- a/evm/src/cpu/kernel/tests/ecrecover.rs +++ b/evm/src/cpu/kernel/tests/ecrecover.rs @@ -25,7 +25,7 @@ fn test_valid_ecrecover( ) -> Result<()> { let ecrecover = kernel.global_labels["ecrecover"]; let initial_stack = u256ify([s, r, v, hash])?; - let stack = run(&kernel.code, ecrecover, initial_stack).stack; + let stack = run(&kernel.code, ecrecover, initial_stack)?.stack; let got = pubkey_to_addr(stack[1], stack[0]); assert_eq!(got, hex::decode(&expected[2..]).unwrap()); @@ -35,7 +35,7 @@ fn test_valid_ecrecover( fn test_invalid_ecrecover(hash: &str, v: &str, r: &str, s: &str, kernel: &Kernel) -> Result<()> { let ecrecover = kernel.global_labels["ecrecover"]; let initial_stack = u256ify(["0xdeadbeef", s, r, v, hash])?; - let stack = run(&kernel.code, ecrecover, initial_stack).stack; + let stack = run(&kernel.code, ecrecover, initial_stack)?.stack; assert_eq!(stack, vec![U256::MAX]); Ok(()) diff --git a/evm/src/cpu/kernel/tests/exp.rs b/evm/src/cpu/kernel/tests/exp.rs index b12b943e..25c88623 100644 --- a/evm/src/cpu/kernel/tests/exp.rs +++ b/evm/src/cpu/kernel/tests/exp.rs @@ -18,26 +18,26 @@ fn test_exp() -> Result<()> { // Random input let initial_stack = vec![U256::from_str("0xdeadbeef")?, b, a]; - let stack_with_kernel = run(&kernel.code, exp, initial_stack).stack; + let stack_with_kernel = run(&kernel.code, exp, initial_stack)?.stack; let initial_stack = vec![b, a]; let code = [0xa, 0x63, 0xde, 0xad, 0xbe, 0xef, 0x56]; // EXP, PUSH4 deadbeef, JUMP - let stack_with_opcode = run(&code, 0, initial_stack).stack; + let stack_with_opcode = run(&code, 0, initial_stack)?.stack; assert_eq!(stack_with_kernel, stack_with_opcode); // 0 base let initial_stack = vec![U256::from_str("0xdeadbeef")?, b, U256::zero()]; - let stack_with_kernel = run(&kernel.code, exp, initial_stack).stack; + let stack_with_kernel = run(&kernel.code, exp, initial_stack)?.stack; let initial_stack = vec![b, U256::zero()]; let code = [0xa, 0x63, 0xde, 0xad, 0xbe, 0xef, 0x56]; // EXP, PUSH4 deadbeef, JUMP - let stack_with_opcode = run(&code, 0, initial_stack).stack; + let stack_with_opcode = run(&code, 0, initial_stack)?.stack; assert_eq!(stack_with_kernel, stack_with_opcode); // 0 exponent let initial_stack = vec![U256::from_str("0xdeadbeef")?, U256::zero(), a]; - let stack_with_kernel = run(&kernel.code, exp, initial_stack).stack; + let stack_with_kernel = run(&kernel.code, exp, initial_stack)?.stack; let initial_stack = vec![U256::zero(), a]; let code = [0xa, 0x63, 0xde, 0xad, 0xbe, 0xef, 0x56]; // EXP, PUSH4 deadbeef, JUMP - let stack_with_opcode = run(&code, 0, initial_stack).stack; + let stack_with_opcode = run(&code, 0, initial_stack)?.stack; assert_eq!(stack_with_kernel, stack_with_opcode); Ok(()) From b29de2c46a78ad3c19244ee5c5506180aae43826 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 18 Jul 2022 09:29:21 -0700 Subject: [PATCH 07/38] tweak --- evm/src/cpu/kernel/asm/memory.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/asm/memory.asm b/evm/src/cpu/kernel/asm/memory.asm index c325ed9d..e3af9954 100644 --- a/evm/src/cpu/kernel/asm/memory.asm +++ b/evm/src/cpu/kernel/asm/memory.asm @@ -1,6 +1,6 @@ // Load a byte from the given segment of the current context's memory space. // Note that main memory values are one byte each, but in general memory values -// can be 256 bits. This macro deals with a single address (unlike MSTORE), so +// can be 256 bits. This macro deals with a single address (unlike MLOAD), so // if it is used with main memory, it will load a single byte. %macro mload_current(segment) // stack: offset From 799d333a903488d30c775f60eb2238c874ab2bc4 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 18 Jul 2022 10:40:02 -0700 Subject: [PATCH 08/38] fix --- evm/src/cpu/kernel/interpreter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index a9507fde..452acd93 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -67,7 +67,7 @@ pub(crate) fn run( }; while interpreter.running { - interpreter.run_opcode(); + interpreter.run_opcode()?; } Ok(interpreter) From 50144a638f7cdd09eddc9851f749c0b5fdf07e5f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 18 Jul 2022 13:48:51 -0700 Subject: [PATCH 09/38] Enable assertions, now working --- evm/src/cpu/kernel/aggregator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 501edd3e..c6c47387 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -23,7 +23,7 @@ pub fn evm_constants() -> HashMap { #[allow(dead_code)] // TODO: Should be used once witness generation is done. pub(crate) fn combined_kernel() -> Kernel { let files = vec![ - // include_str!("asm/assertions.asm"), // TODO: Should work once PR 619 is merged. + include_str!("asm/assertions.asm"), include_str!("asm/basic_macros.asm"), include_str!("asm/exp.asm"), include_str!("asm/curve_mul.asm"), From 6610ec44877740da4eba56019c764d57d7417701 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 18 Jul 2022 14:55:15 -0700 Subject: [PATCH 10/38] Implement memcpy This can be used, for example, to copy `CALL` data (which is a slice of the caller's main memory) to the callee's `CALLDATA` segment. --- evm/src/cpu/kernel/asm/basic_macros.asm | 18 ++++++++ evm/src/cpu/kernel/asm/memory.asm | 56 ++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/asm/basic_macros.asm b/evm/src/cpu/kernel/asm/basic_macros.asm index 7bf001b4..854e0a1a 100644 --- a/evm/src/cpu/kernel/asm/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/basic_macros.asm @@ -26,6 +26,24 @@ %endrep %endmacro +%macro pop5 + %rep 5 + pop + %endrep +%endmacro + +%macro pop6 + %rep 6 + pop + %endrep +%endmacro + +%macro pop7 + %rep 7 + pop + %endrep +%endmacro + %macro add_const(c) // stack: input, ... PUSH $c diff --git a/evm/src/cpu/kernel/asm/memory.asm b/evm/src/cpu/kernel/asm/memory.asm index e3af9954..0bf0fbd1 100644 --- a/evm/src/cpu/kernel/asm/memory.asm +++ b/evm/src/cpu/kernel/asm/memory.asm @@ -1,4 +1,4 @@ -// Load a byte from the given segment of the current context's memory space. +// Load a value from the given segment of the current context's memory space. // Note that main memory values are one byte each, but in general memory values // can be 256 bits. This macro deals with a single address (unlike MLOAD), so // if it is used with main memory, it will load a single byte. @@ -12,7 +12,7 @@ // stack: value %endmacro -// Store a byte to the given segment of the current context's memory space. +// Store a value to the given segment of the current context's memory space. // Note that main memory values are one byte each, but in general memory values // can be 256 bits. This macro deals with a single address (unlike MSTORE), so // if it is used with main memory, it will store a single byte. @@ -25,3 +25,55 @@ MSTORE_GENERAL // stack: (empty) %endmacro + +// Copies `count` values from +// SRC = (src_ctx, src_segment, src_addr) +// to +// DST = (dst_ctx, dst_segment, dst_addr). +// These tuple definitions are used for brevity in the stack comments below. +global memcpy: + JUMPDEST + // stack: DST, SRC, count, retdest + DUP7 + // stack: count, DST, SRC, count, retdest + ISZERO + // stack: count == 0, DST, SRC, count, retdest + %jumpi memcpy_finish + // stack: DST, SRC, count, retdest + + // Copy the next value. + DUP6 + DUP6 + DUP6 + // stack: SRC, DST, SRC, count, retdest + MLOAD_GENERAL + // stack: value, DST, SRC, count, retdest + DUP4 + DUP4 + DUP4 + // stack: DST, value, DST, SRC, count, retdest + MSTORE_GENERAL + // stack: DST, SRC, count, retdest + + // Increment dst_addr. + SWAP2 + %add_const(1) + SWAP2 + // Increment src_addr. + SWAP5 + %add_const(1) + SWAP5 + // Decrement count. + SWAP6 + %sub_const(1) + SWAP6 + + // Recurse! + JUMP memcpy + +memcpy_finish: + JUMPDEST + // stack: DST, SRC, count, retdest + %pop7 + // stack: retdest + JUMP From 80d32f89b6cac50c57f0a76728cbd48b852bee55 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 18 Jul 2022 15:58:12 -0700 Subject: [PATCH 11/38] fixes --- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/memory.asm | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index c6c47387..8784e337 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -28,6 +28,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/exp.asm"), include_str!("asm/curve_mul.asm"), include_str!("asm/curve_add.asm"), + include_str!("asm/memory.asm"), include_str!("asm/moddiv.asm"), include_str!("asm/secp256k1/curve_mul.asm"), include_str!("asm/secp256k1/curve_add.asm"), diff --git a/evm/src/cpu/kernel/asm/memory.asm b/evm/src/cpu/kernel/asm/memory.asm index 0bf0fbd1..42368af5 100644 --- a/evm/src/cpu/kernel/asm/memory.asm +++ b/evm/src/cpu/kernel/asm/memory.asm @@ -38,7 +38,7 @@ global memcpy: // stack: count, DST, SRC, count, retdest ISZERO // stack: count == 0, DST, SRC, count, retdest - %jumpi memcpy_finish + %jumpi(memcpy_finish) // stack: DST, SRC, count, retdest // Copy the next value. @@ -69,7 +69,7 @@ global memcpy: SWAP6 // Recurse! - JUMP memcpy + %jump(memcpy) memcpy_finish: JUMPDEST From 539364c87a597aaa6fe22c0d89d1ba26d11f5e1a Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 18 Jul 2022 21:53:31 -0700 Subject: [PATCH 12/38] clippy --- plonky2/src/gates/gate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plonky2/src/gates/gate.rs b/plonky2/src/gates/gate.rs index 2e6b36ef..781e0cbd 100644 --- a/plonky2/src/gates/gate.rs +++ b/plonky2/src/gates/gate.rs @@ -244,7 +244,7 @@ fn compute_filter(row: usize, group_range: Range, s: K, many_se debug_assert!(group_range.contains(&row)); group_range .filter(|&i| i != row) - .chain(many_selector.then(|| UNUSED_SELECTOR)) + .chain(many_selector.then_some(UNUSED_SELECTOR)) .map(|i| K::from_canonical_usize(i) - s) .product() } @@ -259,7 +259,7 @@ fn compute_filter_circuit, const D: usize>( debug_assert!(group_range.contains(&row)); let v = group_range .filter(|&i| i != row) - .chain(many_selectors.then(|| UNUSED_SELECTOR)) + .chain(many_selectors.then_some(UNUSED_SELECTOR)) .map(|i| { let c = builder.constant_extension(F::Extension::from_canonical_usize(i)); builder.sub_extension(c, s) From e7dbba8d7ba08a3941d491120a60c627e33659d7 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 19 Jul 2022 15:21:44 +0200 Subject: [PATCH 13/38] s/sha3/keccak256 --- evm/src/cpu/columns.rs | 2 +- evm/src/cpu/decode.rs | 2 +- evm/src/cpu/kernel/asm/ecrecover.asm | 8 ++++---- evm/src/cpu/kernel/interpreter.rs | 4 ++-- evm/src/cpu/kernel/opcodes.rs | 1 - 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/evm/src/cpu/columns.rs b/evm/src/cpu/columns.rs index f3a400c6..2f4fe7de 100644 --- a/evm/src/cpu/columns.rs +++ b/evm/src/cpu/columns.rs @@ -52,7 +52,7 @@ pub struct CpuColumnsView { pub is_shl: T, pub is_shr: T, pub is_sar: T, - pub is_sha3: T, + pub is_keccak256: T, pub is_address: T, pub is_balance: T, pub is_origin: T, diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index 0b091558..dce88b3e 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -45,7 +45,7 @@ const OPCODES: [(u64, usize, usize); 102] = [ (0x1c, 0, COL_MAP.is_shr), (0x1d, 0, COL_MAP.is_sar), (0x1e, 1, COL_MAP.is_invalid_1), // 0x1e-0x1f - (0x20, 0, COL_MAP.is_sha3), + (0x20, 0, COL_MAP.is_keccak256), (0x21, 0, COL_MAP.is_invalid_2), (0x22, 1, COL_MAP.is_invalid_3), // 0x22-0x23 (0x24, 2, COL_MAP.is_invalid_4), // 0x24-0x27 diff --git a/evm/src/cpu/kernel/asm/ecrecover.asm b/evm/src/cpu/kernel/asm/ecrecover.asm index 95efd914..538a86dc 100644 --- a/evm/src/cpu/kernel/asm/ecrecover.asm +++ b/evm/src/cpu/kernel/asm/ecrecover.asm @@ -107,7 +107,7 @@ ecrecover_with_first_point: // stack: u2, Y, X, retdest // Compute u2 * GENERATOR and chain the call to `ec_mul` with a call to `ec_add` to compute PUBKEY = (X,Y) + u2 * GENERATOR, - // and a call to `pubkey_to_addr` to get the final result `SHA3(PUBKEY)[-20:]`. + // and a call to `pubkey_to_addr` to get the final result `KECCAK256(PUBKEY)[-20:]`. PUSH pubkey_to_addr // stack: pubkey_to_addr, u2, Y, X, retdest SWAP3 @@ -130,13 +130,13 @@ ecrecover_with_first_point: // stack: Gx, Gy, u2, ec_add_valid_points_secp, X, Y, pubkey_to_addr, retdest %jump(ec_mul_valid_point_secp) -// Take a public key (PKx, PKy) and return the associated address SHA3(PKx || PKy)[-20:]. +// Take a public key (PKx, PKy) and return the associated address KECCAK256(PKx || PKy)[-20:]. pubkey_to_addr: JUMPDEST // stack: PKx, PKy, retdest PUSH 0 // stack: 0, PKx, PKy, retdest - MSTORE + MSTORE // TODO: switch to kernel memory (like `%mstore_current(@SEGMENT_KERNEL_GENERAL)`). // stack: PKy, retdest PUSH 0x20 // stack: 0x20, PKy, retdest @@ -146,7 +146,7 @@ pubkey_to_addr: // stack: 0x40, retdest PUSH 0 // stack: 0, 0x40, retdest - SHA3 + KECCAK256 // stack: hash, retdest PUSH 0xffffffffffffffffffffffffffffffffffffffff // stack: 2^160-1, hash, retdest diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 1839aaf2..cc5b870d 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -125,7 +125,7 @@ impl<'a> Interpreter<'a> { 0x1b => todo!(), // "SHL", 0x1c => todo!(), // "SHR", 0x1d => todo!(), // "SAR", - 0x20 => self.run_sha3(), // "SHA3", + 0x20 => self.run_keccak256(), // "KECCAK256", 0x30 => todo!(), // "ADDRESS", 0x31 => todo!(), // "BALANCE", 0x32 => todo!(), // "ORIGIN", @@ -292,7 +292,7 @@ impl<'a> Interpreter<'a> { self.push(!x); } - fn run_sha3(&mut self) { + fn run_keccak256(&mut self) { let offset = self.pop().as_usize(); let size = self.pop().as_usize(); let bytes = (offset..offset + size) diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index 6ca359b7..b8633178 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -35,7 +35,6 @@ pub(crate) fn get_opcode(mnemonic: &str) -> u8 { "SHR" => 0x1c, "SAR" => 0x1d, "KECCAK256" => 0x20, - "SHA3" => 0x20, "ADDRESS" => 0x30, "BALANCE" => 0x31, "ORIGIN" => 0x32, From a8ce2a60730e0244ef21c0d68f9216f57acde1c0 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 19 Jul 2022 15:27:51 +0200 Subject: [PATCH 14/38] Import fix --- evm/src/cpu/kernel/interpreter.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 3ee95e22..7c59fe66 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -1,6 +1,5 @@ use anyhow::bail; use ethereum_types::{BigEndianHash, U256, U512}; -use ethereum_types::{U256, U512}; use keccak_hash::keccak; /// Halt interpreter execution whenever a jump to this offset is done. From b9b3c24cf9b852169b7090afa3c83e3793f371ca Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 18 Jul 2022 21:01:06 -0700 Subject: [PATCH 15/38] `PROVER_INPUT` instruction --- evm/src/cpu/columns.rs | 2 +- evm/src/cpu/decode.rs | 18 +++++++++--------- evm/src/cpu/kernel/interpreter.rs | 28 +++++++++++++++++++++++++++- evm/src/generation/mod.rs | 19 +++++++++++++++---- evm/src/generation/state.rs | 4 ++++ 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/evm/src/cpu/columns.rs b/evm/src/cpu/columns.rs index c79f9e9d..42b0a5bc 100644 --- a/evm/src/cpu/columns.rs +++ b/evm/src/cpu/columns.rs @@ -78,6 +78,7 @@ pub struct CpuColumnsView { pub is_chainid: T, pub is_selfbalance: T, pub is_basefee: T, + pub is_prover_input: T, pub is_pop: T, pub is_mload: T, pub is_mstore: T, @@ -134,7 +135,6 @@ pub struct CpuColumnsView { pub is_invalid_11: T, pub is_invalid_12: T, pub is_invalid_13: T, - pub is_invalid_14: T, /// If CPU cycle: the opcode, broken up into bits in **big-endian** order. pub opcode_bits: [T; 8], diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index 22a25dc6..bd7ea5fe 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -75,9 +75,9 @@ const OPCODES: [(u64, usize, usize); 107] = [ (0x46, 0, COL_MAP.is_chainid), (0x47, 0, COL_MAP.is_selfbalance), (0x48, 0, COL_MAP.is_basefee), - (0x49, 0, COL_MAP.is_invalid_6), - (0x4a, 1, COL_MAP.is_invalid_7), // 0x4a-0x4b - (0x4c, 2, COL_MAP.is_invalid_8), // 0x4c-0x4f + (0x49, 0, COL_MAP.is_prover_input), + (0x4a, 1, COL_MAP.is_invalid_6), // 0x4a-0x4b + (0x4c, 2, COL_MAP.is_invalid_7), // 0x4c-0x4f (0x50, 0, COL_MAP.is_pop), (0x51, 0, COL_MAP.is_mload), (0x52, 0, COL_MAP.is_mstore), @@ -103,11 +103,11 @@ const OPCODES: [(u64, usize, usize); 107] = [ (0xa3, 0, COL_MAP.is_log3), (0xa4, 0, COL_MAP.is_log4), (0xa5, 0, COL_MAP.is_panic), - (0xa6, 1, COL_MAP.is_invalid_9), // 0xa6-0xa7 - (0xa8, 3, COL_MAP.is_invalid_10), // 0xa8-0xaf - (0xb0, 4, COL_MAP.is_invalid_11), // 0xb0-0xbf - (0xc0, 5, COL_MAP.is_invalid_12), // 0xc0-0xdf - (0xe0, 4, COL_MAP.is_invalid_13), // 0xe0-0xef + (0xa6, 1, COL_MAP.is_invalid_8), // 0xa6-0xa7 + (0xa8, 3, COL_MAP.is_invalid_9), // 0xa8-0xaf + (0xb0, 4, COL_MAP.is_invalid_10), // 0xb0-0xbf + (0xc0, 5, COL_MAP.is_invalid_11), // 0xc0-0xdf + (0xe0, 4, COL_MAP.is_invalid_12), // 0xe0-0xef (0xf0, 0, COL_MAP.is_create), (0xf1, 0, COL_MAP.is_call), (0xf2, 0, COL_MAP.is_callcode), @@ -122,7 +122,7 @@ const OPCODES: [(u64, usize, usize); 107] = [ (0xfb, 0, COL_MAP.is_mload_general), (0xfc, 0, COL_MAP.is_mstore_general), (0xfd, 0, COL_MAP.is_revert), - (0xfe, 0, COL_MAP.is_invalid_14), + (0xfe, 0, COL_MAP.is_invalid_13), (0xff, 0, COL_MAP.is_selfdestruct), ]; diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 452acd93..aa5d1ac3 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -1,4 +1,4 @@ -use anyhow::bail; +use anyhow::{anyhow, bail}; use ethereum_types::{U256, U512}; /// Halt interpreter execution whenever a jump to this offset is done. @@ -49,6 +49,9 @@ pub(crate) struct Interpreter<'a> { offset: usize, pub(crate) stack: Vec, pub(crate) memory: EvmMemory, + /// Non-deterministic prover inputs, stored backwards so that popping the last item gives the + /// next prover input. + prover_inputs: Vec, running: bool, } @@ -57,12 +60,25 @@ pub(crate) fn run( initial_offset: usize, initial_stack: Vec, ) -> anyhow::Result { + run_with_input(code, initial_offset, initial_stack, vec![]) +} + +pub(crate) fn run_with_input( + code: &[u8], + initial_offset: usize, + initial_stack: Vec, + mut prover_inputs: Vec, +) -> anyhow::Result { + // Prover inputs are stored backwards, so that popping the last item gives the next input. + prover_inputs.reverse(); + let mut interpreter = Interpreter { code, jumpdests: find_jumpdests(code), offset: initial_offset, stack: initial_stack, memory: EvmMemory::default(), + prover_inputs, running: true, }; @@ -149,6 +165,7 @@ impl<'a> Interpreter<'a> { 0x45 => todo!(), // "GASLIMIT", 0x46 => todo!(), // "CHAINID", 0x48 => todo!(), // "BASEFEE", + 0x49 => self.run_prover_input()?, // "PROVER_INPUT", 0x50 => self.run_pop(), // "POP", 0x51 => self.run_mload(), // "MLOAD", 0x52 => self.run_mstore(), // "MSTORE", @@ -303,6 +320,15 @@ impl<'a> Interpreter<'a> { self.push(!x); } + fn run_prover_input(&mut self) -> anyhow::Result<()> { + let input = self + .prover_inputs + .pop() + .ok_or_else(|| anyhow!("Out of prover inputs"))?; + self.stack.push(input); + Ok(()) + } + fn run_pop(&mut self) { self.pop(); } diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index b73270db..b8ab0376 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -1,3 +1,4 @@ +use ethereum_types::U256; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; @@ -45,18 +46,28 @@ pub fn generate_traces, const D: usize>( current_cpu_row, memory, keccak_inputs, - logic_ops: logic_inputs, + logic_ops, + prover_inputs, .. } = state; assert_eq!(current_cpu_row, [F::ZERO; NUM_CPU_COLUMNS].into()); + assert_eq!(prover_inputs, vec![], "Not all prover inputs were consumed"); 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 logic_trace = all_stark.logic_stark.generate_trace(logic_ops); 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!() +fn generate_txn(state: &mut GenerationState, txn: &TransactionData) { + // TODO: Add transaction RLP to prover_input. + + // Supply Merkle trie proofs as prover inputs. + for proof in &txn.trie_proofs { + let proof = proof + .iter() + .flat_map(|node_rlp| node_rlp.iter().map(|byte| U256::from(*byte))); + state.prover_inputs.extend(proof); + } } diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index 46ccc4e3..c7f1003e 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -19,6 +19,9 @@ pub(crate) struct GenerationState { pub(crate) keccak_inputs: Vec<[u64; keccak::keccak_stark::NUM_INPUTS]>, pub(crate) logic_ops: Vec, + + /// Non-deterministic inputs provided by the prover. + pub(crate) prover_inputs: Vec, } impl GenerationState { @@ -111,6 +114,7 @@ impl Default for GenerationState { memory: MemoryState::default(), keccak_inputs: vec![], logic_ops: vec![], + prover_inputs: vec![], } } } From 5b1f5640398132105f996ce093b90d43cb4d7e12 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 19 Jul 2022 07:20:57 -0700 Subject: [PATCH 16/38] Feedback --- evm/src/cpu/kernel/asm/memory.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/asm/memory.asm b/evm/src/cpu/kernel/asm/memory.asm index 42368af5..88748a3e 100644 --- a/evm/src/cpu/kernel/asm/memory.asm +++ b/evm/src/cpu/kernel/asm/memory.asm @@ -68,7 +68,7 @@ global memcpy: %sub_const(1) SWAP6 - // Recurse! + // Continue the loop. %jump(memcpy) memcpy_finish: From 3dc79274a83154b0aa69cdef41e57990c39517e5 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 19 Jul 2022 10:36:18 -0700 Subject: [PATCH 17/38] Add a `mload_kernel_code_u32` macro Intended for loading constants in SHA2, and maybe RIPEMD. Sample usage ``` // Loads the i'th K256 constant. %macro k256 // stack: i %mul_const(4) // stack: 4*i PUSH k256_data // stack: k256_data, 4*i ADD // stack: k256_data + 4*i %mload_kernel_code_u32 // stack: K256[4*i] %endmacro k256_data: BYTES 0x42, 0x8a, 0x2f, 0x98 BYTES 0x71, 0x37, 0x44, 0x91 ... ``` Untested for now since our interpreter doesn't have the needed memory support quite yet. --- evm/src/cpu/kernel/asm/basic_macros.asm | 7 +++++ evm/src/cpu/kernel/asm/memory.asm | 41 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/evm/src/cpu/kernel/asm/basic_macros.asm b/evm/src/cpu/kernel/asm/basic_macros.asm index 854e0a1a..20f8958c 100644 --- a/evm/src/cpu/kernel/asm/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/basic_macros.asm @@ -82,6 +82,13 @@ // stack: input / c, ... %endmacro +%macro shl_const(c) + // stack: input, ... + PUSH $c + SHL + // stack: input << c, ... +%endmacro + %macro eq_const(c) // stack: input, ... PUSH $c diff --git a/evm/src/cpu/kernel/asm/memory.asm b/evm/src/cpu/kernel/asm/memory.asm index 88748a3e..26d0b855 100644 --- a/evm/src/cpu/kernel/asm/memory.asm +++ b/evm/src/cpu/kernel/asm/memory.asm @@ -26,6 +26,47 @@ // stack: (empty) %endmacro +// Load a single byte from kernel code. +%macro mload_kernel_code + // stack: offset + PUSH @SEGMENT_CODE + // stack: segment, offset + PUSH 0 // kernel has context 0 + // stack: context, segment, offset + MLOAD_GENERAL + // stack: value +%endmacro + +// Load a big-endian u32, consisting of 4 bytes (c_3, c_2, c_1, c_0), +// from kernel code. +%macro mload_kernel_code_u32 + // stack: offset + DUP1 + %mload_kernel_code + // stack: c_3, offset + %shl_const(8) + // stack: c_3 << 8, offset + DUP2 + %add_const(1) + %mload_kernel_code + OR + // stack: (c_3 << 8) | c_2, offset + %shl_const(8) + // stack: ((c_3 << 8) | c_2) << 8, offset + DUP2 + %add_const(2) + %mload_kernel_code + OR + // stack: (((c_3 << 8) | c_2) << 8) | c_1, offset + %shl_const(8) + // stack: ((((c_3 << 8) | c_2) << 8) | c_1) << 8, offset + SWAP1 + %add_const(3) + %mload_kernel_code + OR + // stack: (((((c_3 << 8) | c_2) << 8) | c_1) << 8) | c_0 +%endmacro + // Copies `count` values from // SRC = (src_ctx, src_segment, src_addr) // to From 05a1fbfbae7125291ee5842ff7587ead38534874 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 19 Jul 2022 15:28:34 -0700 Subject: [PATCH 18/38] Stack manipulation macro Uses a variant of Dijkstra's, with a few pruning mechanics, to find a path of instructions between the two stack states. We don't explicitly store the graph though. The Dijkstra implementation is somewhat inspired by the `pathfinding` crate. That crate doesn't quite fit our needs though. If we need to make it faster later, there are a lot of allocations and clones that we could probably eliminate. --- evm/src/cpu/kernel/asm/curve_add.asm | 24 +-- evm/src/cpu/kernel/assembler.rs | 23 ++- evm/src/cpu/kernel/ast.rs | 15 +- evm/src/cpu/kernel/evm_asm.pest | 11 +- evm/src/cpu/kernel/mod.rs | 1 + evm/src/cpu/kernel/parser.rs | 41 ++++- evm/src/cpu/kernel/stack_manipulation.rs | 224 +++++++++++++++++++++++ 7 files changed, 306 insertions(+), 33 deletions(-) create mode 100644 evm/src/cpu/kernel/stack_manipulation.rs diff --git a/evm/src/cpu/kernel/asm/curve_add.asm b/evm/src/cpu/kernel/asm/curve_add.asm index 4ac4e0e4..f6275787 100644 --- a/evm/src/cpu/kernel/asm/curve_add.asm +++ b/evm/src/cpu/kernel/asm/curve_add.asm @@ -111,18 +111,7 @@ ec_add_snd_zero: // stack: x0, y0, x1, y1, retdest // Just return (x1,y1) - SWAP2 - // stack: x1, y0, x0, y1, retdest - POP - // stack: y0, x0, y1, retdest - SWAP2 - // stack: y1, x0, y0, retdest - POP - // stack: x0, y0, retdest - SWAP1 - // stack: y0, x0, retdest - SWAP2 - // stack: retdest, x0, y0 + %stack (x0, y0, x1, y1, retdest) -> (retdest, x0, y0) JUMP // BN254 elliptic curve addition. @@ -170,16 +159,7 @@ ec_add_valid_points_with_lambda: // stack: y2, x2, lambda, x0, y0, x1, y1, retdest // Return x2,y2 - SWAP5 - // stack: x1, x2, lambda, x0, y0, y2, y1, retdest - POP - // stack: x2, lambda, x0, y0, y2, y1, retdest - SWAP5 - // stack: y1, lambda, x0, y0, y2, x2, retdest - %pop4 - // stack: y2, x2, retdest - SWAP2 - // stack: retdest, x2, y2 + %stack (y2, x2, lambda, x0, y0, x1, y1, retdest) -> (retdest, x2, y2) JUMP // BN254 elliptic curve addition. diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 8b7327dc..ad17ae4c 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -7,6 +7,7 @@ use log::debug; use super::ast::PushTarget; use crate::cpu::kernel::ast::Literal; use crate::cpu::kernel::keccak_util::hash_kernel; +use crate::cpu::kernel::stack_manipulation::expand_stack_manipulation; use crate::cpu::kernel::{ ast::{File, Item}, opcodes::{get_opcode, get_push_opcode}, @@ -63,6 +64,7 @@ pub(crate) fn assemble(files: Vec, constants: HashMap) -> Ke let expanded_file = expand_macros(file.body, ¯os); let expanded_file = expand_repeats(expanded_file); let expanded_file = inline_constants(expanded_file, &constants); + let expanded_file = expand_stack_manipulation(expanded_file); local_labels.push(find_labels(&expanded_file, &mut offset, &mut global_labels)); expanded_files.push(expanded_file); } @@ -187,8 +189,11 @@ fn find_labels( let mut local_labels = HashMap::::new(); for item in body { match item { - Item::MacroDef(_, _, _) | Item::MacroCall(_, _) | Item::Repeat(_, _) => { - panic!("Macros and repeats should have been expanded already") + Item::MacroDef(_, _, _) + | Item::MacroCall(_, _) + | Item::Repeat(_, _) + | Item::StackManipulation(_, _) => { + panic!("Item should have been expanded already: {:?}", item); } Item::GlobalLabelDeclaration(label) => { let old = global_labels.insert(label.clone(), *offset); @@ -215,8 +220,11 @@ fn assemble_file( // Assemble the file. for item in body { match item { - Item::MacroDef(_, _, _) | Item::MacroCall(_, _) | Item::Repeat(_, _) => { - panic!("Macros and repeats should have been expanded already") + Item::MacroDef(_, _, _) + | Item::MacroCall(_, _) + | Item::Repeat(_, _) + | Item::StackManipulation(_, _) => { + panic!("Item should have been expanded already: {:?}", item); } Item::GlobalLabelDeclaration(_) | Item::LocalLabelDeclaration(_) => { // Nothing to do; we processed labels in the prior phase. @@ -427,6 +435,13 @@ mod tests { assert_eq!(kernel.code, vec![add, add, add]); } + #[test] + fn stack_manipulation() { + let kernel = parse_and_assemble(&["%stack (a, b, c) -> (c, b, a)"]); + let swap2 = get_opcode("SWAP2"); + assert_eq!(kernel.code, vec![swap2]); + } + fn parse_and_assemble(files: &[&str]) -> Kernel { parse_and_assemble_with_constants(files, HashMap::new()) } diff --git a/evm/src/cpu/kernel/ast.rs b/evm/src/cpu/kernel/ast.rs index 9bb315ff..92728104 100644 --- a/evm/src/cpu/kernel/ast.rs +++ b/evm/src/cpu/kernel/ast.rs @@ -14,6 +14,11 @@ pub(crate) enum Item { MacroCall(String, Vec), /// Repetition, like `%rep` in NASM. Repeat(Literal, Vec), + /// A directive to manipulate the stack according to a specified pattern. + /// The first list gives names to items on the top of the stack. + /// The second list specifies replacement items. + /// Example: `(a, b, c) -> (c, 5, 0x20, @SOME_CONST, a)`. + StackManipulation(Vec, Vec), /// Declares a global label. GlobalLabelDeclaration(String), /// Declares a label that is local to the current file. @@ -26,6 +31,14 @@ pub(crate) enum Item { Bytes(Vec), } +#[derive(Clone, Debug)] +pub(crate) enum StackReplacement { + NamedItem(String), + Literal(Literal), + MacroVar(String), + Constant(String), +} + /// The target of a `PUSH` operation. #[derive(Clone, Debug)] pub(crate) enum PushTarget { @@ -35,7 +48,7 @@ pub(crate) enum PushTarget { Constant(String), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) enum Literal { Decimal(String), Hex(String), diff --git a/evm/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest index d5a89d99..78938b64 100644 --- a/evm/src/cpu/kernel/evm_asm.pest +++ b/evm/src/cpu/kernel/evm_asm.pest @@ -15,12 +15,15 @@ literal = { literal_hex | literal_decimal } variable = ${ "$" ~ identifier } constant = ${ "@" ~ identifier } -item = { macro_def | macro_call | repeat | global_label | local_label | bytes_item | push_instruction | nullary_instruction } -macro_def = { ^"%macro" ~ identifier ~ macro_paramlist? ~ item* ~ ^"%endmacro" } -macro_call = ${ "%" ~ !(^"macro" | ^"endmacro" | ^"rep" | ^"endrep") ~ identifier ~ macro_arglist? } +item = { macro_def | macro_call | repeat | stack | global_label | local_label | bytes_item | push_instruction | nullary_instruction } +macro_def = { ^"%macro" ~ identifier ~ paramlist? ~ item* ~ ^"%endmacro" } +macro_call = ${ "%" ~ !(^"macro" | ^"endmacro" | ^"rep" | ^"endrep" | ^"stack") ~ identifier ~ macro_arglist? } repeat = { ^"%rep" ~ literal ~ item* ~ ^"%endrep" } -macro_paramlist = { "(" ~ identifier ~ ("," ~ identifier)* ~ ")" } +paramlist = { "(" ~ identifier ~ ("," ~ identifier)* ~ ")" } macro_arglist = !{ "(" ~ push_target ~ ("," ~ push_target)* ~ ")" } +stack = { ^"%stack" ~ paramlist ~ "->" ~ stack_replacements } +stack_replacements = { "(" ~ stack_replacement ~ ("," ~ stack_replacement)* ~ ")" } +stack_replacement = { literal | identifier | constant } global_label = { ^"GLOBAL " ~ identifier ~ ":" } local_label = { identifier ~ ":" } bytes_item = { ^"BYTES " ~ literal ~ ("," ~ literal)* } diff --git a/evm/src/cpu/kernel/mod.rs b/evm/src/cpu/kernel/mod.rs index 2dd70aa3..1f13a042 100644 --- a/evm/src/cpu/kernel/mod.rs +++ b/evm/src/cpu/kernel/mod.rs @@ -4,6 +4,7 @@ mod ast; pub(crate) mod keccak_util; mod opcodes; mod parser; +mod stack_manipulation; #[cfg(test)] mod interpreter; diff --git a/evm/src/cpu/kernel/parser.rs b/evm/src/cpu/kernel/parser.rs index b8ac3f40..aa84ee05 100644 --- a/evm/src/cpu/kernel/parser.rs +++ b/evm/src/cpu/kernel/parser.rs @@ -1,7 +1,7 @@ use pest::iterators::Pair; use pest::Parser; -use crate::cpu::kernel::ast::{File, Item, Literal, PushTarget}; +use crate::cpu::kernel::ast::{File, Item, Literal, PushTarget, StackReplacement}; /// Parses EVM assembly code. #[derive(pest_derive::Parser)] @@ -24,6 +24,7 @@ fn parse_item(item: Pair) -> Item { Rule::macro_def => parse_macro_def(item), Rule::macro_call => parse_macro_call(item), Rule::repeat => parse_repeat(item), + Rule::stack => parse_stack(item), Rule::global_label => { Item::GlobalLabelDeclaration(item.into_inner().next().unwrap().as_str().into()) } @@ -44,7 +45,7 @@ fn parse_macro_def(item: Pair) -> Item { let name = inner.next().unwrap().as_str().into(); // The parameter list is optional. - let params = if let Some(Rule::macro_paramlist) = inner.peek().map(|pair| pair.as_rule()) { + let params = if let Some(Rule::paramlist) = inner.peek().map(|pair| pair.as_rule()) { let params = inner.next().unwrap().into_inner(); params.map(|param| param.as_str().to_string()).collect() } else { @@ -78,6 +79,42 @@ fn parse_repeat(item: Pair) -> Item { Item::Repeat(count, inner.map(parse_item).collect()) } +fn parse_stack(item: Pair) -> Item { + assert_eq!(item.as_rule(), Rule::stack); + let mut inner = item.into_inner().peekable(); + + let params = inner.next().unwrap(); + assert_eq!(params.as_rule(), Rule::paramlist); + let replacements = inner.next().unwrap(); + assert_eq!(replacements.as_rule(), Rule::stack_replacements); + + let params = params + .into_inner() + .map(|param| param.as_str().to_string()) + .collect(); + let replacements = replacements + .into_inner() + .map(parse_stack_replacement) + .collect(); + Item::StackManipulation(params, replacements) +} + +fn parse_stack_replacement(target: Pair) -> StackReplacement { + assert_eq!(target.as_rule(), Rule::stack_replacement); + let inner = target.into_inner().next().unwrap(); + match inner.as_rule() { + Rule::identifier => StackReplacement::NamedItem(inner.as_str().into()), + Rule::literal => StackReplacement::Literal(parse_literal(inner)), + Rule::variable => { + StackReplacement::MacroVar(inner.into_inner().next().unwrap().as_str().into()) + } + Rule::constant => { + StackReplacement::Constant(inner.into_inner().next().unwrap().as_str().into()) + } + _ => panic!("Unexpected {:?}", inner.as_rule()), + } +} + fn parse_push_target(target: Pair) -> PushTarget { assert_eq!(target.as_rule(), Rule::push_target); let inner = target.into_inner().next().unwrap(); diff --git a/evm/src/cpu/kernel/stack_manipulation.rs b/evm/src/cpu/kernel/stack_manipulation.rs new file mode 100644 index 00000000..b09456ac --- /dev/null +++ b/evm/src/cpu/kernel/stack_manipulation.rs @@ -0,0 +1,224 @@ +use std::cmp::Ordering; +use std::collections::hash_map::Entry::{Occupied, Vacant}; +use std::collections::{BinaryHeap, HashMap}; + +use itertools::Itertools; + +use crate::cpu::columns::NUM_CPU_COLUMNS; +use crate::cpu::kernel::ast::{Item, Literal, PushTarget, StackReplacement}; +use crate::cpu::kernel::stack_manipulation::StackOp::Pop; +use crate::memory; + +pub(crate) fn expand_stack_manipulation(body: Vec) -> Vec { + let mut expanded = vec![]; + for item in body { + if let Item::StackManipulation(names, replacements) = item { + expanded.extend(expand(names, replacements)); + } else { + expanded.push(item); + } + } + expanded +} + +fn expand(names: Vec, replacements: Vec) -> Vec { + let mut src = names.into_iter().map(StackItem::NamedItem).collect_vec(); + + let unique_literals = replacements + .iter() + .filter_map(|item| match item { + StackReplacement::Literal(n) => Some(n.clone()), + _ => None, + }) + .unique() + .collect_vec(); + let all_ops = StackOp::all(unique_literals); + + let mut dst = replacements + .into_iter() + .map(|item| match item { + StackReplacement::NamedItem(name) => StackItem::NamedItem(name), + StackReplacement::Literal(n) => StackItem::Literal(n), + StackReplacement::MacroVar(_) | StackReplacement::Constant(_) => { + panic!("Should have been expanded earlier") + } + }) + .collect_vec(); + + // %stack uses our convention where the top item is written on the left side. + // `shortest_path` expects the opposite, so we reverse src and dst. + src.reverse(); + dst.reverse(); + + let path = shortest_path(src, dst, all_ops); + path.into_iter().map(StackOp::into_item).collect() +} + +/// Finds the lowest-cost sequence of `StackOp`s that transforms `src` to `dst`. +/// Uses a variant of Dijkstra's algorithm. +fn shortest_path(src: Vec, dst: Vec, all_ops: Vec) -> Vec { + // Nodes to visit, starting with the lowest-cost node. + let mut queue = BinaryHeap::new(); + queue.push(Node { + stack: src.clone(), + cost: 0, + }); + + // For each node, stores `(best_cost, Option<(parent, op)>)`. + let mut node_info = HashMap::, (u32, Option<(Vec, StackOp)>)>::new(); + node_info.insert(src.clone(), (0, None)); + + while let Some(node) = queue.pop() { + if node.stack == dst { + // The destination is now the lowest-cost node, so we must have found the best path. + let mut path = vec![]; + let mut stack = &node.stack; + // Rewind back to src, recording a list of operations which will be backwards. + while let Some((parent, op)) = &node_info[stack].1 { + stack = parent; + path.push(op.clone()); + } + assert_eq!(stack, &src); + path.reverse(); + return path; + } + + let (best_cost, _) = node_info[&node.stack]; + if best_cost < node.cost { + // Since we can't efficiently remove nodes from the heap, it can contain duplicates. + // In this case, we've already visited this stack state with a lower cost. + continue; + } + + for op in &all_ops { + let neighbor = match op.apply_to(node.stack.clone()) { + Some(n) => n, + None => continue, + }; + + let cost = node.cost + op.cost(); + let entry = node_info.entry(neighbor.clone()); + if let Occupied(e) = &entry && e.get().0 <= cost { + // We already found a better or equal path. + continue; + } + + let neighbor_info = (cost, Some((node.stack.clone(), op.clone()))); + match entry { + Occupied(mut e) => { + e.insert(neighbor_info); + } + Vacant(e) => { + e.insert(neighbor_info); + } + } + + queue.push(Node { + stack: neighbor, + cost, + }); + } + } + + panic!("No path found from {:?} to {:?}", src, dst) +} + +/// A node in the priority queue used by Dijkstra's algorithm. +#[derive(Eq, PartialEq)] +struct Node { + stack: Vec, + cost: u32, +} + +impl PartialOrd for Node { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Node { + fn cmp(&self, other: &Self) -> Ordering { + // We want a min-heap rather than the default max-heap, so this is the opposite of the + // natural ordering of costs. + other.cost.cmp(&self.cost) + } +} + +/// Like `StackReplacement`, but without constants or macro vars, since those were expanded already. +#[derive(Eq, PartialEq, Hash, Clone, Debug)] +enum StackItem { + NamedItem(String), + Literal(Literal), +} + +#[derive(Clone, Debug)] +enum StackOp { + Push(Literal), + Pop, + Dup(u8), + Swap(u8), +} + +fn get_ops(src: Vec, dst: Vec) -> impl Iterator { + +} + +impl StackOp { + fn all(literals: Vec) -> Vec { + let mut all = literals.into_iter().map(StackOp::Push).collect_vec(); + all.push(Pop); + all.extend((1..=32).map(StackOp::Dup)); + all.extend((1..=32).map(StackOp::Swap)); + all + } + + fn cost(&self) -> u32 { + let (cpu_rows, memory_rows) = match self { + StackOp::Push(n) => { + let bytes = n.to_trimmed_be_bytes().len() as u32; + // This is just a rough estimate; we can update it after implementing PUSH. + (bytes, bytes) + } + Pop => (1, 1), + StackOp::Dup(_) => (1, 2), + StackOp::Swap(_) => (1, 4), + }; + + let cpu_cost = cpu_rows * NUM_CPU_COLUMNS as u32; + let memory_cost = memory_rows * memory::columns::NUM_COLUMNS as u32; + cpu_cost + memory_cost + } + + /// Returns an updated stack after this operation is performed, or `None` if this operation + /// would not be valid on the given stack. + fn apply_to(&self, mut stack: Vec) -> Option> { + let len = stack.len(); + match self { + StackOp::Push(n) => { + stack.push(StackItem::Literal(n.clone())); + } + Pop => { + stack.pop()?; + } + StackOp::Dup(n) => { + let idx = len.checked_sub(*n as usize)?; + stack.push(stack[idx].clone()); + } + StackOp::Swap(n) => { + let from = len.checked_sub(1)?; + let to = len.checked_sub(*n as usize + 1)?; + stack.swap(from, to); + } + } + Some(stack) + } + + fn into_item(self) -> Item { + match self { + StackOp::Push(n) => Item::Push(PushTarget::Literal(n)), + Pop => Item::StandardOp("POP".into()), + StackOp::Dup(n) => Item::StandardOp(format!("DUP{}", n)), + StackOp::Swap(n) => Item::StandardOp(format!("SWAP{}", n)), + } + } +} From 1a0d6f44137c697a5b8b0d7944402d586e2ca5be Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 19 Jul 2022 23:43:29 -0700 Subject: [PATCH 19/38] Pruning --- evm/src/cpu/kernel/assembler.rs | 8 +++- evm/src/cpu/kernel/stack_manipulation.rs | 61 +++++++++++++++++++----- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index ad17ae4c..7f793555 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -437,9 +437,15 @@ mod tests { #[test] fn stack_manipulation() { - let kernel = parse_and_assemble(&["%stack (a, b, c) -> (c, b, a)"]); + let pop = get_opcode("POP"); + let swap1 = get_opcode("SWAP1"); let swap2 = get_opcode("SWAP2"); + + let kernel = parse_and_assemble(&["%stack (a, b, c) -> (c, b, a)"]); assert_eq!(kernel.code, vec![swap2]); + + let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); + assert_eq!(kernel.code, vec![pop, swap1, pop]); } fn parse_and_assemble(files: &[&str]) -> Kernel { diff --git a/evm/src/cpu/kernel/stack_manipulation.rs b/evm/src/cpu/kernel/stack_manipulation.rs index b09456ac..140cfd6a 100644 --- a/evm/src/cpu/kernel/stack_manipulation.rs +++ b/evm/src/cpu/kernel/stack_manipulation.rs @@ -32,7 +32,6 @@ fn expand(names: Vec, replacements: Vec) -> Vec }) .unique() .collect_vec(); - let all_ops = StackOp::all(unique_literals); let mut dst = replacements .into_iter() @@ -50,13 +49,17 @@ fn expand(names: Vec, replacements: Vec) -> Vec src.reverse(); dst.reverse(); - let path = shortest_path(src, dst, all_ops); + let path = shortest_path(src, dst, unique_literals); path.into_iter().map(StackOp::into_item).collect() } /// Finds the lowest-cost sequence of `StackOp`s that transforms `src` to `dst`. /// Uses a variant of Dijkstra's algorithm. -fn shortest_path(src: Vec, dst: Vec, all_ops: Vec) -> Vec { +fn shortest_path( + src: Vec, + dst: Vec, + unique_literals: Vec, +) -> Vec { // Nodes to visit, starting with the lowest-cost node. let mut queue = BinaryHeap::new(); queue.push(Node { @@ -90,7 +93,7 @@ fn shortest_path(src: Vec, dst: Vec, all_ops: Vec continue; } - for op in &all_ops { + for op in next_ops(&node.stack, &dst, &unique_literals) { let neighbor = match op.apply_to(node.stack.clone()) { Some(n) => n, None => continue, @@ -159,19 +162,51 @@ enum StackOp { Swap(u8), } -fn get_ops(src: Vec, dst: Vec) -> impl Iterator { +/// A set of candidate operations to consider for the next step in the path from `src` to `dst`. +fn next_ops(src: &[StackItem], dst: &[StackItem], unique_literals: &[Literal]) -> Vec { + if let Some(top) = src.last() && !dst.contains(top) { + // If the top of src doesn't appear in dst, don't bother with anything other than a POP. + return vec![StackOp::Pop] + } + let mut ops = vec![StackOp::Pop]; + + ops.extend( + unique_literals + .iter() + // Only consider pushing this literal if we need more occurrences of it, otherwise swaps + // will be a better way to rearrange the existing occurrences as needed. + .filter(|lit| { + let item = StackItem::Literal((*lit).clone()); + let src_count = src.iter().filter(|x| **x == item).count(); + let dst_count = dst.iter().filter(|x| **x == item).count(); + src_count < dst_count + }) + .cloned() + .map(StackOp::Push), + ); + + let src_len = src.len() as u8; + + ops.extend( + (1..=src_len) + // Only consider duplicating this item if we need more occurrences of it, otherwise swaps + // will be a better way to rearrange the existing occurrences as needed. + .filter(|i| { + let item = &src[src.len() - *i as usize]; + let src_count = src.iter().filter(|x| *x == item).count(); + let dst_count = dst.iter().filter(|x| *x == item).count(); + src_count < dst_count + }) + .map(StackOp::Dup), + ); + + ops.extend((1..src_len).map(StackOp::Swap)); + + ops } impl StackOp { - fn all(literals: Vec) -> Vec { - let mut all = literals.into_iter().map(StackOp::Push).collect_vec(); - all.push(Pop); - all.extend((1..=32).map(StackOp::Dup)); - all.extend((1..=32).map(StackOp::Swap)); - all - } - fn cost(&self) -> u32 { let (cpu_rows, memory_rows) = match self { StackOp::Push(n) => { From 78fb34a9b65cd52ac603c20e78d5d8ccec4898b1 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 20 Jul 2022 00:10:52 -0700 Subject: [PATCH 20/38] Minor --- evm/Cargo.toml | 2 +- evm/src/cpu/kernel/aggregator.rs | 7 +++++ evm/src/cpu/kernel/asm/curve_add.asm | 24 ++--------------- evm/src/cpu/kernel/assembler.rs | 34 +++++++++++++++++++----- evm/src/cpu/kernel/stack_manipulation.rs | 2 +- 5 files changed, 39 insertions(+), 30 deletions(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 1e22ef33..c10ab104 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1.0.40" env_logger = "0.9.0" ethereum-types = "0.13.1" hex = { version = "0.4.3", optional = true } +hex-literal = "0.3.4" itertools = "0.10.3" log = "0.4.14" once_cell = "1.13.0" @@ -24,7 +25,6 @@ keccak-rust = { git = "https://github.com/npwardberkeley/keccak-rust" } keccak-hash = "0.9.0" [dev-dependencies] -hex-literal = "0.3.4" hex = "0.4.3" [features] diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 8784e337..ec42f5c4 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use ethereum_types::U256; +use hex_literal::hex; use itertools::Itertools; use once_cell::sync::Lazy; @@ -14,6 +15,12 @@ pub static KERNEL: Lazy = Lazy::new(combined_kernel); pub fn evm_constants() -> HashMap { let mut c = HashMap::new(); + c.insert( + "BN_BASE".into(), + U256::from_big_endian(&hex!( + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47" + )), + ); for segment in Segment::all() { c.insert(segment.var_name().into(), (segment as u32).into()); } diff --git a/evm/src/cpu/kernel/asm/curve_add.asm b/evm/src/cpu/kernel/asm/curve_add.asm index f6275787..541b1605 100644 --- a/evm/src/cpu/kernel/asm/curve_add.asm +++ b/evm/src/cpu/kernel/asm/curve_add.asm @@ -94,14 +94,8 @@ global ec_add_valid_points: ec_add_first_zero: JUMPDEST // stack: x0, y0, x1, y1, retdest - // Just return (x1,y1) - %pop2 - // stack: x1, y1, retdest - SWAP1 - // stack: y1, x1, retdest - SWAP2 - // stack: retdest, x1, y1 + %stack (x0, y0, x1, y1, retdest) -> (retdest, x1, y1) JUMP // BN254 elliptic curve addition. @@ -271,21 +265,7 @@ global ec_double: // stack: y < N, x < N, x, y AND // stack: (y < N) & (x < N), x, y - SWAP2 - // stack: y, x, (y < N) & (x < N), x - SWAP1 - // stack: x, y, (y < N) & (x < N) - %bn_base - // stack: N, x, y, b - %bn_base - // stack: N, N, x, y, b - DUP3 - // stack: x, N, N, x, y, b - %bn_base - // stack: N, x, N, N, x, y, b - DUP2 - // stack: x, N, x, N, N, x, y, b - DUP1 + %stack (b, x, y) -> (x, x, @BN_BASE, x, @BN_BASE, @BN_BASE, x, y, b) // stack: x, x, N, x, N, N, x, y, b MULMOD // stack: x^2 % N, x, N, N, x, y, b diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 7f793555..070ec291 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -5,7 +5,7 @@ use itertools::izip; use log::debug; use super::ast::PushTarget; -use crate::cpu::kernel::ast::Literal; +use crate::cpu::kernel::ast::{Literal, StackReplacement}; use crate::cpu::kernel::keccak_util::hash_kernel; use crate::cpu::kernel::stack_manipulation::expand_stack_manipulation; use crate::cpu::kernel::{ @@ -165,14 +165,31 @@ fn expand_repeats(body: Vec) -> Vec { } fn inline_constants(body: Vec, constants: &HashMap) -> Vec { + let resolve_const = |c| { + Literal::Decimal( + constants + .get(&c) + .unwrap_or_else(|| panic!("No such constant: {}", c)) + .to_string(), + ) + }; + body.into_iter() .map(|item| { if let Item::Push(PushTarget::Constant(c)) = item { - let value = constants - .get(&c) - .unwrap_or_else(|| panic!("No such constant: {}", c)); - let literal = Literal::Decimal(value.to_string()); - Item::Push(PushTarget::Literal(literal)) + Item::Push(PushTarget::Literal(resolve_const(c))) + } else if let Item::StackManipulation(from, to) = item { + let to = to + .into_iter() + .map(|replacement| { + if let StackReplacement::Constant(c) = replacement { + StackReplacement::Literal(resolve_const(c)) + } else { + replacement + } + }) + .collect(); + Item::StackManipulation(from, to) } else { item } @@ -446,6 +463,11 @@ mod tests { let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); assert_eq!(kernel.code, vec![pop, swap1, pop]); + + let mut consts = HashMap::new(); + consts.insert("LIFE".into(), 42.into()); + parse_and_assemble_with_constants(&["%stack (a, b) -> (b, @LIFE)"], consts); + // We won't check the code since there are two equally efficient implementations. } fn parse_and_assemble(files: &[&str]) -> Kernel { diff --git a/evm/src/cpu/kernel/stack_manipulation.rs b/evm/src/cpu/kernel/stack_manipulation.rs index 140cfd6a..6f20ead6 100644 --- a/evm/src/cpu/kernel/stack_manipulation.rs +++ b/evm/src/cpu/kernel/stack_manipulation.rs @@ -39,7 +39,7 @@ fn expand(names: Vec, replacements: Vec) -> Vec StackReplacement::NamedItem(name) => StackItem::NamedItem(name), StackReplacement::Literal(n) => StackItem::Literal(n), StackReplacement::MacroVar(_) | StackReplacement::Constant(_) => { - panic!("Should have been expanded earlier") + panic!("Should have been expanded already: {:?}", item) } }) .collect_vec(); From c7ba4eb6eee2d73e30fae553d33ed010f236a5af Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 20 Jul 2022 09:45:05 -0700 Subject: [PATCH 21/38] Feedback --- evm/src/cpu/kernel/asm/curve_add.asm | 2 +- evm/src/cpu/kernel/stack_manipulation.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/asm/curve_add.asm b/evm/src/cpu/kernel/asm/curve_add.asm index 541b1605..15f9df05 100644 --- a/evm/src/cpu/kernel/asm/curve_add.asm +++ b/evm/src/cpu/kernel/asm/curve_add.asm @@ -104,7 +104,7 @@ ec_add_snd_zero: JUMPDEST // stack: x0, y0, x1, y1, retdest - // Just return (x1,y1) + // Just return (x0,y0) %stack (x0, y0, x1, y1, retdest) -> (retdest, x0, y0) JUMP diff --git a/evm/src/cpu/kernel/stack_manipulation.rs b/evm/src/cpu/kernel/stack_manipulation.rs index 6f20ead6..63d0566c 100644 --- a/evm/src/cpu/kernel/stack_manipulation.rs +++ b/evm/src/cpu/kernel/stack_manipulation.rs @@ -214,8 +214,11 @@ impl StackOp { // This is just a rough estimate; we can update it after implementing PUSH. (bytes, bytes) } - Pop => (1, 1), + // A POP takes one cycle, and doesn't involve memory, it just decrements a pointer. + Pop => (1, 0), + // A DUP takes one cycle, and a read and a write. StackOp::Dup(_) => (1, 2), + // A SWAP takes one cycle with four memory ops, to read both values then write to them. StackOp::Swap(_) => (1, 4), }; From 47ea00d6c745f4c79628df5fa9db33e19d9f438b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 20 Jul 2022 15:05:09 -0700 Subject: [PATCH 22/38] A few ASM fixes --- evm/src/cpu/kernel/asm/assertions.asm | 14 ++++++------ evm/src/cpu/kernel/asm/memory.asm | 4 ++-- evm/src/cpu/kernel/assembler.rs | 31 +++++++++++++++++++++++++-- evm/src/cpu/kernel/opcodes.rs | 1 + evm/src/memory/segments.rs | 6 +----- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/evm/src/cpu/kernel/asm/assertions.asm b/evm/src/cpu/kernel/asm/assertions.asm index a8e65036..69193e5f 100644 --- a/evm/src/cpu/kernel/asm/assertions.asm +++ b/evm/src/cpu/kernel/asm/assertions.asm @@ -6,13 +6,13 @@ global panic: // Consumes the top element and asserts that it is zero. %macro assert_zero - %jumpi panic + %jumpi(panic) %endmacro // Consumes the top element and asserts that it is nonzero. %macro assert_nonzero ISZERO - %jumpi panic + %jumpi(panic) %endmacro %macro assert_eq @@ -49,34 +49,34 @@ global panic: %endmacro %macro assert_eq_const(c) - %eq_const(c) + %eq_const($c) %assert_nonzero %endmacro %macro assert_lt_const(c) // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x < c) == !(x >= c). - %ge_const(c) + %ge_const($c) %assert_zero %endmacro %macro assert_le_const(c) // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x <= c) == !(x > c). - %gt_const(c) + %gt_const($c) %assert_zero %endmacro %macro assert_gt_const(c) // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x > c) == !(x <= c). - %le_const(c) + %le_const($c) %assert_zero %endmacro %macro assert_ge_const(c) // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x >= c) == !(x < c). - %lt_const(c) + %lt_const($c) %assert_zero %endmacro diff --git a/evm/src/cpu/kernel/asm/memory.asm b/evm/src/cpu/kernel/asm/memory.asm index 26d0b855..81474d12 100644 --- a/evm/src/cpu/kernel/asm/memory.asm +++ b/evm/src/cpu/kernel/asm/memory.asm @@ -6,7 +6,7 @@ // stack: offset PUSH $segment // stack: segment, offset - CURRENT_CONTEXT + GET_CONTEXT // stack: context, segment, offset MLOAD_GENERAL // stack: value @@ -20,7 +20,7 @@ // stack: offset, value PUSH $segment // stack: segment, offset, value - CURRENT_CONTEXT + GET_CONTEXT // stack: context, segment, offset, value MSTORE_GENERAL // stack: (empty) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 070ec291..4dbc46ca 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -132,13 +132,29 @@ fn expand_macro_call( args.len() ); + let get_arg = |var| { + let param_index = _macro.get_param_index(var); + args[param_index].clone() + }; + let expanded_item = _macro .items .iter() .map(|item| { if let Item::Push(PushTarget::MacroVar(var)) = item { - let param_index = _macro.get_param_index(var); - Item::Push(args[param_index].clone()) + Item::Push(get_arg(var)) + } else if let Item::MacroCall(name, args) = item { + let expanded_args = args + .iter() + .map(|arg| { + if let PushTarget::MacroVar(var) = arg { + get_arg(var) + } else { + arg.clone() + } + }) + .collect(); + Item::MacroCall(name.clone(), expanded_args) } else { item.clone() } @@ -419,6 +435,17 @@ mod tests { assert_eq!(kernel.code, vec![push1, 2, push1, 3, add]); } + #[test] + fn macro_in_macro_with_vars() { + let kernel = parse_and_assemble(&[ + "%macro foo(x) %bar($x) %bar($x) %endmacro", + "%macro bar(y) PUSH $y %endmacro", + "%foo(42)", + ]); + let push = get_push_opcode(1); + assert_eq!(kernel.code, vec![push, 42, push, 42]); + } + #[test] #[should_panic] fn macro_with_wrong_vars() { diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index b9ef7d5e..69ee13fe 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -59,6 +59,7 @@ pub(crate) fn get_opcode(mnemonic: &str) -> u8 { "GASLIMIT" => 0x45, "CHAINID" => 0x46, "BASEFEE" => 0x48, + "PROVER_INPUT" => 0x49, "POP" => 0x50, "MLOAD" => 0x51, "MSTORE" => 0x52, diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index f1b92dfc..f6a67dc8 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -21,12 +21,10 @@ pub(crate) enum Segment { TxnData = 7, /// Raw RLP data. RlpRaw = 8, - /// RLP data that has been parsed and converted to a more "friendly" format. - RlpParsed = 9, } impl Segment { - pub(crate) const COUNT: usize = 10; + pub(crate) const COUNT: usize = 9; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -39,7 +37,6 @@ impl Segment { Self::KernelGeneral, Self::TxnData, Self::RlpRaw, - Self::RlpParsed, ] } @@ -55,7 +52,6 @@ impl Segment { Segment::KernelGeneral => "SEGMENT_KERNEL_GENERAL", Segment::TxnData => "SEGMENT_TXN_DATA", Segment::RlpRaw => "SEGMENT_RLP_RAW", - Segment::RlpParsed => "SEGMENT_RLP_PARSED", } } } From bbc2ff27ab0df2dd0dc83ef8a66d5354ad774af7 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Sat, 23 Jul 2022 09:18:41 +0200 Subject: [PATCH 23/38] Fix minor bug where `constant_affine_point` is called on zero --- ecdsa/src/gadgets/curve_fixed_base.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ecdsa/src/gadgets/curve_fixed_base.rs b/ecdsa/src/gadgets/curve_fixed_base.rs index 8d675d12..d99d5760 100644 --- a/ecdsa/src/gadgets/curve_fixed_base.rs +++ b/ecdsa/src/gadgets/curve_fixed_base.rs @@ -40,14 +40,18 @@ pub fn fixed_base_curve_mul_circuit, cons // `s * P = sum s_i * P_i` with `P_i = (16^i) * P` and `s = sum s_i * (16^i)`. for (limb, point) in limbs.into_iter().zip(scaled_base) { // `muls_point[t] = t * P_i` for `t=0..16`. - let muls_point = (0..16) + let mut muls_point = (0..16) .scan(AffinePoint::ZERO, |acc, _| { let tmp = *acc; *acc = (point + *acc).to_affine(); Some(tmp) }) + // First element if zero, so we skip it since `constant_affine_point` takes non-zero input. + .skip(1) .map(|p| builder.constant_affine_point(p)) .collect::>(); + // We add back a point in position 0. `limb == zero` is checked below, so this point can be arbitrary. + muls_point.insert(0, muls_point[0].clone()); let is_zero = builder.is_equal(limb, zero); let should_add = builder.not(is_zero); // `r = s_i * P_i` From ee979428f4c359927a95007141dfbb4365a6dfba Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Sat, 23 Jul 2022 15:35:48 +0200 Subject: [PATCH 24/38] Start implementing context and segments in interpreter --- evm/src/cpu/kernel/interpreter.rs | 83 +++++++++++++++++++++++++++---- evm/src/generation/memory.rs | 4 +- evm/src/generation/mod.rs | 2 +- 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index f2fb276a..9e39fb4f 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -2,6 +2,9 @@ use anyhow::{anyhow, bail}; use ethereum_types::{BigEndianHash, U256, U512}; use keccak_hash::keccak; +use crate::generation::memory::MemoryContextState; +use crate::memory::segments::Segment; + /// Halt interpreter execution whenever a jump to this offset is done. const HALT_OFFSET: usize = 0xdeadbeef; @@ -49,12 +52,36 @@ impl EvmMemory { } } +#[derive(Debug)] +pub(crate) struct ContextMemory { + memory: Vec, +} + +impl Default for ContextMemory { + fn default() -> Self { + Self { + memory: vec![MemoryContextState::default()], + } + } +} + +impl ContextMemory { + fn mload_general(&self, context: usize, segment: Segment, offset: usize) -> U256 { + self.memory[context].segments[segment as usize].get(offset) + } + + fn mstore_general(&mut self, context: usize, segment: Segment, offset: usize, value: U256) { + self.memory[context].segments[segment as usize].set(offset, value) + } +} + pub(crate) struct Interpreter<'a> { code: &'a [u8], jumpdests: Vec, offset: usize, pub(crate) stack: Vec, - pub(crate) memory: EvmMemory, + context: usize, + memory: ContextMemory, /// Non-deterministic prover inputs, stored backwards so that popping the last item gives the /// next prover input. prover_inputs: Vec, @@ -83,7 +110,8 @@ pub(crate) fn run_with_input( jumpdests: find_jumpdests(code), offset: initial_offset, stack: initial_stack, - memory: EvmMemory::default(), + context: 0, + memory: ContextMemory::default(), prover_inputs, running: true, }; @@ -203,8 +231,8 @@ impl<'a> Interpreter<'a> { 0xf3 => todo!(), // "RETURN", 0xf4 => todo!(), // "DELEGATECALL", 0xf5 => todo!(), // "CREATE2", - 0xf6 => todo!(), // "GET_CONTEXT", - 0xf7 => todo!(), // "SET_CONTEXT", + 0xf6 => self.run_get_context(), // "GET_CONTEXT", + 0xf7 => self.run_set_context(), // "SET_CONTEXT", 0xf8 => todo!(), // "CONSUME_GAS", 0xf9 => todo!(), // "EXIT_KERNEL", 0xfa => todo!(), // "STATICCALL", @@ -330,7 +358,11 @@ impl<'a> Interpreter<'a> { let offset = self.pop().as_usize(); let size = self.pop().as_usize(); let bytes = (offset..offset + size) - .map(|i| self.memory.mload8(i)) + .map(|i| { + self.memory + .mload_general(self.context, Segment::MainMemory, i) + .byte(0) + }) .collect::>(); let hash = keccak(bytes); self.push(hash.into_uint()); @@ -351,20 +383,42 @@ impl<'a> Interpreter<'a> { fn run_mload(&mut self) { let offset = self.pop(); - let value = self.memory.mload(offset.as_usize()); + let value = U256::from_big_endian( + &(0..32) + .map(|i| { + self.memory + .mload_general(self.context, Segment::MainMemory, offset.as_usize() + i) + .byte(0) + }) + .collect::>(), + ); self.push(value); } fn run_mstore(&mut self) { let offset = self.pop(); let value = self.pop(); - self.memory.mstore(offset.as_usize(), value); + let mut bytes = [0; 32]; + value.to_big_endian(&mut bytes); + for i in 0..32 { + self.memory.mstore_general( + self.context, + Segment::MainMemory, + offset.as_usize() + i, + bytes[i].into(), + ); + } } fn run_mstore8(&mut self) { let offset = self.pop(); let value = self.pop(); - self.memory.mstore8(offset.as_usize(), value); + self.memory.mstore_general( + self.context, + Segment::MainMemory, + offset.as_usize(), + value.byte(0).into(), + ); } fn run_jump(&mut self) { @@ -404,6 +458,15 @@ impl<'a> Interpreter<'a> { let len = self.stack.len(); self.stack.swap(len - 1, len - n as usize - 1); } + + fn run_get_context(&mut self) { + self.push(self.context.into()); + } + + fn run_set_context(&mut self) { + let x = self.pop(); + self.context = x.as_usize(); + } } /// Return the (ordered) JUMPDEST offsets in the code. @@ -424,7 +487,7 @@ fn find_jumpdests(code: &[u8]) -> Vec { #[cfg(test)] mod tests { - use hex_literal::hex; + // use hex_literal::hex; use crate::cpu::kernel::interpreter::{run, Interpreter}; @@ -459,7 +522,7 @@ mod tests { let run = run(&code, 0, vec![])?; let Interpreter { stack, memory, .. } = run; assert_eq!(stack, vec![0xff.into(), 0xff00.into()]); - assert_eq!(&memory.memory, &hex!("00000000000000000000000000000000000000000000000000000000000000ff0000000000000042000000000000000000000000000000000000000000000000")); + // assert_eq!(&memory.memory, &hex!("00000000000000000000000000000000000000000000000000000000000000ff0000000000000042000000000000000000000000000000000000000000000000")); Ok(()) } } diff --git a/evm/src/generation/memory.rs b/evm/src/generation/memory.rs index 60bfe794..5e2919a4 100644 --- a/evm/src/generation/memory.rs +++ b/evm/src/generation/memory.rs @@ -34,14 +34,14 @@ pub(crate) struct MemorySegmentState { } impl MemorySegmentState { - pub(super) fn get(&self, virtual_addr: usize) -> U256 { + pub(crate) fn get(&self, virtual_addr: usize) -> U256 { self.content .get(virtual_addr) .copied() .unwrap_or(U256::zero()) } - pub(super) fn set(&mut self, virtual_addr: usize, value: U256) { + pub(crate) fn set(&mut self, virtual_addr: usize, value: U256) { if virtual_addr >= self.content.len() { self.content.resize(virtual_addr + 1, U256::zero()); } diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index b8ab0376..02c91d16 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -10,7 +10,7 @@ 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 memory; pub(crate) mod state; /// A piece of data which has been encoded using Recursive Length Prefix (RLP) serialization. From 544c84b4205189dd1bebf659f3996944d19326d0 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 6 Jul 2022 19:47:58 -0700 Subject: [PATCH 25/38] Transaction (RLP) parsing Will add tests once we have the interpreter support for other segmnets. --- evm/src/cpu/kernel/aggregator.rs | 12 ++ evm/src/cpu/kernel/asm/basic_macros.asm | 14 +- evm/src/cpu/kernel/asm/rlp/decode.asm | 145 ++++++++++++++ evm/src/cpu/kernel/asm/rlp/encode.asm | 17 ++ evm/src/cpu/kernel/asm/rlp/read_to_memory.asm | 39 ++++ .../asm/transactions/process_normalized.asm | 5 + .../cpu/kernel/asm/transactions/router.asm | 38 ++++ .../cpu/kernel/asm/transactions/type_0.asm | 189 ++++++++++++++++++ .../cpu/kernel/asm/transactions/type_1.asm | 12 ++ .../cpu/kernel/asm/transactions/type_2.asm | 14 ++ evm/src/cpu/kernel/mod.rs | 1 + evm/src/cpu/kernel/txn_fields.rs | 59 ++++++ evm/src/memory/segments.rs | 14 +- 13 files changed, 553 insertions(+), 6 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/rlp/decode.asm create mode 100644 evm/src/cpu/kernel/asm/rlp/encode.asm create mode 100644 evm/src/cpu/kernel/asm/rlp/read_to_memory.asm create mode 100644 evm/src/cpu/kernel/asm/transactions/process_normalized.asm create mode 100644 evm/src/cpu/kernel/asm/transactions/router.asm create mode 100644 evm/src/cpu/kernel/asm/transactions/type_0.asm create mode 100644 evm/src/cpu/kernel/asm/transactions/type_1.asm create mode 100644 evm/src/cpu/kernel/asm/transactions/type_2.asm create mode 100644 evm/src/cpu/kernel/txn_fields.rs diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index ec42f5c4..1cf61255 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -9,6 +9,7 @@ use once_cell::sync::Lazy; use super::assembler::{assemble, Kernel}; use crate::cpu::kernel::parser::parse; +use crate::cpu::kernel::txn_fields::NormalizedTxnField; use crate::memory::segments::Segment; pub static KERNEL: Lazy = Lazy::new(combined_kernel); @@ -24,6 +25,9 @@ pub fn evm_constants() -> HashMap { for segment in Segment::all() { c.insert(segment.var_name().into(), (segment as u32).into()); } + for txn_field in NormalizedTxnField::all() { + c.insert(txn_field.var_name().into(), (txn_field as u32).into()); + } c } @@ -43,8 +47,16 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/secp256k1/lift_x.asm"), include_str!("asm/secp256k1/inverse_scalar.asm"), include_str!("asm/ecrecover.asm"), + include_str!("asm/rlp/encode.asm"), + include_str!("asm/rlp/decode.asm"), + include_str!("asm/rlp/read_to_memory.asm"), include_str!("asm/storage_read.asm"), include_str!("asm/storage_write.asm"), + include_str!("asm/transactions/process_normalized.asm"), + include_str!("asm/transactions/router.asm"), + include_str!("asm/transactions/type_0.asm"), + include_str!("asm/transactions/type_1.asm"), + include_str!("asm/transactions/type_2.asm"), ]; let parsed_files = files.iter().map(|f| parse(f)).collect_vec(); diff --git a/evm/src/cpu/kernel/asm/basic_macros.asm b/evm/src/cpu/kernel/asm/basic_macros.asm index 20f8958c..e266b2cb 100644 --- a/evm/src/cpu/kernel/asm/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/basic_macros.asm @@ -78,10 +78,22 @@ // stack: c, input, ... SWAP1 // stack: input, c, ... - SUB + DIV // stack: input / c, ... %endmacro +// Slightly inefficient as we need to swap the inputs. +// Consider avoiding this in performance-critical code. +%macro mod_const(c) + // stack: input, ... + PUSH $c + // stack: c, input, ... + SWAP1 + // stack: input, c, ... + MOD + // stack: input % c, ... +%endmacro + %macro shl_const(c) // stack: input, ... PUSH $c diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm new file mode 100644 index 00000000..dde85d05 --- /dev/null +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -0,0 +1,145 @@ +// Note: currently, these methods do not check that RLP input is in canonical +// form; for example a single byte could be encoded with the length-of-length +// form. Technically an EVM must perform these checks, but we aren't really +// concerned with it in our setting. An attacker who corrupted consensus could +// prove a non-canonical state, but this would just temporarily stall the bridge +// until a fix was deployed. We are more concerned with preventing any theft of +// assets. + +// Parse the length of a bytestring from RLP memory. The next len bytes after +// pos' will contain the string. +// +// Pre stack: pos, retdest +// Post stack: pos', len +global decode_rlp_string_len: + JUMPDEST + // stack: pos, retdest + DUP1 + %mload_current(@SEGMENT_RLP_RAW) + // stack: first_byte, pos, retdest + DUP1 + %gt_const(0xb6) + // stack: first_byte >= 0xb7, first_byte, pos, retdest + %jumpi(decode_rlp_string_len_large) + // stack: first_byte, pos, retdest + DUP1 + %gt_const(0x7f) + // stack: first_byte >= 0x80, first_byte, pos, retdest + %jumpi(decode_rlp_string_len_medium) +decode_rlp_string_len_small: + // String is a single byte in the range [0x00, 0x7f]. + %stack (first_byte, pos, retdest) -> (retdest, pos, 1) + JUMP +decode_rlp_string_len_medium: + // String is 0-55 bytes long. First byte contains the len. + // stack: first_byte, pos, retdest + %sub_const(0x80) + // stack: len, pos, retdest + SWAP1 + %add_const(1) + // stack: pos', len, retdest +decode_rlp_string_len_large: + // String is >55 bytes long. First byte contains the len of the len. + // stack: first_byte, pos, retdest + %sub_const(0xb7) + // stack: len_of_len, pos, retdest + SWAP1 + %add_const(1) + // stack: pos', len_of_len, retdest + %jump(decode_int_given_len) + +// Parse a scalar from RLP memory. +// Pre stack: pos, retdest +// Post stack: pos', scalar +// +// Scalars are variable-length, but this method assumes a max length of 32 +// bytes, so that the result can be returned as a single word on the stack. +// As per the spec, scalars must not have leading zeros. +global decode_rlp_scalar: + JUMPDEST + // stack: pos, retdest + PUSH decode_int_given_len + // stack: decode_int_given_len, pos, retdest + SWAP1 + // stack: pos, decode_int_given_len, retdest + // decode_rlp_string_len will return to decode_int_given_len, at which point + // the stack will contain (pos', len, retdest), which are the proper args + // to decode_int_given_len. + %jump(decode_rlp_string_len) + +// Parse the length of an RLP list from memory. +// Pre stack: pos, retdest +// Post stack: pos', len +global decode_rlp_list_len: + JUMPDEST + // stack: pos, retdest + DUP1 + %mload_current(@SEGMENT_RLP_RAW) + // stack: first_byte, pos, retdest + SWAP1 + %add_const(1) // increment pos + SWAP1 + // stack: first_byte, pos', retdest + // If first_byte is >= 0xf7, it's a > 55 byte list, and + // first_byte - 0xf7 is the length of the length. + DUP1 + %gt_const(0xf6) // GT is native while GE is not, so compare to 0xf6 instead + // stack: first_byte >= 0xf7, first_byte, pos', retdest + %jumpi(decode_rlp_list_len_big) +decode_rlp_list_len_small: + // The list length is first_byte - 0xc0. + // stack: first_byte, pos', retdest + %sub_const(0xc0) + // stack: len, pos', retdest + %stack (len, pos, retdest) -> (retdest, pos, len) + JUMP +decode_rlp_list_len_big: + JUMPDEST + // The length of the length is first_byte - 0xf7. + // stack: first_byte, pos', retdest + %sub_const(0xf7) + // stack: len_of_len, pos', retdest + SWAP1 + // stack: pos', len_of_len, retdest + %jump(decode_int_given_len) + +// Parse an integer of the given length. It is assumed that the integer will +// fit in a single (256-bit) word on the stack. +// Pre stack: pos, len, retdest +// Post stack: pos', int +decode_int_given_len: + JUMPDEST + %stack (pos, len, retdest) -> (pos, len, pos, retdest) + ADD + // stack: end_pos, pos, retdest + SWAP1 + // stack: pos, end_pos, retdest + PUSH 0 // initial accumulator state + // stack: acc, pos, end_pos, retdest +decode_int_given_len_loop: + JUMPDEST + // stack: acc, pos, end_pos, retdest + DUP3 + DUP3 + ISZERO + // stack: pos == end_pos, acc, pos, end_pos, retdest + %jumpi(decode_int_given_len_finish) + // stack: acc, pos, end_pos, retdest + %shl_const(8) + // stack: acc << 8, pos, end_pos, retdest + DUP2 + // stack: pos, acc << 8, pos, end_pos, retdest + %mload_current(@SEGMENT_RLP_RAW) + // stack: byte, acc << 8, pos, end_pos, retdest + ADD + // stack: acc', pos, end_pos, retdest + // Increment pos. + SWAP1 + %add_const(1) + SWAP1 + // stack: acc', pos', end_pos, retdest + %jump(decode_int_given_len_loop) +decode_int_given_len_finish: + JUMPDEST + %stack (acc, pos, end_pos, retdest) -> (retdest, pos, acc) + JUMP diff --git a/evm/src/cpu/kernel/asm/rlp/encode.asm b/evm/src/cpu/kernel/asm/rlp/encode.asm new file mode 100644 index 00000000..b2446c37 --- /dev/null +++ b/evm/src/cpu/kernel/asm/rlp/encode.asm @@ -0,0 +1,17 @@ +// RLP-encode a scalar, i.e. a variable-length integer. +// Pre stack: pos, scalar +// Post stack: (empty) +global encode_rlp_scalar: + PANIC // TODO: implement + +// RLP-encode a fixed-length 160-bit string. Assumes string < 2^160. +// Pre stack: pos, string +// Post stack: (empty) +global encode_rlp_160: + PANIC // TODO: implement + +// RLP-encode a fixed-length 256-bit string. +// Pre stack: pos, string +// Post stack: (empty) +global encode_rlp_256: + PANIC // TODO: implement diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm new file mode 100644 index 00000000..1a84c710 --- /dev/null +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -0,0 +1,39 @@ +// Read RLP data from the prover's tape, and save it to the SEGMENT_RLP_RAW +// segment of memory. + +// Pre stack: retdest +// Post stack: (empty) + +global read_rlp_to_memory: + JUMPDEST + // stack: retdest + PROVER_INPUT // Read the RLP blob length from the prover tape. + // stack: len, retdest + PUSH 0 // initial position + // stack: pos, len, retdest + +read_rlp_to_memory_loop: + JUMPDEST + // stack: pos, len, retdest + DUP2 + DUP2 + EQ + // stack: pos == len, pos, len, retdest + %jumpi(read_rlp_to_memory_finish) + // stack: pos, len, retdest + PROVER_INPUT + // stack: byte, pos, len, retdest + DUP1 + // stack: pos, byte, pos, len, retdest + %mstore_current(@SEGMENT_RLP_RAW) + // stack: pos, len, retdest + %add_const(1) + // stack: pos', len, retdest + %jump(read_rlp_to_memory_loop) + +read_rlp_to_memory_finish: + JUMPDEST + // stack: pos, len, retdest + %pop2 + // stack: retdest + JUMP diff --git a/evm/src/cpu/kernel/asm/transactions/process_normalized.asm b/evm/src/cpu/kernel/asm/transactions/process_normalized.asm new file mode 100644 index 00000000..d99041b0 --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/process_normalized.asm @@ -0,0 +1,5 @@ +// After the transaction data has been parsed into a normalized set of fields +// (see TxnField), this routine processes the transaction. + +global process_normalized_txn: + // TODO diff --git a/evm/src/cpu/kernel/asm/transactions/router.asm b/evm/src/cpu/kernel/asm/transactions/router.asm new file mode 100644 index 00000000..01a65fec --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/router.asm @@ -0,0 +1,38 @@ +// This is the entry point of transaction processing. We load the transaction +// RLP data into memory, check the transaction type, then based on the type we +// jump to the appropriate transaction parsing method. + +global route_txn: + JUMPDEST + // stack: (empty) + // First load transaction data into memory, where it will be parsed. + PUSH read_txn_from_memory + %jump(read_rlp_to_memory) + +// At this point, the raw txn data is in memory. +read_txn_from_memory: + JUMPDEST + // stack: (empty) + + // We will peak at the first byte to determine what type of transaction this is. + // Note that type 1 and 2 transactions have a first byte of 1 and 2, respectively. + // Type 0 (legacy) transactions have no such prefix, but their RLP will have a + // first byte >= 0xc0, so there is no overlap. + + PUSH 0 + %mload_current(@SEGMENT_RLP_RAW) + %eq_const(1) + // stack: first_byte == 1 + %jumpi(process_type_1_txn) + // stack: (empty) + + PUSH 0 + %mload_current(@SEGMENT_RLP_RAW) + %eq_const(2) + // stack: first_byte == 2 + %jumpi(process_type_2_txn) + // stack: (empty) + + // At this point, since it's not a type 1 or 2 transaction, + // it must be a legacy (aka type 0) transaction. + %jump(process_type_2_txn) diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm new file mode 100644 index 00000000..3a217fda --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -0,0 +1,189 @@ +// Type 0 transactions, aka legacy transaction, have the format +// rlp([nonce, gas_price, gas_limit, destination, amount, data, v, r, s]) +// +// The field v was originally encoded as +// 27 + y_parity +// but as of EIP 155 it can also be encoded as +// 35 + 2 * chain_id + y_parity +// +// If a chain_id is present in v, the signed data is +// keccak256(rlp([nonce, gas_price, gas_limit, destination, amount, data, chain_id, 0, 0])) +// otherwise, it is +// keccak256(rlp([nonce, gas_price, gas_limit, destination, amount, data])) + +global process_type_0_txn: + JUMPDEST + // stack: (empty) + PUSH process_txn_with_len + PUSH 0 // initial pos + // stack: pos, process_txn_with_len + %jump(decode_rlp_list_len) + +process_txn_with_len: + // We don't actually need the length. + %stack (pos, len) -> (pos) + + PUSH store_nonce + SWAP1 + // stack: pos, store_nonce + %jump(decode_rlp_scalar) + +store_nonce: + %stack (pos, nonce) -> (@TXN_FIELD_NONCE, nonce, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + PUSH store_gas_price + SWAP1 + // stack: pos, store_gas_price + %jump(decode_rlp_scalar) + +store_gas_price: + // For legacy transactions, we set both the + // TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS and TXN_FIELD_MAX_FEE_PER_GAS + // fields to gas_price. + %stack (pos, gas_price) -> (@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS, gas_price, + @TXN_FIELD_MAX_FEE_PER_GAS, gas_price, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + PUSH store_gas_limit + SWAP1 + // stack: pos, store_gas_limit + %jump(decode_rlp_scalar) + +store_gas_limit: + %stack (pos, gas_limit) -> (@TXN_FIELD_GAS_LIMIT, gas_limit, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // Peak at the RLP to see if the next byte is zero. + // If so, there is no destination field, so skip the store_destination step. + // stack: pos + DUP1 + %mload_current(@SEGMENT_RLP_RAW) + ISZERO + // stack: destination_empty, pos + %jumpi(parse_amount) + + // If we got here, there is a destination field. + PUSH store_destination + SWAP1 + // stack: pos, store_destination + %jump(decode_rlp_scalar) + +store_destination: + %stack (pos, destination) -> (@TXN_FIELD_DESTINATION, destination, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + // stack: pos + +parse_amount: + // stack: pos + PUSH store_amount + SWAP1 + // stack: pos, store_amount + %jump(decode_rlp_scalar) + +store_amount: + %stack (pos, amount) -> (@TXN_FIELD_AMOUNT, amount, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + PUSH store_data_len + SWAP1 + // stack: pos, store_data_len + %jump(decode_rlp_string_len) + +store_data_len: + %stack (pos, data_len) -> (@TXN_FIELD_DATA_LEN, data_len, pos, data_len, pos, data_len) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + // stack: pos, data_len, pos, data_len + ADD + // stack: new_pos, pos, data_len + + // Memcpy the txn data from @SEGMENT_RLP_RAW to @SEGMENT_TXN_DATA. + PUSH parse_v + %stack (parse_v, new_pos, old_pos, data_len) -> (old_pos, data_len, parse_v, new_pos) + PUSH @SEGMENT_RLP_RAW + GET_CONTEXT + PUSH 0 + PUSH @SEGMENT_TXN_DATA + GET_CONTEXT + // stack: DST, SRC, data_len, parse_v, new_pos + %jump(memcpy) + +parse_v: + // stack: pos + PUSH process_v + SWAP1 + // stack: pos, process_v + %jump(decode_rlp_scalar) + +process_v: + // stack: pos, v + SWAP1 + // stack: v, pos + DUP1 + %gt_const(28) + // stack: v > 28, v, pos + %jumpi(process_v_new_style) + + // We have an old style v, so y_parity = v - 27. + // No chain ID is present, so we can leave TXN_FIELD_CHAIN_ID_PRESENT and + // TXN_FIELD_CHAIN_ID with their default values of zero. + // stack: v, pos + %sub_const(27) + %stack (y_parity, pos) -> (@TXN_FIELD_Y_PARITY, y_parity, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + %jump(parse_r) + +process_v_new_style: + // stack: v, pos + // We have a new style v, so chain_id_present = 1, + // chain_id = (v - 35) / 2, and y_parity = (v - 35) % 2. + %stack (v, pos) -> (@TXN_FIELD_CHAIN_ID_PRESENT, 1, v, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: v, pos + %sub_const(35) + DUP1 + // stack: v - 35, v - 35, pos + %div_const(2) + // stack: chain_id, v - 35, pos + PUSH @TXN_FIELD_CHAIN_ID + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: v - 35, pos + %mod_const(2) + // stack: y_parity, pos + PUSH @TXN_FIELD_Y_PARITY + %mstore_current(@SEGMENT_NORMALIZED_TXN) + +parse_r: + // stack: pos + PUSH store_r + SWAP1 + // stack: pos, store_r + %jump(decode_rlp_scalar) + +store_r: + %stack (pos, r) -> (@TXN_FIELD_R, r, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + PUSH store_s + SWAP1 + // stack: pos, store_s + %jump(decode_rlp_scalar) + +store_s: + %stack (pos, s) -> (@TXN_FIELD_S, s) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + // stack: (empty) + + // TODO: Write the signed txn data to memory, where it can be hashed and + // checked against the signature. + + %jump(process_normalized_txn) diff --git a/evm/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm new file mode 100644 index 00000000..5b9d2cdf --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/type_1.asm @@ -0,0 +1,12 @@ +// Type 1 transactions, introduced by EIP 2930, have the format +// 0x01 || rlp([chain_id, nonce, gas_price, gas_limit, to, value, data, +// access_list, y_parity, r, s]) +// +// The signed data is +// keccak256(0x01 || rlp([chain_id, nonce, gas_price, gas_limit, to, value, +// data, access_list])) + +global process_type_1_txn: + JUMPDEST + // stack: (empty) + PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm new file mode 100644 index 00000000..bdde4aa6 --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -0,0 +1,14 @@ +// Type 2 transactions, introduced by EIP 1559, have the format +// 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, +// gas_limit, destination, amount, data, access_list, y_parity, +// r, s]) +// +// The signed data is +// keccak256(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, +// max_fee_per_gas, gas_limit, destination, amount, +// data, access_list])) + +global process_type_2_txn: + JUMPDEST + // stack: (empty) + PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/mod.rs b/evm/src/cpu/kernel/mod.rs index 1f13a042..1d545260 100644 --- a/evm/src/cpu/kernel/mod.rs +++ b/evm/src/cpu/kernel/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod keccak_util; mod opcodes; mod parser; mod stack_manipulation; +mod txn_fields; #[cfg(test)] mod interpreter; diff --git a/evm/src/cpu/kernel/txn_fields.rs b/evm/src/cpu/kernel/txn_fields.rs new file mode 100644 index 00000000..4dc8bfbb --- /dev/null +++ b/evm/src/cpu/kernel/txn_fields.rs @@ -0,0 +1,59 @@ +/// These are normalized transaction fields, i.e. not specific to any transaction type. +#[allow(dead_code)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] +pub(crate) enum NormalizedTxnField { + /// Whether a chain ID was present in the txn data. Type 0 transaction with v=27 or v=28 have + /// no chain ID. This affects what fields get signed. + ChainIdPresent = 0, + ChainId = 1, + Nonce = 2, + MaxPriorityFeePerGas = 3, + MaxFeePerGas = 4, + GasLimit = 5, + Destination = 6, + Amount = 7, + /// The length of the data field. The data itself is stored in another segment. + DataLen = 8, + YParity = 9, + R = 10, + S = 11, +} + +impl NormalizedTxnField { + pub(crate) const COUNT: usize = 12; + + pub(crate) fn all() -> [Self; Self::COUNT] { + [ + Self::ChainIdPresent, + Self::ChainId, + Self::Nonce, + Self::MaxPriorityFeePerGas, + Self::MaxFeePerGas, + Self::GasLimit, + Self::Destination, + Self::Amount, + Self::DataLen, + Self::YParity, + Self::R, + Self::S, + ] + } + + /// The variable name that gets passed into kernel assembly code. + pub(crate) fn var_name(&self) -> &'static str { + match self { + NormalizedTxnField::ChainIdPresent => "TXN_FIELD_CHAIN_ID_PRESENT", + NormalizedTxnField::ChainId => "TXN_FIELD_CHAIN_ID", + NormalizedTxnField::Nonce => "TXN_FIELD_NONCE", + NormalizedTxnField::MaxPriorityFeePerGas => "TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS", + NormalizedTxnField::MaxFeePerGas => "TXN_FIELD_MAX_FEE_PER_GAS", + NormalizedTxnField::GasLimit => "TXN_FIELD_GAS_LIMIT", + NormalizedTxnField::Destination => "TXN_FIELD_DESTINATION", + NormalizedTxnField::Amount => "TXN_FIELD_AMOUNT", + NormalizedTxnField::DataLen => "TXN_FIELD_DATA_LEN", + NormalizedTxnField::YParity => "TXN_FIELD_Y_PARITY", + NormalizedTxnField::R => "TXN_FIELD_R", + NormalizedTxnField::S => "TXN_FIELD_S", + } + } +} diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index f6a67dc8..56b56f5b 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -1,4 +1,4 @@ -#[allow(dead_code)] // TODO: Not all segments are used yet. +#[allow(dead_code)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] pub(crate) enum Segment { /// Contains EVM bytecode. @@ -17,14 +17,16 @@ pub(crate) enum Segment { /// General purpose kernel memory, used by various kernel functions. /// In general, calling a helper function can result in this memory being clobbered. KernelGeneral = 6, - /// Contains transaction data (after it's parsed and converted to a standard format). - TxnData = 7, + /// Contains normalized transaction fields; see `TxnField`. + TxnFields = 7, + /// Contains the data field of a transaction. + TxnData = 8, /// Raw RLP data. - RlpRaw = 8, + RlpRaw = 9, } impl Segment { - pub(crate) const COUNT: usize = 9; + pub(crate) const COUNT: usize = 10; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -35,6 +37,7 @@ impl Segment { Self::Returndata, Self::Metadata, Self::KernelGeneral, + Self::TxnFields, Self::TxnData, Self::RlpRaw, ] @@ -50,6 +53,7 @@ impl Segment { Segment::Returndata => "SEGMENT_RETURNDATA", Segment::Metadata => "SEGMENT_METADATA", Segment::KernelGeneral => "SEGMENT_KERNEL_GENERAL", + Segment::TxnFields => "SEGMENT_NORMALIZED_TXN", Segment::TxnData => "SEGMENT_TXN_DATA", Segment::RlpRaw => "SEGMENT_RLP_RAW", } From 0e5dd59d35bbf93bbbf5d9750dcdd2e6d95de56e Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 23 Jul 2022 22:27:38 -0700 Subject: [PATCH 26/38] Use a fixed input buffer size in `Challenger`. Alternate implementation of #633, using `SPONGE_RATE` as the buffer size. --- plonky2/src/iop/challenger.rs | 58 ++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/plonky2/src/iop/challenger.rs b/plonky2/src/iop/challenger.rs index 52412409..b9883a18 100644 --- a/plonky2/src/iop/challenger.rs +++ b/plonky2/src/iop/challenger.rs @@ -33,8 +33,8 @@ impl> Challenger { pub fn new() -> Challenger { Challenger { sponge_state: [F::ZERO; SPONGE_WIDTH], - input_buffer: Vec::new(), - output_buffer: Vec::new(), + input_buffer: Vec::with_capacity(SPONGE_RATE), + output_buffer: Vec::with_capacity(SPONGE_RATE), _phantom: Default::default(), } } @@ -44,6 +44,10 @@ impl> Challenger { self.output_buffer.clear(); self.input_buffer.push(element); + + if self.input_buffer.len() == SPONGE_RATE { + self.duplexing(); + } } pub fn observe_extension_element(&mut self, element: &F::Extension) @@ -79,12 +83,10 @@ impl> Challenger { } pub fn get_challenge(&mut self) -> F { - self.absorb_buffered_inputs(); - - if self.output_buffer.is_empty() { - // Evaluate the permutation to produce `r` new outputs. - self.sponge_state = H::Permutation::permute(self.sponge_state); - self.output_buffer = self.sponge_state[0..SPONGE_RATE].to_vec(); + // If we have buffered inputs, we must perform a duplexing so that the challenge will + // reflect them. Or if we've run out of outputs, we must perform a duplexing to get more. + if !self.input_buffer.is_empty() || self.output_buffer.is_empty() { + self.duplexing(); } self.output_buffer @@ -125,27 +127,24 @@ impl> Challenger { .collect() } - /// Absorb any buffered inputs. After calling this, the input buffer will be empty. - fn absorb_buffered_inputs(&mut self) { - if self.input_buffer.is_empty() { - return; + /// Absorb any buffered inputs. After calling this, the input buffer will be empty, and the + /// output buffer will be full. + fn duplexing(&mut self) { + assert!(self.input_buffer.len() < SPONGE_RATE); + + // Overwrite the first r elements with the inputs. This differs from a standard sponge, + // where we would xor or add in the inputs. This is a well-known variant, though, + // sometimes called "overwrite mode". + for (i, input) in self.input_buffer.drain(..).enumerate() { + self.sponge_state[i] = input; } - for input_chunk in self.input_buffer.chunks(SPONGE_RATE) { - // Overwrite the first r elements with the inputs. This differs from a standard sponge, - // where we would xor or add in the inputs. This is a well-known variant, though, - // sometimes called "overwrite mode". - for (i, &input) in input_chunk.iter().enumerate() { - self.sponge_state[i] = input; - } + // Apply the permutation. + self.sponge_state = H::Permutation::permute(self.sponge_state); - // Apply the permutation. - self.sponge_state = H::Permutation::permute(self.sponge_state); - } - - self.output_buffer = self.sponge_state[0..SPONGE_RATE].to_vec(); - - self.input_buffer.clear(); + self.output_buffer.clear(); + self.output_buffer + .extend_from_slice(&self.sponge_state[0..SPONGE_RATE]); } } @@ -155,7 +154,9 @@ impl> Default for Challenger { } } -/// A recursive version of `Challenger`. +/// A recursive version of `Challenger`. The main difference is that `RecursiveChallenger`'s input +/// buffer can grow beyond `SPONGE_RATE`. This is so that `observe_element` etc do not need access +/// to the `CircuitBuilder`. pub struct RecursiveChallenger, H: AlgebraicHasher, const D: usize> { sponge_state: [Target; SPONGE_WIDTH], @@ -248,7 +249,8 @@ impl, H: AlgebraicHasher, const D: usize> self.get_n_challenges(builder, D).try_into().unwrap() } - /// Absorb any buffered inputs. After calling this, the input buffer will be empty. + /// Absorb any buffered inputs. After calling this, the input buffer will be empty, and the + /// output buffer will be full. fn absorb_buffered_inputs(&mut self, builder: &mut CircuitBuilder) { if self.input_buffer.is_empty() { return; From cddc749a7e102b361836ed0d2ddc2055127fe6e9 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 24 Jul 2022 08:06:02 -0700 Subject: [PATCH 27/38] Fix comparison --- plonky2/src/iop/challenger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/iop/challenger.rs b/plonky2/src/iop/challenger.rs index b9883a18..97d21197 100644 --- a/plonky2/src/iop/challenger.rs +++ b/plonky2/src/iop/challenger.rs @@ -130,7 +130,7 @@ impl> Challenger { /// Absorb any buffered inputs. After calling this, the input buffer will be empty, and the /// output buffer will be full. fn duplexing(&mut self) { - assert!(self.input_buffer.len() < SPONGE_RATE); + assert!(self.input_buffer.len() <= SPONGE_RATE); // Overwrite the first r elements with the inputs. This differs from a standard sponge, // where we would xor or add in the inputs. This is a well-known variant, though, From 1db5b7374da4f3f3cd07a6063aa917d53010c334 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 24 Jul 2022 08:42:06 -0700 Subject: [PATCH 28/38] Move storage asm --- evm/src/cpu/kernel/aggregator.rs | 4 ++-- evm/src/cpu/kernel/asm/storage/read.asm | 2 ++ evm/src/cpu/kernel/asm/storage/write.asm | 2 ++ evm/src/cpu/kernel/asm/storage_read.asm | 10 ---------- evm/src/cpu/kernel/asm/storage_write.asm | 6 ------ 5 files changed, 6 insertions(+), 18 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/storage/read.asm create mode 100644 evm/src/cpu/kernel/asm/storage/write.asm delete mode 100644 evm/src/cpu/kernel/asm/storage_read.asm delete mode 100644 evm/src/cpu/kernel/asm/storage_write.asm diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index ec42f5c4..ac5ec6df 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -43,8 +43,8 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/secp256k1/lift_x.asm"), include_str!("asm/secp256k1/inverse_scalar.asm"), include_str!("asm/ecrecover.asm"), - include_str!("asm/storage_read.asm"), - include_str!("asm/storage_write.asm"), + include_str!("asm/storage/read.asm"), + include_str!("asm/storage/write.asm"), ]; let parsed_files = files.iter().map(|f| parse(f)).collect_vec(); diff --git a/evm/src/cpu/kernel/asm/storage/read.asm b/evm/src/cpu/kernel/asm/storage/read.asm new file mode 100644 index 00000000..04fea17a --- /dev/null +++ b/evm/src/cpu/kernel/asm/storage/read.asm @@ -0,0 +1,2 @@ +global storage_read: + // TODO diff --git a/evm/src/cpu/kernel/asm/storage/write.asm b/evm/src/cpu/kernel/asm/storage/write.asm new file mode 100644 index 00000000..940fb548 --- /dev/null +++ b/evm/src/cpu/kernel/asm/storage/write.asm @@ -0,0 +1,2 @@ +global storage_write: + // TODO diff --git a/evm/src/cpu/kernel/asm/storage_read.asm b/evm/src/cpu/kernel/asm/storage_read.asm deleted file mode 100644 index 6a704c61..00000000 --- a/evm/src/cpu/kernel/asm/storage_read.asm +++ /dev/null @@ -1,10 +0,0 @@ -// TODO: Dummy code for now. -global storage_read: - JUMPDEST - PUSH 1234 - POP - // An infinite loop: -mylabel: - JUMPDEST - PUSH mylabel - JUMP diff --git a/evm/src/cpu/kernel/asm/storage_write.asm b/evm/src/cpu/kernel/asm/storage_write.asm deleted file mode 100644 index 15c41b7c..00000000 --- a/evm/src/cpu/kernel/asm/storage_write.asm +++ /dev/null @@ -1,6 +0,0 @@ -// TODO: Dummy code for now. -global storage_write: - JUMPDEST - PUSH 123 // Whatever. - POP - BYTES 0x1, 0x02, 3 From 715c350ee86fef62534b035d37c9f0b17b26f777 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 25 Jul 2022 10:34:18 +0200 Subject: [PATCH 29/38] Implement mload/store_general --- evm/src/cpu/kernel/interpreter.rs | 78 ++++++++++++------------------- evm/src/memory/segments.rs | 2 + 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 9e39fb4f..fec42351 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -8,50 +8,6 @@ use crate::memory::segments::Segment; /// Halt interpreter execution whenever a jump to this offset is done. const HALT_OFFSET: usize = 0xdeadbeef; -#[derive(Debug, Default)] -pub(crate) struct EvmMemory { - memory: Vec, -} - -impl EvmMemory { - fn len(&self) -> usize { - self.memory.len() - } - - /// Expand memory until `self.len() >= offset`. - fn expand(&mut self, offset: usize) { - while self.len() < offset { - self.memory.extend([0; 32]); - } - } - - fn mload(&mut self, offset: usize) -> U256 { - self.expand(offset + 32); - U256::from_big_endian(&self.memory[offset..offset + 32]) - } - - fn mload8(&mut self, offset: usize) -> u8 { - self.expand(offset + 1); - self.memory[offset] - } - - fn mstore(&mut self, offset: usize, value: U256) { - self.expand(offset + 32); - let value_be = { - let mut tmp = [0; 32]; - value.to_big_endian(&mut tmp); - tmp - }; - self.memory[offset..offset + 32].copy_from_slice(&value_be); - } - - fn mstore8(&mut self, offset: usize, value: U256) { - self.expand(offset + 1); - let value_byte = value.0[0] as u8; - self.memory[offset] = value_byte; - } -} - #[derive(Debug)] pub(crate) struct ContextMemory { memory: Vec, @@ -236,8 +192,8 @@ impl<'a> Interpreter<'a> { 0xf8 => todo!(), // "CONSUME_GAS", 0xf9 => todo!(), // "EXIT_KERNEL", 0xfa => todo!(), // "STATICCALL", - 0xfb => todo!(), // "MLOAD_GENERAL", - 0xfc => todo!(), // "MSTORE_GENERAL", + 0xfb => self.run_mload_general(), // "MLOAD_GENERAL", + 0xfc => self.run_mstore_general(), // "MSTORE_GENERAL", 0xfd => todo!(), // "REVERT", 0xfe => bail!("Executed INVALID"), // "INVALID", 0xff => todo!(), // "SELFDESTRUCT", @@ -467,6 +423,26 @@ impl<'a> Interpreter<'a> { let x = self.pop(); self.context = x.as_usize(); } + + fn run_mload_general(&mut self) { + let context = self.pop().as_usize(); + let segment = self.pop().as_usize(); + let offset = self.pop().as_usize(); + let value = self + .memory + .mload_general(context, Segment::all()[segment], offset); + self.push(value); + } + + fn run_mstore_general(&mut self) { + // stack: context, segment, offset, value + let context = self.pop().as_usize(); + let segment = self.pop().as_usize(); + let offset = self.pop().as_usize(); + let value = self.pop(); + self.memory + .mstore_general(context, Segment::all()[segment], offset, value); + } } /// Return the (ordered) JUMPDEST offsets in the code. @@ -490,6 +466,7 @@ mod tests { // use hex_literal::hex; use crate::cpu::kernel::interpreter::{run, Interpreter}; + use crate::memory::segments::Segment; #[test] fn test_run() -> anyhow::Result<()> { @@ -522,7 +499,14 @@ mod tests { let run = run(&code, 0, vec![])?; let Interpreter { stack, memory, .. } = run; assert_eq!(stack, vec![0xff.into(), 0xff00.into()]); - // assert_eq!(&memory.memory, &hex!("00000000000000000000000000000000000000000000000000000000000000ff0000000000000042000000000000000000000000000000000000000000000000")); + assert_eq!( + memory.memory[0].segments[Segment::MainMemory as usize].get(0x27), + 0x42.into() + ); + assert_eq!( + memory.memory[0].segments[Segment::MainMemory as usize].get(0x1f), + 0xff.into() + ); Ok(()) } } diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index f6a67dc8..ad092d41 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -54,4 +54,6 @@ impl Segment { Segment::RlpRaw => "SEGMENT_RLP_RAW", } } + + pub(crate) } From 304299a007929c60b283033b0db4be2427d07dc7 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 25 Jul 2022 10:39:51 +0200 Subject: [PATCH 30/38] Add assert to range check memory values --- evm/src/cpu/kernel/interpreter.rs | 14 ++++++-------- evm/src/memory/segments.rs | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index fec42351..d332a830 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -426,22 +426,20 @@ impl<'a> Interpreter<'a> { fn run_mload_general(&mut self) { let context = self.pop().as_usize(); - let segment = self.pop().as_usize(); + let segment = Segment::all()[self.pop().as_usize()]; let offset = self.pop().as_usize(); - let value = self - .memory - .mload_general(context, Segment::all()[segment], offset); + let value = self.memory.mload_general(context, segment, offset); + assert!(value < U256::one() << segment.bit_range()); self.push(value); } fn run_mstore_general(&mut self) { - // stack: context, segment, offset, value let context = self.pop().as_usize(); - let segment = self.pop().as_usize(); + let segment = Segment::all()[self.pop().as_usize()]; let offset = self.pop().as_usize(); let value = self.pop(); - self.memory - .mstore_general(context, Segment::all()[segment], offset, value); + assert!(value < U256::one() << segment.bit_range()); + self.memory.mstore_general(context, segment, offset, value); } } diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index ad092d41..106f2963 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -55,5 +55,18 @@ impl Segment { } } - pub(crate) + #[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::Metadata => 8, + Segment::KernelGeneral => 8, + Segment::TxnData => 8, + Segment::RlpRaw => 8, + } + } } From a0295f0079650b9a1e463b4437f0ce0ce1e0698f Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 25 Jul 2022 11:09:41 +0200 Subject: [PATCH 31/38] Minor --- evm/src/cpu/kernel/interpreter.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index d332a830..d7c61f02 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -338,12 +338,12 @@ impl<'a> Interpreter<'a> { } fn run_mload(&mut self) { - let offset = self.pop(); + let offset = self.pop().as_usize(); let value = U256::from_big_endian( &(0..32) .map(|i| { self.memory - .mload_general(self.context, Segment::MainMemory, offset.as_usize() + i) + .mload_general(self.context, Segment::MainMemory, offset + i) .byte(0) }) .collect::>(), @@ -352,27 +352,23 @@ impl<'a> Interpreter<'a> { } fn run_mstore(&mut self) { - let offset = self.pop(); + let offset = self.pop().as_usize(); let value = self.pop(); let mut bytes = [0; 32]; value.to_big_endian(&mut bytes); - for i in 0..32 { - self.memory.mstore_general( - self.context, - Segment::MainMemory, - offset.as_usize() + i, - bytes[i].into(), - ); + for (i, byte) in (0..32).zip(bytes) { + self.memory + .mstore_general(self.context, Segment::MainMemory, offset + i, byte.into()); } } fn run_mstore8(&mut self) { - let offset = self.pop(); + let offset = self.pop().as_usize(); let value = self.pop(); self.memory.mstore_general( self.context, Segment::MainMemory, - offset.as_usize(), + offset, value.byte(0).into(), ); } @@ -461,8 +457,6 @@ fn find_jumpdests(code: &[u8]) -> Vec { #[cfg(test)] mod tests { - // use hex_literal::hex; - use crate::cpu::kernel::interpreter::{run, Interpreter}; use crate::memory::segments::Segment; From 05c7dfa11591461337c1a7d468346d9fc4df346c Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 25 Jul 2022 16:32:59 -0700 Subject: [PATCH 32/38] Feedback --- evm/src/cpu/kernel/asm/rlp/decode.asm | 19 ++++++++++++------- evm/src/cpu/kernel/asm/rlp/read_to_memory.asm | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm index dde85d05..f11c5f16 100644 --- a/evm/src/cpu/kernel/asm/rlp/decode.asm +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -18,15 +18,15 @@ global decode_rlp_string_len: %mload_current(@SEGMENT_RLP_RAW) // stack: first_byte, pos, retdest DUP1 - %gt_const(0xb6) - // stack: first_byte >= 0xb7, first_byte, pos, retdest + %gt_const(0xb7) + // stack: first_byte >= 0xb8, first_byte, pos, retdest %jumpi(decode_rlp_string_len_large) // stack: first_byte, pos, retdest DUP1 %gt_const(0x7f) // stack: first_byte >= 0x80, first_byte, pos, retdest %jumpi(decode_rlp_string_len_medium) -decode_rlp_string_len_small: + // String is a single byte in the range [0x00, 0x7f]. %stack (first_byte, pos, retdest) -> (retdest, pos, 1) JUMP @@ -38,6 +38,8 @@ decode_rlp_string_len_medium: SWAP1 %add_const(1) // stack: pos', len, retdest + %stack (pos, len, retdest) -> (retdest, pos, len) + JUMP decode_rlp_string_len_large: // String is >55 bytes long. First byte contains the len of the len. // stack: first_byte, pos, retdest @@ -80,13 +82,14 @@ global decode_rlp_list_len: %add_const(1) // increment pos SWAP1 // stack: first_byte, pos', retdest - // If first_byte is >= 0xf7, it's a > 55 byte list, and + // If first_byte is >= 0xf8, it's a > 55 byte list, and // first_byte - 0xf7 is the length of the length. DUP1 - %gt_const(0xf6) // GT is native while GE is not, so compare to 0xf6 instead + %gt_const(0xf7) // GT is native while GE is not, so compare to 0xf6 instead // stack: first_byte >= 0xf7, first_byte, pos', retdest %jumpi(decode_rlp_list_len_big) -decode_rlp_list_len_small: + + // This is the "small list" case. // The list length is first_byte - 0xc0. // stack: first_byte, pos', retdest %sub_const(0xc0) @@ -116,12 +119,13 @@ decode_int_given_len: // stack: pos, end_pos, retdest PUSH 0 // initial accumulator state // stack: acc, pos, end_pos, retdest + decode_int_given_len_loop: JUMPDEST // stack: acc, pos, end_pos, retdest DUP3 DUP3 - ISZERO + EQ // stack: pos == end_pos, acc, pos, end_pos, retdest %jumpi(decode_int_given_len_finish) // stack: acc, pos, end_pos, retdest @@ -139,6 +143,7 @@ decode_int_given_len_loop: SWAP1 // stack: acc', pos', end_pos, retdest %jump(decode_int_given_len_loop) + decode_int_given_len_finish: JUMPDEST %stack (acc, pos, end_pos, retdest) -> (retdest, pos, acc) diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm index 1a84c710..ae75e3d7 100644 --- a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -23,7 +23,7 @@ read_rlp_to_memory_loop: // stack: pos, len, retdest PROVER_INPUT // stack: byte, pos, len, retdest - DUP1 + DUP2 // stack: pos, byte, pos, len, retdest %mstore_current(@SEGMENT_RLP_RAW) // stack: pos, len, retdest From d1cb854cf2c4866b7aac89dc2a08c8d7a02b3509 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 26 Jul 2022 16:12:21 -0700 Subject: [PATCH 33/38] terminology --- .../cpu/kernel/asm/transactions/type_0.asm | 32 +++++++++---------- .../cpu/kernel/asm/transactions/type_2.asm | 7 ++-- evm/src/cpu/kernel/txn_fields.rs | 12 +++---- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm index 3a217fda..543095a7 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -1,5 +1,5 @@ // Type 0 transactions, aka legacy transaction, have the format -// rlp([nonce, gas_price, gas_limit, destination, amount, data, v, r, s]) +// rlp([nonce, gas_price, gas_limit, to, value, data, v, r, s]) // // The field v was originally encoded as // 27 + y_parity @@ -7,9 +7,9 @@ // 35 + 2 * chain_id + y_parity // // If a chain_id is present in v, the signed data is -// keccak256(rlp([nonce, gas_price, gas_limit, destination, amount, data, chain_id, 0, 0])) +// keccak256(rlp([nonce, gas_price, gas_limit, to, value, data, chain_id, 0, 0])) // otherwise, it is -// keccak256(rlp([nonce, gas_price, gas_limit, destination, amount, data])) +// keccak256(rlp([nonce, gas_price, gas_limit, to, value, data])) global process_type_0_txn: JUMPDEST @@ -58,34 +58,34 @@ store_gas_limit: %mstore_current(@SEGMENT_NORMALIZED_TXN) // Peak at the RLP to see if the next byte is zero. - // If so, there is no destination field, so skip the store_destination step. + // If so, there is no value field, so skip the store_to step. // stack: pos DUP1 %mload_current(@SEGMENT_RLP_RAW) ISZERO - // stack: destination_empty, pos - %jumpi(parse_amount) + // stack: to_empty, pos + %jumpi(parse_value) - // If we got here, there is a destination field. - PUSH store_destination + // If we got here, there is a "to" field. + PUSH store_to SWAP1 - // stack: pos, store_destination + // stack: pos, store_to %jump(decode_rlp_scalar) -store_destination: - %stack (pos, destination) -> (@TXN_FIELD_DESTINATION, destination, pos) +store_to: + %stack (pos, to) -> (@TXN_FIELD_TO, to, pos) %mstore_current(@SEGMENT_NORMALIZED_TXN) // stack: pos -parse_amount: +parse_value: // stack: pos - PUSH store_amount + PUSH store_value SWAP1 - // stack: pos, store_amount + // stack: pos, store_value %jump(decode_rlp_scalar) -store_amount: - %stack (pos, amount) -> (@TXN_FIELD_AMOUNT, amount, pos) +store_value: + %stack (pos, value) -> (@TXN_FIELD_VALUE, value, pos) %mstore_current(@SEGMENT_NORMALIZED_TXN) // stack: pos diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm index bdde4aa6..9807f88f 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -1,12 +1,11 @@ // Type 2 transactions, introduced by EIP 1559, have the format // 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, -// gas_limit, destination, amount, data, access_list, y_parity, -// r, s]) +// gas_limit, to, value, data, access_list, y_parity, r, s]) // // The signed data is // keccak256(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, -// max_fee_per_gas, gas_limit, destination, amount, -// data, access_list])) +// max_fee_per_gas, gas_limit, to, value, data, +// access_list])) global process_type_2_txn: JUMPDEST diff --git a/evm/src/cpu/kernel/txn_fields.rs b/evm/src/cpu/kernel/txn_fields.rs index 4dc8bfbb..141eee39 100644 --- a/evm/src/cpu/kernel/txn_fields.rs +++ b/evm/src/cpu/kernel/txn_fields.rs @@ -10,8 +10,8 @@ pub(crate) enum NormalizedTxnField { MaxPriorityFeePerGas = 3, MaxFeePerGas = 4, GasLimit = 5, - Destination = 6, - Amount = 7, + To = 6, + Value = 7, /// The length of the data field. The data itself is stored in another segment. DataLen = 8, YParity = 9, @@ -30,8 +30,8 @@ impl NormalizedTxnField { Self::MaxPriorityFeePerGas, Self::MaxFeePerGas, Self::GasLimit, - Self::Destination, - Self::Amount, + Self::To, + Self::Value, Self::DataLen, Self::YParity, Self::R, @@ -48,8 +48,8 @@ impl NormalizedTxnField { NormalizedTxnField::MaxPriorityFeePerGas => "TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS", NormalizedTxnField::MaxFeePerGas => "TXN_FIELD_MAX_FEE_PER_GAS", NormalizedTxnField::GasLimit => "TXN_FIELD_GAS_LIMIT", - NormalizedTxnField::Destination => "TXN_FIELD_DESTINATION", - NormalizedTxnField::Amount => "TXN_FIELD_AMOUNT", + NormalizedTxnField::To => "TXN_FIELD_TO", + NormalizedTxnField::Value => "TXN_FIELD_VALUE", NormalizedTxnField::DataLen => "TXN_FIELD_DATA_LEN", NormalizedTxnField::YParity => "TXN_FIELD_Y_PARITY", NormalizedTxnField::R => "TXN_FIELD_R", From 3d8ac2a3917f081ff05fb87a92810c31ba0fe85f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 26 Jul 2022 16:25:01 -0700 Subject: [PATCH 34/38] style --- evm/src/cpu/kernel/asm/rlp/decode.asm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm index f11c5f16..76daec1a 100644 --- a/evm/src/cpu/kernel/asm/rlp/decode.asm +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -30,6 +30,7 @@ global decode_rlp_string_len: // String is a single byte in the range [0x00, 0x7f]. %stack (first_byte, pos, retdest) -> (retdest, pos, 1) JUMP + decode_rlp_string_len_medium: // String is 0-55 bytes long. First byte contains the len. // stack: first_byte, pos, retdest @@ -40,6 +41,7 @@ decode_rlp_string_len_medium: // stack: pos', len, retdest %stack (pos, len, retdest) -> (retdest, pos, len) JUMP + decode_rlp_string_len_large: // String is >55 bytes long. First byte contains the len of the len. // stack: first_byte, pos, retdest @@ -96,6 +98,7 @@ global decode_rlp_list_len: // stack: len, pos', retdest %stack (len, pos, retdest) -> (retdest, pos, len) JUMP + decode_rlp_list_len_big: JUMPDEST // The length of the length is first_byte - 0xf7. From e8ab92b1157fb2071518e7957c504c70ac32e79d Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 27 Jul 2022 10:05:31 +0200 Subject: [PATCH 35/38] PR feedback --- evm/src/cpu/kernel/interpreter.rs | 27 ++++++++++++++------------- evm/src/memory/segments.rs | 6 +++--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index d7c61f02..016e3c44 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -9,35 +9,36 @@ use crate::memory::segments::Segment; const HALT_OFFSET: usize = 0xdeadbeef; #[derive(Debug)] -pub(crate) struct ContextMemory { - memory: Vec, +pub(crate) struct InterpreterMemory { + context_memory: Vec, } -impl Default for ContextMemory { +impl Default for InterpreterMemory { fn default() -> Self { Self { - memory: vec![MemoryContextState::default()], + context_memory: vec![MemoryContextState::default()], } } } -impl ContextMemory { +impl InterpreterMemory { fn mload_general(&self, context: usize, segment: Segment, offset: usize) -> U256 { - self.memory[context].segments[segment as usize].get(offset) + self.context_memory[context].segments[segment as usize].get(offset) } fn mstore_general(&mut self, context: usize, segment: Segment, offset: usize, value: U256) { - self.memory[context].segments[segment as usize].set(offset, value) + self.context_memory[context].segments[segment as usize].set(offset, value) } } +// TODO: Remove `code` and `stack` fields as they are contained in `memory`. pub(crate) struct Interpreter<'a> { code: &'a [u8], jumpdests: Vec, offset: usize, pub(crate) stack: Vec, context: usize, - memory: ContextMemory, + memory: InterpreterMemory, /// Non-deterministic prover inputs, stored backwards so that popping the last item gives the /// next prover input. prover_inputs: Vec, @@ -67,7 +68,7 @@ pub(crate) fn run_with_input( offset: initial_offset, stack: initial_stack, context: 0, - memory: ContextMemory::default(), + memory: InterpreterMemory::default(), prover_inputs, running: true, }; @@ -425,7 +426,7 @@ impl<'a> Interpreter<'a> { let segment = Segment::all()[self.pop().as_usize()]; let offset = self.pop().as_usize(); let value = self.memory.mload_general(context, segment, offset); - assert!(value < U256::one() << segment.bit_range()); + assert!(value.bits() <= segment.bit_range()); self.push(value); } @@ -434,7 +435,7 @@ impl<'a> Interpreter<'a> { let segment = Segment::all()[self.pop().as_usize()]; let offset = self.pop().as_usize(); let value = self.pop(); - assert!(value < U256::one() << segment.bit_range()); + assert!(value.bits() <= segment.bit_range()); self.memory.mstore_general(context, segment, offset, value); } } @@ -492,11 +493,11 @@ mod tests { let Interpreter { stack, memory, .. } = run; assert_eq!(stack, vec![0xff.into(), 0xff00.into()]); assert_eq!( - memory.memory[0].segments[Segment::MainMemory as usize].get(0x27), + memory.context_memory[0].segments[Segment::MainMemory as usize].get(0x27), 0x42.into() ); assert_eq!( - memory.memory[0].segments[Segment::MainMemory as usize].get(0x1f), + memory.context_memory[0].segments[Segment::MainMemory as usize].get(0x1f), 0xff.into() ); Ok(()) diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index 106f2963..ba90f183 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -63,9 +63,9 @@ impl Segment { Segment::MainMemory => 8, Segment::Calldata => 8, Segment::Returndata => 8, - Segment::Metadata => 8, - Segment::KernelGeneral => 8, - Segment::TxnData => 8, + Segment::Metadata => 256, + Segment::KernelGeneral => 256, + Segment::TxnData => 256, Segment::RlpRaw => 8, } } From ac68ce62c2d27aa0edc8fabc07bd1045a7c1a3cc Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 27 Jul 2022 10:16:04 +0200 Subject: [PATCH 36/38] Merge conflicts --- evm/src/memory/segments.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index 00b4c8de..15545ea0 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -69,6 +69,7 @@ impl Segment { Segment::Returndata => 8, Segment::Metadata => 256, Segment::KernelGeneral => 256, + Segment::TxnFields => 256, Segment::TxnData => 256, Segment::RlpRaw => 8, } From c028afa1f87780d965083f946b80fd186900526b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 27 Jul 2022 07:37:38 -0700 Subject: [PATCH 37/38] Update paper --- plonky2/plonky2.pdf | Bin 215153 -> 214900 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/plonky2/plonky2.pdf b/plonky2/plonky2.pdf index 299d1724e96e442aaff59c0502759b287e8698fc..349b22a6a5792c7c60dca48ba5874159d95b16af 100644 GIT binary patch delta 70147 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 SPKX7nq^JNA71c4i4f-F{j%zmn delta 70193 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

?B(hX@QeH#Zu@q3k{7 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

^oW}|a+K8WWR#bXya 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 RAvRQ50d(VruBjgAe*h^*8Z`g_ From c160c4032d42e62c6019c39773f61ee2726ad880 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Thu, 28 Jul 2022 04:36:33 +1000 Subject: [PATCH 38/38] Inter-row program counter constraints (#639) * Beginning of control flow support * Fixes to halt spin loop --- evm/src/all_stark.rs | 22 +++++- evm/src/cpu/columns.rs | 8 ++- evm/src/cpu/control_flow.rs | 112 +++++++++++++++++++++++++++++++ evm/src/cpu/cpu_stark.rs | 6 +- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/halt.asm | 6 ++ evm/src/cpu/mod.rs | 1 + 7 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 evm/src/cpu/control_flow.rs create mode 100644 evm/src/cpu/kernel/asm/halt.asm diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index ba157fc0..9f520019 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -143,6 +143,7 @@ mod tests { use crate::all_stark::AllStark; use crate::config::StarkConfig; use crate::cpu::cpu_stark::CpuStark; + use crate::cpu::kernel::aggregator::KERNEL; use crate::cross_table_lookup::testutils::check_ctls; use crate::keccak::keccak_stark::{KeccakStark, NUM_INPUTS, NUM_ROUNDS}; use crate::logic::{self, LogicStark, Operation}; @@ -321,8 +322,27 @@ 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]); + let mut row: cpu::columns::CpuColumnsView = + [F::ZERO; CpuStark::::COLUMNS].into(); + row.is_cpu_cycle = F::ONE; + cpu_stark.generate(row.borrow_mut()); + cpu_trace_rows.push(row.into()); } + + // Ensure we finish in a halted state. + { + let num_rows = cpu_trace_rows.len(); + let halt_label = F::from_canonical_usize(KERNEL.global_labels["halt_pc0"]); + + let last_row: &mut cpu::columns::CpuColumnsView = + cpu_trace_rows[num_rows - 1].borrow_mut(); + last_row.program_counter = halt_label; + + let second_last_row: &mut cpu::columns::CpuColumnsView = + cpu_trace_rows[num_rows - 2].borrow_mut(); + second_last_row.next_program_counter = halt_label; + } + trace_rows_to_poly_values(cpu_trace_rows) } diff --git a/evm/src/cpu/columns.rs b/evm/src/cpu/columns.rs index ae6872df..970f0279 100644 --- a/evm/src/cpu/columns.rs +++ b/evm/src/cpu/columns.rs @@ -17,9 +17,12 @@ pub struct CpuColumnsView { pub is_bootstrap_contract: T, /// Filter. 1 if the row corresponds to a cycle of execution and 0 otherwise. - /// Lets us re-use decode columns in non-cycle rows. + /// Lets us re-use columns in non-cycle rows. pub is_cpu_cycle: T, + /// If CPU cycle: The program counter for the current instruction. + pub program_counter: T, + /// If CPU cycle: The opcode being decoded, in {0, ..., 255}. pub opcode: T, @@ -139,6 +142,9 @@ pub struct CpuColumnsView { /// If CPU cycle: the opcode, broken up into bits in **big-endian** order. pub opcode_bits: [T; 8], + /// If CPU cycle: The program counter for the next instruction. + pub next_program_counter: T, + /// Filter. 1 iff a Keccak permutation is computed on this row. pub is_keccak: T, pub keccak_input_limbs: [T; 50], diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs new file mode 100644 index 00000000..cf24afca --- /dev/null +++ b/evm/src/cpu/control_flow.rs @@ -0,0 +1,112 @@ +use plonky2::field::extension::Extendable; +use plonky2::field::packed::PackedField; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cpu::columns::CpuColumnsView; +use crate::cpu::kernel::aggregator::KERNEL; + +fn get_halt_pcs() -> (F, F) { + let halt_pc0 = KERNEL.global_labels["halt_pc0"]; + let halt_pc1 = KERNEL.global_labels["halt_pc1"]; + + ( + F::from_canonical_usize(halt_pc0), + F::from_canonical_usize(halt_pc1), + ) +} + +pub fn eval_packed_generic( + lv: &CpuColumnsView

, + nv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + // Once we start executing instructions, then we continue until the end of the table. + yield_constr.constraint_transition(lv.is_cpu_cycle * (nv.is_cpu_cycle - P::ONES)); + + // If a row is a CPU cycle, then its `next_program_counter` becomes the `program_counter` of the + // next row. + yield_constr + .constraint_transition(lv.is_cpu_cycle * (nv.program_counter - lv.next_program_counter)); + + // If a non-CPU cycle row is followed by a CPU cycle row, then the `program_counter` of the CPU + // cycle row is 0. + yield_constr + .constraint_transition((lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle * nv.program_counter); + + // The first row has nowhere to continue execution from, so if it's a cycle row, then its + // `program_counter` must be 0. + // NB: I know the first few rows will be used for initialization and will not be CPU cycle rows. + // Once that's done, then this constraint can be removed. Until then, it is needed to ensure + // that execution starts at 0 and not at any arbitrary offset. + yield_constr.constraint_first_row(lv.is_cpu_cycle * lv.program_counter); + + // The last row must be a CPU cycle row. + yield_constr.constraint_last_row(lv.is_cpu_cycle - P::ONES); + // Also, the last row's `program_counter` must be inside the `halt` infinite loop. Note that + // that loop consists of two instructions, so we must check for `halt` and `halt_inner` labels. + let (halt_pc0, halt_pc1) = get_halt_pcs::(); + yield_constr + .constraint_last_row((lv.program_counter - halt_pc0) * (lv.program_counter - halt_pc1)); +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + nv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + // Once we start executing instructions, then we continue until the end of the table. + { + let constr = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, lv.is_cpu_cycle); + yield_constr.constraint_transition(builder, constr); + } + + // If a row is a CPU cycle, then its `next_program_counter` becomes the `program_counter` of the + // next row. + { + let constr = builder.sub_extension(nv.program_counter, lv.next_program_counter); + let constr = builder.mul_extension(lv.is_cpu_cycle, constr); + yield_constr.constraint_transition(builder, constr); + } + + // If a non-CPU cycle row is followed by a CPU cycle row, then the `program_counter` of the CPU + // cycle row is 0. + { + let constr = builder.mul_extension(nv.is_cpu_cycle, nv.program_counter); + let constr = builder.mul_sub_extension(lv.is_cpu_cycle, constr, constr); + yield_constr.constraint_transition(builder, constr); + } + + // The first row has nowhere to continue execution from, so if it's a cycle row, then its + // `program_counter` must be 0. + // NB: I know the first few rows will be used for initialization and will not be CPU cycle rows. + // Once that's done, then this constraint can be removed. Until then, it is needed to ensure + // that execution starts at 0 and not at any arbitrary offset. + { + let constr = builder.mul_extension(lv.is_cpu_cycle, lv.program_counter); + yield_constr.constraint_first_row(builder, constr); + } + + // The last row must be a CPU cycle row. + { + let one = builder.one_extension(); + let constr = builder.sub_extension(lv.is_cpu_cycle, one); + yield_constr.constraint_last_row(builder, constr); + } + // Also, the last row's `program_counter` must be inside the `halt` infinite loop. Note that + // that loop consists of two instructions, so we must check for `halt` and `halt_inner` labels. + { + let (halt_pc0, halt_pc1) = get_halt_pcs(); + let halt_pc0_target = builder.constant_extension(halt_pc0); + let halt_pc1_target = builder.constant_extension(halt_pc1); + + let halt_pc0_offset = builder.sub_extension(lv.program_counter, halt_pc0_target); + let halt_pc1_offset = builder.sub_extension(lv.program_counter, halt_pc1_target); + let constr = builder.mul_extension(halt_pc0_offset, halt_pc1_offset); + + yield_constr.constraint_last_row(builder, constr); + } +} diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 1e5cc887..6eb3154e 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::{bootstrap_kernel, decode, simple_logic}; +use crate::cpu::{bootstrap_kernel, control_flow, decode, simple_logic}; use crate::cross_table_lookup::Column; use crate::memory::NUM_CHANNELS; use crate::stark::Stark; @@ -88,7 +88,9 @@ impl, const D: usize> Stark for CpuStark, { let local_values = vars.local_values.borrow(); + let next_values = vars.next_values.borrow(); bootstrap_kernel::eval_bootstrap_kernel(vars, yield_constr); + control_flow::eval_packed_generic(local_values, next_values, yield_constr); decode::eval_packed_generic(local_values, yield_constr); simple_logic::eval_packed(local_values, yield_constr); } @@ -100,7 +102,9 @@ impl, const D: usize> Stark for CpuStark, ) { let local_values = vars.local_values.borrow(); + let next_values = vars.next_values.borrow(); bootstrap_kernel::eval_bootstrap_kernel_circuit(builder, vars, yield_constr); + control_flow::eval_ext_circuit(builder, local_values, next_values, 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/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 1f8ba0da..ec9e34e8 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -39,6 +39,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/exp.asm"), include_str!("asm/curve_mul.asm"), include_str!("asm/curve_add.asm"), + include_str!("asm/halt.asm"), include_str!("asm/memory.asm"), include_str!("asm/moddiv.asm"), include_str!("asm/secp256k1/curve_mul.asm"), diff --git a/evm/src/cpu/kernel/asm/halt.asm b/evm/src/cpu/kernel/asm/halt.asm new file mode 100644 index 00000000..906ce51a --- /dev/null +++ b/evm/src/cpu/kernel/asm/halt.asm @@ -0,0 +1,6 @@ +global halt: + PUSH halt_pc0 +global halt_pc0: + DUP1 +global halt_pc1: + JUMP diff --git a/evm/src/cpu/mod.rs b/evm/src/cpu/mod.rs index 8da8a125..6c767998 100644 --- a/evm/src/cpu/mod.rs +++ b/evm/src/cpu/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod bootstrap_kernel; pub(crate) mod columns; +mod control_flow; pub mod cpu_stark; pub(crate) mod decode; pub mod kernel;