diff --git a/Cargo.lock b/Cargo.lock index 599395df1e..cfab234298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4007,6 +4007,7 @@ dependencies = [ "eth-types", "ff 0.11.0", "halo2", + "itertools", "keccak256", "mock", "num", diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index d9ad6d81e1..e85f42d970 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -7,7 +7,6 @@ use lazy_static::lazy_static; use log::trace; use zkevm_circuits::evm_circuit::witness::block_convert; use zkevm_circuits::state_circuit::StateCircuit; - lazy_static! { pub static ref GEN_DATA: GenDataOutput = GenDataOutput::load(); } @@ -30,11 +29,12 @@ mod test_evm_circuit { witness::{Block, Bytecode, RwMap, Transaction}, EvmCircuit, }; + use zkevm_circuits::rw_table::RwTable; #[derive(Clone)] struct TestCircuitConfig { tx_table: [Column; 4], - rw_table: [Column; 10], + rw_table: RwTable, bytecode_table: [Column; 4], evm_circuit: EvmCircuit, } @@ -50,7 +50,7 @@ mod test_evm_circuit { || "tx table", |mut region| { let mut offset = 0; - for column in self.rw_table { + for column in self.tx_table { region.assign_advice( || "tx table all-zero row", column, @@ -88,27 +88,16 @@ mod test_evm_circuit { || "rw table", |mut region| { let mut offset = 0; - for column in self.rw_table { - region.assign_advice( - || "rw table all-zero row", - column, - offset, - || Ok(F::zero()), - )?; - } + self.rw_table + .assign(&mut region, offset, &Default::default())?; offset += 1; for rw in rws.0.values().flat_map(|rws| rws.iter()) { - for (column, value) in - self.rw_table.iter().zip(rw.table_assignment(randomness)) - { - region.assign_advice( - || format!("rw table row {}", offset), - *column, - offset, - || Ok(value), - )?; - } + self.rw_table.assign( + &mut region, + offset, + &rw.table_assignment(randomness), + )?; offset += 1; } Ok(()) @@ -180,7 +169,7 @@ mod test_evm_circuit { fn configure(meta: &mut ConstraintSystem) -> Self::Config { let tx_table = [(); 4].map(|_| meta.advice_column()); - let rw_table = [(); 10].map(|_| meta.advice_column()); + let rw_table = RwTable::construct(meta); let bytecode_table = [(); 4].map(|_| meta.advice_column()); let block_table = [(); 3].map(|_| meta.advice_column()); @@ -327,12 +316,7 @@ async fn test_state_circuit_block(block_num: u64) { STACK_ROWS_MAX, STACK_ADDRESS_MAX, STORAGE_ROWS_MAX, - > { - randomness: Fr::rand(), - memory_ops, - stack_ops, - storage_ops, - }; + >::new(Fr::rand(), memory_ops, stack_ops, storage_ops); use pairing::bn256::Fr as Fp; let prover = MockProver::::run(DEGREE as u32, &circuit, vec![]).unwrap(); diff --git a/prover/src/bin/prover_cmd.rs b/prover/src/bin/prover_cmd.rs index 65ee91336f..dc9ff70842 100644 --- a/prover/src/bin/prover_cmd.rs +++ b/prover/src/bin/prover_cmd.rs @@ -3,7 +3,6 @@ use bus_mapping::rpc::GethClient; use env_logger::Env; use ethers_providers::Http; use halo2::{ - arithmetic::BaseExt, plonk::*, poly::commitment::Params, transcript::{Blake2bWrite, Challenge255}, @@ -66,10 +65,10 @@ async fn main() { // TODO: only {evm,state}_proof are implemented right now let evm_proof; let state_proof; + let block = block_convert(&builder.block, &builder.code_db); { // generate evm_circuit proof - let block = block_convert(&builder.block, &builder.code_db); - let circuit = TestCircuit::::new(block, FixedTableTag::iterator().collect()); + let circuit = TestCircuit::::new(block.clone(), FixedTableTag::iterator().collect()); // TODO: can this be pre-generated to a file? // related @@ -95,9 +94,6 @@ async fn main() { const STORAGE_ROWS_MAX: usize = 16384; const GLOBAL_COUNTER_MAX: usize = MEMORY_ROWS_MAX + STACK_ROWS_MAX + STORAGE_ROWS_MAX; - let stack_ops = builder.block.container.sorted_stack(); - let memory_ops = builder.block.container.sorted_memory(); - let storage_ops = builder.block.container.sorted_storage(); let circuit = StateCircuit::< Fr, true, @@ -107,12 +103,7 @@ async fn main() { STACK_ROWS_MAX, STACK_ADDRESS_MAX, STORAGE_ROWS_MAX, - > { - randomness: Fr::rand(), - memory_ops, - stack_ops, - storage_ops, - }; + >::new_from_rw_map(block.randomness, &block.rws); // TODO: same quest like in the first scope let vk = keygen_vk(¶ms, &circuit).expect("keygen_vk for params, state_circuit"); diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 872e704904..6d11831f61 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -21,6 +21,7 @@ eth-types = { path = "../eth-types" } serde_json = "1.0.66" rand_xorshift = "0.3" rand = "0.8" +itertools = "0.10.3" keccak256 = { path = "../keccak256"} [dev-dependencies] diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 2faaf293eb..c6c6b4b7f8 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -107,6 +107,7 @@ pub mod test { witness::{Block, BlockContext, Bytecode, RwMap, Transaction}, EvmCircuit, }, + rw_table::RwTable, util::Expr, }; use eth_types::{evm_types::GasCost, Word}; @@ -150,7 +151,7 @@ pub mod test { #[derive(Clone)] pub struct TestCircuitConfig { tx_table: [Column; 4], - rw_table: [Column; 10], + rw_table: RwTable, bytecode_table: [Column; 4], block_table: [Column; 3], evm_circuit: EvmCircuit, @@ -205,27 +206,16 @@ pub mod test { || "rw table", |mut region| { let mut offset = 0; - for column in self.rw_table { - region.assign_advice( - || "rw table all-zero row", - column, - offset, - || Ok(F::zero()), - )?; - } + self.rw_table + .assign(&mut region, offset, &Default::default())?; offset += 1; for rw in rws.0.values().flat_map(|rws| rws.iter()) { - for (column, value) in - self.rw_table.iter().zip(rw.table_assignment(randomness)) - { - region.assign_advice( - || format!("rw table row {}", offset), - *column, - offset, - || Ok(value), - )?; - } + self.rw_table.assign( + &mut region, + offset, + &rw.table_assignment(randomness), + )?; offset += 1; } Ok(()) @@ -334,7 +324,7 @@ pub mod test { fn configure(meta: &mut ConstraintSystem) -> Self::Config { let tx_table = [(); 4].map(|_| meta.advice_column()); - let rw_table = [(); 10].map(|_| meta.advice_column()); + let rw_table = RwTable::construct(meta); let bytecode_table = [(); 4].map(|_| meta.advice_column()); let block_table = [(); 3].map(|_| meta.advice_column()); diff --git a/zkevm-circuits/src/evm_circuit/execution/bitwise.rs b/zkevm-circuits/src/evm_circuit/execution/bitwise.rs index 1e3264fd64..0925bae1b3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/bitwise.rs +++ b/zkevm-circuits/src/evm_circuit/execution/bitwise.rs @@ -102,7 +102,7 @@ mod test { use crate::{ evm_circuit::test::rand_word, test_util::{ - get_fixed_table, run_test_circuits_with_config, BytecodeTestConfig, FixedTableConfig, + get_fixed_table, test_circuits_using_bytecode, BytecodeTestConfig, FixedTableConfig, }, }; use eth_types::{bytecode, Word}; @@ -127,7 +127,7 @@ mod test { evm_circuit_lookup_tags: get_fixed_table(FixedTableConfig::Complete), ..Default::default() }; - assert_eq!(run_test_circuits_with_config(bytecode, test_config), Ok(())); + assert_eq!(test_circuits_using_bytecode(bytecode, test_config), Ok(())); } #[test] diff --git a/zkevm-circuits/src/evm_circuit/execution/memory.rs b/zkevm-circuits/src/evm_circuit/execution/memory.rs index eb6a3dc74c..db831f909d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/memory.rs @@ -196,7 +196,7 @@ impl ExecutionGadget for MemoryGadget { mod test { use crate::{ evm_circuit::test::rand_word, - test_util::{run_test_circuits_with_config, BytecodeTestConfig}, + test_util::{test_circuits_using_bytecode, BytecodeTestConfig}, }; use eth_types::bytecode; use eth_types::evm_types::{GasCost, OpcodeId}; @@ -222,7 +222,7 @@ mod test { enable_state_circuit_test: false, ..Default::default() }; - assert_eq!(run_test_circuits_with_config(bytecode, test_config), Ok(())); + assert_eq!(test_circuits_using_bytecode(bytecode, test_config), Ok(())); } #[test] diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 704289981c..c4f6e03930 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -129,15 +129,15 @@ pub enum BlockContextFieldTag { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum RwTableTag { - TxAccessListAccount = 1, + Stack = 2, + Memory, + AccountStorage, + TxAccessListAccount, TxAccessListAccountStorage, TxRefund, Account, - AccountStorage, AccountDestructed, CallContext, - Stack, - Memory, } impl RwTableTag { diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs index 0fbf035a55..b7fd6acfed 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs @@ -783,3 +783,25 @@ impl MinMaxGadget { }) } } + +// This function generates a Lagrange polynomial in the range [start, end) which +// will be evaluated to 1 when `exp == value`, otherwise 0 +pub(crate) fn generate_lagrange_base_polynomial< + F: FieldExt, + Exp: Expr, + R: Iterator, +>( + exp: Exp, + val: usize, + range: R, +) -> Expression { + let mut numerator = 1u64.expr(); + let mut denominator = F::from(1); + for x in range { + if x != val { + numerator = numerator * (exp.expr() - x.expr()); + denominator *= F::from(val as u64) - F::from(x as u64); + } + } + numerator * denominator.invert().unwrap() +} diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index c299b27802..4113162898 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -16,7 +16,7 @@ use pairing::bn256::Fr as Fp; use sha3::{Digest, Keccak256}; use std::{collections::HashMap, convert::TryInto}; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Block { /// The randomness for random linear combination pub randomness: F, @@ -30,7 +30,7 @@ pub struct Block { pub context: BlockContext, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct BlockContext { /// The address of the miner for the block pub coinbase: Address, @@ -111,7 +111,7 @@ impl BlockContext { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Transaction { /// The transaction identifier in the block pub id: usize, @@ -223,7 +223,7 @@ impl Transaction { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum CodeSource { Account(Word), } @@ -234,7 +234,7 @@ impl Default for CodeSource { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Call { /// The unique identifier of call in the whole proof, using the /// `rw_counter` at the call step. @@ -324,7 +324,7 @@ impl ExecStep { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Bytecode { pub hash: Word, pub bytes: Vec, @@ -389,7 +389,7 @@ impl Bytecode { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct RwMap(pub HashMap>); impl std::ops::Index<(RwTableTag, usize)> for RwMap { @@ -400,6 +400,48 @@ impl std::ops::Index<(RwTableTag, usize)> for RwMap { } } +impl RwMap { + /// These "sorted_xx" methods are used in state circuit + pub fn sorted_memory_rw(&self) -> Vec { + let mut sorted = self.0[&RwTableTag::Memory].clone(); + sorted.sort_by_key(|x| match x { + Rw::Memory { + call_id, + memory_address, + .. + } => (*call_id, *memory_address), + _ => panic!("invalid memory rw"), + }); + sorted + } + + pub fn sorted_stack_rw(&self) -> Vec { + let mut sorted = self.0[&RwTableTag::Stack].clone(); + sorted.sort_by_key(|x| match x { + Rw::Stack { + call_id, + stack_pointer, + .. + } => (*call_id, *stack_pointer), + _ => panic!("invalid stack rw"), + }); + sorted + } + + pub fn sorted_storage_rw(&self) -> Vec { + let mut sorted = self.0[&RwTableTag::AccountStorage].clone(); + sorted.sort_by_key(|x| match x { + Rw::AccountStorage { + account_address, + storage_key, + .. + } => (*account_address, *storage_key), + _ => panic!("invalid storage rw"), + }); + sorted + } +} + #[derive(Clone, Debug)] pub enum Rw { TxAccessListAccount { @@ -472,6 +514,36 @@ pub enum Rw { byte: u8, }, } +#[derive(Default)] +pub struct RwRow { + pub rw_counter: F, + pub is_write: F, + pub tag: F, + pub key2: F, + pub key3: F, + pub key4: F, + pub value: F, + pub value_prev: F, + pub aux1: F, + pub aux2: F, +} + +impl From<[F; 10]> for RwRow { + fn from(row: [F; 10]) -> Self { + Self { + rw_counter: row[0], + is_write: row[1], + tag: row[2], + key2: row[3], + key3: row[4], + key4: row[5], + value: row[6], + value_prev: row[7], + aux1: row[8], + aux2: row[9], + } + } +} impl Rw { pub fn tx_access_list_value_pair(&self) -> (bool, bool) { @@ -516,7 +588,7 @@ impl Rw { } } - pub fn table_assignment(&self, randomness: F) -> [F; 10] { + pub fn table_assignment(&self, randomness: F) -> RwRow { match self { Self::TxAccessListAccount { rw_counter, @@ -536,7 +608,8 @@ impl Rw { F::from(*value_prev as u64), F::zero(), F::zero(), - ], + ] + .into(), Self::TxAccessListAccountStorage { rw_counter, is_write, @@ -559,7 +632,8 @@ impl Rw { F::from(*value_prev as u64), F::zero(), F::zero(), - ], + ] + .into(), Self::Account { rw_counter, is_write, @@ -587,6 +661,7 @@ impl Rw { F::zero(), F::zero(), ] + .into() } Self::CallContext { rw_counter, @@ -614,7 +689,8 @@ impl Rw { F::zero(), F::zero(), F::zero(), - ], + ] + .into(), Self::Stack { rw_counter, is_write, @@ -632,7 +708,8 @@ impl Rw { F::zero(), F::zero(), F::zero(), - ], + ] + .into(), Self::Memory { rw_counter, is_write, @@ -650,7 +727,34 @@ impl Rw { F::zero(), F::zero(), F::zero(), - ], + ] + .into(), + Self::AccountStorage { + rw_counter, + is_write, + account_address, + storage_key, + value, + value_prev, + } => [ + F::from(*rw_counter as u64), + F::from(*is_write as u64), + F::from(RwTableTag::AccountStorage as u64), + account_address.to_scalar().unwrap(), + RandomLinearCombination::random_linear_combine( + storage_key.to_le_bytes(), + randomness, + ), + F::zero(), + RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness), + RandomLinearCombination::random_linear_combine( + value_prev.to_le_bytes(), + randomness, + ), + F::zero(), // TODO: txid + F::zero(), // TODO: committed_value + ] + .into(), _ => unimplemented!(), } } @@ -1014,7 +1118,6 @@ fn tx_convert(tx: &circuit_input_builder::Transaction) -> Transaction { steps: tx.steps().iter().map(step_convert).collect(), } } - pub fn block_convert( block: &circuit_input_builder::Block, code_db: &bus_mapping::state_db::CodeDB, diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index 8fcaf521c3..ebc4969617 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -13,6 +13,7 @@ pub mod bytecode_circuit; pub mod evm_circuit; pub mod gadget; +pub mod rw_table; pub mod state_circuit; #[cfg(test)] pub mod test_util; diff --git a/zkevm-circuits/src/rw_table.rs b/zkevm-circuits/src/rw_table.rs new file mode 100644 index 0000000000..3095f2a7b1 --- /dev/null +++ b/zkevm-circuits/src/rw_table.rs @@ -0,0 +1,79 @@ +#![allow(missing_docs)] +use halo2::{ + arithmetic::FieldExt, + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, VirtualCells}, + poly::Rotation, +}; + +use crate::evm_circuit::{table::LookupTable, witness::RwRow}; + +/// The rw table shared between evm circuit and state circuit +#[derive(Clone, Copy)] +pub struct RwTable { + pub rw_counter: Column, + pub is_write: Column, + pub tag: Column, + pub key2: Column, + pub key3: Column, + pub key4: Column, + pub value: Column, + pub value_prev: Column, + pub aux1: Column, + pub aux2: Column, +} + +impl LookupTable for RwTable { + fn table_exprs(&self, meta: &mut VirtualCells) -> [Expression; 10] { + [ + meta.query_advice(self.rw_counter, Rotation::cur()), + meta.query_advice(self.is_write, Rotation::cur()), + meta.query_advice(self.tag, Rotation::cur()), + meta.query_advice(self.key2, Rotation::cur()), + meta.query_advice(self.key3, Rotation::cur()), + meta.query_advice(self.key4, Rotation::cur()), + meta.query_advice(self.value, Rotation::cur()), + meta.query_advice(self.value_prev, Rotation::cur()), + meta.query_advice(self.aux1, Rotation::cur()), + meta.query_advice(self.aux2, Rotation::cur()), + ] + } +} +impl RwTable { + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + rw_counter: meta.advice_column(), + is_write: meta.advice_column(), + tag: meta.advice_column(), + key2: meta.advice_column(), + key3: meta.advice_column(), + key4: meta.advice_column(), + value: meta.advice_column(), + value_prev: meta.advice_column(), + aux1: meta.advice_column(), + aux2: meta.advice_column(), + } + } + pub fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + row: &RwRow, + ) -> Result<(), Error> { + for (column, value) in [ + (self.rw_counter, row.rw_counter), + (self.is_write, row.is_write), + (self.tag, row.tag), + (self.key2, row.key2), + (self.key3, row.key3), + (self.key4, row.key4), + (self.value, row.value), + (self.value_prev, row.value_prev), + (self.aux1, row.aux1), + (self.aux2, row.aux2), + ] { + region.assign_advice(|| "assign rw row on rw table", column, offset, || Ok(value))?; + } + Ok(()) + } +} diff --git a/zkevm-circuits/src/state_circuit/state.rs b/zkevm-circuits/src/state_circuit/state.rs index 7963311c88..35530d0994 100644 --- a/zkevm-circuits/src/state_circuit/state.rs +++ b/zkevm-circuits/src/state_circuit/state.rs @@ -1,25 +1,25 @@ use crate::{ - evm_circuit::util::RandomLinearCombination, + evm_circuit::{util::math_gadget::generate_lagrange_base_polynomial, witness::RwMap}, gadget::{ is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, monotone::{MonotoneChip, MonotoneConfig}, Variable, }, }; -use bus_mapping::operation::{MemoryOp, Operation, StackOp, StorageOp}; -use eth_types::{ToLittleEndian, ToScalar}; +use bus_mapping::operation::{MemoryOp, Operation, OperationContainer, StackOp, StorageOp}; use halo2::{ circuit::{Layouter, Region, SimpleFloorPlanner}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, poly::Rotation, }; +use crate::evm_circuit::witness::Rw; use pairing::arithmetic::FieldExt; /* Example state table: -| q_target | address | global_counter | value | flag | padding | storage_key | value_prev | +| q_target | address | rw_counter | value | flag | padding | storage_key | value_prev | ------------------------------------------------------------------------------------------- | 1 | 0 | 0 | 0 | 1 | 0 | | | // init row (write value 0) | 2 | 0 | 12 | 12 | 1 | 0 | | | @@ -56,7 +56,7 @@ Example state table: Example bus mapping: // TODO: this is going to change -| target | address | global_counter | value | storage_key | value_prev | flag | +| target | address | rw_counter | value | storage_key | value_prev | flag | ------------------------------------------------------------------------------- | 2 | 0 | 12 | 12 | | | 1 | | 2 | 0 | 24 | 12 | | | 0 | @@ -68,14 +68,20 @@ Example bus mapping: | 3 | 1 | 49 | 32 | | | 0 | */ +const EMPTY_TAG: usize = 0; +const START_TAG: usize = 1; +const MEMORY_TAG: usize = 2; +const STACK_TAG: usize = 3; +const STORAGE_TAG: usize = 4; + /// A mapping derived from witnessed memory operations. /// TODO: The complete version of this mapping will involve storage, stack, /// and opcode details as well. #[derive(Clone, Debug)] pub(crate) struct BusMapping { - global_counter: Variable, - target: Variable, - flag: Variable, + rw_counter: Variable, + target: Variable, + flag: Variable, address: Variable, value: Variable, storage_key: Variable, @@ -85,12 +91,12 @@ pub(crate) struct BusMapping { #[derive(Clone, Debug)] pub struct Config< F: FieldExt, - // When SANITY_CHECK is true, max_address/global_counter/stack_address are + // When SANITY_CHECK is true, max_address/rw_counter/stack_address are // required to be in the range of - // MEMORY_ADDRESS_MAX/GLOBAL_COUNTER_MAX/STACK_ADDRESS_MAX during circuit + // MEMORY_ADDRESS_MAX/RW_COUNTER_MAX/STACK_ADDRESS_MAX during circuit // synthesis const SANITY_CHECK: bool, - const GLOBAL_COUNTER_MAX: usize, + const RW_COUNTER_MAX: usize, const MEMORY_ROWS_MAX: usize, const MEMORY_ADDRESS_MAX: usize, const STACK_ROWS_MAX: usize, @@ -101,14 +107,14 @@ pub struct Config< address: Column, /* used for memory address, stack pointer, and * account address (for storage) */ address_diff_inv: Column, - global_counter: Column, + rw_counter: Column, value: Column, flag: Column, padding: Column, storage_key: Column, storage_key_diff_inv: Column, value_prev: Column, - global_counter_table: Column, + rw_counter_table: Column, memory_address_table_zero: Column, stack_address_table_zero: Column, memory_value_table: Column, @@ -121,7 +127,7 @@ pub struct Config< impl< F: FieldExt, const SANITY_CHECK: bool, - const GLOBAL_COUNTER_MAX: usize, + const RW_COUNTER_MAX: usize, const MEMORY_ROWS_MAX: usize, const MEMORY_ADDRESS_MAX: usize, const STACK_ROWS_MAX: usize, @@ -131,7 +137,7 @@ impl< Config< F, SANITY_CHECK, - GLOBAL_COUNTER_MAX, + RW_COUNTER_MAX, MEMORY_ROWS_MAX, MEMORY_ADDRESS_MAX, STACK_ROWS_MAX, @@ -144,125 +150,67 @@ impl< let q_target = meta.fixed_column(); let address = meta.advice_column(); let address_diff_inv = meta.advice_column(); - let global_counter = meta.advice_column(); + let rw_counter = meta.advice_column(); let value = meta.advice_column(); let flag = meta.advice_column(); let padding = meta.advice_column(); let storage_key = meta.advice_column(); let storage_key_diff_inv = meta.advice_column(); let value_prev = meta.advice_column(); - let global_counter_table = meta.fixed_column(); + let rw_counter_table = meta.fixed_column(); let memory_address_table_zero = meta.fixed_column(); let stack_address_table_zero = meta.fixed_column(); let memory_value_table = meta.fixed_column(); - let one = Expression::Constant(F::one()); - let two = Expression::Constant(F::from(2)); - let three = Expression::Constant(F::from(3)); - let four = Expression::Constant(F::from(4)); + let one = Expression::Constant(F::from(1)); let q_memory_first = |meta: &mut VirtualCells| { - // For first memory row it holds q_target_cur = 1 and q_target_next - // = 2. + // For first memory row it holds q_target_cur = START_TAG and q_target_next + // = MEMORY_TAG. let q_target_cur = meta.query_fixed(q_target, Rotation::cur()); let q_target_next = meta.query_fixed(q_target, Rotation::next()); - // q_target_cur must be 1 - // q_target_next must be 2 - - q_target_cur.clone() - * (two.clone() - q_target_cur.clone()) - * (three.clone() - q_target_cur.clone()) - * (four.clone() - q_target_cur) - * (q_target_next.clone() - one.clone()) - * (three.clone() - q_target_next.clone()) - * (four.clone() - q_target_next) - }; - - let q_memory_first_norm = |meta: &mut VirtualCells| { - let e = q_memory_first(meta); - // q_memory_first is 12 when q_target_cur is 1 and q_target_next is - // 2, we use 1/12 to normalize the value - let inv = F::from(12_u64).invert().unwrap(); - let i = Expression::Constant(inv); - - e * i + generate_lagrange_base_polynomial(q_target_cur, START_TAG, EMPTY_TAG..=STORAGE_TAG) + * generate_lagrange_base_polynomial( + q_target_next, + MEMORY_TAG, + EMPTY_TAG..=STORAGE_TAG, + ) }; let q_memory_not_first = |meta: &mut VirtualCells| { let q_target = meta.query_fixed(q_target, Rotation::cur()); - - q_target.clone() - * (q_target.clone() - one.clone()) - * (three.clone() - q_target.clone()) - * (four.clone() - q_target) - }; - - let q_memory_not_first_norm = |meta: &mut VirtualCells| { - let e = q_memory_not_first(meta); - // q_memory_not_first is 4 when target is 2, we use 1/4 to normalize - // the value - let inv = F::from(4_u64).invert().unwrap(); - let i = Expression::Constant(inv); - - e * i + generate_lagrange_base_polynomial(q_target, MEMORY_TAG, EMPTY_TAG..=STORAGE_TAG) }; let q_stack_first = |meta: &mut VirtualCells| { let q_target_cur = meta.query_fixed(q_target, Rotation::cur()); let q_target_next = meta.query_fixed(q_target, Rotation::next()); - q_target_cur.clone() - * (two.clone() - q_target_cur.clone()) - * (three.clone() - q_target_cur.clone()) - * (four.clone() - q_target_cur) - * (q_target_next.clone() - one.clone()) - * (q_target_next.clone() - two.clone()) - * (four.clone() - q_target_next) - }; - - let q_stack_first_norm = |meta: &mut VirtualCells| { - let e = q_stack_first(meta); - // q_stack_first is 12, we use 1/12 to normalize the value - let inv = F::from(12_u64).invert().unwrap(); - let i = Expression::Constant(inv); - e * i + generate_lagrange_base_polynomial(q_target_cur, START_TAG, EMPTY_TAG..=STORAGE_TAG) + * generate_lagrange_base_polynomial( + q_target_next, + STACK_TAG, + EMPTY_TAG..=STORAGE_TAG, + ) }; let q_stack_not_first = |meta: &mut VirtualCells| { let q_target = meta.query_fixed(q_target, Rotation::cur()); - - q_target.clone() - * (q_target.clone() - one.clone()) - * (q_target.clone() - two.clone()) - * (four.clone() - q_target) + generate_lagrange_base_polynomial(q_target, STACK_TAG, EMPTY_TAG..=STORAGE_TAG) }; - - let q_stack_not_first_norm = |meta: &mut VirtualCells| { - let e = q_stack_not_first(meta); - // q_stack_not_first is 6 when target is 3, we use 1/6 to normalize - // the value - let inv = F::from(6_u64).invert().unwrap(); - let i = Expression::Constant(inv); - - e * i + let q_storage_first = |meta: &mut VirtualCells| { + let q_target_cur = meta.query_fixed(q_target, Rotation::cur()); + let q_target_next = meta.query_fixed(q_target, Rotation::next()); + generate_lagrange_base_polynomial(q_target_cur, START_TAG, EMPTY_TAG..=STORAGE_TAG) + * generate_lagrange_base_polynomial( + q_target_next, + STORAGE_TAG, + EMPTY_TAG..=STORAGE_TAG, + ) }; - let q_storage_not_first = |meta: &mut VirtualCells| { let q_target = meta.query_fixed(q_target, Rotation::cur()); - q_target.clone() - * (q_target.clone() - one.clone()) - * (q_target.clone() - two.clone()) - * (q_target - three.clone()) - }; - - let q_storage_not_first_norm = |meta: &mut VirtualCells| { - let e = q_storage_not_first(meta); - // q_storage_not_first is 24 when target is 4, we use 1/24 to - // normalize the value - let inv = F::from(24_u64).invert().unwrap(); - let i = Expression::Constant(inv); - - e * i + generate_lagrange_base_polynomial(q_target, STORAGE_TAG, EMPTY_TAG..=STORAGE_TAG) }; let address_diff_is_zero = IsZeroChip::configure( @@ -292,7 +240,7 @@ impl< let is_not_padding = one.clone() - padding; // Since q_memory_not_first and q_stack_non_first are // mutually exclusive, q_not_first is binary. - let q_not_first = q_memory_not_first_norm(meta) + q_stack_not_first_norm(meta); + let q_not_first = q_memory_not_first(meta) + q_stack_not_first(meta); q_not_first * is_not_padding }, @@ -304,7 +252,7 @@ impl< // lookup. let padding_monotone = MonotoneChip::::configure( meta, - |meta| q_memory_not_first_norm(meta) + q_stack_not_first_norm(meta), + |meta| q_memory_not_first(meta) + q_stack_not_first(meta), padding, ); @@ -312,27 +260,19 @@ impl< meta.create_gate("First memory row operation", |meta| { let value = meta.query_advice(value, Rotation::cur()); let flag = meta.query_advice(flag, Rotation::cur()); - let global_counter = meta.query_advice(global_counter, Rotation::cur()); + let q_read = one.clone() - flag; let q_memory_first = q_memory_first(meta); - // - // - values[0] == [0] - // - flags[0] == 1 - // - global_counters[0] == 0 - - vec![ - q_memory_first.clone() * value, - q_memory_first.clone() * (one.clone() - flag), - q_memory_first * global_counter, - ] + // read value must be 0 + vec![q_memory_first * q_read * value] }); meta.create_gate("Memory operation + padding", |meta| { - // If address_cur != address_prev, this is an `init`. We must - // constrain: - // - values[0] == [0] - // - flags[0] == 1 - // - global_counters[0] == 0 + // if is_read: + // if address_cur == address_prev: + // value == prev_value + // else: + // value == 0 let q_memory_not_first = q_memory_not_first(meta); let address_diff = { let address_prev = meta.query_advice(address, Rotation::prev()); @@ -342,14 +282,12 @@ impl< let value_cur = meta.query_advice(value, Rotation::cur()); let flag = meta.query_advice(flag, Rotation::cur()); - let global_counter = - meta.query_advice(global_counter, Rotation::cur()); // flag == 0 or 1 // (flag) * (1 - flag) let bool_check_flag = flag.clone() * (one.clone() - flag.clone()); - // If flag == 0 (read), and global_counter != 0, value_prev == + // If flag == 0 (read), and rw_counter != 0, value_prev == // value_cur let value_prev = meta.query_advice(value, Rotation::prev()); let q_read = one.clone() - flag; @@ -359,19 +297,14 @@ impl< let bool_check_padding = padding.clone() * (one.clone() - padding); vec![ - q_memory_not_first.clone() - * address_diff.clone() - * value_cur.clone(), // when address changes, the write value is 0 - q_memory_not_first.clone() - * address_diff.clone() - * q_read.clone(), // when address changes, the flag is 1 (write) - q_memory_not_first.clone() * address_diff * global_counter, // when address changes, global_counter is 0 q_memory_not_first.clone() * bool_check_flag, // flag is either 0 or 1 - q_memory_not_first * q_read * (value_cur - value_prev), // when reading, the value is the same as at the previous op - // Note that this last constraint needs to hold only when address doesn't change, - // but we don't need to check this as the first operation at the address always - // has to be write - that means q_read is 1 only when - // the address and storage key don't change. + // if address changes, read value should be 0 + q_memory_not_first.clone() * address_diff * q_read.clone() * value_cur.clone(), + // or else, read value should be the same as the previous value + q_memory_not_first + * address_diff_is_zero.is_zero_expression.clone() + * q_read + * (value_cur - value_prev), q_target * bool_check_padding, // padding is 0 or 1 ] }); @@ -388,7 +321,7 @@ impl< // (flag) * (1 - flag) let bool_check_flag = flag.clone() * (one.clone() - flag.clone()); - // If flag == 0 (read), and global_counter != 0, value_prev == value_cur + // If flag == 0 (read), and rw_counter != 0, value_prev == value_cur let value_prev = meta.query_advice(value, Rotation::prev()); let q_read = one.clone() - flag; // when addresses changes, we don't require the operation is write as this is @@ -402,33 +335,31 @@ impl< ] }); - // global_counter monotonicity is checked for memory and stack when + // rw_counter monotonicity is checked for memory and stack when // address_cur == address_prev. (Recall that operations are - // ordered first by address, and then by global_counter.) + // ordered first by address, and then by rw_counter.) meta.lookup_any(|meta| { - let global_counter_table = - meta.query_fixed(global_counter_table, Rotation::cur()); - let global_counter_prev = - meta.query_advice(global_counter, Rotation::prev()); - let global_counter = - meta.query_advice(global_counter, Rotation::cur()); + let rw_counter_table = meta.query_fixed(rw_counter_table, Rotation::cur()); + let rw_counter_prev = meta.query_advice(rw_counter, Rotation::prev()); + let rw_counter = meta.query_advice(rw_counter, Rotation::cur()); let padding = meta.query_advice(padding, Rotation::cur()); let is_not_padding = one.clone() - padding; - let q_not_first = - q_memory_not_first_norm(meta) + q_stack_not_first_norm(meta); + let q_not_first = q_memory_not_first(meta) + q_stack_not_first(meta); vec![( q_not_first * is_not_padding * address_diff_is_zero.clone().is_zero_expression - * (global_counter - global_counter_prev - one.clone()), // - 1 because it needs to be strictly monotone - global_counter_table, + * (rw_counter - rw_counter_prev - one.clone()), /* + * - 1 because it needs to + * be strictly monotone */ + rw_counter_table, )] }); // Memory address is in the allowed range. meta.lookup_any(|meta| { - let q_memory = q_memory_first_norm(meta) + q_memory_not_first_norm(meta); + let q_memory = q_memory_first(meta) + q_memory_not_first(meta); let address_cur = meta.query_advice(address, Rotation::cur()); let memory_address_table_zero = meta.query_fixed(memory_address_table_zero, Rotation::cur()); @@ -438,7 +369,7 @@ impl< // Stack address is in the allowed range. meta.lookup_any(|meta| { - let q_stack = q_stack_first_norm(meta) + q_stack_not_first_norm(meta); + let q_stack = q_stack_first(meta) + q_stack_not_first(meta); let address_cur = meta.query_advice(address, Rotation::cur()); let stack_address_table_zero = meta.query_fixed(stack_address_table_zero, Rotation::cur()); @@ -446,19 +377,19 @@ impl< vec![(q_stack * address_cur, stack_address_table_zero)] }); - // global_counter is in the allowed range: + // rw_counter is in the allowed range: meta.lookup_any(|meta| { - let global_counter = meta.query_advice(global_counter, Rotation::cur()); - let global_counter_table = meta.query_fixed(global_counter_table, Rotation::cur()); + let rw_counter = meta.query_advice(rw_counter, Rotation::cur()); + let rw_counter_table = meta.query_fixed(rw_counter_table, Rotation::cur()); - vec![(global_counter, global_counter_table)] + vec![(rw_counter, rw_counter_table)] }); // Memory value (for non-first rows) is in the allowed range. // Memory first row value doesn't need to be checked - it is checked // above where memory init row value has to be 0. meta.lookup_any(|meta| { - let q_memory_not_first = q_memory_not_first_norm(meta); + let q_memory_not_first = q_memory_not_first(meta); let value = meta.query_advice(value, Rotation::cur()); let memory_value_table = meta.query_fixed(memory_value_table, Rotation::cur()); @@ -485,15 +416,7 @@ impl< ); meta.create_gate("First storage row operation", |meta| { - let q_target_cur = meta.query_fixed(q_target, Rotation::cur()); - let q_target_next = meta.query_fixed(q_target, Rotation::next()); - let q_storage_first = q_target_cur.clone() - * (two.clone() - q_target_cur.clone()) - * (three.clone() - q_target_cur.clone()) - * (four.clone() - q_target_cur) - * (q_target_next.clone() - one.clone()) - * (q_target_next.clone() - two.clone()) - * (q_target_next - three.clone()); + let q_storage_first = q_storage_first(meta); let flag = meta.query_advice(flag, Rotation::cur()); let q_read = one.clone() - flag; @@ -530,7 +453,7 @@ impl< // (flag) * (1 - flag) let bool_check_flag = flag.clone() * (one.clone() - flag.clone()); - // If flag == 0 (read), and global_counter != 0, value_prev == value_cur + // If flag == 0 (read), and rw_counter != 0, value_prev == value_cur let value_previous = meta.query_advice(value, Rotation::prev()); let q_read = one.clone() - flag.clone(); @@ -563,29 +486,28 @@ impl< ] }); - // global_counter monotonicity is checked for storage when address_cur + // rw_counter monotonicity is checked for storage when address_cur // == address_prev and storage_key_cur = storage_key_prev. // (Recall that storage operations are ordered first by account address, - // then by storage_key, and finally by global_counter.) + // then by storage_key, and finally by rw_counter.) meta.lookup_any(|meta| { - let global_counter_table = - meta.query_fixed(global_counter_table, Rotation::cur()); - let global_counter_prev = - meta.query_advice(global_counter, Rotation::prev()); - let global_counter = - meta.query_advice(global_counter, Rotation::cur()); + let rw_counter_table = meta.query_fixed(rw_counter_table, Rotation::cur()); + let rw_counter_prev = meta.query_advice(rw_counter, Rotation::prev()); + let rw_counter = meta.query_advice(rw_counter, Rotation::cur()); let padding = meta.query_advice(padding, Rotation::cur()); let is_not_padding = one.clone() - padding; - let q_storage_not_first = q_storage_not_first_norm(meta); + let q_storage_not_first = q_storage_not_first(meta); vec![( q_storage_not_first * is_not_padding * address_diff_is_zero.clone().is_zero_expression * storage_key_diff_is_zero.clone().is_zero_expression - * (global_counter - global_counter_prev - one.clone()), // - 1 because it needs to be strictly monotone - global_counter_table, + * (rw_counter - rw_counter_prev - one.clone()), /* + * - 1 because it needs to + * be strictly monotone */ + rw_counter_table, )] }); @@ -595,14 +517,14 @@ impl< q_target, address, address_diff_inv, - global_counter, + rw_counter, value, flag, padding, storage_key, storage_key_diff_inv, value_prev, - global_counter_table, + rw_counter_table, memory_address_table_zero, stack_address_table_zero, memory_value_table, @@ -619,10 +541,10 @@ impl< .assign_region( || "global counter table", |mut region| { - for idx in 0..=GLOBAL_COUNTER_MAX { + for idx in 0..=RW_COUNTER_MAX { region.assign_fixed( || "global counter table", - self.global_counter_table, + self.rw_counter_table, idx, || Ok(F::from(idx as u64)), )?; @@ -685,74 +607,63 @@ impl< fn assign_memory_ops( &self, region: &mut Region, - _randomness: F, - ops: Vec>, + randomness: F, + ops: Vec, address_diff_is_zero_chip: &IsZeroChip, + offset: usize, ) -> Result>, Error> { - let mut init_rows_num = 0; + let mut bus_mappings: Vec> = Vec::new(); + + let mut offset = offset; + let offset_limit = offset + MEMORY_ROWS_MAX; + for (index, oper) in ops.iter().enumerate() { - let op = oper.op(); - if index > 0 { - if op.address() != ops[index - 1].op().address() { - init_rows_num += 1; - } - } else { - init_rows_num += 1; + if !matches!(oper, Rw::Memory { .. }) { + panic!("expect memory operation"); } - } - - if ops.len() + init_rows_num > MEMORY_ROWS_MAX { - panic!("too many memory operations"); - } + let row = oper.table_assignment(randomness); - let mut bus_mappings: Vec> = Vec::new(); + let address = row.key3; + let address_prev = if index > 0 { + ops[index - 1].table_assignment(randomness).key3 + } else { + F::zero() + }; - let mut address_prev = F::zero(); - let mut offset = 0; - for (index, oper) in ops.iter().enumerate() { - let op = oper.op(); - let address = F::from_bytes(&op.address().to_le_bytes()).unwrap(); if SANITY_CHECK && address > F::from(MEMORY_ADDRESS_MAX as u64) { panic!( "memory address out of range {:?} > {}", address, MEMORY_ADDRESS_MAX ); } - let rwc = usize::from(oper.rwc()); - // value of memory op is of type u8, so random_linear_combine is not - // needed here. - let val = F::from(op.value() as u64); - let mut target = 1; - if index > 0 { - target = 2; - } - // memory ops have init row - if index == 0 || address != address_prev { - self.init(region, offset, address, target)?; - address_diff_is_zero_chip.assign(region, offset, Some(address - address_prev))?; - target = 2; - offset += 1; + let target = if index == 0 { START_TAG } else { MEMORY_TAG }; + if offset >= offset_limit { + panic!("too many memory operations {} > {}", offset, offset_limit); } - let bus_mapping = self.assign_op( region, offset, address, - rwc, - val, - oper.rw().is_write(), - target, + row.rw_counter, + row.value, + row.is_write, + F::from(target as u64), F::zero(), F::zero(), )?; bus_mappings.push(bus_mapping); - address_prev = address; + address_diff_is_zero_chip.assign(region, offset, Some(address - address_prev))?; offset += 1; } - - self.pad_rows(region, offset, 0, MEMORY_ROWS_MAX, 2)?; + self.pad_rows( + region, + ops.is_empty(), + offset, + offset_limit, + MEMORY_TAG as usize, + )?; Ok(bus_mappings) } @@ -761,40 +672,50 @@ impl< &self, region: &mut Region, randomness: F, - ops: Vec>, + ops: Vec, address_diff_is_zero_chip: &IsZeroChip, + offset: usize, ) -> Result>, Error> { if ops.len() > STACK_ROWS_MAX { panic!("too many stack operations"); } let mut bus_mappings: Vec> = Vec::new(); - let mut address_prev = F::zero(); - let mut offset = MEMORY_ROWS_MAX; + let mut offset = offset; + let offset_limit = offset + STACK_ROWS_MAX; for (index, oper) in ops.iter().enumerate() { - let op = oper.op(); - if SANITY_CHECK && usize::from(*op.address()) > STACK_ADDRESS_MAX { - panic!("stack address out of range"); + if !matches!(oper, Rw::Stack { .. }) { + panic!("expect stack operation"); } - let address = F::from(usize::from(*op.address()) as u64); - let rwc = usize::from(oper.rwc()); - let val = RandomLinearCombination::random_linear_combine( - op.value().to_le_bytes(), - randomness, - ); - let mut target = 1; - if index > 0 { - target = 3; + let row = oper.table_assignment(randomness); + let address = row.key3; + let address_prev = if index > 0 { + ops[index - 1].table_assignment(randomness).key3 + } else { + F::zero() + }; + + if SANITY_CHECK && address > F::from(STACK_ADDRESS_MAX as u64) { + panic!( + "stack address out of range {:?} > {}", + address, STACK_ADDRESS_MAX as u64 + ); } + let target = if index > 0 { + STACK_TAG // 3 + } else { + START_TAG // 1 + }; + let bus_mapping = self.assign_op( region, offset, address, - rwc, - val, - oper.rw().is_write(), - target, + row.rw_counter, + row.value, + row.is_write, + F::from(target as u64), F::zero(), F::zero(), )?; @@ -802,11 +723,16 @@ impl< address_diff_is_zero_chip.assign(region, offset, Some(address - address_prev))?; - address_prev = address; offset += 1; } - self.pad_rows(region, offset, MEMORY_ROWS_MAX, STACK_ROWS_MAX, 3)?; + self.pad_rows( + region, + ops.is_empty(), + offset, + offset_limit, + STACK_TAG as usize, + )?; Ok(bus_mappings) } @@ -815,52 +741,45 @@ impl< &self, region: &mut Region, randomness: F, - ops: Vec>, + ops: Vec, address_diff_is_zero_chip: &IsZeroChip, storage_key_diff_is_zero_chip: &IsZeroChip, + offset: usize, ) -> Result>, Error> { if ops.len() > STORAGE_ROWS_MAX { panic!("too many storage operations"); } let mut bus_mappings: Vec> = Vec::new(); - let mut address_prev = F::zero(); - let mut storage_key_prev = F::zero(); - let mut offset = MEMORY_ROWS_MAX + STACK_ROWS_MAX; + let mut offset = offset; + let offset_limit = offset + STORAGE_ROWS_MAX; for (index, oper) in ops.iter().enumerate() { - let op = oper.op(); - let rwc = usize::from(oper.rwc()); - - // address in 160bits, so it can be put into F. - // random_linear_combine is not needed here. - let address = op.address().to_scalar().unwrap(); + if !matches!(oper, Rw::AccountStorage { .. }) { + panic!("expect stack operation"); + } - let val = RandomLinearCombination::random_linear_combine( - op.value().to_le_bytes(), - randomness, - ); - let val_prev = RandomLinearCombination::random_linear_combine( - op.value_prev().to_le_bytes(), - randomness, - ); - let storage_key = - RandomLinearCombination::random_linear_combine(op.key().to_le_bytes(), randomness); + let row = oper.table_assignment(randomness); - let mut target = 1; - if index > 0 { - target = 4; - } + let target = if index > 0 { STORAGE_TAG } else { START_TAG }; + let address = row.key2; + let storage_key = row.key3; + let (address_prev, storage_key_prev) = if index > 0 { + let prev_row = ops[index - 1].table_assignment(randomness); + (prev_row.key2, prev_row.key3) + } else { + (F::zero(), F::zero()) + }; let bus_mapping = self.assign_op( region, offset, - address, - rwc, - val, - oper.rw().is_write(), - target, - storage_key, - val_prev, + row.key2, + row.rw_counter, + row.value, + row.is_write, + F::from(target as u64), + row.key3, + row.value_prev, )?; bus_mappings.push(bus_mapping); @@ -872,17 +791,15 @@ impl< Some(storage_key - storage_key_prev), )?; - address_prev = address; - storage_key_prev = storage_key; offset += 1; } self.pad_rows( region, + ops.is_empty(), offset, - MEMORY_ROWS_MAX + STACK_ROWS_MAX, - STORAGE_ROWS_MAX, - 4, + offset_limit, + STORAGE_TAG as usize, )?; Ok(bus_mappings) @@ -891,26 +808,22 @@ impl< fn pad_rows( &self, region: &mut Region, - offset: usize, + need_pad_start_row: bool, start_offset: usize, - max_rows: usize, + end_offset: usize, target: usize, ) -> Result<(), Error> { // We pad all remaining rows to avoid the check at the first unused row. // Without padding, (address_cur - address_prev) would not be zero at // the first unused row and some checks would be triggered. - for i in offset..start_offset + max_rows { - if i == start_offset { - region.assign_fixed(|| "target", self.q_target, i, || Ok(F::one()))?; + for i in start_offset..end_offset { + let target = if need_pad_start_row && i == start_offset { + START_TAG } else { - region.assign_fixed( - || "target", - self.q_target, - i, - || Ok(F::from(target as u64)), - )?; - } + target + }; + region.assign_fixed(|| "target", self.q_target, i, || Ok(F::from(target as u64)))?; region.assign_advice(|| "padding", self.padding, i, || Ok(F::one()))?; region.assign_advice(|| "memory", self.flag, i, || Ok(F::one()))?; } @@ -923,9 +836,9 @@ impl< &self, mut layouter: impl Layouter, randomness: F, - memory_ops: Vec>, - stack_ops: Vec>, - storage_ops: Vec>, + memory_ops: Vec, + stack_ops: Vec, + storage_ops: Vec, ) -> Result>, Error> { let mut bus_mappings: Vec> = Vec::new(); @@ -947,21 +860,27 @@ impl< layouter.assign_region( || "State operations", |mut region| { + let mut offset = 0; + let memory_mappings = self.assign_memory_ops( &mut region, randomness, memory_ops.clone(), &address_diff_is_zero_chip, + offset, ); bus_mappings.extend(memory_mappings.unwrap()); + offset += MEMORY_ROWS_MAX; let stack_mappings = self.assign_stack_ops( &mut region, randomness, stack_ops.clone(), &address_diff_is_zero_chip, + offset, ); bus_mappings.extend(stack_mappings.unwrap()); + offset += STACK_ROWS_MAX; let storage_mappings = self.assign_storage_ops( &mut region, @@ -969,6 +888,7 @@ impl< storage_ops.clone(), &address_diff_is_zero_chip, &storage_key_diff_is_zero_chip, + offset, ); bus_mappings.extend(storage_mappings.unwrap()); @@ -977,47 +897,16 @@ impl< ) } - /// Initialise first row for a new operation. - fn init( - &self, - region: &mut Region<'_, F>, - offset: usize, - address: F, - target: usize, - ) -> Result<(), Error> { - region.assign_advice(|| "init address", self.address, offset, || Ok(address))?; - - region.assign_advice( - || "init global counter", - self.global_counter, - offset, - || Ok(F::zero()), - )?; - - region.assign_advice(|| "init value", self.value, offset, || Ok(F::zero()))?; - - region.assign_advice(|| "init memory", self.flag, offset, || Ok(F::one()))?; - - region.assign_fixed( - || "target", - self.q_target, - offset, - || Ok(F::from(target as u64)), - )?; - - Ok(()) - } - #[allow(clippy::too_many_arguments)] fn assign_op( &self, region: &mut Region<'_, F>, offset: usize, address: F, - global_counter: usize, + rw_counter: F, value: F, - flag: bool, - target: usize, + flag: F, + target: F, storage_key: F, value_prev: F, ) -> Result, Error> { @@ -1030,23 +919,21 @@ impl< } }; - if SANITY_CHECK && global_counter > GLOBAL_COUNTER_MAX { - panic!("global_counter out of range"); + if SANITY_CHECK && rw_counter > F::from(RW_COUNTER_MAX as u64) { + panic!("rw_counter out of range"); } - let global_counter = { - let field_elem = F::from(global_counter as u64); - + let rw_counter = { let cell = region.assign_advice( || "global counter", - self.global_counter, + self.rw_counter, offset, - || Ok(field_elem), + || Ok(rw_counter), )?; - Variable:: { + Variable:: { cell, - field_elem: Some(field_elem), - value: Some(global_counter), + field_elem: Some(rw_counter), + value: Some(rw_counter), } }; @@ -1091,34 +978,26 @@ impl< }; let flag = { - let field_elem = F::from(flag as u64); - let cell = region.assign_advice(|| "flag", self.flag, offset, || Ok(field_elem))?; + let cell = region.assign_advice(|| "flag", self.flag, offset, || Ok(flag))?; - Variable:: { + Variable:: { cell, - field_elem: Some(field_elem), + field_elem: Some(flag), value: Some(flag), } }; let target = { - let value = Some(target); - let field_elem = Some(F::from(target as u64)); - let cell = region.assign_fixed( - || "target", - self.q_target, - offset, - || Ok(F::from(target as u64)), - )?; - Variable:: { + let cell = region.assign_fixed(|| "target", self.q_target, offset, || Ok(target))?; + Variable:: { cell, - field_elem, - value, + field_elem: Some(target), + value: Some(target), } }; Ok(BusMapping { - global_counter, + rw_counter, target, flag, address, @@ -1134,7 +1013,7 @@ impl< pub struct StateCircuit< F: FieldExt, const SANITY_CHECK: bool, - const GLOBAL_COUNTER_MAX: usize, + const RW_COUNTER_MAX: usize, const MEMORY_ROWS_MAX: usize, const MEMORY_ADDRESS_MAX: usize, const STACK_ROWS_MAX: usize, @@ -1144,17 +1023,17 @@ pub struct StateCircuit< /// randomness used in linear combination pub randomness: F, /// Memory Operations - pub memory_ops: Vec>, + pub memory_ops: Vec, /// Stack Operations - pub stack_ops: Vec>, + pub stack_ops: Vec, /// Storage Operations - pub storage_ops: Vec>, + pub storage_ops: Vec, } impl< F: FieldExt, const SANITY_CHECK: bool, - const GLOBAL_COUNTER_MAX: usize, + const RW_COUNTER_MAX: usize, const MEMORY_ROWS_MAX: usize, const MEMORY_ADDRESS_MAX: usize, const STACK_ROWS_MAX: usize, @@ -1164,7 +1043,7 @@ impl< StateCircuit< F, SANITY_CHECK, - GLOBAL_COUNTER_MAX, + RW_COUNTER_MAX, MEMORY_ROWS_MAX, MEMORY_ADDRESS_MAX, STACK_ROWS_MAX, @@ -1172,26 +1051,37 @@ impl< STORAGE_ROWS_MAX, > { + /// Use rw_map to build a StateCircuit instance + pub fn new_from_rw_map(randomness: F, rw_map: &RwMap) -> Self { + Self { + randomness, + memory_ops: rw_map.sorted_memory_rw(), + stack_ops: rw_map.sorted_stack_rw(), + storage_ops: rw_map.sorted_storage_rw(), + } + } /// Use memory_ops, stack_ops, storage_ops to build a StateCircuit instance. + /// This method should be replaced with `new_from_rw_map` later. pub fn new( randomness: F, memory_ops: Vec>, stack_ops: Vec>, storage_ops: Vec>, ) -> Self { - Self { - randomness, - memory_ops, - stack_ops, - storage_ops, - } + let rw_map = RwMap::from(&OperationContainer { + memory: memory_ops, + stack: stack_ops, + storage: storage_ops, + ..Default::default() + }); + Self::new_from_rw_map(randomness, &rw_map) } } impl< F: FieldExt, const SANITY_CHECK: bool, - const GLOBAL_COUNTER_MAX: usize, + const RW_COUNTER_MAX: usize, const MEMORY_ROWS_MAX: usize, const MEMORY_ADDRESS_MAX: usize, const STACK_ROWS_MAX: usize, @@ -1201,7 +1091,7 @@ impl< for StateCircuit< F, SANITY_CHECK, - GLOBAL_COUNTER_MAX, + RW_COUNTER_MAX, MEMORY_ROWS_MAX, MEMORY_ADDRESS_MAX, STACK_ROWS_MAX, @@ -1212,7 +1102,7 @@ impl< type Config = Config< F, SANITY_CHECK, - GLOBAL_COUNTER_MAX, + RW_COUNTER_MAX, MEMORY_ROWS_MAX, MEMORY_ADDRESS_MAX, STACK_ROWS_MAX, @@ -1256,46 +1146,37 @@ mod tests { use halo2::dev::{MockProver, VerifyFailure::ConstraintNotSatisfied, VerifyFailure::Lookup}; use pairing::bn256::Fr; - macro_rules! test_state_circuit { - ($k:expr, $global_counter_max:expr, $memory_rows_max:expr, $memory_address_max:expr, $stack_rows_max:expr, $stack_address_max:expr, $storage_rows_max:expr, $memory_ops:expr, $stack_ops:expr, $storage_ops:expr, $result:expr) => {{ + macro_rules! test_state_circuit_ok { + ($k:expr, $rw_counter_max:expr, $memory_rows_max:expr, $memory_address_max:expr, $stack_rows_max:expr, $stack_address_max:expr, $storage_rows_max:expr, $memory_ops:expr, $stack_ops:expr, $storage_ops:expr, $result:expr) => {{ let circuit = StateCircuit::< Fr, true, - $global_counter_max, + $rw_counter_max, $memory_rows_max, $memory_address_max, $stack_rows_max, $stack_address_max, $storage_rows_max, - > { - randomness: Fr::rand(), - memory_ops: $memory_ops, - stack_ops: $stack_ops, - storage_ops: $storage_ops, - }; + >::new(Fr::rand(), $memory_ops, $stack_ops, $storage_ops); let prover = MockProver::::run($k, &circuit, vec![]).unwrap(); - assert_eq!(prover.verify(), $result); + let verify_result = prover.verify(); + assert!(verify_result.is_ok(), "verify err: {:#?}", verify_result); }}; } macro_rules! test_state_circuit_error { - ($k:expr, $global_counter_max:expr, $memory_rows_max:expr, $memory_address_max:expr, $stack_rows_max:expr, $stack_address_max:expr, $storage_rows_max:expr, $memory_ops:expr, $stack_ops:expr, $storage_ops:expr) => {{ + ($k:expr, $rw_counter_max:expr, $memory_rows_max:expr, $memory_address_max:expr, $stack_rows_max:expr, $stack_address_max:expr, $storage_rows_max:expr, $memory_ops:expr, $stack_ops:expr, $storage_ops:expr) => {{ let circuit = StateCircuit::< Fr, false, - $global_counter_max, + $rw_counter_max, $memory_rows_max, $memory_address_max, $stack_rows_max, $stack_address_max, $storage_rows_max, - > { - randomness: Fr::rand(), - memory_ops: $memory_ops, - stack_ops: $stack_ops, - storage_ops: $storage_ops, - }; + >::new(Fr::rand(), $memory_ops, $stack_ops, $storage_ops); let prover = MockProver::::run($k, &circuit, vec![]).unwrap(); assert!(prover.verify().is_err()); @@ -1385,7 +1266,7 @@ mod tests { ), ); - test_state_circuit!( + test_state_circuit_ok!( 14, 2000, 100, @@ -1436,7 +1317,7 @@ mod tests { ); const STACK_ROWS_MAX: usize = 2; - test_state_circuit!( + test_state_circuit_ok!( 14, 2000, 100, @@ -1574,12 +1455,12 @@ mod tests { MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), ); let memory_op_1 = Operation::new( - RWCounter::from(GLOBAL_COUNTER_MAX), + RWCounter::from(RW_COUNTER_MAX), RW::READ, MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), ); let memory_op_2 = Operation::new( - RWCounter::from(GLOBAL_COUNTER_MAX + 1), + RWCounter::from(RW_COUNTER_MAX + 1), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), ); @@ -1612,7 +1493,7 @@ mod tests { StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), ); let stack_op_3 = Operation::new( - RWCounter::from(GLOBAL_COUNTER_MAX + 1), + RWCounter::from(RW_COUNTER_MAX + 1), RW::WRITE, StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), ); @@ -1623,13 +1504,13 @@ mod tests { const MEMORY_ROWS_MAX: usize = 7; const STACK_ROWS_MAX: usize = 7; const STORAGE_ROWS_MAX: usize = 7; - const GLOBAL_COUNTER_MAX: usize = 60000; + const RW_COUNTER_MAX: usize = 60000; const MEMORY_ADDRESS_MAX: usize = 100; const STACK_ADDRESS_MAX: usize = 1023; test_state_circuit_error!( 16, - GLOBAL_COUNTER_MAX, + RW_COUNTER_MAX, MEMORY_ROWS_MAX, MEMORY_ADDRESS_MAX, STACK_ROWS_MAX, @@ -1679,13 +1560,13 @@ mod tests { const MEMORY_ROWS_MAX: usize = 2; const STACK_ROWS_MAX: usize = 2; const STORAGE_ROWS_MAX: usize = 2; - const GLOBAL_COUNTER_MAX: usize = 60000; + const RW_COUNTER_MAX: usize = 60000; const MEMORY_ADDRESS_MAX: usize = 100; const STACK_ADDRESS_MAX: usize = 1023; test_state_circuit_error!( 16, - GLOBAL_COUNTER_MAX, + RW_COUNTER_MAX, MEMORY_ROWS_MAX, MEMORY_ADDRESS_MAX, STACK_ROWS_MAX, @@ -1698,7 +1579,7 @@ mod tests { } #[test] - fn non_monotone_global_counter() { + fn non_monotone_rw_counter() { let memory_op_0 = Operation::new( RWCounter::from(1352), RW::WRITE, @@ -1950,24 +1831,26 @@ mod tests { STOP }; let block = bus_mapping::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&bytecode).unwrap(), + mock::new_single_tx_trace_code(&bytecode).unwrap(), ); let mut builder = block.new_circuit_input_builder(); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let stack_ops = builder.block.container.sorted_stack(); + let memory_ops = builder.block.container.sorted_memory(); + let storage_ops = builder.block.container.sorted_storage(); - test_state_circuit!( + test_state_circuit_ok!( 14, 2000, 100, - 2, + 0x80, 100, 1023, 1000, - vec![], + memory_ops, stack_ops, - vec![], + storage_ops, Ok(()) ); } diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index 702b3d1cf7..7c6d887314 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -1,4 +1,7 @@ -use crate::{evm_circuit::table::FixedTableTag, state_circuit::StateCircuit}; +use crate::{ + evm_circuit::{table::FixedTableTag, witness::Block}, + state_circuit::StateCircuit, +}; use eth_types::evm_types::Gas; use halo2::dev::{MockProver, VerifyFailure}; use pairing::bn256::Fr; @@ -43,14 +46,14 @@ impl Default for BytecodeTestConfig { } pub fn run_test_circuits(bytecode: eth_types::Bytecode) -> Result<(), Vec> { - run_test_circuits_with_config(bytecode, BytecodeTestConfig::default()) + test_circuits_using_bytecode(bytecode, BytecodeTestConfig::default()) } -pub fn run_test_circuits_with_config( +pub fn test_circuits_using_bytecode( bytecode: eth_types::Bytecode, config: BytecodeTestConfig, ) -> Result<(), Vec> { - // Step 1: execute the bytecode and get trace + // execute the bytecode and get trace let block_trace = bus_mapping::mock::BlockData::new_from_geth_data( mock::new_single_tx_trace_code_gas(&bytecode, Gas(config.gas_limit)).unwrap(), ); @@ -59,29 +62,33 @@ pub fn run_test_circuits_with_config( .handle_tx(&block_trace.eth_tx, &block_trace.geth_trace) .unwrap(); + // build a witness block from trace result let block = crate::evm_circuit::witness::block_convert(&builder.block, &builder.code_db); - let randomness = block.randomness; + // finish required tests according to config using this witness block + test_circuits_using_witness_block(block, config) +} - // Step 2: run evm circuit test +pub fn test_circuits_using_witness_block( + block: Block, + config: BytecodeTestConfig, +) -> Result<(), Vec> { + // run evm circuit test if config.enable_evm_circuit_test { - crate::evm_circuit::test::run_test_circuit(block, config.evm_circuit_lookup_tags)?; + crate::evm_circuit::test::run_test_circuit(block.clone(), config.evm_circuit_lookup_tags)?; } - // Step 3: run state circuit test + // run state circuit test // TODO: // (1) calculate circuit size(like MEMORY_ROWS_MAX etc) from block // rather than hard code (2) use randomness as one of the circuit // public input, since randomness in state circuit and evm // circuit must be same if config.enable_state_circuit_test { - let block_for_state_circuit = builder.block; - let state_circuit = StateCircuit:: { - randomness, - memory_ops: block_for_state_circuit.container.sorted_memory(), - stack_ops: block_for_state_circuit.container.sorted_stack(), - storage_ops: block_for_state_circuit.container.sorted_storage(), - }; - + let state_circuit = + StateCircuit::::new_from_rw_map( + block.randomness, + &block.rws, + ); let prover = MockProver::::run(12, &state_circuit, vec![]).unwrap(); prover.verify()?; }