Skip to content

Commit

Permalink
Implement EIP-4788 for Cancun (#40)
Browse files Browse the repository at this point in the history
* Implement EIP-4788

* Cleanup test suite

* Fix tests

* Apply comments

* Update PROVER_INPUT opcode value (#42)
  • Loading branch information
Nashtare authored Feb 18, 2024
1 parent 2552e45 commit d43c8e2
Show file tree
Hide file tree
Showing 37 changed files with 865 additions and 388 deletions.
4 changes: 2 additions & 2 deletions docs/arithmetization/cpulogic.tex
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ \subsection{Privileged instructions}
\item[0x21.] \texttt{KECCAK\_GENERAL}. Pops 2 elements (a Memory address, followed by a length $\ell$) and pushes the hash of the memory portion starting at the
constructed address and of length $\ell$. It is similar to KECCAK256 (0x20) instruction, but can be applied to any memory section (i.e. even privileged ones).

\item[0x49.] \texttt{PROVER\_INPUT}. Pushes a single prover input onto the stack.

\item[0xC0-0xDF.] \texttt{MSTORE\_32BYTES}. Pops 2 elements from the stack (a Memory address, and then a value), and pushes
a new address' onto the stack. The value is being decomposed into bytes and written to memory, starting from the fetched address. The new address being pushed is computed as the
initial address + the length of the byte sequence being written to memory. Note that similarly to PUSH (0x60-0x7F) instructions, there are 32 MSTORE\_32BYTES instructions, each
corresponding to a target byte length (length 0 is ignored, for the same reasons as MLOAD\_32BYTES, see below). Writing to memory an integer fitting in $n$ bytes with a length $\ell < n$ will
result in the integer being truncated. On the other hand, specifying a length $\ell$ greater than the byte size of the value being written will result in padding with zeroes. This
process is heavily used when resetting memory sections (by calling MSTORE\_32BYTES\_32 with the value 0).

\item[0xEE.] \texttt{PROVER\_INPUT}. Pushes a single prover input onto the stack.

\item[0xF6.] \texttt{GET\_CONTEXT}. Pushes the current context onto the stack. The kernel always has context 0.

\item[0xF7.] \texttt{SET\_CONTEXT}. Pops the top element of the stack and updates the current context to this value. It is usually used when calling another contract or precompile,
Expand Down
8 changes: 2 additions & 6 deletions evm_arithmetization/src/cpu/control_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub(crate) fn eval_packed_generic<P: PackedField>(
.constraint_transition(is_native_instruction * (lv.is_kernel_mode - nv.is_kernel_mode));

// Apply the same checks as before, for PROVER_INPUT.
let is_prover_input: P = lv.op.push_prover_input * (lv.opcode_bits[5] - P::ONES);
let is_prover_input: P = lv.op.push_prover_input * lv.opcode_bits[7];
yield_constr.constraint_transition(
is_prover_input * (lv.program_counter - nv.program_counter + P::ONES),
);
Expand Down Expand Up @@ -129,11 +129,7 @@ pub(crate) fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
yield_constr.constraint_transition(builder, kernel_constr);

// Same constraints as before, for PROVER_INPUT.
let is_prover_input = builder.mul_sub_extension(
lv.op.push_prover_input,
lv.opcode_bits[5],
lv.op.push_prover_input,
);
let is_prover_input = builder.mul_extension(lv.op.push_prover_input, lv.opcode_bits[7]);
let pc_constr = builder.mul_add_extension(is_prover_input, pc_diff, is_prover_input);
yield_constr.constraint_transition(builder, pc_constr);
let kernel_constr = builder.mul_extension(is_prover_input, kernel_diff);
Expand Down
13 changes: 7 additions & 6 deletions evm_arithmetization/src/cpu/cpu_stark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,7 @@ pub(crate) fn ctl_arithmetic_base_rows<F: Field>() -> TableWithColumns<F> {
// (also `ops` is used as the operation filter). The list of
// operations includes binary operations which will simply ignore
// the third input.
let col_bit = Column::linear_combination_with_constant(
vec![(COL_MAP.opcode_bits[5], F::NEG_ONE)],
F::ONE,
);
let col_bit = Column::single(COL_MAP.opcode_bits[7]);
TableWithColumns::new(
*Table::Cpu,
columns,
Expand Down Expand Up @@ -263,9 +260,13 @@ pub(crate) fn ctl_data_byte_packing_push<F: Field>() -> Vec<Column<F>> {

/// CTL filter for the `PUSH` operation.
pub(crate) fn ctl_filter_byte_packing_push<F: Field>() -> Filter<F> {
let bit_col = Column::single(COL_MAP.opcode_bits[5]);
let col_bit = Column::linear_combination_with_constant(
vec![(COL_MAP.opcode_bits[7], F::NEG_ONE)],
F::ONE,
);

Filter::new(
vec![(Column::single(COL_MAP.op.push_prover_input), bit_col)],
vec![(Column::single(COL_MAP.op.push_prover_input), col_bit)],
vec![],
)
}
Expand Down
15 changes: 5 additions & 10 deletions evm_arithmetization/src/cpu/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,11 @@ pub(crate) fn eval_packed_generic<P: PackedField>(

// Manually check PUSH and PROVER_INPUT.
// PROVER_INPUT is a kernel-only instruction, but not PUSH.
let push_prover_input_constr = (opcode - P::Scalar::from_canonical_usize(0x49_usize))
let push_prover_input_constr = (opcode - P::Scalar::from_canonical_usize(0xee_usize))
* (opcode_high_three - P::Scalar::from_canonical_usize(0x60_usize))
* lv.op.push_prover_input;
yield_constr.constraint(push_prover_input_constr);
let prover_input_constr =
lv.op.push_prover_input * (lv.opcode_bits[5] - P::ONES) * (P::ONES - kernel_mode);
let prover_input_constr = lv.op.push_prover_input * lv.opcode_bits[7] * (P::ONES - kernel_mode);
yield_constr.constraint(prover_input_constr);
}

Expand Down Expand Up @@ -389,20 +388,16 @@ pub(crate) fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
// Manually check PUSH and PROVER_INPUT.
// PROVER_INPUT is a kernel-only instruction, but not PUSH.
let prover_input_opcode =
builder.constant_extension(F::Extension::from_canonical_usize(0x49usize));
let push_opcodes = builder.constant_extension(F::Extension::from_canonical_usize(0x60usize));
builder.constant_extension(F::Extension::from_canonical_usize(0xee_usize));
let push_opcodes = builder.constant_extension(F::Extension::from_canonical_usize(0x60_usize));

let push_constr = builder.sub_extension(opcode_high_three, push_opcodes);
let prover_input_constr = builder.sub_extension(opcode, prover_input_opcode);

let push_prover_input_constr =
builder.mul_many_extension([lv.op.push_prover_input, prover_input_constr, push_constr]);
yield_constr.constraint(builder, push_prover_input_constr);
let prover_input_filter = builder.mul_sub_extension(
lv.op.push_prover_input,
lv.opcode_bits[5],
lv.op.push_prover_input,
);
let prover_input_filter = builder.mul_extension(lv.op.push_prover_input, lv.opcode_bits[7]);
let constr = builder.mul_extension(prover_input_filter, is_not_kernel_mode);
yield_constr.constraint(builder, constr);
}
16 changes: 8 additions & 8 deletions evm_arithmetization/src/cpu/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@ fn eval_packed_accumulate<P: PackedField>(
);

// For PROVER_INPUT and PUSH operations.
// PUSH operations are differentiated from PROVER_INPUT by their 6th bit set to
// PUSH operations are differentiated from PROVER_INPUT by their 8th bit set to
// 1.
let push_prover_input_gas_cost = lv.opcode_bits[5]
let push_prover_input_gas_cost = (P::ONES - lv.opcode_bits[7])
* P::Scalar::from_canonical_u32(G_VERYLOW.unwrap())
+ (P::ONES - lv.opcode_bits[5]) * P::Scalar::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap());
+ lv.opcode_bits[7] * P::Scalar::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap());
yield_constr
.constraint_transition(lv.op.push_prover_input * (gas_diff - push_prover_input_gas_cost));
}
Expand Down Expand Up @@ -282,13 +282,13 @@ fn eval_ext_circuit_accumulate<F: RichField + Extendable<D>, const D: usize>(
yield_constr.constraint_transition(builder, constr);

// For PROVER_INPUT and PUSH operations.
// PUSH operations are differentiated from PROVER_INPUT by their 6th bit set to
// PUSH operations are differentiated from PROVER_INPUT by their 8th bit set to
// 1.
let push_prover_input_gas_cost = builder.arithmetic_extension(
F::from_canonical_u32(G_VERYLOW.unwrap())
- F::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()),
F::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()),
lv.opcode_bits[5],
F::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap())
- F::from_canonical_u32(G_VERYLOW.unwrap()),
F::from_canonical_u32(G_VERYLOW.unwrap()),
lv.opcode_bits[7],
one,
one,
);
Expand Down
1 change: 1 addition & 0 deletions evm_arithmetization/src/cpu/kernel/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) fn combined_kernel() -> Kernel {
let files = vec![
"global jumped_to_0: PANIC",
"global jumped_to_1: PANIC",
include_str!("asm/beacon_roots.asm"),
include_str!("asm/bignum/add.asm"),
include_str!("asm/bignum/addmul.asm"),
include_str!("asm/bignum/cmp.asm"),
Expand Down
48 changes: 48 additions & 0 deletions evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// EIP-4788: Beacon block root in the EVM
/// <https://eips.ethereum.org/EIPS/eip-4788#pseudocode>

global set_beacon_root:
PUSH start_txn
%timestamp
// stack: timestamp, start_txns
PUSH @HISTORY_BUFFER_LENGTH
DUP2
// stack: timestamp, 8191, timestamp, start_txns
MOD
// stack: timestamp_idx, timestamp, start_txns
PUSH write_beacon_roots_to_storage
%parent_beacon_block_root
// stack: calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, start_txns
DUP3
%add_const(@HISTORY_BUFFER_LENGTH)
// stack: root_idx, calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, start_txns

write_beacon_roots_to_storage:
// stack: slot, value, retdest
// First we write the value to MPT data, and get a pointer to it.
%get_trie_data_size
// stack: value_ptr, slot, value, retdest
SWAP2
// stack: value, slot, value_ptr, retdest
%append_to_trie_data
// stack: slot, value_ptr, retdest

// Next, call mpt_insert on the current account's storage root.
%stack (slot, value_ptr) -> (slot, value_ptr, after_beacon_roots_storage_insert)
%slot_to_storage_key
// stack: storage_key, value_ptr, after_beacon_roots_storage_insert, retdest
PUSH 64 // storage_key has 64 nibbles
%get_storage_trie(@BEACON_ROOTS_ADDRESS)
// stack: storage_root_ptr, 64, storage_key, value_ptr, after_beacon_roots_storage_insert, retdest
%jump(mpt_insert)

after_beacon_roots_storage_insert:
// stack: new_storage_root_ptr, retdest
%get_account_data(@BEACON_ROOTS_ADDRESS)
// stack: account_ptr, new_storage_root_ptr, retdest

// Update the copied account with our new storage root pointer.
%add_const(2)
// stack: account_storage_root_ptr_ptr, new_storage_root_ptr, retdest
%mstore_trie_data
JUMP
7 changes: 5 additions & 2 deletions evm_arithmetization/src/cpu/kernel/asm/main.asm
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,13 @@ global hash_initial_tries:
// stack: trie_data_full_len
%mstore_global_metadata(@GLOBAL_METADATA_TRIE_DATA_SIZE)

// If txn_idx == 0, update the beacon_root.
%mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_BEFORE)
ISZERO
%jumpi(set_beacon_root)

global start_txn:
// stack: (empty)
// The special case of an empty trie (i.e. for the first transaction)
// is handled outside of the kernel.
%mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_BEFORE)
// stack: txn_nb
DUP1 %scalar_to_rlp
Expand Down
4 changes: 4 additions & 0 deletions evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,7 @@ global sys_prevrandao:
%mload_global_metadata(@GLOBAL_METADATA_BLOCK_RANDOM)
%stack (random, kexit_info) -> (kexit_info, random)
EXIT_KERNEL

