From e6b5e3656f6f851587e0906521996f94616d94d4 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 7 Oct 2022 12:03:37 -0700 Subject: [PATCH 01/21] Some more uses of %increment, %decrement --- evm/src/cpu/kernel/asm/core/intrinsic_gas.asm | 2 +- evm/src/cpu/kernel/asm/core/util.asm | 2 +- evm/src/cpu/kernel/asm/memory/core.asm | 6 +++--- evm/src/cpu/kernel/asm/memory/memcpy.asm | 6 +++--- evm/src/cpu/kernel/asm/memory/packing.asm | 4 ++-- evm/src/cpu/kernel/asm/mpt/hash.asm | 6 +++--- evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm | 4 ++-- evm/src/cpu/kernel/asm/mpt/hex_prefix.asm | 8 ++++---- evm/src/cpu/kernel/asm/mpt/load.asm | 4 ++-- evm/src/cpu/kernel/asm/mpt/read.asm | 6 +++--- evm/src/cpu/kernel/asm/mpt/util.asm | 4 ++-- evm/src/cpu/kernel/asm/ripemd/memory.asm | 14 +++++++------- evm/src/cpu/kernel/asm/rlp/decode.asm | 8 ++++---- evm/src/cpu/kernel/asm/rlp/encode.asm | 12 ++++++------ evm/src/cpu/kernel/asm/rlp/read_to_memory.asm | 2 +- 15 files changed, 44 insertions(+), 44 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm b/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm index 931a6a7b..5891807c 100644 --- a/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm +++ b/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm @@ -22,7 +22,7 @@ count_zeros_loop: // stack: zeros', i, retdest SWAP1 // stack: i, zeros', retdest - %add_const(1) + %increment // stack: i', zeros', retdest %jump(count_zeros_loop) diff --git a/evm/src/cpu/kernel/asm/core/util.asm b/evm/src/cpu/kernel/asm/core/util.asm index 4ceaec3b..dfacf1a2 100644 --- a/evm/src/cpu/kernel/asm/core/util.asm +++ b/evm/src/cpu/kernel/asm/core/util.asm @@ -14,7 +14,7 @@ %macro next_context_id // stack: (empty) %mload_global_metadata(@GLOBAL_METADATA_LARGEST_CONTEXT) - %add_const(1) + %increment // stack: new_ctx DUP1 %mstore_global_metadata(@GLOBAL_METADATA_LARGEST_CONTEXT) diff --git a/evm/src/cpu/kernel/asm/memory/core.asm b/evm/src/cpu/kernel/asm/memory/core.asm index c2c19811..f4bcf1f1 100644 --- a/evm/src/cpu/kernel/asm/memory/core.asm +++ b/evm/src/cpu/kernel/asm/memory/core.asm @@ -64,7 +64,7 @@ %shl_const(8) // stack: c_3 << 8, offset DUP2 - %add_const(1) + %increment %mload_kernel($segment) OR // stack: (c_3 << 8) | c_2, offset @@ -91,7 +91,7 @@ %mload_kernel($segment) // stack: c0 , offset DUP2 - %add_const(1) + %increment %mload_kernel($segment) %shl_const(8) OR @@ -208,7 +208,7 @@ // stack: c_2, c_1, c_0, offset DUP4 // stack: offset, c_2, c_1, c_0, offset - %add_const(1) + %increment %mstore_kernel($segment) // stack: c_1, c_0, offset DUP3 diff --git a/evm/src/cpu/kernel/asm/memory/memcpy.asm b/evm/src/cpu/kernel/asm/memory/memcpy.asm index 3feca35d..dd0569e7 100644 --- a/evm/src/cpu/kernel/asm/memory/memcpy.asm +++ b/evm/src/cpu/kernel/asm/memory/memcpy.asm @@ -28,15 +28,15 @@ global memcpy: // Increment dst_addr. SWAP2 - %add_const(1) + %increment SWAP2 // Increment src_addr. SWAP5 - %add_const(1) + %increment SWAP5 // Decrement count. SWAP6 - %sub_const(1) + %decrement SWAP6 // Continue the loop. diff --git a/evm/src/cpu/kernel/asm/memory/packing.asm b/evm/src/cpu/kernel/asm/memory/packing.asm index c8b4c468..f12c7b17 100644 --- a/evm/src/cpu/kernel/asm/memory/packing.asm +++ b/evm/src/cpu/kernel/asm/memory/packing.asm @@ -71,9 +71,9 @@ mstore_unpacking_loop: // stack: i, context, segment, offset, value, len, retdest // Increment offset. - SWAP3 %add_const(1) SWAP3 + SWAP3 %increment SWAP3 // Increment i. - %add_const(1) + %increment %jump(mstore_unpacking_loop) diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index abd436fe..ef0158e0 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -82,7 +82,7 @@ global encode_node: DUP1 %mload_trie_data // stack: node_type, node_ptr, encode_value, retdest // Increment node_ptr, so it points to the node payload instead of its type. - SWAP1 %add_const(1) SWAP1 + SWAP1 %increment SWAP1 // stack: node_type, node_payload_ptr, encode_value, retdest DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(encode_node_empty) @@ -214,7 +214,7 @@ encode_node_extension_after_encode_child: PUSH encode_node_extension_after_hex_prefix // retdest PUSH 0 // terminated // stack: terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest - DUP5 %add_const(1) %mload_trie_data // Load the packed_nibbles field, which is at index 1. + DUP5 %increment %mload_trie_data // Load the packed_nibbles field, which is at index 1. // stack: packed_nibbles, terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest DUP6 %mload_trie_data // Load the num_nibbles field, which is at index 0. // stack: num_nibbles, packed_nibbles, terminated, encode_node_extension_after_hex_prefix, result, result_len, node_payload_ptr, retdest @@ -247,7 +247,7 @@ encode_node_leaf: PUSH encode_node_leaf_after_hex_prefix // retdest PUSH 1 // terminated // stack: terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest - DUP3 %add_const(1) %mload_trie_data // Load the packed_nibbles field, which is at index 1. + DUP3 %increment %mload_trie_data // Load the packed_nibbles field, which is at index 1. // stack: packed_nibbles, terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest DUP4 %mload_trie_data // Load the num_nibbles field, which is at index 0. // stack: num_nibbles, packed_nibbles, terminated, encode_node_leaf_after_hex_prefix, node_payload_ptr, encode_value, retdest diff --git a/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm index 80763deb..221c0f20 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm @@ -48,7 +48,7 @@ encode_account: DUP2 %mload_trie_data // nonce = value[0] %rlp_scalar_len // stack: nonce_rlp_len, rlp_pos, value_ptr, retdest - DUP3 %add_const(1) %mload_trie_data // balance = value[1] + DUP3 %increment %mload_trie_data // balance = value[1] %rlp_scalar_len // stack: balance_rlp_len, nonce_rlp_len, rlp_pos, value_ptr, retdest PUSH 66 // storage_root and code_hash fields each take 1 + 32 bytes @@ -68,7 +68,7 @@ encode_account: // stack: nonce, rlp_pos_3, value_ptr, retdest SWAP1 %encode_rlp_scalar // stack: rlp_pos_4, value_ptr, retdest - DUP2 %add_const(1) %mload_trie_data // balance = value[1] + DUP2 %increment %mload_trie_data // balance = value[1] // stack: balance, rlp_pos_4, value_ptr, retdest SWAP1 %encode_rlp_scalar // stack: rlp_pos_5, value_ptr, retdest diff --git a/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm index 72ac18cc..b7a3073b 100644 --- a/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm +++ b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm @@ -15,7 +15,7 @@ global hex_prefix_rlp: // Compute the length of the hex-prefix string, in bytes: // hp_len = num_nibbles / 2 + 1 = i + 1 - DUP1 %add_const(1) + DUP1 %increment // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest // Write the RLP header. @@ -35,7 +35,7 @@ rlp_header_medium: %mstore_rlp // rlp_pos += 1 - SWAP2 %add_const(1) SWAP2 + SWAP2 %increment SWAP2 %jump(start_loop) @@ -49,7 +49,7 @@ rlp_header_large: %mstore_rlp DUP1 // value = hp_len - DUP4 %add_const(1) // offset = rlp_pos + 1 + DUP4 %increment // offset = rlp_pos + 1 %mstore_rlp // rlp_pos += 2 @@ -74,7 +74,7 @@ loop: %mstore_rlp // stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest - %sub_const(1) + %decrement SWAP4 %shr_const(8) SWAP4 // packed_nibbles >>= 8 %jump(loop) diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index f072f202..f37e94ba 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -111,7 +111,7 @@ load_mpt_extension: // one element, appending our child pointer. Thus our child node will start // at i + 1. So we will set our child pointer to i + 1. %get_trie_data_size - %add_const(1) + %increment %append_to_trie_data // stack: retdest @@ -172,7 +172,7 @@ load_mpt_digest: // stack: leaf_part, leaf_len %append_to_trie_data // stack: leaf_len - %sub_const(1) + %decrement // stack: leaf_len' %jump(%%loop) %%finish: diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index f952f49a..32ab8f7f 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -31,7 +31,7 @@ global mpt_read: %mload_trie_data // stack: node_type, node_ptr, num_nibbles, key, retdest // Increment node_ptr, so it points to the node payload instead of its type. - SWAP1 %add_const(1) SWAP1 + SWAP1 %increment SWAP1 // stack: node_type, node_payload_ptr, num_nibbles, key, retdest DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_read_empty) @@ -103,7 +103,7 @@ mpt_read_extension: %mul_const(4) SHR // key_part = key >> (future_nibbles * 4) DUP1 // stack: key_part, key_part, future_nibbles, key, node_payload_ptr, retdest - DUP5 %add_const(1) %mload_trie_data + DUP5 %increment %mload_trie_data // stack: node_key, key_part, key_part, future_nibbles, key, node_payload_ptr, retdest EQ // does the first part of our key match the node's key? %jumpi(mpt_read_extension_found) @@ -131,7 +131,7 @@ mpt_read_leaf: // stack: node_payload_ptr, num_nibbles, key, retdest DUP1 %mload_trie_data // stack: node_num_nibbles, node_payload_ptr, num_nibbles, key, retdest - DUP2 %add_const(1) %mload_trie_data + DUP2 %increment %mload_trie_data // stack: node_key, node_num_nibbles, node_payload_ptr, num_nibbles, key, retdest SWAP3 // stack: num_nibbles, node_num_nibbles, node_payload_ptr, node_key, key, retdest diff --git a/evm/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm index 0e0006d3..19cad943 100644 --- a/evm/src/cpu/kernel/asm/mpt/util.asm +++ b/evm/src/cpu/kernel/asm/mpt/util.asm @@ -28,7 +28,7 @@ %get_trie_data_size // stack: trie_data_size, value DUP1 - %add_const(1) + %increment // stack: trie_data_size', trie_data_size, value %set_trie_data_size // stack: trie_data_size, value @@ -45,7 +45,7 @@ // return (first_nibble, num_nibbles, key) %macro split_first_nibble // stack: num_nibbles, key - %sub_const(1) // num_nibbles -= 1 + %decrement // num_nibbles -= 1 // stack: num_nibbles, key DUP2 // stack: key, num_nibbles, key diff --git a/evm/src/cpu/kernel/asm/ripemd/memory.asm b/evm/src/cpu/kernel/asm/ripemd/memory.asm index 5d0266bd..e3b7cbe6 100644 --- a/evm/src/cpu/kernel/asm/ripemd/memory.asm +++ b/evm/src/cpu/kernel/asm/ripemd/memory.asm @@ -44,7 +44,7 @@ store_input_stack: // stack: offset, byte, rem, length, REM_INP %mstore_kernel_general // stack: rem, length, REM_INP - %sub_const(1) + %decrement DUP1 // stack: rem - 1, rem - 1, length, REM_INP %jumpi(store_input_stack) @@ -66,10 +66,10 @@ store_input: // stack: offset, byte, rem , ADDR , length %mstore_kernel_general // stack: rem , ADDR , length - %sub_const(1) + %decrement // stack: rem-1, ADDR , length SWAP3 - %add_const(1) + %increment SWAP3 // stack: rem-1, ADDR+1, length DUP1 @@ -90,12 +90,12 @@ global buffer_update: // stack: get, set, get , set , times , retdest %mupdate_kernel_general // stack: get , set , times , retdest - %add_const(1) + %increment SWAP1 - %add_const(1) + %increment SWAP1 SWAP2 - %sub_const(1) + %decrement SWAP2 // stack: get+1, set+1, times-1, retdest DUP3 @@ -112,7 +112,7 @@ global buffer_update: // stack: offset = N-i, 0, i %mstore_kernel_general // stack: i - %sub_const(1) + %decrement DUP1 // stack: i-1, i-1 %jumpi($label) diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm index 5749aee7..182354c4 100644 --- a/evm/src/cpu/kernel/asm/rlp/decode.asm +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -36,7 +36,7 @@ decode_rlp_string_len_medium: %sub_const(0x80) // stack: len, pos, retdest SWAP1 - %add_const(1) + %increment // stack: pos', len, retdest %stack (pos, len, retdest) -> (retdest, pos, len) JUMP @@ -47,7 +47,7 @@ decode_rlp_string_len_large: %sub_const(0xb7) // stack: len_of_len, pos, retdest SWAP1 - %add_const(1) + %increment // stack: pos', len_of_len, retdest %jump(decode_int_given_len) @@ -92,7 +92,7 @@ global decode_rlp_list_len: %mload_current(@SEGMENT_RLP_RAW) // stack: first_byte, pos, retdest SWAP1 - %add_const(1) // increment pos + %increment // increment pos SWAP1 // stack: first_byte, pos', retdest // If first_byte is >= 0xf8, it's a > 55 byte list, and @@ -157,7 +157,7 @@ decode_int_given_len_loop: // stack: acc', pos, end_pos, retdest // Increment pos. SWAP1 - %add_const(1) + %increment SWAP1 // stack: acc', pos', end_pos, retdest %jump(decode_int_given_len_loop) diff --git a/evm/src/cpu/kernel/asm/rlp/encode.asm b/evm/src/cpu/kernel/asm/rlp/encode.asm index 851ad3cf..dada98b0 100644 --- a/evm/src/cpu/kernel/asm/rlp/encode.asm +++ b/evm/src/cpu/kernel/asm/rlp/encode.asm @@ -14,7 +14,7 @@ global encode_rlp_scalar: // stack: pos, scalar, pos, retdest %mstore_rlp // stack: pos, retdest - %add_const(1) + %increment // stack: pos', retdest SWAP1 JUMP @@ -76,7 +76,7 @@ encode_rlp_fixed: %mstore_rlp // stack: len, pos, string, retdest SWAP1 - %add_const(1) // increment pos + %increment // increment pos // stack: pos, len, string, retdest %stack (pos, len, string) -> (pos, string, len, encode_rlp_fixed_finish) // stack: context, segment, pos, string, len, encode_rlp_fixed_finish, retdest @@ -159,7 +159,7 @@ global encode_rlp_list_prefix: // stack: pos, prefix, pos, retdest %mstore_rlp // stack: pos, retdest - %add_const(1) + %increment SWAP1 JUMP encode_rlp_list_prefix_large: @@ -172,7 +172,7 @@ encode_rlp_list_prefix_large: DUP3 // pos %mstore_rlp // stack: len_of_len, pos, payload_len, retdest - SWAP1 %add_const(1) + SWAP1 %increment // stack: pos', len_of_len, payload_len, retdest %stack (pos, len_of_len, payload_len) -> (pos, payload_len, len_of_len, @@ -231,7 +231,7 @@ prepend_rlp_list_prefix_big: 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 + DUP1 %increment // 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, @@ -269,7 +269,7 @@ prepend_rlp_list_prefix_big_done_writing_len: // stack: scalar %num_bytes // stack: scalar_bytes - %add_const(1) // Account for the length prefix. + %increment // Account for the length prefix. // stack: rlp_len %%finish: %endmacro diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm index 189edd1d..5d8cbd17 100644 --- a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -25,7 +25,7 @@ read_rlp_to_memory_loop: // stack: pos, byte, pos, len, retdest %mstore_current(@SEGMENT_RLP_RAW) // stack: pos, len, retdest - %add_const(1) + %increment // stack: pos', len, retdest %jump(read_rlp_to_memory_loop) From 817156cd479612d7c16369e462550c8ecd6e1894 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 8 Oct 2022 13:23:00 -0700 Subject: [PATCH 02/21] Begin MPT insert --- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/mpt/delete.asm | 6 ++++ evm/src/cpu/kernel/asm/mpt/read.asm | 2 +- evm/src/cpu/kernel/asm/mpt/write.asm | 50 +++++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/mpt/delete.asm diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 0d94c86f..2dcbd41c 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/delete.asm"), include_str!("asm/mpt/hash.asm"), include_str!("asm/mpt/hash_trie_specific.asm"), include_str!("asm/mpt/hex_prefix.asm"), diff --git a/evm/src/cpu/kernel/asm/mpt/delete.asm b/evm/src/cpu/kernel/asm/mpt/delete.asm new file mode 100644 index 00000000..3e0b8afe --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/delete.asm @@ -0,0 +1,6 @@ +// Return a copy of the given node with the given key deleted. +// +// Pre stack: node_ptr, num_nibbles, key, retdest +// Post stack: updated_node_ptr +global mpt_delete: + PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index 32ab8f7f..c6f36204 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -39,7 +39,7 @@ global mpt_read: DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_read_extension) DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_read_leaf) - // There's still the MPT_NODE_HASH case, but if we hit a digest node, + // There's still the MPT_NODE_HASH case, but if we hit a hash node, // it means the prover failed to provide necessary Merkle data, so panic. PANIC diff --git a/evm/src/cpu/kernel/asm/mpt/write.asm b/evm/src/cpu/kernel/asm/mpt/write.asm index 5b59d016..eab51d3e 100644 --- a/evm/src/cpu/kernel/asm/mpt/write.asm +++ b/evm/src/cpu/kernel/asm/mpt/write.asm @@ -1,3 +1,47 @@ -global mpt_write: - // stack: node_ptr, num_nibbles, key, retdest - // TODO +// TODO: Need a special case for deleting, if value = ''. +// Or canonicalize once, before final hashing, to remove empty leaves etc. + +// Return a copy of the given node, with the given key set to the given value. +// +// Pre stack: node_ptr, num_nibbles, key, value_ptr, retdest +// Post stack: updated_node_ptr +global mpt_insert: + // stack: node_ptr, num_nibbles, key, value_ptr, retdest + DUP1 %mload_trie_data + // stack: node_type, node_ptr, num_nibbles, key, value_ptr, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %increment SWAP1 + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_insert_empty) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_insert_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_insert_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_insert_leaf) + + // There's still the MPT_NODE_HASH case, but if we hit a hash node, + // it means the prover failed to provide necessary Merkle data, so panic. + PANIC + +mpt_insert_empty: + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + POP + // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + PANIC // TODO + +mpt_insert_branch: + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + POP + // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + PANIC // TODO + +mpt_insert_extension: + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + POP + // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + PANIC // TODO + +mpt_insert_leaf: + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + POP + // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + PANIC // TODO From 8ee7265863009917c2e509abd76194eb78a707ca Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 8 Oct 2022 13:51:52 -0700 Subject: [PATCH 03/21] Tweak MPT value storage --- evm/src/cpu/kernel/asm/mpt/hash.asm | 7 ++++++- evm/src/cpu/kernel/asm/mpt/load.asm | 12 +++++++++++- evm/src/cpu/kernel/asm/mpt/read.asm | 14 +++++++++----- evm/src/cpu/kernel/tests/mpt/load.rs | 1 + evm/src/cpu/kernel/tests/mpt/read.rs | 11 ++++++----- 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index ef0158e0..511e9d18 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -137,6 +137,8 @@ encode_node_branch: // stack: rlp_pos', node_payload_ptr, encode_value, retdest SWAP1 %add_const(16) + // stack: value_ptr_ptr, rlp_pos', encode_value, retdest + %mload_trie_data // stack: value_len_ptr, rlp_pos', encode_value, retdest DUP1 %mload_trie_data // stack: value_len, value_len_ptr, rlp_pos', encode_value, retdest @@ -257,7 +259,10 @@ encode_node_leaf: encode_node_leaf_after_hex_prefix: // stack: rlp_pos, node_payload_ptr, encode_value, retdest SWAP1 - %add_const(3) // The value starts at index 3, after num_nibbles, packed_nibbles, and value_len. + %add_const(2) // The value pointer starts at index 3, after num_nibbles and packed_nibbles. + // stack: value_ptr_ptr, rlp_pos, encode_value, retdest + %mload_trie_data + %increment // skip over length prefix // stack: value_ptr, rlp_pos, encode_value, retdest %stack (value_ptr, rlp_pos, encode_value, retdest) -> (encode_value, rlp_pos, value_ptr, encode_node_leaf_after_encode_value, retdest) diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index f37e94ba..62909f2d 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -76,9 +76,14 @@ load_mpt_branch: %get_trie_data_size // stack: ptr_children, retdest DUP1 %add_const(16) - // stack: ptr_leaf, ptr_children, retdest + // stack: ptr_value, ptr_children, retdest %set_trie_data_size // stack: ptr_children, retdest + // We need to append a pointer to where the value will live. + // %load_leaf_value will append the value just after this pointer; + // we add 1 to account for the pointer itself. + %get_trie_data_size %increment %append_to_trie_data + // stack: ptr_children, retdest %load_leaf_value // Load the 16 children. @@ -128,6 +133,11 @@ load_mpt_leaf: PROVER_INPUT(mpt) // read packed_nibbles %append_to_trie_data // stack: retdest + // We need to append a pointer to where the value will live. + // %load_leaf_value will append the value just after this pointer; + // we add 1 to account for the pointer itself. + %get_trie_data_size %increment %append_to_trie_data + // stack: retdest %load_leaf_value // stack: retdest JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index c6f36204..dae97336 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -1,6 +1,6 @@ -// 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. +// Given an address, return a pointer to the associated (length-prefixed) +// 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 @@ -24,7 +24,7 @@ mpt_read_state_trie_after_mstore: // - the key, as a U256 // - 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. +// This function returns a pointer to the length-prefixed leaf, or 0 if the key is not found. global mpt_read: // stack: node_ptr, num_nibbles, key, retdest DUP1 @@ -75,6 +75,8 @@ mpt_read_branch_end_of_key: %stack (node_payload_ptr, num_nibbles, key, retdest) -> (node_payload_ptr, retdest) // stack: node_payload_ptr, retdest %add_const(16) // skip over the 16 child nodes + // stack: value_ptr_ptr, retdest + %mload_trie_data // stack: value_len_ptr, retdest DUP1 %mload_trie_data // stack: value_len, value_len_ptr, retdest @@ -147,7 +149,9 @@ mpt_read_leaf: JUMP mpt_read_leaf_found: // stack: node_payload_ptr, retdest - %add_const(3) // The value is located after num_nibbles, the key, and the value length. + %add_const(2) // The value pointer is located after num_nibbles and the key. + // stack: value_ptr_ptr, retdest + %mload_trie_data // stack: value_ptr, retdest SWAP1 JUMP diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs index 3af39e30..ca4f7071 100644 --- a/evm/src/cpu/kernel/tests/mpt/load.rs +++ b/evm/src/cpu/kernel/tests/mpt/load.rs @@ -48,6 +48,7 @@ fn load_all_mpts() -> Result<()> { type_leaf, 3.into(), // 3 nibbles 0xDEF.into(), // key part + 9.into(), // value pointer 4.into(), // value length account.nonce, account.balance, diff --git a/evm/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs index c45a6b60..06d89ff6 100644 --- a/evm/src/cpu/kernel/tests/mpt/read.rs +++ b/evm/src/cpu/kernel/tests/mpt/read.rs @@ -44,11 +44,12 @@ fn mpt_read() -> Result<()> { assert_eq!(interpreter.stack().len(), 1); let result_ptr = interpreter.stack()[0].as_usize(); - let result = &interpreter.get_trie_data()[result_ptr..][..4]; - assert_eq!(result[0], account.nonce); - assert_eq!(result[1], account.balance); - assert_eq!(result[2], account.storage_root.into_uint()); - assert_eq!(result[3], account.code_hash.into_uint()); + let result = &interpreter.get_trie_data()[result_ptr..][..5]; + assert_eq!(result[0], 4.into()); + assert_eq!(result[1], account.nonce); + assert_eq!(result[2], account.balance); + assert_eq!(result[3], account.storage_root.into_uint()); + assert_eq!(result[4], account.code_hash.into_uint()); Ok(()) } From 443a07000390bc0957321b1aaaa8eb48a7443b00 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 8 Oct 2022 13:59:02 -0700 Subject: [PATCH 04/21] Clippy fix --- evm/src/cpu/kernel/opcodes.rs | 2 +- evm/src/cpu/kernel/tests/ripemd.rs | 2 +- field/src/types.rs | 2 +- plonky2/src/gadgets/arithmetic_extension.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index 2325c53a..c5133050 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -2,7 +2,7 @@ pub(crate) fn get_push_opcode(n: u8) -> u8 { assert!(n > 0); assert!(n <= 32); - 0x60 + (n as u8 - 1) + 0x60 + n - 1 } /// The opcode of a standard instruction (not a `PUSH`). diff --git a/evm/src/cpu/kernel/tests/ripemd.rs b/evm/src/cpu/kernel/tests/ripemd.rs index 6123c336..305548ec 100644 --- a/evm/src/cpu/kernel/tests/ripemd.rs +++ b/evm/src/cpu/kernel/tests/ripemd.rs @@ -46,7 +46,7 @@ fn test_ripemd_reference() -> Result<()> { let kernel = combined_kernel(); let initial_offset = kernel.global_labels["ripemd_stack"]; - let initial_stack: Vec = input.iter().map(|&x| U256::from(x as u32)).rev().collect(); + let initial_stack: Vec = input.iter().map(|&x| U256::from(x)).rev().collect(); let final_stack: Vec = run_with_kernel(&kernel, initial_offset, initial_stack)? .stack() .to_vec(); diff --git a/field/src/types.rs b/field/src/types.rs index b112fde2..545f90c5 100644 --- a/field/src/types.rs +++ b/field/src/types.rs @@ -455,7 +455,7 @@ pub trait PrimeField: Field { let mut x = w * *self; let mut b = x * w; - let mut v = Self::TWO_ADICITY as usize; + let mut v = Self::TWO_ADICITY; while !b.is_one() { let mut k = 0usize; diff --git a/plonky2/src/gadgets/arithmetic_extension.rs b/plonky2/src/gadgets/arithmetic_extension.rs index 23caeac1..23c401b8 100644 --- a/plonky2/src/gadgets/arithmetic_extension.rs +++ b/plonky2/src/gadgets/arithmetic_extension.rs @@ -443,7 +443,7 @@ impl, const D: usize> CircuitBuilder { let mut current = base; let mut product = self.one_extension(); - for j in 0..bits_u64(exponent as u64) { + for j in 0..bits_u64(exponent) { if j != 0 { current = self.square_extension(current); } From 6bb1ad94e8acc9fb144cf8562f4c9b4d0bb74b48 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 8 Oct 2022 15:09:07 -0700 Subject: [PATCH 05/21] MPT insert logic, part 1 --- evm/src/cpu/kernel/aggregator.rs | 3 +- .../cpu/kernel/asm/mpt/hash_trie_specific.asm | 2 +- evm/src/cpu/kernel/asm/mpt/insert.asm | 118 +++++++++++ .../kernel/asm/mpt/insert_trie_specific.asm | 14 ++ evm/src/cpu/kernel/asm/mpt/load.asm | 159 +++++++------- evm/src/cpu/kernel/asm/mpt/write.asm | 47 ----- evm/src/cpu/kernel/interpreter.rs | 11 +- evm/src/cpu/kernel/tests/mpt/insert.rs | 173 ++++++++++++++++ evm/src/cpu/kernel/tests/mpt/load.rs | 194 ++++++++++++++++-- evm/src/cpu/kernel/tests/mpt/mod.rs | 30 +++ evm/src/generation/mpt.rs | 18 +- 11 files changed, 620 insertions(+), 149 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/mpt/insert.asm create mode 100644 evm/src/cpu/kernel/asm/mpt/insert_trie_specific.asm delete mode 100644 evm/src/cpu/kernel/asm/mpt/write.asm create mode 100644 evm/src/cpu/kernel/tests/mpt/insert.rs diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 2dcbd41c..6fb2231e 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -43,12 +43,13 @@ pub(crate) fn combined_kernel() -> Kernel { 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/insert.asm"), + include_str!("asm/mpt/insert_trie_specific.asm"), include_str!("asm/mpt/load.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/util.asm"), - include_str!("asm/mpt/write.asm"), include_str!("asm/ripemd/box.asm"), include_str!("asm/ripemd/compression.asm"), include_str!("asm/ripemd/constants.asm"), diff --git a/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm index 221c0f20..bf2c46f0 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm @@ -39,7 +39,7 @@ global mpt_hash_receipt_trie: %%after: %endmacro -encode_account: +global 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 diff --git a/evm/src/cpu/kernel/asm/mpt/insert.asm b/evm/src/cpu/kernel/asm/mpt/insert.asm new file mode 100644 index 00000000..9a5ed7f2 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/insert.asm @@ -0,0 +1,118 @@ +// Return a copy of the given node, with the given key set to the given value. +// +// Pre stack: node_ptr, num_nibbles, key, value_ptr, retdest +// Post stack: updated_node_ptr +global mpt_insert: + // stack: node_ptr, num_nibbles, key, value_ptr, retdest + DUP1 %mload_trie_data + // stack: node_type, node_ptr, num_nibbles, key, value_ptr, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %increment SWAP1 + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_insert_empty) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_insert_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_insert_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_insert_leaf) + + // There's still the MPT_NODE_HASH case, but if we hit a hash node, + // it means the prover failed to provide necessary Merkle data, so panic. + PANIC + +mpt_insert_empty: + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + %pop2 + // stack: num_nibbles, key, value_ptr, retdest + // We will append a new leaf node to our MPT tape and return a pointer to it. + %get_trie_data_size + // stack: leaf_ptr, num_nibbles, key, value_ptr, retdest + PUSH @MPT_NODE_LEAF %append_to_trie_data + // stack: leaf_ptr, num_nibbles, key, value_ptr, retdest + SWAP1 %append_to_trie_data + // stack: leaf_ptr, key, value_ptr, retdest + SWAP1 %append_to_trie_data + // stack: leaf_ptr, value_ptr, retdest + SWAP1 %append_to_trie_data + // stack: leaf_ptr, retdest + SWAP1 + JUMP + +mpt_insert_branch: + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + %get_trie_data_size + // stack: updated_branch_ptr, node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + SWAP1 + %append_to_trie_data + // stack: updated_branch_ptr, node_payload_ptr, num_nibbles, key, value_ptr, retdest + SWAP1 + // stack: node_payload_ptr, updated_branch_ptr, num_nibbles, key, value_ptr, retdest + + // Copy the original node's data to our updated node. + DUP1 %mload_trie_data %append_to_trie_data // Copy child[0] + DUP1 %add_const(1) %mload_trie_data %append_to_trie_data // ... + DUP1 %add_const(2) %mload_trie_data %append_to_trie_data + DUP1 %add_const(3) %mload_trie_data %append_to_trie_data + DUP1 %add_const(4) %mload_trie_data %append_to_trie_data + DUP1 %add_const(5) %mload_trie_data %append_to_trie_data + DUP1 %add_const(6) %mload_trie_data %append_to_trie_data + DUP1 %add_const(7) %mload_trie_data %append_to_trie_data + DUP1 %add_const(8) %mload_trie_data %append_to_trie_data + DUP1 %add_const(9) %mload_trie_data %append_to_trie_data + DUP1 %add_const(10) %mload_trie_data %append_to_trie_data + DUP1 %add_const(11) %mload_trie_data %append_to_trie_data + DUP1 %add_const(12) %mload_trie_data %append_to_trie_data + DUP1 %add_const(13) %mload_trie_data %append_to_trie_data + DUP1 %add_const(14) %mload_trie_data %append_to_trie_data + DUP1 %add_const(15) %mload_trie_data %append_to_trie_data // Copy child[15] + %add_const(16) %mload_trie_data %append_to_trie_data // Copy value_ptr + + // At this point, we branch based on whether the key terminates with this branch node. + // stack: updated_branch_ptr, num_nibbles, key, value_ptr, retdest + DUP2 %jumpi(mpt_insert_branch_nonterminal) + + // The key terminates here, so the value will be placed right in our (updated) branch node. + // stack: updated_branch_ptr, num_nibbles, key, value_ptr, retdest + SWAP3 + // stack: value_ptr, num_nibbles, key, updated_branch_ptr, retdest + DUP4 %add_const(17) + // stack: updated_branch_value_ptr_ptr, value_ptr, num_nibbles, key, updated_branch_ptr, retdest + %mstore_trie_data + // stack: num_nibbles, key, updated_branch_ptr, retdest + %pop2 + // stack: updated_branch_ptr, retdest + SWAP1 + JUMP + +mpt_insert_branch_nonterminal: + // The key continues, so we split off the first (most significant) nibble, + // and recursively insert into the child associated with that nibble. + // stack: updated_branch_ptr, num_nibbles, key, value_ptr, retdest + %stack (updated_branch_ptr, num_nibbles, key) -> (num_nibbles, key, updated_branch_ptr) + %split_first_nibble + // stack: first_nibble, num_nibbles, key, updated_branch_ptr, value_ptr, retdest + DUP4 %increment ADD + // stack: child_ptr_ptr, num_nibbles, key, updated_branch_ptr, value_ptr, retdest + %stack (child_ptr_ptr, num_nibbles, key, updated_branch_ptr, value_ptr) + -> (child_ptr_ptr, num_nibbles, key, value_ptr, + mpt_insert_branch_nonterminal_after_recursion, + child_ptr_ptr, updated_branch_ptr) + %mload_trie_data // Deref child_ptr_ptr, giving child_ptr + %jump(mpt_insert) +mpt_insert_branch_nonterminal_after_recursion: + // stack: updated_child_ptr, child_ptr_ptr, updated_branch_ptr, retdest + SWAP1 %mstore_trie_data // Store the pointer to the updated child. + // stack: updated_branch_ptr, retdest + SWAP1 + JUMP + +mpt_insert_extension: + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + POP + // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + PANIC // TODO + +mpt_insert_leaf: + // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + POP + // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/insert_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/insert_trie_specific.asm new file mode 100644 index 00000000..4c03d96c --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/insert_trie_specific.asm @@ -0,0 +1,14 @@ +// Insertion logic specific to a particular trie. + +// Mutate the state trie, inserting the given key-value pair. +global mpt_insert_state_trie: + // stack: num_nibbles, key, value_ptr, retdest + %stack (num_nibbles, key, value_ptr) + -> (num_nibbles, key, value_ptr, mpt_insert_state_trie_save) + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + // stack: state_root_ptr, num_nibbles, key, value_ptr, mpt_insert_state_trie_save, retdest + %jump(mpt_insert) +mpt_insert_state_trie_save: + // stack: updated_node_ptr, retdest + %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index 62909f2d..73f58b95 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -9,9 +9,9 @@ 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_TXN_TRIE_ROOT) - %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) + %load_mpt %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %load_mpt %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) + %load_mpt %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) PROVER_INPUT(mpt) // stack: num_storage_tries, retdest @@ -30,7 +30,7 @@ storage_trie_loop: // stack: i, storage_trie_addr, i, num_storage_tries, retdest %mstore_kernel(@SEGMENT_STORAGE_TRIE_ADDRS) // stack: i, num_storage_tries, retdest - %load_mpt_and_return_root_ptr + %load_mpt // stack: root_ptr, i, num_storage_tries, retdest DUP2 // stack: i, root_ptr, i, num_storage_tries, retdest @@ -45,13 +45,11 @@ storage_trie_loop_end: // Load an MPT from prover inputs. // Pre stack: retdest -// Post stack: (empty) +// Post stack: node_ptr load_mpt: // stack: retdest PROVER_INPUT(mpt) // stack: node_type, retdest - DUP1 %append_to_trie_data - // stack: node_type, retdest DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(load_mpt_empty) DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(load_mpt_branch) @@ -61,94 +59,108 @@ load_mpt: PANIC // Invalid node type load_mpt_empty: - // stack: node_type, retdest - POP - // stack: retdest + // TRIE_DATA[0] = 0, and an empty node has type 0, so we can simply return the null pointer. + %stack (node_type, retdest) -> (retdest, 0) JUMP load_mpt_branch: // stack: node_type, retdest - POP - // stack: retdest + %get_trie_data_size + // stack: node_ptr, node_type, retdest + SWAP1 %append_to_trie_data + // stack: node_ptr, retdest // Save the offset of our 16 child pointers so we can write them later. // Then advance out current trie pointer beyond them, so we can load the // value and have it placed after our child pointers. %get_trie_data_size - // stack: ptr_children, retdest - DUP1 %add_const(16) - // stack: ptr_value, ptr_children, retdest + // stack: children_ptr, node_ptr, retdest + DUP1 %add_const(17) // Skip over 16 children plus the value pointer + // stack: value_ptr, children_ptr, node_ptr, retdest %set_trie_data_size - // stack: ptr_children, retdest - // We need to append a pointer to where the value will live. - // %load_leaf_value will append the value just after this pointer; - // we add 1 to account for the pointer itself. - %get_trie_data_size %increment %append_to_trie_data - // stack: ptr_children, retdest - %load_leaf_value + // stack: children_ptr, node_ptr, retdest + %load_value + // stack: children_ptr, value_ptr, node_ptr, retdest + SWAP1 // Load the 16 children. %rep 16 - %load_mpt_and_return_root_ptr - // stack: child_ptr, ptr_next_child, retdest + %load_mpt + // stack: child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, retdest DUP2 - // stack: ptr_next_child, child_ptr, ptr_next_child, retdest + // stack: next_child_ptr_ptr, child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, retdest %mstore_trie_data - // stack: ptr_next_child, retdest + // stack: next_child_ptr_ptr, value_ptr, node_ptr, retdest %increment - // stack: ptr_next_child, retdest + // stack: next_child_ptr_ptr, value_ptr, node_ptr, retdest %endrep - // stack: ptr_next_child, retdest - POP + // stack: value_ptr_ptr, value_ptr, node_ptr, retdest + %mstore_trie_data + // stack: node_ptr, retdest + SWAP1 JUMP load_mpt_extension: // stack: node_type, retdest - POP - // stack: retdest + %get_trie_data_size + // stack: node_ptr, node_type, retdest + SWAP1 %append_to_trie_data + // stack: node_ptr, retdest PROVER_INPUT(mpt) // read num_nibbles %append_to_trie_data PROVER_INPUT(mpt) // read packed_nibbles %append_to_trie_data - // stack: retdest + // stack: node_ptr, retdest - // Let i be the current trie data size. We still need to expand this node by - // one element, appending our child pointer. Thus our child node will start - // at i + 1. So we will set our child pointer to i + 1. %get_trie_data_size - %increment - %append_to_trie_data - // stack: retdest + // stack: child_ptr_ptr, node_ptr, retdest + // Increment trie_data_size, to leave room for child_ptr_ptr, before we load our child. + DUP1 %increment %set_trie_data_size + // stack: child_ptr_ptr, node_ptr, retdest %load_mpt - // stack: retdest + // stack: child_ptr, child_ptr_ptr, node_ptr, retdest + SWAP1 + %mstore_trie_data + // stack: node_ptr, retdest + SWAP1 JUMP load_mpt_leaf: // stack: node_type, retdest - POP - // stack: retdest + %get_trie_data_size + // stack: node_ptr, node_type, retdest + SWAP1 %append_to_trie_data + // stack: node_ptr, retdest PROVER_INPUT(mpt) // read num_nibbles %append_to_trie_data PROVER_INPUT(mpt) // read packed_nibbles %append_to_trie_data - // stack: retdest - // We need to append a pointer to where the value will live. - // %load_leaf_value will append the value just after this pointer; - // we add 1 to account for the pointer itself. - %get_trie_data_size %increment %append_to_trie_data - // stack: retdest - %load_leaf_value - // stack: retdest + // stack: node_ptr, retdest + // We save value_ptr_ptr = get_trie_data_size, then increment trie_data_size + // to skip over the slot for value_ptr. We will write value_ptr after the + // load_value call. + %get_trie_data_size + // stack: value_ptr_ptr, node_ptr, retdest + DUP1 %increment %set_trie_data_size + // stack: value_ptr_ptr, node_ptr, retdest + %load_value + // stack: value_ptr, value_ptr_ptr, node_ptr, retdest + SWAP1 %mstore_trie_data + // stack: node_ptr, retdest + SWAP1 JUMP load_mpt_digest: // stack: node_type, retdest - POP - // stack: retdest + %get_trie_data_size + // stack: node_ptr, node_type, retdest + SWAP1 %append_to_trie_data + // stack: node_ptr, retdest PROVER_INPUT(mpt) // read digest %append_to_trie_data - // stack: retdest + // stack: node_ptr, retdest + SWAP1 JUMP // Convenience macro to call load_mpt and return where we left off. @@ -158,34 +170,37 @@ load_mpt_digest: %%after: %endmacro -%macro load_mpt_and_return_root_ptr - // stack: (empty) - %get_trie_data_size - // stack: ptr - %load_mpt - // stack: ptr -%endmacro - -// Load a leaf from prover input, and append it to trie data. -%macro load_leaf_value +// Load a leaf from prover input, append it to trie data, and return a pointer to it. +%macro load_value // stack: (empty) PROVER_INPUT(mpt) - // stack: leaf_len + // stack: value_len + DUP1 ISZERO + %jumpi(%%return_null) + // stack: value_len + %get_trie_data_size + SWAP1 + // stack: value_len, value_ptr DUP1 %append_to_trie_data - // stack: leaf_len + // stack: value_len, value_ptr %%loop: DUP1 ISZERO - // stack: leaf_len == 0, leaf_len - %jumpi(%%finish) - // stack: leaf_len + // stack: value_len == 0, value_len, value_ptr + %jumpi(%%finish_loop) + // stack: value_len, value_ptr PROVER_INPUT(mpt) - // stack: leaf_part, leaf_len + // stack: leaf_part, value_len, value_ptr %append_to_trie_data - // stack: leaf_len + // stack: value_len, value_ptr %decrement - // stack: leaf_len' + // stack: value_len', value_ptr %jump(%%loop) -%%finish: +%%finish_loop: + // stack: value_len, value_ptr POP - // stack: (empty) + // stack: value_ptr + %jump(%%end) +%%return_null: + %stack (value_len) -> (0) +%%end: %endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/write.asm b/evm/src/cpu/kernel/asm/mpt/write.asm deleted file mode 100644 index eab51d3e..00000000 --- a/evm/src/cpu/kernel/asm/mpt/write.asm +++ /dev/null @@ -1,47 +0,0 @@ -// TODO: Need a special case for deleting, if value = ''. -// Or canonicalize once, before final hashing, to remove empty leaves etc. - -// Return a copy of the given node, with the given key set to the given value. -// -// Pre stack: node_ptr, num_nibbles, key, value_ptr, retdest -// Post stack: updated_node_ptr -global mpt_insert: - // stack: node_ptr, num_nibbles, key, value_ptr, retdest - DUP1 %mload_trie_data - // stack: node_type, node_ptr, num_nibbles, key, value_ptr, retdest - // Increment node_ptr, so it points to the node payload instead of its type. - SWAP1 %increment SWAP1 - // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest - - DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_insert_empty) - DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_insert_branch) - DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_insert_extension) - DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_insert_leaf) - - // There's still the MPT_NODE_HASH case, but if we hit a hash node, - // it means the prover failed to provide necessary Merkle data, so panic. - PANIC - -mpt_insert_empty: - // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest - POP - // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest - PANIC // TODO - -mpt_insert_branch: - // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest - POP - // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest - PANIC // TODO - -mpt_insert_extension: - // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest - POP - // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest - PANIC // TODO - -mpt_insert_leaf: - // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest - POP - // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest - PANIC // TODO diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 589ba6b3..2eb9dcb9 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -168,10 +168,19 @@ impl<'a> Interpreter<'a> { self.memory.context_memory[0].segments[Segment::GlobalMetadata as usize].get(field as usize) } + pub(crate) fn set_global_metadata_field(&mut self, field: GlobalMetadata, value: U256) { + self.memory.context_memory[0].segments[Segment::GlobalMetadata as usize] + .set(field as usize, value) + } + pub(crate) fn get_trie_data(&self) -> &[U256] { &self.memory.context_memory[0].segments[Segment::TrieData as usize].content } + pub(crate) fn get_trie_data_mut(&mut self) -> &mut Vec { + &mut self.memory.context_memory[0].segments[Segment::TrieData as usize].content + } + pub(crate) fn get_rlp_memory(&self) -> Vec { self.memory.context_memory[0].segments[Segment::RlpRaw as usize] .content @@ -205,7 +214,7 @@ impl<'a> Interpreter<'a> { self.push(if x { U256::one() } else { U256::zero() }); } - fn pop(&mut self) -> U256 { + pub(crate) fn pop(&mut self) -> U256 { self.stack_mut().pop().expect("Pop on empty stack.") } diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs new file mode 100644 index 00000000..7aeb4a1a --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -0,0 +1,173 @@ +use anyhow::Result; +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use eth_trie_utils::trie_builder::InsertEntry; +use ethereum_types::{BigEndianHash, H256}; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1_rlp, test_account_2_rlp}; +use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; +use crate::generation::TrieInputs; + +#[test] +fn mpt_insert_empty() -> Result<()> { + let insert = InsertEntry { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + v: test_account_2_rlp(), + }; + test_state_trie(Default::default(), insert) +} + +#[test] +#[ignore] // TODO: Enable when mpt_insert_leaf is done. +fn mpt_insert_leaf_same_key() -> Result<()> { + let key = Nibbles { + count: 3, + packed: 0xABC.into(), + }; + let state_trie = PartialTrie::Leaf { + nibbles: key, + value: test_account_1_rlp(), + }; + let insert = InsertEntry { + nibbles: key, + v: test_account_2_rlp(), + }; + + test_state_trie(state_trie, insert) +} + +#[test] +fn mpt_insert_branch_replacing_empty_child() -> Result<()> { + let children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); + let state_trie = PartialTrie::Branch { + children, + value: vec![], + }; + + let insert = InsertEntry { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + v: test_account_2_rlp(), + }; + + test_state_trie(state_trie, insert) +} + +#[test] +#[ignore] // TODO: Enable when mpt_insert_extension is done. +fn mpt_insert_extension_to_leaf_same_key() -> Result<()> { + let state_trie = extension_to_leaf(test_account_1_rlp()); + + let insert = InsertEntry { + nibbles: Nibbles { + count: 3, + packed: 0xABCDEF.into(), + }, + v: test_account_2_rlp(), + }; + + test_state_trie(state_trie, insert) +} + +#[test] +#[ignore] // TODO: Enable when mpt_insert_leaf is done. +fn mpt_insert_branch_to_leaf_same_key() -> Result<()> { + let leaf = PartialTrie::Leaf { + nibbles: Nibbles { + count: 3, + packed: 0xBCD.into(), + }, + value: test_account_1_rlp(), + }; + let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); + children[0xA] = Box::new(leaf); + let state_trie = PartialTrie::Branch { + children, + value: vec![], + }; + + let insert = InsertEntry { + nibbles: Nibbles { + count: 4, + packed: 0xABCD.into(), + }, + v: test_account_2_rlp(), + }; + + test_state_trie(state_trie, insert) +} + +fn test_state_trie(state_trie: PartialTrie, insert: InsertEntry) -> Result<()> { + let trie_inputs = TrieInputs { + state_trie: state_trie.clone(), + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + let mpt_insert_state_trie = KERNEL.global_labels["mpt_insert_state_trie"]; + 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![]); + + // Next, execute mpt_insert_state_trie. + interpreter.offset = mpt_insert_state_trie; + let trie_data = interpreter.get_trie_data_mut(); + if trie_data.is_empty() { + // In the assembly we skip over 0, knowing trie_data[0] = 0 by default. + // Since we don't explicitly set it to 0, we need to do so here. + trie_data.push(0.into()); + } + let value_ptr = trie_data.len(); + let account: AccountRlp = rlp::decode(&insert.v).expect("Decoding failed"); + let account_data = account.to_vec(); + trie_data.push(account_data.len().into()); + trie_data.extend(account_data); + let trie_data_len = trie_data.len().into(); + interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len); + interpreter.push(0xDEADBEEFu32.into()); + interpreter.push(value_ptr.into()); // value_ptr + interpreter.push(insert.nibbles.packed); // key + interpreter.push(insert.nibbles.count.into()); // num_nibbles + + interpreter.run()?; + assert_eq!(interpreter.stack().len(), 0); + + // Now, execute mpt_hash_state_trie. + interpreter.offset = mpt_hash_state_trie; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.run()?; + + assert_eq!( + interpreter.stack().len(), + 1, + "Expected 1 item on stack, found {:?}", + interpreter.stack() + ); + let hash = H256::from_uint(&interpreter.stack()[0]); + + let expected_state_trie_hash = apply_insert(state_trie, insert).calc_hash(); + assert_eq!(hash, expected_state_trie_hash); + + Ok(()) +} + +fn apply_insert(trie: PartialTrie, insert: InsertEntry) -> PartialTrie { + let mut trie = Box::new(trie); + if let Some(updated_trie) = PartialTrie::insert_into_trie(&mut trie, insert) { + *updated_trie + } else { + *trie + } +} diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs index ca4f7071..ccf8353e 100644 --- a/evm/src/cpu/kernel/tests/mpt/load.rs +++ b/evm/src/cpu/kernel/tests/mpt/load.rs @@ -1,26 +1,19 @@ use anyhow::Result; -use ethereum_types::{BigEndianHash, H256, U256}; +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use ethereum_types::{BigEndianHash, U256}; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::interpreter::Interpreter; -use crate::cpu::kernel::tests::mpt::extension_to_leaf; -use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; +use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1, test_account_1_rlp}; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; use crate::generation::TrieInputs; #[test] -fn load_all_mpts() -> Result<()> { - let account = AccountRlp { - nonce: U256::from(1111), - balance: U256::from(2222), - storage_root: H256::from_uint(&U256::from(3333)), - code_hash: H256::from_uint(&U256::from(4444)), - }; - let account_rlp = rlp::encode(&account); - +fn load_all_mpts_empty() -> Result<()> { let trie_inputs = TrieInputs { - state_trie: extension_to_leaf(account_rlp.to_vec()), + state_trie: Default::default(), transactions_trie: Default::default(), receipts_trie: Default::default(), storage_tries: vec![], @@ -28,13 +21,174 @@ fn load_all_mpts() -> Result<()> { let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; - let initial_stack = vec![0xdeadbeefu32.into()]; + 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![]); + + assert_eq!(interpreter.get_trie_data(), vec![]); + + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot), + 0.into() + ); + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot), + 0.into() + ); + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot), + 0.into() + ); + + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries), + trie_inputs.storage_tries.len().into() + ); + + Ok(()) +} + +#[test] +fn load_all_mpts_leaf() -> Result<()> { + let trie_inputs = TrieInputs { + state_trie: PartialTrie::Leaf { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + value: test_account_1_rlp(), + }, + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + + 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![]); + + let type_leaf = U256::from(PartialTrieType::Leaf as u32); + assert_eq!( + interpreter.get_trie_data(), + vec![ + 0.into(), + type_leaf, + 3.into(), + 0xABC.into(), + 5.into(), // value ptr + 4.into(), // value length + test_account_1().nonce, + test_account_1().balance, + test_account_1().storage_root.into_uint(), + test_account_1().code_hash.into_uint(), + ] + ); + + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot), + 0.into() + ); + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot), + 0.into() + ); + + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries), + trie_inputs.storage_tries.len().into() + ); + + Ok(()) +} + +#[test] +fn load_all_mpts_empty_branch() -> Result<()> { + let children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); + let state_trie = PartialTrie::Branch { + children, + value: vec![], + }; + 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 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![]); + + let type_branch = U256::from(PartialTrieType::Branch as u32); + assert_eq!( + interpreter.get_trie_data(), + vec![ + 0.into(), // First address is unused, so that 0 can be treated as a null pointer. + type_branch, + 0.into(), // child 0 + 0.into(), // ... + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), // child 16 + 0.into(), // value_ptr + ] + ); + + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot), + 0.into() + ); + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot), + 0.into() + ); + + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries), + trie_inputs.storage_tries.len().into() + ); + + Ok(()) +} + +#[test] +fn load_all_mpts_ext_to_leaf() -> Result<()> { + let trie_inputs = TrieInputs { + state_trie: extension_to_leaf(test_account_1_rlp()), + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + + 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![]); - let type_empty = U256::from(PartialTrieType::Empty as u32); let type_extension = U256::from(PartialTrieType::Extension as u32); let type_leaf = U256::from(PartialTrieType::Leaf as u32); assert_eq!( @@ -50,12 +204,10 @@ fn load_all_mpts() -> Result<()> { 0xDEF.into(), // key part 9.into(), // value pointer 4.into(), // value length - account.nonce, - account.balance, - account.storage_root.into_uint(), - account.code_hash.into_uint(), - type_empty, // txn trie - type_empty, // receipt trie + test_account_1().nonce, + test_account_1().balance, + test_account_1().storage_root.into_uint(), + test_account_1().code_hash.into_uint(), ] ); diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs index 55a56653..e3414b38 100644 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -1,10 +1,40 @@ use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use ethereum_types::{BigEndianHash, H256, U256}; + +use crate::generation::mpt::AccountRlp; mod hash; mod hex_prefix; +mod insert; mod load; mod read; +pub(crate) fn test_account_1() -> AccountRlp { + AccountRlp { + nonce: U256::from(1111), + balance: U256::from(2222), + storage_root: H256::from_uint(&U256::from(3333)), + code_hash: H256::from_uint(&U256::from(4444)), + } +} + +pub(crate) fn test_account_1_rlp() -> Vec { + rlp::encode(&test_account_1()).to_vec() +} + +pub(crate) fn test_account_2() -> AccountRlp { + AccountRlp { + nonce: U256::from(5555), + balance: U256::from(6666), + storage_root: H256::from_uint(&U256::from(7777)), + code_hash: H256::from_uint(&U256::from(8888)), + } +} + +pub(crate) fn test_account_2_rlp() -> Vec { + rlp::encode(&test_account_2()).to_vec() +} + /// A `PartialTrie` where an extension node leads to a leaf node containing an account. pub(crate) fn extension_to_leaf(value: Vec) -> PartialTrie { PartialTrie::Extension { diff --git a/evm/src/generation/mpt.rs b/evm/src/generation/mpt.rs index f6bc630d..e35364c6 100644 --- a/evm/src/generation/mpt.rs +++ b/evm/src/generation/mpt.rs @@ -13,6 +13,17 @@ pub(crate) struct AccountRlp { pub(crate) code_hash: H256, } +impl AccountRlp { + pub(crate) fn to_vec(&self) -> Vec { + vec![ + self.nonce, + self.balance, + self.storage_root.into_uint(), + self.code_hash.into_uint(), + ] + } +} + pub(crate) fn all_mpt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec { let mut inputs = all_mpt_prover_inputs(trie_inputs); inputs.reverse(); @@ -25,12 +36,7 @@ pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec { mpt_prover_inputs(&trie_inputs.state_trie, &mut prover_inputs, &|rlp| { let account: AccountRlp = rlp::decode(rlp).expect("Decoding failed"); - vec![ - account.nonce, - account.balance, - account.storage_root.into_uint(), - account.code_hash.into_uint(), - ] + account.to_vec() }); mpt_prover_inputs(&trie_inputs.transactions_trie, &mut prover_inputs, &|rlp| { From 4a055b3a76c650c529bcd6b74161a7bf17318980 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 9 Oct 2022 11:32:01 -0700 Subject: [PATCH 06/21] MPT insert logic, part 2 --- evm/src/cpu/kernel/asm/mpt/insert.asm | 35 +++++++++-- evm/src/cpu/kernel/asm/mpt/util.asm | 85 ++++++++++++++++++++++++++ evm/src/cpu/kernel/tests/mpt/insert.rs | 10 ++- 3 files changed, 123 insertions(+), 7 deletions(-) diff --git a/evm/src/cpu/kernel/asm/mpt/insert.asm b/evm/src/cpu/kernel/asm/mpt/insert.asm index 9a5ed7f2..cc748e79 100644 --- a/evm/src/cpu/kernel/asm/mpt/insert.asm +++ b/evm/src/cpu/kernel/asm/mpt/insert.asm @@ -106,13 +106,40 @@ mpt_insert_branch_nonterminal_after_recursion: JUMP mpt_insert_extension: - // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + // stack: node_type, node_payload_ptr, insert_len, insert_key, value_ptr, retdest POP - // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + // stack: node_payload_ptr, insert_len, insert_key, value_ptr, retdest PANIC // TODO mpt_insert_leaf: - // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest + // stack: node_type, node_payload_ptr, insert_len, insert_key, value_ptr, retdest POP - // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + // stack: node_payload_ptr, insert_len, insert_key, value_ptr, retdest + %stack (node_payload_ptr, insert_len, insert_key) -> (insert_len, insert_key, node_payload_ptr) + // stack: insert_len, insert_key, node_payload_ptr, value_ptr, retdest + DUP3 %increment %mload_trie_data + // stack: node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest + DUP4 %mload_trie_data + // stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest + // TODO: Maybe skip %split_common_prefix if lengths & keys exactly match. + %split_common_prefix + // stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest + DUP3 DUP6 ADD %jumpi(mpt_insert_leaf_not_exact_match) + // If we got here, the node key exactly matches the insert key, so we will + // keep the same leaf node structure and just replace its value. + %stack (common_len, common_key, node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr) + -> (common_len, common_key, value_ptr) + // stack: common_len, common_key, value_ptr, retdest + %get_trie_data_size + // stack: updated_leaf_ptr, common_len, common_key, value_ptr, retdest + PUSH @MPT_NODE_LEAF %append_to_trie_data + SWAP1 %append_to_trie_data // append common_len + SWAP1 %append_to_trie_data // append common_key + SWAP1 %append_to_trie_data // append value_ptr + // stack: updated_leaf_ptr, retdest + SWAP1 + JUMP +mpt_insert_leaf_not_exact_match: + // stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest + // %get_trie_data_size PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm index 19cad943..a3ff7f38 100644 --- a/evm/src/cpu/kernel/asm/mpt/util.asm +++ b/evm/src/cpu/kernel/asm/mpt/util.asm @@ -72,3 +72,88 @@ POP // stack: first_nibble, num_nibbles, key %endmacro + +// Split off the common prefix among two key parts. Roughly equivalent to +// def split_common_prefix(len_1, key_1, len_2, key_2): +// bits_1 = len_1 * 4 +// bits_2 = len_2 * 4 +// len_common = 0 +// key_common = 0 +// while True: +// if bits_1 * bits_2 == 0: +// break +// first_nib_1 = (key_1 >> (bits_1 - 4)) & 0xF +// first_nib_2 = (key_2 >> (bits_2 - 4)) & 0xF +// if first_nib_1 != first_nib_2: +// break +// len_common += 1 +// key_common = key_common * 16 + first_nib_1 +// bits_1 -= 4 +// bits_2 -= 4 +// key_1 -= (first_nib_1 << bits_1) +// key_2 -= (first_nib_2 << bits_2) +// len_1 = bits_1 // 4 +// len_2 = bits_2 // 4 +// return (len_common, key_common, len_1, key_1, len_2, key_2) +%macro split_common_prefix + // stack: len_1, key_1, len_2, key_2 + %mul_const(4) + SWAP2 %mul_const(4) SWAP2 + // stack: bits_1, key_1, bits_2, key_2 + PUSH 0 + PUSH 0 + +%%loop: + // stack: len_common, key_common, bits_1, key_1, bits_2, key_2 + + // if bits_1 * bits_2 == 0: break + DUP3 DUP6 MUL ISZERO %jumpi(%%return) + + // first_nib_2 = (key_2 >> (bits_2 - 4)) & 0xF + DUP6 DUP6 %sub_const(4) SHR %and_const(0xF) + // first_nib_1 = (key_1 >> (bits_1 - 4)) & 0xF + DUP5 DUP5 %sub_const(4) SHR %and_const(0xF) + // stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2 + + // if first_nib_1 != first_nib_2: break + DUP2 DUP2 SUB %jumpi(%%return_with_first_nibs) + + // len_common += 1 + SWAP2 %increment SWAP2 + + // key_common = key_common * 16 + first_nib_1 + SWAP3 + %mul_const(16) + DUP4 ADD + SWAP3 + // stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2 + + // bits_1 -= 4 + SWAP4 %sub_const(4) SWAP4 + // bits_2 -= 4 + SWAP6 %sub_const(4) SWAP6 + // stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2 + + // key_1 -= (first_nib_1 << bits_1) + DUP5 SHL + // stack: first_nib_1 << bits_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2 + DUP6 SUB + // stack: key_1, first_nib_2, len_common, key_common, bits_1, key_1_old, bits_2, key_2 + SWAP5 POP + // stack: first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2 + + // key_2 -= (first_nib_2 << bits_2) + DUP6 SHL + // stack: first_nib_2 << bits_2, len_common, key_common, bits_1, key_1, bits_2, key_2 + DUP7 SUB + // stack: key_2, len_common, key_common, bits_1, key_1, bits_2, key_2_old + SWAP6 POP + // stack: len_common, key_common, bits_1, key_1, bits_2, key_2 + + %jump(%%loop) +%%return_with_first_nibs: + // stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2 + %pop2 +%%return: + // stack: len_common, key_common, len_1, key_1, len_2, key_2 +%endmacro diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs index 7aeb4a1a..11927d52 100644 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -23,7 +23,6 @@ fn mpt_insert_empty() -> Result<()> { } #[test] -#[ignore] // TODO: Enable when mpt_insert_leaf is done. fn mpt_insert_leaf_same_key() -> Result<()> { let key = Nibbles { count: 3, @@ -142,7 +141,12 @@ fn test_state_trie(state_trie: PartialTrie, insert: InsertEntry) -> Result<()> { interpreter.push(insert.nibbles.count.into()); // num_nibbles interpreter.run()?; - assert_eq!(interpreter.stack().len(), 0); + assert_eq!( + interpreter.stack().len(), + 0, + "Expected empty stack after insert, found {:?}", + interpreter.stack() + ); // Now, execute mpt_hash_state_trie. interpreter.offset = mpt_hash_state_trie; @@ -152,7 +156,7 @@ fn test_state_trie(state_trie: PartialTrie, insert: InsertEntry) -> Result<()> { assert_eq!( interpreter.stack().len(), 1, - "Expected 1 item on stack, found {:?}", + "Expected 1 item on stack after hashing, found {:?}", interpreter.stack() ); let hash = H256::from_uint(&interpreter.stack()[0]); From 33dba3a23dfcf2fbfc07bd08c6a7db3a97630c69 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 9 Oct 2022 20:18:16 -0700 Subject: [PATCH 07/21] Insertion optimization for leaf case --- evm/src/cpu/kernel/asm/mpt/insert.asm | 31 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/evm/src/cpu/kernel/asm/mpt/insert.asm b/evm/src/cpu/kernel/asm/mpt/insert.asm index cc748e79..65c18428 100644 --- a/evm/src/cpu/kernel/asm/mpt/insert.asm +++ b/evm/src/cpu/kernel/asm/mpt/insert.asm @@ -121,14 +121,25 @@ mpt_insert_leaf: // stack: node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest DUP4 %mload_trie_data // stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest - // TODO: Maybe skip %split_common_prefix if lengths & keys exactly match. + + // If the keys match, i.e. node_len == insert_len && node_key == insert_key, + // then we're simply replacing the leaf node's value. Since this is a common + // case, it's best to detect it early. Calling %split_common_prefix could be + // expensive as leaf keys tend to be long. + DUP1 DUP4 EQ // node_len == insert_len + DUP3 DUP6 EQ // node_key == insert_key + MUL // Cheaper than AND + // stack: key_match, node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest + %jumpi(mpt_insert_leaf_keys_match) + %split_common_prefix - // stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest - DUP3 DUP6 ADD %jumpi(mpt_insert_leaf_not_exact_match) - // If we got here, the node key exactly matches the insert key, so we will - // keep the same leaf node structure and just replace its value. - %stack (common_len, common_key, node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr) - -> (common_len, common_key, value_ptr) + PANIC // TODO + +mpt_insert_leaf_keys_match: + // The keys match exactly, so we simply create a new leaf node with the new value.xs + // stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest + %stack (node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr) + -> (node_len, node_key, value_ptr) // stack: common_len, common_key, value_ptr, retdest %get_trie_data_size // stack: updated_leaf_ptr, common_len, common_key, value_ptr, retdest @@ -136,10 +147,6 @@ mpt_insert_leaf: SWAP1 %append_to_trie_data // append common_len SWAP1 %append_to_trie_data // append common_key SWAP1 %append_to_trie_data // append value_ptr - // stack: updated_leaf_ptr, retdest + // stack: updated_leaf_ptr, retdestx SWAP1 JUMP -mpt_insert_leaf_not_exact_match: - // stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest - // %get_trie_data_size - PANIC // TODO From cad0473e1d76dad91aa8c1ae215d928c481b4930 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 9 Oct 2022 21:37:46 -0700 Subject: [PATCH 08/21] More MPT insert logic --- evm/src/cpu/kernel/aggregator.rs | 2 + evm/src/cpu/kernel/asm/mpt/insert.asm | 47 +---- .../cpu/kernel/asm/mpt/insert_extension.asm | 5 + evm/src/cpu/kernel/asm/mpt/insert_leaf.asm | 167 ++++++++++++++++++ evm/src/cpu/kernel/asm/mpt/util.asm | 10 +- evm/src/cpu/kernel/asm/util/basic_macros.asm | 6 + evm/src/cpu/kernel/tests/mpt/insert.rs | 24 ++- 7 files changed, 212 insertions(+), 49 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/mpt/insert_extension.asm create mode 100644 evm/src/cpu/kernel/asm/mpt/insert_leaf.asm diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 6fb2231e..032338b7 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -44,6 +44,8 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/mpt/hash_trie_specific.asm"), include_str!("asm/mpt/hex_prefix.asm"), include_str!("asm/mpt/insert.asm"), + include_str!("asm/mpt/insert_extension.asm"), + include_str!("asm/mpt/insert_leaf.asm"), include_str!("asm/mpt/insert_trie_specific.asm"), include_str!("asm/mpt/load.asm"), include_str!("asm/mpt/read.asm"), diff --git a/evm/src/cpu/kernel/asm/mpt/insert.asm b/evm/src/cpu/kernel/asm/mpt/insert.asm index 65c18428..2830d376 100644 --- a/evm/src/cpu/kernel/asm/mpt/insert.asm +++ b/evm/src/cpu/kernel/asm/mpt/insert.asm @@ -98,55 +98,10 @@ mpt_insert_branch_nonterminal: child_ptr_ptr, updated_branch_ptr) %mload_trie_data // Deref child_ptr_ptr, giving child_ptr %jump(mpt_insert) + mpt_insert_branch_nonterminal_after_recursion: // stack: updated_child_ptr, child_ptr_ptr, updated_branch_ptr, retdest SWAP1 %mstore_trie_data // Store the pointer to the updated child. // stack: updated_branch_ptr, retdest SWAP1 JUMP - -mpt_insert_extension: - // stack: node_type, node_payload_ptr, insert_len, insert_key, value_ptr, retdest - POP - // stack: node_payload_ptr, insert_len, insert_key, value_ptr, retdest - PANIC // TODO - -mpt_insert_leaf: - // stack: node_type, node_payload_ptr, insert_len, insert_key, value_ptr, retdest - POP - // stack: node_payload_ptr, insert_len, insert_key, value_ptr, retdest - %stack (node_payload_ptr, insert_len, insert_key) -> (insert_len, insert_key, node_payload_ptr) - // stack: insert_len, insert_key, node_payload_ptr, value_ptr, retdest - DUP3 %increment %mload_trie_data - // stack: node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest - DUP4 %mload_trie_data - // stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest - - // If the keys match, i.e. node_len == insert_len && node_key == insert_key, - // then we're simply replacing the leaf node's value. Since this is a common - // case, it's best to detect it early. Calling %split_common_prefix could be - // expensive as leaf keys tend to be long. - DUP1 DUP4 EQ // node_len == insert_len - DUP3 DUP6 EQ // node_key == insert_key - MUL // Cheaper than AND - // stack: key_match, node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest - %jumpi(mpt_insert_leaf_keys_match) - - %split_common_prefix - PANIC // TODO - -mpt_insert_leaf_keys_match: - // The keys match exactly, so we simply create a new leaf node with the new value.xs - // stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr, retdest - %stack (node_len, node_key, insert_len, insert_key, node_payload_ptr, value_ptr) - -> (node_len, node_key, value_ptr) - // stack: common_len, common_key, value_ptr, retdest - %get_trie_data_size - // stack: updated_leaf_ptr, common_len, common_key, value_ptr, retdest - PUSH @MPT_NODE_LEAF %append_to_trie_data - SWAP1 %append_to_trie_data // append common_len - SWAP1 %append_to_trie_data // append common_key - SWAP1 %append_to_trie_data // append value_ptr - // stack: updated_leaf_ptr, retdestx - SWAP1 - JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/insert_extension.asm b/evm/src/cpu/kernel/asm/mpt/insert_extension.asm new file mode 100644 index 00000000..36458165 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/insert_extension.asm @@ -0,0 +1,5 @@ +global mpt_insert_extension: + // stack: node_type, node_payload_ptr, insert_len, insert_key, value_ptr, retdest + POP + // stack: node_payload_ptr, insert_len, insert_key, value_ptr, retdest + PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm b/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm new file mode 100644 index 00000000..b82653f2 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm @@ -0,0 +1,167 @@ +// The high-level logic can be expressed with the following pseudocode: +// +// if node_len == insert_len && node_key == insert_key: +// return Leaf[node_key, insert_value] +// +// common_len, common_key, node_len, node_key, insert_len, insert_key = +// consume_common_prefix(node_len, node_key, insert_len, insert_key) +// +// branch = [MPT_TYPE_BRANCH] + [0] * 17 +// +// if node_len > 0: +// node_key_first, node_len, node_key = split_first_nibble(node_len, node_key) +// branch[node_key_first + 1] = Leaf[node_len, node_key, node_value] +// else: +// branch[17] = node_value +// +// if insert_len > 0: +// insert_key_first, insert_len, insert_key = split_first_nibble(insert_len, insert_key) +// branch[insert_key_first + 1] = Leaf[insert_len, insert_key, insert_value] +// else: +// branch[17] = insert_value +// +// if common_len > 0: +// return Extension[common_key, branch] +// else: +// return branch + +global mpt_insert_leaf: + // stack: node_type, node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest + POP + // stack: node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest + %stack (node_payload_ptr, insert_len, insert_key) -> (insert_len, insert_key, node_payload_ptr) + // stack: insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest + DUP3 %increment %mload_trie_data + // stack: node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest + DUP4 %mload_trie_data + // stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest + + // If the keys match, i.e. node_len == insert_len && node_key == insert_key, + // then we're simply replacing the leaf node's value. Since this is a common + // case, it's best to detect it early. Calling %split_common_prefix could be + // expensive as leaf keys tend to be long. + DUP1 DUP4 EQ // node_len == insert_len + DUP3 DUP6 EQ // node_key == insert_key + MUL // Cheaper than AND + // stack: keys_match, node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest + %jumpi(keys_match) + + // Replace node_payload_ptr with node_value, which is node_payload[2]. + // stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest + SWAP4 + %add_const(2) + %mload_trie_data + SWAP4 + // stack: node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + + // Split off any common prefix between the node key and the inserted key. + %split_common_prefix + // stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + + // For the remaining cases, we will need a new branch node since the two keys diverge. + // We may also need an extension node above it (if common_len > 0); we will handle that later. + // For now, we allocate the branch node, initially with no children or value. + %get_trie_data_size + PUSH @MPT_NODE_BRANCH %append_to_trie_data + %rep 17 + PUSH 0 %append_to_trie_data + %endrep + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + + // Here, we branch based on whether each key continues beyond the common + // prefix, starting with the node key. + DUP4 // node_len + %jumpi(node_key_continues) + + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + // branch[17] = node_value_ptr + DUP8 // node_value_ptr + DUP2 // branch_ptr + %add_const(17) + %mstore_trie_data + +finished_processing_node_value: + DUP6 // insert_len + %jumpi(insert_key_continues) + + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + // branch[17] = insert_value_ptr + DUP9 // insert_value_ptr + DUP2 // branch_ptr + %add_const(17) + %mstore_trie_data + +finished_processing_insert_value: + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + // If common_len > 0, we need to add an extension node. + DUP2 %jumpi(extension_for_common_key) + // Otherwise, we simply return our branch node. + SWAP8 + %pop8 + // stack: branch_ptr, retdest + SWAP1 + JUMP + +extension_for_common_key: + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + PANIC // TODO + +node_key_continues: + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + // branch[node_key_first + 1] = Leaf[node_len, node_key, node_value] + DUP5 DUP5 + // stack: node_len, node_key, branch_ptr, ... + %split_first_nibble + // stack: node_key_first, node_len, node_key, branch_ptr, ... + %get_trie_data_size + // stack: leaf_ptr, node_key_first, node_len, node_key, branch_ptr, ... + SWAP1 + DUP5 // branch_ptr + %increment // Skip over node type field + ADD // Add node_key_first + %mstore_trie_data + // stack: node_len, node_key, branch_ptr, ... + PUSH @MPT_NODE_LEAF %append_to_trie_data + %append_to_trie_data // Append node_len to our leaf node + %append_to_trie_data // Append node_key to our leaf node + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + DUP8 %append_to_trie_data // Append node_value_ptr to our leaf node + %jump(finished_processing_node_value) + +insert_key_continues: + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + // branch[insert_key_first + 1] = Leaf[insert_len, insert_key, insert_value] + DUP7 DUP7 + // stack: insert_len, insert_key, branch_ptr, ... + %split_first_nibble + // stack: insert_key_first, insert_len, insert_key, branch_ptr, ... + %get_trie_data_size + // stack: leaf_ptr, insert_key_first, insert_len, insert_key, branch_ptr, ... + SWAP1 + DUP5 // branch_ptr + %increment // Skip over node type field + ADD // Add insert_key_first + %mstore_trie_data + // stack: insert_len, insert_key, branch_ptr, ... + PUSH @MPT_NODE_LEAF %append_to_trie_data + %append_to_trie_data // Append insert_len to our leaf node + %append_to_trie_data // Append insert_key to our leaf node + // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + DUP9 %append_to_trie_data // Append insert_value_ptr to our leaf node + %jump(finished_processing_insert_value) + +keys_match: + // The keys match exactly, so we simply create a new leaf node with the new value.xs + // stack: node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr, retdest + %stack (node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr) + -> (node_len, node_key, insert_value_ptr) + // stack: common_len, common_key, insert_value_ptr, retdest + %get_trie_data_size + // stack: updated_leaf_ptr, common_len, common_key, insert_value_ptr, retdest + PUSH @MPT_NODE_LEAF %append_to_trie_data + SWAP1 %append_to_trie_data // Append common_len to our leaf node + SWAP1 %append_to_trie_data // Append common_key to our leaf node + SWAP1 %append_to_trie_data // Append insert_value_ptr to our leaf node + // stack: updated_leaf_ptr, retdestx + SWAP1 + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm index a3ff7f38..0faa72f4 100644 --- a/evm/src/cpu/kernel/asm/mpt/util.asm +++ b/evm/src/cpu/kernel/asm/mpt/util.asm @@ -73,7 +73,12 @@ // stack: first_nibble, num_nibbles, key %endmacro -// Split off the common prefix among two key parts. Roughly equivalent to +// Split off the common prefix among two key parts. +// +// Pre stack: len_1, key_1, len_2, key_2 +// Post stack: len_common, key_common, len_1, key_1, len_2, key_2 +// +// Roughly equivalent to // def split_common_prefix(len_1, key_1, len_2, key_2): // bits_1 = len_1 * 4 // bits_2 = len_2 * 4 @@ -155,5 +160,8 @@ // stack: first_nib_1, first_nib_2, len_common, key_common, bits_1, key_1, bits_2, key_2 %pop2 %%return: + // stack: len_common, key_common, bits_1, key_1, bits_2, key_2 + SWAP2 %div_const(4) SWAP2 // bits_1 -> len_1 (in nibbles) + 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 diff --git a/evm/src/cpu/kernel/asm/util/basic_macros.asm b/evm/src/cpu/kernel/asm/util/basic_macros.asm index bba2a2c1..02a2c807 100644 --- a/evm/src/cpu/kernel/asm/util/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/util/basic_macros.asm @@ -44,6 +44,12 @@ %endrep %endmacro +%macro pop8 + %rep 8 + POP + %endrep +%endmacro + %macro and_const(c) // stack: input, ... PUSH $c diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs index 11927d52..1ac7974f 100644 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -40,6 +40,26 @@ fn mpt_insert_leaf_same_key() -> Result<()> { test_state_trie(state_trie, insert) } +#[test] +fn mpt_insert_leaf_nonoverlapping_key() -> Result<()> { + let state_trie = PartialTrie::Leaf { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + value: test_account_1_rlp(), + }; + let insert = InsertEntry { + nibbles: Nibbles { + count: 3, + packed: 0x123.into(), + }, + v: test_account_2_rlp(), + }; + + test_state_trie(state_trie, insert) +} + #[test] fn mpt_insert_branch_replacing_empty_child() -> Result<()> { let children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); @@ -76,7 +96,6 @@ fn mpt_insert_extension_to_leaf_same_key() -> Result<()> { } #[test] -#[ignore] // TODO: Enable when mpt_insert_leaf is done. fn mpt_insert_branch_to_leaf_same_key() -> Result<()> { let leaf = PartialTrie::Leaf { nibbles: Nibbles { @@ -161,7 +180,8 @@ fn test_state_trie(state_trie: PartialTrie, insert: InsertEntry) -> Result<()> { ); let hash = H256::from_uint(&interpreter.stack()[0]); - let expected_state_trie_hash = apply_insert(state_trie, insert).calc_hash(); + let updated_trie = apply_insert(state_trie, insert); + let expected_state_trie_hash = updated_trie.calc_hash(); assert_eq!(hash, expected_state_trie_hash); Ok(()) From 50002df8e49cd1675ae13d20b126b507d81196d6 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 10 Oct 2022 10:42:02 -0700 Subject: [PATCH 09/21] MPT insert into leaf, overlapping keys case --- evm/src/cpu/kernel/asm/mpt/insert_leaf.asm | 16 +++++++++++++-- evm/src/cpu/kernel/tests/mpt/insert.rs | 24 ++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm b/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm index b82653f2..eeb7612a 100644 --- a/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm +++ b/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm @@ -21,7 +21,7 @@ // branch[17] = insert_value // // if common_len > 0: -// return Extension[common_key, branch] +// return Extension[common_len, common_key, branch] // else: // return branch @@ -104,7 +104,19 @@ finished_processing_insert_value: extension_for_common_key: // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest - PANIC // TODO + // return Extension[common_len, common_key, branch] + %get_trie_data_size + // stack: extension_ptr, branch_ptr, common_len, common_key, ... + PUSH @MPT_NODE_EXTENSION %append_to_trie_data + SWAP2 %append_to_trie_data // Append common_len to our node + SWAP2 %append_to_trie_data // Append common_key to our node + SWAP1 %append_to_trie_data // Append branch_ptr to our node + // stack: extension_ptr, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest + SWAP6 + %pop6 + // stack: extension_ptr, retdest + SWAP1 + JUMP node_key_continues: // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs index 1ac7974f..872ef4af 100644 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -23,7 +23,7 @@ fn mpt_insert_empty() -> Result<()> { } #[test] -fn mpt_insert_leaf_same_key() -> Result<()> { +fn mpt_insert_leaf_identical_keys() -> Result<()> { let key = Nibbles { count: 3, packed: 0xABC.into(), @@ -41,7 +41,7 @@ fn mpt_insert_leaf_same_key() -> Result<()> { } #[test] -fn mpt_insert_leaf_nonoverlapping_key() -> Result<()> { +fn mpt_insert_leaf_nonoverlapping_keys() -> Result<()> { let state_trie = PartialTrie::Leaf { nibbles: Nibbles { count: 3, @@ -60,6 +60,26 @@ fn mpt_insert_leaf_nonoverlapping_key() -> Result<()> { test_state_trie(state_trie, insert) } +#[test] +fn mpt_insert_leaf_overlapping_keys() -> Result<()> { + let state_trie = PartialTrie::Leaf { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + value: test_account_1_rlp(), + }; + let insert = InsertEntry { + nibbles: Nibbles { + count: 3, + packed: 0xADE.into(), + }, + v: test_account_2_rlp(), + }; + + test_state_trie(state_trie, insert) +} + #[test] fn mpt_insert_branch_replacing_empty_child() -> Result<()> { let children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); From caf928b11efacf62989e20a9776e913697022991 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 10 Oct 2022 11:14:16 -0700 Subject: [PATCH 10/21] MPT logic for inserts into extension nodes --- .../cpu/kernel/asm/mpt/insert_extension.asm | 202 +++++++++++++++++- evm/src/cpu/kernel/asm/mpt/insert_leaf.asm | 94 ++++---- evm/src/cpu/kernel/tests/mpt/insert.rs | 65 +++++- 3 files changed, 310 insertions(+), 51 deletions(-) diff --git a/evm/src/cpu/kernel/asm/mpt/insert_extension.asm b/evm/src/cpu/kernel/asm/mpt/insert_extension.asm index 36458165..3ead805b 100644 --- a/evm/src/cpu/kernel/asm/mpt/insert_extension.asm +++ b/evm/src/cpu/kernel/asm/mpt/insert_extension.asm @@ -1,5 +1,201 @@ +/* +Insert into an extension node. +The high-level logic can be expressed with the following pseudocode: + +common_len, common_key, node_len, node_key, insert_len, insert_key = + split_common_prefix(node_len, node_key, insert_len, insert_key) + +if node_len == 0: + new_node = insert(node_child, insert_len, insert_key, insert_value) +else: + new_node = [MPT_TYPE_BRANCH] + [0] * 17 + + // Process the node's child. + if node_len > 1: + // The node key continues with multiple nibbles left, so we can't place + // node_child directly in the branch, but need an extension for it. + node_key_first, node_len, node_key = split_first_nibble(node_len, node_key) + new_node[node_key_first + 1] = [MPT_TYPE_EXTENSION, node_len, node_key, node_child] + else: + // The remaining node_key is a single nibble, so we can place node_child directly in the branch. + new_node[node_key + 1] = node_child + + // Process the inserted entry. + if insert_len > 0: + // The insert key continues. Add a leaf node for it. + insert_key_first, insert_len, insert_key = split_first_nibble(insert_len, insert_key) + new_node[insert_key_first + 1] = [MPT_TYPE_LEAF, insert_len, insert_key, insert_value] + else: + new_node[17] = insert_value + +if common_len > 0: + return [MPT_TYPE_EXTENSION, common_len, common_key, new_node] +else: + return new_node +*/ + global mpt_insert_extension: - // stack: node_type, node_payload_ptr, insert_len, insert_key, value_ptr, retdest + // stack: node_type, node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest POP - // stack: node_payload_ptr, insert_len, insert_key, value_ptr, retdest - PANIC // TODO + // stack: node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest + + // We start by loading the extension node's three fields: node_len, node_key, node_child_ptr + DUP1 %add_const(2) %mload_trie_data + // stack: node_child_ptr, node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest + %stack (node_child_ptr, node_payload_ptr, insert_len, insert_key) + -> (node_payload_ptr, insert_len, insert_key, node_child_ptr) + // stack: node_payload_ptr, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + DUP1 %increment %mload_trie_data + // stack: node_key, node_payload_ptr, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + SWAP1 %mload_trie_data + // stack: node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + + // Next, we split off any key prefix which is common to the node's key and the inserted key. + %split_common_prefix + // stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + + // Now we branch based on whether the node key continues beyond the common prefix. + DUP3 %jumpi(node_key_continues) + + // The node key does not continue. In this case we recurse. Pseudocode: + // new_node = insert(node_child, insert_len, insert_key, insert_value) + // and then proceed to maybe_add_extension_for_common_key. + // stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + PUSH maybe_add_extension_for_common_key + DUP9 // insert_value_ptr + DUP8 // insert_key + DUP8 // insert_len + DUP11 // node_child_ptr + %jump(mpt_insert) + +node_key_continues: + // stack: common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + // Allocate new_node, a branch node which is initially empty + // Pseudocode: new_node = [MPT_TYPE_BRANCH] + [0] * 17 + %get_trie_data_size // pointer to the branch node we're about to create + PUSH @MPT_NODE_BRANCH %append_to_trie_data + %rep 17 + PUSH 0 %append_to_trie_data + %endrep + +process_node_child: + // stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + // We want to check if node_len > 1. We already know node_len > 0 since we're in node_key_continues, + // so it suffices to check 1 - node_len != 0 + DUP4 // node_len + PUSH 1 SUB + %jumpi(node_key_continues_multiple_nibbles) + + // If we got here, node_len = 1. + // Pseudocode: new_node[node_key + 1] = node_child + // stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + DUP8 // node_child_ptr + DUP2 // new_node_ptr + %increment + DUP7 // node_key + ADD + %mstore_trie_data + // stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + %jump(process_inserted_entry) + +node_key_continues_multiple_nibbles: + // stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + // Pseudocode: node_key_first, node_len, node_key = split_first_nibble(node_len, node_key) + // To minimize stack manipulation, we won't actually mutate the node_len, node_key variables in our stack. + // Instead we will duplicate them, and leave the old ones alone; they won't be used. + DUP5 DUP5 + // stack: node_len, node_key, new_node_ptr, ... + %split_first_nibble + // stack: node_key_first, node_len, node_key, new_node_ptr, ... + + // Pseudocode: new_node[node_key_first + 1] = [MPT_TYPE_EXTENSION, node_len, node_key, node_child] + %get_trie_data_size // pointer to the extension node we're about to create + // stack: ext_node_ptr, node_key_first, node_len, node_key, new_node_ptr, ... + PUSH @MPT_NODE_EXTENSION %append_to_trie_data + // stack: ext_node_ptr, node_key_first, node_len, node_key, new_node_ptr, ... + SWAP2 %append_to_trie_data // Append node_len + // stack: node_key_first, ext_node_ptr, node_key, new_node_ptr, ... + SWAP2 %append_to_trie_data // Append node_key + // stack: ext_node_ptr, node_key_first, new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + DUP10 %append_to_trie_data // Append node_child_ptr + + SWAP1 + // stack: node_key_first, ext_node_ptr, new_node_ptr, ... + DUP3 // new_node_ptr + ADD + %increment + // stack: new_node_ptr + node_key_first + 1, ext_node_ptr, new_node_ptr, ... + %mstore_trie_data + %jump(process_inserted_entry) + +process_inserted_entry: + // stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + DUP6 // insert_len + %jumpi(insert_key_continues) + + // If we got here, insert_len = 0, so we store the inserted value directly in our new branch node. + // Pseudocode: new_node[17] = insert_value + DUP9 // insert_value_ptr + DUP2 // new_node_ptr + %add_const(17) + %mstore_trie_data + %jump(maybe_add_extension_for_common_key) + +insert_key_continues: + // stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + // Pseudocode: insert_key_first, insert_len, insert_key = split_first_nibble(insert_len, insert_key) + // To minimize stack manipulation, we won't actually mutate the node_len, node_key variables in our stack. + // Instead we will duplicate them, and leave the old ones alone; they won't be used. + DUP7 DUP7 + // stack: insert_len, insert_key, new_node_ptr, ... + %split_first_nibble + // stack: insert_key_first, insert_len, insert_key, new_node_ptr, ... + + // Pseudocode: new_node[insert_key_first + 1] = [MPT_TYPE_LEAF, insert_len, insert_key, insert_value] + %get_trie_data_size // pointer to the leaf node we're about to create + // stack: leaf_node_ptr, insert_key_first, insert_len, insert_key, new_node_ptr, ... + PUSH @MPT_NODE_LEAF %append_to_trie_data + // stack: leaf_node_ptr, insert_key_first, insert_len, insert_key, new_node_ptr, ... + SWAP2 %append_to_trie_data // Append insert_len + // stack: insert_key_first, leaf_node_ptr, insert_key, new_node_ptr, ... + SWAP2 %append_to_trie_data // Append insert_key + // stack: leaf_node_ptr, insert_key_first, new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + DUP11 %append_to_trie_data // Append insert_value_ptr + + SWAP1 + // stack: insert_key_first, leaf_node_ptr, new_node_ptr, ... + DUP3 // new_node_ptr + ADD + %increment + // stack: new_node_ptr + insert_key_first + 1, leaf_node_ptr, new_node_ptr, ... + %mstore_trie_data + %jump(maybe_add_extension_for_common_key) + +maybe_add_extension_for_common_key: + // stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + // If common_len > 0, we need to add an extension node. + DUP2 %jumpi(add_extension_for_common_key) + // Otherwise, we simply return new_node_ptr. + SWAP8 + %pop8 + // stack: new_node_ptr, retdest + SWAP1 + JUMP + +add_extension_for_common_key: + // stack: new_node_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + // Pseudocode: return [MPT_TYPE_EXTENSION, common_len, common_key, new_node] + %get_trie_data_size // pointer to the extension node we're about to create + // stack: extension_ptr, new_node_ptr, common_len, common_key, ... + PUSH @MPT_NODE_EXTENSION %append_to_trie_data + SWAP2 %append_to_trie_data // Append common_len to our node + // stack: new_node_ptr, extension_ptr, common_key, ... + SWAP2 %append_to_trie_data // Append common_key to our node + // stack: extension_ptr, new_node_ptr, ... + SWAP1 %append_to_trie_data // Append new_node_ptr to our node + // stack: extension_ptr, node_len, node_key, insert_len, insert_key, node_child_ptr, insert_value_ptr, retdest + SWAP6 + %pop6 + // stack: extension_ptr, retdest + SWAP1 + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm b/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm index eeb7612a..6afe2f14 100644 --- a/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm +++ b/evm/src/cpu/kernel/asm/mpt/insert_leaf.asm @@ -1,29 +1,35 @@ -// The high-level logic can be expressed with the following pseudocode: -// -// if node_len == insert_len && node_key == insert_key: -// return Leaf[node_key, insert_value] -// -// common_len, common_key, node_len, node_key, insert_len, insert_key = -// consume_common_prefix(node_len, node_key, insert_len, insert_key) -// -// branch = [MPT_TYPE_BRANCH] + [0] * 17 -// -// if node_len > 0: -// node_key_first, node_len, node_key = split_first_nibble(node_len, node_key) -// branch[node_key_first + 1] = Leaf[node_len, node_key, node_value] -// else: -// branch[17] = node_value -// -// if insert_len > 0: -// insert_key_first, insert_len, insert_key = split_first_nibble(insert_len, insert_key) -// branch[insert_key_first + 1] = Leaf[insert_len, insert_key, insert_value] -// else: -// branch[17] = insert_value -// -// if common_len > 0: -// return Extension[common_len, common_key, branch] -// else: -// return branch +/* +Insert into a leaf node. +The high-level logic can be expressed with the following pseudocode: + +if node_len == insert_len && node_key == insert_key: + return Leaf[node_key, insert_value] + +common_len, common_key, node_len, node_key, insert_len, insert_key = + split_common_prefix(node_len, node_key, insert_len, insert_key) + +branch = [MPT_TYPE_BRANCH] + [0] * 17 + +// Process the node's entry. +if node_len > 0: + node_key_first, node_len, node_key = split_first_nibble(node_len, node_key) + branch[node_key_first + 1] = [MPT_TYPE_LEAF, node_len, node_key, node_value] +else: + branch[17] = node_value + +// Process the inserted entry. +if insert_len > 0: + insert_key_first, insert_len, insert_key = split_first_nibble(insert_len, insert_key) + branch[insert_key_first + 1] = [MPT_TYPE_LEAF, insert_len, insert_key, insert_value] +else: + branch[17] = insert_value + +// Add an extension node if there is a common prefix. +if common_len > 0: + return [MPT_TYPE_EXTENSION, common_len, common_key, branch] +else: + return branch +*/ global mpt_insert_leaf: // stack: node_type, node_payload_ptr, insert_len, insert_key, insert_value_ptr, retdest @@ -61,15 +67,17 @@ global mpt_insert_leaf: // For the remaining cases, we will need a new branch node since the two keys diverge. // We may also need an extension node above it (if common_len > 0); we will handle that later. // For now, we allocate the branch node, initially with no children or value. - %get_trie_data_size + %get_trie_data_size // pointer to the branch node we're about to create PUSH @MPT_NODE_BRANCH %append_to_trie_data %rep 17 PUSH 0 %append_to_trie_data %endrep // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest - // Here, we branch based on whether each key continues beyond the common + // Now, we branch based on whether each key continues beyond the common // prefix, starting with the node key. + +process_node_entry: DUP4 // node_len %jumpi(node_key_continues) @@ -80,7 +88,7 @@ global mpt_insert_leaf: %add_const(17) %mstore_trie_data -finished_processing_node_value: +process_inserted_entry: DUP6 // insert_len %jumpi(insert_key_continues) @@ -91,25 +99,27 @@ finished_processing_node_value: %add_const(17) %mstore_trie_data -finished_processing_insert_value: +maybe_add_extension_for_common_key: // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest // If common_len > 0, we need to add an extension node. - DUP2 %jumpi(extension_for_common_key) - // Otherwise, we simply return our branch node. + DUP2 %jumpi(add_extension_for_common_key) + // Otherwise, we simply return branch_ptr. SWAP8 %pop8 // stack: branch_ptr, retdest SWAP1 JUMP -extension_for_common_key: +add_extension_for_common_key: // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest - // return Extension[common_len, common_key, branch] - %get_trie_data_size + // Pseudocode: return [MPT_TYPE_EXTENSION, common_len, common_key, branch] + %get_trie_data_size // pointer to the extension node we're about to create // stack: extension_ptr, branch_ptr, common_len, common_key, ... PUSH @MPT_NODE_EXTENSION %append_to_trie_data SWAP2 %append_to_trie_data // Append common_len to our node + // stack: branch_ptr, extension_ptr, common_key, ... SWAP2 %append_to_trie_data // Append common_key to our node + // stack: extension_ptr, branch_ptr, ... SWAP1 %append_to_trie_data // Append branch_ptr to our node // stack: extension_ptr, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest SWAP6 @@ -121,11 +131,13 @@ extension_for_common_key: node_key_continues: // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest // branch[node_key_first + 1] = Leaf[node_len, node_key, node_value] + // To minimize stack manipulation, we won't actually mutate the node_len, node_key variables in our stack. + // Instead we will duplicate them, and leave the old ones alone; they won't be used. DUP5 DUP5 // stack: node_len, node_key, branch_ptr, ... %split_first_nibble // stack: node_key_first, node_len, node_key, branch_ptr, ... - %get_trie_data_size + %get_trie_data_size // pointer to the leaf node we're about to create // stack: leaf_ptr, node_key_first, node_len, node_key, branch_ptr, ... SWAP1 DUP5 // branch_ptr @@ -138,16 +150,18 @@ node_key_continues: %append_to_trie_data // Append node_key to our leaf node // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest DUP8 %append_to_trie_data // Append node_value_ptr to our leaf node - %jump(finished_processing_node_value) + %jump(process_inserted_entry) insert_key_continues: // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest // branch[insert_key_first + 1] = Leaf[insert_len, insert_key, insert_value] + // To minimize stack manipulation, we won't actually mutate the insert_len, insert_key variables in our stack. + // Instead we will duplicate them, and leave the old ones alone; they won't be used. DUP7 DUP7 // stack: insert_len, insert_key, branch_ptr, ... %split_first_nibble // stack: insert_key_first, insert_len, insert_key, branch_ptr, ... - %get_trie_data_size + %get_trie_data_size // pointer to the leaf node we're about to create // stack: leaf_ptr, insert_key_first, insert_len, insert_key, branch_ptr, ... SWAP1 DUP5 // branch_ptr @@ -160,7 +174,7 @@ insert_key_continues: %append_to_trie_data // Append insert_key to our leaf node // stack: branch_ptr, common_len, common_key, node_len, node_key, insert_len, insert_key, node_value_ptr, insert_value_ptr, retdest DUP9 %append_to_trie_data // Append insert_value_ptr to our leaf node - %jump(finished_processing_insert_value) + %jump(maybe_add_extension_for_common_key) keys_match: // The keys match exactly, so we simply create a new leaf node with the new value.xs @@ -168,7 +182,7 @@ keys_match: %stack (node_len, node_key, insert_len, insert_key, node_payload_ptr, insert_value_ptr) -> (node_len, node_key, insert_value_ptr) // stack: common_len, common_key, insert_value_ptr, retdest - %get_trie_data_size + %get_trie_data_size // pointer to the leaf node we're about to create // stack: updated_leaf_ptr, common_len, common_key, insert_value_ptr, retdest PUSH @MPT_NODE_LEAF %append_to_trie_data SWAP1 %append_to_trie_data // Append common_len to our leaf node diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs index 872ef4af..103bdd2e 100644 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -6,7 +6,7 @@ use ethereum_types::{BigEndianHash, H256}; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; -use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1_rlp, test_account_2_rlp}; +use crate::cpu::kernel::tests::mpt::{test_account_1_rlp, test_account_2_rlp}; use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; use crate::generation::TrieInputs; @@ -36,7 +36,6 @@ fn mpt_insert_leaf_identical_keys() -> Result<()> { nibbles: key, v: test_account_2_rlp(), }; - test_state_trie(state_trie, insert) } @@ -56,7 +55,6 @@ fn mpt_insert_leaf_nonoverlapping_keys() -> Result<()> { }, v: test_account_2_rlp(), }; - test_state_trie(state_trie, insert) } @@ -76,7 +74,44 @@ fn mpt_insert_leaf_overlapping_keys() -> Result<()> { }, v: test_account_2_rlp(), }; + test_state_trie(state_trie, insert) +} +#[test] +fn mpt_insert_leaf_insert_key_extends_leaf_key() -> Result<()> { + let state_trie = PartialTrie::Leaf { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + value: test_account_1_rlp(), + }; + let insert = InsertEntry { + nibbles: Nibbles { + count: 5, + packed: 0xABCDE.into(), + }, + v: test_account_2_rlp(), + }; + test_state_trie(state_trie, insert) +} + +#[test] +fn mpt_insert_leaf_leaf_key_extends_insert_key() -> Result<()> { + let state_trie = PartialTrie::Leaf { + nibbles: Nibbles { + count: 5, + packed: 0xABCDE.into(), + }, + value: test_account_1_rlp(), + }; + let insert = InsertEntry { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + v: test_account_2_rlp(), + }; test_state_trie(state_trie, insert) } @@ -100,18 +135,32 @@ fn mpt_insert_branch_replacing_empty_child() -> Result<()> { } #[test] -#[ignore] // TODO: Enable when mpt_insert_extension is done. fn mpt_insert_extension_to_leaf_same_key() -> Result<()> { - let state_trie = extension_to_leaf(test_account_1_rlp()); - - let insert = InsertEntry { + let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); + children[0xD] = Box::new(PartialTrie::Leaf { + nibbles: Nibbles { + count: 2, + packed: 0xEF.into(), + }, + value: test_account_1_rlp(), + }); + let state_trie = PartialTrie::Extension { nibbles: Nibbles { count: 3, + packed: 0xABC.into(), + }, + child: Box::new(PartialTrie::Branch { + children, + value: test_account_1_rlp(), + }), + }; + let insert = InsertEntry { + nibbles: Nibbles { + count: 6, packed: 0xABCDEF.into(), }, v: test_account_2_rlp(), }; - test_state_trie(state_trie, insert) } From 0d0067554e7dd32f49936e7768699de196caceee Mon Sep 17 00:00:00 2001 From: Hamish Ivey-Law <426294+unzvfu@users.noreply.github.com> Date: Tue, 11 Oct 2022 18:59:02 +1100 Subject: [PATCH 11/21] Refactor and tidy up `mul.rs` (#764) * Refactor and tidy up `mul.rs`. * Jacqui PR comments. --- evm/src/arithmetic/modular.rs | 27 +++--- evm/src/arithmetic/mul.rs | 161 +++++++++++++++------------------- evm/src/arithmetic/utils.rs | 66 ++++++++------ 3 files changed, 124 insertions(+), 130 deletions(-) diff --git a/evm/src/arithmetic/modular.rs b/evm/src/arithmetic/modular.rs index 1fd31bb1..fd2a2e28 100644 --- a/evm/src/arithmetic/modular.rs +++ b/evm/src/arithmetic/modular.rs @@ -18,12 +18,13 @@ //! a(x) = \sum_{i=0}^15 a[i] x^i //! //! (so A = a(β)) and similarly for b(x), m(x) and c(x). Then -//! operation(A,B) = C (mod M) if and only if the polynomial +//! operation(A,B) = C (mod M) if and only if there exists q such that +//! the polynomial //! //! operation(a(x), b(x)) - c(x) - m(x) * q(x) //! -//! is zero when evaluated at x = β, i.e. it is divisible by (x - β). -//! Thus exists a polynomial s such that +//! is zero when evaluated at x = β, i.e. it is divisible by (x - β); +//! equivalently, there exists a polynomial s such that //! //! operation(a(x), b(x)) - c(x) - m(x) * q(x) - (x - β) * s(x) == 0 //! @@ -34,12 +35,12 @@ //! coefficients must be zero. The variable names of the constituent //! polynomials are (writing N for N_LIMBS=16): //! -//! a(x) = \sum_{i=0}^{N-1} input0[i] * β^i -//! b(x) = \sum_{i=0}^{N-1} input1[i] * β^i -//! c(x) = \sum_{i=0}^{N-1} output[i] * β^i -//! m(x) = \sum_{i=0}^{N-1} modulus[i] * β^i -//! q(x) = \sum_{i=0}^{2N-1} quot[i] * β^i -//! s(x) = \sum_i^{2N-2} aux[i] * β^i +//! a(x) = \sum_{i=0}^{N-1} input0[i] * x^i +//! b(x) = \sum_{i=0}^{N-1} input1[i] * x^i +//! c(x) = \sum_{i=0}^{N-1} output[i] * x^i +//! m(x) = \sum_{i=0}^{N-1} modulus[i] * x^i +//! q(x) = \sum_{i=0}^{2N-1} quot[i] * x^i +//! s(x) = \sum_i^{2N-2} aux[i] * x^i //! //! Because A, B, M and C are 256-bit numbers, the degrees of a, b, m //! and c are (at most) N-1 = 15. If m = 1, then Q would be A*B which @@ -211,7 +212,7 @@ fn generate_modular_op( // constr_poly must be zero when evaluated at x = β := // 2^LIMB_BITS, hence it's divisible by (x - β). `aux_limbs` is // the result of removing that root. - let aux_limbs = pol_remove_root_2exp::(constr_poly); + let aux_limbs = pol_remove_root_2exp::(constr_poly); for deg in 0..N_LIMBS { lv[MODULAR_OUTPUT[deg]] = F::from_canonical_i64(output_limbs[deg]); @@ -303,7 +304,8 @@ fn modular_constr_poly( pol_add_assign(&mut constr_poly, &output); // constr_poly = c(x) + q(x) * m(x) + (x - β) * s(x) - let aux = MODULAR_AUX_INPUT.map(|c| lv[c]); + let mut aux = MODULAR_AUX_INPUT.map(|c| lv[c]); + aux[2 * N_LIMBS - 1] = P::ZEROS; // zero out the MOD_IS_ZERO flag let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS); pol_add_assign(&mut constr_poly, &pol_adjoin_root(aux, base)); @@ -397,7 +399,8 @@ fn modular_constr_poly_ext_circuit, const D: usize> let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap(); pol_add_assign_ext_circuit(builder, &mut constr_poly, &output); - let aux = MODULAR_AUX_INPUT.map(|c| lv[c]); + let mut aux = MODULAR_AUX_INPUT.map(|c| lv[c]); + aux[2 * N_LIMBS - 1] = builder.zero_extension(); let base = builder.constant_extension(F::Extension::from_canonical_u64(1u64 << LIMB_BITS)); let t = pol_adjoin_root_ext_circuit(builder, aux, base); pol_add_assign_ext_circuit(builder, &mut constr_poly, &t); diff --git a/evm/src/arithmetic/mul.rs b/evm/src/arithmetic/mul.rs index 9d6638f1..c98b9af8 100644 --- a/evm/src/arithmetic/mul.rs +++ b/evm/src/arithmetic/mul.rs @@ -3,30 +3,57 @@ //! This crate verifies an EVM MUL instruction, which takes two //! 256-bit inputs A and B, and produces a 256-bit output C satisfying //! -//! C = A*B (mod 2^256). +//! C = A*B (mod 2^256), //! -//! Inputs A and B, and output C, are given as arrays of 16-bit +//! i.e. C is the lower half of the usual long multiplication +//! A*B. Inputs A and B, and output C, are given as arrays of 16-bit //! limbs. For example, if the limbs of A are a[0]...a[15], then //! //! A = \sum_{i=0}^15 a[i] β^i, //! -//! where β = 2^16. To verify that A, B and C satisfy the equation we -//! proceed as follows. Define a(x) = \sum_{i=0}^15 a[i] x^i (so A = a(β)) -//! and similarly for b(x) and c(x). Then A*B = C (mod 2^256) if and only -//! if there exist polynomials q and m such that +//! where β = 2^16 = 2^LIMB_BITS. To verify that A, B and C satisfy +//! the equation we proceed as follows. Define //! -//! a(x)*b(x) - c(x) - m(x)*x^16 - (β - x)*q(x) == 0. +//! a(x) = \sum_{i=0}^15 a[i] x^i +//! +//! (so A = a(β)) and similarly for b(x) and c(x). Then A*B = C (mod +//! 2^256) if and only if there exists q such that the polynomial +//! +//! a(x) * b(x) - c(x) - x^16 * q(x) +//! +//! is zero when evaluated at x = β, i.e. it is divisible by (x - β); +//! equivalently, there exists a polynomial s (representing the +//! carries from the long multiplication) such that +//! +//! a(x) * b(x) - c(x) - x^16 * q(x) - (x - β) * s(x) == 0 +//! +//! As we only need the lower half of the product, we can omit q(x) +//! since it is multiplied by the modulus β^16 = 2^256. Thus we only +//! need to verify +//! +//! a(x) * b(x) - c(x) - (x - β) * s(x) == 0 +//! +//! In the code below, this "constraint polynomial" is constructed in +//! the variable `constr_poly`. It must be identically zero for the +//! multiplication operation to be verified, or, equivalently, each of +//! its coefficients must be zero. The variable names of the +//! constituent polynomials are (writing N for N_LIMBS=16): +//! +//! a(x) = \sum_{i=0}^{N-1} input0[i] * x^i +//! b(x) = \sum_{i=0}^{N-1} input1[i] * x^i +//! c(x) = \sum_{i=0}^{N-1} output[i] * x^i +//! s(x) = \sum_i^{2N-3} aux[i] * x^i //! //! Because A, B and C are 256-bit numbers, the degrees of a, b and c -//! are (at most) 15. Thus deg(a*b) <= 30, so deg(m) <= 14 and deg(q) -//! <= 29. However, the fact that we're verifying the equality modulo -//! 2^256 means that we can ignore terms of degree >= 16, since for -//! them evaluating at β gives a factor of β^16 = 2^256 which is 0. +//! are (at most) 15. Thus deg(a*b) <= 30 and deg(s) <= 29; however, +//! as we're only verifying the lower half of A*B, we only need to +//! know s(x) up to degree 14 (so that (x - β)*s(x) has degree 15). On +//! the other hand, the coefficients of s(x) can be as large as +//! 16*(β-2) or 20 bits. //! -//! Hence, to verify the equality, we don't need m(x) at all, and we -//! only need to know q(x) up to degree 14 (so that (β - x)*q(x) has -//! degree 15). On the other hand, the coefficients of q(x) can be as -//! large as 16*(β-2) or 20 bits. +//! Note that, unlike for the general modular multiplication (see the +//! file `modular.rs`), we don't need to check that output is reduced, +//! since any value of output is less than β^16 and is hence reduced. use plonky2::field::extension::Extendable; use plonky2::field::packed::PackedField; @@ -35,64 +62,42 @@ use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use crate::arithmetic::columns::*; -use crate::arithmetic::utils::{pol_mul_lo, pol_sub_assign}; +use crate::arithmetic::utils::*; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { - let input0_limbs = MUL_INPUT_0.map(|c| lv[c].to_canonical_u64()); - let input1_limbs = MUL_INPUT_1.map(|c| lv[c].to_canonical_u64()); + let input0_limbs = MUL_INPUT_0.map(|c| lv[c].to_canonical_u64() as i64); + let input1_limbs = MUL_INPUT_1.map(|c| lv[c].to_canonical_u64() as i64); - const MASK: u64 = (1u64 << LIMB_BITS) - 1u64; + const MASK: i64 = (1i64 << LIMB_BITS) - 1i64; // Input and output have 16-bit limbs - let mut aux_in_limbs = [0u64; N_LIMBS]; - let mut output_limbs = [0u64; N_LIMBS]; + let mut output_limbs = [0i64; N_LIMBS]; // Column-wise pen-and-paper long multiplication on 16-bit limbs. // First calculate the coefficients of a(x)*b(x) (in unreduced_prod), // then do carry propagation to obtain C = c(β) = a(β)*b(β). - let mut cy = 0u64; + let mut cy = 0i64; let mut unreduced_prod = pol_mul_lo(input0_limbs, input1_limbs); for col in 0..N_LIMBS { let t = unreduced_prod[col] + cy; cy = t >> LIMB_BITS; output_limbs[col] = t & MASK; } - // In principle, the last cy could be dropped because this is // multiplication modulo 2^256. However, we need it below for - // aux_in_limbs to handle the fact that unreduced_prod will - // inevitably contain a one digit's worth that is > 2^256. + // aux_limbs to handle the fact that unreduced_prod will + // inevitably contain one digit's worth that is > 2^256. - for (&c, output_limb) in MUL_OUTPUT.iter().zip(output_limbs) { - lv[c] = F::from_canonical_u64(output_limb); - } pol_sub_assign(&mut unreduced_prod, &output_limbs); - // unreduced_prod is the coefficients of the polynomial a(x)*b(x) - c(x). - // This must be zero when evaluated at x = β = 2^LIMB_BITS, hence it's - // divisible by (β - x). If we write unreduced_prod as - // - // a(x)*b(x) - c(x) = \sum_{i=0}^n p_i x^i + terms of degree > n - // = (β - x) \sum_{i=0}^{n-1} q_i x^i + terms of degree > n - // - // then by comparing coefficients it is easy to see that - // - // q_0 = p_0 / β and q_i = (p_i + q_{i-1}) / β - // - // for 0 < i < n-1 (and the divisions are exact). Because we're - // only calculating the result modulo 2^256, we can ignore the - // terms of degree > n = 15. - aux_in_limbs[0] = unreduced_prod[0] >> LIMB_BITS; - for deg in 1..N_LIMBS - 1 { - aux_in_limbs[deg] = (unreduced_prod[deg] + aux_in_limbs[deg - 1]) >> LIMB_BITS; - } - aux_in_limbs[N_LIMBS - 1] = cy; + let mut aux_limbs = pol_remove_root_2exp::(unreduced_prod); + aux_limbs[N_LIMBS - 1] = -cy; for deg in 0..N_LIMBS { - let c = MUL_AUX_INPUT[deg]; - lv[c] = F::from_canonical_u64(aux_in_limbs[deg]); + lv[MUL_OUTPUT[deg]] = F::from_canonical_i64(output_limbs[deg]); + lv[MUL_AUX_INPUT[deg]] = F::from_noncanonical_i64(aux_limbs[deg]); } } @@ -115,29 +120,26 @@ pub fn eval_packed_generic( // must be identically zero for this multiplication to be // verified. // - // These two lines set constr_poly to the polynomial A(x)B(x) - C(x), - // where A, B and C are the polynomials + // These two lines set constr_poly to the polynomial a(x)b(x) - c(x), + // where a, b and c are the polynomials // - // A(x) = \sum_i input0_limbs[i] * 2^LIMB_BITS - // B(x) = \sum_i input1_limbs[i] * 2^LIMB_BITS - // C(x) = \sum_i output_limbs[i] * 2^LIMB_BITS + // a(x) = \sum_i input0_limbs[i] * β^i + // b(x) = \sum_i input1_limbs[i] * β^i + // c(x) = \sum_i output_limbs[i] * β^i // - // This polynomial should equal (2^LIMB_BITS - x) * Q(x) where Q is + // This polynomial should equal where s is // - // Q(x) = \sum_i aux_limbs[i] * 2^LIMB_BITS + // s(x) = \sum_i aux_limbs[i] * β^i // let mut constr_poly = pol_mul_lo(input0_limbs, input1_limbs); pol_sub_assign(&mut constr_poly, &output_limbs); - // This subtracts (2^LIMB_BITS - x) * Q(x) from constr_poly. + // This subtracts (x - β) * s(x) from constr_poly. let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS); - constr_poly[0] -= base * aux_limbs[0]; - for deg in 1..N_LIMBS { - constr_poly[deg] -= (base * aux_limbs[deg]) - aux_limbs[deg - 1]; - } + pol_sub_assign(&mut constr_poly, &pol_adjoin_root(aux_limbs, base)); // At this point constr_poly holds the coefficients of the - // polynomial A(x)B(x) - C(x) - (2^LIMB_BITS - x)*Q(x). The + // polynomial a(x)b(x) - c(x) - (x - β)*s(x). The // multiplication is valid if and only if all of those // coefficients are zero. for &c in &constr_poly { @@ -154,37 +156,14 @@ pub fn eval_ext_circuit, const D: usize>( let input0_limbs = MUL_INPUT_0.map(|c| lv[c]); let input1_limbs = MUL_INPUT_1.map(|c| lv[c]); let output_limbs = MUL_OUTPUT.map(|c| lv[c]); - let aux_in_limbs = MUL_AUX_INPUT.map(|c| lv[c]); + let aux_limbs = MUL_AUX_INPUT.map(|c| lv[c]); - let zero = builder.zero_extension(); - let mut constr_poly = [zero; N_LIMBS]; // pointless init + let mut constr_poly = pol_mul_lo_ext_circuit(builder, input0_limbs, input1_limbs); + pol_sub_assign_ext_circuit(builder, &mut constr_poly, &output_limbs); - // Invariant: i + j = deg - for col in 0..N_LIMBS { - let mut acc = zero; - for i in 0..=col { - let j = col - i; - acc = builder.mul_add_extension(input0_limbs[i], input1_limbs[j], acc); - } - constr_poly[col] = builder.sub_extension(acc, output_limbs[col]); - } - - let base = F::from_canonical_u64(1 << LIMB_BITS); - let one = builder.one_extension(); - // constr_poly[0] = constr_poly[0] - base * aux_in_limbs[0] - constr_poly[0] = - builder.arithmetic_extension(F::ONE, -base, constr_poly[0], one, aux_in_limbs[0]); - for deg in 1..N_LIMBS { - // constr_poly[deg] -= (base*aux_in_limbs[deg] - aux_in_limbs[deg-1]) - let t = builder.arithmetic_extension( - base, - F::NEG_ONE, - aux_in_limbs[deg], - one, - aux_in_limbs[deg - 1], - ); - constr_poly[deg] = builder.sub_extension(constr_poly[deg], t); - } + let base = builder.constant_extension(F::Extension::from_canonical_u64(1 << LIMB_BITS)); + let rhs = pol_adjoin_root_ext_circuit(builder, aux_limbs, base); + pol_sub_assign_ext_circuit(builder, &mut constr_poly, &rhs); for &c in &constr_poly { let filter = builder.mul_extension(is_mul, c); diff --git a/evm/src/arithmetic/utils.rs b/evm/src/arithmetic/utils.rs index b5356a78..ccb8bc0a 100644 --- a/evm/src/arithmetic/utils.rs +++ b/evm/src/arithmetic/utils.rs @@ -225,6 +225,22 @@ where res } +pub(crate) fn pol_mul_lo_ext_circuit, const D: usize>( + builder: &mut CircuitBuilder, + a: [ExtensionTarget; N_LIMBS], + b: [ExtensionTarget; N_LIMBS], +) -> [ExtensionTarget; N_LIMBS] { + let zero = builder.zero_extension(); + let mut res = [zero; N_LIMBS]; + for deg in 0..N_LIMBS { + for i in 0..=deg { + let j = deg - i; + res[deg] = builder.mul_add_extension(a[i], b[j], res[deg]); + } + } + res +} + /// Adjoin M - N zeros to a, returning [a[0], a[1], ..., a[N-1], 0, 0, ..., 0]. pub(crate) fn pol_extend(a: [T; N]) -> [T; M] where @@ -248,11 +264,9 @@ pub(crate) fn pol_extend_ext_circuit, const D: usiz zero_extend } -/// Given polynomial a(x) = \sum_{i=0}^{2N-2} a[i] x^i and an element +/// Given polynomial a(x) = \sum_{i=0}^{N-2} a[i] x^i and an element /// `root`, return b = (x - root) * a(x). -/// -/// NB: Ignores element a[2 * N_LIMBS - 1], treating it as if it's 0. -pub(crate) fn pol_adjoin_root(a: [T; 2 * N_LIMBS], root: U) -> [T; 2 * N_LIMBS] +pub(crate) fn pol_adjoin_root(a: [T; N], root: U) -> [T; N] where T: Add + Copy + Default + Mul + Sub, U: Copy + Mul + Neg, @@ -261,66 +275,64 @@ where // coefficients, res[0] = -root*a[0] and // res[i] = a[i-1] - root * a[i] - let mut res = [T::default(); 2 * N_LIMBS]; + let mut res = [T::default(); N]; res[0] = -root * a[0]; - for deg in 1..(2 * N_LIMBS - 1) { + for deg in 1..N { res[deg] = a[deg - 1] - (root * a[deg]); } - // NB: We assume that a[2 * N_LIMBS - 1] = 0, so the last - // iteration has no "* root" term. - res[2 * N_LIMBS - 1] = a[2 * N_LIMBS - 2]; res } -pub(crate) fn pol_adjoin_root_ext_circuit, const D: usize>( +pub(crate) fn pol_adjoin_root_ext_circuit< + F: RichField + Extendable, + const D: usize, + const N: usize, +>( builder: &mut CircuitBuilder, - a: [ExtensionTarget; 2 * N_LIMBS], + a: [ExtensionTarget; N], root: ExtensionTarget, -) -> [ExtensionTarget; 2 * N_LIMBS] { +) -> [ExtensionTarget; N] { let zero = builder.zero_extension(); - let mut res = [zero; 2 * N_LIMBS]; + let mut res = [zero; N]; // res[deg] = NEG_ONE * root * a[0] + ZERO * zero res[0] = builder.arithmetic_extension(F::NEG_ONE, F::ZERO, root, a[0], zero); - for deg in 1..(2 * N_LIMBS - 1) { + for deg in 1..N { // res[deg] = NEG_ONE * root * a[deg] + ONE * a[deg - 1] res[deg] = builder.arithmetic_extension(F::NEG_ONE, F::ONE, root, a[deg], a[deg - 1]); } - // NB: We assumes that a[2 * N_LIMBS - 1] = 0, so the last - // iteration has no "* root" term. - res[2 * N_LIMBS - 1] = a[2 * N_LIMBS - 2]; res } -/// Given polynomial a(x) = \sum_{i=0}^{2N-1} a[i] x^i and a root of `a` +/// Given polynomial a(x) = \sum_{i=0}^{N-1} a[i] x^i and a root of `a` /// of the form 2^EXP, return q(x) satisfying a(x) = (x - root) * q(x). /// /// NB: We do not verify that a(2^EXP) = 0; if this doesn't hold the /// result is basically junk. /// -/// NB: The result could be returned in 2*N-1 elements, but we return -/// 2*N and set the last element to zero since the calling code -/// happens to require a result zero-extended to 2*N elements. -pub(crate) fn pol_remove_root_2exp(a: [T; 2 * N_LIMBS]) -> [T; 2 * N_LIMBS] +/// NB: The result could be returned in N-1 elements, but we return +/// N and set the last element to zero since the calling code +/// happens to require a result zero-extended to N elements. +pub(crate) fn pol_remove_root_2exp(a: [T; N]) -> [T; N] where T: Copy + Default + Neg + Shr + Sub, { // By assumption β := 2^EXP is a root of `a`, i.e. (x - β) divides // `a`; if we write // - // a(x) = \sum_{i=0}^{2N-1} a[i] x^i - // = (x - β) \sum_{i=0}^{2N-2} q[i] x^i + // a(x) = \sum_{i=0}^{N-1} a[i] x^i + // = (x - β) \sum_{i=0}^{N-2} q[i] x^i // // then by comparing coefficients it is easy to see that // // q[0] = -a[0] / β and q[i] = (q[i-1] - a[i]) / β // - // for 0 < i <= 2N-1 (and the divisions are exact). + // for 0 < i <= N-1 (and the divisions are exact). - let mut q = [T::default(); 2 * N_LIMBS]; + let mut q = [T::default(); N]; q[0] = -(a[0] >> EXP); // NB: Last element of q is deliberately left equal to zero. - for deg in 1..2 * N_LIMBS - 1 { + for deg in 1..N - 1 { q[deg] = (q[deg - 1] - a[deg]) >> EXP; } q From 68a5428500966679b746a096b61442b57e790be0 Mon Sep 17 00:00:00 2001 From: Hamish Ivey-Law <426294+unzvfu@users.noreply.github.com> Date: Wed, 12 Oct 2022 02:39:13 +1100 Subject: [PATCH 12/21] Represent input columns as ranges rather than arrays (#776) * Use std::ops::Range of columns rather than arrays of column indices. * Refactor reading from the local values table. * The inevitable post-push fmt/clippy commit. --- evm/src/arithmetic/add.rs | 40 +++++++++-------- evm/src/arithmetic/columns.rs | 79 ++++++++++++++------------------- evm/src/arithmetic/compare.rs | 47 ++++++++++---------- evm/src/arithmetic/modular.rs | 83 ++++++++++++++--------------------- evm/src/arithmetic/mul.rs | 40 ++++++++--------- evm/src/arithmetic/sub.rs | 39 +++++++++------- evm/src/arithmetic/utils.rs | 45 ++++++++++++++++--- 7 files changed, 192 insertions(+), 181 deletions(-) diff --git a/evm/src/arithmetic/add.rs b/evm/src/arithmetic/add.rs index d2520fb9..1bf798cc 100644 --- a/evm/src/arithmetic/add.rs +++ b/evm/src/arithmetic/add.rs @@ -6,6 +6,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use crate::arithmetic::columns::*; +use crate::arithmetic::utils::read_value_u64_limbs; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; @@ -94,15 +95,12 @@ where } pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { - let input0_limbs = ADD_INPUT_0.map(|c| lv[c].to_canonical_u64()); - let input1_limbs = ADD_INPUT_1.map(|c| lv[c].to_canonical_u64()); + let input0 = read_value_u64_limbs(lv, ADD_INPUT_0); + let input1 = read_value_u64_limbs(lv, ADD_INPUT_1); // Input and output have 16-bit limbs - let (output_limbs, _) = u256_add_cc(input0_limbs, input1_limbs); - - for (&c, output_limb) in ADD_OUTPUT.iter().zip(output_limbs) { - lv[c] = F::from_canonical_u64(output_limb); - } + let (output_limbs, _) = u256_add_cc(input0, input1); + lv[ADD_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_u64(c))); } pub fn eval_packed_generic( @@ -114,15 +112,20 @@ pub fn eval_packed_generic( range_check_error!(ADD_OUTPUT, 16); let is_add = lv[IS_ADD]; - let input0_limbs = ADD_INPUT_0.iter().map(|&c| lv[c]); - let input1_limbs = ADD_INPUT_1.iter().map(|&c| lv[c]); - let output_limbs = ADD_OUTPUT.iter().map(|&c| lv[c]); + let input0_limbs = &lv[ADD_INPUT_0]; + let input1_limbs = &lv[ADD_INPUT_1]; + let output_limbs = &lv[ADD_OUTPUT]; // This computed output is not yet reduced; i.e. some limbs may be // more than 16 bits. - let output_computed = input0_limbs.zip(input1_limbs).map(|(a, b)| a + b); + let output_computed = input0_limbs.iter().zip(input1_limbs).map(|(&a, &b)| a + b); - eval_packed_generic_are_equal(yield_constr, is_add, output_computed, output_limbs); + eval_packed_generic_are_equal( + yield_constr, + is_add, + output_computed, + output_limbs.iter().copied(), + ); } #[allow(clippy::needless_collect)] @@ -132,17 +135,18 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr: &mut RecursiveConstraintConsumer, ) { let is_add = lv[IS_ADD]; - let input0_limbs = ADD_INPUT_0.iter().map(|&c| lv[c]); - let input1_limbs = ADD_INPUT_1.iter().map(|&c| lv[c]); - let output_limbs = ADD_OUTPUT.iter().map(|&c| lv[c]); + let input0_limbs = &lv[ADD_INPUT_0]; + let input1_limbs = &lv[ADD_INPUT_1]; + let output_limbs = &lv[ADD_OUTPUT]; // Since `map` is lazy and the closure passed to it borrows // `builder`, we can't then borrow builder again below in the call // to `eval_ext_circuit_are_equal`. The solution is to force // evaluation with `collect`. let output_computed = input0_limbs + .iter() .zip(input1_limbs) - .map(|(a, b)| builder.add_extension(a, b)) + .map(|(&a, &b)| builder.add_extension(a, b)) .collect::>>(); eval_ext_circuit_are_equal( @@ -150,7 +154,7 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr, is_add, output_computed.into_iter(), - output_limbs, + output_limbs.iter().copied(), ); } @@ -203,7 +207,7 @@ mod tests { for _ in 0..N_RND_TESTS { // set inputs to random values - for (&ai, bi) in ADD_INPUT_0.iter().zip(ADD_INPUT_1) { + for (ai, bi) in ADD_INPUT_0.zip(ADD_INPUT_1) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); } diff --git a/evm/src/arithmetic/columns.rs b/evm/src/arithmetic/columns.rs index ca8ba549..ee73f223 100644 --- a/evm/src/arithmetic/columns.rs +++ b/evm/src/arithmetic/columns.rs @@ -1,5 +1,7 @@ //! Arithmetic unit +use std::ops::Range; + pub const LIMB_BITS: usize = 16; const EVM_REGISTER_BITS: usize = 256; @@ -44,57 +46,42 @@ pub(crate) const ALL_OPERATIONS: [usize; 16] = [ /// used by any arithmetic circuit, depending on which one is active /// this cycle. Can be increased as needed as other operations are /// implemented. -const NUM_SHARED_COLS: usize = 144; // only need 64 for add, sub, and mul +const NUM_SHARED_COLS: usize = 9 * N_LIMBS; // only need 64 for add, sub, and mul -const fn shared_col(i: usize) -> usize { - assert!(i < NUM_SHARED_COLS); - START_SHARED_COLS + i -} +const GENERAL_INPUT_0: Range = START_SHARED_COLS..START_SHARED_COLS + N_LIMBS; +const GENERAL_INPUT_1: Range = GENERAL_INPUT_0.end..GENERAL_INPUT_0.end + N_LIMBS; +const GENERAL_INPUT_2: Range = GENERAL_INPUT_1.end..GENERAL_INPUT_1.end + N_LIMBS; +const GENERAL_INPUT_3: Range = GENERAL_INPUT_2.end..GENERAL_INPUT_2.end + N_LIMBS; +const AUX_INPUT_0: Range = GENERAL_INPUT_3.end..GENERAL_INPUT_3.end + 2 * N_LIMBS; +const AUX_INPUT_1: Range = AUX_INPUT_0.end..AUX_INPUT_0.end + 2 * N_LIMBS; +const AUX_INPUT_2: Range = AUX_INPUT_1.end..AUX_INPUT_1.end + N_LIMBS; -const fn gen_input_cols(start: usize) -> [usize; N] { - let mut cols = [0usize; N]; - let mut i = 0; - while i < N { - cols[i] = shared_col(start + i); - i += 1; - } - cols -} +pub(crate) const ADD_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const ADD_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const ADD_OUTPUT: Range = GENERAL_INPUT_2; -const GENERAL_INPUT_0: [usize; N_LIMBS] = gen_input_cols::(0); -const GENERAL_INPUT_1: [usize; N_LIMBS] = gen_input_cols::(N_LIMBS); -const GENERAL_INPUT_2: [usize; N_LIMBS] = gen_input_cols::(2 * N_LIMBS); -const GENERAL_INPUT_3: [usize; N_LIMBS] = gen_input_cols::(3 * N_LIMBS); -const AUX_INPUT_0: [usize; 2 * N_LIMBS] = gen_input_cols::<{ 2 * N_LIMBS }>(4 * N_LIMBS); -const AUX_INPUT_1: [usize; 2 * N_LIMBS] = gen_input_cols::<{ 2 * N_LIMBS }>(6 * N_LIMBS); -const AUX_INPUT_2: [usize; N_LIMBS] = gen_input_cols::(8 * N_LIMBS); +pub(crate) const SUB_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const SUB_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const SUB_OUTPUT: Range = GENERAL_INPUT_2; -pub(crate) const ADD_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const ADD_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const ADD_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; +pub(crate) const MUL_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const MUL_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const MUL_OUTPUT: Range = GENERAL_INPUT_2; +pub(crate) const MUL_AUX_INPUT: Range = GENERAL_INPUT_3; -pub(crate) const SUB_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const SUB_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const SUB_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; +pub(crate) const CMP_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const CMP_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const CMP_OUTPUT: usize = GENERAL_INPUT_2.start; +pub(crate) const CMP_AUX_INPUT: Range = GENERAL_INPUT_3; -pub(crate) const MUL_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const MUL_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const MUL_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; -pub(crate) const MUL_AUX_INPUT: [usize; N_LIMBS] = GENERAL_INPUT_3; - -pub(crate) const CMP_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const CMP_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const CMP_OUTPUT: usize = GENERAL_INPUT_2[0]; -pub(crate) const CMP_AUX_INPUT: [usize; N_LIMBS] = GENERAL_INPUT_3; - -pub(crate) const MODULAR_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const MODULAR_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const MODULAR_MODULUS: [usize; N_LIMBS] = GENERAL_INPUT_2; -pub(crate) const MODULAR_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_3; -pub(crate) const MODULAR_QUO_INPUT: [usize; 2 * N_LIMBS] = AUX_INPUT_0; -// NB: Last value is not used in AUX, it is used in IS_ZERO -pub(crate) const MODULAR_AUX_INPUT: [usize; 2 * N_LIMBS] = AUX_INPUT_1; -pub(crate) const MODULAR_MOD_IS_ZERO: usize = AUX_INPUT_1[2 * N_LIMBS - 1]; -pub(crate) const MODULAR_OUT_AUX_RED: [usize; N_LIMBS] = AUX_INPUT_2; +pub(crate) const MODULAR_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const MODULAR_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const MODULAR_MODULUS: Range = GENERAL_INPUT_2; +pub(crate) const MODULAR_OUTPUT: Range = GENERAL_INPUT_3; +pub(crate) const MODULAR_QUO_INPUT: Range = AUX_INPUT_0; +// NB: Last value is not used in AUX, it is used in MOD_IS_ZERO +pub(crate) const MODULAR_AUX_INPUT: Range = AUX_INPUT_1; +pub(crate) const MODULAR_MOD_IS_ZERO: usize = AUX_INPUT_1.end - 1; +pub(crate) const MODULAR_OUT_AUX_RED: Range = AUX_INPUT_2; pub const NUM_ARITH_COLUMNS: usize = START_SHARED_COLS + NUM_SHARED_COLS; diff --git a/evm/src/arithmetic/compare.rs b/evm/src/arithmetic/compare.rs index a6566db5..55dc5764 100644 --- a/evm/src/arithmetic/compare.rs +++ b/evm/src/arithmetic/compare.rs @@ -22,12 +22,13 @@ use plonky2::iop::ext_target::ExtensionTarget; use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal}; use crate::arithmetic::columns::*; use crate::arithmetic::sub::u256_sub_br; +use crate::arithmetic::utils::read_value_u64_limbs; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; pub(crate) fn generate(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) { - let input0 = CMP_INPUT_0.map(|c| lv[c].to_canonical_u64()); - let input1 = CMP_INPUT_1.map(|c| lv[c].to_canonical_u64()); + let input0 = read_value_u64_limbs(lv, CMP_INPUT_0); + let input1 = read_value_u64_limbs(lv, CMP_INPUT_1); let (diff, br) = match op { // input0 - input1 == diff + br*2^256 @@ -39,9 +40,7 @@ pub(crate) fn generate(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) _ => panic!("op code not a comparison"), }; - for (&c, diff_limb) in CMP_AUX_INPUT.iter().zip(diff) { - lv[c] = F::from_canonical_u64(diff_limb); - } + lv[CMP_AUX_INPUT].copy_from_slice(&diff.map(|c| F::from_canonical_u64(c))); lv[CMP_OUTPUT] = F::from_canonical_u64(br); } @@ -56,15 +55,17 @@ fn eval_packed_generic_check_is_one_bit( pub(crate) fn eval_packed_generic_lt( yield_constr: &mut ConstraintConsumer

