diff --git a/Cargo.lock b/Cargo.lock index 174df5affa..81fbfd184f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1785,6 +1785,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2744,6 +2750,7 @@ dependencies = [ "rand_xorshift", "serde", "serde_json", + "strum", "tokio", "zkevm-circuits", ] @@ -3042,6 +3049,12 @@ dependencies = [ "base64 0.13.0", ] +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + [[package]] name = "ryu" version = "1.0.9" @@ -3387,6 +3400,25 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" +[[package]] +name = "strum" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" + +[[package]] +name = "strum_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4096,4 +4128,6 @@ dependencies = [ "rand_xorshift", "serde_json", "sha3 0.7.3", + "strum", + "strum_macros", ] diff --git a/circuit-benchmarks/build.rs b/circuit-benchmarks/build.rs index 146fde0f7e..a33f619657 100644 --- a/circuit-benchmarks/build.rs +++ b/circuit-benchmarks/build.rs @@ -10,23 +10,9 @@ fn main() { .unwrap_or_else(|_| "11".to_string()) .parse() .expect("Cannot parse DEGREE env var as usize"); - let memory_address_max: usize = var("MEMORY_ADDRESS_MAX") - .unwrap_or_else(|_| "2000".to_string()) - .parse() - .expect("Cannot parse MEMORY_ADDRESS_MAX env var as usize"); - let stack_address_max: usize = var("STACK_ADDRESS_MAX") - .unwrap_or_else(|_| "1300".to_string()) - .parse() - .expect("Cannot parse STACK_ADDRESS_MAX env var as usize"); // Add state_circuit module to `lib.rs` - let consts = format!( - "pub(crate) const DEGREE: usize = {}; -pub(crate) const MEMORY_ADDRESS_MAX: usize = {}; -pub(crate) const STACK_ADDRESS_MAX: usize = {}; -", - degree, memory_address_max, stack_address_max - ); + let consts = format!("pub(crate) const DEGREE: usize = {};\n", degree); let mut state_file = File::create("src/bench_params.rs").expect("Error generating bench_params.rs file"); diff --git a/circuit-benchmarks/src/state_circuit.rs b/circuit-benchmarks/src/state_circuit.rs index b26cbd47cb..8c136f361e 100644 --- a/circuit-benchmarks/src/state_circuit.rs +++ b/circuit-benchmarks/src/state_circuit.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod tests { - use crate::bench_params::{DEGREE, MEMORY_ADDRESS_MAX, STACK_ADDRESS_MAX}; + use crate::bench_params::DEGREE; use ark_std::{end_timer, start_timer}; use halo2_proofs::plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, SingleVerifier}; use halo2_proofs::{ @@ -14,20 +14,10 @@ mod tests { use rand_xorshift::XorShiftRng; use zkevm_circuits::state_circuit::StateCircuit; - const RW_COUNTER_MAX: usize = 1 << DEGREE; - const ROWS_MAX: usize = 1 << DEGREE; - #[cfg_attr(not(feature = "benches"), ignore)] #[test] fn bench_state_circuit_prover() { - let empty_circuit = StateCircuit::< - Fr, - true, - RW_COUNTER_MAX, - MEMORY_ADDRESS_MAX, - STACK_ADDRESS_MAX, - ROWS_MAX, - >::default(); + let empty_circuit = StateCircuit::::default(); // Initialize the polynomial commitment parameters let rng = XorShiftRng::from_seed([ diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index 2bf503241f..9c7f17feef 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -41,12 +41,7 @@ async fn test_state_circuit_block(block_num: u64) { let storage_ops = builder.block.container.sorted_storage(); trace!("storage_ops: {:#?}", storage_ops); - const DEGREE: usize = 16; - const MEMORY_ADDRESS_MAX: usize = 2000; - const STACK_ADDRESS_MAX: usize = 1024; - - const RW_COUNTER_MAX: usize = 1 << DEGREE; - const ROWS_MAX: usize = 1 << DEGREE; + const DEGREE: usize = 17; let rw_map = RwMap::from(&OperationContainer { memory: memory_ops, @@ -54,17 +49,13 @@ async fn test_state_circuit_block(block_num: u64) { storage: storage_ops, ..Default::default() }); - let circuit = StateCircuit::< - Fr, - true, - RW_COUNTER_MAX, - MEMORY_ADDRESS_MAX, - STACK_ADDRESS_MAX, - ROWS_MAX, - >::new(Fr::rand(), &rw_map); + + let randomness = Fr::rand(); + let circuit = StateCircuit::::new(randomness, rw_map); + let power_of_randomness = circuit.instance(); use halo2_proofs::pairing::bn256::Fr as Fp; - let prover = MockProver::::run(DEGREE as u32, &circuit, vec![]).unwrap(); + let prover = MockProver::::run(DEGREE as u32, &circuit, power_of_randomness).unwrap(); prover.verify().expect("state_circuit verification failed"); } diff --git a/prover/Cargo.toml b/prover/Cargo.toml index f452d367d7..a7f89aa0c3 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -15,5 +15,6 @@ log = "0.4.14" rand = "0.8.4" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.78" +strum = "0.24" tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread"] } zkevm-circuits = { path = "../zkevm-circuits", features = ["test"] } diff --git a/prover/src/compute_proof.rs b/prover/src/compute_proof.rs index 3d43a86498..3fed746ed8 100644 --- a/prover/src/compute_proof.rs +++ b/prover/src/compute_proof.rs @@ -10,6 +10,7 @@ use halo2_proofs::{ use rand::SeedableRng; use rand_xorshift::XorShiftRng; use std::str::FromStr; +use strum::IntoEnumIterator; use zkevm_circuits::evm_circuit::{ table::FixedTableTag, test::TestCircuit, witness::block_convert, }; @@ -37,7 +38,7 @@ pub async fn compute_proof( let block = block_convert(&builder.block, &builder.code_db); { // generate evm_circuit proof - let circuit = TestCircuit::::new(block.clone(), FixedTableTag::iterator().collect()); + let circuit = TestCircuit::::new(block.clone(), FixedTableTag::iter().collect()); // TODO: can this be pre-generated to a file? // related @@ -60,23 +61,7 @@ pub async fn compute_proof( { // generate state_circuit proof - // - // TODO: this should be configurable - const MEMORY_ADDRESS_MAX: usize = 2000; - const STACK_ADDRESS_MAX: usize = 1300; - const MEMORY_ROWS_MAX: usize = 16384; - const STACK_ROWS_MAX: usize = 16384; - const STORAGE_ROWS_MAX: usize = 16384; - const GLOBAL_COUNTER_MAX: usize = MEMORY_ROWS_MAX + STACK_ROWS_MAX + STORAGE_ROWS_MAX; - - let circuit = StateCircuit::< - Fr, - true, - GLOBAL_COUNTER_MAX, - MEMORY_ADDRESS_MAX, - STACK_ADDRESS_MAX, - GLOBAL_COUNTER_MAX, - >::new(block.randomness, &block.rws); + let circuit = StateCircuit::new(block.randomness, block.rws); // TODO: same quest like in the first scope let vk = keygen_vk(params, &circuit)?; diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 6b8fbe95b4..10a1ded790 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -20,6 +20,8 @@ eth-types = { path = "../eth-types" } gadgets = { path = "../gadgets" } ethers-core = "0.6" serde_json = "1.0.66" +strum = "0.24" +strum_macros = "0.24" rand_xorshift = "0.3" rand = "0.8" itertools = "0.10.3" diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 62e47ec712..66d8cbfa93 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -141,7 +141,6 @@ impl EvmCircuit { #[cfg(any(feature = "test", test))] pub mod test { - use crate::{ evm_circuit::{ table::FixedTableTag, @@ -165,6 +164,7 @@ pub mod test { distributions::uniform::{SampleRange, SampleUniform}, random, thread_rng, Rng, }; + use strum::IntoEnumIterator; pub(crate) fn rand_range(range: R) -> T where @@ -500,6 +500,6 @@ pub mod test { pub fn run_test_circuit_complete_fixed_table( block: Block, ) -> Result<(), Vec> { - run_test_circuit(block, FixedTableTag::iterator().collect()) + run_test_circuit(block, FixedTableTag::iter().collect()) } } diff --git a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs index 2876bc2b3a..b7a244262c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs +++ b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs @@ -261,7 +261,7 @@ pub mod test { tx_id, log_id, field_tag: TxLogFieldTag::Data, - index: idx, + index: idx.try_into().unwrap(), value: Word::from(byte), }); rw_offset += 1; diff --git a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs index 661facb443..7809cafd36 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs @@ -22,6 +22,7 @@ use crate::{ }; use eth_types::{evm_types::MAX_REFUND_QUOTIENT_OF_GAS_USED, Field, ToScalar}; use halo2_proofs::plonk::Error; +use strum::EnumCount; #[derive(Clone, Debug)] pub(crate) struct EndTxGadget { @@ -255,7 +256,7 @@ impl ExecutionGadget for EndTxGadget { } else { let rw = &block.rws[( RwTableTag::TxReceipt, - (tx.id - 1) * TxReceiptFieldTag::amount() - 1, + (tx.id - 1) * TxReceiptFieldTag::COUNT - 1, )]; rw.receipt_value() }; diff --git a/zkevm-circuits/src/evm_circuit/execution/logs.rs b/zkevm-circuits/src/evm_circuit/execution/logs.rs index d3c218cfcb..2bfb12be76 100644 --- a/zkevm-circuits/src/evm_circuit/execution/logs.rs +++ b/zkevm-circuits/src/evm_circuit/execution/logs.rs @@ -382,7 +382,7 @@ mod test { tx_id, log_id: log_id.try_into().unwrap(), field_tag: TxLogFieldTag::Topic, - index: idx, + index: idx.try_into().unwrap(), value: *topic, }); } diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 11ad21eb45..423d4d6d25 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -4,6 +4,7 @@ use halo2_proofs::{ plonk::{Advice, Column, Expression, Fixed, VirtualCells}, poly::Rotation, }; +use strum_macros::{EnumCount, EnumIter}; pub trait LookupTable { fn table_exprs(&self, meta: &mut VirtualCells) -> Vec>; @@ -25,7 +26,7 @@ impl LookupTable for [Column; W] { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, EnumIter)] pub enum FixedTableTag { Zero = 0, Range5, @@ -43,26 +44,6 @@ pub enum FixedTableTag { } impl FixedTableTag { - pub fn iterator() -> impl Iterator { - [ - Self::Zero, - Self::Range5, - Self::Range16, - Self::Range32, - Self::Range64, - Self::Range256, - Self::Range512, - Self::Range1024, - Self::SignByte, - Self::BitwiseAnd, - Self::BitwiseOr, - Self::BitwiseXor, - Self::ResponsibleOpcode, - ] - .iter() - .copied() - } - pub fn build(&self) -> Box> { let tag = F::from(*self as u64); match self { @@ -151,10 +132,11 @@ pub enum BlockContextFieldTag { ChainId, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)] pub enum RwTableTag { - Memory = 2, + Start = 1, Stack, + Memory, AccountStorage, TxAccessListAccount, TxAccessListAccountStorage, @@ -180,7 +162,7 @@ impl RwTableTag { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, EnumIter)] pub enum AccountFieldTag { Nonce = 1, Balance, @@ -201,30 +183,14 @@ pub enum TxLogFieldTag { Data, } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, EnumIter, EnumCount)] pub enum TxReceiptFieldTag { PostStateOrStatus = 1, CumulativeGasUsed, LogLength, } -impl TxReceiptFieldTag { - pub(crate) fn iterator() -> impl Iterator { - [ - Self::PostStateOrStatus, - Self::CumulativeGasUsed, - Self::LogLength, - ] - .iter() - .copied() - } - - pub(crate) fn amount() -> usize { - Self::iterator().count() - } -} - -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, EnumIter)] pub enum CallContextFieldTag { RwCounterEndOfReversion = 1, CallerId, @@ -265,7 +231,7 @@ impl_expr!(BlockContextFieldTag); impl_expr!(TxLogFieldTag); impl_expr!(TxReceiptFieldTag); -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, EnumIter)] pub(crate) enum Table { Fixed, Tx, @@ -275,25 +241,6 @@ pub(crate) enum Table { Byte, } -impl Table { - pub(crate) fn iterator() -> impl Iterator { - [ - Self::Fixed, - Self::Tx, - Self::Rw, - Self::Bytecode, - Self::Block, - Self::Byte, - ] - .iter() - .copied() - } - - pub(crate) fn amount() -> usize { - Self::iterator().count() - } -} - #[derive(Clone, Debug)] pub(crate) enum Lookup { /// Lookup to fixed table, which contains serveral pre-built tables such as diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index e675389578..8adad1a27d 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -144,9 +144,9 @@ impl ReversionInfo { #[derive(Default)] pub struct BaseConstraintBuilder { - constraints: Vec<(&'static str, Expression)>, - max_degree: usize, - condition: Option>, + pub constraints: Vec<(&'static str, Expression)>, + pub max_degree: usize, + pub condition: Option>, } impl BaseConstraintBuilder { @@ -860,13 +860,13 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { false.expr(), RwTableTag::AccountStorage, [ - 0.expr(), + tx_id, account_address, 0.expr(), key, value.clone(), value, - tx_id, + 0.expr(), committed_value, ], ); @@ -887,13 +887,13 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { "AccountStorage write", RwTableTag::AccountStorage, [ - 0.expr(), + tx_id, account_address, 0.expr(), key, value, value_prev, - tx_id, + 0.expr(), committed_value, ], reversion_info, @@ -977,9 +977,9 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { RwTableTag::Stack, [ self.curr.state.call_id.expr(), - 0.expr(), self.curr.state.stack_pointer.expr() + stack_pointer_offset, 0.expr(), + 0.expr(), value, 0.expr(), 0.expr(), @@ -1003,9 +1003,9 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { RwTableTag::Memory, [ call_id.unwrap_or_else(|| self.curr.state.call_id.expr()), - 0.expr(), memory_address, 0.expr(), + 0.expr(), byte, 0.expr(), 0.expr(), @@ -1028,9 +1028,9 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { RwTableTag::Memory, [ self.curr.state.call_id.expr(), - 0.expr(), memory_address, 0.expr(), + 0.expr(), byte, 0.expr(), 0.expr(), @@ -1052,9 +1052,9 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { RwTableTag::TxLog, [ tx_id, - self.curr.state.log_id.expr(), + index + (1u64 << 8).expr() * self.curr.state.log_id.expr(), tag.expr(), - index, + 0.expr(), value, 0.expr(), 0.expr(), diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index b73f2d665f..83253a286d 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -17,6 +17,7 @@ use bus_mapping::{ use eth_types::evm_types::OpcodeId; use eth_types::{Address, Field, ToLittleEndian, ToScalar, ToWord, Word}; +use eth_types::{ToAddress, U256}; use halo2_proofs::arithmetic::{BaseExt, FieldExt}; use halo2_proofs::pairing::bn256::Fr as Fp; use itertools::Itertools; @@ -505,12 +506,12 @@ pub enum Rw { rw_counter: usize, is_write: bool, tx_id: usize, - log_id: u64, + log_id: u64, // pack this can index together into address? field_tag: TxLogFieldTag, - // topic index if field_tag is TxLogFieldTag:Topic - // byte index if field_tag is TxLogFieldTag:Data - // it would be zero for other field tags - index: usize, + // topic index (0..4) if field_tag is TxLogFieldTag:Topic + // byte index (0..32) if field_tag is TxLogFieldTag:Data + // 0 for other field tags + index: u8, // when it is topic field, value can be word type value: Word, @@ -557,22 +558,6 @@ impl From<[F; 11]> for RwRow { } impl Rw { - pub fn rw_counter(&self) -> usize { - match self { - Self::TxAccessListAccount { rw_counter, .. } => (*rw_counter), - Self::TxAccessListAccountStorage { rw_counter, .. } => (*rw_counter), - Self::Stack { rw_counter, .. } => (*rw_counter), - Self::Memory { rw_counter, .. } => (*rw_counter), - Self::Account { rw_counter, .. } => (*rw_counter), - Self::AccountDestructed { rw_counter, .. } => (*rw_counter), - Self::CallContext { rw_counter, .. } => (*rw_counter), - Self::AccountStorage { rw_counter, .. } => (*rw_counter), - Self::TxRefund { rw_counter, .. } => (*rw_counter), - Self::TxLog { rw_counter, .. } => (*rw_counter), - Self::TxReceipt { rw_counter, .. } => (*rw_counter), - } - } - pub fn tx_access_list_value_pair(&self) -> (bool, bool) { match self { Self::TxAccessListAccount { @@ -667,249 +652,213 @@ impl Rw { } pub fn table_assignment(&self, randomness: F) -> RwRow { + RwRow { + rw_counter: F::from(self.rw_counter() as u64), + is_write: F::from(self.is_write() as u64), + tag: F::from(self.tag() as u64), + key1: F::from(self.id().unwrap_or_default() as u64), + key2: self.address().unwrap_or_default().to_scalar().unwrap(), + key3: F::from(self.field_tag().unwrap_or_default() as u64), + key4: RandomLinearCombination::random_linear_combine( + self.storage_key().unwrap_or_default().to_le_bytes(), + randomness, + ), + value: self.value_assignment(randomness), + value_prev: self.value_prev_assignment(randomness).unwrap_or_default(), + aux1: F::zero(), // only used for AccountStorage::tx_id, which moved to key1. + aux2: self + .committed_value_assignment(randomness) + .unwrap_or_default(), + } + } + + pub fn rw_counter(&self) -> usize { + match self { + Self::Memory { rw_counter, .. } + | Self::Stack { rw_counter, .. } + | Self::AccountStorage { rw_counter, .. } + | Self::TxAccessListAccount { rw_counter, .. } + | Self::TxAccessListAccountStorage { rw_counter, .. } + | Self::TxRefund { rw_counter, .. } + | Self::Account { rw_counter, .. } + | Self::AccountDestructed { rw_counter, .. } + | Self::CallContext { rw_counter, .. } + | Self::TxLog { rw_counter, .. } + | Self::TxReceipt { rw_counter, .. } => *rw_counter, + } + } + + pub fn is_write(&self) -> bool { + match self { + Self::Memory { is_write, .. } + | Self::Stack { is_write, .. } + | Self::AccountStorage { is_write, .. } + | Self::TxAccessListAccount { is_write, .. } + | Self::TxAccessListAccountStorage { is_write, .. } + | Self::TxRefund { is_write, .. } + | Self::Account { is_write, .. } + | Self::AccountDestructed { is_write, .. } + | Self::CallContext { is_write, .. } + | Self::TxLog { is_write, .. } + | Self::TxReceipt { is_write, .. } => *is_write, + } + } + + pub fn tag(&self) -> RwTableTag { + match self { + Self::Memory { .. } => RwTableTag::Memory, + Self::Stack { .. } => RwTableTag::Stack, + Self::AccountStorage { .. } => RwTableTag::AccountStorage, + Self::TxAccessListAccount { .. } => RwTableTag::TxAccessListAccount, + Self::TxAccessListAccountStorage { .. } => RwTableTag::TxAccessListAccountStorage, + Self::TxRefund { .. } => RwTableTag::TxRefund, + Self::Account { .. } => RwTableTag::Account, + Self::AccountDestructed { .. } => RwTableTag::AccountDestructed, + Self::CallContext { .. } => RwTableTag::CallContext, + Self::TxLog { .. } => RwTableTag::TxLog, + Self::TxReceipt { .. } => RwTableTag::TxReceipt, + } + } + + pub fn id(&self) -> Option { + match self { + Self::AccountStorage { tx_id, .. } + | Self::TxAccessListAccount { tx_id, .. } + | Self::TxAccessListAccountStorage { tx_id, .. } + | Self::TxRefund { tx_id, .. } + | Self::TxLog { tx_id, .. } + | Self::TxReceipt { tx_id, .. } => Some(*tx_id), + Self::CallContext { call_id, .. } + | Self::Stack { call_id, .. } + | Self::Memory { call_id, .. } => Some(*call_id), + Self::Account { .. } | Self::AccountDestructed { .. } => None, + } + } + + pub fn address(&self) -> Option
{ match self { Self::TxAccessListAccount { - rw_counter, - is_write, - tx_id, - account_address, - is_warm, - is_warm_prev, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::TxAccessListAccount as u64), - F::from(*tx_id as u64), - account_address.to_scalar().unwrap(), - F::zero(), - F::zero(), - F::from(*is_warm as u64), - F::from(*is_warm_prev as u64), - F::zero(), - F::zero(), - ] - .into(), - Self::TxAccessListAccountStorage { - rw_counter, - is_write, - tx_id, - account_address, - storage_key, - is_warm, - is_warm_prev, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::TxAccessListAccountStorage as u64), - F::from(*tx_id as u64), - account_address.to_scalar().unwrap(), - F::zero(), - RandomLinearCombination::random_linear_combine( - storage_key.to_le_bytes(), - randomness, - ), - F::from(*is_warm as u64), - F::from(*is_warm_prev as u64), - F::zero(), - F::zero(), - ] - .into(), - Self::TxRefund { - rw_counter, - is_write, - tx_id, - value, - value_prev, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::TxRefund as u64), - F::from(*tx_id as u64), - F::zero(), - F::zero(), - F::zero(), - F::from(*value), - F::from(*value_prev), - F::zero(), - F::zero(), - ] - .into(), - Self::Account { - rw_counter, - is_write, - account_address, - field_tag, - value, - value_prev, - } => { - let to_scalar = |value: &Word| match field_tag { - AccountFieldTag::Nonce => value.to_scalar().unwrap(), - _ => RandomLinearCombination::random_linear_combine( - value.to_le_bytes(), - randomness, - ), - }; - [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::Account as u64), - F::zero(), - account_address.to_scalar().unwrap(), - F::from(*field_tag as u64), - F::zero(), - to_scalar(value), - to_scalar(value_prev), - F::zero(), - F::zero(), - ] - .into() + account_address, .. + } + | Self::TxAccessListAccountStorage { + account_address, .. } + | Self::Account { + account_address, .. + } + | Self::AccountStorage { + account_address, .. + } + | Self::AccountDestructed { + account_address, .. + } => Some(*account_address), + Self::Memory { memory_address, .. } => Some(U256::from(*memory_address).to_address()), + Self::Stack { stack_pointer, .. } => { + Some(U256::from(*stack_pointer as u64).to_address()) + } + Self::TxLog { log_id, index, .. } => { + Some((U256::from(*index as u64) + (U256::from(*log_id) << 8)).to_address()) + } + Self::CallContext { .. } | Self::TxRefund { .. } | Self::TxReceipt { .. } => None, + } + } + + pub fn field_tag(&self) -> Option { + match self { + Self::Account { field_tag, .. } => Some(*field_tag as u64), + Self::CallContext { field_tag, .. } => Some(*field_tag as u64), + Self::TxLog { field_tag, .. } => Some(*field_tag as u64), + Self::TxReceipt { field_tag, .. } => Some(*field_tag as u64), + Self::Memory { .. } + | Self::Stack { .. } + | Self::AccountStorage { .. } + | Self::TxAccessListAccount { .. } + | Self::TxAccessListAccountStorage { .. } + | Self::TxRefund { .. } + | Self::AccountDestructed { .. } => None, + } + } + + pub fn storage_key(&self) -> Option { + match self { + Self::AccountStorage { storage_key, .. } + | Self::TxAccessListAccountStorage { storage_key, .. } => Some(*storage_key), + Self::CallContext { .. } + | Self::Stack { .. } + | Self::Memory { .. } + | Self::TxRefund { .. } + | Self::Account { .. } + | Self::TxAccessListAccount { .. } + | Self::AccountDestructed { .. } + | Self::TxLog { .. } + | Self::TxReceipt { .. } => None, + } + } + + fn value_assignment(&self, randomness: F) -> F { + match self { Self::CallContext { - rw_counter, - is_write, - call_id, - field_tag, - value, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::CallContext as u64), - F::from(*call_id as u64), - F::zero(), - F::from(*field_tag as u64), - F::zero(), + field_tag, value, .. + } => { match field_tag { + // Only these two tags have values that may not fit into a scalar, so we need to + // RLC. CallContextFieldTag::CodeSource | CallContextFieldTag::Value => { RandomLinearCombination::random_linear_combine( value.to_le_bytes(), randomness, ) } - CallContextFieldTag::CallerAddress - | CallContextFieldTag::CalleeAddress - | CallContextFieldTag::IsSuccess => value.to_scalar().unwrap(), - _ => F::from(value.low_u64()), - }, - F::zero(), - F::zero(), - F::zero(), - ] - .into(), - Self::Stack { - rw_counter, - is_write, - call_id, - stack_pointer, - value, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::Stack as u64), - F::from(*call_id as u64), - F::zero(), - F::from(*stack_pointer as u64), - F::zero(), - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness), - F::zero(), - F::zero(), - F::zero(), - ] - .into(), - Self::Memory { - rw_counter, - is_write, - call_id, - memory_address, - byte, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::Memory as u64), - F::from(*call_id as u64), - F::zero(), - F::from(*memory_address), - F::zero(), - F::from(*byte as u64), - F::zero(), - F::zero(), - F::zero(), - ] - .into(), - Self::AccountStorage { - rw_counter, - is_write, - account_address, - storage_key, - value, - value_prev, - tx_id, - committed_value, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::AccountStorage as u64), - F::zero(), - account_address.to_scalar().unwrap(), - F::zero(), - RandomLinearCombination::random_linear_combine( - storage_key.to_le_bytes(), - randomness, - ), - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness), - RandomLinearCombination::random_linear_combine( + _ => value.to_scalar().unwrap(), + } + } + Self::Account { value, .. } + | Self::AccountStorage { value, .. } + | Self::Stack { value, .. } + | Self::TxLog { value, .. } => { + RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) + } + Self::TxAccessListAccount { is_warm, .. } + | Self::TxAccessListAccountStorage { is_warm, .. } => F::from(*is_warm as u64), + Self::AccountDestructed { is_destructed, .. } => F::from(*is_destructed as u64), + Self::Memory { byte, .. } => F::from(u64::from(*byte)), + Self::TxRefund { value, .. } | Self::TxReceipt { value, .. } => F::from(*value), + } + } + + fn value_prev_assignment(&self, randomness: F) -> Option { + match self { + Self::Account { value_prev, .. } | Self::AccountStorage { value_prev, .. } => { + Some(RandomLinearCombination::random_linear_combine( value_prev.to_le_bytes(), randomness, - ), - F::from(*tx_id as u64), - RandomLinearCombination::random_linear_combine( - committed_value.to_le_bytes(), - randomness, - ), - ] - .into(), - Self::TxLog { - rw_counter, - is_write, - tx_id, - log_id, - field_tag, - index, - value, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::TxLog as u64), - F::from(*tx_id as u64), - F::from(*log_id as u64), - F::from(*field_tag as u64), - F::from(*index as u64), - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness), - F::zero(), - F::zero(), - F::zero(), - ] - .into(), - Self::TxReceipt { - rw_counter, - is_write, - tx_id, - field_tag, - value, - } => [ - F::from(*rw_counter as u64), - F::from(*is_write as u64), - F::from(RwTableTag::TxReceipt as u64), - F::from(*tx_id as u64), - F::zero(), - F::from(*field_tag as u64), - F::zero(), - F::from(*value), - F::zero(), - F::zero(), - F::zero(), - ] - .into(), - _ => unimplemented!(), + )) + } + Self::TxAccessListAccount { is_warm_prev, .. } + | Self::TxAccessListAccountStorage { is_warm_prev, .. } => { + Some(F::from(*is_warm_prev as u64)) + } + Self::AccountDestructed { + is_destructed_prev, .. + } => Some(F::from(*is_destructed_prev as u64)), + Self::TxRefund { value_prev, .. } => Some(F::from(*value_prev)), + Self::Stack { .. } + | Self::Memory { .. } + | Self::CallContext { .. } + | Self::TxLog { .. } + | Self::TxReceipt { .. } => None, + } + } + + fn committed_value_assignment(&self, randomness: F) -> Option { + match self { + Self::AccountStorage { + committed_value, .. + } => Some(RandomLinearCombination::random_linear_combine( + committed_value.to_le_bytes(), + randomness, + )), + _ => None, } } } diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index c5c5b358f9..49dc742100 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -1,4 +1,270 @@ //! The state circuit implementation. +mod constraint_builder; +mod lexicographic_ordering; +mod lookups; +mod multiple_precision_integer; +mod random_linear_combination; +#[cfg(test)] +mod state_tests; -pub(crate) mod state; -pub use state::StateCircuit; +use crate::evm_circuit::{ + param::N_BYTES_WORD, + witness::{Rw, RwMap}, +}; +use constraint_builder::{ConstraintBuilder, Queries}; +use eth_types::{Address, Field}; +use gadgets::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{ + Advice, Circuit, Column, ConstraintSystem, Error, Expression, Fixed, Instance, VirtualCells, + }, + poly::Rotation, +}; +use lexicographic_ordering::{ + Chip as LexicographicOrderingChip, Config as LexicographicOrderingConfig, +}; +use lookups::{Chip as LookupsChip, Config as LookupsConfig, Queries as LookupsQueries}; +use multiple_precision_integer::{Chip as MpiChip, Config as MpiConfig, Queries as MpiQueries}; +use random_linear_combination::{Chip as RlcChip, Config as RlcConfig, Queries as RlcQueries}; + +const N_LIMBS_RW_COUNTER: usize = 2; +const N_LIMBS_ACCOUNT_ADDRESS: usize = 10; +const N_LIMBS_ID: usize = 2; + +/// Config for StateCircuit +#[derive(Clone)] +pub struct StateConfig { + selector: Column, // Figure out why you get errors when this is Selector. + // https://github.com/appliedzkp/zkevm-circuits/issues/407 + rw_counter: MpiConfig, + is_write: Column, + tag: Column, + id: MpiConfig, + address: MpiConfig, + field_tag: Column, + storage_key: RlcConfig, + is_storage_key_unchanged: IsZeroConfig, + value: Column, + lookups: LookupsConfig, + power_of_randomness: [Column; N_BYTES_WORD - 1], + lexicographic_ordering: LexicographicOrderingConfig, +} + +type Lookup = (&'static str, Expression, Expression); + +/// State Circuit for proving RwTable is valid +#[derive(Default)] +pub struct StateCircuit { + pub(crate) randomness: F, + pub(crate) rows: Vec, +} + +impl StateCircuit { + /// make a new state circuit + pub fn new(randomness: F, rw_map: RwMap) -> Self { + let mut rows: Vec<_> = rw_map.0.into_values().flatten().collect(); + rows.sort_by_key(|row| { + ( + row.tag() as u64, + row.field_tag().unwrap_or_default(), + row.id().unwrap_or_default(), + row.address().unwrap_or_default(), + row.storage_key().unwrap_or_default(), + row.rw_counter(), + ) + }); + Self { randomness, rows } + } + + /// powers of randomness for instance columns + pub fn instance(&self) -> Vec> { + (1..32) + .map(|exp| vec![self.randomness.pow(&[exp, 0, 0, 0]); self.rows.len()]) + .collect() + } +} + +impl Circuit for StateCircuit { + type Config = StateConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let selector = meta.fixed_column(); + let lookups = LookupsChip::configure(meta); + let power_of_randomness = [0; N_BYTES_WORD - 1].map(|_| meta.instance_column()); + + let [is_write, tag, field_tag, value, is_zero_chip_advice_column] = + [0; 5].map(|_| meta.advice_column()); + + let id = MpiChip::configure(meta, selector, lookups.u16); + let address = MpiChip::configure(meta, selector, lookups.u16); + let storage_key = RlcChip::configure(meta, selector, lookups.u8, power_of_randomness); + let rw_counter = MpiChip::configure(meta, selector, lookups.u16); + + let is_storage_key_unchanged = IsZeroChip::configure( + meta, + |meta| meta.query_fixed(selector, Rotation::cur()), + |meta| { + meta.query_advice(storage_key.encoded, Rotation::cur()) + - meta.query_advice(storage_key.encoded, Rotation::prev()) + }, + is_zero_chip_advice_column, + ); + + let config = Self::Config { + selector, + rw_counter, + is_write, + tag, + id, + address, + field_tag, + storage_key, + value, + lexicographic_ordering: LexicographicOrderingChip::configure( + meta, + selector, + tag, + field_tag, + id.limbs, + address.limbs, + storage_key.bytes, + rw_counter.limbs, + lookups.u16, + ), + is_storage_key_unchanged, + lookups, + power_of_randomness, + }; + + let mut constraint_builder = ConstraintBuilder::new(); + meta.create_gate("state circuit constraints", |meta| { + let queries = queries(meta, &config); + constraint_builder.build(&queries); + constraint_builder.gate(queries.selector) + }); + for (name, expressions) in constraint_builder.lookups() { + meta.lookup_any(name, |_| vec![expressions]); + } + + config + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + LookupsChip::construct(config.lookups).load(&mut layouter)?; + let is_storage_key_unchanged = + IsZeroChip::construct(config.is_storage_key_unchanged.clone()); + + let lexicographic_ordering_chip = + LexicographicOrderingChip::construct(config.lexicographic_ordering.clone()); + + let mut prev_storage_key = F::zero(); + + layouter.assign_region( + || "assign rw table", + |mut region| { + for (offset, row) in self.rows.iter().enumerate() { + if offset != 0 { + region.assign_fixed( + || "selector", + config.selector, + offset, + || Ok(F::one()), + )?; + + lexicographic_ordering_chip.assign( + &mut region, + offset, + row, + &self.rows[offset - 1], + )?; + } + config + .rw_counter + .assign(&mut region, offset, row.rw_counter() as u32)?; + region.assign_advice( + || "is_write", + config.is_write, + offset, + || Ok(if row.is_write() { F::one() } else { F::zero() }), + )?; + region.assign_advice( + || "tag", + config.tag, + offset, + || Ok(F::from(row.tag() as u64)), + )?; + if let Some(id) = row.id() { + config.id.assign(&mut region, offset, id as u32)?; + } + if let Some(address) = row.address() { + config.address.assign(&mut region, offset, address)?; + } + if let Some(field_tag) = row.field_tag() { + region.assign_advice( + || "field_tag", + config.field_tag, + offset, + || Ok(F::from(field_tag as u64)), + )?; + } + + // TODO: chain assigments idiomatically. + let cur_storage_key = *(config + .storage_key + .assign( + &mut region, + offset, + self.randomness, + row.storage_key().unwrap_or_default(), + )? + .value() + .unwrap_or(&F::zero())); + + is_storage_key_unchanged.assign( + &mut region, + offset, + Some(cur_storage_key - prev_storage_key), + )?; + + prev_storage_key = cur_storage_key; + } + Ok(()) + }, + ) + } +} + +fn queries(meta: &mut VirtualCells<'_, F>, c: &StateConfig) -> Queries { + Queries { + selector: meta.query_fixed(c.selector, Rotation::cur()), + rw_counter: MpiQueries::new(meta, c.rw_counter), + is_write: meta.query_advice(c.is_write, Rotation::cur()), + tag: meta.query_advice(c.tag, Rotation::cur()), + prev_tag: meta.query_advice(c.tag, Rotation::prev()), + id: MpiQueries::new(meta, c.id), + address: MpiQueries::new(meta, c.address), + field_tag: meta.query_advice(c.field_tag, Rotation::cur()), + storage_key: RlcQueries::new(meta, c.storage_key), + value: meta.query_advice(c.value, Rotation::cur()), + lookups: LookupsQueries::new(meta, c.lookups), + power_of_randomness: c + .power_of_randomness + .map(|c| meta.query_instance(c, Rotation::cur())), + is_storage_key_unchanged: c.is_storage_key_unchanged.is_zero_expression.clone(), + lexicographic_ordering_upper_limb_difference_is_zero: c + .lexicographic_ordering + .upper_limb_difference_is_zero + .is_zero_expression + .clone(), + } +} diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs new file mode 100644 index 0000000000..cab9f92015 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -0,0 +1,328 @@ +use super::{ + lookups::Queries as LookupsQueries, multiple_precision_integer::Queries as MpiQueries, + random_linear_combination::Queries as RlcQueries, N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_ID, + N_LIMBS_RW_COUNTER, +}; +use crate::evm_circuit::{ + param::N_BYTES_WORD, + table::{AccountFieldTag, RwTableTag}, + util::{math_gadget::generate_lagrange_base_polynomial, not}, +}; +use crate::util::Expr; +use eth_types::Field; +use halo2_proofs::plonk::Expression; +use strum::IntoEnumIterator; + +#[derive(Clone)] +pub struct Queries { + pub selector: Expression, + pub rw_counter: MpiQueries, + pub is_write: Expression, + pub tag: Expression, + pub prev_tag: Expression, + pub id: MpiQueries, + pub address: MpiQueries, + pub field_tag: Expression, + pub storage_key: RlcQueries, + pub value: Expression, + pub lookups: LookupsQueries, + pub power_of_randomness: [Expression; N_BYTES_WORD - 1], + pub is_storage_key_unchanged: Expression, + pub lexicographic_ordering_upper_limb_difference_is_zero: Expression, +} + +type Constraint = (&'static str, Expression); +type Lookup = (&'static str, (Expression, Expression)); + +pub struct ConstraintBuilder { + pub constraints: Vec>, + lookups: Vec>, + condition: Expression, +} + +impl ConstraintBuilder { + pub fn new() -> Self { + Self { + constraints: vec![], + lookups: vec![], + condition: 1.expr(), + } + } + + pub fn gate(&self, condition: Expression) -> Vec<(&'static str, Expression)> { + self.constraints + .iter() + .cloned() + .map(|(name, expression)| (name, condition.clone() * expression)) + .collect() + } + + pub fn lookups(&self) -> Vec> { + self.lookups.clone() + } + + pub fn build(&mut self, q: &Queries) { + self.build_general_constraints(q); + self.condition(q.tag_matches(RwTableTag::Start), |cb| { + cb.build_start_constraints(q) + }); + self.condition(q.tag_matches(RwTableTag::Memory), |cb| { + cb.build_memory_constraints(q) + }); + self.condition(q.tag_matches(RwTableTag::Stack), |cb| { + cb.build_stack_constraints(q) + }); + self.condition(q.tag_matches(RwTableTag::AccountStorage), |cb| { + cb.build_account_storage_constraints(q) + }); + self.condition(q.tag_matches(RwTableTag::TxAccessListAccount), |cb| { + cb.build_tx_access_list_account_constraints(q) + }); + self.condition( + q.tag_matches(RwTableTag::TxAccessListAccountStorage), + |cb| cb.build_tx_access_list_account_storage_constraints(q), + ); + self.condition(q.tag_matches(RwTableTag::TxRefund), |cb| { + cb.build_tx_refund_constraints(q) + }); + self.condition(q.tag_matches(RwTableTag::Account), |cb| { + cb.build_account_constraints(q) + }); + self.condition(q.tag_matches(RwTableTag::AccountDestructed), |cb| { + cb.build_account_destructed_constraints(q) + }); + self.condition(q.tag_matches(RwTableTag::CallContext), |cb| { + cb.build_call_context_constraints(q) + }); + } + + fn build_general_constraints(&mut self, q: &Queries) { + self.require_in_set("tag in RwTableTag range", q.tag(), set::()); + self.require_boolean("is_write is boolean", q.is_write()); + } + + fn build_start_constraints(&mut self, q: &Queries) { + self.require_zero("rw_counter is 0 for Start", q.rw_counter.value.clone()); + } + + fn build_memory_constraints(&mut self, q: &Queries) { + self.require_zero("field_tag is 0 for Memory", q.field_tag()); + self.require_zero("storage_key is 0 for Memory", q.storage_key.encoded.clone()); + self.require_zero( + "read from a fresh key is 0", + q.first_access() * q.is_read() * q.value(), + ); + // could do this more efficiently by just asserting address = limb0 + 2^16 * + // limb1? + for limb in &q.address.limbs[2..] { + self.require_zero("memory address fits into 2 limbs", limb.clone()); + } + self.add_lookup( + "memory value is a byte", + (q.value.clone(), q.lookups.u8.clone()), + ); + } + + fn build_stack_constraints(&mut self, q: &Queries) { + self.require_zero("field_tag is 0 for Stack", q.field_tag()); + self.require_zero("storage_key is 0 for Stack", q.storage_key.encoded.clone()); + self.require_zero( + "first access to new stack address is a write", + q.first_access() * (1.expr() - q.is_write()), + ); + self.add_lookup( + "stack address fits into 10 bits", + (q.address.value.clone(), q.lookups.u10.clone()), + ); + // this pushes the degree to 17.... + self.condition(q.first_access(), |cb| { + cb.require_zero( + // previous tag is Start <=> this is the first stack rw + "previous tag is Start or address change is 0 or 1", + (q.prev_tag.clone() - RwTableTag::Start.expr()) + * q.address_change() + * (1.expr() - q.address_change()), + ) + }); + } + + fn build_account_storage_constraints(&mut self, q: &Queries) { + // TODO: cold VS warm + // TODO: connection to MPT on first and last access for each (address, key) + // No longer true because we moved id from aux to here. + // self.require_zero("id is 0 for AccountStorage", q.id()); + self.require_zero("field_tag is 0 for AccountStorage", q.field_tag()); + // for every first access, we add an AccountStorage write to setup the + // value from the previous block with rw_counter = 0 + // needs some work... + // self.condition(q.first_access(), |cb| { + // cb.require_zero("first access is a write", q.is_write()); + // // cb.require_zero("first access rw_counter is 0", + // q.rw_counter.value.clone()); }) + } + fn build_tx_access_list_account_constraints(&mut self, q: &Queries) { + self.require_zero("field_tag is 0 for TxAccessListAccount", q.field_tag()); + self.require_zero( + "storage_key is 0 for TxAccessListAccount", + q.storage_key.encoded.clone(), + ); + // TODO: Missing constraints + } + + fn build_tx_access_list_account_storage_constraints(&mut self, q: &Queries) { + self.require_zero( + "field_tag is 0 for TxAccessListAccountStorage", + q.field_tag(), + ); + // TODO: Missing constraints + } + + fn build_tx_refund_constraints(&mut self, q: &Queries) { + self.require_zero("address is 0 for TxRefund", q.address.value.clone()); + self.require_zero("field_tag is 0 for TxRefund", q.field_tag()); + self.require_zero( + "storage_key is 0 for TxRefund", + q.storage_key.encoded.clone(), + ); + // TODO: Missing constraints + } + + fn build_account_constraints(&mut self, q: &Queries) { + self.require_zero("id is 0 for Account", q.id()); + self.require_zero( + "storage_key is 0 for Account", + q.storage_key.encoded.clone(), + ); + self.require_in_set( + "field_tag in AccountFieldTag range", + q.field_tag(), + set::(), + ); + // // for every first access, we add an Account write to setup the value + // from the // previous block with rw_counter = 0 + // self.condition(q.first_access(), |cb| { + // // cb.require_zero("first access is a write", q.is_write()); + // cb.require_zero("first access rw_counter is 0", + // q.rw_counter.value.clone()); }); + } + + fn build_account_destructed_constraints(&mut self, q: &Queries) { + self.require_zero("id is 0 for AccountDestructed", q.id()); + self.require_zero("field_tag is 0 for AccountDestructed", q.field_tag()); + self.require_zero( + "storage_key is 0 for AccountDestructed", + q.storage_key.encoded.clone(), + ); + // TODO: Missing constraints + } + + fn build_call_context_constraints(&mut self, q: &Queries) { + self.require_zero("address is 0 for CallContext", q.address.value.clone()); + self.require_zero( + "storage_key is 0 for CallContext", + q.storage_key.encoded.clone(), + ); + self.add_lookup( + "field_tag in CallContextFieldTag range", + (q.field_tag(), q.lookups.call_context_field_tag.clone()), + ); + // TODO: Missing constraints + } + + fn require_zero(&mut self, name: &'static str, e: Expression) { + self.constraints.push((name, self.condition.clone() * e)); + } + + fn require_boolean(&mut self, name: &'static str, e: Expression) { + self.require_zero(name, e.clone() * (1.expr() - e)) + } + + fn require_in_set(&mut self, name: &'static str, item: Expression, set: Vec>) { + self.require_zero( + name, + set.iter().fold(1.expr(), |acc, element| { + acc * (item.clone() - element.clone()) + }), + ); + } + + fn add_lookup(&mut self, name: &'static str, lookup: (Expression, Expression)) { + let mut lookup = lookup; + lookup.0 = lookup.0 * self.condition.clone(); + self.lookups.push((name, lookup)); + } + + fn condition(&mut self, condition: Expression, build: impl FnOnce(&mut Self)) { + let original_condition = self.condition.clone(); + self.condition = self.condition.clone() * condition; + build(self); + self.condition = original_condition; + } +} + +impl Queries { + fn selector(&self) -> Expression { + self.selector.clone() + } + + fn is_write(&self) -> Expression { + self.is_write.clone() + } + + fn is_read(&self) -> Expression { + not::expr(&self.is_write) + } + + fn tag(&self) -> Expression { + self.tag.clone() + } + + fn id(&self) -> Expression { + self.id.value.clone() + } + + fn id_change(&self) -> Expression { + self.id() - self.id.value_prev.clone() + } + + fn field_tag(&self) -> Expression { + self.field_tag.clone() + } + + fn value(&self) -> Expression { + self.value.clone() + } + + fn tag_matches(&self, tag: RwTableTag) -> Expression { + generate_lagrange_base_polynomial( + self.tag.clone(), + tag as usize, + RwTableTag::iter().map(|x| x as usize), + ) + } + + fn first_access(&self) -> Expression { + not::expr( + self.lexicographic_ordering_upper_limb_difference_is_zero + .clone(), + ) * not::expr(self.is_storage_key_unchanged.clone()) + } + + fn address_change(&self) -> Expression { + self.address.value.clone() - self.address.value_prev.clone() + } +} + +fn from_digits(digits: &[Expression], base: Expression) -> Expression { + digits + .iter() + .fold(Expression::Constant(F::zero()), |result, digit| { + digit.clone() + result * base.clone() + }) +} + +fn set>() -> Vec> { + T::iter().map(|x| x.expr()).collect() // you don't need this collect if you + // can figure out the return type + // without it. +} diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs new file mode 100644 index 0000000000..3a1f89fb55 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -0,0 +1,366 @@ +use super::{N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_ID, N_LIMBS_RW_COUNTER}; +use crate::{ + evm_circuit::{param::N_BYTES_WORD, witness::Rw}, + util::Expr, +}; +use eth_types::{Field, ToBigEndian}; +use gadgets::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; +use halo2_proofs::{ + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, + poly::Rotation, +}; +use itertools::Itertools; +use std::ops::Mul; + +// We use this chip to show that the rows of the rw table are in lexicographic +// order, i.e. ordered by (tag, field_tag, id, address, storage_key, and +// rw_counter). We do this by packing these 6 fields into a 480 bit value X, and +// then showing that X_cur > X_prev. Let A0, A1, ..., A29 be the 30 16-bit limbs +// of X_cur and B0, B1, ..., B29 be 30 16-bit limbs of X_prev, in big endian +// order. + +// Let +// C0 = A0 - B0, +// C1 = C0 << 16 + A1 - B1, +// ... +// C14 = C13 << 16 + A14 - B14, +// and +// C15 = A15 - B15, +// C16 = C15 << 16 + A16 - B16, +// ... +// C29 = C28 << 16 + A29 - B29. +// We have to split the 30 limbs into upper and lower halves between C14 and C15 +// because a field element can only hold 15 16-bit limbs. + +// X_cur > X_prev iff one of the following is true: +// 1. one of C0, ..., C14 is non-zero and fits into 16 bits. +// 2. all of C0, ..., C14 are 0 and one of C15, ..., C29 is non-zero and fits +// into 16 bits. (note that "all of C0, ..., C14 are 0" is equivalent to +// "C14 is 0".) + +// We show that one of these is true with the following constraints: +// 1. upper_limb_difference is (at least) 1 of the 15 values C0, ..., C14. +// 2. lower_limb_difference is (at least) 1 of the 15 values C15, ..., C29. +// 3. upper_limb_difference fits into 16 bits. +// 4. if upper_limb_difference is 0, then lower_limb_difference fits into 16 +// bits. +// 5. if upper_limb_difference is 0, then C14 is 0. +// 6. at least one of upper_limb_difference or lower_limb_difference is not 0. + +// We satisfy these constraints by assigning upper_limb_difference +// to be the first non-zero difference between the first 15 big-endian limbs of +// X_cur and X_prev or 0 if the the limbs are all equal. E.g. if X_curr = (2, 1, +// 6, ...) and X_prev = (2, 1, 2, ...), then upper_limb_difference = C2 = 6 - 2 +// = 4. If there is no difference between the first 15 pairs of limbs, then +// lower_limb_difference is assigned to be the first non-zero difference between +// the last 15 pairs of limbs. This non-zero difference will exist because there +// are no duplicate entries in the rw table. If upper_limb_difference has a +// non-zero value, then we assign lower_limb_difference to be the value of C29. + +// Packing the field into 480 bits: +// 4 bits for tag, +// + 4 bits for field_tag // TODO: this actually needs 5 bits. Either reduce id +// + 24 bits for id // to 23 bits, or add diff_3 etc. +// + 160 bits for address, +// + 256 bits for storage key +// + 32 bits for rw_counter +// ----------------------------------- +// = 480 bits + +#[derive(Clone)] +pub struct Config { + upper_limb_difference: Column, + pub(crate) upper_limb_difference_is_zero: IsZeroConfig, + lower_limb_difference: Column, + lower_limb_difference_is_zero: IsZeroConfig, + tag: Column, + field_tag: Column, + id_limbs: [Column; N_LIMBS_ID], + address_limbs: [Column; N_LIMBS_ACCOUNT_ADDRESS], + storage_key_bytes: [Column; N_BYTES_WORD], + rw_counter_limbs: [Column; N_LIMBS_RW_COUNTER], +} + +pub struct Chip { + config: Config, +} + +impl Chip { + pub fn construct(config: Config) -> Self { + Self { config } + } + + #[allow(clippy::too_many_arguments)] + // TODO: fix this to not have too many arguments? + pub fn configure( + meta: &mut ConstraintSystem, + selector: Column, + tag: Column, + field_tag: Column, + id_limbs: [Column; N_LIMBS_ID], + address_limbs: [Column; N_LIMBS_ACCOUNT_ADDRESS], + storage_key_bytes: [Column; N_BYTES_WORD], + rw_counter_limbs: [Column; N_LIMBS_RW_COUNTER], + u16_range: Column, + ) -> Config { + let [upper_limb_difference, upper_limb_difference_inverse, lower_limb_difference, lower_limb_difference_inverse] = + [0; 4].map(|_| meta.advice_column()); + let [upper_limb_difference_is_zero_config, lower_limb_difference_is_zero_config] = [ + (upper_limb_difference, upper_limb_difference_inverse), + (lower_limb_difference, lower_limb_difference_inverse), + ] + .map(|(value, advice)| { + IsZeroChip::configure( + meta, + |meta| meta.query_fixed(selector, Rotation::cur()), + |meta| meta.query_advice(value, Rotation::cur()), + advice, + ) + }); + + let upper_limb_difference_is_zero = upper_limb_difference_is_zero_config + .is_zero_expression + .clone(); + let lower_limb_difference_is_zero = lower_limb_difference_is_zero_config + .is_zero_expression + .clone(); + + let config = Config { + upper_limb_difference, + upper_limb_difference_is_zero: upper_limb_difference_is_zero_config, + lower_limb_difference, + lower_limb_difference_is_zero: lower_limb_difference_is_zero_config, + tag, + field_tag, + id_limbs, + address_limbs, + storage_key_bytes, + rw_counter_limbs, + }; + meta.create_gate("upper_limb_difference is one of 15 values", |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + let cur = Queries::new(meta, &config, Rotation::cur()); + let prev = Queries::new(meta, &config, Rotation::prev()); + let upper_limb_difference = meta.query_advice(upper_limb_difference, Rotation::cur()); + vec![ + selector + * upper_limb_difference_possible_values(cur, prev) + .iter() + .map(|e| upper_limb_difference.clone() - e.clone()) + .fold(1.expr(), Expression::mul), + ] + }); + assert!(meta.degree() <= 16); + meta.create_gate( + "upper_limb_difference is zero iff all 15 possible values are 0", + |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + let cur = Queries::new(meta, &config, Rotation::cur()); + let prev = Queries::new(meta, &config, Rotation::prev()); + vec![ + // all 15 possible values are 0 iff the final linear combination is 0 + selector + * upper_limb_difference_is_zero.clone() + * upper_limb_difference_possible_values(cur, prev)[14].clone(), + ] + }, + ); + meta.create_gate("lower_limb_difference is one of 15 values", |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + let cur = Queries::new(meta, &config, Rotation::cur()); + let prev = Queries::new(meta, &config, Rotation::prev()); + let lower_limb_difference = meta.query_advice(lower_limb_difference, Rotation::cur()); + vec![ + selector + * lower_limb_difference_possible_values(cur, prev) + .iter() + .map(|e| lower_limb_difference.clone() - e.clone()) + .fold(1.expr(), Expression::mul), + ] + }); + assert!(meta.degree() <= 16); + meta.lookup_any("upper_limb_difference fits into u16", |meta| { + let upper_limb_difference = meta.query_advice(upper_limb_difference, Rotation::cur()); + vec![( + upper_limb_difference, + meta.query_fixed(u16_range, Rotation::cur()), + )] + }); + meta.lookup_any( + "upper_limb_difference is zero or lower_limb_difference fits into u16", + |meta| { + let lower_limb_difference = + meta.query_advice(lower_limb_difference, Rotation::cur()); + vec![( + upper_limb_difference_is_zero * lower_limb_difference, + meta.query_fixed(u16_range, Rotation::cur()), + )] + }, + ); + assert!(meta.degree() <= 16); + meta.create_gate("lower_limb_difference is not zero", |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + vec![(selector * lower_limb_difference_is_zero)] + }); + assert!(meta.degree() <= 16); + + config + } + + pub fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + cur: &Rw, + prev: &Rw, + ) -> Result<(), Error> { + // this doesn't make sense that we have to "construct" the chip every time we + // assign? + let upper_limb_difference_is_zero_chip = + IsZeroChip::construct(self.config.upper_limb_difference_is_zero.clone()); + let lower_limb_difference_is_zero_chip = + IsZeroChip::construct(self.config.lower_limb_difference_is_zero.clone()); + + let cur_be_limbs = rw_to_be_limbs(cur); + let prev_be_limbs = rw_to_be_limbs(prev); + + let find_result = cur_be_limbs + .iter() + .zip(&prev_be_limbs) + .enumerate() + .find(|(_, (a, b))| a != b); + let (index, (cur_limb, prev_limb)) = find_result.expect("repeated rw counter"); + + let mut upper_limb_difference = F::from((cur_limb - prev_limb) as u64); + let mut lower_limb_difference = lower_limb_difference_value(&cur_be_limbs, &prev_be_limbs); + if index >= 15 { + upper_limb_difference = F::zero(); + lower_limb_difference = F::from((cur_limb - prev_limb) as u64); + } + + region.assign_advice( + || "upper_limb_difference", + self.config.upper_limb_difference, + offset, + || Ok(upper_limb_difference), + )?; + region.assign_advice( + || "lower_limb_difference", + self.config.lower_limb_difference, + offset, + || Ok(lower_limb_difference), + )?; + upper_limb_difference_is_zero_chip.assign(region, offset, Some(upper_limb_difference))?; + lower_limb_difference_is_zero_chip.assign(region, offset, Some(lower_limb_difference))?; + Ok(()) + } +} + +struct Queries { + tag: Expression, // 4 bits + field_tag: Expression, // 8 bits, so we can pack tag + field_tag into one limb. + id_limbs: [Expression; N_LIMBS_ID], + address_limbs: [Expression; N_LIMBS_ACCOUNT_ADDRESS], + storage_key_bytes: [Expression; N_BYTES_WORD], + rw_counter_limbs: [Expression; N_LIMBS_RW_COUNTER], +} + +impl Queries { + fn new(meta: &mut VirtualCells<'_, F>, config: &Config, rotation: Rotation) -> Self { + let mut query_advice = |column| meta.query_advice(column, rotation); + Self { + tag: query_advice(config.tag), + field_tag: query_advice(config.field_tag), + id_limbs: config.id_limbs.map(&mut query_advice), + address_limbs: config.address_limbs.map(&mut query_advice), + storage_key_bytes: config.storage_key_bytes.map(&mut query_advice), + rw_counter_limbs: config.rw_counter_limbs.map(query_advice), + } + } + + fn packed_tags(&self) -> Expression { + (1u64 << 4).expr() * self.tag.clone() + self.field_tag.clone() + } + + fn storage_key_be_limbs(&self) -> Vec> { + self.storage_key_bytes + .iter() + .rev() + .tuples() + .map(|(hi, lo)| (1u64 << 8).expr() * hi.clone() + lo.clone()) + .collect() + } + + fn be_limbs(&self) -> Vec> { + let mut limbs: Vec<_> = self + .id_limbs + .iter() + .rev() + .chain(self.address_limbs.iter().rev()) + .chain(&self.storage_key_be_limbs()) + .chain(self.rw_counter_limbs.iter().rev()) + .cloned() + .collect(); + // most significant byte of id should be 0, so safe to overwrite it with packed + // tags. + limbs[0] = limbs[0].clone() + self.packed_tags() * (1u64 << 8).expr(); + limbs + } +} + +fn rw_to_be_limbs(row: &Rw) -> Vec { + let mut be_bytes = vec![]; + be_bytes.extend_from_slice(&(row.id().unwrap_or_default() as u32).to_be_bytes()); + be_bytes.extend_from_slice(&(row.address().unwrap_or_default().0)); + be_bytes.extend_from_slice(&(row.storage_key().unwrap_or_default().to_be_bytes())); + be_bytes.extend_from_slice(&((row.rw_counter() as u32).to_be_bytes())); + + // check that the first byte of id is not used, and overwrites it with packed + // tags. + assert_eq!(be_bytes[0], 0); + be_bytes[0] = row.field_tag().unwrap_or_default() as u8 + ((row.tag() as u8) << 4); + + be_bytes + .iter() + .tuples() + .map(|(hi, lo)| u16::from_be_bytes([*hi, *lo])) + .collect() +} + +fn upper_limb_difference_possible_values( + cur: Queries, + prev: Queries, +) -> Vec> { + let mut result = vec![]; + let mut partial_sum = 0u64.expr(); + for (cur_limb, prev_limb) in cur.be_limbs()[..15].iter().zip(&prev.be_limbs()[..15]) { + partial_sum = partial_sum * (1u64 << 16).expr() + cur_limb.clone() - prev_limb.clone(); + result.push(partial_sum.clone()) + } + result +} + +fn lower_limb_difference_possible_values( + cur: Queries, + prev: Queries, +) -> Vec> { + let mut result = vec![]; + let mut partial_sum = 0u64.expr(); + for (cur_limb, prev_limb) in cur.be_limbs()[15..].iter().zip(&prev.be_limbs()[15..]) { + partial_sum = partial_sum * (1u64 << 16).expr() + cur_limb.clone() - prev_limb.clone(); + result.push(partial_sum.clone()) + } + assert_eq!(result.len(), 15); + result +} + +fn lower_limb_difference_value(cur_limbs: &[u16], prev_limbs: &[u16]) -> F { + be_limbs_to_value::(&cur_limbs[15..]) - be_limbs_to_value::(&prev_limbs[15..]) +} + +fn be_limbs_to_value(limbs: &[u16]) -> F { + limbs.iter().fold(F::zero(), |result, &limb| { + result * F::from(1u64 << 16) + F::from(limb as u64) + }) +} diff --git a/zkevm-circuits/src/state_circuit/lookups.rs b/zkevm-circuits/src/state_circuit/lookups.rs new file mode 100644 index 0000000000..d90ee9e670 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/lookups.rs @@ -0,0 +1,104 @@ +use crate::evm_circuit::table::CallContextFieldTag; +use eth_types::Field; +use halo2_proofs::{ + circuit::Layouter, + plonk::{Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, + poly::Rotation, +}; +use std::marker::PhantomData; +use strum::IntoEnumIterator; + +#[derive(Clone, Copy)] +pub struct Config { + // Can these be TableColumn's? + // https://github.com/zcash/halo2/blob/642efc1536d3ea2566b04814bd60a00c4745ae22/halo2_proofs/src/plonk/circuit.rs#L266 + pub u8: Column, + pub u10: Column, + pub u16: Column, + pub call_context_field_tag: Column, +} + +#[derive(Clone)] +pub struct Queries { + pub u8: Expression, + pub u10: Expression, + pub u16: Expression, + pub call_context_field_tag: Expression, +} + +impl Queries { + pub fn new(meta: &mut VirtualCells<'_, F>, c: Config) -> Self { + Self { + u8: meta.query_fixed(c.u8, Rotation::cur()), + u10: meta.query_fixed(c.u10, Rotation::cur()), + u16: meta.query_fixed(c.u16, Rotation::cur()), + call_context_field_tag: meta.query_fixed(c.call_context_field_tag, Rotation::cur()), + } + } +} + +pub struct Chip { + config: Config, + _marker: PhantomData, +} + +impl Chip { + pub fn construct(config: Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure(meta: &mut ConstraintSystem) -> Config { + Config { + u8: meta.fixed_column(), + u10: meta.fixed_column(), + u16: meta.fixed_column(), + call_context_field_tag: meta.fixed_column(), + } + } + + pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + for (column, exponent) in [ + (self.config.u8, 8), + (self.config.u10, 10), + (self.config.u16, 16), + ] { + layouter.assign_region( + || format!("assign u{} fixed column", exponent), + |mut region| { + for i in 0..(1 << exponent) { + region.assign_fixed( + || format!("assign {} in u{} fixed column", i, exponent), + column, + i, + || Ok(F::from(i as u64)), + )?; + } + Ok(()) + }, + )?; + } + layouter.assign_region( + || "assign call_context_field_tags fixed column", + |mut region| { + for field_tag in CallContextFieldTag::iter() { + region.assign_fixed( + || { + format!( + "assign {:?} in call_context_field_tag fixed column", + field_tag + ) + }, + self.config.call_context_field_tag, + field_tag as usize, + || Ok(F::from(field_tag as u64)), + )?; + } + Ok(()) + }, + )?; + Ok(()) + } +} diff --git a/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs b/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs new file mode 100644 index 0000000000..a31b16c726 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs @@ -0,0 +1,178 @@ +use super::N_LIMBS_ACCOUNT_ADDRESS; +use super::N_LIMBS_RW_COUNTER; +use crate::util::Expr; +use eth_types::{Address, Field, ToScalar}; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Region}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, + poly::Rotation, +}; +use itertools::Itertools; +use std::convert::TryInto; +use std::marker::PhantomData; + +pub trait ToLimbs { + fn to_limbs(&self) -> [u16; N]; +} + +impl ToLimbs for Address { + fn to_limbs(&self) -> [u16; 10] { + // address bytes are be.... maybe just have everything be later? + // you will need this in the future later because it makes the key ordering more + // obvious + let le_bytes: Vec<_> = self.0.iter().rev().cloned().collect(); + le_bytes_to_limbs(&le_bytes).try_into().unwrap() + } +} + +impl ToLimbs for u32 { + fn to_limbs(&self) -> [u16; 2] { + le_bytes_to_limbs(&self.to_le_bytes()).try_into().unwrap() + } +} + +#[derive(Clone, Copy)] +pub struct Config +where + T: ToLimbs, +{ + pub value: Column, + // TODO: we can save a column here by not storing the lsb, and then checking that + // value - value_from_limbs(limbs.prepend(0)) fits into a limb. + // Does this apply for RLC's too? + pub limbs: [Column; N], + _marker: PhantomData, +} + +#[derive(Clone)] +pub struct Queries { + pub value: Expression, + pub value_prev: Expression, // move this up, as it's not always needed. + pub limbs: [Expression; N], +} + +impl Queries { + pub fn new>(meta: &mut VirtualCells<'_, F>, c: Config) -> Self { + Self { + value: meta.query_advice(c.value, Rotation::cur()), + value_prev: meta.query_advice(c.value, Rotation::prev()), + limbs: c.limbs.map(|limb| meta.query_advice(limb, Rotation::cur())), + } + } +} + +impl Config { + pub fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + value: Address, + ) -> Result, Error> { + for (i, &limb) in value.to_limbs().iter().enumerate() { + region.assign_advice( + || format!("limb[{}] in address mpi", i), + self.limbs[i], + offset, + || Ok(F::from(limb as u64)), + )?; + } + region.assign_advice( + || "value in u32 mpi", + self.value, + offset, + || Ok(value.to_scalar().unwrap()), // do this better + ) + } +} + +impl Config { + pub fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + value: u32, + ) -> Result, Error> { + for (i, &limb) in value.to_limbs().iter().enumerate() { + region.assign_advice( + || format!("limb[{}] in u32 mpi", i), + self.limbs[i], + offset, + || Ok(F::from(limb as u64)), + )?; + } + region.assign_advice( + || "value in u32 mpi", + self.value, + offset, + || Ok(F::from(value as u64)), + ) + } +} + +pub struct Chip +where + T: ToLimbs, +{ + config: Config, + _marker: PhantomData, +} + +impl Chip +where + T: ToLimbs, +{ + pub fn construct(config: Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure( + meta: &mut ConstraintSystem, + selector: Column, + u16_range: Column, + ) -> Config { + let value = meta.advice_column(); + let limbs = [0; N].map(|_| meta.advice_column()); + + for &limb in &limbs { + meta.lookup_any("mpi limb fits into u16", |meta| { + vec![( + meta.query_advice(limb, Rotation::cur()), + meta.query_fixed(u16_range, Rotation::cur()), + )] + }); + } + meta.create_gate("mpi value matches claimed limbs", |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + let value = meta.query_advice(value, Rotation::cur()); + let limbs = limbs.map(|limb| meta.query_advice(limb, Rotation::cur())); + vec![selector * (value - value_from_limbs(&limbs))] + }); + + Config { + value, + limbs, + _marker: PhantomData, + } + } + + pub fn load(&self, _layouter: &mut impl Layouter) -> Result<(), Error> { + Ok(()) + } +} + +fn le_bytes_to_limbs(bytes: &[u8]) -> Vec { + bytes + .iter() + .tuples() + .map(|(lo, hi)| u16::from_le_bytes([*lo, *hi])) + .collect() +} + +fn value_from_limbs(limbs: &[Expression]) -> Expression { + limbs.iter().rev().fold(0u64.expr(), |result, limb| { + limb.clone() + result * (1u64 << 16).expr() + }) +} diff --git a/zkevm-circuits/src/state_circuit/random_linear_combination.rs b/zkevm-circuits/src/state_circuit/random_linear_combination.rs new file mode 100644 index 0000000000..a26b19a0b0 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/random_linear_combination.rs @@ -0,0 +1,109 @@ +use crate::evm_circuit::util::RandomLinearCombination as RLC; +use eth_types::{Field, ToLittleEndian, U256}; +use halo2_proofs::{ + circuit::{AssignedCell, Layouter, Region}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Instance, VirtualCells}, + poly::Rotation, +}; +use std::marker::PhantomData; + +#[derive(Clone, Debug, Copy)] +pub struct Config { + pub encoded: Column, + // bytes are little endian + pub bytes: [Column; N], +} + +#[derive(Clone)] +pub struct Queries { + pub encoded: Expression, + pub encoded_prev: Expression, + pub bytes: [Expression; N], +} + +impl Queries { + pub fn new(meta: &mut VirtualCells<'_, F>, c: Config) -> Self { + Self { + encoded: meta.query_advice(c.encoded, Rotation::cur()), + encoded_prev: meta.query_advice(c.encoded, Rotation::prev()), + bytes: c.bytes.map(|byte| meta.query_advice(byte, Rotation::cur())), + } + } +} + +impl Config { + pub fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + randomness: F, + value: U256, + ) -> Result, Error> { + let bytes = value.to_le_bytes(); + for (i, &byte) in bytes.iter().enumerate() { + region.assign_advice( + || format!("byte[{}] in rlc", i), + self.bytes[i], + offset, + || Ok(F::from(byte as u64)), + )?; + } + region.assign_advice( + || "encoded value in rlc", + self.encoded, + offset, + || Ok(RLC::random_linear_combine(bytes, randomness)), + ) + } +} + +pub struct Chip { + config: Config, + _marker: PhantomData, +} + +impl Chip { + pub fn construct(config: Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure( + meta: &mut ConstraintSystem, + selector: Column, + u8_lookup: Column, + power_of_randomness: [Column; 31], + ) -> Config { + let encoded = meta.advice_column(); + let bytes = [0; N].map(|_| meta.advice_column()); + + for &byte in &bytes { + meta.lookup_any("rlc bytes fit into u8", |meta| { + let byte = meta.query_advice(byte, Rotation::cur()); + let u8_lookup = meta.query_fixed(u8_lookup, Rotation::cur()); + vec![(byte, u8_lookup)] + }); + } + + meta.create_gate("rlc encoded value matches bytes", |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + let encoded = meta.query_advice(encoded, Rotation::cur()); + let bytes = bytes.map(|c| meta.query_advice(c, Rotation::cur())); + let power_of_randomness: Vec<_> = power_of_randomness + .iter() + .map(|c| meta.query_instance(*c, Rotation::cur())) + .collect(); + vec![ + selector * (encoded - RLC::random_linear_combine_expr(bytes, &power_of_randomness)), + ] + }); + + Config { encoded, bytes } + } + + pub fn load(&self, _layouter: &mut impl Layouter) -> Result<(), Error> { + Ok(()) + } +} diff --git a/zkevm-circuits/src/state_circuit/state.rs b/zkevm-circuits/src/state_circuit/state.rs deleted file mode 100644 index e7231a229e..0000000000 --- a/zkevm-circuits/src/state_circuit/state.rs +++ /dev/null @@ -1,1434 +0,0 @@ -use crate::evm_circuit::{ - table::RwTableTag, - util::{ - constraint_builder::BaseConstraintBuilder, math_gadget::generate_lagrange_base_polynomial, - }, - witness::{RwMap, RwRow}, -}; -use eth_types::Field; -use gadgets::{ - is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, - Variable, -}; -use halo2_proofs::{ - circuit::{Layouter, Region, SimpleFloorPlanner}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, - poly::Rotation, -}; - -/* -(FIXME) Example state table: - -| | | | keys(4) |key2_limbs(8)| key4_bytes(32) | | -| rw_counter | is_write | value | tag | ... | ... | ... | | | | storage_key | -------------------------------------------------------------------------------------------------------- -| 0 | 1 | 0 | 1 | | | | | | | | // init row (write value 0) -| 12 | 1 | 12 | 2 | | | | | | | | -| 24 | 0 | 12 | 2 | | | | | | | | -| 0 | 1 | 0 | 2 | | | | | | | | // init row (write value 0) -| 2 | 0 | 12 | 2 | | | | | | | | -| 3 | 1 | 4 | 3 | | | | | | | | -| 17 | 1 | 32 | 3 | | | | | | | | -| 89 | 0 | 32 | 3 | | | | | | | | -| 48 | 1 | 32 | 3 | | | | | | | | -| 49 | 0 | 32 | 3 | | | | | | | | -| 55 | 1 | 32 | 4 | | | | | | | 5 | // first storage op at the new address has to be write -| 56 | 1 | 33 | 4 | | | | | 8 | -*/ - -// tag: -// 1 - first row of either target (Note: only the first row, not all init rows) -// 2 - memory -// 3 - stack -// 4 - storage - -const EMPTY_TAG: usize = 0; -const START_TAG: usize = 1; -const MEMORY_TAG: usize = RwTableTag::Memory as usize; -const STACK_TAG: usize = RwTableTag::Stack as usize; -const STORAGE_TAG: usize = RwTableTag::AccountStorage as usize; - -const MAX_DEGREE: usize = 15; - -/// A mapping derived from witnessed operations. -#[derive(Clone, Debug)] -pub(crate) struct BusMapping { - rw_counter: Variable, - target: Variable, - is_write: Variable, - address: Variable, - value: Variable, - storage_key: Variable, -} - -#[derive(Clone, Debug)] -pub struct Config< - F: Field, - // When SANITY_CHECK is true, max_address/rw_counter/stack_address are - // required to be in the range of - // MEMORY_ADDRESS_MAX/RW_COUNTER_MAX/STACK_ADDRESS_MAX during circuit - // synthesis - const SANITY_CHECK: bool, - const RW_COUNTER_MAX: usize, - const MEMORY_ADDRESS_MAX: usize, - const STACK_ADDRESS_MAX: usize, - const ROWS_MAX: usize, -> { - s_enable: Column, - rw_counter: Column, - is_write: Column, - keys: [Column; 5], - - // helper column used for IsZero chip - keys_diff_inv: [Column; 5], - - key2_limbs: [Column; 8], - key4_bytes: [Column; 32], - value: Column, - auxs: [Column; 2], - - // helper chips here - key_is_same_with_prev: [IsZeroConfig; 5], - - // range tables here, TODO: organize them to a single struct? - rw_counter_table: Column, - stack_address_table_zero: Column, - memory_address_table_zero: Column, - memory_value_table: Column, -} - -impl< - F: Field, - const SANITY_CHECK: bool, - const RW_COUNTER_MAX: usize, - const MEMORY_ADDRESS_MAX: usize, - const STACK_ADDRESS_MAX: usize, - const ROWS_MAX: usize, - > Config -{ - fn tag(&self) -> Column { - self.keys[0] - } - fn account_addr(&self) -> Column { - self.keys[2] - } - fn address(&self) -> Column { - self.keys[3] - } - fn storage_key(&self) -> Column { - self.keys[4] - } - - /// Set up custom gates and lookup arguments for this configuration. - pub(crate) fn configure(meta: &mut ConstraintSystem) -> Self { - let rw_counter = meta.advice_column(); - let is_write = meta.advice_column(); - let keys = [(); 5].map(|_| meta.advice_column()); - let keys_diff_inv = [(); 5].map(|_| meta.advice_column()); - let key2_limbs = [(); 8].map(|_| meta.advice_column()); - let key4_bytes = [(); 32].map(|_| meta.advice_column()); - let auxs = [(); 2].map(|_| meta.advice_column()); - - let s_enable = meta.fixed_column(); - - let value = meta.advice_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 new_cb = || BaseConstraintBuilder::::new(MAX_DEGREE); - - // alias keys for later use - let tag = keys[0]; - let address = keys[3]; - - let one = Expression::Constant(F::from(1)); - - let q_tag_is = |meta: &mut VirtualCells, tag_value: usize| { - let tag_cur = meta.query_advice(tag, Rotation::cur()); - let all_possible_values = EMPTY_TAG..=STORAGE_TAG; - generate_lagrange_base_polynomial(tag_cur, tag_value, all_possible_values) - }; - let q_memory = |meta: &mut VirtualCells| q_tag_is(meta, MEMORY_TAG); - let q_stack = |meta: &mut VirtualCells| q_tag_is(meta, STACK_TAG); - let q_storage = |meta: &mut VirtualCells| q_tag_is(meta, STORAGE_TAG); - - let key_is_same_with_prev: [IsZeroConfig; 5] = [0, 1, 2, 3, 4].map(|idx| { - IsZeroChip::configure( - meta, - |meta| meta.query_fixed(s_enable, Rotation::cur()), - |meta| { - let value_cur = meta.query_advice(keys[idx], Rotation::cur()); - let value_prev = meta.query_advice(keys[idx], Rotation::prev()); - value_cur - value_prev - }, - keys_diff_inv[idx], - ) - }); - - let q_all_keys_same = |_meta: &mut VirtualCells| { - key_is_same_with_prev[0].is_zero_expression.clone() - * key_is_same_with_prev[1].is_zero_expression.clone() - * key_is_same_with_prev[2].is_zero_expression.clone() - * key_is_same_with_prev[3].is_zero_expression.clone() - * key_is_same_with_prev[4].is_zero_expression.clone() - }; - let q_not_all_keys_same = |meta: &mut VirtualCells| one.clone() - q_all_keys_same(meta); - - ///////////////////////// General constraints ///////////////////////////////// - // Constraints that affect all rows, no matter which Tag they use - meta.create_gate("General constraints", |meta| { - let mut cb = new_cb(); - let s_enable = meta.query_fixed(s_enable, Rotation::cur()); - let is_write = meta.query_advice(is_write, Rotation::cur()); - let is_read = one.clone() - is_write.clone(); - let value_cur = meta.query_advice(value, Rotation::cur()); - let value_prev = meta.query_advice(value, Rotation::prev()); - - // TODO: 0. key0, key1, key3 are in the expected range - - // TODO: 1. key2 is linear combination of 10 x 16bit limbs and also in range - - // TODO: 2. key4 is RLC encoded - - // 3. is_write is boolean - cb.require_boolean("is_write should be boolean", is_write); - - // 4. Keys are sorted in lexicographic order for same Tag - // - // This check also ensures that Tag monotonically increases for all values - // except for Start - // - // When in two consecutive rows the keys are equal in a column: - // - The corresponding keys in the following column must be increasing. - // - // key4 is RLC encoded, so it doesn't keep the order. We use the key4 bytes - // decomposition instead. Since we will use a chain of comparison gadgets, - // we try to merge multiple keys together to reduce the number of required - // gadgets. - - // 6. Read consistency - // When a row is READ - // AND When all the keys are equal in two consecutive a rows: - //- The corresponding value must be equal to the previous row - cb.require_zero( - "if read and keys are same, value should be same with prev", - q_all_keys_same(meta) * is_read * (value_cur - value_prev), - ); - - cb.gate(s_enable) - }); - - // 5. RWC is monotonically strictly increasing for a set of all keys - // - // When tag is not Start and all the keys are equal in two consecutive a rows: - // - The corresponding rwc must be strictly increasing. - // TODO: rewrite using range check gates rather than lookup - meta.lookup_any("rw counter monotonicity", |meta| { - let s_enable = meta.query_fixed(s_enable, 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()); - - vec![( - s_enable * q_all_keys_same(meta) - * (rw_counter - rw_counter_prev - one.clone()), /* - * - 1 because it needs to - * be strictly monotone */ - rw_counter_table, - )] - }); - - ///////////////////////// Memory related constraints ///////////////////////// - - meta.create_gate("Memory operation", |meta| { - let mut cb = new_cb(); - let s_enable = meta.query_fixed(s_enable, Rotation::cur()); - let value_cur = meta.query_advice(value, Rotation::cur()); - let is_write = meta.query_advice(is_write, Rotation::cur()); - let q_read = one.clone() - is_write; - - // 0. Unused keys are 0 - let key2 = meta.query_advice(keys[2], Rotation::cur()); - let key4 = meta.query_advice(keys[4], Rotation::cur()); - cb.require_zero("key2 is 0", key2); - cb.require_zero("key4 is 0", key4); - - // 1. First access for a set of all keys - // - // When the set of all keys changes (first access of an address in a call) - // - If READ, value must be 0 - cb.require_zero( - "if address changes, read value should be 0", - q_not_all_keys_same(meta) * q_read * value_cur, - ); - - cb.gate(s_enable * q_memory(meta)) - }); - - // 2. mem_addr in range - // TODO: rewrite this using range check gates instead of lookup - meta.lookup_any("Memory address in allowed range", |meta| { - let q_memory = q_memory(meta); - let address_cur = meta.query_advice(address, Rotation::cur()); - let memory_address_table_zero = - meta.query_fixed(memory_address_table_zero, Rotation::cur()); - - // s_enable is omitted here deliberately, since `memory_address_table_zero` will - // contain '0', and 'q_memory * address_cur' on unused rows will be 0 too. - vec![(q_memory * address_cur, memory_address_table_zero)] - }); - - // 3. value is a byte - // Memory value is in the allowed range. - meta.lookup_any("Memory value in allowed range", |meta| { - let q_memory = q_memory(meta); - let value = meta.query_advice(value, Rotation::cur()); - let memory_value_table = meta.query_fixed(memory_value_table, Rotation::cur()); - - vec![(q_memory * value, memory_value_table)] - }); - - ///////////////////////// Stack related constraints ///////////////////////// - - meta.create_gate("Stack operation", |meta| { - let mut cb = new_cb(); - - let s_enable = meta.query_fixed(s_enable, Rotation::cur()); - let is_write = meta.query_advice(is_write, Rotation::cur()); - let q_read = one.clone() - is_write; - let key2 = meta.query_advice(keys[2], Rotation::cur()); - let key4 = meta.query_advice(keys[4], Rotation::cur()); - - // 0. Unused keys are 0 - cb.require_zero("key2 is 0", key2); - cb.require_zero("key4 is 0", key4); - - // 1. First access for a set of all keys - // - // The first stack operation in a stack position is always a write (can't - // read if it isn't written before) - // - // When the set of all keys changes (first access of a stack position in a call) - // - It must be a WRITE - cb.require_zero( - "if address changes, operation is always a write", - q_not_all_keys_same(meta) * q_read, - ); - cb.gate(s_enable * q_stack(meta)) - }); - - // 2. stack_ptr in range - meta.lookup_any("Stack address in allowed range", |meta| { - let q_stack = q_stack(meta); - let address_cur = meta.query_advice(address, Rotation::cur()); - let stack_address_table_zero = - meta.query_fixed(stack_address_table_zero, Rotation::cur()); - - vec![(q_stack * address_cur, stack_address_table_zero)] - }); - - // 3. stack_ptr only increases by 0 or 1 - meta.create_gate("Stack pointer diff be 0 or 1", |meta| { - let mut cb = new_cb(); - let s_enable = meta.query_fixed(s_enable, Rotation::cur()); - let q_stack = q_stack(meta); - let tag_is_same_with_prev = key_is_same_with_prev[0].is_zero_expression.clone(); - let call_id_same_with_prev = key_is_same_with_prev[1].is_zero_expression.clone(); - let stack_ptr = meta.query_advice(keys[3], Rotation::cur()); - let stack_ptr_prev = meta.query_advice(keys[3], Rotation::prev()); - cb.require_boolean( - "stack pointer only increases by 0 or 1", - stack_ptr - stack_ptr_prev, - ); - cb.gate(s_enable * q_stack * tag_is_same_with_prev * call_id_same_with_prev) - }); - - ///////////////////////// Storage related constraints ///////////////////////// - - meta.create_gate("Storage Operation", |meta| { - let mut cb = new_cb(); - let q_storage = q_storage(meta); - - let is_write = meta.query_advice(is_write, Rotation::cur()); - let q_read = one.clone() - is_write; - let s_enable = meta.query_fixed(s_enable, Rotation::cur()); - let rw_counter = meta.query_advice(rw_counter, Rotation::cur()); - let key1 = meta.query_advice(keys[1], Rotation::cur()); - let key3 = meta.query_advice(keys[3], Rotation::cur()); - - // TODO: cold VS warm - // TODO: connection to MPT on first and last access for each (address, key) - - // 0. Unused keys are 0 - cb.require_zero("key1 is 0", key1); - cb.require_zero("key3 is 0", key3); - - // 1. First access for a set of all keys - // - // We add an extra write to set the value of the state in previous block, with - // rwc=0. - // - // When the set of all keys changes (first access of storage (address, key)) - // - It must be a WRITE - cb.require_zero( - "First access for storage is write", - q_not_all_keys_same(meta) * q_read, - ); - cb.require_zero( - "First access for storage has rw_counter as 0", - q_not_all_keys_same(meta) * rw_counter, - ); - - cb.gate(s_enable * q_storage) - }); - - Config { - rw_counter, - value, - is_write, - keys, - keys_diff_inv, - key2_limbs, - key4_bytes, - auxs, - s_enable, - key_is_same_with_prev, - rw_counter_table, - memory_address_table_zero, - stack_address_table_zero, - memory_value_table, - } - } - - /// Load lookup table / other fixed constants for this configuration. - pub(crate) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - layouter.assign_region( - || "rw counter table", - |mut region| { - for idx in 0..=RW_COUNTER_MAX { - region.assign_fixed( - || "rw counter table", - self.rw_counter_table, - idx, - || Ok(F::from(idx as u64)), - )?; - } - Ok(()) - }, - )?; - - layouter.assign_region( - || "memory value table", - |mut region| { - for idx in 0..=255 { - region.assign_fixed( - || "memory value table", - self.memory_value_table, - idx, - || Ok(F::from(idx as u64)), - )?; - } - Ok(()) - }, - )?; - - layouter.assign_region( - || "memory address table with zero", - |mut region| { - for idx in 0..=MEMORY_ADDRESS_MAX { - region.assign_fixed( - || "address table with zero", - self.memory_address_table_zero, - idx, - || Ok(F::from(idx as u64)), - )?; - } - Ok(()) - }, - )?; - - layouter.assign_region( - || "stack address table with zero", - |mut region| { - for idx in 0..=STACK_ADDRESS_MAX { - region.assign_fixed( - || "stack address table with zero", - self.stack_address_table_zero, - idx, - || Ok(F::from(idx as u64)), - )?; - } - Ok(()) - }, - ) - } - - /// Assign cells. - pub(crate) fn assign( - &self, - mut layouter: impl Layouter, - randomness: F, - rw_map: &RwMap, - ) -> Result<(), Error> { - let key_is_same_with_prev_chips: [IsZeroChip; 5] = [0, 1, 2, 3, 4] - .map(|idx| IsZeroChip::construct(self.key_is_same_with_prev[idx].clone())); - - layouter.assign_region( - || "State operations", - |mut region| { - // TODO: a "START_TAG" row should be inserted before all other rows in the final - // implmentation. Here we start from 1 to prevent some - // col.prev() problems since blinding rows are unavailable for constaints. - let mut offset = 1; - - let mut rows: Vec> = [ - RwTableTag::Memory, - RwTableTag::Stack, - RwTableTag::AccountStorage, - ] - .iter() - .map(|tag| { - rw_map.0[tag] - .iter() - .map(|rw| rw.table_assignment(randomness)) - }) - .flatten() - .collect(); - rows.sort_by_key(|rw| (rw.tag, rw.key1, rw.key2, rw.key3, rw.key4, rw.rw_counter)); - - if rows.len() >= ROWS_MAX { - panic!("too many storage operations"); - } - for (index, row) in rows.iter().enumerate() { - let row_prev = if index == 0 { - RwRow::default() - } else { - rows[index - 1] - }; - self.assign_row( - &mut region, - offset, - *row, - row_prev, - &key_is_same_with_prev_chips, - )?; - offset += 1; - } - - Ok(()) - }, - ) - } - - fn assign_row( - &self, - region: &mut Region<'_, F>, - offset: usize, - row: RwRow, - row_prev: RwRow, - diff_is_zero_chips: &[IsZeroChip; 5], - ) -> Result<(), Error> { - let address = row.key3; - let rw_counter = row.rw_counter; - let value = row.value; - let is_write = row.is_write; - - // check witness sanity - if offset > ROWS_MAX { - panic!("too many storage operations"); - } - if SANITY_CHECK { - if rw_counter > F::from(RW_COUNTER_MAX as u64) { - panic!("rw_counter out of range"); - } - if row.tag == F::from(STACK_TAG as u64) && address > F::from(STACK_ADDRESS_MAX as u64) { - panic!( - "stack address out of range {:?} > {}", - address, STACK_ADDRESS_MAX - ); - } - if row.tag == F::from(MEMORY_TAG as u64) && address > F::from(MEMORY_ADDRESS_MAX as u64) - { - panic!( - "memory address out of range {:?} > {}", - address, MEMORY_ADDRESS_MAX - ); - } - } - region.assign_fixed(|| "enable row", self.s_enable, offset, || Ok(F::one()))?; - region.assign_advice(|| "rw counter", self.rw_counter, offset, || Ok(rw_counter))?; - region.assign_advice(|| "value", self.value, offset, || Ok(value))?; - region.assign_advice(|| "is_write", self.is_write, offset, || Ok(is_write))?; - - for (i, diff_is_zero_chip) in diff_is_zero_chips.iter().enumerate() { - let (value, diff) = match i { - // FIXME: find a better way here - 0 => (row.tag, row.tag - row_prev.tag), - 1 => (row.key1, row.key1 - row_prev.key1), - 2 => (row.key2, row.key2 - row_prev.key2), - 3 => (row.key3, row.key3 - row_prev.key3), - 4 => (row.key4, row.key4 - row_prev.key4), - _ => unreachable!(), - }; - region.assign_advice( - || format!("assign key{}", i), - self.keys[i], - offset, - || Ok(value), - )?; - diff_is_zero_chip.assign(region, offset, Some(diff))?; - } - - region.assign_advice(|| "aux1", self.auxs[0], offset, || Ok(row.aux1))?; - region.assign_advice(|| "aux2", self.auxs[1], offset, || Ok(row.aux2))?; - - Ok(()) - } -} - -/// State Circuit struct. -#[derive(Default)] -pub struct StateCircuit< - F: Field, - const SANITY_CHECK: bool, - const RW_COUNTER_MAX: usize, - const MEMORY_ADDRESS_MAX: usize, - const STACK_ADDRESS_MAX: usize, - const ROWS_MAX: usize, -> { - /// randomness used in linear combination - pub randomness: F, - /// witness for rw map - pub rw_map: RwMap, -} - -impl< - F: Field, - const SANITY_CHECK: bool, - const RW_COUNTER_MAX: usize, - const MEMORY_ADDRESS_MAX: usize, - const STACK_ADDRESS_MAX: usize, - const ROWS_MAX: usize, - > - StateCircuit -{ - /// Use rw_map to build a StateCircuit instance - pub fn new(randomness: F, rw_map: &RwMap) -> Self { - Self { - randomness, - rw_map: rw_map.clone(), - } - } -} - -impl< - F: Field, - const SANITY_CHECK: bool, - const RW_COUNTER_MAX: usize, - const MEMORY_ADDRESS_MAX: usize, - const STACK_ADDRESS_MAX: usize, - const ROWS_MAX: usize, - > Circuit - for StateCircuit< - F, - SANITY_CHECK, - RW_COUNTER_MAX, - MEMORY_ADDRESS_MAX, - STACK_ADDRESS_MAX, - ROWS_MAX, - > -{ - type Config = - Config; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - Config::configure(meta) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - config.load(&mut layouter)?; - config.assign(layouter, self.randomness, &self.rw_map)?; - - Ok(()) - } -} -#[cfg(test)] -mod tests { - use super::*; - use bus_mapping::mock::BlockData; - use bus_mapping::operation::{ - MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, - }; - use eth_types::{ - address, bytecode, - evm_types::{MemoryAddress, StackAddress}, - geth_types::GethData, - Word, - }; - use halo2_proofs::arithmetic::BaseExt; - use halo2_proofs::dev::MockProver; - use halo2_proofs::pairing::bn256::Fr; - use mock::TestContext; - - 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 rw_map = RwMap::from(&OperationContainer { - memory: $memory_ops, - stack: $stack_ops, - storage: $storage_ops, - ..Default::default() - }); - let circuit = StateCircuit::< - Fr, - true, - $rw_counter_max, - $memory_address_max, - $stack_address_max, - { $memory_rows_max + $stack_rows_max + $storage_rows_max }, - >::new(Fr::rand(), &rw_map); - - let prover = MockProver::::run($k, &circuit, vec![]).unwrap(); - let verify_result = prover.verify(); - assert!(verify_result.is_ok(), "verify err: {:#?}", verify_result); - }}; - } - - macro_rules! test_state_circuit_error { - ($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 rw_map = RwMap::from(&OperationContainer { - memory: $memory_ops, - stack: $stack_ops, - storage: $storage_ops, - ..Default::default() - }); - let circuit = StateCircuit::< - Fr, - false, - $rw_counter_max, - $memory_address_max, - $stack_address_max, - { $memory_rows_max + $stack_rows_max + $storage_rows_max }, - >::new(Fr::rand(), &rw_map); - - let prover = MockProver::::run($k, &circuit, vec![]).unwrap(); - assert!(prover.verify().is_err()); - }}; - } - - #[test] - fn state_circuit_simple() { - let memory_op_0 = Operation::new( - RWCounter::from(12), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - let memory_op_1 = Operation::new( - RWCounter::from(24), - RW::READ, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - - let memory_op_2 = Operation::new( - RWCounter::from(17), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(1), 32), - ); - let memory_op_3 = Operation::new( - RWCounter::from(87), - RW::READ, - MemoryOp::new(1, MemoryAddress::from(1), 32), - ); - - let stack_op_0 = Operation::new( - RWCounter::from(17), - RW::WRITE, - StackOp::new(1, StackAddress::from(1), Word::from(32)), - ); - let stack_op_1 = Operation::new( - RWCounter::from(87), - RW::READ, - StackOp::new(1, StackAddress::from(1), Word::from(32)), - ); - - let storage_op_0 = Operation::new( - RWCounter::from(0), - RW::WRITE, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - let storage_op_1 = Operation::new( - RWCounter::from(18), - RW::WRITE, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::from(32), - 1usize, - Word::from(32), - ), - ); - let storage_op_2 = Operation::new( - RWCounter::from(19), - RW::WRITE, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::from(32), - 1usize, - Word::from(32), - ), - ); - - test_state_circuit_ok!( - 12, - 2000, - 100, - 2, - 100, - 1023, - 1000, - vec![memory_op_0, memory_op_1, memory_op_2, memory_op_3], - vec![stack_op_0, stack_op_1], - vec![storage_op_0, storage_op_1, storage_op_2], - Ok(()) - ); - } - - #[test] - fn no_stack_padding() { - let memory_op_0 = Operation::new( - RWCounter::from(12), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - let memory_op_1 = Operation::new( - RWCounter::from(24), - RW::READ, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - - let memory_op_2 = Operation::new( - RWCounter::from(17), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(1), 32), - ); - let memory_op_3 = Operation::new( - RWCounter::from(87), - RW::READ, - MemoryOp::new(1, MemoryAddress::from(1), 32), - ); - - let stack_op_0 = Operation::new( - RWCounter::from(17), - RW::WRITE, - StackOp::new(1, StackAddress::from(1), Word::from(32)), - ); - let stack_op_1 = Operation::new( - RWCounter::from(87), - RW::READ, - StackOp::new(1, StackAddress::from(1), Word::from(32)), - ); - - const STACK_ROWS_MAX: usize = 2; - test_state_circuit_ok!( - 14, - 2000, - 100, - STACK_ROWS_MAX, - 100, - 1023, - 1000, - vec![memory_op_0, memory_op_1, memory_op_2, memory_op_3], - vec![stack_op_0, stack_op_1], - vec![], - Ok(()) - ); - } - - #[test] - fn same_address_read() { - let memory_op_0 = Operation::new( - RWCounter::from(12), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(0), 31), - ); - let memory_op_1 = Operation::new( - RWCounter::from(24), - RW::READ, - MemoryOp::new( - 1, - MemoryAddress::from(0), - 32, - /* This should fail as it not the same value as in previous - * write op */ - ), - ); - - let stack_op_0 = Operation::new( - RWCounter::from(19), - RW::WRITE, - StackOp::new(1, StackAddress::from(0), Word::from(12)), - ); - let stack_op_1 = Operation::new( - RWCounter::from(28), - RW::READ, - StackOp::new( - 1, - StackAddress::from(0), - Word::from(13), - /* This should fail as it not the same value as in previous - * write op */ - ), - ); - - const MEMORY_ROWS_MAX: usize = 7; - test_state_circuit_error!( - 14, - 2000, - MEMORY_ROWS_MAX, - 1000, - 100, - 1023, - 1000, - vec![memory_op_0, memory_op_1], - vec![stack_op_0, stack_op_1], - vec![] - ); - } - - #[test] - fn first_write() { - let stack_op_0 = Operation::new( - RWCounter::from(28), - RW::READ, - StackOp::new(1, StackAddress::from(0), Word::from(13)), - ); - - let storage_op_0 = Operation::new( - RWCounter::from(17), - RW::READ, - StorageOp::new( - /* Fails because the first storage op needs to be - * write. */ - address!("0x0000000000000000000000000000000000000002"), - Word::from(0x40), - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - let storage_op_1 = Operation::new( - RWCounter::from(18), - RW::READ, - StorageOp::new( - /* Fails because when storage key changes, the op - * needs to be write. */ - address!("0x0000000000000000000000000000000000000002"), - Word::from(0x41), - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - - let storage_op_2 = Operation::new( - RWCounter::from(19), - RW::READ, - StorageOp::new( - /* Fails because when address changes, the op - * needs to be write. */ - address!("0x0000000000000000000000000000000000000003"), - Word::from(0x40), - /* Intentionally different storage key as the last one in the previous ops to - have two conditions met. */ - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - - const MEMORY_ROWS_MAX: usize = 2; - const STORAGE_ROWS_MAX: usize = 2; - test_state_circuit_error!( - 14, - 2000, - MEMORY_ROWS_MAX, - 1000, - STORAGE_ROWS_MAX, - 1023, - 1000, - vec![], - vec![stack_op_0], - vec![storage_op_0, storage_op_1, storage_op_2] - ); - } - - #[test] - fn max_values() { - let memory_op_0 = Operation::new( - RWCounter::from(12), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), - ); - let memory_op_1 = Operation::new( - RWCounter::from(RW_COUNTER_MAX), - RW::READ, - MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), - ); - let memory_op_2 = Operation::new( - RWCounter::from(RW_COUNTER_MAX + 1), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), - ); - - let memory_op_3 = Operation::new( - RWCounter::from(12), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), 32), - ); - let memory_op_4 = Operation::new( - RWCounter::from(24), - RW::READ, - MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), 32), - ); - - let stack_op_0 = Operation::new( - RWCounter::from(12), - RW::WRITE, - StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX), Word::from(12)), - ); - let stack_op_1 = Operation::new( - RWCounter::from(24), - RW::READ, - StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX), Word::from(12)), - ); - - let stack_op_2 = Operation::new( - RWCounter::from(17), - RW::WRITE, - StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), - ); - let stack_op_3 = Operation::new( - RWCounter::from(RW_COUNTER_MAX + 1), - RW::WRITE, - StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), - ); - - // Small MEMORY_MAX_ROWS is set to avoid having padded rows (all padded - // rows would fail because of the address they would have - the - // address of the last unused row) - const MEMORY_ROWS_MAX: usize = 7; - const STACK_ROWS_MAX: usize = 7; - const STORAGE_ROWS_MAX: usize = 7; - const RW_COUNTER_MAX: usize = 60000; - const MEMORY_ADDRESS_MAX: usize = 100; - const STACK_ADDRESS_MAX: usize = 1023; - - test_state_circuit_error!( - 16, - RW_COUNTER_MAX, - MEMORY_ROWS_MAX, - MEMORY_ADDRESS_MAX, - STACK_ROWS_MAX, - STACK_ADDRESS_MAX, - STORAGE_ROWS_MAX, - vec![ - memory_op_0, - memory_op_1, - memory_op_2, - memory_op_3, - memory_op_4 - ], - vec![stack_op_0, stack_op_1, stack_op_2, stack_op_3], - vec![] - ); - } - - #[test] - fn max_values_first_row() { - // first row of a target needs to be checked for address to be in range - // too - let memory_op_0 = Operation::new( - RWCounter::from(12), - RW::WRITE, - MemoryOp::new( - 1, - MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), - // This address is not in the allowed range - 32, - ), - ); - - let stack_op_0 = Operation::new( - RWCounter::from(12), - RW::WRITE, - StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), - ); - let stack_op_1 = Operation::new( - RWCounter::from(24), - RW::READ, - StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), - ); - - // Small MEMORY_MAX_ROWS is set to avoid having padded rows (all padded - // rows would fail because of the address they would have - the - // address of the last unused row) - const MEMORY_ROWS_MAX: usize = 2; - const STACK_ROWS_MAX: usize = 2; - const STORAGE_ROWS_MAX: usize = 2; - const RW_COUNTER_MAX: usize = 60000; - const MEMORY_ADDRESS_MAX: usize = 100; - const STACK_ADDRESS_MAX: usize = 1023; - - test_state_circuit_error!( - 16, - RW_COUNTER_MAX, - MEMORY_ROWS_MAX, - MEMORY_ADDRESS_MAX, - STACK_ROWS_MAX, - STACK_ADDRESS_MAX, - STORAGE_ROWS_MAX, - vec![memory_op_0], - vec![stack_op_0, stack_op_1], - vec![] - ); - } - - #[test] - fn non_monotone_rw_counter() { - let memory_op_0 = Operation::new( - RWCounter::from(1352), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - let memory_op_1 = Operation::new( - RWCounter::from(1255), - RW::READ, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - - // fails because it needs to be strictly monotone - let memory_op_2 = Operation::new( - RWCounter::from(1255), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - - let stack_op_0 = Operation::new( - RWCounter::from(228), - RW::WRITE, - StackOp::new(1, StackAddress::from(1), Word::from(12)), - ); - let stack_op_1 = Operation::new( - RWCounter::from(217), - RW::READ, - StackOp::new(1, StackAddress::from(1), Word::from(12)), - ); - let stack_op_2 = Operation::new( - RWCounter::from(217), - RW::READ, - StackOp::new(1, StackAddress::from(1), Word::from(12)), - ); - - let storage_op_0 = Operation::new( - RWCounter::from(301), - RW::WRITE, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - let storage_op_1 = Operation::new( - RWCounter::from(302), - RW::READ, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - let storage_op_2 = Operation::new( - RWCounter::from(302), - RW::READ, - StorageOp::new( - /*fails because the address and - * storage key are the same as in - * the previous row */ - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - let storage_op_3 = Operation::new( - RWCounter::from(297), - RW::WRITE, - StorageOp::new( - // Global counter goes down, but it doesn't fail because - // the storage key is not the same as in the previous row. - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x41), - Word::from(32), - Word::from(32), - 1usize, - Word::from(32), - ), - ); - - let storage_op_4 = Operation::new( - RWCounter::from(296), - RW::WRITE, - StorageOp::new( - // Global counter goes down, but it doesn't fail because the - // address is not the same as in the previous row (while the - // storage key is). - address!("0x0000000000000000000000000000000000000002"), - Word::from(0x41), - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - - const MEMORY_ROWS_MAX: usize = 100; - const STACK_ROWS_MAX: usize = 100; - test_state_circuit_error!( - 15, - 10000, - MEMORY_ROWS_MAX, - 10000, - STACK_ROWS_MAX, - 1023, - 1000, - vec![memory_op_0, memory_op_1, memory_op_2], - vec![stack_op_0, stack_op_1, stack_op_2], - vec![ - storage_op_0, - storage_op_1, - storage_op_2, - storage_op_3, - storage_op_4 - ] - ); - } - - #[ignore = "disabled temporarily since we sort rws inside the assign method. FIXME later"] - #[test] - fn non_monotone_address() { - let memory_op_0 = Operation::new( - RWCounter::from(1352), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - let memory_op_1 = Operation::new( - RWCounter::from(1255), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(1), 32), - ); - - // fails because it's not monotone - let memory_op_2 = Operation::new( - RWCounter::from(1255), - RW::WRITE, - MemoryOp::new(1, MemoryAddress::from(0), 32), - ); - - let stack_op_0 = Operation::new( - RWCounter::from(228), - RW::WRITE, - StackOp::new(1, StackAddress::from(0), Word::from(12)), - ); - let stack_op_1 = Operation::new( - RWCounter::from(229), - RW::WRITE, - StackOp::new(1, StackAddress::from(1), Word::from(12)), - ); - let stack_op_2 = Operation::new( - RWCounter::from(230), - RW::WRITE, - StackOp::new( - 1, - StackAddress::from(0), /* this fails because the - * address is not - * monotone */ - Word::from(12), - ), - ); - - const MEMORY_ROWS_MAX: usize = 10; - test_state_circuit_error!( - 14, - 10000, - MEMORY_ROWS_MAX, - 10000, - 10, - 1023, - 1000, - vec![memory_op_0, memory_op_1, memory_op_2], - vec![stack_op_0, stack_op_1, stack_op_2], - vec![] - ); - } - - #[test] - fn storage() { - let storage_op_0 = Operation::new( - RWCounter::from(18), - RW::WRITE, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::zero(), - 1usize, - Word::zero(), - ), - ); - let storage_op_1 = Operation::new( - RWCounter::from(19), - RW::READ, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(33), /* Fails because it is READ op - * and not the same - * value as in the previous - * row. */ - Word::zero(), - 1usize, - Word::zero(), - ), - ); - let storage_op_2 = Operation::new( - RWCounter::from(20), - RW::WRITE, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::zero(), /* Fails because not the same - * as value in the previous row - note: this - * is WRITE. */ - 1usize, - Word::zero(), - ), - ); - let storage_op_3 = Operation::new( - RWCounter::from(21), - RW::READ, - StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), - Word::from(0x40), - Word::from(32), - Word::from(1), /* Fails because not the same - * as value_prev in the previous row - note: - * this is READ. */ - 1usize, - Word::from(1), - ), - ); - - const MEMORY_ROWS_MAX: usize = 2; - const STORAGE_ROWS_MAX: usize = 2; - test_state_circuit_error!( - 14, - 2000, - MEMORY_ROWS_MAX, - 1000, - STORAGE_ROWS_MAX, - 1023, - 1000, - vec![], - vec![], - vec![storage_op_0, storage_op_1, storage_op_2, storage_op_3] - ); - } - - #[test] - fn trace() { - let bytecode = bytecode! { - PUSH1(0x80) - PUSH1(0x40) - MSTORE - PUSH1(0x40) - MLOAD - STOP - }; - - // Create a custom tx setting Gas to - let block: GethData = TestContext::<2, 1>::new( - None, - |accs| { - accs[0] - .address(address!("0x0000000000000000000000000000000000000010")) - .balance(Word::from(1u64 << 20)) - .code(bytecode); - accs[1] - .address(address!("0x0000000000000000000000000000000000000000")) - .balance(Word::from(1u64 << 20)); - }, - |mut txs, accs| { - txs[0].to(accs[0].address).from(accs[1].address); - }, - |block, _tx| block.number(0xcafeu64), - ) - .unwrap() - .into(); - - let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .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_ok!( - 14, - 2000, - 100, - 0x80, - 100, - 1023, - 1000, - memory_ops, - stack_ops, - storage_ops, - Ok(()) - ); - } -} diff --git a/zkevm-circuits/src/state_circuit/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs new file mode 100644 index 0000000000..d4a0b97ab9 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -0,0 +1,247 @@ +mod tests { + use super::super::StateCircuit; + use crate::evm_circuit::table::AccountFieldTag; + use crate::evm_circuit::witness::{Rw, RwMap}; + use bus_mapping::operation::{ + MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, + }; + use eth_types::{ + evm_types::{MemoryAddress, StackAddress}, + Address, ToAddress, Word, U256, + }; + use halo2_proofs::{ + arithmetic::BaseExt, + dev::{MockProver, VerifyFailure}, + pairing::bn256::Fr, + plonk::{Circuit, ConstraintSystem}, + }; + + fn test_state_circuit_ok( + memory_ops: Vec>, + stack_ops: Vec>, + storage_ops: Vec>, + ) { + let rw_map = RwMap::from(&OperationContainer { + memory: memory_ops, + stack: stack_ops, + storage: storage_ops, + ..Default::default() + }); + + let randomness = Fr::rand(); + let circuit = StateCircuit::new(randomness, rw_map); + let power_of_randomness = circuit.instance(); + + let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); + let verify_result = prover.verify(); + assert_eq!(verify_result, Ok(())); + } + + #[test] + fn degree() { + let mut meta = ConstraintSystem::::default(); + StateCircuit::configure(&mut meta); + assert_eq!(meta.degree(), 19); + } + + #[test] + fn state_circuit_simple_2() { + let memory_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let memory_op_1 = Operation::new( + RWCounter::from(24), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + + let memory_op_2 = Operation::new( + RWCounter::from(17), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(1), 32), + ); + let memory_op_3 = Operation::new( + RWCounter::from(87), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(1), 32), + ); + + let stack_op_0 = Operation::new( + RWCounter::from(17), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(32)), + ); + let stack_op_1 = Operation::new( + RWCounter::from(87), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(32)), + ); + + let storage_op_0 = Operation::new( + RWCounter::from(0), + RW::WRITE, + StorageOp::new( + U256::from(100).to_address(), + Word::from(0x40), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + let storage_op_1 = Operation::new( + RWCounter::from(18), + RW::WRITE, + StorageOp::new( + U256::from(100).to_address(), + Word::from(0x40), + Word::from(32), + Word::from(32), + 1usize, + Word::from(32), + ), + ); + let storage_op_2 = Operation::new( + RWCounter::from(19), + RW::WRITE, + StorageOp::new( + U256::from(100).to_address(), + Word::from(0x40), + Word::from(32), + Word::from(32), + 1usize, + Word::from(32), + ), + ); + + test_state_circuit_ok( + vec![memory_op_0, memory_op_1, memory_op_2, memory_op_3], + vec![stack_op_0, stack_op_1], + vec![storage_op_0, storage_op_1, storage_op_2], + ); + } + + #[test] + fn state_circuit_simple_6() { + let memory_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let memory_op_1 = Operation::new( + RWCounter::from(13), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let storage_op_2 = Operation::new( + RWCounter::from(19), + RW::WRITE, + StorageOp::new( + U256::from(100).to_address(), + Word::from(0x40), + Word::from(32), + Word::from(32), + 1usize, + Word::from(32), + ), + ); + test_state_circuit_ok(vec![memory_op_0, memory_op_1], vec![], vec![storage_op_2]); + } + + #[test] + fn lexicographic_ordering_test_1() { + let memory_op = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let storage_op = Operation::new( + RWCounter::from(19), + RW::WRITE, + StorageOp::new( + U256::from(100).to_address(), + Word::from(0x40), + Word::from(32), + Word::from(32), + 1usize, + Word::from(32), + ), + ); + test_state_circuit_ok(vec![memory_op], vec![], vec![storage_op]); + } + + #[test] + fn lexicographic_ordering_test_2() { + let memory_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let memory_op_1 = Operation::new( + RWCounter::from(13), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + test_state_circuit_ok(vec![memory_op_0, memory_op_1], vec![], vec![]); + } + + #[test] + fn first_access_for_stack_is_write() { + let rows = vec![ + Rw::Stack { + rw_counter: 24, + is_write: true, + call_id: 1, + stack_pointer: 1022, + value: U256::from(394500u64), + }, + Rw::Stack { + rw_counter: 25, + is_write: false, + call_id: 1, + stack_pointer: 1022, + value: U256::from(394500u64), + }, + ]; + + assert_eq!(verify(rows), Ok(())); + } + + #[test] + fn diff_1_problem_repro() { + let rows = vec![ + Rw::Account { + rw_counter: 1, + is_write: true, + account_address: Address::default(), + field_tag: AccountFieldTag::CodeHash, + value: U256::zero(), + value_prev: U256::zero(), + }, + Rw::Account { + rw_counter: 2, + is_write: true, + account_address: Address::default(), + field_tag: AccountFieldTag::CodeHash, + value: U256::zero(), + value_prev: U256::zero(), + }, + ]; + + assert_eq!(verify(rows), Ok(())); + } + + fn verify(rows: Vec) -> Result<(), Vec> { + let n_rows = rows.len(); + + let randomness = Fr::rand(); + let circuit = StateCircuit { randomness, rows }; + let power_of_randomness = circuit.instance(); + + MockProver::::run(17, &circuit, power_of_randomness) + .unwrap() + .verify_at_rows(0..n_rows, 0..n_rows) + } +} diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index cbcd2a9852..b953f7150a 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -7,6 +7,7 @@ use eth_types::geth_types::GethData; use halo2_proofs::dev::{MockProver, VerifyFailure}; use halo2_proofs::pairing::bn256::Fr; use mock::TestContext; +use strum::IntoEnumIterator; #[cfg(test)] #[ctor::ctor] @@ -35,7 +36,7 @@ pub fn get_fixed_table(conf: FixedTableConfig) -> Vec { FixedTableTag::ResponsibleOpcode, ] } - FixedTableConfig::Complete => FixedTableTag::iterator().collect(), + FixedTableConfig::Complete => FixedTableTag::iter().collect(), } } @@ -85,16 +86,13 @@ pub fn test_circuits_using_witness_block( } // 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 + // TODO: 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 state_circuit = - StateCircuit::::new(block.randomness, &block.rws); - let prover = MockProver::::run(12, &state_circuit, vec![]).unwrap(); - prover.verify()?; + let state_circuit = StateCircuit::new(block.randomness, block.rws); + let power_of_randomness = state_circuit.instance(); + let prover = MockProver::::run(18, &state_circuit, power_of_randomness).unwrap(); + prover.verify_at_rows(0..state_circuit.rows.len(), 0..state_circuit.rows.len())? } Ok(())