%macro parent_beacon_block_root
%mload_global_metadata(@GLOBAL_METADATA_PARENT_BEACON_BLOCK_ROOT)
%endmacro
22 changes: 22 additions & 0 deletions evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,25 @@
%mload_trie_data
// stack: storage_root_ptr
%endmacro

// Return a pointer to the provided account's data in the state trie.
%macro get_account_data(addr)
PUSH $addr %mpt_read_state_trie
// stack: account_ptr
// account_ptr should be non-null as long as the prover provided the proper
// Merkle data. But a bad prover may not have, and we don't want return a
// null pointer for security reasons.
DUP1 ISZERO %jumpi(panic)
// stack: account_ptr
%endmacro

// Returns a pointer to the root of the storage trie associated with the provided account.
%macro get_storage_trie(addr)
// stack: (empty)
%get_account_data($addr)
// stack: account_ptr
%add_const(2)
// stack: storage_root_ptr_ptr
%mload_trie_data
// stack: storage_root_ptr
%endmacro
4 changes: 2 additions & 2 deletions evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ global mpt_insert:
global mpt_insert_hash_node:
PANIC

mpt_insert_empty:
global mpt_insert_empty:
// stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest
%pop2
// stack: num_nibbles, key, value_ptr, retdest
Expand All @@ -38,7 +38,7 @@ mpt_insert_empty:
SWAP1
JUMP

