From 15dec6faf9b125513fea065aa68217a1979fb838 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 May 2023 09:16:22 +0200 Subject: [PATCH 01/20] Encode `to` as B160. (#1011) --- evm/src/cpu/kernel/asm/transactions/type_0.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm index 45c57361..d00b10d4 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -102,7 +102,7 @@ type_0_compute_signed_data: // stack: rlp_pos, rlp_start, retdest %mload_txn_field(@TXN_FIELD_TO) - SWAP1 %encode_rlp_scalar + SWAP1 %encode_rlp_160 // stack: rlp_pos, rlp_start, retdest %mload_txn_field(@TXN_FIELD_VALUE) From 9b0092ab1d76a8a8ba4233b7f697f921c08b4b9c Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 23 May 2023 09:21:27 +0200 Subject: [PATCH 02/20] Support for type-1 transactions (#1051) * Type 1 txn * Remove magic constants * Remove useless stack element * Read correct original value for sk * EIP-3651 * Fix encoding when TO is 0 * Minor --- evm/src/cpu/kernel/asm/core/access_lists.asm | 2 +- evm/src/cpu/kernel/asm/core/call.asm | 4 +- evm/src/cpu/kernel/asm/core/intrinsic_gas.asm | 5 +- evm/src/cpu/kernel/asm/core/process_txn.asm | 5 + .../asm/transactions/common_decoding.asm | 104 ++++++++++++++++- .../cpu/kernel/asm/transactions/type_0.asm | 7 ++ .../cpu/kernel/asm/transactions/type_1.asm | 105 +++++++++++++++++- .../cpu/kernel/constants/global_metadata.rs | 14 ++- 8 files changed, 234 insertions(+), 12 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/access_lists.asm b/evm/src/cpu/kernel/asm/core/access_lists.asm index 0ba56c30..b1b9fd5d 100644 --- a/evm/src/cpu/kernel/asm/core/access_lists.asm +++ b/evm/src/cpu/kernel/asm/core/access_lists.asm @@ -87,7 +87,7 @@ remove_accessed_addresses_found: %stack (addr, key, value) -> (addr, key, value, %%after) %jump(insert_accessed_storage_keys) %%after: - // stack: cold_access + // stack: cold_access, original_value %endmacro /// Inserts the storage key and value into the access list if it is not already present. diff --git a/evm/src/cpu/kernel/asm/core/call.asm b/evm/src/cpu/kernel/asm/core/call.asm index adbe7412..3a170325 100644 --- a/evm/src/cpu/kernel/asm/core/call.asm +++ b/evm/src/cpu/kernel/asm/core/call.asm @@ -23,7 +23,7 @@ global sys_call: %u256_to_addr // Truncate to 160 bits DUP1 %insert_accessed_addresses %checkpoint // Checkpoint - DUP1 %insert_touched_addresses + DUP2 %insert_touched_addresses %call_charge_gas(1, 1) @@ -123,7 +123,7 @@ global sys_staticcall: %u256_to_addr // Truncate to 160 bits DUP1 %insert_accessed_addresses %checkpoint // Checkpoint - DUP1 %insert_touched_addresses + DUP2 %insert_touched_addresses // Add a value of 0 to the stack. Slightly inefficient but that way we can reuse %call_charge_gas. %stack (cold_access, address, gas, kexit_info) -> (cold_access, address, gas, kexit_info, 0) diff --git a/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm b/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm index 87737302..bb7a21b5 100644 --- a/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm +++ b/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm @@ -65,12 +65,11 @@ count_zeros_finish: PUSH @GAS_TRANSACTION // stack: gas_txn, gas_creation, gas_txndata, retdest - // TODO: Add num_access_list_addresses * GAS_ACCESSLISTADDRESS - // TODO: Add num_access_list_slots * GAS_ACCESSLISTSTORAGE - ADD ADD // stack: total_gas, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_DATA_COST) + ADD SWAP1 JUMP diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index 7a044475..022c1882 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -77,6 +77,11 @@ global increment_sender_nonce: %mload_txn_field(@TXN_FIELD_ORIGIN) %increment_nonce +// EIP-3651 +global warm_coinbase: + %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BENEFICIARY) + %insert_accessed_addresses_no_return + global process_based_on_type: %is_contract_creation %jumpi(process_contract_creation_txn) diff --git a/evm/src/cpu/kernel/asm/transactions/common_decoding.asm b/evm/src/cpu/kernel/asm/transactions/common_decoding.asm index 71440d1c..07d19f47 100644 --- a/evm/src/cpu/kernel/asm/transactions/common_decoding.asm +++ b/evm/src/cpu/kernel/asm/transactions/common_decoding.asm @@ -105,10 +105,15 @@ %macro decode_and_store_access_list // stack: pos + DUP1 %mstore_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_START) %decode_rlp_list_len - %stack (pos, len) -> (len, pos) - %jumpi(todo_access_lists_not_supported_yet) + %stack (pos, len) -> (len, len, pos, %%after) + %jumpi(decode_and_store_access_list) + // stack: len, pos, %%after + POP SWAP1 POP // stack: pos + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_START) DUP2 SUB %mstore_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) +%%after: %endmacro %macro decode_and_store_y_parity @@ -135,5 +140,96 @@ // stack: pos %endmacro -global todo_access_lists_not_supported_yet: - PANIC + +// The access list is of the form `[[{20 bytes}, [{32 bytes}...]]...]`. +global decode_and_store_access_list: + // stack: len, pos + DUP2 ADD + // stack: end_pos, pos + // Store the RLP length. + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_START) DUP2 SUB %mstore_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) + SWAP1 +decode_and_store_access_list_loop: + // stack: pos, end_pos + DUP2 DUP2 EQ %jumpi(decode_and_store_access_list_finish) + // stack: pos, end_pos + %decode_rlp_list_len // Should be a list `[{20 bytes}, [{32 bytes}...]]` + // stack: pos, internal_len, end_pos + SWAP1 POP // We don't need the length of this list. + // stack: pos, end_pos + %decode_rlp_scalar // Address // TODO: Should panic when address is not 20 bytes? + // stack: pos, addr, end_pos + SWAP1 + // stack: addr, pos, end_pos + DUP1 %insert_accessed_addresses_no_return + // stack: addr, pos, end_pos + %add_address_cost + // stack: addr, pos, end_pos + SWAP1 + // stack: pos, addr, end_pos + %decode_rlp_list_len // Should be a list of storage keys `[{32 bytes}...]` + // stack: pos, sk_len, addr, end_pos + SWAP1 DUP2 ADD + // stack: sk_end_pos, pos, addr, end_pos + SWAP1 + // stack: pos, sk_end_pos, addr, end_pos +sk_loop: + DUP2 DUP2 EQ %jumpi(end_sk) + // stack: pos, sk_end_pos, addr, end_pos + %decode_rlp_scalar // Storage key // TODO: Should panic when key is not 32 bytes? + %stack (pos, key, sk_end_pos, addr, end_pos) -> + (addr, key, sk_loop_contd, pos, sk_end_pos, addr, end_pos) + %jump(insert_accessed_storage_keys_with_original_value) +sk_loop_contd: + // stack: pos, sk_end_pos, addr, end_pos + %add_storage_key_cost + %jump(sk_loop) +end_sk: + %stack (pos, sk_end_pos, addr, end_pos) -> (pos, end_pos) + %jump(decode_and_store_access_list_loop) +decode_and_store_access_list_finish: + %stack (pos, end_pos, retdest) -> (retdest, pos) + JUMP + +%macro add_address_cost + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_DATA_COST) + %add_const(@GAS_ACCESSLISTADDRESS) + %mstore_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_DATA_COST) +%endmacro + +%macro add_storage_key_cost + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_DATA_COST) + %add_const(@GAS_ACCESSLISTSTORAGE) + %mstore_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_DATA_COST) +%endmacro + +insert_accessed_storage_keys_with_original_value: + %stack (addr, key, retdest) -> (key, addr, after_read, addr, key, retdest) + %jump(sload_with_addr) +after_read: + %stack (value, addr, key, retdest) -> ( addr, key, value, retdest) + %insert_accessed_storage_keys + %pop2 + JUMP + + +sload_with_addr: + %stack (slot, addr) -> (slot, addr, after_storage_read) + %slot_to_storage_key + // stack: storage_key, addr, after_storage_read + PUSH 64 // storage_key has 64 nibbles + %stack (n64, storage_key, addr, after_storage_read) -> (addr, n64, storage_key, after_storage_read) + %mpt_read_state_trie + // stack: account_ptr, 64, storage_key, after_storage_read + DUP1 ISZERO %jumpi(ret_zero) // TODO: Fix this. This should never happen. + // stack: account_ptr, 64, storage_key, after_storage_read + %add_const(2) + // stack: storage_root_ptr_ptr + %mload_trie_data + // stack: storage_root_ptr, 64, storage_key, after_storage_read + %jump(mpt_read) + +ret_zero: + // stack: account_ptr, 64, storage_key, after_storage_read, retdest + %pop4 + PUSH 0 SWAP1 JUMP diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm index d00b10d4..65836086 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -102,9 +102,16 @@ type_0_compute_signed_data: // stack: rlp_pos, rlp_start, retdest %mload_txn_field(@TXN_FIELD_TO) + DUP1 %jumpi(nonzero_to) + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_scalar + %jump(after_to) +nonzero_to: + // stack: to, rlp_pos, rlp_start, retdest SWAP1 %encode_rlp_160 // stack: rlp_pos, rlp_start, retdest +after_to: %mload_txn_field(@TXN_FIELD_VALUE) SWAP1 %encode_rlp_scalar // stack: rlp_pos, rlp_start, retdest diff --git a/evm/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm index fbd934d9..9d1b4a6d 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_1.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_1.asm @@ -31,6 +31,109 @@ global process_type_1_txn: POP // stack: retdest - // TODO: Check signature. +// From EIP-2930: +// The signatureYParity, signatureR, signatureS elements of this transaction represent a secp256k1 signature +// over keccak256(0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList])). +type_1_compute_signed_data: + %alloc_rlp_block + // stack: rlp_start, retdest + %mload_txn_field(@TXN_FIELD_CHAIN_ID) + // stack: chain_id, rlp_start, retdest + DUP2 + // stack: rlp_pos, chain_id, rlp_start, retdest + %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + %mload_txn_field(@TXN_FIELD_NONCE) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_TO) + DUP1 %jumpi(nonzero_to) + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_scalar + %jump(after_to) +nonzero_to: + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_160 + // stack: rlp_pos, rlp_start, retdest + +after_to: + %mload_txn_field(@TXN_FIELD_VALUE) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + // Encode txn data. + %mload_txn_field(@TXN_FIELD_DATA_LEN) + PUSH 0 // ADDR.virt + PUSH @SEGMENT_TXN_DATA + PUSH 0 // ADDR.context + // stack: ADDR: 3, len, rlp_pos, rlp_start, retdest + PUSH after_serializing_txn_data + // stack: after_serializing_txn_data, ADDR: 3, len, rlp_pos, rlp_start, retdest + SWAP5 + // stack: rlp_pos, ADDR: 3, len, after_serializing_txn_data, rlp_start, retdest + %jump(encode_rlp_string) + +after_serializing_txn_data: + // Instead of manually encoding the access list, we just copy the raw RLP from the transaction. + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_START) + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) + %stack (al_len, al_start, rlp_pos, rlp_start, retdest) -> + ( + 0, @SEGMENT_RLP_RAW, rlp_pos, + 0, @SEGMENT_RLP_RAW, al_start, + al_len, + after_serializing_access_list, + rlp_pos, rlp_start, retdest) + %jump(memcpy) +after_serializing_access_list: + // stack: rlp_pos, rlp_start, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) ADD + // stack: rlp_pos, rlp_start, retdest + %prepend_rlp_list_prefix + // stack: prefix_start_pos, rlp_len, retdest + + // Store a `1` in front of the RLP + %decrement + %stack (pos) -> (0, @SEGMENT_RLP_RAW, pos, 1, pos) + MSTORE_GENERAL + // stack: pos, rlp_len, retdest + + // Hash the RLP + the leading `1` + SWAP1 %increment SWAP1 + PUSH @SEGMENT_RLP_RAW + PUSH 0 // context + // stack: ADDR: 3, len, retdest + KECCAK_GENERAL + // stack: hash, retdest + + %mload_txn_field(@TXN_FIELD_S) + %mload_txn_field(@TXN_FIELD_R) + %mload_txn_field(@TXN_FIELD_Y_PARITY) %add_const(27) // ecrecover interprets v as y_parity + 27 + + PUSH store_origin + // stack: store_origin, v, r, s, hash, retdest + SWAP4 + // stack: hash, v, r, s, store_origin, retdest + %jump(ecrecover) + +store_origin: + // stack: address, retdest + // If ecrecover returned u256::MAX, that indicates failure. + DUP1 + %eq_const(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + %jumpi(panic) + + // stack: address, retdest + %mstore_txn_field(@TXN_FIELD_ORIGIN) + // stack: retdest %jump(process_normalized_txn) diff --git a/evm/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs index 4822c3d3..2846b664 100644 --- a/evm/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -59,10 +59,16 @@ pub(crate) enum GlobalMetadata { /// Current checkpoint. CurrentCheckpoint = 28, TouchedAddressesLen = 29, + // Gas cost for the access list in type-1 txns. See EIP-2930. + AccessListDataCost = 30, + // Start of the access list in the RLP for type-1 txns. + AccessListRlpStart = 31, + // Length of the access list in the RLP for type-1 txns. + AccessListRlpLen = 32, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 29; + pub(crate) const COUNT: usize = 32; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -95,6 +101,9 @@ impl GlobalMetadata { Self::JournalDataLen, Self::CurrentCheckpoint, Self::TouchedAddressesLen, + Self::AccessListDataCost, + Self::AccessListRlpStart, + Self::AccessListRlpLen, ] } @@ -130,6 +139,9 @@ impl GlobalMetadata { Self::JournalDataLen => "GLOBAL_METADATA_JOURNAL_DATA_LEN", Self::CurrentCheckpoint => "GLOBAL_METADATA_CURRENT_CHECKPOINT", Self::TouchedAddressesLen => "GLOBAL_METADATA_TOUCHED_ADDRESSES_LEN", + Self::AccessListDataCost => "GLOBAL_METADATA_ACCESS_LIST_DATA_COST", + Self::AccessListRlpStart => "GLOBAL_METADATA_ACCESS_LIST_RLP_START", + Self::AccessListRlpLen => "GLOBAL_METADATA_ACCESS_LIST_RLP_LEN", } } } From 6e3036017ecc10c548c2cfe399e765c7b0ca0059 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 23 May 2023 15:06:26 +0200 Subject: [PATCH 03/20] Support for type-2 transactions (#1052) * Type-2 txns * Minor * Minor * Fix add11_yml block metadata * Fix simple_transfer test * Minor --- evm/src/cpu/kernel/asm/core/process_txn.asm | 24 ++-- .../cpu/kernel/asm/transactions/type_2.asm | 109 +++++++++++++++++- evm/src/cpu/kernel/asm/util/assertions.asm | 34 ++++++ evm/tests/add11_yml.rs | 6 +- evm/tests/simple_transfer.rs | 19 +-- 5 files changed, 170 insertions(+), 22 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index 022c1882..2eea69dd 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -18,22 +18,27 @@ global process_normalized_txn: // Assert gas_limit >= intrinsic_gas. %mload_txn_field(@TXN_FIELD_INTRINSIC_GAS) %mload_txn_field(@TXN_FIELD_GAS_LIMIT) - %assert_ge + %assert_ge(invalid_txn) + + // Assert block gas limit >= txn gas limit. + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + %mload_global_metadata(@GLOBAL_METADATA_BLOCK_GAS_LIMIT) + %assert_ge(invalid_txn) %mload_txn_field(@TXN_FIELD_ORIGIN) // stack: sender, retdest // Check that txn nonce matches account nonce. DUP1 %nonce - DUP1 %eq_const(@MAX_NONCE) %assert_zero // EIP-2681 + DUP1 %eq_const(@MAX_NONCE) %assert_zero(invalid_txn) // EIP-2681 // stack: sender_nonce, sender, retdest %mload_txn_field(@TXN_FIELD_NONCE) // stack: tx_nonce, sender_nonce, sender, retdest - %assert_eq + %assert_eq(invalid_txn) // stack: sender, retdest // Assert sender has no code. - DUP1 %ext_code_empty %assert_nonzero + DUP1 %ext_code_empty %assert_nonzero(invalid_txn) // stack: sender, retdest // Assert sender balance >= gas_limit * gas_price + value. @@ -44,7 +49,7 @@ global process_normalized_txn: MUL %mload_txn_field(@TXN_FIELD_VALUE) ADD - %assert_le + %assert_le(invalid_txn) // stack: retdest // Assert chain ID matches block metadata @@ -58,7 +63,7 @@ global process_normalized_txn: %mload_global_metadata(@GLOBAL_METADATA_BLOCK_CHAIN_ID) MUL // stack: filtered_block_chain_id, filtered_tx_chain_id, retdest - %assert_eq + %assert_eq(invalid_txn) // stack: retdest global buy_gas: @@ -348,8 +353,9 @@ process_message_txn_fail: %mload_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) // stack: max_fee, max_priority_fee, base_fee - DUP3 DUP2 %assert_ge // Assert max_fee >= base_fee + DUP3 DUP2 %assert_ge(invalid_txn) // Assert max_fee >= base_fee // stack: max_fee, max_priority_fee, base_fee + DUP2 DUP2 %assert_ge(invalid_txn) // Assert max_fee >= max_priority_fee %stack (max_fee, max_priority_fee, base_fee) -> (max_fee, base_fee, max_priority_fee, base_fee) SUB // stack: max_fee - base_fee, max_priority_fee, base_fee @@ -399,3 +405,7 @@ contract_creation_fault_4: %delete_all_touched_addresses %delete_all_selfdestructed_addresses JUMP + + +invalid_txn: + %jump(txn_loop) diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm index d9586c21..464814eb 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -34,6 +34,113 @@ global process_type_2_txn: POP // stack: retdest - // TODO: Check signature. +// From EIP-1559: +// The signature_y_parity, signature_r, signature_s elements of this transaction represent a secp256k1 signature over +// keccak256(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list])) +type_2_compute_signed_data: + %alloc_rlp_block + // stack: rlp_start, retdest + %mload_txn_field(@TXN_FIELD_CHAIN_ID) + // stack: chain_id, rlp_start, retdest + DUP2 + // stack: rlp_pos, chain_id, rlp_start, retdest + %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + %mload_txn_field(@TXN_FIELD_NONCE) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_TO) + DUP1 %jumpi(nonzero_to) + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_scalar + %jump(after_to) +nonzero_to: + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_160 + // stack: rlp_pos, rlp_start, retdest + +after_to: + %mload_txn_field(@TXN_FIELD_VALUE) + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, rlp_start, retdest + + // Encode txn data. + %mload_txn_field(@TXN_FIELD_DATA_LEN) + PUSH 0 // ADDR.virt + PUSH @SEGMENT_TXN_DATA + PUSH 0 // ADDR.context + // stack: ADDR: 3, len, rlp_pos, rlp_start, retdest + PUSH after_serializing_txn_data + // stack: after_serializing_txn_data, ADDR: 3, len, rlp_pos, rlp_start, retdest + SWAP5 + // stack: rlp_pos, ADDR: 3, len, after_serializing_txn_data, rlp_start, retdest + %jump(encode_rlp_string) + +after_serializing_txn_data: + // Instead of manually encoding the access list, we just copy the raw RLP from the transaction. + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_START) + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) + %stack (al_len, al_start, rlp_pos, rlp_start, retdest) -> + ( + 0, @SEGMENT_RLP_RAW, rlp_pos, + 0, @SEGMENT_RLP_RAW, al_start, + al_len, + after_serializing_access_list, + rlp_pos, rlp_start, retdest) + %jump(memcpy) +after_serializing_access_list: + // stack: rlp_pos, rlp_start, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) ADD + // stack: rlp_pos, rlp_start, retdest + %prepend_rlp_list_prefix + // stack: prefix_start_pos, rlp_len, retdest + + // Store a `2` in front of the RLP + %decrement + %stack (pos) -> (0, @SEGMENT_RLP_RAW, pos, 2, pos) + MSTORE_GENERAL + // stack: pos, rlp_len, retdest + + // Hash the RLP + the leading `2` + SWAP1 %increment SWAP1 + PUSH @SEGMENT_RLP_RAW + PUSH 0 // context + // stack: ADDR: 3, len, retdest + KECCAK_GENERAL + // stack: hash, retdest + + %mload_txn_field(@TXN_FIELD_S) + %mload_txn_field(@TXN_FIELD_R) + %mload_txn_field(@TXN_FIELD_Y_PARITY) %add_const(27) // ecrecover interprets v as y_parity + 27 + + PUSH store_origin + // stack: store_origin, v, r, s, hash, retdest + SWAP4 + // stack: hash, v, r, s, store_origin, retdest + %jump(ecrecover) + +store_origin: + // stack: address, retdest + // If ecrecover returned u256::MAX, that indicates failure. + DUP1 + %eq_const(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + %jumpi(panic) + + // stack: address, retdest + %mstore_txn_field(@TXN_FIELD_ORIGIN) + // stack: retdest %jump(process_normalized_txn) diff --git a/evm/src/cpu/kernel/asm/util/assertions.asm b/evm/src/cpu/kernel/asm/util/assertions.asm index 0051219c..017ca10f 100644 --- a/evm/src/cpu/kernel/asm/util/assertions.asm +++ b/evm/src/cpu/kernel/asm/util/assertions.asm @@ -8,17 +8,31 @@ global panic: %jumpi(panic) %endmacro +%macro assert_zero(ret) + %jumpi($ret) +%endmacro + // Consumes the top element and asserts that it is nonzero. %macro assert_nonzero ISZERO %jumpi(panic) %endmacro +%macro assert_nonzero(ret) + ISZERO + %jumpi($ret) +%endmacro + %macro assert_eq EQ %assert_nonzero %endmacro +%macro assert_eq(ret) + EQ + %assert_nonzero($ret) +%endmacro + %macro assert_lt // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x < y) == !(x >= y). @@ -26,6 +40,11 @@ global panic: %assert_zero %endmacro +%macro assert_lt(ret) + GE + %assert_zero($ret) +%endmacro + %macro assert_le // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x <= y) == !(x > y). @@ -33,6 +52,11 @@ global panic: %assert_zero %endmacro +%macro assert_le(ret) + GT + %assert_zero($ret) +%endmacro + %macro assert_gt // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x > y) == !(x <= y). @@ -40,6 +64,11 @@ global panic: %assert_zero %endmacro +%macro assert_gt(ret) + LE + %assert_zero($ret) +%endmacro + %macro assert_ge // %assert_zero is cheaper than %assert_nonzero, so we will leverage the // fact that (x >= y) == !(x < y). @@ -47,6 +76,11 @@ global panic: %assert_zero %endmacro +%macro assert_ge(ret) + LT + %assert_zero($ret) +%endmacro + %macro assert_eq_const(c) %eq_const($c) %assert_nonzero diff --git a/evm/tests/add11_yml.rs b/evm/tests/add11_yml.rs index d4e47378..a17d688e 100644 --- a/evm/tests/add11_yml.rs +++ b/evm/tests/add11_yml.rs @@ -81,8 +81,12 @@ fn add11_yml() -> anyhow::Result<()> { let block_metadata = BlockMetadata { block_beneficiary: Address::from(beneficiary), + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_gaslimit: 0xff112233445566u64.into(), + block_chain_id: 1.into(), block_base_fee: 0xa.into(), - ..BlockMetadata::default() }; let mut contract_code = HashMap::new(); diff --git a/evm/tests/simple_transfer.rs b/evm/tests/simple_transfer.rs index 250631e3..028900fa 100644 --- a/evm/tests/simple_transfer.rs +++ b/evm/tests/simple_transfer.rs @@ -37,15 +37,12 @@ fn test_simple_transfer() -> anyhow::Result<()> { let sender = hex!("2c7536e3605d9c16a7a3d7b1898e529396a65c23"); let to = hex!("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"); - let beneficiary_state_key = keccak(beneficiary); let sender_state_key = keccak(sender); let to_state_key = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); let to_nibbles = Nibbles::from_bytes_be(to_state_key.as_bytes()).unwrap(); - let beneficiary_account_before = AccountRlp::default(); let sender_account_before = AccountRlp { nonce: 5.into(), balance: eth_to_wei(100_000.into()), @@ -72,7 +69,12 @@ fn test_simple_transfer() -> anyhow::Result<()> { let block_metadata = BlockMetadata { block_beneficiary: Address::from(beneficiary), - ..BlockMetadata::default() + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_gaslimit: 0xff112233445566u64.into(), + block_chain_id: 1.into(), + block_base_fee: 0xa.into(), }; let mut contract_code = HashMap::new(); @@ -94,10 +96,6 @@ fn test_simple_transfer() -> anyhow::Result<()> { let txdata_gas = 2 * 16; let gas_used = 21_000 + txdata_gas; - let beneficiary_account_after = AccountRlp { - balance: beneficiary_account_before.balance + gas_used * 10, - ..beneficiary_account_before - }; let sender_account_after = AccountRlp { balance: sender_account_before.balance - value - gas_used * 10, nonce: sender_account_before.nonce + 1, @@ -109,11 +107,6 @@ fn test_simple_transfer() -> anyhow::Result<()> { }; let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[beneficiary_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: beneficiary_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&beneficiary_account_after).to_vec(), - } - .into(); children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { nibbles: sender_nibbles.truncate_n_nibbles_front(1), value: rlp::encode(&sender_account_after).to_vec(), From 354664c86ccaa1f52a1ef9a6c95ed869e90e7249 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 23 May 2023 17:03:33 +0200 Subject: [PATCH 04/20] Fix ecmul (#1053) --- .../asm/curve/bn254/curve_arithmetic/glv.asm | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm index c29d8f14..bd903b21 100644 --- a/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm +++ b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm @@ -42,6 +42,7 @@ // ``` global bn_glv_decompose: // stack: k, retdest + %mod_const(@BN_SCALAR) PUSH @BN_SCALAR DUP1 DUP1 // Compute c2 which is the top 256 bits of k*g1. Use asm from https://medium.com/wicketh/mathemagic-full-multiply-27650fec525d. PUSH @U256_MAX @@ -73,7 +74,15 @@ global bn_glv_decompose: // We compute k2 = q1 + q2 - N, but we check for underflow and return N-q1-q2 instead if there is one, // along with a flag `underflow` set to 1 if there is an underflow, 0 otherwise. - ADD %sub_check_underflow + ADD %bn_sub_check_underflow + // stack: k2, underflow, N, k, retdest + DUP1 %ge_const(0x80000000000000000000000000000000) %jumpi(negate) + %jump(contd) +negate: + // stack: k2, underflow, N, k, retdest + SWAP1 PUSH 1 SUB SWAP1 + PUSH @BN_SCALAR SUB +contd: // stack: k2, underflow, N, k, retdest SWAP3 PUSH @BN_SCALAR DUP5 PUSH @BN_GLV_S // stack: s, k2, N, k, underflow, N, k2, retdest @@ -94,4 +103,14 @@ underflowed: %stack (k1, k2, underflow, retdest) -> (retdest, underflow, k1, k2) JUMP - +%macro bn_sub_check_underflow + // stack: x, y + DUP2 DUP2 LT + // stack: x=y, x (x, y, b, a, c) + SUB MUL ADD + %stack (res, bool) -> (res, @BN_SCALAR, bool) + MOD +%endmacro From 08a061bc4d92e47337e5b31abf3c045f5dda61f4 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 24 May 2023 10:29:34 +0200 Subject: [PATCH 05/20] Implement LOG* gas and remove panic (#1054) * Implement LOG* gas and remove panic * Remove stubs --- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/core/log.asm | 75 +++++++++++++++++++ evm/src/cpu/kernel/asm/core/syscall_stubs.asm | 15 ---- 3 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/core/log.asm diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 1da28e0b..67fab89c 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -41,6 +41,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/core/transfer.asm"), include_str!("asm/core/util.asm"), include_str!("asm/core/access_lists.asm"), + include_str!("asm/core/log.asm"), include_str!("asm/core/selfdestruct_list.asm"), include_str!("asm/core/touched_addresses.asm"), include_str!("asm/core/precompiles/main.asm"), diff --git a/evm/src/cpu/kernel/asm/core/log.asm b/evm/src/cpu/kernel/asm/core/log.asm new file mode 100644 index 00000000..88098588 --- /dev/null +++ b/evm/src/cpu/kernel/asm/core/log.asm @@ -0,0 +1,75 @@ +// TODO: Implement receipts + +global sys_log0: + %check_static + // stack: kexit_info, offset, size + DUP3 DUP3 + %add_or_fault + // stack: offset+size, kexit_info, offset, size + DUP1 %ensure_reasonable_offset + %update_mem_bytes + // stack: kexit_info, offset, size + DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) + // stack: gas, kexit_info, offset, size + %charge_gas + %stack (kexit_info, offset, size) -> (kexit_info) + EXIT_KERNEL + +global sys_log1: + %check_static + // stack: kexit_info, offset, size, topic + DUP3 DUP3 + %add_or_fault + // stack: offset+size, kexit_info, offset, size, topic + DUP1 %ensure_reasonable_offset + %update_mem_bytes + // stack: kexit_info, offset, size, topic + DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) %add_const(@GAS_LOGTOPIC) + // stack: gas, kexit_info, offset, size, topic + %charge_gas + %stack (kexit_info, offset, size, topic) -> (kexit_info) + EXIT_KERNEL + +global sys_log2: + %check_static + // stack: kexit_info, offset, size, topic1, topic2 + DUP3 DUP3 + %add_or_fault + // stack: offset+size, kexit_info, offset, size, topic1, topic2 + DUP1 %ensure_reasonable_offset + %update_mem_bytes + // stack: kexit_info, offset, size, topic1, topic2 + DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) + // stack: gas, kexit_info, offset, size, topic1, topic2 + %charge_gas + %stack (kexit_info, offset, size, topic1, topic2) -> (kexit_info) + EXIT_KERNEL +global sys_log3: + %check_static + // stack: kexit_info, offset, size, topic1, topic2, topic3 + DUP3 DUP3 + %add_or_fault + // stack: offset+size, kexit_info, offset, size, topic1, topic2, topic3 + DUP1 %ensure_reasonable_offset + %update_mem_bytes + // stack: kexit_info, offset, size, topic1, topic2, topic3 + DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) + // stack: gas, kexit_info, offset, size, topic1, topic2, topic3 + %charge_gas + %stack (kexit_info, offset, size, topic1, topic2, topic3) -> (kexit_info) + EXIT_KERNEL + +global sys_log4: + %check_static + // stack: kexit_info, offset, size, topic1, topic2, topic3, topic4 + DUP3 DUP3 + %add_or_fault + // stack: offset+size, kexit_info, offset, size, topic1, topic2, topic3, topic4 + DUP1 %ensure_reasonable_offset + %update_mem_bytes + // stack: kexit_info, offset, size, topic1, topic2, topic3, topic4 + DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) + // stack: gas, kexit_info, offset, size, topic1, topic2, topic3, topic4 + %charge_gas + %stack (kexit_info, offset, size, topic1, topic2, topic3, topic4) -> (kexit_info) + EXIT_KERNEL diff --git a/evm/src/cpu/kernel/asm/core/syscall_stubs.asm b/evm/src/cpu/kernel/asm/core/syscall_stubs.asm index 1232576c..1530872c 100644 --- a/evm/src/cpu/kernel/asm/core/syscall_stubs.asm +++ b/evm/src/cpu/kernel/asm/core/syscall_stubs.asm @@ -6,18 +6,3 @@ global sys_blockhash: global sys_prevrandao: // TODO: What semantics will this have for Edge? PANIC -global sys_log0: - %check_static - PANIC -global sys_log1: - %check_static - PANIC -global sys_log2: - %check_static - PANIC -global sys_log3: - %check_static - PANIC -global sys_log4: - %check_static - PANIC From 9727eaf1e03f031af3e0ad37b2335bd95821d6bb Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 24 May 2023 15:03:02 +0200 Subject: [PATCH 06/20] Fix extcodehash when account is empty (#1055) --- evm/src/cpu/kernel/asm/account_code.asm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/evm/src/cpu/kernel/asm/account_code.asm b/evm/src/cpu/kernel/asm/account_code.asm index d82980db..d7d09b5b 100644 --- a/evm/src/cpu/kernel/asm/account_code.asm +++ b/evm/src/cpu/kernel/asm/account_code.asm @@ -13,10 +13,14 @@ global sys_extcodehash: // stack: kexit_info, address SWAP1 + DUP1 %is_dead %jumpi(extcodehash_dead) %extcodehash // stack: hash, kexit_info SWAP1 EXIT_KERNEL +extcodehash_dead: + %stack (address, kexit_info) -> (kexit_info, 0) + EXIT_KERNEL global extcodehash: // stack: address, retdest From 30b97b29e89ca4c305cbd8eef76eeac53faa8ed1 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 24 May 2023 15:44:40 +0200 Subject: [PATCH 07/20] Fix DelegateCall bug --- evm/src/cpu/kernel/asm/core/call.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/asm/core/call.asm b/evm/src/cpu/kernel/asm/core/call.asm index 3a170325..40290797 100644 --- a/evm/src/cpu/kernel/asm/core/call.asm +++ b/evm/src/cpu/kernel/asm/core/call.asm @@ -201,7 +201,7 @@ global sys_delegatecall: %set_new_ctx_parent_pc(after_call_instruction) DUP4 %set_new_ctx_code - %stack (new_ctx, kexit_info, callgas, address, args_offset, args_size, ret_offset, ret_size) + %stack (new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size) -> (new_ctx, kexit_info, ret_offset, ret_size) %enter_new_ctx From e6a7b8c5cbca9f365704efffdad784dbb4e651b8 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 24 May 2023 17:49:54 +0200 Subject: [PATCH 08/20] Add contract creation flag (#1056) * Add contract creation flag * Minor * Fix intrinsic gas test --- evm/src/cpu/kernel/asm/core/process_txn.asm | 2 +- evm/src/cpu/kernel/asm/core/util.asm | 6 +---- evm/src/cpu/kernel/asm/rlp/decode.asm | 2 +- .../asm/transactions/common_decoding.asm | 22 +++++++++++++++++-- .../cpu/kernel/asm/transactions/type_0.asm | 10 ++++----- .../cpu/kernel/asm/transactions/type_1.asm | 10 ++++----- .../cpu/kernel/asm/transactions/type_2.asm | 10 ++++----- .../cpu/kernel/constants/global_metadata.rs | 6 ++++- .../cpu/kernel/tests/core/intrinsic_gas.rs | 3 +++ 9 files changed, 46 insertions(+), 25 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index 2eea69dd..61c66134 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -407,5 +407,5 @@ contract_creation_fault_4: JUMP -invalid_txn: +global invalid_txn: %jump(txn_loop) diff --git a/evm/src/cpu/kernel/asm/core/util.asm b/evm/src/cpu/kernel/asm/core/util.asm index d64c6059..417a2d24 100644 --- a/evm/src/cpu/kernel/asm/core/util.asm +++ b/evm/src/cpu/kernel/asm/core/util.asm @@ -21,11 +21,7 @@ // Returns whether the current transaction is a contract creation transaction. %macro is_contract_creation // stack: (empty) - %mload_txn_field(@TXN_FIELD_TO) - // stack: to - ISZERO - // If there is no "to" field, then this is a contract creation. - // stack: to == 0 + %mload_global_metadata(@GLOBAL_METADATA_CONTRACT_CREATION) %endmacro %macro is_precompile diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm index 9842bfbd..327edcf1 100644 --- a/evm/src/cpu/kernel/asm/rlp/decode.asm +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -130,7 +130,7 @@ decode_rlp_list_len_big: // fit in a single (256-bit) word on the stack. // Pre stack: pos, len, retdest // Post stack: pos', int -decode_int_given_len: +global decode_int_given_len: %stack (pos, len, retdest) -> (pos, len, pos, retdest) ADD // stack: end_pos, pos, retdest diff --git a/evm/src/cpu/kernel/asm/transactions/common_decoding.asm b/evm/src/cpu/kernel/asm/transactions/common_decoding.asm index 07d19f47..9b12d9c9 100644 --- a/evm/src/cpu/kernel/asm/transactions/common_decoding.asm +++ b/evm/src/cpu/kernel/asm/transactions/common_decoding.asm @@ -61,12 +61,30 @@ %endmacro // Decode the "to" field and store it. +// This field is either 160-bit or empty in the case of a contract creation txn. %macro decode_and_store_to // stack: pos - %decode_rlp_scalar - %stack (pos, to) -> (to, pos) + %decode_rlp_string_len + // stack: pos, len + SWAP1 + // stack: len, pos + DUP1 ISZERO %jumpi(%%contract_creation) + // stack: len, pos + DUP1 %eq_const(20) ISZERO %jumpi(invalid_txn) // Address is 160-bit + %stack (len, pos) -> (pos, len, %%with_scalar) + %jump(decode_int_given_len) +%%with_scalar: + // stack: pos, int + SWAP1 %mstore_txn_field(@TXN_FIELD_TO) // stack: pos + %jump(%%end) +%%contract_creation: + // stack: len, pos + POP + PUSH 1 %mstore_global_metadata(@GLOBAL_METADATA_CONTRACT_CREATION) + // stack: pos +%%end: %endmacro // Decode the "value" field and store it. diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm index 65836086..edd01e51 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -102,13 +102,13 @@ type_0_compute_signed_data: // stack: rlp_pos, rlp_start, retdest %mload_txn_field(@TXN_FIELD_TO) - DUP1 %jumpi(nonzero_to) - // stack: to, rlp_pos, rlp_start, retdest - SWAP1 %encode_rlp_scalar - %jump(after_to) -nonzero_to: + %mload_global_metadata(@GLOBAL_METADATA_CONTRACT_CREATION) %jumpi(zero_to) // stack: to, rlp_pos, rlp_start, retdest SWAP1 %encode_rlp_160 + %jump(after_to) +zero_to: + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_scalar // stack: rlp_pos, rlp_start, retdest after_to: diff --git a/evm/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm index 9d1b4a6d..f8396e50 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_1.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_1.asm @@ -57,13 +57,13 @@ type_1_compute_signed_data: // stack: rlp_pos, rlp_start, retdest %mload_txn_field(@TXN_FIELD_TO) - DUP1 %jumpi(nonzero_to) - // stack: to, rlp_pos, rlp_start, retdest - SWAP1 %encode_rlp_scalar - %jump(after_to) -nonzero_to: + %mload_global_metadata(@GLOBAL_METADATA_CONTRACT_CREATION) %jumpi(zero_to) // stack: to, rlp_pos, rlp_start, retdest SWAP1 %encode_rlp_160 + %jump(after_to) +zero_to: + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_scalar // stack: rlp_pos, rlp_start, retdest after_to: diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm index 464814eb..38f1980f 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -64,13 +64,13 @@ type_2_compute_signed_data: // stack: rlp_pos, rlp_start, retdest %mload_txn_field(@TXN_FIELD_TO) - DUP1 %jumpi(nonzero_to) - // stack: to, rlp_pos, rlp_start, retdest - SWAP1 %encode_rlp_scalar - %jump(after_to) -nonzero_to: + %mload_global_metadata(@GLOBAL_METADATA_CONTRACT_CREATION) %jumpi(zero_to) // stack: to, rlp_pos, rlp_start, retdest SWAP1 %encode_rlp_160 + %jump(after_to) +zero_to: + // stack: to, rlp_pos, rlp_start, retdest + SWAP1 %encode_rlp_scalar // stack: rlp_pos, rlp_start, retdest after_to: diff --git a/evm/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs index 2846b664..5cf8e138 100644 --- a/evm/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -65,10 +65,12 @@ pub(crate) enum GlobalMetadata { AccessListRlpStart = 31, // Length of the access list in the RLP for type-1 txns. AccessListRlpLen = 32, + // Boolean flag indicating if the txn is a contract creation txn. + ContractCreation = 33, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 32; + pub(crate) const COUNT: usize = 33; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -104,6 +106,7 @@ impl GlobalMetadata { Self::AccessListDataCost, Self::AccessListRlpStart, Self::AccessListRlpLen, + Self::ContractCreation, ] } @@ -142,6 +145,7 @@ impl GlobalMetadata { Self::AccessListDataCost => "GLOBAL_METADATA_ACCESS_LIST_DATA_COST", Self::AccessListRlpStart => "GLOBAL_METADATA_ACCESS_LIST_RLP_START", Self::AccessListRlpLen => "GLOBAL_METADATA_ACCESS_LIST_RLP_LEN", + Self::ContractCreation => "GLOBAL_METADATA_CONTRACT_CREATION", } } } diff --git a/evm/src/cpu/kernel/tests/core/intrinsic_gas.rs b/evm/src/cpu/kernel/tests/core/intrinsic_gas.rs index d84002ae..d8badef9 100644 --- a/evm/src/cpu/kernel/tests/core/intrinsic_gas.rs +++ b/evm/src/cpu/kernel/tests/core/intrinsic_gas.rs @@ -1,6 +1,8 @@ use anyhow::Result; +use ethereum_types::U256; use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField; use crate::cpu::kernel::interpreter::Interpreter; @@ -14,6 +16,7 @@ fn test_intrinsic_gas() -> Result<()> { // Contract creation transaction. let initial_stack = vec![0xdeadbeefu32.into()]; let mut interpreter = Interpreter::new_with_kernel(intrinsic_gas, initial_stack.clone()); + interpreter.set_global_metadata_field(GlobalMetadata::ContractCreation, U256::one()); interpreter.run()?; assert_eq!(interpreter.stack(), vec![(GAS_TX + GAS_TXCREATE).into()]); From c0abefdaf55f45fe820502688e442a5b958d1929 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 24 May 2023 18:02:48 +0200 Subject: [PATCH 09/20] Fix DUP in call gas --- evm/src/cpu/kernel/asm/core/call_gas.asm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/call_gas.asm b/evm/src/cpu/kernel/asm/core/call_gas.asm index 4dcbcec1..69e27966 100644 --- a/evm/src/cpu/kernel/asm/core/call_gas.asm +++ b/evm/src/cpu/kernel/asm/core/call_gas.asm @@ -16,7 +16,7 @@ global call_charge_gas: %mul_const(@GAS_COLDACCOUNTACCESS_MINUS_WARMACCESS) %add_const(@GAS_WARMACCESS) // stack: cost, is_call_or_staticcall, is_call_or_callcode, address, gas, kexit_info, value, retdest - DUP4 + DUP3 // stack: is_call_or_callcode, cost, is_call_or_staticcall, is_call_or_callcode, address, gas, kexit_info, value, retdest %jumpi(xfer_cost) after_xfer_cost: @@ -60,7 +60,7 @@ after_new_cost: (retdest, kexit_info, C_callgas, address, value) JUMP -xfer_cost: +global xfer_cost: // stack: cost, is_call_or_staticcall, is_call_or_callcode, address, gas, kexit_info, value, retdest DUP7 // stack: value, cost, is_call_or_staticcall, is_call_or_callcode, address, gas, kexit_info, value, retdest From 42f3301730d5e364cfea91e915fbbd21f1c1f923 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 25 May 2023 14:15:48 +0200 Subject: [PATCH 10/20] Fix ecrecover edge case (#1057) --- evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm index 951ca97c..c84536d8 100644 --- a/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm +++ b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm @@ -100,9 +100,15 @@ ecdsa_after_precompute_loop_contd2: %stack (accx, accy, i, a0, a1, b0, b1, retdest) -> (i, accx, accy, a0, a1, b0, b1, retdest) %decrement %jump(ecdsa_after_precompute_loop) ecdsa_after_precompute_loop_end: + // Check that the public key is not the point at infinity. See https://github.com/ethereum/eth-keys/pull/76 for discussion. + DUP2 DUP2 ISZERO SWAP1 ISZERO MUL %jumpi(pk_is_infinity) %stack (accx, accy, ecdsa_after_precompute_loop_contd2, i, a0, a1, b0, b1, retdest) -> (retdest, accx, accy) JUMP +pk_is_infinity: + %stack (accx, accy, ecdsa_after_precompute_loop_contd2, i, a0, a1, b0, b1, pubkey_to_addr, retdest) -> (retdest, @U256_MAX) + JUMP + // Take a public key (PKx, PKy) and return the associated address KECCAK256(PKx || PKy)[-20:]. pubkey_to_addr: // stack: PKx, PKy, retdest From 7dfdacf247b09c603f26dc50a71018aff3be0ce7 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 25 May 2023 16:47:55 +0200 Subject: [PATCH 11/20] Fix return and revert gas (#1058) --- evm/src/cpu/kernel/asm/core/terminate.asm | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/asm/core/terminate.asm b/evm/src/cpu/kernel/asm/core/terminate.asm index a2a88835..46e44b1b 100644 --- a/evm/src/cpu/kernel/asm/core/terminate.asm +++ b/evm/src/cpu/kernel/asm/core/terminate.asm @@ -16,8 +16,13 @@ global sys_return: %stack (kexit_info, offset, size) -> (offset, size, kexit_info, offset, size) %add_or_fault DUP1 %ensure_reasonable_offset + // stack: offset+size, kexit_info, offset, size + DUP4 ISZERO %jumpi(return_zero_size) %update_mem_bytes - + %jump(return_after_gas) +return_zero_size: + POP +return_after_gas: // Load the parent's context. %mload_context_metadata(@CTX_METADATA_PARENT_CONTEXT) @@ -115,6 +120,13 @@ global sys_revert: %stack (kexit_info, offset, size) -> (offset, size, kexit_info, offset, size) %add_or_fault DUP1 %ensure_reasonable_offset + // stack: offset+size, kexit_info, offset, size + DUP4 ISZERO %jumpi(revert_zero_size) + %update_mem_bytes + %jump(revert_after_gas) +revert_zero_size: + POP +revert_after_gas: %update_mem_bytes // Load the parent's context. From a492d3e1dba6abbb2cb1cce0b3a86a06ef9bae3e Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 25 May 2023 17:43:25 +0200 Subject: [PATCH 12/20] Fix revert gas bug --- evm/src/cpu/kernel/asm/core/terminate.asm | 2 -- 1 file changed, 2 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/terminate.asm b/evm/src/cpu/kernel/asm/core/terminate.asm index 46e44b1b..7e49d45e 100644 --- a/evm/src/cpu/kernel/asm/core/terminate.asm +++ b/evm/src/cpu/kernel/asm/core/terminate.asm @@ -127,8 +127,6 @@ global sys_revert: revert_zero_size: POP revert_after_gas: - %update_mem_bytes - // Load the parent's context. %mload_context_metadata(@CTX_METADATA_PARENT_CONTEXT) From 8153dc7825930c3aa92ae0694cab56188322b4ac Mon Sep 17 00:00:00 2001 From: Hamish Ivey-Law <426294+unzvfu@users.noreply.github.com> Date: Sat, 3 Jun 2023 01:37:13 +1000 Subject: [PATCH 13/20] Remove `-C prefer-dynamic=y` from CI build. --- .github/workflows/continuous-integration-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index c8b35249..93049385 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -44,7 +44,7 @@ jobs: command: test args: --all env: - RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 -Cprefer-dynamic=y + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 RUST_LOG: 1 CARGO_INCREMENTAL: 1 RUST_BACKTRACE: 1 From 0d819cf8882b9bae33fec72b51813dde72e87233 Mon Sep 17 00:00:00 2001 From: Hamish Ivey-Law <426294+unzvfu@users.noreply.github.com> Date: Sat, 3 Jun 2023 02:16:45 +1000 Subject: [PATCH 14/20] Implement EVM `BYTE` operation (#1059) * Initial implementation of BYTE. * Large index constraints; byte range check (hat-tip to Jacqui) * Implement recursive circuit version. * Rebind variable to avoid exceeding degree limit. * Integrate BYTE with arithmetic stark and witness generation. * Clippy. * Document verification proof; miscellaneous tidying. * Update CTL mapping. * Reverse argument order. * Avoid undesired doctest. * Address Jacqui's comments. * Address remaining comments from Jacqui. --- evm/src/arithmetic/arithmetic_stark.rs | 13 +- evm/src/arithmetic/byte.rs | 483 +++++++++++++++++++++++++ evm/src/arithmetic/columns.rs | 3 +- evm/src/arithmetic/mod.rs | 15 +- evm/src/cpu/cpu_stark.rs | 3 +- evm/src/witness/gas.rs | 2 +- evm/src/witness/operation.rs | 22 -- evm/src/witness/transition.rs | 7 +- 8 files changed, 516 insertions(+), 32 deletions(-) create mode 100644 evm/src/arithmetic/byte.rs diff --git a/evm/src/arithmetic/arithmetic_stark.rs b/evm/src/arithmetic/arithmetic_stark.rs index 342bb8c2..4fcbb534 100644 --- a/evm/src/arithmetic/arithmetic_stark.rs +++ b/evm/src/arithmetic/arithmetic_stark.rs @@ -12,7 +12,7 @@ use plonky2::util::transpose; use static_assertions::const_assert; use crate::all_stark::Table; -use crate::arithmetic::{addcy, columns, divmod, modular, mul, Operation}; +use crate::arithmetic::{addcy, byte, columns, divmod, modular, mul, Operation}; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::{Column, TableWithColumns}; use crate::lookup::{eval_lookups, eval_lookups_circuit, permuted_cols}; @@ -49,7 +49,7 @@ fn cpu_arith_data_link(ops: &[usize], regs: &[Range]) -> Vec() -> TableWithColumns { - const ARITH_OPS: [usize; 13] = [ + const ARITH_OPS: [usize; 14] = [ columns::IS_ADD, columns::IS_SUB, columns::IS_MUL, @@ -63,6 +63,7 @@ pub fn ctl_arithmetic_rows() -> TableWithColumns { columns::IS_SUBMOD, columns::IS_DIV, columns::IS_MOD, + columns::IS_BYTE, ]; const REGISTER_MAP: [Range; 4] = [ @@ -183,6 +184,7 @@ impl, const D: usize> Stark for ArithmeticSta addcy::eval_packed_generic(lv, yield_constr); divmod::eval_packed(lv, nv, yield_constr); modular::eval_packed(lv, nv, yield_constr); + byte::eval_packed(lv, yield_constr); } fn eval_ext_circuit( @@ -214,6 +216,7 @@ impl, const D: usize> Stark for ArithmeticSta addcy::eval_ext_circuit(builder, lv, yield_constr); divmod::eval_ext_circuit(builder, lv, nv, yield_constr); modular::eval_ext_circuit(builder, lv, nv, yield_constr); + byte::eval_ext_circuit(builder, lv, yield_constr); } fn constraint_degree(&self) -> usize { @@ -317,7 +320,10 @@ mod tests { // 128 % 13 == 11 let modop = Operation::binary(BinaryOperator::Mod, U256::from(128), U256::from(13)); - let ops: Vec = vec![add, mulmod, addmod, mul, modop, lt1, lt2, lt3, div]; + // byte(30, 0xABCD) = 0xAB + let byte = Operation::binary(BinaryOperator::Byte, U256::from(30), U256::from(0xABCD)); + + let ops: Vec = vec![add, mulmod, addmod, mul, modop, lt1, lt2, lt3, div, byte]; let pols = stark.generate_trace(ops); @@ -341,6 +347,7 @@ mod tests { (9, 1), (10, 0), (11, 9), + (13, 0xAB), ]; for (row, expected) in expected_output { diff --git a/evm/src/arithmetic/byte.rs b/evm/src/arithmetic/byte.rs new file mode 100644 index 00000000..a563eb9f --- /dev/null +++ b/evm/src/arithmetic/byte.rs @@ -0,0 +1,483 @@ +//! Support for the EVM BYTE instruction +//! +//! This crate verifies the EVM BYTE instruction, defined as follows: +//! +//! INPUTS: 256-bit values I and X = \sum_{i=0}^31 X_i B^i, +//! where B = 2^8 and 0 <= X_i < B for all i. +//! +//! OUTPUT: X_{31-I} if 0 <= I < 32, otherwise 0. +//! +//! NB: index I=0 corresponds to byte X_31, i.e. the most significant +//! byte. This is exactly the opposite of anyone would expect; who +//! knows what the EVM designers were thinking. Anyway, if anything +//! below seems confusing, first check to ensure you're counting from +//! the wrong end of X, as the spec requires. +//! +//! Wlog consider 0 <= I < 32, so I has five bits b0,...,b4. We are +//! given X as an array of 16-bit limbs; write X := \sum_{i=0}^15 Y_i +//! 2^{16i} where 0 <= Y_i < 2^16. +//! +//! The technique (hat tip to Jacqui for the idea) is to store a tree +//! of limbs of X that are selected according to the bits in I. The +//! main observation is that each bit `bi` halves the number of +//! candidate bytes that we might return: If b4 is 0, then I < 16 and +//! the possible bytes are in the top half of X: Y_8,..,Y_15 +//! (corresponding to bytes X_16,..,X_31), and if b4 is 1 then I >= 16 +//! and the possible bytes are the bottom half of X: Y_0,..,Y_7 +//! (corresponding to bytes X_0,..,X_15). +//! +//! Let Z_0,..,Z_7 be the bytes selected in the first step. Then, in +//! the next step, if b3 is 0, we select Z_4,..,Z_7 and if it's 1 we +//! select Z_0,..,Z_3. Together, b4 and b3 divide the bytes of X into +//! 4 equal-sized chunks of 4 limbs, and the byte we're after will be +//! among the limbs 4 selected limbs. +//! +//! Repeating for b2 and b1, we reduce to a single 16-bit limb +//! L=x+y*256; the desired byte will be x if b0 is 1 and y if b0 +//! is 0. +//! +//! -*- +//! +//! To prove that the bytes x and y are in the range [0, 2^8) (rather +//! than [0, 2^16), which is all the range-checker guarantees) we do +//! the following (hat tip to Jacqui for this trick too): Instead of +//! storing x and y, we store w = 256 * x and y. Then, to verify that +//! x, y < 256 and the last limb L = x + y * 256, we check that +//! L = w / 256 + y * 256. +//! +//! The proof of why verifying that L = w / 256 + y * 256 +//! suffices is as follows: +//! +//! 1. The given L, w and y are range-checked to be less than 2^16. +//! 2. y * 256 ∈ {0, 256, 512, ..., 2^24 - 512, 2^24 - 256} +//! 3. w / 256 = L - y * 256 ∈ {-2^24 + 256, -2^24 + 257, ..., 2^16 - 2, 2^16 - 1} +//! 4. By inspection, for w < 2^16, if w / 256 < 2^16 or +//! w / 256 >= P - 2^24 + 256 (i.e. if w / 256 falls in the range +//! of point 3 above), then w = 256 * m for some 0 <= m < 256. +//! 5. Hence w / 256 ∈ {0, 1, ..., 255} +//! 6. Hence y * 256 = L - w / 256 ∈ {-255, -254, ..., 2^16 - 1} +//! 7. Taking the intersection of ranges in 2. and 6. we see that +//! y * 256 ∈ {0, 256, 512, ..., 2^16 - 256} +//! 8. Hence y ∈ {0, 1, ..., 255} + +use std::ops::Range; + +use ethereum_types::U256; +use plonky2::field::extension::Extendable; +use plonky2::field::packed::PackedField; +use plonky2::field::types::{Field, PrimeField64}; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use static_assertions::const_assert; + +use crate::arithmetic::columns::*; +use crate::arithmetic::utils::u256_to_array; +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; + +// Give meaningful names to the columns of AUX_INPUT_REGISTER_0 that +// we're using +const BYTE_IDX_DECOMP: Range = AUX_INPUT_REGISTER_0.start..AUX_INPUT_REGISTER_0.start + 6; +const BYTE_IDX_DECOMP_HI: usize = AUX_INPUT_REGISTER_0.start + 5; +const BYTE_LAST_LIMB_LO: usize = AUX_INPUT_REGISTER_0.start + 6; +const BYTE_LAST_LIMB_HI: usize = AUX_INPUT_REGISTER_0.start + 7; +const BYTE_IDX_IS_LARGE: usize = AUX_INPUT_REGISTER_0.start + 8; +const BYTE_IDX_HI_LIMB_SUM_INV_0: usize = AUX_INPUT_REGISTER_0.start + 9; +const BYTE_IDX_HI_LIMB_SUM_INV_1: usize = AUX_INPUT_REGISTER_0.start + 10; +const BYTE_IDX_HI_LIMB_SUM_INV_2: usize = AUX_INPUT_REGISTER_0.start + 11; +const BYTE_IDX_HI_LIMB_SUM_INV_3: usize = AUX_INPUT_REGISTER_0.start + 12; + +/// Decompose `idx` into bits and bobs and store in `idx_decomp`. +/// +/// Specifically, write +/// +/// idx = idx0_lo5 + idx0_hi * 2^5 + \sum_i idx[i] * 2^(16i), +/// +/// where `0 <= idx0_lo5 < 32` and `0 <= idx0_hi < 2^11`. Store the +/// 5 bits of `idx0_lo5` in `idx_decomp[0..5]`; we don't explicitly need +/// the higher 11 bits of the first limb, so we put them in +/// `idx_decomp[5]`. The rest of `idx_decomp` is set to 0. +fn set_idx_decomp(idx_decomp: &mut [F], idx: &U256) { + debug_assert!(idx_decomp.len() == 6); + for i in 0..5 { + idx_decomp[i] = F::from_bool(idx.bit(i)); + } + idx_decomp[5] = F::from_canonical_u16((idx.low_u64() as u16) >> 5); +} + +pub(crate) fn generate(lv: &mut [F], idx: U256, val: U256) { + u256_to_array(&mut lv[INPUT_REGISTER_0], idx); + u256_to_array(&mut lv[INPUT_REGISTER_1], val); + set_idx_decomp(&mut lv[BYTE_IDX_DECOMP], &idx); + + let idx0_hi = lv[BYTE_IDX_DECOMP_HI]; + let hi_limb_sum = lv[INPUT_REGISTER_0][1..] + .iter() + .fold(idx0_hi, |acc, &x| acc + x); + let hi_limb_sum_inv = hi_limb_sum + .try_inverse() + .unwrap_or(F::ONE) + .to_canonical_u64(); + // It's a bit silly that we have to split this value, which + // doesn't need to be range-checked, into 16-bit limbs so that it + // can be range-checked; but the rigidity of the range-checking + // mechanism means we can't optionally switch it off for some + // instructions. + lv[BYTE_IDX_HI_LIMB_SUM_INV_0] = F::from_canonical_u16(hi_limb_sum_inv as u16); + lv[BYTE_IDX_HI_LIMB_SUM_INV_1] = F::from_canonical_u16((hi_limb_sum_inv >> 16) as u16); + lv[BYTE_IDX_HI_LIMB_SUM_INV_2] = F::from_canonical_u16((hi_limb_sum_inv >> 32) as u16); + lv[BYTE_IDX_HI_LIMB_SUM_INV_3] = F::from_canonical_u16((hi_limb_sum_inv >> 48) as u16); + lv[BYTE_IDX_IS_LARGE] = F::from_bool(!hi_limb_sum.is_zero()); + + // Set the tree values according to the low 5 bits of idx, even + // when idx >= 32. + + // Use the bits of idx0 to build a multiplexor that selects + // the correct byte of val. Each level of the tree uses one + // bit to halve the set of possible bytes from the previous + // level. The tree stores limbs rather than bytes though, so + // the last value must be handled specially. + + // Morally, offset at i is 2^i * bit[i], but because of the + // reversed indexing and handling of the last element + // separately, the offset is 2^i * ( ! bit[i + 1]). (The !bit + // corresponds to calculating 31 - bits which is just bitwise NOT.) + + // `lvl_len` is the number of elements of the current level of the + // "tree". Can think of `val_limbs` as level 0, with length = + // N_LIMBS = 16. + const_assert!(N_LIMBS == 16); // Enforce assumption + + // Build the tree of limbs from the low 5 bits of idx: + let mut i = 3; // tree level, from 3 downto 0. + let mut src = INPUT_REGISTER_1.start; // val_limbs start + let mut dest = AUX_INPUT_REGISTER_1.start; // tree start + loop { + let lvl_len = 1 << i; + // pick which half of src becomes the new tree level + let offset = (!idx.bit(i + 1) as usize) * lvl_len; + src += offset; + // copy new tree level to dest + lv.copy_within(src..src + lvl_len, dest); + if i == 0 { + break; + } + // next src is this new tree level + src = dest; + // next dest is after this new tree level + dest += lvl_len; + i -= 1; + } + + // Handle the last bit; i.e. pick a byte of the final limb. + let t = lv[dest].to_canonical_u64(); + let lo = t as u8 as u64; + let hi = t >> 8; + + // Store 256 * lo rather than lo: + lv[BYTE_LAST_LIMB_LO] = F::from_canonical_u64(lo << 8); + lv[BYTE_LAST_LIMB_HI] = F::from_canonical_u64(hi); + + let tree = &mut lv[AUX_INPUT_REGISTER_1]; + let output = if idx.bit(0) { + tree[15] = F::from_canonical_u64(lo); + lo.into() + } else { + tree[15] = F::from_canonical_u64(hi); + hi.into() + }; + + u256_to_array( + &mut lv[OUTPUT_REGISTER], + if idx < 32.into() { + output + } else { + U256::zero() + }, + ); +} + +pub fn eval_packed( + lv: &[P; NUM_ARITH_COLUMNS], + yield_constr: &mut ConstraintConsumer

, +) { + let is_byte = lv[IS_BYTE]; + + let idx = &lv[INPUT_REGISTER_0]; + let val = &lv[INPUT_REGISTER_1]; + let out = &lv[OUTPUT_REGISTER]; + let idx_decomp = &lv[AUX_INPUT_REGISTER_0]; + let tree = &lv[AUX_INPUT_REGISTER_1]; + + // low 5 bits of the first limb of idx: + let mut idx0_lo5 = P::ZEROS; + for i in 0..5 { + let bit = idx_decomp[i]; + yield_constr.constraint(is_byte * (bit * bit - bit)); + idx0_lo5 += bit * P::Scalar::from_canonical_u64(1 << i); + } + // Verify that idx0_hi is the high (11) bits of the first limb of + // idx (in particular idx0_hi is at most 11 bits, since idx[0] is + // at most 16 bits). + let idx0_hi = idx_decomp[5] * P::Scalar::from_canonical_u64(32u64); + yield_constr.constraint(is_byte * (idx[0] - (idx0_lo5 + idx0_hi))); + + // Verify the layers of the tree + // NB: Each of the bit values is negated in place to account for + // the reversed indexing. + let bit = idx_decomp[4]; + for i in 0..8 { + let limb = bit * val[i] + (P::ONES - bit) * val[i + 8]; + yield_constr.constraint(is_byte * (tree[i] - limb)); + } + + let bit = idx_decomp[3]; + for i in 0..4 { + let limb = bit * tree[i] + (P::ONES - bit) * tree[i + 4]; + yield_constr.constraint(is_byte * (tree[i + 8] - limb)); + } + + let bit = idx_decomp[2]; + for i in 0..2 { + let limb = bit * tree[i + 8] + (P::ONES - bit) * tree[i + 10]; + yield_constr.constraint(is_byte * (tree[i + 12] - limb)); + } + + let bit = idx_decomp[1]; + let limb = bit * tree[12] + (P::ONES - bit) * tree[13]; + yield_constr.constraint(is_byte * (tree[14] - limb)); + + // Check byte decomposition of last limb: + + let base8 = P::Scalar::from_canonical_u64(1 << 8); + let lo_byte = lv[BYTE_LAST_LIMB_LO]; + let hi_byte = lv[BYTE_LAST_LIMB_HI]; + yield_constr.constraint(is_byte * (lo_byte + base8 * (base8 * hi_byte - limb))); + + let bit = idx_decomp[0]; + let t = bit * lo_byte + (P::ONES - bit) * base8 * hi_byte; + yield_constr.constraint(is_byte * (base8 * tree[15] - t)); + let expected_out_byte = tree[15]; + + // Sum all higher limbs; sum will be non-zero iff idx >= 32. + let hi_limb_sum = idx0_hi + idx[1..].iter().copied().sum::

(); + let idx_is_large = lv[BYTE_IDX_IS_LARGE]; + + // idx_is_large is 0 or 1 + yield_constr.constraint(is_byte * (idx_is_large * idx_is_large - idx_is_large)); + + // If hi_limb_sum is nonzero, then idx_is_large must be one. + yield_constr.constraint(is_byte * hi_limb_sum * (idx_is_large - P::ONES)); + + let hi_limb_sum_inv = lv[BYTE_IDX_HI_LIMB_SUM_INV_0] + + lv[BYTE_IDX_HI_LIMB_SUM_INV_1] * P::Scalar::from_canonical_u64(1 << 16) + + lv[BYTE_IDX_HI_LIMB_SUM_INV_2] * P::Scalar::from_canonical_u64(1 << 32) + + lv[BYTE_IDX_HI_LIMB_SUM_INV_3] * P::Scalar::from_canonical_u64(1 << 48); + + // If idx_is_large is 1, then hi_limb_sum_inv must be the inverse + // of hi_limb_sum, hence hi_limb_sum is non-zero, hence idx is + // indeed "large". + // + // Otherwise, if idx_is_large is 0, then hi_limb_sum * hi_limb_sum_inv + // is zero, which is only possible if hi_limb_sum is zero, since + // hi_limb_sum_inv is non-zero. + yield_constr.constraint(is_byte * (hi_limb_sum * hi_limb_sum_inv - idx_is_large)); + + let out_byte = out[0]; + let check = out_byte - (P::ONES - idx_is_large) * expected_out_byte; + yield_constr.constraint(is_byte * check); + + // Check that the rest of the output limbs are zero + for i in 1..N_LIMBS { + yield_constr.constraint(is_byte * out[i]); + } +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut CircuitBuilder, + lv: &[ExtensionTarget; NUM_ARITH_COLUMNS], + yield_constr: &mut RecursiveConstraintConsumer, +) { + let is_byte = lv[IS_BYTE]; + + let idx = &lv[INPUT_REGISTER_0]; + let val = &lv[INPUT_REGISTER_1]; + let out = &lv[OUTPUT_REGISTER]; + let idx_decomp = &lv[AUX_INPUT_REGISTER_0]; + let tree = &lv[AUX_INPUT_REGISTER_1]; + + let mut idx0_lo5 = builder.zero_extension(); + for i in 0..5 { + let bit = idx_decomp[i]; + let t = builder.mul_sub_extension(bit, bit, bit); + let t = builder.mul_extension(t, is_byte); + yield_constr.constraint(builder, t); + let scale = F::Extension::from(F::from_canonical_u64(1 << i)); + let scale = builder.constant_extension(scale); + idx0_lo5 = builder.mul_add_extension(bit, scale, idx0_lo5); + } + let t = F::Extension::from(F::from_canonical_u64(32)); + let t = builder.constant_extension(t); + let idx0_hi = builder.mul_extension(idx_decomp[5], t); + let t = builder.add_extension(idx0_lo5, idx0_hi); + let t = builder.sub_extension(idx[0], t); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + + let one = builder.one_extension(); + let bit = idx_decomp[4]; + for i in 0..8 { + let t = builder.mul_extension(bit, val[i]); + let u = builder.sub_extension(one, bit); + let v = builder.mul_add_extension(u, val[i + 8], t); + let t = builder.sub_extension(tree[i], v); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + } + + let bit = idx_decomp[3]; + for i in 0..4 { + let t = builder.mul_extension(bit, tree[i]); + let u = builder.sub_extension(one, bit); + let v = builder.mul_add_extension(u, tree[i + 4], t); + let t = builder.sub_extension(tree[i + 8], v); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + } + + let bit = idx_decomp[2]; + for i in 0..2 { + let t = builder.mul_extension(bit, tree[i + 8]); + let u = builder.sub_extension(one, bit); + let v = builder.mul_add_extension(u, tree[i + 10], t); + let t = builder.sub_extension(tree[i + 12], v); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + } + + let bit = idx_decomp[1]; + let t = builder.mul_extension(bit, tree[12]); + let u = builder.sub_extension(one, bit); + let limb = builder.mul_add_extension(u, tree[13], t); + let t = builder.sub_extension(tree[14], limb); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + + let base8 = F::Extension::from(F::from_canonical_u64(1 << 8)); + let base8 = builder.constant_extension(base8); + let lo_byte = lv[BYTE_LAST_LIMB_LO]; + let hi_byte = lv[BYTE_LAST_LIMB_HI]; + let t = builder.mul_sub_extension(base8, hi_byte, limb); + let t = builder.mul_add_extension(base8, t, lo_byte); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + + let bit = idx_decomp[0]; + let nbit = builder.sub_extension(one, bit); + let t = builder.mul_many_extension([nbit, base8, hi_byte]); + let t = builder.mul_add_extension(bit, lo_byte, t); + let t = builder.mul_sub_extension(base8, tree[15], t); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + let expected_out_byte = tree[15]; + + let mut hi_limb_sum = idx0_hi; + for i in 1..N_LIMBS { + hi_limb_sum = builder.add_extension(hi_limb_sum, idx[i]); + } + let idx_is_large = lv[BYTE_IDX_IS_LARGE]; + let t = builder.mul_sub_extension(idx_is_large, idx_is_large, idx_is_large); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + + let t = builder.sub_extension(idx_is_large, one); + let t = builder.mul_many_extension([is_byte, hi_limb_sum, t]); + yield_constr.constraint(builder, t); + + let base16 = F::from_canonical_u64(1 << 16); + let hi_limb_sum_inv = builder.mul_const_add_extension( + base16, + lv[BYTE_IDX_HI_LIMB_SUM_INV_3], + lv[BYTE_IDX_HI_LIMB_SUM_INV_2], + ); + let hi_limb_sum_inv = + builder.mul_const_add_extension(base16, hi_limb_sum_inv, lv[BYTE_IDX_HI_LIMB_SUM_INV_1]); + let hi_limb_sum_inv = + builder.mul_const_add_extension(base16, hi_limb_sum_inv, lv[BYTE_IDX_HI_LIMB_SUM_INV_0]); + let t = builder.mul_sub_extension(hi_limb_sum, hi_limb_sum_inv, idx_is_large); + let t = builder.mul_extension(is_byte, t); + yield_constr.constraint(builder, t); + + let out_byte = out[0]; + let t = builder.sub_extension(one, idx_is_large); + let t = builder.mul_extension(t, expected_out_byte); + let check = builder.sub_extension(out_byte, t); + let t = builder.mul_extension(is_byte, check); + yield_constr.constraint(builder, t); + + for i in 1..N_LIMBS { + let t = builder.mul_extension(is_byte, out[i]); + yield_constr.constraint(builder, t); + } +} + +#[cfg(test)] +mod tests { + use plonky2::field::goldilocks_field::GoldilocksField; + use rand::{Rng, SeedableRng}; + use rand_chacha::ChaCha8Rng; + + use super::*; + use crate::arithmetic::columns::NUM_ARITH_COLUMNS; + + type F = GoldilocksField; + + fn verify_output(lv: &[F], expected_byte: u64) { + let out_byte = lv[OUTPUT_REGISTER][0].to_canonical_u64(); + assert!(out_byte == expected_byte); + for j in 1..N_LIMBS { + assert!(lv[OUTPUT_REGISTER][j] == F::ZERO); + } + } + + #[test] + fn generate_eval_consistency() { + let mut rng = ChaCha8Rng::seed_from_u64(0x6feb51b7ec230f25); + const N_ITERS: usize = 1000; + + for _ in 0..N_ITERS { + // set entire row to random 16-bit values + let mut lv = + [F::default(); NUM_ARITH_COLUMNS].map(|_| F::from_canonical_u16(rng.gen::())); + + lv[IS_BYTE] = F::ONE; + + let val = U256::from(rng.gen::<[u8; 32]>()); + for i in 0..32 { + let idx = i.into(); + generate(&mut lv, idx, val); + + // Check correctness + let out_byte = val.byte(31 - i) as u64; + verify_output(&lv, out_byte); + + let mut constrant_consumer = ConstraintConsumer::new( + vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)], + F::ONE, + F::ONE, + F::ONE, + ); + eval_packed(&lv, &mut constrant_consumer); + for &acc in &constrant_consumer.constraint_accs { + assert_eq!(acc, F::ZERO); + } + } + // Check that output is zero when the index is big. + let big_indices = [32.into(), 33.into(), val, U256::max_value()]; + for idx in big_indices { + generate(&mut lv, idx, val); + verify_output(&lv, 0); + } + } + } +} diff --git a/evm/src/arithmetic/columns.rs b/evm/src/arithmetic/columns.rs index 98481f64..afdd5832 100644 --- a/evm/src/arithmetic/columns.rs +++ b/evm/src/arithmetic/columns.rs @@ -35,8 +35,9 @@ pub(crate) const IS_SUBFP254: usize = IS_MULFP254 + 1; pub(crate) const IS_SUBMOD: usize = IS_SUBFP254 + 1; pub(crate) const IS_LT: usize = IS_SUBMOD + 1; pub(crate) const IS_GT: usize = IS_LT + 1; +pub(crate) const IS_BYTE: usize = IS_GT + 1; -pub(crate) const START_SHARED_COLS: usize = IS_GT + 1; +pub(crate) const START_SHARED_COLS: usize = IS_BYTE + 1; /// Within the Arithmetic Unit, there are shared columns which can be /// used by any arithmetic circuit, depending on which one is active diff --git a/evm/src/arithmetic/mod.rs b/evm/src/arithmetic/mod.rs index 74f08947..d9d63a0b 100644 --- a/evm/src/arithmetic/mod.rs +++ b/evm/src/arithmetic/mod.rs @@ -5,6 +5,7 @@ use crate::extension_tower::BN_BASE; use crate::util::{addmod, mulmod, submod}; mod addcy; +mod byte; mod divmod; mod modular; mod mul; @@ -25,6 +26,7 @@ pub(crate) enum BinaryOperator { AddFp254, MulFp254, SubFp254, + Byte, } impl BinaryOperator { @@ -52,6 +54,13 @@ impl BinaryOperator { BinaryOperator::AddFp254 => addmod(input0, input1, BN_BASE), BinaryOperator::MulFp254 => mulmod(input0, input1, BN_BASE), BinaryOperator::SubFp254 => submod(input0, input1, BN_BASE), + BinaryOperator::Byte => { + if input0 >= 32.into() { + U256::zero() + } else { + input1.byte(31 - input0.as_usize()).into() + } + } } } @@ -67,6 +76,7 @@ impl BinaryOperator { BinaryOperator::AddFp254 => columns::IS_ADDFP254, BinaryOperator::MulFp254 => columns::IS_MULFP254, BinaryOperator::SubFp254 => columns::IS_SUBFP254, + BinaryOperator::Byte => columns::IS_BYTE, } } } @@ -98,7 +108,6 @@ impl TernaryOperator { } #[derive(Debug)] -#[allow(unused)] // TODO: Should be used soon. pub(crate) enum Operation { BinaryOperation { operator: BinaryOperator, @@ -217,5 +226,9 @@ fn binary_op_to_rows( BinaryOperator::AddFp254 | BinaryOperator::MulFp254 | BinaryOperator::SubFp254 => { ternary_op_to_rows::(op.row_filter(), input0, input1, BN_BASE, result) } + BinaryOperator::Byte => { + byte::generate(&mut row, input0, input1); + (row, None) + } } } diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 069a1609..0686d4d7 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -80,7 +80,7 @@ pub fn ctl_filter_logic() -> Column { } pub fn ctl_arithmetic_rows() -> TableWithColumns { - const OPS: [usize; 13] = [ + const OPS: [usize; 14] = [ COL_MAP.op.add, COL_MAP.op.sub, COL_MAP.op.mul, @@ -94,6 +94,7 @@ pub fn ctl_arithmetic_rows() -> TableWithColumns { COL_MAP.op.submod, COL_MAP.op.div, COL_MAP.op.mod_, + COL_MAP.op.byte, ]; // Create the CPU Table whose columns are those with the three // inputs and one output of the ternary operations listed in `ops` diff --git a/evm/src/witness/gas.rs b/evm/src/witness/gas.rs index 98259ff8..488ab6c0 100644 --- a/evm/src/witness/gas.rs +++ b/evm/src/witness/gas.rs @@ -14,7 +14,6 @@ pub(crate) fn gas_to_charge(op: Operation) -> u64 { match op { Iszero => G_VERYLOW, Not => G_VERYLOW, - Byte => G_VERYLOW, Syscall(_) => KERNEL_ONLY_INSTR, Eq => G_VERYLOW, BinaryLogic(_) => G_VERYLOW, @@ -25,6 +24,7 @@ pub(crate) fn gas_to_charge(op: Operation) -> u64 { BinaryArithmetic(Mod) => G_LOW, BinaryArithmetic(Lt) => G_VERYLOW, BinaryArithmetic(Gt) => G_VERYLOW, + BinaryArithmetic(Byte) => G_VERYLOW, Shl => G_VERYLOW, Shr => G_VERYLOW, BinaryArithmetic(AddFp254) => KERNEL_ONLY_INSTR, diff --git a/evm/src/witness/operation.rs b/evm/src/witness/operation.rs index 58c242a6..5241c71b 100644 --- a/evm/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -25,7 +25,6 @@ use crate::{arithmetic, logic}; pub(crate) enum Operation { Iszero, Not, - Byte, Shl, Shr, Syscall(u8), @@ -413,27 +412,6 @@ pub(crate) fn generate_not( Ok(()) } -pub(crate) fn generate_byte( - state: &mut GenerationState, - mut row: CpuColumnsView, -) -> Result<(), ProgramError> { - let [(i, log_in0), (x, log_in1)] = stack_pop_with_log_and_fill::<2, _>(state, &mut row)?; - - let byte = if i < 32.into() { - // byte(i) is the i'th little-endian byte; we want the i'th big-endian byte. - x.byte(31 - i.as_usize()) - } else { - 0 - }; - let log_out = stack_push_log_and_fill(state, &mut row, byte.into())?; - - state.traces.push_memory(log_in0); - state.traces.push_memory(log_in1); - state.traces.push_memory(log_out); - state.traces.push_cpu(row); - Ok(()) -} - pub(crate) fn generate_iszero( state: &mut GenerationState, mut row: CpuColumnsView, diff --git a/evm/src/witness/transition.rs b/evm/src/witness/transition.rs index 0184e183..5fe9071a 100644 --- a/evm/src/witness/transition.rs +++ b/evm/src/witness/transition.rs @@ -67,7 +67,9 @@ fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::BinaryLogic(logic::Op::Or)), (0x18, _) => Ok(Operation::BinaryLogic(logic::Op::Xor)), (0x19, _) => Ok(Operation::Not), - (0x1a, _) => Ok(Operation::Byte), + (0x1a, _) => Ok(Operation::BinaryArithmetic( + arithmetic::BinaryOperator::Byte, + )), (0x1b, _) => Ok(Operation::Shl), (0x1c, _) => Ok(Operation::Shr), (0x1d, _) => Ok(Operation::Syscall(opcode)), @@ -155,7 +157,6 @@ fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) { Operation::Swap(_) => &mut flags.swap, Operation::Iszero => &mut flags.iszero, Operation::Not => &mut flags.not, - Operation::Byte => &mut flags.byte, Operation::Syscall(_) => &mut flags.syscall, Operation::Eq => &mut flags.eq, Operation::BinaryLogic(logic::Op::And) => &mut flags.and, @@ -168,6 +169,7 @@ fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) { Operation::BinaryArithmetic(arithmetic::BinaryOperator::Mod) => &mut flags.mod_, Operation::BinaryArithmetic(arithmetic::BinaryOperator::Lt) => &mut flags.lt, Operation::BinaryArithmetic(arithmetic::BinaryOperator::Gt) => &mut flags.gt, + Operation::BinaryArithmetic(arithmetic::BinaryOperator::Byte) => &mut flags.byte, Operation::Shl => &mut flags.shl, Operation::Shr => &mut flags.shr, Operation::BinaryArithmetic(arithmetic::BinaryOperator::AddFp254) => &mut flags.addfp254, @@ -202,7 +204,6 @@ fn perform_op( Operation::Swap(n) => generate_swap(n, state, row)?, Operation::Iszero => generate_iszero(state, row)?, Operation::Not => generate_not(state, row)?, - Operation::Byte => generate_byte(state, row)?, Operation::Shl => generate_shl(state, row)?, Operation::Shr => generate_shr(state, row)?, Operation::Syscall(opcode) => generate_syscall(opcode, state, row)?, From 49bbe4e084aa8e6fec1f0a55cc33404996861aa4 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 2 Jun 2023 18:51:21 +0200 Subject: [PATCH 15/20] Fix arithmetic stark padding (#1069) --- evm/src/arithmetic/arithmetic_stark.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/evm/src/arithmetic/arithmetic_stark.rs b/evm/src/arithmetic/arithmetic_stark.rs index 4fcbb534..4695798a 100644 --- a/evm/src/arithmetic/arithmetic_stark.rs +++ b/evm/src/arithmetic/arithmetic_stark.rs @@ -138,8 +138,10 @@ impl ArithmeticStark { } // Pad the trace with zero rows if it doesn't have enough rows - // to accommodate the range check columns. - for _ in trace_rows.len()..RANGE_MAX { + // to accommodate the range check columns. Also make sure the + // trace length is a power of two. + let padded_len = trace_rows.len().next_power_of_two(); + for _ in trace_rows.len()..std::cmp::max(padded_len, RANGE_MAX) { trace_rows.push(vec![F::ZERO; columns::NUM_ARITH_COLUMNS]); } From 730797969466300e5245faefbf58c53de2cb9a00 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 2 Jun 2023 19:57:14 +0200 Subject: [PATCH 16/20] Fix pairing invalid input (#1067) --- evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm index 4479b965..539ec81c 100644 --- a/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm +++ b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm @@ -78,7 +78,7 @@ bn254_input_check: bn_pairing_invalid_input: // stack: inp_j, j, k, inp, out, retdest - %stack (inp_j, j, k, inp, out, retdest) -> (retdest, inp_j) + %stack (inp_j, j, k, inp, out, retdest) -> (retdest, @U256_MAX) JUMP bn254_pairing_start: From f605d912a5b19dc4d09ceadd6cd0f4e105dac0ea Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 2 Jun 2023 19:57:23 +0200 Subject: [PATCH 17/20] Propagate static flag (#1066) --- evm/src/cpu/kernel/asm/core/call.asm | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/evm/src/cpu/kernel/asm/core/call.asm b/evm/src/cpu/kernel/asm/core/call.asm index 40290797..5f31ba49 100644 --- a/evm/src/cpu/kernel/asm/core/call.asm +++ b/evm/src/cpu/kernel/asm/core/call.asm @@ -45,6 +45,7 @@ global sys_call: // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size // Each line in the block below does not change the stack. + %set_static DUP4 %set_new_ctx_addr %address %set_new_ctx_caller DUP5 %set_new_ctx_value @@ -93,6 +94,7 @@ global sys_callcode: // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size // Each line in the block below does not change the stack. + %set_static %address %set_new_ctx_addr %address %set_new_ctx_caller DUP5 %set_new_ctx_value @@ -195,6 +197,7 @@ global sys_delegatecall: // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size // Each line in the block below does not change the stack. + %set_static %address %set_new_ctx_addr %caller %set_new_ctx_caller %callvalue %set_new_ctx_value @@ -243,6 +246,15 @@ call_insufficient_balance: // stack: new_ctx %endmacro +// Set @CTX_METADATA_STATIC of the next context to the current value. +%macro set_static + // stack: new_ctx + %mload_context_metadata(@CTX_METADATA_STATIC) + %stack (is_static, new_ctx) -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_STATIC, is_static, new_ctx) + MSTORE_GENERAL + // stack: new_ctx +%endmacro + %macro set_new_ctx_addr // stack: called_addr, new_ctx %stack (called_addr, new_ctx) From fbf6591b2dd66ec7f240b1336ef50bbc7c856e27 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 2 Jun 2023 19:57:30 +0200 Subject: [PATCH 18/20] Warm precompiles earlier (#1065) --- evm/src/cpu/kernel/asm/core/process_txn.asm | 23 +++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index 61c66134..d41b6e25 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -82,6 +82,18 @@ global increment_sender_nonce: %mload_txn_field(@TXN_FIELD_ORIGIN) %increment_nonce +global warm_precompiles: + // Add precompiles to accessed addresses. + PUSH @ECREC %insert_accessed_addresses_no_return + PUSH @SHA256 %insert_accessed_addresses_no_return + PUSH @RIP160 %insert_accessed_addresses_no_return + PUSH @ID %insert_accessed_addresses_no_return + PUSH @EXPMOD %insert_accessed_addresses_no_return + PUSH @BN_ADD %insert_accessed_addresses_no_return + PUSH @BN_MUL %insert_accessed_addresses_no_return + PUSH @SNARKV %insert_accessed_addresses_no_return + PUSH @BLAKE2_F %insert_accessed_addresses_no_return + // EIP-3651 global warm_coinbase: %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BENEFICIARY) @@ -224,17 +236,6 @@ global process_message_txn: // stack: code_empty, retdest %jumpi(process_message_txn_return) - // Add precompiles to accessed addresses. - PUSH @ECREC %insert_accessed_addresses_no_return - PUSH @SHA256 %insert_accessed_addresses_no_return - PUSH @RIP160 %insert_accessed_addresses_no_return - PUSH @ID %insert_accessed_addresses_no_return - PUSH @EXPMOD %insert_accessed_addresses_no_return - PUSH @BN_ADD %insert_accessed_addresses_no_return - PUSH @BN_MUL %insert_accessed_addresses_no_return - PUSH @SNARKV %insert_accessed_addresses_no_return - PUSH @BLAKE2_F %insert_accessed_addresses_no_return - // Otherwise, load to's code and execute it in a new context. // stack: retdest %create_context From 720faa673d52f4d89eb5f97de92e552a1bfc546a Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 2 Jun 2023 19:57:46 +0200 Subject: [PATCH 19/20] Fix create OOG because of code deposit cost (#1062) * Fix create OOG because of code deposit cost * Set returndata size to 0 in case of oog --- evm/src/cpu/kernel/asm/core/create.asm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/asm/core/create.asm b/evm/src/cpu/kernel/asm/core/create.asm index 9ecc2600..25dd83da 100644 --- a/evm/src/cpu/kernel/asm/core/create.asm +++ b/evm/src/cpu/kernel/asm/core/create.asm @@ -150,7 +150,7 @@ after_constructor: // stack: code_size, leftover_gas, success, address, kexit_info %mul_const(@GAS_CODEDEPOSIT) // stack: code_size_cost, leftover_gas, success, address, kexit_info - DUP2 DUP2 GT %jumpi(fault_exception) + DUP2 DUP2 GT %jumpi(create_oog) SWAP1 SUB // stack: leftover_gas, success, address, kexit_info %pop_checkpoint @@ -213,6 +213,12 @@ create_code_too_large: %stack (code_size, leftover_gas, success, address, kexit_info) -> (kexit_info, 0) EXIT_KERNEL +create_oog: + %revert_checkpoint + %mstore_context_metadata(@CTX_METADATA_RETURNDATA_SIZE, 0) + %stack (code_size_cost, leftover_gas, success, address, kexit_info) -> (kexit_info, 0) + EXIT_KERNEL + %macro set_codehash %stack (addr, codehash) -> (addr, codehash, %%after) %jump(set_codehash) From 973624f12d2d12d74422b3ea051358b9eaacb050 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 2 Jun 2023 19:58:04 +0200 Subject: [PATCH 20/20] Minor fixes to RETURN and RETURNDATACOPY (#1060) --- evm/src/cpu/kernel/asm/core/terminate.asm | 3 ++- evm/src/cpu/kernel/asm/memory/syscalls.asm | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/terminate.asm b/evm/src/cpu/kernel/asm/core/terminate.asm index 7e49d45e..f9c6f21f 100644 --- a/evm/src/cpu/kernel/asm/core/terminate.asm +++ b/evm/src/cpu/kernel/asm/core/terminate.asm @@ -15,9 +15,10 @@ global sys_return: // stack: kexit_info, offset, size %stack (kexit_info, offset, size) -> (offset, size, kexit_info, offset, size) %add_or_fault - DUP1 %ensure_reasonable_offset // stack: offset+size, kexit_info, offset, size DUP4 ISZERO %jumpi(return_zero_size) + // stack: offset+size, kexit_info, offset, size + DUP1 %ensure_reasonable_offset %update_mem_bytes %jump(return_after_gas) return_zero_size: diff --git a/evm/src/cpu/kernel/asm/memory/syscalls.asm b/evm/src/cpu/kernel/asm/memory/syscalls.asm index cae5225d..694b73a1 100644 --- a/evm/src/cpu/kernel/asm/memory/syscalls.asm +++ b/evm/src/cpu/kernel/asm/memory/syscalls.asm @@ -176,9 +176,6 @@ global sys_codecopy: global sys_returndatacopy: // stack: kexit_info, dest_offset, offset, size PUSH @GAS_VERYLOW - DUP5 - // stack: size, Gverylow, kexit_info, dest_offset, offset, size - ISZERO %jumpi(wcopy_empty) // stack: Gverylow, kexit_info, dest_offset, offset, size DUP5 %num_bytes_to_num_words %mul_const(@GAS_COPY) ADD %charge_gas @@ -191,6 +188,11 @@ global sys_returndatacopy: DUP4 DUP4 %add_or_fault // Overflow check %mload_context_metadata(@CTX_METADATA_RETURNDATA_SIZE) LT %jumpi(fault_exception) // Data len check + // stack: kexit_info, dest_offset, offset, size + DUP4 + // stack: size, kexit_info, dest_offset, offset, size + ISZERO %jumpi(returndatacopy_empty) + %mload_context_metadata(@CTX_METADATA_RETURNDATA_SIZE) // stack: total_size, kexit_info, dest_offset, offset, size DUP4 @@ -201,3 +203,7 @@ global sys_returndatacopy: %stack (context, kexit_info, dest_offset, offset, size) -> (context, @SEGMENT_MAIN_MEMORY, dest_offset, context, @SEGMENT_RETURNDATA, offset, size, wcopy_after, kexit_info) %jump(memcpy) + +returndatacopy_empty: + %stack (kexit_info, dest_offset, offset, size) -> (kexit_info) + EXIT_KERNEL