diff --git a/gadgets/src/permutation.rs b/gadgets/src/permutation.rs index 3c6e7dcbc5..56508a3d03 100644 --- a/gadgets/src/permutation.rs +++ b/gadgets/src/permutation.rs @@ -3,11 +3,11 @@ //! power of gamma are defined in columns to trade more columns with less degrees use std::iter; #[rustfmt::skip] -// | q_row_non_first | q_row_enable | alpha | gamma | gamma power 2 | ... | row fingerprint | accmulated fingerprint | -// |-----------------|--------------|-----------|-----------|-----------------| | --------------- | ---------------------- | -// | 0 |1 |alpha | gamma | gamma **2 | ... | F | F | -// | 1 |1 |alpha | gamma | gamma **2 | ... | F | F | -// | 1 |1 |alpha | gamma | gamma **2 | ... | F | F | +// | q_row_non_first | q_row_enable | q_row_last | alpha | gamma | gamma power 2 | ... | row fingerprint | accmulated fingerprint | +// |-----------------|--------------|------------|-----------|-----------|-----------------| | --------------- | ---------------------- | +// | 0 |1 |0 |alpha | gamma | gamma **2 | ... | F | F | +// | 1 |1 |0 |alpha | gamma | gamma **2 | ... | F | F | +// | 1 |1 |1 |alpha | gamma | gamma **2 | ... | F | F | use std::marker::PhantomData; @@ -23,16 +23,20 @@ use itertools::Itertools; /// Config for PermutationChipConfig #[derive(Clone, Debug)] pub struct PermutationChipConfig { - // column - acc_fingerprints: Column, + /// acc_fingerprints + pub acc_fingerprints: Column, row_fingerprints: Column, alpha: Column, power_of_gamma: Vec>, // selector q_row_non_first: Selector, // 1 between (first, end], exclude first q_row_enable: Selector, // 1 for all rows (including first) + /// q_row_last + pub q_row_last: Selector, // 1 in the last row _phantom: PhantomData, + + acc_fingerprints_cur_expr: Expression, } /// (alpha, gamma, prev_acc_fingerprints, next_acc_fingerprints) @@ -122,6 +126,7 @@ impl PermutationChipConfig { // last offset if offset == fingerprints.len() - 1 { last_fingerprint_cell = Some(row_acc_fingerprint_cell); + self.q_row_last.enable(region, offset)?; } } @@ -159,6 +164,11 @@ impl PermutationChipConfig { })) .for_each(|(col, ann)| region.name_column(|| format!("{}_{}", prefix, ann), col)); } + + /// acc_fingerprints_cur_expr + pub fn acc_fingerprints_cur_expr(&self) -> Expression { + self.acc_fingerprints_cur_expr.clone() + } } /// permutation fingerprint gadget @@ -185,11 +195,14 @@ impl PermutationChip { let q_row_non_first = meta.selector(); let q_row_enable = meta.selector(); + let q_row_last = meta.selector(); meta.enable_equality(acc_fingerprints); meta.enable_equality(alpha); meta.enable_equality(power_of_gamma[0]); + let mut acc_fingerprints_cur_expr: Expression = 0.expr(); + meta.create_gate( "acc_fingerprints_cur = acc_fingerprints_prev * row_fingerprints_cur", |meta| { @@ -198,6 +211,8 @@ impl PermutationChip { let acc_fingerprints_cur = meta.query_advice(acc_fingerprints, Rotation::cur()); let row_fingerprints_cur = meta.query_advice(row_fingerprints, Rotation::cur()); + acc_fingerprints_cur_expr = acc_fingerprints_cur.clone(); + [q_row_non_first * (acc_fingerprints_cur - acc_fingerprints_prev * row_fingerprints_cur)] }, @@ -266,9 +281,11 @@ impl PermutationChip { PermutationChipConfig { acc_fingerprints, + acc_fingerprints_cur_expr, row_fingerprints, q_row_non_first, q_row_enable, + q_row_last, alpha, power_of_gamma, _phantom: PhantomData:: {}, diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 20cdd8b809..3edcad2ef4 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -1,14 +1,9 @@ //! The EVM circuit implementation. -use gadgets::{ - is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, - permutation::{PermutationChip, PermutationChipConfig}, - util::Expr, -}; +use gadgets::permutation::{PermutationChip, PermutationChipConfig}; use halo2_proofs::{ - circuit::{AssignedCell, Layouter, SimpleFloorPlanner, Value}, + circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::*, - poly::Rotation, }; mod execution; @@ -25,19 +20,15 @@ use self::{step::HasExecutionState, witness::rw::ToVec}; pub use crate::witness; use crate::{ - evm_circuit::{ - param::{MAX_STEP_HEIGHT, STEP_STATE_HEIGHT}, - util::rlc, - }, + evm_circuit::param::{MAX_STEP_HEIGHT, STEP_STATE_HEIGHT}, table::{ - chunkctx_table::{ChunkCtxFieldTag, ChunkCtxTable}, BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, LookupTable, RwTable, TxTable, UXTable, }, - util::{Challenges, SubCircuit, SubCircuitConfig}, + util::{chunkctx_config::ChunkContextConfig, Challenges, SubCircuit, SubCircuitConfig}, witness::RwMap, }; -use bus_mapping::{circuit_input_builder::ChunkContext, evm::OpcodeId}; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use execution::ExecutionConfig; use itertools::Itertools; @@ -61,10 +52,8 @@ pub struct EvmCircuitConfig { copy_table: CopyTable, keccak_table: KeccakTable, exp_table: ExpTable, - chunkctx_table: ChunkCtxTable, - - // rw permutation config - rw_permutation_config: PermutationChipConfig, + /// rw permutation config + pub rw_permutation_config: PermutationChipConfig, // pi for carry over previous chunk context pi_pre_continuity: Column, @@ -73,13 +62,8 @@ pub struct EvmCircuitConfig { // pi for permutation challenge pi_permutation_challenges: Column, - // chunk context related field - chunk_index: Column, - chunk_index_next: Column, - total_chunks: Column, - q_chunk_context: Selector, - is_first_chunk: IsZeroConfig, - is_last_chunk: IsZeroConfig, + // chunkctx_config + chunkctx_config: ChunkContextConfig, } /// Circuit configuration arguments @@ -104,6 +88,8 @@ pub struct EvmCircuitConfigArgs { pub u8_table: UXTable<8>, /// U16Table pub u16_table: UXTable<16>, + /// chunkctx config + pub chunkctx_config: ChunkContextConfig, } impl SubCircuitConfig for EvmCircuitConfig { @@ -123,59 +109,10 @@ impl SubCircuitConfig for EvmCircuitConfig { exp_table, u8_table, u16_table, + chunkctx_config, }: Self::ConfigArgs, ) -> Self { - // chunk context - let chunk_index = meta.advice_column(); - let chunk_index_inv = meta.advice_column(); - let chunk_index_next = meta.advice_column(); - let chunk_diff = meta.advice_column(); - let total_chunks = meta.advice_column(); - let q_chunk_context = meta.complex_selector(); - let fixed_table = [(); 4].map(|_| meta.fixed_column()); - let chunkctx_table = ChunkCtxTable::construct(meta); - - [ - (ChunkCtxFieldTag::CurrentChunkIndex.expr(), chunk_index), - (ChunkCtxFieldTag::NextChunkIndex.expr(), chunk_index_next), - (ChunkCtxFieldTag::TotalChunks.expr(), total_chunks), - ] - .iter() - .for_each(|(tag_expr, value_col)| { - meta.lookup_any("chunk context lookup", |meta| { - let q_chunk_context = meta.query_selector(q_chunk_context); - let value_col_expr = meta.query_advice(*value_col, Rotation::cur()); - - vec![( - q_chunk_context - * rlc::expr( - &[tag_expr.clone(), value_col_expr], - challenges.lookup_input(), - ), - rlc::expr(&chunkctx_table.table_exprs(meta), challenges.lookup_input()), - )] - }); - }); - - let is_first_chunk = IsZeroChip::configure( - meta, - |meta| meta.query_selector(q_chunk_context), - |meta| meta.query_advice(chunk_index, Rotation::cur()), - chunk_index_inv, - ); - - let is_last_chunk = IsZeroChip::configure( - meta, - |meta| meta.query_selector(q_chunk_context), - |meta| { - let chunk_index = meta.query_advice(chunk_index, Rotation::cur()); - let total_chunks = meta.query_advice(total_chunks, Rotation::cur()); - - total_chunks - chunk_index - 1.expr() - }, - chunk_diff, - ); let execution = Box::new(ExecutionConfig::configure( meta, @@ -190,9 +127,9 @@ impl SubCircuitConfig for EvmCircuitConfig { ©_table, &keccak_table, &exp_table, - &chunkctx_table, - &is_first_chunk, - &is_last_chunk, + &chunkctx_config.chunkctx_table, + chunkctx_config.is_first_chunk.expr(), + chunkctx_config.is_last_chunk.expr(), )); fixed_table.iter().enumerate().for_each(|(idx, &col)| { @@ -207,7 +144,7 @@ impl SubCircuitConfig for EvmCircuitConfig { exp_table.annotate_columns(meta); u8_table.annotate_columns(meta); u16_table.annotate_columns(meta); - chunkctx_table.annotate_columns(meta); + chunkctx_config.chunkctx_table.annotate_columns(meta); let rw_permutation_config = PermutationChip::configure( meta, @@ -221,9 +158,6 @@ impl SubCircuitConfig for EvmCircuitConfig { meta.enable_equality(pi_pre_continuity); meta.enable_equality(pi_next_continuity); meta.enable_equality(pi_permutation_challenges); - meta.enable_equality(chunk_index); - meta.enable_equality(chunk_index_next); - meta.enable_equality(total_chunks); Self { fixed_table, @@ -237,30 +171,15 @@ impl SubCircuitConfig for EvmCircuitConfig { copy_table, keccak_table, exp_table, - chunkctx_table, rw_permutation_config, + chunkctx_config, pi_pre_continuity, pi_next_continuity, pi_permutation_challenges, - chunk_index, - chunk_index_next, - total_chunks, - is_first_chunk, - is_last_chunk, - q_chunk_context, } } } -/// chunk_index, chunk_index_next, total_chunk, initial_rwc, end_rwc -type AssignedChunkContextCell = ( - AssignedCell, - AssignedCell, - AssignedCell, - AssignedCell, - AssignedCell, -); - impl EvmCircuitConfig { /// Load fixed table pub fn load_fixed_table( @@ -284,75 +203,6 @@ impl EvmCircuitConfig { }, ) } - - /// assign chunk context - pub fn assign_chunk_context( - &self, - layouter: &mut impl Layouter, - chunk_context: &ChunkContext, - max_offset_index: usize, - ) -> Result, Error> { - let ( - chunk_index_cell, - chunk_index_next_cell, - total_chunk_cell, - initial_rwc_cell, - end_rwc_cell, - ) = self.chunkctx_table.load(layouter, chunk_context)?; - - let is_first_chunk = IsZeroChip::construct(self.is_first_chunk.clone()); - let is_last_chunk = IsZeroChip::construct(self.is_last_chunk.clone()); - layouter.assign_region( - || "chunk context", - |mut region| { - for offset in 0..max_offset_index + 1 { - self.q_chunk_context.enable(&mut region, offset)?; - - region.assign_advice( - || "chunk_index", - self.chunk_index, - offset, - || Value::known(F::from(chunk_context.chunk_index as u64)), - )?; - - region.assign_advice( - || "chunk_index_next", - self.chunk_index_next, - offset, - || Value::known(F::from(chunk_context.chunk_index as u64 + 1u64)), - )?; - - region.assign_advice( - || "total_chunks", - self.total_chunks, - offset, - || Value::known(F::from(chunk_context.total_chunks as u64)), - )?; - - is_first_chunk.assign( - &mut region, - offset, - Value::known(F::from(chunk_context.chunk_index as u64)), - )?; - is_last_chunk.assign( - &mut region, - offset, - Value::known(F::from( - (chunk_context.total_chunks - chunk_context.chunk_index - 1) as u64, - )), - )?; - } - Ok(()) - }, - )?; - Ok(( - chunk_index_cell, - chunk_index_next_cell, - total_chunk_cell, - initial_rwc_cell, - end_rwc_cell, - )) - } } /// Tx Circuit for verifying transaction signatures @@ -414,6 +264,33 @@ impl EvmCircuit { // It must have one row for EndBlock and at least one unused one num_rows + 2 } + + /// Compute the public inputs for this circuit. + fn instance_extend_chunkctx(&self) -> Vec> { + let block = self.block.as_ref().unwrap(); + + let (rw_table_chunked_index, rw_table_total_chunks) = ( + block.chunk_context.chunk_index, + block.chunk_context.total_chunks, + ); + + let mut instance = vec![ + vec![ + F::from(rw_table_chunked_index as u64), + F::from(rw_table_total_chunks as u64), + F::from(block.chunk_context.initial_rwc as u64), + ], + vec![ + F::from(rw_table_chunked_index as u64) + F::ONE, + F::from(rw_table_total_chunks as u64), + F::from(block.chunk_context.end_rwc as u64), + ], + ]; + + instance.extend(self.instance()); + + instance + } } impl SubCircuit for EvmCircuit { @@ -456,10 +333,7 @@ impl SubCircuit for EvmCircuit { config.load_fixed_table(layouter, self.fixed_table_tags.clone())?; - let max_offset_index = config.execution.assign_block(layouter, block, challenges)?; - - let (prev_chunk_index, next_chunk_index_next, total_chunks, initial_rwc, end_rwc) = - config.assign_chunk_context(layouter, &block.chunk_context, max_offset_index)?; + config.execution.assign_block(layouter, block, challenges)?; let (rw_rows_padding, _) = RwMap::table_assignments_padding( &block.rws.table_assignments(true), @@ -494,7 +368,7 @@ impl SubCircuit for EvmCircuit { Value::known(block.permu_gamma), Value::known(block.permu_chronological_rwtable_prev_continuous_fingerprint), &rw_rows_padding.to2dvec(), - "evm circuit", + "evm_circuit-rw_permutation", )?; Ok(permutation_cells) }, @@ -508,30 +382,16 @@ impl SubCircuit for EvmCircuit { layouter.constrain_instance(cell.cell(), config.pi_permutation_challenges, i) })?; // constraints prev,next fingerprints - [vec![ - prev_continuous_fingerprint_cell, - prev_chunk_index, - total_chunks.clone(), - initial_rwc, - ]] - .iter() - .flatten() - .enumerate() - .try_for_each(|(i, cell)| { - layouter.constrain_instance(cell.cell(), config.pi_pre_continuity, i) - })?; - [vec![ - next_continuous_fingerprint_cell, - next_chunk_index_next, - total_chunks, - end_rwc, - ]] - .iter() - .flatten() - .enumerate() - .try_for_each(|(i, cell)| { - layouter.constrain_instance(cell.cell(), config.pi_next_continuity, i) - })?; + layouter.constrain_instance( + prev_continuous_fingerprint_cell.cell(), + config.pi_pre_continuity, + 0, + )?; + layouter.constrain_instance( + next_continuous_fingerprint_cell.cell(), + config.pi_next_continuity, + 0, + )?; Ok(()) } @@ -539,24 +399,9 @@ impl SubCircuit for EvmCircuit { fn instance(&self) -> Vec> { let block = self.block.as_ref().unwrap(); - let (rw_table_chunked_index, rw_table_total_chunks) = ( - block.chunk_context.chunk_index, - block.chunk_context.total_chunks, - ); - vec![ - vec![ - block.permu_chronological_rwtable_prev_continuous_fingerprint, - F::from(rw_table_chunked_index as u64), - F::from(rw_table_total_chunks as u64), - F::from(block.chunk_context.initial_rwc as u64), - ], - vec![ - block.permu_chronological_rwtable_next_continuous_fingerprint, - F::from(rw_table_chunked_index as u64) + F::ONE, - F::from(rw_table_total_chunks as u64), - F::from(block.chunk_context.end_rwc as u64), - ], + vec![block.permu_chronological_rwtable_prev_continuous_fingerprint], + vec![block.permu_chronological_rwtable_next_continuous_fingerprint], vec![block.permu_alpha, block.permu_gamma], ] } @@ -642,7 +487,7 @@ pub(crate) mod cached { } pub(crate) fn instance(&self) -> Vec> { - self.0.instance() + self.0.instance_extend_chunkctx() } } } @@ -670,6 +515,7 @@ impl Circuit for EvmCircuit { let u16_table = UXTable::construct(meta); let challenges = Challenges::construct(meta); let challenges_expr = challenges.exprs(meta); + let chunkctx_config = ChunkContextConfig::new(meta, &challenges_expr); ( EvmCircuitConfig::new( @@ -685,6 +531,7 @@ impl Circuit for EvmCircuit { exp_table, u8_table, u16_table, + chunkctx_config, }, ), challenges, @@ -721,7 +568,16 @@ impl Circuit for EvmCircuit { config.u8_table.load(&mut layouter)?; config.u16_table.load(&mut layouter)?; - self.synthesize_sub(&config, &challenges, &mut layouter) + // synthesize chunk context + config.chunkctx_config.assign_chunk_context( + &mut layouter, + &self.block.as_ref().unwrap().chunk_context, + Self::get_num_rows_required(self.block.as_ref().unwrap()) - 1, + )?; + + self.synthesize_sub(&config, &challenges, &mut layouter)?; + + Ok(()) } } @@ -789,7 +645,7 @@ mod evm_circuit_stats { let k = block.get_test_degree(); let circuit = EvmCircuit::::get_test_circuit_from_block(block); - let instance = circuit.instance(); + let instance = circuit.instance_extend_chunkctx(); let prover1 = MockProver::::run(k, &circuit, instance).unwrap(); let code = bytecode! { @@ -811,7 +667,7 @@ mod evm_circuit_stats { let block = block_convert::(&builder).unwrap(); let k = block.get_test_degree(); let circuit = EvmCircuit::::get_test_circuit_from_block(block); - let instance = circuit.instance(); + let instance = circuit.instance_extend_chunkctx(); let prover2 = MockProver::::run(k, &circuit, instance).unwrap(); assert_eq!(prover1.fixed().len(), prover2.fixed().len()); diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 57437ef5ac..52b69eb678 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -28,7 +28,7 @@ use crate::{ }; use bus_mapping::operation::Target; use eth_types::{evm_unimplemented, Field}; -use gadgets::{is_zero::IsZeroConfig, util::not}; +use gadgets::util::not; use halo2_proofs::{ circuit::{Layouter, Region, Value}, plonk::{ @@ -236,7 +236,7 @@ pub struct ExecutionConfig { // Selector enabled in the row where the first execution step starts. q_step_first: Selector, // Selector enabled in the row where the last execution step starts. - q_step_last: Selector, + pub q_step_last: Selector, advices: [Column; STEP_WIDTH], step: Step, pub(crate) height_map: HashMap, @@ -352,8 +352,8 @@ impl ExecutionConfig { keccak_table: &dyn LookupTable, exp_table: &dyn LookupTable, chunkctx_table: &dyn LookupTable, - is_first_chunk: &IsZeroConfig, - is_last_chunk: &IsZeroConfig, + is_first_chunk: Expression, + is_last_chunk: Expression, ) -> Self { let mut instrument = Instrument::default(); let q_usable = meta.complex_selector(); @@ -1031,7 +1031,7 @@ impl ExecutionConfig { layouter: &mut impl Layouter, block: &Block, challenges: &Challenges>, - ) -> Result { + ) -> Result<(), Error> { // Track number of calls to `layouter.assign_region` as layouter assignment passes. let mut assign_pass = 0; layouter.assign_region( @@ -1208,7 +1208,8 @@ impl ExecutionConfig { )?; assign_pass += 1; - Ok(offset) + + Ok(()) }, ) } diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index a6c1b07122..6077983631 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -71,8 +71,8 @@ pub struct StateCircuitConfig { // External tables mpt_table: MptTable, - // rw permutation config - rw_permutation_config: PermutationChipConfig, + /// rw permutation config + pub rw_permutation_config: PermutationChipConfig, // pi for carry over previous chunk context pi_pre_continuity: Column, @@ -610,7 +610,7 @@ impl SubCircuit for StateCircuit { Value::known(self.permu_gamma), Value::known(self.permu_prev_continuous_fingerprint), &rows, - "state_circuit", + "state_circuit-rw_permutation", )?; #[cfg(test)] { diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index 2e46c56ce8..1e43d14bad 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -216,8 +216,33 @@ impl ConstraintBuilder { } fn build_padding_constraints(&mut self, q: &Queries) { - // padding shared same constraints as start - self.build_start_constraints(q) + // 1.0. Unused keys are 0 + self.require_zero("field_tag is 0 for Start", q.field_tag()); + self.require_zero("address is 0 for Start", q.rw_table.address.clone()); + self.require_zero("id is 0 for Start", q.id()); + self.require_word_zero("storage_key is 0 for Start", q.rw_table.storage_key.clone()); + // 1.1. rw_counter increases by 0 or 1 for every non-first row + // this is to serve multiple chunk usage, for padding rw counter is only local unique + // and not global unique + self.condition(q.not_first_access.clone(), |cb| { + cb.require_boolean( + "if previous row is also Padding. rw counter change is 0 or 1", + q.rw_counter_change(), + ) + }); + // 1.2. Start value is 0 + self.require_word_zero("Start value is 0", q.value()); + // 1.3. Start initial value is 0 + self.require_word_zero("Start initial_value is 0", q.initial_value()); + // 1.4. state_root is unchanged for every non-first row + self.condition(q.lexicographic_ordering_selector.clone(), |cb| { + cb.require_word_equal( + "state_root is unchanged for Start", + q.state_root(), + q.state_root_prev(), + ) + }); + self.require_word_zero("value_prev column is 0 for Start", q.value_prev_column()); } fn build_start_constraints(&mut self, q: &Queries) { diff --git a/zkevm-circuits/src/state_circuit/test.rs b/zkevm-circuits/src/state_circuit/test.rs index 59abea95d5..c4d1078604 100644 --- a/zkevm-circuits/src/state_circuit/test.rs +++ b/zkevm-circuits/src/state_circuit/test.rs @@ -720,18 +720,21 @@ fn all_padding() { } #[test] -fn skipped_start_rw_counter() { +fn invalid_padding_rw_counter_change() { let overrides = HashMap::from([ ( - (AdviceColumn::RwCounter, -1), + (AdviceColumn::RwCounter, 0), // The original assignment is 1 << 16. Fr::from((1 << 16) + 1), ), - ((AdviceColumn::RwCounterLimb0, -1), Fr::ONE), + ((AdviceColumn::RwCounterLimb0, 0), Fr::ONE), ]); - let result = prover(vec![], overrides).verify_at_rows(1..2, 1..2); - assert_error_matches(result, "rw_counter increases by 1 for every non-first row"); + let result = prover(vec![], overrides).verify_at_rows(2..3, 2..3); + assert_error_matches( + result, + "if previous row is also Padding. rw counter change is 0 or 1", + ); } #[test] diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index 9cb686c705..8cc49d7c2f 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -66,7 +66,9 @@ use crate::{ UXTable, }, tx_circuit::{TxCircuit, TxCircuitConfig, TxCircuitConfigArgs}, - util::{log2_ceil, Challenges, SubCircuit, SubCircuitConfig}, + util::{ + chunkctx_config::ChunkContextConfig, log2_ceil, Challenges, SubCircuit, SubCircuitConfig, + }, witness::{block_convert, Block, MptUpdates}, }; use bus_mapping::{ @@ -97,6 +99,7 @@ pub struct SuperCircuitConfig { keccak_circuit: KeccakCircuitConfig, pi_circuit: PiCircuitConfig, exp_circuit: ExpCircuitConfig, + chunkctx_config: ChunkContextConfig, } /// Circuit configuration arguments @@ -108,7 +111,6 @@ pub struct SuperCircuitConfigArgs { /// Mock randomness pub mock_randomness: F, } - impl SubCircuitConfig for SuperCircuitConfig { type ConfigArgs = SuperCircuitConfigArgs; @@ -147,6 +149,8 @@ impl SubCircuitConfig for SuperCircuitConfig { power_of_randomness[0].clone(), ); + let chunkctx_config = ChunkContextConfig::new(meta, &challenges); + let keccak_circuit = KeccakCircuitConfig::new( meta, KeccakCircuitConfigArgs { @@ -208,7 +212,7 @@ impl SubCircuitConfig for SuperCircuitConfig { let evm_circuit = EvmCircuitConfig::new( meta, EvmCircuitConfigArgs { - challenges, + challenges: challenges.clone(), tx_table, rw_table: chronological_rw_table, bytecode_table, @@ -218,6 +222,31 @@ impl SubCircuitConfig for SuperCircuitConfig { exp_table, u8_table, u16_table, + chunkctx_config: chunkctx_config.clone(), + }, + ); + + // constraint chronological/by address rwtable fingerprint must be the same in last chunk + // last row. + meta.create_gate( + "chronological rwtable fingerprint == by address rwtable fingerprint", + |meta| { + let is_last_chunk = chunkctx_config.is_last_chunk.expr(); + let chronological_rwtable_acc_fingerprint = evm_circuit + .rw_permutation_config + .acc_fingerprints_cur_expr(); + let by_address_rwtable_acc_fingerprint = state_circuit + .rw_permutation_config + .acc_fingerprints_cur_expr(); + + let q_row_last = meta.query_selector(evm_circuit.rw_permutation_config.q_row_last); + + vec![ + is_last_chunk + * q_row_last + * (chronological_rwtable_acc_fingerprint + - by_address_rwtable_acc_fingerprint), + ] }, ); @@ -235,6 +264,7 @@ impl SubCircuitConfig for SuperCircuitConfig { keccak_circuit, pi_circuit, exp_circuit, + chunkctx_config, } } } @@ -242,6 +272,8 @@ impl SubCircuitConfig for SuperCircuitConfig { /// The Super Circuit contains all the zkEVM circuits #[derive(Clone, Default, Debug)] pub struct SuperCircuit { + /// Block + pub block: Option>, /// EVM Circuit pub evm_circuit: EvmCircuit, /// State Circuit @@ -305,6 +337,7 @@ impl SubCircuit for SuperCircuit { let keccak_circuit = KeccakCircuit::new_from_block(block); SuperCircuit::<_> { + block: Some(block.clone()), evm_circuit, state_circuit, tx_circuit, @@ -321,6 +354,22 @@ impl SubCircuit for SuperCircuit { /// Returns suitable inputs for the SuperCircuit. fn instance(&self) -> Vec> { let mut instance = Vec::new(); + + let block = self.block.as_ref().unwrap(); + + instance.extend_from_slice(&[ + vec![ + F::from(block.chunk_context.chunk_index as u64), + F::from(block.chunk_context.total_chunks as u64), + F::from(block.chunk_context.initial_rwc as u64), + ], + vec![ + F::from(block.chunk_context.chunk_index as u64) + F::ONE, + F::from(block.chunk_context.total_chunks as u64), + F::from(block.chunk_context.end_rwc as u64), + ], + ]); + instance.extend_from_slice(&self.keccak_circuit.instance()); instance.extend_from_slice(&self.pi_circuit.instance()); instance.extend_from_slice(&self.tx_circuit.instance()); @@ -360,6 +409,13 @@ impl SubCircuit for SuperCircuit { challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { + // synthesize chunk context + config.chunkctx_config.assign_chunk_context( + layouter, + &self.block.as_ref().unwrap().chunk_context, + self.block.as_ref().unwrap().circuits_params.max_rws - 1, + )?; + self.keccak_circuit .synthesize_sub(&config.keccak_circuit, challenges, layouter)?; self.bytecode_circuit diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index 2e12eb3b0e..1805c6149b 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -20,6 +20,9 @@ pub mod cell_manager; /// Cell Placement strategies pub mod cell_placement_strategy; +/// Chunk Ctx Config +pub mod chunkctx_config; + /// Steal the expression from gate pub fn query_expression( meta: &mut ConstraintSystem, diff --git a/zkevm-circuits/src/util/chunkctx_config.rs b/zkevm-circuits/src/util/chunkctx_config.rs new file mode 100644 index 0000000000..715ddb5de9 --- /dev/null +++ b/zkevm-circuits/src/util/chunkctx_config.rs @@ -0,0 +1,203 @@ +use bus_mapping::circuit_input_builder::ChunkContext; +use gadgets::{ + is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, + util::Expr, +}; +use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Instance, Selector}, + poly::Rotation, +}; + +use crate::{ + evm_circuit::util::rlc, + table::{ + chunkctx_table::{ChunkCtxFieldTag, ChunkCtxTable}, + LookupTable, + }, +}; +use eth_types::Field; + +use super::Challenges; + +/// chunk context config +#[derive(Clone, Debug)] +pub struct ChunkContextConfig { + chunk_index: Column, + chunk_index_next: Column, + total_chunks: Column, + q_chunk_context: Selector, + + /// is_first_chunk config + pub is_first_chunk: IsZeroConfig, + /// is_last_chunk config + pub is_last_chunk: IsZeroConfig, + + /// ChunkCtxTable + pub chunkctx_table: ChunkCtxTable, + /// instance column for prev chunk context + pub pi_pre_chunkctx: Column, + /// instance column for next chunk context + pub pi_next_chunkctx: Column, +} + +impl ChunkContextConfig { + /// new a chunk context config + pub fn new(meta: &mut ConstraintSystem, challenges: &Challenges>) -> Self { + let q_chunk_context = meta.complex_selector(); + let chunk_index = meta.advice_column(); + let chunk_index_inv = meta.advice_column(); + let chunk_index_next = meta.advice_column(); + let chunk_diff = meta.advice_column(); + let total_chunks = meta.advice_column(); + + let pi_pre_chunkctx = meta.instance_column(); + let pi_next_chunkctx = meta.instance_column(); + meta.enable_equality(pi_pre_chunkctx); + meta.enable_equality(pi_next_chunkctx); + + let chunkctx_table = ChunkCtxTable::construct(meta); + chunkctx_table.annotate_columns(meta); + + [ + (ChunkCtxFieldTag::CurrentChunkIndex.expr(), chunk_index), + (ChunkCtxFieldTag::NextChunkIndex.expr(), chunk_index_next), + (ChunkCtxFieldTag::TotalChunks.expr(), total_chunks), + ] + .iter() + .for_each(|(tag_expr, value_col)| { + meta.lookup_any("chunk context lookup", |meta| { + let q_chunk_context = meta.query_selector(q_chunk_context); + let value_col_expr = meta.query_advice(*value_col, Rotation::cur()); + + vec![( + q_chunk_context + * rlc::expr( + &[tag_expr.clone(), value_col_expr], + challenges.lookup_input(), + ), + rlc::expr(&chunkctx_table.table_exprs(meta), challenges.lookup_input()), + )] + }); + }); + + let is_first_chunk = IsZeroChip::configure( + meta, + |meta| meta.query_selector(q_chunk_context), + |meta| meta.query_advice(chunk_index, Rotation::cur()), + chunk_index_inv, + ); + + let is_last_chunk = IsZeroChip::configure( + meta, + |meta| meta.query_selector(q_chunk_context), + |meta| { + let chunk_index = meta.query_advice(chunk_index, Rotation::cur()); + let total_chunks = meta.query_advice(total_chunks, Rotation::cur()); + + total_chunks - chunk_index - 1.expr() + }, + chunk_diff, + ); + + Self { + q_chunk_context, + chunk_index, + chunk_index_next, + total_chunks, + is_first_chunk, + is_last_chunk, + chunkctx_table, + pi_pre_chunkctx, + pi_next_chunkctx, + } + } + + /// assign chunk context + pub fn assign_chunk_context( + &self, + layouter: &mut impl Layouter, + chunk_context: &ChunkContext, + max_offset_index: usize, + ) -> Result<(), Error> { + let ( + chunk_index_cell, + chunk_index_next_cell, + total_chunk_cell, + initial_rwc_cell, + end_rwc_cell, + ) = self.chunkctx_table.load(layouter, chunk_context)?; + + let is_first_chunk = IsZeroChip::construct(self.is_first_chunk.clone()); + let is_last_chunk = IsZeroChip::construct(self.is_last_chunk.clone()); + layouter.assign_region( + || "chunk context", + |mut region| { + region.name_column(|| "chunk_index", self.chunk_index); + region.name_column(|| "chunk_index_next", self.chunk_index_next); + region.name_column(|| "total_chunks", self.total_chunks); + region.name_column(|| "pi_pre_chunkctx", self.pi_pre_chunkctx); + region.name_column(|| "pi_next_chunkctx", self.pi_next_chunkctx); + self.is_first_chunk + .annotate_columns_in_region(&mut region, "is_first_chunk"); + self.is_last_chunk + .annotate_columns_in_region(&mut region, "is_last_chunk"); + self.chunkctx_table.annotate_columns_in_region(&mut region); + + for offset in 0..max_offset_index + 1 { + self.q_chunk_context.enable(&mut region, offset)?; + + region.assign_advice( + || "chunk_index", + self.chunk_index, + offset, + || Value::known(F::from(chunk_context.chunk_index as u64)), + )?; + + region.assign_advice( + || "chunk_index_next", + self.chunk_index_next, + offset, + || Value::known(F::from(chunk_context.chunk_index as u64 + 1u64)), + )?; + + region.assign_advice( + || "total_chunks", + self.total_chunks, + offset, + || Value::known(F::from(chunk_context.total_chunks as u64)), + )?; + + is_first_chunk.assign( + &mut region, + offset, + Value::known(F::from(chunk_context.chunk_index as u64)), + )?; + is_last_chunk.assign( + &mut region, + offset, + Value::known(F::from( + (chunk_context.total_chunks - chunk_context.chunk_index - 1) as u64, + )), + )?; + } + Ok(()) + }, + )?; + + vec![chunk_index_cell, total_chunk_cell.clone(), initial_rwc_cell] + .iter() + .enumerate() + .try_for_each(|(i, cell)| { + layouter.constrain_instance(cell.cell(), self.pi_pre_chunkctx, i) + })?; + [chunk_index_next_cell, total_chunk_cell, end_rwc_cell] + .iter() + .enumerate() + .try_for_each(|(i, cell)| { + layouter.constrain_instance(cell.cell(), self.pi_next_chunkctx, i) + })?; + + Ok(()) + } +} diff --git a/zkevm-circuits/src/witness/rw.rs b/zkevm-circuits/src/witness/rw.rs index f33e656347..43f588c9bb 100644 --- a/zkevm-circuits/src/witness/rw.rs +++ b/zkevm-circuits/src/witness/rw.rs @@ -148,8 +148,18 @@ impl RwMap { length } }; - let start_padding_rw_counter = - rows_trimmed.last().map(|row| row.rw_counter()).unwrap_or(1) + 1; + + // option 1: need to provide padding starting rw_counter at function parameters + // option 2: just padding after local max rw_counter + 1 + // We adapt option 2 for now + // the side effect is it introduce malleable proof when append `Rw::Padding` rw_counter, + // because `Rw::Padding` is not global unique + let start_padding_rw_counter = rows_trimmed + .iter() + .map(|rw| rw.rw_counter()) + .max() + .unwrap_or(1) + + 1; let padding = (start_padding_rw_counter..start_padding_rw_counter + padding_length) .map(|rw_counter| Rw::Padding { rw_counter }); ( @@ -165,7 +175,7 @@ impl RwMap { pub fn table_assignments(&self, keep_chronological_order: bool) -> Vec { let mut rows: Vec = self.0.values().flatten().cloned().collect(); if keep_chronological_order { - rows.sort_by_key(|row| row.rw_counter()); + rows.sort_by_key(|row| (row.rw_counter(), row.tag() as u64)); } else { rows.sort_by_key(|row| { ( @@ -421,8 +431,9 @@ impl RwRow> { }; let start_padding_rw_counter = { let start_padding_rw_counter = rows_trimmed - .last() - .map(|row| unwrap_value(row.rw_counter)) + .iter() + .map(|rw| unwrap_value(rw.rw_counter)) + .max() .unwrap_or(F::from(1u64)) + F::ONE; // Assume root of unity < 2**64