mpt_insert_branch:
global mpt_insert_branch:
// stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest
POP

Expand Down
6 changes: 3 additions & 3 deletions evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ global mpt_read_extension_not_found:
// Not found; return 0.
%stack (key_part, future_nibbles, key, node_payload_ptr, retdest) -> (retdest, 0)
JUMP
mpt_read_extension_found:
global mpt_read_extension_found:
// stack: key_part, future_nibbles, key, node_payload_ptr, retdest
DUP2 %mul_const(4) SHL // key_part_shifted = (key_part << (future_nibbles * 4))
// stack: key_part_shifted, future_nibbles, key, node_payload_ptr, retdest
Expand All @@ -121,7 +121,7 @@ mpt_read_extension_found:
// stack: child_ptr, future_nibbles, key, retdest
%jump(mpt_read) // recurse

mpt_read_leaf:
global mpt_read_leaf:
// stack: node_type, node_payload_ptr, num_nibbles, key, retdest
POP
// stack: node_payload_ptr, num_nibbles, key, retdest
Expand All @@ -142,7 +142,7 @@ global mpt_read_leaf_not_found:
// Not found; return 0.
%stack (node_payload_ptr, retdest) -> (retdest, 0)
JUMP
mpt_read_leaf_found:
global mpt_read_leaf_found:
// stack: node_payload_ptr, retdest
%add_const(2) // The value pointer is located after num_nibbles and the key.
// stack: value_ptr_ptr, retdest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ pub(crate) enum GlobalMetadata {
BlockGasUsedAfter,
/// Current block header hash
BlockCurrentHash,
/// EIP-4788: hash tree root of the beacon chain parent block.
ParentBeaconBlockRoot,

