diff --git a/integration-tests/src/integration_test_circuits.rs b/integration-tests/src/integration_test_circuits.rs index 5d209050c8f..2faa9a49b4c 100644 --- a/integration-tests/src/integration_test_circuits.rs +++ b/integration-tests/src/integration_test_circuits.rs @@ -362,7 +362,7 @@ pub async fn test_super_circuit_block(block_num: u64) { .unwrap(); let (builder, _) = cli.gen_inputs(block_num).await.unwrap(); let (k, circuit, instance) = - SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_RWS>::build_from_circuit_input_builder( + SuperCircuit::::build_from_circuit_input_builder( &builder, ) .unwrap(); diff --git a/testool/src/statetest/executor.rs b/testool/src/statetest/executor.rs index a59e1791fec..4f1e86ecda0 100644 --- a/testool/src/statetest/executor.rs +++ b/testool/src/statetest/executor.rs @@ -7,7 +7,7 @@ use ethers_core::k256::ecdsa::SigningKey; use ethers_core::types::TransactionRequest; use ethers_signers::{LocalWallet, Signer}; use external_tracer::TraceConfig; -use halo2_proofs::dev::MockProver; +use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; use std::{collections::HashMap, str::FromStr}; use thiserror::Error; use zkevm_circuits::{super_circuit::SuperCircuit, test_util::BytecodeTestConfig}; @@ -326,7 +326,7 @@ pub fn run_test( geth_data.sign(&wallets); let (k, circuit, instance, _builder) = - SuperCircuit::<_, 1, 32, 255>::build(geth_data).unwrap(); + SuperCircuit::::build(geth_data).unwrap(); builder = _builder; let prover = MockProver::run(k, &circuit, instance).unwrap(); diff --git a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs index 993219ce569..a5f82d68a54 100644 --- a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs +++ b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs @@ -748,6 +748,15 @@ impl SubCircuit for BytecodeCircuit { Self::new_from_block_sized(block, bytecode_size) } + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize { + block + .bytecodes + .values() + .map(|bytecode| bytecode.bytes.len() + 1) + .sum() + } + /// Make the assignments to the TxCircuit fn synthesize_sub( &self, diff --git a/zkevm-circuits/src/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs index d9e289bac7e..1448fae2a9e 100644 --- a/zkevm-circuits/src/copy_circuit.rs +++ b/zkevm-circuits/src/copy_circuit.rs @@ -645,6 +645,11 @@ impl SubCircuit for CopyCircuit { Self::new(block.circuits_params.max_txs, block.clone()) } + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize { + block.copy_events.iter().map(|c| c.bytes.len() * 2).sum() + } + /// Make the assignments to the CopyCircuit fn synthesize_sub( &self, @@ -754,6 +759,7 @@ mod tests { mock::BlockData, }; use eth_types::{bytecode, geth_types::GethData, Word}; + use halo2_proofs::halo2curves::bn256::Fr; use mock::test_ctx::helpers::account_0_code_account_1_no_code; use mock::TestContext; @@ -855,28 +861,28 @@ mod tests { #[test] fn copy_circuit_valid_calldatacopy() { let builder = gen_calldatacopy_data(); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); assert_eq!(test_copy_circuit(14, block), Ok(())); } #[test] fn copy_circuit_valid_codecopy() { let builder = gen_codecopy_data(); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); assert_eq!(test_copy_circuit(10, block), Ok(())); } #[test] fn copy_circuit_valid_sha3() { let builder = gen_sha3_data(); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); assert_eq!(test_copy_circuit(20, block), Ok(())); } #[test] fn copy_circuit_tx_log() { let builder = gen_tx_log_data(); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); assert_eq!(test_copy_circuit(10, block), Ok(())); } diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 90d7c2ce12a..337646d8e9a 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -14,8 +14,9 @@ pub(crate) mod util; pub mod table; use crate::table::{BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, RwTable, TxTable}; -use crate::util::{Challenges, SubCircuit, SubCircuitConfig}; +use crate::util::{log2_ceil, Challenges, SubCircuit, SubCircuitConfig}; pub use crate::witness; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use execution::ExecutionConfig; use itertools::Itertools; @@ -211,6 +212,20 @@ impl SubCircuit for EvmCircuit { Self::new(block.clone()) } + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize { + let num_rows_required_for_execution_steps: usize = + EvmCircuit::::get_num_rows_required(block); + let num_rows_required_for_fixed_table: usize = detect_fixed_table_tags(block) + .iter() + .map(|tag| tag.build::().count()) + .sum(); + std::cmp::max( + num_rows_required_for_execution_steps, + num_rows_required_for_fixed_table, + ) + } + /// Make the assignments to the EvmCircuit fn synthesize_sub( &self, @@ -226,18 +241,42 @@ impl SubCircuit for EvmCircuit { } } +/// create fixed_table_tags needed given witness block +pub(crate) fn detect_fixed_table_tags(block: &Block) -> Vec { + let need_bitwise_lookup = block.txs.iter().any(|tx| { + tx.steps.iter().any(|step| { + matches!( + step.opcode, + Some(OpcodeId::AND) + | Some(OpcodeId::OR) + | Some(OpcodeId::XOR) + | Some(OpcodeId::NOT) + ) + }) + }); + FixedTableTag::iter() + .filter(|t| { + !matches!( + t, + FixedTableTag::BitwiseAnd | FixedTableTag::BitwiseOr | FixedTableTag::BitwiseXor + ) || need_bitwise_lookup + }) + .collect() +} + #[cfg(any(feature = "test", test))] pub mod test { use super::*; use crate::{ - evm_circuit::{table::FixedTableTag, witness::Block, EvmCircuitConfig}, + evm_circuit::{witness::Block, EvmCircuitConfig}, exp_circuit::OFFSET_INCREMENT, table::{BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, RwTable, TxTable}, util::{power_of_randomness_from_instance, Challenges}, witness::block_convert, }; - use bus_mapping::{circuit_input_builder::CircuitsParams, evm::OpcodeId, mock::BlockData}; + use bus_mapping::{circuit_input_builder::CircuitsParams, mock::BlockData}; use eth_types::{geth_types::GethData, Field, Word}; + use halo2_proofs::halo2curves::bn256::Fr; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, dev::{MockProver, VerifyFailure}, @@ -247,7 +286,6 @@ pub mod test { distributions::uniform::{SampleRange, SampleUniform}, random, thread_rng, Rng, }; - use strum::IntoEnumIterator; pub(crate) fn rand_range(range: R) -> T where @@ -269,31 +307,6 @@ pub mod test { Word::from_big_endian(&rand_bytes_array::<32>()) } - /// create fixed_table_tags needed given witness block - pub(crate) fn detect_fixed_table_tags(block: &Block) -> Vec { - let need_bitwise_lookup = block.txs.iter().any(|tx| { - tx.steps.iter().any(|step| { - matches!( - step.opcode, - Some(OpcodeId::AND) - | Some(OpcodeId::OR) - | Some(OpcodeId::XOR) - | Some(OpcodeId::NOT) - ) - }) - }); - FixedTableTag::iter() - .filter(|t| { - !matches!( - t, - FixedTableTag::BitwiseAnd - | FixedTableTag::BitwiseOr - | FixedTableTag::BitwiseXor - ) || need_bitwise_lookup - }) - .collect() - } - impl Circuit for EvmCircuit { type Config = EvmCircuitConfig; type FloorPlanner = SimpleFloorPlanner; @@ -391,7 +404,7 @@ pub mod test { builder .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); run_test_circuit(block) } @@ -404,7 +417,7 @@ pub mod test { builder .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); run_test_circuit(block) } @@ -432,8 +445,6 @@ pub mod test { .map(|e| e.steps.len() * OFFSET_INCREMENT) .sum(); - let log2_ceil = |n| u32::BITS - (n as u32).leading_zeros() - (n & (n - 1) == 0) as u32; - const NUM_BLINDING_ROWS: usize = 64; let rows_needed: usize = itertools::max([ diff --git a/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs b/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs index add77bf8f1e..e17bfab625e 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs @@ -238,6 +238,7 @@ mod test { use eth_types::evm_types::OpcodeId; use eth_types::geth_types::Account; use eth_types::{address, bytecode, Address, ToWord, Word}; + use halo2_proofs::halo2curves::bn256::Fr; use mock::TestContext; fn test_invalid_jump(destination: usize, out_of_range: bool) { @@ -448,7 +449,7 @@ mod test { builder .handle_block(&block_data.eth_block, &block_data.geth_traces) .unwrap(); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); assert_eq!(run_test_circuit(block), Ok(())); } diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs index 3c73d629abe..c68fec2ff41 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs @@ -330,6 +330,7 @@ mod test { use eth_types::{address, bytecode}; use eth_types::{bytecode::Bytecode, evm_types::OpcodeId, geth_types::Account}; use eth_types::{Address, ToWord, Word}; + use halo2_proofs::halo2curves::bn256::Fr; use itertools::Itertools; use mock::TestContext; use std::default::Default; @@ -420,7 +421,7 @@ mod test { builder .handle_block(&block_data.eth_block, &block_data.geth_traces) .unwrap(); - let block = block_convert(&builder.block, &builder.code_db); + let block = block_convert::(&builder.block, &builder.code_db); assert_eq!(run_test_circuit(block.unwrap()), Ok(())); } diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_constant.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_constant.rs index e5dde02f94f..f51b799ad1a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_constant.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_constant.rs @@ -291,7 +291,7 @@ mod test { builder .handle_block(&block_data.eth_block, &block_data.geth_traces) .unwrap(); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); assert_eq!(run_test_circuit(block), Ok(())); } diff --git a/zkevm-circuits/src/evm_circuit/execution/error_stack.rs b/zkevm-circuits/src/evm_circuit/execution/error_stack.rs index cf93e747581..e534b669c8c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_stack.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_stack.rs @@ -165,6 +165,7 @@ mod test { use eth_types::{ self, address, bytecode, bytecode::Bytecode, geth_types::Account, Address, ToWord, Word, }; + use halo2_proofs::halo2curves::bn256::Fr; use mock::TestContext; @@ -320,7 +321,7 @@ mod test { builder .handle_block(&block_data.eth_block, &block_data.geth_traces) .unwrap(); - let block = block_convert(&builder.block, &builder.code_db); + let block = block_convert::(&builder.block, &builder.code_db); assert_eq!(run_test_circuit(block.unwrap()), Ok(())); } diff --git a/zkevm-circuits/src/evm_circuit/execution/gas.rs b/zkevm-circuits/src/evm_circuit/execution/gas.rs index 0567e50a038..f70d95ef845 100644 --- a/zkevm-circuits/src/evm_circuit/execution/gas.rs +++ b/zkevm-circuits/src/evm_circuit/execution/gas.rs @@ -92,6 +92,7 @@ mod test { }; use bus_mapping::mock::BlockData; use eth_types::{address, bytecode, geth_types::GethData, Word}; + use halo2_proofs::halo2curves::bn256::Fr; use mock::TestContext; fn test_ok() { @@ -150,7 +151,7 @@ mod test { builder .handle_block(&block.eth_block, &block.geth_traces) .expect("could not handle block tx"); - let mut block = block_convert(&builder.block, &builder.code_db).unwrap(); + let mut block = block_convert::(&builder.block, &builder.code_db).unwrap(); // The above block has 2 steps (GAS and STOP). We forcefully assign a // wrong `gas_left` value for the second step, to assert that diff --git a/zkevm-circuits/src/exp_circuit.rs b/zkevm-circuits/src/exp_circuit.rs index bc9ccaac45f..d07f5b63400 100644 --- a/zkevm-circuits/src/exp_circuit.rs +++ b/zkevm-circuits/src/exp_circuit.rs @@ -404,6 +404,15 @@ impl SubCircuit for ExpCircuit { Self::new(block.clone()) } + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize { + block + .exp_events + .iter() + .map(|e| e.steps.len() * OFFSET_INCREMENT) + .sum() + } + /// Make the assignments to the ExpCircuit fn synthesize_sub( &self, @@ -462,6 +471,7 @@ pub mod dev { mod tests { use bus_mapping::{circuit_input_builder::CircuitInputBuilder, evm::OpcodeId, mock::BlockData}; use eth_types::{bytecode, geth_types::GethData, Bytecode, Word}; + use halo2_proofs::halo2curves::bn256::Fr; use mock::TestContext; use crate::{evm_circuit::witness::block_convert, exp_circuit::dev::test_exp_circuit}; @@ -499,14 +509,14 @@ mod tests { fn test_ok(base: Word, exponent: Word, k: Option) { let code = gen_code_single(base, exponent); let builder = gen_data(code); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); assert_eq!(test_exp_circuit(k.unwrap_or(10), block), Ok(())); } fn test_ok_multiple(args: Vec<(Word, Word)>) { let code = gen_code_multiple(args); let builder = gen_data(code); - let block = block_convert(&builder.block, &builder.code_db).unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); assert_eq!(test_exp_circuit(20, block), Ok(())); } diff --git a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs index 8bb9b9426bd..9ff9e5d39ef 100644 --- a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs +++ b/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs @@ -30,7 +30,7 @@ const THETA_C_LOOKUP_RANGE: usize = 6; const RHO_PI_LOOKUP_RANGE: usize = 4; const CHI_BASE_LOOKUP_RANGE: usize = 5; -fn get_num_rows_per_round() -> usize { +pub(crate) fn get_num_rows_per_round() -> usize { var("KECCAK_ROWS") .unwrap_or_else(|_| "5".to_string()) .parse() @@ -370,6 +370,16 @@ impl SubCircuit for KeccakCircuit { ) } + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize { + let rows_per_chunk = (NUM_ROUNDS + 1) * get_num_rows_per_round(); + block + .keccak_inputs + .iter() + .map(|bytes| (bytes.len() as f64 / 136.0).ceil() as usize * rows_per_chunk) + .sum() + } + /// Make the assignments to the KeccakCircuit fn synthesize_sub( &self, diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index fc4ba0b5632..f0932888a8b 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -14,6 +14,7 @@ use halo2_proofs::plonk::Instance; use crate::table::BlockTable; use crate::table::TxFieldTag; use crate::table::TxTable; +use crate::tx_circuit::TX_LEN; use crate::util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}; use crate::witness; use gadgets::is_zero::IsZeroChip; @@ -25,7 +26,6 @@ use halo2_proofs::{ }; /// Fixed by the spec -const TX_LEN: usize = 10; const BLOCK_LEN: usize = 7 + 256; const EXTRA_LEN: usize = 2; const ZERO_BYTE_GAS_COST: u64 = 4; @@ -1143,6 +1143,15 @@ impl SubCircuit for PiCircuit { ) } + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize { + BLOCK_LEN + + 1 + + EXTRA_LEN + + 3 * (TX_LEN * block.circuits_params.max_txs + 1) + + block.circuits_params.max_calldata + } + /// Compute the public inputs for this circuit. fn instance(&self) -> Vec> { let rlc_rpi_col = raw_public_inputs_col::( diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index f22411bbbe4..f4b67b08370 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -378,6 +378,11 @@ impl SubCircuit for StateCircuit { Self::new(block.rws.clone(), block.circuits_params.max_rws) } + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize { + block.circuits_params.max_rws + } + /// Make the assignments to the StateCircuit fn synthesize_sub( &self, diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index 88086ab1b08..8396f3e645e 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -55,9 +55,7 @@ use crate::bytecode_circuit::bytecode_unroller::{ BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs, }; use crate::copy_circuit::{CopyCircuit, CopyCircuitConfig, CopyCircuitConfigArgs}; -use crate::evm_circuit::{ - table::FixedTableTag, EvmCircuit, EvmCircuitConfig, EvmCircuitConfigArgs, -}; +use crate::evm_circuit::{EvmCircuit, EvmCircuitConfig, EvmCircuitConfigArgs}; use crate::exp_circuit::{ExpCircuit, ExpCircuitConfig}; use crate::keccak_circuit::keccak_packed_multi::{ KeccakCircuit, KeccakCircuitConfig, KeccakCircuitConfigArgs, @@ -68,20 +66,18 @@ use crate::table::{ BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, RwTable, TxTable, }; use crate::tx_circuit::{TxCircuit, TxCircuitConfig, TxCircuitConfigArgs}; -use crate::util::{Challenges, SubCircuit, SubCircuitConfig}; +use crate::util::{log2_ceil, Challenges, SubCircuit, SubCircuitConfig}; use crate::witness::{block_convert, Block, MptUpdates}; use bus_mapping::circuit_input_builder::{CircuitInputBuilder, CircuitsParams}; use bus_mapping::mock::BlockData; use eth_types::geth_types::GethData; use eth_types::Field; -use halo2_proofs::halo2curves::bn256::Fr; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{Circuit, ConstraintSystem, Error, Expression}, }; use std::array; -use strum::IntoEnumIterator; /// Mock randomness used for `SuperCircuit`. pub const MOCK_RANDOMNESS: u64 = 0x100; @@ -306,8 +302,8 @@ impl - SuperCircuit +impl + SuperCircuit { /// From the witness data, generate a SuperCircuit instance with all of the /// sub-circuits filled with their corresponding witnesses. @@ -317,7 +313,7 @@ impl #[allow(clippy::type_complexity)] pub fn build( geth_data: GethData, - ) -> Result<(u32, Self, Vec>, CircuitInputBuilder), bus_mapping::Error> { + ) -> Result<(u32, Self, Vec>, CircuitInputBuilder), bus_mapping::Error> { let block_data = BlockData::new_from_geth_data_with_params( geth_data.clone(), CircuitsParams { @@ -344,29 +340,13 @@ impl /// the Public Inputs needed. pub fn build_from_circuit_input_builder( builder: &CircuitInputBuilder, - ) -> Result<(u32, Self, Vec>), bus_mapping::Error> { + ) -> Result<(u32, Self, Vec>), bus_mapping::Error> { let mut block = block_convert(&builder.block, &builder.code_db).unwrap(); - block.randomness = Fr::from(MOCK_RANDOMNESS); - - let fixed_table_tags: Vec = FixedTableTag::iter().collect(); - let log2_ceil = |n| u32::BITS - (n as u32).leading_zeros() - (n & (n - 1) == 0) as u32; - - let num_rows_required = - SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_RWS>::get_num_rows_required(&block); + block.randomness = F::from(MOCK_RANDOMNESS); - let k = log2_ceil( - 64 + fixed_table_tags - .iter() - .map(|tag| tag.build::().count()) - .sum::(), - ); - let bytecodes_len = block - .bytecodes - .iter() - .map(|(_, bytecode)| bytecode.bytes.len()) - .sum::(); - let k = k.max(log2_ceil(64 + bytecodes_len)); - let k = k.max(log2_ceil(64 + num_rows_required)); + const NUM_BLINDING_ROWS: usize = 64; + let rows_needed = Self::min_num_rows_block(&block); + let k = log2_ceil(NUM_BLINDING_ROWS + rows_needed); log::debug!("super circuit uses k = {}", k); let evm_circuit = EvmCircuit::new_from_block(&block); @@ -394,26 +374,35 @@ impl } /// Returns suitable inputs for the SuperCircuit. - pub fn instance(&self) -> Vec> { + pub fn instance(&self) -> Vec> { // SignVerifyChip -> ECDSAChip -> MainGate instance column let pi_instance = self.pi_circuit.instance(); let instance = vec![pi_instance[0].clone(), vec![]]; instance } -} -// TODO: Add tests -// - multiple txs == MAX_TXS -// - multiple txs < MAX_TXS -// - max_rws padding -// - evm_rows padding + /// Return the minimum number of rows required to prove the block + pub fn min_num_rows_block(block: &Block) -> usize { + let evm = EvmCircuit::min_num_rows_block(block); + let state = StateCircuit::min_num_rows_block(block); + let bytecode = BytecodeCircuit::min_num_rows_block(block); + let copy = CopyCircuit::min_num_rows_block(block); + let keccak = KeccakCircuit::min_num_rows_block(block); + let tx = TxCircuit::min_num_rows_block(block); + let exp = ExpCircuit::min_num_rows_block(block); + let pi = PiCircuit::min_num_rows_block(block); + + itertools::max([evm, state, bytecode, copy, keccak, tx, exp, pi]).unwrap() + } +} #[cfg(test)] mod super_circuit_tests { use super::*; use ethers_signers::{LocalWallet, Signer}; use halo2_proofs::dev::MockProver; + use halo2_proofs::halo2curves::bn256::Fr; use log::error; use mock::{TestContext, MOCK_CHAIN_ID}; use rand::SeedableRng; @@ -431,11 +420,20 @@ mod super_circuit_tests { assert!(cs.degree() <= 9); } - // High memory usage test. Run in serial with: - // `cargo test [...] serial_ -- --ignored --test-threads 1` - #[ignore] - #[test] - fn serial_test_super_circuit() { + fn test_super_circuit( + block: GethData, + ) { + let (k, circuit, instance, _) = + SuperCircuit::::build(block).unwrap(); + let prover = MockProver::run(k, &circuit, instance).unwrap(); + let res = prover.verify_par(); + if let Err(err) = res { + error!("Verification failures: {:#?}", err); + panic!("Failed verification"); + } + } + + fn block_1tx() -> GethData { let mut rng = ChaCha20Rng::seed_from_u64(2); let chain_id = (*MOCK_CHAIN_ID).as_u64(); @@ -472,15 +470,82 @@ mod super_circuit_tests { ) .unwrap() .into(); + block.sign(&wallets); + block + } + + fn block_2tx() -> GethData { + let mut rng = ChaCha20Rng::seed_from_u64(2); + + let chain_id = (*MOCK_CHAIN_ID).as_u64(); + + let bytecode = bytecode! { + GAS + STOP + }; + + let wallet_a = LocalWallet::new(&mut rng).with_chain_id(chain_id); + + let addr_a = wallet_a.address(); + let addr_b = address!("0x000000000000000000000000000000000000BBBB"); + + let mut wallets = HashMap::new(); + wallets.insert(wallet_a.address(), wallet_a); + let mut block: GethData = TestContext::<2, 2>::new( + None, + |accs| { + accs[0] + .address(addr_b) + .balance(Word::from(1u64 << 20)) + .code(bytecode); + accs[1].address(addr_a).balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .gas(Word::from(1_000_000u64)); + txs[1] + .from(accs[1].address) + .to(accs[0].address) + .gas(Word::from(1_000_000u64)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(); block.sign(&wallets); + block + } - let (k, circuit, instance, _) = SuperCircuit::<_, 1, 32, 256>::build(block).unwrap(); - let prover = MockProver::run(k, &circuit, instance).unwrap(); - let res = prover.verify_par(); - if let Err(err) = res { - error!("Verification failures: {:#?}", err); - panic!("Failed verification"); - } + // High memory usage test. Run in serial with: + // `cargo test [...] serial_ -- --ignored --test-threads 1` + #[ignore] + #[test] + fn serial_test_super_circuit_1tx_1max_tx() { + let block = block_1tx(); + const MAX_TXS: usize = 1; + const MAX_CALLDATA: usize = 32; + const MAX_RWS: usize = 256; + test_super_circuit::(block); + } + #[ignore] + #[test] + fn serial_test_super_circuit_1tx_2max_tx() { + let block = block_1tx(); + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 32; + const MAX_RWS: usize = 256; + test_super_circuit::(block); + } + #[ignore] + #[test] + fn serial_test_super_circuit_2tx_2max_tx() { + let block = block_2tx(); + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 32; + const MAX_RWS: usize = 256; + test_super_circuit::(block); } } diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index ae4e48663ae..e4265a1fd87 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -32,6 +32,13 @@ pub use halo2_proofs::halo2curves::{ secp256k1::{self, Secp256k1Affine, Secp256k1Compressed}, }; +/// Number of static fields per tx: [nonce, gas, gas_price, +/// caller_address, callee_address, is_create, value, call_data_length, +/// call_data_gas_cost, tx_sign_hash]. +/// Note that call data bytes are layed out in the TxTable after all the static +/// fields arranged by txs. +pub(crate) const TX_LEN: usize = 10; + /// Config for TxCircuit #[derive(Clone, Debug)] pub struct TxCircuitConfig { @@ -161,6 +168,13 @@ impl TxCircuit { } } + /// Return the minimum number of rows required to prove an input of a + /// particular size. + pub fn min_num_rows(txs_len: usize, call_data_len: usize) -> usize { + let tx_table_len = txs_len * TX_LEN + call_data_len; + std::cmp::max(tx_table_len, SignVerifyChip::::min_num_rows(txs_len)) + } + fn assign_tx_table( &self, config: &TxCircuitConfig, @@ -320,6 +334,14 @@ impl SubCircuit for TxCircuit { ) } + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize { + Self::min_num_rows( + block.txs.len(), + block.txs.iter().map(|tx| tx.call_data.len()).sum(), + ) + } + /// Make the assignments to the TxCircuit fn synthesize_sub( &self, @@ -399,6 +421,7 @@ impl Circuit for TxCircuit { #[cfg(test)] mod tx_circuit_tests { use super::*; + use crate::util::log2_ceil; use eth_types::address; use halo2_proofs::{ dev::{MockProver, VerifyFailure}, @@ -407,13 +430,15 @@ mod tx_circuit_tests { use mock::AddrOrWallet; use pretty_assertions::assert_eq; + const NUM_BLINDING_ROWS: usize = 64; + fn run( - k: u32, txs: Vec, chain_id: u64, max_txs: usize, max_calldata: usize, ) -> Result<(), Vec> { + let k = log2_ceil(NUM_BLINDING_ROWS + TxCircuit::::min_num_rows(max_txs, max_calldata)); // SignVerifyChip -> ECDSAChip -> MainGate instance column let circuit = TxCircuit::::new(max_txs, max_calldata, chain_id, txs); @@ -425,15 +450,13 @@ mod tx_circuit_tests { } #[test] - fn tx_circuit_2tx() { + fn tx_circuit_2tx_2max_tx() { const NUM_TXS: usize = 2; const MAX_TXS: usize = 2; const MAX_CALLDATA: usize = 32; - let k = 19; assert_eq!( run::( - k, mock::CORRECT_MOCK_TXS[..NUM_TXS] .iter() .map(|tx| Transaction::from(tx.clone())) @@ -447,7 +470,7 @@ mod tx_circuit_tests { } #[test] - fn tx_circuit_1tx() { + fn tx_circuit_1tx_1max_tx() { const MAX_TXS: usize = 1; const MAX_CALLDATA: usize = 32; @@ -455,11 +478,19 @@ mod tx_circuit_tests { let tx: Transaction = mock::CORRECT_MOCK_TXS[0].clone().into(); - let k = 19; - assert_eq!( - run::(k, vec![tx], chain_id, MAX_TXS, MAX_CALLDATA), - Ok(()) - ); + assert_eq!(run::(vec![tx], chain_id, MAX_TXS, MAX_CALLDATA), Ok(())); + } + + #[test] + fn tx_circuit_1tx_2max_tx() { + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 32; + + let chain_id: u64 = mock::MOCK_CHAIN_ID.as_u64(); + + let tx: Transaction = mock::CORRECT_MOCK_TXS[0].clone().into(); + + assert_eq!(run::(vec![tx], chain_id, MAX_TXS, MAX_CALLDATA), Ok(())); } #[test] @@ -471,9 +502,7 @@ mod tx_circuit_tests { // This address doesn't correspond to the account that signed this tx. tx.from = AddrOrWallet::from(address!("0x1230000000000000000000000000000000000456")); - let k = 19; assert!(run::( - k, vec![tx.into()], mock::MOCK_CHAIN_ID.as_u64(), MAX_TXS, diff --git a/zkevm-circuits/src/tx_circuit/sign_verify.rs b/zkevm-circuits/src/tx_circuit/sign_verify.rs index 37868181d18..d90f105fbf3 100644 --- a/zkevm-circuits/src/tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/tx_circuit/sign_verify.rs @@ -69,6 +69,28 @@ impl SignVerifyChip { _marker: PhantomData, } } + + /// Return the minimum number of rows required to prove an input of a + /// particular size. + pub fn min_num_rows(num_verif: usize) -> usize { + // The values rows_ecc_chip_aux, rows_ecdsa_chip_verification and + // rows_ecdsa_chip_verification have been obtained from log debugs while running + // the tx circuit with max_txs=1. For example: + // `RUST_LOG=debug RUST_BACKTRACE=1 cargo test tx_circuit_1tx_1max_tx --release + // --all-features -- --nocapture` + // The value rows_range_chip_table has been optained by patching the halo2 + // library to report the number of rows used in the range chip table + // region. TODO: Figure out a way to get these numbers automatically. + let rows_range_chip_table = 295188; + let rows_ecc_chip_aux = 226; + let rows_ecdsa_chip_verification = 140360; + let rows_signature_address_verify = 76; + std::cmp::max( + rows_range_chip_table, + (rows_ecc_chip_aux + rows_ecdsa_chip_verification + rows_signature_address_verify) + * num_verif, + ) + } } impl Default for SignVerifyChip { @@ -615,7 +637,12 @@ impl SignVerifyChip { layouter.assign_region( || "ecc chip aux", - |region| self.assign_aux(&mut RegionCtx::new(region, 0), &mut ecc_chip), + |region| { + let mut ctx = RegionCtx::new(region, 0); + self.assign_aux(&mut ctx, &mut ecc_chip)?; + log::debug!("ecc chip aux: {} rows", ctx.offset()); + Ok(()) + }, )?; let ecdsa_chip = EcdsaChip::new(ecc_chip.clone()); @@ -643,6 +670,7 @@ impl SignVerifyChip { let assigned_ecdsa = self.assign_ecdsa(&mut ctx, &chips, &signature)?; assigned_ecdsas.push(assigned_ecdsa); } + log::debug!("ecdsa chip verification: {} rows", ctx.offset()); Ok(assigned_ecdsas) }, )?; @@ -664,6 +692,7 @@ impl SignVerifyChip { )?; assigned_sig_verifs.push(assigned_sig_verif); } + log::debug!("signature address verify: {} rows", ctx.offset()); Ok(assigned_sig_verifs) }, ) diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index a02a9e637cb..eff206056b7 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -155,6 +155,9 @@ pub trait SubCircuit { challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error>; + + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> usize; } /// SubCircuit configuration @@ -165,3 +168,8 @@ pub trait SubCircuitConfig { /// Type constructor fn new(meta: &mut ConstraintSystem, args: Self::ConfigArgs) -> Self; } + +/// Ceiling of log_2(n) +pub fn log2_ceil(n: usize) -> u32 { + u32::BITS - (n as u32).leading_zeros() - (n & (n - 1) == 0) as u32 +} diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index ce4f1c4b2dc..4c8e09a3f7e 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -6,7 +6,6 @@ use bus_mapping::{ Error, }; use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; -use halo2_proofs::halo2curves::bn256::Fr; use super::{step::step_convert, tx::tx_convert, Bytecode, ExecStep, RwMap, Transaction}; @@ -162,13 +161,13 @@ impl From<&circuit_input_builder::Block> for BlockContext { } /// Convert a block struct in bus-mapping to a witness block used in circuits -pub fn block_convert( +pub fn block_convert( block: &circuit_input_builder::Block, code_db: &bus_mapping::state_db::CodeDB, -) -> Result, Error> { +) -> Result, Error> { Ok(Block { - // randomness: Fr::from(0xcafeu64), // TODO: Uncomment - randomness: Fr::from(0x10000), // Special value to reveal elements after RLC + randomness: F::from(0xcafeu64), + // randomness: F::from(0x100), // Special value to reveal elements after RLC context: block.into(), rws: RwMap::from(&block.container), txs: block