From f14a3d4cad98a4a43b7f3134b7a5d2454fc32ea6 Mon Sep 17 00:00:00 2001 From: Kimi Wu Date: Wed, 28 Jun 2023 10:01:56 +0800 Subject: [PATCH] Feat/#1387 tx circuit refactor word rlc into word lo/hi (#1418) ### Description part of word lo/hi ### Issue Link #1387 ### Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ### Contents - applied word lo/hi to tx_circuit --------- Co-authored-by: sm.wu Co-authored-by: Eduard S --- eth-types/src/geth_types.rs | 4 +- zkevm-circuits/src/tx_circuit.rs | 90 ++++---- zkevm-circuits/src/tx_circuit/sign_verify.rs | 219 +++++++++++-------- 3 files changed, 173 insertions(+), 140 deletions(-) diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 0b16ab307d..48d624ba34 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -249,11 +249,11 @@ impl Transaction { .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }) } - /// Get the "to" address. If `to` is None then zero adddress + /// Get the "to" address. If `to` is None then zero address pub fn to_or_zero(&self) -> Address { self.to.unwrap_or_default() } - /// Get the "to" address. If `to` is None then compute contract adddress + /// Get the "to" address. If `to` is None then compute contract address pub fn to_or_contract_addr(&self) -> Address { self.to .unwrap_or_else(|| get_contract_address(self.from, self.nonce.to_word())) diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index ffd60993aa..553303e475 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -15,10 +15,10 @@ pub use dev::TxCircuit as TestTxCircuit; use crate::{ table::{KeccakTable, TxFieldTag, TxTable}, - util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}, + util::{word::Word, Challenges, SubCircuit, SubCircuitConfig}, witness, }; -use eth_types::{geth_types::Transaction, sign_types::SignData, Field, ToLittleEndian, ToScalar}; +use eth_types::{geth_types::Transaction, sign_types::SignData, Field}; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, Value}, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed}, @@ -41,7 +41,7 @@ pub struct TxCircuitConfig { tx_id: Column, tag: Column, index: Column, - value: Column, + value: Word>, sign_verify: SignVerifyConfig, _marker: PhantomData, // External tables @@ -73,8 +73,9 @@ impl SubCircuitConfig for TxCircuitConfig { let tx_id = tx_table.tx_id; let tag = tx_table.tag; let index = tx_table.index; - let value = tx_table.value; - meta.enable_equality(value); + let value = tx_table.value_word; + meta.enable_equality(value.lo()); + meta.enable_equality(value.hi()); let sign_verify = SignVerifyConfig::new(meta, keccak_table.clone(), challenges); @@ -96,7 +97,7 @@ impl TxCircuitConfig { self.sign_verify.load_range(layouter) } - /// Assigns a tx circuit row and returns the assigned cell of the value in + /// Assigns a tx circuit row and returns the assigned cell of the value in `word` in /// the row. fn assign_row( &self, @@ -105,8 +106,8 @@ impl TxCircuitConfig { tx_id: usize, tag: TxFieldTag, index: usize, - value: Value, - ) -> Result, Error> { + value: Word>, + ) -> Result>, Error> { region.assign_advice( || "tx_id", self.tx_id, @@ -125,7 +126,7 @@ impl TxCircuitConfig { offset, || Value::known(F::from(index as u64)), )?; - region.assign_advice(|| "value", self.value, offset, || value) + value.assign_advice(region, || "value", self.value, offset) } /// Get number of rows required. @@ -174,7 +175,6 @@ impl TxCircuit { fn assign_tx_table( &self, config: &TxCircuitConfig, - challenges: &Challenges>, layouter: &mut impl Layouter, assigned_sig_verifs: Vec>, ) -> Result<(), Error> { @@ -189,7 +189,7 @@ impl TxCircuit { 0, TxFieldTag::Null, 0, - Value::known(F::ZERO), + Word::default().into_value(), )?; offset += 1; // Assign al Tx fields except for call data @@ -202,46 +202,36 @@ impl TxCircuit { }; for (tag, value) in [ - (TxFieldTag::Nonce, Value::known(F::from(tx.nonce.as_u64()))), - ( - TxFieldTag::Gas, - Value::known(F::from(tx.gas_limit.as_u64())), - ), ( - TxFieldTag::GasPrice, - challenges - .evm_word() - .map(|challenge| rlc(tx.gas_price.to_le_bytes(), challenge)), + TxFieldTag::Nonce, + Word::from(tx.nonce.as_u64()).into_value(), ), ( - TxFieldTag::CallerAddress, - Value::known(tx.from.to_scalar().expect("tx.from too big")), + TxFieldTag::Gas, + Word::from(tx.gas_limit.as_u64()).into_value(), ), + (TxFieldTag::GasPrice, Word::from(tx.gas_price).into_value()), + (TxFieldTag::CallerAddress, Word::from(tx.from).into_value()), ( TxFieldTag::CalleeAddress, - Value::known(tx.to_or_zero().to_scalar().expect("tx.to too big")), + Word::from(tx.to_or_zero()).into_value(), ), ( TxFieldTag::IsCreate, - Value::known(F::from(tx.is_create() as u64)), - ), - ( - TxFieldTag::Value, - challenges - .evm_word() - .map(|challenge| rlc(tx.value.to_le_bytes(), challenge)), + Word::from(tx.is_create() as u64).into_value(), ), + (TxFieldTag::Value, Word::from(tx.value).into_value()), ( TxFieldTag::CallDataLength, - Value::known(F::from(tx.call_data.0.len() as u64)), + Word::from(tx.call_data.0.len() as u64).into_value(), ), ( TxFieldTag::CallDataGasCost, - Value::known(F::from(tx.call_data_gas_cost())), + Word::from(tx.call_data_gas_cost()).into_value(), ), ( TxFieldTag::TxSignHash, - assigned_sig_verif.msg_hash_rlc.value().copied(), + assigned_sig_verif.msg_hash.map(|x| x.value().copied()), ), ] { let assigned_cell = @@ -251,14 +241,26 @@ impl TxCircuit { // Ref. spec 0. Copy constraints using fixed offsets between the tx rows and // the SignVerifyChip match tag { - TxFieldTag::CallerAddress => region.constrain_equal( - assigned_cell.cell(), - assigned_sig_verif.address.cell(), - )?, - TxFieldTag::TxSignHash => region.constrain_equal( - assigned_cell.cell(), - assigned_sig_verif.msg_hash_rlc.cell(), - )?, + TxFieldTag::CallerAddress => { + region.constrain_equal( + assigned_cell.lo().cell(), + assigned_sig_verif.address.lo().cell(), + )?; + region.constrain_equal( + assigned_cell.hi().cell(), + assigned_sig_verif.address.hi().cell(), + )? + } + TxFieldTag::TxSignHash => { + region.constrain_equal( + assigned_cell.lo().cell(), + assigned_sig_verif.msg_hash.lo().cell(), + )?; + region.constrain_equal( + assigned_cell.hi().cell(), + assigned_sig_verif.msg_hash.hi().cell(), + )? + } _ => (), } } @@ -275,7 +277,7 @@ impl TxCircuit { i + 1, // tx_id TxFieldTag::CallData, index, - Value::known(F::from(*byte as u64)), + Word::from(*byte as u64).into_value(), )?; offset += 1; calldata_count += 1; @@ -288,7 +290,7 @@ impl TxCircuit { 0, // tx_id TxFieldTag::CallData, 0, - Value::known(F::ZERO), + Word::default().into_value(), )?; offset += 1; } @@ -358,7 +360,7 @@ impl SubCircuit for TxCircuit { let assigned_sig_verifs = self.sign_verify .assign(&config.sign_verify, layouter, &sign_datas, challenges)?; - self.assign_tx_table(config, challenges, layouter, assigned_sig_verifs)?; + self.assign_tx_table(config, layouter, assigned_sig_verifs)?; Ok(()) } diff --git a/zkevm-circuits/src/tx_circuit/sign_verify.rs b/zkevm-circuits/src/tx_circuit/sign_verify.rs index 0683ee998f..4a23301f23 100644 --- a/zkevm-circuits/src/tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/tx_circuit/sign_verify.rs @@ -5,9 +5,12 @@ // - *_le: Little-Endian bytes use crate::{ - evm_circuit::util::{not, rlc}, + evm_circuit::{ + param::N_BYTES_ACCOUNT_ADDRESS, + util::{from_bytes, not, rlc}, + }, table::KeccakTable, - util::{Challenges, Expr}, + util::{word::Word, Challenges, Expr}, }; use ecc::{maingate, EccConfig, GeneralEccChip}; use ecdsa::ecdsa::{AssignedEcdsaSig, AssignedPublicKey, EcdsaChip}; @@ -59,7 +62,7 @@ impl SignVerifyChip { /// Return a new SignVerifyChip pub fn new(max_verif: usize) -> Self { // TODO: Investigate if it is safe to use a random point as aux generator that - // is choosen by the prover. If this is unsafe, we will need to update the + // is chosen by the prover. If this is unsafe, we will need to update the // EccChip to calculate an aux generator using the challange API. // https://github.com/privacy-scaling-explorations/halo2wrong/issues/53 let mut rng = ChaCha20Rng::seed_from_u64(0); @@ -81,7 +84,7 @@ impl SignVerifyChip { // the tx circuit with max_txs=1. For example: // `RUST_LOG=debug RUST_BACKTRACE=1 cargo test tx_circuit_1tx_1max_tx --release // --all-features -- --nocapture` - // The value rows_range_chip_table has been optained by patching the halo2 + // The value rows_range_chip_table has been obtained by patching the halo2 // library to report the number of rows used in the range chip table // region. TODO: Figure out a way to get these numbers automatically. let rows_range_chip_table = 295188; @@ -118,7 +121,6 @@ pub(crate) struct SignVerifyConfig { main_gate_config: MainGateConfig, range_config: RangeConfig, // RLC - q_rlc_evm_word: Selector, q_rlc_keccak_input: Selector, rlc: Column, // Keccak @@ -144,19 +146,10 @@ impl SignVerifyConfig { ); // RLC - let q_rlc_evm_word = meta.selector(); let q_rlc_keccak_input = meta.selector(); let rlc = meta.advice_column_in(SecondPhase); meta.enable_equality(rlc); - Self::configure_rlc( - meta, - "evm_word_rlc", - main_gate_config.clone(), - q_rlc_evm_word, - rlc, - challenges.evm_word(), - ); Self::configure_rlc( meta, "keccak_input_rlc", @@ -172,28 +165,29 @@ impl SignVerifyConfig { let q_keccak = meta.complex_selector(); meta.lookup_any("keccak", |meta| { // When address is 0, we disable the signature verification by using a dummy pk, - // msg_hash and signature which is not constrainted to match msg_hash_rlc nor - // the address. + // msg_hash and signature which is not constrained to match msg_hash nor the address. // Layout: - // | q_keccak | a | rlc | - // | -------- | --------------- | ----------- | - // | 1 | is_address_zero | pk_rlc | - // | | | pk_hash_rlc | + // | q_keccak | a | b | c | rlc | + // | -------- | --------------- |--------- | --------- | ------- | + // | 1 | is_addr_zero | word_lo | word_hi | pk_rlc | let q_keccak = meta.query_selector(q_keccak); let is_address_zero = meta.query_advice(main_gate_config.advices()[0], Rotation::cur()); let is_enable = q_keccak * not::expr(is_address_zero); - + let word_lo = meta.query_advice(main_gate_config.advices()[1], Rotation::cur()); + let word_hi = meta.query_advice(main_gate_config.advices()[2], Rotation::cur()); let input = [ is_enable.clone(), is_enable.clone() * meta.query_advice(rlc, Rotation::cur()), is_enable.clone() * 64usize.expr(), - is_enable * meta.query_advice(rlc, Rotation::next()), + is_enable.clone() * word_lo, + is_enable * word_hi, ]; let table = [ keccak_table.is_enabled, keccak_table.input_rlc, keccak_table.input_len, - keccak_table.output_rlc, + keccak_table.output.lo(), + keccak_table.output.hi(), ] .map(|column| meta.query_advice(column, Rotation::cur())); @@ -204,7 +198,6 @@ impl SignVerifyConfig { range_config, main_gate_config, keccak_table, - q_rlc_evm_word, q_rlc_keccak_input, rlc, q_keccak, @@ -307,8 +300,8 @@ pub(crate) struct AssignedECDSA { #[derive(Debug)] pub(crate) struct AssignedSignatureVerify { - pub(crate) address: AssignedValue, - pub(crate) msg_hash_rlc: AssignedValue, + pub(crate) address: Word>, + pub(crate) msg_hash: Word>, } // Return an array of bytes that corresponds to the little endian representation @@ -336,7 +329,7 @@ fn integer_to_bytes_le( } /// Helper structure pass around references to all the chips required for an -/// ECDSA veficication. +/// ECDSA verification. struct ChipsRef<'a, F: Field, const NUMBER_OF_LIMBS: usize, const BIT_LEN_LIMB: usize> { main_gate: &'a MainGate, range_chip: &'a RangeChip, @@ -466,7 +459,7 @@ impl SignVerifyChip { ctx: &mut RegionCtx, is_address_zero: &AssignedCell, pk_rlc: &AssignedCell, - pk_hash_rlc: &AssignedCell, + pk_hash: &Word>, ) -> Result<(), Error> { let copy = |ctx: &mut RegionCtx, name, column, assigned: &AssignedCell| { let copied = ctx.assign_advice(|| name, column, assigned.value().copied())?; @@ -474,12 +467,26 @@ impl SignVerifyChip { Ok::<_, Error>(()) }; - let a = config.main_gate_config.advices()[0]; ctx.enable(config.q_keccak)?; - copy(ctx, "is_address_zero", a, is_address_zero)?; + copy( + ctx, + "is_address_zero", + config.main_gate_config.advices()[0], + is_address_zero, + )?; copy(ctx, "pk_rlc", config.rlc, pk_rlc)?; - ctx.next(); - copy(ctx, "pk_hash_rlc", config.rlc, pk_hash_rlc)?; + copy( + ctx, + "pk_hash_lo", + config.main_gate_config.advices()[1], + &pk_hash.lo(), + )?; + copy( + ctx, + "pk_hash_hi", + config.main_gate_config.advices()[2], + &pk_hash.hi(), + )?; ctx.next(); Ok(()) @@ -496,6 +503,7 @@ impl SignVerifyChip { challenges: &Challenges>, ) -> Result, Error> { let main_gate = chips.main_gate; + let range_chip = chips.range_chip; let (padding, sign_data) = match sign_data { Some(sign_data) => (false, sign_data.clone()), @@ -504,60 +512,97 @@ impl SignVerifyChip { let pk_le = pk_bytes_le(&sign_data.pk); let pk_be = pk_bytes_swap_endianness(&pk_le); - let pk_hash = (!padding) - .then(|| keccak256(&pk_be)) - .unwrap_or_default() - .map(|byte| Value::known(F::from(byte as u64))); - let pk_hash_hi = pk_hash[..12].to_vec(); + let mut pk_hash = (!padding).then(|| keccak256(&pk_be)).unwrap_or_default(); + pk_hash.reverse(); + + let powers_of_256 = iter::successors(Some(F::ONE), |coeff| Some(F::from(256) * coeff)) + .take(16) + .collect_vec(); + // Ref. spec SignVerifyChip 2. Verify that the first 20 bytes of the // pub_key_hash equal the address - let (address, pk_hash_lo) = { - let powers_of_256 = iter::successors(Some(F::ONE), |coeff| Some(F::from(256) * coeff)) - .take(20) - .collect_vec(); - let terms = pk_hash[12..] - .iter() - .zip(powers_of_256.into_iter().rev()) - .map(|(byte, coeff)| maingate::Term::Unassigned(*byte, coeff)) - .collect_vec(); - let (address, pk_hash_lo) = main_gate.decompose(ctx, &terms, F::ZERO, |_, _| Ok(()))?; + let (address_cells, pk_hash_cells) = { + // Diagram of byte decomposition of little-endian pk_hash, and how address is built + // from it: + // + // byte 0 15 16 20 21 32 + // [ address_lo ] [ address_hi ] [ ] + // [ pk_hash_lo ] [ pk_hash_hi ] + + let pk_hash_lo_bytes = &pk_hash[..16]; + let pk_hash_hi_bytes = &pk_hash[16..]; + let pk_hash_lo = from_bytes::value::(pk_hash_lo_bytes); + let pk_hash_hi = from_bytes::value::(pk_hash_hi_bytes); + // Assign all bytes of pk_hash to cells which are range constrained to be 8 bits. Then + // constrain the lower 16 cell bytes to build the lo cell, and the higher 16 bytes to + // build the hi cell. + let (pk_hash_cell_lo, pk_hash_lo_cell_bytes) = + range_chip.decompose(ctx, Value::known(pk_hash_lo), 8, 128)?; + let (pk_hash_cell_hi, pk_hash_hi_cell_bytes) = + range_chip.decompose(ctx, Value::known(pk_hash_hi), 8, 128)?; + + // Take the 20 lowest assigned byte cells of pk_hash and constrain them to build + // address. From the lower 16 build the lo cell, and from the higher 4 build the hi + // cell. + let (address_cell_lo, _) = main_gate.decompose( + ctx, + &pk_hash_lo_cell_bytes + .iter() + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + let (address_cell_hi, _) = main_gate.decompose( + ctx, + &pk_hash_hi_cell_bytes + .iter() + .take(N_BYTES_ACCOUNT_ADDRESS - 16) + .zip(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; ( - address, - pk_hash_lo - .into_iter() - .zip(pk_hash[12..].iter()) - .map(|(assigned, byte)| Term::assigned(assigned.cell(), *byte)) - .collect_vec(), + Word::new([address_cell_lo, address_cell_hi]), + Word::new([pk_hash_cell_lo, pk_hash_cell_hi]), ) }; - let is_address_zero = main_gate.is_zero(ctx, &address)?; + + let iz_zero_hi = main_gate.is_zero(ctx, &address_cells.hi())?; + let iz_zero_lo = main_gate.is_zero(ctx, &address_cells.lo())?; + let is_address_zero = main_gate.and(ctx, &iz_zero_lo, &iz_zero_hi)?; // Ref. spec SignVerifyChip 3. Verify that the signed message in the ecdsa_chip - // with RLC encoding corresponds to msg_hash_rlc - let msg_hash_rlc = { - let zero = main_gate.assign_constant(ctx, F::ZERO)?; - let assigned_msg_hash_le = assigned_ecdsa - .msg_hash_le - .iter() - .map(|byte| main_gate.select(ctx, &zero, byte, &is_address_zero)) - .collect::, _>>()?; - let msg_hash_le = (!padding) - .then(|| sign_data.msg_hash.to_bytes()) - .unwrap_or_default() - .map(|byte| Value::known(F::from(byte as u64))); - self.assign_rlc_le( - config, + // corresponds to msg_hash + let msg_hash_cells = { + let msg_hash_lo_cell_bytes = &assigned_ecdsa.msg_hash_le[..16]; + let msg_hash_hi_cell_bytes = &assigned_ecdsa.msg_hash_le[16..]; + let (msg_hash_cell_lo, _) = main_gate.decompose( ctx, - chips, - "msg_hash", - config.q_rlc_evm_word, - challenges.evm_word(), - assigned_msg_hash_le + &msg_hash_lo_cell_bytes .iter() - .zip(msg_hash_le) - .map(|(assigned, byte)| Term::assigned(assigned.cell(), byte)), - )? + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + let (msg_hash_cell_hi, _) = main_gate.decompose( + ctx, + &msg_hash_hi_cell_bytes + .iter() + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + + Word::new([msg_hash_cell_lo, msg_hash_cell_hi]) }; let pk_rlc = { @@ -581,23 +626,10 @@ impl SignVerifyChip { )? }; - let pk_hash_rlc = self.assign_rlc_le( - config, - ctx, - chips, - "pk_hash_rlc", - config.q_rlc_evm_word, - challenges.evm_word(), - iter::empty() - .chain(pk_hash_lo.into_iter().rev()) - .chain(pk_hash_hi.into_iter().rev().map(Term::unassigned)), - )?; - - self.enable_keccak_lookup(config, ctx, &is_address_zero, &pk_rlc, &pk_hash_rlc)?; - + self.enable_keccak_lookup(config, ctx, &is_address_zero, &pk_rlc, &pk_hash_cells)?; Ok(AssignedSignatureVerify { - address, - msg_hash_rlc, + address: address_cells, + msg_hash: msg_hash_cells, }) } @@ -711,7 +743,6 @@ mod sign_verify_tests { }, plonk::Circuit, }; - use pretty_assertions::assert_eq; use rand::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -800,7 +831,7 @@ mod sign_verify_tests { Ok(prover) => prover, Err(e) => panic!("{:#?}", e), }; - assert_eq!(prover.verify(), Ok(())); + prover.assert_satisfied_par(); } // Generate a test key pair