diff --git a/circuit-benchmarks/Cargo.toml b/circuit-benchmarks/Cargo.toml index 3646c329a5..222db7931d 100644 --- a/circuit-benchmarks/Cargo.toml +++ b/circuit-benchmarks/Cargo.toml @@ -11,7 +11,7 @@ halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.gi ark-std = { version = "0.3", features = ["print-trace"] } zkevm-circuits = { path = "../zkevm-circuits", features = ["test"]} keccak256 = { path = "../keccak256" } -bus-mapping = { path = "../bus-mapping" } +bus-mapping = { path = "../bus-mapping", features = ["test"] } rand_xorshift = "0.3" rand = "0.8" itertools = "0.10" diff --git a/circuit-benchmarks/src/bytecode_circuit.rs b/circuit-benchmarks/src/bytecode_circuit.rs index 0142ce3f9c..16c055410e 100644 --- a/circuit-benchmarks/src/bytecode_circuit.rs +++ b/circuit-benchmarks/src/bytecode_circuit.rs @@ -25,7 +25,7 @@ mod tests { use std::env::var; use zkevm_circuits::bytecode_circuit::bytecode_unroller::{unroll, UnrolledBytecode}; - use zkevm_circuits::bytecode_circuit::circuit::BytecodeCircuit; + use zkevm_circuits::bytecode_circuit::TestBytecodeCircuit; #[cfg_attr(not(feature = "benches"), ignore)] #[test] @@ -51,7 +51,7 @@ mod tests { let bytecodes_num: usize = max_bytecode_row_num / bytecode_len; // Create the circuit - let bytecode_circuit = BytecodeCircuit::::new( + let bytecode_circuit = TestBytecodeCircuit::::new( fillup_codebytes(bytecodes_num, bytecode_len), 2usize.pow(degree), ); @@ -88,7 +88,7 @@ mod tests { Challenge255, XorShiftRng, Blake2bWrite, G1Affine, Challenge255>, - BytecodeCircuit, + TestBytecodeCircuit, >( &general_params, &pk, diff --git a/circuit-benchmarks/src/copy_circuit.rs b/circuit-benchmarks/src/copy_circuit.rs index fc6d57c39e..e08e4aa8fd 100644 --- a/circuit-benchmarks/src/copy_circuit.rs +++ b/circuit-benchmarks/src/copy_circuit.rs @@ -25,7 +25,7 @@ mod tests { use rand_xorshift::XorShiftRng; use std::env::var; use zkevm_circuits::{ - copy_circuit::CopyCircuit, + copy_circuit::TestCopyCircuit, evm_circuit::witness::{block_convert, Block}, util::SubCircuit, }; @@ -52,7 +52,7 @@ mod tests { // Create the circuit let block = generate_full_events_block(degree); - let circuit = CopyCircuit::::new_from_block(&block); + let circuit = TestCopyCircuit::::new_from_block(&block); // Bench setup generation let setup_message = format!("{} {} with degree = {}", BENCHMARK_ID, setup_prfx, degree); @@ -79,7 +79,7 @@ mod tests { Challenge255, XorShiftRng, Blake2bWrite, G1Affine, Challenge255>, - CopyCircuit, + TestCopyCircuit, >(&general_params, &pk, &[circuit], &[], rng, &mut transcript) .expect("proof generation should not fail"); let proof = transcript.finalize(); diff --git a/circuit-benchmarks/src/evm_circuit.rs b/circuit-benchmarks/src/evm_circuit.rs index f055ad5873..aaf887261c 100644 --- a/circuit-benchmarks/src/evm_circuit.rs +++ b/circuit-benchmarks/src/evm_circuit.rs @@ -24,7 +24,7 @@ mod evm_circ_benches { use rand::SeedableRng; use rand_xorshift::XorShiftRng; use std::env::var; - use zkevm_circuits::evm_circuit::{witness::block_convert, EvmCircuit}; + use zkevm_circuits::evm_circuit::{witness::block_convert, TestEvmCircuit}; #[cfg_attr(not(feature = "benches"), ignore)] #[test] @@ -56,7 +56,7 @@ mod evm_circ_benches { let block = block_convert(&builder.block, &builder.code_db).unwrap(); - let circuit = EvmCircuit::::new(block); + let circuit = TestEvmCircuit::::new(block); let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 0xe5, @@ -87,7 +87,7 @@ mod evm_circ_benches { Challenge255, XorShiftRng, Blake2bWrite, G1Affine, Challenge255>, - EvmCircuit, + TestEvmCircuit, >( &general_params, &pk, diff --git a/circuit-benchmarks/src/exp_circuit.rs b/circuit-benchmarks/src/exp_circuit.rs index 2ad14f3812..28e1279178 100644 --- a/circuit-benchmarks/src/exp_circuit.rs +++ b/circuit-benchmarks/src/exp_circuit.rs @@ -27,7 +27,7 @@ mod tests { use std::env::var; use zkevm_circuits::{ evm_circuit::witness::{block_convert, Block}, - exp_circuit::ExpCircuit, + exp_circuit::TestExpCircuit, }; #[cfg_attr(not(feature = "benches"), ignore)] @@ -50,7 +50,7 @@ mod tests { let base = Word::from(132); let exponent = Word::from(27); let block = generate_full_events_block(degree, base, exponent); - let circuit = ExpCircuit::::new( + let circuit = TestExpCircuit::::new( block.exp_events.clone(), block.circuits_params.max_exp_steps, ); @@ -86,7 +86,7 @@ mod tests { Challenge255, XorShiftRng, Blake2bWrite, G1Affine, Challenge255>, - ExpCircuit, + TestExpCircuit, >( &general_params, &pk, diff --git a/circuit-benchmarks/src/packed_multi_keccak.rs b/circuit-benchmarks/src/packed_multi_keccak.rs index 1a3f6c4b12..e9268b98a2 100644 --- a/circuit-benchmarks/src/packed_multi_keccak.rs +++ b/circuit-benchmarks/src/packed_multi_keccak.rs @@ -21,7 +21,7 @@ mod tests { use rand::SeedableRng; use rand_xorshift::XorShiftRng; use std::env::var; - use zkevm_circuits::keccak_circuit::KeccakCircuit; + use zkevm_circuits::keccak_circuit::TestKeccakCircuit; #[cfg_attr(not(feature = "benches"), ignore)] #[test] @@ -41,7 +41,7 @@ mod tests { let inputs = vec![(0u8..135).collect::>(); 3]; // Create the circuit. Leave last dozens of rows for blinding. - let circuit = KeccakCircuit::new(2usize.pow(degree) - 64, inputs); + let circuit = TestKeccakCircuit::new(2usize.pow(degree) - 64, inputs); // Initialize the polynomial commitment parameters let mut rng = XorShiftRng::from_seed([ @@ -74,7 +74,7 @@ mod tests { Challenge255, XorShiftRng, Blake2bWrite, G1Affine, Challenge255>, - KeccakCircuit, + TestKeccakCircuit, >( &general_params, &pk, diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index a95848d960..3b02409e8e 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -11,7 +11,7 @@ lazy_static = "1.4" ethers = { version = "0.17.0", features = ["ethers-solc"] } serde_json = "1.0.66" serde = { version = "1.0.130", features = ["derive"] } -bus-mapping = { path = "../bus-mapping" } +bus-mapping = { path = "../bus-mapping" , features = ["test"] } eth-types = { path = "../eth-types" } zkevm-circuits = { path = "../zkevm-circuits", features = ["test"] } tokio = { version = "1.13", features = ["macros", "rt-multi-thread"] } diff --git a/integration-tests/src/integration_test_circuits.rs b/integration-tests/src/integration_test_circuits.rs index e33bd3d4fd..5eb08aa43a 100644 --- a/integration-tests/src/integration_test_circuits.rs +++ b/integration-tests/src/integration_test_circuits.rs @@ -28,14 +28,14 @@ use rand_xorshift::XorShiftRng; use std::{collections::HashMap, marker::PhantomData, sync::Mutex}; use tokio::sync::Mutex as TokioMutex; use zkevm_circuits::{ - bytecode_circuit::circuit::BytecodeCircuit, - copy_circuit::CopyCircuit, - evm_circuit::EvmCircuit, - exp_circuit::ExpCircuit, - keccak_circuit::KeccakCircuit, - state_circuit::StateCircuit, + bytecode_circuit::TestBytecodeCircuit, + copy_circuit::TestCopyCircuit, + evm_circuit::TestEvmCircuit, + exp_circuit::TestExpCircuit, + keccak_circuit::TestKeccakCircuit, + state_circuit::TestStateCircuit, super_circuit::SuperCircuit, - tx_circuit::TxCircuit, + tx_circuit::TestTxCircuit, util::SubCircuit, witness::{block_convert, Block}, }; @@ -95,27 +95,27 @@ lazy_static! { lazy_static! { /// Integration test for EVM circuit - pub static ref EVM_CIRCUIT_TEST: TokioMutex>> = + pub static ref EVM_CIRCUIT_TEST: TokioMutex>> = TokioMutex::new(IntegrationTest::new("EVM", EVM_CIRCUIT_DEGREE)); /// Integration test for State circuit - pub static ref STATE_CIRCUIT_TEST: TokioMutex>> = + pub static ref STATE_CIRCUIT_TEST: TokioMutex>> = TokioMutex::new(IntegrationTest::new("State", STATE_CIRCUIT_DEGREE)); /// Integration test for State circuit - pub static ref TX_CIRCUIT_TEST: TokioMutex>> = + pub static ref TX_CIRCUIT_TEST: TokioMutex>> = TokioMutex::new(IntegrationTest::new("Tx", TX_CIRCUIT_DEGREE)); /// Integration test for Bytecode circuit - pub static ref BYTECODE_CIRCUIT_TEST: TokioMutex>> = + pub static ref BYTECODE_CIRCUIT_TEST: TokioMutex>> = TokioMutex::new(IntegrationTest::new("Bytecode", BYTECODE_CIRCUIT_DEGREE)); /// Integration test for Copy circuit - pub static ref COPY_CIRCUIT_TEST: TokioMutex>> = + pub static ref COPY_CIRCUIT_TEST: TokioMutex>> = TokioMutex::new(IntegrationTest::new("Copy", COPY_CIRCUIT_DEGREE)); /// Integration test for Keccak circuit - pub static ref KECCAK_CIRCUIT_TEST: TokioMutex>> = + pub static ref KECCAK_CIRCUIT_TEST: TokioMutex>> = TokioMutex::new(IntegrationTest::new("Keccak", KECCAK_CIRCUIT_DEGREE)); /// Integration test for Copy circuit @@ -123,7 +123,7 @@ lazy_static! { TokioMutex::new(IntegrationTest::new("Super", SUPER_CIRCUIT_DEGREE)); /// Integration test for Exp circuit - pub static ref EXP_CIRCUIT_TEST: TokioMutex>> = + pub static ref EXP_CIRCUIT_TEST: TokioMutex>> = TokioMutex::new(IntegrationTest::new("Exp", EXP_CIRCUIT_DEGREE)); } diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 0324f1970b..17cf8a03fb 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -52,6 +52,6 @@ serde_json = "1.0.78" [features] default = [] -test = ["ethers-signers", "mock"] +test = ["ethers-signers", "mock", "bus-mapping/test"] test-circuits = [] warn-unimplemented = ["eth-types/warn-unimplemented"] diff --git a/zkevm-circuits/src/bytecode_circuit.rs b/zkevm-circuits/src/bytecode_circuit.rs index a9db23040f..297e3a1622 100644 --- a/zkevm-circuits/src/bytecode_circuit.rs +++ b/zkevm-circuits/src/bytecode_circuit.rs @@ -4,7 +4,12 @@ pub mod bytecode_unroller; /// Bytecode circuit pub mod circuit; +pub(crate) mod param; + +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +mod dev; /// Bytecode circuit tester #[cfg(any(feature = "test", test))] -pub mod dev; -pub(crate) mod param; +mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use dev::BytecodeCircuit as TestBytecodeCircuit; diff --git a/zkevm-circuits/src/bytecode_circuit/circuit.rs b/zkevm-circuits/src/bytecode_circuit/circuit.rs index 41afc5bde4..87ea2351f5 100644 --- a/zkevm-circuits/src/bytecode_circuit/circuit.rs +++ b/zkevm-circuits/src/bytecode_circuit/circuit.rs @@ -22,9 +22,6 @@ use super::{ param::PUSH_TABLE_WIDTH, }; -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; - #[derive(Clone, Debug)] /// Bytecode circuit configuration pub struct BytecodeCircuitConfig { @@ -804,294 +801,3 @@ impl SubCircuit for BytecodeCircuit { config.assign_internal(layouter, self.size, &self.bytecodes, challenges, false) } } - -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -impl Circuit for BytecodeCircuit { - type Config = (BytecodeCircuitConfig, Challenges); - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let bytecode_table = BytecodeTable::construct(meta); - let keccak_table = KeccakTable::construct(meta); - let challenges = Challenges::construct(meta); - - let config = { - let challenges = challenges.exprs(meta); - BytecodeCircuitConfig::new( - meta, - BytecodeCircuitConfigArgs { - bytecode_table, - keccak_table, - challenges, - }, - ) - }; - - (config, challenges) - } - - fn synthesize( - &self, - (config, challenges): Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let challenges = challenges.values(&mut layouter); - - config.keccak_table.dev_load( - &mut layouter, - self.bytecodes.iter().map(|b| &b.bytes), - &challenges, - )?; - self.synthesize_sub(&config, &challenges, &mut layouter)?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - bytecode_circuit::{bytecode_unroller::BytecodeRow, dev::test_bytecode_circuit_unrolled}, - util::{is_push, keccak}, - }; - use bus_mapping::evm::OpcodeId; - use eth_types::{Bytecode, Word}; - use halo2_proofs::halo2curves::bn256::Fr; - - /// Verify unrolling code - #[test] - fn bytecode_unrolling() { - let k = 10; - let mut rows = vec![]; - let mut bytecode = Bytecode::default(); - // First add all non-push bytes, which should all be seen as code - for byte in 0u8..=255u8 { - if !is_push(byte) { - bytecode.write(byte, true); - rows.push(BytecodeRow { - code_hash: Word::zero(), - tag: Fr::from(BytecodeFieldTag::Byte as u64), - index: Fr::from(rows.len() as u64), - is_code: Fr::from(true as u64), - value: Fr::from(byte as u64), - }); - } - } - // Now add the different push ops - for n in 1..=32 { - let data_byte = OpcodeId::PUSH32.as_u8(); - bytecode.push( - n, - Word::from_little_endian(&vec![data_byte; n as usize][..]), - ); - rows.push(BytecodeRow { - code_hash: Word::zero(), - tag: Fr::from(BytecodeFieldTag::Byte as u64), - index: Fr::from(rows.len() as u64), - is_code: Fr::from(true as u64), - value: Fr::from(OpcodeId::PUSH1.as_u64() + ((n - 1) as u64)), - }); - for _ in 0..n { - rows.push(BytecodeRow { - code_hash: Word::zero(), - tag: Fr::from(BytecodeFieldTag::Byte as u64), - index: Fr::from(rows.len() as u64), - is_code: Fr::from(false as u64), - value: Fr::from(data_byte as u64), - }); - } - } - // Set the code_hash of the complete bytecode in the rows - let code_hash = keccak(&bytecode.to_vec()[..]); - for row in rows.iter_mut() { - row.code_hash = code_hash; - } - rows.insert( - 0, - BytecodeRow { - code_hash, - tag: Fr::from(BytecodeFieldTag::Header as u64), - index: Fr::zero(), - is_code: Fr::zero(), - value: Fr::from(bytecode.to_vec().len() as u64), - }, - ); - // Unroll the bytecode - let unrolled = unroll(bytecode.to_vec()); - // Check if the bytecode was unrolled correctly - assert_eq!( - UnrolledBytecode { - bytes: bytecode.to_vec(), - rows, - }, - unrolled, - ); - // Verify the unrolling in the circuit - test_bytecode_circuit_unrolled::(k, vec![unrolled], true); - } - - /// Tests a fully empty circuit - #[test] - fn bytecode_empty() { - let k = 9; - test_bytecode_circuit_unrolled::(k, vec![unroll(vec![])], true); - } - - #[test] - fn bytecode_simple() { - let k = 9; - let bytecodes = vec![unroll(vec![7u8]), unroll(vec![6u8]), unroll(vec![5u8])]; - test_bytecode_circuit_unrolled::(k, bytecodes, true); - } - - /// Tests a fully full circuit - #[test] - fn bytecode_full() { - let k = 9; - test_bytecode_circuit_unrolled::(k, vec![unroll(vec![7u8; 2usize.pow(k) - 8])], true); - } - - #[test] - fn bytecode_last_row_with_byte() { - let k = 9; - // Last row must be a padding row, so we have one row less for actual bytecode - test_bytecode_circuit_unrolled::(k, vec![unroll(vec![7u8; 2usize.pow(k) - 7])], false); - } - - /// Tests a circuit with incomplete bytecode - #[test] - fn bytecode_incomplete() { - let k = 9; - test_bytecode_circuit_unrolled::(k, vec![unroll(vec![7u8; 2usize.pow(k) + 1])], false); - } - - /// Tests multiple bytecodes in a single circuit - #[test] - fn bytecode_push() { - let k = 9; - test_bytecode_circuit_unrolled::( - k, - vec![ - unroll(vec![]), - unroll(vec![OpcodeId::PUSH32.as_u8()]), - unroll(vec![OpcodeId::PUSH32.as_u8(), OpcodeId::ADD.as_u8()]), - unroll(vec![OpcodeId::ADD.as_u8(), OpcodeId::PUSH32.as_u8()]), - unroll(vec![ - OpcodeId::ADD.as_u8(), - OpcodeId::PUSH32.as_u8(), - OpcodeId::ADD.as_u8(), - ]), - ], - true, - ); - } - - /// Test invalid code_hash data - #[test] - fn bytecode_invalid_hash_data() { - let k = 9; - let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode); - test_bytecode_circuit_unrolled::(k, vec![unrolled.clone()], true); - // Change the code_hash on the first position (header row) - { - let mut invalid = unrolled; - invalid.rows[0].code_hash += Word::one(); - trace!("bytecode_invalid_hash_data: Change the code_hash on the first position"); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - // TODO: other rows code_hash are ignored by the witness generation, to - // test other rows invalid code_hash, we would need to inject an evil - // witness. - } - - /// Test invalid index - #[test] - #[ignore] - fn bytecode_invalid_index() { - let k = 9; - let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode); - test_bytecode_circuit_unrolled::(k, vec![unrolled.clone()], true); - // Start the index at 1 - { - let mut invalid = unrolled.clone(); - for row in invalid.rows.iter_mut() { - row.index += Fr::one(); - } - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - // Don't increment an index once - { - let mut invalid = unrolled; - invalid.rows.last_mut().unwrap().index -= Fr::one(); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - } - - /// Test invalid byte data - #[test] - fn bytecode_invalid_byte_data() { - let k = 9; - let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode); - test_bytecode_circuit_unrolled::(k, vec![unrolled.clone()], true); - // Change the first byte - { - let mut invalid = unrolled.clone(); - invalid.rows[1].value = Fr::from(9u64); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - // Change a byte on another position - { - let mut invalid = unrolled.clone(); - invalid.rows[5].value = Fr::from(6u64); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - // Set a byte value out of range - { - let mut invalid = unrolled; - invalid.rows[3].value = Fr::from(256u64); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - } - - /// Test invalid is_code data - #[test] - fn bytecode_invalid_is_code() { - let k = 9; - let bytecode = vec![ - OpcodeId::ADD.as_u8(), - OpcodeId::PUSH1.as_u8(), - OpcodeId::PUSH1.as_u8(), - OpcodeId::SUB.as_u8(), - OpcodeId::PUSH7.as_u8(), - OpcodeId::ADD.as_u8(), - OpcodeId::PUSH6.as_u8(), - ]; - let unrolled = unroll(bytecode); - test_bytecode_circuit_unrolled::(k, vec![unrolled.clone()], true); - // Mark the 3rd byte as code (is push data from the first PUSH1) - { - let mut invalid = unrolled.clone(); - invalid.rows[3].is_code = Fr::one(); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - // Mark the 4rd byte as data (is code) - { - let mut invalid = unrolled.clone(); - invalid.rows[4].is_code = Fr::zero(); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - // Mark the 7th byte as code (is data for the PUSH7) - { - let mut invalid = unrolled; - invalid.rows[7].is_code = Fr::one(); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - } -} diff --git a/zkevm-circuits/src/bytecode_circuit/dev.rs b/zkevm-circuits/src/bytecode_circuit/dev.rs index d4610c32d0..dd33c563ab 100644 --- a/zkevm-circuits/src/bytecode_circuit/dev.rs +++ b/zkevm-circuits/src/bytecode_circuit/dev.rs @@ -1,49 +1,57 @@ -use super::{ - bytecode_unroller::{unroll, UnrolledBytecode}, - circuit::BytecodeCircuit, -}; +pub use super::circuit::BytecodeCircuit; +use crate::{ + bytecode_circuit::circuit::{BytecodeCircuitConfig, BytecodeCircuitConfigArgs}, + table::{BytecodeTable, KeccakTable}, + util::{Challenges, SubCircuit, SubCircuitConfig}, +}; use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, +}; -use halo2_proofs::dev::MockProver; -use log::error; +impl Circuit for BytecodeCircuit { + type Config = (BytecodeCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; -impl BytecodeCircuit { - /// Verify that the selected bytecode fulfills the circuit - pub fn verify_raw(k: u32, bytecodes: Vec>) { - let unrolled: Vec<_> = bytecodes.iter().map(|b| unroll(b.clone())).collect(); - Self::verify(k, unrolled, true); + fn without_witnesses(&self) -> Self { + Self::default() } - pub(crate) fn verify(k: u32, bytecodes: Vec>, success: bool) { - let circuit = BytecodeCircuit::::new(bytecodes, 2usize.pow(k)); - - let prover = MockProver::::run(k, &circuit, Vec::new()).unwrap(); - let result = prover.verify(); - if let Err(failures) = &result { - for failure in failures.iter() { - error!("{}", failure); - } - } - assert_eq!(result.is_ok(), success); + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let bytecode_table = BytecodeTable::construct(meta); + let keccak_table = KeccakTable::construct(meta); + let challenges = Challenges::construct(meta); + + let config = { + let challenges = challenges.exprs(meta); + BytecodeCircuitConfig::new( + meta, + BytecodeCircuitConfigArgs { + bytecode_table, + keccak_table, + challenges, + }, + ) + }; + + (config, challenges) } -} -/// Test bytecode circuit with unrolled bytecode -pub fn test_bytecode_circuit_unrolled( - k: u32, - bytecodes: Vec>, - success: bool, -) { - let circuit = BytecodeCircuit::::new(bytecodes, 2usize.pow(k)); - - let prover = MockProver::::run(k, &circuit, Vec::new()).unwrap(); - let result = prover.verify_par(); - if let Err(failures) = &result { - for failure in failures.iter() { - error!("{}", failure); - } + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + + config.keccak_table.dev_load( + &mut layouter, + self.bytecodes.iter().map(|b| &b.bytes), + &challenges, + )?; + self.synthesize_sub(&config, &challenges, &mut layouter)?; + Ok(()) } - let error_msg = if success { "valid" } else { "invalid" }; - assert_eq!(result.is_ok(), success, "proof must be {}", error_msg); } diff --git a/zkevm-circuits/src/bytecode_circuit/test.rs b/zkevm-circuits/src/bytecode_circuit/test.rs new file mode 100644 index 0000000000..85ec8f80df --- /dev/null +++ b/zkevm-circuits/src/bytecode_circuit/test.rs @@ -0,0 +1,283 @@ +#![allow(unused_imports)] +use crate::{ + bytecode_circuit::{bytecode_unroller::*, circuit::BytecodeCircuit}, + table::BytecodeFieldTag, + util::{is_push, keccak, Challenges, SubCircuit}, +}; +use bus_mapping::evm::OpcodeId; +use eth_types::{Bytecode, Field, Word}; +use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; +use log::error; + +impl BytecodeCircuit { + /// Verify that the selected bytecode fulfills the circuit + pub fn verify_raw(k: u32, bytecodes: Vec>) { + let unrolled: Vec<_> = bytecodes.iter().map(|b| unroll(b.clone())).collect(); + Self::verify(k, unrolled, true); + } + + pub(crate) fn verify(k: u32, bytecodes: Vec>, success: bool) { + let circuit = BytecodeCircuit::::new(bytecodes, 2usize.pow(k)); + + let prover = MockProver::::run(k, &circuit, Vec::new()).unwrap(); + let result = prover.verify(); + if let Err(failures) = &result { + for failure in failures.iter() { + error!("{}", failure); + } + } + assert_eq!(result.is_ok(), success); + } +} + +/// Test bytecode circuit with unrolled bytecode +pub fn test_bytecode_circuit_unrolled( + k: u32, + bytecodes: Vec>, + success: bool, +) { + let circuit = BytecodeCircuit::::new(bytecodes, 2usize.pow(k)); + + let prover = MockProver::::run(k, &circuit, Vec::new()).unwrap(); + let result = prover.verify_par(); + if let Err(failures) = &result { + for failure in failures.iter() { + error!("{}", failure); + } + } + let error_msg = if success { "valid" } else { "invalid" }; + assert_eq!(result.is_ok(), success, "proof must be {}", error_msg); +} + +/// Verify unrolling code +#[test] +fn bytecode_unrolling() { + let k = 10; + let mut rows = vec![]; + let mut bytecode = Bytecode::default(); + // First add all non-push bytes, which should all be seen as code + for byte in 0u8..=255u8 { + if !is_push(byte) { + bytecode.write(byte, true); + rows.push(BytecodeRow { + code_hash: Word::zero(), + tag: Fr::from(BytecodeFieldTag::Byte as u64), + index: Fr::from(rows.len() as u64), + is_code: Fr::from(true as u64), + value: Fr::from(byte as u64), + }); + } + } + // Now add the different push ops + for n in 1..=32 { + let data_byte = OpcodeId::PUSH32.as_u8(); + bytecode.push( + n, + Word::from_little_endian(&vec![data_byte; n as usize][..]), + ); + rows.push(BytecodeRow { + code_hash: Word::zero(), + tag: Fr::from(BytecodeFieldTag::Byte as u64), + index: Fr::from(rows.len() as u64), + is_code: Fr::from(true as u64), + value: Fr::from(OpcodeId::PUSH1.as_u64() + ((n - 1) as u64)), + }); + for _ in 0..n { + rows.push(BytecodeRow { + code_hash: Word::zero(), + tag: Fr::from(BytecodeFieldTag::Byte as u64), + index: Fr::from(rows.len() as u64), + is_code: Fr::from(false as u64), + value: Fr::from(data_byte as u64), + }); + } + } + // Set the code_hash of the complete bytecode in the rows + let code_hash = keccak(&bytecode.to_vec()[..]); + for row in rows.iter_mut() { + row.code_hash = code_hash; + } + rows.insert( + 0, + BytecodeRow { + code_hash, + tag: Fr::from(BytecodeFieldTag::Header as u64), + index: Fr::zero(), + is_code: Fr::zero(), + value: Fr::from(bytecode.to_vec().len() as u64), + }, + ); + // Unroll the bytecode + let unrolled = unroll(bytecode.to_vec()); + // Check if the bytecode was unrolled correctly + assert_eq!( + UnrolledBytecode { + bytes: bytecode.to_vec(), + rows, + }, + unrolled, + ); + // Verify the unrolling in the circuit + test_bytecode_circuit_unrolled::(k, vec![unrolled], true); +} + +/// Tests a fully empty circuit +#[test] +fn bytecode_empty() { + let k = 9; + test_bytecode_circuit_unrolled::(k, vec![unroll(vec![])], true); +} + +#[test] +fn bytecode_simple() { + let k = 9; + let bytecodes = vec![unroll(vec![7u8]), unroll(vec![6u8]), unroll(vec![5u8])]; + test_bytecode_circuit_unrolled::(k, bytecodes, true); +} + +/// Tests a fully full circuit +#[test] +fn bytecode_full() { + let k = 9; + test_bytecode_circuit_unrolled::(k, vec![unroll(vec![7u8; 2usize.pow(k) - 8])], true); +} + +#[test] +fn bytecode_last_row_with_byte() { + let k = 9; + // Last row must be a padding row, so we have one row less for actual bytecode + test_bytecode_circuit_unrolled::(k, vec![unroll(vec![7u8; 2usize.pow(k) - 7])], false); +} + +/// Tests a circuit with incomplete bytecode +#[test] +fn bytecode_incomplete() { + let k = 9; + test_bytecode_circuit_unrolled::(k, vec![unroll(vec![7u8; 2usize.pow(k) + 1])], false); +} + +/// Tests multiple bytecodes in a single circuit +#[test] +fn bytecode_push() { + let k = 9; + test_bytecode_circuit_unrolled::( + k, + vec![ + unroll(vec![]), + unroll(vec![OpcodeId::PUSH32.as_u8()]), + unroll(vec![OpcodeId::PUSH32.as_u8(), OpcodeId::ADD.as_u8()]), + unroll(vec![OpcodeId::ADD.as_u8(), OpcodeId::PUSH32.as_u8()]), + unroll(vec![ + OpcodeId::ADD.as_u8(), + OpcodeId::PUSH32.as_u8(), + OpcodeId::ADD.as_u8(), + ]), + ], + true, + ); +} + +/// Test invalid code_hash data +#[test] +fn bytecode_invalid_hash_data() { + let k = 9; + let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; + let unrolled = unroll(bytecode); + test_bytecode_circuit_unrolled::(k, vec![unrolled.clone()], true); + // Change the code_hash on the first position (header row) + { + let mut invalid = unrolled; + invalid.rows[0].code_hash += Word::one(); + log::trace!("bytecode_invalid_hash_data: Change the code_hash on the first position"); + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } + // TODO: other rows code_hash are ignored by the witness generation, to + // test other rows invalid code_hash, we would need to inject an evil + // witness. +} + +/// Test invalid index +#[test] +#[ignore] +fn bytecode_invalid_index() { + let k = 9; + let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; + let unrolled = unroll(bytecode); + test_bytecode_circuit_unrolled::(k, vec![unrolled.clone()], true); + // Start the index at 1 + { + let mut invalid = unrolled.clone(); + for row in invalid.rows.iter_mut() { + row.index += Fr::one(); + } + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } + // Don't increment an index once + { + let mut invalid = unrolled; + invalid.rows.last_mut().unwrap().index -= Fr::one(); + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } +} + +/// Test invalid byte data +#[test] +fn bytecode_invalid_byte_data() { + let k = 9; + let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; + let unrolled = unroll(bytecode); + test_bytecode_circuit_unrolled::(k, vec![unrolled.clone()], true); + // Change the first byte + { + let mut invalid = unrolled.clone(); + invalid.rows[1].value = Fr::from(9u64); + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } + // Change a byte on another position + { + let mut invalid = unrolled.clone(); + invalid.rows[5].value = Fr::from(6u64); + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } + // Set a byte value out of range + { + let mut invalid = unrolled; + invalid.rows[3].value = Fr::from(256u64); + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } +} + +/// Test invalid is_code data +#[test] +fn bytecode_invalid_is_code() { + let k = 9; + let bytecode = vec![ + OpcodeId::ADD.as_u8(), + OpcodeId::PUSH1.as_u8(), + OpcodeId::PUSH1.as_u8(), + OpcodeId::SUB.as_u8(), + OpcodeId::PUSH7.as_u8(), + OpcodeId::ADD.as_u8(), + OpcodeId::PUSH6.as_u8(), + ]; + let unrolled = unroll(bytecode); + test_bytecode_circuit_unrolled::(k, vec![unrolled.clone()], true); + // Mark the 3rd byte as code (is push data from the first PUSH1) + { + let mut invalid = unrolled.clone(); + invalid.rows[3].is_code = Fr::one(); + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } + // Mark the 4rd byte as data (is code) + { + let mut invalid = unrolled.clone(); + invalid.rows[4].is_code = Fr::zero(); + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } + // Mark the 7th byte as code (is data for the PUSH7) + { + let mut invalid = unrolled; + invalid.rows[7].is_code = Fr::one(); + test_bytecode_circuit_unrolled::(k, vec![invalid], false); + } +} diff --git a/zkevm-circuits/src/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs index 03e556cd28..57a83fded2 100644 --- a/zkevm-circuits/src/copy_circuit.rs +++ b/zkevm-circuits/src/copy_circuit.rs @@ -1,9 +1,18 @@ //! The Copy circuit implements constraints and lookups for read-write steps for //! copied bytes while execution opcodes such as CALLDATACOPY, CODECOPY, LOGS, //! etc. +pub(crate) mod util; -use bus_mapping::circuit_input_builder::{CopyDataType, CopyEvent, NumberOrHash}; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +mod dev; +#[cfg(any(feature = "test", test))] +mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use dev::CopyCircuit as TestCopyCircuit; + +use bus_mapping::circuit_input_builder::{CopyDataType, CopyEvent}; use eth_types::{Field, Word}; + use gadgets::{ binary_number::BinaryNumberChip, less_than::{LtChip, LtConfig, LtInstruction}, @@ -18,7 +27,7 @@ use itertools::Itertools; use std::{collections::HashMap, marker::PhantomData}; use crate::{ - evm_circuit::util::{constraint_builder::BaseConstraintBuilder, rlc}, + evm_circuit::util::constraint_builder::BaseConstraintBuilder, table::{ BytecodeFieldTag, BytecodeTable, CopyTable, LookupTable, RwTable, RwTableTag, TxContextFieldTag, TxTable, @@ -28,30 +37,6 @@ use crate::{ witness::{Bytecode, RwMap, Transaction}, }; -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -use halo2_proofs::{ - circuit::SimpleFloorPlanner, - plonk::{Challenge, Circuit}, -}; - -/// Encode the type `NumberOrHash` into a field element -pub fn number_or_hash_to_field(v: &NumberOrHash, challenge: Value) -> Value { - match v { - NumberOrHash::Number(n) => Value::known(F::from(*n as u64)), - NumberOrHash::Hash(h) => { - // since code hash in the bytecode table is represented in - // the little-endian form, we reverse the big-endian bytes - // of H256. - let le_bytes = { - let mut b = h.to_fixed_bytes(); - b.reverse(); - b - }; - challenge.map(|challenge| rlc::value(&le_bytes, challenge)) - } - } -} - /// The rw table shared between evm circuit and state circuit #[derive(Clone, Debug)] pub struct CopyCircuitConfig { @@ -785,427 +770,6 @@ impl SubCircuit for CopyCircuit { } } -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -impl Circuit for CopyCircuit { - type Config = (CopyCircuitConfig, Challenges); - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let tx_table = TxTable::construct(meta); - let rw_table = RwTable::construct(meta); - let bytecode_table = BytecodeTable::construct(meta); - let q_enable = meta.fixed_column(); - let copy_table = CopyTable::construct(meta, q_enable); - let challenges = Challenges::construct(meta); - let challenge_exprs = challenges.exprs(meta); - - ( - CopyCircuitConfig::new( - meta, - CopyCircuitConfigArgs { - tx_table, - rw_table, - bytecode_table, - copy_table, - q_enable, - challenges: challenge_exprs, - }, - ), - challenges, - ) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), halo2_proofs::plonk::Error> { - let challenge_values = config.1.values(&mut layouter); - - config.0.tx_table.load( - &mut layouter, - &self.external_data.txs, - self.external_data.max_txs, - self.external_data.max_calldata, - &challenge_values, - )?; - - config.0.rw_table.load( - &mut layouter, - &self.external_data.rws.table_assignments(), - self.external_data.max_rws, - challenge_values.evm_word(), - )?; - - config.0.bytecode_table.load( - &mut layouter, - self.external_data.bytecodes.values(), - &challenge_values, - )?; - self.synthesize_sub(&config.0, &challenge_values, &mut layouter) - } -} - -/// Dev helpers -#[cfg(any(feature = "test", test))] -pub mod dev { - use crate::{copy_circuit::*, witness::Block}; - use halo2_proofs::dev::{MockProver, VerifyFailure}; - - /// Test copy circuit from copy events and test data - pub fn test_copy_circuit( - k: u32, - copy_events: Vec, - max_copy_rows: usize, - external_data: ExternalData, - ) -> Result<(), Vec> { - let circuit = - CopyCircuit::::new_with_external_data(copy_events, max_copy_rows, external_data); - - let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); - prover.verify_par() - } - - /// Test copy circuit with the provided block witness - pub fn test_copy_circuit_from_block( - k: u32, - block: Block, - ) -> Result<(), Vec> { - test_copy_circuit::( - k, - block.copy_events, - block.circuits_params.max_copy_rows, - ExternalData { - max_txs: block.circuits_params.max_txs, - max_calldata: block.circuits_params.max_calldata, - txs: block.txs, - max_rws: block.circuits_params.max_rws, - rws: block.rws, - bytecodes: block.bytecodes, - }, - ) - } -} - -#[cfg(test)] -mod tests { - use super::dev::test_copy_circuit_from_block; - use crate::{ - copy_circuit::CopyCircuit, - evm_circuit::{test::rand_bytes, witness::block_convert}, - }; - use bus_mapping::{ - circuit_input_builder::{CircuitInputBuilder, CircuitsParams}, - evm::{gen_sha3_code, MemoryKind}, - mock::BlockData, - }; - use eth_types::{bytecode, geth_types::GethData, ToWord, Word}; - use halo2_proofs::{ - dev::{MockProver, VerifyFailure}, - halo2curves::bn256::Fr, - }; - use mock::{test_ctx::helpers::account_0_code_account_1_no_code, TestContext, MOCK_ACCOUNTS}; - use pretty_assertions::assert_eq; - - fn gen_calldatacopy_data() -> CircuitInputBuilder { - let length = 0x0fffusize; - let code = bytecode! { - PUSH32(Word::from(length)) - PUSH32(Word::from(0x00)) - PUSH32(Word::from(0x00)) - CALLDATACOPY - STOP - }; - let calldata = rand_bytes(length); - let test_ctx = TestContext::<2, 1>::new( - None, - account_0_code_account_1_no_code(code), - |mut txs, accs| { - txs[0] - .from(accs[1].address) - .to(accs[0].address) - .input(calldata.into()); - }, - |block, _txs| block.number(0xcafeu64), - ) - .unwrap(); - let block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data_with_params( - block.clone(), - CircuitsParams { - max_rws: 8192, - max_copy_rows: 8192 + 2, - max_calldata: 5000, - ..Default::default() - }, - ) - .new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - builder - } - - fn gen_codecopy_data() -> CircuitInputBuilder { - let code = bytecode! { - PUSH32(Word::from(0x20)) - PUSH32(Word::from(0x00)) - PUSH32(Word::from(0x00)) - CODECOPY - STOP - }; - let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); - let block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - builder - } - - fn gen_extcodecopy_data() -> CircuitInputBuilder { - let external_address = MOCK_ACCOUNTS[0]; - let code = bytecode! { - PUSH1(0x30usize) - PUSH1(0x0usize) - PUSH1(0x0usize) - PUSH20(external_address.to_word()) - EXTCODECOPY - STOP - }; - let code_ext = rand_bytes(0x0fffusize); - let test_ctx = TestContext::<3, 1>::new( - None, - |accs| { - accs[0].address(MOCK_ACCOUNTS[1]).code(code.clone()); - - accs[1].address(external_address).code(code_ext.clone()); - - accs[2] - .address(MOCK_ACCOUNTS[2]) - .balance(Word::from(1u64 << 20)); - }, - |mut txs, accs| { - txs[0].to(accs[0].address).from(accs[2].address); - }, - |block, _tx| block.number(0xcafeu64), - ) - .unwrap(); - let block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - builder - } - - fn gen_sha3_data() -> CircuitInputBuilder { - let (code, _) = gen_sha3_code(0x20, 0x200, MemoryKind::EqualToSize); - let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); - let block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data_with_params( - block.clone(), - CircuitsParams { - max_rws: 2000, - max_copy_rows: 0x200 * 2 + 2, - ..Default::default() - }, - ) - .new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - builder - } - - fn gen_tx_log_data() -> CircuitInputBuilder { - let code = bytecode! { - PUSH32(200) // value - PUSH32(0) // offset - MSTORE - PUSH32(Word::MAX) // topic - PUSH1(32) // length - PUSH1(0) // offset - LOG1 - STOP - }; - let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); - let block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - builder - } - - #[test] - fn copy_circuit_valid_calldatacopy() { - let builder = gen_calldatacopy_data(); - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); - } - - #[test] - fn copy_circuit_valid_codecopy() { - let builder = gen_codecopy_data(); - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - assert_eq!(test_copy_circuit_from_block(10, block), Ok(())); - } - - #[test] - fn copy_circuit_valid_extcodecopy() { - let builder = gen_extcodecopy_data(); - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); - } - - #[test] - fn copy_circuit_valid_sha3() { - let builder = gen_sha3_data(); - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); - } - - #[test] - fn copy_circuit_valid_tx_log() { - let builder = gen_tx_log_data(); - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - assert_eq!(test_copy_circuit_from_block(10, block), Ok(())); - } - - #[test] - fn copy_circuit_invalid_calldatacopy() { - let mut builder = gen_calldatacopy_data(); - - // modify first byte of first copy event - builder.block.copy_events[0].bytes[0].0 = - builder.block.copy_events[0].bytes[0].0.wrapping_add(1); - - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - - assert_error_matches( - test_copy_circuit_from_block(14, block), - vec!["Memory lookup", "Tx calldata lookup"], - ); - } - - #[test] - fn copy_circuit_invalid_codecopy() { - let mut builder = gen_codecopy_data(); - - // modify first byte of first copy event - builder.block.copy_events[0].bytes[0].0 = - builder.block.copy_events[0].bytes[0].0.wrapping_add(1); - - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - - assert_error_matches( - test_copy_circuit_from_block(10, block), - vec!["Memory lookup", "Bytecode lookup"], - ); - } - - #[test] - fn copy_circuit_invalid_extcodecopy() { - let mut builder = gen_extcodecopy_data(); - - // modify first byte of first copy event - builder.block.copy_events[0].bytes[0].0 = - builder.block.copy_events[0].bytes[0].0.wrapping_add(1); - - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - - assert_error_matches( - test_copy_circuit_from_block(14, block), - vec!["Memory lookup", "Bytecode lookup"], - ); - } - - #[test] - fn copy_circuit_invalid_sha3() { - let mut builder = gen_sha3_data(); - - // modify first byte of first copy event - builder.block.copy_events[0].bytes[0].0 = - builder.block.copy_events[0].bytes[0].0.wrapping_add(1); - - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - - assert_error_matches( - test_copy_circuit_from_block(14, block), - vec!["Memory lookup"], - ); - } - - #[test] - fn copy_circuit_invalid_tx_log() { - let mut builder = gen_tx_log_data(); - - // modify first byte of first copy event - builder.block.copy_events[0].bytes[0].0 = - builder.block.copy_events[0].bytes[0].0.wrapping_add(1); - - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - - assert_error_matches( - test_copy_circuit_from_block(10, block), - vec!["Memory lookup", "TxLog lookup"], - ); - } - - #[test] - fn variadic_size_check() { - let builder = gen_tx_log_data(); - let block1 = block_convert::(&builder.block, &builder.code_db).unwrap(); - - let block: GethData = TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b) - .unwrap() - .into(); - let mut builder = - BlockData::new_from_geth_data_with_params(block.clone(), CircuitsParams::default()) - .new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - let block2 = block_convert::(&builder.block, &builder.code_db).unwrap(); - - let circuit = - CopyCircuit::::new(block1.copy_events, block1.circuits_params.max_copy_rows); - let prover1 = MockProver::::run(14, &circuit, vec![]).unwrap(); - - let circuit = CopyCircuit::::new( - block2.copy_events.clone(), - block2.circuits_params.max_copy_rows, - ); - let prover2 = MockProver::::run(14, &circuit, vec![]).unwrap(); - - assert_eq!(prover1.fixed(), prover2.fixed()); - assert_eq!(prover1.permutation(), prover2.permutation()); - } - - fn assert_error_matches(result: Result<(), Vec>, names: Vec<&str>) { - let errors = result.expect_err("result is not an error"); - assert_eq!(errors.len(), names.len(), "{:?}", errors); - for i in 0..names.len() { - match &errors[i] { - VerifyFailure::Lookup { - name: lookup_name, .. - } => { - assert_eq!(lookup_name, &names[i]) - } - VerifyFailure::ConstraintNotSatisfied { .. } => panic!(), - VerifyFailure::CellNotAssigned { .. } => panic!(), - VerifyFailure::ConstraintPoisoned { .. } => panic!(), - VerifyFailure::Permutation { .. } => panic!(), - } - } - } -} - #[cfg(test)] mod copy_circuit_stats { use crate::{ diff --git a/zkevm-circuits/src/copy_circuit/dev.rs b/zkevm-circuits/src/copy_circuit/dev.rs new file mode 100644 index 0000000000..9671989777 --- /dev/null +++ b/zkevm-circuits/src/copy_circuit/dev.rs @@ -0,0 +1,76 @@ +pub use super::CopyCircuit; + +use crate::{ + copy_circuit::{CopyCircuitConfig, CopyCircuitConfigArgs}, + table::{BytecodeTable, CopyTable, RwTable, TxTable}, + util::{Challenges, SubCircuit, SubCircuitConfig}, +}; +use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Challenge, Circuit, ConstraintSystem, Error}, +}; + +impl Circuit for CopyCircuit { + type Config = (CopyCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let tx_table = TxTable::construct(meta); + let rw_table = RwTable::construct(meta); + let bytecode_table = BytecodeTable::construct(meta); + let q_enable = meta.fixed_column(); + let copy_table = CopyTable::construct(meta, q_enable); + let challenges = Challenges::construct(meta); + let challenge_exprs = challenges.exprs(meta); + + ( + CopyCircuitConfig::new( + meta, + CopyCircuitConfigArgs { + tx_table, + rw_table, + bytecode_table, + copy_table, + q_enable, + challenges: challenge_exprs, + }, + ), + challenges, + ) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenge_values = config.1.values(&mut layouter); + + config.0.tx_table.load( + &mut layouter, + &self.external_data.txs, + self.external_data.max_txs, + self.external_data.max_calldata, + &challenge_values, + )?; + + config.0.rw_table.load( + &mut layouter, + &self.external_data.rws.table_assignments(), + self.external_data.max_rws, + challenge_values.evm_word(), + )?; + + config.0.bytecode_table.load( + &mut layouter, + self.external_data.bytecodes.values(), + &challenge_values, + )?; + self.synthesize_sub(&config.0, &challenge_values, &mut layouter) + } +} diff --git a/zkevm-circuits/src/copy_circuit/test.rs b/zkevm-circuits/src/copy_circuit/test.rs new file mode 100644 index 0000000000..4fba9ce756 --- /dev/null +++ b/zkevm-circuits/src/copy_circuit/test.rs @@ -0,0 +1,345 @@ +#![allow(unused_imports)] + +use crate::{ + copy_circuit::*, + evm_circuit::{test::rand_bytes, witness::block_convert}, + witness::Block, +}; +use bus_mapping::{ + circuit_input_builder::{CircuitInputBuilder, CircuitsParams}, + evm::{gen_sha3_code, MemoryKind}, + mock::BlockData, +}; +use eth_types::{bytecode, geth_types::GethData, ToWord, Word}; +use halo2_proofs::{ + dev::{MockProver, VerifyFailure}, + halo2curves::bn256::Fr, +}; +use mock::{test_ctx::helpers::account_0_code_account_1_no_code, TestContext, MOCK_ACCOUNTS}; + +/// Test copy circuit from copy events and test data +pub fn test_copy_circuit( + k: u32, + copy_events: Vec, + max_copy_rows: usize, + external_data: ExternalData, +) -> Result<(), Vec> { + let circuit = + CopyCircuit::::new_with_external_data(copy_events, max_copy_rows, external_data); + + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); + prover.verify_par() +} + +/// Test copy circuit with the provided block witness +pub fn test_copy_circuit_from_block( + k: u32, + block: Block, +) -> Result<(), Vec> { + test_copy_circuit::( + k, + block.copy_events, + block.circuits_params.max_copy_rows, + ExternalData { + max_txs: block.circuits_params.max_txs, + max_calldata: block.circuits_params.max_calldata, + txs: block.txs, + max_rws: block.circuits_params.max_rws, + rws: block.rws, + bytecodes: block.bytecodes, + }, + ) +} + +fn gen_calldatacopy_data() -> CircuitInputBuilder { + let length = 0x0fffusize; + let code = bytecode! { + PUSH32(Word::from(length)) + PUSH32(Word::from(0x00)) + PUSH32(Word::from(0x00)) + CALLDATACOPY + STOP + }; + let calldata = rand_bytes(length); + let test_ctx = TestContext::<2, 1>::new( + None, + account_0_code_account_1_no_code(code), + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .input(calldata.into()); + }, + |block, _txs| block.number(0xcafeu64), + ) + .unwrap(); + let block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data_with_params( + block.clone(), + CircuitsParams { + max_rws: 8192, + max_copy_rows: 8192 + 2, + max_calldata: 5000, + ..Default::default() + }, + ) + .new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + builder +} + +fn gen_codecopy_data() -> CircuitInputBuilder { + let code = bytecode! { + PUSH32(Word::from(0x20)) + PUSH32(Word::from(0x00)) + PUSH32(Word::from(0x00)) + CODECOPY + STOP + }; + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); + let block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + builder +} + +fn gen_extcodecopy_data() -> CircuitInputBuilder { + let external_address = MOCK_ACCOUNTS[0]; + let code = bytecode! { + PUSH1(0x30usize) + PUSH1(0x0usize) + PUSH1(0x0usize) + PUSH20(external_address.to_word()) + EXTCODECOPY + STOP + }; + let code_ext = rand_bytes(0x0fffusize); + let test_ctx = TestContext::<3, 1>::new( + None, + |accs| { + accs[0].address(MOCK_ACCOUNTS[1]).code(code.clone()); + + accs[1].address(external_address).code(code_ext.clone()); + + accs[2] + .address(MOCK_ACCOUNTS[2]) + .balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0].to(accs[0].address).from(accs[2].address); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap(); + let block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + builder +} + +fn gen_sha3_data() -> CircuitInputBuilder { + let (code, _) = gen_sha3_code(0x20, 0x200, MemoryKind::EqualToSize); + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); + let block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data_with_params( + block.clone(), + CircuitsParams { + max_rws: 2000, + max_copy_rows: 0x200 * 2 + 2, + ..Default::default() + }, + ) + .new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + builder +} + +fn gen_tx_log_data() -> CircuitInputBuilder { + let code = bytecode! { + PUSH32(200) // value + PUSH32(0) // offset + MSTORE + PUSH32(Word::MAX) // topic + PUSH1(32) // length + PUSH1(0) // offset + LOG1 + STOP + }; + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); + let block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + builder +} + +#[test] +fn copy_circuit_valid_calldatacopy() { + let builder = gen_calldatacopy_data(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); +} + +#[test] +fn copy_circuit_valid_codecopy() { + let builder = gen_codecopy_data(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + assert_eq!(test_copy_circuit_from_block(10, block), Ok(())); +} + +#[test] +fn copy_circuit_valid_extcodecopy() { + let builder = gen_extcodecopy_data(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); +} + +#[test] +fn copy_circuit_valid_sha3() { + let builder = gen_sha3_data(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); +} + +#[test] +fn copy_circuit_valid_tx_log() { + let builder = gen_tx_log_data(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + assert_eq!(test_copy_circuit_from_block(10, block), Ok(())); +} + +#[test] +fn copy_circuit_invalid_calldatacopy() { + let mut builder = gen_calldatacopy_data(); + + // modify first byte of first copy event + builder.block.copy_events[0].bytes[0].0 = + builder.block.copy_events[0].bytes[0].0.wrapping_add(1); + + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + + assert_error_matches( + test_copy_circuit_from_block(14, block), + vec!["Memory lookup", "Tx calldata lookup"], + ); +} + +#[test] +fn copy_circuit_invalid_codecopy() { + let mut builder = gen_codecopy_data(); + + // modify first byte of first copy event + builder.block.copy_events[0].bytes[0].0 = + builder.block.copy_events[0].bytes[0].0.wrapping_add(1); + + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + + assert_error_matches( + test_copy_circuit_from_block(10, block), + vec!["Memory lookup", "Bytecode lookup"], + ); +} + +#[test] +fn copy_circuit_invalid_extcodecopy() { + let mut builder = gen_extcodecopy_data(); + + // modify first byte of first copy event + builder.block.copy_events[0].bytes[0].0 = + builder.block.copy_events[0].bytes[0].0.wrapping_add(1); + + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + + assert_error_matches( + test_copy_circuit_from_block(14, block), + vec!["Memory lookup", "Bytecode lookup"], + ); +} + +#[test] +fn copy_circuit_invalid_sha3() { + let mut builder = gen_sha3_data(); + + // modify first byte of first copy event + builder.block.copy_events[0].bytes[0].0 = + builder.block.copy_events[0].bytes[0].0.wrapping_add(1); + + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + + assert_error_matches( + test_copy_circuit_from_block(14, block), + vec!["Memory lookup"], + ); +} + +#[test] +fn copy_circuit_invalid_tx_log() { + let mut builder = gen_tx_log_data(); + + // modify first byte of first copy event + builder.block.copy_events[0].bytes[0].0 = + builder.block.copy_events[0].bytes[0].0.wrapping_add(1); + + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + + assert_error_matches( + test_copy_circuit_from_block(10, block), + vec!["Memory lookup", "TxLog lookup"], + ); +} + +#[test] +fn variadic_size_check() { + let builder = gen_tx_log_data(); + let block1 = block_convert::(&builder.block, &builder.code_db).unwrap(); + + let block: GethData = TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b) + .unwrap() + .into(); + let mut builder = + BlockData::new_from_geth_data_with_params(block.clone(), CircuitsParams::default()) + .new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + let block2 = block_convert::(&builder.block, &builder.code_db).unwrap(); + + let circuit = CopyCircuit::::new(block1.copy_events, block1.circuits_params.max_copy_rows); + let prover1 = MockProver::::run(14, &circuit, vec![]).unwrap(); + + let circuit = CopyCircuit::::new( + block2.copy_events.clone(), + block2.circuits_params.max_copy_rows, + ); + let prover2 = MockProver::::run(14, &circuit, vec![]).unwrap(); + + assert_eq!(prover1.fixed(), prover2.fixed()); + assert_eq!(prover1.permutation(), prover2.permutation()); +} + +fn assert_error_matches(result: Result<(), Vec>, names: Vec<&str>) { + let errors = result.expect_err("result is not an error"); + assert_eq!(errors.len(), names.len(), "{:?}", errors); + for i in 0..names.len() { + match &errors[i] { + VerifyFailure::Lookup { + name: lookup_name, .. + } => { + assert_eq!(lookup_name, &names[i]) + } + VerifyFailure::ConstraintNotSatisfied { .. } => panic!(), + VerifyFailure::CellNotAssigned { .. } => panic!(), + VerifyFailure::ConstraintPoisoned { .. } => panic!(), + VerifyFailure::Permutation { .. } => panic!(), + } + } +} diff --git a/zkevm-circuits/src/copy_circuit/util.rs b/zkevm-circuits/src/copy_circuit/util.rs new file mode 100644 index 0000000000..87d5056949 --- /dev/null +++ b/zkevm-circuits/src/copy_circuit/util.rs @@ -0,0 +1,22 @@ +use crate::evm_circuit::util::rlc; +use bus_mapping::circuit_input_builder::NumberOrHash; +use eth_types::Field; +use halo2_proofs::circuit::Value; + +/// Encode the type `NumberOrHash` into a field element +pub fn number_or_hash_to_field(v: &NumberOrHash, challenge: Value) -> Value { + match v { + NumberOrHash::Number(n) => Value::known(F::from(*n as u64)), + NumberOrHash::Hash(h) => { + // since code hash in the bytecode table is represented in + // the little-endian form, we reverse the big-endian bytes + // of H256. + let le_bytes = { + let mut b = h.to_fixed_bytes(); + b.reverse(); + b + }; + challenge.map(|challenge| rlc::value(&le_bytes, challenge)) + } + } +} diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index ddd4c1a563..19f198486f 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -9,9 +9,13 @@ use halo2_proofs::{ mod execution; pub mod param; pub(crate) mod step; +pub mod table; pub(crate) mod util; -pub mod table; +#[cfg(any(feature = "test", test))] +pub(crate) mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use self::EvmCircuit as TestEvmCircuit; pub use crate::witness; use crate::{ @@ -423,45 +427,6 @@ impl Circuit for EvmCircuit { } } -#[cfg(any(feature = "test", test))] -pub mod test { - use super::*; - use crate::evm_circuit::witness::Block; - - use eth_types::{Field, Word}; - use rand::{ - distributions::uniform::{SampleRange, SampleUniform}, - random, thread_rng, Rng, - }; - - pub(crate) fn rand_range(range: R) -> T - where - T: SampleUniform, - R: SampleRange, - { - thread_rng().gen_range(range) - } - - pub(crate) fn rand_bytes(n: usize) -> Vec { - (0..n).map(|_| random()).collect() - } - - pub(crate) fn rand_bytes_array() -> [u8; N] { - [(); N].map(|_| random()) - } - - pub(crate) fn rand_word() -> Word { - Word::from_big_endian(&rand_bytes_array::<32>()) - } - - impl EvmCircuit { - pub fn get_test_cicuit_from_block(block: Block) -> Self { - let fixed_table_tags = detect_fixed_table_tags(&block); - EvmCircuit::::new_dev(block, fixed_table_tags) - } - } -} - #[cfg(test)] mod evm_circuit_stats { use crate::{ diff --git a/zkevm-circuits/src/evm_circuit/test.rs b/zkevm-circuits/src/evm_circuit/test.rs new file mode 100644 index 0000000000..b282deb3b1 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/test.rs @@ -0,0 +1,36 @@ +#![allow(unused_imports)] +pub use super::EvmCircuit; +use crate::evm_circuit::{detect_fixed_table_tags, witness::Block}; + +use eth_types::{Field, Word}; +use rand::{ + distributions::uniform::{SampleRange, SampleUniform}, + random, thread_rng, Rng, +}; + +pub(crate) fn rand_range(range: R) -> T +where + T: SampleUniform, + R: SampleRange, +{ + thread_rng().gen_range(range) +} + +pub(crate) fn rand_bytes(n: usize) -> Vec { + (0..n).map(|_| random()).collect() +} + +pub(crate) fn rand_bytes_array() -> [u8; N] { + [(); N].map(|_| random()) +} + +pub(crate) fn rand_word() -> Word { + Word::from_big_endian(&rand_bytes_array::<32>()) +} + +impl EvmCircuit { + pub fn get_test_cicuit_from_block(block: Block) -> Self { + let fixed_table_tags = detect_fixed_table_tags(&block); + EvmCircuit::::new_dev(block, fixed_table_tags) + } +} diff --git a/zkevm-circuits/src/exp_circuit.rs b/zkevm-circuits/src/exp_circuit.rs index 5355f4051b..b050077455 100644 --- a/zkevm-circuits/src/exp_circuit.rs +++ b/zkevm-circuits/src/exp_circuit.rs @@ -1,7 +1,19 @@ //! Exponentiation verification circuit. -use std::{marker::PhantomData, ops::Add}; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +mod dev; +pub(crate) mod param; +#[cfg(any(feature = "test", test))] +mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use dev::ExpCircuit as TestExpCircuit; +use crate::{ + evm_circuit::util::constraint_builder::BaseConstraintBuilder, + table::{ExpTable, LookupTable}, + util::{Challenges, SubCircuit, SubCircuitConfig}, + witness, +}; use bus_mapping::circuit_input_builder::{ExpEvent, ExpStep}; use eth_types::{Field, ToScalar, U256}; use gadgets::{ @@ -13,27 +25,8 @@ use halo2_proofs::{ plonk::{ConstraintSystem, Error, Selector}, poly::Rotation, }; - -use crate::{ - evm_circuit::util::constraint_builder::BaseConstraintBuilder, - table::{ExpTable, LookupTable}, - util::{Challenges, SubCircuit, SubCircuitConfig}, - witness, -}; - -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; - -/// The number of rows assigned for each step in an exponentiation trace. -pub const OFFSET_INCREMENT: usize = 7usize; -/// The number of rows required for the exponentiation table within the circuit -/// for each step. -pub const ROWS_PER_STEP: usize = 4usize; -/// The gate "verify all but the last step" at constraint "`base_limb[i]` is the -/// same across all steps" uses rotation 10 in `exp_table.base_limb` which is -/// enabled with `q_usable`, which in turn is enabled in all steps. This means -/// this circuit requires these extra rows after the last enabled `q_usable`. -const UNUSABLE_EXP_ROWS: usize = 10usize; +use param::*; +use std::{marker::PhantomData, ops::Add}; /// Layout for the Exponentiation circuit. #[derive(Clone, Debug)] @@ -528,185 +521,3 @@ impl SubCircuit for ExpCircuit { config.assign_exp_events(layouter, &self.exp_events, self.max_exp_rows) } } - -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -impl Circuit for ExpCircuit { - type Config = (ExpCircuitConfig, Challenges); - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let exp_table = ExpTable::construct(meta); - let challenges = Challenges::construct(meta); - (ExpCircuitConfig::new(meta, exp_table), challenges) - } - - fn synthesize( - &self, - (config, challenges): Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), halo2_proofs::plonk::Error> { - let challenges = challenges.values(&mut layouter); - self.synthesize_sub(&config, &challenges, &mut layouter) - } -} - -#[cfg(any(feature = "test", test))] -/// Dev helpers -pub mod dev { - use super::*; - use eth_types::Field; - use halo2_proofs::dev::MockProver; - - use crate::evm_circuit::witness::Block; - - /// Test exponentiation circuit with the provided block witness - pub fn test_exp_circuit(k: u32, block: Block) { - let circuit = ExpCircuit::::new( - block.exp_events.clone(), - block.circuits_params.max_exp_steps, - ); - let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); - prover.assert_satisfied_par() - } -} - -#[cfg(test)] -mod tests { - use bus_mapping::{ - circuit_input_builder::{CircuitInputBuilder, CircuitsParams}, - evm::OpcodeId, - mock::BlockData, - }; - use eth_types::{bytecode, geth_types::GethData, Bytecode, Word}; - use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; - use mock::TestContext; - - use crate::{ - evm_circuit::witness::block_convert, - exp_circuit::{dev::test_exp_circuit, ExpCircuit}, - }; - - fn gen_code_single(base: Word, exponent: Word) -> Bytecode { - bytecode! { - PUSH32(exponent) - PUSH32(base) - EXP - STOP - } - } - - fn gen_code_multiple(args: Vec<(Word, Word)>) -> Bytecode { - let mut code = Bytecode::default(); - for (base, exponent) in args.into_iter() { - code.push(32, exponent); - code.push(32, base); - code.write_op(OpcodeId::EXP); - } - code.write_op(OpcodeId::STOP); - code - } - - fn gen_data(code: Bytecode) -> CircuitInputBuilder { - let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); - let block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - builder - } - - 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(); - test_exp_circuit(k.unwrap_or(18), block); - } - - 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(); - test_exp_circuit(20, block); - } - - #[test] - fn exp_circuit_single() { - test_ok(2.into(), 2.into(), None); - test_ok(3.into(), 7.into(), None); - test_ok(5.into(), 11.into(), None); - test_ok(7.into(), 13.into(), None); - test_ok(11.into(), 17.into(), None); - test_ok(13.into(), 23.into(), None); - test_ok(29.into(), 43.into(), None); - test_ok(41.into(), 259.into(), None); - } - - #[test] - fn exp_circuit_big() { - test_ok( - 2.into(), - Word::from_str_radix("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE", 16).unwrap(), - Some(20), - ); - } - - #[test] - fn exp_circuit_multiple() { - test_ok_multiple(vec![ - (3.into(), 7.into()), - (5.into(), 11.into()), - (7.into(), 13.into()), - (11.into(), 17.into()), - (13.into(), 23.into()), - (29.into(), 43.into()), - (41.into(), 259.into()), - ]); - } - - #[test] - fn variadic_size_check() { - let k = 20; - // Empty - let block: GethData = TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b) - .unwrap() - .into(); - let mut builder = - BlockData::new_from_geth_data_with_params(block.clone(), CircuitsParams::default()) - .new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - let circuit = ExpCircuit::::new( - block.exp_events.clone(), - block.circuits_params.max_exp_steps, - ); - let prover1 = MockProver::::run(k, &circuit, vec![]).unwrap(); - - // Non-empty - let code = bytecode! { - PUSH32(8) - PUSH32(10) - EXP - PUSH32(3) - PUSH32(5) - EXP - STOP - }; - let builder = gen_data(code); - let block = block_convert::(&builder.block, &builder.code_db).unwrap(); - let circuit = ExpCircuit::::new( - block.exp_events.clone(), - block.circuits_params.max_exp_steps, - ); - let prover2 = MockProver::::run(k, &circuit, vec![]).unwrap(); - - assert_eq!(prover1.fixed(), prover2.fixed()); - assert_eq!(prover1.permutation(), prover2.permutation()); - } -} diff --git a/zkevm-circuits/src/exp_circuit/dev.rs b/zkevm-circuits/src/exp_circuit/dev.rs new file mode 100644 index 0000000000..45a1831b6d --- /dev/null +++ b/zkevm-circuits/src/exp_circuit/dev.rs @@ -0,0 +1,36 @@ +pub use super::ExpCircuit; + +use crate::{ + exp_circuit::ExpCircuitConfig, + table::ExpTable, + util::{Challenges, SubCircuit, SubCircuitConfig}, +}; +use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, +}; + +impl Circuit for ExpCircuit { + type Config = (ExpCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let exp_table = ExpTable::construct(meta); + let challenges = Challenges::construct(meta); + (ExpCircuitConfig::new(meta, exp_table), challenges) + } + + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + self.synthesize_sub(&config, &challenges, &mut layouter) + } +} diff --git a/zkevm-circuits/src/exp_circuit/param.rs b/zkevm-circuits/src/exp_circuit/param.rs new file mode 100644 index 0000000000..f5c42a14ce --- /dev/null +++ b/zkevm-circuits/src/exp_circuit/param.rs @@ -0,0 +1,10 @@ +/// The number of rows assigned for each step in an exponentiation trace. +pub(crate) const OFFSET_INCREMENT: usize = 7usize; +/// The number of rows required for the exponentiation table within the circuit +/// for each step. +pub(crate) const ROWS_PER_STEP: usize = 4usize; +/// The gate "verify all but the last step" at constraint "`base_limb[i]` is the +/// same across all steps" uses rotation 10 in `exp_table.base_limb` which is +/// enabled with `q_usable`, which in turn is enabled in all steps. This means +/// this circuit requires these extra rows after the last enabled `q_usable`. +pub(crate) const UNUSABLE_EXP_ROWS: usize = 10usize; diff --git a/zkevm-circuits/src/exp_circuit/test.rs b/zkevm-circuits/src/exp_circuit/test.rs new file mode 100644 index 0000000000..ecd032e1e5 --- /dev/null +++ b/zkevm-circuits/src/exp_circuit/test.rs @@ -0,0 +1,145 @@ +#![allow(unused_imports)] +use crate::{ + evm_circuit::witness::{block_convert, Block}, + exp_circuit::ExpCircuit, +}; +use bus_mapping::{ + circuit_input_builder::{CircuitInputBuilder, CircuitsParams}, + evm::OpcodeId, + mock::BlockData, +}; +use eth_types::{bytecode, geth_types::GethData, Bytecode, Field, Word}; +use halo2_proofs::{ + circuit::SimpleFloorPlanner, dev::MockProver, halo2curves::bn256::Fr, plonk::Circuit, +}; +use mock::TestContext; + +/// Test exponentiation circuit with the provided block witness +pub fn test_exp_circuit(k: u32, block: Block) { + let circuit = ExpCircuit::::new( + block.exp_events.clone(), + block.circuits_params.max_exp_steps, + ); + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); + prover.assert_satisfied_par() +} + +fn gen_code_single(base: Word, exponent: Word) -> Bytecode { + bytecode! { + PUSH32(exponent) + PUSH32(base) + EXP + STOP + } +} + +fn gen_code_multiple(args: Vec<(Word, Word)>) -> Bytecode { + let mut code = Bytecode::default(); + for (base, exponent) in args.into_iter() { + code.push(32, exponent); + code.push(32, base); + code.write_op(OpcodeId::EXP); + } + code.write_op(OpcodeId::STOP); + code +} + +fn gen_data(code: Bytecode) -> CircuitInputBuilder { + let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); + let block: GethData = test_ctx.into(); + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + builder +} + +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(); + test_exp_circuit(k.unwrap_or(18), block); +} + +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(); + test_exp_circuit(20, block); +} + +#[test] +fn exp_circuit_single() { + test_ok(2.into(), 2.into(), None); + test_ok(3.into(), 7.into(), None); + test_ok(5.into(), 11.into(), None); + test_ok(7.into(), 13.into(), None); + test_ok(11.into(), 17.into(), None); + test_ok(13.into(), 23.into(), None); + test_ok(29.into(), 43.into(), None); + test_ok(41.into(), 259.into(), None); +} + +#[test] +fn exp_circuit_big() { + test_ok( + 2.into(), + Word::from_str_radix("0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE", 16).unwrap(), + Some(20), + ); +} + +#[test] +fn exp_circuit_multiple() { + test_ok_multiple(vec![ + (3.into(), 7.into()), + (5.into(), 11.into()), + (7.into(), 13.into()), + (11.into(), 17.into()), + (13.into(), 23.into()), + (29.into(), 43.into()), + (41.into(), 259.into()), + ]); +} + +#[test] +fn variadic_size_check() { + let k = 20; + // Empty + let block: GethData = TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b) + .unwrap() + .into(); + let mut builder = + BlockData::new_from_geth_data_with_params(block.clone(), CircuitsParams::default()) + .new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + let circuit = ExpCircuit::::new( + block.exp_events.clone(), + block.circuits_params.max_exp_steps, + ); + let prover1 = MockProver::::run(k, &circuit, vec![]).unwrap(); + + // Non-empty + let code = bytecode! { + PUSH32(8) + PUSH32(10) + EXP + PUSH32(3) + PUSH32(5) + EXP + STOP + }; + let builder = gen_data(code); + let block = block_convert::(&builder.block, &builder.code_db).unwrap(); + let circuit = ExpCircuit::::new( + block.exp_events.clone(), + block.circuits_params.max_exp_steps, + ); + let prover2 = MockProver::::run(k, &circuit, vec![]).unwrap(); + + assert_eq!(prover1.fixed(), prover2.fixed()); + assert_eq!(prover1.permutation(), prover2.permutation()); +} diff --git a/zkevm-circuits/src/keccak_circuit.rs b/zkevm-circuits/src/keccak_circuit.rs index 7d814bb58b..0fe0f2a6a2 100644 --- a/zkevm-circuits/src/keccak_circuit.rs +++ b/zkevm-circuits/src/keccak_circuit.rs @@ -4,11 +4,16 @@ mod cell_manager; pub mod keccak_packed_multi; mod param; mod table; -#[cfg(test)] -mod test; /// Util mod util; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +mod dev; +#[cfg(any(feature = "test", test))] +mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use dev::KeccakCircuit as TestKeccakCircuit; + use std::marker::PhantomData; pub use KeccakCircuitConfig as KeccakConfig; @@ -28,9 +33,6 @@ use halo2_proofs::{ }; use log::info; -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; - /// KeccakConfig #[derive(Clone, Debug)] pub struct KeccakCircuitConfig { @@ -1005,42 +1007,6 @@ impl SubCircuit for KeccakCircuit { } } -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -impl Circuit for KeccakCircuit { - type Config = (KeccakCircuitConfig, Challenges); - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let keccak_table = KeccakTable::construct(meta); - let challenges = Challenges::construct(meta); - - let config = { - let challenges = challenges.exprs(meta); - KeccakCircuitConfig::new( - meta, - KeccakCircuitConfigArgs { - keccak_table, - challenges, - }, - ) - }; - (config, challenges) - } - - fn synthesize( - &self, - (config, challenges): Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let challenges = challenges.values(&mut layouter); - self.synthesize_sub(&config, &challenges, &mut layouter) - } -} - impl KeccakCircuit { /// Creates a new circuit instance pub fn new(num_rows: usize, inputs: Vec>) -> Self { diff --git a/zkevm-circuits/src/keccak_circuit/dev.rs b/zkevm-circuits/src/keccak_circuit/dev.rs new file mode 100644 index 0000000000..44e7ac07a5 --- /dev/null +++ b/zkevm-circuits/src/keccak_circuit/dev.rs @@ -0,0 +1,47 @@ +pub use super::KeccakCircuit; + +use crate::{ + keccak_circuit::{KeccakCircuitConfig, KeccakCircuitConfigArgs}, + table::KeccakTable, + util::{Challenges, SubCircuit, SubCircuitConfig}, +}; +use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, +}; + +impl Circuit for KeccakCircuit { + type Config = (KeccakCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let keccak_table = KeccakTable::construct(meta); + let challenges = Challenges::construct(meta); + + let config = { + let challenges = challenges.exprs(meta); + KeccakCircuitConfig::new( + meta, + KeccakCircuitConfigArgs { + keccak_table, + challenges, + }, + ) + }; + (config, challenges) + } + + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + self.synthesize_sub(&config, &challenges, &mut layouter) + } +} diff --git a/zkevm-circuits/src/keccak_circuit/test.rs b/zkevm-circuits/src/keccak_circuit/test.rs index 70f0213e9d..31eae29303 100644 --- a/zkevm-circuits/src/keccak_circuit/test.rs +++ b/zkevm-circuits/src/keccak_circuit/test.rs @@ -1,6 +1,12 @@ -use crate::keccak_circuit::KeccakCircuit; +#![allow(unused_imports)] +use super::*; use eth_types::Field; -use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + dev::MockProver, + halo2curves::bn256::Fr, + plonk::{Circuit, ConstraintSystem, Error}, +}; use log::error; use std::iter::zip; diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index 0fea2abcb2..44ba6e375e 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -1,6 +1,12 @@ //! Public Input Circuit implementation +mod param; -use std::marker::PhantomData; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +mod dev; +#[cfg(any(feature = "test", test))] +mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use dev::PiTestCircuit; use eth_types::{ geth_types::{BlockConstants, Transaction}, @@ -9,6 +15,8 @@ use eth_types::{ }; use halo2_proofs::plonk::{Instance, SecondPhase}; use keccak256::plain::Keccak; +use param::*; +use std::marker::PhantomData; use crate::{ table::{BlockTable, LookupTable, TxFieldTag, TxTable}, @@ -29,12 +37,6 @@ use halo2_proofs::{ #[cfg(any(feature = "test", test, feature = "test-circuits"))] use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; -/// Fixed by the spec -const BLOCK_LEN: usize = 7 + 256; -const EXTRA_LEN: usize = 2; -const ZERO_BYTE_GAS_COST: u64 = 4; -const NONZERO_BYTE_GAS_COST: u64 = 16; - /// Values of the block table (as in the spec) #[derive(Clone, Default, Debug)] pub struct BlockValues { @@ -1442,92 +1444,6 @@ impl SubCircuit for PiCircuit { } } -// We define the PiTestCircuit as a wrapper over PiCircuit extended to take the -// generic const parameters MAX_TXS and MAX_CALLDATA. This is necessary because -// the trait Circuit requires an implementation of `configure` that doesn't take -// any circuit parameters, and the PiCircuit defines gates that use rotations -// that depend on MAX_TXS and MAX_CALLDATA, so these two values are required -// during the configuration. -/// Test Circuit for PiCircuit -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -#[derive(Default, Clone)] -pub struct PiTestCircuit( - pub PiCircuit, -); - -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -impl SubCircuit - for PiTestCircuit -{ - type Config = PiCircuitConfig; - - fn new_from_block(block: &witness::Block) -> Self { - assert_eq!(block.circuits_params.max_txs, MAX_TXS); - assert_eq!(block.circuits_params.max_calldata, MAX_CALLDATA); - - Self(PiCircuit::new_from_block(block)) - } - - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { - assert_eq!(block.circuits_params.max_txs, MAX_TXS); - assert_eq!(block.circuits_params.max_calldata, MAX_CALLDATA); - - PiCircuit::min_num_rows_block(block) - } - - /// Compute the public inputs for this circuit. - fn instance(&self) -> Vec> { - self.0.instance() - } - - fn synthesize_sub( - &self, - _config: &Self::Config, - _challenges: &Challenges>, - _layouter: &mut impl Layouter, - ) -> Result<(), Error> { - panic!("use PiCircuit for embedding instead"); - } -} - -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -impl Circuit - for PiTestCircuit -{ - type Config = (PiCircuitConfig, Challenges); - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let block_table = BlockTable::construct(meta); - let tx_table = TxTable::construct(meta); - ( - PiCircuitConfig::new( - meta, - PiCircuitConfigArgs { - max_txs: MAX_TXS, - max_calldata: MAX_CALLDATA, - block_table, - tx_table, - }, - ), - Challenges::construct(meta), - ) - } - - fn synthesize( - &self, - (config, challenges): Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let challenges = challenges.values(&mut layouter); - self.0.synthesize_sub(&config, &challenges, &mut layouter) - } -} - /// Compute the raw_public_inputs column from the verifier's perspective. fn raw_public_inputs_col( max_txs: usize, @@ -1655,133 +1571,3 @@ pub fn gen_rand_rpi( let rand_rpi = Word::from(keccak.digest().as_slice()) % F::MODULUS; rand_rpi.to_scalar().expect("rand_rpi.to_scalar") } - -#[cfg(test)] -mod pi_circuit_test { - use super::*; - use halo2_proofs::{ - dev::{MockProver, VerifyFailure}, - halo2curves::bn256::Fr, - }; - use mock::{CORRECT_MOCK_TXS, MOCK_CHAIN_ID}; - use pretty_assertions::assert_eq; - use rand::SeedableRng; - use rand_chacha::ChaCha20Rng; - - fn run( - k: u32, - 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 = PiTestCircuit::(PiCircuit::new( - MAX_TXS, - MAX_CALLDATA, - randomness, - rand_rpi, - public_data, - )); - let public_inputs = circuit.0.instance(); - - let prover = match MockProver::run(k, &circuit, public_inputs) { - Ok(prover) => prover, - Err(e) => panic!("{:#?}", e), - }; - prover.verify() - } - - #[test] - fn test_default_pi() { - const MAX_TXS: usize = 2; - const MAX_CALLDATA: usize = 8; - let public_data = PublicData::default(); - - let k = 17; - assert_eq!(run::(k, public_data), Ok(())); - } - - #[test] - fn test_simple_pi() { - const MAX_TXS: usize = 8; - const MAX_CALLDATA: usize = 200; - - let mut public_data = PublicData::default(); - - let n_tx = 4; - for i in 0..n_tx { - public_data - .transactions - .push(CORRECT_MOCK_TXS[i].clone().into()); - } - - let k = 17; - assert_eq!(run::(k, public_data), Ok(())); - } - - fn run_size_check( - 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 = PiTestCircuit::(PiCircuit::new( - MAX_TXS, - MAX_CALLDATA, - randomness, - rand_rpi, - public_data[0].clone(), - )); - let public_inputs = circuit.0.instance(); - let prover1 = MockProver::run(20, &circuit, public_inputs).unwrap(); - - let circuit2 = PiTestCircuit::(PiCircuit::new( - MAX_TXS, - MAX_CALLDATA, - randomness, - rand_rpi, - public_data[1].clone(), - )); - let public_inputs = circuit2.0.instance(); - let prover2 = MockProver::run(20, &circuit, public_inputs).unwrap(); - - assert_eq!(prover1.fixed(), prover2.fixed()); - assert_eq!(prover1.permutation(), prover2.permutation()); - } - - #[test] - fn variadic_size_check() { - const MAX_TXS: usize = 8; - const MAX_CALLDATA: usize = 200; - - let mut pub_dat_1 = PublicData { - chain_id: *MOCK_CHAIN_ID, - ..Default::default() - }; - - let n_tx = 2; - for i in 0..n_tx { - pub_dat_1 - .transactions - .push(CORRECT_MOCK_TXS[i].clone().into()); - } - - let mut pub_dat_2 = PublicData { - chain_id: *MOCK_CHAIN_ID, - ..Default::default() - }; - - let n_tx = 4; - for i in 0..n_tx { - pub_dat_2 - .transactions - .push(CORRECT_MOCK_TXS[i].clone().into()); - } - - run_size_check::([pub_dat_1, pub_dat_2]); - } -} diff --git a/zkevm-circuits/src/pi_circuit/dev.rs b/zkevm-circuits/src/pi_circuit/dev.rs new file mode 100644 index 0000000000..c901fb9f58 --- /dev/null +++ b/zkevm-circuits/src/pi_circuit/dev.rs @@ -0,0 +1,84 @@ +use super::*; + +// We define the PiTestCircuit as a wrapper over PiCircuit extended to take the +// generic const parameters MAX_TXS and MAX_CALLDATA. This is necessary because +// the trait Circuit requires an implementation of `configure` that doesn't take +// any circuit parameters, and the PiCircuit defines gates that use rotations +// that depend on MAX_TXS and MAX_CALLDATA, so these two values are required +// during the configuration. +/// Test Circuit for PiCircuit +#[derive(Default, Clone)] +pub struct PiTestCircuit( + pub PiCircuit, +); + +impl SubCircuit + for PiTestCircuit +{ + type Config = PiCircuitConfig; + + fn new_from_block(block: &witness::Block) -> Self { + assert_eq!(block.circuits_params.max_txs, MAX_TXS); + assert_eq!(block.circuits_params.max_calldata, MAX_CALLDATA); + + Self(PiCircuit::new_from_block(block)) + } + + fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + assert_eq!(block.circuits_params.max_txs, MAX_TXS); + assert_eq!(block.circuits_params.max_calldata, MAX_CALLDATA); + + PiCircuit::min_num_rows_block(block) + } + + /// Compute the public inputs for this circuit. + fn instance(&self) -> Vec> { + self.0.instance() + } + + fn synthesize_sub( + &self, + _config: &Self::Config, + _challenges: &Challenges>, + _layouter: &mut impl Layouter, + ) -> Result<(), Error> { + panic!("use PiCircuit for embedding instead"); + } +} + +impl Circuit + for PiTestCircuit +{ + type Config = (PiCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let block_table = BlockTable::construct(meta); + let tx_table = TxTable::construct(meta); + ( + PiCircuitConfig::new( + meta, + PiCircuitConfigArgs { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + block_table, + tx_table, + }, + ), + Challenges::construct(meta), + ) + } + + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + self.0.synthesize_sub(&config, &challenges, &mut layouter) + } +} diff --git a/zkevm-circuits/src/pi_circuit/param.rs b/zkevm-circuits/src/pi_circuit/param.rs new file mode 100644 index 0000000000..8c05677fdd --- /dev/null +++ b/zkevm-circuits/src/pi_circuit/param.rs @@ -0,0 +1,5 @@ +/// 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; diff --git a/zkevm-circuits/src/pi_circuit/test.rs b/zkevm-circuits/src/pi_circuit/test.rs new file mode 100644 index 0000000000..0310d6a0fd --- /dev/null +++ b/zkevm-circuits/src/pi_circuit/test.rs @@ -0,0 +1,126 @@ +#![allow(unused_imports)] +use super::{dev::*, *}; +use halo2_proofs::{ + dev::{MockProver, VerifyFailure}, + halo2curves::bn256::Fr, +}; +use mock::{CORRECT_MOCK_TXS, MOCK_CHAIN_ID}; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; + +fn run( + k: u32, + 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 = PiTestCircuit::(PiCircuit::new( + MAX_TXS, + MAX_CALLDATA, + randomness, + rand_rpi, + public_data, + )); + let public_inputs = circuit.0.instance(); + + let prover = match MockProver::run(k, &circuit, public_inputs) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + prover.verify() +} + +#[test] +fn test_default_pi() { + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 8; + let public_data = PublicData::default(); + + let k = 17; + assert_eq!(run::(k, public_data), Ok(())); +} + +#[test] +fn test_simple_pi() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + + let mut public_data = PublicData::default(); + + let n_tx = 4; + for i in 0..n_tx { + public_data + .transactions + .push(CORRECT_MOCK_TXS[i].clone().into()); + } + + let k = 17; + assert_eq!(run::(k, public_data), Ok(())); +} + +fn run_size_check( + 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 = PiTestCircuit::(PiCircuit::new( + MAX_TXS, + MAX_CALLDATA, + randomness, + rand_rpi, + public_data[0].clone(), + )); + let public_inputs = circuit.0.instance(); + let prover1 = MockProver::run(20, &circuit, public_inputs).unwrap(); + + let circuit2 = PiTestCircuit::(PiCircuit::new( + MAX_TXS, + MAX_CALLDATA, + randomness, + rand_rpi, + public_data[1].clone(), + )); + let public_inputs = circuit2.0.instance(); + let prover2 = MockProver::run(20, &circuit, public_inputs).unwrap(); + + assert_eq!(prover1.fixed(), prover2.fixed()); + assert_eq!(prover1.permutation(), prover2.permutation()); +} + +#[test] +fn variadic_size_check() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + + let mut pub_dat_1 = PublicData { + chain_id: *MOCK_CHAIN_ID, + ..Default::default() + }; + + let n_tx = 2; + for i in 0..n_tx { + pub_dat_1 + .transactions + .push(CORRECT_MOCK_TXS[i].clone().into()); + } + + let mut pub_dat_2 = PublicData { + chain_id: *MOCK_CHAIN_ID, + ..Default::default() + }; + + let n_tx = 4; + for i in 0..n_tx { + pub_dat_2 + .transactions + .push(CORRECT_MOCK_TXS[i].clone().into()); + } + + run_size_check::([pub_dat_1, pub_dat_2]); +} diff --git a/zkevm-circuits/src/root_circuit.rs b/zkevm-circuits/src/root_circuit.rs index 9eb0b35af2..440424fa18 100644 --- a/zkevm-circuits/src/root_circuit.rs +++ b/zkevm-circuits/src/root_circuit.rs @@ -13,6 +13,11 @@ use std::iter; mod aggregation; +#[cfg(any(feature = "test", test))] +mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use self::RootCircuit as TestRootCircuit; + pub use aggregation::{ aggregate, AggregationConfig, EccChip, Halo2Loader, KzgAs, KzgDk, KzgSvk, PlonkSuccinctVerifier, PlonkVerifier, PoseidonTranscript, Snark, SnarkWitness, BITS, LIMBS, @@ -137,90 +142,3 @@ impl<'a, M: MultiMillerLoop> Circuit for RootCircuit<'a, M> { Ok(()) } } - -#[cfg(test)] -mod test { - use crate::{ - root_circuit::{compile, Config, PoseidonTranscript, RootCircuit}, - super_circuit::{super_circuit_tests::block_1tx, SuperCircuit}, - }; - use bus_mapping::circuit_input_builder::CircuitsParams; - use halo2_proofs::{ - circuit::Value, - dev::MockProver, - halo2curves::bn256::Bn256, - plonk::{create_proof, keygen_pk, keygen_vk}, - poly::kzg::{ - commitment::{KZGCommitmentScheme, ParamsKZG}, - multiopen::ProverGWC, - }, - }; - use itertools::Itertools; - use rand::rngs::OsRng; - - #[ignore = "Due to high memory requirement"] - #[test] - fn test_root_circuit() { - let (params, protocol, proof, instance) = { - // Preprocess - const MAX_TXS: usize = 1; - const MAX_CALLDATA: usize = 32; - const TEST_MOCK_RANDOMNESS: u64 = 0x100; - let circuits_params = CircuitsParams { - max_txs: MAX_TXS, - max_calldata: MAX_CALLDATA, - max_rws: 256, - max_copy_rows: 256, - max_exp_steps: 256, - max_bytecode: 512, - max_evm_rows: 0, - max_keccak_rows: 0, - }; - let (k, circuit, instance, _) = - SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, TEST_MOCK_RANDOMNESS>::build( - block_1tx(), - circuits_params, - ) - .unwrap(); - let params = ParamsKZG::::setup(k, OsRng); - let pk = keygen_pk(¶ms, keygen_vk(¶ms, &circuit).unwrap(), &circuit).unwrap(); - let protocol = compile( - ¶ms, - pk.get_vk(), - Config::kzg() - .with_num_instance(instance.iter().map(|instance| instance.len()).collect()), - ); - - // Create proof - let proof = { - let mut transcript = PoseidonTranscript::new(Vec::new()); - create_proof::, ProverGWC<_>, _, _, _, _>( - ¶ms, - &pk, - &[circuit], - &[&instance.iter().map(Vec::as_slice).collect_vec()], - OsRng, - &mut transcript, - ) - .unwrap(); - transcript.finalize() - }; - - (params, protocol, proof, instance) - }; - - let root_circuit = RootCircuit::new( - ¶ms, - &protocol, - Value::known(&instance), - Value::known(&proof), - ) - .unwrap(); - assert_eq!( - MockProver::run(26, &root_circuit, root_circuit.instance()) - .unwrap() - .verify_par(), - Ok(()) - ); - } -} diff --git a/zkevm-circuits/src/root_circuit/test.rs b/zkevm-circuits/src/root_circuit/test.rs new file mode 100644 index 0000000000..36106bc899 --- /dev/null +++ b/zkevm-circuits/src/root_circuit/test.rs @@ -0,0 +1,82 @@ +#![allow(unused_imports)] +pub use super::*; +use crate::super_circuit::{test::block_1tx, SuperCircuit}; +use bus_mapping::circuit_input_builder::CircuitsParams; +use halo2_proofs::{ + circuit::Value, + dev::MockProver, + halo2curves::bn256::Bn256, + plonk::{create_proof, keygen_pk, keygen_vk}, + poly::kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::ProverGWC, + }, +}; +use itertools::Itertools; +use rand::rngs::OsRng; + +#[ignore = "Due to high memory requirement"] +#[test] +fn test_root_circuit() { + let (params, protocol, proof, instance) = { + // Preprocess + const MAX_TXS: usize = 1; + const MAX_CALLDATA: usize = 32; + const TEST_MOCK_RANDOMNESS: u64 = 0x100; + let circuits_params = CircuitsParams { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_rws: 256, + max_copy_rows: 256, + max_exp_steps: 256, + max_bytecode: 512, + max_evm_rows: 0, + max_keccak_rows: 0, + }; + let (k, circuit, instance, _) = + SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, TEST_MOCK_RANDOMNESS>::build( + block_1tx(), + circuits_params, + ) + .unwrap(); + let params = ParamsKZG::::setup(k, OsRng); + let pk = keygen_pk(¶ms, keygen_vk(¶ms, &circuit).unwrap(), &circuit).unwrap(); + let protocol = compile( + ¶ms, + pk.get_vk(), + Config::kzg() + .with_num_instance(instance.iter().map(|instance| instance.len()).collect()), + ); + + // Create proof + let proof = { + let mut transcript = PoseidonTranscript::new(Vec::new()); + create_proof::, ProverGWC<_>, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[&instance.iter().map(Vec::as_slice).collect_vec()], + OsRng, + &mut transcript, + ) + .unwrap(); + transcript.finalize() + }; + + (params, protocol, proof, instance) + }; + + let root_circuit = RootCircuit::new( + ¶ms, + &protocol, + Value::known(&instance), + Value::known(&proof), + ) + .unwrap(); + assert_eq!( + MockProver::run(26, &root_circuit, root_circuit.instance()) + .unwrap() + .verify_par(), + Ok(()) + ); +} diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 749df0750a..acbd8fd908 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -3,10 +3,20 @@ mod constraint_builder; mod lexicographic_ordering; mod lookups; mod multiple_precision_integer; +mod param; mod random_linear_combination; -#[cfg(test)] + +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +mod dev; +#[cfg(any(feature = "test", test))] mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use dev::StateCircuit as TestStateCircuit; +use self::{ + constraint_builder::{MptUpdateTableQueries, RwTableQueries}, + lexicographic_ordering::LimbIndex, +}; use crate::{ evm_circuit::{param::N_BYTES_WORD, util::rlc}, table::{AccountFieldTag, LookupTable, MPTProofType, MptTable, RwTable, RwTableTag}, @@ -29,22 +39,12 @@ use halo2_proofs::{ use lexicographic_ordering::Config as LexicographicOrderingConfig; use lookups::{Chip as LookupsChip, Config as LookupsConfig, Queries as LookupsQueries}; use multiple_precision_integer::{Chip as MpiChip, Config as MpiConfig, Queries as MpiQueries}; +use param::*; use random_linear_combination::{Chip as RlcChip, Config as RlcConfig, Queries as RlcQueries}; -#[cfg(test)] -use std::collections::HashMap; use std::marker::PhantomData; -use self::{ - constraint_builder::{MptUpdateTableQueries, RwTableQueries}, - lexicographic_ordering::LimbIndex, -}; - #[cfg(any(feature = "test", test, feature = "test-circuits"))] -use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; - -const N_LIMBS_RW_COUNTER: usize = 2; -const N_LIMBS_ACCOUNT_ADDRESS: usize = 10; -const N_LIMBS_ID: usize = 2; +use std::collections::HashMap; /// Config for StateCircuit #[derive(Clone)] @@ -427,8 +427,8 @@ pub struct StateCircuit { pub rows: Vec, updates: MptUpdates, pub(crate) n_rows: usize, - #[cfg(test)] - overrides: HashMap<(test::AdviceColumn, isize), F>, + #[cfg(any(feature = "test", test, feature = "test-circuits"))] + overrides: HashMap<(dev::AdviceColumn, isize), F>, _marker: PhantomData, } @@ -441,7 +441,7 @@ impl StateCircuit { rows, updates, n_rows, - #[cfg(test)] + #[cfg(any(feature = "test", test, feature = "test-circuits"))] overrides: HashMap::new(), _marker: PhantomData::default(), } @@ -494,7 +494,7 @@ impl SubCircuit for StateCircuit { self.n_rows, randomness, )?; - #[cfg(test)] + #[cfg(any(feature = "test", test, feature = "test-circuits"))] { let padding_length = RwMap::padding_len(self.rows.len(), self.n_rows); for ((column, row_offset), &f) in &self.overrides { @@ -522,51 +522,6 @@ impl SubCircuit for StateCircuit { } } -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -impl Circuit for StateCircuit -where - F: Field, -{ - type Config = (StateCircuitConfig, Challenges); - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let rw_table = RwTable::construct(meta); - let mpt_table = MptTable::construct(meta); - let challenges = Challenges::construct(meta); - - let config = { - let challenges = challenges.exprs(meta); - StateCircuitConfig::new( - meta, - StateCircuitConfigArgs { - rw_table, - mpt_table, - challenges, - }, - ) - }; - - (config, challenges) - } - - fn synthesize( - &self, - (config, challenges): Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let challenges = challenges.values(&mut layouter); - config - .mpt_table - .load(&mut layouter, &self.updates, challenges.evm_word())?; - self.synthesize_sub(&config, &challenges, &mut layouter) - } -} - fn queries(meta: &mut VirtualCells<'_, F>, c: &StateCircuitConfig) -> Queries { let first_different_limb = c.lexicographic_ordering.first_different_limb; let final_bits_sum = meta.query_advice(first_different_limb.bits[3], Rotation::cur()) diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index 37ac6f32cf..947c6e21c9 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -1,7 +1,6 @@ use super::{ lookups::Queries as LookupsQueries, multiple_precision_integer::Queries as MpiQueries, - random_linear_combination::Queries as RlcQueries, N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_ID, - N_LIMBS_RW_COUNTER, + param::*, random_linear_combination::Queries as RlcQueries, }; use crate::{ evm_circuit::{ diff --git a/zkevm-circuits/src/state_circuit/dev.rs b/zkevm-circuits/src/state_circuit/dev.rs new file mode 100644 index 0000000000..db8262fcce --- /dev/null +++ b/zkevm-circuits/src/state_circuit/dev.rs @@ -0,0 +1,119 @@ +pub use super::StateCircuit; + +use crate::{ + state_circuit::{StateCircuitConfig, StateCircuitConfigArgs}, + table::{MptTable, RwTable}, + util::{Challenges, SubCircuit, SubCircuitConfig}, +}; +use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, +}; + +impl Circuit for StateCircuit +where + F: Field, +{ + type Config = (StateCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let rw_table = RwTable::construct(meta); + let mpt_table = MptTable::construct(meta); + let challenges = Challenges::construct(meta); + + let config = { + let challenges = challenges.exprs(meta); + StateCircuitConfig::new( + meta, + StateCircuitConfigArgs { + rw_table, + mpt_table, + challenges, + }, + ) + }; + + (config, challenges) + } + + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + config + .mpt_table + .load(&mut layouter, &self.updates, challenges.evm_word())?; + self.synthesize_sub(&config, &challenges, &mut layouter) + } +} + +#[derive(Hash, Eq, PartialEq, Clone, Debug)] +pub enum AdviceColumn { + IsWrite, + Address, + AddressLimb0, + AddressLimb1, + StorageKey, + StorageKeyByte0, + StorageKeyByte1, + Value, + ValuePrev, + RwCounter, + RwCounterLimb0, + RwCounterLimb1, + Tag, + TagBit0, + TagBit1, + TagBit2, + TagBit3, + LimbIndexBit0, // most significant bit + LimbIndexBit1, + LimbIndexBit2, + LimbIndexBit3, + LimbIndexBit4, // least significant bit + InitialValue, + IsZero, // committed_value and value are 0 + // NonEmptyWitness is the BatchedIsZero chip witness that contains the + // inverse of the non-zero value if any in [committed_value, value] + NonEmptyWitness, +} + +impl AdviceColumn { + pub fn value(&self, config: &StateCircuitConfig) -> Column { + match self { + Self::IsWrite => config.rw_table.is_write, + Self::Address => config.rw_table.address, + Self::AddressLimb0 => config.sort_keys.address.limbs[0], + Self::AddressLimb1 => config.sort_keys.address.limbs[1], + Self::StorageKey => config.rw_table.storage_key, + Self::StorageKeyByte0 => config.sort_keys.storage_key.bytes[0], + Self::StorageKeyByte1 => config.sort_keys.storage_key.bytes[1], + Self::Value => config.rw_table.value, + Self::ValuePrev => config.rw_table.value_prev, + Self::RwCounter => config.rw_table.rw_counter, + Self::RwCounterLimb0 => config.sort_keys.rw_counter.limbs[0], + Self::RwCounterLimb1 => config.sort_keys.rw_counter.limbs[1], + Self::Tag => config.rw_table.tag, + Self::TagBit0 => config.sort_keys.tag.bits[0], + Self::TagBit1 => config.sort_keys.tag.bits[1], + Self::TagBit2 => config.sort_keys.tag.bits[2], + Self::TagBit3 => config.sort_keys.tag.bits[3], + Self::LimbIndexBit0 => config.lexicographic_ordering.first_different_limb.bits[0], + Self::LimbIndexBit1 => config.lexicographic_ordering.first_different_limb.bits[1], + Self::LimbIndexBit2 => config.lexicographic_ordering.first_different_limb.bits[2], + Self::LimbIndexBit3 => config.lexicographic_ordering.first_different_limb.bits[3], + Self::LimbIndexBit4 => config.lexicographic_ordering.first_different_limb.bits[4], + Self::InitialValue => config.initial_value, + Self::IsZero => config.is_non_exist.is_zero, + Self::NonEmptyWitness => config.is_non_exist.nonempty_witness, + } + } +} diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index cb84cab754..e43c9b07d0 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -1,4 +1,4 @@ -use super::{lookups, SortKeysConfig, N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_ID, N_LIMBS_RW_COUNTER}; +use super::{lookups, param::*, SortKeysConfig}; use crate::{evm_circuit::param::N_BYTES_WORD, impl_expr, util::Expr, witness::Rw}; use eth_types::{Field, ToBigEndian}; use gadgets::binary_number::{AsBits, BinaryNumberChip, BinaryNumberConfig}; diff --git a/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs b/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs index fcd71f1bdf..2a8d963baa 100644 --- a/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs +++ b/zkevm-circuits/src/state_circuit/multiple_precision_integer.rs @@ -1,4 +1,4 @@ -use super::{lookups, N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_RW_COUNTER}; +use super::{lookups, param::*}; use crate::util::Expr; use eth_types::{Address, Field}; use halo2_proofs::{ diff --git a/zkevm-circuits/src/state_circuit/param.rs b/zkevm-circuits/src/state_circuit/param.rs new file mode 100644 index 0000000000..fb30860c12 --- /dev/null +++ b/zkevm-circuits/src/state_circuit/param.rs @@ -0,0 +1,3 @@ +pub(super) const N_LIMBS_RW_COUNTER: usize = 2; +pub(super) const N_LIMBS_ACCOUNT_ADDRESS: usize = 10; +pub(super) const N_LIMBS_ID: usize = 2; diff --git a/zkevm-circuits/src/state_circuit/test.rs b/zkevm-circuits/src/state_circuit/test.rs index a8e273fcf6..55bc7acfec 100644 --- a/zkevm-circuits/src/state_circuit/test.rs +++ b/zkevm-circuits/src/state_circuit/test.rs @@ -1,4 +1,5 @@ -use super::{StateCircuit, StateCircuitConfig}; +#![allow(unused_imports)] +pub use super::{dev::*, *}; use crate::{ table::{AccountFieldTag, CallContextFieldTag, RwTableTag, TxLogFieldTag, TxReceiptFieldTag}, util::SubCircuit, @@ -15,6 +16,7 @@ use eth_types::{ use gadgets::binary_number::AsBits; use halo2_proofs::{ arithmetic::Field as Halo2Field, + circuit::SimpleFloorPlanner, dev::{MockProver, VerifyFailure}, halo2curves::bn256::{Bn256, Fr}, plonk::{keygen_vk, Advice, Circuit, Column, ConstraintSystem}, @@ -26,69 +28,6 @@ use strum::IntoEnumIterator; const N_ROWS: usize = 1 << 16; -#[derive(Hash, Eq, PartialEq, Clone, Debug)] -pub enum AdviceColumn { - IsWrite, - Address, - AddressLimb0, - AddressLimb1, - StorageKey, - StorageKeyByte0, - StorageKeyByte1, - Value, - ValuePrev, - RwCounter, - RwCounterLimb0, - RwCounterLimb1, - Tag, - TagBit0, - TagBit1, - TagBit2, - TagBit3, - LimbIndexBit0, // most significant bit - LimbIndexBit1, - LimbIndexBit2, - LimbIndexBit3, - LimbIndexBit4, // least significant bit - InitialValue, - IsZero, // committed_value and value are 0 - // NonEmptyWitness is the BatchedIsZero chip witness that contains the - // inverse of the non-zero value if any in [committed_value, value] - NonEmptyWitness, -} - -impl AdviceColumn { - pub fn value(&self, config: &StateCircuitConfig) -> Column { - match self { - Self::IsWrite => config.rw_table.is_write, - Self::Address => config.rw_table.address, - Self::AddressLimb0 => config.sort_keys.address.limbs[0], - Self::AddressLimb1 => config.sort_keys.address.limbs[1], - Self::StorageKey => config.rw_table.storage_key, - Self::StorageKeyByte0 => config.sort_keys.storage_key.bytes[0], - Self::StorageKeyByte1 => config.sort_keys.storage_key.bytes[1], - Self::Value => config.rw_table.value, - Self::ValuePrev => config.rw_table.value_prev, - Self::RwCounter => config.rw_table.rw_counter, - Self::RwCounterLimb0 => config.sort_keys.rw_counter.limbs[0], - Self::RwCounterLimb1 => config.sort_keys.rw_counter.limbs[1], - Self::Tag => config.rw_table.tag, - Self::TagBit0 => config.sort_keys.tag.bits[0], - Self::TagBit1 => config.sort_keys.tag.bits[1], - Self::TagBit2 => config.sort_keys.tag.bits[2], - Self::TagBit3 => config.sort_keys.tag.bits[3], - Self::LimbIndexBit0 => config.lexicographic_ordering.first_different_limb.bits[0], - Self::LimbIndexBit1 => config.lexicographic_ordering.first_different_limb.bits[1], - Self::LimbIndexBit2 => config.lexicographic_ordering.first_different_limb.bits[2], - Self::LimbIndexBit3 => config.lexicographic_ordering.first_different_limb.bits[3], - Self::LimbIndexBit4 => config.lexicographic_ordering.first_different_limb.bits[4], - Self::InitialValue => config.initial_value, - Self::IsZero => config.is_non_exist.is_zero, - Self::NonEmptyWitness => config.is_non_exist.nonempty_witness, - } - } -} - fn test_state_circuit_ok( memory_ops: Vec>, stack_ops: Vec>, diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index 64060c11a7..9f4e55ebd3 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -50,6 +50,9 @@ //! - [x] Tx Circuit //! - [ ] MPT Circuit +#[cfg(any(feature = "test", test))] +pub(crate) mod test; + use crate::{ bytecode_circuit::circuit::{ BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs, @@ -450,193 +453,3 @@ impl::default(); - SuperCircuit::<_, 1, 32, 0x100>::configure(&mut cs); - log::info!("super circuit degree: {}", cs.degree()); - log::info!("super circuit minimum_rows: {}", cs.minimum_rows()); - assert!(cs.degree() <= 9); - } - - fn test_super_circuit< - const MAX_TXS: usize, - const MAX_CALLDATA: usize, - const MOCK_RANDOMNESS: u64, - >( - block: GethData, - circuits_params: CircuitsParams, - ) { - let (k, circuit, instance, _) = - SuperCircuit::::build( - block, - circuits_params, - ) - .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"); - } - } - - pub(crate) fn block_1tx() -> 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, 1>::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)); - }, - |block, _tx| block.number(0xcafeu64), - ) - .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 - } - - const TEST_MOCK_RANDOMNESS: u64 = 0x100; - - // 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; - let circuits_params = CircuitsParams { - max_txs: MAX_TXS, - max_calldata: MAX_CALLDATA, - max_rws: 256, - max_copy_rows: 256, - max_exp_steps: 256, - max_bytecode: 512, - max_evm_rows: 0, - max_keccak_rows: 0, - }; - test_super_circuit::(block, circuits_params); - } - #[ignore] - #[test] - fn serial_test_super_circuit_1tx_2max_tx() { - let block = block_1tx(); - const MAX_TXS: usize = 2; - const MAX_CALLDATA: usize = 32; - let circuits_params = CircuitsParams { - max_txs: MAX_TXS, - max_calldata: MAX_CALLDATA, - max_rws: 256, - max_copy_rows: 256, - max_exp_steps: 256, - max_bytecode: 512, - max_evm_rows: 0, - max_keccak_rows: 0, - }; - test_super_circuit::(block, circuits_params); - } - #[ignore] - #[test] - fn serial_test_super_circuit_2tx_2max_tx() { - let block = block_2tx(); - const MAX_TXS: usize = 2; - const MAX_CALLDATA: usize = 32; - let circuits_params = CircuitsParams { - max_txs: MAX_TXS, - max_calldata: MAX_CALLDATA, - max_rws: 256, - max_copy_rows: 256, - max_exp_steps: 256, - max_bytecode: 512, - max_evm_rows: 0, - max_keccak_rows: 0, - }; - test_super_circuit::(block, circuits_params); - } -} diff --git a/zkevm-circuits/src/super_circuit/test.rs b/zkevm-circuits/src/super_circuit/test.rs new file mode 100644 index 0000000000..989d143958 --- /dev/null +++ b/zkevm-circuits/src/super_circuit/test.rs @@ -0,0 +1,183 @@ +pub use super::*; +use ethers_signers::{LocalWallet, Signer}; +use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; +use log::error; +use mock::{TestContext, MOCK_CHAIN_ID}; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; +use std::collections::HashMap; + +use eth_types::{address, bytecode, geth_types::GethData, Word}; + +#[test] +fn super_circuit_degree() { + let mut cs = ConstraintSystem::::default(); + SuperCircuit::<_, 1, 32, 0x100>::configure(&mut cs); + log::info!("super circuit degree: {}", cs.degree()); + log::info!("super circuit minimum_rows: {}", cs.minimum_rows()); + assert!(cs.degree() <= 9); +} + +fn test_super_circuit< + const MAX_TXS: usize, + const MAX_CALLDATA: usize, + const MOCK_RANDOMNESS: u64, +>( + block: GethData, + circuits_params: CircuitsParams, +) { + let (k, circuit, instance, _) = + SuperCircuit::::build(block, circuits_params) + .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"); + } +} + +pub(crate) fn block_1tx() -> 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, 1>::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)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .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 +} + +const TEST_MOCK_RANDOMNESS: u64 = 0x100; + +// 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; + let circuits_params = CircuitsParams { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_rws: 256, + max_copy_rows: 256, + max_exp_steps: 256, + max_bytecode: 512, + max_evm_rows: 0, + max_keccak_rows: 0, + }; + test_super_circuit::(block, circuits_params); +} +#[ignore] +#[test] +fn serial_test_super_circuit_1tx_2max_tx() { + let block = block_1tx(); + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 32; + let circuits_params = CircuitsParams { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_rws: 256, + max_copy_rows: 256, + max_exp_steps: 256, + max_bytecode: 512, + max_evm_rows: 0, + max_keccak_rows: 0, + }; + test_super_circuit::(block, circuits_params); +} +#[ignore] +#[test] +fn serial_test_super_circuit_2tx_2max_tx() { + let block = block_2tx(); + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 32; + let circuits_params = CircuitsParams { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_rws: 256, + max_copy_rows: 256, + max_exp_steps: 256, + max_bytecode: 512, + max_evm_rows: 0, + max_keccak_rows: 0, + }; + test_super_circuit::(block, circuits_params); +} diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 7c4b2f36a6..c1eddcbb83 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -1,9 +1,9 @@ //! Table definitions used cross-circuits use crate::{ - copy_circuit::number_or_hash_to_field, + copy_circuit::util::number_or_hash_to_field, evm_circuit::util::rlc, - exp_circuit::{OFFSET_INCREMENT, ROWS_PER_STEP}, + exp_circuit::param::{OFFSET_INCREMENT, ROWS_PER_STEP}, impl_expr, util::{build_tx_log_address, Challenges}, witness::{ diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index ab1d09f55e..d49db63e21 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -6,6 +6,13 @@ pub mod sign_verify; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +mod dev; +#[cfg(any(feature = "test", test))] +mod test; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use dev::TxCircuit as TestTxCircuit; + use crate::{ table::{KeccakTable, TxFieldTag, TxTable}, util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}, @@ -23,20 +30,6 @@ use log::error; use sign_verify::{AssignedSignatureVerify, SignVerifyChip, SignVerifyConfig}; use std::marker::PhantomData; -pub use halo2_proofs::halo2curves::{ - group::{ - ff::{Field as GroupField, PrimeField}, - prime::PrimeCurveAffine, - Curve, Group, GroupEncoding, - }, - secp256k1::{self, Secp256k1Affine, Secp256k1Compressed}, -}; - -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -use bus_mapping::circuit_input_builder::keccak_inputs_tx_circuit; -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; - /// 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]. @@ -386,163 +379,3 @@ impl SubCircuit for TxCircuit { vec![vec![]] } } - -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -impl Circuit for TxCircuit { - type Config = (TxCircuitConfig, Challenges); - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let tx_table = TxTable::construct(meta); - let keccak_table = KeccakTable::construct(meta); - let challenges = Challenges::construct(meta); - - let config = { - let challenges = challenges.exprs(meta); - TxCircuitConfig::new( - meta, - TxCircuitConfigArgs { - tx_table, - keccak_table, - challenges, - }, - ) - }; - - (config, challenges) - } - - fn synthesize( - &self, - (config, challenges): Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let challenges = challenges.values(&mut layouter); - - config.keccak_table.dev_load( - &mut layouter, - &keccak_inputs_tx_circuit(&self.txs[..], self.chain_id).map_err(|e| { - error!("keccak_inputs_tx_circuit error: {:?}", e); - Error::Synthesis - })?, - &challenges, - )?; - self.synthesize_sub(&config, &challenges, &mut layouter) - } -} - -#[cfg(test)] -mod tx_circuit_tests { - use super::*; - use crate::util::log2_ceil; - use eth_types::address; - use halo2_proofs::{ - dev::{MockProver, VerifyFailure}, - halo2curves::bn256::Fr, - }; - use mock::AddrOrWallet; - use pretty_assertions::assert_eq; - - const NUM_BLINDING_ROWS: usize = 64; - - fn run( - 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); - - let prover = match MockProver::run(k, &circuit, vec![vec![]]) { - Ok(prover) => prover, - Err(e) => panic!("{:#?}", e), - }; - prover.verify() - } - - #[test] - fn tx_circuit_2tx_2max_tx() { - const NUM_TXS: usize = 2; - const MAX_TXS: usize = 2; - const MAX_CALLDATA: usize = 32; - - assert_eq!( - run::( - mock::CORRECT_MOCK_TXS[..NUM_TXS] - .iter() - .map(|tx| Transaction::from(tx.clone())) - .collect_vec(), - mock::MOCK_CHAIN_ID.as_u64(), - MAX_TXS, - MAX_CALLDATA - ), - Ok(()) - ); - } - - #[test] - fn tx_circuit_1tx_1max_tx() { - const MAX_TXS: usize = 1; - 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] - 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] - fn tx_circuit_bad_address() { - const MAX_TXS: usize = 1; - const MAX_CALLDATA: usize = 32; - - let mut tx = mock::CORRECT_MOCK_TXS[0].clone(); - // This address doesn't correspond to the account that signed this tx. - tx.from = AddrOrWallet::from(address!("0x1230000000000000000000000000000000000456")); - - assert!(run::( - vec![tx.into()], - mock::MOCK_CHAIN_ID.as_u64(), - MAX_TXS, - MAX_CALLDATA - ) - .is_err(),); - } - - #[test] - fn variadic_size_check() { - const MAX_TXS: usize = 2; - const MAX_CALLDATA: usize = 32; - - let chain_id: u64 = mock::MOCK_CHAIN_ID.as_u64(); - let tx1: Transaction = mock::CORRECT_MOCK_TXS[0].clone().into(); - let tx2: Transaction = mock::CORRECT_MOCK_TXS[1].clone().into(); - let circuit = TxCircuit::::new(MAX_TXS, MAX_CALLDATA, chain_id, vec![tx1.clone()]); - let prover1 = MockProver::::run(20, &circuit, vec![vec![]]).unwrap(); - - let circuit = TxCircuit::::new(MAX_TXS, MAX_CALLDATA, chain_id, vec![tx1, tx2]); - let prover2 = MockProver::::run(20, &circuit, vec![vec![]]).unwrap(); - - assert_eq!(prover1.fixed(), prover2.fixed()); - assert_eq!(prover1.permutation(), prover2.permutation()); - } -} diff --git a/zkevm-circuits/src/tx_circuit/dev.rs b/zkevm-circuits/src/tx_circuit/dev.rs new file mode 100644 index 0000000000..5ddeb02c34 --- /dev/null +++ b/zkevm-circuits/src/tx_circuit/dev.rs @@ -0,0 +1,61 @@ +pub use super::TxCircuit; + +use crate::{ + table::{KeccakTable, TxTable}, + tx_circuit::{TxCircuitConfig, TxCircuitConfigArgs}, + util::{Challenges, SubCircuit, SubCircuitConfig}, +}; +use bus_mapping::circuit_input_builder::keccak_inputs_tx_circuit; +use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, +}; +use log::error; + +impl Circuit for TxCircuit { + type Config = (TxCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let tx_table = TxTable::construct(meta); + let keccak_table = KeccakTable::construct(meta); + let challenges = Challenges::construct(meta); + + let config = { + let challenges = challenges.exprs(meta); + TxCircuitConfig::new( + meta, + TxCircuitConfigArgs { + tx_table, + keccak_table, + challenges, + }, + ) + }; + + (config, challenges) + } + + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + + config.keccak_table.dev_load( + &mut layouter, + &keccak_inputs_tx_circuit(&self.txs[..], self.chain_id).map_err(|e| { + error!("keccak_inputs_tx_circuit error: {:?}", e); + Error::Synthesis + })?, + &challenges, + )?; + self.synthesize_sub(&config, &challenges, &mut layouter) + } +} diff --git a/zkevm-circuits/src/tx_circuit/test.rs b/zkevm-circuits/src/tx_circuit/test.rs new file mode 100644 index 0000000000..71ed0ce597 --- /dev/null +++ b/zkevm-circuits/src/tx_circuit/test.rs @@ -0,0 +1,108 @@ +#![allow(unused_imports)] +use super::*; +use crate::util::log2_ceil; +use eth_types::address; +use halo2_proofs::{ + dev::{MockProver, VerifyFailure}, + halo2curves::bn256::Fr, +}; +use mock::AddrOrWallet; + +const NUM_BLINDING_ROWS: usize = 64; + +fn run( + 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); + + let prover = match MockProver::run(k, &circuit, vec![vec![]]) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + prover.verify() +} + +#[test] +fn tx_circuit_2tx_2max_tx() { + const NUM_TXS: usize = 2; + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 32; + + assert_eq!( + run::( + mock::CORRECT_MOCK_TXS[..NUM_TXS] + .iter() + .map(|tx| Transaction::from(tx.clone())) + .collect_vec(), + mock::MOCK_CHAIN_ID.as_u64(), + MAX_TXS, + MAX_CALLDATA + ), + Ok(()) + ); +} + +#[test] +fn tx_circuit_1tx_1max_tx() { + const MAX_TXS: usize = 1; + 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] +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] +fn tx_circuit_bad_address() { + const MAX_TXS: usize = 1; + const MAX_CALLDATA: usize = 32; + + let mut tx = mock::CORRECT_MOCK_TXS[0].clone(); + // This address doesn't correspond to the account that signed this tx. + tx.from = AddrOrWallet::from(address!("0x1230000000000000000000000000000000000456")); + + assert!(run::( + vec![tx.into()], + mock::MOCK_CHAIN_ID.as_u64(), + MAX_TXS, + MAX_CALLDATA + ) + .is_err(),); +} + +#[test] +fn variadic_size_check() { + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 32; + + let chain_id: u64 = mock::MOCK_CHAIN_ID.as_u64(); + let tx1: Transaction = mock::CORRECT_MOCK_TXS[0].clone().into(); + let tx2: Transaction = mock::CORRECT_MOCK_TXS[1].clone().into(); + let circuit = TxCircuit::::new(MAX_TXS, MAX_CALLDATA, chain_id, vec![tx1.clone()]); + let prover1 = MockProver::::run(20, &circuit, vec![vec![]]).unwrap(); + + let circuit = TxCircuit::::new(MAX_TXS, MAX_CALLDATA, chain_id, vec![tx1, tx2]); + let prover2 = MockProver::::run(20, &circuit, vec![vec![]]).unwrap(); + + assert_eq!(prover1.fixed(), prover2.fixed()); + assert_eq!(prover1.permutation(), prover2.permutation()); +} diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index a6a025da60..6c42920dab 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -68,7 +68,7 @@ impl Block { } #[cfg(feature = "test")] -use crate::exp_circuit::OFFSET_INCREMENT; +use crate::exp_circuit::param::OFFSET_INCREMENT; #[cfg(feature = "test")] use crate::util::log2_ceil;