diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 25c62467d0..f691d33413 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -57,7 +57,7 @@ paste = "1.0" default = ["test", "test-circuits", "debug-annotations", "parallel_syn"] test = ["mock", "bus-mapping/test"] -scroll = ["bus-mapping/scroll", "eth-types/scroll", "mock?/scroll", "zktrie", "poseidon-codehash"] +scroll = ["bus-mapping/scroll", "eth-types/scroll", "mock?/scroll", "zktrie", "poseidon-codehash", "dual-bytecode"] strict-ccc = ["bus-mapping/strict-ccc"] test-circuits = [] @@ -71,3 +71,5 @@ debug-annotations = [] enable-stack = ["bus-mapping/enable-stack"] enable-memory = ["bus-mapping/enable-memory"] enable-storage = ["bus-mapping/enable-storage"] +dual-bytecode = [] + diff --git a/zkevm-circuits/src/bytecode_circuit/circuit.rs b/zkevm-circuits/src/bytecode_circuit/circuit.rs index 5af5e91dbc..7062b04d04 100644 --- a/zkevm-circuits/src/bytecode_circuit/circuit.rs +++ b/zkevm-circuits/src/bytecode_circuit/circuit.rs @@ -16,7 +16,7 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, poly::Rotation, }; -use std::vec; +use std::{cmp::max, vec}; use super::{ bytecode_unroller::{unroll_with_codehash, BytecodeRow, UnrolledBytecode}, @@ -1014,6 +1014,22 @@ impl BytecodeCircuit { .collect(); Self::new(bytecodes, bytecode_size) } + + #[cfg(feature = "dual-bytecode")] + /// Creates bytecode sub circuit from block, is_first_bytecode indicates first or second + /// sub bytecode circuit. + pub fn new_from_block_for_dual_circuit( + block: &witness::Block, + is_first_bytecode: bool, + ) -> Self { + let bytecodes: Vec> = block + .bytecodes + .iter() + .filter(|code| block.is_first_bytecode_circuit(code.0) == is_first_bytecode) + .map(|(codehash, b)| unroll_with_codehash(*codehash, b.bytes.clone())) + .collect(); + Self::new(bytecodes, block.circuits_params.max_bytecode) + } } impl SubCircuit for BytecodeCircuit { @@ -1038,14 +1054,30 @@ impl SubCircuit for BytecodeCircuit { /// Return the minimum number of rows required to prove the block fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { - ( - block - .bytecodes - .values() - .map(|bytecode| bytecode.bytes.len() + 1) - .sum(), - block.circuits_params.max_bytecode, - ) + if block.bytecode_map.is_some() { + // when enable feature "dual-bytecode", get two sets of bytecodes here. + let (first_bytecodes, second_bytecodes) = block.get_bytecodes_for_dual_sub_circuits(); + let minimum_row: usize = max( + first_bytecodes + .iter() + .map(|bytecode| bytecode.rows_required()) + .sum(), + second_bytecodes + .iter() + .map(|bytecode| bytecode.rows_required()) + .sum(), + ); + (minimum_row, block.circuits_params.max_bytecode) + } else { + ( + block + .bytecodes + .values() + .map(|bytecode| bytecode.rows_required()) + .sum(), + block.circuits_params.max_bytecode, + ) + } } /// Make the assignments to the TxCircuit diff --git a/zkevm-circuits/src/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs index e9f92fad8a..5b38597dfb 100644 --- a/zkevm-circuits/src/copy_circuit.rs +++ b/zkevm-circuits/src/copy_circuit.rs @@ -48,10 +48,10 @@ use crate::{ use self::copy_gadgets::{ constrain_address, constrain_bytes_left, constrain_event_rlc_acc, constrain_first_last, - constrain_forward_parameters, constrain_is_memory_copy, constrain_is_pad, constrain_mask, - constrain_masked_value, constrain_must_terminate, constrain_non_pad_non_mask, - constrain_rw_counter, constrain_rw_word_complete, constrain_tag, constrain_value_rlc, - constrain_word_index, constrain_word_rlc, + constrain_forward_parameters, constrain_is_first_bytecode_table, constrain_is_memory_copy, + constrain_is_pad, constrain_mask, constrain_masked_value, constrain_must_terminate, + constrain_non_pad_non_mask, constrain_rw_counter, constrain_rw_word_complete, constrain_tag, + constrain_value_rlc, constrain_word_index, constrain_word_rlc, }; /// The current row. @@ -122,6 +122,9 @@ pub struct CopyCircuitConfig { pub is_word_end: IsEqualConfig, /// non pad and non mask witness to reduce the degree of lookups. pub non_pad_non_mask: Column, + #[cfg(feature = "dual-bytecode")] + /// Whether the bytecode is belong to the first bytecode sub circuit . + pub is_first_bytecode_table: Column, // External tables /// TxTable pub tx_table: TxTable, @@ -129,6 +132,9 @@ pub struct CopyCircuitConfig { pub rw_table: RwTable, /// BytecodeTable pub bytecode_table: BytecodeTable, + #[cfg(feature = "dual-bytecode")] + /// BytecodeTable1 + pub bytecode_table1: BytecodeTable, } /// Circuit configuration arguments @@ -139,6 +145,9 @@ pub struct CopyCircuitConfigArgs { pub rw_table: RwTable, /// BytecodeTable pub bytecode_table: BytecodeTable, + #[cfg(feature = "dual-bytecode")] + /// BytecodeTable1 + pub bytecode_table1: BytecodeTable, /// CopyTable pub copy_table: CopyTable, /// q_enable @@ -158,6 +167,8 @@ impl SubCircuitConfig for CopyCircuitConfig { tx_table, rw_table, bytecode_table, + #[cfg(feature = "dual-bytecode")] + bytecode_table1, copy_table, q_enable, challenges, @@ -176,6 +187,8 @@ impl SubCircuitConfig for CopyCircuitConfig { let [is_pad, is_tx_calldata, is_bytecode, is_memory, is_memory_copy, is_tx_log, is_access_list_address, is_access_list_storage_key] = array_init(|_| meta.advice_column()); let is_first = copy_table.is_first; + #[cfg(feature = "dual-bytecode")] + let is_first_bytecode_table = meta.advice_column(); let id = copy_table.id; let addr = copy_table.addr; let src_addr_end = copy_table.src_addr_end; @@ -193,6 +206,9 @@ impl SubCircuitConfig for CopyCircuitConfig { tx_table.annotate_columns(meta); rw_table.annotate_columns(meta); bytecode_table.annotate_columns(meta); + #[cfg(feature = "dual-bytecode")] + bytecode_table1.annotate_columns(meta); + copy_table.annotate_columns(meta); let is_id_unchange = IsEqualChip::configure_with_value_inv( @@ -384,6 +400,9 @@ impl SubCircuitConfig for CopyCircuitConfig { is_memory_copy, ); constrain_rw_word_complete(cb, is_last_step, is_rw_word_type.expr(), is_word_end); + + #[cfg(feature = "dual-bytecode")] + constrain_is_first_bytecode_table(cb, meta, is_first_bytecode_table, is_last_col); } cb.gate(meta.query_fixed(q_enable, CURRENT)) @@ -444,11 +463,22 @@ impl SubCircuitConfig for CopyCircuitConfig { .collect() }); + // lookup first bytecode table meta.lookup_any("Bytecode lookup", |meta| { - let cond = meta.query_fixed(q_enable, CURRENT) + #[cfg(feature = "dual-bytecode")] + let is_first_bytecode = meta.query_advice(is_first_bytecode_table, CURRENT); + + let mut cond = meta.query_fixed(q_enable, CURRENT) * meta.query_advice(is_bytecode, CURRENT) * meta.query_advice(non_pad_non_mask, CURRENT); + #[cfg(feature = "dual-bytecode")] + { + cond = cond * is_first_bytecode.expr(); + } + + let table_expr = bytecode_table.table_exprs_mini(meta); + vec![ 1.expr(), meta.query_advice(id, CURRENT), @@ -457,12 +487,37 @@ impl SubCircuitConfig for CopyCircuitConfig { meta.query_advice(value, CURRENT), ] .into_iter() - .zip_eq(bytecode_table.table_exprs_mini(meta)) + .zip_eq(table_expr) .map(|(arg, table)| (cond.clone() * arg, table)) .collect() }); - meta.lookup_any("rw lookup", |meta| { + // lookup second bytecode table + #[cfg(feature = "dual-bytecode")] + meta.lookup_any("Bytecode1 lookup", |meta| { + let is_first_bytecode = meta.query_advice(is_first_bytecode_table, CURRENT); + + let cond = meta.query_fixed(q_enable, CURRENT) + * meta.query_advice(is_bytecode, CURRENT) + * meta.query_advice(non_pad_non_mask, CURRENT) + * (1.expr() - is_first_bytecode); + + let table_expr = bytecode_table1.table_exprs_mini(meta); + + vec![ + 1.expr(), + meta.query_advice(id, CURRENT), + BytecodeFieldTag::Byte.expr(), + meta.query_advice(addr, CURRENT), + meta.query_advice(value, CURRENT), + ] + .into_iter() + .zip_eq(table_expr) + .map(|(arg, table)| (cond.clone() * arg, table)) + .collect() + }); + + meta.lookup_any("tx lookup for CallData", |meta| { let cond = meta.query_fixed(q_enable, CURRENT) * meta.query_advice(is_tx_calldata, CURRENT) * meta.query_advice(non_pad_non_mask, CURRENT); @@ -604,9 +659,13 @@ impl SubCircuitConfig for CopyCircuitConfig { is_src_end, is_word_end, non_pad_non_mask, + #[cfg(feature = "dual-bytecode")] + is_first_bytecode_table, tx_table, rw_table, bytecode_table, + #[cfg(feature = "dual-bytecode")] + bytecode_table1, } } } @@ -624,12 +683,11 @@ impl CopyCircuitConfig { lt_word_end_chip: &IsEqualChip, challenges: Challenges>, copy_event: &CopyEvent, + bytecode_map: Option<&BTreeMap>, ) -> Result<(), Error> { - for (step_idx, (tag, table_row, circuit_row)) in - CopyTable::assignments(copy_event, challenges) - .iter() - .enumerate() - { + let copy_rows = CopyTable::assignments(copy_event, challenges, bytecode_map); + + for (step_idx, (tag, table_row, circuit_row)) in copy_rows.iter().enumerate() { let is_read = step_idx % 2 == 0; // Copy table assignments @@ -670,6 +728,8 @@ impl CopyCircuitConfig { self.mask, self.front_mask, self.word_index, + #[cfg(feature = "dual-bytecode")] + self.is_first_bytecode_table, ] .iter() .zip_eq(circuit_row) @@ -787,6 +847,7 @@ impl CopyCircuitConfig { copy_events: &[CopyEvent], max_copy_rows: usize, challenges: Challenges>, + bytecode_map: Option<&BTreeMap>, ) -> Result<(), Error> { let copy_rows_needed = copy_events .iter() @@ -846,6 +907,7 @@ impl CopyCircuitConfig { <_word_end_chip, challenges, copy_event, + bytecode_map, )?; log::trace!("offset after {}th copy event: {}", ev_idx, offset); } @@ -909,6 +971,14 @@ impl CopyCircuitConfig { *offset, || Value::known(F::zero()), )?; + #[cfg(feature = "dual-bytecode")] + // is_first_bytecode_table + region.assign_advice( + || format!("assign is_first_bytecode_table {}", *offset), + self.is_first_bytecode_table, + *offset, + || Value::known(F::zero()), + )?; // is_last region.assign_advice( || format!("assign is_last {}", *offset), @@ -1105,17 +1175,25 @@ pub struct CopyCircuit { pub copy_events: Vec, /// Max number of rows in copy circuit pub max_copy_rows: usize, + /// map for bool value indicates come from first + /// bytecode circuit. + pub bytecode_map: Option>, _marker: PhantomData, - /// Data for external lookup tables + /// Data for external lookup tables, currently this field only used for testing. pub external_data: ExternalData, } impl CopyCircuit { /// Return a new CopyCircuit - pub fn new(copy_events: Vec, max_copy_rows: usize) -> Self { + pub fn new( + copy_events: Vec, + max_copy_rows: usize, + bytecode_map: Option>, + ) -> Self { Self { copy_events, max_copy_rows, + bytecode_map, _marker: PhantomData, external_data: ExternalData::default(), } @@ -1125,11 +1203,13 @@ impl CopyCircuit { pub fn new_with_external_data( copy_events: Vec, max_copy_rows: usize, + bytecode_map: Option>, external_data: ExternalData, ) -> Self { Self { copy_events, max_copy_rows, + bytecode_map, _marker: PhantomData, external_data, } @@ -1143,6 +1223,7 @@ impl CopyCircuit { Self::new( block.copy_events.clone(), block.circuits_params.max_copy_rows, + block.bytecode_map.clone(), ) } } @@ -1160,6 +1241,7 @@ impl SubCircuit for CopyCircuit { Self::new_with_external_data( block.copy_events.clone(), block.circuits_params.max_copy_rows, + block.bytecode_map.clone(), ExternalData { max_txs: block.circuits_params.max_txs, max_calldata: block.circuits_params.max_calldata, @@ -1190,7 +1272,13 @@ impl SubCircuit for CopyCircuit { challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { - config.assign_copy_events(layouter, &self.copy_events, self.max_copy_rows, *challenges) + config.assign_copy_events( + layouter, + &self.copy_events, + self.max_copy_rows, + *challenges, + self.bytecode_map.as_ref(), + ) } } diff --git a/zkevm-circuits/src/copy_circuit/copy_gadgets.rs b/zkevm-circuits/src/copy_circuit/copy_gadgets.rs index fdd96702ea..398a4307ff 100644 --- a/zkevm-circuits/src/copy_circuit/copy_gadgets.rs +++ b/zkevm-circuits/src/copy_circuit/copy_gadgets.rs @@ -608,3 +608,32 @@ pub fn constrain_rw_word_complete( ]), ); } + +/// Ensure the 'is_first_bytecode_table' column is bool and not changed in one copy event. +pub fn constrain_is_first_bytecode_table( + cb: &mut BaseConstraintBuilder, + meta: &mut VirtualCells<'_, F>, + is_first_bytecode_col: Column, + is_last_col: Column, +) { + let is_first_bytecode_table = meta.query_advice(is_first_bytecode_col, CURRENT); + let is_first_bytecode_table_next = meta.query_advice(is_first_bytecode_col, NEXT_ROW); + + cb.require_boolean( + "is_first_bytecode_table is bool type", + is_first_bytecode_table.clone(), + ); + + let is_last_step = meta.query_advice(is_last_col, CURRENT); + let is_next_last_step = meta.query_advice(is_last_col, NEXT_ROW); + cb.condition( + not::expr(is_last_step) * not::expr(is_next_last_step), + |cb| { + cb.require_equal( + "is_first_bytecode_table doesn't change in one copy event ", + is_first_bytecode_table, + is_first_bytecode_table_next, + ); + }, + ); +} diff --git a/zkevm-circuits/src/copy_circuit/dev.rs b/zkevm-circuits/src/copy_circuit/dev.rs index 71f94ff6d2..9d3babc52a 100644 --- a/zkevm-circuits/src/copy_circuit/dev.rs +++ b/zkevm-circuits/src/copy_circuit/dev.rs @@ -4,6 +4,7 @@ use crate::{ copy_circuit::{CopyCircuitConfig, CopyCircuitConfigArgs}, table::{BytecodeTable, CopyTable, RwTable, TxTable}, util::{Challenges, Field, SubCircuit, SubCircuitConfig}, + witness::Block, }; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, @@ -24,6 +25,9 @@ impl Circuit for CopyCircuit { let tx_table = TxTable::construct(meta); let rw_table = RwTable::construct(meta); let bytecode_table = BytecodeTable::construct(meta); + #[cfg(feature = "dual-bytecode")] + let bytecode_table1 = BytecodeTable::construct(meta); + let q_enable = meta.fixed_column(); let copy_table = CopyTable::construct(meta, q_enable); let challenges = Challenges::construct(meta); @@ -36,6 +40,8 @@ impl Circuit for CopyCircuit { tx_table, rw_table, bytecode_table, + #[cfg(feature = "dual-bytecode")] + bytecode_table1, copy_table, q_enable, challenges: challenge_exprs, @@ -68,11 +74,34 @@ impl Circuit for CopyCircuit { challenge_values.evm_word(), )?; - config.0.bytecode_table.dev_load( - &mut layouter, - self.external_data.bytecodes.values(), - &challenge_values, - )?; + // when enable feature "dual-bytecode", get two sets of bytecodes here. + if self.bytecode_map.is_some() { + // enable feature = "dual-bytecode" + let (first_bytecodes, second_bytecodes) = Block::split_bytecodes_for_dual_sub_circuits( + &self.external_data.bytecodes, + self.bytecode_map + .as_ref() + .expect("bytecode_map is not none when enable feature 'dual-bytecode'"), + ); + config + .0 + .bytecode_table + .dev_load(&mut layouter, first_bytecodes, &challenge_values)?; + + #[cfg(feature = "dual-bytecode")] + config.0.bytecode_table1.dev_load( + &mut layouter, + second_bytecodes, + &challenge_values, + )?; + } else { + config.0.bytecode_table.dev_load( + &mut layouter, + self.external_data.bytecodes.values(), + &challenge_values, + )?; + }; + self.synthesize_sub(&config.0, &challenge_values, &mut layouter) } } diff --git a/zkevm-circuits/src/copy_circuit/test.rs b/zkevm-circuits/src/copy_circuit/test.rs index 1b9f8ef8a8..ec9788eb01 100644 --- a/zkevm-circuits/src/copy_circuit/test.rs +++ b/zkevm-circuits/src/copy_circuit/test.rs @@ -40,10 +40,15 @@ fn copy_circuit_unusable_rows() { pub fn test_copy_circuit( copy_events: Vec, max_copy_rows: usize, + bytecode_map: Option>, external_data: ExternalData, ) -> Result<(), Vec> { - let circuit = - CopyCircuit::::new_with_external_data(copy_events, max_copy_rows, external_data); + let circuit = CopyCircuit::::new_with_external_data( + copy_events, + max_copy_rows, + bytecode_map, + external_data, + ); let prover = MockProver::::run(K, &circuit, vec![]).unwrap(); prover.verify_par() @@ -54,6 +59,7 @@ pub fn test_copy_circuit_from_block(block: Block) -> Result<(), Vec::new(block1.copy_events, block1.circuits_params.max_copy_rows); + let circuit = CopyCircuit::::new( + block1.copy_events, + block1.circuits_params.max_copy_rows, + block1.bytecode_map, + ); let prover1 = MockProver::::run(14, &circuit, vec![]).unwrap(); let circuit = CopyCircuit::::new( block2.copy_events.clone(), block2.circuits_params.max_copy_rows, + block2.bytecode_map, ); let prover2 = MockProver::::run(14, &circuit, vec![]).unwrap(); diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 0a6c6ce1fa..298c3b33f6 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -45,6 +45,8 @@ pub struct EvmCircuitConfig { tx_table: TxTable, rw_table: RwTable, bytecode_table: BytecodeTable, + #[cfg(feature = "dual-bytecode")] + bytecode_table1: BytecodeTable, block_table: BlockTable, copy_table: CopyTable, keccak_table: KeccakTable, @@ -66,6 +68,9 @@ pub struct EvmCircuitConfigArgs { pub rw_table: RwTable, /// BytecodeTable pub bytecode_table: BytecodeTable, + #[cfg(feature = "dual-bytecode")] + /// BytecodeTable + pub bytecode_table1: BytecodeTable, /// BlockTable pub block_table: BlockTable, /// CopyTable @@ -105,6 +110,8 @@ impl SubCircuitConfig for EvmCircuitConfig { tx_table, rw_table, bytecode_table, + #[cfg(feature = "dual-bytecode")] + bytecode_table1, block_table, copy_table, keccak_table, @@ -126,6 +133,8 @@ impl SubCircuitConfig for EvmCircuitConfig { &tx_table, &rw_table, &bytecode_table, + #[cfg(feature = "dual-bytecode")] + &bytecode_table1, &block_table, ©_table, &keccak_table, @@ -144,6 +153,8 @@ impl SubCircuitConfig for EvmCircuitConfig { tx_table.annotate_columns(meta); rw_table.annotate_columns(meta); bytecode_table.annotate_columns(meta); + #[cfg(feature = "dual-bytecode")] + bytecode_table1.annotate_columns(meta); block_table.annotate_columns(meta); copy_table.annotate_columns(meta); keccak_table.annotate_columns(meta); @@ -160,6 +171,8 @@ impl SubCircuitConfig for EvmCircuitConfig { tx_table, rw_table, bytecode_table, + #[cfg(feature = "dual-bytecode")] + bytecode_table1, block_table, copy_table, keccak_table, @@ -452,6 +465,8 @@ impl Circuit for EvmCircuit { let rw_table = RwTable::construct(meta); let tx_table = TxTable::construct(meta); let bytecode_table = BytecodeTable::construct(meta); + #[cfg(feature = "dual-bytecode")] + let bytecode_table1 = BytecodeTable::construct(meta); let block_table = BlockTable::construct(meta); let q_copy_table = meta.fixed_column(); let copy_table = CopyTable::construct(meta, q_copy_table); @@ -470,6 +485,8 @@ impl Circuit for EvmCircuit { tx_table, rw_table, bytecode_table, + #[cfg(feature = "dual-bytecode")] + bytecode_table1, block_table, copy_table, keccak_table, @@ -510,6 +527,23 @@ impl Circuit for EvmCircuit { block.circuits_params.max_rws, challenges.evm_word(), )?; + + #[cfg(feature = "dual-bytecode")] + { + // when enable feature "dual-bytecode", get two sets of bytecodes here. + let (first_bytecodes, second_bytecodes) = block.get_bytecodes_for_dual_sub_circuits(); + // assign first bytecode_table + config + .bytecode_table + .dev_load(&mut layouter, first_bytecodes, &challenges)?; + + // assign second bytecode_table + config + .bytecode_table1 + .dev_load(&mut layouter, second_bytecodes, &challenges)?; + } + + #[cfg(not(feature = "dual-bytecode"))] config .bytecode_table .dev_load(&mut layouter, block.bytecodes.values(), &challenges)?; diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 23141b50e3..ffc699279a 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -391,6 +391,7 @@ impl ExecutionConfig { tx_table: &dyn LookupTable, rw_table: &dyn LookupTable, bytecode_table: &dyn LookupTable, + #[cfg(feature = "dual-bytecode")] bytecode_table1: &dyn LookupTable, block_table: &dyn LookupTable, copy_table: &dyn LookupTable, keccak_table: &dyn LookupTable, @@ -687,6 +688,8 @@ impl ExecutionConfig { tx_table, rw_table, bytecode_table, + #[cfg(feature = "dual-bytecode")] + bytecode_table1, block_table, copy_table, keccak_table, @@ -956,6 +959,7 @@ impl ExecutionConfig { tx_table: &dyn LookupTable, rw_table: &dyn LookupTable, bytecode_table: &dyn LookupTable, + #[cfg(feature = "dual-bytecode")] bytecode_table1: &dyn LookupTable, block_table: &dyn LookupTable, copy_table: &dyn LookupTable, keccak_table: &dyn LookupTable, @@ -977,6 +981,8 @@ impl ExecutionConfig { Table::Tx => tx_table, Table::Rw => rw_table, Table::Bytecode => bytecode_table, + #[cfg(feature = "dual-bytecode")] + Table::Bytecode1 => bytecode_table1, Table::Block => block_table, Table::Copy => copy_table, Table::Keccak => keccak_table, @@ -1418,6 +1424,8 @@ impl ExecutionConfig { ("EVM_lookup_tx", TX_TABLE_LOOKUPS), ("EVM_lookup_rw", RW_TABLE_LOOKUPS), ("EVM_lookup_bytecode", BYTECODE_TABLE_LOOKUPS), + #[cfg(feature = "dual-bytecode")] + ("EVM_lookup_bytecode1", BYTECODE_TABLE_LOOKUPS), ("EVM_lookup_block", BLOCK_TABLE_LOOKUPS), ("EVM_lookup_copy", COPY_TABLE_LOOKUPS), ("EVM_lookup_keccak", KECCAK_TABLE_LOOKUPS), diff --git a/zkevm-circuits/src/evm_circuit/execution/callop.rs b/zkevm-circuits/src/evm_circuit/execution/callop.rs index 17e78198e9..102f8511fc 100644 --- a/zkevm-circuits/src/evm_circuit/execution/callop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/callop.rs @@ -782,7 +782,8 @@ impl ExecutionGadget for CallOpGadget { self.is_depth_ok .assign(region, offset, F::from(depth.low_u64()), F::from(1025))?; - + self.opcode_gadget + .assign(region, offset, block, call, step)?; // This offset is used to change the index offset of `step.rw_indices`. // Since both CALL and CALLCODE have an extra stack pop `value`, and // opcode DELEGATECALL has two extra call context lookups - current @@ -849,8 +850,6 @@ impl ExecutionGadget for CallOpGadget { } } - self.opcode_gadget - .assign(region, offset, block, call, step)?; self.is_call.assign( region, offset, diff --git a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs index 58879cb04c..21e2c1abd3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs @@ -134,6 +134,11 @@ impl ExecutionGadget for CodeCopyGadget { code_size.expr(), code_len_gadget.code_length.expr(), ); + cb.require_equal( + "code_len_gadget and same_context have the same is_first_bytecode_table", + code_len_gadget.is_first_bytecode_table.expr(), + same_context.is_first_bytecode_table().expr(), + ); Self { same_context, diff --git a/zkevm-circuits/src/evm_circuit/execution/codesize.rs b/zkevm-circuits/src/evm_circuit/execution/codesize.rs index 09428e88af..38fcfdc044 100644 --- a/zkevm-circuits/src/evm_circuit/execution/codesize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/codesize.rs @@ -56,6 +56,11 @@ impl ExecutionGadget for CodesizeGadget { from_bytes::expr(&codesize_bytes), code_len_gadget.code_length.expr(), ); + cb.require_equal( + "code_len_gadget and same_context have the same is_first_bytecode_table", + code_len_gadget.is_first_bytecode_table.expr(), + same_context.is_first_bytecode_table(), + ); Self { same_context, @@ -88,7 +93,6 @@ impl ExecutionGadget for CodesizeGadget { self.code_len_gadget .assign(region, offset, block, &call.code_hash)?; - Ok(()) } } diff --git a/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs b/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs index 4b82a82bfe..fed4903f4c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs @@ -15,6 +15,7 @@ use crate::{ }; use eth_types::{evm_types::OpcodeId, U256}; +use gadgets::util::not; use halo2_proofs::{circuit::Value, plonk::Error}; #[derive(Clone, Debug)] @@ -72,23 +73,45 @@ impl ExecutionGadget for ErrorInvalidJumpGadget { cb.require_zero("condition is not zero", is_condition_zero.expr()); }); + let common_error_gadget = + CommonErrorGadget::construct(cb, opcode.expr(), 3.expr() + is_jumpi.expr()); + // If destination is in valid range, lookup for the value. cb.condition(dest.lt_cap(), |cb| { - cb.bytecode_lookup( - cb.curr.state.code_hash.expr(), - dest.valid_value(), - is_code.expr(), - value.expr(), - push_rlc.expr(), + cb.condition(code_len_gadget.is_first_bytecode_table.expr(), |cb| { + cb.bytecode_lookup( + cb.curr.state.code_hash.expr(), + dest.valid_value(), + is_code.expr(), + value.expr(), + push_rlc.expr(), + ); + }); + + #[cfg(feature = "dual-bytecode")] + cb.condition( + not::expr(code_len_gadget.is_first_bytecode_table.expr()), + |cb| { + cb.bytecode_lookup1( + cb.curr.state.code_hash.expr(), + dest.valid_value(), + is_code.expr(), + value.expr(), + push_rlc.expr(), + ); + }, ); + cb.require_zero( "is_code is false or not JUMPDEST", is_code.expr() * is_jump_dest.expr(), ); }); - - let common_error_gadget = - CommonErrorGadget::construct(cb, opcode.expr(), 3.expr() + is_jumpi.expr()); + cb.require_equal( + "code_len_gadget and common_error_gadget have the same is_first_bytecode_table", + code_len_gadget.is_first_bytecode_table.expr(), + common_error_gadget.is_first_bytecode_table.expr(), + ); Self { opcode, diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodecopy.rs b/zkevm-circuits/src/evm_circuit/execution/extcodecopy.rs index b1cb0a2052..8e512a1cf0 100644 --- a/zkevm-circuits/src/evm_circuit/execution/extcodecopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/extcodecopy.rs @@ -60,6 +60,7 @@ impl ExecutionGadget for ExtcodecopyGadget { let memory_length = cb.query_word_rlc(); let memory_offset = cb.query_cell_phase2(); + let code_hash = cb.query_cell_phase2(); let not_exists = IsZeroGadget::construct(cb, code_hash.clone().expr()); let exists = not::expr(not_exists.expr()); diff --git a/zkevm-circuits/src/evm_circuit/execution/jump.rs b/zkevm-circuits/src/evm_circuit/execution/jump.rs index 1769843c7b..1e00685247 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jump.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jump.rs @@ -35,13 +35,6 @@ impl ExecutionGadget for JumpGadget { // Pop the value from the stack cb.stack_pop(destination.expr()); - // Lookup opcode at destination - cb.opcode_lookup_at( - from_bytes::expr(&destination.cells), - OpcodeId::JUMPDEST.expr(), - 1.expr(), - ); - // State transition let opcode = cb.query_cell(); let step_state_transition = StepStateTransition { @@ -53,6 +46,13 @@ impl ExecutionGadget for JumpGadget { }; let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + // Lookup opcode at destination + cb.opcode_lookup_at( + from_bytes::expr(&destination.cells), + OpcodeId::JUMPDEST.expr(), + same_context.is_first_bytecode_table(), + ); + Self { same_context, destination, diff --git a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs index b709039267..84b799f6fd 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs @@ -44,21 +44,10 @@ impl ExecutionGadget for JumpiGadget { let is_condition_zero = IsZeroGadget::construct(cb, phase2_condition.expr()); let should_jump = 1.expr() - is_condition_zero.expr(); - // Lookup opcode at destination when should_jump - cb.condition(should_jump.clone(), |cb| { - cb.require_equal( - "JUMPI destination must be within range if condition is non-zero", - dest.not_overflow(), - 1.expr(), - ); - - cb.opcode_lookup_at(dest.valid_value(), OpcodeId::JUMPDEST.expr(), 1.expr()); - }); - // Transit program_counter to destination when should_jump, otherwise by // delta 1. let next_program_counter = select::expr( - should_jump, + should_jump.expr(), dest.valid_value(), cb.curr.state.program_counter.expr() + 1.expr(), ); @@ -74,6 +63,21 @@ impl ExecutionGadget for JumpiGadget { }; let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + // Lookup opcode at destination when should_jump + cb.condition(should_jump.expr(), |cb| { + cb.require_equal( + "JUMPI destination must be within range if condition is non-zero", + dest.not_overflow(), + 1.expr(), + ); + + cb.opcode_lookup_at( + dest.valid_value(), + OpcodeId::JUMPDEST.expr(), + same_context.is_first_bytecode_table(), + ); + }); + Self { same_context, dest, diff --git a/zkevm-circuits/src/evm_circuit/execution/stop.rs b/zkevm-circuits/src/evm_circuit/execution/stop.rs index 08eaf4bbee..943353666a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/stop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/stop.rs @@ -54,6 +54,12 @@ impl ExecutionGadget for StopGadget { OpcodeId::STOP.expr(), ); + cb.require_equal( + "code_len_gadget and opcode_gadget have the same is_first_bytecode_table", + opcode_gadget.is_first_bytecode_table.expr(), + code_len_gadget.is_first_bytecode_table.expr(), + ); + // Call ends with STOP must be successful cb.call_context_lookup(false.expr(), None, CallContextFieldTag::IsSuccess, 1.expr()); diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index bc7f537e05..95d78e2cc7 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -34,6 +34,12 @@ pub(crate) const EVM_LOOKUP_COLS: usize = FIXED_TABLE_LOOKUPS + TX_TABLE_LOOKUPS + RW_TABLE_LOOKUPS + BYTECODE_TABLE_LOOKUPS + // only add when feature 'dual-bytecode' is enabled + + if cfg!(feature = "dual-bytecode"){ + BYTECODE_TABLE_LOOKUPS + }else{ + 0 + } + BLOCK_TABLE_LOOKUPS + COPY_TABLE_LOOKUPS + KECCAK_TABLE_LOOKUPS @@ -59,6 +65,8 @@ pub(crate) const LOOKUP_CONFIG: &[(Table, usize)] = &[ (Table::ModExp, MODEXP_TABLE_LOOKUPS), (Table::Ecc, ECC_TABLE_LOOKUPS), (Table::PowOfRand, POW_OF_RAND_TABLE_LOOKUPS), + #[cfg(feature = "dual-bytecode")] + (Table::Bytecode1, BYTECODE_TABLE_LOOKUPS), ]; /// Fixed Table lookups done in EVMCircuit diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index de5ab7a00b..913b1125b6 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -169,6 +169,8 @@ pub(crate) enum Table { Tx, Rw, Bytecode, + #[cfg(feature = "dual-bytecode")] + Bytecode1, Block, Copy, Keccak, @@ -272,6 +274,27 @@ pub(crate) enum Lookup { /// Warning: If the bytecode is truncated, this is the actual data, without zero-padding. push_rlc: Expression, }, + + #[cfg(feature = "dual-bytecode")] + /// Lookup to second bytecode table, which contains all used creation code and + /// contract code. + Bytecode1 { + /// Hash to specify which code to read. + hash: Expression, + /// Tag to specify whether its the bytecode length or byte value in the + /// bytecode. + tag: Expression, + /// Index to specify which byte of bytecode. + index: Expression, + /// A boolean value to specify if the value is executable opcode or the + /// data portion of PUSH* operations. + is_code: Expression, + /// Value corresponding to the tag. + value: Expression, + /// The RLC of the PUSH data (LE order), or 0. + /// Warning: If the bytecode is truncated, this is the actual data, without zero-padding. + push_rlc: Expression, + }, /// Lookup to block table, which contains constants of this block. Block { /// Tag to specify which field to read. @@ -382,6 +405,8 @@ impl Lookup { Self::Tx { .. } => Table::Tx, Self::Rw { .. } => Table::Rw, Self::Bytecode { .. } => Table::Bytecode, + #[cfg(feature = "dual-bytecode")] + Self::Bytecode1 { .. } => Table::Bytecode1, Self::Block { .. } => Table::Block, Self::CopyTable { .. } => Table::Copy, Self::KeccakTable { .. } => Table::Keccak, @@ -449,6 +474,25 @@ impl Lookup { push_rlc.clone(), ] } + #[cfg(feature = "dual-bytecode")] + Self::Bytecode1 { + hash, + tag, + index, + is_code, + value, + push_rlc, + } => { + vec![ + 1.expr(), // q_enable + hash.clone(), + tag.clone(), + index.clone(), + is_code.clone(), + value.clone(), + push_rlc.clone(), + ] + } Self::Block { field_tag, number, diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index 2eb75a11e9..f7a4a18fba 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -887,3 +887,88 @@ fn test_find_two_closest_subset() { assert_eq!(set1, empty_vec); assert_eq!(set2, empty_vec); } + +pub(crate) struct PartitionData { + pub(crate) first_part: Vec<(T, usize)>, + pub(crate) second_part: Vec<(T, usize)>, +} + +/// The function of this algorithm: Split a vec into two subsets such that +/// the sums of the two subsets are close, but not necessarily return the most optimal result. +/// Note: if stable result is required, make use you pass the stable parameter vec. +pub(crate) fn greedy_simple_partition(nums: Vec<(T, usize)>) -> PartitionData { + let mut nums = nums; + // sorted in descending order + // sort_by helper is stable, see doc at https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort_by + nums.sort_by(|a, b| b.1.cmp(&a.1)); + let mut sum1 = 0; + let mut sum2 = 0; + + // construct two sub sets + let mut subset1 = Vec::new(); + let mut subset2 = Vec::new(); + + for num in nums { + if sum1 <= sum2 + num.1 { + sum1 += num.1; + subset1.push(num); + } else { + sum2 += num.1; + subset2.push(num); + } + } + + PartitionData { + first_part: subset1, + second_part: subset2, + } +} + +// tests for algorithm of `greedy_simple_partition` +#[test] +fn test_greedy_partition() { + let mut nums = vec![("key1", 1), ("key2", 5), ("key3", 11), ("key4", 5)]; + let mut partition_data = greedy_simple_partition(nums); + + // the most optimal set: set1: [11], set2 [1, 5, 5] + assert_eq!(partition_data.first_part, [("key3", 11), ("key1", 1)]); + assert_eq!(partition_data.second_part, [("key2", 5), ("key4", 5)]); + + nums = vec![("key1", 80), ("key2", 100), ("key3", 10), ("key4", 20)]; + partition_data = greedy_simple_partition(nums); + // close to the most optimal set: set1: [20, 80], set2 [10, 100] + assert_eq!(partition_data.first_part, [("key2", 100), ("key4", 20)]); + assert_eq!(partition_data.second_part, [("key1", 80), ("key3", 10)]); + + nums = vec![ + ("key1", 80), + ("key2", 20), + ("key3", 50), + ("key4", 110), + ("key5", 32), + ]; + partition_data = greedy_simple_partition(nums); + // close to the most optimal set: set1 [32, 110], set2 [50, 20, 80] + + assert_eq!(partition_data.first_part, [("key4", 110), ("key3", 50)]); + assert_eq!( + partition_data.second_part, + [("key1", 80), ("key5", 32), ("key2", 20)] + ); + + nums = vec![ + ("key1", 1), + ("key2", 5), + ("key3", 11), + ("key4", 5), + ("key5", 10), + ]; + partition_data = greedy_simple_partition(nums); + + // close to the most optimal sets: set1 [10, 5, 1], set2 [11, 5] + assert_eq!( + partition_data.first_part, + [("key3", 11), ("key2", 5), ("key1", 1)] + ); + assert_eq!(partition_data.second_part, [("key5", 10), ("key4", 5)]); +} diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index 4406b0141e..de2ee03757 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -48,6 +48,9 @@ pub(crate) use tx_l1_msg::TxL1MsgGadget; #[derive(Clone, Debug)] pub(crate) struct SameContextGadget { opcode: Cell, + // indicates current op code belongs to first or second bytecode table. + // should be bool type. + is_first_bytecode_table: Cell, sufficient_gas_left: RangeCheckGadget, } @@ -66,7 +69,10 @@ impl SameContextGadget { step_state_transition: StepStateTransition, push_rlc: Expression, ) -> Self { - cb.opcode_lookup_rlc(opcode.expr(), push_rlc); + let is_first_bytecode_table = cb.query_bool(); + + cb.lookup_opcode_with_push_rlc(opcode.expr(), push_rlc, is_first_bytecode_table.expr()); + cb.add_lookup( "Responsible opcode lookup", Lookup::Fixed { @@ -87,22 +93,37 @@ impl SameContextGadget { Self { opcode, + is_first_bytecode_table, sufficient_gas_left, } } + // Check if current bytecode is belong to first bytecode table. + // Note: always return true when feature 'dual-bytecode' is disabled. + pub(crate) fn is_first_bytecode_table(&self) -> Expression { + self.is_first_bytecode_table.expr() + } + pub(crate) fn assign_exec_step( &self, region: &mut CachedRegion<'_, '_, F>, offset: usize, - _block: &Block, - _call: &Call, + block: &Block, + call: &Call, step: &ExecStep, ) -> Result<(), Error> { let opcode = step.opcode.unwrap(); + let is_first_bytecode_table = block.is_first_bytecode_circuit(&call.code_hash); + self.opcode .assign(region, offset, Value::known(F::from(opcode.as_u64())))?; + self.is_first_bytecode_table.assign( + region, + offset, + Value::known(F::from(is_first_bytecode_table as u64)), + )?; + self.sufficient_gas_left .assign(region, offset, F::from(step.gas_left - step.gas_cost))?; @@ -110,20 +131,33 @@ impl SameContextGadget { } } -// this helper does bytecode length lookup. #[derive(Clone, Debug)] pub(crate) struct BytecodeLengthGadget { pub(crate) code_length: Cell, - // more fields here later + // indicates current op code belongs to first or second bytecode table. + // should be bool type. + pub(crate) is_first_bytecode_table: Cell, } impl BytecodeLengthGadget { pub(crate) fn construct(cb: &mut EVMConstraintBuilder, code_hash: Cell) -> Self { // Fetch the bytecode length from the bytecode table. let code_length = cb.query_cell(); - cb.bytecode_length(code_hash.expr(), code_length.expr()); + let is_first_bytecode_table = cb.query_bool(); + + cb.condition(is_first_bytecode_table.clone().expr(), |cb| { + cb.bytecode_length(code_hash.expr(), code_length.expr()); + }); + + #[cfg(feature = "dual-bytecode")] + cb.condition(not::expr(is_first_bytecode_table.clone().expr()), |cb| { + cb.bytecode1_length(code_hash.expr(), code_length.expr()); + }); - Self { code_length } + Self { + code_length, + is_first_bytecode_table, + } } pub(crate) fn assign( @@ -147,6 +181,11 @@ impl BytecodeLengthGadget { self.code_length .assign(region, offset, Value::known(F::from(code_length)))?; + self.is_first_bytecode_table.assign( + region, + offset, + Value::known(F::from(block.is_first_bytecode_circuit(code_hash))), + )?; Ok(code_length) } } @@ -156,29 +195,40 @@ impl BytecodeLengthGadget { #[derive(Clone, Debug)] pub(crate) struct BytecodeLookupGadget { pub(crate) opcode: Cell, - // more fields here later + pub(crate) is_first_bytecode_table: Cell, } impl BytecodeLookupGadget { pub(crate) fn construct(cb: &mut EVMConstraintBuilder) -> Self { let opcode = cb.query_cell(); - cb.opcode_lookup(opcode.expr(), 1.expr()); + let is_first_bytecode_table = cb.query_bool(); + cb.lookup_opcode(opcode.expr(), is_first_bytecode_table.expr()); - Self { opcode } + Self { + opcode, + is_first_bytecode_table, + } } pub(crate) fn assign( &self, region: &mut CachedRegion<'_, '_, F>, offset: usize, - _block: &Block, - _call: &Call, + block: &Block, + call: &Call, step: &ExecStep, ) -> Result<(), Error> { let opcode = step.opcode.unwrap(); self.opcode .assign(region, offset, Value::known(F::from(opcode.as_u64())))?; + let is_first_bytecode_table = block.is_first_bytecode_circuit(&call.code_hash); + self.is_first_bytecode_table.assign( + region, + offset, + Value::known(F::from(is_first_bytecode_table)), + )?; + Ok(()) } } @@ -1519,6 +1569,9 @@ pub(crate) fn cal_sstore_gas_cost_for_assignment( pub(crate) struct CommonErrorGadget { rw_counter_end_of_reversion: Cell, restore_context: RestoreContextGadget, + // indicates current op code belongs to first or second bytecode table. + // should be bool type. + pub(crate) is_first_bytecode_table: Cell, } impl CommonErrorGadget { @@ -1564,7 +1617,16 @@ impl CommonErrorGadget { return_data_length: Expression, push_rlc: Expression, ) -> Self { - cb.opcode_lookup_rlc(opcode.expr(), push_rlc); + let is_first_bytecode_table = cb.query_bool(); + + cb.condition(is_first_bytecode_table.expr(), |cb| { + cb.opcode_lookup_rlc(opcode.expr(), push_rlc.clone()); + }); + + #[cfg(feature = "dual-bytecode")] + cb.condition(not::expr(is_first_bytecode_table.expr()), |cb| { + cb.opcode_lookup_rlc2(opcode.expr(), push_rlc); + }); let rw_counter_end_of_reversion = cb.query_cell(); @@ -1623,6 +1685,7 @@ impl CommonErrorGadget { Self { rw_counter_end_of_reversion, restore_context, + is_first_bytecode_table, } } @@ -1644,6 +1707,13 @@ impl CommonErrorGadget { self.restore_context .assign(region, offset, block, call, step, rw_offset)?; + let is_first_bytecode_table = block.is_first_bytecode_circuit(&call.code_hash); + self.is_first_bytecode_table.assign( + region, + offset, + Value::known(F::from(is_first_bytecode_table)), + )?; + // NOTE: return value not use for now. Ok(1u64) } diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index a229d364a4..1ad21f128d 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -649,27 +649,58 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { ); } - // Opcode + // lookup real opcode. opcode_lookup_rlc ensures is_code = 1 + pub(crate) fn lookup_opcode( + &mut self, + opcode: Expression, + is_first_bytecode_table: Expression, + ) { + self.lookup_opcode_with_push_rlc(opcode, 0.expr(), is_first_bytecode_table); + } + + pub(crate) fn lookup_opcode_with_push_rlc( + &mut self, + opcode: Expression, + push_rlc: Expression, + is_first_bytecode_table: Expression, + ) { + self.condition(is_first_bytecode_table.expr(), |cb| { + cb.opcode_lookup_rlc(opcode.expr(), push_rlc.expr()); + }); - pub(crate) fn opcode_lookup(&mut self, opcode: Expression, is_code: Expression) { - assert_eq!(is_code, 1.expr()); - self.opcode_lookup_rlc(opcode, 0.expr()); + #[cfg(feature = "dual-bytecode")] + self.condition(not::expr(is_first_bytecode_table.expr()), |cb| { + cb.opcode_lookup_rlc2(opcode.expr(), push_rlc); + }); } pub(crate) fn opcode_lookup_at( &mut self, index: Expression, opcode: Expression, - is_code: Expression, + is_first_bytecode_table: Expression, ) { - assert_eq!(is_code, 1.expr()); - self.opcode_lookup_at_rlc(index, opcode, 0.expr()); + self.condition(is_first_bytecode_table.clone(), |cb| { + cb.opcode_lookup_at_rlc(index.clone(), opcode.clone(), 0.expr()); + }); + + #[cfg(feature = "dual-bytecode")] + self.condition(not::expr(is_first_bytecode_table), |cb| { + cb.opcode_lookup_at_rlc1(index, opcode, 0.expr()); + }); } pub(crate) fn opcode_lookup_rlc(&mut self, opcode: Expression, push_rlc: Expression) { self.opcode_lookup_at_rlc(self.curr.state.program_counter.expr(), opcode, push_rlc); } + #[cfg(feature = "dual-bytecode")] + // helper to lookup second bytecode table. + pub(crate) fn opcode_lookup_rlc2(&mut self, opcode: Expression, push_rlc: Expression) { + self.opcode_lookup_at_rlc1(self.curr.state.program_counter.expr(), opcode, push_rlc); + } + + // lookup bytecode_table. pub(crate) fn opcode_lookup_at_rlc( &mut self, index: Expression, @@ -691,6 +722,29 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { ); } + #[cfg(feature = "dual-bytecode")] + // lookup bytecode_table1. + pub(crate) fn opcode_lookup_at_rlc1( + &mut self, + index: Expression, + opcode: Expression, + push_rlc: Expression, + ) { + let is_root_create = self.curr.state.is_root.expr() * self.curr.state.is_create.expr(); + self.add_lookup( + "Opcode lookup from Bytecode1", + Lookup::Bytecode1 { + hash: self.curr.state.code_hash.expr(), + tag: BytecodeFieldTag::Byte.expr(), + index, + is_code: 1.expr(), + value: opcode, + push_rlc, + } + .conditional(1.expr() - is_root_create), + ); + } + // Bytecode table pub(crate) fn bytecode_lookup( @@ -714,6 +768,28 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { ) } + #[cfg(feature = "dual-bytecode")] + pub(crate) fn bytecode_lookup1( + &mut self, + code_hash: Expression, + index: Expression, + is_code: Expression, + value: Expression, + push_rlc: Expression, + ) { + self.add_lookup( + "Bytecode1 (byte) lookup", + Lookup::Bytecode1 { + hash: code_hash, + tag: BytecodeFieldTag::Byte.expr(), + index, + is_code, + value, + push_rlc, + }, + ) + } + pub(crate) fn bytecode_length(&mut self, code_hash: Expression, value: Expression) { self.add_lookup( "Bytecode (length)", @@ -728,6 +804,21 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { ); } + #[cfg(feature = "dual-bytecode")] + pub(crate) fn bytecode1_length(&mut self, code_hash: Expression, value: Expression) { + self.add_lookup( + "Bytecode1 (length)", + Lookup::Bytecode1 { + hash: code_hash, + tag: BytecodeFieldTag::Header.expr(), + index: 0.expr(), + is_code: 0.expr(), + value, + push_rlc: 0.expr(), + }, + ); + } + // Tx context pub(crate) fn tx_context( diff --git a/zkevm-circuits/src/evm_circuit/util/instrumentation.rs b/zkevm-circuits/src/evm_circuit/util/instrumentation.rs index 8a0fdcddf9..988a711d4b 100644 --- a/zkevm-circuits/src/evm_circuit/util/instrumentation.rs +++ b/zkevm-circuits/src/evm_circuit/util/instrumentation.rs @@ -93,6 +93,10 @@ impl Instrument { CellType::Lookup(Table::Bytecode) => { report.bytecode_table = data_entry; } + #[cfg(feature = "dual-bytecode")] + CellType::Lookup(Table::Bytecode1) => { + report.bytecode_table1 = data_entry; + } CellType::Lookup(Table::Block) => { report.block_table = data_entry; } @@ -142,6 +146,8 @@ pub(crate) struct ExecStateReport { pub(crate) tx_table: StateReportRow, pub(crate) rw_table: StateReportRow, pub(crate) bytecode_table: StateReportRow, + #[cfg(feature = "dual-bytecode")] + pub(crate) bytecode_table1: StateReportRow, pub(crate) block_table: StateReportRow, pub(crate) copy_table: StateReportRow, pub(crate) keccak_table: StateReportRow, diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index a9894900ec..c237d3c4a1 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -127,8 +127,13 @@ pub struct SuperCircuitConfig { sha256_circuit: SHA256CircuitConfig, #[cfg(not(feature = "poseidon-codehash"))] bytecode_circuit: BytecodeCircuitConfig, + #[cfg(all(feature = "dual-bytecode", not(feature = "poseidon-codehash")))] + bytecode_circuit1: BytecodeCircuitConfig, + #[cfg(feature = "poseidon-codehash")] bytecode_circuit: ToHashBlockCircuitConfig, + #[cfg(all(feature = "dual-bytecode", feature = "poseidon-codehash"))] + bytecode_circuit1: ToHashBlockCircuitConfig, copy_circuit: CopyCircuitConfig, keccak_circuit: KeccakCircuitConfig, poseidon_circuit: PoseidonCircuitConfig, @@ -184,6 +189,9 @@ impl SubCircuitConfig for SuperCircuitConfig { log_circuit_info(meta, "poseidon table"); let bytecode_table = BytecodeTable::construct(meta); + #[cfg(feature = "dual-bytecode")] + let bytecode_table1 = BytecodeTable::construct(meta); + log_circuit_info(meta, "bytecode table"); let block_table = BlockTable::construct(meta); log_circuit_info(meta, "block table"); @@ -282,6 +290,16 @@ impl SubCircuitConfig for SuperCircuitConfig { challenges: challenges_expr.clone(), }, ); + + #[cfg(all(feature = "dual-bytecode", not(feature = "poseidon-codehash")))] + let bytecode_circuit1 = BytecodeCircuitConfig::new( + meta, + BytecodeCircuitConfigArgs { + bytecode_table: bytecode_table1.clone(), + keccak_table: keccak_table.clone(), + challenges: challenges_expr.clone(), + }, + ); #[cfg(feature = "poseidon-codehash")] let bytecode_circuit = ToHashBlockCircuitConfig::new( meta, @@ -295,6 +313,19 @@ impl SubCircuitConfig for SuperCircuitConfig { }, ); + #[cfg(all(feature = "dual-bytecode", feature = "poseidon-codehash"))] + let bytecode_circuit1 = ToHashBlockCircuitConfig::new( + meta, + ToHashBlockBytecodeCircuitConfigArgs { + base_args: BytecodeCircuitConfigArgs { + bytecode_table: bytecode_table1.clone(), + keccak_table: keccak_table.clone(), + challenges: challenges_expr.clone(), + }, + poseidon_table, + }, + ); + log_circuit_info(meta, "bytecode circuit"); let copy_circuit = CopyCircuitConfig::new( @@ -303,6 +334,8 @@ impl SubCircuitConfig for SuperCircuitConfig { tx_table: tx_table.clone(), rw_table, bytecode_table: bytecode_table.clone(), + #[cfg(feature = "dual-bytecode")] + bytecode_table1: bytecode_table1.clone(), copy_table, q_enable: q_copy_table, challenges: challenges_expr.clone(), @@ -350,6 +383,8 @@ impl SubCircuitConfig for SuperCircuitConfig { tx_table: tx_table.clone(), rw_table, bytecode_table, + #[cfg(feature = "dual-bytecode")] + bytecode_table1, block_table: block_table.clone(), copy_table, keccak_table: keccak_table.clone(), @@ -406,6 +441,8 @@ impl SubCircuitConfig for SuperCircuitConfig { ecc_circuit, sha256_circuit, bytecode_circuit, + #[cfg(feature = "dual-bytecode")] + bytecode_circuit1, copy_circuit, keccak_circuit, poseidon_circuit, @@ -449,6 +486,9 @@ pub struct SuperCircuit< pub pi_circuit: PiCircuit, /// Bytecode Circuit pub bytecode_circuit: BytecodeCircuit, + #[cfg(feature = "dual-bytecode")] + /// second Bytecode Circuit + pub bytecode_circuit1: BytecodeCircuit, /// Copy Circuit pub copy_circuit: CopyCircuit, /// Exp Circuit @@ -600,7 +640,16 @@ impl< let state_circuit = StateCircuit::new_from_block(block); let tx_circuit = TxCircuit::new_from_block(block); let pi_circuit = PiCircuit::new_from_block(block); + + // Get each sub circuit's bytecodes and assign + #[cfg(feature = "dual-bytecode")] + let bytecode_circuit = BytecodeCircuit::new_from_block_for_dual_circuit(block, true); + #[cfg(feature = "dual-bytecode")] + let bytecode_circuit1 = BytecodeCircuit::new_from_block_for_dual_circuit(block, false); + + #[cfg(not(feature = "dual-bytecode"))] let bytecode_circuit = BytecodeCircuit::new_from_block(block); + let copy_circuit = CopyCircuit::new_from_block_no_external(block); let exp_circuit = ExpCircuit::new_from_block(block); let modexp_circuit = ModExpCircuit::new_from_block(block); @@ -618,6 +667,8 @@ impl< tx_circuit, pi_circuit, bytecode_circuit, + #[cfg(feature = "dual-bytecode")] + bytecode_circuit1, copy_circuit, exp_circuit, keccak_circuit, @@ -640,6 +691,8 @@ impl< instance.extend_from_slice(&self.pi_circuit.instance()); instance.extend_from_slice(&self.tx_circuit.instance()); instance.extend_from_slice(&self.bytecode_circuit.instance()); + #[cfg(feature = "dual-bytecode")] + instance.extend_from_slice(&self.bytecode_circuit1.instance()); instance.extend_from_slice(&self.copy_circuit.instance()); instance.extend_from_slice(&self.state_circuit.instance()); instance.extend_from_slice(&self.exp_circuit.instance()); @@ -693,6 +746,16 @@ impl< log::debug!("assigning bytecode_circuit"); self.bytecode_circuit .synthesize_sub(&config.bytecode_circuit, challenges, layouter)?; + #[cfg(feature = "dual-bytecode")] + { + log::debug!("assigning second bytecode_circuit1"); + self.bytecode_circuit1.synthesize_sub( + &config.bytecode_circuit1, + challenges, + layouter, + )?; + } + log::debug!("assigning tx_circuit"); self.tx_circuit .synthesize_sub(&config.tx_circuit, challenges, layouter)?; diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 2643eda4ec..c1ebb77d22 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -48,6 +48,7 @@ use halo2_proofs::plonk::SecondPhase; use halo2_proofs::plonk::TableColumn; use itertools::Itertools; use std::array; +use std::collections::BTreeMap; use strum_macros::{EnumCount, EnumIter}; /// Trait used to define lookup tables @@ -1759,6 +1760,10 @@ pub struct CopyTable { } type CopyTableRow = [(Value, &'static str); 8]; +#[cfg(feature = "dual-bytecode")] +type CopyCircuitRow = [(Value, &'static str); 11]; + +#[cfg(not(feature = "dual-bytecode"))] type CopyCircuitRow = [(Value, &'static str); 10]; /// CopyThread is the state used while generating rows of the copy table. @@ -1797,6 +1802,7 @@ impl CopyTable { pub fn assignments( copy_event: &CopyEvent, challenges: Challenges>, + bytecode_map: Option<&BTreeMap>, ) -> Vec<(CopyDataType, CopyTableRow, CopyCircuitRow)> { assert!(copy_event.src_addr_end >= copy_event.src_addr); assert!( @@ -1980,6 +1986,24 @@ impl CopyTable { (rw_counter, rwc_inc_left) }; + #[cfg(feature = "dual-bytecode")] + // For codecopy & extcodecopy copy bytecodes, src_type == Bytecode. + // For return in creating/deploy contract case, dst_type == Bytecode. + let is_first_bytecode_table = if copy_event.src_type == CopyDataType::Bytecode { + let code_hash = Word::from_big_endian(copy_event.src_id.get_hash().as_bytes()); + // bytecode_map includes all the code_hash, for normal cases, unwrap would be safe. + // but for extcodecopy, the external_address can be non existed code hash, hence use `unwrap_or`. + *bytecode_map.unwrap().get(&code_hash).unwrap_or(&true) + } else if copy_event.dst_type == CopyDataType::Bytecode { + let code_hash = Word::from_big_endian(copy_event.dst_id.get_hash().as_bytes()); + + *bytecode_map.unwrap().get(&code_hash).unwrap() + } else { + // if not code related copy case, default value is true, even it is true, copy circuit will not do lookup if current row is + // not bytecode type. + true + }; + assignments.push(( thread.tag, [ @@ -2006,6 +2030,12 @@ impl CopyTable { (Value::known(F::from(copy_step.mask)), "mask"), (Value::known(F::from(thread.front_mask)), "front_mask"), (Value::known(F::from(word_index)), "word_index"), + #[cfg(feature = "dual-bytecode")] + ( + // set value from block get bytecode circuit. + Value::known(F::from(is_first_bytecode_table)), + "is_first_bytecode_table", + ), ], )); @@ -2064,7 +2094,10 @@ impl CopyTable { let tag_chip = BinaryNumberChip::construct(self.tag); let copy_table_columns = >::advice_columns(self); for copy_event in block.copy_events.iter() { - for (tag, row, _) in Self::assignments(copy_event, *challenges) { + let copy_rows = + Self::assignments(copy_event, *challenges, block.bytecode_map.as_ref()); + + for (tag, row, _) in copy_rows { region.assign_fixed( || format!("q_enable at row: {offset}"), self.q_enable, diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index a2566324cc..334adfb864 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -6,7 +6,7 @@ use std::collections::{BTreeMap, HashMap}; use crate::evm_circuit::{detect_fixed_table_tags, EvmCircuit}; use crate::{ - evm_circuit::util::rlc, + evm_circuit::util::{greedy_simple_partition, rlc}, super_circuit::params::get_super_circuit_params, table::{BlockContextFieldTag, RwTableTag}, util::{Field, SubCircuit}, @@ -50,6 +50,10 @@ pub struct Block { pub rws: RwMap, /// Bytecode used in the block pub bytecodes: BTreeMap, + /// Map from code hash to boolean () that + /// indicates whether the code with the said code hash belongs to the first or second + /// instance of the bytecode table. + pub bytecode_map: Option>, /// The block context pub context: BlockContexts, /// Copy events for the copy circuit's table. @@ -282,6 +286,69 @@ impl Block { log::debug!("tx_log num: {}", self.rws.rw_num(RwTableTag::TxLog)); log::debug!("start num: {}", self.rws.rw_num(RwTableTag::Start)); } + + // A helper that returns a boolean that indicates whether the bytecode with `code_hash` belongs to first bytecode circuit. + // Always return true when the feature `dual-bytecode` is disabled. + pub(crate) fn is_first_bytecode_circuit(&self, code_hash: &U256) -> bool { + // bytecode_map should cover the target 'code_hash', + // but for extcodecopy, the external_address can be non existed code hash. + // `unwrap` here is not safe. + if self.bytecode_map.is_none() { + // not config feature 'dual-bytecode' case. + true + } else { + let bytecode_map = self + .bytecode_map + .as_ref() + .expect("bytecode_map is not none when enable 'dual-bytecode' feature"); + *bytecode_map.get(code_hash).unwrap_or(&true) + } + } + + // Get two sets of bytecodes for two bytecode sub circuits when enable feature 'dual-bytecode'. + pub(crate) fn get_bytecodes_for_dual_sub_circuits(&self) -> (Vec<&Bytecode>, Vec<&Bytecode>) { + if self.bytecode_map.is_none() { + log::error!("error: bytecode_map is none"); + return (vec![], vec![]); + } + let (first_subcircuit_bytecodes, second_subcircuit_bytecodes) = + Self::split_bytecodes_for_dual_sub_circuits( + &self.bytecodes, + self.bytecode_map + .as_ref() + .expect("bytecode_map is not none when enable feature 'dual-bytecode'"), + ); + + (first_subcircuit_bytecodes, second_subcircuit_bytecodes) + } + + // Split two sets of bytecodes for two bytecode sub circuits. + //#[cfg(feature = "dual-bytecode")] + pub(crate) fn split_bytecodes_for_dual_sub_circuits<'a>( + bytecodes: &'a BTreeMap, + bytecode_map: &BTreeMap, + ) -> (Vec<&'a Bytecode>, Vec<&'a Bytecode>) { + let mut first_subcircuit_bytecodes = Vec::<&Bytecode>::new(); + let mut second_subcircuit_bytecodes = Vec::<&Bytecode>::new(); + + let first_subcircuit_code_hashes: Vec = bytecode_map + .iter() + .filter_map(|item| if *item.1 { Some(*item.0) } else { None }) + .collect(); + + let _ = bytecodes + .iter() + .map(|code| { + if first_subcircuit_code_hashes.contains(code.0) { + first_subcircuit_bytecodes.push(code.1) + } else { + second_subcircuit_bytecodes.push(code.1) + } + }) + .collect::>(); + + (first_subcircuit_bytecodes, second_subcircuit_bytecodes) + } } #[cfg(feature = "test")] @@ -577,6 +644,14 @@ pub fn block_convert( log::error!("withdraw root is not available"); } + let bytecodes: BTreeMap = get_bytecodes(code_db); + // if not enable 'dual-bytecode' feature, set bytecode_map to None. + let bytecode_map = if cfg!(feature = "dual-bytecode") { + Some(get_bytecode_map(&bytecodes)) + } else { + None + }; + let block = Block { context: BlockContexts::from(block), rws, @@ -596,20 +671,8 @@ pub fn block_convert( sigs: block.txs().iter().map(|tx| tx.signature).collect(), padding_step, end_block_step, - bytecodes: code_db - .0 - .iter() - .map(|(code_hash, bytes)| { - let hash = Word::from_big_endian(code_hash.as_bytes()); - ( - hash, - Bytecode { - hash, - bytes: bytes.clone(), - }, - ) - }) - .collect(), + bytecodes, + bytecode_map, copy_events: block.copy_events.clone(), exp_events: block.exp_events.clone(), sha3_inputs: block.sha3_inputs.clone(), @@ -636,3 +699,52 @@ pub fn dummy_witness_block(chain_id: u64) -> Block { builder.finalize_building().expect("should not fail"); block_convert(&builder.block, &builder.code_db).expect("should not fail") } + +// helper to extract bytecode info from CodeDB. +pub fn get_bytecodes(code_db: &CodeDB) -> BTreeMap { + let bytecodes: BTreeMap = code_db + .0 + .iter() + .map(|(code_hash, bytes)| { + let hash = Word::from_big_endian(code_hash.as_bytes()); + ( + hash, + Bytecode { + hash, + bytes: bytes.clone(), + }, + ) + }) + .collect(); + bytecodes +} + +// helper to extract bytecode map info (code_hash, is_first_bytecode_table) when enable feature 'dual-bytecode'. +pub fn get_bytecode_map(bytecodes: &BTreeMap) -> BTreeMap { + let bytecode_pairs = bytecodes + .iter() + .map(|(hash, bytecode)| (*hash, bytecode.bytes.len())) + .collect_vec(); + let partition_result = greedy_simple_partition(bytecode_pairs.clone()); + + let bytecode_map: BTreeMap = bytecode_pairs + .iter() + .map(|(hash, len)| { + if partition_result.first_part.contains(&(*hash, *len)) { + (*hash, true) + } else if partition_result.second_part.contains(&(*hash, *len)) { + (*hash, false) + } else { + // here should be not reachable, panic or return a placeholder. + // panic!("“Find an unexpected element that is not present in either first_set or second_set”) + log::error!( + "found unexpected code_hash {:?} when generate bytecode_map", + hash, + ); + (U256::zero(), false) + } + }) + .collect(); + + bytecode_map +} diff --git a/zkevm-circuits/src/witness/bytecode.rs b/zkevm-circuits/src/witness/bytecode.rs index 325b9d4f15..a713ebab1b 100644 --- a/zkevm-circuits/src/witness/bytecode.rs +++ b/zkevm-circuits/src/witness/bytecode.rs @@ -126,4 +126,10 @@ impl Bytecode { (byte, is_code, push_rlc) } + + /// The number of rows the `BytecodeCircuit` utilises to include the bytecode. + /// We use a single header row, plus a row per byte. + pub fn rows_required(&self) -> usize { + self.bytes.len() + 1 + } }