Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 157 additions & 50 deletions zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,12 @@ pub struct ContractCreateGadget<F, const IS_CREATE2: bool> {
caller_address: RandomLinearCombination<F, N_BYTES_ACCOUNT_ADDRESS>,
/// Sender nonce of the contract creation tx.
nonce: RlpU64Gadget<F>,
/// Keccak256 hash of init code, used for CREATE2.
code_hash: RandomLinearCombination<F, N_BYTES_WORD>,
/// Keccak256 hash of init code, used for CREATE2. We don't use a
/// RandomLinearCombination here since we require both keccak and word
/// RLC in the case of init code hash, for BeginTx and
/// CREATE2 respectively. Instead, we store just the bytes and calculate the
/// appropriate RLC wherever needed.
code_hash: [Cell<F>; N_BYTES_WORD],
/// Random salt for CREATE2.
salt: RandomLinearCombination<F, N_BYTES_WORD>,
}
Expand All @@ -195,8 +199,8 @@ impl<F: Field, const IS_CREATE2: bool> ContractCreateGadget<F, IS_CREATE2> {
pub(crate) fn construct(cb: &mut ConstraintBuilder<F>) -> Self {
let caller_address = cb.query_keccak_rlc();
let nonce = RlpU64Gadget::construct(cb);
let code_hash = cb.query_word_rlc();
let salt = cb.query_word_rlc();
let code_hash = array_init::array_init(|_| cb.query_byte());
let salt = cb.query_keccak_rlc();

Self {
caller_address,
Expand All @@ -223,12 +227,17 @@ impl<F: Field, const IS_CREATE2: bool> ContractCreateGadget<F, IS_CREATE2> {

self.nonce.assign(region, offset, caller_nonce)?;

for (word, value) in [(&self.code_hash, code_hash), (&self.salt, salt)] {
word.assign(
region,
offset,
Some(value.map(|v| v.to_le_bytes()).unwrap_or_default()),
)?;
self.salt.assign(
region,
offset,
Some(salt.map(|v| v.to_le_bytes()).unwrap_or_default()),
)?;
for (c, v) in self
.code_hash
.iter()
.zip(code_hash.map(|v| v.to_le_bytes()).unwrap_or_default())
{
c.assign(region, offset, Value::known(F::from(v as u64)))?;
}

Ok(())
Expand All @@ -244,13 +253,32 @@ impl<F: Field, const IS_CREATE2: bool> ContractCreateGadget<F, IS_CREATE2> {
self.nonce.value()
}

/// Code hash RLC.
pub(crate) fn code_hash_rlc(&self) -> Expression<F> {
self.code_hash.expr()
/// Code hash word RLC.
pub(crate) fn code_hash_word_rlc(&self, cb: &ConstraintBuilder<F>) -> Expression<F> {
cb.word_rlc::<N_BYTES_WORD>(
self.code_hash
.iter()
.map(Expr::expr)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
)
}

/// Code hash keccak RLC.
pub(crate) fn code_hash_keccak_rlc(&self, cb: &ConstraintBuilder<F>) -> Expression<F> {
cb.keccak_rlc::<N_BYTES_WORD>(
self.code_hash
.iter()
.map(Expr::expr)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
)
}

/// Salt RLC.
pub(crate) fn salt_rlc(&self) -> Expression<F> {
/// Salt keccak RLC.
pub(crate) fn salt_keccak_rlc(&self) -> Expression<F> {
self.salt.expr()
}

Expand All @@ -267,11 +295,11 @@ impl<F: Field, const IS_CREATE2: bool> ContractCreateGadget<F, IS_CREATE2> {
/// Length of the input data to the keccak hash function.
pub(crate) fn input_length(&self) -> Expression<F> {
if IS_CREATE2 {
// 0xff | caller_address | salt | code_hash
// | 0xff | caller_address | salt | code_hash |
// |------|----------------|------|-----------|
// | 1 | 20 | 32 | 32 |
(1 + 20 + 32 + 32).expr()
} else {
// RLP([caller_address, caller_nonce])
//
// | prefix | addr-prefix | addr | nonce-bytes |
// |--------|-------------|------|-------------------|
// | 1 | 1 | 20 | rlp_length(nonce) |
Expand All @@ -284,15 +312,19 @@ impl<F: Field, const IS_CREATE2: bool> ContractCreateGadget<F, IS_CREATE2> {
let challenges = cb.challenges().keccak_powers_of_randomness::<21>();
let challenge_power_20 = challenges[19].clone();
if IS_CREATE2 {
// RLC(0xff | caller_address | salt | code_hash)
// RLC(le-bytes([0xff | caller_address | salt | code_hash]))
//
Comment thread
adria0 marked this conversation as resolved.
// | 0xff | caller address | salt | init code hash |
// |------|----------------|------|----------------|
// | 1 | 20 | 32 | 32 |
let challenge_power_16 = challenges[15].clone();
let challenge_power_32 = challenge_power_16.square();
let challenge_power_64 = challenge_power_32.clone().square();
let challenge_power_84 = challenge_power_64.clone() * challenge_power_20;
(0xff.expr() * challenge_power_84)
+ (self.caller_address_rlc() * challenge_power_64)
+ (self.salt_rlc() * challenge_power_32)
+ self.code_hash_rlc()
+ (self.salt_keccak_rlc() * challenge_power_32)
+ self.code_hash_keccak_rlc(cb)
} else {
// RLC(RLP([caller_address, caller_nonce]))
let challenge_power_21 = challenges[20].clone();
Expand All @@ -310,42 +342,66 @@ mod test {
use super::super::test_util::*;
use super::ContractCreateGadget;
use eth_types::{Field, ToAddress, ToLittleEndian, ToWord, Word};
use gadgets::util::Expr;
use gadgets::util::{not, Expr};
use halo2_proofs::halo2curves::bn256::Fr;

use crate::evm_circuit::{
param::N_BYTES_WORD,
util::{
constraint_builder::ConstraintBuilder, CachedRegion, Cell, RandomLinearCombination,
},
};
use crate::evm_circuit::util::{constraint_builder::ConstraintBuilder, CachedRegion, Cell};

#[derive(Clone)]
struct ContractCreateGadgetContainer<F> {
create_gadget: ContractCreateGadget<F, false>,
struct ContractCreateGadgetContainer<F, const IS_CREATE2: bool> {
create_gadget: ContractCreateGadget<F, IS_CREATE2>,
input_len_expected: Cell<F>,
input_rlc_expected: RandomLinearCombination<F, N_BYTES_WORD>,
create_input_rlc_expected: [Cell<F>; 32],
create2_input_rlc_expected: [Cell<F>; 85],
}

impl<F: Field> MathGadgetContainer<F> for ContractCreateGadgetContainer<F> {
impl<F: Field, const IS_CREATE2: bool> MathGadgetContainer<F>
for ContractCreateGadgetContainer<F, IS_CREATE2>
{
fn configure_gadget_container(cb: &mut ConstraintBuilder<F>) -> Self {
let create_gadget = ContractCreateGadget::construct(cb);
let input_len_expected = cb.query_cell();
let input_rlc_expected = cb.query_keccak_rlc();
let create_input_rlc_expected = array_init::array_init(|_| cb.query_byte());
let create2_input_rlc_expected = array_init::array_init(|_| cb.query_byte());
cb.require_equal(
"RLP length correct",
input_len_expected.expr(),
create_gadget.input_length(),
);
cb.require_equal(
"RLP-encoding correct",
input_rlc_expected.expr(),
create_gadget.input_rlc(cb),
);
cb.condition(IS_CREATE2.expr(), |cb| {
cb.require_equal(
"CREATE2 RLP-encoding correct",
cb.keccak_rlc::<85>(
create2_input_rlc_expected
.iter()
.map(Expr::expr)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
),
create_gadget.input_rlc(cb),
);
});
cb.condition(not::expr(IS_CREATE2.expr()), |cb| {
cb.require_equal(
"CREATE RLP-encoding correct",
cb.keccak_rlc::<32>(
create_input_rlc_expected
.iter()
.map(Expr::expr)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
),
create_gadget.input_rlc(cb),
);
});

Self {
create_gadget,
input_len_expected,
input_rlc_expected,
create_input_rlc_expected,
create2_input_rlc_expected,
}
}

Expand All @@ -357,15 +413,50 @@ mod test {
let offset = 0;
let caller_address = witnesses[0].to_address();
let caller_nonce = witnesses[1].as_u64();
let rlp_encoding = witnesses[2];
let rlp_length = witnesses[3].as_u64();
let input_len = witnesses[2].as_u64();
let (salt, init_code_hash) = if IS_CREATE2 {
(Some(witnesses[5]), Some(witnesses[6]))
} else {
(None, None)
};

self.create_gadget
.assign(region, offset, caller_address, caller_nonce, None, None)?;
self.create_gadget.assign(
region,
offset,
caller_address,
caller_nonce,
init_code_hash,
salt,
)?;
self.input_len_expected
.assign(region, offset, Value::known(F::from(rlp_length)))?;
self.input_rlc_expected
.assign(region, offset, Some(rlp_encoding.to_le_bytes()))?;
.assign(region, offset, Value::known(F::from(input_len)))?;
if IS_CREATE2 {
for c in self.create_input_rlc_expected.iter() {
c.assign(region, offset, Value::known(F::zero()))?;
}
for (c, v) in self.create2_input_rlc_expected.iter().zip(
[
witnesses[6].to_le_bytes().as_ref(), // 32-byte init code hash
witnesses[5].to_le_bytes().as_ref(), // 32-byte salt
witnesses[4].to_le_bytes()[0..20].as_ref(), // 20-byte address
witnesses[3].to_le_bytes()[0..1].as_ref(), // 0xff
]
.concat(),
) {
c.assign(region, offset, Value::known(F::from(v as u64)))?;
}
} else {
for (c, v) in self
.create_input_rlc_expected
.iter()
.zip(witnesses[3].to_le_bytes())
{
c.assign(region, offset, Value::known(F::from(v as u64)))?;
}
for c in self.create2_input_rlc_expected.iter() {
c.assign(region, offset, Value::known(F::zero()))?;
}
}

Ok(())
}
Expand Down Expand Up @@ -399,21 +490,37 @@ mod test {
)
};
try_test!(
ContractCreateGadgetContainer<Fr>,
ContractCreateGadgetContainer<Fr, false>,
vec![
caller_address.to_word(),
Word::from(caller_nonce),
rlp_len,
rlp_word,
rlp_len
Word::default(),
Word::default(),
],
true
);
}
}

#[test]
#[ignore]
fn create2_address() {
todo!()
let caller_address = mock::MOCK_ACCOUNTS[0];
let salt = Word::from(0xbeefcafedeadu64);
let code_hash = Word::from(0xdeadcafeu64);
try_test!(
ContractCreateGadgetContainer<Fr, true>,
vec![
caller_address.to_word(),
Word::default(),
85u64.into(),
Word::from(0xffu64),
caller_address.to_word(),
salt,
code_hash,
],
true
)
}
}