Merge branch 'main' into blake_precompile

This commit is contained in:
Nicholas Ward 2023-04-24 16:59:17 -07:00
commit 5dc4491681
10 changed files with 1845 additions and 1062 deletions

View File

@ -16,6 +16,7 @@ pub(crate) fn combined_kernel() -> Kernel {
include_str!("asm/bignum/add.asm"),
include_str!("asm/bignum/addmul.asm"),
include_str!("asm/bignum/cmp.asm"),
include_str!("asm/bignum/isone.asm"),
include_str!("asm/bignum/iszero.asm"),
include_str!("asm/bignum/modexp.asm"),
include_str!("asm/bignum/modmul.asm"),

View File

@ -0,0 +1,35 @@
// Arithmetic on little-endian integers represented with 128-bit limbs.
// All integers must be under a given length bound, and are padded with leading zeroes.
global isone_bignum:
// stack: len, start_loc, retdest
DUP1
// stack: len, len, start_loc, retdest
ISZERO
%jumpi(eqzero)
// stack: len, start_loc, retdest
DUP2
// stack: start_loc, len, start_loc, retdest
%mload_kernel_general
// stack: start_val, len, start_loc, retdest
%eq_const(1)
%jumpi(starts_with_one)
// Does not start with one, so not equal to one.
// stack: len, start_loc, retdest
%stack (vals: 2, retdest) -> (retdest, 0)
JUMP
eqzero:
// Is zero, so not equal to one.
// stack: cur_loc, end_loc, retdest
%stack (vals: 2, retdest) -> (retdest, 0)
// stack: retdest, 0
JUMP
starts_with_one:
// Starts with one, so check that the remaining limbs are zero.
// stack: len, start_loc, retdest
%decrement
SWAP1
%increment
SWAP1
// stack: len-1, start_loc+1, retdest
%jump(iszero_bignum)

View File

@ -8,10 +8,55 @@
// All of scratch_2..scratch_5 must have size 2 * length and be initialized with zeroes.
// Also, scratch_2..scratch_5 must be CONSECUTIVE in memory.
global modexp_bignum:
// stack: len, b_loc, e_loc, m_loc, out_loc, s1 (=scratch_1), s2, s3, s4, s5, retdest
DUP1
ISZERO
%jumpi(len_zero)
// Special input cases:
// (1) Modulus is zero (also covers len=0 case).
PUSH modulus_zero_return
// stack: modulus_zero_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
DUP5
// stack: m_loc, modulus_zero_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
DUP3
// stack: len, m_loc, modulus_zero_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
%jump(iszero_bignum)
modulus_zero_return:
// stack: m==0, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
%jumpi(modulus_zero_or_one)
// (2) Modulus is one.
PUSH modulus_one_return
// stack: modulus_one_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
DUP5
// stack: m_loc, modulus_one_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
DUP3
// stack: len, m_loc, modulus_one_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
%jump(isone_bignum)
modulus_one_return:
// stack: m==1, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
%jumpi(modulus_zero_or_one)
// (3) Both b and e are zero.
PUSH b_zero_return
// stack: b_zero_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
DUP3
// stack: b_loc, b_zero_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
DUP3
// stack: len, b_loc, b_zero_return, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
%jump(iszero_bignum)
b_zero_return:
// stack: b==0, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
PUSH e_zero_return
// stack: e_zero_return, b==0, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
DUP5
// stack: e_loc, e_zero_return, b==0, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
DUP4
// stack: len, e_loc, e_zero_return, b==0, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
%jump(iszero_bignum)
e_zero_return:
// stack: e==0, b==0, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
MUL // logical and
%jumpi(b_and_e_zero)
// End of special cases.
// We store the repeated-squares accumulator x_i in scratch_1, starting with x_0 := b.
DUP1
@ -128,8 +173,18 @@ modexp_iszero_return:
// stack: e != 0, len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
%jumpi(modexp_loop)
// end of modexp_loop
len_zero:
modulus_zero_or_one:
// If modulus is zero or one, return 0.
// stack: len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
%pop10
// stack: retdest
JUMP
b_and_e_zero:
// If base and exponent are zero (and modulus > 1), return 1.
// stack: len, b_loc, e_loc, m_loc, out_loc, s1, s2, s3, s4, s5, retdest
PUSH 1
DUP6
%mstore_kernel_general
%pop10
// stack: retdest
JUMP

View File

@ -10,7 +10,10 @@ global sys_create:
%checked_mem_expansion
// stack: kexit_info, value, code_offset, code_len
%charge_gas_const(@GAS_CREATE)
// TODO: If using EIP-3860, we should limit and charge gas on `code_len`.
// stack: kexit_info, value, code_offset, code_len
DUP4
// stack: code_len, kexit_info, value, code_offset, code_len
%check_initcode_size
%stack (kexit_info, value, code_offset, code_len)
-> (sys_create_got_address, value, code_offset, code_len, kexit_info)
@ -39,7 +42,11 @@ global sys_create2:
// stack: kexit_info, value, code_offset, code_len, salt
DUP4 %num_bytes_to_num_words
%mul_const(@GAS_KECCAK256WORD) %add_const(@GAS_CREATE) %charge_gas
// TODO: If using EIP-3860, we should limit and charge gas on `code_len`.
// stack: kexit_info, value, code_offset, code_len, salt
DUP4
// stack: code_len, kexit_info, value, code_offset, code_len, salt
%check_initcode_size
SWAP4
%stack (salt) -> (salt, create_common)
@ -130,9 +137,11 @@ after_constructor:
// stack: new_ctx, leftover_gas, success, address, kexit_info
POP
// TODO: EIP-3541: Reject new contract code starting with the 0xEF byte
// TODO: Skip blocks below if success is false.
// EIP-3541: Reject new contract code starting with the 0xEF byte
PUSH 0 %mload_current(@SEGMENT_RETURNDATA) %eq_const(0xEF) %jumpi(fault_exception)
// Charge gas for the code size.
SWAP3
// stack: kexit_info, success, address, leftover_gas
@ -150,6 +159,7 @@ after_constructor:
%stack (size, ctx) -> (ctx, @SEGMENT_RETURNDATA, 0, size) // context, segment, offset, len
KECCAK_GENERAL
// stack: codehash, leftover_gas, success, address, kexit_info
%observe_new_contract
DUP4
// stack: address, codehash, leftover_gas, success, address, kexit_info
%set_codehash
@ -180,7 +190,7 @@ after_constructor:
// Pre stack: addr, codehash, redest
// Post stack: (empty)
// TODO: Should it be copy-on-write (with make_account_copy) instead of mutating the trie?
set_codehash:
global set_codehash:
// stack: addr, codehash, retdest
%mpt_read_state_trie
// stack: account_ptr, codehash, retdest
@ -189,3 +199,30 @@ set_codehash:
%mstore_trie_data
// stack: retdest
JUMP
// Check and charge gas cost for initcode size. See EIP-3860.
// Pre stack: code_size, kexit_info
// Post stack: kexit_info
%macro check_initcode_size
DUP1 %gt_const(@MAX_INITCODE_SIZE) %jumpi(fault_exception)
// stack: code_size, kexit_info
%num_bytes_to_num_words %mul_const(@INITCODE_WORD_COST)
%charge_gas
%endmacro
// This should be called whenever a new contract is created.
// It does nothing, but just provides a single hook where code can react to newly created contracts.
// When called, the code corresponding to `codehash` should be stored in the return data.
// Pre stack: codehash, retdest
// Post stack: codehash
global observe_new_contract:
// stack codehash, retdest
SWAP1 JUMP
%macro observe_new_contract
%stack (codehash) -> (codehash, %%after)
%jump(observe_new_contract)
%%after:
// stack: codehash
%endmacro

View File

@ -45,7 +45,21 @@ count_zeros_finish:
// stack: gas_txndata, retdest
%is_contract_creation
DUP1
%mul_const(@GAS_TXCREATE)
// stack: gas_creation, is_creation, gas_txndata, retdest
SWAP1
// stack: is_creation, gas_creation, gas_txndata, retdest
DUP1
// stack: is_creation, is_creation, gas_creation, gas_txndata, retdest
%mload_txn_field(@TXN_FIELD_DATA_LEN) %gt_const(@MAX_INITCODE_SIZE)
// stack: initcode_size > max, is_creation, is_creation, gas_creation, gas_txndata, retdest
MUL // Cheaper than AND
%assert_zero
// stack: is_creation, gas_creation, gas_txndata, retdest
%mload_txn_field(@TXN_FIELD_DATA_LEN) %num_bytes_to_num_words
// stack: initcode_words, is_creation, gas_creation, gas_txndata, retdest
%mul_const(@INITCODE_WORD_COST) MUL ADD
// stack: gas_creation, gas_txndata, retdest
PUSH @GAS_TRANSACTION

View File

@ -144,6 +144,10 @@ process_contract_creation_txn_after_code_loaded:
global process_contract_creation_txn_after_constructor:
// stack: success, leftover_gas, new_ctx, address, retdest
POP // TODO: Success will go into the receipt when we support that.
// EIP-3541: Reject new contract code starting with the 0xEF byte
PUSH 0 %mload_current(@SEGMENT_RETURNDATA) %eq_const(0xEF) %assert_zero // TODO: need to revert changes here.
// stack: leftover_gas, new_ctx, address, retdest
%returndatasize // Size of the code.
// stack: code_size, leftover_gas, new_ctx, address, retdest
@ -154,6 +158,19 @@ global process_contract_creation_txn_after_constructor:
DUP2 DUP2 LT %jumpi(panic) // TODO: need to revert changes here.
// stack: leftover_gas, codedeposit_cost, new_ctx, address, retdest
SUB
// Store the code hash of the new contract.
// stack: leftover_gas, new_ctx, address, retdest
GET_CONTEXT
%returndatasize
%stack (size, ctx) -> (ctx, @SEGMENT_RETURNDATA, 0, size) // context, segment, offset, len
KECCAK_GENERAL
// stack: codehash, leftover_gas, new_ctx, address, retdest
%observe_new_contract
DUP4
// stack: address, codehash, leftover_gas, new_ctx, address, retdest
%set_codehash
// stack: leftover_gas, new_ctx, address, retdest
%pay_coinbase_and_refund_sender
// TODO: Delete accounts in self-destruct list and empty touched addresses.

View File

@ -29,6 +29,7 @@ const TEST_DATA_ADDMUL_OUTPUTS: &str = "addmul_outputs";
const TEST_DATA_MUL_OUTPUTS: &str = "mul_outputs";
const TEST_DATA_MODMUL_OUTPUTS: &str = "modmul_outputs";
const TEST_DATA_MODEXP_OUTPUTS: &str = "modexp_outputs";
const TEST_DATA_MODEXP_OUTPUTS_FULL: &str = "modexp_outputs_full";
const BIT_SIZES_TO_TEST: [usize; 15] = [
0, 1, 2, 127, 128, 129, 255, 256, 257, 512, 1000, 1023, 1024, 1025, 31415,
@ -282,7 +283,7 @@ fn test_modexp_bignum(b: BigUint, e: BigUint, m: BigUint, expected_output: BigUi
let scratch_3 = 7 * len; // size 2*len
let scratch_4 = 9 * len; // size 2*len
let scratch_5 = 11 * len; // size 2*len
let (new_memory, _new_stack) = run_test(
let (mut new_memory, _new_stack) = run_test(
"modexp_bignum",
memory,
vec![
@ -298,6 +299,10 @@ fn test_modexp_bignum(b: BigUint, e: BigUint, m: BigUint, expected_output: BigUi
scratch_5.into(),
],
)?;
new_memory.resize(
new_memory.len().max(output_start_loc + output_len),
0.into(),
);
let output = mem_vec_to_biguint(&new_memory[output_start_loc..output_start_loc + output_len]);
assert_eq!(output, expected_output);
@ -533,9 +538,8 @@ fn test_modexp_bignum_all() -> Result<()> {
let mut modexp_outputs_iter = modexp_outputs.into_iter();
for b in &inputs[..9] {
// Include only smaller exponents, to keep tests from becoming too slow.
for e in &inputs[..7] {
// For m, skip the first input, which is zero.
for m in &inputs[1..] {
for e in &inputs[..6] {
for m in &inputs[..9] {
let output = modexp_outputs_iter.next().unwrap();
test_modexp_bignum(b.clone(), e.clone(), m.clone(), output)?;
}
@ -572,13 +576,12 @@ fn test_modexp_bignum_all_full() -> Result<()> {
}
let inputs = test_data_biguint(TEST_DATA_BIGNUM_INPUTS);
let modexp_outputs = test_data_biguint(TEST_DATA_MODEXP_OUTPUTS);
let modexp_outputs = test_data_biguint(TEST_DATA_MODEXP_OUTPUTS_FULL);
let mut modexp_outputs_iter = modexp_outputs.into_iter();
for b in &inputs {
// Include only smaller exponents, to keep tests from becoming too slow.
for e in &inputs[..7] {
// For m, skip the first input, which is zero.
for m in &inputs[1..] {
for m in &inputs {
let output = modexp_outputs_iter.next().unwrap();
test_modexp_bignum(b.clone(), e.clone(), m.clone(), output)?;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,12 @@ use keccak_hash::keccak;
use plonky2::field::types::Field;
use crate::cpu::kernel::aggregator::KERNEL;
use crate::cpu::kernel::constants::context_metadata::ContextMetadata;
use crate::generation::mpt::all_mpt_prover_inputs_reversed;
use crate::generation::rlp::all_rlp_prover_inputs_reversed;
use crate::generation::GenerationInputs;
use crate::witness::memory::MemoryState;
use crate::memory::segments::Segment;
use crate::witness::memory::{MemoryAddress, MemoryState};
use crate::witness::state::RegistersState;
use crate::witness::traces::{TraceCheckpoint, Traces};
use crate::witness::util::stack_peek;
@ -83,6 +85,10 @@ impl<F: Field> GenerationState<F> {
let tip_h256 = H256::from_uint(&tip_u256);
let tip_h160 = H160::from(tip_h256);
self.observe_address(tip_h160);
} else if dst == KERNEL.global_labels["observe_new_contract"] {
let tip_u256 = stack_peek(self, 0).expect("Empty stack");
let tip_h256 = H256::from_uint(&tip_u256);
self.observe_contract(tip_h256);
}
}
@ -93,6 +99,30 @@ impl<F: Field> GenerationState<F> {
self.state_key_to_address.insert(state_key, address);
}
/// Observe the given code hash and store the associated code.
/// When called, the code corresponding to `codehash` should be stored in the return data.
pub fn observe_contract(&mut self, codehash: H256) {
if self.inputs.contract_code.contains_key(&codehash) {
return; // Return early if the code hash has already been observed.
}
let ctx = self.registers.context;
let returndata_size_addr = MemoryAddress::new(
ctx,
Segment::ContextMetadata,
ContextMetadata::ReturndataSize as usize,
);
let returndata_size = self.memory.get(returndata_size_addr).as_usize();
let code = self.memory.contexts[ctx].segments[Segment::Returndata as usize].content
[..returndata_size]
.iter()
.map(|x| x.as_u32() as u8)
.collect::<Vec<_>>();
debug_assert_eq!(keccak(&code), codehash);
self.inputs.contract_code.insert(codehash, code);
}
pub fn checkpoint(&self) -> GenerationStateCheckpoint {
GenerationStateCheckpoint {
registers: self.registers,