From cde6f902399470ea2642a16da2a7bbdf303d443c Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 20:19:37 -0700 Subject: [PATCH 01/33] add row counting interface for keccak --- .../src/keccak_circuit/keccak_packed_multi.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 544041cbac..5cef534076 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -12,12 +12,20 @@ use std::{env::var, vec}; const MAX_DEGREE: usize = 9; -pub(crate) fn get_num_rows_per_round() -> usize { +/// Obtain the rows required for 1 iteration of f-box's inner round +/// function (consisting of 5 phases) within Keccak circuit +pub fn get_num_rows_per_round() -> usize { var("KECCAK_ROWS") .unwrap_or_else(|_| format!("{DEFAULT_KECCAK_ROWS}")) .parse() .expect("Cannot parse KECCAK_ROWS env var as usize") } +/// Obtain the rows required for 1 iteration of the f-box +/// function (consisting of nr = 12 + 2*l inner rounds) +/// within Keccak circuit +pub fn get_num_rows_per_update() -> usize { + get_num_rows_per_round() *(NUM_ROUNDS + 1) +} pub(crate) fn keccak_unusable_rows() -> usize { const UNUSABLE_ROWS_BY_KECCAK_ROWS: [usize; 24] = [ From 018d97b15664967f63bceb39f9d92d798e76352f Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 20:34:41 -0700 Subject: [PATCH 02/33] add class level capacity calculator for keccak --- zkevm-circuits/src/keccak_circuit.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/zkevm-circuits/src/keccak_circuit.rs b/zkevm-circuits/src/keccak_circuit.rs index 60e39855af..8eb885dfe6 100644 --- a/zkevm-circuits/src/keccak_circuit.rs +++ b/zkevm-circuits/src/keccak_circuit.rs @@ -1073,6 +1073,17 @@ impl KeccakCircuit { } } + /// The number of keccak_f's that can be done for + /// a particular row number depending on current Keccak params + pub fn capacity_for_row(num_rows: usize) -> Option { + if num_rows > 0 { + // Subtract two for unusable rows + Some(num_rows / ((NUM_ROUNDS + 1) * get_num_rows_per_round()) - 2) + } else { + None + } + } + /// Sets the witness using the data to be hashed pub(crate) fn generate_witness(&self, challenges: Challenges>) -> Vec> { multi_keccak(self.inputs.as_slice(), challenges, self.capacity()) From 3b352ab97220dd2bf525dc18faf3ecf66a85d864 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 20:48:41 -0700 Subject: [PATCH 03/33] remove f capacity from core --- aggregator/src/core.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index 34c3f2f2f9..6e4ef5c02b 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -20,7 +20,7 @@ use snark_verifier_sdk::{ Snark, }; use zkevm_circuits::{ - keccak_circuit::{keccak_packed_multi::multi_keccak, KeccakCircuitConfig}, + keccak_circuit::{keccak_packed_multi::multi_keccak, KeccakCircuitConfig, KeccakCircuit}, table::LookupTable, util::Challenges, }; @@ -164,7 +164,7 @@ pub(crate) fn extract_hash_cells( preimages: &[Vec], ) -> Result { let mut is_first_time = true; - let num_rows = 1 << LOG_DEGREE; + let keccak_capacity = KeccakCircuit::::capacity_for_row(1 << LOG_DEGREE); let timer = start_timer!(|| ("multi keccak").to_string()); // preimages consists of the following parts @@ -181,7 +181,7 @@ pub(crate) fn extract_hash_cells( // (3) batchDataHash preimage = // (chunk[0].dataHash || ... || chunk[k-1].dataHash) // each part of the preimage is mapped to image by Keccak256 - let witness = multi_keccak(preimages, challenges, keccak_round_capacity(num_rows)) + let witness = multi_keccak(preimages, challenges, keccak_capacity) .map_err(|e| Error::AssertionFailure(format!("multi keccak assignment failed: {e:?}")))?; end_timer!(timer); From 47f6e1f51036a258c6e5cfd4927589f74ddf49c5 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 20:56:02 -0700 Subject: [PATCH 04/33] remove capacity calculator in aggregator util --- aggregator/src/core.rs | 2 +- aggregator/src/tests/rlc/dynamic_hashes.rs | 5 ++--- aggregator/src/util.rs | 16 ---------------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index 6e4ef5c02b..e9ea00802c 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -31,7 +31,7 @@ use crate::{ MAX_KECCAK_ROUNDS, ROWS_PER_ROUND, }, util::{ - assert_conditional_equal, assert_equal, assert_exist, get_indices, keccak_round_capacity, + assert_conditional_equal, assert_equal, assert_exist, get_indices, parse_hash_digest_cells, parse_hash_preimage_cells, parse_pi_hash_rlc_cells, }, AggregationConfig, RlcConfig, CHUNK_DATA_HASH_INDEX, POST_STATE_ROOT_INDEX, diff --git a/aggregator/src/tests/rlc/dynamic_hashes.rs b/aggregator/src/tests/rlc/dynamic_hashes.rs index 85abaa5ff7..3ca4ea9c32 100644 --- a/aggregator/src/tests/rlc/dynamic_hashes.rs +++ b/aggregator/src/tests/rlc/dynamic_hashes.rs @@ -9,7 +9,7 @@ use snark_verifier::loader::halo2::halo2_ecc::halo2_base::utils::fs::gen_srs; use snark_verifier_sdk::{gen_pk, gen_snark_shplonk, verify_snark_shplonk, CircuitExt}; use zkevm_circuits::{ keccak_circuit::{ - keccak_packed_multi::multi_keccak, KeccakCircuitConfig, KeccakCircuitConfigArgs, + keccak_packed_multi::multi_keccak, KeccakCircuitConfig, KeccakCircuitConfigArgs, KeccakCircuit }, table::{KeccakTable, LookupTable}, util::{Challenges, SubCircuitConfig}, @@ -18,7 +18,6 @@ use zkevm_circuits::{ use crate::{ aggregation::RlcConfig, constants::{LOG_DEGREE, ROWS_PER_ROUND}, - util::keccak_round_capacity, }; #[derive(Default, Debug, Clone)] @@ -95,7 +94,7 @@ impl Circuit for DynamicHashCircuit { let witness = multi_keccak( &[hash_preimage.clone()], challenge, - keccak_round_capacity(1 << LOG_DEGREE), + KeccakCircuit::::capacity_for_row(1 << LOG_DEGREE), ) .unwrap(); diff --git a/aggregator/src/util.rs b/aggregator/src/util.rs index 7181cb2b99..e2978c8277 100644 --- a/aggregator/src/util.rs +++ b/aggregator/src/util.rs @@ -18,22 +18,6 @@ use crate::{ use std::env::var; -pub(crate) fn keccak_round_capacity(num_rows: usize) -> Option { - if num_rows > 0 { - // Subtract two for unusable rows - Some(num_rows / ((NUM_ROUNDS + 1) * get_num_rows_per_round()) - 2) - } else { - None - } -} - -pub(crate) fn get_num_rows_per_round() -> usize { - var("KECCAK_ROWS") - .unwrap_or_else(|_| format!("{DEFAULT_KECCAK_ROWS}")) - .parse() - .expect("Cannot parse KECCAK_ROWS env var as usize") -} - /// Return /// - the indices of the rows that contain the input preimages /// - the indices of the rows that contain the output digest From 1bda8a887f697011c7b9054611cd3a24c42a4d82 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 20:56:53 -0700 Subject: [PATCH 05/33] remove unnecessary imports --- aggregator/src/util.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/aggregator/src/util.rs b/aggregator/src/util.rs index e2978c8277..e5f0ce5261 100644 --- a/aggregator/src/util.rs +++ b/aggregator/src/util.rs @@ -13,11 +13,8 @@ use snark_verifier::loader::halo2::halo2_ecc::halo2_base::{ use crate::{ aggregation::RlcConfig, constants::{DIGEST_LEN, INPUT_LEN_PER_ROUND, MAX_AGG_SNARKS}, - DEFAULT_KECCAK_ROWS, NUM_ROUNDS, }; -use std::env::var; - /// Return /// - the indices of the rows that contain the input preimages /// - the indices of the rows that contain the output digest From 4ffbb43d93a0449af477b5b07197607f3f97c83d Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 21:14:53 -0700 Subject: [PATCH 06/33] replace max keccak round in core --- aggregator/src/core.rs | 8 +++++--- aggregator/src/util.rs | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index e9ea00802c..22e5578cdc 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -20,7 +20,7 @@ use snark_verifier_sdk::{ Snark, }; use zkevm_circuits::{ - keccak_circuit::{keccak_packed_multi::multi_keccak, KeccakCircuitConfig, KeccakCircuit}, + keccak_circuit::{keccak_packed_multi::{multi_keccak, self}, KeccakCircuitConfig, KeccakCircuit}, table::LookupTable, util::Challenges, }; @@ -31,7 +31,7 @@ use crate::{ MAX_KECCAK_ROUNDS, ROWS_PER_ROUND, }, util::{ - assert_conditional_equal, assert_equal, assert_exist, get_indices, + assert_conditional_equal, assert_equal, assert_exist, get_indices, get_max_keccak_updates, parse_hash_digest_cells, parse_hash_preimage_cells, parse_pi_hash_rlc_cells, }, AggregationConfig, RlcConfig, CHUNK_DATA_HASH_INDEX, POST_STATE_ROOT_INDEX, @@ -165,6 +165,8 @@ pub(crate) fn extract_hash_cells( ) -> Result { let mut is_first_time = true; let keccak_capacity = KeccakCircuit::::capacity_for_row(1 << LOG_DEGREE); + let max_keccak_updates = get_max_keccak_updates(MAX_AGG_SNARKS); + let keccak_f_rows = keccak_packed_multi::get_num_rows_per_update(); let timer = start_timer!(|| ("multi keccak").to_string()); // preimages consists of the following parts @@ -229,7 +231,7 @@ pub(crate) fn extract_hash_cells( hash_output_cells.push(row.last().unwrap().clone()); // sage unwrap cur_digest_index = digest_indices_iter.next(); } - if offset % ROWS_PER_ROUND == 0 && offset / ROWS_PER_ROUND <= MAX_KECCAK_ROUNDS + if offset % keccak_f_rows == 0 && offset / keccak_f_rows <= max_keccak_updates { // first column is is_final is_final_cells.push(row[0].clone()); diff --git a/aggregator/src/util.rs b/aggregator/src/util.rs index e5f0ce5261..3250471b35 100644 --- a/aggregator/src/util.rs +++ b/aggregator/src/util.rs @@ -15,6 +15,18 @@ use crate::{ constants::{DIGEST_LEN, INPUT_LEN_PER_ROUND, MAX_AGG_SNARKS}, }; +// Calculates the maximum keccak updates (1 absorb, or 1 f-box invoke) +// needed for the number of snarks +pub(crate) fn get_max_keccak_updates(max_snarks: usize) -> usize { + let pi_rounds = 2; + let chunk_hash_rounds = 2 * max_snarks; + + let data_hash_rounds = (32 * max_snarks) / INPUT_LEN_PER_ROUND; + let padding_round = if data_hash_rounds * INPUT_LEN_PER_ROUND < 32 * max_snarks { 1 } else { 0 }; + + pi_rounds + chunk_hash_rounds + data_hash_rounds + padding_round +} + /// Return /// - the indices of the rows that contain the input preimages /// - the indices of the rows that contain the output digest From c4d40683a831e8892e707f316e189ebeb161163e Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 21:15:38 -0700 Subject: [PATCH 07/33] replace reference for max keccak --- aggregator/src/core.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index 22e5578cdc..2a7727279a 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -28,7 +28,6 @@ use zkevm_circuits::{ use crate::{ constants::{ CHAIN_ID_LEN, DIGEST_LEN, INPUT_LEN_PER_ROUND, LOG_DEGREE, MAX_AGG_SNARKS, - MAX_KECCAK_ROUNDS, ROWS_PER_ROUND, }, util::{ assert_conditional_equal, assert_equal, assert_exist, get_indices, get_max_keccak_updates, @@ -249,7 +248,7 @@ pub(crate) fn extract_hash_cells( // sanity assert_eq!( hash_input_cells.len(), - MAX_KECCAK_ROUNDS * INPUT_LEN_PER_ROUND + max_keccak_updates * INPUT_LEN_PER_ROUND ); assert_eq!(hash_output_cells.len(), (MAX_AGG_SNARKS + 4) * DIGEST_LEN); From 492aac13a7d61b994e113f50d938a91bd7db060f Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 21:17:19 -0700 Subject: [PATCH 08/33] remove unnecessary keccak imports and constants --- aggregator/src/constants.rs | 9 --------- aggregator/src/tests/rlc/dynamic_hashes.rs | 7 ++++--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/aggregator/src/constants.rs b/aggregator/src/constants.rs index 01a00acf0a..8f0204832c 100644 --- a/aggregator/src/constants.rs +++ b/aggregator/src/constants.rs @@ -11,15 +11,6 @@ pub(crate) const DIGEST_LEN: usize = 32; /// Input length per round pub(crate) const INPUT_LEN_PER_ROUND: usize = 136; -// Each round requires (NUM_ROUNDS+1) * DEFAULT_KECCAK_ROWS = 300 rows. -// This library is hard coded for this parameter. -// Modifying the following parameters may result into bugs. -// Adopted from keccak circuit -pub(crate) const DEFAULT_KECCAK_ROWS: usize = 12; -// Adopted from keccak circuit -pub(crate) const NUM_ROUNDS: usize = 24; -pub(crate) const ROWS_PER_ROUND: usize = (NUM_ROUNDS + 1) * DEFAULT_KECCAK_ROWS; - // TODO(ZZ): update to the right degree #[allow(dead_code)] pub(crate) const LOG_DEGREE: u32 = 19; diff --git a/aggregator/src/tests/rlc/dynamic_hashes.rs b/aggregator/src/tests/rlc/dynamic_hashes.rs index 3ca4ea9c32..f23f27a0db 100644 --- a/aggregator/src/tests/rlc/dynamic_hashes.rs +++ b/aggregator/src/tests/rlc/dynamic_hashes.rs @@ -9,7 +9,7 @@ use snark_verifier::loader::halo2::halo2_ecc::halo2_base::utils::fs::gen_srs; use snark_verifier_sdk::{gen_pk, gen_snark_shplonk, verify_snark_shplonk, CircuitExt}; use zkevm_circuits::{ keccak_circuit::{ - keccak_packed_multi::multi_keccak, KeccakCircuitConfig, KeccakCircuitConfigArgs, KeccakCircuit + keccak_packed_multi::{multi_keccak, self}, KeccakCircuitConfig, KeccakCircuitConfigArgs, KeccakCircuit }, table::{KeccakTable, LookupTable}, util::{Challenges, SubCircuitConfig}, @@ -17,7 +17,7 @@ use zkevm_circuits::{ use crate::{ aggregation::RlcConfig, - constants::{LOG_DEGREE, ROWS_PER_ROUND}, + constants::LOG_DEGREE, }; #[derive(Default, Debug, Clone)] @@ -77,6 +77,7 @@ impl Circuit for DynamicHashCircuit { mut layouter: impl Layouter, ) -> Result<(), Error> { let (config, challenges) = config; + let keccak_f_rows = keccak_packed_multi::get_num_rows_per_update(); config .keccak_circuit_config @@ -112,7 +113,7 @@ impl Circuit for DynamicHashCircuit { config .keccak_circuit_config .set_row(&mut region, offset, keccak_row)?; - if offset % ROWS_PER_ROUND == 0 && data_rlc_cells.len() < 4 { + if offset % keccak_f_rows == 0 && data_rlc_cells.len() < 4 { // second element is data rlc data_rlc_cells.push(row[1].clone()); } From f5468d4d5ade6e211459c575ca48762c12ba4a0b Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 21:24:04 -0700 Subject: [PATCH 09/33] remove max keccak constant --- aggregator/src/constants.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/aggregator/src/constants.rs b/aggregator/src/constants.rs index 8f0204832c..14848cc685 100644 --- a/aggregator/src/constants.rs +++ b/aggregator/src/constants.rs @@ -49,9 +49,3 @@ pub(crate) const BITS: usize = 88; /// will be padded. // TODO: update me(?) pub const MAX_AGG_SNARKS: usize = 10; - -/// The number of keccak rounds is the sum of -/// - batch public input hash: 2 rounds -/// - chunk's public input hash: 2 * MAX_AGG_SNARKS -/// - batch data hash: (32 * MAX_AGG_SNARKS)/136 = 3 -pub(crate) const MAX_KECCAK_ROUNDS: usize = 2 * MAX_AGG_SNARKS + 5; From 2b903bcb5796e485f282aa9b40b9d1dcb17ce022 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 9 Aug 2023 21:56:58 -0700 Subject: [PATCH 10/33] remove constants in hash cell parsing --- aggregator/src/util.rs | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/aggregator/src/util.rs b/aggregator/src/util.rs index 3250471b35..27be87a5f7 100644 --- a/aggregator/src/util.rs +++ b/aggregator/src/util.rs @@ -9,7 +9,7 @@ use snark_verifier::loader::halo2::halo2_ecc::halo2_base::{ gates::{flex_gate::FlexGateConfig, GateInstructions}, AssignedValue, Context, }; - +use zkevm_circuits::keccak_circuit::keccak_packed_multi::{get_num_rows_per_round, get_num_rows_per_update}; use crate::{ aggregation::RlcConfig, constants::{DIGEST_LEN, INPUT_LEN_PER_ROUND, MAX_AGG_SNARKS}, @@ -20,11 +20,15 @@ use crate::{ pub(crate) fn get_max_keccak_updates(max_snarks: usize) -> usize { let pi_rounds = 2; let chunk_hash_rounds = 2 * max_snarks; - + let data_hash_rounds = get_data_hash_keccak_updates(max_snarks); + + pi_rounds + chunk_hash_rounds + data_hash_rounds +} +pub(crate) fn get_data_hash_keccak_updates(max_snarks: usize) -> usize { let data_hash_rounds = (32 * max_snarks) / INPUT_LEN_PER_ROUND; let padding_round = if data_hash_rounds * INPUT_LEN_PER_ROUND < 32 * max_snarks { 1 } else { 0 }; - pi_rounds + chunk_hash_rounds + data_hash_rounds + padding_round + data_hash_rounds + padding_round } /// Return @@ -35,9 +39,14 @@ pub(crate) fn get_indices(preimages: &[Vec]) -> (Vec, Vec) { let mut digest_indices = vec![]; let mut round_ctr = 0; + let keccak_f_rows = get_num_rows_per_update(); + let inner_round_rows = get_num_rows_per_round(); + for preimage in preimages.iter().take(MAX_AGG_SNARKS + 1) { // 136 = 17 * 8 is the size in bytes of each // input chunk that can be processed by Keccak circuit using absorb + + // For example, if num_rows_per_inner_round for Keccak is 12, then // each chunk of size 136 needs 300 Keccak circuit rows to prove // which consists of 12 Keccak rows for each of 24 + 1 Keccak circuit rounds // digest only happens at the end of the last input chunk with @@ -46,17 +55,21 @@ pub(crate) fn get_indices(preimages: &[Vec]) -> (Vec, Vec) { let mut preimage_padded = preimage.clone(); preimage_padded.resize(INPUT_LEN_PER_ROUND * num_rounds, 0); for (i, round) in preimage_padded.chunks(INPUT_LEN_PER_ROUND).enumerate() { + let f_round_offset = round_ctr * keccak_f_rows; // indices for preimages for (j, _chunk) in round.chunks(8).into_iter().enumerate() { + let inner_offset = f_round_offset + (j + 1) * inner_round_rows; for k in 0..8 { - preimage_indices.push(round_ctr * 300 + j * 12 + k + 12) + preimage_indices.push(inner_offset + k); } } // indices for digests if i == num_rounds - 1 { for j in 0..4 { + let inner_offset = + f_round_offset + j * inner_round_rows + (keccak_f_rows - inner_round_rows * (DIGEST_LEN / 8)); for k in 0..8 { - digest_indices.push(round_ctr * 300 + j * 12 + k + 252) + digest_indices.push(inner_offset + k); } } } @@ -64,20 +77,23 @@ pub(crate) fn get_indices(preimages: &[Vec]) -> (Vec, Vec) { } } // last hash is for data_hash and has various length, so we output all the possible cells - for _i in 0..3 { + for _i in 0..get_data_hash_keccak_updates(MAX_AGG_SNARKS) { for (j, _) in (0..INPUT_LEN_PER_ROUND) .into_iter() .chunks(8) .into_iter() .enumerate() { + let inner_offset = round_ctr * keccak_f_rows + (j + 1) * inner_round_rows; for k in 0..8 { - preimage_indices.push(round_ctr * 300 + j * 12 + k + 12) + preimage_indices.push(inner_offset + k); } } for j in 0..4 { + let inner_offset = + round_ctr * keccak_f_rows + j * inner_round_rows + (keccak_f_rows - inner_round_rows * (DIGEST_LEN / 8)); for k in 0..8 { - digest_indices.push(round_ctr * 300 + j * 12 + k + 252) + digest_indices.push(inner_offset + k); } } round_ctr += 1; From 54912a5fe4ab9485f68847c643293f7dc50feeb9 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Thu, 10 Aug 2023 00:04:24 -0700 Subject: [PATCH 11/33] remove constant column sanity check --- aggregator/src/aggregation/config.rs | 10 +--------- zkevm-circuits/src/keccak_circuit.rs | 4 ++++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/aggregator/src/aggregation/config.rs b/aggregator/src/aggregation/config.rs index 43ffe781ac..051fb84dff 100644 --- a/aggregator/src/aggregation/config.rs +++ b/aggregator/src/aggregation/config.rs @@ -83,18 +83,10 @@ impl AggregationConfig { params.degree as usize, ); - // The current code base is hardcoded for KeccakCircuit configured - // with 300 rows and 87 columns per hash call. let columns = keccak_circuit_config.cell_manager.columns(); - assert_eq!( - columns.len(), - 87, - "cell manager configuration does not match the hard coded setup" - ); - // enabling equality for preimage column - meta.enable_equality(columns[6].advice); + meta.enable_equality(columns[keccak_circuit_config.preimage_column].advice); // enable equality for the digest column meta.enable_equality(columns.last().unwrap().advice); // enable equality for the data RLC column diff --git a/zkevm-circuits/src/keccak_circuit.rs b/zkevm-circuits/src/keccak_circuit.rs index 8eb885dfe6..ae68cf50a1 100644 --- a/zkevm-circuits/src/keccak_circuit.rs +++ b/zkevm-circuits/src/keccak_circuit.rs @@ -65,6 +65,8 @@ pub struct KeccakCircuitConfig { normalize_6: [TableColumn; 2], chi_base_table: [TableColumn; 2], pack_table: [TableColumn; 2], + /// The column for enabling copy constraints in aggregator + pub preimage_column: usize, _marker: PhantomData, } @@ -185,6 +187,7 @@ impl SubCircuitConfig for KeccakCircuitConfig { log::debug!("- Post absorb:"); log::debug!("Lookups: {}", lookup_counter); log::debug!("Columns: {}", cell_manager.get_width()); + let preimage_column: usize = cell_manager.get_width() + 1; total_lookup_counter += lookup_counter; // Process inputs. @@ -863,6 +866,7 @@ impl SubCircuitConfig for KeccakCircuitConfig { normalize_6, chi_base_table, pack_table, + preimage_column, _marker: PhantomData, } } From 976ba927cf96b2af439f630dd0686282a27b9eda Mon Sep 17 00:00:00 2001 From: darth-cy Date: Sun, 13 Aug 2023 07:51:24 -0700 Subject: [PATCH 12/33] add state column usage log --- zkevm-circuits/src/keccak_circuit.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zkevm-circuits/src/keccak_circuit.rs b/zkevm-circuits/src/keccak_circuit.rs index ae68cf50a1..3661d1c26f 100644 --- a/zkevm-circuits/src/keccak_circuit.rs +++ b/zkevm-circuits/src/keccak_circuit.rs @@ -140,6 +140,8 @@ impl SubCircuitConfig for KeccakCircuitConfig { s_next[i][j] = cell.at_offset(meta, get_num_rows_per_round() as i32).expr(); } } + log::debug!("- Post states:"); + log::debug!("Columns: {}", cell_manager.get_width()); // Absorb data let absorb_from = cell_manager.query_cell(meta); let absorb_data = cell_manager.query_cell(meta); From 024ee4d43f4e9db8c5e569f5f95223aaf9391123 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Sun, 13 Aug 2023 19:38:44 -0700 Subject: [PATCH 13/33] adjust input bytes column --- aggregator/src/core.rs | 3 +-- .../src/keccak_circuit/keccak_packed_multi.rs | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index 2a7727279a..1f8464404d 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -221,8 +221,7 @@ pub(crate) fn extract_hash_cells( let row = keccak_config.set_row(&mut region, offset, keccak_row)?; if cur_preimage_index.is_some() && *cur_preimage_index.unwrap() == offset { - // 10-th column is Keccak input in Keccak circuit - hash_input_cells.push(row[10].clone()); + hash_input_cells.push(row[keccak_packed_multi::get_input_bytes_col_cell_manager() + 4].clone()); cur_preimage_index = preimage_indices_iter.next(); } if cur_digest_index.is_some() && *cur_digest_index.unwrap() == offset { diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 5cef534076..6bf4deb805 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -26,6 +26,29 @@ pub fn get_num_rows_per_round() -> usize { pub fn get_num_rows_per_update() -> usize { get_num_rows_per_round() *(NUM_ROUNDS + 1) } +/// Obtain the column position of the hash inputs +/// within cell_manager for an inner round. +/// This value is determined by the number of rows allocated +/// to each inner round and target part_size for u64 +pub fn get_input_bytes_col_cell_manager() -> usize { + let mut col: usize = 0; + let inner_round_num_rows = get_num_rows_per_round(); + + col += 28 / inner_round_num_rows; + if inner_round_num_rows * col < 28 { + col += 1; + } + + let part_size = get_num_bits_per_absorb_lookup(); + let part_length = WordParts::new(part_size, 0, false).parts.len(); + + let mut absorb_parts_col = part_length / inner_round_num_rows; + if inner_round_num_rows * absorb_parts_col < part_length { + absorb_parts_col += 1; + } + + col + absorb_parts_col * 2 + 1 +} pub(crate) fn keccak_unusable_rows() -> usize { const UNUSABLE_ROWS_BY_KECCAK_ROWS: [usize; 24] = [ From c05c3c8f2406656c2dae0d531c0ad089dd6568f8 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Sun, 13 Aug 2023 22:55:54 -0700 Subject: [PATCH 14/33] add long column padding --- zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 6bf4deb805..1bfcbf4913 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -603,6 +603,14 @@ pub(crate) fn keccak( absorb_data.assign(&mut region, 0, absorb_row.absorb); absorb_result.assign(&mut region, 0, absorb_row.result); + // Column padding + if get_num_rows_per_round() > 28 { + for _ in 28..get_num_rows_per_round() { + let padding_cell = cell_manager.query_cell_value(); + padding_cell.assign(&mut region, 0, F::zero()); + } + } + // Absorb cell_manager.start_region(); let part_size = get_num_bits_per_absorb_lookup(); From 7abc84534ad935c5add66e4a05726ec81450a4c1 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Sun, 13 Aug 2023 23:27:34 -0700 Subject: [PATCH 15/33] correct fmt --- aggregator/src/core.rs | 17 ++++++++------ aggregator/src/tests/rlc/dynamic_hashes.rs | 8 +++---- aggregator/src/util.rs | 26 ++++++++++++++-------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index 1f8464404d..f452a7652a 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -20,15 +20,16 @@ use snark_verifier_sdk::{ Snark, }; use zkevm_circuits::{ - keccak_circuit::{keccak_packed_multi::{multi_keccak, self}, KeccakCircuitConfig, KeccakCircuit}, + keccak_circuit::{ + keccak_packed_multi::{self, multi_keccak}, + KeccakCircuit, KeccakCircuitConfig, + }, table::LookupTable, util::Challenges, }; use crate::{ - constants::{ - CHAIN_ID_LEN, DIGEST_LEN, INPUT_LEN_PER_ROUND, LOG_DEGREE, MAX_AGG_SNARKS, - }, + constants::{CHAIN_ID_LEN, DIGEST_LEN, INPUT_LEN_PER_ROUND, LOG_DEGREE, MAX_AGG_SNARKS}, util::{ assert_conditional_equal, assert_equal, assert_exist, get_indices, get_max_keccak_updates, parse_hash_digest_cells, parse_hash_preimage_cells, parse_pi_hash_rlc_cells, @@ -221,7 +222,10 @@ pub(crate) fn extract_hash_cells( let row = keccak_config.set_row(&mut region, offset, keccak_row)?; if cur_preimage_index.is_some() && *cur_preimage_index.unwrap() == offset { - hash_input_cells.push(row[keccak_packed_multi::get_input_bytes_col_cell_manager() + 4].clone()); + hash_input_cells.push( + row[keccak_packed_multi::get_input_bytes_col_cell_manager() + 4] + .clone(), + ); cur_preimage_index = preimage_indices_iter.next(); } if cur_digest_index.is_some() && *cur_digest_index.unwrap() == offset { @@ -229,8 +233,7 @@ pub(crate) fn extract_hash_cells( hash_output_cells.push(row.last().unwrap().clone()); // sage unwrap cur_digest_index = digest_indices_iter.next(); } - if offset % keccak_f_rows == 0 && offset / keccak_f_rows <= max_keccak_updates - { + if offset % keccak_f_rows == 0 && offset / keccak_f_rows <= max_keccak_updates { // first column is is_final is_final_cells.push(row[0].clone()); // second column is data rlc diff --git a/aggregator/src/tests/rlc/dynamic_hashes.rs b/aggregator/src/tests/rlc/dynamic_hashes.rs index f23f27a0db..e7d0b46c74 100644 --- a/aggregator/src/tests/rlc/dynamic_hashes.rs +++ b/aggregator/src/tests/rlc/dynamic_hashes.rs @@ -9,16 +9,14 @@ use snark_verifier::loader::halo2::halo2_ecc::halo2_base::utils::fs::gen_srs; use snark_verifier_sdk::{gen_pk, gen_snark_shplonk, verify_snark_shplonk, CircuitExt}; use zkevm_circuits::{ keccak_circuit::{ - keccak_packed_multi::{multi_keccak, self}, KeccakCircuitConfig, KeccakCircuitConfigArgs, KeccakCircuit + keccak_packed_multi::{self, multi_keccak}, + KeccakCircuit, KeccakCircuitConfig, KeccakCircuitConfigArgs, }, table::{KeccakTable, LookupTable}, util::{Challenges, SubCircuitConfig}, }; -use crate::{ - aggregation::RlcConfig, - constants::LOG_DEGREE, -}; +use crate::{aggregation::RlcConfig, constants::LOG_DEGREE}; #[derive(Default, Debug, Clone)] struct DynamicHashCircuit { diff --git a/aggregator/src/util.rs b/aggregator/src/util.rs index 27be87a5f7..9fccc2e056 100644 --- a/aggregator/src/util.rs +++ b/aggregator/src/util.rs @@ -1,3 +1,7 @@ +use crate::{ + aggregation::RlcConfig, + constants::{DIGEST_LEN, INPUT_LEN_PER_ROUND, MAX_AGG_SNARKS}, +}; use eth_types::Field; use halo2_proofs::{ circuit::{AssignedCell, Region}, @@ -9,10 +13,8 @@ use snark_verifier::loader::halo2::halo2_ecc::halo2_base::{ gates::{flex_gate::FlexGateConfig, GateInstructions}, AssignedValue, Context, }; -use zkevm_circuits::keccak_circuit::keccak_packed_multi::{get_num_rows_per_round, get_num_rows_per_update}; -use crate::{ - aggregation::RlcConfig, - constants::{DIGEST_LEN, INPUT_LEN_PER_ROUND, MAX_AGG_SNARKS}, +use zkevm_circuits::keccak_circuit::keccak_packed_multi::{ + get_num_rows_per_round, get_num_rows_per_update, }; // Calculates the maximum keccak updates (1 absorb, or 1 f-box invoke) @@ -26,7 +28,11 @@ pub(crate) fn get_max_keccak_updates(max_snarks: usize) -> usize { } pub(crate) fn get_data_hash_keccak_updates(max_snarks: usize) -> usize { let data_hash_rounds = (32 * max_snarks) / INPUT_LEN_PER_ROUND; - let padding_round = if data_hash_rounds * INPUT_LEN_PER_ROUND < 32 * max_snarks { 1 } else { 0 }; + let padding_round = if data_hash_rounds * INPUT_LEN_PER_ROUND < 32 * max_snarks { + 1 + } else { + 0 + }; data_hash_rounds + padding_round } @@ -66,8 +72,9 @@ pub(crate) fn get_indices(preimages: &[Vec]) -> (Vec, Vec) { // indices for digests if i == num_rounds - 1 { for j in 0..4 { - let inner_offset = - f_round_offset + j * inner_round_rows + (keccak_f_rows - inner_round_rows * (DIGEST_LEN / 8)); + let inner_offset = f_round_offset + + j * inner_round_rows + + (keccak_f_rows - inner_round_rows * (DIGEST_LEN / 8)); for k in 0..8 { digest_indices.push(inner_offset + k); } @@ -90,8 +97,9 @@ pub(crate) fn get_indices(preimages: &[Vec]) -> (Vec, Vec) { } } for j in 0..4 { - let inner_offset = - round_ctr * keccak_f_rows + j * inner_round_rows + (keccak_f_rows - inner_round_rows * (DIGEST_LEN / 8)); + let inner_offset = round_ctr * keccak_f_rows + + j * inner_round_rows + + (keccak_f_rows - inner_round_rows * (DIGEST_LEN / 8)); for k in 0..8 { digest_indices.push(inner_offset + k); } From 918d087f5d7a8ac00191571a2db47f148adf0003 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Mon, 14 Aug 2023 18:12:59 -0700 Subject: [PATCH 16/33] fix fmt --- zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 1bfcbf4913..6b7a0c5073 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -24,7 +24,7 @@ pub fn get_num_rows_per_round() -> usize { /// function (consisting of nr = 12 + 2*l inner rounds) /// within Keccak circuit pub fn get_num_rows_per_update() -> usize { - get_num_rows_per_round() *(NUM_ROUNDS + 1) + get_num_rows_per_round() * (NUM_ROUNDS + 1) } /// Obtain the column position of the hash inputs /// within cell_manager for an inner round. @@ -33,7 +33,7 @@ pub fn get_num_rows_per_update() -> usize { pub fn get_input_bytes_col_cell_manager() -> usize { let mut col: usize = 0; let inner_round_num_rows = get_num_rows_per_round(); - + col += 28 / inner_round_num_rows; if inner_round_num_rows * col < 28 { col += 1; From 0484598548060b8c0782a21d33d3c1f2ba945166 Mon Sep 17 00:00:00 2001 From: Zhuo Zhang Date: Wed, 16 Aug 2023 06:43:34 +0000 Subject: [PATCH 17/33] minor fixes --- zkevm-circuits/src/keccak_circuit.rs | 7 +------ zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 5 ++++- zkevm-circuits/src/keccak_circuit/test.rs | 6 +++--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit.rs b/zkevm-circuits/src/keccak_circuit.rs index 3661d1c26f..8e5a670b9d 100644 --- a/zkevm-circuits/src/keccak_circuit.rs +++ b/zkevm-circuits/src/keccak_circuit.rs @@ -1071,12 +1071,7 @@ impl KeccakCircuit { /// The number of keccak_f's that can be done in this circuit pub fn capacity(&self) -> Option { - if self.num_rows > 0 { - // Subtract two for unusable rows - Some(self.num_rows / ((NUM_ROUNDS + 1) * get_num_rows_per_round()) - 2) - } else { - None - } + Self::capacity_for_row(self.num_rows) } /// The number of keccak_f's that can be done for diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 6b7a0c5073..726f09d3b0 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -55,7 +55,10 @@ pub(crate) fn keccak_unusable_rows() -> usize { 53, 67, 63, 59, 45, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59, 57, 71, 89, 107, 107, 107, 107, 107, ]; - UNUSABLE_ROWS_BY_KECCAK_ROWS[get_num_rows_per_round() - NUM_BYTES_PER_WORD - 1] + UNUSABLE_ROWS_BY_KECCAK_ROWS + .get(get_num_rows_per_round() - NUM_BYTES_PER_WORD - 1) + .cloned() + .unwrap_or(107) } pub(crate) fn get_num_bits_per_absorb_lookup() -> usize { diff --git a/zkevm-circuits/src/keccak_circuit/test.rs b/zkevm-circuits/src/keccak_circuit/test.rs index 64b16e4e74..53fac437fa 100644 --- a/zkevm-circuits/src/keccak_circuit/test.rs +++ b/zkevm-circuits/src/keccak_circuit/test.rs @@ -18,7 +18,7 @@ use super::util::{target_part_sizes, target_part_sizes_rot, WordParts}; #[ignore] #[test] fn serial_keccak_circuit_unusable_rows() { - for keccak_rows in NUM_BYTES_PER_WORD + 1..=32 { + for keccak_rows in NUM_BYTES_PER_WORD + 1..=50 { std::env::set_var("KECCAK_ROWS", format!("{keccak_rows}")); assert_eq!( KeccakCircuit::::unusable_rows(), @@ -45,7 +45,7 @@ fn verify(k: u32, inputs: Vec>, success: bool) { #[test] fn packed_multi_keccak_simple() { - let k = 19; + let k = get_degree(); let inputs = vec![ vec![], (0u8..1).collect::>(), @@ -58,7 +58,7 @@ fn packed_multi_keccak_simple() { #[test] fn variadic_size_check() { - let k = 19; + let k = get_degree(); let num_rows = 2usize.pow(k); // Empty let inputs = vec![]; From 49f75b2cdd2101b5e238ffe59112e66cba88420c Mon Sep 17 00:00:00 2001 From: Zhuo Zhang Date: Wed, 16 Aug 2023 06:58:40 +0000 Subject: [PATCH 18/33] fix --- zkevm-circuits/src/keccak_circuit/test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit/test.rs b/zkevm-circuits/src/keccak_circuit/test.rs index 53fac437fa..e3f6c3e813 100644 --- a/zkevm-circuits/src/keccak_circuit/test.rs +++ b/zkevm-circuits/src/keccak_circuit/test.rs @@ -45,7 +45,7 @@ fn verify(k: u32, inputs: Vec>, success: bool) { #[test] fn packed_multi_keccak_simple() { - let k = get_degree(); + let k = get_degree() as u32; let inputs = vec![ vec![], (0u8..1).collect::>(), @@ -58,7 +58,7 @@ fn packed_multi_keccak_simple() { #[test] fn variadic_size_check() { - let k = get_degree(); + let k = get_degree() as u32; let num_rows = 2usize.pow(k); // Empty let inputs = vec![]; From 80d0c37becde24582d4220a3d298a6fd95b0935c Mon Sep 17 00:00:00 2001 From: xkx Date: Wed, 16 Aug 2023 15:06:33 +0800 Subject: [PATCH 19/33] Fix: allow skipping of L1Msg tx part 2 (calculate num_all_txs in tx circuit) (#778) * calculate num_l1_msgs and num_l2_txs in tx circuit * fix * fmt and clippy * fix: non-last tx requires next is calldata * add NumAllTxs in block table and copy it from pi to block table * add lookup for NumAllTxs in tx circuit * clippy * add block num diff check to avoid two real block have same num * clippy * address comments --- circuit-benchmarks/src/tx_circuit.rs | 2 +- zkevm-circuits/src/pi_circuit.rs | 74 ++-- zkevm-circuits/src/pi_circuit/param.rs | 6 +- zkevm-circuits/src/table.rs | 8 +- zkevm-circuits/src/tx_circuit.rs | 473 ++++++++++++++++++++++--- zkevm-circuits/src/tx_circuit/dev.rs | 13 +- zkevm-circuits/src/tx_circuit/test.rs | 31 +- zkevm-circuits/src/witness/block.rs | 6 + 8 files changed, 520 insertions(+), 93 deletions(-) diff --git a/circuit-benchmarks/src/tx_circuit.rs b/circuit-benchmarks/src/tx_circuit.rs index 4cf168a665..41fc55d3c6 100644 --- a/circuit-benchmarks/src/tx_circuit.rs +++ b/circuit-benchmarks/src/tx_circuit.rs @@ -84,7 +84,7 @@ mod tests { let max_txs: usize = 2_usize.pow(degree) / ROWS_PER_TX; let txs = vec![mock::CORRECT_MOCK_TXS[0].clone().into()]; - let circuit = TxCircuit::::new(max_txs, MAX_CALLDATA, *mock::MOCK_CHAIN_ID, txs); + let circuit = TxCircuit::::new(max_txs, MAX_CALLDATA, *mock::MOCK_CHAIN_ID, 0, txs); (degree as usize, circuit) } diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index 84c2b65153..968e9c3924 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -28,7 +28,7 @@ use crate::{ evm_circuit::{util::constraint_builder::BaseConstraintBuilder, EvmCircuitExports}, pi_circuit::param::{ BASE_FEE_OFFSET, BLOCK_HEADER_BYTES_NUM, BLOCK_LEN, BLOCK_NUM_OFFSET, BYTE_POW_BASE, - CHAIN_ID_OFFSET, GAS_LIMIT_OFFSET, KECCAK_DIGEST_SIZE, NUM_TXS_OFFSET, RPI_CELL_IDX, + CHAIN_ID_OFFSET, GAS_LIMIT_OFFSET, KECCAK_DIGEST_SIZE, RPI_CELL_IDX, RPI_LENGTH_ACC_CELL_IDX, RPI_RLC_ACC_CELL_IDX, TIMESTAMP_OFFSET, }, state_circuit::StateCircuitExports, @@ -46,11 +46,12 @@ use once_cell::sync::Lazy; use crate::{ evm_circuit::param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_U64, N_BYTES_WORD}, - pi_circuit::param::{COINBASE_OFFSET, DIFFICULTY_OFFSET}, + pi_circuit::param::{COINBASE_OFFSET, DIFFICULTY_OFFSET, NUM_ALL_TXS_OFFSET}, table::{ BlockContextFieldTag, BlockContextFieldTag::{ - BaseFee, ChainId, Coinbase, CumNumTxs, Difficulty, GasLimit, NumTxs, Number, Timestamp, + BaseFee, ChainId, Coinbase, CumNumTxs, Difficulty, GasLimit, NumAllTxs, NumTxs, Number, + Timestamp, }, }, util::rlc_be_bytes, @@ -99,11 +100,12 @@ impl Default for PublicData { } impl PublicData { - fn get_num_txs(&self) -> BTreeMap { - let mut num_txs_in_blocks = BTreeMap::new(); + // Return num of all txs in each block (taking skipped l1 msgs into account) + fn get_num_all_txs(&self) -> BTreeMap { + let mut num_all_txs_in_blocks = BTreeMap::new(); // short for total number of l1 msgs popped before let mut total_l1_popped = self.start_l1_queue_index; - log::debug!("start_l1_queue_index: {}", total_l1_popped); + log::debug!("[public_data] start_l1_queue_index: {}", total_l1_popped); for &block_num in self.block_ctxs.ctxs.keys() { let num_l2_txs = self .transactions @@ -118,33 +120,33 @@ impl PublicData { .map(|tx| tx.nonce) .max() .map_or(0, |max_queue_index| max_queue_index - total_l1_popped + 1); - total_l1_popped += num_l1_msgs; let num_txs = num_l2_txs + num_l1_msgs; - num_txs_in_blocks.insert(block_num, num_txs); + num_all_txs_in_blocks.insert(block_num, num_txs); - log::trace!( - "[block {}] total_l1_popped: {}, num_l1_msgs: {}, num_l2_txs: {}, num_txs: {}", + log::debug!( + "[public_data][block {}] total_l1_popped_before: {}, num_l1_msgs: {}, num_l2_txs: {}, num_txs: {}", block_num, total_l1_popped, num_l1_msgs, num_l2_txs, num_txs ); + total_l1_popped += num_l1_msgs; } - num_txs_in_blocks + num_all_txs_in_blocks } /// Compute the bytes for dataHash from the verifier's perspective. fn data_bytes(&self) -> Vec { - let num_txs_in_blocks = self.get_num_txs(); + let num_all_txs_in_blocks = self.get_num_all_txs(); let result = iter::empty() .chain(self.block_ctxs.ctxs.iter().flat_map(|(block_num, block)| { - let num_txs = num_txs_in_blocks + let num_all_txs = num_all_txs_in_blocks .get(block_num) .cloned() - .unwrap_or_else(|| panic!("get num_txs in block {block_num}")) + .unwrap_or_else(|| panic!("get num_all_txs in block {block_num}")) as u16; iter::empty() // Block Values @@ -152,7 +154,7 @@ impl PublicData { .chain(block.timestamp.as_u64().to_be_bytes()) .chain(block.base_fee.to_be_bytes()) .chain(block.gas_limit.to_be_bytes()) - .chain(num_txs.to_be_bytes()) + .chain(num_all_txs.to_be_bytes()) })) // Tx Hashes .chain( @@ -196,7 +198,10 @@ impl PublicData { fn get_pi(&self) -> H256 { let data_hash = H256(keccak256(self.data_bytes())); - log::debug!("data hash: {}", hex::encode(data_hash.to_fixed_bytes())); + log::debug!( + "[pi] chunk data hash: {}", + hex::encode(data_hash.to_fixed_bytes()) + ); let pi_bytes = self.pi_bytes(data_hash); let pi_hash = keccak256(pi_bytes); @@ -660,7 +665,7 @@ impl PiCircuitConfig { .iter() .map(|tx| tx.hash) .collect::>(); - let num_txs_in_blocks = public_data.get_num_txs(); + let num_all_txs_in_blocks = public_data.get_num_all_txs(); let mut offset = 0; let mut block_copy_cells = vec![]; @@ -693,8 +698,12 @@ impl PiCircuitConfig { { let is_rpi_padding = i >= block_values.ctxs.len(); let block_num = block.number.as_u64(); - let num_txs = num_txs_in_blocks.get(&block_num).cloned().unwrap_or(0) as u16; - log::debug!("num_txs in block {}: {}", block_num, num_txs); + let num_all_txs = num_all_txs_in_blocks.get(&block_num).cloned().unwrap_or(0) as u16; + log::debug!( + "[pi assign] num_all_txs in block {}: {}", + block_num, + num_all_txs + ); // Assign fields in pi columns and connect them to block table let fields = vec![ @@ -708,7 +717,7 @@ impl PiCircuitConfig { ), // timestamp (block.base_fee.to_be_bytes().to_vec(), BASE_FEE_OFFSET), // base_fee (block.gas_limit.to_be_bytes().to_vec(), GAS_LIMIT_OFFSET), // gas_limit - (num_txs.to_be_bytes().to_vec(), NUM_TXS_OFFSET), // num_txs + (num_all_txs.to_be_bytes().to_vec(), NUM_ALL_TXS_OFFSET), // num_all_txs ]; for (bytes, block_offset) in fields { let cells = self.assign_field_in_pi( @@ -722,15 +731,10 @@ impl PiCircuitConfig { false, challenges, )?; - // do not copy num_txs to block table as the meaning of num_txs - // in block table is len(block.txs), and this is different from num_l1_msgs + - // num_l2_txs - if block_offset != NUM_TXS_OFFSET { - block_copy_cells.push(( - cells[RPI_CELL_IDX].clone(), - block_table_offset + block_offset, - )); - } + block_copy_cells.push(( + cells[RPI_CELL_IDX].clone(), + block_table_offset + block_offset, + )); } block_table_offset += BLOCK_LEN; @@ -1355,26 +1359,30 @@ impl PiCircuitConfig { let mut cum_num_txs = 0usize; let mut block_value_cells = vec![]; let block_ctxs = &public_data.block_ctxs; - // let num_txs_in_blocks = public_data.get_num_txs(); + let num_all_txs_in_blocks = public_data.get_num_all_txs(); for block_ctx in block_ctxs.ctxs.values().cloned().chain( (block_ctxs.ctxs.len()..max_inner_blocks) .into_iter() .map(|_| BlockContext::padding(public_data.chain_id)), ) { - // note that let num_txs = public_data .transactions .iter() .filter(|tx| tx.block_number == block_ctx.number.as_u64()) .count(); + // unwrap_or(0) for padding block + let num_all_txs = num_all_txs_in_blocks + .get(&block_ctx.number.as_u64()) + .cloned() + .unwrap_or(0); let tag = [ Coinbase, Timestamp, Number, Difficulty, GasLimit, BaseFee, ChainId, NumTxs, - CumNumTxs, + CumNumTxs, NumAllTxs, ]; let mut cum_num_txs_field = F::from(cum_num_txs as u64); cum_num_txs += num_txs; for (row, tag) in block_ctx - .table_assignments(num_txs, cum_num_txs, challenges) + .table_assignments(num_txs, cum_num_txs, num_all_txs, challenges) .into_iter() .zip(tag.iter()) { diff --git a/zkevm-circuits/src/pi_circuit/param.rs b/zkevm-circuits/src/pi_circuit/param.rs index a9ffc7e93c..0a3c7563f6 100644 --- a/zkevm-circuits/src/pi_circuit/param.rs +++ b/zkevm-circuits/src/pi_circuit/param.rs @@ -1,5 +1,5 @@ /// Fixed by the spec -pub(super) const BLOCK_LEN: usize = 9; +pub(super) const BLOCK_LEN: usize = 10; pub(super) const BYTE_POW_BASE: u64 = 256; pub(super) const BLOCK_HEADER_BYTES_NUM: usize = 58; pub(super) const KECCAK_DIGEST_SIZE: usize = 32; @@ -19,5 +19,5 @@ pub(super) const DIFFICULTY_OFFSET: usize = 3; pub(super) const GAS_LIMIT_OFFSET: usize = 4; pub(super) const BASE_FEE_OFFSET: usize = 5; pub(super) const CHAIN_ID_OFFSET: usize = 6; -pub(super) const NUM_TXS_OFFSET: usize = 7; -pub(super) const CUM_NUM_TXS_OFFSET: usize = 8; +// pub(super) const CUM_NUM_TXS_OFFSET: usize = 8; +pub(super) const NUM_ALL_TXS_OFFSET: usize = 9; diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 2086483944..c05162aaa9 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -1192,11 +1192,15 @@ pub enum BlockContextFieldTag { /// add it here for convenience. ChainId, /// In a multi-block setup, this variant represents the total number of txs - /// included in this block. + /// included (executed) in this block. NumTxs, /// In a multi-block setup, this variant represents the cumulative number of /// txs included up to this block, including the txs in this block. CumNumTxs, + /// In a multi-block setup, this variant represents the total number of txs + /// included in this block which also taking skipped l1 msgs into account. + /// This could possibly be larger than NumTxs. + NumAllTxs, } impl_expr!(BlockContextFieldTag); @@ -1257,7 +1261,7 @@ impl BlockTable { .filter(|tx| tx.block_number == block_ctx.number.as_u64()) .count(); cum_num_txs += num_txs; - for row in block_ctx.table_assignments(num_txs, cum_num_txs, challenges) { + for row in block_ctx.table_assignments(num_txs, cum_num_txs, 0, challenges) { region.assign_fixed( || format!("block table row {offset}"), self.tag, diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index fc41d288ca..a0622f5986 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -16,7 +16,7 @@ use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, sig_circuit::SigCircuit, table::{ - BlockContextFieldTag::CumNumTxs, + BlockContextFieldTag::{CumNumTxs, NumAllTxs}, BlockTable, KeccakTable, LookupTable, RlpFsmRlpTable as RlpTable, SigTable, TxFieldTag, TxFieldTag::{ BlockNumber, CallData, CallDataGasCost, CallDataLength, CallDataRLC, CalleeAddress, @@ -97,6 +97,8 @@ enum LookupCondition { pub struct TxCircuitConfig { minimum_rows: usize, + /// Only the 2nd row of tx table is enabled (as the first row is empty). + q_second: Column, tx_table: TxTable, tx_tag_bits: BinaryNumberConfig, @@ -127,6 +129,13 @@ pub struct TxCircuitConfig { is_chain_id: Column, lookup_conditions: HashMap>, + /// Columns for computing num_l1_msgs and num_l2_txs + tx_nonce: Column, + block_num: Column, + block_num_unchanged: IsEqualConfig, + num_all_txs_acc: Column, + total_l1_popped_before: Column, + /// Columns for accumulating call_data_length and call_data_gas_cost /// A boolean advice column, which is turned on only for the last byte in /// call data. @@ -194,6 +203,29 @@ impl SubCircuitConfig for TxCircuitConfig { ) -> Self { let q_enable = tx_table.q_enable; + let q_second = meta.fixed_column(); + // Since we allow skipping l1 txs that could cause potential circuit overflow, + // the num_all_txs (num_l1_msgs + num_l2_txs) in the input to get chunk data hash + // does not necessarily equal to num_txs (self.txs.len()) in block table. + // Therefore we calculated two numbers (num_l1_msgs, num_l2_txs) in tx circuit + // and then asserts that `num_l1_msgs + num_l2_txs = num_all_txs` in pi circuit. + // + // In more detail, all txs in same block are grouped together and we iterate over + // its txs to get `num_all_txs`. + // + // | is_l1_msg | queue_index | total_l1_popped_before | num_all_txs | + // | true | q1 | c | q1-c+1 | + // | false | | q1+1 | q1-c+2 | + // | true | q2 | q1+1 | q2-c+2 | + // | true | q3 | q2+1 | q3-c+2 | + + let tx_nonce = meta.advice_column(); + let block_num = meta.advice_column(); + + let total_l1_popped_before = meta.advice_column(); + // num_all_txs = num_l1_msgs + num_l2_txs + let num_all_txs_acc = meta.advice_column(); + // tag, rlp_tag, tx_type, is_none let tx_type = meta.advice_column(); let rlp_tag = meta.advice_column(); @@ -299,8 +331,8 @@ impl SubCircuitConfig for TxCircuitConfig { |meta| meta.advice_column_in(SecondPhase), // value is at 2nd phase ); - // tx_id transition - meta.create_gate("tx_id transition", |meta| { + // tx_id transition in the fixed part of tx table + meta.create_gate("tx_id transition in the fixed part of tx table", |meta| { let mut cb = BaseConstraintBuilder::default(); // if tag_next == Nonce, then tx_id' = tx_id + 1 @@ -320,11 +352,28 @@ impl SubCircuitConfig for TxCircuitConfig { meta.query_advice(tx_table.tx_id, Rotation::next()), meta.query_advice(tx_table.tx_id, Rotation::cur()), ); - cb.require_equal( - "tx_type does not change", - meta.query_advice(tx_type, Rotation::next()), - meta.query_advice(tx_type, Rotation::cur()), - ); + // tx meta infos that extracted at some row and need to be copied to all rows of + // same tx + let tx_meta_info_fields = vec![ + ("tx_type", tx_type), // extracted at SigV row + ("is_padding_tx", is_padding_tx), // extracted at CallerAddress row + ("sv_address", sv_address), // extracted at ChainID row + ("block_num", block_num), // extracted at BlockNum row + ("total_l1_popped_before", total_l1_popped_before), + ("num_all_txs_acc", num_all_txs_acc), + // is_l1_msg does not need to spread out as it's extracted from tx_type + + // these do not need to spread out as they are related to tx_table.tag + // (which is fixed col) is_chain_id, + // is_caller_address, is_tag_block_num, is_calldata + ]; + for (col_name, meta_info) in tx_meta_info_fields { + cb.require_equal( + col_name, + meta.query_advice(meta_info, Rotation::next()), + meta.query_advice(meta_info, Rotation::cur()), + ); + } }, ); @@ -676,19 +725,209 @@ impl SubCircuitConfig for TxCircuitConfig { }); /////////////////////////////////////////////////////////////////////// - /////////////// constraints on BlockNum ///////////////////////////// + /////////////// constraints on num_all_txs // /////////////////////// + /////////////////////////////////////////////////////////////////////// + meta.create_gate("copy tx_nonce", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.condition(is_nonce(meta), |cb| { + cb.require_equal( + "tx_nonce = tx_table.value if tag == Nonce", + meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(tx_nonce, Rotation::cur()), + ); + }); + + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }); + + meta.create_gate("copy block_num", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.condition(meta.query_advice(is_tag_block_num, Rotation::cur()), |cb| { + cb.require_equal( + "block_num = tx_table.value if tag == BlockNum", + meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(block_num, Rotation::cur()), + ); + }); + + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }); + + // block num is the last row of each tx's fixed rows and since block num is + // copied to TX_LEN rows. The row at which tag = BlockNum and tx_id = i, + // its next row has tx_id = i+1. That is, we can use Rotation::next() to get next + // tx's all meta-infos (including block_num, tx_nonce, num_all_txs_acc, ...) + let block_num_unchanged = IsEqualChip::configure( + meta, + |meta| { + and::expr([ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_tag_block_num, Rotation::cur()), + ]) + }, + |meta| meta.query_advice(block_num, Rotation::next()), + |meta| meta.query_advice(block_num, Rotation::cur()), + ); + + meta.lookup("block_num is non-decreasing till padding txs", |meta| { + // Block nums like this [1, 3, 5, 4, 0] is rejected by this. But [1, 2, 3, 5, 0] is + // acceptable. + let lookup_condition = and::expr([ + // next row should not belong to a padding tx + not::expr(meta.query_advice(is_padding_tx, Rotation::next())), + // next row should not be in the calldata region + not::expr(meta.query_advice(is_calldata, Rotation::next())), + meta.query_advice(is_tag_block_num, Rotation::cur()), + ]); + + let block_num_diff = meta.query_advice(block_num, Rotation::next()) + - meta.query_advice(block_num, Rotation::cur()); + + vec![(lookup_condition * block_num_diff, u16_table.into())] + }); + + meta.create_gate("num_all_txs in a block", |meta| { + let mut cb = BaseConstraintBuilder::default(); + let queue_index = tx_nonce; + // first tx in tx table + cb.condition(meta.query_fixed(q_second, Rotation::cur()), |cb| { + cb.require_equal( + "num_all_txs_acc = is_l1_msg ? queue_index - total_l1_popped_before + 1 : 1", + meta.query_advice(num_all_txs_acc, Rotation::cur()), + select::expr( + meta.query_advice(is_l1_msg, Rotation::cur()), + // first tx is l1 msg + meta.query_advice(queue_index, Rotation::cur()) + - meta.query_advice(total_l1_popped_before, Rotation::cur()) + + 1.expr(), + 1.expr(), + ), + ); + }); + + // non-last tx in cur block + cb.condition( + and::expr([ + // see the comment below + not::expr(meta.query_advice(is_calldata, Rotation::next())), + block_num_unchanged.expr(), + ]), + |cb| { + cb.require_equal( + "total_l1_popped' = tx.is_l1_msg ? queue_index + 1 : total_l1_popped", + meta.query_advice(total_l1_popped_before, Rotation::next()), + select::expr( + meta.query_advice(is_l1_msg, Rotation::cur()), + meta.query_advice(queue_index, Rotation::cur()) + 1.expr(), + meta.query_advice(total_l1_popped_before, Rotation::cur()), + ), + ); + + // num_all_txs_acc' - num_all_txs_acc = is_l1_msg' ? queue_index' - + // total_l1_popped + 1 : 1 + cb.require_equal( + "num_all_txs_acc' - num_all_txs_acc", + meta.query_advice(num_all_txs_acc, Rotation::next()) + - meta.query_advice(num_all_txs_acc, Rotation::cur()), + select::expr( + meta.query_advice(is_l1_msg, Rotation::next()), + meta.query_advice(tx_nonce, Rotation::next()) + - meta.query_advice(total_l1_popped_before, Rotation::cur()) + + 1.expr(), + 1.expr(), + ), + ); + }, + ); + + // last tx in cur block (next tx is the first tx in next block) + // and cur block is not the last block (s.t. we can init next block's num_all_txs) + cb.condition( + and::expr([ + // We need this condition because if this is the last tx of fixed part of tx + // table, not(block_num_unchanged.expr()) is very likely to + // be true. Since it does not make sense to assign values + // to `num_all_txs` col in the calldata part of tx table. + // Therefore we can skip assign any values to fixed part related cols + // (e.g. block_num, tx_type, is_padding_tx, ....). The witness assignment of + // calldata part need only make sure that (is_final, + // calldata_gas_cost_acc) are correctly assigned. + not::expr(meta.query_advice(is_calldata, Rotation::next())), + not::expr(block_num_unchanged.expr()), + ]), + |cb| { + cb.require_equal( + "total_l1_popped' = tx.is_l1_msg ? queue_index + 1 : total_l1_popped", + meta.query_advice(total_l1_popped_before, Rotation::next()), + select::expr( + meta.query_advice(is_l1_msg, Rotation::cur()), + meta.query_advice(queue_index, Rotation::cur()) + 1.expr(), + meta.query_advice(total_l1_popped_before, Rotation::cur()), + ), + ); + + // init new block's num_all_txs + // num_all_txs_acc' = is_l1_msg' ? queue_index' - total_l1_popped_before' + 1 : + // 1 + cb.require_equal( + "init new block's num_all_txs", + meta.query_advice(num_all_txs_acc, Rotation::next()), + select::expr( + meta.query_advice(is_l1_msg, Rotation::next()), + meta.query_advice(tx_nonce, Rotation::next()) + - meta.query_advice(total_l1_popped_before, Rotation::next()) + + 1.expr(), + 1.expr(), + ), + ); + }, + ); + + // no constraints on last tx in the fixed part of tx table + + cb.gate(and::expr([ + meta.query_fixed(tx_table.q_enable, Rotation::cur()), + // we are in the fixed part of tx table + not::expr(meta.query_advice(is_calldata, Rotation::cur())), + // calculate num_all_txs at tag = BlockNum row + meta.query_advice(is_tag_block_num, Rotation::cur()), + ])) + }); + + meta.lookup_any("num_all_txs in block table", |meta| { + let is_tag_block_num = meta.query_advice(is_tag_block_num, Rotation::cur()); + let block_num = meta.query_advice(tx_table.value, Rotation::cur()); + let num_all_txs_acc = meta.query_advice(num_all_txs_acc, Rotation::cur()); + + let input_expr = vec![NumAllTxs.expr(), block_num, num_all_txs_acc]; + let table_expr = block_table.table_exprs(meta); + let condition = and::expr([ + is_tag_block_num, + not::expr(block_num_unchanged.expr()), // the last tx in each block + not::expr(meta.query_advice(is_padding_tx, Rotation::cur())), + ]); + + input_expr + .into_iter() + .zip(table_expr.into_iter()) + .map(|(input, table)| (input * condition.clone(), table)) + .collect::>() + }); + + /////////////////////////////////////////////////////////////////////// + /////// constraints on block_table's num_txs & num_cum_txs ////////// /////////////////////////////////////////////////////////////////////// meta.create_gate("is_padding_tx", |meta| { let is_tag_caller_addr = is_caller_addr(meta); let mut cb = BaseConstraintBuilder::default(); - // the offset between CallerAddress and BlockNumber - let offset = usize::from(BlockNumber) - usize::from(CallerAddress); // if tag == CallerAddress cb.condition(is_tag_caller_addr.expr(), |cb| { cb.require_equal( "is_padding_tx = true if caller_address = 0", - meta.query_advice(is_padding_tx, Rotation(offset as i32)), + meta.query_advice(is_padding_tx, Rotation::cur()), value_is_zero.expr(Rotation::cur())(meta), ); }); @@ -885,6 +1124,7 @@ impl SubCircuitConfig for TxCircuitConfig { log_deg("tx_circuit", meta); Self { + q_second, minimum_rows: meta.minimum_rows(), tx_tag_bits: tag_bits, tx_type, @@ -902,6 +1142,11 @@ impl SubCircuitConfig for TxCircuitConfig { cum_num_txs, is_padding_tx, lookup_conditions, + tx_nonce, + block_num, + block_num_unchanged, + num_all_txs_acc, + total_l1_popped_before, is_l1_msg, is_chain_id, is_final, @@ -1208,6 +1453,10 @@ impl TxCircuitConfig { cum_num_txs: Option, is_final: Option, calldata_gas_cost_acc: Option, + cur_block_num: Option, + next_block_number: Option, + num_all_txs_acc: Option, + total_l1_popped_before: Option, ) -> Result<(), Error> { // assign to tag, rlp_tag, is_none let tag_chip = BinaryNumberChip::construct(self.tx_tag_bits); @@ -1234,6 +1483,39 @@ impl TxCircuitConfig { || Value::known(F::from(is_none.unwrap_or(false) as u64)), )?; + let block_num_unchanged_chip = IsEqualChip::construct(self.block_num_unchanged.clone()); + block_num_unchanged_chip.assign( + region, + *offset, + Value::known(F::from(next_block_number.unwrap_or(0))), + Value::known(F::from(cur_block_num.unwrap_or(0))), + )?; + region.assign_advice( + || "tx_nonce", + self.tx_nonce, + *offset, + || Value::known(F::from(tx.map_or(0, |tx| tx.nonce))), + )?; + region.assign_advice( + || "block_num", + self.block_num, + *offset, + || Value::known(F::from(cur_block_num.unwrap_or(0))), + )?; + region.assign_advice( + || "num_all_txs_acc", + self.num_all_txs_acc, + *offset, + || Value::known(F::from(num_all_txs_acc.unwrap_or(0))), + )?; + + region.assign_advice( + || "total_l1_popped_before", + self.total_l1_popped_before, + *offset, + || Value::known(F::from(total_l1_popped_before.unwrap_or(0))), + )?; + // assign to lookup condition columns let is_l1_msg = tx.map(|tx| tx.tx_type.is_l1_msg()).unwrap_or(false); let mut conditions = HashMap::>::new(); @@ -1505,6 +1787,8 @@ pub struct TxCircuit { pub txs: Vec, /// Chain ID pub chain_id: u64, + /// Start L1 Queue Index + pub start_l1_queue_index: u64, /// Size pub size: usize, _marker: PhantomData, @@ -1512,7 +1796,13 @@ pub struct TxCircuit { impl TxCircuit { /// Return a new TxCircuit - pub fn new(max_txs: usize, max_calldata: usize, chain_id: u64, txs: Vec) -> Self { + pub fn new( + max_txs: usize, + max_calldata: usize, + chain_id: u64, + start_l1_queue_index: u64, + txs: Vec, + ) -> Self { log::info!( "TxCircuit::new(max_txs = {}, max_calldata = {}, chain_id = {})", max_txs, @@ -1527,6 +1817,7 @@ impl TxCircuit { txs, size: Self::min_num_rows(max_txs, max_calldata), chain_id, + start_l1_queue_index, _marker: PhantomData::default(), } } @@ -1577,53 +1868,84 @@ impl TxCircuit { txs_len * TX_LEN + call_data_len } + // assign num_txs, cum_num_txs, num_all_txs only as we only lookup into + // block table for these three fields and this is mainly used for unit-test fn assign_dev_block_table( &self, config: TxCircuitConfig, layouter: &mut impl Layouter, ) -> Result<(), Error> { + let mut total_l1_popped_before = 0; let block_nums = self .txs .iter() .map(|tx| tx.block_number) .collect::>(); let mut num_txs_in_blocks = BTreeMap::new(); + let mut num_all_txs_in_blocks: BTreeMap = BTreeMap::new(); for tx in self.txs.iter() { if let Some(num_txs) = num_txs_in_blocks.get_mut(&tx.block_number) { *num_txs += 1; } else { num_txs_in_blocks.insert(tx.block_number, 1_usize); } + + if let Some(num_all_txs) = num_all_txs_in_blocks.get_mut(&tx.block_number) { + if tx.tx_type.is_l1_msg() { + *num_all_txs += tx.nonce - total_l1_popped_before + 1; + total_l1_popped_before = tx.nonce + 1; + } else { + *num_all_txs += 1; + } + } else { + let num_all_txs = if tx.tx_type.is_l1_msg() { + tx.nonce - total_l1_popped_before + 1 + } else { + 1 + }; + num_all_txs_in_blocks.insert(tx.block_number, num_all_txs); + } } + log::debug!("block_nums: {:?}", block_nums); + log::debug!("num_all_txs: {:?}", num_all_txs_in_blocks); layouter.assign_region( || "dev block table", |mut region| { - for (offset, (block_num, cum_num_txs)) in iter::once((0, 0)) + for (offset, (block_num, cum_num_txs, num_all_txs)) in iter::once((0, 0, 0)) .chain(block_nums.iter().scan(0, |cum_num_txs, block_num| { + let num_all_txs = num_all_txs_in_blocks[block_num]; *cum_num_txs += num_txs_in_blocks[block_num]; - Some((*block_num, *cum_num_txs)) + + Some((*block_num, *cum_num_txs, num_all_txs)) })) .enumerate() { - region.assign_fixed( - || "block_table.tag", - config.block_table.tag, - offset, - || Value::known(F::from(CumNumTxs as u64)), - )?; - region.assign_advice( - || "block_table.index", - config.block_table.index, - offset, - || Value::known(F::from(block_num)), - )?; - region.assign_advice( - || "block_table.value", - config.block_table.value, - offset, - || Value::known(F::from(cum_num_txs as u64)), - )?; + for (j, (tag, value)) in + [(CumNumTxs, cum_num_txs as u64), (NumAllTxs, num_all_txs)] + .into_iter() + .enumerate() + { + let row = offset * 2 + j; + region.assign_fixed( + || "block_table.tag", + config.block_table.tag, + row, + || Value::known(F::from(tag as u64)), + )?; + region.assign_advice( + || "block_table.index", + config.block_table.index, + row, + || Value::known(F::from(block_num)), + )?; + region.assign_advice( + || "block_table.value", + config.block_table.value, + row, + || Value::known(F::from(value)), + )?; + } } Ok(()) }, @@ -1635,6 +1957,7 @@ impl TxCircuit { config: &TxCircuitConfig, challenges: &crate::util::Challenges>, layouter: &mut impl Layouter, + start_l1_queue_index: u64, sign_datas: Vec, padding_txs: &[Transaction], ) -> Result<(), Error> { @@ -1649,6 +1972,8 @@ impl TxCircuit { let mut cum_num_txs; let mut is_padding_tx; + let mut num_all_txs_acc = 0; + let mut total_l1_popped_before = start_l1_queue_index; // Empty entry config.assign_row( &mut region, @@ -1664,6 +1989,10 @@ impl TxCircuit { None, None, None, + None, + None, + None, + None, )?; // Assign all tx fields except for call data @@ -1682,9 +2011,35 @@ impl TxCircuit { .filter(|tx| tx.block_number <= self.txs[i].block_number) .count(); is_padding_tx = false; + let mut init_new_block = |tx: &Transaction| { + if tx.tx_type.is_l1_msg() { + let queue_index = tx.nonce; + num_all_txs_acc = queue_index - total_l1_popped_before + 1; + total_l1_popped_before = queue_index + 1; + } else { + // total_l1_popped_before do not change + num_all_txs_acc = 1; + } + }; + // first tx of all or first tx of next block + if i == 0 || tx.block_number != self.txs[i - 1].block_number { + init_new_block(tx); + } else { + // same block + if tx.tx_type.is_l1_msg() { + let queue_index = tx.nonce; + num_all_txs_acc += queue_index - total_l1_popped_before + 1; + total_l1_popped_before = queue_index + 1; + } else { + // total_l1_popped_before do not change + num_all_txs_acc += 1; + } + } } else { cum_num_txs = 0; is_padding_tx = true; + // padding_tx is an l2 tx + num_all_txs_acc = (i - self.txs.len() + 1) as u64; } let tx_sign_hash = { @@ -1856,20 +2211,36 @@ impl TxCircuit { Value::known(F::from(tx.block_number)), ), ] { - let tx_id_next = match tag { + let (tx_id_next, cur_block_num, next_block_num) = match tag { BlockNumber => { + log::debug!( + "tx_id: {}, block_num: {}, num_all_txs_acc: {}", + i, + tx.block_number, + num_all_txs_acc + ); if i == sigs.len() - 1 { - self.txs - .iter() - .enumerate() - .find(|(_i, tx)| !tx.call_data.is_empty()) - .map(|(i, _tx)| i + 1) - .unwrap_or_else(|| 0) + ( + self.txs + .iter() + .enumerate() + .find(|(_i, tx)| !tx.call_data.is_empty()) + .map(|(i, _tx)| i + 1) + .unwrap_or_else(|| 0), + tx.block_number, + 0, + ) } else { - i + 2 + // tx_id in tx table starts with 1 + if i + 1 >= self.txs.len() { + (i + 2, tx.block_number, padding_txs[0].block_number) + } else { + (i + 2, tx.block_number, self.txs[i + 1].block_number) + } } } - _ => i + 1, + _ => (i + 1, tx.block_number, tx.block_number), /* tx_id in tx table + * starts with 1 */ }; config.assign_row( &mut region, @@ -1885,6 +2256,10 @@ impl TxCircuit { Some(cum_num_txs), None, None, + Some(cur_block_num), + Some(next_block_num), + Some(num_all_txs_acc), + Some(total_l1_popped_before), )?; let sv_address: F = sign_data.get_addr().to_scalar().unwrap(); region.assign_advice( @@ -1939,6 +2314,10 @@ impl TxCircuit { None, Some(is_final), Some(calldata_gas_cost), + None, + None, + None, + None, )?; } } @@ -1996,6 +2375,7 @@ impl SubCircuit for TxCircuit { block.circuits_params.max_txs, block.circuits_params.max_calldata, block.chain_id, + block.start_l1_queue_index, block.txs.clone(), ) } @@ -2072,7 +2452,14 @@ impl SubCircuit for TxCircuit { } } - self.assign(config, challenges, layouter, sign_datas, &padding_txs)?; + self.assign( + config, + challenges, + layouter, + self.start_l1_queue_index, + sign_datas, + &padding_txs, + )?; Ok(()) } diff --git a/zkevm-circuits/src/tx_circuit/dev.rs b/zkevm-circuits/src/tx_circuit/dev.rs index 7c6fa7057a..75482551b4 100644 --- a/zkevm-circuits/src/tx_circuit/dev.rs +++ b/zkevm-circuits/src/tx_circuit/dev.rs @@ -106,14 +106,20 @@ pub struct TxCircuitTester { impl TxCircuitTester { /// Return a new TxCircuit - pub fn new(max_txs: usize, max_calldata: usize, chain_id: u64, txs: Vec) -> Self { + pub fn new( + max_txs: usize, + max_calldata: usize, + chain_id: u64, + start_l1_queue_index: u64, + txs: Vec, + ) -> Self { TxCircuitTester:: { sig_circuit: SigCircuit { max_verif: max_txs, signatures: get_sign_data(&txs, max_txs, chain_id as usize).unwrap(), _marker: PhantomData, }, - tx_circuit: TxCircuit::new(max_txs, max_calldata, chain_id, txs), + tx_circuit: TxCircuit::new(max_txs, max_calldata, chain_id, start_l1_queue_index, txs), } } } @@ -126,7 +132,8 @@ impl SubCircuit for TxCircuitTester { let max_txs = block.circuits_params.max_txs; let chain_id = block.chain_id; let max_calldata = block.circuits_params.max_calldata; - Self::new(max_txs, max_calldata, chain_id, txs) + let start_l1_queue_index = block.start_l1_queue_index; + Self::new(max_txs, max_calldata, chain_id, start_l1_queue_index, txs) } fn synthesize_sub( diff --git a/zkevm-circuits/src/tx_circuit/test.rs b/zkevm-circuits/src/tx_circuit/test.rs index 165e3bbb90..f1608c708f 100644 --- a/zkevm-circuits/src/tx_circuit/test.rs +++ b/zkevm-circuits/src/tx_circuit/test.rs @@ -113,6 +113,7 @@ fn run( chain_id: u64, max_txs: usize, max_calldata: usize, + start_l1_queue_index: u64, ) -> Result<(), Vec> { let active_row_num = TxCircuit::::min_num_rows(max_txs, max_calldata); @@ -123,7 +124,7 @@ fn run( signatures: get_sign_data(&txs, max_txs, chain_id as usize).unwrap(), _marker: PhantomData, }, - tx_circuit: TxCircuit::new(max_txs, max_calldata, chain_id, txs), + tx_circuit: TxCircuit::new(max_txs, max_calldata, chain_id, start_l1_queue_index, txs), }; let prover = match MockProver::run(k, &circuit, vec![]) { Ok(prover) => prover, @@ -156,7 +157,8 @@ fn tx_circuit_2tx_2max_tx() { .collect(), *mock::MOCK_CHAIN_ID, MAX_TXS, - MAX_CALLDATA + MAX_CALLDATA, + 0, ), Ok(()) ); @@ -169,7 +171,7 @@ fn tx_circuit_0tx_1max_tx() { const MAX_CALLDATA: usize = 32; assert_eq!( - run::(vec![], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA), + run::(vec![], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA, 0), Ok(()) ); } @@ -183,7 +185,7 @@ fn tx_circuit_1tx_1max_tx() { let tx: Transaction = mock::CORRECT_MOCK_TXS[0].clone().into(); assert_eq!( - run::(vec![tx], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA), + run::(vec![tx], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA, 0), Ok(()) ); } @@ -197,7 +199,7 @@ fn tx_circuit_1tx_2max_tx() { let tx = build_pre_eip155_tx(); assert_eq!( - run::(vec![tx], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA), + run::(vec![tx], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA, 0), Ok(()) ); } @@ -211,7 +213,7 @@ fn tx_circuit_l1_msg_tx() { let tx = build_l1_msg_tx(); assert_eq!( - run::(vec![tx], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA), + run::(vec![tx], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA, 0), Ok(()) ); } @@ -226,7 +228,14 @@ fn tx_circuit_bad_address() { // This address doesn't correspond to the account that signed this tx. tx.from = AddrOrWallet::from(address!("0x1230000000000000000000000000000000000456")); - assert!(run::(vec![tx.into()], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA).is_err(),); + assert!(run::( + vec![tx.into()], + *mock::MOCK_CHAIN_ID, + MAX_TXS, + MAX_CALLDATA, + 0 + ) + .is_err(),); } #[test] @@ -239,7 +248,13 @@ fn tx_circuit_to_is_zero() { tx.transaction_index = U64::from(1); assert_eq!( - run::(vec![tx.into()], *mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA), + run::( + vec![tx.into()], + *mock::MOCK_CHAIN_ID, + MAX_TXS, + MAX_CALLDATA, + 0 + ), Ok(()) ); } diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index 3ad812c8e8..5c09547358 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -228,6 +228,7 @@ impl BlockContext { &self, num_txs: usize, cum_num_txs: usize, + num_all_txs: u64, challenges: &Challenges>, ) -> Vec<[Value; 3]> { let current_block_number = self.number.to_scalar().unwrap(); @@ -280,6 +281,11 @@ impl BlockContext { Value::known(current_block_number), Value::known(F::from(cum_num_txs as u64)), ], + [ + Value::known(F::from(BlockContextFieldTag::NumAllTxs as u64)), + Value::known(current_block_number), + Value::known(F::from(num_all_txs)), + ], ], self.block_hash_assignments(randomness), ] From fd2be1c5c4ff57acfa68484fa1aaa85b280c6ecf Mon Sep 17 00:00:00 2001 From: xkx Date: Wed, 16 Aug 2023 18:18:06 +0800 Subject: [PATCH 20/33] Fix the bugs in RLP/Tx/PI circuit which are reported by Zellic & KALOS auditors (#572) * fix finding 3 (#575) * Fix zellic finding 4 (#576) * fix finding 3 (#575) * fix finding 4 --------- Co-authored-by: Rohit Narurkar * add range check on diffs (#586) * Fix finding 10 (#578) * fix finding 3 (#575) * fix finding 10 * Fix finding 13 (#579) * fix finding 3 (#575) * fix finding 13 * Fix zellic finding 14 (#580) * fix finding 3 (#575) * fix finding 14 * Fix zellic finding 5 (#584) * fix finding 3 (#575) * fix finding 5 * refine comments * fmt * Fix finding 17 (#602) * add q_last * fix * add more diff range check * fix finding 7 (#625) * tx_id = 1 when sm starts * Fix finding 11 : use length for rlc in rlp table (#719) * fix: use tag_bytes_rlc and tag_length to copy tag's bytes around * fix lookup input for Len & RLC & GasCost fields in tx circuit * refactor * fix * refactor * fix col phase issue * refactor bytes_rlc type * Fix the bugs in Tx & PI circuits reported by Zellic & KALOS auditors (#612) * lookup chain_id to RLP table * fix finding 22 (#614) * fix finding 21 (#613) * fix finding 23 (#618) * fix finding 26 (#622) * fix finding 28 (#624) Co-authored-by: Rohit Narurkar * fix finding 29 (#623) Co-authored-by: Rohit Narurkar * enforce is_final is true at the last row and fix RLC related vul (#735) * Fix finding 30 (#733) * enforce all txs in a block are included in the tx table * clippy --------- Co-authored-by: Rohit Narurkar * Fix Zellic / Kalos finding25 (#619) * fix finding 25 * add comment --------- Co-authored-by: Rohit Narurkar * fix conflicts --------- Co-authored-by: Rohit Narurkar Co-authored-by: Zhang Zhuo * use q_first instead * fmt --------- Co-authored-by: Rohit Narurkar Co-authored-by: Zhang Zhuo --- eth-types/src/geth_types.rs | 5 + zkevm-circuits/src/pi_circuit.rs | 23 +- zkevm-circuits/src/rlp_circuit_fsm.rs | 164 +++++++-- zkevm-circuits/src/table.rs | 20 + zkevm-circuits/src/tx_circuit.rs | 502 +++++++++++++++++++++++--- zkevm-circuits/src/tx_circuit/test.rs | 2 +- zkevm-circuits/src/witness/l1_msg.rs | 4 +- zkevm-circuits/src/witness/rlp_fsm.rs | 112 ++++-- zkevm-circuits/src/witness/tx.rs | 32 +- 9 files changed, 747 insertions(+), 117 deletions(-) diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 344d8cd738..bfc3c6e73b 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -52,6 +52,11 @@ impl TxType { matches!(*self, TxType::L1Msg) } + /// If this type is Eip155 or not + pub fn is_eip155_tx(&self) -> bool { + matches!(*self, TxType::Eip155) + } + /// Get the type of transaction pub fn get_tx_type(tx: &crate::Transaction) -> Self { match tx.transaction_type { diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index 968e9c3924..b7079d17b7 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -1379,6 +1379,11 @@ impl PiCircuitConfig { Coinbase, Timestamp, Number, Difficulty, GasLimit, BaseFee, ChainId, NumTxs, CumNumTxs, NumAllTxs, ]; + + // index_cells of same block are equal to block_number. + let mut index_cells = vec![]; + let mut block_number_cell = None; + let mut cum_num_txs_field = F::from(cum_num_txs as u64); cum_num_txs += num_txs; for (row, tag) in block_ctx @@ -1392,9 +1397,6 @@ impl PiCircuitConfig { offset, || row[0], )?; - // index_cells of same block are equal to block_number. - let mut index_cells = vec![]; - let mut block_number_cell = None; for (column, value) in block_table_columns.iter().zip_eq(&row[1..]) { let cell = region.assign_advice( || format!("block table row {offset}"), @@ -1412,15 +1414,6 @@ impl PiCircuitConfig { block_value_cells.push(cell); } } - for i in 0..(index_cells.len() - 1) { - region.constrain_equal(index_cells[i].cell(), index_cells[i + 1].cell())?; - } - if *tag == Number { - region.constrain_equal( - block_number_cell.unwrap().cell(), - index_cells[0].cell(), - )?; - } region.assign_fixed( || "is_block_num_txs", @@ -1460,6 +1453,12 @@ impl PiCircuitConfig { } offset += 1; } + // block_num == index[0] + region.constrain_equal(block_number_cell.unwrap().cell(), index_cells[0].cell())?; + // index[i] == index[i+1] + for i in 0..(index_cells.len() - 1) { + region.constrain_equal(index_cells[i].cell(), index_cells[i + 1].cell())?; + } } Ok(block_value_cells) diff --git a/zkevm-circuits/src/rlp_circuit_fsm.rs b/zkevm-circuits/src/rlp_circuit_fsm.rs index 5796a64017..63d727d31a 100644 --- a/zkevm-circuits/src/rlp_circuit_fsm.rs +++ b/zkevm-circuits/src/rlp_circuit_fsm.rs @@ -186,6 +186,8 @@ impl RlpFsmRomTable { pub struct RlpCircuitConfig { /// Whether the row is the first row. q_first: Column, + /// Whether the row is the last row. + q_last: Column, /// The state of RLP verifier at the current row. state: Column, /// A utility gadget to compare/query what state we are at. @@ -215,8 +217,6 @@ pub struct RlpCircuitConfig { /// When the tag occupies several bytes, this index denotes the /// incremental index of the byte within this tag instance. tag_idx: Column, - /// The length of bytes that hold this tag's value. - tag_length: Column, /// The accumulated value of the tag's bytes up to `tag_idx`. tag_value_acc: Column, /// The depth at this row. Since RLP encoded data can be nested, we use @@ -269,10 +269,14 @@ pub struct RlpCircuitConfig { tidx_lte_tlength: ComparatorConfig, /// Check for max_length <= 32 mlength_lte_0x20: ComparatorConfig, + /// Check for tag_length <= max_length + tlength_lte_mlength: ComparatorConfig, /// Check for depth == 0 depth_check: IsEqualConfig, /// Check for depth == 1 depth_eq_one: IsEqualConfig, + /// Check for byte_value == 0 + byte_value_is_zero: IsZeroConfig, /// Internal tables /// Data table @@ -294,9 +298,11 @@ impl RlpCircuitConfig { challenges: &Challenges>, ) -> Self { let (tx_id, format) = (rlp_table.tx_id, rlp_table.format); + let tag_length = rlp_table.tag_length; let q_enabled = rlp_table.q_enable; let ( q_first, + q_last, byte_idx, byte_rev_idx, byte_value, @@ -307,7 +313,6 @@ impl RlpCircuitConfig { is_list, max_length, tag_idx, - tag_length, depth, is_tag_begin, is_tag_end, @@ -316,7 +321,7 @@ impl RlpCircuitConfig { is_same_rlp_instance, ) = ( meta.fixed_column(), - meta.advice_column(), + meta.fixed_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), @@ -499,7 +504,9 @@ impl RlpCircuitConfig { }, ); - // if (tx_id' == tx_id and format' != format) or (tx_id' != tx_id and tx_id' != 0) + // These two cases are not the very last non-padding RLP instance. + // 1. (tx_id' == tx_id and format' != format) + // 2. (tx_id' != tx_id and tx_id' != 0) cb.condition( sum::expr([ // case 1 @@ -535,13 +542,30 @@ impl RlpCircuitConfig { }, ); + // For the very last non-padding RLP instance, we have + // tx_id' != tx_id && tx_id' == 0 + cb.condition( + and::expr([ + is_padding_in_dt.expr(Rotation::next())(meta), + not::expr(tx_id_check_in_dt.is_equal_expression.expr()), + ]), + |cb| { + // byte_rev_idx == 1 + cb.require_equal( + "byte_rev_idx is 1 at the last index", + meta.query_advice(data_table.byte_rev_idx, Rotation::cur()), + 1.expr(), + ); + }, + ); + cb.gate(meta.query_fixed(q_enabled, Rotation::cur())) }); meta.lookup("byte value check", |meta| { let cond = and::expr([ meta.query_fixed(q_enabled, Rotation::cur()), - is_padding_in_dt.expr(Rotation::cur())(meta), + not::expr(is_padding_in_dt.expr(Rotation::cur())(meta)), ]); vec![( @@ -722,6 +746,13 @@ impl RlpCircuitConfig { |_meta| 0x20.expr(), u8_table.into(), ); + let tlength_lte_mlength = ComparatorChip::configure( + meta, + cmp_enabled, + |meta| meta.query_advice(tag_length, Rotation::cur()), + |meta| meta.query_advice(max_length, Rotation::cur()), + u8_table.into(), + ); let depth_check = IsEqualChip::configure( meta, cmp_enabled, @@ -746,6 +777,12 @@ impl RlpCircuitConfig { |meta| meta.query_advice(format, Rotation::cur()), |meta| meta.query_advice(format, Rotation::next()), ); + let byte_value_is_zero = IsZeroChip::configure( + meta, + |meta| meta.query_fixed(q_enabled, Rotation::cur()), + byte_value, + |meta| meta.advice_column(), + ); // constraints on the booleans that we use to reduce degree meta.create_gate("booleans for reducing degree (part one)", |meta| { @@ -832,6 +869,9 @@ impl RlpCircuitConfig { let tag_idx_expr = |meta: &mut VirtualCells| meta.query_advice(tag_idx, Rotation::cur()); let tag_value_acc_expr = |meta: &mut VirtualCells| meta.query_advice(tag_value_acc, Rotation::cur()); + let tag_bytes_rlc_expr = |meta: &mut VirtualCells| { + meta.query_advice(rlp_table.tag_bytes_rlc, Rotation::cur()) + }; let is_tag_next_end_expr = |meta: &mut VirtualCells| meta.query_advice(is_tag_end, Rotation::next()); let is_tag_end_expr = @@ -863,6 +903,8 @@ impl RlpCircuitConfig { let mut cb = BaseConstraintBuilder::default(); let tag = tag_expr(meta); + constrain_eq!(meta, cb, state, DecodeTagStart.expr()); + constrain_eq!(meta, cb, tx_id, 1.expr()); constrain_eq!(meta, cb, byte_idx, 1.expr()); cb.require_zero( "tag == TxType or tag == BeginList", @@ -891,6 +933,8 @@ impl RlpCircuitConfig { // is_list = false, tag_value_acc = byte_value constrain_eq!(meta, cb, is_list, false); constrain_eq!(meta, cb, rlp_table.tag_value, byte_value_expr); + constrain_eq!(meta, cb, rlp_table.tag_bytes_rlc, byte_value_expr); + constrain_eq!(meta, cb, rlp_table.tag_length, 1); // state transitions. update_state!(meta, cb, tag, tag_next_expr(meta)); @@ -907,6 +951,8 @@ impl RlpCircuitConfig { constrain_eq!(meta, cb, is_list, false); constrain_eq!(meta, cb, rlp_table.tag_value, 0); + constrain_eq!(meta, cb, rlp_table.tag_bytes_rlc, 0); + constrain_eq!(meta, cb, rlp_table.tag_length, 0); // state transitions. update_state!(meta, cb, tag, tag_next_expr(meta)); @@ -980,20 +1026,13 @@ impl RlpCircuitConfig { cb.condition( meta.query_advice(transit_to_new_rlp_instance, Rotation::cur()), |cb| { - let tx_id = meta.query_advice(rlp_table.tx_id, Rotation::cur()); - let tx_id_next = meta.query_advice(rlp_table.tx_id, Rotation::next()); - let format = meta.query_advice(rlp_table.format, Rotation::cur()); - let format_next = meta.query_advice(rlp_table.format, Rotation::next()); - let tag_next = tag_next_expr(meta); + let tag_next = meta.query_advice(tag, Rotation::next()); // state transition. update_state!(meta, cb, byte_idx, 1); update_state!(meta, cb, depth, 0); update_state!(meta, cb, state, DecodeTagStart); - cb.require_zero( - "(tx_id' == tx_id + 1) or (format' == format + 1)", - (tx_id_next - tx_id - 1.expr()) * (format_next - format - 1.expr()), - ); + cb.require_zero( "tag == TxType or tag == BeginList", (tag_next.expr() - TxType.expr()) @@ -1001,6 +1040,30 @@ impl RlpCircuitConfig { ); }, ); + // tx_id' == tx_id => format' == format + 1 + cb.condition( + and::expr([ + meta.query_advice(transit_to_new_rlp_instance, Rotation::cur()), + tx_id_check_in_sm.is_equal_expression.expr(), + ]), + |cb| { + let format = meta.query_advice(rlp_table.format, Rotation::cur()); + let format_next = meta.query_advice(rlp_table.format, Rotation::next()); + cb.require_equal("format' == format + 1", format_next, format + 1.expr()); + }, + ); + // tx_id' != tx_id => tx_id' == tx_id + 1 + cb.condition( + and::expr([ + meta.query_advice(transit_to_new_rlp_instance, Rotation::cur()), + not::expr(tx_id_check_in_sm.is_equal_expression.expr()), + ]), + |cb| { + let tx_id = meta.query_advice(rlp_table.tx_id, Rotation::cur()); + let tx_id_next = meta.query_advice(rlp_table.tx_id, Rotation::next()); + cb.require_equal("tx_id' == tx_id + 1", tx_id_next, tx_id + 1.expr()); + }, + ); cb.condition( and::expr([ case_4.expr(), @@ -1042,6 +1105,7 @@ impl RlpCircuitConfig { update_state!(meta, cb, tag_idx, 1); update_state!(meta, cb, tag_length, byte_value_expr(meta) - 0x80.expr()); update_state!(meta, cb, tag_value_acc, byte_value_next_expr(meta)); + update_state!(meta, cb, rlp_table.tag_bytes_rlc, byte_value_next_expr(meta)); update_state!(meta, cb, state, State::Bytes); // depth is unchanged. @@ -1066,7 +1130,7 @@ impl RlpCircuitConfig { let b = select::expr( mlen_lt_0x20, 256.expr(), - select::expr(mlen_eq_0x20, evm_word_rand, keccak_input_rand), + select::expr(mlen_eq_0x20, evm_word_rand, keccak_input_rand.expr()), ); // Bytes => Bytes @@ -1078,6 +1142,8 @@ impl RlpCircuitConfig { update_state!(meta, cb, tag_idx, tag_idx_expr(meta) + 1.expr()); update_state!(meta, cb, tag_value_acc, tag_value_acc_expr(meta) * b.expr() + byte_value_next_expr(meta)); + update_state!(meta, cb, rlp_table.tag_bytes_rlc, + tag_bytes_rlc_expr(meta) * keccak_input_rand.expr() + byte_value_next_expr(meta)); update_state!(meta, cb, state, State::Bytes); // depth, tag_length unchanged. @@ -1087,7 +1153,20 @@ impl RlpCircuitConfig { // Bytes => DecodeTagStart cb.condition(tidx_eq_tlen, |cb| { // assertions + let (lt, eq) = tlength_lte_mlength.expr(meta, Some(Rotation::cur())); + cb.require_equal( + "tag_length <= max_length", + // we can use `sum` instead of `or` for two reasons + // 1. both `lt` and `eq` are boolean and they cannot be true at the same time, + // therefore the result of `sum` is same as `or`. + // 2. sum has lower degree + sum::expr([ + lt, eq, + ]), + true.expr(), + ); emit_rlp_tag!(meta, cb, tag_expr(meta), false); + constrain_eq!(meta, cb, rlp_table.tag_value, tag_value_acc_expr(meta)); // state transitions. update_state!(meta, cb, tag, tag_next_expr(meta)); @@ -1166,6 +1245,8 @@ impl RlpCircuitConfig { // state transition. update_state!(meta, cb, tag_length, tag_value_acc_expr(meta)); update_state!(meta, cb, tag_idx, 1); + update_state!(meta, cb, rlp_table.tag_bytes_rlc, byte_value_next_expr(meta)); + update_state!(meta, cb, tag_value_acc, byte_value_next_expr(meta)); update_state!(meta, cb, state, State::Bytes); // depth is unchanged. @@ -1190,6 +1271,7 @@ impl RlpCircuitConfig { ]); cb.condition(cond.expr(), |cb| { // assertions. + do_not_emit!(meta, cb); constrain_eq!(meta, cb, is_tag_begin, true); // state transitions @@ -1236,6 +1318,8 @@ impl RlpCircuitConfig { // LongList => DecodeTagStart cb.condition(tidx_eq_tlen.expr(), |cb| { // assertions + let (lt, eq) = tlength_lte_mlength.expr(meta, Some(Rotation::cur())); + cb.require_equal("tag_length <= max_length", sum::expr([lt, eq]), true.expr()); // state transitions update_state!(meta, cb, tag, tag_next_expr(meta)); @@ -1313,8 +1397,17 @@ impl RlpCircuitConfig { ])) }); + meta.create_gate("sm ends in End state", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + constrain_eq!(meta, cb, state, State::End); + + cb.gate(meta.query_fixed(q_last, Rotation::cur())) + }); + Self { q_first, + q_last, state, state_bits, rlp_table, @@ -1327,7 +1420,6 @@ impl RlpCircuitConfig { bytes_rlc, gas_cost_acc, tag_idx, - tag_length, tag_value_acc, is_list, max_length, @@ -1358,8 +1450,10 @@ impl RlpCircuitConfig { byte_value_gte_0xf8, tidx_lte_tlength, mlength_lte_0x20, + tlength_lte_mlength, depth_check, depth_eq_one, + byte_value_is_zero, // internal tables data_table, @@ -1411,6 +1505,18 @@ impl RlpCircuitConfig { row, || witness.rlp_table.tag_value, )?; + region.assign_advice( + || "rlp_table.tag_bytes_rlc", + self.rlp_table.tag_bytes_rlc, + row, + || witness.rlp_table.tag_bytes_rlc, + )?; + region.assign_advice( + || "rlp_table.tag_length", + self.rlp_table.tag_length, + row, + || Value::known(F::from(witness.rlp_table.tag_length as u64)), + )?; region.assign_advice( || "rlp_table.is_output", self.rlp_table.is_output, @@ -1461,12 +1567,6 @@ impl RlpCircuitConfig { row, || Value::known(F::from(witness.state_machine.tag_idx as u64)), )?; - region.assign_advice( - || "sm.tag_length", - self.tag_length, - row, - || Value::known(F::from(witness.state_machine.tag_length as u64)), - )?; region.assign_advice( || "sm.depth", self.depth, @@ -1577,7 +1677,14 @@ impl RlpCircuitConfig { region, row, F::from(witness.state_machine.tag_idx as u64), - F::from(witness.state_machine.tag_length as u64), + F::from(witness.rlp_table.tag_length as u64), + )?; + let tlength_lte_mlength_chip = ComparatorChip::construct(self.tlength_lte_mlength.clone()); + tlength_lte_mlength_chip.assign( + region, + row, + F::from(witness.rlp_table.tag_length as u64), + F::from(witness.state_machine.max_length as u64), )?; let depth_check_chip = IsEqualChip::construct(self.depth_check.clone()); @@ -1636,6 +1743,8 @@ impl RlpCircuitConfig { for (chip, lhs, rhs) in byte_value_checks { chip.assign(region, row, lhs, rhs)?; } + let bv_chip = IsZeroChip::construct(self.byte_value_is_zero.clone()); + bv_chip.assign(region, row, Value::known(byte_value))?; Ok(()) } @@ -1764,6 +1873,13 @@ impl RlpCircuitConfig { for i in sm_rows.len()..last_row { self.assign_sm_end_row(&mut region, i)?; } + region.assign_fixed(|| "q_first", self.q_first, 0, || Value::known(F::one()))?; + region.assign_fixed( + || "q_last", + self.q_last, + last_row - 1, + || Value::known(F::one()), + )?; Ok(()) }, diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index c05162aaa9..7e6d0733b8 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -2081,6 +2081,10 @@ pub struct RlpFsmRlpTable { pub rlp_tag: Column, /// The actual value of the current tag being decoded. pub tag_value: Column, + /// RLC of the tag's big-endian bytes + pub tag_bytes_rlc: Column, + /// The actual length of bytes of the current tag being decoded. + pub tag_length: Column, /// Whether or not the row emits an output value. pub is_output: Column, /// Whether or not the current tag's value was nil. @@ -2095,6 +2099,8 @@ impl LookupTable for RlpFsmRlpTable { self.format.into(), self.rlp_tag.into(), self.tag_value.into(), + self.tag_bytes_rlc.into(), + self.tag_length.into(), self.is_output.into(), self.is_none.into(), ] @@ -2107,6 +2113,8 @@ impl LookupTable for RlpFsmRlpTable { String::from("format"), String::from("rlp_tag"), String::from("tag_value_acc"), + String::from("tag_bytes_rlc"), + String::from("tag_length"), String::from("is_output"), String::from("is_none"), ] @@ -2122,6 +2130,8 @@ impl RlpFsmRlpTable { format: meta.advice_column(), rlp_tag: meta.advice_column(), tag_value: meta.advice_column_in(SecondPhase), + tag_bytes_rlc: meta.advice_column_in(SecondPhase), + tag_length: meta.advice_column(), is_output: meta.advice_column(), is_none: meta.advice_column(), } @@ -2175,6 +2185,16 @@ impl RlpFsmRlpTable { Value::known(F::from(usize::from(row.rlp_tag) as u64)), ), ("tag_value", self.tag_value.into(), row.tag_value), + ( + "tag_bytes_rlc", + self.tag_bytes_rlc.into(), + row.tag_bytes_rlc, + ), + ( + "tag_length", + self.tag_length.into(), + Value::known(F::from(row.tag_length as u64)), + ), ("is_output", self.is_output.into(), Value::known(F::one())), ( "is_none", diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index a0622f5986..b05e1c2bce 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -16,7 +16,7 @@ use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, sig_circuit::SigCircuit, table::{ - BlockContextFieldTag::{CumNumTxs, NumAllTxs}, + BlockContextFieldTag::{CumNumTxs, NumAllTxs, NumTxs}, BlockTable, KeccakTable, LookupTable, RlpFsmRlpTable as RlpTable, SigTable, TxFieldTag, TxFieldTag::{ BlockNumber, CallData, CallDataGasCost, CallDataLength, CallDataRLC, CalleeAddress, @@ -31,7 +31,7 @@ use crate::{ }, witness, witness::{ - rlp_fsm::Tag, + rlp_fsm::{Tag, ValueTagLength}, Format::{L1MsgHash, TxHashEip155, TxHashPreEip155, TxSignEip155, TxSignPreEip155}, RlpTag, RlpTag::{GasCost, Len, Null, RLC}, @@ -46,12 +46,13 @@ use eth_types::{ TxType::{Eip155, L1Msg, PreEip155}, }, sign_types::SignData, - Address, Field, ToAddress, ToLittleEndian, ToScalar, + Address, Field, ToAddress, ToBigEndian, ToLittleEndian, ToScalar, }; use gadgets::{ binary_number::{BinaryNumberChip, BinaryNumberConfig}, comparator::{ComparatorChip, ComparatorConfig, ComparatorInstruction}, is_equal::{IsEqualChip, IsEqualConfig, IsEqualInstruction}, + less_than::{LtChip, LtConfig, LtInstruction}, util::{and, not, select, sum, Expr}, }; use halo2_proofs::{ @@ -72,6 +73,7 @@ use halo2_proofs::plonk::FirstPhase as SecondPhase; use halo2_proofs::plonk::Fixed; #[cfg(not(feature = "onephase"))] use halo2_proofs::plonk::SecondPhase; +use itertools::Itertools; /// Number of rows of one tx occupies in the fixed part of tx table pub const TX_LEN: usize = 23; @@ -97,8 +99,11 @@ enum LookupCondition { pub struct TxCircuitConfig { minimum_rows: usize, - /// Only the 2nd row of tx table is enabled (as the first row is empty). - q_second: Column, + // This is only true at the first row of calldata part of tx table + q_calldata_first: Column, + q_calldata_last: Column, + // A selector which is enabled at 1st row + q_first: Column, tx_table: TxTable, tx_tag_bits: BinaryNumberConfig, @@ -108,6 +113,8 @@ pub struct TxCircuitConfig { rlp_tag: Column, // Whether tag's RLP-encoded value is 0x80 = rlp([]) is_none: Column, + tx_value_length: Column, + tx_value_rlc: Column, u8_table: U8Table, u16_table: U16Table, @@ -129,7 +136,7 @@ pub struct TxCircuitConfig { is_chain_id: Column, lookup_conditions: HashMap>, - /// Columns for computing num_l1_msgs and num_l2_txs + /// Columns for computing num_all_txs tx_nonce: Column, block_num: Column, block_num_unchanged: IsEqualConfig, @@ -143,13 +150,22 @@ pub struct TxCircuitConfig { /// An accumulator value used to correctly calculate the calldata gas cost /// for a tx. calldata_gas_cost_acc: Column, + /// An accumulator value used to correctly calculate the RLC(calldata) for a tx. + calldata_rlc: Column, + /// 1st phase column which equals to tx_table.value when is_calldata is true + /// We need this because tx_table.value is a 2nd phase column and is used to get calldata_rlc. + /// It's not safe to do RLC on columns of same phase. + calldata_byte: Column, /// Columns for ensuring that BlockNum is correct is_padding_tx: Column, /// Tx id must be no greater than cum_num_txs tx_id_cmp_cum_num_txs: ComparatorConfig, + tx_id_gt_prev_cnt: LtConfig, /// Cumulative number of txs up to a block cum_num_txs: Column, + /// Number of txs in a block + num_txs: Column, /// Address recovered by SignVerifyChip sv_address: Column, @@ -198,12 +214,14 @@ impl SubCircuitConfig for TxCircuitConfig { sig_table, u8_table, u16_table, - challenges: _, + challenges, }: Self::ConfigArgs, ) -> Self { let q_enable = tx_table.q_enable; - let q_second = meta.fixed_column(); + let q_first = meta.fixed_column(); + let q_calldata_first = meta.fixed_column(); + let q_calldata_last = meta.fixed_column(); // Since we allow skipping l1 txs that could cause potential circuit overflow, // the num_all_txs (num_l1_msgs + num_l2_txs) in the input to get chunk data hash // does not necessarily equal to num_txs (self.txs.len()) in block table. @@ -229,17 +247,23 @@ impl SubCircuitConfig for TxCircuitConfig { // tag, rlp_tag, tx_type, is_none let tx_type = meta.advice_column(); let rlp_tag = meta.advice_column(); + let tx_value_rlc = meta.advice_column_in(SecondPhase); + let tx_value_length = meta.advice_column(); let is_none = meta.advice_column(); let tag_bits = BinaryNumberChip::configure(meta, q_enable, Some(tx_table.tag.into())); let tx_type_bits = BinaryNumberChip::configure(meta, q_enable, Some(tx_type.into())); // columns for constraining BlockNum is valid let cum_num_txs = meta.advice_column(); + // num_of_txs that each block contains + let num_txs = meta.advice_column(); let is_padding_tx = meta.advice_column(); // columns for accumulating length and gas_cost of call_data let is_final = meta.advice_column(); let calldata_gas_cost_acc = meta.advice_column(); + let calldata_rlc = meta.advice_column_in(SecondPhase); + let calldata_byte = meta.advice_column(); // booleans to reduce degree let is_l1_msg = meta.advice_column(); @@ -311,6 +335,13 @@ impl SubCircuitConfig for TxCircuitConfig { is_tx_tag!(is_block_num, BlockNumber); is_tx_tag!(is_tx_type, TxType); + let tx_id_unchanged = IsEqualChip::configure( + meta, + |meta| meta.query_fixed(q_enable, Rotation::cur()), + |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), + |meta| meta.query_advice(tx_table.tx_id, Rotation::next()), + ); + // testing if value is zero for tags let value_is_zero = IsZeroChip::configure( meta, @@ -332,6 +363,19 @@ impl SubCircuitConfig for TxCircuitConfig { ); // tx_id transition in the fixed part of tx table + meta.create_gate("tx_id starts with 1", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + // the first row in tx table are all-zero rows + cb.require_equal( + "tx_id == 1", + meta.query_advice(tx_table.tx_id, Rotation::next()), + 1.expr(), + ); + + cb.gate(meta.query_fixed(q_first, Rotation::cur())) + }); + meta.create_gate("tx_id transition in the fixed part of tx table", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -360,6 +404,8 @@ impl SubCircuitConfig for TxCircuitConfig { ("sv_address", sv_address), // extracted at ChainID row ("block_num", block_num), // extracted at BlockNum row ("total_l1_popped_before", total_l1_popped_before), + ("num_txs", num_txs), + ("cum_num_txs", cum_num_txs), ("num_all_txs_acc", num_all_txs_acc), // is_l1_msg does not need to spread out as it's extracted from tx_type @@ -412,7 +458,7 @@ impl SubCircuitConfig for TxCircuitConfig { (is_hash(meta), Null), (is_data(meta), Null), (is_block_num(meta), Null), - (is_chain_id_expr(meta), Null), + (is_chain_id_expr(meta), Tag::ChainId.into()), (is_tx_type(meta), Null), ]; @@ -507,7 +553,7 @@ impl SubCircuitConfig for TxCircuitConfig { cb.require_equal( "is_calldata", - tag_bits.value_equals(CallData, Rotation::cur())(meta), + is_data(meta), meta.query_advice(is_calldata, Rotation::cur()), ); @@ -519,7 +565,7 @@ impl SubCircuitConfig for TxCircuitConfig { cb.require_equal( "is_caller_address", - tag_bits.value_equals(CallerAddress, Rotation::cur())(meta), + is_caller_addr(meta), meta.query_advice(is_caller_address, Rotation::cur()), ); @@ -531,7 +577,7 @@ impl SubCircuitConfig for TxCircuitConfig { cb.require_equal( "is_chain_id", - tag_bits.value_equals(ChainID, Rotation::cur())(meta), + is_chain_id_expr(meta), meta.query_advice(is_chain_id, Rotation::cur()), ); @@ -590,6 +636,10 @@ impl SubCircuitConfig for TxCircuitConfig { is_to(meta), is_value(meta), is_data_rlc(meta), + and::expr([ + meta.query_advice(is_chain_id, Rotation::cur()), + tx_type_bits.value_equals(Eip155, Rotation::cur())(meta), + ]), is_sign_length(meta), is_sign_rlc(meta), ]); @@ -694,14 +744,19 @@ impl SubCircuitConfig for TxCircuitConfig { meta, q_enable, rlp_tag, + tx_value_rlc, + tx_value_length, tx_type_bits, + tx_id_is_zero.clone(), is_none, &lookup_conditions, is_final, + is_calldata, is_chain_id, is_l1_msg, sv_address, calldata_gas_cost_acc, + calldata_rlc, tx_table.clone(), keccak_table.clone(), rlp_table, @@ -792,7 +847,7 @@ impl SubCircuitConfig for TxCircuitConfig { let mut cb = BaseConstraintBuilder::default(); let queue_index = tx_nonce; // first tx in tx table - cb.condition(meta.query_fixed(q_second, Rotation::cur()), |cb| { + cb.condition(meta.query_fixed(q_first, Rotation::next()), |cb| { cb.require_equal( "num_all_txs_acc = is_l1_msg ? queue_index - total_l1_popped_before + 1 : 1", meta.query_advice(num_all_txs_acc, Rotation::cur()), @@ -934,6 +989,44 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); + // prev block's cum_num_txs < tx_id + let tx_id_gt_prev_cnt = LtChip::configure( + meta, + |meta| meta.query_fixed(q_enable, Rotation::cur()), + |meta| { + let num_txs = meta.query_advice(num_txs, Rotation::cur()); + let cum_num_txs = meta.query_advice(cum_num_txs, Rotation::cur()); + + cum_num_txs - num_txs + }, + |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), + u8_table.into(), + ); + + // last non-padding tx must have tx_id == cum_num_txs + meta.create_gate( + "last non-padding tx must have tx_id == cum_num_txs", + |meta| { + let mut cb = BaseConstraintBuilder::default(); + let is_tag_block_num = meta.query_advice(is_tag_block_num, Rotation::cur()); + let is_cur_tx_non_padding = + not::expr(meta.query_advice(is_padding_tx, Rotation::cur())); + let is_next_tx_padding = meta.query_advice(is_padding_tx, Rotation::next()); + let cum_num_txs = meta.query_advice(cum_num_txs, Rotation::cur()); + let tx_id = meta.query_advice(tx_table.tx_id, Rotation::cur()); + + // tag == BlockNum && cur tx is the last non-padding tx + cb.condition( + and::expr([is_tag_block_num, is_cur_tx_non_padding, is_next_tx_padding]), + |cb| { + cb.require_equal("tx_id == cum_num_txs", tx_id, cum_num_txs); + }, + ); + + cb.gate(meta.query_fixed(tx_table.q_enable, Rotation::cur())) + }, + ); + // tx_id <= cum_num_txs let tx_id_cmp_cum_num_txs = ComparatorChip::configure( meta, @@ -957,6 +1050,26 @@ impl SubCircuitConfig for TxCircuitConfig { ])) }); + meta.lookup_any("num_txs in block table", |meta| { + let is_tag_block_num = meta.query_advice(is_tag_block_num, Rotation::cur()); + let block_num = meta.query_advice(tx_table.value, Rotation::cur()); + let num_txs = meta.query_advice(num_txs, Rotation::cur()); + + let input_expr = vec![NumTxs.expr(), block_num, num_txs]; + let table_expr = block_table.table_exprs(meta); + let condition = and::expr([ + is_tag_block_num, + not::expr(meta.query_advice(is_padding_tx, Rotation::cur())), + meta.query_fixed(q_enable, Rotation::cur()), + ]); + + input_expr + .into_iter() + .zip(table_expr.into_iter()) + .map(|(input, table)| (input * condition.clone(), table)) + .collect::>() + }); + meta.lookup_any("cum_num_txs in block table", |meta| { let is_tag_block_num = meta.query_advice(is_tag_block_num, Rotation::cur()); let block_num = meta.query_advice(tx_table.value, Rotation::cur()); @@ -980,13 +1093,6 @@ impl SubCircuitConfig for TxCircuitConfig { //////////////////////////////////////////////////////////////////////// /////////// CallData length and gas_cost calculation ///////////////// //////////////////////////////////////////////////////////////////////// - let tx_id_unchanged = IsEqualChip::configure( - meta, - |meta| meta.query_fixed(q_enable, Rotation::cur()), - |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), - |meta| meta.query_advice(tx_table.tx_id, Rotation::next()), - ); - meta.lookup("tx_id_diff must in u16", |meta| { let q_enable = meta.query_fixed(q_enable, Rotation::next()); let is_calldata = meta.query_advice(is_calldata, Rotation::cur()); @@ -1000,6 +1106,55 @@ impl SubCircuitConfig for TxCircuitConfig { vec![(lookup_condition * (tx_id_next - tx_id), u16_table.into())] }); + meta.create_gate("last row of call data", |meta| { + let q_calldata_last = meta.query_fixed(q_calldata_last, Rotation::cur()); + let is_final = meta.query_advice(is_final, Rotation::cur()); + + vec![(q_calldata_last * (is_final - true.expr()))] + }); + meta.create_gate("calldata_byte == tx_table.value", |meta| { + let mut cb = BaseConstraintBuilder::default(); + let is_calldata = meta.query_advice(is_calldata, Rotation::cur()); + + cb.condition(is_calldata, |cb| { + cb.require_equal( + "calldata_byte == tx_table.value", + meta.query_advice(calldata_byte, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + ); + }); + + cb.gate(meta.query_fixed(tx_table.q_enable, Rotation::cur())) + }); + + meta.create_gate("tx call data init", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + let value_is_zero = value_is_zero.expr(Rotation::cur())(meta); + let gas_cost = select::expr(value_is_zero, 4.expr(), 16.expr()); + + cb.require_equal( + "index == 0", + meta.query_advice(tx_table.index, Rotation::cur()), + 0.expr(), + ); + cb.require_equal( + "calldata_gas_cost_acc == gas_cost", + meta.query_advice(calldata_gas_cost_acc, Rotation::cur()), + gas_cost, + ); + cb.require_equal( + "calldata_rlc == byte", + meta.query_advice(calldata_rlc, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + ); + + cb.gate(and::expr([ + meta.query_fixed(q_calldata_first, Rotation::cur()), + not::expr(tx_id_is_zero.expr(Rotation::cur())(meta)), + ])) + }); + meta.create_gate("tx call data bytes", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -1027,16 +1182,49 @@ impl SubCircuitConfig for TxCircuitConfig { meta.query_advice(calldata_gas_cost_acc, Rotation::next()), meta.query_advice(calldata_gas_cost_acc, Rotation::cur()) + gas_cost_next, ); + cb.require_equal( + "calldata_rlc' = calldata_rlc * r + byte'", + meta.query_advice(calldata_rlc, Rotation::next()), + meta.query_advice(calldata_rlc, Rotation::cur()) * challenges.keccak_input() + + meta.query_advice(tx_table.value, Rotation::next()), + ); }); // on the final call data byte, tx_id must change. - cb.condition(is_final_cur, |cb| { + cb.condition(is_final_cur.expr(), |cb| { cb.require_zero( "tx_id changes at is_final == 1", tx_id_unchanged.is_equal_expression.clone(), ); }); + cb.condition( + and::expr([ + is_final_cur, + not::expr(tx_id_is_zero.expr(Rotation::next())(meta)), + ]), + |cb| { + let value_next_is_zero = value_is_zero.expr(Rotation::next())(meta); + let gas_cost_next = select::expr(value_next_is_zero, 4.expr(), 16.expr()); + + cb.require_equal( + "index' == 0", + meta.query_advice(tx_table.index, Rotation::next()), + 0.expr(), + ); + cb.require_equal( + "calldata_gas_cost_acc' == gas_cost_next", + meta.query_advice(calldata_gas_cost_acc, Rotation::next()), + gas_cost_next, + ); + cb.require_equal( + "calldata_rlc' == byte'", + meta.query_advice(calldata_rlc, Rotation::next()), + meta.query_advice(tx_table.value, Rotation::next()), + ); + }, + ); + cb.gate(and::expr(vec![ meta.query_fixed(q_enable, Rotation::cur()), meta.query_advice(is_calldata, Rotation::cur()), @@ -1124,13 +1312,17 @@ impl SubCircuitConfig for TxCircuitConfig { log_deg("tx_circuit", meta); Self { - q_second, minimum_rows: meta.minimum_rows(), + q_first, + q_calldata_first, + q_calldata_last, tx_tag_bits: tag_bits, tx_type, tx_type_bits, rlp_tag, is_none, + tx_value_rlc, + tx_value_length, u8_table, u16_table, tx_id_is_zero, @@ -1139,6 +1331,7 @@ impl SubCircuitConfig for TxCircuitConfig { is_calldata, is_caller_address, tx_id_cmp_cum_num_txs, + tx_id_gt_prev_cnt, cum_num_txs, is_padding_tx, lookup_conditions, @@ -1151,6 +1344,8 @@ impl SubCircuitConfig for TxCircuitConfig { is_chain_id, is_final, calldata_gas_cost_acc, + calldata_rlc, + calldata_byte, sv_address, sig_table, block_table, @@ -1159,6 +1354,7 @@ impl SubCircuitConfig for TxCircuitConfig { rlp_table, is_tag_block_num, _marker: PhantomData, + num_txs, } } } @@ -1169,14 +1365,19 @@ impl TxCircuitConfig { meta: &mut ConstraintSystem, q_enable: Column, rlp_tag: Column, + tx_value_rlc: Column, + tx_value_length: Column, tx_type_bits: BinaryNumberConfig, + tx_id_is_zero: IsZeroConfig, is_none: Column, lookup_conditions: &HashMap>, is_final: Column, + is_calldata: Column, is_chain_id: Column, is_l1_msg_col: Column, sv_address: Column, calldata_gas_cost_acc: Column, + calldata_rlc: Column, tx_table: TxTable, keccak_table: KeccakTable, rlp_table: RlpTable, @@ -1259,6 +1460,33 @@ impl TxCircuitConfig { .map(|(arg, table)| (enable.clone() * arg, table)) .collect() }); + meta.lookup_any("lookup CallDataRLC in the calldata part", |meta| { + let is_call_data = meta.query_advice(is_calldata, Rotation::cur()); + let calldata_rlc = meta.query_advice(calldata_rlc, Rotation::cur()); + let enable = and::expr([ + meta.query_fixed(tx_table.q_enable, Rotation::cur()), + is_call_data, + not::expr(tx_id_is_zero.expr(Rotation::cur())(meta)), + meta.query_advice(is_final, Rotation::cur()), + ]); + + let input_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + CallDataRLC.expr(), + calldata_rlc.expr(), + ]; + let table_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + meta.query_fixed(tx_table.tag, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + ]; + + input_exprs + .into_iter() + .zip(table_exprs.into_iter()) + .map(|(input, table)| (input * enable.expr(), table)) + .collect() + }); ///////////////////////////////////////////////////////////////// ///////////////// RLP table lookups ////////////////////// @@ -1272,6 +1500,8 @@ impl TxCircuitConfig { let enable = and::expr([meta.query_fixed(q_enable, Rotation::cur()), is_l1_msg(meta)]); let hash_format = L1MsgHash.expr(); let tag_value = 0x7E.expr(); + let tag_bytes_rlc = 0x7E.expr(); + let tag_length = 1.expr(); let input_exprs = vec![ 1.expr(), // q_enable = true @@ -1279,6 +1509,8 @@ impl TxCircuitConfig { hash_format, RLPTxType.expr(), tag_value, + tag_bytes_rlc, + tag_length, 1.expr(), // is_output = true 0.expr(), // is_none = false ]; @@ -1312,11 +1544,13 @@ impl TxCircuitConfig { sign_format, rlp_tag, meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(tx_value_rlc, Rotation::cur()), + meta.query_advice(tx_value_length, Rotation::cur()), 1.expr(), // is_output = true is_none, ] .into_iter() - .zip(rlp_table.table_exprs(meta).into_iter()) // tag_length_eq_one is the 6th column in rlp table + .zip_eq(rlp_table.table_exprs(meta).into_iter()) .map(|(arg, table)| (enable.clone() * arg, table)) .collect() }); @@ -1348,11 +1582,13 @@ impl TxCircuitConfig { hash_format, rlp_tag, meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(tx_value_rlc, Rotation::cur()), + meta.query_advice(tx_value_length, Rotation::cur()), 1.expr(), // is_output = true is_none, ] .into_iter() - .zip(rlp_table.table_exprs(meta).into_iter()) + .zip_eq(rlp_table.table_exprs(meta).into_iter()) .map(|(arg, table)| (enable.clone() * arg, table)) .collect() }); @@ -1447,12 +1683,16 @@ impl TxCircuitConfig { tx_id_next: usize, tag: TxFieldTag, value: Value, + be_bytes_rlc: Value, + be_bytes_length: Option, rlp_tag: Option, is_none: Option, is_padding_tx: Option, cum_num_txs: Option, + num_txs: Option, is_final: Option, calldata_gas_cost_acc: Option, + calldata_rlc: Option>, cur_block_num: Option, next_block_number: Option, num_all_txs_acc: Option, @@ -1464,6 +1704,7 @@ impl TxCircuitConfig { let tx_type = tx.map_or(Default::default(), |tx| tx.tx_type); let tx_type_chip = BinaryNumberChip::construct(self.tx_type_bits); tx_type_chip.assign(region, *offset, &tx_type)?; + region.assign_advice( || "tx_type", self.tx_type, @@ -1482,6 +1723,18 @@ impl TxCircuitConfig { *offset, || Value::known(F::from(is_none.unwrap_or(false) as u64)), )?; + region.assign_advice( + || "value_be_bytes_rlc", + self.tx_value_rlc, + *offset, + || be_bytes_rlc, + )?; + region.assign_advice( + || "value_be_bytes_length", + self.tx_value_length, + *offset, + || Value::known(F::from(be_bytes_length.unwrap_or(0) as u64)), + )?; let block_num_unchanged_chip = IsEqualChip::construct(self.block_num_unchanged.clone()); block_num_unchanged_chip.assign( @@ -1552,7 +1805,9 @@ impl TxCircuitConfig { TxSignRLC, ]; let is_tag_in_set = sign_set.into_iter().filter(|_tag| tag == *_tag).count() == 1; - Value::known(F::from((is_tag_in_set && !is_l1_msg) as u64)) + let case1 = is_tag_in_set && !is_l1_msg; + let case2 = (tag == ChainID) && tx.map_or(false, |tx| tx.tx_type.is_eip155_tx()); + Value::known(F::from((case1 || case2) as u64)) }); // lookup to RLP table for hashing (non L1 msg) conditions.insert(LookupCondition::RlpHashTag, { @@ -1665,6 +1920,15 @@ impl TxCircuitConfig { *offset, || Value::known(F::from(calldata_gas_cost_acc.unwrap_or_default())), )?; + region.assign_advice( + || "calldata_rlc", + self.calldata_rlc, + *offset, + || calldata_rlc.unwrap_or(Value::known(F::zero())), + )?; + if tag == CallData { + region.assign_advice(|| "calldata_byte", self.calldata_byte, *offset, || value)?; + } // assign to region.assign_advice( @@ -1680,12 +1944,25 @@ impl TxCircuitConfig { F::from(tx_id as u64), F::from(cum_num_txs.unwrap_or_default() as u64), )?; + let tx_id_gt_prev_cnt = LtChip::construct(self.tx_id_gt_prev_cnt); + tx_id_gt_prev_cnt.assign( + region, + *offset, + F::from((cum_num_txs.unwrap_or_default() - num_txs.unwrap_or_default()) as u64), + F::from(tx_id as u64), + )?; region.assign_advice( || "cum_num_txs", self.cum_num_txs, *offset, || Value::known(F::from(cum_num_txs.unwrap_or_default() as u64)), )?; + region.assign_advice( + || "num_txs", + self.num_txs, + *offset, + || Value::known(F::from(num_txs.unwrap_or_default() as u64)), + )?; *offset += 1; @@ -1912,21 +2189,26 @@ impl TxCircuit { layouter.assign_region( || "dev block table", |mut region| { - for (offset, (block_num, cum_num_txs, num_all_txs)) in iter::once((0, 0, 0)) - .chain(block_nums.iter().scan(0, |cum_num_txs, block_num| { - let num_all_txs = num_all_txs_in_blocks[block_num]; - *cum_num_txs += num_txs_in_blocks[block_num]; - - Some((*block_num, *cum_num_txs, num_all_txs)) - })) - .enumerate() + for (offset, (block_num, num_txs, cum_num_txs, num_all_txs)) in + iter::once((0, 0, 0, 0)) + .chain(block_nums.iter().scan(0, |cum_num_txs, block_num| { + let num_txs = num_txs_in_blocks[block_num]; + let num_all_txs = num_all_txs_in_blocks[block_num]; + *cum_num_txs += num_txs; + + Some((*block_num, num_txs, *cum_num_txs, num_all_txs)) + })) + .enumerate() { - for (j, (tag, value)) in - [(CumNumTxs, cum_num_txs as u64), (NumAllTxs, num_all_txs)] - .into_iter() - .enumerate() + for (j, (tag, value)) in [ + (NumTxs, num_txs as u64), + (CumNumTxs, cum_num_txs as u64), + (NumAllTxs, num_all_txs), + ] + .into_iter() + .enumerate() { - let row = offset * 2 + j; + let row = offset * 3 + j; region.assign_fixed( || "block_table.tag", config.block_table.tag, @@ -1971,6 +2253,7 @@ impl TxCircuit { debug_assert_eq!(padding_txs.len() + self.txs.len(), sigs.len()); let mut cum_num_txs; + let mut num_txs; let mut is_padding_tx; let mut num_all_txs_acc = 0; let mut total_l1_popped_before = start_l1_queue_index; @@ -1983,6 +2266,10 @@ impl TxCircuit { !sigs.is_empty() as usize, // tx_id_next TxFieldTag::Null, Value::known(F::zero()), + challenges.keccak_input().map(|_| F::zero()), + None, + None, + None, None, None, None, @@ -1995,6 +2282,9 @@ impl TxCircuit { None, )?; + region.assign_fixed(|| "q_first", config.q_first, 0, || Value::known(F::one()))?; + let zero_rlc = challenges.keccak_input().map(|_| F::zero()); + // Assign all tx fields except for call data for (i, sign_data) in sigs.iter().enumerate() { let tx = if i < self.txs.len() { @@ -2002,14 +2292,21 @@ impl TxCircuit { } else { &padding_txs[i - self.txs.len()] }; + let block_num = tx.block_number; let rlp_unsigned_tx_be_bytes = tx.rlp_unsigned.clone(); let rlp_signed_tx_be_bytes = tx.rlp_signed.clone(); if i < self.txs.len() { cum_num_txs = self .txs .iter() - .filter(|tx| tx.block_number <= self.txs[i].block_number) + .filter(|tx| tx.block_number <= block_num) .count(); + num_txs = self + .txs + .iter() + .filter(|tx| tx.block_number == block_num) + .count(); + log::info!("num_txs: {}", num_txs); is_padding_tx = false; let mut init_new_block = |tx: &Transaction| { if tx.tx_type.is_l1_msg() { @@ -2037,6 +2334,7 @@ impl TxCircuit { } } else { cum_num_txs = 0; + num_txs = 0; is_padding_tx = true; // padding_tx is an l2 tx num_all_txs_acc = (i - self.txs.len() + 1) as u64; @@ -2052,38 +2350,55 @@ impl TxCircuit { }) }; log::debug!("calldata len: {}", tx.call_data.len()); - for (tag, rlp_tag, is_none, value) in [ + for (tag, rlp_tag, is_none, be_bytes_rlc, be_bytes_length, value) in [ // need to be in same order as that tx table load function uses ( Nonce, - Some(Tag::Nonce.into()), + Some(Tag::Nonce.into()), // lookup into RLP table Some(tx.nonce == 0), + rlc_be_bytes(&tx.nonce.to_be_bytes(), challenges.keccak_input()), + Some(tx.nonce.tag_length()), Value::known(F::from(tx.nonce)), ), - ( - Gas, - Some(Tag::Gas.into()), - Some(tx.gas == 0), - Value::known(F::from(tx.gas)), - ), ( GasPrice, Some(Tag::GasPrice.into()), Some(tx.gas_price.is_zero()), + rlc_be_bytes(&tx.gas_price.to_be_bytes(), challenges.keccak_input()), + Some(tx.gas_price.tag_length()), challenges .evm_word() .map(|challenge| rlc(tx.gas_price.to_le_bytes(), challenge)), ), + ( + Gas, + Some(Tag::Gas.into()), + Some(tx.gas == 0), + rlc_be_bytes(&tx.gas.to_be_bytes(), challenges.keccak_input()), + Some(tx.gas.tag_length()), + Value::known(F::from(tx.gas)), + ), ( CallerAddress, Some(Tag::Sender.into()), None, + rlc_be_bytes( + &tx.caller_address.to_fixed_bytes(), + challenges.keccak_input(), + ), + Some(tx.caller_address.tag_length()), Value::known(tx.caller_address.to_scalar().expect("tx.from too big")), ), ( CalleeAddress, Some(Tag::To.into()), Some(tx.callee_address.is_none()), + rlc_be_bytes( + &tx.callee_address + .map_or(vec![], |callee| callee.to_fixed_bytes().to_vec()), + challenges.keccak_input(), + ), + Some(tx.callee_address.tag_length()), Value::known( tx.callee_address .unwrap_or(Address::zero()) @@ -2093,7 +2408,9 @@ impl TxCircuit { ), ( IsCreate, + None, // do not lookup into RLP table None, + zero_rlc, None, Value::known(F::from(tx.is_create as u64)), ), @@ -2101,6 +2418,8 @@ impl TxCircuit { TxFieldTag::Value, Some(Tag::Value.into()), Some(tx.value.is_zero()), + rlc_be_bytes(&tx.value.to_be_bytes(), challenges.keccak_input()), + Some(tx.value.tag_length()), challenges .evm_word() .map(|challenge| rlc(tx.value.to_le_bytes(), challenge)), @@ -2110,36 +2429,55 @@ impl TxCircuit { Some(Tag::Data.into()), Some(tx.call_data.is_empty()), rlc_be_bytes(&tx.call_data, challenges.keccak_input()), + Some(tx.call_data.tag_length()), + rlc_be_bytes(&tx.call_data, challenges.keccak_input()), ), ( CallDataLength, None, None, + zero_rlc, + None, Value::known(F::from(tx.call_data.len() as u64)), ), ( CallDataGasCost, None, None, + zero_rlc, + None, Value::known(F::from(tx.call_data_gas_cost)), ), ( TxDataGasCost, Some(GasCost), None, + zero_rlc, + None, Value::known(F::from(tx.tx_data_gas_cost)), ), - (ChainID, None, None, Value::known(F::from(tx.chain_id))), + ( + ChainID, + Some(Tag::ChainId.into()), + Some(tx.chain_id.is_zero()), + rlc_be_bytes(&tx.chain_id.to_be_bytes(), challenges.keccak_input()), + Some(tx.chain_id.tag_length()), + Value::known(F::from(tx.chain_id)), + ), ( SigV, Some(Tag::SigV.into()), Some(tx.v.is_zero()), + rlc_be_bytes(&tx.v.to_be_bytes(), challenges.keccak_input()), + Some(tx.v.tag_length()), Value::known(F::from(tx.v)), ), ( SigR, Some(Tag::SigR.into()), Some(tx.r.is_zero()), + rlc_be_bytes(&tx.r.to_be_bytes(), challenges.keccak_input()), + Some(tx.r.tag_length()), challenges .evm_word() .map(|challenge| rlc(tx.r.to_le_bytes(), challenge)), @@ -2148,6 +2486,8 @@ impl TxCircuit { SigS, Some(Tag::SigS.into()), Some(tx.s.is_zero()), + rlc_be_bytes(&tx.s.to_be_bytes(), challenges.keccak_input()), + Some(tx.s.tag_length()), challenges .evm_word() .map(|challenge| rlc(tx.s.to_le_bytes(), challenge)), @@ -2156,29 +2496,37 @@ impl TxCircuit { TxSignLength, Some(Len), Some(false), + zero_rlc, + Some(rlp_unsigned_tx_be_bytes.len().tag_length()), Value::known(F::from(rlp_unsigned_tx_be_bytes.len() as u64)), ), ( TxSignRLC, Some(RLC), Some(false), + zero_rlc, + None, challenges.keccak_input().map(|rand| { rlp_unsigned_tx_be_bytes .iter() .fold(F::zero(), |acc, byte| acc * rand + F::from(*byte as u64)) }), ), - (TxSignHash, None, None, tx_sign_hash), + (TxSignHash, None, None, zero_rlc, None, tx_sign_hash), ( TxHashLength, Some(Len), Some(false), + zero_rlc, + Some(rlp_signed_tx_be_bytes.len().tag_length()), Value::known(F::from(rlp_signed_tx_be_bytes.len() as u64)), ), ( TxHashRLC, Some(RLC), Some(false), + zero_rlc, + None, challenges.keccak_input().map(|rand| { rlp_signed_tx_be_bytes .iter() @@ -2189,6 +2537,8 @@ impl TxCircuit { TxFieldTag::TxHash, None, None, + zero_rlc, + None, challenges.evm_word().map(|challenge| { tx.hash .to_fixed_bytes() @@ -2202,12 +2552,16 @@ impl TxCircuit { TxFieldTag::TxType, None, None, + zero_rlc, + None, Value::known(F::from(tx.tx_type as u64)), ), ( BlockNumber, None, None, + zero_rlc, + None, Value::known(F::from(tx.block_number)), ), ] { @@ -2250,10 +2604,14 @@ impl TxCircuit { tx_id_next, // tx_id_next tag, value, + be_bytes_rlc, + be_bytes_length, rlp_tag, is_none, Some(is_padding_tx), Some(cum_num_txs), + Some(num_txs), + None, None, None, Some(cur_block_num), @@ -2272,11 +2630,13 @@ impl TxCircuit { } log::debug!("assigning calldata, offset {}", offset); + assert_eq!(offset, self.max_txs * TX_LEN + 1); // Assign call data let mut calldata_count = 0; for (i, tx) in self.txs.iter().enumerate() { let mut calldata_gas_cost = 0; + let mut calldata_rlc = Value::known(F::zero()); let calldata_length = tx.call_data.len(); calldata_count += calldata_length; for (index, byte) in tx.call_data.iter().enumerate() { @@ -2300,6 +2660,17 @@ impl TxCircuit { (i + 1, false) }; calldata_gas_cost += if byte.is_zero() { 4 } else { 16 }; + if i == 0 && index == 0 { + region.assign_fixed( + || "q_calldata_first", + config.q_calldata_first, + offset, + || Value::known(F::one()), + )?; + } + calldata_rlc = calldata_rlc + .zip(challenges.keccak_input()) + .map(|(rlc, r)| rlc * r + F::from(*byte as u64)); config.assign_row( &mut region, &mut offset, @@ -2308,12 +2679,16 @@ impl TxCircuit { tx_id_next, // tx_id_next CallData, Value::known(F::from(*byte as u64)), + zero_rlc, + None, + None, None, None, None, None, Some(is_final), Some(calldata_gas_cost), + Some(calldata_rlc), None, None, None, @@ -2322,6 +2697,16 @@ impl TxCircuit { } } + assert!(calldata_count <= self.max_calldata); + let q_calldata_last_offset = self.max_txs * TX_LEN + self.max_calldata; + if offset == q_calldata_last_offset + 1 { + region.assign_fixed( + || "q_calldata_last", + config.q_calldata_last, + q_calldata_last_offset, + || Value::known(F::one()), + )?; + } debug_assert_eq!(offset, self.max_txs * TX_LEN + 1 + calldata_count); Ok(offset) @@ -2338,6 +2723,25 @@ impl TxCircuit { layouter.assign_region( || "tx table (calldata zeros and paddings)", |mut region| { + if last_off == self.max_txs * TX_LEN + 1 { + // The txs do not have any call data bytes + // Therefore q_calldata_first is not assigned in prev region. + region.assign_fixed( + || "q_calldata_first", + config.q_calldata_first, + 0, + || Value::known(F::one()), + )?; + } + if last_off < self.max_txs * TX_LEN + 1 + self.max_calldata { + let calldata_count = last_off - self.max_txs * TX_LEN - 1; + region.assign_fixed( + || "q_calldata_last", + config.q_calldata_last, + self.max_calldata - calldata_count - 1, + || Value::known(F::one()), + )?; + } config.assign_calldata_zeros( &mut region, 0, diff --git a/zkevm-circuits/src/tx_circuit/test.rs b/zkevm-circuits/src/tx_circuit/test.rs index f1608c708f..cc9bd9c9a4 100644 --- a/zkevm-circuits/src/tx_circuit/test.rs +++ b/zkevm-circuits/src/tx_circuit/test.rs @@ -138,7 +138,7 @@ fn run( #[cfg(feature = "scroll")] fn tx_circuit_2tx_2max_tx() { const NUM_TXS: usize = 2; - const MAX_TXS: usize = 4; + const MAX_TXS: usize = 2; const MAX_CALLDATA: usize = 32; assert_eq!( diff --git a/zkevm-circuits/src/witness/l1_msg.rs b/zkevm-circuits/src/witness/l1_msg.rs index 97c0633738..1eb13bb916 100644 --- a/zkevm-circuits/src/witness/l1_msg.rs +++ b/zkevm-circuits/src/witness/l1_msg.rs @@ -1,7 +1,7 @@ use crate::{ evm_circuit::param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_U64, N_BYTES_WORD}, witness::{ - rlp_fsm::{N_BYTES_CALLDATA, N_BYTES_LIST}, + rlp_fsm::{MAX_TAG_LENGTH_OF_LIST, N_BYTES_CALLDATA}, Format::L1MsgHash, RomTableRow, Tag::{BeginList, Data, EndList, Gas, Nonce, Sender, To, TxType, Value as TxValue}, @@ -21,7 +21,7 @@ impl Encodable for L1MsgTx { pub fn rom_table_rows() -> Vec { let rows = vec![ (TxType, BeginList, 1, vec![1]), - (BeginList, Nonce, N_BYTES_LIST, vec![2]), + (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![2]), (Nonce, Gas, N_BYTES_U64, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), (To, TxValue, N_BYTES_ACCOUNT_ADDRESS, vec![5]), diff --git a/zkevm-circuits/src/witness/rlp_fsm.rs b/zkevm-circuits/src/witness/rlp_fsm.rs index ed0ba00794..487691fa8d 100644 --- a/zkevm-circuits/src/witness/rlp_fsm.rs +++ b/zkevm-circuits/src/witness/rlp_fsm.rs @@ -1,10 +1,59 @@ -use eth_types::Field; +use eth_types::{Address, Field, H160, U256}; use gadgets::{impl_expr, util::Expr}; use halo2_proofs::{arithmetic::FieldExt, circuit::Value, plonk::Expression}; use strum_macros::EnumIter; use crate::util::Challenges; +pub(crate) trait ValueTagLength { + fn tag_length(&self) -> u32; +} + +impl ValueTagLength for u64 { + fn tag_length(&self) -> u32 { + // note that 0_u64 is encoded as [0x80] in RLP + // see the relevant code at https://github.com/paritytech/parity-common/blob/master/rlp/src/impls.rs#L208 + (64 - self.leading_zeros() + 7) / 8 + } +} + +impl ValueTagLength for usize { + fn tag_length(&self) -> u32 { + // usize is treated as same as u64 + (*self as u64).tag_length() + } +} + +impl ValueTagLength for U256 { + fn tag_length(&self) -> u32 { + // note that U256::zero() is encoded as [0x80] in RLP + // see the relevant code at https://github.com/paritytech/parity-common/blob/impl-rlp-v0.3.0/primitive-types/src/lib.rs#L117 + (256 - self.leading_zeros() + 7) / 8 + } +} + +impl ValueTagLength for H160 { + fn tag_length(&self) -> u32 { + 20 + } +} + +impl ValueTagLength for Option
{ + fn tag_length(&self) -> u32 { + if self.is_none() { + 0 + } else { + self.unwrap().tag_length() + } + } +} + +impl ValueTagLength for Vec { + fn tag_length(&self) -> u32 { + self.len() as u32 + } +} + /// RLP tags #[derive(Default, Clone, Copy, Debug, EnumIter, PartialEq, Eq)] pub enum Tag { @@ -151,13 +200,15 @@ use crate::{ }, }; -// The number of bytes of list can not larger than 2^24. -pub(crate) const N_BYTES_LIST: usize = 1 << 24; +// The number of bytes of list can not be larger than 2^24 = 2^(8*3). +// This const is meant to be the maximum of tag_length for representing a `LongList`. +// For example, [0xf9, 0xff, 0xff] has tag_length = 2 and has 0xffff bytes inside. +pub(crate) const MAX_TAG_LENGTH_OF_LIST: usize = 3; pub(crate) const N_BYTES_CALLDATA: usize = 1 << 24; fn eip155_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, N_BYTES_LIST, vec![1]), + (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), (Nonce, GasPrice, N_BYTES_U64, vec![2]), (GasPrice, Gas, N_BYTES_WORD, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), @@ -179,7 +230,7 @@ fn eip155_tx_sign_rom_table_rows() -> Vec { fn eip155_tx_hash_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, N_BYTES_LIST, vec![1]), + (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), (Nonce, GasPrice, N_BYTES_U64, vec![2]), (GasPrice, Gas, N_BYTES_WORD, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), @@ -201,7 +252,7 @@ fn eip155_tx_hash_rom_table_rows() -> Vec { pub fn pre_eip155_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, N_BYTES_LIST, vec![1]), + (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), (Nonce, GasPrice, N_BYTES_U64, vec![2]), (GasPrice, Gas, N_BYTES_WORD, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), @@ -220,7 +271,7 @@ pub fn pre_eip155_tx_sign_rom_table_rows() -> Vec { pub fn pre_eip155_tx_hash_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, N_BYTES_LIST, vec![1]), + (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), (Nonce, GasPrice, N_BYTES_U64, vec![2]), (GasPrice, Gas, N_BYTES_WORD, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), @@ -243,7 +294,7 @@ pub fn pre_eip155_tx_hash_rom_table_rows() -> Vec { pub fn eip1559_tx_hash_rom_table_rows() -> Vec { let rows = vec![ (TxType, BeginList, 1, vec![1]), - (BeginList, ChainId, N_BYTES_LIST, vec![2]), + (BeginList, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), (ChainId, Nonce, N_BYTES_U64, vec![3]), (Nonce, MaxPriorityFeePerGas, N_BYTES_U64, vec![4]), (MaxPriorityFeePerGas, MaxFeePerGas, N_BYTES_WORD, vec![5]), @@ -252,21 +303,26 @@ pub fn eip1559_tx_hash_rom_table_rows() -> Vec { (To, TxValue, N_BYTES_ACCOUNT_ADDRESS, vec![8]), (TxValue, Data, N_BYTES_WORD, vec![9]), (Data, BeginVector, N_BYTES_CALLDATA, vec![10, 11]), - (BeginVector, EndVector, N_BYTES_LIST, vec![21]), // access_list is none - (BeginVector, BeginList, N_BYTES_LIST, vec![12]), - (BeginList, AccessListAddress, N_BYTES_LIST, vec![13]), + (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![21]), // access_list is none + (BeginVector, BeginList, MAX_TAG_LENGTH_OF_LIST, vec![12]), + ( + BeginList, + AccessListAddress, + MAX_TAG_LENGTH_OF_LIST, + vec![13], + ), ( AccessListAddress, BeginVector, N_BYTES_ACCOUNT_ADDRESS, vec![14, 15], ), - (BeginVector, EndVector, N_BYTES_LIST, vec![18]), /* access_list.storage_keys - * is none */ + (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![18]), /* access_list.storage_keys + * is none */ ( BeginVector, AccessListStorageKey, - N_BYTES_LIST, + MAX_TAG_LENGTH_OF_LIST, vec![16, 17], ), (AccessListStorageKey, EndVector, N_BYTES_WORD, vec![18]), // finished parsing storage keys @@ -295,7 +351,7 @@ pub fn eip1559_tx_hash_rom_table_rows() -> Vec { pub fn eip1559_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, ChainId, N_BYTES_LIST, vec![1]), + (BeginList, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![1]), (ChainId, Nonce, N_BYTES_U64, vec![2]), (Nonce, MaxPriorityFeePerGas, N_BYTES_U64, vec![3]), (MaxPriorityFeePerGas, MaxFeePerGas, N_BYTES_WORD, vec![4]), @@ -304,20 +360,27 @@ pub fn eip1559_tx_sign_rom_table_rows() -> Vec { (To, TxValue, N_BYTES_ACCOUNT_ADDRESS, vec![7]), (TxValue, Data, N_BYTES_WORD, vec![8]), (Data, BeginVector, N_BYTES_CALLDATA, vec![9, 10]), - (BeginVector, EndVector, N_BYTES_LIST, vec![20]), // access_list is none - (BeginVector, BeginList, N_BYTES_LIST, vec![11]), - (BeginList, AccessListAddress, N_BYTES_LIST, vec![12]), + (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![20]), // access_list is none + (BeginVector, BeginList, MAX_TAG_LENGTH_OF_LIST, vec![11]), + ( + BeginList, + AccessListAddress, + MAX_TAG_LENGTH_OF_LIST, + vec![12], + ), ( AccessListAddress, BeginVector, N_BYTES_ACCOUNT_ADDRESS, vec![13, 14], ), - (BeginVector, EndVector, N_BYTES_LIST, vec![17]), /* access_list.storage_keys is none */ + (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![17]), /* access_list.storage_keys + * is + * none */ ( BeginVector, AccessListStorageKey, - N_BYTES_LIST, + MAX_TAG_LENGTH_OF_LIST, vec![15, 16], ), (AccessListStorageKey, EndVector, N_BYTES_WORD, vec![17]), // finished parsing storage keys @@ -497,6 +560,12 @@ pub struct RlpTable { pub rlp_tag: RlpTag, /// The tag's value pub tag_value: Value, + /// RLC of the tag's big-endian bytes + pub tag_bytes_rlc: Value, + /// Length of the tag's big-endian bytes + /// Note that we use (tag_bytes_rlc, tag_length) to identify + /// the tag's dynamic-sized big-endian bytes + pub tag_length: usize, /// If current row is for output pub is_output: bool, /// If current tag's value is None. @@ -522,8 +591,6 @@ pub struct StateMachine { pub byte_value: u8, /// The index of the actual bytes of tag pub tag_idx: usize, - /// The length of the actual bytes of tag - pub tag_length: usize, /// The accumulated value of bytes up to `tag_idx` of tag /// In most cases, RlpTable.tag_value == StateMachine.tag_value_acc. /// However, for RlpTag::Len, we have @@ -567,4 +634,5 @@ pub(crate) struct SmState { pub(crate) tag_idx: usize, pub(crate) tag_length: usize, pub(crate) tag_value_acc: Value, + pub(crate) tag_bytes_rlc: Value, } diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 1da83b83c5..5074292945 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -170,12 +170,6 @@ impl Transaction { Value::known(F::zero()), Value::known(F::from(self.nonce)), ], - [ - Value::known(F::from(self.id as u64)), - Value::known(F::from(TxContextFieldTag::Gas as u64)), - Value::known(F::zero()), - Value::known(F::from(self.gas)), - ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::GasPrice as u64)), @@ -184,6 +178,12 @@ impl Transaction { .evm_word() .map(|challenge| rlc::value(&self.gas_price.to_le_bytes(), challenge)), ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::Gas as u64)), + Value::known(F::zero()), + Value::known(F::from(self.gas)), + ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::CallerAddress as u64)), @@ -391,6 +391,7 @@ impl Transaction { tag_idx: 0, tag_length: 0, tag_value_acc: Value::known(F::zero()), + tag_bytes_rlc: Value::known(F::zero()), byte_idx: 0, depth: 0, }; @@ -459,6 +460,8 @@ impl Transaction { assert!(!cur.tag.is_list()); is_output = true; cur.tag_value_acc = Value::known(F::from(byte_value as u64)); + cur.tag_bytes_rlc = cur.tag_value_acc; + cur.tag_length = 1; // state transitions next.state = DecodeTagStart; @@ -468,6 +471,8 @@ impl Transaction { is_output = true; is_none = true; cur.tag_value_acc = Value::known(F::zero()); + cur.tag_bytes_rlc = cur.tag_value_acc; + cur.tag_length = 0; // state transitions next.state = DecodeTagStart; @@ -480,6 +485,7 @@ impl Transaction { next.tag_length = (byte_value - 0x80) as usize; next.tag_value_acc = Value::known(F::from(rlp_bytes[cur.byte_idx + 1] as u64)); + next.tag_bytes_rlc = next.tag_value_acc; next.state = State::Bytes; } else if byte_value < 0xc0 { // assertions @@ -499,6 +505,7 @@ impl Transaction { rlp_tag = RlpTag::Len; } cur.tag_value_acc = Value::known(F::from(u64::from(byte_value - 0xc0))); + cur.tag_length = 1; // state transitions let num_bytes_of_new_list = usize::from(byte_value - 0xc0); @@ -543,6 +550,8 @@ impl Transaction { next.tag_idx = cur.tag_idx + 1; next.tag_value_acc = cur.tag_value_acc * b + Value::known(F::from(rlp_bytes[cur.byte_idx + 1] as u64)); + next.tag_bytes_rlc = cur.tag_bytes_rlc * keccak_rand + + Value::known(F::from(rlp_bytes[cur.byte_idx + 1] as u64)); } else { // assertions is_output = true; @@ -570,6 +579,7 @@ impl Transaction { next.tag_length = lb_len; next.tag_value_acc = Value::known(F::from(u64::from(rlp_bytes[cur.byte_idx + 1]))); + next.tag_bytes_rlc = next.tag_value_acc; next.state = State::Bytes; } } @@ -654,6 +664,13 @@ impl Transaction { RlpTag::Tag(_) => cur.tag_value_acc, RlpTag::Null => unreachable!("Null is not used"), }; + let (tag_bytes_rlc, tag_length) = match rlp_tag { + // Len | RLC | GasCost are just meta-info extracted from keccak input bytes + RlpTag::Len => (Value::known(F::zero()), cur.tag_length), + RlpTag::RLC | RlpTag::GasCost => (Value::known(F::zero()), 0), + RlpTag::Tag(_) => (cur.tag_bytes_rlc, cur.tag_length), + RlpTag::Null => unreachable!("Null is not used"), + }; witness.push(RlpFsmWitnessRow { rlp_table: RlpTable { @@ -661,6 +678,8 @@ impl Transaction { format, rlp_tag, tag_value, + tag_bytes_rlc, + tag_length, is_output, is_none, }, @@ -673,7 +692,6 @@ impl Transaction { byte_rev_idx: rlp_bytes.len() - cur.byte_idx, byte_value, tag_idx: cur.tag_idx, - tag_length: cur.tag_length, tag_acc_value: cur.tag_value_acc, depth: cur.depth, bytes_rlc, From f2790caa024ea3f7f7fd61972f5a8885af958ed7 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 08:11:29 -0700 Subject: [PATCH 21/33] add pi comments --- aggregator/src/util.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aggregator/src/util.rs b/aggregator/src/util.rs index ab15580f73..9a4c1e9411 100644 --- a/aggregator/src/util.rs +++ b/aggregator/src/util.rs @@ -20,6 +20,10 @@ use zkevm_circuits::keccak_circuit::keccak_packed_multi::{ // Calculates the maximum keccak updates (1 absorb, or 1 f-box invoke) // needed for the number of snarks pub(crate) fn get_max_keccak_updates(max_snarks: usize) -> usize { + // The public input hash for the batch is derived from hashing + // chain_id || chunk_0's prev_state || chunk_k-1's post_state || + // chunk_k-1's withdraw_root || batch_data_hash. + // In total there're 168 bytes. Therefore 2 pi rounds are required. let pi_rounds = 2; let chunk_hash_rounds = 2 * max_snarks; let data_hash_rounds = get_data_hash_keccak_updates(max_snarks); From 1c558be78d79eee51d023477efc6f5f978c33c65 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 08:13:36 -0700 Subject: [PATCH 22/33] rename preimage col idx --- aggregator/src/aggregation/config.rs | 2 +- zkevm-circuits/src/keccak_circuit.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aggregator/src/aggregation/config.rs b/aggregator/src/aggregation/config.rs index 051fb84dff..4a57a03c95 100644 --- a/aggregator/src/aggregation/config.rs +++ b/aggregator/src/aggregation/config.rs @@ -86,7 +86,7 @@ impl AggregationConfig { let columns = keccak_circuit_config.cell_manager.columns(); // enabling equality for preimage column - meta.enable_equality(columns[keccak_circuit_config.preimage_column].advice); + meta.enable_equality(columns[keccak_circuit_config.preimage_column_index].advice); // enable equality for the digest column meta.enable_equality(columns.last().unwrap().advice); // enable equality for the data RLC column diff --git a/zkevm-circuits/src/keccak_circuit.rs b/zkevm-circuits/src/keccak_circuit.rs index 8e5a670b9d..e270ac74c0 100644 --- a/zkevm-circuits/src/keccak_circuit.rs +++ b/zkevm-circuits/src/keccak_circuit.rs @@ -66,7 +66,7 @@ pub struct KeccakCircuitConfig { chi_base_table: [TableColumn; 2], pack_table: [TableColumn; 2], /// The column for enabling copy constraints in aggregator - pub preimage_column: usize, + pub preimage_column_index: usize, _marker: PhantomData, } @@ -189,7 +189,7 @@ impl SubCircuitConfig for KeccakCircuitConfig { log::debug!("- Post absorb:"); log::debug!("Lookups: {}", lookup_counter); log::debug!("Columns: {}", cell_manager.get_width()); - let preimage_column: usize = cell_manager.get_width() + 1; + let preimage_column_index: usize = cell_manager.get_width() + 1; total_lookup_counter += lookup_counter; // Process inputs. @@ -868,7 +868,7 @@ impl SubCircuitConfig for KeccakCircuitConfig { normalize_6, chi_base_table, pack_table, - preimage_column, + preimage_column_index, _marker: PhantomData, } } From b728347081759376ef5cfc6a5d3d28fdc7f9ca95 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 08:23:26 -0700 Subject: [PATCH 23/33] add keccak rows check --- zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 726f09d3b0..73e40a316b 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -15,10 +15,15 @@ const MAX_DEGREE: usize = 9; /// Obtain the rows required for 1 iteration of f-box's inner round /// function (consisting of 5 phases) within Keccak circuit pub fn get_num_rows_per_round() -> usize { - var("KECCAK_ROWS") + let r = var("KECCAK_ROWS") .unwrap_or_else(|_| format!("{DEFAULT_KECCAK_ROWS}")) .parse() - .expect("Cannot parse KECCAK_ROWS env var as usize") + .expect("Cannot parse KECCAK_ROWS env var as usize"); + assert!( + r >= 9 && r <= 32, + "env variable KECCAK_ROWS must be in range [9, 32]." + ); + r } /// Obtain the rows required for 1 iteration of the f-box /// function (consisting of nr = 12 + 2*l inner rounds) From 409485551da0712d9726df7d905055052d6f5ba9 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 08:26:44 -0700 Subject: [PATCH 24/33] rename input bytes col finder fn --- aggregator/src/core.rs | 2 +- zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index f55df0963c..70d8de74fb 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -244,7 +244,7 @@ pub(crate) fn extract_hash_cells( if cur_preimage_index.is_some() && *cur_preimage_index.unwrap() == offset { hash_input_cells.push( - row[keccak_packed_multi::get_input_bytes_col_cell_manager() + 4] + row[keccak_packed_multi::get_input_bytes_col_idx_in_cell_manager() + 4] .clone(), ); cur_preimage_index = preimage_indices_iter.next(); diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 73e40a316b..765bf5ee47 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -35,7 +35,7 @@ pub fn get_num_rows_per_update() -> usize { /// within cell_manager for an inner round. /// This value is determined by the number of rows allocated /// to each inner round and target part_size for u64 -pub fn get_input_bytes_col_cell_manager() -> usize { +pub fn get_input_bytes_col_idx_in_cell_manager() -> usize { let mut col: usize = 0; let inner_round_num_rows = get_num_rows_per_round(); From 3c51241fe8ce9220ba119720f9fc835415434da4 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 08:32:02 -0700 Subject: [PATCH 25/33] modify keccak row env constaint --- zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 765bf5ee47..331d5aeb83 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -20,8 +20,8 @@ pub fn get_num_rows_per_round() -> usize { .parse() .expect("Cannot parse KECCAK_ROWS env var as usize"); assert!( - r >= 9 && r <= 32, - "env variable KECCAK_ROWS must be in range [9, 32]." + r >= 9, + "env variable KECCAK_ROWS must be greater than (NUM_BYTES_PER_WORD + 1)." ); r } From 13d618dd35592201098d7610602a86d76f066571 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 08:36:39 -0700 Subject: [PATCH 26/33] modify keccak row env constaint --- zkevm-circuits/src/keccak_circuit/param.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zkevm-circuits/src/keccak_circuit/param.rs b/zkevm-circuits/src/keccak_circuit/param.rs index 847c10e8e6..7046a105fc 100644 --- a/zkevm-circuits/src/keccak_circuit/param.rs +++ b/zkevm-circuits/src/keccak_circuit/param.rs @@ -11,6 +11,7 @@ pub(crate) const NUM_BITS_PER_WORD: usize = NUM_BYTES_PER_WORD * NUM_BITS_PER_BY pub(crate) const KECCAK_WIDTH: usize = 5 * 5; pub(crate) const KECCAK_WIDTH_IN_BITS: usize = KECCAK_WIDTH * NUM_BITS_PER_WORD; pub(crate) const NUM_ROUNDS: usize = 24; +pub(crate) const NUM_SETUP_VARS_FOR_ROUND: usize = KECCAK_WIDTH + 3; // 25 state vars, absorb_from, absorb_data, absorb_result pub(crate) const NUM_WORDS_TO_ABSORB: usize = 17; pub(crate) const NUM_WORDS_TO_SQUEEZE: usize = 4; pub(crate) const ABSORB_WIDTH_PER_ROW: usize = NUM_BITS_PER_WORD; From 9126451212a43092d090dd8da7c8e42c7b0649fb Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 08:37:54 -0700 Subject: [PATCH 27/33] add named constant setup vars --- zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 331d5aeb83..e91f5ba210 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -39,8 +39,8 @@ pub fn get_input_bytes_col_idx_in_cell_manager() -> usize { let mut col: usize = 0; let inner_round_num_rows = get_num_rows_per_round(); - col += 28 / inner_round_num_rows; - if inner_round_num_rows * col < 28 { + col += NUM_SETUP_VARS_FOR_ROUND / inner_round_num_rows; + if inner_round_num_rows * col < NUM_SETUP_VARS_FOR_ROUND { col += 1; } From ac5e1771385b2e7fd4af3283883bef27aa8bd14f Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 09:02:17 -0700 Subject: [PATCH 28/33] modify keccak row check --- zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index e91f5ba210..048543c253 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -20,7 +20,7 @@ pub fn get_num_rows_per_round() -> usize { .parse() .expect("Cannot parse KECCAK_ROWS env var as usize"); assert!( - r >= 9, + r >= NUM_BYTES_PER_WORD + 1, "env variable KECCAK_ROWS must be greater than (NUM_BYTES_PER_WORD + 1)." ); r From 0f1215e3df7883e986745b39d28cf9d483503f1c Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 09:16:59 -0700 Subject: [PATCH 29/33] clippy advised --- zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 048543c253..bf7e7bbfc2 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -20,7 +20,7 @@ pub fn get_num_rows_per_round() -> usize { .parse() .expect("Cannot parse KECCAK_ROWS env var as usize"); assert!( - r >= NUM_BYTES_PER_WORD + 1, + r > NUM_BYTES_PER_WORD, "env variable KECCAK_ROWS must be greater than (NUM_BYTES_PER_WORD + 1)." ); r From a6b81d404c7c691a8de5bb63c281e9f7acd00450 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 09:27:14 -0700 Subject: [PATCH 30/33] add comments on chunk hash --- aggregator/src/util.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aggregator/src/util.rs b/aggregator/src/util.rs index 9a4c1e9411..e523112b4a 100644 --- a/aggregator/src/util.rs +++ b/aggregator/src/util.rs @@ -25,6 +25,9 @@ pub(crate) fn get_max_keccak_updates(max_snarks: usize) -> usize { // chunk_k-1's withdraw_root || batch_data_hash. // In total there're 168 bytes. Therefore 2 pi rounds are required. let pi_rounds = 2; + // Hash for each chunk is derived from hashing the chunk's + // chain_id || prev_state || post_state || withdraw_root || data_hash + // Each chunk hash therefore also requires 2 keccak rounds for 168 bytes. let chunk_hash_rounds = 2 * max_snarks; let data_hash_rounds = get_data_hash_keccak_updates(max_snarks); From 8b51c34d76628338b1c4c723d94c8d7fe391c1a1 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 09:31:25 -0700 Subject: [PATCH 31/33] fmt --- aggregator/src/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aggregator/src/util.rs b/aggregator/src/util.rs index e523112b4a..35b3c706f5 100644 --- a/aggregator/src/util.rs +++ b/aggregator/src/util.rs @@ -25,7 +25,7 @@ pub(crate) fn get_max_keccak_updates(max_snarks: usize) -> usize { // chunk_k-1's withdraw_root || batch_data_hash. // In total there're 168 bytes. Therefore 2 pi rounds are required. let pi_rounds = 2; - // Hash for each chunk is derived from hashing the chunk's + // Hash for each chunk is derived from hashing the chunk's // chain_id || prev_state || post_state || withdraw_root || data_hash // Each chunk hash therefore also requires 2 keccak rounds for 168 bytes. let chunk_hash_rounds = 2 * max_snarks; From feb7699045f707c0eb742dae7841b5727b84751d Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 16:30:09 -0700 Subject: [PATCH 32/33] avoid constant lookup table --- aggregator/src/core.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index 70d8de74fb..9504af920c 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -27,7 +27,7 @@ use zkevm_circuits::{ keccak_packed_multi::{self, multi_keccak}, KeccakCircuit, KeccakCircuitConfig, }, - table::LookupTable, + table::{KeccakTable, LookupTable}, util::Challenges, }; @@ -244,8 +244,13 @@ pub(crate) fn extract_hash_cells( if cur_preimage_index.is_some() && *cur_preimage_index.unwrap() == offset { hash_input_cells.push( - row[keccak_packed_multi::get_input_bytes_col_idx_in_cell_manager() + 4] - .clone(), + row[keccak_packed_multi::get_input_bytes_col_idx_in_cell_manager() + + >::columns( + &keccak_config.keccak_table, + ) + .len() + - 1] + .clone(), ); cur_preimage_index = preimage_indices_iter.next(); } From ce79016e0f18026eb38556aeb67b2e40fe51a496 Mon Sep 17 00:00:00 2001 From: darth-cy Date: Wed, 16 Aug 2023 17:18:32 -0700 Subject: [PATCH 33/33] avoid repetitive computation of input_bytes_col_idx --- aggregator/src/core.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/aggregator/src/core.rs b/aggregator/src/core.rs index 9504af920c..a25ce31633 100644 --- a/aggregator/src/core.rs +++ b/aggregator/src/core.rs @@ -239,19 +239,16 @@ pub(crate) fn extract_hash_cells( let timer = start_timer!(|| "assign row"); log::trace!("witness length: {}", witness.len()); + let input_bytes_col_idx = + keccak_packed_multi::get_input_bytes_col_idx_in_cell_manager() + + >::columns(&keccak_config.keccak_table) + .len() + - 1; for (offset, keccak_row) in witness.iter().enumerate() { let row = keccak_config.set_row(&mut region, offset, keccak_row)?; if cur_preimage_index.is_some() && *cur_preimage_index.unwrap() == offset { - hash_input_cells.push( - row[keccak_packed_multi::get_input_bytes_col_idx_in_cell_manager() - + >::columns( - &keccak_config.keccak_table, - ) - .len() - - 1] - .clone(), - ); + hash_input_cells.push(row[input_bytes_col_idx].clone()); cur_preimage_index = preimage_indices_iter.next(); } if cur_digest_index.is_some() && *cur_digest_index.unwrap() == offset {