From e1bc6d8c5d88f90e5ec5cc534f498a5a81edab47 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 4 Apr 2022 21:35:54 -0400 Subject: [PATCH 01/31] Refactor StateCircuit + add lexicographic ordering constraints --- Cargo.lock | 33 + prover/src/compute_proof.rs | 9 +- zkevm-circuits/Cargo.toml | 2 + zkevm-circuits/src/evm_circuit.rs | 4 +- .../src/evm_circuit/execution/end_tx.rs | 3 +- zkevm-circuits/src/evm_circuit/table.rs | 71 +- .../evm_circuit/util/constraint_builder.rs | 20 +- zkevm-circuits/src/evm_circuit/witness.rs | 431 +++++------ zkevm-circuits/src/state_circuit.rs | 245 ++++++- .../src/state_circuit/constraint_builder.rs | 310 ++++++++ .../state_circuit/lexicographic_ordering.rs | 296 ++++++++ zkevm-circuits/src/state_circuit/lookups.rs | 111 +++ .../multiple_precision_integer.rs | 178 +++++ .../random_linear_combination.rs | 109 +++ .../{state.rs => state_tests.rs} | 683 +++++++++--------- zkevm-circuits/src/state_circuit/tests.rs | 185 +++++ zkevm-circuits/src/test_util.rs | 20 +- 17 files changed, 2040 insertions(+), 670 deletions(-) create mode 100644 zkevm-circuits/src/state_circuit/constraint_builder.rs create mode 100644 zkevm-circuits/src/state_circuit/lexicographic_ordering.rs create mode 100644 zkevm-circuits/src/state_circuit/lookups.rs create mode 100644 zkevm-circuits/src/state_circuit/multiple_precision_integer.rs create mode 100644 zkevm-circuits/src/state_circuit/random_linear_combination.rs rename zkevm-circuits/src/state_circuit/{state.rs => state_tests.rs} (69%) create mode 100644 zkevm-circuits/src/state_circuit/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 174df5affa..9bccb3543b 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" @@ -3042,6 +3048,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 +3399,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 +4127,6 @@ dependencies = [ "rand_xorshift", "serde_json", "sha3 0.7.3", + "strum", + "strum_macros", ] diff --git a/prover/src/compute_proof.rs b/prover/src/compute_proof.rs index 3d43a86498..aa5ce91d20 100644 --- a/prover/src/compute_proof.rs +++ b/prover/src/compute_proof.rs @@ -69,14 +69,7 @@ pub async fn compute_proof( 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..d5f0e7bd51 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -141,7 +141,7 @@ impl EvmCircuit { #[cfg(any(feature = "test", test))] pub mod test { - + use strum::IntoEnumIterator; use crate::{ evm_circuit::{ table::FixedTableTag, @@ -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/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/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 11ad21eb45..fa8f60b1d2 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::{EnumIter, EnumCount}; 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,9 +132,10 @@ 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, + Memory, Stack, AccountStorage, TxAccessListAccount, @@ -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..c59f05ec2e 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(), diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index b73f2d665f..6786b752b6 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; @@ -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,203 @@ 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, .. } => *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, .. } => *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, + } + } + + 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, .. } => 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::CallContext { .. } | Self::TxRefund { .. } | Self::TxLog { .. } => 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::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 { .. } => 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, .. } => 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 { .. } => 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..2ad4125cb4 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -1,4 +1,245 @@ //! The state circuit implementation. +mod constraint_builder; +mod lexicographic_ordering; +mod lookups; +mod multiple_precision_integer; +mod random_linear_combination; +#[cfg(test)] +mod tests; -pub(crate) mod state; -pub use state::StateCircuit; +use crate::evm_circuit::{param::N_BYTES_WORD, witness::RwMap}; +use constraint_builder::{ConstraintBuilder, Queries}; +use eth_types::{Address, Field}; +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, Copy)] +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, + value: Column, + lookups: LookupsConfig, + power_of_randomness: [Column; N_BYTES_WORD - 1], + // lexicographic_ordering config, etc. + lexicographic_ordering: LexicographicOrderingConfig, +} + +type Lookup = (&'static str, Expression, Expression); + +/// State Circuit for proving RwTable is valid +#[derive(Default)] +pub struct StateCircuit { + randomness: F, + rw_map: RwMap, +} + +impl StateCircuit { + /// make a new state circuit + pub fn new(randomness: F, rw_map: RwMap) -> Self { + Self { randomness, rw_map } + } +} + +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] = [0; 4].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 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, + ), + 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> { + // dbg!("synthesize being called"); + // Is this clone ok? + LookupsChip::construct(config.lookups).load(&mut layouter)?; + + // TODO: move sorting out of synthesize, so we can check that unsorted witnesses + // don't verify. + let mut rows: Vec<_> = self.rw_map.0.values().flatten().collect(); + rows.sort_by_key(|row| { + ( + row.tag() as u64, + row.id().unwrap_or_default(), + row.address().unwrap_or_default(), + row.field_tag().unwrap_or_default(), + row.storage_key().unwrap_or_default(), + row.rw_counter(), + ) + }); + + layouter.assign_region( + || "assign rw table", + |mut region| { + dbg!("assign_region closure being called"); + let mut offset = 0; + + for row in &rows { + dbg!(offset); + if offset != 0 { + // just treat selector as is_start? + region.assign_fixed( + || "selector", + config.selector, + offset, + || Ok(F::one()), + )?; + + config.lexicographic_ordering.assign( + &mut region, + offset, + row, + &rows[offset - 1], + )?; + } + // config.selector.enable(&mut region, offset)?; + config + .rw_counter + .assign(&mut region, offset, row.rw_counter() as u32)?; + // dbg!(row.is_write() as u64); + region.assign_advice( + || "is_write", + config.is_write, + offset, + || Ok(F::from(row.is_write() as u64)), + )?; + 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: must explicitly assign to cells to convince MockProver. + // I don't think this was needed in the past? + // this also isn't needed for address??? + // config.storage_key.assign( + // &mut region, + // offset, + // self.randomness, + // row.storage_key().unwrap_or_default(), + // )?; + if let Some(storage_key) = row.storage_key() { + config.storage_key.assign( + &mut region, + offset, + self.randomness, + storage_key, + )?; + } + + offset += 1; + } + 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()), + id: MpiQueries::new(meta, c.rw_counter), + 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())), + } +} 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..61317b9a87 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -0,0 +1,310 @@ +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 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], + // lexicographic_ordering expressions, etc. +} + +pub struct ConstraintBuilder { + constraints: Vec<(&'static str, Expression)>, + lookups: Vec<(&'static str, (Expression, Expression))>, + 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)> { + // dbg!(self.constraints.clone()); + self.constraints + .iter() + .cloned() + .map(|(name, expression)| (name, condition.clone() * expression)) + .collect() + } + + pub fn lookups(&self) -> Vec<(&'static str, (Expression, Expression))> { + 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() * q.is_write(), + ); + self.add_lookup( + "stack address fits into 10 bits", + (q.address.value.clone(), q.lookups.u10.clone()), + ); + self.condition(not::expr(q.first_access()), |cb| { + cb.require_boolean("stack address change is 0 or 1", 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) + 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 + 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)) { + // handle nested conditions? + 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 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 { + // TODO(mason) fix meeeeee + 1.expr() + } + + 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..014bb31bf5 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -0,0 +1,296 @@ +// use super::constraint_builder::from_digits; +use super::N_LIMBS_ACCOUNT_ADDRESS; +use super::{N_LIMBS_ID, N_LIMBS_RW_COUNTER}; +use crate::evm_circuit::param::N_BYTES_WORD; +use crate::evm_circuit::util::{not, select}; +use crate::evm_circuit::witness::Rw; +use crate::util::Expr; +use eth_types::Field; +use eth_types::ToBigEndian; +use halo2_proofs::{ + circuit::{AssignedCell, Region}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, + poly::Rotation, +}; +use itertools::Itertools; + +use std::marker::PhantomData; +use std::ops::Mul; + +// 2 limbs for tag, field_tag and id. +// 10 limbs for address, +// 16 limbs for storage key +// 2 limbs for rw_counter +// 30 limbs in total -> can fit into two field elements +#[derive(Clone, Copy)] +pub struct Config { + diff_1: Column, + diff_2: Column, + diff_inverse: Column, + diff_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], +} + +impl Config { + pub fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + cur: &Rw, + prev: &Rw, + ) -> Result, Error> { + 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"); + dbg!(index); + + // TODO: simplify this + let mut diff_1 = F::zero(); + let mut diff_2 = F::zero(); + let mut diff_inverse = F::zero(); + let mut diff_selector = F::zero(); + + if index < 15 { + diff_1 = F::from((cur_limb - prev_limb) as u64); + diff_inverse = diff_1.invert().unwrap(); + diff_selector = F::one(); + + // you need to find a valid difference to fill in for diff_2 still. + // you've just been lucky that 0 is a valid value for all of the + // test cases. + } else { + diff_2 = F::from((cur_limb - prev_limb) as u64); + diff_inverse = diff_2.invert().unwrap(); + } + + region.assign_advice(|| "diff_1", self.diff_1, offset, || Ok(diff_1))?; + region.assign_advice(|| "diff_2", self.diff_2, offset, || Ok(diff_2))?; + region.assign_advice( + || "diff_inverse", + self.diff_inverse, + offset, + || Ok(diff_inverse), + )?; + region.assign_advice( + || "diff_selector", + self.diff_selector, + offset, + || Ok(diff_selector), + ) + } +} + +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, + 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 diff_1 = meta.advice_column(); + let diff_2 = meta.advice_column(); + let diff_inverse = meta.advice_column(); + let diff_selector = meta.advice_column(); + let config = Config { + diff_1, + diff_2, + diff_inverse, + diff_selector, + tag, + field_tag, + id_limbs, + address_limbs, + storage_key_bytes, + rw_counter_limbs, + }; + meta.create_gate("diff_1 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 diff_1 = meta.query_advice(diff_1, Rotation::cur()); + vec![ + selector.clone() + * diff_1_possible_values(cur, prev) + .iter() + .map(|e| diff_1.clone() - e.clone()) + .fold(1.expr(), Expression::mul), + ] + }); + + meta.create_gate("diff_2 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 diff_2 = meta.query_advice(diff_2, Rotation::cur()); + vec![ + selector.clone() + * diff_2_possible_values(cur, prev) + .iter() + .map(|e| diff_2.clone() - e.clone()) + .fold(1.expr(), Expression::mul), + ] + }); + + meta.lookup_any("diff_1 fits into u16", |meta| { + let diff_selector = meta.query_advice(diff_selector, Rotation::cur()); + let diff_1 = meta.query_advice(diff_1, Rotation::cur()); + vec![( + diff_selector * diff_1, + meta.query_fixed(u16_range, Rotation::cur()), + )] + }); + meta.lookup_any("diff_2 fits into u16", |meta| { + let diff_selector = meta.query_advice(diff_selector, Rotation::cur()); + let diff_2 = meta.query_advice(diff_2, Rotation::cur()); + vec![( + not::expr(diff_selector) * diff_2, + meta.query_fixed(u16_range, Rotation::cur()), + )] + }); + + meta.create_gate("diff_selector is boolean", |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + let diff_selector = meta.query_advice(diff_selector, Rotation::cur()); + vec![selector * diff_selector.clone() * not::expr(diff_selector)] + }); + + meta.create_gate("diff_inverse", |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + let diff_selector = meta.query_advice(diff_selector, Rotation::cur()); + let diff_inverse = meta.query_advice(diff_inverse, Rotation::cur()); + let diff_1 = meta.query_advice(diff_1, Rotation::cur()); + let diff_2 = meta.query_advice(diff_2, Rotation::cur()); + vec![ + selector + * select::expr( + diff_selector, + diff_inverse.clone() * diff_1 - 1u64.expr(), + diff_inverse * diff_2 - 1u64.expr(), + ), + ] + }); + + config + } +} + +struct Queries { + tag: Expression, // 4 bits + field_tag: Expression, // < 12 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 { + // witness: query_advice(config.witness), + 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_limbs(&self) -> Vec> { + self.storage_key_bytes + .iter() + .tuples() + .map(|(hi, lo)| (1u64 << 16).expr() * hi.clone() + lo.clone()) + .collect() + } + + fn be_limbs(&self) -> Vec> { + let mut limbs: Vec<_> = self + .id_limbs + .iter() + .chain(&self.address_limbs) + .chain(&self.storage_key_limbs()) + .chain(&self.rw_counter_limbs) + .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()); + + // check that the first byte of id is not use, and overwrite it with packed + // tags. + assert_eq!(be_bytes[0], 0); + be_bytes[0] = (row.tag() as u8) << 4 + row.field_tag().unwrap_or_default() as u8; + + 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().to_be_bytes())); + + be_bytes + .iter() + .tuples() + .map(|(hi, lo)| u16::from_be_bytes([*hi, *lo])) + .collect() +} + +fn diff_1_possible_values(cur: Queries, prev: Queries) -> Vec> { + let mut result = vec![]; + let mut partial_product = 0u64.expr(); + for (cur_limb, prev_limb) in cur.be_limbs()[..15].iter().zip(&prev.be_limbs()[..15]) { + partial_product = + partial_product * (1u64 << 16).expr() + cur_limb.clone() - prev_limb.clone(); + result.push(partial_product.clone()) + } + result +} + +fn diff_2_possible_values(cur: Queries, prev: Queries) -> Vec> { + let mut result = vec![]; + let mut partial_product = 0u64.expr(); + for (cur_limb, prev_limb) in cur.be_limbs()[15..].iter().zip(&prev.be_limbs()[15..]) { + partial_product = + partial_product * (1u64 << 16).expr() + cur_limb.clone() - prev_limb.clone(); + result.push(partial_product.clone()) + } + result +} diff --git a/zkevm-circuits/src/state_circuit/lookups.rs b/zkevm-circuits/src/state_circuit/lookups.rs new file mode 100644 index 0000000000..c716810c95 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/lookups.rs @@ -0,0 +1,111 @@ +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()), + } + } +} + +// impl Config { +// pub fn u8_range(&self) -> Expression { +// self.u8.cur.clone() +// } +// } + +// This doesn't seem like it needs to exist? +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( + || format!("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..250dd54f4f --- /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, + pub bytes: [Column; N], +} + +#[derive(Clone)] +pub struct Queries { + pub encoded: 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()), + 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 selector = meta.query_selector(selector); // will constraints be poisoned + // without this? actually it was the opposite. + 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_tests.rs similarity index 69% rename from zkevm-circuits/src/state_circuit/state.rs rename to zkevm-circuits/src/state_circuit/state_tests.rs index e7231a229e..46a7514a7d 100644 --- a/zkevm-circuits/src/state_circuit/state.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -1,20 +1,28 @@ -use crate::evm_circuit::{ - table::RwTableTag, - util::{ - constraint_builder::BaseConstraintBuilder, math_gadget::generate_lagrange_base_polynomial, +use super::cells::Cells; +use super::constraint_builder::ConstraintBuilder; +use super::param::N_LIMBS_ACCOUNT_ADDRESS; +use crate::state_circuit::constraint_builder::sort_key_values; +use crate::state_circuit::fixed_table::FixedTable; +use crate::{ + evm_circuit::{ + param::N_BYTES_WORD, + table::RwTableTag, + util::{constraint_builder::BaseConstraintBuilder, RandomLinearCombination}, + witness::{Rw, RwMap, RwRow}, }, - witness::{RwMap, RwRow}, -}; -use eth_types::Field; -use gadgets::{ - is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, - Variable, + gadget::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, + util::Expr, }; +use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; use halo2_proofs::{ circuit::{Layouter, Region, SimpleFloorPlanner}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, poly::Rotation, }; +use itertools::Itertools; +use pairing::arithmetic::FieldExt; +use std::convert::TryInto; +use strum::IntoEnumIterator; /* (FIXME) Example state table: @@ -42,8 +50,6 @@ use halo2_proofs::{ // 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; @@ -75,26 +81,23 @@ pub struct Config< const ROWS_MAX: usize, > { s_enable: Column, - rw_counter: Column, - is_write: Column, + // rw_counter: Column, keys: [Column; 5], - // helper column used for IsZero chip - keys_diff_inv: [Column; 5], + key2_limbs: [Column; N_LIMBS_ACCOUNT_ADDRESS], + // Use WordConfig here instead. + key4_bytes: [Column; N_BYTES_WORD], + + auxs: Column, + + power_of_randomness: [Expression; N_BYTES_WORD - 1], - key2_limbs: [Column; 8], - key4_bytes: [Column; 32], - value: Column, - auxs: [Column; 2], + lexicographic_ordering: [IsZeroConfig; 2], - // helper chips here - key_is_same_with_prev: [IsZeroConfig; 5], + // Fixed columns for range lookups + fixed_table: FixedTable, - // 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, + cells: Cells, } impl< @@ -106,138 +109,139 @@ impl< 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()); + pub(crate) fn configure( + meta: &mut ConstraintSystem, + power_of_randomness: [Expression; 31], + ) -> Self { + let cells = Cells::new(meta, power_of_randomness.clone()); - let s_enable = meta.fixed_column(); + let fixed_table = FixedTable::configure(meta); - let value = meta.advice_column(); + // let rw_counter = meta.advice_column(); + let keys = [(); 5].map(|_| meta.advice_column()); + let key2_limbs = [(); N_LIMBS_ACCOUNT_ADDRESS].map(|_| meta.advice_column()); + let key4_bytes = [(); N_BYTES_WORD].map(|_| meta.advice_column()); + let auxs = 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 s_enable = meta.fixed_column(); let new_cb = || BaseConstraintBuilder::::new(MAX_DEGREE); + let qb = ConstraintBuilder::::new( + meta, + keys, + key2_limbs, + s_enable, + key4_bytes, + power_of_randomness.clone(), /* TODO: these don't both of these need + * power_of_randomness */ + /* rw_counter, */ + ); + + let lexicographic_ordering = + [(0, meta.advice_column()), (1, meta.advice_column())].map(|(i, advice_column)| { + IsZeroChip::configure( + meta, + |meta| qb.s_enable(meta), + |meta| qb.sort_keys_delta(meta)[i].clone(), + advice_column, + ) + }); - // 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_all_keys_same = |_: &mut VirtualCells| { + lexicographic_ordering[0].is_zero_expression.clone() + * lexicographic_ordering[1].is_zero_expression.clone() }; - let q_not_all_keys_same = |meta: &mut VirtualCells| one.clone() - q_all_keys_same(meta); + let q_not_all_keys_same = |meta: &mut VirtualCells| 1u64.expr() - 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 + // 0. tag in RwTableTag range + // TODO: check key1 and key3 ranges. + cb.require_in_set( + "tag in RwTableTag range", + qb.tag(meta), + RwTableTag::iter().map(|x| x.expr()).collect(), + ); - // TODO: 1. key2 is linear combination of 10 x 16bit limbs and also in range + // 1. key2 expands to its limbs + cb.require_equal( + "account address matches its limbs", + qb.address(meta), + qb.address_limbs(meta) + .iter() + .fold(0u64.expr(), |result, limb| { + limb.clone() + result * (1u64 << 16).expr() + }), + // qb.address_from_limbs(meta), + ); - // TODO: 2. key4 is RLC encoded + // 2. key4 is RLC encoded + cb.require_equal( + "storage key matches its RLC encoding", + qb.storage_key(meta), + RandomLinearCombination::random_linear_combine_expr( + qb.storage_key_bytes(meta), + qb.power_of_randomness(meta), + ), + ); // 3. is_write is boolean - cb.require_boolean("is_write should be boolean", is_write); + cb.require_boolean("is_write should be boolean", cells.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. + // see lexicographic_ordering chips + + // 5. RWC is monotonically strictly increasing for a set of all keys + // see below // 6. Read consistency - // When a row is READ - // AND When all the keys are equal in two consecutive a rows: + // 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), + q_all_keys_same(meta) * cells.is_read() * (cells.value_delta()), ); - cb.gate(s_enable) + cb.gate(qb.s_enable(meta)) }); + // TODO: move this into constraint builder + for i in 0..N_LIMBS_ACCOUNT_ADDRESS { + meta.lookup_any("address limbs fit into u16", |meta| { + vec![( + qb.s_enable(meta) * qb.address_limbs(meta)[i].clone(), + fixed_table.u16(meta), + )] + }); + } + + // Check that storage key bytes are between 0 and 255. + // TODO: move this into constraint builder + for i in 0..N_BYTES_WORD { + meta.lookup_any("storage key byte is between 0 and 255", |meta| { + vec![( + qb.s_enable(meta) * qb.storage_key_bytes(meta)[i].clone(), + fixed_table.u8(meta), + )] + }); + } + // 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, + qb.s_enable(meta) + * q_all_keys_same(meta) + * (cells.rw_counter_delta() - 1u64.expr()), + // TODO(mason) this isn't correct. The specs say this should be u32.... + fixed_table.u10(meta), )] }); @@ -245,16 +249,10 @@ impl< 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); + cb.require_zero("field tag is 0", qb.field_tag(meta)); + cb.require_zero("storage key is 0", qb.storage_key(meta)); // 1. First access for a set of all keys // @@ -262,33 +260,18 @@ impl< // - 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, + q_not_all_keys_same(meta) * cells.is_read() * cells.value(), ); - cb.gate(s_enable * q_memory(meta)) + cb.gate(qb.s_enable(meta) * qb.tag_is(meta, RwTableTag::Memory)) }); - // 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)] + // 2. value is a byte when tag is Memory + meta.lookup_any("value is a byte when tag is Memory", |meta| { + vec![( + qb.tag_is(meta, RwTableTag::Memory) * cells.value(), + fixed_table.u8(meta), + )] }); ///////////////////////// Stack related constraints ///////////////////////// @@ -296,15 +279,9 @@ impl< 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); + cb.require_zero("field tag is 0", qb.field_tag(meta)); + cb.require_zero("storage key is 0", qb.storage_key(meta)); // 1. First access for a set of all keys // @@ -315,56 +292,40 @@ impl< // - It must be a WRITE cb.require_zero( "if address changes, operation is always a write", - q_not_all_keys_same(meta) * q_read, + q_not_all_keys_same(meta) * cells.is_read(), ); - cb.gate(s_enable * q_stack(meta)) + cb.gate(qb.s_enable(meta) * qb.tag_is(meta, RwTableTag::Stack)) }); // 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)] + vec![( + qb.tag_is(meta, RwTableTag::Stack) * qb.address(meta), + // todo: this should be u32, so we need to add two rw limb columns. + fixed_table.u10(meta), + )] }); // 3. stack_ptr only increases by 0 or 1 - meta.create_gate("Stack pointer diff be 0 or 1", |meta| { + meta.create_gate("Within a call, 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, + qb.address_delta(meta), ); - cb.gate(s_enable * q_stack * tag_is_same_with_prev * call_id_same_with_prev) + cb.gate(qb.s_enable(meta) * q_all_keys_same(meta) * qb.tag_is(meta, RwTableTag::Stack)) }); ///////////////////////// 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); + // cb.require_zero("key1 is 0", qb.id(meta)); // moved from aux to key1 + cb.require_zero("key3 is 0", qb.field_tag(meta)); // 1. First access for a set of all keys // @@ -375,97 +336,29 @@ impl< // - It must be a WRITE cb.require_zero( "First access for storage is write", - q_not_all_keys_same(meta) * q_read, + q_not_all_keys_same(meta) * cells.is_read(), ); cb.require_zero( "First access for storage has rw_counter as 0", - q_not_all_keys_same(meta) * rw_counter, + q_not_all_keys_same(meta) * cells.rw_counter(), ); - cb.gate(s_enable * q_storage) + cb.gate(qb.s_enable(meta) * qb.tag_is(meta, RwTableTag::AccountStorage)) }); 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, + fixed_table, + power_of_randomness, + lexicographic_ordering, + cells, } } - /// 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, @@ -473,48 +366,80 @@ impl< 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())); + let sort_key_chips = self + .lexicographic_ordering + .clone() + .map(|config| IsZeroChip::construct(config)); 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)); + let mut rows: Vec<(Rw, RwRow)> = rw_map + .0 + .values() + .flatten() + .map(|row| (row.clone(), row.table_assignment(randomness))) + .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( + for (index, (rw, rw_row)) in rows.iter().enumerate() { + self.assign_row(&mut region, offset, *rw_row)?; + + if let Some(address) = rw.address() { + self.assign_address_and_limbs(&mut region, offset, address)?; + } + + if let Some(storage_key) = rw.storage_key() { + self.assign_storage_key_and_bytes( + &mut region, + offset, + randomness, + storage_key, + )?; + } + + let (sort_key_0, sort_key_1): (F, F) = sort_key_values( + rw.tag(), + rw.id().unwrap_or_default().try_into().unwrap(), + rw.address().unwrap_or_default(), + rw.field_tag().unwrap_or_default(), + rw.storage_key().unwrap_or_default().to_le_bytes(), + ); + + let mut sort_key_prev_0 = F::zero(); + let mut sort_key_prev_1 = F::zero(); + if index != 0 { + let rw_prev = rows[index - 1].0.clone(); + + let (a, b) = sort_key_values( + rw_prev.tag(), + rw_prev.id().unwrap_or_default().try_into().unwrap(), + rw_prev.address().unwrap_or_default(), + rw_prev.field_tag().unwrap_or_default(), + rw_prev.storage_key().unwrap_or_default().to_le_bytes(), + ); + + sort_key_prev_0 = a; + sort_key_prev_1 = b; + } + + sort_key_chips[0].assign( + &mut region, + offset, + Some(sort_key_0 - sort_key_prev_0), + )?; + sort_key_chips[1].assign( &mut region, offset, - *row, - row_prev, - &key_is_same_with_prev_chips, + Some(sort_key_1 - sort_key_prev_1), )?; + offset += 1; } @@ -528,10 +453,8 @@ impl< region: &mut Region<'_, F>, offset: usize, row: RwRow, - row_prev: RwRow, - diff_is_zero_chips: &[IsZeroChip; 5], ) -> Result<(), Error> { - let address = row.key3; + let memory_or_stack_address = row.key3; let rw_counter = row.rw_counter; let value = row.value; let is_write = row.is_write; @@ -544,33 +467,38 @@ impl< 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) { + if row.tag == F::from(STACK_TAG as u64) + && memory_or_stack_address > F::from(STACK_ADDRESS_MAX as u64) + { panic!( "stack address out of range {:?} > {}", - address, STACK_ADDRESS_MAX + memory_or_stack_address, STACK_ADDRESS_MAX ); } - if row.tag == F::from(MEMORY_TAG as u64) && address > F::from(MEMORY_ADDRESS_MAX as u64) + if row.tag == F::from(MEMORY_TAG as u64) + && memory_or_stack_address > F::from(MEMORY_ADDRESS_MAX as u64) { panic!( "memory address out of range {:?} > {}", - address, MEMORY_ADDRESS_MAX + memory_or_stack_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 { + self.cells + .rw_counter + .assign(region, offset, Some(rw_counter))?; + self.cells.value.assign(region, offset, Some(value))?; + self.cells.is_write.assign(region, offset, Some(is_write))?; + + for i in 0..5 { + let value = 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), + 0 => row.tag, + 1 => row.key1, + 2 => row.key2, + 3 => row.key3, + 4 => row.key4, _ => unreachable!(), }; region.assign_advice( @@ -579,11 +507,64 @@ impl< 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(()) + } + + fn assign_address_and_limbs( + &self, + region: &mut Region<'_, F>, + offset: usize, + address: Address, + ) -> Result<(), Error> { + let limbs = address + .0 + .iter() + .tuples() + .map(|(hi, lo)| u16::from_le_bytes([*lo, *hi])); + for (limb, col) in limbs.zip(&self.key2_limbs) { + region.assign_advice(|| "key2_limb", *col, offset, || Ok(F::from(limb.into())))?; + } + + region.assign_advice( + || "key2 (address)", + self.keys[2], + offset, + || Ok(address.to_scalar().unwrap()), + )?; + + Ok(()) + } + + fn assign_storage_key_and_bytes( + &self, + region: &mut Region<'_, F>, + offset: usize, + randomness: F, + key: Word, + ) -> Result<(), Error> { + region.assign_advice( + || "key4 (storage_key)", + self.keys[4], + offset, + || { + Ok(RandomLinearCombination::random_linear_combine( + key.to_le_bytes(), + randomness, + )) + }, + )?; + + // TODO: use array_zip if/when it stabilizes. + for (col, byte) in self.key4_bytes.iter().zip(&key.to_le_bytes()) { + region.assign_advice( + || "storage key byte", + *col, + offset, + || Ok(F::from(*byte as u64)), + )?; + } Ok(()) } @@ -603,6 +584,7 @@ pub struct StateCircuit< pub randomness: F, /// witness for rw map pub rw_map: RwMap, + // pub operations: OperationContainer, } impl< @@ -620,6 +602,7 @@ impl< Self { randomness, rw_map: rw_map.clone(), + // operations, } } } @@ -650,7 +633,21 @@ impl< } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - Config::configure(meta) + let power_of_randomness = { + let columns = [(); 31].map(|_| meta.instance_column()); + let mut power_of_randomness = None; + + meta.create_gate("", |meta| { + power_of_randomness = + Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); + + [0.expr()] + }); + + power_of_randomness.unwrap() + }; + + Config::configure(meta, power_of_randomness) } fn synthesize( @@ -658,7 +655,7 @@ impl< config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - config.load(&mut layouter)?; + config.fixed_table.load(&mut layouter)?; config.assign(layouter, self.randomness, &self.rw_map)?; Ok(()) @@ -699,7 +696,16 @@ mod tests { { $memory_rows_max + $stack_rows_max + $storage_rows_max }, >::new(Fr::rand(), &rw_map); - let prover = MockProver::::run($k, &circuit, vec![]).unwrap(); + let power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + circuit.randomness.pow(&[exp, 0, 0, 0]); + { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? + ] + }) + .collect(); + + let prover = MockProver::::run($k, &circuit, power_of_randomness).unwrap(); let verify_result = prover.verify(); assert!(verify_result.is_ok(), "verify err: {:#?}", verify_result); }}; @@ -722,7 +728,16 @@ mod tests { { $memory_rows_max + $stack_rows_max + $storage_rows_max }, >::new(Fr::rand(), &rw_map); - let prover = MockProver::::run($k, &circuit, vec![]).unwrap(); + let power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + circuit.randomness.pow(&[exp, 0, 0, 0]); + { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? + ] + }) + .collect(); + + let prover = MockProver::::run($k, &circuit, power_of_randomness).unwrap(); assert!(prover.verify().is_err()); }}; } @@ -766,7 +781,7 @@ mod tests { RWCounter::from(0), RW::WRITE, StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), + U256::from(100).to_address(), Word::from(0x40), Word::from(32), Word::zero(), @@ -778,7 +793,7 @@ mod tests { RWCounter::from(18), RW::WRITE, StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), + U256::from(100).to_address(), Word::from(0x40), Word::from(32), Word::from(32), @@ -790,7 +805,7 @@ mod tests { RWCounter::from(19), RW::WRITE, StorageOp::new( - address!("0x0000000000000000000000000000000000000001"), + U256::from(100).to_address(), Word::from(0x40), Word::from(32), Word::from(32), @@ -800,7 +815,7 @@ mod tests { ); test_state_circuit_ok!( - 12, + 17, 2000, 100, 2, @@ -851,7 +866,7 @@ mod tests { const STACK_ROWS_MAX: usize = 2; test_state_circuit_ok!( - 14, + 17, 2000, 100, STACK_ROWS_MAX, @@ -903,7 +918,7 @@ mod tests { const MEMORY_ROWS_MAX: usize = 7; test_state_circuit_error!( - 14, + 17, 2000, MEMORY_ROWS_MAX, 1000, @@ -973,7 +988,7 @@ mod tests { const MEMORY_ROWS_MAX: usize = 2; const STORAGE_ROWS_MAX: usize = 2; test_state_circuit_error!( - 14, + 17, 2000, MEMORY_ROWS_MAX, 1000, @@ -1048,7 +1063,7 @@ mod tests { const STACK_ADDRESS_MAX: usize = 1023; test_state_circuit_error!( - 16, + 18, RW_COUNTER_MAX, MEMORY_ROWS_MAX, MEMORY_ADDRESS_MAX, @@ -1104,7 +1119,7 @@ mod tests { const STACK_ADDRESS_MAX: usize = 1023; test_state_circuit_error!( - 16, + 18, RW_COUNTER_MAX, MEMORY_ROWS_MAX, MEMORY_ADDRESS_MAX, @@ -1226,7 +1241,7 @@ mod tests { const MEMORY_ROWS_MAX: usize = 100; const STACK_ROWS_MAX: usize = 100; test_state_circuit_error!( - 15, + 17, 10000, MEMORY_ROWS_MAX, 10000, @@ -1290,7 +1305,7 @@ mod tests { const MEMORY_ROWS_MAX: usize = 10; test_state_circuit_error!( - 14, + 18, 10000, MEMORY_ROWS_MAX, 10000, @@ -1364,7 +1379,7 @@ mod tests { const MEMORY_ROWS_MAX: usize = 2; const STORAGE_ROWS_MAX: usize = 2; test_state_circuit_error!( - 14, + 17, 2000, MEMORY_ROWS_MAX, 1000, @@ -1418,7 +1433,7 @@ mod tests { let storage_ops = builder.block.container.sorted_storage(); test_state_circuit_ok!( - 14, + 17, 2000, 100, 0x80, diff --git a/zkevm-circuits/src/state_circuit/tests.rs b/zkevm-circuits/src/state_circuit/tests.rs new file mode 100644 index 0000000000..60aca756e8 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/tests.rs @@ -0,0 +1,185 @@ +mod tests { + use super::super::StateCircuit; + use crate::evm_circuit::witness::RwMap; + use bus_mapping::operation::{ + MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, + }; + use eth_types::{ + evm_types::{MemoryAddress, StackAddress}, + ToAddress, Word, U256, + }; + use halo2_proofs::{arithmetic::BaseExt, dev::MockProver, pairing::bn256::Fr}; + + 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 circuit = StateCircuit { + randomness: Fr::rand(), + rw_map, + }; + + let power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + circuit.randomness.pow(&[exp, 0, 0, 0]); + 2usize.pow(6)// you don't need the full value here, because they get padded later. + ] + }) + .collect(); + + let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); + let verify_result = prover.verify(); + assert_eq!(verify_result, Ok(())); + } + + #[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![]); + } +} diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index cbcd2a9852..893751c294 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -5,8 +5,9 @@ use crate::{ use bus_mapping::mock::BlockData; use eth_types::geth_types::GethData; use halo2_proofs::dev::{MockProver, VerifyFailure}; -use halo2_proofs::pairing::bn256::Fr; +use halo2_proofs::pairing::{arithmetic::BaseExt, 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(), } } @@ -91,9 +92,18 @@ pub fn test_circuits_using_witness_block( // 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(); + let state_circuit = StateCircuit::new(block.randomness, block.rws.clone()); + + let power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + block.randomness.pow(&[exp, 0, 0, 0]); + 20 // number of rows in the rw table. + ] + }) + .collect(); + + let prover = MockProver::::run(18, &state_circuit, power_of_randomness).unwrap(); prover.verify()?; } From edc633aa6aaa371c66d93222b1c71ec9848e6230 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 4 Apr 2022 22:44:09 -0400 Subject: [PATCH 02/31] Implement first_access method and fix stack change constraint --- zkevm-circuits/src/state_circuit.rs | 2 ++ .../src/state_circuit/constraint_builder.rs | 35 +++++++++++-------- .../state_circuit/lexicographic_ordering.rs | 25 +++++++------ .../random_linear_combination.rs | 6 ++-- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 2ad4125cb4..607b5b5049 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -241,5 +241,7 @@ fn queries(meta: &mut VirtualCells<'_, F>, c: StateConfig) -> Queries< power_of_randomness: c .power_of_randomness .map(|c| meta.query_instance(c, Rotation::cur())), + lexicographic_ordering_diff_selector: meta + .query_advice(c.lexicographic_ordering.diff_selector, Rotation::cur()), } } diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index 61317b9a87..54ee68c2bb 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -26,7 +26,8 @@ pub struct Queries { pub value: Expression, pub lookups: LookupsQueries, pub power_of_randomness: [Expression; N_BYTES_WORD - 1], - // lexicographic_ordering expressions, etc. + + pub lexicographic_ordering_diff_selector: Expression, } pub struct ConstraintBuilder { @@ -45,7 +46,6 @@ impl ConstraintBuilder { } pub fn gate(&self, condition: Expression) -> Vec<(&'static str, Expression)> { - // dbg!(self.constraints.clone()); self.constraints .iter() .cloned() @@ -65,12 +65,12 @@ impl ConstraintBuilder { 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::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) }); @@ -130,15 +130,17 @@ impl ConstraintBuilder { "stack address fits into 10 bits", (q.address.value.clone(), q.lookups.u10.clone()), ); - self.condition(not::expr(q.first_access()), |cb| { - cb.require_boolean("stack address change is 0 or 1", q.address_change()) - }) + self.require_zero( + "if call_id doesn't change, stack address change is 0 or 1", + q.id_change() * 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) - self.require_zero("id is 0 for AccountStorage", q.id()); + // 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 @@ -240,7 +242,6 @@ impl ConstraintBuilder { } fn condition(&mut self, condition: Expression, build: impl FnOnce(&mut Self)) { - // handle nested conditions? let original_condition = self.condition.clone(); self.condition = self.condition.clone() * condition; build(self); @@ -269,6 +270,10 @@ impl Queries { 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() } @@ -286,8 +291,8 @@ impl Queries { } fn first_access(&self) -> Expression { - // TODO(mason) fix meeeeee - 1.expr() + not::expr(self.lexicographic_ordering_diff_selector.clone()) + * (self.storage_key.encoded.clone() - self.storage_key.encoded_prev.clone()) } fn address_change(&self) -> Expression { diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 014bb31bf5..5e743271b1 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -1,21 +1,20 @@ -// use super::constraint_builder::from_digits; -use super::N_LIMBS_ACCOUNT_ADDRESS; -use super::{N_LIMBS_ID, N_LIMBS_RW_COUNTER}; -use crate::evm_circuit::param::N_BYTES_WORD; -use crate::evm_circuit::util::{not, select}; -use crate::evm_circuit::witness::Rw; -use crate::util::Expr; -use eth_types::Field; -use eth_types::ToBigEndian; +use super::{N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_ID, N_LIMBS_RW_COUNTER}; +use crate::{ + evm_circuit::{ + param::N_BYTES_WORD, + util::{not, select}, + witness::Rw, + }, + util::Expr, +}; +use eth_types::{Field, ToBigEndian}; use halo2_proofs::{ circuit::{AssignedCell, Region}, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, poly::Rotation, }; use itertools::Itertools; - -use std::marker::PhantomData; -use std::ops::Mul; +use std::{marker::PhantomData, ops::Mul}; // 2 limbs for tag, field_tag and id. // 10 limbs for address, @@ -27,7 +26,7 @@ pub struct Config { diff_1: Column, diff_2: Column, diff_inverse: Column, - diff_selector: Column, + pub diff_selector: Column, tag: Column, field_tag: Column, id_limbs: [Column; N_LIMBS_ID], diff --git a/zkevm-circuits/src/state_circuit/random_linear_combination.rs b/zkevm-circuits/src/state_circuit/random_linear_combination.rs index 250dd54f4f..a26b19a0b0 100644 --- a/zkevm-circuits/src/state_circuit/random_linear_combination.rs +++ b/zkevm-circuits/src/state_circuit/random_linear_combination.rs @@ -1,5 +1,4 @@ use crate::evm_circuit::util::RandomLinearCombination as RLC; - use eth_types::{Field, ToLittleEndian, U256}; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region}, @@ -11,12 +10,14 @@ 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], } @@ -24,6 +25,7 @@ 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())), } } @@ -79,8 +81,6 @@ impl Chip { for &byte in &bytes { meta.lookup_any("rlc bytes fit into u8", |meta| { - // let selector = meta.query_selector(selector); // will constraints be poisoned - // without this? actually it was the opposite. let byte = meta.query_advice(byte, Rotation::cur()); let u8_lookup = meta.query_fixed(u8_lookup, Rotation::cur()); vec![(byte, u8_lookup)] From 8671e8a43af404238d8911ee66ac6fc76a08ae08 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Tue, 5 Apr 2022 00:41:25 -0400 Subject: [PATCH 03/31] Cleanup and comment out failing constraint --- zkevm-circuits/src/state_circuit.rs | 21 ++++------ .../state_circuit/lexicographic_ordering.rs | 39 +++++++++++-------- zkevm-circuits/src/state_circuit/lookups.rs | 2 +- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 607b5b5049..cfc63d19d1 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -7,6 +7,7 @@ mod random_linear_combination; #[cfg(test)] mod tests; +use crate::evm_circuit::table::RwTableTag; use crate::evm_circuit::{param::N_BYTES_WORD, witness::RwMap}; use constraint_builder::{ConstraintBuilder, Queries}; use eth_types::{Address, Field}; @@ -126,8 +127,6 @@ impl Circuit for StateCircuit { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - // dbg!("synthesize being called"); - // Is this clone ok? LookupsChip::construct(config.lookups).load(&mut layouter)?; // TODO: move sorting out of synthesize, so we can check that unsorted witnesses @@ -136,22 +135,20 @@ impl Circuit for StateCircuit { 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.field_tag().unwrap_or_default(), row.storage_key().unwrap_or_default(), row.rw_counter(), ) }); + dbg!(rows.clone()); + layouter.assign_region( || "assign rw table", |mut region| { - dbg!("assign_region closure being called"); - let mut offset = 0; - - for row in &rows { - dbg!(offset); + for (offset, row) in rows.iter().enumerate() { if offset != 0 { // just treat selector as is_start? region.assign_fixed( @@ -165,14 +162,12 @@ impl Circuit for StateCircuit { &mut region, offset, row, - &rows[offset - 1], + rows[offset - 1], )?; } - // config.selector.enable(&mut region, offset)?; config .rw_counter .assign(&mut region, offset, row.rw_counter() as u32)?; - // dbg!(row.is_write() as u64); region.assign_advice( || "is_write", config.is_write, @@ -217,8 +212,6 @@ impl Circuit for StateCircuit { storage_key, )?; } - - offset += 1; } Ok(()) }, @@ -232,7 +225,7 @@ fn queries(meta: &mut VirtualCells<'_, F>, c: StateConfig) -> Queries< 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()), - id: MpiQueries::new(meta, c.rw_counter), + 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), diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 5e743271b1..30c36407d0 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -45,6 +45,8 @@ impl Config { ) -> Result, Error> { let cur_be_limbs = rw_to_be_limbs(cur); let prev_be_limbs = rw_to_be_limbs(prev); + assert_eq!(cur_be_limbs.len(), 30); + assert_eq!(prev_be_limbs.len(), 30); let find_result = cur_be_limbs .iter() @@ -130,19 +132,17 @@ impl Chip { storage_key_bytes, rw_counter_limbs, }; - meta.create_gate("diff_1 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 diff_1 = meta.query_advice(diff_1, Rotation::cur()); - vec![ - selector.clone() - * diff_1_possible_values(cur, prev) - .iter() - .map(|e| diff_1.clone() - e.clone()) - .fold(1.expr(), Expression::mul), - ] - }); + // meta.create_gate("diff_1 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 diff_1 = meta.query_advice(diff_1, Rotation::cur()); + // vec![ + // selector + // * diff_1_possible_values(cur, prev) .iter() .map(|e| + // diff_1.clone() - e.clone()) .fold(1.expr(), Expression::mul), + // ] + // }); meta.create_gate("diff_2 is one of 15 values", |meta| { let selector = meta.query_fixed(selector, Rotation::cur()); @@ -150,7 +150,7 @@ impl Chip { let prev = Queries::new(meta, config, Rotation::prev()); let diff_2 = meta.query_advice(diff_2, Rotation::cur()); vec![ - selector.clone() + selector * diff_2_possible_values(cur, prev) .iter() .map(|e| diff_2.clone() - e.clone()) @@ -255,15 +255,20 @@ impl Queries { 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()); + assert_eq!(be_bytes.len(), 4); - // check that the first byte of id is not use, and overwrite it with packed + // 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.tag() as u8) << 4 + row.field_tag().unwrap_or_default() as u8; + be_bytes[0] = row.field_tag().unwrap_or_default() as u8 + ((row.tag() as u8) << 4); + assert_eq!(be_bytes.len(), 4); be_bytes.extend_from_slice(&(row.address().unwrap_or_default().0)); + assert_eq!(be_bytes.len(), 24); be_bytes.extend_from_slice(&(row.storage_key().unwrap_or_default().to_be_bytes())); - be_bytes.extend_from_slice(&(row.rw_counter().to_be_bytes())); + assert_eq!(be_bytes.len(), 24 + 32); + be_bytes.extend_from_slice(&((row.rw_counter() as u32).to_be_bytes())); + assert_eq!(be_bytes.len(), 24 + 32 + 4); be_bytes .iter() diff --git a/zkevm-circuits/src/state_circuit/lookups.rs b/zkevm-circuits/src/state_circuit/lookups.rs index c716810c95..9e54b38de0 100644 --- a/zkevm-circuits/src/state_circuit/lookups.rs +++ b/zkevm-circuits/src/state_circuit/lookups.rs @@ -88,7 +88,7 @@ impl Chip { )?; } layouter.assign_region( - || format!("assign call_context_field_tags fixed column"), + || "assign call_context_field_tags fixed column", |mut region| { for field_tag in CallContextFieldTag::iter() { region.assign_fixed( From ca8e2ec2021a1328a5beb6d1bca9c2d4ad752a51 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Tue, 5 Apr 2022 13:49:04 -0400 Subject: [PATCH 04/31] Fix failing tests by correctly getting limbs in be order in lexicographic ordering --- .../state_circuit/lexicographic_ordering.rs | 48 ++++++++--------- zkevm-circuits/src/state_circuit/tests.rs | 51 ++++++++++++++++++- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 30c36407d0..414668468a 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -54,7 +54,6 @@ impl Config { .enumerate() .find(|(_, (a, b))| a != b); let (index, (cur_limb, prev_limb)) = find_result.expect("repeated rw counter"); - dbg!(index); // TODO: simplify this let mut diff_1 = F::zero(); @@ -132,17 +131,19 @@ impl Chip { storage_key_bytes, rw_counter_limbs, }; - // meta.create_gate("diff_1 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 diff_1 = meta.query_advice(diff_1, Rotation::cur()); - // vec![ - // selector - // * diff_1_possible_values(cur, prev) .iter() .map(|e| - // diff_1.clone() - e.clone()) .fold(1.expr(), Expression::mul), - // ] - // }); + meta.create_gate("diff_1 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 diff_1 = meta.query_advice(diff_1, Rotation::cur()); + vec![ + selector + * diff_1_possible_values(cur, prev) + .iter() + .map(|e| diff_1.clone() - e.clone()) + .fold(1.expr(), Expression::mul), + ] + }); meta.create_gate("diff_2 is one of 15 values", |meta| { let selector = meta.query_fixed(selector, Rotation::cur()); @@ -203,7 +204,7 @@ impl Chip { struct Queries { tag: Expression, // 4 bits - field_tag: Expression, // < 12 bits, so we can pack tag + field_tag into one limb. + 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], @@ -240,9 +241,10 @@ impl Queries { let mut limbs: Vec<_> = self .id_limbs .iter() - .chain(&self.address_limbs) - .chain(&self.storage_key_limbs()) - .chain(&self.rw_counter_limbs) + .rev() + .chain(self.address_limbs.iter().rev()) + .chain(self.storage_key_limbs().iter().rev()) + .chain(self.rw_counter_limbs.iter().rev()) .cloned() .collect(); // most significant byte of id should be 0, so safe to overwrite it with packed @@ -279,22 +281,20 @@ fn rw_to_be_limbs(row: &Rw) -> Vec { fn diff_1_possible_values(cur: Queries, prev: Queries) -> Vec> { let mut result = vec![]; - let mut partial_product = 0u64.expr(); + let mut partial_sum = 0u64.expr(); for (cur_limb, prev_limb) in cur.be_limbs()[..15].iter().zip(&prev.be_limbs()[..15]) { - partial_product = - partial_product * (1u64 << 16).expr() + cur_limb.clone() - prev_limb.clone(); - result.push(partial_product.clone()) + partial_sum = partial_sum * (1u64 << 16).expr() + cur_limb.clone() - prev_limb.clone(); + result.push(partial_sum.clone()) } result } fn diff_2_possible_values(cur: Queries, prev: Queries) -> Vec> { let mut result = vec![]; - let mut partial_product = 0u64.expr(); + let mut partial_sum = 0u64.expr(); for (cur_limb, prev_limb) in cur.be_limbs()[15..].iter().zip(&prev.be_limbs()[15..]) { - partial_product = - partial_product * (1u64 << 16).expr() + cur_limb.clone() - prev_limb.clone(); - result.push(partial_product.clone()) + partial_sum = partial_sum * (1u64 << 16).expr() + cur_limb.clone() - prev_limb.clone(); + result.push(partial_sum.clone()) } result } diff --git a/zkevm-circuits/src/state_circuit/tests.rs b/zkevm-circuits/src/state_circuit/tests.rs index 60aca756e8..8c8f5240fb 100644 --- a/zkevm-circuits/src/state_circuit/tests.rs +++ b/zkevm-circuits/src/state_circuit/tests.rs @@ -1,14 +1,16 @@ mod tests { use super::super::StateCircuit; - use crate::evm_circuit::witness::RwMap; + use crate::evm_circuit::table::{AccountFieldTag, RwTableTag}; + 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}, - ToAddress, Word, U256, + Address, ToAddress, Word, U256, }; use halo2_proofs::{arithmetic::BaseExt, dev::MockProver, pairing::bn256::Fr}; + use std::collections::HashMap; fn test_state_circuit_ok( memory_ops: Vec>, @@ -182,4 +184,49 @@ mod tests { ); test_state_circuit_ok(vec![memory_op_0, memory_op_1], vec![], vec![]); } + + #[test] + fn failing_test_from_timestamp() { + // really you shoudl just take in a vec of Rw's.... + let rw_map = RwMap(HashMap::from([ + ( + RwTableTag::TxRefund, + vec![Rw::TxRefund { + rw_counter: 25, + is_write: false, + tx_id: 1, // passes when this is 0... + value: 0, + value_prev: 0, + }], + ), + ( + RwTableTag::Account, + vec![Rw::Account { + rw_counter: 4, + is_write: true, + account_address: Address::default(), + field_tag: AccountFieldTag::Nonce, + value: U256::one(), + value_prev: U256::zero(), + }], + ), + ])); + let circuit = StateCircuit { + randomness: Fr::rand(), + rw_map, + }; + + let power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + circuit.randomness.pow(&[exp, 0, 0, 0]); + 2usize.pow(6)// you don't need the full value here, because they get padded later. + ] + }) + .collect(); + + let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); + let verify_result = prover.verify(); + assert_eq!(verify_result, Ok(())); + } } From 1b59c57e223f31f92d181bd00d6c36810af48292 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Wed, 6 Apr 2022 21:10:19 -0400 Subject: [PATCH 05/31] fix clippy --- circuit-benchmarks/src/state_circuit.rs | 9 +-- integration-tests/tests/circuits.rs | 22 ++----- zkevm-circuits/src/state_circuit.rs | 8 ++- .../src/state_circuit/constraint_builder.rs | 9 ++- .../state_circuit/lexicographic_ordering.rs | 25 ++++--- zkevm-circuits/src/state_circuit/tests.rs | 65 ++----------------- 6 files changed, 37 insertions(+), 101 deletions(-) diff --git a/circuit-benchmarks/src/state_circuit.rs b/circuit-benchmarks/src/state_circuit.rs index b26cbd47cb..5375b6fc28 100644 --- a/circuit-benchmarks/src/state_circuit.rs +++ b/circuit-benchmarks/src/state_circuit.rs @@ -20,14 +20,7 @@ mod tests { #[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..821e454b97 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,14 @@ 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 power_of_randomness = StateCircuit::instance(&randomness, rw_map.0.len()); + + let circuit = StateCircuit::::new(randomness, &rw_map); 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/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index cfc63d19d1..5d6d0ac679 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -7,7 +7,6 @@ mod random_linear_combination; #[cfg(test)] mod tests; -use crate::evm_circuit::table::RwTableTag; use crate::evm_circuit::{param::N_BYTES_WORD, witness::RwMap}; use constraint_builder::{ConstraintBuilder, Queries}; use eth_types::{Address, Field}; @@ -62,6 +61,13 @@ impl StateCircuit { pub fn new(randomness: F, rw_map: RwMap) -> Self { Self { randomness, rw_map } } + + /// powers of randomness for instance columns + pub fn instance(randomness: &F, n_rows: usize) -> Vec> { + (1..32) + .map(|exp| vec![randomness.pow(&[exp, 0, 0, 0]); n_rows]) + .collect() + } } impl Circuit for StateCircuit { diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index 54ee68c2bb..adff8c56b9 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -30,9 +30,12 @@ pub struct Queries { pub lexicographic_ordering_diff_selector: Expression, } +type Constraint = (&'static str, Expression); +type Lookup = (&'static str, (Expression, Expression)); + pub struct ConstraintBuilder { - constraints: Vec<(&'static str, Expression)>, - lookups: Vec<(&'static str, (Expression, Expression))>, + constraints: Vec>, + lookups: Vec>, condition: Expression, } @@ -53,7 +56,7 @@ impl ConstraintBuilder { .collect() } - pub fn lookups(&self) -> Vec<(&'static str, (Expression, Expression))> { + pub fn lookups(&self) -> Vec> { self.lookups.clone() } diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 414668468a..a2a2d5aafa 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -55,23 +55,18 @@ impl Config { .find(|(_, (a, b))| a != b); let (index, (cur_limb, prev_limb)) = find_result.expect("repeated rw counter"); - // TODO: simplify this - let mut diff_1 = F::zero(); + let mut diff_1 = F::from((cur_limb - prev_limb).into()); + // you need to find a valid difference to fill in for diff_2 still. + // you've just been lucky that 0 is a valid value for all of the + // test cases. let mut diff_2 = F::zero(); let mut diff_inverse = F::zero(); - let mut diff_selector = F::zero(); - - if index < 15 { - diff_1 = F::from((cur_limb - prev_limb) as u64); - diff_inverse = diff_1.invert().unwrap(); - diff_selector = F::one(); - - // you need to find a valid difference to fill in for diff_2 still. - // you've just been lucky that 0 is a valid value for all of the - // test cases. - } else { - diff_2 = F::from((cur_limb - prev_limb) as u64); + let mut diff_selector = F::one(); + if index >= 15 { + diff_1 = F::zero(); + diff_2 = F::from((cur_limb - prev_limb).into()); diff_inverse = diff_2.invert().unwrap(); + diff_selector = F::zero(); } region.assign_advice(|| "diff_1", self.diff_1, offset, || Ok(diff_1))?; @@ -104,6 +99,8 @@ impl Chip { } } + #[allow(clippy::too_many_arguments)] + // TODO: fix this to not have too many arguments? pub fn configure( meta: &mut ConstraintSystem, selector: Column, diff --git a/zkevm-circuits/src/state_circuit/tests.rs b/zkevm-circuits/src/state_circuit/tests.rs index 8c8f5240fb..e67b808d25 100644 --- a/zkevm-circuits/src/state_circuit/tests.rs +++ b/zkevm-circuits/src/state_circuit/tests.rs @@ -1,13 +1,12 @@ mod tests { use super::super::StateCircuit; - use crate::evm_circuit::table::{AccountFieldTag, RwTableTag}; - use crate::evm_circuit::witness::{Rw, RwMap}; + use crate::evm_circuit::witness::RwMap; use bus_mapping::operation::{ MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, }; use eth_types::{ evm_types::{MemoryAddress, StackAddress}, - Address, ToAddress, Word, U256, + ToAddress, Word, U256, }; use halo2_proofs::{arithmetic::BaseExt, dev::MockProver, pairing::bn256::Fr}; use std::collections::HashMap; @@ -23,19 +22,10 @@ mod tests { storage: storage_ops, ..Default::default() }); - let circuit = StateCircuit { - randomness: Fr::rand(), - rw_map, - }; - let power_of_randomness: Vec<_> = (1..32) - .map(|exp| { - vec![ - circuit.randomness.pow(&[exp, 0, 0, 0]); - 2usize.pow(6)// you don't need the full value here, because they get padded later. - ] - }) - .collect(); + let randomness = Fr::rand(); + let power_of_randomness = StateCircuit::instance(&randomness, rw_map.0.len()); + let circuit = StateCircuit { randomness, rw_map }; let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); let verify_result = prover.verify(); @@ -184,49 +174,4 @@ mod tests { ); test_state_circuit_ok(vec![memory_op_0, memory_op_1], vec![], vec![]); } - - #[test] - fn failing_test_from_timestamp() { - // really you shoudl just take in a vec of Rw's.... - let rw_map = RwMap(HashMap::from([ - ( - RwTableTag::TxRefund, - vec![Rw::TxRefund { - rw_counter: 25, - is_write: false, - tx_id: 1, // passes when this is 0... - value: 0, - value_prev: 0, - }], - ), - ( - RwTableTag::Account, - vec![Rw::Account { - rw_counter: 4, - is_write: true, - account_address: Address::default(), - field_tag: AccountFieldTag::Nonce, - value: U256::one(), - value_prev: U256::zero(), - }], - ), - ])); - let circuit = StateCircuit { - randomness: Fr::rand(), - rw_map, - }; - - let power_of_randomness: Vec<_> = (1..32) - .map(|exp| { - vec![ - circuit.randomness.pow(&[exp, 0, 0, 0]); - 2usize.pow(6)// you don't need the full value here, because they get padded later. - ] - }) - .collect(); - - let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); - let verify_result = prover.verify(); - assert_eq!(verify_result, Ok(())); - } } From e7c47c4354c086faba7d92b916f7e25dc2c5fa0a Mon Sep 17 00:00:00 2001 From: z2trillion Date: Wed, 6 Apr 2022 21:32:36 -0400 Subject: [PATCH 06/31] Rename test files for clippy --- zkevm-circuits/src/state_circuit.rs | 2 +- .../src/state_circuit/old_state_tests.rs | 788 ++++++++++ .../src/state_circuit/state_tests.rs | 1352 +---------------- 3 files changed, 829 insertions(+), 1313 deletions(-) create mode 100644 zkevm-circuits/src/state_circuit/old_state_tests.rs diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 5d6d0ac679..c901222759 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -5,7 +5,7 @@ mod lookups; mod multiple_precision_integer; mod random_linear_combination; #[cfg(test)] -mod tests; +mod state_tests; use crate::evm_circuit::{param::N_BYTES_WORD, witness::RwMap}; use constraint_builder::{ConstraintBuilder, Queries}; diff --git a/zkevm-circuits/src/state_circuit/old_state_tests.rs b/zkevm-circuits/src/state_circuit/old_state_tests.rs new file mode 100644 index 0000000000..6462dfc1ca --- /dev/null +++ b/zkevm-circuits/src/state_circuit/old_state_tests.rs @@ -0,0 +1,788 @@ +// TODO: migrate these over. + +#[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 mock::TestContext; + use pairing::bn256::Fr; + + 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 power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + circuit.randomness.pow(&[exp, 0, 0, 0]); + { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? + ] + }) + .collect(); + + let prover = MockProver::::run($k, &circuit, power_of_randomness).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 power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + circuit.randomness.pow(&[exp, 0, 0, 0]); + { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? + ] + }) + .collect(); + + let prover = MockProver::::run($k, &circuit, power_of_randomness).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( + 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!( + 17, + 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!( + 17, + 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!( + 17, + 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!( + 17, + 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!( + 18, + 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!( + 18, + 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!( + 17, + 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!( + 18, + 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!( + 17, + 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!( + 17, + 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 index 46a7514a7d..e0a7f15800 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -1,749 +1,39 @@ -use super::cells::Cells; -use super::constraint_builder::ConstraintBuilder; -use super::param::N_LIMBS_ACCOUNT_ADDRESS; -use crate::state_circuit::constraint_builder::sort_key_values; -use crate::state_circuit::fixed_table::FixedTable; -use crate::{ - evm_circuit::{ - param::N_BYTES_WORD, - table::RwTableTag, - util::{constraint_builder::BaseConstraintBuilder, RandomLinearCombination}, - witness::{Rw, RwMap, RwRow}, - }, - gadget::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, - util::Expr, -}; -use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; -use halo2_proofs::{ - circuit::{Layouter, Region, SimpleFloorPlanner}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, - poly::Rotation, -}; -use itertools::Itertools; -use pairing::arithmetic::FieldExt; -use std::convert::TryInto; -use strum::IntoEnumIterator; - -/* -(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 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, - keys: [Column; 5], - - key2_limbs: [Column; N_LIMBS_ACCOUNT_ADDRESS], - // Use WordConfig here instead. - key4_bytes: [Column; N_BYTES_WORD], - - auxs: Column, - - power_of_randomness: [Expression; N_BYTES_WORD - 1], - - lexicographic_ordering: [IsZeroConfig; 2], - - // Fixed columns for range lookups - fixed_table: FixedTable, - - cells: Cells, -} - -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 -{ - /// Set up custom gates and lookup arguments for this configuration. - pub(crate) fn configure( - meta: &mut ConstraintSystem, - power_of_randomness: [Expression; 31], - ) -> Self { - let cells = Cells::new(meta, power_of_randomness.clone()); - - let fixed_table = FixedTable::configure(meta); - - // let rw_counter = meta.advice_column(); - let keys = [(); 5].map(|_| meta.advice_column()); - let key2_limbs = [(); N_LIMBS_ACCOUNT_ADDRESS].map(|_| meta.advice_column()); - let key4_bytes = [(); N_BYTES_WORD].map(|_| meta.advice_column()); - let auxs = meta.advice_column(); - - let s_enable = meta.fixed_column(); - - let new_cb = || BaseConstraintBuilder::::new(MAX_DEGREE); - let qb = ConstraintBuilder::::new( - meta, - keys, - key2_limbs, - s_enable, - key4_bytes, - power_of_randomness.clone(), /* TODO: these don't both of these need - * power_of_randomness */ - /* rw_counter, */ - ); - - let lexicographic_ordering = - [(0, meta.advice_column()), (1, meta.advice_column())].map(|(i, advice_column)| { - IsZeroChip::configure( - meta, - |meta| qb.s_enable(meta), - |meta| qb.sort_keys_delta(meta)[i].clone(), - advice_column, - ) - }); - - let q_all_keys_same = |_: &mut VirtualCells| { - lexicographic_ordering[0].is_zero_expression.clone() - * lexicographic_ordering[1].is_zero_expression.clone() - }; - let q_not_all_keys_same = |meta: &mut VirtualCells| 1u64.expr() - q_all_keys_same(meta); - - ///////////////////////// General constraints ///////////////////////////////// - - meta.create_gate("General constraints", |meta| { - let mut cb = new_cb(); - - // 0. tag in RwTableTag range - // TODO: check key1 and key3 ranges. - cb.require_in_set( - "tag in RwTableTag range", - qb.tag(meta), - RwTableTag::iter().map(|x| x.expr()).collect(), - ); - - // 1. key2 expands to its limbs - cb.require_equal( - "account address matches its limbs", - qb.address(meta), - qb.address_limbs(meta) - .iter() - .fold(0u64.expr(), |result, limb| { - limb.clone() + result * (1u64 << 16).expr() - }), - // qb.address_from_limbs(meta), - ); - - // 2. key4 is RLC encoded - cb.require_equal( - "storage key matches its RLC encoding", - qb.storage_key(meta), - RandomLinearCombination::random_linear_combine_expr( - qb.storage_key_bytes(meta), - qb.power_of_randomness(meta), - ), - ); - - // 3. is_write is boolean - cb.require_boolean("is_write should be boolean", cells.is_write()); - - // 4. Keys are sorted in lexicographic order for same Tag - // see lexicographic_ordering chips - - // 5. RWC is monotonically strictly increasing for a set of all keys - // see below - - // 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) * cells.is_read() * (cells.value_delta()), - ); - - cb.gate(qb.s_enable(meta)) - }); - - // TODO: move this into constraint builder - for i in 0..N_LIMBS_ACCOUNT_ADDRESS { - meta.lookup_any("address limbs fit into u16", |meta| { - vec![( - qb.s_enable(meta) * qb.address_limbs(meta)[i].clone(), - fixed_table.u16(meta), - )] - }); - } - - // Check that storage key bytes are between 0 and 255. - // TODO: move this into constraint builder - for i in 0..N_BYTES_WORD { - meta.lookup_any("storage key byte is between 0 and 255", |meta| { - vec![( - qb.s_enable(meta) * qb.storage_key_bytes(meta)[i].clone(), - fixed_table.u8(meta), - )] - }); - } - - // 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| { - vec![( - qb.s_enable(meta) - * q_all_keys_same(meta) - * (cells.rw_counter_delta() - 1u64.expr()), - // TODO(mason) this isn't correct. The specs say this should be u32.... - fixed_table.u10(meta), - )] - }); - - ///////////////////////// Memory related constraints ///////////////////////// - - meta.create_gate("Memory operation", |meta| { - let mut cb = new_cb(); - - // 0. Unused keys are 0 - cb.require_zero("field tag is 0", qb.field_tag(meta)); - cb.require_zero("storage key is 0", qb.storage_key(meta)); - - // 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) * cells.is_read() * cells.value(), - ); - - cb.gate(qb.s_enable(meta) * qb.tag_is(meta, RwTableTag::Memory)) - }); - - // 2. value is a byte when tag is Memory - meta.lookup_any("value is a byte when tag is Memory", |meta| { - vec![( - qb.tag_is(meta, RwTableTag::Memory) * cells.value(), - fixed_table.u8(meta), - )] - }); - - ///////////////////////// Stack related constraints ///////////////////////// - - meta.create_gate("Stack operation", |meta| { - let mut cb = new_cb(); - - // 0. Unused keys are 0 - cb.require_zero("field tag is 0", qb.field_tag(meta)); - cb.require_zero("storage key is 0", qb.storage_key(meta)); - - // 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) * cells.is_read(), - ); - cb.gate(qb.s_enable(meta) * qb.tag_is(meta, RwTableTag::Stack)) - }); - - // 2. stack_ptr in range - meta.lookup_any("Stack address in allowed range", |meta| { - vec![( - qb.tag_is(meta, RwTableTag::Stack) * qb.address(meta), - // todo: this should be u32, so we need to add two rw limb columns. - fixed_table.u10(meta), - )] - }); - - // 3. stack_ptr only increases by 0 or 1 - meta.create_gate("Within a call, Stack pointer diff be 0 or 1", |meta| { - let mut cb = new_cb(); - cb.require_boolean( - "stack pointer only increases by 0 or 1", - qb.address_delta(meta), - ); - cb.gate(qb.s_enable(meta) * q_all_keys_same(meta) * qb.tag_is(meta, RwTableTag::Stack)) - }); - - ///////////////////////// Storage related constraints ///////////////////////// - - meta.create_gate("Storage Operation", |meta| { - let mut cb = new_cb(); - // 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", qb.id(meta)); // moved from aux to key1 - cb.require_zero("key3 is 0", qb.field_tag(meta)); - - // 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) * cells.is_read(), - ); - cb.require_zero( - "First access for storage has rw_counter as 0", - q_not_all_keys_same(meta) * cells.rw_counter(), - ); - - cb.gate(qb.s_enable(meta) * qb.tag_is(meta, RwTableTag::AccountStorage)) - }); - - Config { - keys, - key2_limbs, - key4_bytes, - auxs, - s_enable, - fixed_table, - power_of_randomness, - lexicographic_ordering, - cells, - } - } - - /// Assign cells. - pub(crate) fn assign( - &self, - mut layouter: impl Layouter, - randomness: F, - rw_map: &RwMap, - ) -> Result<(), Error> { - let sort_key_chips = self - .lexicographic_ordering - .clone() - .map(|config| IsZeroChip::construct(config)); - - layouter.assign_region( - || "State operations", - |mut region| { - let mut offset = 1; - let mut rows: Vec<(Rw, RwRow)> = rw_map - .0 - .values() - .flatten() - .map(|row| (row.clone(), row.table_assignment(randomness))) - .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, (rw, rw_row)) in rows.iter().enumerate() { - self.assign_row(&mut region, offset, *rw_row)?; - - if let Some(address) = rw.address() { - self.assign_address_and_limbs(&mut region, offset, address)?; - } - - if let Some(storage_key) = rw.storage_key() { - self.assign_storage_key_and_bytes( - &mut region, - offset, - randomness, - storage_key, - )?; - } - - let (sort_key_0, sort_key_1): (F, F) = sort_key_values( - rw.tag(), - rw.id().unwrap_or_default().try_into().unwrap(), - rw.address().unwrap_or_default(), - rw.field_tag().unwrap_or_default(), - rw.storage_key().unwrap_or_default().to_le_bytes(), - ); - - let mut sort_key_prev_0 = F::zero(); - let mut sort_key_prev_1 = F::zero(); - if index != 0 { - let rw_prev = rows[index - 1].0.clone(); - - let (a, b) = sort_key_values( - rw_prev.tag(), - rw_prev.id().unwrap_or_default().try_into().unwrap(), - rw_prev.address().unwrap_or_default(), - rw_prev.field_tag().unwrap_or_default(), - rw_prev.storage_key().unwrap_or_default().to_le_bytes(), - ); - - sort_key_prev_0 = a; - sort_key_prev_1 = b; - } - - sort_key_chips[0].assign( - &mut region, - offset, - Some(sort_key_0 - sort_key_prev_0), - )?; - sort_key_chips[1].assign( - &mut region, - offset, - Some(sort_key_1 - sort_key_prev_1), - )?; - - offset += 1; - } - - Ok(()) - }, - ) - } - - fn assign_row( - &self, - region: &mut Region<'_, F>, - offset: usize, - row: RwRow, - ) -> Result<(), Error> { - let memory_or_stack_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) - && memory_or_stack_address > F::from(STACK_ADDRESS_MAX as u64) - { - panic!( - "stack address out of range {:?} > {}", - memory_or_stack_address, STACK_ADDRESS_MAX - ); - } - if row.tag == F::from(MEMORY_TAG as u64) - && memory_or_stack_address > F::from(MEMORY_ADDRESS_MAX as u64) - { - panic!( - "memory address out of range {:?} > {}", - memory_or_stack_address, MEMORY_ADDRESS_MAX - ); - } - } - region.assign_fixed(|| "enable row", self.s_enable, offset, || Ok(F::one()))?; - self.cells - .rw_counter - .assign(region, offset, Some(rw_counter))?; - self.cells.value.assign(region, offset, Some(value))?; - self.cells.is_write.assign(region, offset, Some(is_write))?; - - for i in 0..5 { - let value = match i { - // FIXME: find a better way here - 0 => row.tag, - 1 => row.key1, - 2 => row.key2, - 3 => row.key3, - 4 => row.key4, - _ => unreachable!(), - }; - region.assign_advice( - || format!("assign key{}", i), - self.keys[i], - offset, - || Ok(value), - )?; - } - - Ok(()) - } - - fn assign_address_and_limbs( - &self, - region: &mut Region<'_, F>, - offset: usize, - address: Address, - ) -> Result<(), Error> { - let limbs = address - .0 - .iter() - .tuples() - .map(|(hi, lo)| u16::from_le_bytes([*lo, *hi])); - for (limb, col) in limbs.zip(&self.key2_limbs) { - region.assign_advice(|| "key2_limb", *col, offset, || Ok(F::from(limb.into())))?; - } - - region.assign_advice( - || "key2 (address)", - self.keys[2], - offset, - || Ok(address.to_scalar().unwrap()), - )?; - - Ok(()) - } - - fn assign_storage_key_and_bytes( - &self, - region: &mut Region<'_, F>, - offset: usize, - randomness: F, - key: Word, - ) -> Result<(), Error> { - region.assign_advice( - || "key4 (storage_key)", - self.keys[4], - offset, - || { - Ok(RandomLinearCombination::random_linear_combine( - key.to_le_bytes(), - randomness, - )) - }, - )?; - - // TODO: use array_zip if/when it stabilizes. - for (col, byte) in self.key4_bytes.iter().zip(&key.to_le_bytes()) { - region.assign_advice( - || "storage key byte", - *col, - offset, - || Ok(F::from(*byte as u64)), - )?; - } - - 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, - // pub operations: OperationContainer, -} - -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(), - // operations, - } - } -} - -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 { - let power_of_randomness = { - let columns = [(); 31].map(|_| meta.instance_column()); - let mut power_of_randomness = None; - - meta.create_gate("", |meta| { - power_of_randomness = - Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); - - [0.expr()] - }); - - power_of_randomness.unwrap() - }; - - Config::configure(meta, power_of_randomness) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - config.fixed_table.load(&mut layouter)?; - config.assign(layouter, self.randomness, &self.rw_map)?; - - Ok(()) - } -} -#[cfg(test)] mod tests { - use super::*; - use bus_mapping::mock::BlockData; + use super::super::StateCircuit; + use crate::evm_circuit::witness::RwMap; use bus_mapping::operation::{ MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, }; use eth_types::{ - address, bytecode, evm_types::{MemoryAddress, StackAddress}, - geth_types::GethData, - Word, + ToAddress, Word, U256, }; - 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 power_of_randomness: Vec<_> = (1..32) - .map(|exp| { - vec![ - circuit.randomness.pow(&[exp, 0, 0, 0]); - { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? - ] - }) - .collect(); - - let prover = MockProver::::run($k, &circuit, power_of_randomness).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); + use halo2_proofs::{arithmetic::BaseExt, dev::MockProver}; + use pairing::bn256::Fr; + + 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 power_of_randomness: Vec<_> = (1..32) - .map(|exp| { - vec![ - circuit.randomness.pow(&[exp, 0, 0, 0]); - { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? - ] - }) - .collect(); + let randomness = Fr::rand(); + let power_of_randomness = StateCircuit::instance(&randomness, rw_map.0.len()); + let circuit = StateCircuit { randomness, rw_map }; - let prover = MockProver::::run($k, &circuit, power_of_randomness).unwrap(); - assert!(prover.verify().is_err()); - }}; + let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); + let verify_result = prover.verify(); + assert_eq!(verify_result, Ok(())); } #[test] - fn state_circuit_simple() { + fn state_circuit_simple_2() { let memory_op_0 = Operation::new( RWCounter::from(12), RW::WRITE, @@ -814,636 +104,74 @@ mod tests { ), ); - test_state_circuit_ok!( - 17, - 2000, - 100, - 2, - 100, - 1023, - 1000, + 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], - Ok(()) ); } #[test] - fn no_stack_padding() { + 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(24), + RWCounter::from(13), 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!( - 17, - 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( + let storage_op_2 = 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!( - 17, - 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"), + 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::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!( - 17, - 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_state_circuit_ok(vec![memory_op_0, memory_op_1], vec![], vec![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( + fn lexicographic_ordering_test_1() { + let memory_op = 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!( - 18, - 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!( - 18, - 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), + let storage_op = Operation::new( + RWCounter::from(19), 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"), + U256::from(100).to_address(), 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!( - 17, - 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 - ] - ); + test_state_circuit_ok(vec![memory_op], vec![], vec![storage_op]); } - #[ignore = "disabled temporarily since we sort rws inside the assign method. FIXME later"] #[test] - fn non_monotone_address() { + fn lexicographic_ordering_test_2() { let memory_op_0 = Operation::new( - RWCounter::from(1352), + RWCounter::from(12), 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), + RWCounter::from(13), 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!( - 18, - 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!( - 17, - 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!( - 17, - 2000, - 100, - 0x80, - 100, - 1023, - 1000, - memory_ops, - stack_ops, - storage_ops, - Ok(()) - ); + test_state_circuit_ok(vec![memory_op_0, memory_op_1], vec![], vec![]); } } From 450a202f779c4ddfbfe5280ac941403d251f4737 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Wed, 6 Apr 2022 21:37:03 -0400 Subject: [PATCH 07/31] Cleanup benchmarks --- circuit-benchmarks/src/state_circuit.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/circuit-benchmarks/src/state_circuit.rs b/circuit-benchmarks/src/state_circuit.rs index 5375b6fc28..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,9 +14,6 @@ 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() { From 79bff4a58723121290ec0db17f2d7e8924620ba7 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Wed, 6 Apr 2022 22:18:43 -0400 Subject: [PATCH 08/31] fix build --- integration-tests/tests/circuits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index 821e454b97..f7e312dda3 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -53,7 +53,7 @@ async fn test_state_circuit_block(block_num: u64) { let randomness = Fr::rand(); let power_of_randomness = StateCircuit::instance(&randomness, rw_map.0.len()); - let circuit = StateCircuit::::new(randomness, &rw_map); + let circuit = StateCircuit::::new(randomness, rw_map); use halo2_proofs::pairing::bn256::Fr as Fp; let prover = MockProver::::run(DEGREE as u32, &circuit, power_of_randomness).unwrap(); From e39b198852fb1e7e2f5e3dc2ae57f2c709089948 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Wed, 6 Apr 2022 23:45:15 -0400 Subject: [PATCH 09/31] Assign diff_inverse correctly --- .../src/state_circuit/lexicographic_ordering.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index a2a2d5aafa..08feac4d7d 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -56,11 +56,12 @@ impl Config { let (index, (cur_limb, prev_limb)) = find_result.expect("repeated rw counter"); let mut diff_1 = F::from((cur_limb - prev_limb).into()); - // you need to find a valid difference to fill in for diff_2 still. - // you've just been lucky that 0 is a valid value for all of the - // test cases. - let mut diff_2 = F::zero(); - let mut diff_inverse = F::zero(); + let mut diff_2 = if cur_be_limbs[15] >= prev_be_limbs[15] { + F::from((cur_be_limbs[15] - prev_be_limbs[15]).into()) + } else { + -F::from((prev_be_limbs[15] - cur_be_limbs[15]).into()) + }; + let mut diff_inverse = diff_1.invert().unwrap(); let mut diff_selector = F::one(); if index >= 15 { diff_1 = F::zero(); From ea9c4f872a77ac68f9ebbd7f59d727e9ac126510 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 11 Apr 2022 10:55:33 -0400 Subject: [PATCH 10/31] Fix instance function and convert it to a method --- integration-tests/tests/circuits.rs | 3 +-- zkevm-circuits/src/state_circuit.rs | 5 +++-- zkevm-circuits/src/state_circuit/state_tests.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index f7e312dda3..9c7f17feef 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -51,9 +51,8 @@ async fn test_state_circuit_block(block_num: u64) { }); let randomness = Fr::rand(); - let power_of_randomness = StateCircuit::instance(&randomness, rw_map.0.len()); - 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, power_of_randomness).unwrap(); diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index c901222759..f4b298e366 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -63,9 +63,10 @@ impl StateCircuit { } /// powers of randomness for instance columns - pub fn instance(randomness: &F, n_rows: usize) -> Vec> { + pub fn instance(&self) -> Vec> { + let n_rows = self.rw_map.0.values().flatten().count(); (1..32) - .map(|exp| vec![randomness.pow(&[exp, 0, 0, 0]); n_rows]) + .map(|exp| vec![self.randomness.pow(&[exp, 0, 0, 0]); n_rows]) .collect() } } diff --git a/zkevm-circuits/src/state_circuit/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs index e0a7f15800..7417cc5e09 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -24,8 +24,8 @@ mod tests { }); let randomness = Fr::rand(); - let power_of_randomness = StateCircuit::instance(&randomness, rw_map.0.len()); let circuit = StateCircuit { randomness, rw_map }; + let power_of_randomness = circuit.instance(); let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); let verify_result = prover.verify(); From 25750df879a2c4c605ae90e6e3ab2241bd01cc5f Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 11 Apr 2022 11:02:44 -0400 Subject: [PATCH 11/31] Fix storage_key_be_limbs method --- zkevm-circuits/src/state_circuit/lexicographic_ordering.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 08feac4d7d..5abaeb0c15 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -227,11 +227,12 @@ impl Queries { (1u64 << 4).expr() * self.tag.clone() + self.field_tag.clone() } - fn storage_key_limbs(&self) -> Vec> { + fn storage_key_be_limbs(&self) -> Vec> { self.storage_key_bytes .iter() + .rev() .tuples() - .map(|(hi, lo)| (1u64 << 16).expr() * hi.clone() + lo.clone()) + .map(|(hi, lo)| (1u64 << 8).expr() * hi.clone() + lo.clone()) .collect() } @@ -241,7 +242,7 @@ impl Queries { .iter() .rev() .chain(self.address_limbs.iter().rev()) - .chain(self.storage_key_limbs().iter().rev()) + .chain(&self.storage_key_be_limbs()) .chain(self.rw_counter_limbs.iter().rev()) .cloned() .collect(); From f8e6676e7af314d4a9e3a21f5206abcb67b50d90 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 11 Apr 2022 11:25:18 -0400 Subject: [PATCH 12/31] Add degree test --- zkevm-circuits/src/state_circuit/lookups.rs | 7 ------- zkevm-circuits/src/state_circuit/state_tests.rs | 10 ++++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/zkevm-circuits/src/state_circuit/lookups.rs b/zkevm-circuits/src/state_circuit/lookups.rs index 9e54b38de0..d90ee9e670 100644 --- a/zkevm-circuits/src/state_circuit/lookups.rs +++ b/zkevm-circuits/src/state_circuit/lookups.rs @@ -37,13 +37,6 @@ impl Queries { } } -// impl Config { -// pub fn u8_range(&self) -> Expression { -// self.u8.cur.clone() -// } -// } - -// This doesn't seem like it needs to exist? pub struct Chip { config: Config, _marker: PhantomData, diff --git a/zkevm-circuits/src/state_circuit/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs index 7417cc5e09..83d3c77a35 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -8,6 +8,8 @@ mod tests { evm_types::{MemoryAddress, StackAddress}, ToAddress, Word, U256, }; + use halo2_proofs::plonk::Circuit; + use halo2_proofs::plonk::ConstraintSystem; use halo2_proofs::{arithmetic::BaseExt, dev::MockProver}; use pairing::bn256::Fr; @@ -32,6 +34,14 @@ mod tests { assert_eq!(verify_result, Ok(())); } + #[test] + fn degree() { + let mut meta = ConstraintSystem::::default(); + StateCircuit::configure(&mut meta); + + assert_eq!(meta.degree(), 16); + } + #[test] fn state_circuit_simple_2() { let memory_op_0 = Operation::new( From 691ea89347a413c57d3677115df2b329e0f575b1 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Tue, 12 Apr 2022 20:54:26 -0400 Subject: [PATCH 13/31] Have StateCircuit use Vec instead of RwMap --- zkevm-circuits/src/state_circuit.rs | 45 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index f4b298e366..e547064722 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -7,7 +7,10 @@ mod random_linear_combination; #[cfg(test)] mod state_tests; -use crate::evm_circuit::{param::N_BYTES_WORD, witness::RwMap}; +use crate::evm_circuit::{ + param::N_BYTES_WORD, + witness::{Rw, RwMap}, +}; use constraint_builder::{ConstraintBuilder, Queries}; use eth_types::{Address, Field}; use halo2_proofs::{ @@ -52,21 +55,31 @@ type Lookup = (&'static str, Expression, Expression); /// State Circuit for proving RwTable is valid #[derive(Default)] pub struct StateCircuit { - randomness: F, - rw_map: RwMap, + pub(crate) randomness: F, + pub(crate) rows: Vec, } impl StateCircuit { /// make a new state circuit pub fn new(randomness: F, rw_map: RwMap) -> Self { - Self { randomness, rw_map } + 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> { - let n_rows = self.rw_map.0.values().flatten().count(); (1..32) - .map(|exp| vec![self.randomness.pow(&[exp, 0, 0, 0]); n_rows]) + .map(|exp| vec![self.randomness.pow(&[exp, 0, 0, 0]); self.rows.len()]) .collect() } } @@ -136,26 +149,10 @@ impl Circuit for StateCircuit { ) -> Result<(), Error> { LookupsChip::construct(config.lookups).load(&mut layouter)?; - // TODO: move sorting out of synthesize, so we can check that unsorted witnesses - // don't verify. - let mut rows: Vec<_> = self.rw_map.0.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(), - ) - }); - - dbg!(rows.clone()); - layouter.assign_region( || "assign rw table", |mut region| { - for (offset, row) in rows.iter().enumerate() { + for (offset, row) in self.rows.iter().enumerate() { if offset != 0 { // just treat selector as is_start? region.assign_fixed( @@ -169,7 +166,7 @@ impl Circuit for StateCircuit { &mut region, offset, row, - rows[offset - 1], + &self.rows[offset - 1], )?; } config From 04847ec6409f637affe9c1ca1f5b929766a325f3 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Tue, 12 Apr 2022 22:40:21 -0400 Subject: [PATCH 14/31] Compute first_access correctly --- zkevm-circuits/src/evm_circuit/table.rs | 2 +- zkevm-circuits/src/state_circuit.rs | 52 ++++++++++----- .../src/state_circuit/constraint_builder.rs | 65 ++++++++++++++----- .../src/state_circuit/state_tests.rs | 44 +++++++++++-- 4 files changed, 122 insertions(+), 41 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index fa8f60b1d2..a1efbea8db 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -135,8 +135,8 @@ pub enum BlockContextFieldTag { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)] pub enum RwTableTag { Start = 1, - Memory, Stack, + Memory, AccountStorage, TxAccessListAccount, TxAccessListAccountStorage, diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index e547064722..d6503bc60e 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -42,12 +42,15 @@ pub struct StateConfig { id: MpiConfig, address: MpiConfig, field_tag: Column, + // use WordConfig storage_key: RlcConfig, value: Column, lookups: LookupsConfig, power_of_randomness: [Column; N_BYTES_WORD - 1], // lexicographic_ordering config, etc. lexicographic_ordering: LexicographicOrderingConfig, + // use IsZeroChip instead. + storage_key_diff_inverse: Column, } type Lookup = (&'static str, Expression, Expression); @@ -97,7 +100,8 @@ impl Circuit for StateCircuit { 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] = [0; 4].map(|_| meta.advice_column()); + let [is_write, tag, field_tag, value, storage_key_diff_inverse] = + [0; 5].map(|_| meta.advice_column()); let id = MpiChip::configure(meta, selector, lookups.u16); let address = MpiChip::configure(meta, selector, lookups.u16); @@ -125,6 +129,7 @@ impl Circuit for StateCircuit { rw_counter.limbs, lookups.u16, ), + storage_key_diff_inverse, lookups, power_of_randomness, }; @@ -149,12 +154,15 @@ impl Circuit for StateCircuit { ) -> Result<(), Error> { LookupsChip::construct(config.lookups).load(&mut layouter)?; + dbg!(self.rows.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 { - // just treat selector as is_start? region.assign_fixed( || "selector", config.selector, @@ -172,11 +180,12 @@ impl Circuit for StateCircuit { config .rw_counter .assign(&mut region, offset, row.rw_counter() as u32)?; + dbg!(row, if row.is_write() { F::one() } else { F::zero() }); region.assign_advice( || "is_write", config.is_write, offset, - || Ok(F::from(row.is_write() as u64)), + || Ok(if row.is_write() { F::one() } else { F::zero() }), )?; region.assign_advice( || "tag", @@ -199,23 +208,30 @@ impl Circuit for StateCircuit { )?; } - // TODO: must explicitly assign to cells to convince MockProver. - // I don't think this was needed in the past? - // this also isn't needed for address??? - // config.storage_key.assign( - // &mut region, - // offset, - // self.randomness, - // row.storage_key().unwrap_or_default(), - // )?; - if let Some(storage_key) = row.storage_key() { - config.storage_key.assign( + // TODO: chain assigments idiomatically. + let cur_storage_key = *(config + .storage_key + .assign( &mut region, offset, self.randomness, - storage_key, - )?; - } + row.storage_key().unwrap_or_default(), + )? + .value() + .unwrap_or(&F::zero())); + + region.assign_advice( + || "storage_key_diff_inverse", + config.storage_key_diff_inverse, + offset, + || { + Ok((cur_storage_key - prev_storage_key) + .invert() + .unwrap_or(F::zero())) + }, + )?; + + prev_storage_key = cur_storage_key; } Ok(()) }, @@ -229,6 +245,7 @@ fn queries(meta: &mut VirtualCells<'_, F>, c: StateConfig) -> Queries< 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()), @@ -240,5 +257,6 @@ fn queries(meta: &mut VirtualCells<'_, F>, c: StateConfig) -> Queries< .map(|c| meta.query_instance(c, Rotation::cur())), lexicographic_ordering_diff_selector: meta .query_advice(c.lexicographic_ordering.diff_selector, Rotation::cur()), + storage_key_diff_inverse: meta.query_advice(c.storage_key_diff_inverse, Rotation::cur()), } } diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index adff8c56b9..935e2781bc 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -19,6 +19,7 @@ pub struct Queries { 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, @@ -28,6 +29,7 @@ pub struct Queries { pub power_of_randomness: [Expression; N_BYTES_WORD - 1], pub lexicographic_ordering_diff_selector: Expression, + pub storage_key_diff_inverse: Expression, } type Constraint = (&'static str, Expression); @@ -98,6 +100,17 @@ impl ConstraintBuilder { 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()); + + // TODO: use IsZeroChip instead. + self.require_zero( + "todooooo_1", + q.is_storage_key_diff_zero() * q.storage_key_diff_inverse.clone(), + ); + self.require_zero( + "todooooo_2", + q.is_storage_key_diff_zero() + * (q.storage_key.encoded.clone() - q.storage_key.encoded_prev.clone()), + ); } fn build_start_constraints(&mut self, q: &Queries) { @@ -127,16 +140,20 @@ impl ConstraintBuilder { 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() * q.is_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()), ); - self.require_zero( - "if call_id doesn't change, stack address change is 0 or 1", - q.id_change() * q.address_change(), - ) + self.condition(q.first_access(), |cb| { + cb.require_zero( + "previous tag is Start, address change is 0 or address change is 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) { @@ -145,12 +162,13 @@ impl ConstraintBuilder { // 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 - 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()); - }) + // 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()); @@ -190,12 +208,12 @@ impl ConstraintBuilder { 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()); - }); + // // 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) { @@ -294,13 +312,24 @@ impl Queries { } fn first_access(&self) -> Expression { + not::expr(self.not_first_access()) + } + + fn not_first_access(&self) -> Expression { not::expr(self.lexicographic_ordering_diff_selector.clone()) - * (self.storage_key.encoded.clone() - self.storage_key.encoded_prev.clone()) + * self.is_storage_key_diff_zero() } fn address_change(&self) -> Expression { self.address.value.clone() - self.address.value_prev.clone() } + + // replace this with IsZeroChip + fn is_storage_key_diff_zero(&self) -> Expression { + 1.expr() + - self.storage_key_diff_inverse.clone() + * (self.storage_key.encoded.clone() - self.storage_key.encoded_prev.clone()) + } } fn from_digits(digits: &[Expression], base: Expression) -> Expression { diff --git a/zkevm-circuits/src/state_circuit/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs index 83d3c77a35..ab7be49709 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -1,6 +1,6 @@ mod tests { use super::super::StateCircuit; - use crate::evm_circuit::witness::RwMap; + use crate::evm_circuit::witness::{Rw, RwMap}; use bus_mapping::operation::{ MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, }; @@ -8,9 +8,11 @@ mod tests { evm_types::{MemoryAddress, StackAddress}, ToAddress, Word, U256, }; - use halo2_proofs::plonk::Circuit; - use halo2_proofs::plonk::ConstraintSystem; - use halo2_proofs::{arithmetic::BaseExt, dev::MockProver}; + use halo2_proofs::{ + arithmetic::BaseExt, + dev::{MockProver, VerifyFailure}, + plonk::{Circuit, ConstraintSystem}, + }; use pairing::bn256::Fr; fn test_state_circuit_ok( @@ -26,7 +28,7 @@ mod tests { }); let randomness = Fr::rand(); - let circuit = StateCircuit { randomness, rw_map }; + let circuit = StateCircuit::new(randomness, rw_map); let power_of_randomness = circuit.instance(); let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); @@ -184,4 +186,36 @@ mod tests { ); 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(())); + } + + fn verify(rows: Vec) -> Result<(), Vec> { + let randomness = Fr::rand(); + let circuit = StateCircuit { randomness, rows }; + let power_of_randomness = circuit.instance(); + + MockProver::::run(19, &circuit, power_of_randomness) + .unwrap() + .verify() + } } From 374cf71f0a1553ad8b955a8b1584dd93ac1a29dd Mon Sep 17 00:00:00 2001 From: z2trillion Date: Wed, 13 Apr 2022 10:20:33 -0400 Subject: [PATCH 15/31] wip, but tests pass --- zkevm-circuits/src/state_circuit.rs | 62 +++--- .../src/state_circuit/constraint_builder.rs | 41 ++-- .../state_circuit/lexicographic_ordering.rs | 207 +++++++++--------- .../src/state_circuit/state_tests.rs | 2 +- 4 files changed, 152 insertions(+), 160 deletions(-) diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index d6503bc60e..90e508b9ab 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -11,6 +11,7 @@ use crate::evm_circuit::{ param::N_BYTES_WORD, witness::{Rw, RwMap}, }; +use crate::gadget::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; use constraint_builder::{ConstraintBuilder, Queries}; use eth_types::{Address, Field}; use halo2_proofs::{ @@ -32,8 +33,8 @@ const N_LIMBS_ACCOUNT_ADDRESS: usize = 10; const N_LIMBS_ID: usize = 2; /// Config for StateCircuit -#[derive(Clone, Copy)] -pub struct StateConfig { +#[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, @@ -42,15 +43,13 @@ pub struct StateConfig { id: MpiConfig, address: MpiConfig, field_tag: Column, - // use WordConfig + // Consider using WordConfig instead? storage_key: RlcConfig, + is_storage_key_unchanged: IsZeroConfig, value: Column, lookups: LookupsConfig, power_of_randomness: [Column; N_BYTES_WORD - 1], - // lexicographic_ordering config, etc. - lexicographic_ordering: LexicographicOrderingConfig, - // use IsZeroChip instead. - storage_key_diff_inverse: Column, + lexicographic_ordering: LexicographicOrderingConfig, } type Lookup = (&'static str, Expression, Expression); @@ -88,7 +87,7 @@ impl StateCircuit { } impl Circuit for StateCircuit { - type Config = StateConfig; + type Config = StateConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -100,7 +99,7 @@ impl Circuit for StateCircuit { 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, storage_key_diff_inverse] = + 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); @@ -108,6 +107,16 @@ impl Circuit for StateCircuit { 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, @@ -129,14 +138,14 @@ impl Circuit for StateCircuit { rw_counter.limbs, lookups.u16, ), - storage_key_diff_inverse, + 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); + let queries = queries(meta, &config); constraint_builder.build(&queries); constraint_builder.gate(queries.selector) }); @@ -153,6 +162,11 @@ impl Circuit for StateCircuit { 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()); dbg!(self.rows.clone()); @@ -170,7 +184,7 @@ impl Circuit for StateCircuit { || Ok(F::one()), )?; - config.lexicographic_ordering.assign( + lexicographic_ordering_chip.assign( &mut region, offset, row, @@ -180,7 +194,7 @@ impl Circuit for StateCircuit { config .rw_counter .assign(&mut region, offset, row.rw_counter() as u32)?; - dbg!(row, if row.is_write() { F::one() } else { F::zero() }); + // dbg!(row, if row.is_write() { F::one() } else { F::zero() }); region.assign_advice( || "is_write", config.is_write, @@ -220,15 +234,10 @@ impl Circuit for StateCircuit { .value() .unwrap_or(&F::zero())); - region.assign_advice( - || "storage_key_diff_inverse", - config.storage_key_diff_inverse, + is_storage_key_unchanged.assign( + &mut region, offset, - || { - Ok((cur_storage_key - prev_storage_key) - .invert() - .unwrap_or(F::zero())) - }, + Some(cur_storage_key - prev_storage_key), )?; prev_storage_key = cur_storage_key; @@ -239,7 +248,7 @@ impl Circuit for StateCircuit { } } -fn queries(meta: &mut VirtualCells<'_, F>, c: StateConfig) -> Queries { +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), @@ -255,8 +264,11 @@ fn queries(meta: &mut VirtualCells<'_, F>, c: StateConfig) -> Queries< power_of_randomness: c .power_of_randomness .map(|c| meta.query_instance(c, Rotation::cur())), - lexicographic_ordering_diff_selector: meta - .query_advice(c.lexicographic_ordering.diff_selector, Rotation::cur()), - storage_key_diff_inverse: meta.query_advice(c.storage_key_diff_inverse, Rotation::cur()), + is_storage_key_unchanged: c.is_storage_key_unchanged.is_zero_expression.clone(), + lexicographic_ordering_diff_1_is_zero: c + .lexicographic_ordering + .diff_1_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 index 935e2781bc..b7c822d59b 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -27,16 +27,15 @@ pub struct Queries { pub value: Expression, pub lookups: LookupsQueries, pub power_of_randomness: [Expression; N_BYTES_WORD - 1], - - pub lexicographic_ordering_diff_selector: Expression, - pub storage_key_diff_inverse: Expression, + pub is_storage_key_unchanged: Expression, + pub lexicographic_ordering_diff_1_is_zero: Expression, } type Constraint = (&'static str, Expression); type Lookup = (&'static str, (Expression, Expression)); pub struct ConstraintBuilder { - constraints: Vec>, + pub constraints: Vec>, lookups: Vec>, condition: Expression, } @@ -51,6 +50,13 @@ impl ConstraintBuilder { } pub fn gate(&self, condition: Expression) -> Vec<(&'static str, Expression)> { + // for (name, constraint) in &self.constraints { + // if constraint.degree() >= 16 { + // dbg!(name); + // panic!(); + // } + // // assert!(constraint.degree() <= 16); + // } self.constraints .iter() .cloned() @@ -100,17 +106,6 @@ impl ConstraintBuilder { 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()); - - // TODO: use IsZeroChip instead. - self.require_zero( - "todooooo_1", - q.is_storage_key_diff_zero() * q.storage_key_diff_inverse.clone(), - ); - self.require_zero( - "todooooo_2", - q.is_storage_key_diff_zero() - * (q.storage_key.encoded.clone() - q.storage_key.encoded_prev.clone()), - ); } fn build_start_constraints(&mut self, q: &Queries) { @@ -146,6 +141,7 @@ impl ConstraintBuilder { "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, address change is 0 or address change is 1", @@ -312,24 +308,13 @@ impl Queries { } fn first_access(&self) -> Expression { - not::expr(self.not_first_access()) - } - - fn not_first_access(&self) -> Expression { - not::expr(self.lexicographic_ordering_diff_selector.clone()) - * self.is_storage_key_diff_zero() + not::expr(self.lexicographic_ordering_diff_1_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() } - - // replace this with IsZeroChip - fn is_storage_key_diff_zero(&self) -> Expression { - 1.expr() - - self.storage_key_diff_inverse.clone() - * (self.storage_key.encoded.clone() - self.storage_key.encoded_prev.clone()) - } } fn from_digits(digits: &[Expression], base: Expression) -> Expression { diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 5abaeb0c15..a2f1d571e1 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -5,6 +5,7 @@ use crate::{ util::{not, select}, witness::Rw, }, + gadget::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, util::Expr, }; use eth_types::{Field, ToBigEndian}; @@ -21,12 +22,12 @@ use std::{marker::PhantomData, ops::Mul}; // 16 limbs for storage key // 2 limbs for rw_counter // 30 limbs in total -> can fit into two field elements -#[derive(Clone, Copy)] -pub struct Config { +#[derive(Clone)] +pub struct Config { diff_1: Column, + pub(crate) diff_1_is_zero: IsZeroConfig, diff_2: Column, - diff_inverse: Column, - pub diff_selector: Column, + pub(crate) diff_2_is_zero: IsZeroConfig, tag: Column, field_tag: Column, id_limbs: [Column; N_LIMBS_ID], @@ -35,69 +36,13 @@ pub struct Config { rw_counter_limbs: [Column; N_LIMBS_RW_COUNTER], } -impl Config { - pub fn assign( - &self, - region: &mut Region<'_, F>, - offset: usize, - cur: &Rw, - prev: &Rw, - ) -> Result, Error> { - let cur_be_limbs = rw_to_be_limbs(cur); - let prev_be_limbs = rw_to_be_limbs(prev); - assert_eq!(cur_be_limbs.len(), 30); - assert_eq!(prev_be_limbs.len(), 30); - - 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 diff_1 = F::from((cur_limb - prev_limb).into()); - let mut diff_2 = if cur_be_limbs[15] >= prev_be_limbs[15] { - F::from((cur_be_limbs[15] - prev_be_limbs[15]).into()) - } else { - -F::from((prev_be_limbs[15] - cur_be_limbs[15]).into()) - }; - let mut diff_inverse = diff_1.invert().unwrap(); - let mut diff_selector = F::one(); - if index >= 15 { - diff_1 = F::zero(); - diff_2 = F::from((cur_limb - prev_limb).into()); - diff_inverse = diff_2.invert().unwrap(); - diff_selector = F::zero(); - } - - region.assign_advice(|| "diff_1", self.diff_1, offset, || Ok(diff_1))?; - region.assign_advice(|| "diff_2", self.diff_2, offset, || Ok(diff_2))?; - region.assign_advice( - || "diff_inverse", - self.diff_inverse, - offset, - || Ok(diff_inverse), - )?; - region.assign_advice( - || "diff_selector", - self.diff_selector, - offset, - || Ok(diff_selector), - ) - } -} - pub struct Chip { - config: Config, - _marker: PhantomData, + config: Config, } impl Chip { - pub fn construct(config: Config) -> Self { - Self { - config, - _marker: PhantomData, - } + pub fn construct(config: Config) -> Self { + Self { config } } #[allow(clippy::too_many_arguments)] @@ -112,16 +57,26 @@ impl Chip { storage_key_bytes: [Column; N_BYTES_WORD], rw_counter_limbs: [Column; N_LIMBS_RW_COUNTER], u16_range: Column, - ) -> Config { - let diff_1 = meta.advice_column(); - let diff_2 = meta.advice_column(); - let diff_inverse = meta.advice_column(); - let diff_selector = meta.advice_column(); + ) -> Config { + let [diff_1, diff_1_inverse, diff_2, diff_2_inverse] = [0; 4].map(|_| meta.advice_column()); + let [diff_1_is_zero_config, diff_2_is_zero_config] = + [(diff_1, diff_1_inverse), (diff_2, diff_2_inverse)].map(|(value, advice)| { + IsZeroChip::configure( + meta, + |meta| meta.query_fixed(selector, Rotation::cur()), + |meta| meta.query_advice(value, Rotation::cur()), + advice, + ) + }); + + let diff_1_is_zero = diff_1_is_zero_config.is_zero_expression.clone(); + let diff_2_is_zero = diff_2_is_zero_config.is_zero_expression.clone(); + let config = Config { diff_1, + diff_1_is_zero: diff_1_is_zero_config, diff_2, - diff_inverse, - diff_selector, + diff_2_is_zero: diff_2_is_zero_config, tag, field_tag, id_limbs, @@ -131,8 +86,8 @@ impl Chip { }; meta.create_gate("diff_1 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 cur = Queries::new(meta, &config, Rotation::cur()); + let prev = Queries::new(meta, &config, Rotation::prev()); let diff_1 = meta.query_advice(diff_1, Rotation::cur()); vec![ selector @@ -142,11 +97,21 @@ impl Chip { .fold(1.expr(), Expression::mul), ] }); - + assert!(meta.degree() <= 16); + // meta.lookup_any("diff_1 is zero iff all 15 possible values are 0", |meta| { + // let cur = Queries::new(meta, &config, Rotation::cur()); + // let prev = Queries::new(meta, &config, Rotation::prev()); + // let diff_1 = meta.query_advice(diff_1, Rotation::cur()); + // vec![( + // // all 15 possible values are 0 iff the final linear combination is 0 + // diff_1_is_zero.clone() * diff_1_possible_values(cur, + // prev)[0].clone(), meta.query_fixed(u16_range, + // Rotation::cur()), )] + // }); meta.create_gate("diff_2 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 cur = Queries::new(meta, &config, Rotation::cur()); + let prev = Queries::new(meta, &config, Rotation::prev()); let diff_2 = meta.query_advice(diff_2, Rotation::cur()); vec![ selector @@ -156,48 +121,78 @@ impl Chip { .fold(1.expr(), Expression::mul), ] }); - + assert!(meta.degree() <= 16); meta.lookup_any("diff_1 fits into u16", |meta| { - let diff_selector = meta.query_advice(diff_selector, Rotation::cur()); let diff_1 = meta.query_advice(diff_1, Rotation::cur()); - vec![( - diff_selector * diff_1, - meta.query_fixed(u16_range, Rotation::cur()), - )] + vec![(diff_1, meta.query_fixed(u16_range, Rotation::cur()))] }); - meta.lookup_any("diff_2 fits into u16", |meta| { - let diff_selector = meta.query_advice(diff_selector, Rotation::cur()); + meta.lookup_any("diff_1 is zero or diff_2 fits into u16", |meta| { let diff_2 = meta.query_advice(diff_2, Rotation::cur()); vec![( - not::expr(diff_selector) * diff_2, + diff_1_is_zero * diff_2, meta.query_fixed(u16_range, Rotation::cur()), )] }); - - meta.create_gate("diff_selector is boolean", |meta| { - let selector = meta.query_fixed(selector, Rotation::cur()); - let diff_selector = meta.query_advice(diff_selector, Rotation::cur()); - vec![selector * diff_selector.clone() * not::expr(diff_selector)] - }); - - meta.create_gate("diff_inverse", |meta| { + assert!(meta.degree() <= 16); + meta.create_gate("diff_2 is not zero", |meta| { let selector = meta.query_fixed(selector, Rotation::cur()); - let diff_selector = meta.query_advice(diff_selector, Rotation::cur()); - let diff_inverse = meta.query_advice(diff_inverse, Rotation::cur()); - let diff_1 = meta.query_advice(diff_1, Rotation::cur()); - let diff_2 = meta.query_advice(diff_2, Rotation::cur()); - vec![ - selector - * select::expr( - diff_selector, - diff_inverse.clone() * diff_1 - 1u64.expr(), - diff_inverse * diff_2 - 1u64.expr(), - ), - ] + vec![(selector * diff_2_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 diff_1_is_zero_chip = IsZeroChip::construct(self.config.diff_1_is_zero.clone()); + let diff_2_is_zero_chip = IsZeroChip::construct(self.config.diff_2_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 diff_1 = F::from((cur_limb - prev_limb).into()); + let mut diff_2 = if cur_be_limbs[29] >= prev_be_limbs[29] { + F::from((cur_be_limbs[29] - prev_be_limbs[29]).into()) + } else { + -F::from((prev_be_limbs[29] - cur_be_limbs[29]).into()) + }; + if index >= 15 { + dbg!(cur_be_limbs.clone()); + dbg!(prev_be_limbs.clone()); + dbg!(index); + diff_1 = F::zero(); + diff_2 = F::from((cur_limb - prev_limb).into()); + } + + if diff_2 == F::zero() { + panic!(); + } + + if diff_1 == F::zero() { + dbg!(cur, prev); + } + + region.assign_advice(|| "diff_1", self.config.diff_1, offset, || Ok(diff_1))?; + region.assign_advice(|| "diff_2", self.config.diff_2, offset, || Ok(diff_2))?; + diff_1_is_zero_chip.assign(region, offset, Some(diff_1))?; + diff_2_is_zero_chip.assign(region, offset, Some(diff_2))?; + Ok(()) + } } struct Queries { @@ -210,10 +205,9 @@ struct Queries { } impl Queries { - fn new(meta: &mut VirtualCells<'_, F>, config: Config, rotation: Rotation) -> Self { + fn new(meta: &mut VirtualCells<'_, F>, config: &Config, rotation: Rotation) -> Self { let mut query_advice = |column| meta.query_advice(column, rotation); Self { - // witness: query_advice(config.witness), tag: query_advice(config.tag), field_tag: query_advice(config.field_tag), id_limbs: config.id_limbs.map(&mut query_advice), @@ -295,5 +289,6 @@ fn diff_2_possible_values(cur: Queries, prev: Queries) -> Vec::default(); StateCircuit::configure(&mut meta); - assert_eq!(meta.degree(), 16); + assert_eq!(meta.degree(), 17); } #[test] From c491a3abaeda761b3432dd87d82bb2c0327cf93a Mon Sep 17 00:00:00 2001 From: z2trillion Date: Wed, 13 Apr 2022 12:16:07 -0400 Subject: [PATCH 16/31] Fix diff_2 assignment --- zkevm-circuits/src/state_circuit.rs | 1 - .../state_circuit/lexicographic_ordering.rs | 56 +++++++++++-------- .../src/state_circuit/state_tests.rs | 27 ++++++++- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 90e508b9ab..59e0826b65 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -194,7 +194,6 @@ impl Circuit for StateCircuit { config .rw_counter .assign(&mut region, offset, row.rw_counter() as u32)?; - // dbg!(row, if row.is_write() { F::one() } else { F::zero() }); region.assign_advice( || "is_write", config.is_write, diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index a2f1d571e1..475a5f292c 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -98,16 +98,15 @@ impl Chip { ] }); assert!(meta.degree() <= 16); - // meta.lookup_any("diff_1 is zero iff all 15 possible values are 0", |meta| { - // let cur = Queries::new(meta, &config, Rotation::cur()); - // let prev = Queries::new(meta, &config, Rotation::prev()); - // let diff_1 = meta.query_advice(diff_1, Rotation::cur()); - // vec![( - // // all 15 possible values are 0 iff the final linear combination is 0 - // diff_1_is_zero.clone() * diff_1_possible_values(cur, - // prev)[0].clone(), meta.query_fixed(u16_range, - // Rotation::cur()), )] - // }); + meta.create_gate("diff_1 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 * diff_1_is_zero.clone() * diff_1_possible_values(cur, prev)[14].clone(), + ] + }); meta.create_gate("diff_2 is one of 15 values", |meta| { let selector = meta.query_fixed(selector, Rotation::cur()); let cur = Queries::new(meta, &config, Rotation::cur()); @@ -166,11 +165,12 @@ impl Chip { let (index, (cur_limb, prev_limb)) = find_result.expect("repeated rw counter"); let mut diff_1 = F::from((cur_limb - prev_limb).into()); - let mut diff_2 = if cur_be_limbs[29] >= prev_be_limbs[29] { - F::from((cur_be_limbs[29] - prev_be_limbs[29]).into()) - } else { - -F::from((prev_be_limbs[29] - cur_be_limbs[29]).into()) - }; + let mut diff_2 = diff_2_value(&cur_be_limbs, &prev_be_limbs); + // let mut diff_2 = if cur_be_limbs[29] >= prev_be_limbs[29] { + // F::from((cur_be_limbs[29] - prev_be_limbs[29]).into()) + // } else { + // -F::from((prev_be_limbs[29] - cur_be_limbs[29]).into()) + // }; if index >= 15 { dbg!(cur_be_limbs.clone()); dbg!(prev_be_limbs.clone()); @@ -178,14 +178,14 @@ impl Chip { diff_1 = F::zero(); diff_2 = F::from((cur_limb - prev_limb).into()); } - - if diff_2 == F::zero() { - panic!(); - } - - if diff_1 == F::zero() { - dbg!(cur, prev); - } + // + // if diff_2 == F::zero() { + // panic!(); + // } + // + // if diff_1 == F::zero() { + // dbg!(cur, prev); + // } region.assign_advice(|| "diff_1", self.config.diff_1, offset, || Ok(diff_1))?; region.assign_advice(|| "diff_2", self.config.diff_2, offset, || Ok(diff_2))?; @@ -292,3 +292,13 @@ fn diff_2_possible_values(cur: Queries, prev: Queries) -> Vec(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/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs index 4bcc40bfff..df515337e6 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -1,12 +1,13 @@ 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}, - ToAddress, Word, U256, + Address, ToAddress, Word, U256, }; use halo2_proofs::{ arithmetic::BaseExt, @@ -209,6 +210,30 @@ mod tests { 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 randomness = Fr::rand(); let circuit = StateCircuit { randomness, rows }; From a3a5ce5a68343629720b2bb2a398df58656b1dee Mon Sep 17 00:00:00 2001 From: z2trillion Date: Wed, 13 Apr 2022 19:02:26 -0400 Subject: [PATCH 17/31] cleanup --- .../src/state_circuit/constraint_builder.rs | 10 +---- .../state_circuit/lexicographic_ordering.rs | 38 +++---------------- zkevm-circuits/src/test_util.rs | 13 +------ 3 files changed, 10 insertions(+), 51 deletions(-) diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index b7c822d59b..40c4d19ca9 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -50,13 +50,6 @@ impl ConstraintBuilder { } pub fn gate(&self, condition: Expression) -> Vec<(&'static str, Expression)> { - // for (name, constraint) in &self.constraints { - // if constraint.degree() >= 16 { - // dbg!(name); - // panic!(); - // } - // // assert!(constraint.degree() <= 16); - // } self.constraints .iter() .cloned() @@ -144,7 +137,8 @@ impl ConstraintBuilder { // this pushes the degree to 17.... self.condition(q.first_access(), |cb| { cb.require_zero( - "previous tag is Start, address change is 0 or address change is 1", + // 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()), diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 475a5f292c..52de955120 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -1,21 +1,17 @@ use super::{N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_ID, N_LIMBS_RW_COUNTER}; use crate::{ - evm_circuit::{ - param::N_BYTES_WORD, - util::{not, select}, - witness::Rw, - }, + evm_circuit::{param::N_BYTES_WORD, witness::Rw}, gadget::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, util::Expr, }; use eth_types::{Field, ToBigEndian}; use halo2_proofs::{ - circuit::{AssignedCell, Region}, + circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, poly::Rotation, }; use itertools::Itertools; -use std::{marker::PhantomData, ops::Mul}; +use std::ops::Mul; // 2 limbs for tag, field_tag and id. // 10 limbs for address, @@ -166,26 +162,10 @@ impl Chip { let mut diff_1 = F::from((cur_limb - prev_limb).into()); let mut diff_2 = diff_2_value(&cur_be_limbs, &prev_be_limbs); - // let mut diff_2 = if cur_be_limbs[29] >= prev_be_limbs[29] { - // F::from((cur_be_limbs[29] - prev_be_limbs[29]).into()) - // } else { - // -F::from((prev_be_limbs[29] - cur_be_limbs[29]).into()) - // }; if index >= 15 { - dbg!(cur_be_limbs.clone()); - dbg!(prev_be_limbs.clone()); - dbg!(index); diff_1 = F::zero(); diff_2 = F::from((cur_limb - prev_limb).into()); } - // - // if diff_2 == F::zero() { - // panic!(); - // } - // - // if diff_1 == F::zero() { - // dbg!(cur, prev); - // } region.assign_advice(|| "diff_1", self.config.diff_1, offset, || Ok(diff_1))?; region.assign_advice(|| "diff_2", self.config.diff_2, offset, || Ok(diff_2))?; @@ -250,20 +230,14 @@ impl Queries { 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()); - assert_eq!(be_bytes.len(), 4); + 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); - assert_eq!(be_bytes.len(), 4); - - be_bytes.extend_from_slice(&(row.address().unwrap_or_default().0)); - assert_eq!(be_bytes.len(), 24); - be_bytes.extend_from_slice(&(row.storage_key().unwrap_or_default().to_be_bytes())); - assert_eq!(be_bytes.len(), 24 + 32); - be_bytes.extend_from_slice(&((row.rw_counter() as u32).to_be_bytes())); - assert_eq!(be_bytes.len(), 24 + 32 + 4); be_bytes .iter() diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index 893751c294..d5995e2938 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -92,17 +92,8 @@ pub fn test_circuits_using_witness_block( // 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.clone()); - - let power_of_randomness: Vec<_> = (1..32) - .map(|exp| { - vec![ - block.randomness.pow(&[exp, 0, 0, 0]); - 20 // number of rows in the rw table. - ] - }) - .collect(); - + 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()?; } From 36293e58fb8b9b48ffbcfd1031fcac981fa84aff Mon Sep 17 00:00:00 2001 From: z2trillion Date: Thu, 14 Apr 2022 11:58:07 -0400 Subject: [PATCH 18/31] Use verify_at_rows to speed up tests and derive EnumIter --- Cargo.lock | 1 + prover/Cargo.toml | 1 + zkevm-circuits/src/evm_circuit.rs | 1 + zkevm-circuits/src/state_circuit.rs | 9 +++++++++ zkevm-circuits/src/state_circuit/state_tests.rs | 6 ++++-- zkevm-circuits/src/test_util.rs | 9 +++------ 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bccb3543b..81fbfd184f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2750,6 +2750,7 @@ dependencies = [ "rand_xorshift", "serde", "serde_json", + "strum", "tokio", "zkevm-circuits", ] 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/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index d5f0e7bd51..59e639d450 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -165,6 +165,7 @@ pub mod test { distributions::uniform::{SampleRange, SampleUniform}, random, thread_rng, Rng, }; + use strum::IntoEnumIterator; pub(crate) fn rand_range(range: R) -> T where diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 59e0826b65..31d200f03f 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -84,6 +84,15 @@ impl StateCircuit { .map(|exp| vec![self.randomness.pow(&[exp, 0, 0, 0]); self.rows.len()]) .collect() } + + // /// Calculate which rows are "actually" used in the circuit + // pub fn get_active_rows(block: &Block) -> (Vec, Vec) { + // let max_offset = block.txs.iter().map(|tx| tx.steps.len()).sum::() + // * STEP_HEIGHT; // gates are only enabled at "q_step" rows let + // gates_row_ids = (0..max_offset).step_by(STEP_HEIGHT).collect(); // lookups + // are enabled at "q_step" rows and byte lookup rows let lookup_row_ids = + // (0..max_offset).collect(); (gates_row_ids, lookup_row_ids) + // } } impl Circuit for StateCircuit { diff --git a/zkevm-circuits/src/state_circuit/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs index df515337e6..a4dc005dc5 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -235,12 +235,14 @@ mod tests { } 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(19, &circuit, power_of_randomness) + MockProver::::run(17, &circuit, power_of_randomness) .unwrap() - .verify() + .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 d5995e2938..f70625d239 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -86,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 power_of_randomness = state_circuit.instance(); let prover = MockProver::::run(18, &state_circuit, power_of_randomness).unwrap(); - prover.verify()?; + prover.verify_at_rows(0..state_circuit.rows.len(), 0..state_circuit.rows.len())? } Ok(()) From 37c0644f816d10e722cb7d142fe34e5ebff90d17 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Fri, 15 Apr 2022 16:29:36 -0400 Subject: [PATCH 19/31] Add cleanup and explain diff_1 and diff_2 --- zkevm-circuits/src/state_circuit.rs | 10 ---- .../state_circuit/lexicographic_ordering.rs | 48 +++++++++++++++++-- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 31d200f03f..c64a766b81 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -43,7 +43,6 @@ pub struct StateConfig { id: MpiConfig, address: MpiConfig, field_tag: Column, - // Consider using WordConfig instead? storage_key: RlcConfig, is_storage_key_unchanged: IsZeroConfig, value: Column, @@ -84,15 +83,6 @@ impl StateCircuit { .map(|exp| vec![self.randomness.pow(&[exp, 0, 0, 0]); self.rows.len()]) .collect() } - - // /// Calculate which rows are "actually" used in the circuit - // pub fn get_active_rows(block: &Block) -> (Vec, Vec) { - // let max_offset = block.txs.iter().map(|tx| tx.steps.len()).sum::() - // * STEP_HEIGHT; // gates are only enabled at "q_step" rows let - // gates_row_ids = (0..max_offset).step_by(STEP_HEIGHT).collect(); // lookups - // are enabled at "q_step" rows and byte lookup rows let lookup_row_ids = - // (0..max_offset).collect(); (gates_row_ids, lookup_row_ids) - // } } impl Circuit for StateCircuit { diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 52de955120..b1a3489862 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -13,11 +13,49 @@ use halo2_proofs::{ use itertools::Itertools; use std::ops::Mul; -// 2 limbs for tag, field_tag and id. -// 10 limbs for address, -// 16 limbs for storage key -// 2 limbs for rw_counter -// 30 limbs in total -> can fit into two field elements +// 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. + +// X_prev > 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: +// - diff_1 is (at least) 1 of the 15 values C0, ..., C14. +// - diff_2 is (at least) 1 of the 15 values C15, ..., C29. +// - diff_1 fits into 16 bits. +// - if diff_1 is 0, then diff_2 fits into 16 bits. +// - if diff_1 is 0, then C14 is 0. +// - diff_2 is not 0. (there is always a valid assignment for this because the +// rw_counter is increasing.) + +// 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 { diff_1: Column, From 12cb311f2274bfbcf94b400158783b74bb9c287c Mon Sep 17 00:00:00 2001 From: z2trillion Date: Thu, 28 Apr 2022 14:28:48 -0400 Subject: [PATCH 20/31] cleanup --- circuit-benchmarks/build.rs | 15 ++------------- prover/src/compute_proof.rs | 12 ++---------- zkevm-circuits/src/state_circuit.rs | 2 +- .../src/state_circuit/lexicographic_ordering.rs | 6 +++--- 4 files changed, 8 insertions(+), 27 deletions(-) diff --git a/circuit-benchmarks/build.rs b/circuit-benchmarks/build.rs index 146fde0f7e..3b0a63a6a0 100644 --- a/circuit-benchmarks/build.rs +++ b/circuit-benchmarks/build.rs @@ -10,22 +10,11 @@ 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 + "pub(crate) const DEGREE: usize = {};", + degree ); let mut state_file = diff --git a/prover/src/compute_proof.rs b/prover/src/compute_proof.rs index aa5ce91d20..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,15 +61,6 @@ 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::new(block.randomness, block.rws); // TODO: same quest like in the first scope diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index c64a766b81..45fab7acd4 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -11,9 +11,9 @@ use crate::evm_circuit::{ param::N_BYTES_WORD, witness::{Rw, RwMap}, }; -use crate::gadget::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; use constraint_builder::{ConstraintBuilder, Queries}; use eth_types::{Address, Field}; +use gadgets::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, plonk::{ diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index b1a3489862..588eacd05f 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -1,10 +1,10 @@ use super::{N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_ID, N_LIMBS_RW_COUNTER}; use crate::{ evm_circuit::{param::N_BYTES_WORD, witness::Rw}, - gadget::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, 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}, @@ -31,7 +31,7 @@ use std::ops::Mul; // ... // C29 = C28 << 16 + A29 - B29. -// X_prev > X_prev iff one of the following is true: +// 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 @@ -44,7 +44,7 @@ use std::ops::Mul; // - if diff_1 is 0, then diff_2 fits into 16 bits. // - if diff_1 is 0, then C14 is 0. // - diff_2 is not 0. (there is always a valid assignment for this because the -// rw_counter is increasing.) +// rw_counter is increasing.) // Packing the field into 480 bits: // 4 bits for tag, From 96bee8ca7a4f7a22e00491b461d968940ae4dbf5 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Thu, 28 Apr 2022 15:03:25 -0400 Subject: [PATCH 21/31] fmt --- circuit-benchmarks/build.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/circuit-benchmarks/build.rs b/circuit-benchmarks/build.rs index 3b0a63a6a0..9258d093d4 100644 --- a/circuit-benchmarks/build.rs +++ b/circuit-benchmarks/build.rs @@ -12,10 +12,7 @@ fn main() { .expect("Cannot parse DEGREE env var as usize"); // Add state_circuit module to `lib.rs` - let consts = format!( - "pub(crate) const DEGREE: usize = {};", - degree - ); + let consts = format!("pub(crate) const DEGREE: usize = {};", degree); let mut state_file = File::create("src/bench_params.rs").expect("Error generating bench_params.rs file"); From 247c591dd25226fb1bbd9c6474cee101c694e0f9 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Thu, 28 Apr 2022 16:30:34 -0400 Subject: [PATCH 22/31] Add newline to fix cargo fmt --- circuit-benchmarks/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuit-benchmarks/build.rs b/circuit-benchmarks/build.rs index 9258d093d4..a33f619657 100644 --- a/circuit-benchmarks/build.rs +++ b/circuit-benchmarks/build.rs @@ -12,7 +12,7 @@ fn main() { .expect("Cannot parse DEGREE env var as usize"); // Add state_circuit module to `lib.rs` - let consts = format!("pub(crate) const DEGREE: usize = {};", degree); + 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"); From 5a6993437e8a2a7274bde083abbae2cb4e0797ce Mon Sep 17 00:00:00 2001 From: z2trillion Date: Fri, 29 Apr 2022 11:25:09 -0400 Subject: [PATCH 23/31] Fix degree test and TxLog table assignment --- .../evm_circuit/util/constraint_builder.rs | 4 ++-- zkevm-circuits/src/evm_circuit/witness.rs | 24 +++++++++++++++---- zkevm-circuits/src/state_circuit.rs | 1 + .../src/state_circuit/state_tests.rs | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index c59f05ec2e..26ac8118db 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -1052,9 +1052,9 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { RwTableTag::TxLog, [ tx_id, - self.curr.state.log_id.expr(), + self.curr.state.log_id.expr() + (1u64 << 32).expr() * (1u64 << 32).expr() * index, 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 6786b752b6..3685ac8c85 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -506,10 +506,10 @@ 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 + // topic index if field_tag is TxLogFieldTag:Topic 0-3 + // byte index if field_tag is TxLogFieldTag:Data 0-255? // it would be zero for other field tags index: usize, @@ -651,6 +651,19 @@ impl Rw { } } + // for tx log..... + // rw_Counter, + // is_Write, + // tag, // + // tx_id, // key1 + // log_id, // key2 + // field_tag, // key3 + // index, // key4 this has to be rlced///// + // rlc(value) + // 0, + // 0 + // 0 + pub fn table_assignment(&self, randomness: F) -> RwRow { RwRow { rw_counter: F::from(self.rw_counter() as u64), @@ -752,7 +765,10 @@ impl Rw { Self::Stack { stack_pointer, .. } => { Some(U256::from(*stack_pointer as u64).to_address()) } - Self::CallContext { .. } | Self::TxRefund { .. } | Self::TxLog { .. } => None, + Self::TxLog { log_id, index, .. } => { + Some(U256([*log_id, *index as u64, 0, 0]).to_address()) + } + Self::CallContext { .. } | Self::TxRefund { .. } => None, } } diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 45fab7acd4..6bee4a6364 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -168,6 +168,7 @@ impl Circuit for StateCircuit { LexicographicOrderingChip::construct(config.lexicographic_ordering.clone()); dbg!(self.rows.clone()); + assert!(false); let mut prev_storage_key = F::zero(); diff --git a/zkevm-circuits/src/state_circuit/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs index a4dc005dc5..52054f553c 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -42,7 +42,7 @@ mod tests { let mut meta = ConstraintSystem::::default(); StateCircuit::configure(&mut meta); - assert_eq!(meta.degree(), 17); + assert_eq!(meta.degree(), 18); } #[test] From a96321ff65e86b23844f42a3cb1d3a2f6c6bb16f Mon Sep 17 00:00:00 2001 From: z2trillion Date: Fri, 29 Apr 2022 12:23:23 -0400 Subject: [PATCH 24/31] Cleanup and change index type to u8 from usize --- .../src/evm_circuit/execution/copy_to_log.rs | 2 +- .../src/evm_circuit/execution/logs.rs | 2 +- .../evm_circuit/util/constraint_builder.rs | 2 +- zkevm-circuits/src/evm_circuit/witness.rs | 23 ++++--------------- zkevm-circuits/src/state_circuit.rs | 3 --- 5 files changed, 8 insertions(+), 24 deletions(-) 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/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/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 26ac8118db..8adad1a27d 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -1052,7 +1052,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { RwTableTag::TxLog, [ tx_id, - self.curr.state.log_id.expr() + (1u64 << 32).expr() * (1u64 << 32).expr() * index, + index + (1u64 << 8).expr() * self.curr.state.log_id.expr(), tag.expr(), 0.expr(), value, diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 3685ac8c85..e56dc64763 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -508,10 +508,10 @@ pub enum Rw { tx_id: usize, log_id: u64, // pack this can index together into address? field_tag: TxLogFieldTag, - // topic index if field_tag is TxLogFieldTag:Topic 0-3 - // byte index if field_tag is TxLogFieldTag:Data 0-255? - // 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, @@ -651,19 +651,6 @@ impl Rw { } } - // for tx log..... - // rw_Counter, - // is_Write, - // tag, // - // tx_id, // key1 - // log_id, // key2 - // field_tag, // key3 - // index, // key4 this has to be rlced///// - // rlc(value) - // 0, - // 0 - // 0 - pub fn table_assignment(&self, randomness: F) -> RwRow { RwRow { rw_counter: F::from(self.rw_counter() as u64), @@ -766,7 +753,7 @@ impl Rw { Some(U256::from(*stack_pointer as u64).to_address()) } Self::TxLog { log_id, index, .. } => { - Some(U256([*log_id, *index as u64, 0, 0]).to_address()) + Some((U256::from(*index as u64) + (U256::from(*log_id) << 8)).to_address()) } Self::CallContext { .. } | Self::TxRefund { .. } => None, } diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 6bee4a6364..8aa751794d 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -167,9 +167,6 @@ impl Circuit for StateCircuit { let lexicographic_ordering_chip = LexicographicOrderingChip::construct(config.lexicographic_ordering.clone()); - dbg!(self.rows.clone()); - assert!(false); - let mut prev_storage_key = F::zero(); layouter.assign_region( From 808c7fe3a2f6db4e2fe6af1236c54056ec55f626 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Fri, 29 Apr 2022 14:45:11 -0400 Subject: [PATCH 25/31] Rename to diff_1->upper_limb_difference and diff_2->lower_limb_difference --- zkevm-circuits/src/state_circuit.rs | 4 +- .../src/state_circuit/constraint_builder.rs | 8 +- .../state_circuit/lexicographic_ordering.rs | 173 +++++++++++------- 3 files changed, 114 insertions(+), 71 deletions(-) diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 8aa751794d..49dc742100 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -261,9 +261,9 @@ fn queries(meta: &mut VirtualCells<'_, F>, c: &StateConfig) -> Quer .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_diff_1_is_zero: c + lexicographic_ordering_upper_limb_difference_is_zero: c .lexicographic_ordering - .diff_1_is_zero + .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 index 40c4d19ca9..cab9f92015 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -28,7 +28,7 @@ pub struct Queries { pub lookups: LookupsQueries, pub power_of_randomness: [Expression; N_BYTES_WORD - 1], pub is_storage_key_unchanged: Expression, - pub lexicographic_ordering_diff_1_is_zero: Expression, + pub lexicographic_ordering_upper_limb_difference_is_zero: Expression, } type Constraint = (&'static str, Expression); @@ -302,8 +302,10 @@ impl Queries { } fn first_access(&self) -> Expression { - not::expr(self.lexicographic_ordering_diff_1_is_zero.clone()) - * not::expr(self.is_storage_key_unchanged.clone()) + not::expr( + self.lexicographic_ordering_upper_limb_difference_is_zero + .clone(), + ) * not::expr(self.is_storage_key_unchanged.clone()) } fn address_change(&self) -> Expression { diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 588eacd05f..699ceca286 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -30,6 +30,8 @@ use std::ops::Mul; // 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. @@ -38,13 +40,14 @@ use std::ops::Mul; // "C14 is 0".) // We show that one of these is true with the following constraints: -// - diff_1 is (at least) 1 of the 15 values C0, ..., C14. -// - diff_2 is (at least) 1 of the 15 values C15, ..., C29. -// - diff_1 fits into 16 bits. -// - if diff_1 is 0, then diff_2 fits into 16 bits. -// - if diff_1 is 0, then C14 is 0. -// - diff_2 is not 0. (there is always a valid assignment for this because the -// rw_counter is increasing.) +// - upper_limb_difference is (at least) 1 of the 15 values C0, ..., C14. +// - lower_limb_difference is (at least) 1 of the 15 values C15, ..., C29. +// - upper_limb_difference fits into 16 bits. +// - if upper_limb_difference is 0, then lower_limb_difference fits into 16 +// bits. +// - if upper_limb_difference is 0, then C14 is 0. +// - lower_limb_difference is not 0. (there is always a valid assignment for +// this because the rw_counter is increasing.) // Packing the field into 480 bits: // 4 bits for tag, @@ -58,10 +61,10 @@ use std::ops::Mul; #[derive(Clone)] pub struct Config { - diff_1: Column, - pub(crate) diff_1_is_zero: IsZeroConfig, - diff_2: Column, - pub(crate) diff_2_is_zero: IsZeroConfig, + 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], @@ -92,25 +95,33 @@ impl Chip { rw_counter_limbs: [Column; N_LIMBS_RW_COUNTER], u16_range: Column, ) -> Config { - let [diff_1, diff_1_inverse, diff_2, diff_2_inverse] = [0; 4].map(|_| meta.advice_column()); - let [diff_1_is_zero_config, diff_2_is_zero_config] = - [(diff_1, diff_1_inverse), (diff_2, diff_2_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, 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 diff_1_is_zero = diff_1_is_zero_config.is_zero_expression.clone(); - let diff_2_is_zero = diff_2_is_zero_config.is_zero_expression.clone(); + 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 { - diff_1, - diff_1_is_zero: diff_1_is_zero_config, - diff_2, - diff_2_is_zero: diff_2_is_zero_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, @@ -118,58 +129,70 @@ impl Chip { storage_key_bytes, rw_counter_limbs, }; - meta.create_gate("diff_1 is one of 15 values", |meta| { + 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 diff_1 = meta.query_advice(diff_1, Rotation::cur()); + let upper_limb_difference = meta.query_advice(upper_limb_difference, Rotation::cur()); vec![ selector - * diff_1_possible_values(cur, prev) + * upper_limb_difference_possible_values(cur, prev) .iter() - .map(|e| diff_1.clone() - e.clone()) + .map(|e| upper_limb_difference.clone() - e.clone()) .fold(1.expr(), Expression::mul), ] }); assert!(meta.degree() <= 16); - meta.create_gate("diff_1 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 * diff_1_is_zero.clone() * diff_1_possible_values(cur, prev)[14].clone(), - ] - }); - meta.create_gate("diff_2 is one of 15 values", |meta| { + 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 diff_2 = meta.query_advice(diff_2, Rotation::cur()); + let lower_limb_difference = meta.query_advice(lower_limb_difference, Rotation::cur()); vec![ selector - * diff_2_possible_values(cur, prev) + * lower_limb_difference_possible_values(cur, prev) .iter() - .map(|e| diff_2.clone() - e.clone()) + .map(|e| lower_limb_difference.clone() - e.clone()) .fold(1.expr(), Expression::mul), ] }); assert!(meta.degree() <= 16); - meta.lookup_any("diff_1 fits into u16", |meta| { - let diff_1 = meta.query_advice(diff_1, Rotation::cur()); - vec![(diff_1, meta.query_fixed(u16_range, Rotation::cur()))] - }); - meta.lookup_any("diff_1 is zero or diff_2 fits into u16", |meta| { - let diff_2 = meta.query_advice(diff_2, Rotation::cur()); + meta.lookup_any("upper_limb_difference fits into u16", |meta| { + let upper_limb_difference = meta.query_advice(upper_limb_difference, Rotation::cur()); vec![( - diff_1_is_zero * diff_2, + 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("diff_2 is not zero", |meta| { + meta.create_gate("lower_limb_difference is not zero", |meta| { let selector = meta.query_fixed(selector, Rotation::cur()); - vec![(selector * diff_2_is_zero)] + vec![(selector * lower_limb_difference_is_zero)] }); assert!(meta.degree() <= 16); @@ -185,8 +208,10 @@ impl Chip { ) -> Result<(), Error> { // this doesn't make sense that we have to "construct" the chip every time we // assign? - let diff_1_is_zero_chip = IsZeroChip::construct(self.config.diff_1_is_zero.clone()); - let diff_2_is_zero_chip = IsZeroChip::construct(self.config.diff_2_is_zero.clone()); + 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); @@ -198,17 +223,27 @@ impl Chip { .find(|(_, (a, b))| a != b); let (index, (cur_limb, prev_limb)) = find_result.expect("repeated rw counter"); - let mut diff_1 = F::from((cur_limb - prev_limb).into()); - let mut diff_2 = diff_2_value(&cur_be_limbs, &prev_be_limbs); + let mut upper_limb_difference = F::from((cur_limb - prev_limb).into()); + let mut lower_limb_difference = lower_limb_difference_value(&cur_be_limbs, &prev_be_limbs); if index >= 15 { - diff_1 = F::zero(); - diff_2 = F::from((cur_limb - prev_limb).into()); + upper_limb_difference = F::zero(); + lower_limb_difference = F::from((cur_limb - prev_limb).into()); } - region.assign_advice(|| "diff_1", self.config.diff_1, offset, || Ok(diff_1))?; - region.assign_advice(|| "diff_2", self.config.diff_2, offset, || Ok(diff_2))?; - diff_1_is_zero_chip.assign(region, offset, Some(diff_1))?; - diff_2_is_zero_chip.assign(region, offset, Some(diff_2))?; + 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(()) } } @@ -284,7 +319,10 @@ fn rw_to_be_limbs(row: &Rw) -> Vec { .collect() } -fn diff_1_possible_values(cur: Queries, prev: Queries) -> Vec> { +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]) { @@ -294,7 +332,10 @@ fn diff_1_possible_values(cur: Queries, prev: Queries) -> Vec(cur: Queries, prev: Queries) -> Vec> { +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..]) { @@ -305,7 +346,7 @@ fn diff_2_possible_values(cur: Queries, prev: Queries) -> Vec(cur_limbs: &[u16], prev_limbs: &[u16]) -> F { +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..]) } From dd6126a81a89f2fd7b0dcbe2f138ab5937f40b10 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 2 May 2022 10:44:42 -0400 Subject: [PATCH 26/31] Explain assignment in comments --- .../state_circuit/lexicographic_ordering.rs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 699ceca286..768095ea94 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -40,14 +40,23 @@ use std::ops::Mul; // "C14 is 0".) // We show that one of these is true with the following constraints: -// - upper_limb_difference is (at least) 1 of the 15 values C0, ..., C14. -// - lower_limb_difference is (at least) 1 of the 15 values C15, ..., C29. -// - upper_limb_difference fits into 16 bits. -// - if upper_limb_difference is 0, then lower_limb_difference fits into 16 +// 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. -// - if upper_limb_difference is 0, then C14 is 0. -// - lower_limb_difference is not 0. (there is always a valid assignment for -// this because the rw_counter is increasing.) +// 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, From 243a0124535f46cbf536ac062a36261553ff2d64 Mon Sep 17 00:00:00 2001 From: Zhang Zhuo Date: Wed, 4 May 2022 17:47:17 +0800 Subject: [PATCH 27/31] fix conflicts --- zkevm-circuits/src/state_circuit/lexicographic_ordering.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index 768095ea94..3a1f89fb55 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -232,11 +232,11 @@ impl Chip { .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).into()); + 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).into()); + lower_limb_difference = F::from((cur_limb - prev_limb) as u64); } region.assign_advice( From 14c0f29cb30f3f044b13d083703e07a071b62173 Mon Sep 17 00:00:00 2001 From: Zhang Zhuo Date: Mon, 9 May 2022 12:53:39 +0800 Subject: [PATCH 28/31] lint --- zkevm-circuits/src/state_circuit/state_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkevm-circuits/src/state_circuit/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs index 52054f553c..bb97c328c4 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -12,9 +12,9 @@ mod tests { use halo2_proofs::{ arithmetic::BaseExt, dev::{MockProver, VerifyFailure}, + pairing::bn256::Fr, plonk::{Circuit, ConstraintSystem}, }; - use pairing::bn256::Fr; fn test_state_circuit_ok( memory_ops: Vec>, From 2295a4e94a90f4e7f3c6c934690ef26d7f6e58eb Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 9 May 2022 19:59:53 -0400 Subject: [PATCH 29/31] fix rebase --- zkevm-circuits/src/test_util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index f70625d239..b953f7150a 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -5,7 +5,7 @@ use crate::{ use bus_mapping::mock::BlockData; use eth_types::geth_types::GethData; use halo2_proofs::dev::{MockProver, VerifyFailure}; -use halo2_proofs::pairing::{arithmetic::BaseExt, bn256::Fr}; +use halo2_proofs::pairing::bn256::Fr; use mock::TestContext; use strum::IntoEnumIterator; From fb920b841de638bd7937f6e64efec893b7bf623c Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 9 May 2022 20:44:35 -0400 Subject: [PATCH 30/31] fix build and tests from rebase --- zkevm-circuits/src/evm_circuit.rs | 1 - zkevm-circuits/src/evm_circuit/table.rs | 2 +- zkevm-circuits/src/evm_circuit/witness.rs | 21 +- .../src/state_circuit/old_state_tests.rs | 788 ------------------ .../src/state_circuit/state_tests.rs | 2 +- zkevm-circuits/src/state_circuit/tests.rs | 177 ---- 6 files changed, 16 insertions(+), 975 deletions(-) delete mode 100644 zkevm-circuits/src/state_circuit/old_state_tests.rs delete mode 100644 zkevm-circuits/src/state_circuit/tests.rs diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 59e639d450..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 strum::IntoEnumIterator; use crate::{ evm_circuit::{ table::FixedTableTag, diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index a1efbea8db..423d4d6d25 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -4,7 +4,7 @@ use halo2_proofs::{ plonk::{Advice, Column, Expression, Fixed, VirtualCells}, poly::Rotation, }; -use strum_macros::{EnumIter, EnumCount}; +use strum_macros::{EnumCount, EnumIter}; pub trait LookupTable { fn table_exprs(&self, meta: &mut VirtualCells) -> Vec>; diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index e56dc64763..83253a286d 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -683,7 +683,8 @@ impl Rw { | Self::Account { rw_counter, .. } | Self::AccountDestructed { rw_counter, .. } | Self::CallContext { rw_counter, .. } - | Self::TxLog { rw_counter, .. } => *rw_counter, + | Self::TxLog { rw_counter, .. } + | Self::TxReceipt { rw_counter, .. } => *rw_counter, } } @@ -698,7 +699,8 @@ impl Rw { | Self::Account { is_write, .. } | Self::AccountDestructed { is_write, .. } | Self::CallContext { is_write, .. } - | Self::TxLog { is_write, .. } => *is_write, + | Self::TxLog { is_write, .. } + | Self::TxReceipt { is_write, .. } => *is_write, } } @@ -714,6 +716,7 @@ impl Rw { Self::AccountDestructed { .. } => RwTableTag::AccountDestructed, Self::CallContext { .. } => RwTableTag::CallContext, Self::TxLog { .. } => RwTableTag::TxLog, + Self::TxReceipt { .. } => RwTableTag::TxReceipt, } } @@ -723,7 +726,8 @@ impl Rw { | Self::TxAccessListAccount { tx_id, .. } | Self::TxAccessListAccountStorage { tx_id, .. } | Self::TxRefund { tx_id, .. } - | Self::TxLog { tx_id, .. } => Some(*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), @@ -755,7 +759,7 @@ impl Rw { Self::TxLog { log_id, index, .. } => { Some((U256::from(*index as u64) + (U256::from(*log_id) << 8)).to_address()) } - Self::CallContext { .. } | Self::TxRefund { .. } => None, + Self::CallContext { .. } | Self::TxRefund { .. } | Self::TxReceipt { .. } => None, } } @@ -764,6 +768,7 @@ impl Rw { 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 { .. } @@ -785,7 +790,8 @@ impl Rw { | Self::Account { .. } | Self::TxAccessListAccount { .. } | Self::AccountDestructed { .. } - | Self::TxLog { .. } => None, + | Self::TxLog { .. } + | Self::TxReceipt { .. } => None, } } @@ -816,7 +822,7 @@ impl Rw { | 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, .. } => F::from(*value), + Self::TxRefund { value, .. } | Self::TxReceipt { value, .. } => F::from(*value), } } @@ -839,7 +845,8 @@ impl Rw { Self::Stack { .. } | Self::Memory { .. } | Self::CallContext { .. } - | Self::TxLog { .. } => None, + | Self::TxLog { .. } + | Self::TxReceipt { .. } => None, } } diff --git a/zkevm-circuits/src/state_circuit/old_state_tests.rs b/zkevm-circuits/src/state_circuit/old_state_tests.rs deleted file mode 100644 index 6462dfc1ca..0000000000 --- a/zkevm-circuits/src/state_circuit/old_state_tests.rs +++ /dev/null @@ -1,788 +0,0 @@ -// TODO: migrate these over. - -#[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 mock::TestContext; - use pairing::bn256::Fr; - - 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 power_of_randomness: Vec<_> = (1..32) - .map(|exp| { - vec![ - circuit.randomness.pow(&[exp, 0, 0, 0]); - { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? - ] - }) - .collect(); - - let prover = MockProver::::run($k, &circuit, power_of_randomness).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 power_of_randomness: Vec<_> = (1..32) - .map(|exp| { - vec![ - circuit.randomness.pow(&[exp, 0, 0, 0]); - { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? - ] - }) - .collect(); - - let prover = MockProver::::run($k, &circuit, power_of_randomness).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( - 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!( - 17, - 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!( - 17, - 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!( - 17, - 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!( - 17, - 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!( - 18, - 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!( - 18, - 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!( - 17, - 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!( - 18, - 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!( - 17, - 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!( - 17, - 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 index bb97c328c4..cb88769bc6 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -42,7 +42,7 @@ mod tests { let mut meta = ConstraintSystem::::default(); StateCircuit::configure(&mut meta); - assert_eq!(meta.degree(), 18); + assert_eq!(meta.degree(), 19); } #[test] diff --git a/zkevm-circuits/src/state_circuit/tests.rs b/zkevm-circuits/src/state_circuit/tests.rs deleted file mode 100644 index e67b808d25..0000000000 --- a/zkevm-circuits/src/state_circuit/tests.rs +++ /dev/null @@ -1,177 +0,0 @@ -mod tests { - use super::super::StateCircuit; - use crate::evm_circuit::witness::RwMap; - use bus_mapping::operation::{ - MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, - }; - use eth_types::{ - evm_types::{MemoryAddress, StackAddress}, - ToAddress, Word, U256, - }; - use halo2_proofs::{arithmetic::BaseExt, dev::MockProver, pairing::bn256::Fr}; - use std::collections::HashMap; - - 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 power_of_randomness = StateCircuit::instance(&randomness, rw_map.0.len()); - let circuit = StateCircuit { randomness, rw_map }; - - let prover = MockProver::::run(19, &circuit, power_of_randomness).unwrap(); - let verify_result = prover.verify(); - assert_eq!(verify_result, Ok(())); - } - - #[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![]); - } -} From 19e25dc3e35ff664b5daa362d9ea143a8620da11 Mon Sep 17 00:00:00 2001 From: z2trillion Date: Mon, 9 May 2022 21:23:57 -0400 Subject: [PATCH 31/31] make change to rerun tests --- zkevm-circuits/src/state_circuit/state_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zkevm-circuits/src/state_circuit/state_tests.rs b/zkevm-circuits/src/state_circuit/state_tests.rs index cb88769bc6..d4a0b97ab9 100644 --- a/zkevm-circuits/src/state_circuit/state_tests.rs +++ b/zkevm-circuits/src/state_circuit/state_tests.rs @@ -41,7 +41,6 @@ mod tests { fn degree() { let mut meta = ConstraintSystem::::default(); StateCircuit::configure(&mut meta); - assert_eq!(meta.degree(), 19); }