, is_op: P, - input0: [P; N_LIMBS], - input1: [P; N_LIMBS], - aux: [P; N_LIMBS], + input0: &[P], + input1: &[P], + aux: &[P], output: P, ) { + debug_assert!(input0.len() == N_LIMBS && input1.len() == N_LIMBS && aux.len() == N_LIMBS); + // Verify (input0 < input1) == output by providing aux such that // input0 - input1 == aux + output*2^256. - let lhs_limbs = input0.iter().zip(input1).map(|(&a, b)| a - b); - let cy = eval_packed_generic_are_equal(yield_constr, is_op, aux.into_iter(), lhs_limbs); + let lhs_limbs = input0.iter().zip(input1).map(|(&a, &b)| a - b); + let cy = eval_packed_generic_are_equal(yield_constr, is_op, aux.iter().copied(), lhs_limbs); // We don't need to check that cy is 0 or 1, since output has // already been checked to be 0 or 1. yield_constr.constraint(is_op * (cy - output)); @@ -81,9 +82,9 @@ pub fn eval_packed_generic( let is_lt = lv[IS_LT]; let is_gt = lv[IS_GT]; - let input0 = CMP_INPUT_0.map(|c| lv[c]); - let input1 = CMP_INPUT_1.map(|c| lv[c]); - let aux = CMP_AUX_INPUT.map(|c| lv[c]); + let input0 = &lv[CMP_INPUT_0]; + let input1 = &lv[CMP_INPUT_1]; + let aux = &lv[CMP_AUX_INPUT]; let output = lv[CMP_OUTPUT]; let is_cmp = is_lt + is_gt; @@ -109,11 +110,13 @@ pub(crate) fn eval_ext_circuit_lt, const D: usize>( builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, yield_constr: &mut RecursiveConstraintConsumer, is_op: ExtensionTarget, - input0: [ExtensionTarget; N_LIMBS], - input1: [ExtensionTarget; N_LIMBS], - aux: [ExtensionTarget; N_LIMBS], + input0: &[ExtensionTarget], + input1: &[ExtensionTarget], + aux: &[ExtensionTarget], output: ExtensionTarget, ) { + debug_assert!(input0.len() == N_LIMBS && input1.len() == N_LIMBS && aux.len() == N_LIMBS); + // Since `map` is lazy and the closure passed to it borrows // `builder`, we can't then borrow builder again below in the call // to `eval_ext_circuit_are_equal`. The solution is to force @@ -121,14 +124,14 @@ pub(crate) fn eval_ext_circuit_lt, const D: usize>( let lhs_limbs = input0 .iter() .zip(input1) - .map(|(&a, b)| builder.sub_extension(a, b)) + .map(|(&a, &b)| builder.sub_extension(a, b)) .collect::>>(); let cy = eval_ext_circuit_are_equal( builder, yield_constr, is_op, - aux.into_iter(), + aux.iter().copied(), lhs_limbs.into_iter(), ); let good_output = builder.sub_extension(cy, output); @@ -144,9 +147,9 @@ pub fn eval_ext_circuit, const D: usize>( let is_lt = lv[IS_LT]; let is_gt = lv[IS_GT]; - let input0 = CMP_INPUT_0.map(|c| lv[c]); - let input1 = CMP_INPUT_1.map(|c| lv[c]); - let aux = CMP_AUX_INPUT.map(|c| lv[c]); + let input0 = &lv[CMP_INPUT_0]; + let input1 = &lv[CMP_INPUT_1]; + let aux = &lv[CMP_AUX_INPUT]; let output = lv[CMP_OUTPUT]; let is_cmp = builder.add_extension(is_lt, is_gt); @@ -210,7 +213,7 @@ mod tests { lv[other_op] = F::ZERO; // set inputs to random values - for (&ai, bi) in CMP_INPUT_0.iter().zip(CMP_INPUT_1) { + for (ai, bi) in CMP_INPUT_0.zip(CMP_INPUT_1) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); } diff --git a/evm/src/arithmetic/modular.rs b/evm/src/arithmetic/modular.rs index fd2a2e28..53051cda 100644 --- a/evm/src/arithmetic/modular.rs +++ b/evm/src/arithmetic/modular.rs @@ -160,9 +160,9 @@ fn generate_modular_op( ) { // Inputs are all range-checked in [0, 2^16), so the "as i64" // conversion is safe. - let input0_limbs = MODULAR_INPUT_0.map(|c| F::to_canonical_u64(&lv[c]) as i64); - let input1_limbs = MODULAR_INPUT_1.map(|c| F::to_canonical_u64(&lv[c]) as i64); - let mut modulus_limbs = MODULAR_MODULUS.map(|c| F::to_canonical_u64(&lv[c]) as i64); + let input0_limbs = read_value_i64_limbs(lv, MODULAR_INPUT_0); + let input1_limbs = read_value_i64_limbs(lv, MODULAR_INPUT_1); + let mut modulus_limbs = read_value_i64_limbs(lv, MODULAR_MODULUS); // The use of BigUints is just to avoid having to implement // modular reduction. @@ -175,12 +175,11 @@ fn generate_modular_op( let mut constr_poly = [0i64; 2 * N_LIMBS]; constr_poly[..2 * N_LIMBS - 1].copy_from_slice(&operation(input0_limbs, input1_limbs)); + let mut mod_is_zero = F::ZERO; if modulus.is_zero() { modulus += 1u32; modulus_limbs[0] += 1i64; - lv[MODULAR_MOD_IS_ZERO] = F::ONE; - } else { - lv[MODULAR_MOD_IS_ZERO] = F::ZERO; + mod_is_zero = F::ONE; } let input = columns_to_biguint(&constr_poly); @@ -214,19 +213,11 @@ fn generate_modular_op( // the result of removing that root. let aux_limbs = pol_remove_root_2exp::(constr_poly); - for deg in 0..N_LIMBS { - lv[MODULAR_OUTPUT[deg]] = F::from_canonical_i64(output_limbs[deg]); - lv[MODULAR_OUT_AUX_RED[deg]] = F::from_canonical_i64(out_aux_red[deg]); - lv[MODULAR_QUO_INPUT[deg]] = F::from_canonical_i64(quot_limbs[deg]); - lv[MODULAR_QUO_INPUT[deg + N_LIMBS]] = F::from_canonical_i64(quot_limbs[deg + N_LIMBS]); - lv[MODULAR_AUX_INPUT[deg]] = F::from_noncanonical_i64(aux_limbs[deg]); - // Don't overwrite MODULAR_MOD_IS_ZERO, which is at the last - // index of MODULAR_AUX_INPUT - if deg < N_LIMBS - 1 { - lv[MODULAR_AUX_INPUT[deg + N_LIMBS]] = - F::from_noncanonical_i64(aux_limbs[deg + N_LIMBS]); - } - } + lv[MODULAR_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_i64(c))); + lv[MODULAR_OUT_AUX_RED].copy_from_slice(&out_aux_red.map(|c| F::from_canonical_i64(c))); + lv[MODULAR_QUO_INPUT].copy_from_slice("_limbs.map(|c| F::from_canonical_i64(c))); + lv[MODULAR_AUX_INPUT].copy_from_slice(&aux_limbs.map(|c| F::from_noncanonical_i64(c))); + lv[MODULAR_MOD_IS_ZERO] = mod_is_zero; } /// Generate the output and auxiliary values for modular operations. @@ -262,7 +253,7 @@ fn modular_constr_poly( range_check_error!(MODULAR_AUX_INPUT, 20, signed); range_check_error!(MODULAR_OUTPUT, 16); - let mut modulus = MODULAR_MODULUS.map(|c| lv[c]); + let mut modulus = read_value::(lv, MODULAR_MODULUS); let mod_is_zero = lv[MODULAR_MOD_IS_ZERO]; // Check that mod_is_zero is zero or one @@ -277,22 +268,22 @@ fn modular_constr_poly( // modulus = 0. modulus[0] += mod_is_zero; - let output = MODULAR_OUTPUT.map(|c| lv[c]); + let output = &lv[MODULAR_OUTPUT]; // Verify that the output is reduced, i.e. output < modulus. - let out_aux_red = MODULAR_OUT_AUX_RED.map(|c| lv[c]); + let out_aux_red = &lv[MODULAR_OUT_AUX_RED]; let is_less_than = P::ONES; eval_packed_generic_lt( yield_constr, filter, output, - modulus, + &modulus, out_aux_red, is_less_than, ); // prod = q(x) * m(x) - let quot = MODULAR_QUO_INPUT.map(|c| lv[c]); + let quot = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_QUO_INPUT); let prod = pol_mul_wide2(quot, modulus); // higher order terms must be zero for &x in prod[2 * N_LIMBS..].iter() { @@ -301,10 +292,10 @@ fn modular_constr_poly( // constr_poly = c(x) + q(x) * m(x) let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap(); - pol_add_assign(&mut constr_poly, &output); + pol_add_assign(&mut constr_poly, output); // constr_poly = c(x) + q(x) * m(x) + (x - β) * s(x) - let mut aux = MODULAR_AUX_INPUT.map(|c| lv[c]); + let mut aux = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_AUX_INPUT); aux[2 * N_LIMBS - 1] = P::ZEROS; // zero out the MOD_IS_ZERO flag let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS); pol_add_assign(&mut constr_poly, &pol_adjoin_root(aux, base)); @@ -324,8 +315,8 @@ pub(crate) fn eval_packed_generic( // constr_poly has 2*N_LIMBS limbs let constr_poly = modular_constr_poly(lv, yield_constr, filter); - let input0 = MODULAR_INPUT_0.map(|c| lv[c]); - let input1 = MODULAR_INPUT_1.map(|c| lv[c]); + let input0 = read_value(lv, MODULAR_INPUT_0); + let input1 = read_value(lv, MODULAR_INPUT_1); let add_input = pol_add(input0, input1); let mul_input = pol_mul_wide(input0, input1); @@ -362,7 +353,7 @@ fn modular_constr_poly_ext_circuit, const D: usize> yield_constr: &mut RecursiveConstraintConsumer, filter: ExtensionTarget, ) -> [ExtensionTarget; 2 * N_LIMBS] { - let mut modulus = MODULAR_MODULUS.map(|c| lv[c]); + let mut modulus = read_value::(lv, MODULAR_MODULUS); let mod_is_zero = lv[MODULAR_MOD_IS_ZERO]; let t = builder.mul_sub_extension(mod_is_zero, mod_is_zero, mod_is_zero); @@ -376,20 +367,20 @@ fn modular_constr_poly_ext_circuit, const D: usize> modulus[0] = builder.add_extension(modulus[0], mod_is_zero); - let output = MODULAR_OUTPUT.map(|c| lv[c]); - let out_aux_red = MODULAR_OUT_AUX_RED.map(|c| lv[c]); + let output = &lv[MODULAR_OUTPUT]; + let out_aux_red = &lv[MODULAR_OUT_AUX_RED]; let is_less_than = builder.one_extension(); eval_ext_circuit_lt( builder, yield_constr, filter, output, - modulus, + &modulus, out_aux_red, is_less_than, ); - let quot = MODULAR_QUO_INPUT.map(|c| lv[c]); + let quot = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_QUO_INPUT); let prod = pol_mul_wide2_ext_circuit(builder, quot, modulus); for &x in prod[2 * N_LIMBS..].iter() { let t = builder.mul_extension(filter, x); @@ -397,9 +388,9 @@ fn modular_constr_poly_ext_circuit, const D: usize> } let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap(); - pol_add_assign_ext_circuit(builder, &mut constr_poly, &output); + pol_add_assign_ext_circuit(builder, &mut constr_poly, output); - let mut aux = MODULAR_AUX_INPUT.map(|c| lv[c]); + let mut aux = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_AUX_INPUT); aux[2 * N_LIMBS - 1] = builder.zero_extension(); let base = builder.constant_extension(F::Extension::from_canonical_u64(1u64 << LIMB_BITS)); let t = pol_adjoin_root_ext_circuit(builder, aux, base); @@ -421,8 +412,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( let constr_poly = modular_constr_poly_ext_circuit(lv, builder, yield_constr, filter); - let input0 = MODULAR_INPUT_0.map(|c| lv[c]); - let input1 = MODULAR_INPUT_1.map(|c| lv[c]); + let input0 = read_value(lv, MODULAR_INPUT_0); + let input1 = read_value(lv, MODULAR_INPUT_1); let add_input = pol_add_ext_circuit(builder, input0, input1); let mul_input = pol_mul_wide_ext_circuit(builder, input0, input1); @@ -498,11 +489,7 @@ mod tests { for i in 0..N_RND_TESTS { // set inputs to random values - for (&ai, &bi, &mi) in izip!( - MODULAR_INPUT_0.iter(), - MODULAR_INPUT_1.iter(), - MODULAR_MODULUS.iter() - ) { + for (ai, bi, mi) in izip!(MODULAR_INPUT_0, MODULAR_INPUT_1, MODULAR_MODULUS) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); lv[mi] = F::from_canonical_u16(rng.gen()); @@ -514,7 +501,7 @@ mod tests { if i > N_RND_TESTS / 2 { // 1 <= start < N_LIMBS let start = (rng.gen::() % (N_LIMBS - 1)) + 1; - for &mi in &MODULAR_MODULUS[start..N_LIMBS] { + for mi in MODULAR_MODULUS.skip(start) { lv[mi] = F::ZERO; } } @@ -552,11 +539,7 @@ mod tests { for _i in 0..N_RND_TESTS { // set inputs to random values and the modulus to zero; // the output is defined to be zero when modulus is zero. - for (&ai, &bi, &mi) in izip!( - MODULAR_INPUT_0.iter(), - MODULAR_INPUT_1.iter(), - MODULAR_MODULUS.iter() - ) { + for (ai, bi, mi) in izip!(MODULAR_INPUT_0, MODULAR_INPUT_1, MODULAR_MODULUS) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); lv[mi] = F::ZERO; @@ -565,7 +548,7 @@ mod tests { generate(&mut lv, op_filter); // check that the correct output was generated - assert!(MODULAR_OUTPUT.iter().all(|&oi| lv[oi] == F::ZERO)); + assert!(lv[MODULAR_OUTPUT].iter().all(|&c| c == F::ZERO)); let mut constraint_consumer = ConstraintConsumer::new( vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)], @@ -580,7 +563,7 @@ mod tests { .all(|&acc| acc == F::ZERO)); // Corrupt one output limb by setting it to a non-zero value - let random_oi = MODULAR_OUTPUT[rng.gen::() % N_LIMBS]; + let random_oi = MODULAR_OUTPUT.start + rng.gen::() % N_LIMBS; lv[random_oi] = F::from_canonical_u16(rng.gen_range(1..u16::MAX)); eval_packed_generic(&lv, &mut constraint_consumer); diff --git a/evm/src/arithmetic/mul.rs b/evm/src/arithmetic/mul.rs index c98b9af8..7dda18e2 100644 --- a/evm/src/arithmetic/mul.rs +++ b/evm/src/arithmetic/mul.rs @@ -67,8 +67,8 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::range_check_error; pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { - let input0_limbs = MUL_INPUT_0.map(|c| lv[c].to_canonical_u64() as i64); - let input1_limbs = MUL_INPUT_1.map(|c| lv[c].to_canonical_u64() as i64); + let input0 = read_value_i64_limbs(lv, MUL_INPUT_0); + let input1 = read_value_i64_limbs(lv, MUL_INPUT_1); const MASK: i64 = (1i64 << LIMB_BITS) - 1i64; @@ -79,7 +79,7 @@ pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { // First calculate the coefficients of a(x)*b(x) (in unreduced_prod), // then do carry propagation to obtain C = c(β) = a(β)*b(β). let mut cy = 0i64; - let mut unreduced_prod = pol_mul_lo(input0_limbs, input1_limbs); + let mut unreduced_prod = pol_mul_lo(input0, input1); for col in 0..N_LIMBS { let t = unreduced_prod[col] + cy; cy = t >> LIMB_BITS; @@ -90,15 +90,13 @@ pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { // aux_limbs to handle the fact that unreduced_prod will // inevitably contain one digit's worth that is > 2^256. + lv[MUL_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_i64(c))); pol_sub_assign(&mut unreduced_prod, &output_limbs); let mut aux_limbs = pol_remove_root_2exp::(unreduced_prod); aux_limbs[N_LIMBS - 1] = -cy; - for deg in 0..N_LIMBS { - lv[MUL_OUTPUT[deg]] = F::from_canonical_i64(output_limbs[deg]); - lv[MUL_AUX_INPUT[deg]] = F::from_noncanonical_i64(aux_limbs[deg]); - } + lv[MUL_AUX_INPUT].copy_from_slice(&aux_limbs.map(|c| F::from_noncanonical_i64(c))); } pub fn eval_packed_generic( @@ -111,10 +109,10 @@ pub fn eval_packed_generic( range_check_error!(MUL_AUX_INPUT, 20); let is_mul = lv[IS_MUL]; - let input0_limbs = MUL_INPUT_0.map(|c| lv[c]); - let input1_limbs = MUL_INPUT_1.map(|c| lv[c]); - let output_limbs = MUL_OUTPUT.map(|c| lv[c]); - let aux_limbs = MUL_AUX_INPUT.map(|c| lv[c]); + let input0_limbs = read_value::(lv, MUL_INPUT_0); + let input1_limbs = read_value::(lv, MUL_INPUT_1); + let output_limbs = read_value::(lv, MUL_OUTPUT); + let aux_limbs = read_value::(lv, MUL_AUX_INPUT); // Constraint poly holds the coefficients of the polynomial that // must be identically zero for this multiplication to be @@ -123,13 +121,13 @@ pub fn eval_packed_generic( // These two lines set constr_poly to the polynomial a(x)b(x) - c(x), // where a, b and c are the polynomials // - // a(x) = \sum_i input0_limbs[i] * β^i - // b(x) = \sum_i input1_limbs[i] * β^i - // c(x) = \sum_i output_limbs[i] * β^i + // a(x) = \sum_i input0_limbs[i] * x^i + // b(x) = \sum_i input1_limbs[i] * x^i + // c(x) = \sum_i output_limbs[i] * x^i // - // This polynomial should equal where s is + // This polynomial should equal (x - β)*s(x) where s is // - // s(x) = \sum_i aux_limbs[i] * β^i + // s(x) = \sum_i aux_limbs[i] * x^i // let mut constr_poly = pol_mul_lo(input0_limbs, input1_limbs); pol_sub_assign(&mut constr_poly, &output_limbs); @@ -153,10 +151,10 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr: &mut RecursiveConstraintConsumer, ) { let is_mul = lv[IS_MUL]; - let input0_limbs = MUL_INPUT_0.map(|c| lv[c]); - let input1_limbs = MUL_INPUT_1.map(|c| lv[c]); - let output_limbs = MUL_OUTPUT.map(|c| lv[c]); - let aux_limbs = MUL_AUX_INPUT.map(|c| lv[c]); + let input0_limbs = read_value::(lv, MUL_INPUT_0); + let input1_limbs = read_value::(lv, MUL_INPUT_1); + let output_limbs = read_value::(lv, MUL_OUTPUT); + let aux_limbs = read_value::(lv, MUL_AUX_INPUT); let mut constr_poly = pol_mul_lo_ext_circuit(builder, input0_limbs, input1_limbs); pol_sub_assign_ext_circuit(builder, &mut constr_poly, &output_limbs); @@ -220,7 +218,7 @@ mod tests { for _i in 0..N_RND_TESTS { // set inputs to random values - for (&ai, bi) in MUL_INPUT_0.iter().zip(MUL_INPUT_1) { + for (ai, bi) in MUL_INPUT_0.zip(MUL_INPUT_1) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); } diff --git a/evm/src/arithmetic/sub.rs b/evm/src/arithmetic/sub.rs index 25834406..f8377651 100644 --- a/evm/src/arithmetic/sub.rs +++ b/evm/src/arithmetic/sub.rs @@ -6,6 +6,7 @@ use plonky2::iop::ext_target::ExtensionTarget; use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal}; use crate::arithmetic::columns::*; +use crate::arithmetic::utils::read_value_u64_limbs; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; @@ -28,14 +29,12 @@ pub(crate) fn u256_sub_br(input0: [u64; N_LIMBS], input1: [u64; N_LIMBS]) -> ([u } pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { - let input0_limbs = SUB_INPUT_0.map(|c| lv[c].to_canonical_u64()); - let input1_limbs = SUB_INPUT_1.map(|c| lv[c].to_canonical_u64()); + let input0 = read_value_u64_limbs(lv, SUB_INPUT_0); + let input1 = read_value_u64_limbs(lv, SUB_INPUT_1); - let (output_limbs, _) = u256_sub_br(input0_limbs, input1_limbs); + let (output_limbs, _) = u256_sub_br(input0, input1); - for (&c, output_limb) in SUB_OUTPUT.iter().zip(output_limbs) { - lv[c] = F::from_canonical_u64(output_limb); - } + lv[SUB_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_u64(c))); } pub fn eval_packed_generic( @@ -47,13 +46,18 @@ pub fn eval_packed_generic( range_check_error!(SUB_OUTPUT, 16); let is_sub = lv[IS_SUB]; - let input0_limbs = SUB_INPUT_0.iter().map(|&c| lv[c]); - let input1_limbs = SUB_INPUT_1.iter().map(|&c| lv[c]); - let output_limbs = SUB_OUTPUT.iter().map(|&c| lv[c]); + let input0_limbs = &lv[SUB_INPUT_0]; + let input1_limbs = &lv[SUB_INPUT_1]; + let output_limbs = &lv[SUB_OUTPUT]; - let output_computed = input0_limbs.zip(input1_limbs).map(|(a, b)| a - b); + let output_computed = input0_limbs.iter().zip(input1_limbs).map(|(&a, &b)| a - b); - eval_packed_generic_are_equal(yield_constr, is_sub, output_limbs, output_computed); + eval_packed_generic_are_equal( + yield_constr, + is_sub, + output_limbs.iter().copied(), + output_computed, + ); } #[allow(clippy::needless_collect)] @@ -63,24 +67,25 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr: &mut RecursiveConstraintConsumer, ) { let is_sub = lv[IS_SUB]; - let input0_limbs = SUB_INPUT_0.iter().map(|&c| lv[c]); - let input1_limbs = SUB_INPUT_1.iter().map(|&c| lv[c]); - let output_limbs = SUB_OUTPUT.iter().map(|&c| lv[c]); + let input0_limbs = &lv[SUB_INPUT_0]; + let input1_limbs = &lv[SUB_INPUT_1]; + let output_limbs = &lv[SUB_OUTPUT]; // Since `map` is lazy and the closure passed to it borrows // `builder`, we can't then borrow builder again below in the call // to `eval_ext_circuit_are_equal`. The solution is to force // evaluation with `collect`. let output_computed = input0_limbs + .iter() .zip(input1_limbs) - .map(|(a, b)| builder.sub_extension(a, b)) + .map(|(&a, &b)| builder.sub_extension(a, b)) .collect::>>(); eval_ext_circuit_are_equal( builder, yield_constr, is_sub, - output_limbs, + output_limbs.iter().copied(), output_computed.into_iter(), ); } @@ -134,7 +139,7 @@ mod tests { for _ in 0..N_RND_TESTS { // set inputs to random values - for (&ai, bi) in SUB_INPUT_0.iter().zip(SUB_INPUT_1) { + for (ai, bi) in SUB_INPUT_0.zip(SUB_INPUT_1) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); } diff --git a/evm/src/arithmetic/utils.rs b/evm/src/arithmetic/utils.rs index ccb8bc0a..871a9646 100644 --- a/evm/src/arithmetic/utils.rs +++ b/evm/src/arithmetic/utils.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, AddAssign, Mul, Neg, Shr, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Mul, Neg, Range, Shr, Sub, SubAssign}; use log::error; use plonky2::field::extension::Extendable; @@ -6,7 +6,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::plonk::circuit_builder::CircuitBuilder; -use crate::arithmetic::columns::N_LIMBS; +use crate::arithmetic::columns::{NUM_ARITH_COLUMNS, N_LIMBS}; /// Emit an error message regarding unchecked range assumptions. /// Assumes the values in `cols` are `[cols[0], cols[0] + 1, ..., @@ -14,7 +14,7 @@ use crate::arithmetic::columns::N_LIMBS; pub(crate) fn _range_check_error( file: &str, line: u32, - cols: &[usize], + cols: Range, signedness: &str, ) { error!( @@ -23,8 +23,8 @@ pub(crate) fn _range_check_error( file, RC_BITS, signedness, - cols[0], - cols[0] + cols.len() - 1 + cols.start, + cols.end - 1, ); } @@ -34,7 +34,7 @@ macro_rules! range_check_error { $crate::arithmetic::utils::_range_check_error::<$rc_bits>( file!(), line!(), - &$cols, + $cols, "unsigned", ); }; @@ -42,7 +42,7 @@ macro_rules! range_check_error { $crate::arithmetic::utils::_range_check_error::<$rc_bits>( file!(), line!(), - &$cols, + $cols, "signed", ); }; @@ -337,3 +337,34 @@ where } q } + +/// Read the range `value_idxs` of values from `lv` into an array of +/// length `N`. Panics if the length of the range is not `N`. +pub(crate) fn read_value( + lv: &[T; NUM_ARITH_COLUMNS], + value_idxs: Range, +) -> [T; N] { + lv[value_idxs].try_into().unwrap() +} + +/// Read the range `value_idxs` of values from `lv` into an array of +/// length `N`, interpreting the values as `u64`s. Panics if the +/// length of the range is not `N`. +pub(crate) fn read_value_u64_limbs( + lv: &[F; NUM_ARITH_COLUMNS], + value_idxs: Range, +) -> [u64; N] { + let limbs: [_; N] = lv[value_idxs].try_into().unwrap(); + limbs.map(|c| F::to_canonical_u64(&c)) +} + +/// Read the range `value_idxs` of values from `lv` into an array of +/// length `N`, interpreting the values as `i64`s. Panics if the +/// length of the range is not `N`. +pub(crate) fn read_value_i64_limbs( + lv: &[F; NUM_ARITH_COLUMNS], + value_idxs: Range, +) -> [i64; N] { + let limbs: [_; N] = lv[value_idxs].try_into().unwrap(); + limbs.map(|c| F::to_canonical_u64(&c) as i64) +} From f4c0337af7b70e6fec25dff7451b8aebcbcdbd9d Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 10 Oct 2022 23:46:45 -0700 Subject: [PATCH 13/21] Interpreter feature to configure debug offsets --- evm/src/cpu/kernel/interpreter.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 2eb9dcb9..a85f3db9 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -75,6 +75,7 @@ pub struct Interpreter<'a> { pub(crate) generation_state: GenerationState, prover_inputs_map: &'a HashMap, pub(crate) halt_offsets: Vec, + pub(crate) debug_offsets: Vec, running: bool, } @@ -128,6 +129,7 @@ impl<'a> Interpreter<'a> { prover_inputs_map: prover_inputs, context: 0, halt_offsets: vec![DEFAULT_HALT_OFFSET], + debug_offsets: vec![], running: false, } } @@ -283,7 +285,7 @@ impl<'a> Interpreter<'a> { 0x55 => todo!(), // "SSTORE", 0x56 => self.run_jump(), // "JUMP", 0x57 => self.run_jumpi(), // "JUMPI", - 0x58 => todo!(), // "GETPC", + 0x58 => self.run_pc(), // "PC", 0x59 => self.run_msize(), // "MSIZE", 0x5a => todo!(), // "GAS", 0x5b => self.run_jumpdest(), // "JUMPDEST", @@ -318,9 +320,24 @@ impl<'a> Interpreter<'a> { 0xff => todo!(), // "SELFDESTRUCT", _ => bail!("Unrecognized opcode {}.", opcode), }; + + if self.debug_offsets.contains(&self.offset) { + println!("At {}, stack={:?}", self.offset_name(), self.stack()); + } + Ok(()) } + /// Get a string representation of the current offset for debugging purposes. + fn offset_name(&self) -> String { + // TODO: Not sure we should use KERNEL? Interpreter is more general in other places. + let label = KERNEL + .global_labels + .iter() + .find_map(|(k, v)| (*v == self.offset).then(|| k.clone())); + label.unwrap_or_else(|| self.offset.to_string()) + } + fn run_stop(&mut self) { self.running = false; } @@ -476,6 +493,7 @@ impl<'a> Interpreter<'a> { let bytes = (offset..offset + size) .map(|i| self.memory.mload_general(context, segment, i).byte(0)) .collect::>(); + println!("Hashing {:?}", &bytes); let hash = keccak(bytes); self.push(U256::from_big_endian(hash.as_bytes())); } @@ -544,6 +562,10 @@ impl<'a> Interpreter<'a> { } } + fn run_pc(&mut self) { + self.push((self.offset - 1).into()); + } + fn run_msize(&mut self) { let num_bytes = self.memory.context_memory[self.context].segments [Segment::MainMemory as usize] From 299aabf860241be96e4eaba4f1d7761289afbfb1 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 11 Oct 2022 08:46:40 -0700 Subject: [PATCH 14/21] Fix branch hashing bug --- evm/src/cpu/kernel/asm/memory/core.asm | 13 +++++ evm/src/cpu/kernel/asm/mpt/hash.asm | 56 ++++++++++++------- .../cpu/kernel/constants/global_metadata.rs | 8 ++- evm/src/cpu/kernel/interpreter.rs | 10 +++- evm/src/cpu/kernel/tests/mpt/hash.rs | 6 +- evm/src/cpu/kernel/tests/mpt/insert.rs | 34 ++++++++++- evm/src/memory/segments.rs | 12 +++- 7 files changed, 113 insertions(+), 26 deletions(-) diff --git a/evm/src/cpu/kernel/asm/memory/core.asm b/evm/src/cpu/kernel/asm/memory/core.asm index f4bcf1f1..2b4d2b68 100644 --- a/evm/src/cpu/kernel/asm/memory/core.asm +++ b/evm/src/cpu/kernel/asm/memory/core.asm @@ -55,6 +55,19 @@ // stack: (empty) %endmacro +// Store a single value from the given segment of kernel (context 0) memory. +%macro mstore_kernel(segment, offset) + // stack: value + PUSH $offset + // stack: offset, value + PUSH $segment + // stack: segment, offset, value + PUSH 0 // kernel has context 0 + // stack: context, segment, offset, value + MSTORE_GENERAL + // stack: (empty) +%endmacro + // Load from the kernel a big-endian u32, consisting of 4 bytes (c_3, c_2, c_1, c_0) %macro mload_kernel_u32(segment) // stack: offset diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index 511e9d18..8342d650 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -118,14 +118,22 @@ encode_node_branch: POP // stack: node_payload_ptr, encode_value, retdest + // Get the next unused offset within the encoded child buffers. + // Then immediately increment the next unused offset by 16, so any + // recursive calls will use nonoverlapping offsets. + %mload_global_metadata(@TRIE_ENCODED_CHILD_SIZE) + DUP1 %add_const(16) + %mstore_global_metadata(@TRIE_ENCODED_CHILD_SIZE) + // stack: base_offset, node_payload_ptr, encode_value, retdest + // We will call encode_or_hash_node on each child. For the i'th child, we - // will store the result in SEGMENT_KERNEL_GENERAL[i], and its length in - // SEGMENT_KERNEL_GENERAL_2[i]. + // will store the result in SEGMENT_TRIE_ENCODED_CHILD[base + i], and its length in + // SEGMENT_TRIE_ENCODED_CHILD_LEN[base + i]. %encode_child(0) %encode_child(1) %encode_child(2) %encode_child(3) %encode_child(4) %encode_child(5) %encode_child(6) %encode_child(7) %encode_child(8) %encode_child(9) %encode_child(10) %encode_child(11) %encode_child(12) %encode_child(13) %encode_child(14) %encode_child(15) - // stack: node_payload_ptr, encode_value, retdest + // stack: base_offset, node_payload_ptr, encode_value, retdest // Now, append each child to our RLP tape. PUSH 9 // rlp_pos; we start at 9 to leave room to prepend a list prefix @@ -133,6 +141,11 @@ encode_node_branch: %append_child(4) %append_child(5) %append_child(6) %append_child(7) %append_child(8) %append_child(9) %append_child(10) %append_child(11) %append_child(12) %append_child(13) %append_child(14) %append_child(15) + // stack: rlp_pos', base_offset, node_payload_ptr, encode_value, retdest + + // We no longer need base_offset. + SWAP1 + POP // stack: rlp_pos', node_payload_ptr, encode_value, retdest SWAP1 @@ -165,43 +178,44 @@ encode_node_branch_prepend_prefix: JUMP // Part of the encode_node_branch function. Encodes the i'th child. -// Stores the result in SEGMENT_KERNEL_GENERAL[i], and its length in -// SEGMENT_KERNEL_GENERAL_2[i]. +// Stores the result in SEGMENT_TRIE_ENCODED_CHILD[base + i], and its length in +// SEGMENT_TRIE_ENCODED_CHILD_LEN[base + i]. %macro encode_child(i) - // stack: node_payload_ptr, encode_value, retdest + // stack: base_offset, node_payload_ptr, encode_value, retdest PUSH %%after_encode - DUP3 DUP3 - // stack: node_payload_ptr, encode_value, %%after_encode, node_payload_ptr, encode_value, retdest + DUP4 DUP4 + // stack: node_payload_ptr, encode_value, %%after_encode, base_offset, node_payload_ptr, encode_value, retdest %add_const($i) %mload_trie_data - // stack: child_i_ptr, encode_value, %%after_encode, node_payload_ptr, encode_value, retdest + // stack: child_i_ptr, encode_value, %%after_encode, base_offset, node_payload_ptr, encode_value, retdest %jump(encode_or_hash_node) %%after_encode: - // stack: result, result_len, node_payload_ptr, encode_value, retdest - %mstore_kernel_general($i) - %mstore_kernel_general_2($i) - // stack: node_payload_ptr, encode_value, retdest + // stack: result, result_len, base_offset, node_payload_ptr, encode_value, retdest + DUP3 %add_const($i) %mstore_kernel(@SEGMENT_TRIE_ENCODED_CHILD) + // stack: result_len, base_offset, node_payload_ptr, encode_value, retdest + DUP2 %add_const($i) %mstore_kernel(@SEGMENT_TRIE_ENCODED_CHILD_LEN) + // stack: base_offset, node_payload_ptr, encode_value, retdest %endmacro // Part of the encode_node_branch function. Appends the i'th child's RLP. %macro append_child(i) - // stack: rlp_pos, node_payload_ptr, encode_value, retdest - %mload_kernel_general($i) // load result - %mload_kernel_general_2($i) // load result_len - // stack: result_len, result, rlp_pos, node_payload_ptr, encode_value, retdest + // stack: rlp_pos, base_offset, node_payload_ptr, encode_value, retdest + DUP2 %add_const($i) %mload_kernel(@SEGMENT_TRIE_ENCODED_CHILD) // load result + DUP3 %add_const($i) %mload_kernel(@SEGMENT_TRIE_ENCODED_CHILD_LEN) // load result_len + // stack: result_len, result, rlp_pos, base_offset, node_payload_ptr, encode_value, retdest // If result_len != 32, result is raw RLP, with an appropriate RLP prefix already. DUP1 %sub_const(32) %jumpi(%%unpack) // Otherwise, result is a hash, and we need to add the prefix 0x80 + 32 = 160. - // stack: result_len, result, rlp_pos, node_payload_ptr, encode_value, retdest + // stack: result_len, result, rlp_pos, base_offset, node_payload_ptr, encode_value, retdest PUSH 160 DUP4 // rlp_pos %mstore_rlp SWAP2 %increment SWAP2 // rlp_pos += 1 %%unpack: - %stack (result_len, result, rlp_pos, node_payload_ptr, encode_value, retdest) - -> (rlp_pos, result, result_len, %%after_unpacking, node_payload_ptr, encode_value, retdest) + %stack (result_len, result, rlp_pos, base_offset, node_payload_ptr, encode_value, retdest) + -> (rlp_pos, result, result_len, %%after_unpacking, base_offset, node_payload_ptr, encode_value, retdest) %jump(mstore_unpacking_rlp) %%after_unpacking: - // stack: rlp_pos', node_payload_ptr, encode_value, retdest + // stack: rlp_pos', base_offset, node_payload_ptr, encode_value, retdest %endmacro encode_node_extension: diff --git a/evm/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs index f3f34e7a..295cdfd5 100644 --- a/evm/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -31,10 +31,14 @@ pub(crate) enum GlobalMetadata { StateTrieRootDigestAfter = 11, TransactionTrieRootDigestAfter = 12, ReceiptTrieRootDigestAfter = 13, + + /// The sizes of the `TrieEncodedChild` and `TrieEncodedChildLen` buffers. In other words, the + /// next available offset in these buffers. + TrieEncodedChildSize = 14, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 14; + pub(crate) const COUNT: usize = 15; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -52,6 +56,7 @@ impl GlobalMetadata { Self::StateTrieRootDigestAfter, Self::TransactionTrieRootDigestAfter, Self::ReceiptTrieRootDigestAfter, + Self::TrieEncodedChildSize, ] } @@ -80,6 +85,7 @@ impl GlobalMetadata { GlobalMetadata::ReceiptTrieRootDigestAfter => { "GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER" } + GlobalMetadata::TrieEncodedChildSize => "TRIE_ENCODED_CHILD_SIZE", } } } diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 2eb9dcb9..5f3c7dcc 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -1,3 +1,5 @@ +//! An EVM interpreter for testing and debugging purposes. + use std::collections::HashMap; use anyhow::{anyhow, bail, ensure}; @@ -609,7 +611,13 @@ 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()); + assert!( + value.bits() <= segment.bit_range(), + "Value {} exceeds {:?} range of {} bits", + value, + segment, + segment.bit_range() + ); self.memory.mstore_general(context, segment, offset, value); } } diff --git a/evm/src/cpu/kernel/tests/mpt/hash.rs b/evm/src/cpu/kernel/tests/mpt/hash.rs index 6b31a523..dd09f350 100644 --- a/evm/src/cpu/kernel/tests/mpt/hash.rs +++ b/evm/src/cpu/kernel/tests/mpt/hash.rs @@ -90,7 +90,11 @@ fn mpt_hash_branch_to_leaf() -> Result<()> { value: account_rlp.to_vec(), }; let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); - children[0] = Box::new(leaf); + children[5] = Box::new(PartialTrie::Branch { + children: children.clone(), + value: vec![], + }); + children[3] = Box::new(leaf); let state_trie = PartialTrie::Branch { children, value: vec![], diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs index 103bdd2e..218e1681 100644 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -135,7 +135,8 @@ fn mpt_insert_branch_replacing_empty_child() -> Result<()> { } #[test] -fn mpt_insert_extension_to_leaf_same_key() -> Result<()> { +fn mpt_insert_extension_nonoverlapping_keys() -> Result<()> { + // Existing keys are 0xABC, 0xABCDEF; inserted key is 0x12345. let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); children[0xD] = Box::new(PartialTrie::Leaf { nibbles: Nibbles { @@ -154,6 +155,37 @@ fn mpt_insert_extension_to_leaf_same_key() -> Result<()> { value: test_account_1_rlp(), }), }; + let insert = InsertEntry { + nibbles: Nibbles { + count: 5, + packed: 0x12345.into(), + }, + v: test_account_2_rlp(), + }; + test_state_trie(state_trie, insert) +} + +#[test] +fn mpt_insert_extension_insert_key_extends_node_key() -> Result<()> { + // Existing keys are 0xA, 0xABCD; inserted key is 0xABCDEF. + let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); + children[0xB] = Box::new(PartialTrie::Leaf { + nibbles: Nibbles { + count: 2, + packed: 0xCD.into(), + }, + value: test_account_1_rlp(), + }); + let state_trie = PartialTrie::Extension { + nibbles: Nibbles { + count: 1, + packed: 0xA.into(), + }, + child: Box::new(PartialTrie::Branch { + children, + value: test_account_1_rlp(), + }), + }; let insert = InsertEntry { nibbles: Nibbles { count: 6, diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index 44390a9b..b6254900 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -38,10 +38,14 @@ pub(crate) enum Segment { /// `StorageTriePointers` with `StorageTrieCheckpointPointers`. /// See also `StateTrieCheckpointPointer`. StorageTrieCheckpointPointers = 15, + /// A buffer used to store the encodings of a branch node's children. + TrieEncodedChild = 16, + /// A buffer used to store the lengths of the encodings of a branch node's children. + TrieEncodedChildLen = 17, } impl Segment { - pub(crate) const COUNT: usize = 16; + pub(crate) const COUNT: usize = 18; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -61,6 +65,8 @@ impl Segment { Self::StorageTrieAddresses, Self::StorageTriePointers, Self::StorageTrieCheckpointPointers, + Self::TrieEncodedChild, + Self::TrieEncodedChildLen, ] } @@ -83,6 +89,8 @@ impl Segment { Segment::StorageTrieAddresses => "SEGMENT_STORAGE_TRIE_ADDRS", Segment::StorageTriePointers => "SEGMENT_STORAGE_TRIE_PTRS", Segment::StorageTrieCheckpointPointers => "SEGMENT_STORAGE_TRIE_CHECKPOINT_PTRS", + Segment::TrieEncodedChild => "SEGMENT_TRIE_ENCODED_CHILD", + Segment::TrieEncodedChildLen => "SEGMENT_TRIE_ENCODED_CHILD_LEN", } } @@ -105,6 +113,8 @@ impl Segment { Segment::StorageTrieAddresses => 160, Segment::StorageTriePointers => 32, Segment::StorageTrieCheckpointPointers => 32, + Segment::TrieEncodedChild => 256, + Segment::TrieEncodedChildLen => 6, } } } From cb2e69a2c9d2247828fd20d11aa5449fa62e59fe Mon Sep 17 00:00:00 2001 From: BGluth Date: Tue, 11 Oct 2022 20:15:33 -0600 Subject: [PATCH 15/21] Updated `eth_trie_utils` to `0.2.0` --- evm/Cargo.toml | 2 +- evm/src/cpu/kernel/tests/mpt/hash.rs | 25 ++-- evm/src/cpu/kernel/tests/mpt/insert.rs | 193 ++++++------------------- evm/src/cpu/kernel/tests/mpt/load.rs | 11 +- evm/src/cpu/kernel/tests/mpt/mod.rs | 21 ++- 5 files changed, 76 insertions(+), 176 deletions(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 848dff15..a3dc09e2 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } -eth_trie_utils = "0.1.0" +eth_trie_utils = "0.2.0" anyhow = "1.0.40" env_logger = "0.9.0" ethereum-types = "0.14.0" diff --git a/evm/src/cpu/kernel/tests/mpt/hash.rs b/evm/src/cpu/kernel/tests/mpt/hash.rs index dd09f350..de519797 100644 --- a/evm/src/cpu/kernel/tests/mpt/hash.rs +++ b/evm/src/cpu/kernel/tests/mpt/hash.rs @@ -1,7 +1,8 @@ use anyhow::Result; -use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use eth_trie_utils::partial_trie::PartialTrie; use ethereum_types::{BigEndianHash, H256, U256}; +use super::nibbles; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::interpreter::Interpreter; use crate::cpu::kernel::tests::mpt::extension_to_leaf; @@ -33,10 +34,7 @@ fn mpt_hash_leaf() -> Result<()> { let account_rlp = rlp::encode(&account); let state_trie = PartialTrie::Leaf { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, + nibbles: nibbles(0xABC), value: account_rlp.to_vec(), }; @@ -83,18 +81,17 @@ fn mpt_hash_branch_to_leaf() -> Result<()> { let account_rlp = rlp::encode(&account); let leaf = PartialTrie::Leaf { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, + nibbles: nibbles(0xABC), value: account_rlp.to_vec(), - }; - let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); - children[5] = Box::new(PartialTrie::Branch { + } + .into(); + let mut children = std::array::from_fn(|_| PartialTrie::Empty.into()); + children[5] = PartialTrie::Branch { children: children.clone(), value: vec![], - }); - children[3] = Box::new(leaf); + } + .into(); + children[3] = leaf; let state_trie = PartialTrie::Branch { children, value: vec![], diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs index 218e1681..469ad1e4 100644 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -1,8 +1,8 @@ use anyhow::Result; use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; -use eth_trie_utils::trie_builder::InsertEntry; use ethereum_types::{BigEndianHash, H256}; +use super::nibbles; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; @@ -12,218 +12,124 @@ use crate::generation::TrieInputs; #[test] fn mpt_insert_empty() -> Result<()> { - let insert = InsertEntry { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, - v: test_account_2_rlp(), - }; - test_state_trie(Default::default(), insert) + test_state_trie(Default::default(), nibbles(0xABC), test_account_2_rlp()) } #[test] fn mpt_insert_leaf_identical_keys() -> Result<()> { - let key = Nibbles { - count: 3, - packed: 0xABC.into(), - }; + let key = nibbles(0xABC); let state_trie = PartialTrie::Leaf { nibbles: key, value: test_account_1_rlp(), }; - let insert = InsertEntry { - nibbles: key, - v: test_account_2_rlp(), - }; - test_state_trie(state_trie, insert) + test_state_trie(state_trie, key, test_account_2_rlp()) } #[test] fn mpt_insert_leaf_nonoverlapping_keys() -> Result<()> { let state_trie = PartialTrie::Leaf { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, + nibbles: nibbles(0xABC), value: test_account_1_rlp(), }; - let insert = InsertEntry { - nibbles: Nibbles { - count: 3, - packed: 0x123.into(), - }, - v: test_account_2_rlp(), - }; - test_state_trie(state_trie, insert) + test_state_trie(state_trie, nibbles(0x123), test_account_2_rlp()) } #[test] fn mpt_insert_leaf_overlapping_keys() -> Result<()> { let state_trie = PartialTrie::Leaf { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, + nibbles: nibbles(0xABC), value: test_account_1_rlp(), }; - let insert = InsertEntry { - nibbles: Nibbles { - count: 3, - packed: 0xADE.into(), - }, - v: test_account_2_rlp(), - }; - test_state_trie(state_trie, insert) + test_state_trie(state_trie, nibbles(0xADE), test_account_2_rlp()) } #[test] fn mpt_insert_leaf_insert_key_extends_leaf_key() -> Result<()> { let state_trie = PartialTrie::Leaf { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, + nibbles: nibbles(0xABC), value: test_account_1_rlp(), }; - let insert = InsertEntry { - nibbles: Nibbles { - count: 5, - packed: 0xABCDE.into(), - }, - v: test_account_2_rlp(), - }; - test_state_trie(state_trie, insert) + test_state_trie(state_trie, nibbles(0xABCDE), test_account_2_rlp()) } #[test] fn mpt_insert_leaf_leaf_key_extends_insert_key() -> Result<()> { let state_trie = PartialTrie::Leaf { - nibbles: Nibbles { - count: 5, - packed: 0xABCDE.into(), - }, + nibbles: nibbles(0xABCDE), value: test_account_1_rlp(), }; - let insert = InsertEntry { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, - v: test_account_2_rlp(), - }; - test_state_trie(state_trie, insert) + test_state_trie(state_trie, nibbles(0xABC), test_account_2_rlp()) } #[test] fn mpt_insert_branch_replacing_empty_child() -> Result<()> { - let children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); + let children = std::array::from_fn(|_| PartialTrie::Empty.into()); let state_trie = PartialTrie::Branch { children, value: vec![], }; - let insert = InsertEntry { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, - v: test_account_2_rlp(), - }; - - test_state_trie(state_trie, insert) + test_state_trie(state_trie, nibbles(0xABC), test_account_2_rlp()) } #[test] fn mpt_insert_extension_nonoverlapping_keys() -> Result<()> { // Existing keys are 0xABC, 0xABCDEF; inserted key is 0x12345. - let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); - children[0xD] = Box::new(PartialTrie::Leaf { - nibbles: Nibbles { - count: 2, - packed: 0xEF.into(), - }, + let mut children = std::array::from_fn(|_| PartialTrie::Empty.into()); + children[0xD] = PartialTrie::Leaf { + nibbles: nibbles(0xEF), value: test_account_1_rlp(), - }); + } + .into(); let state_trie = PartialTrie::Extension { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, - child: Box::new(PartialTrie::Branch { + nibbles: nibbles(0xABC), + child: PartialTrie::Branch { children, value: test_account_1_rlp(), - }), + } + .into(), }; - let insert = InsertEntry { - nibbles: Nibbles { - count: 5, - packed: 0x12345.into(), - }, - v: test_account_2_rlp(), - }; - test_state_trie(state_trie, insert) + test_state_trie(state_trie, nibbles(0x12345), test_account_2_rlp()) } #[test] fn mpt_insert_extension_insert_key_extends_node_key() -> Result<()> { // Existing keys are 0xA, 0xABCD; inserted key is 0xABCDEF. - let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); - children[0xB] = Box::new(PartialTrie::Leaf { - nibbles: Nibbles { - count: 2, - packed: 0xCD.into(), - }, + let mut children = std::array::from_fn(|_| PartialTrie::Empty.into()); + children[0xB] = PartialTrie::Leaf { + nibbles: nibbles(0xCD), value: test_account_1_rlp(), - }); + } + .into(); let state_trie = PartialTrie::Extension { - nibbles: Nibbles { - count: 1, - packed: 0xA.into(), - }, - child: Box::new(PartialTrie::Branch { + nibbles: nibbles(0xA), + child: PartialTrie::Branch { children, value: test_account_1_rlp(), - }), + } + .into(), }; - let insert = InsertEntry { - nibbles: Nibbles { - count: 6, - packed: 0xABCDEF.into(), - }, - v: test_account_2_rlp(), - }; - test_state_trie(state_trie, insert) + test_state_trie(state_trie, nibbles(0xABCDEF), test_account_2_rlp()) } #[test] fn mpt_insert_branch_to_leaf_same_key() -> Result<()> { let leaf = PartialTrie::Leaf { - nibbles: Nibbles { - count: 3, - packed: 0xBCD.into(), - }, + nibbles: nibbles(0xBCD), value: test_account_1_rlp(), - }; - let mut children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); - children[0xA] = Box::new(leaf); + } + .into(); + let mut children = std::array::from_fn(|_| PartialTrie::Empty.into()); + children[0xA] = leaf; let state_trie = PartialTrie::Branch { children, value: vec![], }; - let insert = InsertEntry { - nibbles: Nibbles { - count: 4, - packed: 0xABCD.into(), - }, - v: test_account_2_rlp(), - }; - - test_state_trie(state_trie, insert) + test_state_trie(state_trie, nibbles(0xABCD), test_account_2_rlp()) } -fn test_state_trie(state_trie: PartialTrie, insert: InsertEntry) -> Result<()> { +fn test_state_trie(state_trie: PartialTrie, k: Nibbles, v: Vec) -> Result<()> { let trie_inputs = TrieInputs { state_trie: state_trie.clone(), transactions_trie: Default::default(), @@ -249,7 +155,7 @@ fn test_state_trie(state_trie: PartialTrie, insert: InsertEntry) -> Result<()> { trie_data.push(0.into()); } let value_ptr = trie_data.len(); - let account: AccountRlp = rlp::decode(&insert.v).expect("Decoding failed"); + let account: AccountRlp = rlp::decode(&v).expect("Decoding failed"); let account_data = account.to_vec(); trie_data.push(account_data.len().into()); trie_data.extend(account_data); @@ -257,8 +163,8 @@ fn test_state_trie(state_trie: PartialTrie, insert: InsertEntry) -> Result<()> { interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len); interpreter.push(0xDEADBEEFu32.into()); interpreter.push(value_ptr.into()); // value_ptr - interpreter.push(insert.nibbles.packed); // key - interpreter.push(insert.nibbles.count.into()); // num_nibbles + interpreter.push(k.packed); // key + interpreter.push(k.count.into()); // num_nibbles interpreter.run()?; assert_eq!( @@ -281,18 +187,9 @@ fn test_state_trie(state_trie: PartialTrie, insert: InsertEntry) -> Result<()> { ); let hash = H256::from_uint(&interpreter.stack()[0]); - let updated_trie = apply_insert(state_trie, insert); + let updated_trie = state_trie.insert(k, v); let expected_state_trie_hash = updated_trie.calc_hash(); assert_eq!(hash, expected_state_trie_hash); Ok(()) } - -fn apply_insert(trie: PartialTrie, insert: InsertEntry) -> PartialTrie { - let mut trie = Box::new(trie); - if let Some(updated_trie) = PartialTrie::insert_into_trie(&mut trie, insert) { - *updated_trie - } else { - *trie - } -} diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs index ccf8353e..0572458d 100644 --- a/evm/src/cpu/kernel/tests/mpt/load.rs +++ b/evm/src/cpu/kernel/tests/mpt/load.rs @@ -1,12 +1,12 @@ use anyhow::Result; -use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use eth_trie_utils::partial_trie::PartialTrie; use ethereum_types::{BigEndianHash, U256}; -use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::interpreter::Interpreter; use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1, test_account_1_rlp}; +use crate::cpu::kernel::{aggregator::KERNEL, tests::mpt::nibbles}; use crate::generation::mpt::all_mpt_prover_inputs_reversed; use crate::generation::TrieInputs; @@ -54,10 +54,7 @@ fn load_all_mpts_empty() -> Result<()> { fn load_all_mpts_leaf() -> Result<()> { let trie_inputs = TrieInputs { state_trie: PartialTrie::Leaf { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, + nibbles: nibbles(0xABC), value: test_account_1_rlp(), }, transactions_trie: Default::default(), @@ -109,7 +106,7 @@ fn load_all_mpts_leaf() -> Result<()> { #[test] fn load_all_mpts_empty_branch() -> Result<()> { - let children = std::array::from_fn(|_| Box::new(PartialTrie::Empty)); + let children = std::array::from_fn(|_| PartialTrie::Empty.into()); let state_trie = PartialTrie::Branch { children, value: vec![], diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs index e3414b38..2c7999df 100644 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -9,6 +9,17 @@ mod insert; mod load; mod read; +/// Helper function to reduce code duplication. +/// Note that this preserves all nibbles (eg. `0x123` is not interpreted as `0x0123`). +pub(crate) fn nibbles>(v: T) -> Nibbles { + let packed = v.into(); + + Nibbles { + count: Nibbles::get_num_nibbles_in_key(&packed), + packed, + } +} + pub(crate) fn test_account_1() -> AccountRlp { AccountRlp { nonce: U256::from(1111), @@ -38,16 +49,14 @@ pub(crate) fn test_account_2_rlp() -> Vec { /// A `PartialTrie` where an extension node leads to a leaf node containing an account. pub(crate) fn extension_to_leaf(value: Vec) -> PartialTrie { PartialTrie::Extension { - nibbles: Nibbles { - count: 3, - packed: 0xABC.into(), - }, - child: Box::new(PartialTrie::Leaf { + nibbles: nibbles(0xABC), + child: PartialTrie::Leaf { nibbles: Nibbles { count: 3, packed: 0xDEF.into(), }, value, - }), + } + .into(), } } From 06475c2b61a0d3b26d3f3ded791456052b94251b Mon Sep 17 00:00:00 2001 From: BGluth Date: Tue, 11 Oct 2022 22:07:32 -0600 Subject: [PATCH 16/21] Bumped patch version --- evm/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index a3dc09e2..bcbc5cd9 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } -eth_trie_utils = "0.2.0" +eth_trie_utils = "0.2.1" anyhow = "1.0.40" env_logger = "0.9.0" ethereum-types = "0.14.0" From 69bdbf6bf646cf95a06b92f8bca063f893d14f70 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 13 Oct 2022 18:19:05 +0200 Subject: [PATCH 17/21] Redundant `degree_bits` --- evm/src/recursive_verifier.rs | 4 ++-- plonky2/examples/bench_recursion.rs | 6 +++--- plonky2/src/plonk/circuit_builder.rs | 1 - plonky2/src/plonk/circuit_data.rs | 16 +++++++++------- plonky2/src/plonk/get_challenges.rs | 4 ++-- plonky2/src/plonk/prover.rs | 8 ++++---- plonky2/src/plonk/recursive_verifier.rs | 16 ++++++++-------- plonky2/src/plonk/verifier.rs | 2 +- 8 files changed, 29 insertions(+), 28 deletions(-) diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index a16063c4..a7c4fa5d 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -146,7 +146,7 @@ impl, C: GenericConfig, const D: usize> } // Verify the CTL checks. - let degrees_bits = std::array::from_fn(|i| verifier_data[i].common.degree_bits); + let degrees_bits = std::array::from_fn(|i| verifier_data[i].common.degree_bits()); verify_cross_table_lookups::( cross_table_lookups, pis.map(|p| p.ctl_zs_last), @@ -221,7 +221,7 @@ impl, C: GenericConfig, const D: usize> } // Verify the CTL checks. - let degrees_bits = std::array::from_fn(|i| verifier_data[i].common.degree_bits); + let degrees_bits = std::array::from_fn(|i| verifier_data[i].common.degree_bits()); verify_cross_table_lookups_circuit::( builder, cross_table_lookups, diff --git a/plonky2/examples/bench_recursion.rs b/plonky2/examples/bench_recursion.rs index 8073c9dc..24c8922d 100644 --- a/plonky2/examples/bench_recursion.rs +++ b/plonky2/examples/bench_recursion.rs @@ -190,7 +190,7 @@ fn benchmark(config: &CircuitConfig, log2_inner_size: usize) -> Result<()> { info!( "Initial proof degree {} = 2^{}", cd.degree(), - cd.degree_bits + cd.degree_bits() ); // Recursively verify the proof @@ -199,7 +199,7 @@ fn benchmark(config: &CircuitConfig, log2_inner_size: usize) -> Result<()> { info!( "Single recursion proof degree {} = 2^{}", cd.degree(), - cd.degree_bits + cd.degree_bits() ); // Add a second layer of recursion to shrink the proof size further @@ -208,7 +208,7 @@ fn benchmark(config: &CircuitConfig, log2_inner_size: usize) -> Result<()> { info!( "Double recursion proof degree {} = 2^{}", cd.degree(), - cd.degree_bits + cd.degree_bits() ); test_serialization(proof, cd)?; diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index bfa012da..7bc1af95 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -829,7 +829,6 @@ impl, const D: usize> CircuitBuilder { let common = CommonCircuitData { config: self.config, fri_params, - degree_bits, gates, selectors_info, quotient_degree_factor, diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index 7e69ef31..6d631206 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -273,8 +273,6 @@ pub struct CommonCircuitData< pub(crate) fri_params: FriParams, - pub degree_bits: usize, - /// The types of gates used in this circuit, along with their prefixes. pub(crate) gates: Vec>, @@ -306,16 +304,20 @@ pub struct CommonCircuitData< impl, C: GenericConfig, const D: usize> CommonCircuitData { + pub const fn degree_bits(&self) -> usize { + self.fri_params.degree_bits + } + pub fn degree(&self) -> usize { - 1 << self.degree_bits + 1 << self.degree_bits() } pub fn lde_size(&self) -> usize { - 1 << (self.degree_bits + self.config.fri_config.rate_bits) + self.fri_params.lde_size() } pub fn lde_generator(&self) -> F { - F::primitive_root_of_unity(self.degree_bits + self.config.fri_config.rate_bits) + F::primitive_root_of_unity(self.degree_bits() + self.config.fri_config.rate_bits) } pub fn constraint_degree(&self) -> usize { @@ -358,7 +360,7 @@ impl, C: GenericConfig, const D: usize> }; // The Z polynomials are also opened at g * zeta. - let g = F::Extension::primitive_root_of_unity(self.degree_bits); + let g = F::Extension::primitive_root_of_unity(self.degree_bits()); let zeta_next = g * zeta; let zeta_next_batch = FriBatchInfo { point: zeta_next, @@ -384,7 +386,7 @@ impl, C: GenericConfig, const D: usize> }; // The Z polynomials are also opened at g * zeta. - let g = F::primitive_root_of_unity(self.degree_bits); + let g = F::primitive_root_of_unity(self.degree_bits()); let zeta_next = builder.mul_const_extension(g, zeta); let zeta_next_batch = FriBatchInfoTarget { point: zeta_next, diff --git a/plonky2/src/plonk/get_challenges.rs b/plonky2/src/plonk/get_challenges.rs index a8ca52e5..9c3e4aff 100644 --- a/plonky2/src/plonk/get_challenges.rs +++ b/plonky2/src/plonk/get_challenges.rs @@ -61,7 +61,7 @@ fn get_challenges, C: GenericConfig, cons commit_phase_merkle_caps, final_poly, pow_witness, - common_data.degree_bits, + common_data.degree_bits(), &config.fri_config, ), }) @@ -175,7 +175,7 @@ impl, C: GenericConfig, const D: usize> &self.proof.openings.to_fri_openings(), *fri_alpha, ); - let log_n = common_data.degree_bits + common_data.config.fri_config.rate_bits; + let log_n = common_data.degree_bits() + common_data.config.fri_config.rate_bits; // Simulate the proof verification and collect the inferred elements. // The content of the loop is basically the same as the `fri_verifier_query_round` function. for &(mut x_index) in fri_query_indices { diff --git a/plonky2/src/plonk/prover.rs b/plonky2/src/plonk/prover.rs index 3e81942b..7b4a7569 100644 --- a/plonky2/src/plonk/prover.rs +++ b/plonky2/src/plonk/prover.rs @@ -172,9 +172,9 @@ where // To avoid leaking witness data, we want to ensure that our opening locations, `zeta` and // `g * zeta`, are not in our subgroup `H`. It suffices to check `zeta` only, since // `(g * zeta)^n = zeta^n`, where `n` is the order of `g`. - let g = F::Extension::primitive_root_of_unity(common_data.degree_bits); + let g = F::Extension::primitive_root_of_unity(common_data.degree_bits()); ensure!( - zeta.exp_power_of_2(common_data.degree_bits) != F::Extension::ONE, + zeta.exp_power_of_2(common_data.degree_bits()) != F::Extension::ONE, "Opening point is in the subgroup." ); @@ -342,10 +342,10 @@ fn compute_quotient_polys< // steps away since we work on an LDE of degree `max_filtered_constraint_degree`. let next_step = 1 << quotient_degree_bits; - let points = F::two_adic_subgroup(common_data.degree_bits + quotient_degree_bits); + let points = F::two_adic_subgroup(common_data.degree_bits() + quotient_degree_bits); let lde_size = points.len(); - let z_h_on_coset = ZeroPolyOnCoset::new(common_data.degree_bits, quotient_degree_bits); + let z_h_on_coset = ZeroPolyOnCoset::new(common_data.degree_bits(), quotient_degree_bits); let points_batches = points.par_chunks(BATCH_SIZE); let num_batches = ceil_div_usize(points.len(), BATCH_SIZE); diff --git a/plonky2/src/plonk/recursive_verifier.rs b/plonky2/src/plonk/recursive_verifier.rs index ecce34e1..14ae117e 100644 --- a/plonky2/src/plonk/recursive_verifier.rs +++ b/plonky2/src/plonk/recursive_verifier.rs @@ -66,7 +66,7 @@ impl, const D: usize> CircuitBuilder { let partial_products = &proof.openings.partial_products; let zeta_pow_deg = - self.exp_power_of_2_extension(challenges.plonk_zeta, inner_common_data.degree_bits); + self.exp_power_of_2_extension(challenges.plonk_zeta, inner_common_data.degree_bits()); let vanishing_polys_zeta = with_context!( self, "evaluate the vanishing polynomial at our challenge point, zeta.", @@ -223,17 +223,17 @@ mod tests { // Start with a degree 2^14 proof let (proof, vd, cd) = dummy_proof::(&config, 16_000)?; - assert_eq!(cd.degree_bits, 14); + assert_eq!(cd.degree_bits(), 14); // Shrink it to 2^13. let (proof, vd, cd) = recursive_proof::(proof, vd, cd, &config, Some(13), false, false)?; - assert_eq!(cd.degree_bits, 13); + assert_eq!(cd.degree_bits(), 13); // Shrink it to 2^12. let (proof, _vd, cd) = recursive_proof::(proof, vd, cd, &config, None, true, true)?; - assert_eq!(cd.degree_bits, 12); + assert_eq!(cd.degree_bits(), 12); test_serialization(&proof, &cd)?; @@ -255,11 +255,11 @@ mod tests { // An initial dummy proof. let (proof, vd, cd) = dummy_proof::(&standard_config, 4_000)?; - assert_eq!(cd.degree_bits, 12); + assert_eq!(cd.degree_bits(), 12); // A standard recursive proof. let (proof, vd, cd) = recursive_proof(proof, vd, cd, &standard_config, None, false, false)?; - assert_eq!(cd.degree_bits, 12); + assert_eq!(cd.degree_bits(), 12); // A high-rate recursive proof, designed to be verifiable with fewer routed wires. let high_rate_config = CircuitConfig { @@ -273,7 +273,7 @@ mod tests { }; let (proof, vd, cd) = recursive_proof::(proof, vd, cd, &high_rate_config, None, true, true)?; - assert_eq!(cd.degree_bits, 12); + assert_eq!(cd.degree_bits(), 12); // A final proof, optimized for size. let final_config = CircuitConfig { @@ -289,7 +289,7 @@ mod tests { }; let (proof, _vd, cd) = recursive_proof::(proof, vd, cd, &final_config, None, true, true)?; - assert_eq!(cd.degree_bits, 12, "final proof too large"); + assert_eq!(cd.degree_bits(), 12, "final proof too large"); test_serialization(&proof, &cd)?; diff --git a/plonky2/src/plonk/verifier.rs b/plonky2/src/plonk/verifier.rs index 6a4f3790..43f028cb 100644 --- a/plonky2/src/plonk/verifier.rs +++ b/plonky2/src/plonk/verifier.rs @@ -78,7 +78,7 @@ where let quotient_polys_zeta = &proof.openings.quotient_polys; let zeta_pow_deg = challenges .plonk_zeta - .exp_power_of_2(common_data.degree_bits); + .exp_power_of_2(common_data.degree_bits()); let z_h_zeta = zeta_pow_deg - F::Extension::ONE; // `quotient_polys_zeta` holds `num_challenges * quotient_degree_factor` evaluations. // Each chunk of `quotient_degree_factor` holds the evaluations of `t_0(zeta),...,t_{quotient_degree_factor-1}(zeta)` From ec3391f9c4e14055e811697dae957e6c85631639 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Thu, 13 Oct 2022 14:02:19 -0700 Subject: [PATCH 18/21] Add Fp254 ops to the CPU table (#779) * Add Fp254 ops to the CPU table * Add forgotten file --- evm/src/cpu/columns/ops.rs | 3 ++ evm/src/cpu/control_flow.rs | 5 ++- evm/src/cpu/cpu_stark.rs | 4 ++- evm/src/cpu/decode.rs | 5 ++- evm/src/cpu/kernel/interpreter.rs | 3 ++ evm/src/cpu/kernel/opcodes.rs | 3 ++ evm/src/cpu/mod.rs | 1 + evm/src/cpu/modfp254.rs | 53 +++++++++++++++++++++++++++++++ evm/src/cpu/stack.rs | 3 ++ 9 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 evm/src/cpu/modfp254.rs diff --git a/evm/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs index e0cb2952..04d4d0f2 100644 --- a/evm/src/cpu/columns/ops.rs +++ b/evm/src/cpu/columns/ops.rs @@ -19,6 +19,9 @@ pub struct OpsColumnsView { pub mulmod: T, pub exp: T, pub signextend: T, + pub addfp254: T, + pub mulfp254: T, + pub subfp254: T, pub lt: T, pub gt: T, pub slt: T, diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index 3856726c..c7b7c6bb 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -9,7 +9,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; use crate::cpu::kernel::aggregator::KERNEL; // TODO: This list is incomplete. -const NATIVE_INSTRUCTIONS: [usize; 25] = [ +const NATIVE_INSTRUCTIONS: [usize; 28] = [ COL_MAP.op.add, COL_MAP.op.mul, COL_MAP.op.sub, @@ -20,6 +20,9 @@ const NATIVE_INSTRUCTIONS: [usize; 25] = [ COL_MAP.op.addmod, COL_MAP.op.mulmod, COL_MAP.op.signextend, + COL_MAP.op.addfp254, + COL_MAP.op.mulfp254, + COL_MAP.op.subfp254, COL_MAP.op.lt, COL_MAP.op.gt, COL_MAP.op.slt, diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index b11ff9f5..7b34cc4f 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -11,7 +11,7 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; use crate::cpu::{ - bootstrap_kernel, control_flow, decode, dup_swap, jumps, membus, simple_logic, stack, + bootstrap_kernel, control_flow, decode, dup_swap, jumps, membus, modfp254, simple_logic, stack, stack_bounds, syscalls, }; use crate::cross_table_lookup::Column; @@ -150,6 +150,7 @@ impl, const D: usize> Stark for CpuStark, const D: usize> Stark for CpuStark Interpreter<'a> { 0x09 => self.run_mulmod(), // "MULMOD", 0x0a => self.run_exp(), // "EXP", 0x0b => todo!(), // "SIGNEXTEND", + 0x0c => todo!(), // "ADDFP254", + 0x0d => todo!(), // "MULFP254", + 0x0e => todo!(), // "SUBFP254", 0x10 => self.run_lt(), // "LT", 0x11 => self.run_gt(), // "GT", 0x12 => todo!(), // "SLT", diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index c5133050..20601267 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -20,6 +20,9 @@ pub(crate) fn get_opcode(mnemonic: &str) -> u8 { "MULMOD" => 0x09, "EXP" => 0x0a, "SIGNEXTEND" => 0x0b, + "ADDFP254" => 0x0c, + "MULFP254" => 0x0d, + "SUBFP254" => 0x0e, "LT" => 0x10, "GT" => 0x11, "SLT" => 0x12, diff --git a/evm/src/cpu/mod.rs b/evm/src/cpu/mod.rs index bde06585..fda5db80 100644 --- a/evm/src/cpu/mod.rs +++ b/evm/src/cpu/mod.rs @@ -7,6 +7,7 @@ mod dup_swap; mod jumps; pub mod kernel; pub(crate) mod membus; +mod modfp254; mod simple_logic; mod stack; mod stack_bounds; diff --git a/evm/src/cpu/modfp254.rs b/evm/src/cpu/modfp254.rs new file mode 100644 index 00000000..defbf862 --- /dev/null +++ b/evm/src/cpu/modfp254.rs @@ -0,0 +1,53 @@ +use itertools::izip; +use plonky2::field::extension::Extendable; +use plonky2::field::packed::PackedField; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cpu::columns::CpuColumnsView; + +// Python: +// >>> P = 21888242871839275222246405745257275088696311157297823662689037894645226208583 +// >>> "[" + ", ".join(hex((P >> n) % 2**32) for n in range(0, 256, 32)) + "]" +const P_LIMBS: [u32; 8] = [ + 0xd87cfd47, 0x3c208c16, 0x6871ca8d, 0x97816a91, 0x8181585d, 0xb85045b6, 0xe131a029, 0x30644e72, +]; + +pub fn eval_packed( + lv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + let filter = lv.is_cpu_cycle * (lv.op.addfp254 + lv.op.mulfp254 + lv.op.subfp254); + + // We want to use all the same logic as the usual mod operations, but without needing to read + // the modulus from the stack. We simply constrain `mem_channels[2]` to be our prime (that's + // where the modulus goes in the generalized operations). + let channel_val = lv.mem_channels[2].value; + for (channel_limb, p_limb) in izip!(channel_val, P_LIMBS) { + let p_limb = P::Scalar::from_canonical_u32(p_limb); + yield_constr.constraint(filter * (channel_limb - p_limb)); + } +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + let filter = { + let flag_sum = builder.add_many_extension([lv.op.addfp254, lv.op.mulfp254, lv.op.subfp254]); + builder.mul_extension(lv.is_cpu_cycle, flag_sum) + }; + + // We want to use all the same logic as the usual mod operations, but without needing to read + // the modulus from the stack. We simply constrain `mem_channels[2]` to be our prime (that's + // where the modulus goes in the generalized operations). + let channel_val = lv.mem_channels[2].value; + for (channel_limb, p_limb) in izip!(channel_val, P_LIMBS) { + let p_limb = F::from_canonical_u32(p_limb); + let constr = builder.arithmetic_extension(F::ONE, -p_limb, filter, channel_limb, filter); + yield_constr.constraint(builder, constr); + } +} diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index 9bc08091..c72688ed 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -52,6 +52,9 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { mulmod: BASIC_TERNARY_OP, exp: None, // TODO signextend: BASIC_BINARY_OP, + addfp254: BASIC_BINARY_OP, + mulfp254: BASIC_BINARY_OP, + subfp254: BASIC_BINARY_OP, lt: BASIC_BINARY_OP, gt: BASIC_BINARY_OP, slt: BASIC_BINARY_OP, From ecce5be9e399a0f1c81a43ad04050ab527e3c071 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 14 Oct 2022 18:08:25 -0700 Subject: [PATCH 19/21] MPT format tweaks Don't need value lengths in memory. Branches with null values recognized as value_ptr = null. --- evm/spec/{tries.tex => mpts.tex} | 16 ++++++++-------- evm/spec/zkevm.pdf | Bin 161092 -> 153232 bytes evm/spec/zkevm.tex | 2 +- evm/src/cpu/kernel/asm/mpt/hash.asm | 13 ++++--------- evm/src/cpu/kernel/asm/mpt/load.asm | 21 ++++++++++----------- evm/src/cpu/kernel/asm/mpt/read.asm | 17 ++++------------- evm/src/cpu/kernel/tests/mpt/insert.rs | 1 - evm/src/cpu/kernel/tests/mpt/load.rs | 2 -- evm/src/cpu/kernel/tests/mpt/read.rs | 11 +++++------ 9 files changed, 32 insertions(+), 51 deletions(-) rename evm/spec/{tries.tex => mpts.tex} (53%) diff --git a/evm/spec/tries.tex b/evm/spec/mpts.tex similarity index 53% rename from evm/spec/tries.tex rename to evm/spec/mpts.tex index 7ec0fcce..49d1d328 100644 --- a/evm/spec/tries.tex +++ b/evm/spec/mpts.tex @@ -6,21 +6,21 @@ Withour our zkEVM's kernel memory, \begin{enumerate} \item An empty node is encoded as $(\texttt{MPT\_NODE\_EMPTY})$. - \item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, c_1, \dots, c_{16}, \abs{v}, v)$, where each $c_i$ is a pointer to a child node, and $v$ is a value of length $\abs{v}$.\footnote{If a branch node has no associated value, then $\abs{v} = 0$ and $v = ()$.} + \item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, c_1, \dots, c_{16}, v)$, where each $c_i$ is a pointer to a child node, and $v$ is a pointer to a value. If a branch node has no associated value, then $v = 0$, i.e. the null pointer. \item An extension node is encoded as $(\texttt{MPT\_NODE\_EXTENSION}, k, c)$, $k$ represents the part of the key associated with this extension, and is encoded as a 2-tuple $(\texttt{packed\_nibbles}, \texttt{num\_nibbles})$. $c$ is a pointer to a child node. - \item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, \abs{v}, v)$, where $k$ is a 2-tuple as above, and $v$ is a leaf payload. - \item A digest node is encoded as $(\texttt{MPT\_NODE\_DIGEST}, d)$, where $d$ is a Keccak256 digest. + \item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, v)$, where $k$ is a 2-tuple as above, and $v$ is a pointer to a value. + \item A digest node is encoded as $(\texttt{MPT\_NODE\_HASH}, d)$, where $d$ is a Keccak256 digest. \end{enumerate} \subsection{Prover input format} -The initial state of each trie is given by the prover as a nondeterministic input tape. This tape has a similar format: +The initial state of each trie is given by the prover as a nondeterministic input tape. This tape has a slightly different format: \begin{enumerate} \item An empty node is encoded as $(\texttt{MPT\_NODE\_EMPTY})$. - \item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, \abs{v}, v, c_1, \dots, c_{16})$, where $\abs{v}$ is the length of the value, and $v$ is the value itself. Each $c_i$ is the encoding of a child node. + \item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, v_?, c_1, \dots, c_{16})$. Here $v_?$ consists of a flag indicating whether a value is present,\todo{In the current implementation, we use a length prefix rather than a is-present prefix, but we plan to change that.} followed by the actual value payload if one is present. Each $c_i$ is the encoding of a child node. \item An extension node is encoded as $(\texttt{MPT\_NODE\_EXTENSION}, k, c)$, $k$ represents the part of the key associated with this extension, and is encoded as a 2-tuple $(\texttt{packed\_nibbles}, \texttt{num\_nibbles})$. $c$ is a pointer to a child node. - \item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, \abs{v}, v)$, where $k$ is a 2-tuple as above, and $v$ is a leaf payload. - \item A digest node is encoded as $(\texttt{MPT\_NODE\_DIGEST}, d)$, where $d$ is a Keccak256 digest. + \item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, v)$, where $k$ is a 2-tuple as above, and $v$ is a value payload. + \item A digest node is encoded as $(\texttt{MPT\_NODE\_HASH}, d)$, where $d$ is a Keccak256 digest. \end{enumerate} -Nodes are thus given in depth-first order, leading to natural recursive methods for encoding and decoding this format. +Nodes are thus given in depth-first order, enabling natural recursive methods for encoding and decoding this format. diff --git a/evm/spec/zkevm.pdf b/evm/spec/zkevm.pdf index 184ba36b1d30f5b348c1d79900597a461f054d29..f181eba624273229b664ab2127b74209ff289eb3 100644 GIT binary patch delta 49211 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 a3KC(Hab3Z1;n|p3SQ%l-$ix)HVgC;lBnD0Z delta 57114 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 (rlp_pos, 0x80, rlp_pos) + // stack: value_ptr, rlp_pos', encode_value, retdest + %stack (value_ptr, rlp_pos, encode_value) -> (rlp_pos, 0x80, rlp_pos) %mstore_rlp // stack: rlp_pos', retdest %increment // stack: rlp_pos'', retdest %jump(encode_node_branch_prepend_prefix) encode_node_branch_with_value: - // stack: value_len_ptr, rlp_pos', encode_value, retdest - %increment // stack: value_ptr, rlp_pos', encode_value, retdest %stack (value_ptr, rlp_pos, encode_value) -> (encode_value, rlp_pos, value_ptr, encode_node_branch_prepend_prefix) @@ -276,7 +272,6 @@ encode_node_leaf_after_hex_prefix: %add_const(2) // The value pointer starts at index 3, after num_nibbles and packed_nibbles. // stack: value_ptr_ptr, rlp_pos, encode_value, retdest %mload_trie_data - %increment // skip over length prefix // stack: value_ptr, rlp_pos, encode_value, retdest %stack (value_ptr, rlp_pos, encode_value, retdest) -> (encode_value, rlp_pos, value_ptr, encode_node_leaf_after_encode_value, retdest) diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index 73f58b95..49258a31 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -70,7 +70,7 @@ load_mpt_branch: SWAP1 %append_to_trie_data // stack: node_ptr, retdest // Save the offset of our 16 child pointers so we can write them later. - // Then advance out current trie pointer beyond them, so we can load the + // Then advance our current trie pointer beyond them, so we can load the // value and have it placed after our child pointers. %get_trie_data_size // stack: children_ptr, node_ptr, retdest @@ -79,8 +79,8 @@ load_mpt_branch: %set_trie_data_size // stack: children_ptr, node_ptr, retdest %load_value - // stack: children_ptr, value_ptr, node_ptr, retdest SWAP1 + // stack: children_ptr, value_ptr, node_ptr, retdest // Load the 16 children. %rep 16 @@ -170,26 +170,28 @@ load_mpt_digest: %%after: %endmacro -// Load a leaf from prover input, append it to trie data, and return a pointer to it. +// Load a value from prover input, append it to trie data, and return a pointer to it. +// Return null if the value is empty. %macro load_value // stack: (empty) PROVER_INPUT(mpt) // stack: value_len - DUP1 ISZERO - %jumpi(%%return_null) + DUP1 %jumpi(%%has_value) + %stack (value_len) -> (0) + %jump(%%end) +%%has_value: // stack: value_len %get_trie_data_size + // stack: value_ptr, value_len SWAP1 // stack: value_len, value_ptr - DUP1 %append_to_trie_data - // stack: value_len, value_ptr %%loop: DUP1 ISZERO // stack: value_len == 0, value_len, value_ptr %jumpi(%%finish_loop) // stack: value_len, value_ptr PROVER_INPUT(mpt) - // stack: leaf_part, value_len, value_ptr + // stack: value_part, value_len, value_ptr %append_to_trie_data // stack: value_len, value_ptr %decrement @@ -199,8 +201,5 @@ load_mpt_digest: // stack: value_len, value_ptr POP // stack: value_ptr - %jump(%%end) -%%return_null: - %stack (value_len) -> (0) %%end: %endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index dae97336..d375bedc 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -1,6 +1,6 @@ -// Given an address, return a pointer to the associated (length-prefixed) -// 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. +// 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 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 @@ -24,7 +24,7 @@ mpt_read_state_trie_after_mstore: // - the key, as a U256 // - the number of nibbles in the key (should start at 64) // -// This function returns a pointer to the length-prefixed leaf, or 0 if the key is not found. +// This function returns a pointer to the value, or 0 if the key is not found. global mpt_read: // stack: node_ptr, num_nibbles, key, retdest DUP1 @@ -77,15 +77,6 @@ mpt_read_branch_end_of_key: %add_const(16) // skip over the 16 child nodes // stack: value_ptr_ptr, retdest %mload_trie_data - // stack: value_len_ptr, retdest - DUP1 %mload_trie_data - // stack: value_len, value_len_ptr, retdest - %jumpi(mpt_read_branch_found_value) - // This branch node contains no value, so return null. - %stack (value_len_ptr, retdest) -> (retdest, 0) -mpt_read_branch_found_value: - // stack: value_len_ptr, retdest - %increment // stack: value_ptr, retdest SWAP1 JUMP diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs index 469ad1e4..5310d431 100644 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -157,7 +157,6 @@ fn test_state_trie(state_trie: PartialTrie, k: Nibbles, v: Vec) -> Result<() let value_ptr = trie_data.len(); let account: AccountRlp = rlp::decode(&v).expect("Decoding failed"); let account_data = account.to_vec(); - trie_data.push(account_data.len().into()); trie_data.extend(account_data); let trie_data_len = trie_data.len().into(); interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len); diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs index 0572458d..b7b3b108 100644 --- a/evm/src/cpu/kernel/tests/mpt/load.rs +++ b/evm/src/cpu/kernel/tests/mpt/load.rs @@ -79,7 +79,6 @@ fn load_all_mpts_leaf() -> Result<()> { 3.into(), 0xABC.into(), 5.into(), // value ptr - 4.into(), // value length test_account_1().nonce, test_account_1().balance, test_account_1().storage_root.into_uint(), @@ -200,7 +199,6 @@ fn load_all_mpts_ext_to_leaf() -> Result<()> { 3.into(), // 3 nibbles 0xDEF.into(), // key part 9.into(), // value pointer - 4.into(), // value length test_account_1().nonce, test_account_1().balance, test_account_1().storage_root.into_uint(), diff --git a/evm/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs index 06d89ff6..c45a6b60 100644 --- a/evm/src/cpu/kernel/tests/mpt/read.rs +++ b/evm/src/cpu/kernel/tests/mpt/read.rs @@ -44,12 +44,11 @@ fn mpt_read() -> Result<()> { assert_eq!(interpreter.stack().len(), 1); let result_ptr = interpreter.stack()[0].as_usize(); - let result = &interpreter.get_trie_data()[result_ptr..][..5]; - assert_eq!(result[0], 4.into()); - assert_eq!(result[1], account.nonce); - assert_eq!(result[2], account.balance); - assert_eq!(result[3], account.storage_root.into_uint()); - assert_eq!(result[4], account.code_hash.into_uint()); + let result = &interpreter.get_trie_data()[result_ptr..][..4]; + assert_eq!(result[0], account.nonce); + assert_eq!(result[1], account.balance); + assert_eq!(result[2], account.storage_root.into_uint()); + assert_eq!(result[3], account.code_hash.into_uint()); Ok(()) } From a8e30b0ca02fcb3ee2cc3cf03d80c3b9099bca26 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 15 Oct 2022 08:45:01 -0700 Subject: [PATCH 20/21] Switch a few uses of current-context memory to kernel memory E.g. make sure the RLP segment is only used with the kernel context. Using current-context memory would also work, we just need to be consistent. For transaction parsing etc, the context should be 0 anyway, but explicitly referring to kernel memory feels more idiomatic to me. --- evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm | 2 +- evm/src/cpu/kernel/asm/rlp/decode.asm | 6 +++--- evm/src/cpu/kernel/asm/rlp/read_to_memory.asm | 2 +- evm/src/cpu/kernel/asm/transactions/router.asm | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm index 96e177ff..a1c2ff3c 100644 --- a/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm +++ b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm @@ -132,7 +132,7 @@ pubkey_to_addr: // stack: PKx, PKy, retdest PUSH 0 // stack: 0, PKx, PKy, retdest - MSTORE // TODO: switch to kernel memory (like `%mstore_current(@SEGMENT_KERNEL_GENERAL)`). + MSTORE // TODO: switch to kernel memory (like `%mstore_kernel(@SEGMENT_KERNEL_GENERAL)`). // stack: PKy, retdest PUSH 0x20 // stack: 0x20, PKy, retdest diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm index 182354c4..9842bfbd 100644 --- a/evm/src/cpu/kernel/asm/rlp/decode.asm +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -14,7 +14,7 @@ global decode_rlp_string_len: // stack: pos, retdest DUP1 - %mload_current(@SEGMENT_RLP_RAW) + %mload_kernel(@SEGMENT_RLP_RAW) // stack: first_byte, pos, retdest DUP1 %gt_const(0xb7) @@ -89,7 +89,7 @@ global decode_rlp_scalar: global decode_rlp_list_len: // stack: pos, retdest DUP1 - %mload_current(@SEGMENT_RLP_RAW) + %mload_kernel(@SEGMENT_RLP_RAW) // stack: first_byte, pos, retdest SWAP1 %increment // increment pos @@ -151,7 +151,7 @@ decode_int_given_len_loop: // stack: acc << 8, pos, end_pos, retdest DUP2 // stack: pos, acc << 8, pos, end_pos, retdest - %mload_current(@SEGMENT_RLP_RAW) + %mload_kernel(@SEGMENT_RLP_RAW) // stack: byte, acc << 8, pos, end_pos, retdest ADD // stack: acc', pos, end_pos, retdest diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm index 5d8cbd17..2d71e65a 100644 --- a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -23,7 +23,7 @@ read_rlp_to_memory_loop: // stack: byte, pos, len, retdest DUP2 // stack: pos, byte, pos, len, retdest - %mstore_current(@SEGMENT_RLP_RAW) + %mstore_kernel(@SEGMENT_RLP_RAW) // stack: pos, len, retdest %increment // stack: pos', len, retdest diff --git a/evm/src/cpu/kernel/asm/transactions/router.asm b/evm/src/cpu/kernel/asm/transactions/router.asm index 974fed99..3f4ebe37 100644 --- a/evm/src/cpu/kernel/asm/transactions/router.asm +++ b/evm/src/cpu/kernel/asm/transactions/router.asm @@ -18,14 +18,14 @@ read_txn_from_memory: // first byte >= 0xc0, so there is no overlap. PUSH 0 - %mload_current(@SEGMENT_RLP_RAW) + %mload_kernel(@SEGMENT_RLP_RAW) %eq_const(1) // stack: first_byte == 1, retdest %jumpi(process_type_1_txn) // stack: retdest PUSH 0 - %mload_current(@SEGMENT_RLP_RAW) + %mload_kernel(@SEGMENT_RLP_RAW) %eq_const(2) // stack: first_byte == 2, retdest %jumpi(process_type_2_txn) From 7f366cdace01e7f86fc7489c5ccf56155f66457e Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 16 Oct 2022 10:11:45 -0700 Subject: [PATCH 21/21] Treat storage tries as sub-tries of the state trie I.e. have leaves in the state trie point to the root of a storage trie --- evm/Cargo.toml | 2 +- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/mpt/hash.asm | 44 ++-- .../cpu/kernel/asm/mpt/hash_trie_specific.asm | 12 +- evm/src/cpu/kernel/asm/mpt/load.asm | 206 ++++++++---------- .../cpu/kernel/asm/mpt/load_trie_specific.asm | 40 ++++ .../cpu/kernel/constants/context_metadata.rs | 2 +- .../cpu/kernel/constants/global_metadata.rs | 7 +- evm/src/cpu/kernel/tests/mpt/hash.rs | 78 ++++--- evm/src/cpu/kernel/tests/mpt/insert.rs | 46 ++-- evm/src/cpu/kernel/tests/mpt/load.rs | 62 ++++-- evm/src/cpu/kernel/tests/mpt/read.rs | 25 +-- evm/src/generation/mpt.rs | 138 +++++++++--- evm/src/memory/segments.rs | 24 +- 14 files changed, 396 insertions(+), 291 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/mpt/load_trie_specific.asm diff --git a/evm/Cargo.toml b/evm/Cargo.toml index bcbc5cd9..7c179318 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -14,7 +14,7 @@ ethereum-types = "0.14.0" hex = { version = "0.4.3", optional = true } hex-literal = "0.3.4" itertools = "0.10.3" -keccak-hash = "0.9.0" +keccak-hash = "0.10.0" log = "0.4.14" num = "0.4.0" maybe_rayon = { path = "../maybe_rayon" } diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 032338b7..afed6a31 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -48,6 +48,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/mpt/insert_leaf.asm"), include_str!("asm/mpt/insert_trie_specific.asm"), include_str!("asm/mpt/load.asm"), + include_str!("asm/mpt/load_trie_specific.asm"), include_str!("asm/mpt/read.asm"), include_str!("asm/mpt/storage_read.asm"), include_str!("asm/mpt/storage_write.asm"), diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index ee8b5ca3..9fe0edef 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -47,7 +47,29 @@ mpt_hash_hash_rlp_after_unpacking: // Pre stack: node_ptr, encode_value, retdest // Post stack: result, result_len global encode_or_hash_node: - %stack (node_ptr, encode_value) -> (node_ptr, encode_value, maybe_hash_node) + // stack: node_ptr, encode_value, retdest + DUP1 %mload_trie_data + + // Check if we're dealing with a concrete node, i.e. not a hash node. + // stack: node_type, node_ptr, encode_value, retdest + DUP1 + PUSH @MPT_NODE_HASH + SUB + %jumpi(encode_or_hash_concrete_node) + + // If we got here, node_type == @MPT_NODE_HASH. + // Load the hash and return (hash, 32). + // stack: node_type, node_ptr, encode_value, retdest + POP + // stack: node_ptr, encode_value, retdest + %increment // Skip over node type prefix + // stack: hash_ptr, encode_value, retdest + %mload_trie_data + // stack: hash, encode_value, retdest + %stack (hash, encode_value, retdest) -> (retdest, hash, 32) + JUMP +encode_or_hash_concrete_node: + %stack (node_type, node_ptr, encode_value) -> (node_type, node_ptr, encode_value, maybe_hash_node) %jump(encode_node) maybe_hash_node: // stack: result_ptr, result_len, retdest @@ -75,22 +97,22 @@ after_packed_small_rlp: // RLP encode the given trie node, and return an (pointer, length) pair // indicating where the data lives within @SEGMENT_RLP_RAW. // -// Pre stack: node_ptr, encode_value, retdest +// Pre stack: node_type, node_ptr, encode_value, retdest // Post stack: result_ptr, result_len -global encode_node: - // stack: node_ptr, encode_value, retdest - DUP1 %mload_trie_data +encode_node: // stack: node_type, node_ptr, encode_value, retdest // Increment node_ptr, so it points to the node payload instead of its type. SWAP1 %increment SWAP1 // stack: node_type, node_payload_ptr, encode_value, retdest DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(encode_node_empty) - DUP1 %eq_const(@MPT_NODE_HASH) %jumpi(encode_node_hash) DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(encode_node_branch) DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(encode_node_extension) DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(encode_node_leaf) - PANIC // Invalid node type? Shouldn't get here. + + // If we got here, node_type is either @MPT_NODE_HASH, which should have + // been handled earlier in encode_or_hash_node, or something invalid. + PANIC global encode_node_empty: // stack: node_type, node_payload_ptr, encode_value, retdest @@ -105,14 +127,6 @@ global encode_node_empty: %stack (retdest) -> (retdest, 0, 1) JUMP -global encode_node_hash: - // stack: node_type, node_payload_ptr, encode_value, retdest - POP - // stack: node_payload_ptr, encode_value, retdest - %mload_trie_data - %stack (hash, encode_value, retdest) -> (retdest, hash, 32) - JUMP - encode_node_branch: // stack: node_type, node_payload_ptr, encode_value, retdest POP diff --git a/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm index bf2c46f0..4f9b58b4 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm @@ -72,8 +72,13 @@ global encode_account: // stack: balance, rlp_pos_4, value_ptr, retdest SWAP1 %encode_rlp_scalar // stack: rlp_pos_5, value_ptr, retdest - DUP2 %add_const(2) %mload_trie_data // storage_root = value[2] - // stack: storage_root, rlp_pos_5, value_ptr, retdest + PUSH encode_account_after_hash_storage_trie + PUSH encode_storage_value + DUP4 %add_const(2) %mload_trie_data // storage_root_ptr = value[2] + // stack: storage_root_ptr, encode_storage_value, encode_account_after_hash_storage_trie, rlp_pos_5, value_ptr, retdest + %jump(mpt_hash) +encode_account_after_hash_storage_trie: + // stack: storage_root_digest, rlp_pos_5, value_ptr, retdest SWAP1 %encode_rlp_256 // stack: rlp_pos_6, value_ptr, retdest SWAP1 %add_const(3) %mload_trie_data // code_hash = value[3] @@ -88,3 +93,6 @@ encode_txn: encode_receipt: PANIC // TODO + +encode_storage_value: + PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index 49258a31..d787074b 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -1,6 +1,3 @@ -// TODO: Receipt trie leaves are variable-length, so we need to be careful not -// to permit buffer over-reads. - // Load all partial trie data from prover inputs. global load_all_mpts: // stack: retdest @@ -9,47 +6,20 @@ global load_all_mpts: PUSH 1 %set_trie_data_size - %load_mpt %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) - %load_mpt %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) - %load_mpt %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) + %load_mpt(mpt_load_state_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %load_mpt(mpt_load_txn_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) + %load_mpt(mpt_load_receipt_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) - PROVER_INPUT(mpt) - // stack: num_storage_tries, retdest - DUP1 %mstore_global_metadata(@GLOBAL_METADATA_NUM_STORAGE_TRIES) - // stack: num_storage_tries, retdest - PUSH 0 // i = 0 - // stack: i, num_storage_tries, retdest -storage_trie_loop: - DUP2 DUP2 EQ - // stack: i == num_storage_tries, i, num_storage_tries, retdest - %jumpi(storage_trie_loop_end) - // stack: i, num_storage_tries, retdest - PROVER_INPUT(mpt) - // stack: storage_trie_addr, i, num_storage_tries, retdest - DUP2 - // stack: i, storage_trie_addr, i, num_storage_tries, retdest - %mstore_kernel(@SEGMENT_STORAGE_TRIE_ADDRS) - // stack: i, num_storage_tries, retdest - %load_mpt - // stack: root_ptr, i, num_storage_tries, retdest - DUP2 - // stack: i, root_ptr, i, num_storage_tries, retdest - %mstore_kernel(@SEGMENT_STORAGE_TRIE_PTRS) - // stack: i, num_storage_tries, retdest - %jump(storage_trie_loop) -storage_trie_loop_end: - // stack: i, num_storage_tries, retdest - %pop2 // stack: retdest JUMP // Load an MPT from prover inputs. -// Pre stack: retdest +// Pre stack: load_value, retdest // Post stack: node_ptr -load_mpt: - // stack: retdest +global load_mpt: + // stack: load_value, retdest PROVER_INPUT(mpt) - // stack: node_type, retdest + // stack: node_type, load_value, retdest DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(load_mpt_empty) DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(load_mpt_branch) @@ -60,146 +30,144 @@ load_mpt: load_mpt_empty: // TRIE_DATA[0] = 0, and an empty node has type 0, so we can simply return the null pointer. - %stack (node_type, retdest) -> (retdest, 0) + %stack (node_type, load_value, retdest) -> (retdest, 0) JUMP load_mpt_branch: - // stack: node_type, retdest + // stack: node_type, load_value, retdest %get_trie_data_size - // stack: node_ptr, node_type, retdest + // stack: node_ptr, node_type, load_value, retdest SWAP1 %append_to_trie_data - // stack: node_ptr, retdest + // stack: node_ptr, load_value, retdest // Save the offset of our 16 child pointers so we can write them later. // Then advance our current trie pointer beyond them, so we can load the // value and have it placed after our child pointers. %get_trie_data_size - // stack: children_ptr, node_ptr, retdest + // stack: children_ptr, node_ptr, load_value, retdest DUP1 %add_const(17) // Skip over 16 children plus the value pointer - // stack: value_ptr, children_ptr, node_ptr, retdest - %set_trie_data_size - // stack: children_ptr, node_ptr, retdest - %load_value + // stack: end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest + DUP1 %set_trie_data_size + // Now the top of the stack points to where the branch node will end and the + // value will begin, if there is a value. But we need to ask the prover if a + // value is present, and point to null if not. + // stack: end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest + PROVER_INPUT(mpt) + // stack: is_value_present, end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest + %jumpi(load_mpt_branch_value_present) + // There is no value present, so value_ptr = null. + %stack (end_of_branch_ptr) -> (0) + // stack: value_ptr, children_ptr, node_ptr, load_value, retdest + %jump(load_mpt_branch_after_load_value) +load_mpt_branch_value_present: + // stack: value_ptr, children_ptr, node_ptr, load_value, retdest + PUSH load_mpt_branch_after_load_value + DUP5 // load_value + JUMP +load_mpt_branch_after_load_value: + // stack: value_ptr, children_ptr, node_ptr, load_value, retdest SWAP1 - // stack: children_ptr, value_ptr, node_ptr, retdest + // stack: children_ptr, value_ptr, node_ptr, load_value, retdest // Load the 16 children. %rep 16 + DUP4 // load_value %load_mpt - // stack: child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, retdest + // stack: child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest DUP2 - // stack: next_child_ptr_ptr, child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, retdest + // stack: next_child_ptr_ptr, child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest %mstore_trie_data - // stack: next_child_ptr_ptr, value_ptr, node_ptr, retdest + // stack: next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest %increment - // stack: next_child_ptr_ptr, value_ptr, node_ptr, retdest + // stack: next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest %endrep - // stack: value_ptr_ptr, value_ptr, node_ptr, retdest + // stack: value_ptr_ptr, value_ptr, node_ptr, load_value, retdest %mstore_trie_data - // stack: node_ptr, retdest - SWAP1 + %stack (node_ptr, load_value, retdest) -> (retdest, node_ptr) JUMP load_mpt_extension: - // stack: node_type, retdest + // stack: node_type, load_value, retdest %get_trie_data_size - // stack: node_ptr, node_type, retdest + // stack: node_ptr, node_type, load_value, retdest SWAP1 %append_to_trie_data - // stack: node_ptr, retdest + // stack: node_ptr, load_value, retdest PROVER_INPUT(mpt) // read num_nibbles %append_to_trie_data PROVER_INPUT(mpt) // read packed_nibbles %append_to_trie_data - // stack: node_ptr, retdest + // stack: node_ptr, load_value, retdest %get_trie_data_size - // stack: child_ptr_ptr, node_ptr, retdest + // stack: child_ptr_ptr, node_ptr, load_value, retdest // Increment trie_data_size, to leave room for child_ptr_ptr, before we load our child. DUP1 %increment %set_trie_data_size - // stack: child_ptr_ptr, node_ptr, retdest - - %load_mpt - // stack: child_ptr, child_ptr_ptr, node_ptr, retdest - SWAP1 - %mstore_trie_data - // stack: node_ptr, retdest - SWAP1 + %stack (child_ptr_ptr, node_ptr, load_value, retdest) + -> (load_value, load_mpt_extension_after_load_mpt, + child_ptr_ptr, retdest, node_ptr) + %jump(load_mpt) +load_mpt_extension_after_load_mpt: + // stack: child_ptr, child_ptr_ptr, retdest, node_ptr + SWAP1 %mstore_trie_data + // stack: retdest, node_ptr JUMP load_mpt_leaf: - // stack: node_type, retdest + // stack: node_type, load_value, retdest %get_trie_data_size - // stack: node_ptr, node_type, retdest + // stack: node_ptr, node_type, load_value, retdest SWAP1 %append_to_trie_data - // stack: node_ptr, retdest + // stack: node_ptr, load_value, retdest PROVER_INPUT(mpt) // read num_nibbles %append_to_trie_data PROVER_INPUT(mpt) // read packed_nibbles %append_to_trie_data - // stack: node_ptr, retdest + // stack: node_ptr, load_value, retdest // We save value_ptr_ptr = get_trie_data_size, then increment trie_data_size - // to skip over the slot for value_ptr. We will write value_ptr after the - // load_value call. + // to skip over the slot for value_ptr_ptr. We will write to value_ptr_ptr + // after the load_value call. %get_trie_data_size - // stack: value_ptr_ptr, node_ptr, retdest - DUP1 %increment %set_trie_data_size - // stack: value_ptr_ptr, node_ptr, retdest - %load_value - // stack: value_ptr, value_ptr_ptr, node_ptr, retdest - SWAP1 %mstore_trie_data - // stack: node_ptr, retdest - SWAP1 + // stack: value_ptr_ptr, node_ptr, load_value, retdest + DUP1 %increment + // stack: value_ptr, value_ptr_ptr, node_ptr, load_value, retdest + DUP1 %set_trie_data_size + // stack: value_ptr, value_ptr_ptr, node_ptr, load_value, retdest + %stack (value_ptr, value_ptr_ptr, node_ptr, load_value, retdest) + -> (load_value, load_mpt_leaf_after_load_value, + value_ptr_ptr, value_ptr, retdest, node_ptr) + JUMP +load_mpt_leaf_after_load_value: + // stack: value_ptr_ptr, value_ptr, retdest, node_ptr + %mstore_trie_data + // stack: retdest, node_ptr JUMP load_mpt_digest: - // stack: node_type, retdest + // stack: node_type, load_value, retdest %get_trie_data_size - // stack: node_ptr, node_type, retdest + // stack: node_ptr, node_type, load_value, retdest SWAP1 %append_to_trie_data - // stack: node_ptr, retdest + // stack: node_ptr, load_value, retdest PROVER_INPUT(mpt) // read digest %append_to_trie_data - // stack: node_ptr, retdest - SWAP1 + %stack (node_ptr, load_value, retdest) -> (retdest, node_ptr) JUMP // Convenience macro to call load_mpt and return where we left off. +// Pre stack: load_value +// Post stack: node_ptr %macro load_mpt - PUSH %%after + %stack (load_value) -> (load_value, %%after) %jump(load_mpt) %%after: %endmacro -// Load a value from prover input, append it to trie data, and return a pointer to it. -// Return null if the value is empty. -%macro load_value - // stack: (empty) - PROVER_INPUT(mpt) - // stack: value_len - DUP1 %jumpi(%%has_value) - %stack (value_len) -> (0) - %jump(%%end) -%%has_value: - // stack: value_len - %get_trie_data_size - // stack: value_ptr, value_len - SWAP1 - // stack: value_len, value_ptr -%%loop: - DUP1 ISZERO - // stack: value_len == 0, value_len, value_ptr - %jumpi(%%finish_loop) - // stack: value_len, value_ptr - PROVER_INPUT(mpt) - // stack: value_part, value_len, value_ptr - %append_to_trie_data - // stack: value_len, value_ptr - %decrement - // stack: value_len', value_ptr - %jump(%%loop) -%%finish_loop: - // stack: value_len, value_ptr - POP - // stack: value_ptr -%%end: +// Convenience macro to call load_mpt and return where we left off. +// Pre stack: (empty) +// Post stack: node_ptr +%macro load_mpt(load_value) + PUSH %%after + PUSH $load_value + %jump(load_mpt) +%%after: %endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/load_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/load_trie_specific.asm new file mode 100644 index 00000000..b93b36e4 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/load_trie_specific.asm @@ -0,0 +1,40 @@ +global mpt_load_state_trie_value: + // stack: retdest + + // Load and append the nonce and balance. + PROVER_INPUT(mpt) %append_to_trie_data + PROVER_INPUT(mpt) %append_to_trie_data + + // Now increment the trie data size by 2, to leave room for our storage trie + // pointer and code hash fields, before calling load_mpt which will append + // our storage trie data. + %get_trie_data_size + // stack: storage_trie_ptr_ptr, retdest + DUP1 %add_const(2) + // stack: storage_trie_ptr, storage_trie_ptr_ptr, retdest + %set_trie_data_size + // stack: storage_trie_ptr_ptr, retdest + + %load_mpt(mpt_load_storage_trie_value) + // stack: storage_trie_ptr, storage_trie_ptr_ptr, retdest + DUP2 %mstore_trie_data + // stack: storage_trie_ptr_ptr, retdest + %increment + // stack: code_hash_ptr, retdest + PROVER_INPUT(mpt) + // stack: code_hash, code_hash_ptr, retdest + SWAP1 %mstore_trie_data + // stack: retdest + JUMP + +global mpt_load_txn_trie_value: + // stack: retdest + PANIC // TODO + +global mpt_load_receipt_trie_value: + // stack: retdest + PANIC // TODO + +global mpt_load_storage_trie_value: + // stack: retdest + PANIC // TODO diff --git a/evm/src/cpu/kernel/constants/context_metadata.rs b/evm/src/cpu/kernel/constants/context_metadata.rs index 17945d98..a2c460fc 100644 --- a/evm/src/cpu/kernel/constants/context_metadata.rs +++ b/evm/src/cpu/kernel/constants/context_metadata.rs @@ -21,7 +21,7 @@ pub(crate) enum ContextMetadata { /// prohibited. Static = 8, /// Pointer to the initial version of the state trie, at the creation of this context. Used when - /// we need to revert a context. See also `StorageTrieCheckpointPointers`. + /// we need to revert a context. StateTrieCheckpointPointer = 9, } diff --git a/evm/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs index 295cdfd5..1fa62efe 100644 --- a/evm/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -18,9 +18,6 @@ pub(crate) enum GlobalMetadata { TransactionTrieRoot = 5, /// A pointer to the root of the receipt trie within the `TrieData` buffer. ReceiptTrieRoot = 6, - /// The number of storage tries involved in these transactions. I.e. the number of values in - /// `StorageTrieAddresses`, `StorageTriePointers` and `StorageTrieCheckpointPointers`. - NumStorageTries = 7, // The root digests of each Merkle trie before these transactions. StateTrieRootDigestBefore = 8, @@ -38,7 +35,7 @@ pub(crate) enum GlobalMetadata { } impl GlobalMetadata { - pub(crate) const COUNT: usize = 15; + pub(crate) const COUNT: usize = 14; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -49,7 +46,6 @@ impl GlobalMetadata { Self::StateTrieRoot, Self::TransactionTrieRoot, Self::ReceiptTrieRoot, - Self::NumStorageTries, Self::StateTrieRootDigestBefore, Self::TransactionTrieRootDigestBefore, Self::ReceiptTrieRootDigestBefore, @@ -70,7 +66,6 @@ impl GlobalMetadata { GlobalMetadata::StateTrieRoot => "GLOBAL_METADATA_STATE_TRIE_ROOT", GlobalMetadata::TransactionTrieRoot => "GLOBAL_METADATA_TXN_TRIE_ROOT", GlobalMetadata::ReceiptTrieRoot => "GLOBAL_METADATA_RECEIPT_TRIE_ROOT", - GlobalMetadata::NumStorageTries => "GLOBAL_METADATA_NUM_STORAGE_TRIES", GlobalMetadata::StateTrieRootDigestBefore => "GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE", GlobalMetadata::TransactionTrieRootDigestBefore => { "GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE" diff --git a/evm/src/cpu/kernel/tests/mpt/hash.rs b/evm/src/cpu/kernel/tests/mpt/hash.rs index de519797..19c38e91 100644 --- a/evm/src/cpu/kernel/tests/mpt/hash.rs +++ b/evm/src/cpu/kernel/tests/mpt/hash.rs @@ -1,12 +1,12 @@ use anyhow::Result; use eth_trie_utils::partial_trie::PartialTrie; -use ethereum_types::{BigEndianHash, H256, U256}; +use ethereum_types::{BigEndianHash, H256}; use super::nibbles; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::interpreter::Interpreter; -use crate::cpu::kernel::tests::mpt::extension_to_leaf; -use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; +use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1_rlp, test_account_2_rlp}; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; use crate::generation::TrieInputs; // TODO: Test with short leaf. Might need to be a storage trie. @@ -24,73 +24,69 @@ fn mpt_hash_empty() -> Result<()> { } #[test] -fn mpt_hash_leaf() -> Result<()> { - let account = AccountRlp { - nonce: U256::from(1111), - balance: U256::from(2222), - storage_root: H256::from_uint(&U256::from(3333)), - code_hash: H256::from_uint(&U256::from(4444)), +fn mpt_hash_empty_branch() -> Result<()> { + let children = std::array::from_fn(|_| PartialTrie::Empty.into()); + let state_trie = PartialTrie::Branch { + children, + value: vec![], }; - let account_rlp = rlp::encode(&account); - - let state_trie = PartialTrie::Leaf { - nibbles: nibbles(0xABC), - value: account_rlp.to_vec(), - }; - let trie_inputs = TrieInputs { state_trie, transactions_trie: Default::default(), receipts_trie: Default::default(), storage_tries: vec![], }; + test_state_trie(trie_inputs) +} +#[test] +fn mpt_hash_hash() -> Result<()> { + let hash = H256::random(); + let trie_inputs = TrieInputs { + state_trie: PartialTrie::Hash(hash), + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + + test_state_trie(trie_inputs) +} + +#[test] +fn mpt_hash_leaf() -> Result<()> { + let state_trie = PartialTrie::Leaf { + nibbles: nibbles(0xABC), + value: test_account_1_rlp(), + }; + let trie_inputs = TrieInputs { + state_trie, + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; test_state_trie(trie_inputs) } #[test] fn mpt_hash_extension_to_leaf() -> Result<()> { - let account = AccountRlp { - nonce: U256::from(1111), - balance: U256::from(2222), - storage_root: H256::from_uint(&U256::from(3333)), - code_hash: H256::from_uint(&U256::from(4444)), - }; - let account_rlp = rlp::encode(&account); - - let state_trie = extension_to_leaf(account_rlp.to_vec()); - + let state_trie = extension_to_leaf(test_account_1_rlp()); let trie_inputs = TrieInputs { state_trie, transactions_trie: Default::default(), receipts_trie: Default::default(), storage_tries: vec![], }; - test_state_trie(trie_inputs) } #[test] fn mpt_hash_branch_to_leaf() -> Result<()> { - let account = AccountRlp { - nonce: U256::from(1111), - balance: U256::from(2222), - storage_root: H256::from_uint(&U256::from(3333)), - code_hash: H256::from_uint(&U256::from(4444)), - }; - let account_rlp = rlp::encode(&account); - let leaf = PartialTrie::Leaf { nibbles: nibbles(0xABC), - value: account_rlp.to_vec(), + value: test_account_2_rlp(), } .into(); let mut children = std::array::from_fn(|_| PartialTrie::Empty.into()); - children[5] = PartialTrie::Branch { - children: children.clone(), - value: vec![], - } - .into(); children[3] = leaf; let state_trie = PartialTrie::Branch { children, diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs index 5310d431..3a52948d 100644 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -6,13 +6,13 @@ use super::nibbles; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; -use crate::cpu::kernel::tests::mpt::{test_account_1_rlp, test_account_2_rlp}; +use crate::cpu::kernel::tests::mpt::{test_account_1_rlp, test_account_2}; use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; use crate::generation::TrieInputs; #[test] fn mpt_insert_empty() -> Result<()> { - test_state_trie(Default::default(), nibbles(0xABC), test_account_2_rlp()) + test_state_trie(Default::default(), nibbles(0xABC), test_account_2()) } #[test] @@ -22,7 +22,7 @@ fn mpt_insert_leaf_identical_keys() -> Result<()> { nibbles: key, value: test_account_1_rlp(), }; - test_state_trie(state_trie, key, test_account_2_rlp()) + test_state_trie(state_trie, key, test_account_2()) } #[test] @@ -31,7 +31,7 @@ fn mpt_insert_leaf_nonoverlapping_keys() -> Result<()> { nibbles: nibbles(0xABC), value: test_account_1_rlp(), }; - test_state_trie(state_trie, nibbles(0x123), test_account_2_rlp()) + test_state_trie(state_trie, nibbles(0x123), test_account_2()) } #[test] @@ -40,7 +40,7 @@ fn mpt_insert_leaf_overlapping_keys() -> Result<()> { nibbles: nibbles(0xABC), value: test_account_1_rlp(), }; - test_state_trie(state_trie, nibbles(0xADE), test_account_2_rlp()) + test_state_trie(state_trie, nibbles(0xADE), test_account_2()) } #[test] @@ -49,7 +49,7 @@ fn mpt_insert_leaf_insert_key_extends_leaf_key() -> Result<()> { nibbles: nibbles(0xABC), value: test_account_1_rlp(), }; - test_state_trie(state_trie, nibbles(0xABCDE), test_account_2_rlp()) + test_state_trie(state_trie, nibbles(0xABCDE), test_account_2()) } #[test] @@ -58,7 +58,7 @@ fn mpt_insert_leaf_leaf_key_extends_insert_key() -> Result<()> { nibbles: nibbles(0xABCDE), value: test_account_1_rlp(), }; - test_state_trie(state_trie, nibbles(0xABC), test_account_2_rlp()) + test_state_trie(state_trie, nibbles(0xABC), test_account_2()) } #[test] @@ -69,10 +69,13 @@ fn mpt_insert_branch_replacing_empty_child() -> Result<()> { value: vec![], }; - test_state_trie(state_trie, nibbles(0xABC), test_account_2_rlp()) + test_state_trie(state_trie, nibbles(0xABC), test_account_2()) } #[test] +// TODO: Not a valid test because branches state trie cannot have branch values. +// We should change it to use a different trie. +#[ignore] fn mpt_insert_extension_nonoverlapping_keys() -> Result<()> { // Existing keys are 0xABC, 0xABCDEF; inserted key is 0x12345. let mut children = std::array::from_fn(|_| PartialTrie::Empty.into()); @@ -89,10 +92,13 @@ fn mpt_insert_extension_nonoverlapping_keys() -> Result<()> { } .into(), }; - test_state_trie(state_trie, nibbles(0x12345), test_account_2_rlp()) + test_state_trie(state_trie, nibbles(0x12345), test_account_2()) } #[test] +// TODO: Not a valid test because branches state trie cannot have branch values. +// We should change it to use a different trie. +#[ignore] fn mpt_insert_extension_insert_key_extends_node_key() -> Result<()> { // Existing keys are 0xA, 0xABCD; inserted key is 0xABCDEF. let mut children = std::array::from_fn(|_| PartialTrie::Empty.into()); @@ -109,7 +115,7 @@ fn mpt_insert_extension_insert_key_extends_node_key() -> Result<()> { } .into(), }; - test_state_trie(state_trie, nibbles(0xABCDEF), test_account_2_rlp()) + test_state_trie(state_trie, nibbles(0xABCDEF), test_account_2()) } #[test] @@ -126,10 +132,14 @@ fn mpt_insert_branch_to_leaf_same_key() -> Result<()> { value: vec![], }; - test_state_trie(state_trie, nibbles(0xABCD), test_account_2_rlp()) + test_state_trie(state_trie, nibbles(0xABCD), test_account_2()) } -fn test_state_trie(state_trie: PartialTrie, k: Nibbles, v: Vec) -> Result<()> { +/// Note: The account's storage_root is ignored, as we can't insert a new storage_root without the +/// accompanying trie data. An empty trie's storage_root is used instead. +fn test_state_trie(state_trie: PartialTrie, k: Nibbles, mut account: AccountRlp) -> Result<()> { + account.storage_root = PartialTrie::Empty.calc_hash(); + let trie_inputs = TrieInputs { state_trie: state_trie.clone(), transactions_trie: Default::default(), @@ -155,9 +165,13 @@ fn test_state_trie(state_trie: PartialTrie, k: Nibbles, v: Vec) -> Result<() trie_data.push(0.into()); } let value_ptr = trie_data.len(); - let account: AccountRlp = rlp::decode(&v).expect("Decoding failed"); - let account_data = account.to_vec(); - trie_data.extend(account_data); + trie_data.push(account.nonce); + trie_data.push(account.balance); + // In memory, storage_root gets interpreted as a pointer to a storage trie, + // so we have to ensure the pointer is valid. It's easiest to set it to 0, + // which works as an empty node, since trie_data[0] = 0 = MPT_TYPE_EMPTY. + trie_data.push(H256::zero().into_uint()); + trie_data.push(account.code_hash.into_uint()); let trie_data_len = trie_data.len().into(); interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len); interpreter.push(0xDEADBEEFu32.into()); @@ -186,7 +200,7 @@ fn test_state_trie(state_trie: PartialTrie, k: Nibbles, v: Vec) -> Result<() ); let hash = H256::from_uint(&interpreter.stack()[0]); - let updated_trie = state_trie.insert(k, v); + let updated_trie = state_trie.insert(k, rlp::encode(&account).to_vec()); let expected_state_trie_hash = updated_trie.calc_hash(); assert_eq!(hash, expected_state_trie_hash); diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs index b7b3b108..78129a1c 100644 --- a/evm/src/cpu/kernel/tests/mpt/load.rs +++ b/evm/src/cpu/kernel/tests/mpt/load.rs @@ -1,6 +1,6 @@ use anyhow::Result; use eth_trie_utils::partial_trie::PartialTrie; -use ethereum_types::{BigEndianHash, U256}; +use ethereum_types::{BigEndianHash, H256, U256}; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::trie_type::PartialTrieType; @@ -42,11 +42,6 @@ fn load_all_mpts_empty() -> Result<()> { 0.into() ); - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries), - trie_inputs.storage_tries.len().into() - ); - Ok(()) } @@ -81,8 +76,11 @@ fn load_all_mpts_leaf() -> Result<()> { 5.into(), // value ptr test_account_1().nonce, test_account_1().balance, - test_account_1().storage_root.into_uint(), + 9.into(), // pointer to storage trie root test_account_1().code_hash.into_uint(), + // These last two elements encode the storage trie, which is a hash node. + (PartialTrieType::Hash as u32).into(), + test_account_1().storage_root.into_uint(), ] ); @@ -95,9 +93,40 @@ fn load_all_mpts_leaf() -> Result<()> { 0.into() ); + Ok(()) +} + +#[test] +fn load_all_mpts_hash() -> Result<()> { + let hash = H256::random(); + let trie_inputs = TrieInputs { + state_trie: PartialTrie::Hash(hash), + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + + 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![]); + + let type_hash = U256::from(PartialTrieType::Hash as u32); assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries), - trie_inputs.storage_tries.len().into() + interpreter.get_trie_data(), + vec![0.into(), type_hash, hash.into_uint(),] + ); + + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot), + 0.into() + ); + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot), + 0.into() ); Ok(()) @@ -160,11 +189,6 @@ fn load_all_mpts_empty_branch() -> Result<()> { 0.into() ); - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries), - trie_inputs.storage_tries.len().into() - ); - Ok(()) } @@ -201,15 +225,13 @@ fn load_all_mpts_ext_to_leaf() -> Result<()> { 9.into(), // value pointer test_account_1().nonce, test_account_1().balance, - test_account_1().storage_root.into_uint(), + 13.into(), // pointer to storage trie root test_account_1().code_hash.into_uint(), + // These last two elements encode the storage trie, which is a hash node. + (PartialTrieType::Hash as u32).into(), + test_account_1().storage_root.into_uint(), ] ); - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries), - trie_inputs.storage_tries.len().into() - ); - Ok(()) } diff --git a/evm/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs index c45a6b60..d8808e24 100644 --- a/evm/src/cpu/kernel/tests/mpt/read.rs +++ b/evm/src/cpu/kernel/tests/mpt/read.rs @@ -1,25 +1,17 @@ use anyhow::Result; -use ethereum_types::{BigEndianHash, H256, U256}; +use ethereum_types::BigEndianHash; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; -use crate::cpu::kernel::tests::mpt::extension_to_leaf; -use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; +use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1, test_account_1_rlp}; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; use crate::generation::TrieInputs; #[test] fn mpt_read() -> Result<()> { - let account = AccountRlp { - nonce: U256::from(1111), - balance: U256::from(2222), - storage_root: H256::from_uint(&U256::from(3333)), - code_hash: H256::from_uint(&U256::from(4444)), - }; - let account_rlp = rlp::encode(&account); - let trie_inputs = TrieInputs { - state_trie: extension_to_leaf(account_rlp.to_vec()), + state_trie: extension_to_leaf(test_account_1_rlp()), transactions_trie: Default::default(), receipts_trie: Default::default(), storage_tries: vec![], @@ -45,10 +37,11 @@ fn mpt_read() -> Result<()> { assert_eq!(interpreter.stack().len(), 1); let result_ptr = interpreter.stack()[0].as_usize(); let result = &interpreter.get_trie_data()[result_ptr..][..4]; - assert_eq!(result[0], account.nonce); - assert_eq!(result[1], account.balance); - assert_eq!(result[2], account.storage_root.into_uint()); - assert_eq!(result[3], account.code_hash.into_uint()); + assert_eq!(result[0], test_account_1().nonce); + assert_eq!(result[1], test_account_1().balance); + // result[2] is the storage root pointer. We won't check that it matches a + // particular address, since that seems like over-specifying. + assert_eq!(result[3], test_account_1().code_hash.into_uint()); Ok(()) } diff --git a/evm/src/generation/mpt.rs b/evm/src/generation/mpt.rs index e35364c6..8ceb195a 100644 --- a/evm/src/generation/mpt.rs +++ b/evm/src/generation/mpt.rs @@ -1,5 +1,8 @@ -use eth_trie_utils::partial_trie::PartialTrie; +use std::collections::HashMap; + +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; use ethereum_types::{BigEndianHash, H256, U256}; +use keccak_hash::keccak; use rlp_derive::{RlpDecodable, RlpEncodable}; use crate::cpu::kernel::constants::trie_type::PartialTrieType; @@ -13,17 +16,6 @@ pub(crate) struct AccountRlp { pub(crate) code_hash: H256, } -impl AccountRlp { - pub(crate) fn to_vec(&self) -> Vec { - vec![ - self.nonce, - self.balance, - self.storage_root.into_uint(), - self.code_hash.into_uint(), - ] - } -} - pub(crate) fn all_mpt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec { let mut inputs = all_mpt_prover_inputs(trie_inputs); inputs.reverse(); @@ -34,10 +26,18 @@ pub(crate) fn all_mpt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec Vec { let mut prover_inputs = vec![]; - mpt_prover_inputs(&trie_inputs.state_trie, &mut prover_inputs, &|rlp| { - let account: AccountRlp = rlp::decode(rlp).expect("Decoding failed"); - account.to_vec() - }); + let storage_tries_by_state_key = trie_inputs + .storage_tries + .iter() + .map(|(address, storage_trie)| (Nibbles::from(keccak(address)), storage_trie)) + .collect(); + + mpt_prover_inputs_state_trie( + &trie_inputs.state_trie, + empty_nibbles(), + &mut prover_inputs, + &storage_tries_by_state_key, + ); mpt_prover_inputs(&trie_inputs.transactions_trie, &mut prover_inputs, &|rlp| { rlp::decode_list(rlp) @@ -48,14 +48,6 @@ pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec { vec![] }); - prover_inputs.push(trie_inputs.storage_tries.len().into()); - for (addr, storage_trie) in &trie_inputs.storage_tries { - prover_inputs.push(addr.0.as_ref().into()); - mpt_prover_inputs(storage_trie, &mut prover_inputs, &|leaf_be| { - vec![U256::from_big_endian(leaf_be)] - }); - } - prover_inputs } @@ -66,7 +58,7 @@ pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec { pub(crate) fn mpt_prover_inputs( trie: &PartialTrie, prover_inputs: &mut Vec, - parse_leaf: &F, + parse_value: &F, ) where F: Fn(&[u8]) -> Vec, { @@ -76,28 +68,108 @@ 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 length=0. + // There's no value, so value_len = 0. prover_inputs.push(U256::zero()); } else { - let leaf = parse_leaf(value); - prover_inputs.push(leaf.len().into()); - prover_inputs.extend(leaf); + let parsed_value = parse_value(value); + prover_inputs.push(parsed_value.len().into()); + prover_inputs.extend(parsed_value); } for child in children { - mpt_prover_inputs(child, prover_inputs, parse_leaf); + mpt_prover_inputs(child, prover_inputs, parse_value); } } PartialTrie::Extension { nibbles, child } => { prover_inputs.push(nibbles.count.into()); prover_inputs.push(nibbles.packed); - mpt_prover_inputs(child, prover_inputs, parse_leaf); + mpt_prover_inputs(child, prover_inputs, parse_value); } PartialTrie::Leaf { nibbles, value } => { prover_inputs.push(nibbles.count.into()); prover_inputs.push(nibbles.packed); - let leaf = parse_leaf(value); - prover_inputs.push(leaf.len().into()); + let leaf = parse_value(value); prover_inputs.extend(leaf); } } } + +/// Like `mpt_prover_inputs`, but for the state trie, which is a bit unique since each value +/// leads to a storage trie which we recursively traverse. +pub(crate) fn mpt_prover_inputs_state_trie( + trie: &PartialTrie, + key: Nibbles, + prover_inputs: &mut Vec, + storage_tries_by_state_key: &HashMap, +) { + prover_inputs.push((PartialTrieType::of(trie) as u32).into()); + match trie { + PartialTrie::Empty => {} + 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()); + + for (i, child) in children.iter().enumerate() { + let extended_key = key.merge(&Nibbles { + count: 1, + packed: i.into(), + }); + mpt_prover_inputs_state_trie( + child, + extended_key, + prover_inputs, + storage_tries_by_state_key, + ); + } + } + PartialTrie::Extension { nibbles, child } => { + prover_inputs.push(nibbles.count.into()); + prover_inputs.push(nibbles.packed); + let extended_key = key.merge(nibbles); + mpt_prover_inputs_state_trie( + child, + extended_key, + prover_inputs, + storage_tries_by_state_key, + ); + } + PartialTrie::Leaf { nibbles, value } => { + let account: AccountRlp = rlp::decode(value).expect("Decoding failed"); + let AccountRlp { + nonce, + balance, + storage_root, + code_hash, + } = account; + + let storage_hash_only = PartialTrie::Hash(storage_root); + let storage_trie: &PartialTrie = storage_tries_by_state_key + .get(&key) + .copied() + .unwrap_or(&storage_hash_only); + + assert_eq!(storage_trie.calc_hash(), storage_root, + "In TrieInputs, an account's storage_root didn't match the associated storage trie hash"); + + prover_inputs.push(nibbles.count.into()); + prover_inputs.push(nibbles.packed); + prover_inputs.push(nonce); + prover_inputs.push(balance); + mpt_prover_inputs(storage_trie, prover_inputs, &parse_storage_value); + prover_inputs.push(code_hash.into_uint()); + } + } +} + +fn parse_storage_value(value_rlp: &[u8]) -> Vec { + let value: U256 = rlp::decode(value_rlp).expect("Decoding failed"); + vec![value] +} + +fn empty_nibbles() -> Nibbles { + Nibbles { + count: 0, + packed: U256::zero(), + } +} diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index b6254900..b8ba904f 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -29,23 +29,14 @@ pub(crate) enum Segment { /// Contains all trie data. Tries are stored as immutable, copy-on-write trees, so this is an /// append-only buffer. It is owned by the kernel, so it only lives on context 0. TrieData = 12, - /// The account address associated with the `i`th storage trie. Only lives on context 0. - StorageTrieAddresses = 13, - /// A pointer to the `i`th storage trie within the `TrieData` buffer. Only lives on context 0. - StorageTriePointers = 14, - /// Like `StorageTriePointers`, except that these pointers correspond to the version of each - /// trie at the creation of a given context. This lets us easily revert a context by replacing - /// `StorageTriePointers` with `StorageTrieCheckpointPointers`. - /// See also `StateTrieCheckpointPointer`. - StorageTrieCheckpointPointers = 15, /// A buffer used to store the encodings of a branch node's children. - TrieEncodedChild = 16, + TrieEncodedChild = 13, /// A buffer used to store the lengths of the encodings of a branch node's children. - TrieEncodedChildLen = 17, + TrieEncodedChildLen = 14, } impl Segment { - pub(crate) const COUNT: usize = 18; + pub(crate) const COUNT: usize = 15; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -62,9 +53,6 @@ impl Segment { Self::TxnData, Self::RlpRaw, Self::TrieData, - Self::StorageTrieAddresses, - Self::StorageTriePointers, - Self::StorageTrieCheckpointPointers, Self::TrieEncodedChild, Self::TrieEncodedChildLen, ] @@ -86,9 +74,6 @@ impl Segment { Segment::TxnData => "SEGMENT_TXN_DATA", Segment::RlpRaw => "SEGMENT_RLP_RAW", Segment::TrieData => "SEGMENT_TRIE_DATA", - Segment::StorageTrieAddresses => "SEGMENT_STORAGE_TRIE_ADDRS", - Segment::StorageTriePointers => "SEGMENT_STORAGE_TRIE_PTRS", - Segment::StorageTrieCheckpointPointers => "SEGMENT_STORAGE_TRIE_CHECKPOINT_PTRS", Segment::TrieEncodedChild => "SEGMENT_TRIE_ENCODED_CHILD", Segment::TrieEncodedChildLen => "SEGMENT_TRIE_ENCODED_CHILD_LEN", } @@ -110,9 +95,6 @@ impl Segment { Segment::TxnData => 256, Segment::RlpRaw => 8, Segment::TrieData => 256, - Segment::StorageTrieAddresses => 160, - Segment::StorageTriePointers => 32, - Segment::StorageTrieCheckpointPointers => 32, Segment::TrieEncodedChild => 256, Segment::TrieEncodedChildLen => 6, }