diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs index 6eaea9a237..d3f877b94f 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget/rlp.rs @@ -184,8 +184,12 @@ pub struct ContractCreateGadget { caller_address: RandomLinearCombination, /// Sender nonce of the contract creation tx. nonce: RlpU64Gadget, - /// Keccak256 hash of init code, used for CREATE2. - code_hash: RandomLinearCombination, + /// 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; N_BYTES_WORD], /// Random salt for CREATE2. salt: RandomLinearCombination, } @@ -195,8 +199,8 @@ impl ContractCreateGadget { pub(crate) fn construct(cb: &mut ConstraintBuilder) -> 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, @@ -223,12 +227,17 @@ impl ContractCreateGadget { 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(()) @@ -244,13 +253,32 @@ impl ContractCreateGadget { self.nonce.value() } - /// Code hash RLC. - pub(crate) fn code_hash_rlc(&self) -> Expression { - self.code_hash.expr() + /// Code hash word RLC. + pub(crate) fn code_hash_word_rlc(&self, cb: &ConstraintBuilder) -> Expression { + cb.word_rlc::( + self.code_hash + .iter() + .map(Expr::expr) + .collect::>() + .try_into() + .unwrap(), + ) + } + + /// Code hash keccak RLC. + pub(crate) fn code_hash_keccak_rlc(&self, cb: &ConstraintBuilder) -> Expression { + cb.keccak_rlc::( + self.code_hash + .iter() + .map(Expr::expr) + .collect::>() + .try_into() + .unwrap(), + ) } - /// Salt RLC. - pub(crate) fn salt_rlc(&self) -> Expression { + /// Salt keccak RLC. + pub(crate) fn salt_keccak_rlc(&self) -> Expression { self.salt.expr() } @@ -267,11 +295,11 @@ impl ContractCreateGadget { /// Length of the input data to the keccak hash function. pub(crate) fn input_length(&self) -> Expression { 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) | @@ -284,15 +312,19 @@ impl ContractCreateGadget { 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])) + // + // | 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(); @@ -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 { - create_gadget: ContractCreateGadget, + struct ContractCreateGadgetContainer { + create_gadget: ContractCreateGadget, input_len_expected: Cell, - input_rlc_expected: RandomLinearCombination, + create_input_rlc_expected: [Cell; 32], + create2_input_rlc_expected: [Cell; 85], } - impl MathGadgetContainer for ContractCreateGadgetContainer { + impl MathGadgetContainer + for ContractCreateGadgetContainer + { fn configure_gadget_container(cb: &mut ConstraintBuilder) -> 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::>() + .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::>() + .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, } } @@ -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(()) } @@ -399,12 +490,14 @@ mod test { ) }; try_test!( - ContractCreateGadgetContainer, + ContractCreateGadgetContainer, vec![ caller_address.to_word(), Word::from(caller_nonce), + rlp_len, rlp_word, - rlp_len + Word::default(), + Word::default(), ], true ); @@ -412,8 +505,22 @@ mod test { } #[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, + vec![ + caller_address.to_word(), + Word::default(), + 85u64.into(), + Word::from(0xffu64), + caller_address.to_word(), + salt, + code_hash, + ], + true + ) } }