/// Gas to refund at the end of the transaction.
RefundCounter,
Expand Down Expand Up @@ -101,7 +103,7 @@ pub(crate) enum GlobalMetadata {
}

impl GlobalMetadata {
pub(crate) const COUNT: usize = 49;
pub(crate) const COUNT: usize = 50;

/// Unscales this virtual offset by their respective `Segment` value.
pub(crate) const fn unscale(&self) -> usize {
Expand Down Expand Up @@ -134,6 +136,7 @@ impl GlobalMetadata {
Self::BlockGasUsed,
Self::BlockGasUsedBefore,
Self::BlockGasUsedAfter,
Self::ParentBeaconBlockRoot,
Self::RefundCounter,
Self::AccessedAddressesLen,
Self::AccessedStorageKeysLen,
Expand Down Expand Up @@ -190,6 +193,7 @@ impl GlobalMetadata {
Self::BlockGasUsedBefore => "GLOBAL_METADATA_BLOCK_GAS_USED_BEFORE",
Self::BlockGasUsedAfter => "GLOBAL_METADATA_BLOCK_GAS_USED_AFTER",
Self::BlockCurrentHash => "GLOBAL_METADATA_BLOCK_CURRENT_HASH",
Self::ParentBeaconBlockRoot => "GLOBAL_METADATA_PARENT_BEACON_BLOCK_ROOT",
Self::RefundCounter => "GLOBAL_METADATA_REFUND_COUNTER",
Self::AccessedAddressesLen => "GLOBAL_METADATA_ACCESSED_ADDRESSES_LEN",
Self::AccessedStorageKeysLen => "GLOBAL_METADATA_ACCESSED_STORAGE_KEYS_LEN",
Expand Down
41 changes: 40 additions & 1 deletion evm_arithmetization/src/cpu/kernel/constants/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::collections::HashMap;

use ethereum_types::U256;
use ethereum_types::{H256, U256};
use hex_literal::hex;

use crate::cpu::kernel::constants::context_metadata::ContextMetadata;
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
use crate::cpu::kernel::constants::journal_entry::JournalEntry;
use crate::cpu::kernel::constants::trie_type::PartialTrieType;
use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField;
use crate::generation::mpt::AccountRlp;
use crate::memory::segments::Segment;

pub(crate) mod context_metadata;
Expand Down Expand Up @@ -56,6 +57,14 @@ pub(crate) fn evm_constants() -> HashMap<String, U256> {

c.insert(MAX_NONCE.0.into(), U256::from(MAX_NONCE.1));
c.insert(CALL_STACK_LIMIT.0.into(), U256::from(CALL_STACK_LIMIT.1));
c.insert(
cancun_constants::BEACON_ROOTS_ADDRESS.0.into(),
U256::from_big_endian(&cancun_constants::BEACON_ROOTS_ADDRESS.1),
);
c.insert(
cancun_constants::HISTORY_BUFFER_LENGTH.0.into(),
cancun_constants::HISTORY_BUFFER_LENGTH.1.into(),
);

for segment in Segment::all() {
c.insert(segment.var_name().into(), (segment as usize).into());
Expand Down Expand Up @@ -285,3 +294,33 @@ const CODE_SIZE_LIMIT: [(&str, u64); 3] = [

const MAX_NONCE: (&str, u64) = ("MAX_NONCE", 0xffffffffffffffff);
const CALL_STACK_LIMIT: (&str, u64) = ("CALL_STACK_LIMIT", 1024);

/// Cancun-related constants
/// See <https://eips.ethereum.org/EIPS/eip-4788#deployment>.
pub mod cancun_constants {
use super::*;

pub const BEACON_ROOTS_ADDRESS: (&str, [u8; 20]) = (
"BEACON_ROOTS_ADDRESS",
hex!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"),
);

pub const HISTORY_BUFFER_LENGTH: (&str, u64) = ("HISTORY_BUFFER_LENGTH", 8191);

pub const BEACON_ROOTS_CONTRACT_CODE: [u8; 106] = hex!("60618060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500");
pub const BEACON_ROOTS_CONTRACT_CODE_HASH: [u8; 32] =
hex!("468e991a328ab315e08296896adc222230a4960692e90cb6e096006ba6ae75d5");

pub const BEACON_ROOTS_CONTRACT_ADDRESS_HASHED: [u8; 32] =
hex!("37d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e42");

pub const BEACON_ROOTS_ACCOUNT: AccountRlp = AccountRlp {
nonce: U256::zero(),
balance: U256::zero(),
// Storage root for this account at genesis.
storage_root: H256(hex!(
"f58e5f1eae7ce386de44266ff0286a88dafe7e4269c1ffa97f79dbbcf4f59e5c"
)),
code_hash: H256(BEACON_ROOTS_CONTRACT_CODE_HASH),
};
}
Loading

0 comments on commit d43c8e2

Please sign in to comment.