diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 21bf75cc..bee641a3 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -156,7 +156,7 @@ impl, const D: usize> Stark for CpuStark, const D: usize> Stark for CpuStark + (args_size, args_offset, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %checked_mem_expansion + %stack (kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (ret_size, ret_offset, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %checked_mem_expansion + %create_context - // stack: new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size + // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size // TODO: Consider call depth // Each line in the block below does not change the stack. @@ -20,15 +35,15 @@ global sys_call: DUP5 %set_new_ctx_value DUP5 DUP5 %address %transfer_eth %jumpi(panic) // TODO: Fix this panic. %set_new_ctx_parent_pc(after_call_instruction) - DUP3 %set_new_ctx_gas_limit // TODO: This is not correct in most cases. Use C_callgas as in the YP. + DUP3 %set_new_ctx_gas_limit DUP4 %set_new_ctx_code - %stack (new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size) -> - (new_ctx, args_offset, args_size, new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size) + %stack (new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (new_ctx, args_offset, args_size, new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) %copy_mem_to_calldata - // stack: new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size - %stack (new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size) + // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size + %stack (new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> (new_ctx, kexit_info, ret_offset, ret_size) %enter_new_ctx @@ -36,25 +51,38 @@ global sys_call: // given account. In particular the storage remains the same. global sys_callcode: // stack: kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size - // TODO: Charge gas. SWAP2 // stack: address, gas, kexit_info, value, args_offset, args_size, ret_offset, ret_size %u256_to_addr // Truncate to 160 bits - DUP1 %insert_accessed_addresses POP // TODO: Use return value in gas calculation. - SWAP2 - // stack: kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size + DUP1 %insert_accessed_addresses + + %call_charge_gas + + %stack (kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (args_size, args_offset, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %checked_mem_expansion + %stack (kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (ret_size, ret_offset, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %checked_mem_expansion + + // stack: kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size %create_context - // stack: new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size + // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size // Each line in the block below does not change the stack. %address %set_new_ctx_addr %address %set_new_ctx_caller DUP5 %set_new_ctx_value - DUP5 DUP5 %address %transfer_eth %jumpi(panic) // TODO: Fix this panic. %set_new_ctx_parent_pc(after_call_instruction) + DUP3 %set_new_ctx_gas_limit + DUP4 %set_new_ctx_code - // stack: new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size - %stack (new_ctx, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size) + %stack (new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (new_ctx, args_offset, args_size, new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %copy_mem_to_calldata + + // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size + %stack (new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> (new_ctx, kexit_info, ret_offset, ret_size) %enter_new_ctx @@ -65,15 +93,25 @@ global sys_callcode: // CALL if the value sent is not 0. global sys_staticcall: // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size - // TODO: Charge gas. SWAP2 // stack: address, gas, kexit_info, args_offset, args_size, ret_offset, ret_size %u256_to_addr // Truncate to 160 bits - DUP1 %insert_accessed_addresses POP // TODO: Use return value in gas calculation. - SWAP2 - // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size + DUP1 %insert_accessed_addresses + + // Add a value of 0 to the stack. Slightly inefficient but that way we can reuse %call_charge_gas. + %stack (cold_access, address, gas, kexit_info) -> (cold_access, address, gas, kexit_info, 0) + %call_charge_gas + + %stack (kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (args_size, args_offset, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %checked_mem_expansion + %stack (kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (ret_size, ret_offset, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %checked_mem_expansion + + // stack: kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size %create_context - // stack: new_ctx, kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size + // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size // Each line in the block below does not change the stack. %set_static_true @@ -81,8 +119,14 @@ global sys_staticcall: %address %set_new_ctx_caller PUSH 0 %set_new_ctx_value %set_new_ctx_parent_pc(after_call_instruction) + DUP3 %set_new_ctx_gas_limit + DUP4 %set_new_ctx_code - %stack (new_ctx, kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size) + %stack (new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (new_ctx, args_offset, args_size, new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %copy_mem_to_calldata + + %stack (new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> (new_ctx, kexit_info, ret_offset, ret_size) %enter_new_ctx @@ -91,23 +135,39 @@ global sys_staticcall: // value remain the same. global sys_delegatecall: // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size - // TODO: Charge gas. SWAP2 // stack: address, gas, kexit_info, args_offset, args_size, ret_offset, ret_size %u256_to_addr // Truncate to 160 bits - DUP1 %insert_accessed_addresses POP // TODO: Use return value in gas calculation. - SWAP2 - // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size + DUP1 %insert_accessed_addresses + + // Add a value of 0 to the stack. Slightly inefficient but that way we can reuse %call_charge_gas. + %stack (cold_access, address, gas, kexit_info) -> (cold_access, address, gas, kexit_info, 0) + %call_charge_gas + + %stack (kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (args_size, args_offset, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %checked_mem_expansion + %stack (kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (ret_size, ret_offset, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %checked_mem_expansion + + // stack: kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size %create_context - // stack: new_ctx, kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size + // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size // Each line in the block below does not change the stack. %address %set_new_ctx_addr %caller %set_new_ctx_caller %callvalue %set_new_ctx_value %set_new_ctx_parent_pc(after_call_instruction) + DUP3 %set_new_ctx_gas_limit + DUP4 %set_new_ctx_code - %stack (new_ctx, kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size) + %stack (new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> + (new_ctx, args_offset, args_size, new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) + %copy_mem_to_calldata + + %stack (new_ctx, kexit_info, callgas, address, args_offset, args_size, ret_offset, ret_size) -> (new_ctx, kexit_info, ret_offset, ret_size) %enter_new_ctx @@ -117,7 +177,7 @@ global after_call_instruction: SWAP3 // stack: kexit_info, leftover_gas, new_ctx, success, ret_offset, ret_size // Add the leftover gas into the appropriate bits of kexit_info. - SWAP1 %shl_const(192) ADD + SWAP1 %shl_const(192) SWAP1 SUB // stack: kexit_info, new_ctx, success, ret_offset, ret_size // The callee's terminal instruction will have populated RETURNDATA. @@ -203,9 +263,7 @@ global after_call_instruction: %stack (address, new_ctx) -> (address, new_ctx, @SEGMENT_CODE, %%after, new_ctx) %jump(load_code) %%after: - %stack (code_size, new_ctx) - -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_CODE_SIZE, code_size, new_ctx) - MSTORE_GENERAL + %set_new_ctx_code_size // stack: new_ctx %endmacro @@ -239,14 +297,93 @@ global after_call_instruction: %macro copy_returndata_to_mem // stack: kexit_info, new_ctx, success, ret_offset, ret_size + SWAP4 + %returndatasize + // stack: returndata_size, ret_size, new_ctx, success, ret_offset, kexit_info + %min GET_CONTEXT - %stack (ctx, kexit_info, new_ctx, success, ret_offset, ret_size) -> + %stack (ctx, n, new_ctx, success, ret_offset, kexit_info) -> ( ctx, @SEGMENT_MAIN_MEMORY, ret_offset, // DST ctx, @SEGMENT_RETURNDATA, 0, // SRC - ret_size, %%after, // count, retdest + n, %%after, // count, retdest kexit_info, success ) %jump(memcpy) %%after: %endmacro + +// Charge gas for *call opcodes and return the sub-context gas limit. +// Doesn't include memory expansion costs. +%macro call_charge_gas + // Compute C_aaccess + // stack: cold_access, address, gas, kexit_info, value + %mul_const(@GAS_COLDACCOUNTACCESS_MINUS_WARMACCESS) + %add_const(@GAS_WARMACCESS) + + // Compute C_xfer + // stack: Caaccess, address, gas, kexit_info, value + DUP5 ISZERO %not_bit + // stack: value≠0, Caaccess, address, gas, kexit_info, value + DUP1 + %mul_const(@GAS_CALLVALUE) + + // Compute C_new + // stack: Cxfer, value≠0, Caaccess, address, gas, kexit_info, value + SWAP1 + // stack: value≠0, Cxfer, Caaccess, address, gas, kexit_info, value + DUP4 %is_dead MUL + // stack: is_dead(address) and value≠0, Cxfer, Caaccess, address, gas, kexit_info, value + %mul_const(@GAS_NEWACCOUNT) + // stack: Cnew, Cxfer, Caaccess, address, gas, kexit_info, value + + // Compute C_extra + ADD ADD + + // Compute C_gascap + // stack: Cextra, address, gas, kexit_info, value + DUP4 %leftover_gas + // stack: leftover_gas, Cextra, address, gas, kexit_info, value + DUP2 DUP2 LT + // stack: leftover_gas=Cextra, (leftover_gas=Cextra, (leftover_gas=Cextra, (leftover_gas (Cextra, Cgascap, Cgascap) + ADD + %stack (C_call, Cgascap, address, gas, kexit_info, value) -> + (C_call, kexit_info, Cgascap, address, gas, value) + %charge_gas + + // Compute C_callgas + %stack (kexit_info, Cgascap, address, gas, value) -> + (Cgascap, address, gas, kexit_info, value) + DUP5 ISZERO %not_bit + // stack: value!=0, Cgascap, address, gas, kexit_info, value + %mul_const(@GAS_CALLSTIPEND) ADD + %stack (C_callgas, address, gas, kexit_info, value) -> + (kexit_info, C_callgas, address, value) +%endmacro + +// Checked memory expansion. +%macro checked_mem_expansion + // stack: size, offset, kexit_info + DUP1 ISZERO %jumpi(%%zero) + ADD // TODO: check for overflow + // stack: expanded_num_bytes, kexit_info + DUP1 %ensure_reasonable_offset + %update_mem_bytes + %jump(%%after) +%%zero: + %pop2 +%%after: +%endmacro diff --git a/evm/src/cpu/kernel/asm/core/create.asm b/evm/src/cpu/kernel/asm/core/create.asm index 3cc457d9..866e482c 100644 --- a/evm/src/cpu/kernel/asm/core/create.asm +++ b/evm/src/cpu/kernel/asm/core/create.asm @@ -4,6 +4,7 @@ // Pre stack: kexit_info, value, code_offset, code_len // Post stack: address global sys_create: + %check_static // stack: kexit_info, value, code_offset, code_len // TODO: Charge gas. %stack (kexit_info, value, code_offset, code_len) @@ -25,6 +26,7 @@ sys_create_got_address: // Pre stack: kexit_info, value, code_offset, code_len, salt // Post stack: address global sys_create2: + %check_static // stack: kexit_info, value, code_offset, code_len, salt // TODO: Charge gas. SWAP4 diff --git a/evm/src/cpu/kernel/asm/core/syscall_stubs.asm b/evm/src/cpu/kernel/asm/core/syscall_stubs.asm index 95b50b0b..1232576c 100644 --- a/evm/src/cpu/kernel/asm/core/syscall_stubs.asm +++ b/evm/src/cpu/kernel/asm/core/syscall_stubs.asm @@ -6,22 +6,18 @@ global sys_blockhash: global sys_prevrandao: // TODO: What semantics will this have for Edge? PANIC -global sys_chainid: - // TODO: Return the block's chain ID instead of the txn's, even though they should match. - // stack: kexit_info - %charge_gas_const(@GAS_BASE) - // stack: kexit_info - %mload_txn_field(@TXN_FIELD_CHAIN_ID) - // stack: chain_id, kexit_info - SWAP1 - EXIT_KERNEL global sys_log0: + %check_static PANIC global sys_log1: + %check_static PANIC global sys_log2: + %check_static PANIC global sys_log3: + %check_static PANIC global sys_log4: + %check_static PANIC diff --git a/evm/src/cpu/kernel/asm/core/terminate.asm b/evm/src/cpu/kernel/asm/core/terminate.asm index 5d9ed853..a46bbf0f 100644 --- a/evm/src/cpu/kernel/asm/core/terminate.asm +++ b/evm/src/cpu/kernel/asm/core/terminate.asm @@ -45,6 +45,7 @@ sys_return_finish: %jump(terminate_common) global sys_selfdestruct: + %check_static // stack: kexit_info, recipient SWAP1 %u256_to_addr %address DUP1 %balance diff --git a/evm/src/cpu/kernel/asm/core/util.asm b/evm/src/cpu/kernel/asm/core/util.asm index de3dfb70..a8e41a7b 100644 --- a/evm/src/cpu/kernel/asm/core/util.asm +++ b/evm/src/cpu/kernel/asm/core/util.asm @@ -66,4 +66,4 @@ DUP1 %is_non_existent SWAP1 %is_empty ADD // OR -%endmacro \ No newline at end of file +%endmacro diff --git a/evm/src/cpu/kernel/asm/memory/metadata.asm b/evm/src/cpu/kernel/asm/memory/metadata.asm index 4941f8d6..c158718b 100644 --- a/evm/src/cpu/kernel/asm/memory/metadata.asm +++ b/evm/src/cpu/kernel/asm/memory/metadata.asm @@ -200,6 +200,19 @@ global sys_gaslimit: SWAP1 EXIT_KERNEL +%macro blockchainid + %mload_global_metadata(@GLOBAL_METADATA_BLOCK_CHAIN_ID) +%endmacro + +global sys_chainid: + // stack: kexit_info + %charge_gas_const(@GAS_BASE) + // stack: kexit_info + %blockchainid + // stack: chain_id, kexit_info + SWAP1 + EXIT_KERNEL + %macro basefee %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BASE_FEE) %endmacro @@ -283,3 +296,10 @@ global sys_basefee: %jumpi(fault_exception) // stack: (empty) %endmacro + +// Convenience macro for checking if the current context is static. +// Called before state-changing opcodes. +%macro check_static + %mload_context_metadata(@CTX_METADATA_STATIC) + %jumpi(fault_exception) +%endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm index 90fb0e0b..6da8f567 100644 --- a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm @@ -4,6 +4,7 @@ // Post stack: (empty) global sys_sstore: + %check_static %stack (kexit_info, slot, value) -> (slot, kexit_info, slot, value) %address %insert_accessed_storage_keys POP // TODO: Use return value in gas calculation. // TODO: Assuming a cold zero -> nonzero write for now. diff --git a/evm/src/cpu/kernel/asm/util/basic_macros.asm b/evm/src/cpu/kernel/asm/util/basic_macros.asm index c57f0649..279d449a 100644 --- a/evm/src/cpu/kernel/asm/util/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/util/basic_macros.asm @@ -360,8 +360,6 @@ %macro not_bit // stack: b - PUSH 1 - // stack: 1, b - SUB - // stack: 1 - b + ISZERO + // stack: not b %endmacro diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 3f699260..fa3788cf 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -1,5 +1,6 @@ //! An EVM interpreter for testing and debugging purposes. +use core::cmp::Ordering; use std::collections::HashMap; use std::ops::Range; @@ -305,20 +306,20 @@ impl<'a> Interpreter<'a> { 0x02 => self.run_mul(), // "MUL", 0x03 => self.run_sub(), // "SUB", 0x04 => self.run_div(), // "DIV", - 0x05 => todo!(), // "SDIV", + 0x05 => self.run_sdiv(), // "SDIV", 0x06 => self.run_mod(), // "MOD", - 0x07 => todo!(), // "SMOD", + 0x07 => self.run_smod(), // "SMOD", 0x08 => self.run_addmod(), // "ADDMOD", 0x09 => self.run_mulmod(), // "MULMOD", 0x0a => self.run_exp(), // "EXP", - 0x0b => todo!(), // "SIGNEXTEND", + 0x0b => self.run_signextend(), // "SIGNEXTEND", 0x0c => self.run_addfp254(), // "ADDFP254", 0x0d => self.run_mulfp254(), // "MULFP254", 0x0e => self.run_subfp254(), // "SUBFP254", 0x10 => self.run_lt(), // "LT", 0x11 => self.run_gt(), // "GT", - 0x12 => todo!(), // "SLT", - 0x13 => todo!(), // "SGT", + 0x12 => self.run_slt(), // "SLT", + 0x13 => self.run_sgt(), // "SGT", 0x14 => self.run_eq(), // "EQ", 0x15 => self.run_iszero(), // "ISZERO", 0x16 => self.run_and(), // "AND", @@ -328,7 +329,7 @@ impl<'a> Interpreter<'a> { 0x1a => self.run_byte(), // "BYTE", 0x1b => self.run_shl(), // "SHL", 0x1c => self.run_shr(), // "SHR", - 0x1d => todo!(), // "SAR", + 0x1d => self.run_sar(), // "SAR", 0x20 => self.run_keccak256(), // "KECCAK256", 0x21 => self.run_keccak_general(), // "KECCAK_GENERAL", 0x30 => todo!(), // "ADDRESS", @@ -467,12 +468,75 @@ impl<'a> Interpreter<'a> { self.push(if y.is_zero() { U256::zero() } else { x / y }); } + fn run_sdiv(&mut self) { + let mut x = self.pop(); + let mut y = self.pop(); + + let y_is_zero = y.is_zero(); + + if y_is_zero { + self.push(U256::zero()); + } else if y.eq(&MINUS_ONE) && x.eq(&MIN_VALUE) { + self.push(MIN_VALUE); + } else { + let x_is_pos = x.eq(&(x & SIGN_MASK)); + let y_is_pos = y.eq(&(y & SIGN_MASK)); + + // We compute the absolute quotient first, + // then adapt its sign based on the operands. + if !x_is_pos { + x = two_complement(x); + } + if !y_is_pos { + y = two_complement(y); + } + let div = x / y; + if div.eq(&U256::zero()) { + self.push(U256::zero()); + } + + self.push(if x_is_pos == y_is_pos { + div + } else { + two_complement(div) + }); + } + } + fn run_mod(&mut self) { let x = self.pop(); let y = self.pop(); self.push(if y.is_zero() { U256::zero() } else { x % y }); } + fn run_smod(&mut self) { + let mut x = self.pop(); + let mut y = self.pop(); + + if y.is_zero() { + self.push(U256::zero()); + } else { + let x_is_pos = x.eq(&(x & SIGN_MASK)); + let y_is_pos = y.eq(&(y & SIGN_MASK)); + + // We compute the absolute remainder first, + // then adapt its sign based on the operands. + if !x_is_pos { + x = two_complement(x); + } + if !y_is_pos { + y = two_complement(y); + } + let rem = x % y; + if rem.eq(&U256::zero()) { + self.push(U256::zero()); + } + + // Remainder always has the same sign as the dividend. + self.push(if x_is_pos { rem } else { two_complement(rem) }); + } + } + fn run_addmod(&mut self) { let x = U512::from(self.pop()); let y = U512::from(self.pop()); @@ -513,6 +577,43 @@ impl<'a> Interpreter<'a> { self.push_bool(x > y); } + fn run_slt(&mut self) { + let x = self.pop(); + let y = self.pop(); + self.push_bool(signed_cmp(x, y) == Ordering::Less); + } + + fn run_sgt(&mut self) { + let x = self.pop(); + let y = self.pop(); + self.push_bool(signed_cmp(x, y) == Ordering::Greater); + } + + fn run_signextend(&mut self) { + let n = self.pop(); + let x = self.pop(); + if n > U256::from(31) { + self.push(x); + } else { + let n = n.low_u64() as usize; + let num_bytes_prepend = 31 - n; + + let mut x_bytes = [0u8; 32]; + x.to_big_endian(&mut x_bytes); + let x_bytes = x_bytes[num_bytes_prepend..].to_vec(); + let sign_bit = x_bytes[0] >> 7; + + let mut bytes = if sign_bit == 0 { + vec![0; num_bytes_prepend] + } else { + vec![0xff; num_bytes_prepend] + }; + bytes.extend_from_slice(&x_bytes); + + self.push(U256::from_big_endian(&bytes)); + } + } + fn run_eq(&mut self) { let x = self.pop(); let y = self.pop(); @@ -574,6 +675,30 @@ impl<'a> Interpreter<'a> { self.push(value >> shift); } + fn run_sar(&mut self) { + let shift = self.pop(); + let value = self.pop(); + let value_is_neg = !value.eq(&(value & SIGN_MASK)); + + if shift < U256::from(256usize) { + let shift = shift.low_u64() as usize; + let mask = !(MINUS_ONE >> shift); + let value_shifted = value >> shift; + + if value_is_neg { + self.push(value_shifted | mask); + } else { + self.push(value_shifted); + }; + } else { + self.push(if value_is_neg { + MINUS_ONE + } else { + U256::zero() + }); + } + } + fn run_keccak256(&mut self) { let offset = self.pop().as_usize(); let size = self.pop().as_usize(); @@ -837,6 +962,70 @@ impl<'a> Interpreter<'a> { } } +// Computes the two's complement of the given integer. +fn two_complement(x: U256) -> U256 { + let flipped_bits = x ^ MINUS_ONE; + flipped_bits.overflowing_add(U256::one()).0 +} + +fn signed_cmp(x: U256, y: U256) -> Ordering { + let x_is_zero = x.is_zero(); + let y_is_zero = y.is_zero(); + + if x_is_zero && y_is_zero { + return Ordering::Equal; + } + + let x_is_pos = x.eq(&(x & SIGN_MASK)); + let y_is_pos = y.eq(&(y & SIGN_MASK)); + + if x_is_zero { + if y_is_pos { + return Ordering::Less; + } else { + return Ordering::Greater; + } + }; + + if y_is_zero { + if x_is_pos { + return Ordering::Greater; + } else { + return Ordering::Less; + } + }; + + match (x_is_pos, y_is_pos) { + (true, true) => x.cmp(&y), + (true, false) => Ordering::Greater, + (false, true) => Ordering::Less, + (false, false) => x.cmp(&y).reverse(), + } +} + +/// -1 in two's complement representation consists in all bits set to 1. +const MINUS_ONE: U256 = U256([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, +]); + +/// -2^255 in two's complement representation consists in the MSB set to 1. +const MIN_VALUE: U256 = U256([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x8000000000000000, +]); + +const SIGN_MASK: U256 = U256([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0x7fffffffffffffff, +]); + /// Return the (ordered) JUMPDEST offsets in the code. fn find_jumpdests(code: &[u8]) -> Vec { let mut offset = 0; diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index e9258172..f5e9c78c 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -29,7 +29,7 @@ const BASIC_BINARY_OP: Option = Some(StackBehavior { disable_other_channels: true, }); const BASIC_TERNARY_OP: Option = Some(StackBehavior { - num_pops: 2, + num_pops: 3, pushes: true, disable_other_channels: true, }); @@ -60,8 +60,16 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { xor: BASIC_BINARY_OP, not: BASIC_UNARY_OP, byte: BASIC_BINARY_OP, - shl: BASIC_BINARY_OP, - shr: BASIC_BINARY_OP, + shl: Some(StackBehavior { + num_pops: 2, + pushes: true, + disable_other_channels: false, + }), + shr: Some(StackBehavior { + num_pops: 2, + pushes: true, + disable_other_channels: false, + }), keccak_general: None, // TODO prover_input: None, // TODO pop: None, // TODO diff --git a/evm/src/keccak_sponge/columns.rs b/evm/src/keccak_sponge/columns.rs index 440c59ab..d15e0e4b 100644 --- a/evm/src/keccak_sponge/columns.rs +++ b/evm/src/keccak_sponge/columns.rs @@ -17,10 +17,6 @@ pub(crate) struct KeccakSpongeColumnsView { /// not a padding byte; 0 otherwise. pub is_full_input_block: T, - /// 1 if this row represents the final block of a sponge, in which case some or all of the bytes - /// in the block will be padding bytes; 0 otherwise. - pub is_final_block: T, - // The base address at which we will read the input block. pub context: T, pub segment: T, diff --git a/evm/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs index 6c8f7f0d..4c91d6fd 100644 --- a/evm/src/keccak_sponge/keccak_sponge_stark.rs +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -128,7 +128,7 @@ pub(crate) fn ctl_looking_logic(i: usize) -> Vec> { pub(crate) fn ctl_looked_filter() -> Column { // The CPU table is only interested in our final-block rows, since those contain the final // sponge output. - Column::single(KECCAK_SPONGE_COL_MAP.is_final_block) + Column::sum(KECCAK_SPONGE_COL_MAP.is_final_input_len) } /// CTL filter for reading the `i`th byte of input from memory. @@ -143,12 +143,12 @@ pub(crate) fn ctl_looking_memory_filter(i: usize) -> Column { /// CTL filter for looking at XORs in the logic table. pub(crate) fn ctl_looking_logic_filter() -> Column { let cols = KECCAK_SPONGE_COL_MAP; - Column::sum([cols.is_full_input_block, cols.is_final_block]) + Column::sum(once(&cols.is_full_input_block).chain(&cols.is_final_input_len)) } pub(crate) fn ctl_looking_keccak_filter() -> Column { let cols = KECCAK_SPONGE_COL_MAP; - Column::sum([cols.is_full_input_block, cols.is_final_block]) + Column::sum(once(&cols.is_full_input_block).chain(&cols.is_final_input_len)) } /// Information about a Keccak sponge operation needed for witness generation. @@ -269,10 +269,7 @@ impl, const D: usize> KeccakSpongeStark { ) -> KeccakSpongeColumnsView { assert_eq!(already_absorbed_bytes + final_inputs.len(), op.input.len()); - let mut row = KeccakSpongeColumnsView { - is_final_block: F::ONE, - ..Default::default() - }; + let mut row = KeccakSpongeColumnsView::default(); for (block_byte, input_byte) in row.block_bytes.iter_mut().zip(final_inputs) { *block_byte = F::from_canonical_u8(*input_byte); @@ -372,7 +369,7 @@ impl, const D: usize> Stark for KeccakSpongeS let is_full_input_block = local_values.is_full_input_block; yield_constr.constraint(is_full_input_block * (is_full_input_block - P::ONES)); - let is_final_block = local_values.is_final_block; + let is_final_block: P = local_values.is_final_input_len.iter().copied().sum(); yield_constr.constraint(is_final_block * (is_final_block - P::ONES)); for &is_final_len in local_values.is_final_input_len.iter() { @@ -382,13 +379,6 @@ impl, const D: usize> Stark for KeccakSpongeS // Ensure that full-input block and final block flags are not set to 1 at the same time. yield_constr.constraint(is_final_block * is_full_input_block); - // Sum of is_final_input_len should equal is_final_block (which will be 0 or 1). - let is_final_input_len_sum: P = local_values.is_final_input_len.iter().copied().sum(); - yield_constr.constraint(is_final_input_len_sum - is_final_block); - - // If this is a full-input block, is_final_input_len should contain all 0s. - yield_constr.constraint(is_full_input_block * is_final_input_len_sum); - // If this is the first row, the original sponge state should be 0 and already_absorbed_bytes = 0. let already_absorbed_bytes = local_values.already_absorbed_bytes; yield_constr.constraint_first_row(already_absorbed_bytes); @@ -447,8 +437,9 @@ impl, const D: usize> Stark for KeccakSpongeS // A dummy row is always followed by another dummy row, so the prover can't put dummy rows "in between" to avoid the above checks. let is_dummy = P::ONES - is_full_input_block - is_final_block; + let next_is_final_block: P = next_values.is_final_input_len.iter().copied().sum(); yield_constr.constraint_transition( - is_dummy * (next_values.is_full_input_block + next_values.is_final_block), + is_dummy * (next_values.is_full_input_block + next_is_final_block), ); // If this is a final block, is_final_input_len implies `len - already_absorbed == i`. @@ -479,7 +470,7 @@ impl, const D: usize> Stark for KeccakSpongeS ); yield_constr.constraint(builder, constraint); - let is_final_block = local_values.is_final_block; + let is_final_block = builder.add_many_extension(local_values.is_final_input_len); let constraint = builder.mul_sub_extension(is_final_block, is_final_block, is_final_block); yield_constr.constraint(builder, constraint); @@ -492,21 +483,6 @@ impl, const D: usize> Stark for KeccakSpongeS let constraint = builder.mul_extension(is_final_block, is_full_input_block); yield_constr.constraint(builder, constraint); - // Sum of is_final_input_len should equal is_final_block (which will be 0 or 1). - let mut is_final_input_len_sum = builder.add_extension( - local_values.is_final_input_len[0], - local_values.is_final_input_len[1], - ); - for &input_len in local_values.is_final_input_len.iter().skip(2) { - is_final_input_len_sum = builder.add_extension(is_final_input_len_sum, input_len); - } - let constraint = builder.sub_extension(is_final_input_len_sum, is_final_block); - yield_constr.constraint(builder, constraint); - - // If this is a full-input block, is_final_input_len should contain all 0s. - let constraint = builder.mul_extension(is_full_input_block, is_final_input_len_sum); - yield_constr.constraint(builder, constraint); - // If this is the first row, the original sponge state should be 0 and already_absorbed_bytes = 0. let already_absorbed_bytes = local_values.already_absorbed_bytes; yield_constr.constraint_first_row(builder, already_absorbed_bytes); @@ -580,9 +556,9 @@ impl, const D: usize> Stark for KeccakSpongeS let tmp = builder.sub_extension(one, is_final_block); builder.sub_extension(tmp, is_full_input_block) }; + let next_is_final_block = builder.add_many_extension(next_values.is_final_input_len); let constraint = { - let tmp = - builder.add_extension(next_values.is_final_block, next_values.is_full_input_block); + let tmp = builder.add_extension(next_is_final_block, next_values.is_full_input_block); builder.mul_extension(is_dummy, tmp) }; yield_constr.constraint_transition(builder, constraint);