diff --git a/evm/src/cpu/bootstrap_kernel.rs b/evm/src/cpu/bootstrap_kernel.rs index 533589af..dd52f166 100644 --- a/evm/src/cpu/bootstrap_kernel.rs +++ b/evm/src/cpu/bootstrap_kernel.rs @@ -17,7 +17,6 @@ use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::keccak_util::keccakf_u32s; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; -use crate::memory::NUM_CHANNELS; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; /// The Keccak rate (1088 bits), measured in bytes. @@ -47,8 +46,7 @@ pub(crate) fn generate_bootstrap_kernel(state: &mut GenerationState // Write this chunk to memory, while simultaneously packing its bytes into a u32 word. let mut packed_bytes: u32 = 0; - for (addr, byte) in chunk { - let channel = addr % NUM_CHANNELS; + for (channel, (addr, byte)) in chunk.enumerate() { state.set_mem_cpu_current(channel, Segment::Code, addr, byte.into()); packed_bytes = (packed_bytes << 8) | byte as u32; diff --git a/evm/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs index 28087b9e..e0cb2952 100644 --- a/evm/src/cpu/columns/ops.rs +++ b/evm/src/cpu/columns/ops.rs @@ -34,6 +34,7 @@ pub struct OpsColumnsView { pub shr: T, pub sar: T, pub keccak256: T, + pub keccak_general: T, pub address: T, pub balance: T, pub origin: T, diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index c086aff5..6fcbae1c 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -22,7 +22,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; /// behavior. /// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to /// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid. -const OPCODES: [(u8, usize, bool, usize); 92] = [ +const OPCODES: [(u8, usize, bool, usize); 93] = [ // (start index of block, number of top bits to check (log2), kernel-only, flag column) (0x00, 0, false, COL_MAP.op.stop), (0x01, 0, false, COL_MAP.op.add), @@ -51,6 +51,7 @@ const OPCODES: [(u8, usize, bool, usize); 92] = [ (0x1c, 0, false, COL_MAP.op.shr), (0x1d, 0, false, COL_MAP.op.sar), (0x20, 0, false, COL_MAP.op.keccak256), + (0x21, 0, true, COL_MAP.op.keccak_general), (0x30, 0, false, COL_MAP.op.address), (0x31, 0, false, COL_MAP.op.balance), (0x32, 0, false, COL_MAP.op.origin), diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index a07c6eb9..ae728f09 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -49,6 +49,8 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/rlp/decode.asm"), include_str!("asm/rlp/read_to_memory.asm"), include_str!("asm/mpt/hash.asm"), + include_str!("asm/mpt/hash_trie_specific.asm"), + include_str!("asm/mpt/hex_prefix.asm"), include_str!("asm/mpt/load.asm"), include_str!("asm/mpt/read.asm"), include_str!("asm/mpt/storage_read.asm"), diff --git a/evm/src/cpu/kernel/asm/core/transfer.asm b/evm/src/cpu/kernel/asm/core/transfer.asm index 0ed48f4d..b12bc9de 100644 --- a/evm/src/cpu/kernel/asm/core/transfer.asm +++ b/evm/src/cpu/kernel/asm/core/transfer.asm @@ -1,11 +1,15 @@ // Transfers some ETH from one address to another. The amount is given in wei. // Pre stack: from, to, amount, retdest // Post stack: (empty) - global transfer_eth: // stack: from, to, amount, retdest - // TODO: Replace with actual implementation. - %pop3 + %stack (from, to, amount, retdest) + -> (from, amount, to, amount) + %deduct_eth + // TODO: Handle exception from %deduct_eth? + // stack: to, amount, retdest + %add_eth + // stack: retdest JUMP // Convenience macro to call transfer_eth and return where we left off. @@ -26,3 +30,26 @@ global transfer_eth: %transfer_eth %%after: %endmacro + +global deduct_eth: + // stack: addr, amount, retdest + %jump(mpt_read_state_trie) +deduct_eth_after_read: + PANIC // TODO + +// Convenience macro to call deduct_eth and return where we left off. +%macro deduct_eth + %stack (addr, amount) -> (addr, amount, %%after) + %jump(deduct_eth) +%%after: +%endmacro + +global add_eth: + PANIC // TODO + +// Convenience macro to call add_eth and return where we left off. +%macro add_eth + %stack (addr, amount) -> (addr, amount, %%after) + %jump(add_eth) +%%after: +%endmacro diff --git a/evm/src/cpu/kernel/asm/main.asm b/evm/src/cpu/kernel/asm/main.asm index d0402f17..e8c8e3e4 100644 --- a/evm/src/cpu/kernel/asm/main.asm +++ b/evm/src/cpu/kernel/asm/main.asm @@ -1,8 +1,24 @@ global main: + // First, load all MPT data from the prover. + PUSH txn_loop + %jump(load_all_mpts) + +hash_initial_tries: + %mpt_hash_state_trie %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) + %mpt_hash_txn_trie %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE) + %mpt_hash_receipt_trie %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_BEFORE) + +txn_loop: // If the prover has no more txns for us to process, halt. PROVER_INPUT(end_of_txns) - %jumpi(halt) + %jumpi(hash_final_tries) - // Call route_txn, returning to main to continue the loop. - PUSH main + // Call route_txn. When we return, continue the txn loop. + PUSH txn_loop %jump(route_txn) + +hash_final_tries: + %mpt_hash_state_trie %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER) + %mpt_hash_txn_trie %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_AFTER) + %mpt_hash_receipt_trie %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER) + %jump(halt) diff --git a/evm/src/cpu/kernel/asm/memory/core.asm b/evm/src/cpu/kernel/asm/memory/core.asm index 6c7d9b40..cfce6f6e 100644 --- a/evm/src/cpu/kernel/asm/memory/core.asm +++ b/evm/src/cpu/kernel/asm/memory/core.asm @@ -118,3 +118,10 @@ %mstore_kernel(@SEGMENT_CODE) // stack: (empty) %endmacro + +// Store a single byte to @SEGMENT_RLP_RAW. +%macro mstore_rlp + // stack: offset, value + %mstore_kernel(@SEGMENT_RLP_RAW) + // stack: (empty) +%endmacro diff --git a/evm/src/cpu/kernel/asm/memory/packing.asm b/evm/src/cpu/kernel/asm/memory/packing.asm index cb498464..4150567f 100644 --- a/evm/src/cpu/kernel/asm/memory/packing.asm +++ b/evm/src/cpu/kernel/asm/memory/packing.asm @@ -7,7 +7,7 @@ global mload_packing: // stack: value // Pre stack: context, segment, offset, value, len, retdest -// Post stack: (empty) +// Post stack: offset' global mstore_unpacking: // stack: context, segment, offset, value, len, retdest // We will enumerate i in (32 - len)..32. @@ -42,6 +42,6 @@ mstore_unpacking_loop: mstore_unpacking_finish: // stack: i, context, segment, offset, value, len, retdest - %pop6 - // stack: retdest + %pop3 + %stack (offset, value, len, retdest) -> (retdest, offset) JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index 41795d5d..053f357c 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -1,2 +1,82 @@ -global mpt_hash: - // TODO +// Computes the Merkle root of the given trie node. +// +// The encode_value function should take as input +// - the position withing @SEGMENT_RLP_RAW to write to, +// - the offset of a value within @SEGMENT_TRIE_DATA, and +// - a return address. +// It should serialize the value, write it to @SEGMENT_RLP_RAW starting at the +// given position, and return an updated position (the next unused offset). +%macro mpt_hash(encode_value) + // stack: node_ptr, retdest + DUP1 + %mload_trie_data + // stack: node_type, node_ptr, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %add_const(1) SWAP1 + // stack: node_type, node_payload_ptr, retdest + + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_hash_empty) + DUP1 %eq_const(@MPT_NODE_HASH) %jumpi(mpt_hash_hash) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(%%mpt_hash_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(%%mpt_hash_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(%%mpt_hash_leaf) + PANIC // Invalid node type? Shouldn't get here. + +%%mpt_hash_branch: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + PANIC // TODO + +%%mpt_hash_extension: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + PANIC // TODO + +%%mpt_hash_leaf: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + PUSH %%mpt_hash_leaf_after_hex_prefix // retdest + PUSH 1 // terminated + // stack: terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest + DUP3 %add_const(1) %mload_trie_data // Load the packed_nibbles field, which is at index 1. + // stack: packed_nibbles, terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest + DUP4 %mload_trie_data // Load the num_nibbles field, which is at index 0. + // stack: num_nibbles, packed_nibbles, terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest + PUSH 9 // We start at 9 to leave room to prepend the largest possible RLP list header. + // stack: rlp_start, num_nibbles, packed_nibbles, terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest + %jump(hex_prefix_rlp) +%%mpt_hash_leaf_after_hex_prefix: + // stack: rlp_pos, node_payload_ptr, retdest + SWAP1 + %add_const(2) // The value starts at index 2. + %stack (value_ptr, rlp_pos, retdest) + -> (rlp_pos, value_ptr, %%mpt_hash_leaf_after_encode_value, retdest) + %jump($encode_value) +%%mpt_hash_leaf_after_encode_value: + // stack: rlp_end_pos, retdest + %prepend_rlp_list_prefix + // stack: rlp_start_pos, rlp_len, retdest + PUSH @SEGMENT_RLP_RAW + PUSH 0 // kernel context + // stack: rlp_start_addr: 3, rlp_len, retdest + KECCAK_GENERAL + // stack: hash, retdest + SWAP1 + JUMP +%endmacro + +global mpt_hash_empty: + %stack (node_type, node_payload_ptr, retdest) -> (retdest, @EMPTY_NODE_HASH) + JUMP + +global mpt_hash_hash: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + %mload_trie_data + // stack: hash, retdest + SWAP1 + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm new file mode 100644 index 00000000..30ea730f --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm @@ -0,0 +1,80 @@ +// Hashing logic specific to a particular trie. + +global mpt_hash_state_trie: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + // stack: node_ptr, retdest + %mpt_hash(encode_account) + +%macro mpt_hash_state_trie + PUSH %%after + %jump(mpt_hash_state_trie) +%%after: +%endmacro + +global mpt_hash_txn_trie: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) + // stack: node_ptr, retdest + %mpt_hash(encode_txn) + +%macro mpt_hash_txn_trie + PUSH %%after + %jump(mpt_hash_txn_trie) +%%after: +%endmacro + +global mpt_hash_receipt_trie: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) + // stack: node_ptr, retdest + %mpt_hash(encode_receipt) + +%macro mpt_hash_receipt_trie + PUSH %%after + %jump(mpt_hash_receipt_trie) +%%after: +%endmacro + +encode_account: + // stack: rlp_pos, value_ptr, retdest + // First, we compute the length of the RLP data we're about to write. + // The nonce and balance fields are variable-length, so we need to load them + // to determine their contribution, while the other two fields are fixed + // 32-bytes integers. + DUP2 %mload_trie_data // nonce = value[0] + %scalar_rlp_len + // stack: nonce_rlp_len, rlp_pos, value_ptr, retdest + DUP3 %add_const(1) %mload_trie_data // balance = value[1] + %scalar_rlp_len + // stack: balance_rlp_lenm, nonce_rlp_len, rlp_pos, value_ptr, retdest + PUSH 66 // storage_root and code_hash fields each take 1 + 32 bytes + ADD ADD + // stack: payload_len, rlp_pos, value_ptr, retdest + SWAP1 + %encode_rlp_list_prefix + // stack: rlp_pos', value_ptr, retdest + DUP2 %mload_trie_data // nonce = value[0] + // stack: nonce, rlp_pos', value_ptr, retdest + SWAP1 %encode_rlp_scalar + // stack: rlp_pos'', value_ptr, retdest + DUP2 %add_const(1) %mload_trie_data // balance = value[1] + // stack: balance, rlp_pos'', value_ptr, retdest + SWAP1 %encode_rlp_scalar + // stack: rlp_pos''', value_ptr, retdest + DUP2 %add_const(2) %mload_trie_data // storage_root = value[2] + // stack: storage_root, rlp_pos''', value_ptr, retdest + SWAP1 %encode_rlp_256 + // stack: rlp_pos'''', value_ptr, retdest + SWAP1 %add_const(3) %mload_trie_data // code_hash = value[3] + // stack: code_hash, rlp_pos'''', retdest + SWAP1 %encode_rlp_256 + // stack: rlp_pos''''', retdest + SWAP1 + JUMP + +encode_txn: + PANIC // TODO + +encode_receipt: + PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm new file mode 100644 index 00000000..72ac18cc --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm @@ -0,0 +1,104 @@ +// Computes the RLP encoding of the hex-prefix encoding of the given nibble list +// and termination flag. Writes the result to @SEGMENT_RLP_RAW starting at the +// given position, and returns the updated position, i.e. a pointer to the next +// unused offset. +// +// Pre stack: rlp_start_pos, num_nibbles, packed_nibbles, terminated, retdest +// Post stack: rlp_end_pos + +global hex_prefix_rlp: + // stack: rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + // We will iterate backwards, from i = num_nibbles / 2 to i = 0, so that we + // can take nibbles from the least-significant end of packed_nibbles. + PUSH 2 DUP3 DIV // i = num_nibbles / 2 + // stack: i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + + // Compute the length of the hex-prefix string, in bytes: + // hp_len = num_nibbles / 2 + 1 = i + 1 + DUP1 %add_const(1) + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + + // Write the RLP header. + DUP1 %gt_const(55) %jumpi(rlp_header_large) + DUP1 %gt_const(1) %jumpi(rlp_header_medium) + + // The hex-prefix is a single byte. It must be <= 127, since its first + // nibble only has two bits. So this is the "small" RLP string case, where + // the byte is its own RLP encoding. + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + %jump(start_loop) + +rlp_header_medium: + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + DUP1 %add_const(0x80) // value = 0x80 + hp_len + DUP4 // offset = rlp_pos + %mstore_rlp + + // rlp_pos += 1 + SWAP2 %add_const(1) SWAP2 + + %jump(start_loop) + +rlp_header_large: + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + // In practice hex-prefix length will never exceed 256, so the length of the + // length will always be 1 byte in this case. + + PUSH 0xb8 // value = 0xb7 + len_of_len = 0xb8 + DUP4 // offset = rlp_pos + %mstore_rlp + + DUP1 // value = hp_len + DUP4 %add_const(1) // offset = rlp_pos + 1 + %mstore_rlp + + // rlp_pos += 2 + SWAP2 %add_const(2) SWAP2 + +start_loop: + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + SWAP1 + +loop: + // stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + // If i == 0, break to first_byte. + DUP1 ISZERO %jumpi(first_byte) + + // stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + DUP5 // packed_nibbles + %and_const(0xFF) + // stack: byte_i, i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + DUP4 // rlp_pos + DUP3 // i + ADD // We'll write to offset rlp_pos + i + %mstore_rlp + + // stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + %sub_const(1) + SWAP4 %shr_const(8) SWAP4 // packed_nibbles >>= 8 + %jump(loop) + +first_byte: + // stack: 0, hp_len, rlp_pos, num_nibbles, first_nibble_or_zero, terminated, retdest + POP + // stack: hp_len, rlp_pos, num_nibbles, first_nibble_or_zero, terminated, retdest + DUP2 ADD + // stack: rlp_end_pos, rlp_pos, num_nibbles, first_nibble_or_zero, terminated, retdest + SWAP4 + // stack: terminated, rlp_pos, num_nibbles, first_nibble_or_zero, rlp_end_pos, retdest + %mul_const(2) + // stack: terminated * 2, rlp_pos, num_nibbles, first_nibble_or_zero, rlp_end_pos, retdest + %stack (terminated_x2, rlp_pos, num_nibbles, first_nibble_or_zero) + -> (num_nibbles, terminated_x2, first_nibble_or_zero, rlp_pos) + // stack: num_nibbles, terminated * 2, first_nibble_or_zero, rlp_pos, rlp_end_pos, retdest + %mod_const(2) // parity + ADD + // stack: parity + terminated * 2, first_nibble_or_zero, rlp_pos, rlp_end_pos, retdest + %mul_const(16) + ADD + // stack: first_byte, rlp_pos, rlp_end_pos, retdest + SWAP1 + %mstore_rlp + // stack: rlp_end_pos, retdest + SWAP1 + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index 57c84ddb..2f1bd624 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -9,8 +9,7 @@ global load_all_mpts: PUSH 1 %set_trie_data_size - %load_mpt_and_return_root_ptr - %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) @@ -39,7 +38,6 @@ storage_trie_loop: // stack: i, num_storage_tries, retdest %jump(storage_trie_loop) storage_trie_loop_end: - // TODO: Hash tries and set @GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE, etc. // stack: i, num_storage_tries, retdest %pop2 // stack: retdest diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index 934b6bbf..aec0c776 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -1,3 +1,22 @@ +// Given an address, return a pointer to the associated account data, which +// consists of four words (nonce, balance, storage_root, code_hash), in the +// state trie. Returns 0 if the address is not found. +global mpt_read_state_trie: + // stack: addr, retdest + // The key is the hash of the address. Since KECCAK_GENERAL takes input from + // memory, we will write addr bytes to SEGMENT_KERNEL_GENERAL[0..20] first. + %stack (addr) -> (0, @SEGMENT_KERNEL_GENERAL, 0, addr, 20, mpt_read_state_trie_after_mstore) + %jump(mstore_unpacking) +mpt_read_state_trie_after_mstore: + // stack: retdest + %stack () -> (0, @SEGMENT_KERNEL_GENERAL, 0, 20) // context, segment, offset, len + KECCAK_GENERAL + // stack: key, retdest + PUSH 64 // num_nibbles + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) // node_ptr + // stack: node_ptr, num_nibbles, key, retdest + %jump(mpt_read) + // Read a value from a MPT. // // Arguments: @@ -6,7 +25,6 @@ // - the number of nibbles in the key (should start at 64) // // This function returns a pointer to the leaf, or 0 if the key is not found. - global mpt_read: // stack: node_ptr, num_nibbles, key, retdest DUP1 diff --git a/evm/src/cpu/kernel/asm/mpt/write.asm b/evm/src/cpu/kernel/asm/mpt/write.asm index be593a97..5b59d016 100644 --- a/evm/src/cpu/kernel/asm/mpt/write.asm +++ b/evm/src/cpu/kernel/asm/mpt/write.asm @@ -1,2 +1,3 @@ global mpt_write: + // stack: node_ptr, num_nibbles, key, retdest // TODO diff --git a/evm/src/cpu/kernel/asm/rlp/encode.asm b/evm/src/cpu/kernel/asm/rlp/encode.asm index 7e296f9d..fa67ace0 100644 --- a/evm/src/cpu/kernel/asm/rlp/encode.asm +++ b/evm/src/cpu/kernel/asm/rlp/encode.asm @@ -78,22 +78,129 @@ encode_rlp_fixed: SWAP1 %add_const(1) // increment pos // stack: pos, len, string, retdest - %stack (pos, len, string) -> (@SEGMENT_RLP_RAW, pos, string, len, encode_rlp_fixed_finish, pos, len) - GET_CONTEXT - // stack: context, segment, pos, string, len, encode_rlp_fixed, pos, retdest - %jump(mstore_unpacking) - + %stack (pos, len, string) -> (pos, string, len, encode_rlp_fixed_finish) + // stack: context, segment, pos, string, len, encode_rlp_fixed_finish, retdest + %jump(mstore_unpacking_rlp) encode_rlp_fixed_finish: - // stack: pos, len, retdest - ADD // stack: pos', retdest SWAP1 JUMP +// Pre stack: pos, payload_len, retdest +// Post stack: pos' +global encode_rlp_list_prefix: + // stack: pos, payload_len, retdest + DUP2 %gt_const(55) + %jumpi(encode_rlp_list_prefix_large) + // Small case: prefix is just 0xc0 + length. + // stack: pos, payload_len, retdest + SWAP1 + %add_const(0xc0) + // stack: prefix, pos, retdest + DUP2 + // stack: pos, prefix, pos, retdest + %mstore_rlp + // stack: pos, retdest + %add_const(1) + SWAP1 + JUMP +encode_rlp_list_prefix_large: + // Write 0xf7 + len_of_len. + // stack: pos, payload_len, retdest + DUP2 %num_bytes + // stack: len_of_len, pos, payload_len, retdest + DUP1 %add_const(0xf7) + // stack: first_byte, len_of_len, pos, payload_len, retdest + DUP3 // pos + %mstore_rlp + // stack: len_of_len, pos, payload_len, retdest + SWAP1 %add_const(1) + // stack: pos', len_of_len, payload_len, retdest + %stack (pos, len_of_len, payload_len, retdest) + -> (pos, payload_len, len_of_len, + encode_rlp_list_prefix_large_done_writing_len, + retdest) + %jump(mstore_unpacking_rlp) +encode_rlp_list_prefix_large_done_writing_len: + // stack: pos'', retdest + SWAP1 + JUMP + +%macro encode_rlp_list_prefix + %stack (pos, payload_len) -> (pos, payload_len, %%after) + %jump(encode_rlp_list_prefix) +%%after: +%endmacro + +// Given an RLP list payload which starts at position 9 and ends at the given +// position, prepend the appropriate RLP list prefix. Returns the updated start +// position, as well as the length of the RLP data (including the newly-added +// prefix). +// +// (We sometimes start list payloads at position 9 because 9 is the length of +// the longest possible RLP list prefix.) +// +// Pre stack: end_pos, retdest +// Post stack: start_pos, rlp_len +global prepend_rlp_list_prefix: + // stack: end_pos, retdest + // Since the list payload starts at position 9, payload_len = end_pos - 9. + PUSH 9 DUP2 SUB + // stack: payload_len, end_pos, retdest + DUP1 %gt_const(55) + %jumpi(prepend_rlp_list_prefix_big) + + // If we got here, we have a small list, so we prepend 0xc0 + len at position 8. + // stack: payload_len, end_pos, retdest + %add_const(0xc0) + // stack: prefix_byte, end_pos, retdest + PUSH 8 // offset + %mstore_rlp + // stack: end_pos, retdest + %sub_const(8) + // stack: rlp_len, retdest + PUSH 8 // start_pos + %stack (start_pos, rlp_len, retdest) -> (retdest, start_pos, rlp_len) + JUMP + +prepend_rlp_list_prefix_big: + // We have a large list, so we prepend 0xf7 + len_of_len at position + // 8 - len_of_len, followed by the length itself. + // stack: payload_len, end_pos, retdest + DUP1 %num_bytes + // stack: len_of_len, payload_len, end_pos, retdest + DUP1 + PUSH 8 + SUB + // stack: start_pos, len_of_len, payload_len, end_pos, retdest + DUP2 %add_const(0xf7) DUP2 %mstore_rlp // rlp[start_pos] = 0xf7 + len_of_len + DUP1 %add_const(1) // start_len_pos = start_pos + 1 + %stack (start_len_pos, start_pos, len_of_len, payload_len, end_pos, retdest) + -> (start_len_pos, payload_len, len_of_len, + prepend_rlp_list_prefix_big_done_writing_len, + start_pos, end_pos, retdest) + %jump(mstore_unpacking_rlp) +prepend_rlp_list_prefix_big_done_writing_len: + // stack: 9, start_pos, end_pos, retdest + %stack (_9, start_pos, end_pos) -> (end_pos, start_pos, start_pos) + // stack: end_pos, start_pos, start_pos, retdest + SUB + // stack: rlp_len, start_pos, retdest + %stack (rlp_len, start_pos, retdest) -> (retdest, start_pos, rlp_len) + JUMP + +// Convenience macro to call prepend_rlp_list_prefix and return where we left off. +%macro prepend_rlp_list_prefix + %stack (start_pos) -> (start_pos, %%after) + %jump(prepend_rlp_list_prefix) +%%after: +%endmacro + // Get the number of bytes required to represent the given scalar. // The scalar is assumed to be non-zero, as small scalars like zero should // have already been handled with the small-scalar encoding. -num_bytes: +// TODO: Should probably unroll the loop +global num_bytes: // stack: x, retdest PUSH 0 // i // stack: i, x, retdest @@ -125,3 +232,31 @@ num_bytes_finish: %jump(num_bytes) %%after: %endmacro + +// Given some scalar, compute the number of bytes used in its RLP encoding, +// including any length prefix. +%macro scalar_rlp_len + // stack: scalar + // Since the scalar fits in a word, we can't hit the large (>55 byte) + // case, so we just check for small vs medium. + DUP1 %gt_const(0x7f) + // stack: is_medium, scalar + %jumpi(%%medium) + // Small case; result is 1. + %stack (scalar) -> (1) +%%medium: + // stack: scalar + %num_bytes + // stack: scalar_bytes + %add_const(1) // Account for the length prefix. + // stack: rlp_len +%endmacro + +// Like mstore_unpacking, but specifically for the RLP segment. +// Pre stack: offset, value, len, retdest +// Post stack: offset' +mstore_unpacking_rlp: + // stack: offset, value, len, retdest + PUSH @SEGMENT_RLP_RAW + PUSH 0 // context + %jump(mstore_unpacking) diff --git a/evm/src/cpu/kernel/constants/mod.rs b/evm/src/cpu/kernel/constants/mod.rs index 0db347cf..2694b82a 100644 --- a/evm/src/cpu/kernel/constants/mod.rs +++ b/evm/src/cpu/kernel/constants/mod.rs @@ -18,6 +18,9 @@ pub fn evm_constants() -> HashMap { for (name, value) in EC_CONSTANTS { c.insert(name.into(), U256::from_big_endian(&value)); } + for (name, value) in HASH_CONSTANTS { + c.insert(name.into(), U256::from_big_endian(&value)); + } for (name, value) in GAS_CONSTANTS { c.insert(name.into(), U256::from(value)); } @@ -43,6 +46,14 @@ pub fn evm_constants() -> HashMap { c } +const HASH_CONSTANTS: [(&str, [u8; 32]); 1] = [ + // Hash of an empty node: keccak(rlp.encode(b'')).hex() + ( + "EMPTY_NODE_HASH", + hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + ), +]; + const EC_CONSTANTS: [(&str, [u8; 32]); 3] = [ ( "BN_BASE", diff --git a/evm/src/cpu/kernel/global_metadata.rs b/evm/src/cpu/kernel/global_metadata.rs index ddc3c839..f3f34e7a 100644 --- a/evm/src/cpu/kernel/global_metadata.rs +++ b/evm/src/cpu/kernel/global_metadata.rs @@ -24,13 +24,13 @@ pub(crate) enum GlobalMetadata { // The root digests of each Merkle trie before these transactions. StateTrieRootDigestBefore = 8, - TransactionsTrieRootDigestBefore = 9, - ReceiptsTrieRootDigestBefore = 10, + TransactionTrieRootDigestBefore = 9, + ReceiptTrieRootDigestBefore = 10, // The root digests of each Merkle trie after these transactions. StateTrieRootDigestAfter = 11, - TransactionsTrieRootDigestAfter = 12, - ReceiptsTrieRootDigestAfter = 13, + TransactionTrieRootDigestAfter = 12, + ReceiptTrieRootDigestAfter = 13, } impl GlobalMetadata { @@ -47,11 +47,11 @@ impl GlobalMetadata { Self::ReceiptTrieRoot, Self::NumStorageTries, Self::StateTrieRootDigestBefore, - Self::TransactionsTrieRootDigestBefore, - Self::ReceiptsTrieRootDigestBefore, + Self::TransactionTrieRootDigestBefore, + Self::ReceiptTrieRootDigestBefore, Self::StateTrieRootDigestAfter, - Self::TransactionsTrieRootDigestAfter, - Self::ReceiptsTrieRootDigestAfter, + Self::TransactionTrieRootDigestAfter, + Self::ReceiptTrieRootDigestAfter, ] } @@ -67,18 +67,18 @@ impl GlobalMetadata { GlobalMetadata::ReceiptTrieRoot => "GLOBAL_METADATA_RECEIPT_TRIE_ROOT", GlobalMetadata::NumStorageTries => "GLOBAL_METADATA_NUM_STORAGE_TRIES", GlobalMetadata::StateTrieRootDigestBefore => "GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE", - GlobalMetadata::TransactionsTrieRootDigestBefore => { - "GLOBAL_METADATA_TXNS_TRIE_DIGEST_BEFORE" + GlobalMetadata::TransactionTrieRootDigestBefore => { + "GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE" } - GlobalMetadata::ReceiptsTrieRootDigestBefore => { - "GLOBAL_METADATA_RECEIPTS_TRIE_DIGEST_BEFORE" + GlobalMetadata::ReceiptTrieRootDigestBefore => { + "GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_BEFORE" } GlobalMetadata::StateTrieRootDigestAfter => "GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER", - GlobalMetadata::TransactionsTrieRootDigestAfter => { - "GLOBAL_METADATA_TXNS_TRIE_DIGEST_AFTER" + GlobalMetadata::TransactionTrieRootDigestAfter => { + "GLOBAL_METADATA_TXN_TRIE_DIGEST_AFTER" } - GlobalMetadata::ReceiptsTrieRootDigestAfter => { - "GLOBAL_METADATA_RECEIPTS_TRIE_DIGEST_AFTER" + GlobalMetadata::ReceiptTrieRootDigestAfter => { + "GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER" } } } diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 8a898cb3..45211848 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -47,10 +47,21 @@ impl InterpreterMemory { impl InterpreterMemory { fn mload_general(&self, context: usize, segment: Segment, offset: usize) -> U256 { - self.context_memory[context].segments[segment as usize].get(offset) + let value = self.context_memory[context].segments[segment as usize].get(offset); + assert!( + value.bits() <= segment.bit_range(), + "Value read from memory exceeds expected range of {:?} segment", + segment + ); + value } fn mstore_general(&mut self, context: usize, segment: Segment, offset: usize, value: U256) { + assert!( + value.bits() <= segment.bit_range(), + "Value written to memory exceeds expected range of {:?} segment", + segment + ); self.context_memory[context].segments[segment as usize].set(offset, value) } } @@ -162,7 +173,7 @@ impl<'a> Interpreter<'a> { } pub(crate) fn get_rlp_memory(&self) -> Vec { - self.memory.context_memory[self.context].segments[Segment::RlpRaw as usize] + self.memory.context_memory[0].segments[Segment::RlpRaw as usize] .content .iter() .map(|x| x.as_u32() as u8) @@ -170,7 +181,7 @@ impl<'a> Interpreter<'a> { } pub(crate) fn set_rlp_memory(&mut self, rlp: Vec) { - self.memory.context_memory[self.context].segments[Segment::RlpRaw as usize].content = + self.memory.context_memory[0].segments[Segment::RlpRaw as usize].content = rlp.into_iter().map(U256::from).collect(); } @@ -229,6 +240,7 @@ impl<'a> Interpreter<'a> { 0x1c => self.run_shr(), // "SHR", 0x1d => todo!(), // "SAR", 0x20 => self.run_keccak256(), // "KECCAK256", + 0x21 => self.run_keccak_general(), // "KECCAK_GENERAL", 0x30 => todo!(), // "ADDRESS", 0x31 => todo!(), // "BALANCE", 0x32 => todo!(), // "ORIGIN", @@ -447,6 +459,18 @@ impl<'a> Interpreter<'a> { self.push(U256::from_big_endian(hash.as_bytes())); } + fn run_keccak_general(&mut self) { + let context = self.pop().as_usize(); + let segment = Segment::all()[self.pop().as_usize()]; + let offset = self.pop().as_usize(); + let size = self.pop().as_usize(); + let bytes = (offset..offset + size) + .map(|i| self.memory.mload_general(context, segment, i).byte(0)) + .collect::>(); + let hash = keccak(bytes); + self.push(U256::from_big_endian(hash.as_bytes())); + } + fn run_prover_input(&mut self) -> anyhow::Result<()> { let prover_input_fn = self .prover_inputs_map @@ -567,7 +591,6 @@ 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.bits() <= segment.bit_range()); self.push(value); } @@ -576,7 +599,6 @@ impl<'a> Interpreter<'a> { let segment = Segment::all()[self.pop().as_usize()]; let offset = self.pop().as_usize(); let value = self.pop(); - assert!(value.bits() <= segment.bit_range()); self.memory.mstore_general(context, segment, offset, value); } } diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index 69ee13fe..2325c53a 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, + "KECCAK_GENERAL" => 0x21, "ADDRESS" => 0x30, "BALANCE" => 0x31, "ORIGIN" => 0x32, diff --git a/evm/src/cpu/kernel/optimizer.rs b/evm/src/cpu/kernel/optimizer.rs index e23bf520..e2504203 100644 --- a/evm/src/cpu/kernel/optimizer.rs +++ b/evm/src/cpu/kernel/optimizer.rs @@ -80,9 +80,9 @@ fn no_op_jumps(code: &mut Vec) { replace_windows(code, |window| { if let [Push(Label(l)), StandardOp(jump), decl] = window && &jump == "JUMP" - && (decl == LocalLabelDeclaration(l.clone()) || decl == GlobalLabelDeclaration(l.clone())) + && (decl == LocalLabelDeclaration(l.clone()) || decl == GlobalLabelDeclaration(l)) { - Some(vec![LocalLabelDeclaration(l)]) + Some(vec![decl]) } else { None } diff --git a/evm/src/cpu/kernel/tests/mpt/hash.rs b/evm/src/cpu/kernel/tests/mpt/hash.rs new file mode 100644 index 00000000..5f212e3c --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/hash.rs @@ -0,0 +1,62 @@ +use anyhow::Result; +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use ethereum_types::{BigEndianHash, H256, U256}; +use hex_literal::hex; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; +use crate::generation::TrieInputs; + +#[test] +fn mpt_hash() -> Result<()> { + let nonce = U256::from(1111); + let balance = U256::from(2222); + let storage_root = U256::from(3333); + let code_hash = U256::from(4444); + + let account = &[nonce, balance, storage_root, code_hash]; + let account_rlp = rlp::encode_list(account); + + // TODO: Try this more "advanced" trie. + // let state_trie = state_trie_ext_to_account_leaf(account_rlp.to_vec()); + let state_trie = PartialTrie::Leaf { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + value: account_rlp.to_vec(), + }; + // TODO: It seems like calc_hash isn't giving the expected hash yet, so for now, I'm using a + // hardcoded hash obtained from py-evm. + // let state_trie_hash = state_trie.calc_hash(); + let state_trie_hash = + hex!("e38d6053838fe057c865ec0c74a8f0de21865d74fac222a2d3241fe57c9c3a0f").into(); + + let trie_inputs = TrieInputs { + state_trie, + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; + + let initial_stack = vec![0xdeadbeefu32.into()]; + let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack); + interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![]); + + // Now, execute mpt_hash_state_trie. + interpreter.offset = mpt_hash_state_trie; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.run()?; + + assert_eq!(interpreter.stack().len(), 1); + let hash = H256::from_uint(&interpreter.stack()[0]); + assert_eq!(hash, state_trie_hash); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs new file mode 100644 index 00000000..c13b8122 --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs @@ -0,0 +1,87 @@ +use anyhow::Result; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::interpreter::Interpreter; + +#[test] +fn hex_prefix_even_nonterminated() -> Result<()> { + let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; + + let retdest = 0xDEADBEEFu32.into(); + let terminated = 0.into(); + let packed_nibbles = 0xABCDEF.into(); + let num_nibbles = 6.into(); + let rlp_pos = 0.into(); + let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; + let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![5.into()]); + + assert_eq!( + interpreter.get_rlp_memory(), + vec![ + 0x80 + 4, // prefix + 0, // neither flag is set + 0xAB, + 0xCD, + 0xEF + ] + ); + + Ok(()) +} + +#[test] +fn hex_prefix_odd_terminated() -> Result<()> { + let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; + + let retdest = 0xDEADBEEFu32.into(); + let terminated = 1.into(); + let packed_nibbles = 0xABCDE.into(); + let num_nibbles = 5.into(); + let rlp_pos = 0.into(); + let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; + let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![4.into()]); + + assert_eq!( + interpreter.get_rlp_memory(), + vec![ + 0x80 + 3, // prefix + (2 + 1) * 16 + 0xA, + 0xBC, + 0xDE, + ] + ); + + Ok(()) +} + +#[test] +fn hex_prefix_odd_terminated_tiny() -> Result<()> { + let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; + + let retdest = 0xDEADBEEFu32.into(); + let terminated = 1.into(); + let packed_nibbles = 0xA.into(); + let num_nibbles = 1.into(); + let rlp_pos = 2.into(); + let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; + let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![3.into()]); + + assert_eq!( + interpreter.get_rlp_memory(), + vec![ + // Since rlp_pos = 2, we skipped over the first two bytes. + 0, + 0, + // No length prefix; this tiny string is its own RLP encoding. + (2 + 1) * 16 + 0xA, + ] + ); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs index 2d14c89a..8308962a 100644 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -1,5 +1,7 @@ use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +mod hash; +mod hex_prefix; mod load; mod read; diff --git a/evm/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs index e26ee4ab..e20aa0fe 100644 --- a/evm/src/cpu/kernel/tests/mpt/read.rs +++ b/evm/src/cpu/kernel/tests/mpt/read.rs @@ -42,10 +42,8 @@ fn mpt_read() -> Result<()> { interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); interpreter.run()?; - dbg!(interpreter.stack()); assert_eq!(interpreter.stack().len(), 1); let result_ptr = interpreter.stack()[0].as_usize(); - dbg!(result_ptr); let result = &interpreter.get_trie_data()[result_ptr..][..account.len()]; assert_eq!(result, account); diff --git a/evm/src/cpu/kernel/tests/packing.rs b/evm/src/cpu/kernel/tests/packing.rs index 44b9242d..dcfdd69b 100644 --- a/evm/src/cpu/kernel/tests/packing.rs +++ b/evm/src/cpu/kernel/tests/packing.rs @@ -19,7 +19,7 @@ fn test_mstore_unpacking() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(mstore_unpacking, initial_stack); interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); + assert_eq!(interpreter.stack(), vec![4.into()]); assert_eq!( &interpreter.get_txn_data(), &[0xAB.into(), 0xCD.into(), 0x12.into(), 0x34.into()] diff --git a/evm/src/cpu/kernel/tests/rlp.rs b/evm/src/cpu/kernel/tests/rlp/decode.rs similarity index 59% rename from evm/src/cpu/kernel/tests/rlp.rs rename to evm/src/cpu/kernel/tests/rlp/decode.rs index 37949e13..a1ca3609 100644 --- a/evm/src/cpu/kernel/tests/rlp.rs +++ b/evm/src/cpu/kernel/tests/rlp/decode.rs @@ -3,84 +3,6 @@ use anyhow::Result; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::interpreter::Interpreter; -#[test] -fn test_encode_rlp_scalar_small() -> Result<()> { - let encode_rlp_scalar = KERNEL.global_labels["encode_rlp_scalar"]; - - let retdest = 0xDEADBEEFu32.into(); - let scalar = 42.into(); - let pos = 2.into(); - let initial_stack = vec![retdest, scalar, pos]; - let mut interpreter = Interpreter::new_with_kernel(encode_rlp_scalar, initial_stack); - - interpreter.run()?; - let expected_stack = vec![3.into()]; // pos' = pos + rlp_len = 2 + 1 - let expected_rlp = vec![0, 0, 42]; - assert_eq!(interpreter.stack(), expected_stack); - assert_eq!(interpreter.get_rlp_memory(), expected_rlp); - - Ok(()) -} - -#[test] -fn test_encode_rlp_scalar_medium() -> Result<()> { - let encode_rlp_scalar = KERNEL.global_labels["encode_rlp_scalar"]; - - let retdest = 0xDEADBEEFu32.into(); - let scalar = 0x12345.into(); - let pos = 2.into(); - let initial_stack = vec![retdest, scalar, pos]; - let mut interpreter = Interpreter::new_with_kernel(encode_rlp_scalar, initial_stack); - - interpreter.run()?; - let expected_stack = vec![6.into()]; // pos' = pos + rlp_len = 2 + 4 - let expected_rlp = vec![0, 0, 0x80 + 3, 0x01, 0x23, 0x45]; - assert_eq!(interpreter.stack(), expected_stack); - assert_eq!(interpreter.get_rlp_memory(), expected_rlp); - - Ok(()) -} - -#[test] -fn test_encode_rlp_160() -> Result<()> { - let encode_rlp_160 = KERNEL.global_labels["encode_rlp_160"]; - - let retdest = 0xDEADBEEFu32.into(); - let string = 0x12345.into(); - let pos = 0.into(); - let initial_stack = vec![retdest, string, pos]; - let mut interpreter = Interpreter::new_with_kernel(encode_rlp_160, initial_stack); - - interpreter.run()?; - let expected_stack = vec![(1 + 20).into()]; // pos' - #[rustfmt::skip] - let expected_rlp = vec![0x80 + 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x23, 0x45]; - assert_eq!(interpreter.stack(), expected_stack); - assert_eq!(interpreter.get_rlp_memory(), expected_rlp); - - Ok(()) -} - -#[test] -fn test_encode_rlp_256() -> Result<()> { - let encode_rlp_256 = KERNEL.global_labels["encode_rlp_256"]; - - let retdest = 0xDEADBEEFu32.into(); - let string = 0x12345.into(); - let pos = 0.into(); - let initial_stack = vec![retdest, string, pos]; - let mut interpreter = Interpreter::new_with_kernel(encode_rlp_256, initial_stack); - - interpreter.run()?; - let expected_stack = vec![(1 + 32).into()]; // pos' - #[rustfmt::skip] - let expected_rlp = vec![0x80 + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x23, 0x45]; - assert_eq!(interpreter.stack(), expected_stack); - assert_eq!(interpreter.get_rlp_memory(), expected_rlp); - - Ok(()) -} - #[test] fn test_decode_rlp_string_len_short() -> Result<()> { let decode_rlp_string_len = KERNEL.global_labels["decode_rlp_string_len"]; diff --git a/evm/src/cpu/kernel/tests/rlp/encode.rs b/evm/src/cpu/kernel/tests/rlp/encode.rs new file mode 100644 index 00000000..4e04b248 --- /dev/null +++ b/evm/src/cpu/kernel/tests/rlp/encode.rs @@ -0,0 +1,155 @@ +use anyhow::Result; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::interpreter::Interpreter; + +#[test] +fn test_encode_rlp_scalar_small() -> Result<()> { + let encode_rlp_scalar = KERNEL.global_labels["encode_rlp_scalar"]; + + let retdest = 0xDEADBEEFu32.into(); + let scalar = 42.into(); + let pos = 2.into(); + let initial_stack = vec![retdest, scalar, pos]; + let mut interpreter = Interpreter::new_with_kernel(encode_rlp_scalar, initial_stack); + + interpreter.run()?; + let expected_stack = vec![3.into()]; // pos' = pos + rlp_len = 2 + 1 + let expected_rlp = vec![0, 0, 42]; + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_encode_rlp_scalar_medium() -> Result<()> { + let encode_rlp_scalar = KERNEL.global_labels["encode_rlp_scalar"]; + + let retdest = 0xDEADBEEFu32.into(); + let scalar = 0x12345.into(); + let pos = 2.into(); + let initial_stack = vec![retdest, scalar, pos]; + let mut interpreter = Interpreter::new_with_kernel(encode_rlp_scalar, initial_stack); + + interpreter.run()?; + let expected_stack = vec![6.into()]; // pos' = pos + rlp_len = 2 + 4 + let expected_rlp = vec![0, 0, 0x80 + 3, 0x01, 0x23, 0x45]; + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_encode_rlp_160() -> Result<()> { + let encode_rlp_160 = KERNEL.global_labels["encode_rlp_160"]; + + let retdest = 0xDEADBEEFu32.into(); + let string = 0x12345.into(); + let pos = 0.into(); + let initial_stack = vec![retdest, string, pos]; + let mut interpreter = Interpreter::new_with_kernel(encode_rlp_160, initial_stack); + + interpreter.run()?; + let expected_stack = vec![(1 + 20).into()]; // pos' + #[rustfmt::skip] + let expected_rlp = vec![0x80 + 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x23, 0x45]; + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_encode_rlp_256() -> Result<()> { + let encode_rlp_256 = KERNEL.global_labels["encode_rlp_256"]; + + let retdest = 0xDEADBEEFu32.into(); + let string = 0x12345.into(); + let pos = 0.into(); + let initial_stack = vec![retdest, string, pos]; + let mut interpreter = Interpreter::new_with_kernel(encode_rlp_256, initial_stack); + + interpreter.run()?; + let expected_stack = vec![(1 + 32).into()]; // pos' + #[rustfmt::skip] + let expected_rlp = vec![0x80 + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x23, 0x45]; + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_prepend_rlp_list_prefix_small() -> Result<()> { + let prepend_rlp_list_prefix = KERNEL.global_labels["prepend_rlp_list_prefix"]; + + let retdest = 0xDEADBEEFu32.into(); + let end_pos = (9 + 5).into(); + let initial_stack = vec![retdest, end_pos]; + let mut interpreter = Interpreter::new_with_kernel(prepend_rlp_list_prefix, initial_stack); + interpreter.set_rlp_memory(vec![ + // Nine 0s to leave room for the longest possible RLP list prefix. + 0, 0, 0, 0, 0, 0, 0, 0, 0, + // The actual RLP list payload, consisting of 5 tiny strings. + 1, 2, 3, 4, 5, + ]); + + interpreter.run()?; + + let expected_rlp_len = 6.into(); + let expected_start_pos = 8.into(); + let expected_stack = vec![expected_rlp_len, expected_start_pos]; + let expected_rlp = vec![0, 0, 0, 0, 0, 0, 0, 0, 0xc0 + 5, 1, 2, 3, 4, 5]; + + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_prepend_rlp_list_prefix_large() -> Result<()> { + let prepend_rlp_list_prefix = KERNEL.global_labels["prepend_rlp_list_prefix"]; + + let retdest = 0xDEADBEEFu32.into(); + let end_pos = (9 + 60).into(); + let initial_stack = vec![retdest, end_pos]; + let mut interpreter = Interpreter::new_with_kernel(prepend_rlp_list_prefix, initial_stack); + + #[rustfmt::skip] + interpreter.set_rlp_memory(vec![ + // Nine 0s to leave room for the longest possible RLP list prefix. + 0, 0, 0, 0, 0, 0, 0, 0, 0, + // The actual RLP list payload, consisting of 60 tiny strings. + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + ]); + + interpreter.run()?; + + let expected_rlp_len = 62.into(); + let expected_start_pos = 7.into(); + let expected_stack = vec![expected_rlp_len, expected_start_pos]; + + #[rustfmt::skip] + let expected_rlp = vec![ + 0, 0, 0, 0, 0, 0, 0, 0xf7 + 1, 60, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + ]; + + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/rlp/mod.rs b/evm/src/cpu/kernel/tests/rlp/mod.rs new file mode 100644 index 00000000..bc9bde59 --- /dev/null +++ b/evm/src/cpu/kernel/tests/rlp/mod.rs @@ -0,0 +1,2 @@ +mod decode; +mod encode; diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index d478571a..3186b5ae 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -62,6 +62,7 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { shr: BASIC_BINARY_OP, sar: BASIC_BINARY_OP, keccak256: None, // TODO + keccak_general: None, // TODO address: None, // TODO balance: None, // TODO origin: None, // TODO diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index a24f44d5..11fbd879 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -89,18 +89,16 @@ pub(crate) fn generate_traces, const D: usize>( let trie_roots_before = TrieRoots { state_root: H256::from_uint(&read_metadata(GlobalMetadata::StateTrieRootDigestBefore)), transactions_root: H256::from_uint(&read_metadata( - GlobalMetadata::TransactionsTrieRootDigestBefore, - )), - receipts_root: H256::from_uint(&read_metadata( - GlobalMetadata::ReceiptsTrieRootDigestBefore, + GlobalMetadata::TransactionTrieRootDigestBefore, )), + receipts_root: H256::from_uint(&read_metadata(GlobalMetadata::ReceiptTrieRootDigestBefore)), }; let trie_roots_after = TrieRoots { state_root: H256::from_uint(&read_metadata(GlobalMetadata::StateTrieRootDigestAfter)), transactions_root: H256::from_uint(&read_metadata( - GlobalMetadata::TransactionsTrieRootDigestAfter, + GlobalMetadata::TransactionTrieRootDigestAfter, )), - receipts_root: H256::from_uint(&read_metadata(GlobalMetadata::ReceiptsTrieRootDigestAfter)), + receipts_root: H256::from_uint(&read_metadata(GlobalMetadata::ReceiptTrieRootDigestAfter)), }; let GenerationState { diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs new file mode 100644 index 00000000..6e16fa47 --- /dev/null +++ b/evm/tests/empty_txn_list.rs @@ -0,0 +1,81 @@ +use std::collections::HashMap; + +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::plonk::config::PoseidonGoldilocksConfig; +use plonky2::util::timing::TimingTree; +use plonky2_evm::all_stark::AllStark; +use plonky2_evm::config::StarkConfig; +use plonky2_evm::generation::{GenerationInputs, TrieInputs}; +use plonky2_evm::proof::BlockMetadata; +use plonky2_evm::prover::prove; +use plonky2_evm::verifier::verify_proof; + +type F = GoldilocksField; +const D: usize = 2; +type C = PoseidonGoldilocksConfig; + +/// Execute the empty list of transactions, i.e. a no-op. +#[test] +#[ignore] // TODO: Won't work until witness generation logic is finished. +fn test_empty_txn_list() -> anyhow::Result<()> { + let all_stark = AllStark::::default(); + let config = StarkConfig::standard_fast_config(); + + let block_metadata = BlockMetadata::default(); + + let state_trie = PartialTrie::Leaf { + nibbles: Nibbles { + count: 5, + packed: 0xABCDE.into(), + }, + value: vec![1, 2, 3], + }; + let transactions_trie = PartialTrie::Empty; + let receipts_trie = PartialTrie::Empty; + let storage_tries = vec![]; + + let state_trie_root = state_trie.calc_hash(); + let txns_trie_root = transactions_trie.calc_hash(); + let receipts_trie_root = receipts_trie.calc_hash(); + + let inputs = GenerationInputs { + signed_txns: vec![], + tries: TrieInputs { + state_trie, + transactions_trie, + receipts_trie, + storage_tries, + }, + contract_code: HashMap::new(), + block_metadata, + }; + + let proof = prove::(&all_stark, &config, inputs, &mut TimingTree::default())?; + assert_eq!( + proof.public_values.trie_roots_before.state_root, + state_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_after.state_root, + state_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_before.transactions_root, + txns_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_after.transactions_root, + txns_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_before.receipts_root, + receipts_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_after.receipts_root, + receipts_trie_root + ); + + verify_proof(all_stark, proof, &config) +}