diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 11e676d5619..035b94c87f6 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -203,6 +203,8 @@ impl<'a> CircuitInputBuilder { let geth_trace = &geth_traces[tx_index]; self.handle_tx(tx, geth_trace, tx_index + 1 == eth_block.transactions.len())?; } + // set eth_block + self.block.eth_block = eth_block.clone(); self.set_value_ops_call_context_rwc_eor(); self.set_end_block(); Ok(()) diff --git a/circuit-benchmarks/src/pi_circuit.rs b/circuit-benchmarks/src/pi_circuit.rs index eb1295e1291..d3a4ed2ae9d 100644 --- a/circuit-benchmarks/src/pi_circuit.rs +++ b/circuit-benchmarks/src/pi_circuit.rs @@ -4,7 +4,6 @@ mod tests { use ark_std::{end_timer, start_timer}; use eth_types::Word; use halo2_proofs::{ - arithmetic::Field, halo2curves::bn256::{Bn256, Fr, G1Affine}, plonk::{create_proof, keygen_pk, keygen_vk, verify_proof}, poly::{ @@ -20,13 +19,9 @@ mod tests { }, }; use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; use rand_xorshift::XorShiftRng; use std::env::var; - use zkevm_circuits::{ - pi_circuit::{PiCircuit, PublicData}, - util::SubCircuit, - }; + use zkevm_circuits::{instance::PublicData, pi_circuit::PiCircuit, util::SubCircuit}; #[cfg_attr(not(feature = "benches"), ignore)] #[test] @@ -45,12 +40,8 @@ mod tests { .parse() .expect("Cannot parse DEGREE env var as u32"); - let mut rng = ChaCha20Rng::seed_from_u64(2); - let randomness = Fr::random(&mut rng); - let rand_rpi = Fr::random(&mut rng); let public_data = generate_publicdata(MAX_TXS); - let circuit = - PiCircuit::::new(MAX_TXS, MAX_CALLDATA, randomness, rand_rpi, public_data); + let circuit = PiCircuit::::new(MAX_TXS, MAX_CALLDATA, public_data); let public_inputs = circuit.instance(); let instance: Vec<&[Fr]> = public_inputs.iter().map(|input| &input[..]).collect(); let instances = &[&instance[..]]; diff --git a/integration-tests/run.sh b/integration-tests/run.sh index a67bdb49025..1a4a33e4fa8 100755 --- a/integration-tests/run.sh +++ b/integration-tests/run.sh @@ -1,5 +1,6 @@ #!/bin/sh set -e +set -o xtrace ARG_DEFAULT_SUDO= ARG_DEFAULT_STEPS="setup gendata tests cleanup" diff --git a/integration-tests/src/integration_test_circuits.rs b/integration-tests/src/integration_test_circuits.rs index 4771edd86ea..28a9fdba449 100644 --- a/integration-tests/src/integration_test_circuits.rs +++ b/integration-tests/src/integration_test_circuits.rs @@ -59,7 +59,7 @@ const MAX_EVM_ROWS: usize = 10000; /// MAX_EXP_STEPS const MAX_EXP_STEPS: usize = 1000; -const MAX_KECCAK_ROWS: usize = 15000; +const MAX_KECCAK_ROWS: usize = 38000; const CIRCUITS_PARAMS: CircuitsParams = CircuitsParams { max_rws: MAX_RWS, diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index e21530bb9c6..5bfab878674 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -80,6 +80,9 @@ pub(crate) const MAX_N_BYTES_INTEGER: usize = 31; // Number of bytes an EVM word has. pub(crate) const N_BYTES_WORD: usize = 32; +// Number of bytes an half EVM word has. +pub(crate) const N_BYTES_HALF_WORD: usize = 16; + // Number of bytes an u64 has. pub(crate) const N_BYTES_U64: usize = 8; @@ -106,6 +109,49 @@ pub(crate) const N_BYTES_GAS: usize = N_BYTES_U64; // Number of bytes that will be used for call data's size. pub(crate) const N_BYTES_CALLDATASIZE: usize = N_BYTES_U64; +// Number of bytes that will be used for block values +pub(crate) const N_BYTES_COINBASE: usize = N_BYTES_ACCOUNT_ADDRESS; +pub(crate) const N_BYTES_GAS_LIMIT: usize = N_BYTES_U64; +pub(crate) const N_BYTES_NUMBER: usize = N_BYTES_U64; +pub(crate) const N_BYTES_TIMESTAMP: usize = N_BYTES_U64; +pub(crate) const N_BYTES_DIFFICULTY: usize = N_BYTES_WORD; +pub(crate) const N_BYTES_BASE_FEE: usize = N_BYTES_WORD; +pub(crate) const N_BYTES_CHAIN_ID: usize = N_BYTES_U64; +pub(crate) const N_BYTES_PREV_HASH: usize = 256 * N_BYTES_WORD; + +pub(crate) const N_BYTES_BLOCK: usize = N_BYTES_COINBASE + + N_BYTES_GAS_LIMIT + + N_BYTES_NUMBER + + N_BYTES_TIMESTAMP + + N_BYTES_DIFFICULTY + + N_BYTES_BASE_FEE + + N_BYTES_CHAIN_ID + + N_BYTES_PREV_HASH; + +pub(crate) const N_BYTES_EXTRA_VALUE: usize = N_BYTES_WORD + N_BYTES_WORD; + +// Number of bytes that will be used for tx values +pub(crate) const N_BYTES_TX_NONCE: usize = N_BYTES_U64; +pub(crate) const N_BYTES_TX_GAS_LIMIT: usize = N_BYTES_U64; // gas limit type is U256, different with gas U64 +pub(crate) const N_BYTES_TX_GASPRICE: usize = N_BYTES_WORD; +pub(crate) const N_BYTES_TX_FROM: usize = N_BYTES_ACCOUNT_ADDRESS; +pub(crate) const N_BYTES_TX_TO: usize = N_BYTES_ACCOUNT_ADDRESS; +pub(crate) const N_BYTES_TX_IS_CREATE: usize = N_BYTES_U64; +pub(crate) const N_BYTES_TX_VALUE: usize = N_BYTES_WORD; +pub(crate) const N_BYTES_TX_CALLDATA_LEN: usize = N_BYTES_CALLDATASIZE; +pub(crate) const N_BYTES_TX_CALLDATA_GASCOST: usize = N_BYTES_U64; +pub(crate) const N_BYTES_TX_TXSIGNHASH: usize = N_BYTES_WORD; +pub(crate) const N_BYTES_TX: usize = N_BYTES_TX_NONCE + + N_BYTES_TX_GAS_LIMIT + + N_BYTES_TX_GASPRICE + + N_BYTES_TX_FROM + + N_BYTES_TX_TO + + N_BYTES_TX_IS_CREATE + + N_BYTES_TX_VALUE + + N_BYTES_TX_CALLDATA_LEN + + N_BYTES_TX_CALLDATA_GASCOST + + N_BYTES_TX_TXSIGNHASH; + lazy_static::lazy_static! { // Step slot height in evm circuit pub(crate) static ref EXECUTION_STATE_HEIGHT_MAP : HashMap = get_step_height_map(); diff --git a/zkevm-circuits/src/instance.rs b/zkevm-circuits/src/instance.rs new file mode 100644 index 00000000000..2c723140975 --- /dev/null +++ b/zkevm-circuits/src/instance.rs @@ -0,0 +1,288 @@ +//! The instance definition. + +use std::iter; + +use eth_types::{geth_types::BlockConstants, BigEndianHash, Field, Keccak}; + +use eth_types::{geth_types::Transaction, Address, ToBigEndian, Word, H256}; +use itertools::Itertools; + +use crate::{util::word, witness::Block}; + +pub(super) const ZERO_BYTE_GAS_COST: u64 = 4; +pub(super) const NONZERO_BYTE_GAS_COST: u64 = 16; + +/// Values of the block table (as in the spec) +#[derive(Clone, Default, Debug)] +pub struct BlockValues { + /// coinbase + pub coinbase: Address, + /// gas_limit + pub gas_limit: u64, + /// number + pub number: u64, + /// timestamp + pub timestamp: u64, + /// difficulty + pub difficulty: Word, + /// base_fee + pub base_fee: Word, // NOTE: BaseFee was added by EIP-1559 and is ignored in legacy headers. + /// chain_id + pub chain_id: u64, + /// history_hashes + pub history_hashes: Vec, +} + +/// Values of the tx table (as in the spec) +#[derive(Default, Debug, Clone)] +pub struct TxValues { + /// nonce + pub nonce: u64, + /// gas_limit + pub gas_limit: u64, + /// gas_price + pub gas_price: Word, + /// from_addr + pub from_addr: Address, + /// to_addr + pub to_addr: Address, + /// is_create + pub is_create: u64, + /// value + pub value: Word, + /// call_data_len + pub call_data_len: u64, + /// call_data_gas_cost + pub call_data_gas_cost: u64, + /// tx_sign_hash + pub tx_sign_hash: [u8; 32], +} + +/// Extra values (not contained in block or tx tables) +#[derive(Default, Debug, Clone)] +pub struct ExtraValues { + // block_hash: H256, + /// state_root + pub state_root: H256, + /// prev_state_root + pub prev_state_root: H256, +} + +/// PublicData contains all the values that the PiCircuit recieves as input +#[derive(Debug, Clone)] +pub struct PublicData { + /// chain id + pub chain_id: Word, + /// History hashes contains the most recent 256 block hashes in history, + /// where the latest one is at history_hashes[history_hashes.len() - 1]. + pub history_hashes: Vec, + /// Block Transactions + pub transactions: Vec, + /// Block State Root + pub state_root: H256, + /// Previous block root + pub prev_state_root: H256, + /// Constants related to Ethereum block + pub block_constants: BlockConstants, +} + +impl Default for PublicData { + fn default() -> Self { + PublicData { + chain_id: Word::default(), + history_hashes: vec![], + transactions: vec![], + state_root: H256::zero(), + prev_state_root: H256::zero(), + block_constants: BlockConstants::default(), + } + } +} + +impl PublicData { + /// Returns struct with values for the block table + pub fn get_block_table_values(&self) -> BlockValues { + let history_hashes = [ + vec![H256::zero(); 256 - self.history_hashes.len()], + self.history_hashes + .iter() + .map(|&hash| H256::from(hash.to_be_bytes())) + .collect(), + ] + .concat(); + BlockValues { + coinbase: self.block_constants.coinbase, + gas_limit: self.block_constants.gas_limit.as_u64(), + number: self.block_constants.number.as_u64(), + timestamp: self.block_constants.timestamp.as_u64(), + difficulty: self.block_constants.difficulty, + base_fee: self.block_constants.base_fee, + chain_id: self.chain_id.as_u64(), + history_hashes, + } + } + + /// Returns struct with values for the tx table + pub fn get_tx_table_values(&self) -> Vec { + let chain_id: u64 = self + .chain_id + .try_into() + .expect("Error converting chain_id to u64"); + let mut tx_vals = vec![]; + for tx in &self.txs() { + let sign_data_res = tx.sign_data(chain_id); + let msg_hash_le = + sign_data_res.map_or_else(|_| [0u8; 32], |sign_data| sign_data.msg_hash.to_bytes()); + tx_vals.push(TxValues { + nonce: tx.nonce.low_u64(), + gas_price: tx.gas_price, + gas_limit: tx.gas_limit.low_u64(), + from_addr: tx.from, + to_addr: tx.to.unwrap_or_else(Address::zero), + is_create: (tx.to.is_none() as u64), + value: tx.value, + call_data_len: tx.call_data.0.len() as u64, + call_data_gas_cost: tx.call_data.0.iter().fold(0, |acc, byte| { + acc + if *byte == 0 { + ZERO_BYTE_GAS_COST + } else { + NONZERO_BYTE_GAS_COST + } + }), + tx_sign_hash: msg_hash_le, + }); + } + tx_vals + } + + /// Returns struct with the extra values + pub fn get_extra_values(&self) -> ExtraValues { + ExtraValues { + // block_hash: self.hash.unwrap_or_else(H256::zero), + state_root: self.state_root, + prev_state_root: self.prev_state_root, + } + } + + /// Return converted transaction + pub fn txs(&self) -> Vec { + self.transactions + .iter() + .map(Transaction::from) + .collect_vec() + } + + /// get the serialized public data bytes + pub fn get_pi_bytes(&self, max_txs: usize, max_calldata: usize) -> Vec { + // Assign block table + let block_values = self.get_block_table_values(); + let result = iter::empty() + .chain(0u8.to_be_bytes()) // zero byte + .chain(block_values.coinbase.to_fixed_bytes()) // coinbase + .chain(block_values.gas_limit.to_be_bytes()) // gas_limit + .chain(block_values.number.to_be_bytes()) // number + .chain(block_values.timestamp.to_be_bytes()) // timestamp + .chain(block_values.difficulty.to_be_bytes()) // difficulty + .chain(block_values.base_fee.to_be_bytes()) // base_fee + .chain(block_values.chain_id.to_be_bytes()) // chain_id + .chain( + block_values + .history_hashes + .iter() + .flat_map(|prev_hash| prev_hash.to_fixed_bytes()), + ); // history_hashes + + // Assign extra fields + let extra_vals = self.get_extra_values(); + let result = result + .chain(extra_vals.state_root.to_fixed_bytes()) // block state root + .chain(extra_vals.prev_state_root.to_fixed_bytes()); // previous block state root + + // Assign Tx table + let tx_field_byte_fn = |tx_id: u64, index: u64, value_bytes: &[u8]| { + iter::empty() + .chain(tx_id.to_be_bytes()) // tx_id + .chain(index.to_be_bytes()) // index + .chain(value_bytes.to_vec()) // value + }; + let tx_bytes_fn = |tx_id: u64, index: u64, tx: &TxValues| { + vec![ + tx.nonce.to_be_bytes().to_vec(), // nonce + tx.gas_limit.to_be_bytes().to_vec(), // gas_limit + tx.gas_price.to_be_bytes().to_vec(), // gas price + tx.from_addr.as_fixed_bytes().to_vec(), // from_addr + tx.to_addr.as_fixed_bytes().to_vec(), // to_addr + tx.is_create.to_be_bytes().to_vec(), // is_create + tx.value.to_be_bytes().to_vec(), // value + tx.call_data_len.to_be_bytes().to_vec(), // call_data_len + tx.call_data_gas_cost.to_be_bytes().to_vec(), // call_data_gas_cost + tx.tx_sign_hash.iter().rev().copied().collect_vec(), // tx sign hash + ] + .iter() + .flat_map(move |value_bytes| tx_field_byte_fn(tx_id, index, value_bytes)) + .collect_vec() + }; + + let txs_values = self.get_tx_table_values(); + let tx_values_default = TxValues::default(); + + // all tx bytes including tx padding + let all_tx_bytes = iter::empty() + .chain(&txs_values) + .chain((0..(max_txs - txs_values.len())).map(|_| &tx_values_default)) + .enumerate() + .flat_map(|(i, tx)| { + let i: u64 = i.try_into().unwrap(); + tx_bytes_fn(i + 1, 0, tx) + }); + + // first tx empty row happened here + let result = result + .chain(tx_field_byte_fn(0, 0, &[0u8; 1])) // empty row + .chain(all_tx_bytes); + + // Tx Table CallData + let txs = self.txs(); + let all_calldata = txs + .iter() + .flat_map(|tx| tx.call_data.0.as_ref().iter().copied()) + .collect_vec(); + let calldata_count = all_calldata.len(); + // concat call data with call data padding + let calldata_chain = iter::empty() + .chain(all_calldata) + .chain((0..max_calldata - calldata_count).map(|_| 0u8)); + result.chain(calldata_chain).collect_vec() + } + + /// generate public data from validator perspective + pub fn get_rpi_digest_word( + &self, + max_txs: usize, + max_calldata: usize, + ) -> word::Word { + let mut keccak = Keccak::default(); + keccak.update(&self.get_pi_bytes(max_txs, max_calldata)); + let digest = keccak.digest(); + word::Word::from(Word::from_big_endian(&digest)) + } +} + +/// convert witness block to public data +pub fn public_data_convert(block: &Block) -> PublicData { + PublicData { + chain_id: block.context.chain_id, + history_hashes: block.context.history_hashes.clone(), + transactions: block.eth_block.transactions.clone(), + state_root: block.eth_block.state_root, + prev_state_root: H256::from_uint(&block.prev_state_root), + block_constants: BlockConstants { + coinbase: block.context.coinbase, + timestamp: block.context.timestamp, + number: block.context.number.as_u64().into(), + difficulty: block.context.difficulty, + gas_limit: block.context.gas_limit.into(), + base_fee: block.context.base_fee, + }, + } +} diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index de9b5e2b799..0ced14d041e 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -33,6 +33,7 @@ pub mod table; #[cfg(any(feature = "test", test))] pub mod test_util; +pub mod instance; pub mod tx_circuit; pub mod util; pub mod witness; diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index cebe069cba0..872484bfbd4 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -5,22 +5,34 @@ mod param; mod dev; #[cfg(any(feature = "test", test))] mod test; +use std::{cmp::min, iter, marker::PhantomData}; + #[cfg(any(feature = "test", test, feature = "test-circuits"))] pub use dev::PiCircuit as TestPiCircuit; -use eth_types::{ - geth_types::{BlockConstants, Transaction}, - sign_types::SignData, - Address, BigEndianHash, Field, Keccak, ToBigEndian, ToLittleEndian, ToScalar, Word, H256, -}; -use halo2_proofs::plonk::{Instance, SecondPhase}; +use eth_types::{self, Field, ToLittleEndian}; +use halo2_proofs::plonk::{Expression, Instance, SecondPhase}; +use itertools::Itertools; use param::*; -use std::marker::PhantomData; use crate::{ - table::{BlockTable, LookupTable, TxFieldTag, TxTable}, + evm_circuit::{ + param::{ + N_BYTES_BLOCK, N_BYTES_EXTRA_VALUE, N_BYTES_HALF_WORD, N_BYTES_TX, N_BYTES_U64, + N_BYTES_WORD, + }, + util::{ + constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, + from_bytes, + }, + }, + instance::{ + public_data_convert, BlockValues, ExtraValues, PublicData, TxValues, NONZERO_BYTE_GAS_COST, + ZERO_BYTE_GAS_COST, + }, + table::{BlockTable, KeccakTable, LookupTable, TxFieldTag, TxTable}, tx_circuit::TX_LEN, - util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}, + util::{word::Word, Challenges, SubCircuit, SubCircuitConfig}, witness, }; use gadgets::{ @@ -36,139 +48,6 @@ use halo2_proofs::{ #[cfg(any(feature = "test", test, feature = "test-circuits"))] use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; -/// Values of the block table (as in the spec) -#[derive(Clone, Default, Debug)] -pub struct BlockValues { - coinbase: Address, - gas_limit: u64, - number: u64, - timestamp: u64, - difficulty: Word, - base_fee: Word, // NOTE: BaseFee was added by EIP-1559 and is ignored in legacy headers. - chain_id: u64, - history_hashes: Vec, -} - -/// Values of the tx table (as in the spec) -#[derive(Default, Debug, Clone)] -pub struct TxValues { - nonce: u64, - gas: u64, // gas limit - gas_price: Word, - from_addr: Address, - to_addr: Address, - is_create: bool, - value: Word, - call_data_len: u64, - call_data_gas_cost: u64, - tx_sign_hash: [u8; 32], -} - -/// Extra values (not contained in block or tx tables) -#[derive(Default, Debug, Clone)] -pub struct ExtraValues { - // block_hash: H256, - state_root: H256, - prev_state_root: H256, -} - -/// PublicData contains all the values that the PiCircuit recieves as input -#[derive(Debug, Clone)] -pub struct PublicData { - /// chain id - pub chain_id: Word, - /// History hashes contains the most recent 256 block hashes in history, - /// where the latest one is at history_hashes[history_hashes.len() - 1]. - pub history_hashes: Vec, - /// Block Transactions - pub transactions: Vec, - /// Block State Root - pub state_root: H256, - /// Previous block root - pub prev_state_root: H256, - /// Constants related to Ethereum block - pub block_constants: BlockConstants, -} - -impl Default for PublicData { - fn default() -> Self { - PublicData { - chain_id: Word::default(), - history_hashes: vec![], - transactions: vec![], - state_root: H256::zero(), - prev_state_root: H256::zero(), - block_constants: BlockConstants::default(), - } - } -} - -impl PublicData { - /// Returns struct with values for the block table - pub fn get_block_table_values(&self) -> BlockValues { - let history_hashes = [ - vec![H256::zero(); 256 - self.history_hashes.len()], - self.history_hashes - .iter() - .map(|&hash| H256::from(hash.to_be_bytes())) - .collect(), - ] - .concat(); - BlockValues { - coinbase: self.block_constants.coinbase, - gas_limit: self.block_constants.gas_limit.as_u64(), - number: self.block_constants.number.as_u64(), - timestamp: self.block_constants.timestamp.as_u64(), - difficulty: self.block_constants.difficulty, - base_fee: self.block_constants.base_fee, - chain_id: self.chain_id.as_u64(), - history_hashes, - } - } - - /// Returns struct with values for the tx table - pub fn get_tx_table_values(&self) -> Vec { - let chain_id: u64 = self - .chain_id - .try_into() - .expect("Error converting chain_id to u64"); - let mut tx_vals = vec![]; - for tx in &self.txs() { - let sign_data: SignData = tx - .sign_data(chain_id) - .expect("Error computing tx_sign_hash"); - let mut msg_hash_le = [0u8; 32]; - msg_hash_le.copy_from_slice(sign_data.msg_hash.to_bytes().as_slice()); - tx_vals.push(TxValues { - nonce: tx.nonce.as_u64(), - gas_price: tx.gas_price, - gas: tx.gas_limit.as_u64(), - from_addr: tx.from, - to_addr: tx.to_or_zero(), - is_create: tx.is_create(), - value: tx.value, - call_data_len: tx.call_data.len() as u64, - call_data_gas_cost: tx.call_data_gas_cost(), - tx_sign_hash: msg_hash_le, - }); - } - tx_vals - } - - /// Returns struct with the extra values - pub fn get_extra_values(&self) -> ExtraValues { - ExtraValues { - // block_hash: self.hash.unwrap_or_else(H256::zero), - state_root: self.state_root, - prev_state_root: self.prev_state_root, - } - } - - fn txs(&self) -> Vec { - self.transactions.iter().map(Transaction::from).collect() - } -} - /// Config for PiCircuit #[derive(Clone, Debug)] pub struct PiCircuitConfig { @@ -177,34 +56,57 @@ pub struct PiCircuitConfig { /// Max number of supported calldata bytes max_calldata: usize, - q_block_table: Selector, + q_digest_last: Selector, + q_bytes_last: Selector, q_tx_table: Selector, q_tx_calldata: Selector, q_calldata_start: Selector, + q_rpi_keccak_lookup: Selector, + // q_rpi_value_start: assure rpi_bytes sync with rpi_value_lc when cross boundary. + // because we layout rpi bytes vertically, which is concated from multiple original values. + // The value can be one byte or multiple bytes. The order of values is pre-defined and + // hardcode. can't use selector here because we need rotation + q_rpi_value_start: Column, + // q_digest_value_start: mark starting of hi and low. can't use selector here because we need + // rotation + q_digest_value_start: Column, tx_id_inv: Column, - tx_value_inv: Column, + // Do not need tx_value_hi_inv, because tx_value_inv only + // refer in tx_calldata constrains, and only need tx_value.lo() part + tx_value_lo_inv: Column, tx_id_diff_inv: Column, fixed_u16: Column, calldata_gas_cost: Column, is_final: Column, + // is_value_rlc: Column, + + // rpi_bytes: raw public input bytes laid verticlly + rpi_bytes: Column, + // rpi_bytes_keccakrlc: rpi_bytes rlc by keccak challenge. This is for Keccak lookup input + // rlc + rpi_bytes_keccakrlc: Column, + // rpi_value_lc: This is similar with rpi_bytes_keccakrlc, while the key differences is + // it's rlc in value based and reset for next new value. rand value is control by is_value_rlc + rpi_value_lc: Column, + // rpi_digest_bytes: Keccak digest raw bytes laid verticlly in this column + rpi_digest_bytes: Column, + // rpi_digest_bytes_limbs: hi, lo limbs of digest + rpi_digest_bytes_limbs: Column, - raw_public_inputs: Column, - rpi_rlc_acc: Column, - rand_rpi: Column, - q_not_end: Selector, - q_end: Selector, + q_rpi_byte_enable: Selector, - pi: Column, // rpi_rand, rpi_rlc, chain_ID, state_root, prev_state_root + pi_instance: Column, // keccak_digest_hi, keccak_digest_lo _marker: PhantomData, // External tables block_table: BlockTable, tx_table: TxTable, + keccak_table: KeccakTable, } /// Circuit configuration arguments -pub struct PiCircuitConfigArgs { +pub struct PiCircuitConfigArgs { /// Max number of supported transactions pub max_txs: usize, /// Max number of supported calldata bytes @@ -213,10 +115,14 @@ pub struct PiCircuitConfigArgs { pub tx_table: TxTable, /// BlockTable pub block_table: BlockTable, + /// Keccak Table + pub keccak_table: KeccakTable, + /// Challenges + pub challenges: Challenges>, } impl SubCircuitConfig for PiCircuitConfig { - type ConfigArgs = PiCircuitConfigArgs; + type ConfigArgs = PiCircuitConfigArgs; /// Return a new PiCircuitConfig fn new( @@ -226,20 +132,21 @@ impl SubCircuitConfig for PiCircuitConfig { max_calldata, block_table, tx_table, + keccak_table, + challenges, }: Self::ConfigArgs, ) -> Self { - let q_block_table = meta.selector(); - let q_tx_table = meta.complex_selector(); let q_tx_calldata = meta.complex_selector(); let q_calldata_start = meta.complex_selector(); + let q_rpi_keccak_lookup = meta.complex_selector(); // Tx Table let tx_id = tx_table.tx_id; - let tx_value = tx_table.value; + let tx_value = tx_table.value_word; let tag = tx_table.tag; let index = tx_table.index; let tx_id_inv = meta.advice_column(); - let tx_value_inv = meta.advice_column_in(SecondPhase); + let tx_value_lo_inv = meta.advice_column(); let tx_id_diff_inv = meta.advice_column(); // The difference of tx_id of adjacent rows in calldata part of tx table // lies in the interval [0, 2^16] if their tx_id both do not equal to zero. @@ -249,113 +156,146 @@ impl SubCircuitConfig for PiCircuitConfig { let calldata_gas_cost = meta.advice_column_in(SecondPhase); let is_final = meta.advice_column(); - let raw_public_inputs = meta.advice_column_in(SecondPhase); - let rpi_rlc_acc = meta.advice_column_in(SecondPhase); - let rand_rpi = meta.advice_column(); - let q_not_end = meta.selector(); - let q_end = meta.selector(); + let q_digest_last = meta.complex_selector(); + let q_bytes_last = meta.complex_selector(); + let q_rpi_byte_enable = meta.complex_selector(); + let q_rpi_value_start = meta.fixed_column(); + let q_digest_value_start = meta.fixed_column(); + + let rpi_bytes = meta.advice_column(); + let rpi_bytes_keccakrlc = meta.advice_column_in(SecondPhase); + let rpi_value_lc = meta.advice_column(); + // let is_value_rlc = meta.fixed_column(); + let rpi_digest_bytes = meta.advice_column(); + let rpi_digest_bytes_limbs = meta.advice_column(); - let pi = meta.instance_column(); + let pi_instance = meta.instance_column(); // Annotate table columns tx_table.annotate_columns(meta); block_table.annotate_columns(meta); - meta.enable_equality(raw_public_inputs); - meta.enable_equality(rpi_rlc_acc); - meta.enable_equality(rand_rpi); - meta.enable_equality(pi); + meta.enable_equality(block_table.value_word.lo()); + meta.enable_equality(block_table.value_word.hi()); + meta.enable_equality(tx_table.tx_id); + meta.enable_equality(tx_table.index); + meta.enable_equality(tx_table.value_word.lo()); + meta.enable_equality(tx_table.value_word.hi()); - // 0.0 rpi_rlc_acc[0] == RLC(raw_public_inputs, rand_rpi) - meta.create_gate( - "rpi_rlc_acc[i] = rand_rpi * rpi_rlc_acc[i+1] + raw_public_inputs[i]", - |meta| { - // q_not_end * row.rpi_rlc_acc == - // (q_not_end * row_next.rpi_rlc_acc * row.rand_rpi + row.raw_public_inputs ) - let q_not_end = meta.query_selector(q_not_end); - let cur_rpi_rlc_acc = meta.query_advice(rpi_rlc_acc, Rotation::cur()); - let next_rpi_rlc_acc = meta.query_advice(rpi_rlc_acc, Rotation::next()); - let rand_rpi = meta.query_advice(rand_rpi, Rotation::cur()); - let raw_public_inputs = meta.query_advice(raw_public_inputs, Rotation::cur()); + meta.enable_equality(rpi_value_lc); + meta.enable_equality(rpi_bytes_keccakrlc); - vec![ - q_not_end * (next_rpi_rlc_acc * rand_rpi + raw_public_inputs - cur_rpi_rlc_acc), - ] - }, - ); - meta.create_gate("rpi_rlc_acc[last] = raw_public_inputs[last]", |meta| { - let q_end = meta.query_selector(q_end); - let raw_public_inputs = meta.query_advice(raw_public_inputs, Rotation::cur()); - let rpi_rlc_acc = meta.query_advice(rpi_rlc_acc, Rotation::cur()); - vec![q_end * (raw_public_inputs - rpi_rlc_acc)] - }); + meta.enable_equality(rpi_digest_bytes_limbs); - // 0.1 rand_rpi[i] == rand_rpi[j] - meta.create_gate("rand_pi = rand_rpi.next", |meta| { - // q_not_end * row.rand_rpi == q_not_end * row_next.rand_rpi - let q_not_end = meta.query_selector(q_not_end); - let cur_rand_rpi = meta.query_advice(rand_rpi, Rotation::cur()); - let next_rand_rpi = meta.query_advice(rand_rpi, Rotation::next()); + meta.enable_equality(pi_instance); - vec![q_not_end * (cur_rand_rpi - next_rand_rpi)] - }); + // gate 1 and gate 2 are compensation branch + // 1: rpi_bytes_keccakrlc[last] = rpi_bytes[last] + meta.create_gate("rpi_bytes_keccakrlc[last] = rpi_bytes[last]", |meta| { + let mut cb = BaseConstraintBuilder::default(); - // 0.2 Block table -> value column match with raw_public_inputs at expected - // offset - meta.create_gate("block_table[i] = raw_public_inputs[offset + i]", |meta| { - let q_block_table = meta.query_selector(q_block_table); - let block_value = meta.query_advice(block_table.value, Rotation::cur()); - let rpi_block_value = meta.query_advice(raw_public_inputs, Rotation::cur()); - vec![q_block_table * (block_value - rpi_block_value)] - }); + cb.require_equal( + "rpi_bytes_keccakrlc[last] = rpi_bytes[last]", + meta.query_advice(rpi_bytes_keccakrlc, Rotation::cur()), + meta.query_advice(rpi_bytes, Rotation::cur()), + ); - let offset = BLOCK_LEN + 1 + EXTRA_LEN; - let tx_table_len = max_txs * TX_LEN + 1; + cb.gate(meta.query_selector(q_bytes_last) * meta.query_selector(q_rpi_byte_enable)) + }); - // 0.3 Tx table -> {tx_id, index, value} column match with raw_public_inputs - // at expected offset + // 2: rpi_bytes_keccakrlc[i] = keccak_rand * rpi_bytes_keccakrlc[i+1] + rpi_bytes[i]" meta.create_gate( - "tx_table.tx_id[i] == raw_public_inputs[offset + i]", + "rpi_bytes_keccakrlc[i] = keccak_rand * rpi_bytes_keccakrlc[i+1] + rpi_bytes[i]", |meta| { - // row.q_tx_table * row.tx_table.tx_id - // == row.q_tx_table * row_offset_tx_table_tx_id.raw_public_inputs - let q_tx_table = meta.query_selector(q_tx_table); - let tx_id = meta.query_advice(tx_table.tx_id, Rotation::cur()); - let rpi_tx_id = meta.query_advice(raw_public_inputs, Rotation(offset as i32)); + let mut cb = BaseConstraintBuilder::default(); + + let rpi_bytes_keccakrlc_cur = + meta.query_advice(rpi_bytes_keccakrlc, Rotation::cur()); + let rpi_bytes_keccakrlc_next = + meta.query_advice(rpi_bytes_keccakrlc, Rotation::next()); + let rpi_bytes_cur = meta.query_advice(rpi_bytes, Rotation::cur()); + + let keccak_rand = challenges.keccak_input(); + cb.require_equal( + "rpi_bytes_keccakrlc[i] = keccak_rand * rpi_bytes_keccakrlc[i+1] + rpi_bytes[i]", + rpi_bytes_keccakrlc_cur, + rpi_bytes_keccakrlc_next * keccak_rand + rpi_bytes_cur, + ); - vec![q_tx_table * (tx_id - rpi_tx_id)] + cb.gate( + not::expr(meta.query_selector(q_bytes_last)) * + meta.query_selector(q_rpi_byte_enable) + ) }, ); + // gate 3 and gate 4 are compensation branch + // 3: rpi_value_lc[i] = rpi_value_lc[i+1] * byte_pow_base + // + rpi_bytes[i] meta.create_gate( - "tx_table.index[i] == raw_public_inputs[offset + tx_table_len + i]", + "rpi_value_lc[i] = rpi_value_lc[i-1] * byte_pow_base + rpi_bytes[i]", |meta| { - // row.q_tx_table * row.tx_table.tx_index - // == row.q_tx_table * row_offset_tx_table_tx_index.raw_public_inputs - let q_tx_table = meta.query_selector(q_tx_table); - let tx_index = meta.query_advice(tx_table.index, Rotation::cur()); - let rpi_tx_index = - meta.query_advice(raw_public_inputs, Rotation((offset + tx_table_len) as i32)); + let mut cb = BaseConstraintBuilder::default(); + let q_rpi_value_start_cur = meta.query_fixed(q_rpi_value_start, Rotation::cur()); + let rpi_value_lc_next = meta.query_advice(rpi_value_lc, Rotation::next()); + let rpi_value_lc_cur = meta.query_advice(rpi_value_lc, Rotation::cur()); + let rpi_bytes_cur = meta.query_advice(rpi_bytes, Rotation::cur()); + + cb.require_equal( + "rpi_value_lc[i] = rpi_value_lc[i+1] * r + rpi_bytes[i]", + rpi_value_lc_cur, + rpi_value_lc_next * BYTE_POW_BASE.expr() + rpi_bytes_cur, + ); - vec![q_tx_table * (tx_index - rpi_tx_index)] + cb.gate(not::expr(q_rpi_value_start_cur) * meta.query_selector(q_rpi_byte_enable)) }, ); - meta.create_gate( - "tx_table.tx_value[i] == raw_public_inputs[offset + 2* tx_table_len + i]", + // 4. rpi_value_lc[i] = rpi_bytes[i] + meta.create_gate("rpi_value_lc[i] = rpi_bytes[i]", |meta| { + let mut cb = BaseConstraintBuilder::default(); + let q_rpi_value_start_cur = meta.query_fixed(q_rpi_value_start, Rotation::cur()); + + cb.require_equal( + "rpi_value_lc[i] = rpi_bytes[i]", + meta.query_advice(rpi_bytes, Rotation::cur()), + meta.query_advice(rpi_value_lc, Rotation::cur()), + ); + + cb.gate(q_rpi_value_start_cur * meta.query_selector(q_rpi_byte_enable)) + }); + + // 5. lookup rpi_bytes_keccakrlc against rpi_digest_bytes_limbs + meta.lookup_any( + "lookup rpi_bytes_keccakrlc against rpi_digest_bytes_limbs", |meta| { - // (row.q_tx_calldata | row.q_tx_table) * row.tx_table.tx_value - // == (row.q_tx_calldata | row.q_tx_table) * - // row_offset_tx_table_tx_value.raw_public_inputs - let q_tx_table = meta.query_selector(q_tx_table); - let tx_value = meta.query_advice(tx_value, Rotation::cur()); - let q_tx_calldata = meta.query_selector(q_tx_calldata); - let rpi_tx_value = meta.query_advice( - raw_public_inputs, - Rotation((offset + 2 * tx_table_len) as i32), - ); + let circuit_len = + PiCircuitConfig::::circuit_len_by_txs_calldata(max_txs, max_calldata).expr(); + let is_enabled = meta.query_advice(keccak_table.is_enabled, Rotation::cur()); + let input_rlc = meta.query_advice(keccak_table.input_rlc, Rotation::cur()); + let input_len = meta.query_advice(keccak_table.input_len, Rotation::cur()); + let output_lo = meta.query_advice(keccak_table.output.lo(), Rotation::cur()); + let output_hi = meta.query_advice(keccak_table.output.hi(), Rotation::cur()); + + // is_enabled + let q_rpi_keccak_lookup = meta.query_selector(q_rpi_keccak_lookup); + // input_rlc + let rpi_bytes_keccakrlc_cur = + meta.query_advice(rpi_bytes_keccakrlc, Rotation::cur()); + // output + let rpi_digest_lo = meta.query_advice(rpi_digest_bytes_limbs, Rotation::cur()); + let rpi_digest_hi = meta.query_advice(rpi_digest_bytes_limbs, Rotation::next()); - vec![or::expr([q_tx_table, q_tx_calldata]) * (tx_value - rpi_tx_value)] + vec![ + (q_rpi_keccak_lookup.expr() * 1.expr(), is_enabled), + ( + q_rpi_keccak_lookup.expr() * rpi_bytes_keccakrlc_cur, + input_rlc, + ), + (q_rpi_keccak_lookup.expr() * circuit_len, input_len), + (q_rpi_keccak_lookup.expr() * rpi_digest_lo, output_lo), + (q_rpi_keccak_lookup * rpi_digest_hi, output_hi), + ] }, ); @@ -365,7 +305,8 @@ impl SubCircuitConfig for PiCircuitConfig { |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), tx_id_inv, ); - let tx_value_is_zero_config = IsZeroChip::configure( + + let tx_value_is_zero_lo_config = IsZeroChip::configure( meta, |meta| { or::expr([ @@ -373,9 +314,10 @@ impl SubCircuitConfig for PiCircuitConfig { meta.query_selector(q_tx_calldata), ]) }, - |meta| meta.query_advice(tx_value, Rotation::cur()), - tx_value_inv, + |meta| meta.query_advice(tx_value.lo(), Rotation::cur()), + tx_value_lo_inv, ); + let tx_value_is_zero_config = tx_value_is_zero_lo_config.expr(); let _tx_id_diff_is_zero_config = IsZeroChip::configure( meta, |meta| meta.query_selector(q_tx_calldata), @@ -412,8 +354,8 @@ impl SubCircuitConfig for PiCircuitConfig { let tx_idx_diff_inv = meta.query_advice(tx_id_diff_inv, Rotation::cur()); let idx = meta.query_advice(index, Rotation::cur()); let idx_next = meta.query_advice(index, Rotation::next()); - let value_next = meta.query_advice(tx_value, Rotation::next()); - let value_next_inv = meta.query_advice(tx_value_inv, Rotation::next()); + let value_next_lo = meta.query_advice(tx_value.lo(), Rotation::next()); + let value_inv_next_lo = meta.query_advice(tx_value_lo_inv, Rotation::next()); let gas_cost = meta.query_advice(calldata_gas_cost, Rotation::cur()); let gas_cost_next = meta.query_advice(calldata_gas_cost, Rotation::next()); @@ -425,7 +367,7 @@ impl SubCircuitConfig for PiCircuitConfig { let is_value_zero = tx_value_is_zero_config.expr(); let is_value_nonzero = not::expr(tx_value_is_zero_config.expr()); - let is_value_next_nonzero = value_next.expr() * value_next_inv.expr(); + let is_value_next_nonzero = value_next_lo.expr() * value_inv_next_lo.expr(); let is_value_next_zero = not::expr(is_value_next_nonzero.expr()); // gas = value == 0 ? 4 : 16 @@ -499,7 +441,7 @@ impl SubCircuitConfig for PiCircuitConfig { let is_calldata_length_zero = tx_value_is_zero_config.expr(); let is_calldata_length_row = tx_tag_is_cdl_config.expr(); - let calldata_cost = meta.query_advice(tx_value, Rotation::next()); + let calldata_cost = meta.query_advice(tx_value.lo(), Rotation::next()); vec![q_tx_table * is_calldata_length_row * is_calldata_length_zero * calldata_cost] }, @@ -513,7 +455,7 @@ impl SubCircuitConfig for PiCircuitConfig { // calldata gas cost assigned in the tx table // CallDataGasCost is on the next row of CallDataLength - let calldata_cost_assigned = meta.query_advice(tx_value, Rotation::next()); + let calldata_cost_assigned = meta.query_advice(tx_value.lo(), Rotation::next()); // calldata gas cost calculated in call data let calldata_cost_calc = meta.query_advice(calldata_gas_cost, Rotation::cur()); @@ -538,24 +480,30 @@ impl SubCircuitConfig for PiCircuitConfig { Self { max_txs, max_calldata, - q_block_table, block_table, - q_tx_table, + q_digest_last, + q_bytes_last, q_tx_calldata, q_calldata_start, + q_rpi_keccak_lookup, + q_rpi_value_start, + q_tx_table, + q_digest_value_start, tx_table, + keccak_table, tx_id_inv, - tx_value_inv, + tx_value_lo_inv, tx_id_diff_inv, fixed_u16, calldata_gas_cost, is_final, - raw_public_inputs, - rpi_rlc_acc, - rand_rpi, - q_not_end, - q_end, - pi, + rpi_bytes, + rpi_bytes_keccakrlc, + rpi_value_lc, + rpi_digest_bytes, + rpi_digest_bytes_limbs, + q_rpi_byte_enable, + pi_instance, _marker: PhantomData, } } @@ -565,20 +513,68 @@ impl PiCircuitConfig { /// Return the number of rows in the circuit #[inline] fn circuit_len(&self) -> usize { - // +1 empty row in block table, +1 empty row in tx_table - BLOCK_LEN + 1 + EXTRA_LEN + 3 * (TX_LEN * self.max_txs + 1) + self.max_calldata + Self::circuit_len_by_txs_calldata(self.max_txs, self.max_calldata) + } + + /// Return the number of rows for txs and calldata + #[inline] + fn circuit_len_by_txs_calldata(txs: usize, calldata: usize) -> usize { + N_BYTES_ONE + + N_BYTES_BLOCK + + N_BYTES_EXTRA_VALUE + + Self::circuit_len_tx_id(txs) + + Self::circuit_len_tx_index(txs) + + Self::circuit_len_tx_values(txs) + + calldata + } + + #[inline] + fn circuit_len_tx_values(txs: usize) -> usize { + N_BYTES_TX * (txs) + N_BYTES_ONE + } + + #[inline] + fn circuit_len_tx_id(txs: usize) -> usize { + N_BYTES_U64 * TX_LEN * txs + N_BYTES_U64 // empty row + } + + #[inline] + fn circuit_len_tx_index(txs: usize) -> usize { + N_BYTES_U64 * TX_LEN * txs + N_BYTES_U64 // empty row } - fn assign_tx_empty_row(&self, region: &mut Region<'_, F>, offset: usize) -> Result<(), Error> { + fn assign_empty_txtable_row( + &self, + region: &mut Region<'_, F>, + offset: usize, + ) -> Result<(), Error> { region.assign_advice( - || "tx_id", - self.tx_table.tx_id, + || "tx_id_inv", + self.tx_id_inv, offset, || Value::known(F::ZERO), )?; region.assign_advice( - || "tx_id_inv", - self.tx_id_inv, + || "tx_value_lo_inv", + self.tx_value_lo_inv, + offset, + || Value::known(F::ZERO), + )?; + region.assign_advice( + || "is_final", + self.is_final, + offset, + || Value::known(F::ZERO), + )?; + region.assign_advice( + || "gas_cost", + self.calldata_gas_cost, + offset, + || Value::known(F::ZERO), + )?; + region.assign_advice( + || "tx_id", + self.tx_table.tx_id, offset, || Value::known(F::ZERO), )?; @@ -594,32 +590,62 @@ impl PiCircuitConfig { offset, || Value::known(F::ZERO), )?; - region.assign_advice( + Word::default().into_value().assign_advice( + region, || "tx_value", - self.tx_table.value, + self.tx_table.value_word, + offset, + )?; + Ok(()) + } + + fn reset_rpi_digest_row(&self, region: &mut Region<'_, F>, offset: usize) -> Result<(), Error> { + region.assign_advice( + || "rpi_digest_bytes_limbs", + self.rpi_digest_bytes_limbs, + offset, + || Value::known(F::ZERO), + )?; + + Ok(()) + } + + fn reset_rpi_bytes_row(&self, region: &mut Region<'_, F>, offset: usize) -> Result<(), Error> { + // assign q_rpi_value_start + region.assign_fixed( + || "q_rpi_value_start", + self.q_rpi_value_start, offset, || Value::known(F::ZERO), )?; + + // assign rpi bytes region.assign_advice( - || "tx_value_inv", - self.tx_value_inv, + || "rpi_bytes", + self.rpi_bytes, offset, || Value::known(F::ZERO), )?; + + // assign rpi_bytes_keccakrlc region.assign_advice( - || "is_final", - self.is_final, + || "rpi_bytes_keccakrlc", + self.rpi_bytes_keccakrlc, offset, || Value::known(F::ZERO), )?; + + // assign rpi_value_lc region.assign_advice( - || "gas_cost", - self.calldata_gas_cost, + || "rpi_value_lc", + self.rpi_value_lc, offset, || Value::known(F::ZERO), )?; + Ok(()) } + /// Assigns a tx_table row and stores the values in a vec for the /// raw_public_inputs column #[allow(clippy::too_many_arguments)] @@ -627,13 +653,16 @@ impl PiCircuitConfig { &self, region: &mut Region<'_, F>, offset: usize, - tx_id: usize, + tx_id: u64, tag: TxFieldTag, - index: usize, - tx_value: F, - raw_pi_vals: &mut [F], + index: u64, + tx_value_bytes_le: &[u8], + rpi_bytes_keccakrlc: &mut Value, + challenges: &Challenges>, + current_offset: &mut usize, + rpi_bytes: &mut [u8], + zero_cell: AssignedCell, ) -> Result<(), Error> { - let tx_id = F::from(tx_id as u64); // tx_id_inv = (tag - CallDataLength)^(-1) let tx_id_inv = if tag != TxFieldTag::CallDataLength { let x = F::from(tag as u64) - F::from(TxFieldTag::CallDataLength as u64); @@ -642,78 +671,92 @@ impl PiCircuitConfig { F::ZERO }; let tag = F::from(tag as u64); - let index = F::from(index as u64); - let tx_value = tx_value; - let tx_value_inv = tx_value.invert().unwrap_or(F::ZERO); + let tx_value = Word::new([ + from_bytes::value( + &tx_value_bytes_le[..min(N_BYTES_HALF_WORD, tx_value_bytes_le.len())], + ), + if tx_value_bytes_le.len() > N_BYTES_HALF_WORD { + from_bytes::value(&tx_value_bytes_le[N_BYTES_HALF_WORD..]) + } else { + F::ZERO + }, + ]) + .into_value(); + let tx_value_inv = tx_value.map(|t| t.map(|x| x.invert().unwrap_or(F::ZERO))); self.q_tx_table.enable(region, offset)?; // Assign vals to Tx_table - region.assign_advice( + let tx_id_assignedcell = region.assign_advice( || "tx_id", self.tx_table.tx_id, offset, - || Value::known(tx_id), + || Value::known(F::from(tx_id)), )?; region.assign_fixed(|| "tag", self.tx_table.tag, offset, || Value::known(tag))?; - region.assign_advice( + + let tx_index_assignedcell = region.assign_advice( || "index", self.tx_table.index, offset, - || Value::known(index), + || Value::known(F::from(index)), )?; - region.assign_advice( - || "tx_value", - self.tx_table.value, - offset, - || Value::known(tx_value), + + let tx_value_assignedcell = + tx_value.assign_advice(region, || "tx_value", self.tx_table.value_word, offset)?; + + // tx_id + let (_, raw_tx_id) = self.assign_raw_bytes( + region, + &tx_id.to_le_bytes(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; + region.constrain_equal(tx_id_assignedcell.cell(), raw_tx_id.lo().cell())?; + + // index + let (_, raw_tx_index) = self.assign_raw_bytes( + region, + &index.to_le_bytes(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), + )?; + region.constrain_equal(tx_index_assignedcell.cell(), raw_tx_index.lo().cell())?; + + // tx value + let (_, raw_tx_value) = self.assign_raw_bytes( + region, + tx_value_bytes_le, + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell, + )?; + region.constrain_equal(tx_value_assignedcell.lo().cell(), raw_tx_value.lo().cell())?; + region.constrain_equal(tx_value_assignedcell.hi().cell(), raw_tx_value.hi().cell())?; + + // derived inverse not belong to TxTable so do not need copy constraints region.assign_advice( || "tx_id_inv", self.tx_id_inv, offset, || Value::known(tx_id_inv), )?; + // tx_value_lo_inv have no use in tx table non-calldata row region.assign_advice( - || "tx_value_inverse", - self.tx_value_inv, + || "tx_value_lo_inv", + self.tx_value_lo_inv, offset, - || Value::known(tx_value_inv), - )?; - - // Assign vals to raw_public_inputs column - let tx_table_len = TX_LEN * self.max_txs + 1; - - let id_offset = BLOCK_LEN + 1 + EXTRA_LEN; - let index_offset = id_offset + tx_table_len; - let value_offset = index_offset + tx_table_len; - - region.assign_advice( - || "raw_pi.tx_id", - self.raw_public_inputs, - offset + id_offset, - || Value::known(tx_id), - )?; - - region.assign_advice( - || "raw_pi.tx_index", - self.raw_public_inputs, - offset + index_offset, - || Value::known(index), + || tx_value_inv.lo(), )?; - region.assign_advice( - || "raw_pi.tx_value", - self.raw_public_inputs, - offset + value_offset, - || Value::known(tx_value), - )?; - - // Add copy to vec - raw_pi_vals[offset + id_offset] = tx_id; - raw_pi_vals[offset + index_offset] = index; - raw_pi_vals[offset + value_offset] = tx_value; - Ok(()) } @@ -726,282 +769,446 @@ impl PiCircuitConfig { tx_id: usize, tx_id_next: usize, index: usize, - tx_value: F, + tx_value_byte: u8, + rpi_bytes_keccakrlc: &mut Value, + challenges: &Challenges>, + current_offset: &mut usize, + rpi_bytes: &mut [u8], is_final: bool, gas_cost: F, - raw_pi_vals: &mut [F], - ) -> Result<(), Error> { + zero_cell: AssignedCell, + ) -> Result, Error> { let tx_id = F::from(tx_id as u64); let tx_id_inv = tx_id.invert().unwrap_or(F::ZERO); let tx_id_diff = F::from(tx_id_next as u64) - tx_id; let tx_id_diff_inv = tx_id_diff.invert().unwrap_or(F::ZERO); let tag = F::from(TxFieldTag::CallData as u64); let index = F::from(index as u64); - let tx_value = tx_value; - let tx_value_inv = tx_value.invert().unwrap_or(F::ZERO); + let tx_value: Word> = Word::from(tx_value_byte).into_value(); + let tx_value_inv = tx_value.map(|t| t.map(|x| x.invert().unwrap_or(F::ZERO))); let is_final = if is_final { F::ONE } else { F::ZERO }; - // Assign vals to raw_public_inputs column - let tx_table_len = TX_LEN * self.max_txs + 1; - let calldata_offset = tx_table_len + offset; - - self.q_tx_calldata.enable(region, calldata_offset)?; + self.q_tx_calldata.enable(region, offset)?; // Assign vals to Tx_table region.assign_advice( || "tx_id", self.tx_table.tx_id, - calldata_offset, + offset, || Value::known(tx_id), )?; region.assign_advice( || "tx_id_inv", self.tx_id_inv, - calldata_offset, + offset, || Value::known(tx_id_inv), )?; - region.assign_fixed( - || "tag", - self.tx_table.tag, - calldata_offset, - || Value::known(tag), - )?; + region.assign_fixed(|| "tag", self.tx_table.tag, offset, || Value::known(tag))?; region.assign_advice( || "index", self.tx_table.index, - calldata_offset, + offset, || Value::known(index), )?; + + let tx_value_cell = + tx_value.assign_advice(region, || "tx_value", self.tx_table.value_word, offset)?; + region.assign_advice( - || "tx_value", - self.tx_table.value, - calldata_offset, - || Value::known(tx_value), - )?; - region.assign_advice( - || "tx_value_inv", - self.tx_value_inv, - calldata_offset, - || Value::known(tx_value_inv), + || "tx_value_lo_inv", + self.tx_value_lo_inv, + offset, + || tx_value_inv.lo(), )?; region.assign_advice( || "tx_id_diff_inv", self.tx_id_diff_inv, - calldata_offset, + offset, || Value::known(tx_id_diff_inv), )?; region.assign_advice( || "is_final", self.is_final, - calldata_offset, + offset, || Value::known(is_final), )?; region.assign_advice( || "gas_cost", self.calldata_gas_cost, - calldata_offset, + offset, || Value::known(gas_cost), )?; - let value_offset = BLOCK_LEN + 1 + EXTRA_LEN + 3 * tx_table_len; - - region.assign_advice( - || "raw_pi.tx_value", - self.raw_public_inputs, - offset + value_offset, - || Value::known(tx_value), + let (rpi_bytes_keccakrlc_cell, rpi_value_lc_cell) = self.assign_raw_bytes( + region, + &[tx_value_byte], + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell, )?; - // Add copy to vec - raw_pi_vals[offset + value_offset] = tx_value; + // calldata value also need to be in copy constraint + region.constrain_equal(rpi_value_lc_cell.lo().cell(), tx_value_cell.lo().cell())?; + region.constrain_equal(rpi_value_lc_cell.hi().cell(), tx_value_cell.hi().cell())?; - Ok(()) + Ok(rpi_bytes_keccakrlc_cell) + } + + /// assign raw bytes + #[allow(clippy::too_many_arguments)] + fn assign_raw_bytes( + &self, + region: &mut Region<'_, F>, + value_bytes_le: &[u8], + rpi_bytes_keccakrlc: &mut Value, + rpi_bytes: &mut [u8], + current_offset: &mut usize, + challenges: &Challenges>, + zero_cell: AssignedCell, + ) -> Result, Error> { + assert!(!value_bytes_le.is_empty()); + assert!(value_bytes_le.len() <= N_BYTES_WORD); + + let keccak_rand = challenges.keccak_input(); + + let mut rpi_value_lc_cells: Vec> = vec![]; + let mut rpi_bytes_keccakrlc_cells: Vec> = vec![]; + let start_offset = *current_offset; + + // mut current_offset will be handle only once + value_bytes_le + .iter() + .rev() + .enumerate() + .try_for_each(|(i, byte)| -> Result<(), Error> { + let offset = *current_offset; + rpi_bytes[offset] = *byte; + + // this is mutable for accumulated across value + *rpi_bytes_keccakrlc = rpi_bytes_keccakrlc + .zip(keccak_rand) + .and_then(|(acc, rand)| Value::known(acc * rand + F::from(*byte as u64))); + + // enable + self.q_rpi_byte_enable.enable(region, offset)?; + + // assign rpi bytes + region.assign_advice( + || "rpi_bytes", + self.rpi_bytes, + offset, + || Value::known(F::from(*byte as u64)), + )?; + + // assign rpi_bytes_keccakrlc + let rpi_bytes_keccakrlc_cell = region.assign_advice( + || "rpi_bytes_keccakrlc", + self.rpi_bytes_keccakrlc, + offset, + || *rpi_bytes_keccakrlc, + )?; + + if i == value_bytes_le.len() - 1 { + rpi_bytes_keccakrlc_cells.push(rpi_bytes_keccakrlc_cell); + } + + if *current_offset > 0 { + *current_offset -= 1; + } + + Ok(()) + })?; + + let value_bytes_be: Vec = value_bytes_le.iter().rev().copied().collect_vec(); + let value_bytes_chunk: Vec> = value_bytes_be + .rchunks(N_BYTES_HALF_WORD) + // chunks will go from right to left first, here we reverse the order to assure left to + // right + .rev() + .map(|x| x.to_vec()) + .collect(); + + value_bytes_chunk.iter().try_fold( + // after rchunk + start_offset, + |mut offset, bytes| -> Result { + bytes.iter().enumerate().try_fold( + Value::known(F::ZERO), + |rpi_value_lc, (i, byte)| -> Result, Error> { + // assign q_rpi_value_start when index match beginning of chunk size + region.assign_fixed( + || "q_rpi_value_start", + self.q_rpi_value_start, + offset, + || Value::known(if i == 0 { F::ONE } else { F::ZERO }), + )?; + + let rpi_value_lc = if i == 0 { + Value::known(F::ZERO) + } else { + rpi_value_lc + } + .zip(Value::known(F::from(BYTE_POW_BASE))) + .and_then(|(acc, rand)| Value::known(acc * rand + F::from(*byte as u64))); + + // assign rpi_value_lc + let rpi_value_lc_cell = region.assign_advice( + || "rpi_value_lc", + self.rpi_value_lc, + offset, + || rpi_value_lc, + )?; + + // for rpi_value_lc_cell, it accumulated per N_BYTES_HALF_WORD chunk size, + // and the remains + if i == bytes.len() - 1 { + rpi_value_lc_cells.push(rpi_value_lc_cell); + } + + offset = offset.saturating_sub(1); + + Ok(rpi_value_lc) + }, + )?; + Ok(offset) + }, + )?; + + assert!(rpi_value_lc_cells.len() <= 2); // at most hi, lo 2 cells + rpi_value_lc_cells.reverse(); // reverse to lo, hi order + assert!(rpi_bytes_keccakrlc_cells.len() == 1); // keccak rlc only 1 cell + + Ok(( + rpi_bytes_keccakrlc_cells[0].clone(), + Word::new( + (0..2) // padding rpi_value_lc_cells to 2 limbs if less then 2 + .map(|i| rpi_value_lc_cells.get(i).unwrap_or(&zero_cell).clone()) + .collect_vec() + .try_into() + .unwrap(), + ), + )) } /// Assigns the values for block table in the block_table column - /// and in the raw_public_inputs column. A copy is also stored in - /// a vector for computing RLC(raw_public_inputs) + /// and rpi_bytes columns. Copy constraints will be enable + /// to assure block_table value cell equal with respective rpi_byte_rlc cell + #[allow(clippy::too_many_arguments)] fn assign_block_table( &self, region: &mut Region<'_, F>, + block_table_offset: &mut usize, block_values: BlockValues, - randomness: F, - raw_pi_vals: &mut [F], - ) -> Result, Error> { - let mut offset = 0; - for i in 0..BLOCK_LEN + 1 { - self.q_block_table.enable(region, offset + i)?; - } - - // zero row - region.assign_advice( - || "zero", - self.block_table.value, - offset, - || Value::known(F::ZERO), - )?; - region.assign_advice( - || "zero", - self.raw_public_inputs, - offset, - || Value::known(F::ZERO), - )?; - raw_pi_vals[offset] = F::ZERO; - offset += 1; + rpi_bytes_keccakrlc: &mut Value, + challenges: &Challenges>, + current_offset: &mut usize, + rpi_bytes: &mut [u8], + zero_cell: AssignedCell, + ) -> Result<(), Error> { + let mut block_copy_cells = vec![]; // coinbase - let coinbase = block_values.coinbase.to_scalar().unwrap(); - region.assign_advice( - || "coinbase", - self.block_table.value, - offset, - || Value::known(coinbase), - )?; - region.assign_advice( - || "coinbase", - self.raw_public_inputs, - offset, - || Value::known(coinbase), + let block_value = Word::from(block_values.coinbase) + .into_value() + .assign_advice( + region, + || "coinbase", + self.block_table.value_word, + *block_table_offset, + )?; + let (_, word) = self.assign_raw_bytes( + region, + &block_values + .coinbase + .to_fixed_bytes() + .iter() + .rev() + .copied() + .collect_vec(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = coinbase; - offset += 1; + block_copy_cells.push((block_value, word)); + *block_table_offset += 1; // gas_limit - let gas_limit = F::from(block_values.gas_limit); - region.assign_advice( - || "gas_limit", - self.block_table.value, - offset, - || Value::known(gas_limit), - )?; - region.assign_advice( - || "gas_limit", - self.raw_public_inputs, - offset, - || Value::known(gas_limit), + let block_value = Word::from(block_values.gas_limit) + .into_value() + .assign_advice( + region, + || "gas_limit", + self.block_table.value_word, + *block_table_offset, + )?; + let (_, word) = self.assign_raw_bytes( + region, + &block_values.gas_limit.to_le_bytes(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = gas_limit; - offset += 1; + block_copy_cells.push((block_value, word)); + *block_table_offset += 1; // number - let number = F::from(block_values.number); - region.assign_advice( + let block_value = Word::from(block_values.number).into_value().assign_advice( + region, || "number", - self.block_table.value, - offset, - || Value::known(number), + self.block_table.value_word, + *block_table_offset, )?; - region.assign_advice( - || "number", - self.raw_public_inputs, - offset, - || Value::known(number), + let (_, word) = self.assign_raw_bytes( + region, + &block_values.number.to_le_bytes(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = number; - offset += 1; + block_copy_cells.push((block_value, word)); + *block_table_offset += 1; // timestamp - let timestamp = F::from(block_values.timestamp); - region.assign_advice( - || "timestamp", - self.block_table.value, - offset, - || Value::known(timestamp), - )?; - region.assign_advice( - || "timestamp", - self.raw_public_inputs, - offset, - || Value::known(timestamp), + let block_value = Word::from(block_values.timestamp) + .into_value() + .assign_advice( + region, + || "timestamp", + self.block_table.value_word, + *block_table_offset, + )?; + let (_, word) = self.assign_raw_bytes( + region, + &block_values.timestamp.to_le_bytes(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = timestamp; - offset += 1; + block_copy_cells.push((block_value, word)); + *block_table_offset += 1; // difficulty - let difficulty = rlc(block_values.difficulty.to_le_bytes(), randomness); - region.assign_advice( - || "difficulty", - self.block_table.value, - offset, - || Value::known(difficulty), - )?; - region.assign_advice( - || "difficulty", - self.raw_public_inputs, - offset, - || Value::known(difficulty), + let block_value = Word::from(block_values.difficulty) + .into_value() + .assign_advice( + region, + || "difficulty", + self.block_table.value_word, + *block_table_offset, + )?; + let (_, word) = self.assign_raw_bytes( + region, + &block_values.difficulty.to_le_bytes(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = difficulty; - offset += 1; + block_copy_cells.push((block_value, word)); + *block_table_offset += 1; // base_fee - let base_fee = rlc(block_values.base_fee.to_le_bytes(), randomness); - region.assign_advice( - || "base_fee", - self.block_table.value, - offset, - || Value::known(base_fee), - )?; - region.assign_advice( - || "base_fee", - self.raw_public_inputs, - offset, - || Value::known(base_fee), + let block_value = Word::from(block_values.base_fee) + .into_value() + .assign_advice( + region, + || "base_fee", + self.block_table.value_word, + *block_table_offset, + )?; + let (_, word) = self.assign_raw_bytes( + region, + &block_values.base_fee.to_le_bytes(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = base_fee; - offset += 1; + block_copy_cells.push((block_value, word)); + *block_table_offset += 1; // chain_id - let chain_id = F::from(block_values.chain_id); - region.assign_advice( - || "chain_id", - self.block_table.value, - offset, - || Value::known(chain_id), - )?; - let chain_id_cell = region.assign_advice( - || "chain_id", - self.raw_public_inputs, - offset, - || Value::known(chain_id), + let block_value = Word::from(block_values.chain_id) + .into_value() + .assign_advice( + region, + || "chain_id", + self.block_table.value_word, + *block_table_offset, + )?; + let (_, word) = self.assign_raw_bytes( + region, + &block_values.chain_id.to_le_bytes(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = chain_id; - offset += 1; + block_copy_cells.push((block_value, word)); + *block_table_offset += 1; for prev_hash in block_values.history_hashes { - let prev_hash = rlc(prev_hash.to_fixed_bytes(), randomness); - region.assign_advice( + let block_value = Word::from(prev_hash).into_value().assign_advice( + region, || "prev_hash", - self.block_table.value, - offset, - || Value::known(prev_hash), + self.block_table.value_word, + *block_table_offset, )?; - region.assign_advice( - || "prev_hash", - self.raw_public_inputs, - offset, - || Value::known(prev_hash), + let (_, word) = self.assign_raw_bytes( + region, + &prev_hash + .to_fixed_bytes() + .iter() + .rev() + .copied() + .collect_vec(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = prev_hash; - offset += 1; + block_copy_cells.push((block_value, word)); + *block_table_offset += 1; } - Ok(chain_id_cell) + block_copy_cells.iter().try_for_each(|(left, right)| { + region.constrain_equal(left.lo().cell(), right.lo().cell())?; + region.constrain_equal(left.hi().cell(), right.hi().cell())?; + Ok::<(), Error>(()) + })?; + + Ok(()) } /// Assigns the extra fields (not in block or tx tables): /// - state root /// - previous block state root - /// to the raw_public_inputs column and stores a copy in a - /// vector for computing RLC(raw_public_inputs). + /// to the rpi_byte column + #[allow(clippy::too_many_arguments)] fn assign_extra_fields( &self, region: &mut Region<'_, F>, extra: ExtraValues, - randomness: F, - raw_pi_vals: &mut [F], - ) -> Result<[AssignedCell; 2], Error> { - let mut offset = BLOCK_LEN + 1; + rpi_bytes_keccakrlc: &mut Value, + challenges: &Challenges>, + current_offset: &mut usize, + rpi_bytes: &mut [u8], + zero_cell: AssignedCell, + ) -> Result<(), Error> { // block hash // let block_hash = rlc(extra.block_hash.to_fixed_bytes(), randomness); // region.assign_advice( @@ -1014,88 +1221,61 @@ impl PiCircuitConfig { // offset += 1; // block state root - let state_root = rlc(extra.state_root.to_fixed_bytes(), randomness); - let state_root_cell = region.assign_advice( - || "state.root", - self.raw_public_inputs, - offset, - || Value::known(state_root), + self.assign_raw_bytes( + region, + &extra + .state_root + .to_fixed_bytes() + .iter() + .copied() + .rev() + .collect_vec(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell.clone(), )?; - raw_pi_vals[offset] = state_root; - offset += 1; // previous block state root - let prev_state_root = rlc(extra.prev_state_root.to_fixed_bytes(), randomness); - let prev_state_root_cell = region.assign_advice( - || "parent_block.hash", - self.raw_public_inputs, - offset, - || Value::known(prev_state_root), + self.assign_raw_bytes( + region, + &extra + .prev_state_root + .to_fixed_bytes() + .iter() + .copied() + .rev() + .collect_vec(), + rpi_bytes_keccakrlc, + rpi_bytes, + current_offset, + challenges, + zero_cell, )?; - raw_pi_vals[offset] = prev_state_root; - Ok([state_root_cell, prev_state_root_cell]) + + Ok(()) } - /// Assign `rpi_rlc_acc` and `rand_rpi` columns - #[allow(clippy::type_complexity)] - fn assign_rlc_pi( + /// Assign digest word + fn assign_rpi_digest_word( &self, region: &mut Region<'_, F>, - rand_rpi: F, - raw_pi_vals: Vec, - ) -> Result<(AssignedCell, AssignedCell), Error> { - let circuit_len = self.circuit_len(); - assert_eq!(circuit_len, raw_pi_vals.len()); - - // Last row - let offset = circuit_len - 1; - let mut rpi_rlc_acc = raw_pi_vals[offset]; - region.assign_advice( - || "rpi_rlc_acc", - self.rpi_rlc_acc, - offset, - || Value::known(rpi_rlc_acc), - )?; - region.assign_advice( - || "rand_rpi", - self.rand_rpi, - offset, - || Value::known(rand_rpi), - )?; - self.q_end.enable(region, offset)?; - - // Next rows - for offset in (1..circuit_len - 1).rev() { - rpi_rlc_acc *= rand_rpi; - rpi_rlc_acc += raw_pi_vals[offset]; - region.assign_advice( - || "rpi_rlc_acc", - self.rpi_rlc_acc, - offset, - || Value::known(rpi_rlc_acc), - )?; - region.assign_advice( - || "rand_rpi", - self.rand_rpi, - offset, - || Value::known(rand_rpi), - )?; - self.q_not_end.enable(region, offset)?; - } - - // First row - rpi_rlc_acc *= rand_rpi; - rpi_rlc_acc += raw_pi_vals[0]; - let rpi_rlc = region.assign_advice( - || "rpi_rlc_acc", - self.rpi_rlc_acc, + digest_word: Word, + ) -> Result>, Error> { + let lo_assigned_cell = region.assign_advice( + || "rpi_digest_bytes_limbs_lo", + self.rpi_digest_bytes_limbs, 0, - || Value::known(rpi_rlc_acc), + || digest_word.into_value().lo(), )?; - let rpi_rand = - region.assign_advice(|| "rand_rpi", self.rand_rpi, 0, || Value::known(rand_rpi))?; - self.q_not_end.enable(region, 0)?; - Ok((rpi_rand, rpi_rlc)) + let hi_assigned_cell = region.assign_advice( + || "rpi_digest_bytes_limbs_hi", + self.rpi_digest_bytes_limbs, + 1, + || digest_word.into_value().hi(), + )?; + Ok(Word::new([lo_assigned_cell, hi_assigned_cell])) } } @@ -1104,29 +1284,19 @@ impl PiCircuitConfig { pub struct PiCircuit { max_txs: usize, max_calldata: usize, - /// Randomness for RLC encdoing - pub randomness: F, - /// Randomness for PI encoding - pub rand_rpi: F, /// PublicInputs data known by the verifier pub public_data: PublicData, + _marker: PhantomData, } impl PiCircuit { /// Creates a new PiCircuit - pub fn new( - max_txs: usize, - max_calldata: usize, - randomness: impl Into, - rand_rpi: impl Into, - public_data: PublicData, - ) -> Self { + pub fn new(max_txs: usize, max_calldata: usize, public_data: PublicData) -> Self { Self { max_txs, max_calldata, - randomness: randomness.into(), - rand_rpi: rand_rpi.into(), public_data, + _marker: PhantomData, } } } @@ -1135,55 +1305,26 @@ impl SubCircuit for PiCircuit { type Config = PiCircuitConfig; fn unusable_rows() -> usize { - // Column raw_public_inputs is queried at 4 distinct rotations at - // - Rotation::cur() - // - Rotation(BLOCK_LEN + 1 + EXTRA_LEN) - // - Rotation(BLOCK_LEN + 1 + EXTRA_LEN + max_txs * TX_LEN + 1) - // - Rotation(BLOCK_LEN + 1 + EXTRA_LEN + 2 * (max_txs * TX_LEN + 1)) - // so returns 7 unusable rows. - 7 + // No column queried at more than 3 distinct rotations, so returns 6 as + // minimum unusable rows. + 6 } fn new_from_block(block: &witness::Block) -> Self { - let public_data = PublicData { - chain_id: block.context.chain_id, - history_hashes: block.context.history_hashes.clone(), - transactions: block.eth_block.transactions.clone(), - state_root: block.eth_block.state_root, - prev_state_root: H256::from_uint(&block.prev_state_root), - block_constants: BlockConstants { - coinbase: block.context.coinbase, - timestamp: block.context.timestamp, - number: block.context.number.as_u64().into(), - difficulty: block.context.difficulty, - gas_limit: block.context.gas_limit.into(), - base_fee: block.context.base_fee, - }, - }; - let rand_rpi = gen_rand_rpi::( - block.circuits_params.max_txs, - block.circuits_params.max_calldata, - &public_data, - block.randomness, - ); + let public_data = public_data_convert(block); PiCircuit::new( block.circuits_params.max_txs, block.circuits_params.max_calldata, - block.randomness, - rand_rpi, public_data, ) } /// Return the minimum number of rows required to prove the block fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { - let row_num = |tx_num, calldata_len| { - BLOCK_LEN + 1 + EXTRA_LEN + 3 * (TX_LEN * tx_num + 1) + calldata_len - }; let calldata_len = block.txs.iter().map(|tx| tx.call_data.len()).sum(); ( - row_num(block.txs.len(), calldata_len), - row_num( + Self::Config::circuit_len_by_txs_calldata(block.txs.len(), calldata_len), + Self::Config::circuit_len_by_txs_calldata( block.circuits_params.max_txs, block.circuits_params.max_calldata, ), @@ -1192,50 +1333,18 @@ impl SubCircuit for PiCircuit { /// Compute the public inputs for this circuit. fn instance(&self) -> Vec> { - let rlc_rpi_col = raw_public_inputs_col::( - self.max_txs, - self.max_calldata, - &self.public_data, - self.randomness, - ); - assert_eq!( - rlc_rpi_col.len(), - BLOCK_LEN + 1 + EXTRA_LEN + 3 * (TX_LEN * self.max_txs + 1) + self.max_calldata - ); - - // Computation of raw_pulic_inputs - let rlc_rpi = rlc_rpi_col - .iter() - .rev() - .fold(F::ZERO, |acc, val| acc * self.rand_rpi + val); - - // let block_hash = public_data - // .eth_block - // .hash - // .unwrap_or_else(H256::zero) - // .to_fixed_bytes(); - let public_inputs = vec![ - self.rand_rpi, - rlc_rpi, - F::from(self.public_data.chain_id.as_u64()), - rlc( - self.public_data.state_root.to_fixed_bytes(), - self.randomness, - ), - rlc( - self.public_data.prev_state_root.to_fixed_bytes(), - self.randomness, - ), - ]; + let rpi_digest_byte_field = self + .public_data + .get_rpi_digest_word(self.max_txs, self.max_calldata); - vec![public_inputs] + vec![vec![rpi_digest_byte_field.lo(), rpi_digest_byte_field.hi()]] } /// Make the assignments to the PiCircuit fn synthesize_sub( &self, config: &Self::Config, - _challenges: &Challenges>, + challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { layouter.assign_region( @@ -1253,114 +1362,205 @@ impl SubCircuit for PiCircuit { Ok(()) }, )?; - let pi_cells = layouter.assign_region( + let digest_word_assigned = layouter.assign_region( || "region 0", |mut region| { // Annotate columns config.tx_table.annotate_columns_in_region(&mut region); config.block_table.annotate_columns_in_region(&mut region); - - region.name_column(|| "raw_public_inputs", config.raw_public_inputs); + config.keccak_table.annotate_columns_in_region(&mut region); + + region.name_column(|| "q_rpi_value_start", config.q_rpi_value_start); + region.name_column(|| "rpi_bytes", config.rpi_bytes); + region.name_column(|| "rpi_bytes_keccakrlc", config.rpi_bytes_keccakrlc); + region.name_column(|| "rpi_value_lc", config.rpi_value_lc); + // region.name_column(|| "is_value_rlc", config.is_value_rlc); + region.name_column(|| "q_digest_value_start", config.q_digest_value_start); + region.name_column(|| "rpi_digest_bytes", config.rpi_digest_bytes); + region.name_column(|| "rpi_digest_bytes_lc", config.rpi_digest_bytes_limbs); region.name_column(|| "tx_id_inv", config.tx_id_inv); - region.name_column(|| "tx_value_inv", config.tx_value_inv); + region.name_column(|| "tx_value_lo_inv", config.tx_value_lo_inv); region.name_column(|| "tx_id_diff_inv", config.tx_id_diff_inv); region.name_column(|| "fixed_u16", config.fixed_u16); region.name_column(|| "calldata_gas_cost", config.calldata_gas_cost); region.name_column(|| "is_final", config.is_final); - region.name_column(|| "rpi_rlc_acc", config.rpi_rlc_acc); - region.name_column(|| "rand_rpi", config.rand_rpi); - - region.name_column(|| "Public_Inputs", config.pi); + region.name_column(|| "Public_Inputs", config.pi_instance); let circuit_len = config.circuit_len(); - let mut raw_pi_vals = vec![F::ZERO; circuit_len]; + let mut rpi_bytes = vec![0u8; circuit_len]; + + let mut rpi_bytes_keccakrlc = Value::known(F::ZERO); + + // traverse reversely of the region + let mut current_offset: usize = circuit_len - 1; + let start_offset = current_offset; + + config.q_digest_last.enable(&mut region, N_BYTES_WORD - 1)?; // digest is 32 bytes + config.q_bytes_last.enable(&mut region, start_offset)?; + + // assign last + 1 to 0 to as wordaround to skip CellNotAssigned Error from + // Mock_prover + config.reset_rpi_bytes_row(&mut region, start_offset + 1)?; + config.reset_rpi_digest_row(&mut region, N_BYTES_WORD)?; // Assign block table let block_values = self.public_data.get_block_table_values(); - let chain_id = config.assign_block_table( + let mut block_table_offset = 0; + + // assign empty row in block table + let zero_word = Word::default().into_value().assign_advice( &mut region, + || "zero", + config.block_table.value_word, + block_table_offset, + )?; + let zero_cell = zero_word.hi(); + let (_, _) = config.assign_raw_bytes( + &mut region, + &0u8.to_le_bytes(), + &mut rpi_bytes_keccakrlc, + &mut rpi_bytes, + &mut current_offset, + challenges, + zero_cell.clone(), + )?; + block_table_offset += 1; + config.assign_block_table( + &mut region, + &mut block_table_offset, block_values, - self.randomness, - &mut raw_pi_vals, + &mut rpi_bytes_keccakrlc, + challenges, + &mut current_offset, + &mut rpi_bytes, + zero_cell.clone(), )?; + assert_eq!(start_offset - current_offset, N_BYTES_ONE + N_BYTES_BLOCK); // Assign extra fields let extra_vals = self.public_data.get_extra_values(); - let [state_root, prev_state_root] = config.assign_extra_fields( + config.assign_extra_fields( &mut region, extra_vals, - self.randomness, - &mut raw_pi_vals, + &mut rpi_bytes_keccakrlc, + challenges, + &mut current_offset, + &mut rpi_bytes, + zero_cell, )?; + assert_eq!( + start_offset - current_offset, + N_BYTES_ONE + N_BYTES_BLOCK + N_BYTES_EXTRA_VALUE + ); - let mut offset = 0; + let mut tx_table_offset = 0; // Assign Tx table let txs = self.public_data.get_tx_table_values(); assert!(txs.len() <= config.max_txs); let tx_default = TxValues::default(); // Add empty row + // assign first tx_value empty row, and to obtain zero cell via hi() part. + // we use hi() part to copy-constrains other tx_table value `hi` cells. + let zero_cell = Word::default() + .into_value() + .assign_advice(&mut region, || "tx_value", config.tx_table.value_word, 0)? + .hi(); config.assign_tx_row( &mut region, - offset, - 0, + tx_table_offset, + 0u64, TxFieldTag::Null, - 0, - F::ZERO, - &mut raw_pi_vals, + 0u64, + &[0u8; 1], + &mut rpi_bytes_keccakrlc, + challenges, + &mut current_offset, + &mut rpi_bytes, + zero_cell.clone(), )?; - offset += 1; - - for i in 0..config.max_txs { - let tx = if i < txs.len() { &txs[i] } else { &tx_default }; - - for (tag, value) in &[ - (TxFieldTag::Nonce, F::from(tx.nonce)), - (TxFieldTag::Gas, F::from(tx.gas)), - ( - TxFieldTag::GasPrice, - rlc(tx.gas_price.to_le_bytes(), self.randomness), - ), - ( - TxFieldTag::CallerAddress, - tx.from_addr.to_scalar().expect("tx.from too big"), - ), - ( - TxFieldTag::CalleeAddress, - tx.to_addr.to_scalar().expect("tx.to too big"), - ), - (TxFieldTag::IsCreate, F::from(tx.is_create as u64)), - ( - TxFieldTag::Value, - rlc(tx.value.to_le_bytes(), self.randomness), - ), - (TxFieldTag::CallDataLength, F::from(tx.call_data_len)), - (TxFieldTag::CallDataGasCost, F::from(tx.call_data_gas_cost)), - ( - TxFieldTag::TxSignHash, - rlc(tx.tx_sign_hash, self.randomness), - ), - ] { - config.assign_tx_row( - &mut region, - offset, - i + 1, - *tag, - 0, - *value, - &mut raw_pi_vals, - )?; - offset += 1; - } - } + tx_table_offset += 1; + + iter::empty() + .chain(&txs) + .chain((0..(config.max_txs - txs.len())).map(|_| &tx_default)) + .enumerate() + .try_for_each(|(i, tx)| -> Result<(), Error> { + for (tag, value_bytes) in &[ + (TxFieldTag::Nonce, tx.nonce.to_le_bytes().to_vec()), + (TxFieldTag::Gas, tx.gas_limit.to_le_bytes().to_vec()), + (TxFieldTag::GasPrice, tx.gas_price.to_le_bytes().to_vec()), + ( + TxFieldTag::CallerAddress, + tx.from_addr + .as_fixed_bytes() + .iter() + .copied() + .rev() + .collect_vec(), + ), + ( + TxFieldTag::CalleeAddress, + tx.to_addr + .as_fixed_bytes() + .iter() + .copied() + .rev() + .collect_vec(), + ), + (TxFieldTag::IsCreate, tx.is_create.to_le_bytes().to_vec()), + (TxFieldTag::Value, tx.value.to_le_bytes().to_vec()), + ( + TxFieldTag::CallDataLength, + tx.call_data_len.to_le_bytes().to_vec(), + ), + ( + TxFieldTag::CallDataGasCost, + tx.call_data_gas_cost.to_le_bytes().to_vec(), + ), + // TODO witness tx.tx_sign_hash + (TxFieldTag::TxSignHash, tx.tx_sign_hash.to_vec()), + ] { + let i: u64 = i.try_into().unwrap(); + // assign tx field + config.assign_tx_row( + &mut region, + tx_table_offset, + i + 1, + *tag, + 0, + value_bytes, + &mut rpi_bytes_keccakrlc, + challenges, + &mut current_offset, + &mut rpi_bytes, + zero_cell.clone(), + )?; + tx_table_offset += 1; + } + Ok(()) + })?; + assert_eq!( + start_offset - current_offset, + N_BYTES_ONE + + N_BYTES_BLOCK + + N_BYTES_EXTRA_VALUE + + Self::Config::circuit_len_tx_id(config.max_txs) + + Self::Config::circuit_len_tx_index(config.max_txs) + + Self::Config::circuit_len_tx_values(config.max_txs) + ); + // Tx Table CallData let mut calldata_count = 0; - config.q_calldata_start.enable(&mut region, offset)?; - // the call data bytes assignment starts at offset 0 - offset = 0; + config + .q_calldata_start + .enable(&mut region, tx_table_offset)?; + + let mut call_data_offset = TX_LEN * self.max_txs + EMPTY_TX_ROW_COUNT; + let txs = self.public_data.txs(); for (i, tx) in self.public_data.txs().iter().enumerate() { let call_data_length = tx.call_data.0.len(); @@ -1389,185 +1589,68 @@ impl SubCircuit for PiCircuit { config.assign_tx_calldata_row( &mut region, - offset, + call_data_offset, i + 1, tx_id_next, index, - F::from(*byte as u64), + *byte, + &mut rpi_bytes_keccakrlc, + challenges, + &mut current_offset, + &mut rpi_bytes, is_final, gas_cost, - &mut raw_pi_vals, + zero_cell.clone(), )?; - offset += 1; + call_data_offset += 1; calldata_count += 1; } } + for _ in calldata_count..config.max_calldata { config.assign_tx_calldata_row( &mut region, - offset, + call_data_offset, 0, // tx_id 0, 0, - F::ZERO, + 0u8, + &mut rpi_bytes_keccakrlc, + challenges, + &mut current_offset, + &mut rpi_bytes, false, F::ZERO, - &mut raw_pi_vals, + zero_cell.clone(), )?; - offset += 1; + call_data_offset += 1; } - // NOTE: we add this empty row so as to pass mock prover's check - // otherwise it will emit CellNotAssigned Error - let tx_table_len = TX_LEN * self.max_txs + 1; - config.assign_tx_empty_row(&mut region, tx_table_len + offset)?; - - // rpi_rlc and rand_rpi cols - let (rpi_rand, rpi_rlc) = - config.assign_rlc_pi(&mut region, self.rand_rpi, raw_pi_vals)?; - - Ok(vec![ - rpi_rand, - rpi_rlc, - chain_id, - state_root, - prev_state_root, - ]) - }, - )?; + assert_eq!(current_offset, 0); - // Constrain raw_public_input cells to public inputs - for (i, pi_cell) in pi_cells.iter().enumerate() { - layouter.constrain_instance(pi_cell.cell(), config.pi, i)?; - } + // assign keccak digest + let digest_word = self + .public_data + .get_rpi_digest_word::(config.max_txs, config.max_calldata); - Ok(()) - } -} + let digest_word_assigned = + config.assign_rpi_digest_word(&mut region, digest_word)?; -/// Compute the raw_public_inputs column from the verifier's perspective. -fn raw_public_inputs_col( - max_txs: usize, - max_calldata: usize, - public_data: &PublicData, - randomness: F, // For RLC encoding -) -> Vec { - let block = public_data.get_block_table_values(); - let extra = public_data.get_extra_values(); - let txs = public_data.get_tx_table_values(); - - let mut offset = 0; - let mut result = - vec![F::ZERO; BLOCK_LEN + 1 + EXTRA_LEN + 3 * (TX_LEN * max_txs + 1) + max_calldata]; - - // Insert Block Values - // zero row - result[offset] = F::ZERO; - offset += 1; - // coinbase - result[offset] = block.coinbase.to_scalar().unwrap(); - offset += 1; - // gas_limit - result[offset] = F::from(block.gas_limit); - offset += 1; - // number - result[offset] = F::from(block.number); - offset += 1; - // timestamp - result[offset] = F::from(block.timestamp); - offset += 1; - // difficulty - result[offset] = rlc(block.difficulty.to_le_bytes(), randomness); - offset += 1; - // base_fee - result[offset] = rlc(block.base_fee.to_le_bytes(), randomness); - offset += 1; - // chain_id - result[offset] = F::from(block.chain_id); - offset += 1; - // Previous block hashes - for prev_hash in block.history_hashes { - result[offset] = rlc(prev_hash.to_fixed_bytes(), randomness); - offset += 1; - } + // lookup assignment + // also assign empty to last of TxTable + config.assign_empty_txtable_row(&mut region, call_data_offset)?; - // Insert Extra Values - // block Root - result[BLOCK_LEN + 1] = rlc(extra.state_root.to_fixed_bytes(), randomness); - // parent block hash - result[BLOCK_LEN + 2] = rlc(extra.prev_state_root.to_fixed_bytes(), randomness); - - // Insert Tx table - offset = 0; - assert!(txs.len() <= max_txs); - let tx_default = TxValues::default(); - - let tx_table_len = TX_LEN * max_txs + 1; - - let id_offset = BLOCK_LEN + 1 + EXTRA_LEN; - let index_offset = id_offset + tx_table_len; - let value_offset = index_offset + tx_table_len; - - // Insert zero row - result[id_offset + offset] = F::ZERO; - result[index_offset + offset] = F::ZERO; - result[value_offset + offset] = F::ZERO; - - offset += 1; - - for i in 0..max_txs { - let tx = if i < txs.len() { &txs[i] } else { &tx_default }; - - for val in &[ - F::from(tx.nonce), - F::from(tx.gas), - rlc(tx.gas_price.to_le_bytes(), randomness), - tx.from_addr.to_scalar().expect("tx.from too big"), - tx.to_addr.to_scalar().expect("tx.to too big"), - F::from(tx.is_create as u64), - rlc(tx.value.to_le_bytes(), randomness), - F::from(tx.call_data_len), - F::from(tx.call_data_gas_cost), - rlc(tx.tx_sign_hash, randomness), - ] { - result[id_offset + offset] = F::from((i + 1) as u64); - result[index_offset + offset] = F::ZERO; - result[value_offset + offset] = *val; - - offset += 1; - } - } - // Tx Table CallData - let mut calldata_count = 0; - for (_i, tx) in public_data.txs().iter().enumerate() { - for (_index, byte) in tx.call_data.0.iter().enumerate() { - assert!(calldata_count < max_calldata); - result[value_offset + offset] = F::from(*byte as u64); - offset += 1; - calldata_count += 1; - } - } - for _ in calldata_count..max_calldata { - result[value_offset + offset] = F::ZERO; - offset += 1; - } + // keccak lookup occur on offset 0 + config.q_rpi_keccak_lookup.enable(&mut region, 0)?; - result -} + Ok(digest_word_assigned) + }, + )?; -/// Computes `rand_rpi` - a commitment to the `raw_public_inputs_col` values. -pub fn gen_rand_rpi( - max_txs: usize, - max_calldata: usize, - public_data: &PublicData, - randomness: F, -) -> F { - let rlc_rpi_col = raw_public_inputs_col::(max_txs, max_calldata, public_data, randomness); - let mut keccak = Keccak::default(); - for value in rlc_rpi_col.iter() { - let mut tmp = value.to_repr(); - tmp.reverse(); - keccak.update(&tmp); + // Constrain raw_public_input cells to public inputs + + layouter.constrain_instance(digest_word_assigned.lo().cell(), config.pi_instance, 0)?; + layouter.constrain_instance(digest_word_assigned.hi().cell(), config.pi_instance, 1)?; + + Ok(()) } - let rand_rpi = Word::from(keccak.digest().as_slice()) % F::MODULUS; - rand_rpi.to_scalar().expect("rand_rpi.to_scalar") } diff --git a/zkevm-circuits/src/pi_circuit/dev.rs b/zkevm-circuits/src/pi_circuit/dev.rs index 2ca0345ecbe..6a8033a07ea 100644 --- a/zkevm-circuits/src/pi_circuit/dev.rs +++ b/zkevm-circuits/src/pi_circuit/dev.rs @@ -29,6 +29,9 @@ impl Circuit for PiCircuit { fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { let block_table = BlockTable::construct(meta); let tx_table = TxTable::construct(meta); + let keccak_table = KeccakTable::construct(meta); + let challenges = Challenges::construct(meta); + let challenge_exprs = challenges.exprs(meta); ( PiCircuitConfig::new( meta, @@ -37,9 +40,11 @@ impl Circuit for PiCircuit { max_calldata: params.max_calldata, block_table, tx_table, + keccak_table, + challenges: challenge_exprs, }, ), - Challenges::construct(meta), + challenges, ) } @@ -53,6 +58,14 @@ impl Circuit for PiCircuit { mut layouter: impl Layouter, ) -> Result<(), Error> { let challenges = challenges.values(&mut layouter); + // assign keccak table + let rpi_bytes = self + .public_data + .get_pi_bytes(config.max_txs, config.max_calldata); + config + .keccak_table + .dev_load(&mut layouter, vec![&rpi_bytes], &challenges)?; + self.synthesize_sub(&config, &challenges, &mut layouter) } } diff --git a/zkevm-circuits/src/pi_circuit/param.rs b/zkevm-circuits/src/pi_circuit/param.rs index 8c05677fdd2..a6e263ac16b 100644 --- a/zkevm-circuits/src/pi_circuit/param.rs +++ b/zkevm-circuits/src/pi_circuit/param.rs @@ -1,5 +1,13 @@ +use halo2_proofs::circuit::AssignedCell; + +use crate::util::word::Word; + /// Fixed by the spec pub(super) const BLOCK_LEN: usize = 7 + 256; pub(super) const EXTRA_LEN: usize = 2; -pub(super) const ZERO_BYTE_GAS_COST: u64 = 4; -pub(super) const NONZERO_BYTE_GAS_COST: u64 = 16; +pub(super) const BYTE_POW_BASE: u64 = 256; +pub(super) const EMPTY_TX_ROW_COUNT: usize = 1; +pub(super) const EMPTY_BLOCK_ROW_COUNT: usize = 1; +pub(super) const N_BYTES_ONE: usize = 1; + +pub(super) type AssignedByteCells = (AssignedCell, Word>); diff --git a/zkevm-circuits/src/pi_circuit/test.rs b/zkevm-circuits/src/pi_circuit/test.rs index 5b6db6499bd..88bbab75a02 100644 --- a/zkevm-circuits/src/pi_circuit/test.rs +++ b/zkevm-circuits/src/pi_circuit/test.rs @@ -1,13 +1,19 @@ #![allow(unused_imports)] -use super::{dev::*, *}; -use crate::util::unusable_rows; +use std::collections::HashMap; + +use crate::{pi_circuit::dev::PiCircuitParams, util::unusable_rows, witness::block_convert}; + +use super::*; +use bus_mapping::{circuit_input_builder::CircuitsParams, mock::BlockData}; +use eth_types::{bytecode, geth_types::GethData, Word, H160}; +use ethers_signers::{LocalWallet, Signer}; use halo2_proofs::{ dev::{MockProver, VerifyFailure}, halo2curves::bn256::Fr, }; -use mock::{CORRECT_MOCK_TXS, MOCK_CHAIN_ID}; +use mock::{eth, TestContext, CORRECT_MOCK_TXS, MOCK_ACCOUNTS, MOCK_CHAIN_ID}; use rand::SeedableRng; -use rand_chacha::ChaCha20Rng; +use rand_chacha::ChaChaRng; #[test] fn pi_circuit_unusable_rows() { @@ -26,13 +32,11 @@ fn run( max_calldata: usize, public_data: PublicData, ) -> Result<(), Vec> { - let mut rng = ChaCha20Rng::seed_from_u64(2); - let randomness = F::random(&mut rng); - let rand_rpi = F::random(&mut rng); let mut public_data = public_data; public_data.chain_id = *MOCK_CHAIN_ID; - let circuit = PiCircuit::::new(max_txs, max_calldata, randomness, rand_rpi, public_data); + let circuit = PiCircuit::::new(max_txs, max_calldata, public_data); + let public_inputs = circuit.instance(); let prover = match MockProver::run(k, &circuit, public_inputs) { @@ -59,6 +63,11 @@ fn test_simple_pi() { let mut public_data = PublicData::default(); + public_data.block_constants.coinbase = H160( + vec![1u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + .try_into() + .unwrap(), + ); let n_tx = 4; for i in 0..n_tx { public_data @@ -70,28 +79,78 @@ fn test_simple_pi() { assert_eq!(run::(k, max_txs, max_calldata, public_data), Ok(())); } +#[test] +fn test_1tx_1maxtx() { + const MAX_TXS: usize = 1; + const MAX_CALLDATA: usize = 32; + let mut rng = ChaChaRng::seed_from_u64(2); + let wallet_a = LocalWallet::new(&mut rng).with_chain_id(MOCK_CHAIN_ID.as_u64()); + + let addr_a = wallet_a.address(); + let addr_b = MOCK_ACCOUNTS[0]; + + let degree = 17; + let calldata = vec![]; + let code = bytecode! { + PUSH4(0x1000) // size + PUSH2(0x00) // offset + RETURN + }; + let test_ctx = TestContext::<2, 1>::new( + Some(vec![Word::from("0xdeadbeef")]), + |accs| { + accs[0].address(addr_b).balance(eth(10)).code(code); + accs[1].address(addr_a).balance(eth(10)); + }, + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .input(calldata.into()) + .gas((1e16 as u64).into()); + }, + |block, _txs| block.number(0xcafeu64).chain_id(*MOCK_CHAIN_ID), + ) + .unwrap(); + let mut wallets = HashMap::new(); + wallets.insert(wallet_a.address(), wallet_a); + + let mut block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data_with_params( + block.clone(), + CircuitsParams { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_rws: 1 << (degree - 1), + ..Default::default() + }, + ) + .new_circuit_input_builder(); + + block.sign(&wallets); + + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + + let block = block_convert(&builder.block, &builder.code_db).unwrap(); + // MAX_TXS, MAX_TXS align with `CircuitsParams` + let circuit = PiCircuit::::new_from_block(&block); + let public_inputs = circuit.instance(); + + let prover = match MockProver::run(degree, &circuit, public_inputs) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + assert_eq!(prover.verify(), Ok(())); +} + fn run_size_check(max_txs: usize, max_calldata: usize, public_data: [PublicData; 2]) { - let mut rng = ChaCha20Rng::seed_from_u64(2); - let randomness = F::random(&mut rng); - let rand_rpi = F::random(&mut rng); - - let circuit = PiCircuit::::new( - max_txs, - max_calldata, - randomness, - rand_rpi, - public_data[0].clone(), - ); + let circuit = PiCircuit::::new(max_txs, max_calldata, public_data[0].clone()); let public_inputs = circuit.instance(); let prover1 = MockProver::run(20, &circuit, public_inputs).unwrap(); - let circuit2 = PiCircuit::new( - max_txs, - max_calldata, - randomness, - rand_rpi, - public_data[1].clone(), - ); + let circuit2 = PiCircuit::::new(max_txs, max_calldata, public_data[1].clone()); let public_inputs = circuit2.instance(); let prover2 = MockProver::run(20, &circuit, public_inputs).unwrap(); diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index a7c20c83c18..dd999a9e21e 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -155,6 +155,8 @@ impl SubCircuitConfig for SuperCircuitConfig { max_calldata, block_table: block_table.clone(), tx_table: tx_table.clone(), + keccak_table: keccak_table.clone(), + challenges: challenges.clone(), }, ); let tx_circuit = TxCircuitConfig::new( diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index ffd60993aa9..99d3d733826 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -192,7 +192,7 @@ impl TxCircuit { Value::known(F::ZERO), )?; offset += 1; - // Assign al Tx fields except for call data + // Assign all Tx fields except for call data let tx_default = Transaction::default(); for (i, assigned_sig_verif) in assigned_sig_verifs.iter().enumerate() { let tx = if i < self.txs.len() { diff --git a/zkevm-circuits/src/util/word.rs b/zkevm-circuits/src/util/word.rs index ca9bb34a3cd..74fa8a1fabc 100644 --- a/zkevm-circuits/src/util/word.rs +++ b/zkevm-circuits/src/util/word.rs @@ -3,7 +3,7 @@ // - Limbs: An EVN word is 256 bits. Limbs N means split 256 into N limb. For example, N = 4, each // limb is 256/4 = 64 bits -use eth_types::{Field, ToLittleEndian, H160}; +use eth_types::{Field, ToLittleEndian, H160, H256}; use gadgets::util::{not, or, Expr}; use halo2_proofs::{ circuit::{AssignedCell, Region, Value}, @@ -290,6 +290,21 @@ impl From for Word { } } +impl From for Word { + /// Construct the word from H256 + fn from(h: H256) -> Self { + let le_bytes = { + let mut b = h.to_fixed_bytes(); + b.reverse(); + b + }; + Word::new([ + from_bytes::value(&le_bytes[..N_BYTES_HALF_WORD]), + from_bytes::value(&le_bytes[N_BYTES_HALF_WORD..]), + ]) + } +} + impl From for Word { /// Construct the word from u64 fn from(value: u64) -> Self { diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index e3c4de37641..7a50e43ebc7 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use crate::{ evm_circuit::{detect_fixed_table_tags, util::rlc, EvmCircuit}, exp_circuit::param::OFFSET_INCREMENT, + instance::public_data_convert, table::BlockContextFieldTag, util::{log2_ceil, SubCircuit}, }; @@ -236,7 +237,7 @@ pub fn block_convert( ) -> Result, Error> { let rws = RwMap::from(&block.container); rws.check_value(); - Ok(Block { + let mut block = Block { // randomness: F::from(0x100), // Special value to reveal elements after RLC randomness: F::from(0xcafeu64), context: block.into(), @@ -265,5 +266,13 @@ pub fn block_convert( prev_state_root: block.prev_state_root, keccak_inputs: circuit_input_builder::keccak_inputs(block, code_db)?, eth_block: block.eth_block.clone(), - }) + }; + let public_data = public_data_convert(&block); + let rpi_bytes = public_data.get_pi_bytes( + block.circuits_params.max_txs, + block.circuits_params.max_calldata, + ); + // PI Circuit + block.keccak_inputs.extend_from_slice(&[rpi_bytes]); + Ok(block) }