diff --git a/aggregator/src/aggregation/blob_data.rs b/aggregator/src/aggregation/blob_data.rs index 9c847cd92b..5a4a2af5eb 100644 --- a/aggregator/src/aggregation/blob_data.rs +++ b/aggregator/src/aggregation/blob_data.rs @@ -13,6 +13,7 @@ use zkevm_circuits::{ }; use crate::{ + aggregation::rlc::POWS_OF_256, blob::{ BlobData, BLOB_WIDTH, N_BYTES_31, N_BYTES_32, N_ROWS_BLOB_DATA_CONFIG, N_ROWS_DATA, N_ROWS_DIGEST_BYTES, N_ROWS_DIGEST_RLC, N_ROWS_METADATA, @@ -36,6 +37,8 @@ pub struct BlobDataConfig { /// A boolean witness that is set only when we encounter the end of a chunk. We enable a lookup /// to the Keccak table when the boundary is met. is_boundary: Column, + /// A running accumulator of the boundary counts. + boundary_count: Column, /// A boolean witness to indicate padded rows at the end of the data section. is_padding: Column, /// Represents the running random linear combination of bytes seen so far, that are a part of @@ -66,6 +69,7 @@ struct AssignedBlobDataConfig { pub accumulator: AssignedCell, pub chunk_idx: AssignedCell, pub is_boundary: AssignedCell, + pub boundary_count: AssignedCell, pub is_padding: AssignedCell, pub preimage_rlc: AssignedCell, pub digest_rlc: AssignedCell, @@ -85,6 +89,7 @@ impl BlobDataConfig { byte: meta.advice_column(), accumulator: meta.advice_column(), is_boundary: meta.advice_column(), + boundary_count: meta.advice_column(), chunk_idx: meta.advice_column(), is_padding: meta.advice_column(), preimage_rlc: meta.advice_column_in(SecondPhase), @@ -97,6 +102,7 @@ impl BlobDataConfig { meta.enable_equality(config.byte); meta.enable_equality(config.accumulator); meta.enable_equality(config.is_boundary); + meta.enable_equality(config.boundary_count); meta.enable_equality(config.is_padding); meta.enable_equality(config.chunk_idx); meta.enable_equality(config.preimage_rlc); @@ -121,18 +127,25 @@ impl BlobDataConfig { let cond = is_not_hash * is_boundary * (1.expr() - is_padding_next); let chunk_idx_curr = meta.query_advice(config.chunk_idx, Rotation::cur()); let chunk_idx_next = meta.query_advice(config.chunk_idx, Rotation::next()); - // chunk_idx increases by at least 1 and at most MAX_AGG_SNARKS when condition is met. - vec![(cond * (chunk_idx_next - chunk_idx_curr - 1.expr()), config.chunk_idx_range_table.into())] + // chunk_idx increases by at least 1 and at most MAX_AGG_SNARKS when condition is + // met. + vec![( + cond * (chunk_idx_next - chunk_idx_curr - 1.expr()), + config.chunk_idx_range_table.into(), + )] }, ); meta.lookup( - "chunk_idx for non-padding, data rows in [1..MAX_AGG_SNARKS]", + "BlobDataConfig (chunk_idx for non-padding, data rows in [1..MAX_AGG_SNARKS])", |meta| { let is_data = meta.query_selector(config.data_selector); let is_padding = meta.query_advice(config.is_padding, Rotation::cur()); let chunk_idx = meta.query_advice(config.chunk_idx, Rotation::cur()); - vec![(is_data * (1.expr() - is_padding) * (chunk_idx - 1.expr()), config.chunk_idx_range_table.into())] + vec![( + is_data * (1.expr() - is_padding) * (chunk_idx - 1.expr()), + config.chunk_idx_range_table.into(), + )] }, ); @@ -147,6 +160,9 @@ impl BlobDataConfig { let preimage_rlc_next = meta.query_advice(config.preimage_rlc, Rotation::next()); let byte_next = meta.query_advice(config.byte, Rotation::next()); + let boundary_count_curr = meta.query_advice(config.boundary_count, Rotation::cur()); + let boundary_count_prev = meta.query_advice(config.boundary_count, Rotation::prev()); + vec![ // if boundary followed by padding, length and preimage_rlc is 0. cond.expr() * is_padding_next.expr() * len_next.expr(), @@ -157,6 +173,9 @@ impl BlobDataConfig { cond.expr() * (1.expr() - is_padding_next.expr()) * (preimage_rlc_next - byte_next.expr()), + // the boundary count increments, i.e. + // boundary_count_curr == boundary_count_prev + 1 + cond.expr() * (boundary_count_curr - boundary_count_prev - 1.expr()), ] }); @@ -175,6 +194,8 @@ impl BlobDataConfig { let preimage_rlc_curr = meta.query_advice(config.preimage_rlc, Rotation::cur()); let preimage_rlc_next = meta.query_advice(config.preimage_rlc, Rotation::next()); let byte_next = meta.query_advice(config.byte, Rotation::next()); + let boundary_count_curr = meta.query_advice(config.boundary_count, Rotation::cur()); + let boundary_count_prev = meta.query_advice(config.boundary_count, Rotation::prev()); vec![ // chunk idx unchanged. @@ -183,6 +204,8 @@ impl BlobDataConfig { cond.expr() * (len_next - len_curr - 1.expr()), // preimage rlc is updated. cond.expr() * (preimage_rlc_curr * r + byte_next - preimage_rlc_next), + // boundary count continues. + cond.expr() * (boundary_count_curr - boundary_count_prev), ] }); @@ -193,6 +216,8 @@ impl BlobDataConfig { let is_padding_next = meta.query_advice(config.is_padding, Rotation::next()); let diff = is_padding_next - is_padding_curr.expr(); let byte = meta.query_advice(config.byte, Rotation::cur()); + let boundary_count_curr = meta.query_advice(config.boundary_count, Rotation::cur()); + let boundary_count_prev = meta.query_advice(config.boundary_count, Rotation::prev()); vec![ // byte is 0 when padding in the "chunk data" section. @@ -203,6 +228,8 @@ impl BlobDataConfig { is_data.expr() * is_padding_curr.expr() * (1.expr() - is_padding_curr.expr()), // is_padding transitions from 0 -> 1 only once. is_data.expr() * diff.expr() * (1.expr() - diff.expr()), + // boundary count continues if padding + is_data.expr() * is_padding_curr * (boundary_count_curr - boundary_count_prev), ] }); @@ -333,6 +360,7 @@ impl BlobDataConfig { } let mut assigned_rows = Vec::with_capacity(N_ROWS_BLOB_DATA_CONFIG); + let mut count = 0u64; for (i, row) in rows.iter().enumerate() { let byte = region.assign_advice( || "byte", @@ -358,6 +386,18 @@ impl BlobDataConfig { i, || Value::known(Fr::from(row.is_boundary as u64)), )?; + let bcount = if (N_ROWS_METADATA..N_ROWS_METADATA + N_ROWS_DATA).contains(&i) { + count += row.is_boundary as u64; + count + } else { + 0 + }; + let boundary_count = region.assign_advice( + || "boundary_count", + self.boundary_count, + i, + || Value::known(Fr::from(bcount)), + )?; let is_padding = region.assign_advice( || "is_padding", self.is_padding, @@ -381,6 +421,7 @@ impl BlobDataConfig { accumulator, chunk_idx, is_boundary, + boundary_count, is_padding, preimage_rlc, digest_rlc, @@ -415,17 +456,42 @@ impl BlobDataConfig { region.constrain_equal(one.cell(), one_cell)?; one }; - let two_fifty_six = { - let two_fifty_six = rlc_config.load_private( - &mut region, - &Fr::from(256), - &mut rlc_config_offset, - )?; - let two_fifty_six_fixed = rlc_config - .two_hundred_and_fifty_size_cell(two_fifty_six.cell().region_index); - region.constrain_equal(two_fifty_six.cell(), two_fifty_six_fixed)?; - two_fifty_six + let fixed_chunk_indices = { + let mut fixed_chunk_indices = vec![one.clone()]; + for i in 2..=MAX_AGG_SNARKS { + let i_cell = rlc_config.load_private( + &mut region, + &Fr::from(i as u64), + &mut rlc_config_offset, + )?; + let i_fixed_cell = rlc_config + .fixed_up_to_max_agg_snarks_cell(i_cell.cell().region_index, i); + region.constrain_equal(i_cell.cell(), i_fixed_cell)?; + fixed_chunk_indices.push(i_cell); + } + fixed_chunk_indices }; + let pows_of_256 = { + let mut pows_of_256 = vec![one.clone()]; + for (exponent, pow_of_256) in (1..=POWS_OF_256).zip_eq( + std::iter::successors(Some(Fr::from(256)), |n| Some(n * Fr::from(256))) + .take(POWS_OF_256), + ) { + let pow_cell = rlc_config.load_private( + &mut region, + &pow_of_256, + &mut rlc_config_offset, + )?; + let fixed_pow_cell = rlc_config.pow_of_two_hundred_and_fifty_six_cell( + pow_cell.cell().region_index, + exponent, + ); + region.constrain_equal(pow_cell.cell(), fixed_pow_cell)?; + pows_of_256.push(pow_cell); + } + pows_of_256 + }; + let two_fifty_six = pows_of_256[1].clone(); // read randomness challenges for RLC computations. let r_keccak = rlc_config.read_challenge1( @@ -579,6 +645,8 @@ impl BlobDataConfig { // on the last row of the "metadata" section we want to ensure the keccak table // lookup would be enabled for the metadata digest + // + // and boundary_count must be 0 region.constrain_equal( assigned_rows .get(N_ROWS_METADATA - 1) @@ -587,6 +655,14 @@ impl BlobDataConfig { .cell(), one.cell(), )?; + region.constrain_equal( + assigned_rows + .get(N_ROWS_METADATA - 1) + .unwrap() + .boundary_count + .cell(), + zero.cell(), + )?; //////////////////////////////////////////////////////////////////////////////// ////////////////////////////////// CHUNK_DATA ////////////////////////////////// @@ -628,21 +704,12 @@ impl BlobDataConfig { &mut rlc_config_offset, )?; - // we do a lookup to the keccak table (from the "chunk data" section) every time we - // encounter a boundary. And such a lookup is done only for non-empty chunks, i.e. - // chunks that have at least one L2 transaction. We wish to equate this summation - // to the number of non-empty chunks we decoded from the metadata. - let mut num_lookups = zero.clone(); - // TODO: optimize this loop as each add takes 4 rows - for row in rows.iter() { - num_lookups = rlc_config.add( - &mut region, - &row.is_boundary, - &num_lookups, - &mut rlc_config_offset, - )?; - } - region.constrain_equal(num_lookups.cell(), num_nonempty_chunks.cell())?; + // get the boundary count at the end of the "chunk data" section, and equate it to + // the number of non-empty chunks in the batch. + region.constrain_equal( + rows.last().unwrap().boundary_count.cell(), + num_nonempty_chunks.cell(), + )?; //////////////////////////////////////////////////////////////////////////////// ////////////////////////////////// DIGEST RLC ////////////////////////////////// @@ -656,12 +723,13 @@ impl BlobDataConfig { // rows have chunk_idx set from 0 (metadata) -> MAX_AGG_SNARKS. region.constrain_equal(rows[0].chunk_idx.cell(), zero.cell())?; - // TODO: this can be replaced by fetching 1 -> MAX_AGG_SNARKS from fixed column - // instead. The additions will be avoided. - let mut i_val = zero.clone(); - for row in rows.iter().skip(1).take(MAX_AGG_SNARKS) { - i_val = rlc_config.add(&mut region, &i_val, &one, &mut rlc_config_offset)?; - region.constrain_equal(i_val.cell(), row.chunk_idx.cell())?; + for (row, fixed_chunk_idx) in rows + .iter() + .skip(1) + .take(MAX_AGG_SNARKS) + .zip_eq(fixed_chunk_indices.iter()) + { + region.constrain_equal(row.chunk_idx.cell(), fixed_chunk_idx.cell())?; } let challenge_digest_preimage_rlc_specified = &rows.last().unwrap().preimage_rlc; @@ -834,33 +902,23 @@ impl BlobDataConfig { let challenge_digest_crt = barycentric_assignments .get(BLOB_WIDTH) .expect("challenge digest CRT"); - let powers_of_256 = std::iter::successors(Some(Ok(one)), |coeff| { - Some(rlc_config.mul( - &mut region, - &two_fifty_six, - coeff.as_ref().expect("coeff expected"), - &mut rlc_config_offset, - )) - }) - .take(11) - .collect::, Error>>()?; let challenge_digest_limb1 = rlc_config.inner_product( &mut region, &export.challenge_digest[0..11], - &powers_of_256[0..11], + &pows_of_256, &mut rlc_config_offset, )?; let challenge_digest_limb2 = rlc_config.inner_product( &mut region, &export.challenge_digest[11..22], - &powers_of_256[0..11], + &pows_of_256, &mut rlc_config_offset, )?; let challenge_digest_limb3 = rlc_config.inner_product( &mut region, &export.challenge_digest[22..32], - &powers_of_256[0..10], + &pows_of_256[0..10], &mut rlc_config_offset, )?; region.constrain_equal( @@ -879,19 +937,19 @@ impl BlobDataConfig { let limb1 = rlc_config.inner_product( &mut region, &blob_field[0..11], - &powers_of_256[0..11], + &pows_of_256, &mut rlc_config_offset, )?; let limb2 = rlc_config.inner_product( &mut region, &blob_field[11..22], - &powers_of_256[0..11], + &pows_of_256, &mut rlc_config_offset, )?; let limb3 = rlc_config.inner_product( &mut region, &blob_field[22..31], - &powers_of_256[0..9], + &pows_of_256[0..9], &mut rlc_config_offset, )?; region.constrain_equal(limb1.cell(), blob_crt.truncation.limbs[0].cell())?; diff --git a/aggregator/src/aggregation/rlc.rs b/aggregator/src/aggregation/rlc.rs index e89a4d31c9..3c7f67e67c 100644 --- a/aggregator/src/aggregation/rlc.rs +++ b/aggregator/src/aggregation/rlc.rs @@ -2,3 +2,4 @@ mod config; mod gates; pub(crate) use config::RlcConfig; +pub(crate) use gates::POWS_OF_256; diff --git a/aggregator/src/aggregation/rlc/gates.rs b/aggregator/src/aggregation/rlc/gates.rs index b3069db072..f32473b1a6 100644 --- a/aggregator/src/aggregation/rlc/gates.rs +++ b/aggregator/src/aggregation/rlc/gates.rs @@ -7,60 +7,97 @@ use halo2_proofs::{ }; use zkevm_circuits::util::Challenges; -use crate::{constants::LOG_DEGREE, util::assert_equal}; +use crate::{constants::LOG_DEGREE, util::assert_equal, MAX_AGG_SNARKS}; use super::RlcConfig; +const FIXED_OFFSET_32: usize = MAX_AGG_SNARKS + 1; +const FIXED_OFFSET_168: usize = FIXED_OFFSET_32 + 1; +const FIXED_OFFSET_200: usize = FIXED_OFFSET_168 + 1; +const FIXED_OFFSET_2_POW_32: usize = FIXED_OFFSET_200 + 1; +const FIXED_OFFSET_256: usize = FIXED_OFFSET_2_POW_32 + 1; +const FIXED_OFFSET_EMPTY_KECCAK: usize = FIXED_OFFSET_256 + POWS_OF_256; + +pub(crate) const POWS_OF_256: usize = 10; + impl RlcConfig { /// initialize the chip with fixed cells + /// + /// The layout for fixed cells is: + /// + /// | Offset | Fixed value | + /// |------------------------|----------------------| + /// | 0 | 0 | + /// | 1 | 1 | + /// | i ... | i ... | + /// | MAX_AGG_SNARKS | MAX_AGG_SNARKS | + /// | MAX_AGG_SNARKS + 1 | 32 | + /// | MAX_AGG_SNARKS + 2 | 168 | + /// | MAX_AGG_SNARKS + 3 | 200 | + /// | MAX_AGG_SNARKS + 4 | 2 ^ 32 | + /// | MAX_AGG_SNARKS + 5 | 256 | + /// | MAX_AGG_SNARKS + 6 | 256 ^ 2 | + /// | MAX_AGG_SNARKS + 7 | 256 ^ 3 | + /// | MAX_AGG_SNARKS + j ... | 256 ^ (j - 4) | + /// | MAX_AGG_SNARKS + 14 | 256 ^ 10 | + /// | MAX_AGG_SNARKS + 15 | EMPTY_KECCAK[0] | + /// | MAX_AGG_SNARKS + 16 | EMPTY_KECCAK[1] | + /// | MAX_AGG_SNARKS + k ... | EMPTY_KECCAK[k - 15] | + /// | MAX_AGG_SNARKS + 46 | EMPTY_KECCAK[31] | + /// |------------------------|----------------------| pub(crate) fn init(&self, region: &mut Region) -> Result<(), Error> { - region.assign_fixed(|| "const zero", self.fixed, 0, || Value::known(Fr::zero()))?; - region.assign_fixed(|| "const one", self.fixed, 1, || Value::known(Fr::one()))?; - region.assign_fixed(|| "const two", self.fixed, 2, || Value::known(Fr::from(2)))?; - region.assign_fixed(|| "const five", self.fixed, 3, || Value::known(Fr::from(5)))?; - region.assign_fixed(|| "const nine", self.fixed, 4, || Value::known(Fr::from(9)))?; - region.assign_fixed(|| "const 13", self.fixed, 5, || Value::known(Fr::from(13)))?; - region.assign_fixed(|| "const 32", self.fixed, 6, || Value::known(Fr::from(32)))?; - region.assign_fixed( - || "const 136", - self.fixed, - 7, - || Value::known(Fr::from(136)), - )?; - region.assign_fixed( - || "const 2^32", - self.fixed, - 8, - || Value::known(Fr::from(1 << 32)), - )?; - region.assign_fixed( - || "const 256", - self.fixed, - 9, - || Value::known(Fr::from(256)), - )?; - region.assign_fixed( - || "const 168", - self.fixed, - 10, - || Value::known(Fr::from(168)), - )?; - region.assign_fixed( - || "const 200", - self.fixed, - 11, - || Value::known(Fr::from(200)), - )?; + let mut offset = 0; + + // [0, ..., MAX_AGG_SNARKS] + for const_val in 0..=MAX_AGG_SNARKS { + region.assign_fixed( + || format!("const at offset={offset}"), + self.fixed, + offset, + || Value::known(Fr::from(const_val as u64)), + )?; + offset += 1; + } + assert_eq!(offset, FIXED_OFFSET_32); + + // [32, 168, 200, 1 << 32] + for const_val in [32, 168, 200, 1 << 32] { + region.assign_fixed( + || format!("const at offset={offset}"), + self.fixed, + offset, + || Value::known(Fr::from(const_val)), + )?; + offset += 1; + } + assert_eq!(offset, FIXED_OFFSET_256); + + // [256, ..., 256 ^ i, ..., 256 ^ 10] + for const_val in std::iter::successors(Some(Fr::from(256)), |n| Some(n * Fr::from(256))) + .take(POWS_OF_256) + { + region.assign_fixed( + || format!("const at offset={offset}"), + self.fixed, + offset, + || Value::known(const_val), + )?; + offset += 1; + } + assert_eq!(offset, FIXED_OFFSET_EMPTY_KECCAK); + // [EMPTY_KECCAK[0], ..., EMPTY_KECCAK[31]] let empty_keccak = keccak256([]); - for (i, &byte) in empty_keccak.iter().enumerate() { + for &byte in empty_keccak.iter() { region.assign_fixed( - || "const empty_keccak[i]", + || format!("const at offset={offset}"), self.fixed, - 12 + i, + offset, || Value::known(Fr::from(byte as u64)), )?; + offset += 1; } + assert_eq!(offset, FIXED_OFFSET_EMPTY_KECCAK + 32); Ok(()) } @@ -96,7 +133,7 @@ impl RlcConfig { pub(crate) fn five_cell(&self, region_index: RegionIndex) -> Cell { Cell { region_index, - row_offset: 3, + row_offset: 5, column: self.fixed.into(), } } @@ -105,7 +142,7 @@ impl RlcConfig { pub(crate) fn nine_cell(&self, region_index: RegionIndex) -> Cell { Cell { region_index, - row_offset: 4, + row_offset: 9, column: self.fixed.into(), } } @@ -114,7 +151,21 @@ impl RlcConfig { pub(crate) fn thirteen_cell(&self, region_index: RegionIndex) -> Cell { Cell { region_index, - row_offset: 5, + row_offset: 13, + column: self.fixed.into(), + } + } + + #[inline] + pub(crate) fn fixed_up_to_max_agg_snarks_cell( + &self, + region_index: RegionIndex, + index: usize, + ) -> Cell { + assert!(index <= MAX_AGG_SNARKS, "only up to MAX_AGG_SNARKS"); + Cell { + region_index, + row_offset: index, column: self.fixed.into(), } } @@ -123,60 +174,62 @@ impl RlcConfig { pub(crate) fn thirty_two_cell(&self, region_index: RegionIndex) -> Cell { Cell { region_index, - row_offset: 6, + row_offset: FIXED_OFFSET_32, column: self.fixed.into(), } } - // #[inline] - // pub(crate) fn one_hundred_and_thirty_six_cell(&self, region_index: RegionIndex) -> Cell { - // Cell { - // region_index, - // row_offset: 7, - // column: self.fixed.into(), - // } - // } #[inline] - pub(crate) fn two_to_thirty_two_cell(&self, region_index: RegionIndex) -> Cell { + pub(crate) fn one_hundred_and_sixty_eight_cell(&self, region_index: RegionIndex) -> Cell { Cell { region_index, - row_offset: 8, + row_offset: FIXED_OFFSET_168, column: self.fixed.into(), } } #[inline] - pub(crate) fn two_hundred_and_fifty_size_cell(&self, region_index: RegionIndex) -> Cell { + pub(crate) fn two_hundred_cell(&self, region_index: RegionIndex) -> Cell { Cell { region_index, - row_offset: 9, + row_offset: FIXED_OFFSET_200, column: self.fixed.into(), } } #[inline] - pub(crate) fn one_hundred_and_sixty_eight_cell(&self, region_index: RegionIndex) -> Cell { + pub(crate) fn two_to_thirty_two_cell(&self, region_index: RegionIndex) -> Cell { Cell { region_index, - row_offset: 10, + row_offset: FIXED_OFFSET_2_POW_32, column: self.fixed.into(), } } #[inline] - pub(crate) fn two_hundred_cell(&self, region_index: RegionIndex) -> Cell { + pub(crate) fn pow_of_two_hundred_and_fifty_six_cell( + &self, + region_index: RegionIndex, + exponent: usize, + ) -> Cell { + assert!(exponent > 0, "for exponent == 0, fetch the one cell"); + assert!( + exponent <= POWS_OF_256, + "only up to 256 ^ 10 in fixed column" + ); Cell { region_index, - row_offset: 11, + row_offset: FIXED_OFFSET_256 + exponent - 1, column: self.fixed.into(), } } #[inline] pub(crate) fn empty_keccak_cell_i(&self, region_index: RegionIndex, index: usize) -> Cell { + assert!(index <= 31, "keccak digest only has 32 bytes"); Cell { region_index, - row_offset: 12 + index, + row_offset: FIXED_OFFSET_EMPTY_KECCAK + index, column: self.fixed.into(), } } @@ -653,6 +706,7 @@ impl RlcConfig { self.is_zero(region, &diff, offset) } } + #[inline] fn byte_to_bits_le(byte: &u8) -> Vec { let mut res = vec![];