diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index e0a64314..8fe49739 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/memory/metadata.asm"), include_str!("asm/memory/packing.asm"), include_str!("asm/memory/txn_fields.asm"), + include_str!("asm/mpt/accounts.asm"), include_str!("asm/mpt/delete/delete.asm"), include_str!("asm/mpt/hash/hash.asm"), include_str!("asm/mpt/hash/hash_trie_specific.asm"), @@ -50,8 +51,8 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/mpt/load/load.asm"), include_str!("asm/mpt/load/load_trie_specific.asm"), include_str!("asm/mpt/read.asm"), - include_str!("asm/mpt/storage_read.asm"), - include_str!("asm/mpt/storage_write.asm"), + include_str!("asm/mpt/storage/storage_read.asm"), + include_str!("asm/mpt/storage/storage_write.asm"), include_str!("asm/mpt/util.asm"), include_str!("asm/ripemd/box.asm"), include_str!("asm/ripemd/compression.asm"), @@ -77,6 +78,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/transactions/type_2.asm"), include_str!("asm/util/assertions.asm"), include_str!("asm/util/basic_macros.asm"), + include_str!("asm/util/keccak.asm"), ]; let parsed_files = files.iter().map(|f| parse(f)).collect_vec(); diff --git a/evm/src/cpu/kernel/asm/mpt/accounts.asm b/evm/src/cpu/kernel/asm/mpt/accounts.asm new file mode 100644 index 00000000..0e49da98 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/accounts.asm @@ -0,0 +1,53 @@ +// Return a pointer to the current account's data in the state trie. +%macro current_account_data + ADDRESS %mpt_read_state_trie + // stack: account_ptr + // account_ptr should be non-null as long as the prover provided the proper + // Merkle data. But a bad prover may not have, and we don't want return a + // null pointer for security reasons. + DUP1 ISZERO %jumpi(panic) + // stack: account_ptr +%endmacro + +// Returns a pointer to the root of the storage trie associated with the current account. +%macro current_storage_trie + // stack: (empty) + %current_account_data + // stack: account_ptr + %add_const(2) + // stack: storage_root_ptr_ptr + %mload_trie_data + // stack: storage_root_ptr +%endmacro + +global make_default_account: + PANIC // TODO + +// Create a copy of the given account. The copy can then safely be mutated as +// needed, while leaving the original account data untouched. +// +// This writes the new account's data to MPT data, but does not register the new +// account in the state trie. +// +// Pre stack: old_account_ptr, retdest +// Post stack: new_account_ptr +global make_account_copy: + // stack: old_account_ptr, retdest + %get_trie_data_size // pointer to new account we're about to create + // stack: new_account_ptr, old_account_ptr, retdest + + DUP2 %mload_trie_data %append_to_trie_data + DUP2 %add_const(1) %mload_trie_data %append_to_trie_data + DUP2 %add_const(3) %mload_trie_data %append_to_trie_data + SWAP1 %add_const(4) %mload_trie_data %append_to_trie_data + + // stack: new_account_ptr, retdest + SWAP1 + JUMP + +// Convenience macro to call make_account_copy and return where we left off. +%macro make_account_copy + %stack (old_account_ptr) -> (old_account_ptr, %%after) + %jump(make_account_copy) +%%after: +%endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm index 4f9b58b4..39253b9f 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm @@ -95,4 +95,4 @@ encode_receipt: PANIC // TODO encode_storage_value: - PANIC // TODO + PANIC // TODO: RLP encode as variable-len scalar? diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index d375bedc..08aa02c3 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -3,26 +3,26 @@ // state trie. Returns null 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 + %addr_to_state_key // 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) +// Convenience macro to call mpt_read_state_trie and return where we left off. +%macro mpt_read_state_trie + %stack (addr) -> (addr, %%after) + %jump(mpt_read_state_trie) +%%after: +%endmacro + // Read a value from a MPT. // // Arguments: // - the virtual address of the trie to search in -// - the key, as a U256 // - the number of nibbles in the key (should start at 64) +// - the key, as a U256 // // This function returns a pointer to the value, or 0 if the key is not found. global mpt_read: diff --git a/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm new file mode 100644 index 00000000..d9f74714 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm @@ -0,0 +1,28 @@ +// Read a word from the current account's storage trie. +// +// Pre stack: slot, retdest +// Post stack: value + +global storage_read: + // stack: slot, retdest + %stack (slot) -> (slot, after_storage_read) + %slot_to_storage_key + // stack: storage_key, after_storage_read, retdest + PUSH 64 // storage_key has 64 nibbles + %current_storage_trie + // stack: storage_root_ptr, 64, storage_key, after_storage_read, retdest + %jump(mpt_read) + +after_storage_read: + // stack: value_ptr, retdest + DUP1 %jumpi(storage_key_exists) + + // Storage key not found; return default value of 0. + %stack (value_ptr, retdest) -> (retdest, 0) + JUMP + +storage_key_exists: + // stack: value_ptr, retdest + %mload_trie_data // TODO: If we end up not using value pointers in storage tries, remove this. + SWAP1 + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm new file mode 100644 index 00000000..6afe0a64 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm @@ -0,0 +1,33 @@ +// Write a word to the current account's storage trie. +// +// Pre stack: slot, value, retdest +// Post stack: (empty) + +global storage_write: + // stack: slot, value, retdest + // TODO: If value = 0, delete the key instead of inserting 0? + // TODO: Do we need to write value to MPT data and insert value_ptr? Currently some logic assumes all values are pointers, but could be relaxed so a value is any single word. + %stack (slot, value) -> (slot, value, after_storage_insert) + %slot_to_storage_key + // stack: storage_key, value, after_storage_write, retdest + PUSH 64 // storage_key has 64 nibbles + %current_storage_trie + // stack: storage_root_ptr, 64, storage_key, value, after_storage_insert, retdest + %jump(mpt_insert) + +after_storage_insert: + // stack: new_storage_root_ptr, retdest + %current_account_data + // stack: old_account_ptr, new_storage_root_ptr, retdest + %make_account_copy + // stack: new_account_ptr, new_storage_root_ptr, retdest + + // Update the copied account with our new storage root pointer. + %stack (new_account_ptr, new_storage_root_ptr) -> (new_account_ptr, new_storage_root_ptr, new_account_ptr) + %add_const(2) + // stack: new_account_storage_root_ptr_ptr, new_storage_root_ptr, new_account_ptr, retdest + %mstore_trie_data + // stack: new_account_ptr, retdest + + SWAP1 + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/storage_read.asm b/evm/src/cpu/kernel/asm/mpt/storage_read.asm deleted file mode 100644 index 04fea17a..00000000 --- a/evm/src/cpu/kernel/asm/mpt/storage_read.asm +++ /dev/null @@ -1,2 +0,0 @@ -global storage_read: - // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/storage_write.asm b/evm/src/cpu/kernel/asm/mpt/storage_write.asm deleted file mode 100644 index 940fb548..00000000 --- a/evm/src/cpu/kernel/asm/mpt/storage_write.asm +++ /dev/null @@ -1,2 +0,0 @@ -global storage_write: - // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm index 0faa72f4..0f7689e1 100644 --- a/evm/src/cpu/kernel/asm/mpt/util.asm +++ b/evm/src/cpu/kernel/asm/mpt/util.asm @@ -165,3 +165,14 @@ SWAP4 %div_const(4) SWAP4 // bits_2 -> len_2 (in nibbles) // stack: len_common, key_common, len_1, key_1, len_2, key_2 %endmacro + +// Computes state_key = Keccak256(addr). Clobbers @SEGMENT_KERNEL_GENERAL. +%macro addr_to_state_key + %keccak256_word(20) +%endmacro + +// Given a storage slot (a 256-bit integer), computes storage_key = Keccak256(slot). +// Clobbers @SEGMENT_KERNEL_GENERAL. +%macro slot_to_storage_key + %keccak256_word(32) +%endmacro diff --git a/evm/src/cpu/kernel/asm/util/keccak.asm b/evm/src/cpu/kernel/asm/util/keccak.asm new file mode 100644 index 00000000..5c05a2d4 --- /dev/null +++ b/evm/src/cpu/kernel/asm/util/keccak.asm @@ -0,0 +1,14 @@ +// Computes Keccak256(input_word). Clobbers @SEGMENT_KERNEL_GENERAL. +// +// Pre stack: input_word +// Post stack: hash +%macro keccak256_word(num_bytes) + // Since KECCAK_GENERAL takes its input from memory, we will first write + // input_word's bytes to @SEGMENT_KERNEL_GENERAL[0..$num_bytes]. + %stack (word) -> (0, @SEGMENT_KERNEL_GENERAL, 0, word, $num_bytes, %%after_mstore) + %jump(mstore_unpacking) +%%after_mstore: + // stack: (empty) + %stack () -> (0, @SEGMENT_KERNEL_GENERAL, 0, $num_bytes) // context, segment, offset, len + KECCAK_GENERAL +%endmacro diff --git a/evm/src/generation/mpt.rs b/evm/src/generation/mpt.rs index 8ceb195a..4107b978 100644 --- a/evm/src/generation/mpt.rs +++ b/evm/src/generation/mpt.rs @@ -68,11 +68,10 @@ pub(crate) fn mpt_prover_inputs( PartialTrie::Hash(h) => prover_inputs.push(U256::from_big_endian(h.as_bytes())), PartialTrie::Branch { children, value } => { if value.is_empty() { - // There's no value, so value_len = 0. - prover_inputs.push(U256::zero()); + prover_inputs.push(U256::zero()); // value_present = 0 } else { let parsed_value = parse_value(value); - prover_inputs.push(parsed_value.len().into()); + prover_inputs.push(U256::one()); // value_present = 1 prover_inputs.extend(parsed_value); } for child in children { @@ -107,8 +106,7 @@ pub(crate) fn mpt_prover_inputs_state_trie( PartialTrie::Hash(h) => prover_inputs.push(U256::from_big_endian(h.as_bytes())), PartialTrie::Branch { children, value } => { assert!(value.is_empty(), "State trie should not have branch values"); - // There's no value, so value_len = 0. - prover_inputs.push(U256::zero()); + prover_inputs.push(U256::zero()); // value_present = 0 for (i, child) in children.iter().enumerate() { let extended_key = key.merge(&Nibbles {