diff --git a/Cargo.lock b/Cargo.lock index 1f50a07fbb..119a4ce58f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4132,6 +4132,7 @@ dependencies = [ "strum_macros", "thiserror", "toml", + "urlencoding", "yaml-rust", "zkevm-circuits", ] @@ -4450,6 +4451,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" + [[package]] name = "utf-8" version = "0.7.6" diff --git a/bus-mapping/src/error.rs b/bus-mapping/src/error.rs index 0660993159..d954209a67 100644 --- a/bus-mapping/src/error.rs +++ b/bus-mapping/src/error.rs @@ -94,16 +94,10 @@ pub enum OogError { Sload, /// Out of Gas for SSTORE Sstore, - /// Out of Gas for CALL + /// Out of Gas for CALL, CALLCODE, DELEGATECALL and STATICCALL Call, - /// Out of Gas for CALLCODE - CallCode, - /// Out of Gas for DELEGATECALL - DelegateCall, /// Out of Gas for CREATE2 Create2, - /// Out of Gas for STATICCALL - StaticCall, /// Out of Gas for SELFDESTRUCT SelfDestruct, } @@ -166,11 +160,10 @@ pub(crate) fn get_step_reported_error(op: &OpcodeId, error: &str) -> ExecError { OpcodeId::EXTCODECOPY => OogError::ExtCodeCopy, OpcodeId::SLOAD => OogError::Sload, OpcodeId::SSTORE => OogError::Sstore, - OpcodeId::CALL => OogError::Call, - OpcodeId::CALLCODE => OogError::CallCode, - OpcodeId::DELEGATECALL => OogError::DelegateCall, + OpcodeId::CALL | OpcodeId::CALLCODE | OpcodeId::DELEGATECALL | OpcodeId::STATICCALL => { + OogError::Call + } OpcodeId::CREATE2 => OogError::Create2, - OpcodeId::STATICCALL => OogError::StaticCall, OpcodeId::SELFDESTRUCT => OogError::SelfDestruct, _ => OogError::Constant, }; diff --git a/bus-mapping/src/evm/opcodes/error_oog_call.rs b/bus-mapping/src/evm/opcodes/error_oog_call.rs index aefeb1cd6f..a4e01978cf 100644 --- a/bus-mapping/src/evm/opcodes/error_oog_call.rs +++ b/bus-mapping/src/evm/opcodes/error_oog_call.rs @@ -1,13 +1,14 @@ use super::Opcode; -use crate::{ - circuit_input_builder::{CircuitInputStateRef, ExecStep}, - operation::{AccountField, CallContextField, TxAccessListAccountOp, RW}, - Error, -}; +use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep}; +use crate::operation::{AccountField, CallContextField, TxAccessListAccountOp, RW}; +use crate::Error; +use eth_types::evm_types::OpcodeId; use eth_types::{GethExecStep, ToAddress, ToWord, Word}; /// Placeholder structure used to implement [`Opcode`] trait over it -/// corresponding to the `OpcodeId::CALL` `OpcodeId`. +/// corresponding to the out of gas errors for [`OpcodeId::CALL`], +/// [`OpcodeId::CALLCODE`], [`OpcodeId::DELEGATECALL`] and +/// [`OpcodeId::STATICCALL`]. #[derive(Debug, Copy, Clone)] pub(crate) struct OOGCall; @@ -17,6 +18,12 @@ impl Opcode for OOGCall { geth_steps: &[GethExecStep], ) -> Result, Error> { let geth_step = &geth_steps[0]; + let stack_input_num = match geth_step.op { + OpcodeId::CALL | OpcodeId::CALLCODE => 7, + OpcodeId::DELEGATECALL | OpcodeId::STATICCALL => 6, + op => unreachable!("{op} should not happen in OOGCall"), + }; + let mut exec_step = state.new_step(geth_step)?; let next_step = if geth_steps.len() > 1 { Some(&geth_steps[1]) @@ -25,10 +32,10 @@ impl Opcode for OOGCall { }; exec_step.error = state.get_step_err(geth_step, next_step).unwrap(); - let args_offset = geth_step.stack.nth_last(3)?.as_usize(); - let args_length = geth_step.stack.nth_last(4)?.as_usize(); - let ret_offset = geth_step.stack.nth_last(5)?.as_usize(); - let ret_length = geth_step.stack.nth_last(6)?.as_usize(); + let args_offset = geth_step.stack.nth_last(stack_input_num - 4)?.as_usize(); + let args_length = geth_step.stack.nth_last(stack_input_num - 3)?.as_usize(); + let ret_offset = geth_step.stack.nth_last(stack_input_num - 2)?.as_usize(); + let ret_length = geth_step.stack.nth_last(stack_input_num - 1)?.as_usize(); state.call_expand_memory(args_offset, args_length, ret_offset, ret_length)?; @@ -46,7 +53,7 @@ impl Opcode for OOGCall { state.call_context_read(&mut exec_step, current_call.call_id, field, value); } - for i in 0..7 { + for i in 0..stack_input_num { state.stack_read( &mut exec_step, geth_step.stack.nth_last_filled(i), @@ -56,8 +63,9 @@ impl Opcode for OOGCall { state.stack_write( &mut exec_step, - geth_step.stack.nth_last_filled(6), - (0u64).into(), // must fail + geth_step.stack.nth_last_filled(stack_input_num - 1), + // Must fail. + (0_u64).into(), )?; let (_, callee_account) = state.sdb.get_account(&call_address); diff --git a/circuit-benchmarks/src/bytecode_circuit.rs b/circuit-benchmarks/src/bytecode_circuit.rs index bb6e0e9de3..f4487db60c 100644 --- a/circuit-benchmarks/src/bytecode_circuit.rs +++ b/circuit-benchmarks/src/bytecode_circuit.rs @@ -19,9 +19,9 @@ mod tests { use rand::SeedableRng; use rand_xorshift::XorShiftRng; use std::env::var; - use zkevm_circuits::bytecode_circuit::bytecode_unroller::{ - unroll, BytecodeCircuit, UnrolledBytecode, - }; + use zkevm_circuits::bytecode_circuit::bytecode_unroller::{unroll, UnrolledBytecode}; + + use zkevm_circuits::bytecode_circuit::circuit::BytecodeCircuit; #[cfg_attr(not(feature = "benches"), ignore)] #[test] diff --git a/circuit-benchmarks/src/super_circuit.rs b/circuit-benchmarks/src/super_circuit.rs index 8d24b04403..ac9cddfcd5 100644 --- a/circuit-benchmarks/src/super_circuit.rs +++ b/circuit-benchmarks/src/super_circuit.rs @@ -3,6 +3,7 @@ #[cfg(test)] mod tests { use ark_std::{end_timer, start_timer}; + use bus_mapping::circuit_input_builder::CircuitsParams; use eth_types::geth_types::GethData; use eth_types::{address, bytecode, Word}; use ethers_signers::LocalWallet; @@ -71,7 +72,18 @@ mod tests { block.sign(&wallets); - let (_, circuit, instance, _) = SuperCircuit::<_, 1, 32, 512, 512>::build(block).unwrap(); + 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_bytecode: 512, + keccak_padding: None, + }; + let (_, circuit, instance, _) = + SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, 0x100>::build(block, circuits_params).unwrap(); let instance_refs: Vec<&[Fr]> = instance.iter().map(|v| &v[..]).collect(); // Bench setup generation @@ -96,7 +108,7 @@ mod tests { Challenge255, ChaChaRng, Blake2bWrite, G1Affine, Challenge255>, - SuperCircuit, + SuperCircuit, >( &general_params, &pk, diff --git a/integration-tests/src/integration_test_circuits.rs b/integration-tests/src/integration_test_circuits.rs index 1f17cc7e6a..cc2ed097c0 100644 --- a/integration-tests/src/integration_test_circuits.rs +++ b/integration-tests/src/integration_test_circuits.rs @@ -23,29 +23,49 @@ use rand_core::RngCore; use rand_xorshift::XorShiftRng; use std::collections::HashMap; use std::sync::Mutex; -use zkevm_circuits::bytecode_circuit::bytecode_unroller::BytecodeCircuit; +use zkevm_circuits::bytecode_circuit::circuit::BytecodeCircuit; use zkevm_circuits::copy_circuit::CopyCircuit; -use zkevm_circuits::evm_circuit::test::get_test_degree; -use zkevm_circuits::evm_circuit::{test::get_test_cicuit_from_block, witness::block_convert}; +use zkevm_circuits::evm_circuit::witness::block_convert; use zkevm_circuits::state_circuit::StateCircuit; -use zkevm_circuits::super_circuit::SuperCircuit; use zkevm_circuits::tx_circuit::TxCircuit; use zkevm_circuits::util::SubCircuit; use zkevm_circuits::witness::Block; +/// TEST_MOCK_RANDOMNESS +pub const TEST_MOCK_RANDOMNESS: u64 = 0x100; + +/// MAX_TXS +pub const MAX_TXS: usize = 4; +/// MAX_CALLDATA +pub const MAX_CALLDATA: usize = 512; +/// MAX_RWS +pub const MAX_RWS: usize = 5888; +/// MAX_BYTECODE +pub const MAX_BYTECODE: usize = 5000; +/// MAX_COPY_ROWS +pub const MAX_COPY_ROWS: usize = 5888; + const CIRCUITS_PARAMS: CircuitsParams = CircuitsParams { - max_rws: 16384, - max_txs: 4, - max_calldata: 4000, - max_bytecode: 4000, - max_copy_rows: 16384, + max_rws: MAX_RWS, + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_bytecode: MAX_BYTECODE, + max_copy_rows: MAX_COPY_ROWS, keccak_padding: None, }; -const STATE_CIRCUIT_DEGREE: u32 = 17; -const TX_CIRCUIT_DEGREE: u32 = 20; -const BYTECODE_CIRCUIT_DEGREE: u32 = 16; -const COPY_CIRCUIT_DEGREE: u32 = 16; +/// EVM Circuit degree +pub const EVM_CIRCUIT_DEGREE: u32 = 18; +/// State Circuit degree +pub const STATE_CIRCUIT_DEGREE: u32 = 17; +/// Tx Circuit degree +pub const TX_CIRCUIT_DEGREE: u32 = 20; +/// Bytecode Circuit degree +pub const BYTECODE_CIRCUIT_DEGREE: u32 = 16; +/// Copy Circuit degree +pub const COPY_CIRCUIT_DEGREE: u32 = 16; +/// Super Circuit degree +pub const SUPER_CIRCUIT_DEGREE: u32 = 20; lazy_static! { /// Data generation. @@ -61,7 +81,8 @@ lazy_static! { } lazy_static! { - static ref STATE_CIRCUIT_KEY: ProvingKey = { + /// State Circuit proving key + pub static ref STATE_CIRCUIT_KEY: ProvingKey = { let block = new_empty_block(); let circuit = StateCircuit::::new_from_block(&block); let general_params = get_general_params(STATE_CIRCUIT_DEGREE); @@ -70,7 +91,8 @@ lazy_static! { keygen_vk(&general_params, &circuit).expect("keygen_vk should not fail"); keygen_pk(&general_params, verifying_key, &circuit).expect("keygen_pk should not fail") }; - static ref TX_CIRCUIT_KEY: ProvingKey = { + /// Tx Circuit proving key + pub static ref TX_CIRCUIT_KEY: ProvingKey = { let block = new_empty_block(); let circuit = TxCircuit::::new_from_block(&block); let general_params = get_general_params(TX_CIRCUIT_DEGREE); @@ -79,7 +101,8 @@ lazy_static! { keygen_vk(&general_params, &circuit).expect("keygen_vk should not fail"); keygen_pk(&general_params, verifying_key, &circuit).expect("keygen_pk should not fail") }; - static ref BYTECODE_CIRCUIT_KEY: ProvingKey = { + /// Bytecode Circuit proving key + pub static ref BYTECODE_CIRCUIT_KEY: ProvingKey = { let block = new_empty_block(); let circuit = BytecodeCircuit::::new_from_block(&block); let general_params = get_general_params(BYTECODE_CIRCUIT_DEGREE); @@ -88,7 +111,8 @@ lazy_static! { keygen_vk(&general_params, &circuit).expect("keygen_vk should not fail"); keygen_pk(&general_params, verifying_key, &circuit).expect("keygen_pk should not fail") }; - static ref COPY_CIRCUIT_KEY: ProvingKey = { + /// Copy Circuit proving key + pub static ref COPY_CIRCUIT_KEY: ProvingKey = { let block = new_empty_block(); let circuit = CopyCircuit::::new_from_block(&block); let general_params = get_general_params(COPY_CIRCUIT_DEGREE); @@ -238,142 +262,24 @@ fn test_mock>(degree: u32, circuit: &C, instance: Vec>) { .expect("mock prover verification failed"); } -/// Integration test for evm circuit. -pub async fn test_evm_circuit_block(block_num: u64, actual: bool) { - log::info!("test evm circuit, block number: {}", block_num); - let (builder, _) = gen_inputs(block_num).await; - - let block = block_convert(&builder.block, &builder.code_db).unwrap(); - - let degree = get_test_degree(&block); - let circuit = get_test_cicuit_from_block(block); - - if actual { - test_actual(degree, circuit, vec![], None); - } else { - test_mock(degree, &circuit, vec![]); - } -} - -/// Integration test for state circuit. -pub async fn test_state_circuit_block(block_num: u64, actual: bool) { - log::info!("test state circuit, block number: {}", block_num); - +/// Integration test generic function +pub async fn test_circuit_at_block + Circuit>( + circuit_name: &str, + degree: u32, + block_num: u64, + actual: bool, + proving_key: Option>, +) { + log::info!("test {} circuit, block number: {}", circuit_name, block_num); let (builder, _) = gen_inputs(block_num).await; - let block = block_convert(&builder.block, &builder.code_db).unwrap(); - - let circuit = StateCircuit::::new_from_block(&block); + let mut block = block_convert(&builder.block, &builder.code_db).unwrap(); + block.randomness = Fr::from(TEST_MOCK_RANDOMNESS); + let circuit = C::new_from_block(&block); let instance = circuit.instance(); if actual { - test_actual( - STATE_CIRCUIT_DEGREE, - circuit, - instance, - Some((*STATE_CIRCUIT_KEY).clone()), - ); + test_actual(degree, circuit, instance, proving_key); } else { - test_mock(STATE_CIRCUIT_DEGREE, &circuit, instance); - } -} - -/// Integration test for tx circuit. -pub async fn test_tx_circuit_block(block_num: u64, actual: bool) { - log::info!("test tx circuit, block number: {}", block_num); - - let (builder, _) = gen_inputs(block_num).await; - - let block = block_convert(&builder.block, &builder.code_db).unwrap(); - let circuit = TxCircuit::::new_from_block(&block); - - if actual { - test_actual( - TX_CIRCUIT_DEGREE, - circuit, - vec![vec![]], - Some((*TX_CIRCUIT_KEY).clone()), - ); - } else { - test_mock(TX_CIRCUIT_DEGREE, &circuit, vec![vec![]]); - } -} - -/// Integration test for bytecode circuit. -pub async fn test_bytecode_circuit_block(block_num: u64, actual: bool) { - log::info!("test bytecode circuit, block number: {}", block_num); - let (builder, _) = gen_inputs(block_num).await; - - let block = block_convert(&builder.block, &builder.code_db).unwrap(); - let circuit = - BytecodeCircuit::::new_from_block_sized(&block, 2usize.pow(BYTECODE_CIRCUIT_DEGREE)); - - if actual { - test_actual( - BYTECODE_CIRCUIT_DEGREE, - circuit, - Vec::new(), - Some((*BYTECODE_CIRCUIT_KEY).clone()), - ); - } else { - test_mock(BYTECODE_CIRCUIT_DEGREE, &circuit, Vec::new()); - } -} - -/// Integration test for copy circuit. -pub async fn test_copy_circuit_block(block_num: u64, actual: bool) { - log::info!("test copy circuit, block number: {}", block_num); - let (builder, _) = gen_inputs(block_num).await; - let block = block_convert(&builder.block, &builder.code_db).unwrap(); - - let circuit = CopyCircuit::::new_from_block(&block); - - if actual { - test_actual( - COPY_CIRCUIT_DEGREE, - circuit, - vec![], - Some((*COPY_CIRCUIT_KEY).clone()), - ); - } else { - test_mock(COPY_CIRCUIT_DEGREE, &circuit, vec![]); - } -} - -/// Integration test for super circuit. -pub async fn test_super_circuit_block(block_num: u64) { - const MAX_TXS: usize = 4; - const MAX_CALLDATA: usize = 512; - const MAX_RWS: usize = 5888; - const MAX_BYTECODE: usize = 5000; - const MAX_COPY_ROWS: usize = 5888; - - log::info!("test super circuit, block number: {}", block_num); - let cli = get_client(); - let cli = BuilderClient::new( - cli, - CircuitsParams { - max_rws: MAX_RWS, - max_txs: MAX_TXS, - max_calldata: MAX_CALLDATA, - max_bytecode: MAX_BYTECODE, - max_copy_rows: MAX_COPY_ROWS, - keccak_padding: None, - }, - ) - .await - .unwrap(); - let (builder, _) = cli.gen_inputs(block_num).await.unwrap(); - let (k, circuit, instance) = - SuperCircuit::::build_from_circuit_input_builder( - &builder, - ) - .unwrap(); - // TODO: add actual prover - let prover = MockProver::run(k, &circuit, instance).unwrap(); - let res = prover.verify_par(); - if let Err(err) = res { - eprintln!("Verification failures:"); - eprintln!("{:#?}", err); - panic!("Failed verification"); + test_mock(degree, &circuit, instance); } } diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index 6702ec46db..ce0a7bd4e2 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -5,35 +5,54 @@ macro_rules! declare_tests { async fn []() { log_init(); let block_num = GEN_DATA.blocks.get($block_tag).unwrap(); - test_evm_circuit_block(*block_num, $real_prover).await; + let pk = None; + test_circuit_at_block::>( + "evm", EVM_CIRCUIT_DEGREE, *block_num, $real_prover, pk).await; } #[tokio::test] async fn []() { log_init(); let block_num = GEN_DATA.blocks.get($block_tag).unwrap(); - test_state_circuit_block(*block_num, $real_prover).await; + let pk = if $real_prover { Some((*STATE_CIRCUIT_KEY).clone()) } else { None }; + test_circuit_at_block::> + ("state", STATE_CIRCUIT_DEGREE, *block_num, $real_prover, pk).await; } #[tokio::test] async fn []() { log_init(); let block_num = GEN_DATA.blocks.get($block_tag).unwrap(); - test_tx_circuit_block(*block_num, $real_prover).await; + let pk = if $real_prover { Some((*TX_CIRCUIT_KEY).clone()) } else { None }; + test_circuit_at_block::> + ("tx", TX_CIRCUIT_DEGREE, *block_num, $real_prover, pk).await; } #[tokio::test] async fn []() { log_init(); let block_num = GEN_DATA.blocks.get($block_tag).unwrap(); - test_bytecode_circuit_block(*block_num, $real_prover).await; + let pk = if $real_prover { Some((*BYTECODE_CIRCUIT_KEY).clone()) } else { None }; + test_circuit_at_block::> + ("bytecode", BYTECODE_CIRCUIT_DEGREE, *block_num, $real_prover, pk).await; } #[tokio::test] async fn []() { log_init(); let block_num = GEN_DATA.blocks.get($block_tag).unwrap(); - test_copy_circuit_block(*block_num, $real_prover).await; + let pk = if $real_prover { Some((*COPY_CIRCUIT_KEY).clone()) } else { None }; + test_circuit_at_block::> + ("copy", COPY_CIRCUIT_DEGREE, *block_num, $real_prover, pk).await; + } + + #[tokio::test] + async fn []() { + log_init(); + let block_num = GEN_DATA.blocks.get($block_tag).unwrap(); + let pk = None; + test_circuit_at_block::> + ("super", SUPER_CIRCUIT_DEGREE, *block_num, $real_prover, pk).await; } } }; @@ -41,25 +60,27 @@ macro_rules! declare_tests { macro_rules! unroll_tests { ($($arg:tt),*) => { + use paste::paste; + use zkevm_circuits::{ + state_circuit::StateCircuit, + super_circuit::SuperCircuit, + tx_circuit::TxCircuit, + evm_circuit::EvmCircuit, + bytecode_circuit::circuit::BytecodeCircuit, + copy_circuit::CopyCircuit + }; + use halo2_proofs::halo2curves::bn256::Fr; + use integration_tests::integration_test_circuits::*; + use integration_tests::log_init; mod real_prover { - use paste::paste; - use integration_tests::integration_test_circuits::{ - test_bytecode_circuit_block, test_copy_circuit_block, test_evm_circuit_block, - test_state_circuit_block, test_tx_circuit_block, GEN_DATA, - }; - use integration_tests::log_init; + use super::*; $( declare_tests! ($arg, true) ; )* } mod mock_prover { - use paste::paste; - use integration_tests::integration_test_circuits::{ - test_bytecode_circuit_block, test_copy_circuit_block, test_evm_circuit_block, - test_state_circuit_block, test_tx_circuit_block, GEN_DATA, - }; - use integration_tests::log_init; + use super::*; $( declare_tests! ($arg, false) ; )* diff --git a/testool/Cargo.toml b/testool/Cargo.toml index 0e4dd6ab56..25ce600e36 100644 --- a/testool/Cargo.toml +++ b/testool/Cargo.toml @@ -34,6 +34,7 @@ zkevm-circuits = { path="../zkevm-circuits", features=["test"] } rand_chacha = "0.3" rand = "0.8" halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_01_20" } +urlencoding = "2.1.2" [features] diff --git a/testool/src/statetest/executor.rs b/testool/src/statetest/executor.rs index a5ee4e62ca..b9143311eb 100644 --- a/testool/src/statetest/executor.rs +++ b/testool/src/statetest/executor.rs @@ -275,8 +275,19 @@ pub fn run_test( } else { geth_data.sign(&wallets); + 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_bytecode: 512, + keccak_padding: None, + }; let (k, circuit, instance, _builder) = - SuperCircuit::::build(geth_data).unwrap(); + SuperCircuit::::build(geth_data, circuits_params) + .unwrap(); builder = _builder; let prover = MockProver::run(k, &circuit, instance).unwrap(); diff --git a/testool/src/statetest/report.handlebars b/testool/src/statetest/report.handlebars index df5204ea2a..38a847bddd 100644 --- a/testool/src/statetest/report.handlebars +++ b/testool/src/statetest/report.handlebars @@ -3,16 +3,26 @@ -

Diffs

+ +

Report

+This file contains:
+ + +

Diffs

{{{ diffs }}} -

Results by folder

+

Results by folder

{{{ by_folder }}} -

Results by type

+

Results by type

{{{ by_result }}} -

All results

+

All results

diff --git a/testool/src/statetest/results.rs b/testool/src/statetest/results.rs index 7437482558..0ff648f521 100644 --- a/testool/src/statetest/results.rs +++ b/testool/src/statetest/results.rs @@ -14,6 +14,8 @@ use std::str::FromStr; use strum::IntoEnumIterator; use strum_macros::{EnumIter, EnumString}; // 0.17.1 +const MAX_DETAILS_LEN: usize = 128; + #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, EnumIter, EnumString, Serialize, Deserialize)] pub enum ResultLevel { Success, @@ -51,6 +53,14 @@ pub struct Diffs { tests: Vec, } +fn trim(s: &str, max_len: usize) -> &str { + if s.len() > max_len { + &s[0..max_len] + } else { + s + } +} + impl Diffs { pub fn gen_info(&self) -> (String, Table) { let mut stat: HashMap = HashMap::new(); @@ -86,7 +96,10 @@ impl Diffs { t.id, format!( "{:?}({}) => {:?}({})", - prev.level, prev.details, curr.level, curr.details + prev.level, + trim(&prev.details, MAX_DETAILS_LEN), + curr.level, + trim(&curr.details, MAX_DETAILS_LEN) ), ]); } @@ -165,7 +178,9 @@ impl Results { let level = split.next().unwrap(); let level = ResultLevel::from_str(level).unwrap(); let id = split.next().unwrap().to_string(); - let details = split.next().unwrap().to_string(); + let details = urlencoding::decode(split.next().unwrap()) + .expect("should be urldecodeable") + .to_string(); tests.insert(id, ResultInfo { level, details }); } Ok(Self { cache: None, tests }) @@ -333,12 +348,12 @@ impl Results { result.details ); } - let details = result - .details - .replace('\n', "") - .replace(' ', "") - .replace('\t', ""); - let entry = format!("{:?};{};{}\n", result.level, test_id, details); + let entry = format!( + "{:?};{};{}\n", + result.level, + test_id, + urlencoding::encode(&result.details) + ); if let Some(path) = &self.cache { std::fs::OpenOptions::new() .read(true) diff --git a/zkevm-circuits/src/bytecode_circuit.rs b/zkevm-circuits/src/bytecode_circuit.rs index ff10723438..a9db23040f 100644 --- a/zkevm-circuits/src/bytecode_circuit.rs +++ b/zkevm-circuits/src/bytecode_circuit.rs @@ -2,6 +2,8 @@ /// Bytecode unroller pub mod bytecode_unroller; +/// Bytecode circuit +pub mod circuit; /// Bytecode circuit tester #[cfg(any(feature = "test", test))] pub mod dev; diff --git a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs index 2fb206dc11..e032461a1c 100644 --- a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs +++ b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs @@ -1,647 +1,25 @@ use crate::{ - evm_circuit::util::{ - and, constraint_builder::BaseConstraintBuilder, not, or, select, RandomLinearCombination, - }, - table::{BytecodeFieldTag, BytecodeTable, DynamicTableColumns, KeccakTable}, - util::{Challenges, Expr, SubCircuit, SubCircuitConfig}, - witness, + table::BytecodeFieldTag, + util::{get_push_size, keccak}, }; -use bus_mapping::evm::OpcodeId; -use eth_types::{Field, ToLittleEndian, Word}; -use gadgets::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; -use halo2_proofs::{ - circuit::{Layouter, Region, Value}, - plonk::{ - Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector, - VirtualCells, - }, - poly::Rotation, -}; -use keccak256::plain::Keccak; +use eth_types::{Field, Word}; use std::vec; -use super::param::PUSH_TABLE_WIDTH; /// Public data for the bytecode #[derive(Clone, Debug, PartialEq)] pub(crate) struct BytecodeRow { - code_hash: Word, - tag: F, - index: F, - is_code: F, - value: F, + pub(crate) code_hash: Word, + pub(crate) tag: F, + pub(crate) index: F, + pub(crate) is_code: F, + pub(crate) value: F, } /// Unrolled bytecode #[derive(Clone, Debug, PartialEq)] pub struct UnrolledBytecode { pub(crate) bytes: Vec, - rows: Vec>, -} - -#[derive(Clone, Debug)] -/// Bytecode circuit configuration -pub struct BytecodeCircuitConfig { - minimum_rows: usize, - q_enable: Column, - q_first: Column, - q_last: Selector, - bytecode_table: BytecodeTable, - push_rindex: Column, - hash_input_rlc: Column, - code_length: Column, - byte_push_size: Column, - is_final: Column, - padding: Column, - push_rindex_inv: Column, - push_rindex_is_zero: IsZeroConfig, - length_inv: Column, - length_is_zero: IsZeroConfig, - push_table: [Column; PUSH_TABLE_WIDTH], - // External tables - pub(crate) keccak_table: KeccakTable, -} - -/// Circuit configuration arguments -pub struct BytecodeCircuitConfigArgs { - /// BytecodeTable - pub bytecode_table: BytecodeTable, - /// KeccakTable - pub keccak_table: KeccakTable, - /// Challenges - pub challenges: Challenges>, -} - -impl SubCircuitConfig for BytecodeCircuitConfig { - type ConfigArgs = BytecodeCircuitConfigArgs; - - /// Return a new BytecodeCircuitConfig - fn new( - meta: &mut ConstraintSystem, - Self::ConfigArgs { - bytecode_table, - keccak_table, - challenges, - }: Self::ConfigArgs, - ) -> Self { - let q_enable = meta.fixed_column(); - let q_first = meta.fixed_column(); - let q_last = meta.selector(); - let value = bytecode_table.value; - let push_rindex = meta.advice_column(); - let hash_input_rlc = meta.advice_column_in(SecondPhase); - let code_length = meta.advice_column(); - let byte_push_size = meta.advice_column(); - let is_final = meta.advice_column(); - let padding = meta.advice_column(); - let push_rindex_inv = meta.advice_column(); - let length_inv = meta.advice_column(); - let push_table = array_init::array_init(|_| meta.fixed_column()); - - // A byte is an opcode when `push_rindex == 0` on the previous row, - // else it's push data. - let push_rindex_is_zero = IsZeroChip::configure( - meta, - |meta| { - // Conditions: - // - Not on the first row - meta.query_fixed(q_enable, Rotation::cur()) - * not::expr(meta.query_fixed(q_first, Rotation::cur())) - }, - |meta| meta.query_advice(push_rindex, Rotation::prev()), - push_rindex_inv, - ); - - // Does the current row have bytecode field tag == Length? - let is_row_tag_length = |meta: &mut VirtualCells| { - and::expr(vec![ - not::expr(meta.query_advice(padding, Rotation::cur())), - not::expr(meta.query_advice(bytecode_table.tag, Rotation::cur())), - ]) - }; - - // Does the current row have bytecode field tag == Byte? - let is_row_tag_byte = |meta: &mut VirtualCells| { - and::expr(vec![ - not::expr(meta.query_advice(padding, Rotation::cur())), - meta.query_advice(bytecode_table.tag, Rotation::cur()), - ]) - }; - - // For a row tagged Length, is the length (value) zero? - let length_is_zero = IsZeroChip::configure( - meta, - |meta| meta.query_fixed(q_enable, Rotation::cur()) * is_row_tag_length(meta), - |meta| meta.query_advice(value, Rotation::cur()), - length_inv, - ); - - let q_continue = |meta: &mut VirtualCells| { - // When - // - Not on the first row - // - The previous row did not contain the last byte - and::expr(vec![ - not::expr(meta.query_fixed(q_first, Rotation::cur())), - not::expr(meta.query_advice(is_final, Rotation::prev())), - ]) - }; - - meta.create_gate("continue", |meta| { - let mut cb = BaseConstraintBuilder::default(); - - // Did the previous row have bytecode field tag == Length? - let is_prev_row_tag_length = |meta: &mut VirtualCells| { - and::expr(vec![ - not::expr(meta.query_fixed(q_first, Rotation::cur())), - not::expr(meta.query_advice(padding, Rotation::prev())), - not::expr(meta.query_advice(bytecode_table.tag, Rotation::prev())), - ]) - }; - - cb.require_equal( - "if prev_row.tag == Length: index == 0 else index == index + 1", - meta.query_advice(bytecode_table.index, Rotation::cur()), - select::expr( - is_prev_row_tag_length(meta), - 0.expr(), - meta.query_advice(bytecode_table.index, Rotation::prev()) + 1.expr(), - ), - ); - cb.require_equal( - "is_code := push_rindex_prev == 0", - meta.query_advice(bytecode_table.is_code, Rotation::cur()), - select::expr( - is_prev_row_tag_length(meta), - 1.expr(), - push_rindex_is_zero.clone().is_zero_expression, - ), - ); - cb.require_equal( - "hash_input_rlc := hash_input_rlc_prev * challenges.keccak_input + byte", - meta.query_advice(hash_input_rlc, Rotation::cur()), - meta.query_advice(hash_input_rlc, Rotation::prev()) * challenges.keccak_input() - + meta.query_advice(value, Rotation::cur()), - ); - cb.require_equal( - "code_hash needs to remain the same", - meta.query_advice(bytecode_table.code_hash, Rotation::cur()), - meta.query_advice(bytecode_table.code_hash, Rotation::prev()), - ); - cb.require_equal( - "code_length needs to remain the same", - meta.query_advice(code_length, Rotation::cur()), - meta.query_advice(code_length, Rotation::prev()), - ); - cb.require_equal( - "padding needs to remain the same", - meta.query_advice(padding, Rotation::cur()), - meta.query_advice(padding, Rotation::prev()), - ); - - // Conditions: - // - Continuing - cb.gate(and::expr(vec![ - meta.query_fixed(q_enable, Rotation::cur()), - q_continue(meta), - ])) - }); - - meta.create_gate("start of bytecode", |meta| { - let mut cb = BaseConstraintBuilder::default(); - cb.require_equal( - "next_row.tag == (tag.Length or tag.Padding) if length == 0 else tag.Byte", - meta.query_advice(bytecode_table.tag, Rotation::next()), - select::expr( - length_is_zero.clone().is_zero_expression, - select::expr( - meta.query_advice(padding, Rotation::next()), - BytecodeFieldTag::Padding.expr(), - BytecodeFieldTag::Length.expr(), - ), - BytecodeFieldTag::Byte.expr(), - ), - ); - cb.require_equal( - "if row.tag == tag.Length: value == row.code_length", - meta.query_advice(value, Rotation::cur()), - meta.query_advice(code_length, Rotation::cur()), - ); - // FIXME: Since randomness is only known at synthesis time, the RLC of empty - // code_hash is not constant. Consider doing a lookup to the empty code_hash - // value? cb.condition(length_is_zero.clone().is_zero_expression, - // |cb| { cb.require_equal( - // "if length == 0: code_hash == RLC(EMPTY_HASH, randomness)", - // meta.query_advice(bytecode_table.code_hash, Rotation::cur()), - // Expression::Constant(keccak(&[], randomness)), - // ); - // }); - - // Conditions: - // - Not Continuing - // - This is the start of a new bytecode - cb.gate(and::expr(vec![ - meta.query_fixed(q_enable, Rotation::cur()), - is_row_tag_length(meta), - not::expr(q_continue(meta)), - ])) - }); - - meta.create_gate("start of padding", |meta| { - let mut cb = BaseConstraintBuilder::default(); - cb.require_equal( - "row needs to be marked as padding", - meta.query_advice(padding, Rotation::cur()), - 1.expr(), - ); - // Conditions: - // - Not Continuing - // - This is not the start of a new bytecode - cb.gate(and::expr(vec![ - meta.query_fixed(q_enable, Rotation::cur()), - not::expr(is_row_tag_length(meta)), - not::expr(q_continue(meta)), - ])) - }); - - meta.create_gate("length needs to be correct", |meta| { - let mut cb = BaseConstraintBuilder::default(); - cb.condition(1.expr() - length_is_zero.clone().is_zero_expression, |cb| { - cb.require_equal( - "index + 1 needs to equal code_length", - meta.query_advice(bytecode_table.index, Rotation::cur()) + 1.expr(), - meta.query_advice(code_length, Rotation::cur()), - ); - }); - // Conditions: - // - On the row with the last byte (`is_final == 1`) - // - Of bytecode with length > 0 - // - Not padding - cb.gate(and::expr(vec![ - meta.query_fixed(q_enable, Rotation::cur()), - meta.query_advice(is_final, Rotation::cur()), - not::expr(meta.query_advice(padding, Rotation::cur())), - ])) - }); - - meta.create_gate("always", |meta| { - let mut cb = BaseConstraintBuilder::default(); - cb.require_boolean( - "is_final needs to be boolean", - meta.query_advice(is_final, Rotation::cur()), - ); - cb.require_boolean( - "padding needs to be boolean", - meta.query_advice(padding, Rotation::cur()), - ); - cb.condition(is_row_tag_byte(meta), |cb| { - cb.require_equal( - "push_rindex := is_code ? byte_push_size : push_rindex_prev - 1", - meta.query_advice(push_rindex, Rotation::cur()), - select::expr( - meta.query_advice(bytecode_table.is_code, Rotation::cur()), - meta.query_advice(byte_push_size, Rotation::cur()), - meta.query_advice(push_rindex, Rotation::prev()) - 1.expr(), - ), - ); - }); - // Conditions: Always - cb.gate(meta.query_fixed(q_enable, Rotation::cur())) - }); - - meta.create_gate("padding", |meta| { - let mut cb = BaseConstraintBuilder::default(); - cb.require_boolean( - "padding can only go 0 -> 1 once", - meta.query_advice(padding, Rotation::cur()) - - meta.query_advice(padding, Rotation::prev()), - ); - // Conditions: - // - Not on the first row - cb.gate(and::expr(vec![ - meta.query_fixed(q_enable, Rotation::cur()), - not::expr(meta.query_fixed(q_first, Rotation::cur())), - ])) - }); - - // The code_hash is checked on the latest row because only then have - // we accumulated all the bytes. We also have to go through the bytes - // in a forward manner because that's the only way we can know which - // bytes are op codes and which are push data. - meta.create_gate("last row", |meta| { - let mut cb = BaseConstraintBuilder::default(); - cb.require_equal( - "padding needs to be enabled OR the last row needs to be the last byte", - or::expr(vec![ - meta.query_advice(padding, Rotation::cur()), - meta.query_advice(is_final, Rotation::cur()), - ]), - 1.expr(), - ); - // Conditions: - // - On the last row - cb.gate(meta.query_selector(q_last)) - }); - - // Lookup how many bytes the current opcode pushes - // (also indirectly range checks `byte` to be in [0, 255]) - meta.lookup_any("Range bytes", |meta| { - // Conditions: Always - let q_enable = meta.query_fixed(q_enable, Rotation::cur()) * is_row_tag_byte(meta); - let lookup_columns = vec![value, byte_push_size]; - let mut constraints = vec![]; - for i in 0..PUSH_TABLE_WIDTH { - constraints.push(( - q_enable.clone() * meta.query_advice(lookup_columns[i], Rotation::cur()), - meta.query_fixed(push_table[i], Rotation::cur()), - )) - } - constraints - }); - - // keccak lookup - meta.lookup_any("keccak", |meta| { - // Conditions: - // - On the row with the last byte (`is_final == 1`) - // - Not padding - let enable = and::expr(vec![ - meta.query_advice(is_final, Rotation::cur()), - not::expr(meta.query_advice(padding, Rotation::cur())), - ]); - let lookup_columns = vec![hash_input_rlc, code_length, bytecode_table.code_hash]; - let mut constraints = vec![( - enable.clone(), - meta.query_advice(keccak_table.is_enabled, Rotation::cur()), - )]; - for (i, column) in keccak_table.columns().iter().skip(1).enumerate() { - constraints.push(( - enable.clone() * meta.query_advice(lookup_columns[i], Rotation::cur()), - meta.query_advice(*column, Rotation::cur()), - )) - } - constraints - }); - - BytecodeCircuitConfig { - minimum_rows: meta.minimum_rows(), - q_enable, - q_first, - q_last, - bytecode_table, - push_rindex, - hash_input_rlc, - code_length, - byte_push_size, - is_final, - padding, - push_rindex_inv, - push_rindex_is_zero, - length_inv, - length_is_zero, - push_table, - keccak_table, - } - } -} - -impl BytecodeCircuitConfig { - pub(crate) fn assign( - &self, - layouter: &mut impl Layouter, - size: usize, - witness: &[UnrolledBytecode], - challenges: &Challenges>, - ) -> Result<(), Error> { - self.assign_internal(layouter, size, witness, challenges, true) - } - - pub(crate) fn assign_internal( - &self, - layouter: &mut impl Layouter, - size: usize, - witness: &[UnrolledBytecode], - challenges: &Challenges>, - fail_fast: bool, - ) -> Result<(), Error> { - let push_rindex_is_zero_chip = IsZeroChip::construct(self.push_rindex_is_zero.clone()); - let length_is_zero_chip = IsZeroChip::construct(self.length_is_zero.clone()); - - // Subtract the unusable rows from the size - assert!(size > self.minimum_rows); - let last_row_offset = size - self.minimum_rows + 1; - - layouter.assign_region( - || "assign bytecode", - |mut region| { - let mut offset = 0; - let mut push_rindex_prev = 0; - for bytecode in witness.iter() { - // Run over all the bytes - let mut push_rindex = 0; - let mut byte_push_size = 0; - let mut hash_input_rlc = challenges.keccak_input().map(|_| F::zero()); - let code_length = F::from(bytecode.bytes.len() as u64); - for (idx, row) in bytecode.rows.iter().enumerate() { - if fail_fast && offset > last_row_offset { - log::error!( - "Bytecode Circuit: offset={} > last_row_offset={}", - offset, - last_row_offset - ); - return Err(Error::Synthesis); - } - - let code_hash = challenges.evm_word().map(|challenge| { - RandomLinearCombination::::random_linear_combine( - row.code_hash.to_le_bytes(), - challenge, - ) - }); - - // Track which byte is an opcode and which is push - // data - let is_code = push_rindex == 0; - if idx > 0 { - byte_push_size = get_push_size(row.value.get_lower_128() as u8); - push_rindex = if is_code { - byte_push_size - } else { - push_rindex - 1 - }; - hash_input_rlc.as_mut().zip(challenges.keccak_input()).map( - |(hash_input_rlc, challenge)| { - *hash_input_rlc = *hash_input_rlc * challenge + row.value - }, - ); - } - - // Set the data for this row - if offset <= last_row_offset { - self.set_row( - &mut region, - &push_rindex_is_zero_chip, - &length_is_zero_chip, - offset, - true, - offset == last_row_offset, - code_hash, - row.tag, - row.index, - row.is_code, - row.value, - push_rindex, - hash_input_rlc, - code_length, - F::from(byte_push_size as u64), - idx == bytecode.bytes.len(), - false, - F::from(push_rindex_prev), - )?; - push_rindex_prev = push_rindex; - offset += 1; - } - } - } - - // Padding - for idx in offset..=last_row_offset { - self.set_row( - &mut region, - &push_rindex_is_zero_chip, - &length_is_zero_chip, - idx, - idx < last_row_offset, - idx == last_row_offset, - Value::known(F::zero()), - F::from(BytecodeFieldTag::Padding as u64), - F::zero(), - F::one(), - F::zero(), - 0, - Value::known(F::zero()), - F::zero(), - F::zero(), - true, - true, - F::from(push_rindex_prev), - )?; - push_rindex_prev = 0; - } - Ok(()) - }, - ) - } - - #[allow(clippy::too_many_arguments)] - fn set_row( - &self, - region: &mut Region<'_, F>, - push_rindex_is_zero_chip: &IsZeroChip, - length_is_zero_chip: &IsZeroChip, - offset: usize, - enable: bool, - last: bool, - code_hash: Value, - tag: F, - index: F, - is_code: F, - value: F, - push_rindex: u64, - hash_input_rlc: Value, - code_length: F, - byte_push_size: F, - is_final: bool, - padding: bool, - push_rindex_prev: F, - ) -> Result<(), Error> { - // q_enable - region.assign_fixed( - || format!("assign q_enable {}", offset), - self.q_enable, - offset, - || Value::known(F::from(enable as u64)), - )?; - - // q_first - region.assign_fixed( - || format!("assign q_first {}", offset), - self.q_first, - offset, - || Value::known(F::from((offset == 0) as u64)), - )?; - - // q_last - if last { - self.q_last.enable(region, offset)?; - } - - // Advices - for (name, column, value) in [ - ("tag", self.bytecode_table.tag, tag), - ("index", self.bytecode_table.index, index), - ("is_code", self.bytecode_table.is_code, is_code), - ("value", self.bytecode_table.value, value), - ("push_rindex", self.push_rindex, F::from(push_rindex)), - ("code_length", self.code_length, code_length), - ("byte_push_size", self.byte_push_size, byte_push_size), - ("is_final", self.is_final, F::from(is_final as u64)), - ("padding", self.padding, F::from(padding as u64)), - ] { - region.assign_advice( - || format!("assign {} {}", name, offset), - column, - offset, - || Value::known(value), - )?; - } - for (name, column, value) in [ - ("code_hash", self.bytecode_table.code_hash, code_hash), - ("hash_input_rlc", self.hash_input_rlc, hash_input_rlc), - ] { - region.assign_advice( - || format!("assign {} {}", name, offset), - column, - offset, - || value, - )?; - } - - // push_rindex_is_zero_chip - push_rindex_is_zero_chip.assign(region, offset, Value::known(push_rindex_prev))?; - - // length_is_zero chip - length_is_zero_chip.assign(region, offset, Value::known(code_length))?; - - Ok(()) - } - - /// load fixed tables - pub(crate) fn load_aux_tables(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - // push table: BYTE -> NUM_PUSHED: - // [0, OpcodeId::PUSH1] -> 0 - // [OpcodeId::PUSH1, OpcodeId::PUSH32] -> [1..32] - // [OpcodeId::PUSH32, 256] -> 0 - layouter.assign_region( - || "push table", - |mut region| { - for byte in 0usize..256 { - let push_size = get_push_size(byte as u8); - for (name, column, value) in &[ - ("byte", self.push_table[0], byte as u64), - ("push_size", self.push_table[1], push_size), - ] { - region.assign_fixed( - || format!("Push table assign {} {}", name, byte), - *column, - byte, - || Value::known(F::from(*value)), - )?; - } - } - Ok(()) - }, - )?; - - Ok(()) - } + pub(crate) rows: Vec>, } /// Get unrolled bytecode from raw bytes @@ -649,7 +27,7 @@ pub fn unroll(bytes: Vec) -> UnrolledBytecode { let code_hash = keccak(&bytes[..]); let mut rows = vec![BytecodeRow:: { code_hash, - tag: F::from(BytecodeFieldTag::Length as u64), + tag: F::from(BytecodeFieldTag::Header as u64), index: F::zero(), is_code: F::zero(), value: F::from(bytes.len() as u64), @@ -675,347 +53,3 @@ pub fn unroll(bytes: Vec) -> UnrolledBytecode { } UnrolledBytecode { bytes, rows } } - -fn is_push(byte: u8) -> bool { - OpcodeId::from(byte).is_push() -} - -fn get_push_size(byte: u8) -> u64 { - if is_push(byte) { - byte as u64 - OpcodeId::PUSH1.as_u64() + 1 - } else { - 0u64 - } -} - -fn keccak(msg: &[u8]) -> Word { - let mut keccak = Keccak::default(); - keccak.update(msg); - Word::from_big_endian(keccak.digest().as_slice()) -} - -fn into_words(message: &[u8]) -> Vec { - let words_total = message.len() / 8; - let mut words: Vec = vec![0; words_total]; - - for i in 0..words_total { - let mut word_bits: [u8; 8] = Default::default(); - word_bits.copy_from_slice(&message[i * 8..i * 8 + 8]); - words[i] = u64::from_le_bytes(word_bits); - } - - words -} - -/// BytecodeCircuit -#[derive(Clone, Default, Debug)] -pub struct BytecodeCircuit { - /// Unrolled bytecodes - pub bytecodes: Vec>, - /// Circuit size - pub size: usize, -} - -impl BytecodeCircuit { - /// new BytecodeCircuitTester - pub fn new(bytecodes: Vec>, size: usize) -> Self { - BytecodeCircuit { bytecodes, size } - } - - /// Creates bytecode circuit from block and bytecode_size. - pub fn new_from_block_sized(block: &witness::Block, bytecode_size: usize) -> Self { - let bytecodes: Vec> = block - .bytecodes - .iter() - .map(|(_, b)| unroll(b.bytes.clone())) - .collect(); - Self::new(bytecodes, bytecode_size) - } -} - -impl SubCircuit for BytecodeCircuit { - type Config = BytecodeCircuitConfig; - - fn new_from_block(block: &witness::Block) -> Self { - // TODO: Find a nicer way to add the extra `128`. Is this to account for - // unusable rows? Then it could be calculated like this: - // fn unusable_rows>() -> usize { - // let mut cs = ConstraintSystem::default(); - // C::configure(&mut cs); - // cs.blinding_factors() - // } - let bytecode_size = block.circuits_params.max_bytecode + 128; - Self::new_from_block_sized(block, bytecode_size) - } - - /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { - ( - block - .bytecodes - .values() - .map(|bytecode| bytecode.bytes.len() + 1) - .sum(), - block.circuits_params.max_bytecode, - ) - } - - /// Make the assignments to the TxCircuit - fn synthesize_sub( - &self, - config: &Self::Config, - challenges: &Challenges>, - layouter: &mut impl Layouter, - ) -> Result<(), Error> { - config.load_aux_tables(layouter)?; - config.assign_internal(layouter, self.size, &self.bytecodes, challenges, false) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::bytecode_circuit::dev::test_bytecode_circuit_unrolled; - use eth_types::Bytecode; - use halo2_proofs::halo2curves::bn256::Fr; - - fn get_randomness() -> F { - F::from(123456) - } - - /// 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::Length 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) - 7])], true); - } - - /// 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 - { - let mut invalid = unrolled.clone(); - invalid.rows[0].code_hash += Word::one(); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - // Change the code_hash on another position - { - let mut invalid = unrolled.clone(); - invalid.rows[4].code_hash += Word::one(); - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - // Change all the hashes so it doesn't match the keccak lookup code_hash - { - let mut invalid = unrolled; - for row in invalid.rows.iter_mut() { - row.code_hash = Word::one(); - } - test_bytecode_circuit_unrolled::(k, vec![invalid], false); - } - } - - /// 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/circuit.rs b/zkevm-circuits/src/bytecode_circuit/circuit.rs new file mode 100644 index 0000000000..1463e78bc7 --- /dev/null +++ b/zkevm-circuits/src/bytecode_circuit/circuit.rs @@ -0,0 +1,1022 @@ +use crate::{ + evm_circuit::util::{and, constraint_builder::BaseConstraintBuilder, not, or, rlc, select}, + table::{BytecodeFieldTag, BytecodeTable, KeccakTable}, + util::{get_push_size, Challenges, Expr, SubCircuit, SubCircuitConfig}, + witness, +}; +use eth_types::{Field, ToLittleEndian}; +use gadgets::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; +use halo2_proofs::{ + circuit::{Layouter, Region, Value}, + plonk::{ + Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, VirtualCells, + }, + poly::Rotation, +}; +use keccak256::EMPTY_HASH_LE; +use log::trace; +use std::vec; + +use super::{ + bytecode_unroller::{unroll, UnrolledBytecode}, + param::PUSH_TABLE_WIDTH, +}; + +#[derive(Clone, Debug)] +/// Bytecode circuit configuration +pub struct BytecodeCircuitConfig { + minimum_rows: usize, + q_enable: Column, + q_first: Column, + q_last: Column, + bytecode_table: BytecodeTable, + push_data_left: Column, + value_rlc: Column, + length: Column, + push_data_size: Column, + push_data_left_inv: Column, + push_data_left_is_zero: IsZeroConfig, + push_table: [Column; PUSH_TABLE_WIDTH], + // External tables + pub(crate) keccak_table: KeccakTable, +} + +/// Circuit configuration arguments +pub struct BytecodeCircuitConfigArgs { + /// BytecodeTable + pub bytecode_table: BytecodeTable, + /// KeccakTable + pub keccak_table: KeccakTable, + /// Challenges + pub challenges: Challenges>, +} + +impl SubCircuitConfig for BytecodeCircuitConfig { + type ConfigArgs = BytecodeCircuitConfigArgs; + + /// Return a new BytecodeCircuitConfig + fn new( + meta: &mut ConstraintSystem, + Self::ConfigArgs { + bytecode_table, + keccak_table, + challenges, + }: Self::ConfigArgs, + ) -> Self { + let q_enable = meta.fixed_column(); + let q_first = meta.fixed_column(); + let q_last = meta.fixed_column(); + let value = bytecode_table.value; + let push_data_left = meta.advice_column(); + let value_rlc = meta.advice_column_in(SecondPhase); + let length = meta.advice_column(); + let push_data_size = meta.advice_column(); + let push_data_left_inv = meta.advice_column(); + let push_table = array_init::array_init(|_| meta.fixed_column()); + + let is_header_to_header = |meta: &mut VirtualCells| { + and::expr(vec![ + not::expr(meta.query_advice(bytecode_table.tag, Rotation::cur())), + not::expr(meta.query_advice(bytecode_table.tag, Rotation::next())), + ]) + }; + + let is_header_to_byte = |meta: &mut VirtualCells| { + and::expr(vec![ + not::expr(meta.query_advice(bytecode_table.tag, Rotation::cur())), + meta.query_advice(bytecode_table.tag, Rotation::next()), + ]) + }; + + let is_byte_to_header = |meta: &mut VirtualCells| { + and::expr(vec![ + meta.query_advice(bytecode_table.tag, Rotation::cur()), + not::expr(meta.query_advice(bytecode_table.tag, Rotation::next())), + ]) + }; + + let is_byte_to_byte = |meta: &mut VirtualCells| { + and::expr(vec![ + meta.query_advice(bytecode_table.tag, Rotation::cur()), + meta.query_advice(bytecode_table.tag, Rotation::next()), + ]) + }; + + let is_header = |meta: &mut VirtualCells| { + not::expr(meta.query_advice(bytecode_table.tag, Rotation::cur())) + }; + + let is_byte = + |meta: &mut VirtualCells| meta.query_advice(bytecode_table.tag, Rotation::cur()); + + // A byte is an opcode when `push_data_left == 0` on the current row, + // else it's push data. + let push_data_left_is_zero = IsZeroChip::configure( + meta, + |meta| meta.query_fixed(q_enable, Rotation::cur()), + |meta| meta.query_advice(push_data_left, Rotation::cur()), + push_data_left_inv, + ); + + // When q_first || q_last -> + // assert cur.tag == Header + meta.create_gate("first and last row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_zero( + "cur.tag == Header", + meta.query_advice(bytecode_table.tag, Rotation::cur()), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + or::expr(vec![ + meta.query_fixed(q_first, Rotation::cur()), + meta.query_fixed(q_last, Rotation::cur()), + ]), + ])) + }); + + // When is_header -> + // assert cur.index == 0 + // assert cur.value == cur.length + meta.create_gate("Header row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_zero( + "cur.index == 0", + meta.query_advice(bytecode_table.index, Rotation::cur()), + ); + + cb.require_equal( + "cur.value == cur.length", + meta.query_advice(bytecode_table.value, Rotation::cur()), + meta.query_advice(length, Rotation::cur()), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + not::expr(meta.query_fixed(q_last, Rotation::cur())), + is_header(meta), + ])) + }); + + // When is_byte -> + // assert push_data_size_table_lookup(cur.value, cur.push_data_size) + // assert cur.is_code == (cur.push_data_left == 0) + meta.create_gate("Byte row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "cur.is_code == (cur.push_data_left == 0)", + meta.query_advice(bytecode_table.is_code, Rotation::cur()), + push_data_left_is_zero.clone().is_zero_expression, + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + not::expr(meta.query_fixed(q_last, Rotation::cur())), + is_byte(meta), + ])) + }); + meta.lookup_any( + "push_data_size_table_lookup(cur.value, cur.push_data_size)", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + not::expr(meta.query_fixed(q_last, Rotation::cur())), + is_byte(meta), + ]); + + let lookup_columns = vec![value, push_data_size]; + + let mut constraints = vec![]; + + for i in 0..PUSH_TABLE_WIDTH { + constraints.push(( + enable.clone() * meta.query_advice(lookup_columns[i], Rotation::cur()), + meta.query_fixed(push_table[i], Rotation::cur()), + )) + } + constraints + }, + ); + + // When is_header_to_header or q_last -> + // assert cur.length == 0 + // assert cur.hash == EMPTY_HASH + meta.create_gate("Header to header row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_zero( + "cur.length == 0", + meta.query_advice(length, Rotation::cur()), + ); + + let empty_hash = rlc::expr( + &EMPTY_HASH_LE.map(|v| Expression::Constant(F::from(v as u64))), + challenges.evm_word(), + ); + + cb.require_equal( + "assert cur.hash == EMPTY_HASH", + meta.query_advice(bytecode_table.code_hash, Rotation::cur()), + empty_hash, + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + or::expr(vec![ + is_header_to_header(meta), + meta.query_fixed(q_last, Rotation::cur()), + ]), + ])) + }); + + // When is_header_to_byte -> + // assert next.length == cur.length + // assert next.index == 0 + // assert next.is_code == 1 + // assert next.hash == cur.hash + // assert next.value_rlc == next.value + meta.create_gate("Header to byte row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "next.length == cur.length", + meta.query_advice(length, Rotation::next()), + meta.query_advice(length, Rotation::cur()), + ); + + cb.require_zero( + "next.index == 0", + meta.query_advice(bytecode_table.index, Rotation::next()), + ); + + cb.require_equal( + "next.is_code == 1", + meta.query_advice(bytecode_table.is_code, Rotation::next()), + 1.expr(), + ); + + cb.require_equal( + "next.hash == cur.hash", + meta.query_advice(bytecode_table.code_hash, Rotation::next()), + meta.query_advice(bytecode_table.code_hash, Rotation::cur()), + ); + + cb.require_equal( + "next.value_rlc == next.value", + meta.query_advice(value_rlc, Rotation::next()), + meta.query_advice(bytecode_table.value, Rotation::next()), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + not::expr(meta.query_fixed(q_last, Rotation::cur())), + is_header_to_byte(meta), + ])) + }); + + // When is_byte_to_byte -> + // assert next.length == cur.length + // assert next.index == cur.index + 1 + // assert next.hash == cur.hash + // assert next.value_rlc == cur.value_rlc * randomness + next.value + // if cur.is_code: + // assert next.push_data_left == cur.push_data_size + // else: + // assert next.push_data_left == cur.push_data_left - 1 + meta.create_gate("Byte to Byte row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "next.length == cur.length", + meta.query_advice(length, Rotation::next()), + meta.query_advice(length, Rotation::cur()), + ); + + cb.require_equal( + "next.index == cur.index + 1", + meta.query_advice(bytecode_table.index, Rotation::next()), + meta.query_advice(bytecode_table.index, Rotation::cur()) + 1.expr(), + ); + + cb.require_equal( + "next.hash == cur.hash", + meta.query_advice(bytecode_table.code_hash, Rotation::next()), + meta.query_advice(bytecode_table.code_hash, Rotation::cur()), + ); + + cb.require_equal( + "next.value_rlc == cur.value_rlc * randomness + next.value", + meta.query_advice(value_rlc, Rotation::next()), + meta.query_advice(value_rlc, Rotation::cur()) * challenges.keccak_input() + + meta.query_advice(value, Rotation::next()), + ); + + cb.require_equal( + "next.push_data_left == cur.is_code ? cur.push_data_size : cur.push_data_left - 1", + meta.query_advice(push_data_left, Rotation::next()), + select::expr( + meta.query_advice(bytecode_table.is_code, Rotation::cur()), + meta.query_advice(push_data_size, Rotation::cur()), + meta.query_advice(push_data_left, Rotation::cur()) - 1.expr(), + ), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + not::expr(meta.query_fixed(q_last, Rotation::cur())), + is_byte_to_byte(meta), + ])) + }); + + // When is_byte_to_header -> + // assert cur.index + 1 == cur.length + // assert keccak256_table_lookup(cur.hash, cur.length, cur.value_rlc) + meta.create_gate("Byte to Header row", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "cur.index + 1 == cur.length", + meta.query_advice(bytecode_table.index, Rotation::cur()) + 1.expr(), + meta.query_advice(length, Rotation::cur()), + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + not::expr(meta.query_fixed(q_last, Rotation::cur())), + is_byte_to_header(meta), + ])) + }); + meta.lookup_any( + "keccak256_table_lookup(cur.value_rlc, cur.length, cur.hash)", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + not::expr(meta.query_fixed(q_last, Rotation::cur())), + is_byte_to_header(meta), + ]); + + let mut constraints = vec![( + enable.clone(), + meta.query_advice(keccak_table.is_enabled, Rotation::cur()), + )]; + + for (circuit_column, table_column) in + keccak_table.match_columns(value_rlc, length, bytecode_table.code_hash) + { + constraints.push(( + enable.clone() * meta.query_advice(circuit_column, Rotation::cur()), + meta.query_advice(table_column, Rotation::cur()), + )) + } + + constraints + }, + ); + + BytecodeCircuitConfig { + minimum_rows: meta.minimum_rows(), + q_enable, + q_first, + q_last, + bytecode_table, + push_data_left, + value_rlc, + length, + push_data_size, + push_data_left_inv, + push_data_left_is_zero, + push_table, + keccak_table, + } + } +} + +impl BytecodeCircuitConfig { + pub(crate) fn assign( + &self, + layouter: &mut impl Layouter, + size: usize, + witness: &[UnrolledBytecode], + challenges: &Challenges>, + ) -> Result<(), Error> { + self.assign_internal(layouter, size, witness, challenges, true) + } + + pub(crate) fn assign_internal( + &self, + layouter: &mut impl Layouter, + size: usize, + witness: &[UnrolledBytecode], + challenges: &Challenges>, + fail_fast: bool, + ) -> Result<(), Error> { + let push_data_left_is_zero_chip = + IsZeroChip::construct(self.push_data_left_is_zero.clone()); + + // Subtract the unusable rows from the size + assert!(size > self.minimum_rows); + let last_row_offset = size - self.minimum_rows + 1; + + trace!( + "size: {}, minimum_rows: {}, last_row_offset:{}", + size, + self.minimum_rows, + last_row_offset + ); + + let empty_hash = challenges + .evm_word() + .map(|challenge| rlc::value(EMPTY_HASH_LE.as_ref(), challenge)); + + layouter.assign_region( + || "assign bytecode", + |mut region| { + let mut offset = 0; + for bytecode in witness.iter() { + self.assign_bytecode( + &mut region, + bytecode, + challenges, + &push_data_left_is_zero_chip, + empty_hash, + &mut offset, + last_row_offset, + fail_fast, + )?; + } + + // Padding + for idx in offset..=last_row_offset { + self.set_padding_row( + &mut region, + &push_data_left_is_zero_chip, + empty_hash, + idx, + last_row_offset, + )?; + } + Ok(()) + }, + ) + } + + #[allow(clippy::too_many_arguments)] + fn assign_bytecode( + &self, + region: &mut Region<'_, F>, + bytecode: &UnrolledBytecode, + challenges: &Challenges>, + push_data_left_is_zero_chip: &IsZeroChip, + empty_hash: Value, + offset: &mut usize, + last_row_offset: usize, + fail_fast: bool, + ) -> Result<(), Error> { + // Run over all the bytes + let mut push_data_left = 0; + let mut next_push_data_left = 0; + let mut push_data_size = 0; + let mut value_rlc = challenges.keccak_input().map(|_| F::zero()); + let length = F::from(bytecode.bytes.len() as u64); + + // Code hash with challenge is calculated only using the first row of the + // bytecode (header row), the rest of the code_hash in other rows are ignored. + let code_hash = challenges + .evm_word() + .map(|challenge| rlc::value(&bytecode.rows[0].code_hash.to_le_bytes(), challenge)); + + for (idx, row) in bytecode.rows.iter().enumerate() { + if fail_fast && *offset > last_row_offset { + log::error!( + "Bytecode Circuit: offset={} > last_row_offset={}", + offset, + last_row_offset + ); + return Err(Error::Synthesis); + } + + // Track which byte is an opcode and which is push + // data + if idx > 0 { + let is_code = push_data_left == 0; + + push_data_size = get_push_size(row.value.get_lower_128() as u8); + + next_push_data_left = if is_code { + push_data_size + } else { + push_data_left - 1 + }; + + value_rlc + .as_mut() + .zip(challenges.keccak_input()) + .map(|(value_rlc, challenge)| *value_rlc = *value_rlc * challenge + row.value); + } + + // Set the data for this row + if *offset < last_row_offset { + self.set_row( + region, + push_data_left_is_zero_chip, + *offset, + true, + *offset == last_row_offset, + code_hash, + row.tag, + row.index, + row.is_code, + row.value, + push_data_left, + value_rlc, + length, + F::from(push_data_size as u64), + )?; + + trace!( + "bytecode.set_row({}): last:{} h:{:?} t:{:?} i:{:?} c:{:?} v:{:?} pdl:{} rlc:{:?} l:{:?} pds:{:?}", + offset, + *offset == last_row_offset, + code_hash, + row.tag.get_lower_32(), + row.index.get_lower_32(), + row.is_code.get_lower_32(), + row.value.get_lower_32(), + push_data_left, + value_rlc, + length.get_lower_32(), + push_data_size + ); + + *offset += 1; + push_data_left = next_push_data_left + } + if *offset == last_row_offset { + self.set_padding_row( + region, + push_data_left_is_zero_chip, + empty_hash, + *offset, + last_row_offset, + )?; + } + } + + Ok(()) + } + + fn set_padding_row( + &self, + region: &mut Region<'_, F>, + push_data_left_is_zero_chip: &IsZeroChip, + empty_hash: Value, + offset: usize, + last_row_offset: usize, + ) -> Result<(), Error> { + self.set_row( + region, + push_data_left_is_zero_chip, + offset, + offset < last_row_offset, + offset == last_row_offset, + empty_hash, + F::from(BytecodeFieldTag::Header as u64), + F::zero(), + F::zero(), + F::zero(), + 0, + Value::known(F::zero()), + F::zero(), + F::zero(), + ) + } + + #[allow(clippy::too_many_arguments)] + fn set_row( + &self, + region: &mut Region<'_, F>, + push_data_left_is_zero_chip: &IsZeroChip, + offset: usize, + enable: bool, + last: bool, + code_hash: Value, + tag: F, + index: F, + is_code: F, + value: F, + push_data_left: u64, + value_rlc: Value, + length: F, + push_data_size: F, + ) -> Result<(), Error> { + // q_enable + region.assign_fixed( + || format!("assign q_enable {}", offset), + self.q_enable, + offset, + || Value::known(F::from(enable as u64)), + )?; + + // q_first + region.assign_fixed( + || format!("assign q_first {}", offset), + self.q_first, + offset, + || Value::known(F::from((offset == 0) as u64)), + )?; + + // q_last + let q_last_value = if last { F::one() } else { F::zero() }; + region.assign_fixed( + || format!("assign q_first {}", offset), + self.q_last, + offset, + || Value::known(q_last_value), + )?; + + // Advices + for (name, column, value) in [ + ("tag", self.bytecode_table.tag, tag), + ("index", self.bytecode_table.index, index), + ("is_code", self.bytecode_table.is_code, is_code), + ("value", self.bytecode_table.value, value), + ( + "push_data_left", + self.push_data_left, + F::from(push_data_left), + ), + ("length", self.length, length), + ("push_data_size", self.push_data_size, push_data_size), + ] { + region.assign_advice( + || format!("assign {} {}", name, offset), + column, + offset, + || Value::known(value), + )?; + } + for (name, column, value) in [ + ("code_hash", self.bytecode_table.code_hash, code_hash), + ("value_rlc", self.value_rlc, value_rlc), + ] { + region.assign_advice( + || format!("assign {} {}", name, offset), + column, + offset, + || value, + )?; + } + + push_data_left_is_zero_chip.assign( + region, + offset, + Value::known(F::from(push_data_left)), + )?; + + Ok(()) + } + + /// load fixed tables + pub(crate) fn load_aux_tables(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + // push table: BYTE -> NUM_PUSHED: + // [0, OpcodeId::PUSH1] -> 0 + // [OpcodeId::PUSH1, OpcodeId::PUSH32] -> [1..32] + // [OpcodeId::PUSH32, 256] -> 0 + layouter.assign_region( + || "push table", + |mut region| { + for byte in 0usize..256 { + let push_size = get_push_size(byte as u8); + for (name, column, value) in &[ + ("byte", self.push_table[0], byte as u64), + ("push_size", self.push_table[1], push_size), + ] { + region.assign_fixed( + || format!("Push table assign {} {}", name, byte), + *column, + byte, + || Value::known(F::from(*value)), + )?; + } + } + Ok(()) + }, + )?; + + Ok(()) + } +} + +/// BytecodeCircuit +#[derive(Clone, Default, Debug)] +pub struct BytecodeCircuit { + /// Unrolled bytecodes + pub bytecodes: Vec>, + /// Circuit size + pub size: usize, +} + +impl BytecodeCircuit { + /// new BytecodeCircuitTester + pub fn new(bytecodes: Vec>, size: usize) -> Self { + BytecodeCircuit { bytecodes, size } + } + + /// Creates bytecode circuit from block and bytecode_size. + pub fn new_from_block_sized(block: &witness::Block, bytecode_size: usize) -> Self { + let bytecodes: Vec> = block + .bytecodes + .iter() + .map(|(_, b)| unroll(b.bytes.clone())) + .collect(); + Self::new(bytecodes, bytecode_size) + } +} + +impl SubCircuit for BytecodeCircuit { + type Config = BytecodeCircuitConfig; + + fn new_from_block(block: &witness::Block) -> Self { + // TODO: Find a nicer way to add the extra `128`. Is this to account for + // unusable rows? Then it could be calculated like this: + // fn unusable_rows>() -> usize { + // let mut cs = ConstraintSystem::default(); + // C::configure(&mut cs); + // cs.blinding_factors() + // } + let bytecode_size = block.circuits_params.max_bytecode + 128; + Self::new_from_block_sized(block, bytecode_size) + } + + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + ( + block + .bytecodes + .values() + .map(|bytecode| bytecode.bytes.len() + 1) + .sum(), + block.circuits_params.max_bytecode, + ) + } + + /// Make the assignments to the TxCircuit + fn synthesize_sub( + &self, + config: &Self::Config, + challenges: &Challenges>, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + config.load_aux_tables(layouter)?; + config.assign_internal(layouter, self.size, &self.bytecodes, challenges, false) + } +} + +#[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 54c2578648..a8ac0b55a2 100644 --- a/zkevm-circuits/src/bytecode_circuit/dev.rs +++ b/zkevm-circuits/src/bytecode_circuit/dev.rs @@ -1,6 +1,5 @@ -use super::bytecode_unroller::{ - unroll, BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs, UnrolledBytecode, -}; +use super::bytecode_unroller::{unroll, UnrolledBytecode}; +use super::circuit::{BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs}; use crate::table::{BytecodeTable, KeccakTable}; use crate::util::{Challenges, SubCircuit, SubCircuitConfig}; use eth_types::Field; @@ -92,5 +91,6 @@ pub fn test_bytecode_circuit_unrolled( error!("{}", failure); } } - assert_eq!(result.is_ok(), success); + 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/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs index e9f200f9a1..242c476f34 100644 --- a/zkevm-circuits/src/copy_circuit.rs +++ b/zkevm-circuits/src/copy_circuit.rs @@ -24,7 +24,7 @@ use std::marker::PhantomData; use crate::witness::{Bytecode, RwMap, Transaction}; use crate::{ - evm_circuit::util::{constraint_builder::BaseConstraintBuilder, RandomLinearCombination}, + evm_circuit::util::{constraint_builder::BaseConstraintBuilder, rlc}, table::{ BytecodeFieldTag, BytecodeTable, CopyTable, LookupTable, RwTable, RwTableTag, TxContextFieldTag, TxTable, @@ -46,9 +46,7 @@ pub fn number_or_hash_to_field(v: &NumberOrHash, challenge: Value) b.reverse(); b }; - challenge.map(|challenge| { - RandomLinearCombination::random_linear_combine(le_bytes, challenge) - }) + challenge.map(|challenge| rlc::value(&le_bytes, challenge)) } } } diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index e25b2c4700..73c402ac9a 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -273,11 +273,8 @@ pub(crate) struct ExecutionConfig { error_oog_account_access: DummyGadget, error_oog_sha3: DummyGadget, error_oog_ext_codecopy: DummyGadget, - error_oog_call_code: DummyGadget, - error_oog_delegate_call: DummyGadget, error_oog_exp: DummyGadget, error_oog_create2: DummyGadget, - error_oog_static_call: DummyGadget, error_oog_self_destruct: DummyGadget, error_oog_code_store: DummyGadget, error_insufficient_balance: DummyGadget, @@ -421,8 +418,6 @@ impl ExecutionConfig { let mut stored_expressions_map = HashMap::new(); let step_next = Step::new(meta, advices, MAX_STEP_HEIGHT, true); - let word_powers_of_randomness = challenges.evm_word_powers_of_randomness(); - let lookup_powers_of_randomness = challenges.lookup_input_powers_of_randomness(); macro_rules! configure_gadget { () => { Self::configure_gadget( @@ -434,8 +429,6 @@ impl ExecutionConfig { q_step_first, q_step_last, &challenges, - &word_powers_of_randomness, - &lookup_powers_of_randomness, &step_curr, &step_next, &mut height_map, @@ -531,11 +524,8 @@ impl ExecutionConfig { error_oog_account_access: configure_gadget!(), error_oog_sha3: configure_gadget!(), error_oog_ext_codecopy: configure_gadget!(), - error_oog_call_code: configure_gadget!(), - error_oog_delegate_call: configure_gadget!(), error_oog_exp: configure_gadget!(), error_oog_create2: configure_gadget!(), - error_oog_static_call: configure_gadget!(), error_oog_self_destruct: configure_gadget!(), error_oog_code_store: configure_gadget!(), error_insufficient_balance: configure_gadget!(), @@ -579,8 +569,6 @@ impl ExecutionConfig { q_step_first: Selector, q_step_last: Selector, challenges: &Challenges>, - word_powers_of_randomness: &[Expression; 31], - lookup_powers_of_randomness: &[Expression; 12], step_curr: &Step, step_next: &Step, height_map: &mut HashMap, @@ -593,8 +581,6 @@ impl ExecutionConfig { step_curr.clone(), step_next.clone(), challenges, - word_powers_of_randomness, - lookup_powers_of_randomness, G::EXECUTION_STATE, ); G::configure(&mut cb); @@ -608,8 +594,6 @@ impl ExecutionConfig { step_curr.clone(), step_next.clone(), challenges, - word_powers_of_randomness, - lookup_powers_of_randomness, G::EXECUTION_STATE, ); @@ -744,8 +728,6 @@ impl ExecutionConfig { challenges: &Challenges>, cell_manager: &CellManager, ) { - let lookup_powers_of_randomness: [Expression; 31] = - challenges.lookup_input_powers_of_randomness(); for column in cell_manager.columns().iter() { if let CellType::Lookup(table) = column.cell_type { let name = format!("{:?}", table); @@ -763,7 +745,7 @@ impl ExecutionConfig { .table_exprs(meta); vec![( column.expr(), - rlc::expr(&table_expressions, &lookup_powers_of_randomness), + rlc::expr(&table_expressions, challenges.lookup_input()), )] }); } @@ -1141,7 +1123,7 @@ impl ExecutionConfig { ExecutionState::ErrorOutOfGasConstant => { assign_exec_step!(self.error_oog_constant) } - ExecutionState::ErrorOutOfGasCALL => { + ExecutionState::ErrorOutOfGasCall => { assign_exec_step!(self.error_oog_call) } ExecutionState::ErrorOutOfGasDynamicMemoryExpansion => { @@ -1168,21 +1150,12 @@ impl ExecutionConfig { ExecutionState::ErrorOutOfGasEXTCODECOPY => { assign_exec_step!(self.error_oog_ext_codecopy) } - ExecutionState::ErrorOutOfGasCALLCODE => { - assign_exec_step!(self.error_oog_call_code) - } - ExecutionState::ErrorOutOfGasDELEGATECALL => { - assign_exec_step!(self.error_oog_delegate_call) - } ExecutionState::ErrorOutOfGasEXP => { assign_exec_step!(self.error_oog_exp) } ExecutionState::ErrorOutOfGasCREATE2 => { assign_exec_step!(self.error_oog_create2) } - ExecutionState::ErrorOutOfGasSTATICCALL => { - assign_exec_step!(self.error_oog_static_call) - } ExecutionState::ErrorOutOfGasSELFDESTRUCT => { assign_exec_step!(self.error_oog_self_destruct) } diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs index 768b40d872..4b0b1aa522 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs @@ -1,27 +1,36 @@ -use crate::evm_circuit::{ - execution::ExecutionGadget, - param::N_BYTES_GAS, - step::ExecutionState, - util::{ - common_gadget::{CommonCallGadget, RestoreContextGadget}, - constraint_builder::{ - ConstraintBuilder, StepStateTransition, - Transition::{Delta, Same}, +use crate::table::CallContextFieldTag; +use crate::util::Expr; +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::N_BYTES_GAS, + step::ExecutionState, + util::{ + common_gadget::{CommonCallGadget, RestoreContextGadget}, + constraint_builder::{ + ConstraintBuilder, StepStateTransition, + Transition::{Delta, Same}, + }, + math_gadget::{IsZeroGadget, LtGadget}, + CachedRegion, Cell, }, - math_gadget::LtGadget, - CachedRegion, Cell, }, witness::{Block, Call, ExecStep, Transaction}, }; -use crate::table::CallContextFieldTag; -use crate::util::Expr; use bus_mapping::evm::OpcodeId; use eth_types::{Field, U256}; use halo2_proofs::{circuit::Value, plonk::Error}; +/// Gadget to implement the corresponding out of gas errors for +/// [`OpcodeId::CALL`], [`OpcodeId::CALLCODE`], [`OpcodeId::DELEGATECALL`] and +/// [`OpcodeId::STATICCALL`]. #[derive(Clone, Debug)] pub(crate) struct ErrorOOGCallGadget { opcode: Cell, + is_call: IsZeroGadget, + is_callcode: IsZeroGadget, + is_delegatecall: IsZeroGadget, + is_staticcall: IsZeroGadget, tx_id: Cell, is_static: Cell, call: CommonCallGadget, @@ -34,23 +43,37 @@ pub(crate) struct ErrorOOGCallGadget { impl ExecutionGadget for ErrorOOGCallGadget { const NAME: &'static str = "ErrorOutOfGasCall"; - const EXECUTION_STATE: ExecutionState = ExecutionState::ErrorOutOfGasCALL; + const EXECUTION_STATE: ExecutionState = ExecutionState::ErrorOutOfGasCall; fn configure(cb: &mut ConstraintBuilder) -> Self { let opcode = cb.query_cell(); cb.opcode_lookup(opcode.expr(), 1.expr()); - // TODO: add CallCode etc. when handle ErrorOutOfGasCALLCODE in furture - // implementation + let is_call = IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::CALL.expr()); + let is_callcode = IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::CALLCODE.expr()); + let is_delegatecall = + IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::DELEGATECALL.expr()); + let is_staticcall = + IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::STATICCALL.expr()); + + // We do the responsible opcode check explicitly here because we're not + // using the SameContextGadget for CALL, CALLCODE, DELEGATECALL or + // STATICCALL. cb.require_equal( - "ErrorOutOfGasCall opcode is Call", - opcode.expr(), - OpcodeId::CALL.expr(), + "ErrorOutOfGasCall opcode should be CALL, CALLCODE, DELEGATECALL or STATICCALL", + is_call.expr() + is_callcode.expr() + is_delegatecall.expr() + is_staticcall.expr(), + 1.expr(), ); let rw_counter_end_of_reversion = cb.query_cell(); let tx_id = cb.call_context(None, CallContextFieldTag::TxId); let is_static = cb.call_context(None, CallContextFieldTag::IsStatic); - let call_gadget = CommonCallGadget::construct(cb, 1.expr(), 0.expr(), 0.expr()); + + let call_gadget = CommonCallGadget::construct( + cb, + is_call.expr(), + is_callcode.expr(), + is_delegatecall.expr(), + ); // Add callee to access list let is_warm = cb.query_bool(); @@ -66,7 +89,7 @@ impl ExecutionGadget for ErrorOOGCallGadget { // Check if the amount of gas available is less than the amount of gas required let insufficient_gas = LtGadget::construct(cb, cb.curr.state.gas_left.expr(), gas_cost); cb.require_equal( - "gas left is less than gas required ", + "gas left is less than gas required", insufficient_gas.expr(), 1.expr(), ); @@ -94,7 +117,14 @@ impl ExecutionGadget for ErrorOOGCallGadget { // Do step state transition cb.require_step_state_transition(StepStateTransition { call_id: Same, - rw_counter: Delta(14.expr() + cb.curr.state.reversible_write_counter.expr()), + // Both CALL and CALLCODE opcodes have an extra stack pop `value` relative to + // DELEGATECALL and STATICCALL. + rw_counter: Delta( + 13.expr() + + is_call.expr() + + is_callcode.expr() + + cb.curr.state.reversible_write_counter.expr(), + ), ..StepStateTransition::any() }); }); @@ -124,6 +154,10 @@ impl ExecutionGadget for ErrorOOGCallGadget { Self { opcode, + is_call, + is_callcode, + is_delegatecall, + is_staticcall, tx_id, is_static, call: call_gadget, @@ -144,24 +178,36 @@ impl ExecutionGadget for ErrorOOGCallGadget { step: &ExecStep, ) -> Result<(), Error> { let opcode = step.opcode.unwrap(); + let is_call_or_callcode = + usize::from([OpcodeId::CALL, OpcodeId::CALLCODE].contains(&opcode)); let [tx_id, is_static] = [step.rw_indices[0], step.rw_indices[1]].map(|idx| block.rws[idx].call_context_value()); let stack_index = 2; - let [gas, callee_address, value, cd_offset, cd_length, rd_offset, rd_length] = [ + let [gas, callee_address] = [ step.rw_indices[stack_index], step.rw_indices[stack_index + 1], - step.rw_indices[stack_index + 2], - step.rw_indices[stack_index + 3], - step.rw_indices[stack_index + 4], - step.rw_indices[stack_index + 5], - step.rw_indices[stack_index + 6], + ] + .map(|idx| block.rws[idx].stack_value()); + let value = if is_call_or_callcode == 1 { + block.rws[step.rw_indices[stack_index + 2]].stack_value() + } else { + U256::zero() + }; + let [cd_offset, cd_length, rd_offset, rd_length] = [ + step.rw_indices[stack_index + is_call_or_callcode + 2], + step.rw_indices[stack_index + is_call_or_callcode + 3], + step.rw_indices[stack_index + is_call_or_callcode + 4], + step.rw_indices[stack_index + is_call_or_callcode + 5], ] .map(|idx| block.rws[idx].stack_value()); - let callee_code_hash = block.rws[step.rw_indices[10]].account_value_pair().0; + let callee_code_hash = block.rws[step.rw_indices[9 + is_call_or_callcode]] + .account_value_pair() + .0; let callee_exists = !callee_code_hash.is_zero(); - let (is_warm, is_warm_prev) = block.rws[step.rw_indices[11]].tx_access_list_value_pair(); + let (is_warm, is_warm_prev) = + block.rws[step.rw_indices[10 + is_call_or_callcode]].tx_access_list_value_pair(); let memory_expansion_gas_cost = self.call.assign( region, @@ -181,6 +227,27 @@ impl ExecutionGadget for ErrorOOGCallGadget { self.opcode .assign(region, offset, Value::known(F::from(opcode.as_u64())))?; + self.is_call.assign( + region, + offset, + F::from(opcode.as_u64()) - F::from(OpcodeId::CALL.as_u64()), + )?; + self.is_callcode.assign( + region, + offset, + F::from(opcode.as_u64()) - F::from(OpcodeId::CALLCODE.as_u64()), + )?; + self.is_delegatecall.assign( + region, + offset, + F::from(opcode.as_u64()) - F::from(OpcodeId::DELEGATECALL.as_u64()), + )?; + self.is_staticcall.assign( + region, + offset, + F::from(opcode.as_u64()) - F::from(OpcodeId::STATICCALL.as_u64()), + )?; + self.tx_id .assign(region, offset, Value::known(F::from(tx_id.low_u64())))?; @@ -212,9 +279,10 @@ impl ExecutionGadget for ErrorOOGCallGadget { Value::known(F::from(call.rw_counter_end_of_reversion as u64)), )?; + // Both CALL and CALLCODE opcodes have an extra stack pop `value` relative to + // DELEGATECALL and STATICCALL. self.restore_context - .assign(region, offset, block, call, step, 14)?; - Ok(()) + .assign(region, offset, block, call, step, 13 + is_call_or_callcode) } } @@ -225,11 +293,17 @@ mod test { use eth_types::{bytecode::Bytecode, evm_types::OpcodeId, geth_types::Account}; use eth_types::{Address, ToWord, Word}; use halo2_proofs::halo2curves::bn256::Fr; - use itertools::Itertools; use mock::TestContext; use pretty_assertions::assert_eq; use std::default::Default; + const TEST_CALL_OPCODES: &[OpcodeId] = &[ + OpcodeId::CALL, + OpcodeId::CALLCODE, + OpcodeId::DELEGATECALL, + OpcodeId::STATICCALL, + ]; + #[derive(Clone, Copy, Debug, Default)] struct Stack { gas: u64, @@ -240,27 +314,30 @@ mod test { rd_length: u64, } - fn caller(stack: Stack, caller_is_success: bool) -> Account { - let terminator = if caller_is_success { - OpcodeId::RETURN - } else { - OpcodeId::REVERT - }; - - // Call twice for testing both cold and warm access - let bytecode = bytecode! { + fn call_bytecode(opcode: OpcodeId, address: Address, stack: Stack) -> Bytecode { + let mut bytecode = bytecode! { PUSH32(Word::from(stack.rd_length)) PUSH32(Word::from(stack.rd_offset)) PUSH32(Word::from(stack.cd_length)) PUSH32(Word::from(stack.cd_offset)) - PUSH32(stack.value) - PUSH32(Address::repeat_byte(0xff).to_word()) + }; + if opcode == OpcodeId::CALL || opcode == OpcodeId::CALLCODE { + bytecode.push(32, stack.value); + } + bytecode.append(&bytecode! { + PUSH32(address.to_word()) PUSH32(Word::from(stack.gas)) - CALL + .write_op(opcode) PUSH1(0) PUSH1(0) - .write_op(terminator) - }; + .write_op(OpcodeId::REVERT) + }); + + bytecode + } + + fn caller(opcode: OpcodeId, stack: Stack) -> Account { + let bytecode = call_bytecode(opcode, Address::repeat_byte(0xff), stack); Account { address: Address::repeat_byte(0xfe), @@ -282,7 +359,7 @@ mod test { } } - fn test_oog(caller: Account, callee: Account, is_root: bool) { + fn test_oog(caller: &Account, callee: &Account, is_root: bool) { let tx_gas = if is_root { 21100 } else { 25000 }; let block = TestContext::<3, 1>::new( None, @@ -292,12 +369,12 @@ mod test { .balance(Word::from(10u64.pow(19))); accs[1] .address(caller.address) - .code(caller.code) + .code(caller.code.clone()) .nonce(caller.nonce) .balance(caller.balance); accs[2] .address(callee.address) - .code(callee.code) + .code(callee.code.clone()) .nonce(callee.nonce) .balance(callee.balance); }, @@ -322,66 +399,51 @@ mod test { #[test] fn call_with_oog_root() { - let stacks = vec![ - // With gas and memory expansion - Stack { - gas: 100, - cd_offset: 64, - cd_length: 320, - rd_offset: 0, - rd_length: 32, - ..Default::default() - }, - ]; - - let bytecode = bytecode! { + let stack = Stack { + gas: 100, + cd_offset: 64, + cd_length: 320, + rd_offset: 0, + rd_length: 32, + ..Default::default() + }; + let callee = callee(bytecode! { PUSH32(Word::from(0)) PUSH32(Word::from(0)) STOP - }; - let callees = vec![callee(bytecode)]; - for (stack, callee) in stacks.into_iter().cartesian_product(callees.into_iter()) { - test_oog(caller(stack, true), callee, true); + }); + for opcode in TEST_CALL_OPCODES { + test_oog(&caller(*opcode, stack), &callee, true); } } #[test] fn call_with_oog_internal() { - let stacks = vec![ - // first call stack - Stack { - gas: 100, - cd_offset: 64, - cd_length: 320, - rd_offset: 0, - rd_length: 32, - ..Default::default() - }, - // second call stack - Stack { - gas: 21, - cd_offset: 64, - cd_length: 320, - rd_offset: 0, - rd_length: 32, - ..Default::default() - }, - ]; - - let stack = stacks[1]; - let bytecode = bytecode! { - PUSH32(Word::from(stack.rd_length)) - PUSH32(Word::from(stack.rd_offset)) - PUSH32(Word::from(stack.cd_length)) - PUSH32(Word::from(stack.cd_offset)) - PUSH32(stack.value) - PUSH32(Address::repeat_byte(0xfe).to_word()) - PUSH32(Word::from(stack.gas)) - CALL // make this call out of gas - PUSH32(Word::from(0)) - PUSH32(Word::from(0)) + let caller_stack = Stack { + gas: 100, + cd_offset: 64, + cd_length: 320, + rd_offset: 0, + rd_length: 32, + ..Default::default() }; - let callee = callee(bytecode); - test_oog(caller(stacks[0], false), callee, false); + let callee_stack = Stack { + gas: 21, + cd_offset: 64, + cd_length: 320, + rd_offset: 0, + rd_length: 32, + ..Default::default() + }; + + let caller = caller(OpcodeId::CALL, caller_stack); + for callee_opcode in TEST_CALL_OPCODES { + let callee = callee(call_bytecode( + *callee_opcode, + Address::repeat_byte(0xfe), + callee_stack, + )); + test_oog(&caller, &callee, false); + } } } diff --git a/zkevm-circuits/src/evm_circuit/execution/signextend.rs b/zkevm-circuits/src/evm_circuit/execution/signextend.rs index ba670c8c60..bcee51d8f6 100644 --- a/zkevm-circuits/src/evm_circuit/execution/signextend.rs +++ b/zkevm-circuits/src/evm_circuit/execution/signextend.rs @@ -8,7 +8,7 @@ use crate::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, math_gadget::{IsEqualGadget, IsZeroGadget}, - select, sum, CachedRegion, Cell, Word, + rlc, select, sum, CachedRegion, Cell, Word, }, witness::{Block, Call, ExecStep, Transaction}, }, @@ -17,10 +17,7 @@ use crate::{ use array_init::array_init; use bus_mapping::evm::OpcodeId; use eth_types::{Field, ToLittleEndian}; -use halo2_proofs::{ - circuit::Value, - plonk::{Error, Expression}, -}; +use halo2_proofs::{circuit::Value, plonk::Error}; #[derive(Clone, Debug)] pub(crate) struct SignextendGadget { @@ -111,10 +108,8 @@ impl ExecutionGadget for SignextendGadget { // enabled need to be changed to the sign byte. // When a byte was selected all the **following** bytes need to be // replaced (hence the `selectors[idx - 1]`). - let powers_of_randomness: [Expression; 31] = - cb.challenges().evm_word_powers_of_randomness(); - let result = Word::random_linear_combine_expr( - array_init(|idx| { + let result = rlc::expr( + &array_init::<_, _, 32>(|idx| { if idx == 0 { value.cells[idx].expr() } else { @@ -125,7 +120,7 @@ impl ExecutionGadget for SignextendGadget { ) } }), - &powers_of_randomness, + cb.challenges().evm_word(), ); // Pop the byte index and the value from the stack, push the result on diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index 638f301b1b..c4204a66bf 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -107,11 +107,8 @@ pub enum ExecutionState { ErrorOutOfGasEXTCODECOPY, ErrorOutOfGasSLOAD, ErrorOutOfGasSSTORE, - ErrorOutOfGasCALL, - ErrorOutOfGasCALLCODE, - ErrorOutOfGasDELEGATECALL, + ErrorOutOfGasCall, ErrorOutOfGasCREATE2, - ErrorOutOfGasSTATICCALL, ErrorOutOfGasSELFDESTRUCT, } @@ -155,11 +152,8 @@ impl ExecutionState { | Self::ErrorOutOfGasEXTCODECOPY | Self::ErrorOutOfGasSLOAD | Self::ErrorOutOfGasSSTORE - | Self::ErrorOutOfGasCALL - | Self::ErrorOutOfGasCALLCODE - | Self::ErrorOutOfGasDELEGATECALL + | Self::ErrorOutOfGasCall | Self::ErrorOutOfGasCREATE2 - | Self::ErrorOutOfGasSTATICCALL | Self::ErrorOutOfGasSELFDESTRUCT ) } diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index 22e26f6835..ced8f1b785 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -189,7 +189,7 @@ impl<'r, 'b, F: FieldExt> CachedRegion<'r, 'b, F> { pub fn word_rlc(&self, n: U256) -> Value { self.challenges .evm_word() - .map(|r| Word::random_linear_combine(n.to_le_bytes(), r)) + .map(|r| rlc::value(&n.to_le_bytes(), r)) } pub fn empty_hash_rlc(&self) -> Value { self.word_rlc(U256::from_little_endian(&*EMPTY_HASH_LE)) @@ -243,11 +243,11 @@ impl StoredExpression { fixed_query.rotation(), )) }, - &|advide_query| { + &|advice_query| { Value::known(region.get_advice( offset, - advide_query.column_index(), - advide_query.rotation(), + advice_query.column_index(), + advice_query.rotation(), )) }, &|_| unimplemented!("instance column"), @@ -462,23 +462,9 @@ pub(crate) struct RandomLinearCombination { impl RandomLinearCombination { const N_BYTES: usize = N; - pub(crate) fn random_linear_combine(bytes: [u8; N], randomness: F) -> F { - rlc::value(&bytes, randomness) - } - - pub(crate) fn random_linear_combine_expr( - bytes: [Expression; N], - power_of_randomness: &[Expression], - ) -> Expression { - rlc::expr(&bytes, power_of_randomness) - } - - pub(crate) fn new(cells: [Cell; N], power_of_randomness: &[Expression]) -> Self { + pub(crate) fn new(cells: [Cell; N], randomness: Expression) -> Self { Self { - expression: Self::random_linear_combine_expr( - cells.clone().map(|cell| cell.expr()), - power_of_randomness, - ), + expression: rlc::expr(&cells.clone().map(|cell| cell.expr()), randomness), cells, } } @@ -547,20 +533,17 @@ pub(crate) mod from_bytes { /// Returns the random linear combination of the inputs. /// Encoding is done as follows: v_0 * R^0 + v_1 * R^1 + ... pub(crate) mod rlc { + use std::ops::{Add, Mul}; + use crate::util::Expr; use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; - pub(crate) fn expr>( - expressions: &[E], - power_of_randomness: &[E], - ) -> Expression { - debug_assert!(expressions.len() <= power_of_randomness.len() + 1); - - let mut rlc = expressions[0].expr(); - for (expression, randomness) in expressions[1..].iter().zip(power_of_randomness.iter()) { - rlc = rlc + expression.expr() * randomness.expr(); + pub(crate) fn expr>(expressions: &[E], randomness: E) -> Expression { + if !expressions.is_empty() { + generic(expressions.iter().map(|e| e.expr()), randomness.expr()) + } else { + 0.expr() } - rlc } pub(crate) fn value<'a, F: FieldExt, I>(values: I, randomness: F) -> F @@ -568,9 +551,27 @@ pub(crate) mod rlc { I: IntoIterator, ::IntoIter: DoubleEndedIterator, { - values.into_iter().rev().fold(F::zero(), |acc, value| { - acc * randomness + F::from(*value as u64) - }) + let values = values + .into_iter() + .map(|v| F::from(*v as u64)) + .collect::>(); + if !values.is_empty() { + generic(values, randomness) + } else { + F::zero() + } + } + + fn generic(values: I, randomness: V) -> V + where + I: IntoIterator, + ::IntoIter: DoubleEndedIterator, + V: Clone + Add + Mul, + { + let mut values = values.into_iter().rev(); + let init = values.next().expect("values should not be empty"); + + values.fold(init, |acc, value| acc * randomness.clone() + value) } } diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index b508fe1845..5e24e71c7a 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -266,8 +266,6 @@ pub(crate) struct ConstraintBuilder<'a, F> { pub(crate) curr: Step, pub(crate) next: Step, challenges: &'a Challenges>, - word_powers_of_randomness: &'a [Expression; 31], - lookup_powers_of_randomness: &'a [Expression; 12], execution_state: ExecutionState, constraints: Constraints, rw_counter_offset: Expression, @@ -285,8 +283,6 @@ impl<'a, F: Field> ConstraintBuilder<'a, F> { curr: Step, next: Step, challenges: &'a Challenges>, - word_powers_of_randomness: &'a [Expression; 31], - lookup_powers_of_randomness: &'a [Expression; 12], execution_state: ExecutionState, ) -> Self { Self { @@ -309,8 +305,6 @@ impl<'a, F: Field> ConstraintBuilder<'a, F> { condition: None, constraints_location: ConstraintLocation::Step, stored_expressions: Vec::new(), - word_powers_of_randomness, - lookup_powers_of_randomness, } } @@ -379,7 +373,7 @@ impl<'a, F: Field> ConstraintBuilder<'a, F> { } pub(crate) fn query_word_rlc(&mut self) -> RandomLinearCombination { - RandomLinearCombination::::new(self.query_bytes(), self.word_powers_of_randomness) + RandomLinearCombination::::new(self.query_bytes(), self.challenges.evm_word()) } pub(crate) fn query_bytes(&mut self) -> [Cell; N] { @@ -423,7 +417,7 @@ impl<'a, F: Field> ConstraintBuilder<'a, F> { } pub(crate) fn word_rlc(&self, bytes: [Expression; N]) -> Expression { - RandomLinearCombination::random_linear_combine_expr(bytes, self.word_powers_of_randomness) + rlc::expr(&bytes, self.challenges.evm_word()) } pub(crate) fn empty_hash_rlc(&self) -> Expression { @@ -621,7 +615,7 @@ impl<'a, F: Field> ConstraintBuilder<'a, F> { "Bytecode (length)", Lookup::Bytecode { hash: code_hash, - tag: BytecodeFieldTag::Length.expr(), + tag: BytecodeFieldTag::Header.expr(), index: 0.expr(), is_code: 0.expr(), value, @@ -1435,7 +1429,7 @@ impl<'a, F: Field> ConstraintBuilder<'a, F> { }; let compressed_expr = self.split_expression( "Lookup compression", - rlc::expr(&lookup.input_exprs(), self.lookup_powers_of_randomness), + rlc::expr(&lookup.input_exprs(), self.challenges.lookup_input()), MAX_DEGREE - IMPLICIT_DEGREE, ); self.store_expression(name, compressed_expr, CellType::Lookup(lookup.table())); diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs index da078ba884..daff32423a 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs @@ -124,15 +124,10 @@ impl> Circuit for UnitTestMathGadgetBaseC let step_curr = Step::new(meta, advices, 0, false); let step_next = Step::new(meta, advices, MAX_STEP_HEIGHT, true); - let evm_word_powers_of_randomness = challenges_exprs.evm_word_powers_of_randomness(); - let lookup_input_powers_of_randomness = - challenges_exprs.lookup_input_powers_of_randomness(); let mut cb = ConstraintBuilder::new( step_curr.clone(), step_next, &challenges_exprs, - &evm_word_powers_of_randomness, - &lookup_input_powers_of_randomness, ExecutionState::STOP, ); let math_gadget_container = G::configure_gadget_container(&mut cb); @@ -157,7 +152,7 @@ impl> Circuit for UnitTestMathGadgetBaseC let table_expressions = fixed_table.table_exprs(meta); vec![( column.expr(), - rlc::expr(&table_expressions, &lookup_input_powers_of_randomness), + rlc::expr(&table_expressions, challenges_exprs.lookup_input()), )] }); } diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index b72e34f72a..ef32b64c52 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -107,7 +107,7 @@ impl SubCircuitConfig for StateCircuitConfig { selector, rw_table.storage_key, lookups, - power_of_randomness.clone(), + challenges.evm_word(), ); let initial_value = meta.advice_column_in(SecondPhase); diff --git a/zkevm-circuits/src/state_circuit/random_linear_combination.rs b/zkevm-circuits/src/state_circuit/random_linear_combination.rs index 26776622b4..698e1b6a31 100644 --- a/zkevm-circuits/src/state_circuit/random_linear_combination.rs +++ b/zkevm-circuits/src/state_circuit/random_linear_combination.rs @@ -67,7 +67,7 @@ impl Chip { selector: Column, encoded: Column, lookup: lookups::Config, - power_of_randomness: [Expression; 31], + randomness: Expression, ) -> Config { let bytes = [0; N].map(|_| meta.advice_column()); @@ -81,7 +81,7 @@ impl Chip { let selector = meta.query_fixed(selector, Rotation::cur()); let encoded = meta.query_advice(encoded, Rotation::cur()); let bytes = bytes.map(|c| meta.query_advice(c, Rotation::cur())); - vec![selector * (encoded - rlc::expr(&bytes, &power_of_randomness))] + vec![selector * (encoded - rlc::expr(&bytes, randomness))] }); Config { bytes } diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index c2c0538db2..b5b581905d 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -51,7 +51,7 @@ //! - [x] Tx Circuit //! - [ ] MPT Circuit -use crate::bytecode_circuit::bytecode_unroller::{ +use crate::bytecode_circuit::circuit::{ BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs, }; use crate::copy_circuit::{CopyCircuit, CopyCircuitConfig, CopyCircuitConfigArgs}; @@ -79,19 +79,9 @@ use halo2_proofs::{ use std::array; -/// Mock randomness used for `SuperCircuit`. -pub const MOCK_RANDOMNESS: u64 = 0x100; -// TODO: Figure out if we can remove MAX_TXS, MAX_CALLDATA and MAX_RWS from the -// struct. - /// Configuration of the Super Circuit #[derive(Clone)] -pub struct SuperCircuitConfig< - F: Field, - const MAX_TXS: usize, - const MAX_CALLDATA: usize, - const MAX_RWS: usize, -> { +pub struct SuperCircuitConfig { block_table: BlockTable, mpt_table: MptTable, evm_circuit: EvmCircuitConfig, @@ -104,65 +94,28 @@ pub struct SuperCircuitConfig< exp_circuit: ExpCircuitConfig, } -/// The Super Circuit contains all the zkEVM circuits -#[derive(Clone, Default, Debug)] -pub struct SuperCircuit< - F: Field, - const MAX_TXS: usize, - const MAX_CALLDATA: usize, - const MAX_RWS: usize, - const MAX_COPY_ROWS: usize, -> { - /// EVM Circuit - pub evm_circuit: EvmCircuit, - /// State Circuit - pub state_circuit: StateCircuit, - /// The transaction circuit that will be used in the `synthesize` step. - pub tx_circuit: TxCircuit, - /// Public Input Circuit - pub pi_circuit: PiCircuit, - /// Bytecode Circuit - pub bytecode_circuit: BytecodeCircuit, - /// Copy Circuit - pub copy_circuit: CopyCircuit, - /// Exp Circuit - pub exp_circuit: ExpCircuit, - /// Keccak Circuit - pub keccak_circuit: KeccakCircuit, +/// Circuit configuration arguments +pub struct SuperCircuitConfigArgs { + /// Max txs + pub max_txs: usize, + /// Max calldata + pub max_calldata: usize, + /// Mock randomness + pub mock_randomness: u64, } -impl< - F: Field, - const MAX_TXS: usize, - const MAX_CALLDATA: usize, - const MAX_RWS: usize, - const MAX_COPY_ROWS: usize, - > SuperCircuit -{ - /// Return the number of rows required to verify a given block - pub fn get_num_rows_required(block: &Block) -> usize { - let num_rows_evm_circuit = EvmCircuit::::get_num_rows_required(block); - let num_rows_tx_circuit = TxCircuitConfig::::get_num_rows_required(MAX_TXS); - num_rows_evm_circuit.max(num_rows_tx_circuit) - } -} - -impl< - F: Field, - const MAX_TXS: usize, - const MAX_CALLDATA: usize, - const MAX_RWS: usize, - const MAX_COPY_ROWS: usize, - > Circuit for SuperCircuit -{ - type Config = SuperCircuitConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { +impl SubCircuitConfig for SuperCircuitConfig { + type ConfigArgs = SuperCircuitConfigArgs; + + /// Configure SuperCircuitConfig + fn new( + meta: &mut ConstraintSystem, + Self::ConfigArgs { + max_txs, + max_calldata, + mock_randomness, + }: Self::ConfigArgs, + ) -> Self { let tx_table = TxTable::construct(meta); let rw_table = RwTable::construct(meta); let mpt_table = MptTable::construct(meta); @@ -173,8 +126,10 @@ impl< let exp_table = ExpTable::construct(meta); let keccak_table = KeccakTable::construct(meta); + // Use a mock randomness instead of the randomness derived from the challange + // (either from mock or real prover) to help debugging assignments. let power_of_randomness: [Expression; 31] = array::from_fn(|i| { - Expression::Constant(F::from(MOCK_RANDOMNESS).pow(&[1 + i as u64, 0, 0, 0])) + Expression::Constant(F::from(mock_randomness).pow(&[1 + i as u64, 0, 0, 0])) }); let challenges = Challenges::mock( @@ -194,8 +149,8 @@ impl< let pi_circuit = PiCircuitConfig::new( meta, PiCircuitConfigArgs { - max_txs: MAX_TXS, - max_calldata: MAX_CALLDATA, + max_txs, + max_calldata, block_table: block_table.clone(), tx_table: tx_table.clone(), }, @@ -250,7 +205,7 @@ impl< }, ); - Self::Config { + Self { block_table, mpt_table, evm_circuit, @@ -263,6 +218,159 @@ impl< exp_circuit, } } +} + +/// The Super Circuit contains all the zkEVM circuits +#[derive(Clone, Default, Debug)] +pub struct SuperCircuit< + F: Field, + const MAX_TXS: usize, + const MAX_CALLDATA: usize, + const MOCK_RANDOMNESS: u64, +> { + /// EVM Circuit + pub evm_circuit: EvmCircuit, + /// State Circuit + pub state_circuit: StateCircuit, + /// The transaction circuit that will be used in the `synthesize` step. + pub tx_circuit: TxCircuit, + /// Public Input Circuit + pub pi_circuit: PiCircuit, + /// Bytecode Circuit + pub bytecode_circuit: BytecodeCircuit, + /// Copy Circuit + pub copy_circuit: CopyCircuit, + /// Exp Circuit + pub exp_circuit: ExpCircuit, + /// Keccak Circuit + pub keccak_circuit: KeccakCircuit, +} + +impl + SuperCircuit +{ + /// Return the number of rows required to verify a given block + pub fn get_num_rows_required(block: &Block) -> usize { + let num_rows_evm_circuit = EvmCircuit::::get_num_rows_required(block); + assert_eq!(block.circuits_params.max_txs, MAX_TXS); + let num_rows_tx_circuit = + TxCircuitConfig::::get_num_rows_required(block.circuits_params.max_txs); + num_rows_evm_circuit.max(num_rows_tx_circuit) + } +} + +// Eventhough the SuperCircuit is not a subcircuit we implement the SubCircuit +// trait for it in order to get the `new_from_block` and `instance` methods that +// allow us to generalize integration tests. +impl + SubCircuit for SuperCircuit +{ + type Config = SuperCircuitConfig; + + fn new_from_block(block: &Block) -> Self { + let evm_circuit = EvmCircuit::new_from_block(block); + let state_circuit = StateCircuit::new_from_block(block); + let tx_circuit = TxCircuit::new_from_block(block); + let pi_circuit = PiCircuit::new_from_block(block); + let bytecode_circuit = BytecodeCircuit::new_from_block(block); + let copy_circuit = CopyCircuit::new_from_block_no_external(block); + let exp_circuit = ExpCircuit::new_from_block(block); + let keccak_circuit = KeccakCircuit::new_from_block(block); + + SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MOCK_RANDOMNESS> { + evm_circuit, + state_circuit, + tx_circuit, + pi_circuit, + bytecode_circuit, + copy_circuit, + exp_circuit, + keccak_circuit, + } + } + + /// Returns suitable inputs for the SuperCircuit. + fn instance(&self) -> Vec> { + let mut instance = Vec::new(); + instance.extend_from_slice(&self.keccak_circuit.instance()); + instance.extend_from_slice(&self.pi_circuit.instance()); + instance.extend_from_slice(&self.tx_circuit.instance()); + instance.extend_from_slice(&self.bytecode_circuit.instance()); + instance.extend_from_slice(&self.copy_circuit.instance()); + instance.extend_from_slice(&self.state_circuit.instance()); + instance.extend_from_slice(&self.exp_circuit.instance()); + instance.extend_from_slice(&self.evm_circuit.instance()); + + instance + } + + /// Return the minimum number of rows required to prove the block + fn min_num_rows_block(block: &Block) -> (usize, usize) { + let evm = EvmCircuit::min_num_rows_block(block); + let state = StateCircuit::min_num_rows_block(block); + let bytecode = BytecodeCircuit::min_num_rows_block(block); + let copy = CopyCircuit::min_num_rows_block(block); + let keccak = KeccakCircuit::min_num_rows_block(block); + let tx = TxCircuit::min_num_rows_block(block); + let exp = ExpCircuit::min_num_rows_block(block); + let pi = PiCircuit::min_num_rows_block(block); + + let rows: Vec<(usize, usize)> = vec![evm, state, bytecode, copy, keccak, tx, exp, pi]; + let (rows_without_padding, rows_with_padding): (Vec, Vec) = + rows.into_iter().unzip(); + ( + itertools::max(rows_without_padding).unwrap(), + itertools::max(rows_with_padding).unwrap(), + ) + } + + /// Make the assignments to the SuperCircuit + fn synthesize_sub( + &self, + config: &Self::Config, + challenges: &Challenges>, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + self.keccak_circuit + .synthesize_sub(&config.keccak_circuit, challenges, layouter)?; + self.bytecode_circuit + .synthesize_sub(&config.bytecode_circuit, challenges, layouter)?; + self.tx_circuit + .synthesize_sub(&config.tx_circuit, challenges, layouter)?; + self.state_circuit + .synthesize_sub(&config.state_circuit, challenges, layouter)?; + self.copy_circuit + .synthesize_sub(&config.copy_circuit, challenges, layouter)?; + self.exp_circuit + .synthesize_sub(&config.exp_circuit, challenges, layouter)?; + self.evm_circuit + .synthesize_sub(&config.evm_circuit, challenges, layouter)?; + self.pi_circuit + .synthesize_sub(&config.pi_circuit, challenges, layouter)?; + Ok(()) + } +} + +impl + Circuit for SuperCircuit +{ + type Config = SuperCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + Self::Config::new( + meta, + SuperCircuitConfigArgs { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + mock_randomness: MOCK_RANDOMNESS, + }, + ) + } fn synthesize( &self, @@ -289,36 +397,12 @@ impl< Value::known(block.randomness), )?; - self.keccak_circuit - .synthesize_sub(&config.keccak_circuit, &challenges, &mut layouter)?; - self.bytecode_circuit.synthesize_sub( - &config.bytecode_circuit, - &challenges, - &mut layouter, - )?; - self.tx_circuit - .synthesize_sub(&config.tx_circuit, &challenges, &mut layouter)?; - self.state_circuit - .synthesize_sub(&config.state_circuit, &challenges, &mut layouter)?; - self.copy_circuit - .synthesize_sub(&config.copy_circuit, &challenges, &mut layouter)?; - self.exp_circuit - .synthesize_sub(&config.exp_circuit, &challenges, &mut layouter)?; - self.evm_circuit - .synthesize_sub(&config.evm_circuit, &challenges, &mut layouter)?; - self.pi_circuit - .synthesize_sub(&config.pi_circuit, &challenges, &mut layouter)?; - Ok(()) + self.synthesize_sub(&config, &challenges, &mut layouter) } } -impl< - F: Field, - const MAX_TXS: usize, - const MAX_CALLDATA: usize, - const MAX_RWS: usize, - const MAX_COPY_ROWS: usize, - > SuperCircuit +impl + SuperCircuit { /// From the witness data, generate a SuperCircuit instance with all of the /// sub-circuits filled with their corresponding witnesses. @@ -328,18 +412,10 @@ impl< #[allow(clippy::type_complexity)] pub fn build( geth_data: GethData, + circuits_params: CircuitsParams, ) -> Result<(u32, Self, Vec>, CircuitInputBuilder), bus_mapping::Error> { - let block_data = BlockData::new_from_geth_data_with_params( - geth_data.clone(), - CircuitsParams { - max_txs: MAX_TXS, - max_calldata: MAX_CALLDATA, - max_rws: MAX_RWS, - max_copy_rows: MAX_COPY_ROWS, - max_bytecode: 512, - keccak_padding: None, - }, - ); + let block_data = + BlockData::new_from_geth_data_with_params(geth_data.clone(), circuits_params); let mut builder = block_data.new_circuit_input_builder(); builder .handle_block(&geth_data.eth_block, &geth_data.geth_traces) @@ -359,64 +435,20 @@ impl< ) -> Result<(u32, Self, Vec>), bus_mapping::Error> { let mut block = block_convert(&builder.block, &builder.code_db).unwrap(); block.randomness = F::from(MOCK_RANDOMNESS); + assert_eq!(block.circuits_params.max_txs, MAX_TXS); + assert_eq!(block.circuits_params.max_calldata, MAX_CALLDATA); const NUM_BLINDING_ROWS: usize = 64; let (_, rows_needed) = Self::min_num_rows_block(&block); let k = log2_ceil(NUM_BLINDING_ROWS + rows_needed); log::debug!("super circuit uses k = {}", k); - let evm_circuit = EvmCircuit::new_from_block(&block); - let state_circuit = StateCircuit::new_from_block(&block); - let tx_circuit = TxCircuit::new_from_block(&block); - let pi_circuit = PiCircuit::new_from_block(&block); - let bytecode_circuit = BytecodeCircuit::new_from_block(&block); - let copy_circuit = CopyCircuit::new_from_block_no_external(&block); - let exp_circuit = ExpCircuit::new_from_block(&block); - let keccak_circuit = KeccakCircuit::new_from_block(&block); - - let circuit = SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MAX_RWS, MAX_COPY_ROWS> { - evm_circuit, - state_circuit, - tx_circuit, - pi_circuit, - bytecode_circuit, - copy_circuit, - exp_circuit, - keccak_circuit, - }; + let circuit = + SuperCircuit::<_, MAX_TXS, MAX_CALLDATA, MOCK_RANDOMNESS>::new_from_block(&block); let instance = circuit.instance(); Ok((k, circuit, instance)) } - - /// Returns suitable inputs for the SuperCircuit. - pub fn instance(&self) -> Vec> { - // SignVerifyChip -> ECDSAChip -> MainGate instance column - let pi_instance = self.pi_circuit.instance(); - let instance = vec![pi_instance[0].clone(), vec![]]; - - instance - } - - /// Return the minimum number of rows required to prove the block - pub fn min_num_rows_block(block: &Block) -> (usize, usize) { - let evm = EvmCircuit::min_num_rows_block(block); - let state = StateCircuit::min_num_rows_block(block); - let bytecode = BytecodeCircuit::min_num_rows_block(block); - let copy = CopyCircuit::min_num_rows_block(block); - let keccak = KeccakCircuit::min_num_rows_block(block); - let tx = TxCircuit::min_num_rows_block(block); - let exp = ExpCircuit::min_num_rows_block(block); - let pi = PiCircuit::min_num_rows_block(block); - - let rows: Vec<(usize, usize)> = vec![evm, state, bytecode, copy, keccak, tx, exp, pi]; - let (rows_without_padding, rows_with_padding): (Vec, Vec) = - rows.into_iter().unzip(); - ( - itertools::max(rows_without_padding).unwrap(), - itertools::max(rows_with_padding).unwrap(), - ) - } } #[cfg(test)] @@ -436,7 +468,7 @@ mod super_circuit_tests { #[test] fn super_circuit_degree() { let mut cs = ConstraintSystem::::default(); - SuperCircuit::<_, 1, 32, 256, 32>::configure(&mut cs); + 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); @@ -445,14 +477,17 @@ mod super_circuit_tests { fn test_super_circuit< const MAX_TXS: usize, const MAX_CALLDATA: usize, - const MAX_RWS: usize, - const MAX_COPY_ROWS: usize, + const MOCK_RANDOMNESS: u64, >( block: GethData, + circuits_params: CircuitsParams, ) { let (k, circuit, instance, _) = - SuperCircuit::::build(block) - .unwrap(); + SuperCircuit::::build( + block, + circuits_params, + ) + .unwrap(); let prover = MockProver::run(k, &circuit, instance).unwrap(); let res = prover.verify_par(); if let Err(err) = res { @@ -547,6 +582,8 @@ mod super_circuit_tests { block } + const TEST_MOCK_RANDOMNESS: u64 = 0x100; + // High memory usage test. Run in serial with: // `cargo test [...] serial_ -- --ignored --test-threads 1` #[ignore] @@ -555,9 +592,15 @@ mod super_circuit_tests { let block = block_1tx(); const MAX_TXS: usize = 1; const MAX_CALLDATA: usize = 32; - const MAX_RWS: usize = 256; - const MAX_COPY_ROWS: usize = 256; - test_super_circuit::(block); + let circuits_params = CircuitsParams { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_rws: 256, + max_copy_rows: 256, + max_bytecode: 512, + keccak_padding: None, + }; + test_super_circuit::(block, circuits_params); } #[ignore] #[test] @@ -565,9 +608,15 @@ mod super_circuit_tests { let block = block_1tx(); const MAX_TXS: usize = 2; const MAX_CALLDATA: usize = 32; - const MAX_RWS: usize = 256; - const MAX_COPY_ROWS: usize = 256; - test_super_circuit::(block); + let circuits_params = CircuitsParams { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_rws: 256, + max_copy_rows: 256, + max_bytecode: 512, + keccak_padding: None, + }; + test_super_circuit::(block, circuits_params); } #[ignore] #[test] @@ -575,8 +624,14 @@ mod super_circuit_tests { let block = block_2tx(); const MAX_TXS: usize = 2; const MAX_CALLDATA: usize = 32; - const MAX_RWS: usize = 256; - const MAX_COPY_ROWS: usize = 256; - test_super_circuit::(block); + let circuits_params = CircuitsParams { + max_txs: MAX_TXS, + max_calldata: MAX_CALLDATA, + max_rws: 256, + max_copy_rows: 256, + max_bytecode: 512, + keccak_padding: None, + }; + test_super_circuit::(block, circuits_params); } } diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 4755232959..af9ddc4832 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -1,7 +1,7 @@ //! Table definitions used cross-circuits use crate::copy_circuit::number_or_hash_to_field; -use crate::evm_circuit::util::{rlc, RandomLinearCombination}; +use crate::evm_circuit::util::rlc; use crate::exp_circuit::{OFFSET_INCREMENT, ROWS_PER_STEP}; use crate::impl_expr; use crate::util::build_tx_log_address; @@ -552,12 +552,10 @@ impl MptTable { /// Tag to identify the field in a Bytecode Table row #[derive(Clone, Copy, Debug)] pub enum BytecodeFieldTag { - /// Length field - Length, + /// Header field + Header, /// Byte field Byte, - /// Padding field - Padding, } impl_expr!(BytecodeFieldTag); @@ -772,8 +770,8 @@ impl KeccakTable { keccak.update(input); let output = keccak.digest(); let output_rlc = challenges.evm_word().map(|challenge| { - RandomLinearCombination::::random_linear_combine( - Word::from_big_endian(output.as_slice()).to_le_bytes(), + rlc::value( + &Word::from_big_endian(output.as_slice()).to_le_bytes(), challenge, ) }); @@ -840,6 +838,21 @@ impl KeccakTable { }, ) } + + /// returns matchings between the circuit columns passed as parameters and + /// the table collumns + pub fn match_columns( + &self, + value_rlc: Column, + length: Column, + code_hash: Column, + ) -> Vec<(Column, Column)> { + vec![ + (value_rlc, self.input_rlc), + (length, self.input_len), + (code_hash, self.output_rlc), + ] + } } impl DynamicTableColumns for KeccakTable { diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index c8de88e68e..09e6d98f7d 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -374,6 +374,12 @@ impl SubCircuit for TxCircuit { self.assign_tx_table(config, challenges, layouter, assigned_sig_verifs)?; Ok(()) } + + fn instance(&self) -> Vec> { + // The maingate expects an instance column, but we don't use it, so we return an + // "empty" instance column + vec![vec![]] + } } #[cfg(any(feature = "test", test))] diff --git a/zkevm-circuits/src/tx_circuit/sign_verify.rs b/zkevm-circuits/src/tx_circuit/sign_verify.rs index d90f105fbf..1667cc1d16 100644 --- a/zkevm-circuits/src/tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/tx_circuit/sign_verify.rs @@ -234,13 +234,8 @@ impl SignVerifyConfig { let [rlc, rlc_next] = [Rotation::cur(), Rotation::next()] .map(|rotation| meta.query_advice(rlc, rotation)); let inputs = [e, d, c, b, a, rlc]; - let powers_of_challenge = iter::successors(challenge.clone().into(), |power| { - (challenge.clone() * power.clone()).into() - }) - .take(inputs.len() - 1) - .collect_vec(); - vec![q_rlc * (rlc_next - rlc::expr(&inputs, &powers_of_challenge))] + vec![q_rlc * (rlc_next - rlc::expr(&inputs, challenge))] }); } } diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index 82c8d0bbca..245433ee3f 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -1,4 +1,5 @@ //! Common utility traits and functions. +use bus_mapping::evm::OpcodeId; use halo2_proofs::{ arithmetic::FieldExt, circuit::{Layouter, Value}, @@ -6,10 +7,11 @@ use halo2_proofs::{ Challenge, ConstraintSystem, Error, Expression, FirstPhase, SecondPhase, VirtualCells, }, }; +use keccak256::plain::Keccak; -use crate::table::TxLogFieldTag; use crate::witness; -use eth_types::{Field, ToAddress}; +use crate::{evm_circuit::util::rlc, table::TxLogFieldTag}; +use eth_types::{Field, ToAddress, Word}; pub use ethers_core::types::{Address, U256}; pub use gadgets::util::Expr; @@ -26,7 +28,7 @@ pub(crate) fn query_expression( } pub(crate) fn random_linear_combine_word(bytes: [u8; 32], randomness: F) -> F { - crate::evm_circuit::util::Word::random_linear_combine(bytes, randomness) + rlc::value(&bytes, randomness) } /// All challenges used in `SuperCircuit`. @@ -188,3 +190,21 @@ pub trait SubCircuitConfig { pub fn log2_ceil(n: usize) -> u32 { u32::BITS - (n as u32).leading_zeros() - (n & (n - 1) == 0) as u32 } + +pub(crate) fn keccak(msg: &[u8]) -> Word { + let mut keccak = Keccak::default(); + keccak.update(msg); + Word::from_big_endian(keccak.digest().as_slice()) +} + +pub(crate) fn is_push(byte: u8) -> bool { + OpcodeId::from(byte).is_push() +} + +pub(crate) fn get_push_size(byte: u8) -> u64 { + if is_push(byte) { + byte as u64 - OpcodeId::PUSH1.as_u64() + 1 + } else { + 0u64 + } +} diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index c7878c94ff..4bf4092b18 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{evm_circuit::util::RandomLinearCombination, table::BlockContextFieldTag}; +use crate::{evm_circuit::util::rlc, table::BlockContextFieldTag}; use bus_mapping::{ circuit_input_builder::{self, CircuitsParams, CopyEvent, ExpEvent}, Error, @@ -112,12 +112,8 @@ impl BlockContext { [ Value::known(F::from(BlockContextFieldTag::Difficulty as u64)), Value::known(F::zero()), - randomness.map(|randomness| { - RandomLinearCombination::random_linear_combine( - self.difficulty.to_le_bytes(), - randomness, - ) - }), + randomness + .map(|randomness| rlc::value(&self.difficulty.to_le_bytes(), randomness)), ], [ Value::known(F::from(BlockContextFieldTag::GasLimit as u64)), @@ -127,22 +123,14 @@ impl BlockContext { [ Value::known(F::from(BlockContextFieldTag::BaseFee as u64)), Value::known(F::zero()), - randomness.map(|randomness| { - RandomLinearCombination::random_linear_combine( - self.base_fee.to_le_bytes(), - randomness, - ) - }), + randomness + .map(|randomness| rlc::value(&self.base_fee.to_le_bytes(), randomness)), ], [ Value::known(F::from(BlockContextFieldTag::ChainId as u64)), Value::known(F::zero()), - randomness.map(|randomness| { - RandomLinearCombination::random_linear_combine( - self.chain_id.to_le_bytes(), - randomness, - ) - }), + randomness + .map(|randomness| rlc::value(&self.chain_id.to_le_bytes(), randomness)), ], ], { @@ -154,12 +142,8 @@ impl BlockContext { [ Value::known(F::from(BlockContextFieldTag::BlockHash as u64)), Value::known((self.number - len_history + idx).to_scalar().unwrap()), - randomness.map(|randomness| { - RandomLinearCombination::random_linear_combine( - hash.to_le_bytes(), - randomness, - ) - }), + randomness + .map(|randomness| rlc::value(&hash.to_le_bytes(), randomness)), ] }) .collect() diff --git a/zkevm-circuits/src/witness/bytecode.rs b/zkevm-circuits/src/witness/bytecode.rs index 7c5c92f9fb..75345c45b2 100644 --- a/zkevm-circuits/src/witness/bytecode.rs +++ b/zkevm-circuits/src/witness/bytecode.rs @@ -3,9 +3,7 @@ use eth_types::{Field, ToLittleEndian, Word}; use halo2_proofs::circuit::Value; use sha3::{Digest, Keccak256}; -use crate::{ - evm_circuit::util::RandomLinearCombination, table::BytecodeFieldTag, util::Challenges, -}; +use crate::{evm_circuit::util::rlc, table::BytecodeFieldTag, util::Challenges}; /// Bytecode #[derive(Clone, Debug)] @@ -30,13 +28,13 @@ impl Bytecode { ) -> Vec<[Value; 5]> { let n = 1 + self.bytes.len(); let mut rows = Vec::with_capacity(n); - let hash = challenges.evm_word().map(|challenge| { - RandomLinearCombination::random_linear_combine(self.hash.to_le_bytes(), challenge) - }); + let hash = challenges + .evm_word() + .map(|challenge| rlc::value(&self.hash.to_le_bytes(), challenge)); rows.push([ hash, - Value::known(F::from(BytecodeFieldTag::Length as u64)), + Value::known(F::from(BytecodeFieldTag::Header as u64)), Value::known(F::zero()), Value::known(F::zero()), Value::known(F::from(self.bytes.len() as u64)), diff --git a/zkevm-circuits/src/witness/mpt.rs b/zkevm-circuits/src/witness/mpt.rs index de0d4919df..362007ab6f 100644 --- a/zkevm-circuits/src/witness/mpt.rs +++ b/zkevm-circuits/src/witness/mpt.rs @@ -1,4 +1,5 @@ -use crate::evm_circuit::{util::RandomLinearCombination, witness::Rw}; +use crate::evm_circuit::util::rlc; +use crate::evm_circuit::witness::Rw; use crate::table::{AccountFieldTag, ProofType}; use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; use halo2_proofs::circuit::Value; @@ -116,7 +117,7 @@ impl MptUpdate { field_tag: AccountFieldTag::Nonce | AccountFieldTag::NonExisting, .. } => x.to_scalar().unwrap(), - _ => RandomLinearCombination::random_linear_combine(x.to_le_bytes(), word_randomness), + _ => rlc::value(&x.to_le_bytes(), word_randomness), }; (assign(self.new_value), assign(self.old_value)) @@ -124,14 +125,8 @@ impl MptUpdate { pub(crate) fn root_assignments(&self, word_randomness: F) -> (F, F) { ( - RandomLinearCombination::random_linear_combine( - self.new_root.to_le_bytes(), - word_randomness, - ), - RandomLinearCombination::random_linear_combine( - self.old_root.to_le_bytes(), - word_randomness, - ), + rlc::value(&self.new_root.to_le_bytes(), word_randomness), + rlc::value(&self.old_root.to_le_bytes(), word_randomness), ) } } @@ -194,10 +189,7 @@ impl Key { match self { Self::Account { .. } => F::zero(), Self::AccountStorage { storage_key, .. } => { - RandomLinearCombination::random_linear_combine( - storage_key.to_le_bytes(), - randomness, - ) + rlc::value(&storage_key.to_le_bytes(), randomness) } } } diff --git a/zkevm-circuits/src/witness/rw.rs b/zkevm-circuits/src/witness/rw.rs index a1e683b195..00c407a414 100644 --- a/zkevm-circuits/src/witness/rw.rs +++ b/zkevm-circuits/src/witness/rw.rs @@ -6,11 +6,11 @@ use eth_types::{Address, Field, ToAddress, ToLittleEndian, ToScalar, Word, U256} use halo2_proofs::circuit::Value; use itertools::Itertools; -use crate::util::build_tx_log_address; -use crate::{ - evm_circuit::util::RandomLinearCombination, - table::{AccountFieldTag, CallContextFieldTag, RwTableTag, TxLogFieldTag, TxReceiptFieldTag}, +use crate::evm_circuit::util::rlc; +use crate::table::{ + AccountFieldTag, CallContextFieldTag, RwTableTag, TxLogFieldTag, TxReceiptFieldTag, }; +use crate::util::build_tx_log_address; /// Rw constainer for a witness block #[derive(Debug, Default, Clone)] @@ -343,8 +343,8 @@ impl Rw { id: F::from(self.id().unwrap_or_default() as u64), address: self.address().unwrap_or_default().to_scalar().unwrap(), field_tag: F::from(self.field_tag().unwrap_or_default() as u64), - storage_key: RandomLinearCombination::random_linear_combine( - self.storage_key().unwrap_or_default().to_le_bytes(), + storage_key: rlc::value( + &self.storage_key().unwrap_or_default().to_le_bytes(), randomness, ), value: self.value_assignment(randomness), @@ -365,8 +365,8 @@ impl Rw { address: Value::known(self.address().unwrap_or_default().to_scalar().unwrap()), field_tag: Value::known(F::from(self.field_tag().unwrap_or_default() as u64)), storage_key: randomness.map(|randomness| { - RandomLinearCombination::random_linear_combine( - self.storage_key().unwrap_or_default().to_le_bytes(), + rlc::value( + &self.storage_key().unwrap_or_default().to_le_bytes(), randomness, ) }), @@ -529,10 +529,7 @@ impl Rw { // Only these two tags have values that may not fit into a scalar, so we need to // RLC. CallContextFieldTag::CodeHash | CallContextFieldTag::Value => { - RandomLinearCombination::random_linear_combine( - value.to_le_bytes(), - randomness, - ) + rlc::value(&value.to_le_bytes(), randomness) } _ => value.to_scalar().unwrap(), } @@ -541,20 +538,18 @@ impl Rw { value, field_tag, .. } => match field_tag { AccountFieldTag::CodeHash | AccountFieldTag::Balance => { - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) + rlc::value(&value.to_le_bytes(), randomness) } AccountFieldTag::Nonce | AccountFieldTag::NonExisting => value.to_scalar().unwrap(), }, Self::AccountStorage { value, .. } | Self::Stack { value, .. } => { - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) + rlc::value(&value.to_le_bytes(), randomness) } Self::TxLog { field_tag, value, .. } => match field_tag { - TxLogFieldTag::Topic => { - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) - } + TxLogFieldTag::Topic => rlc::value(&value.to_le_bytes(), randomness), _ => value.to_scalar().unwrap(), }, @@ -574,20 +569,14 @@ impl Rw { .. } => Some(match field_tag { AccountFieldTag::CodeHash | AccountFieldTag::Balance => { - RandomLinearCombination::random_linear_combine( - value_prev.to_le_bytes(), - randomness, - ) + rlc::value(&value_prev.to_le_bytes(), randomness) } AccountFieldTag::Nonce | AccountFieldTag::NonExisting => { value_prev.to_scalar().unwrap() } }), Self::AccountStorage { value_prev, .. } => { - Some(RandomLinearCombination::random_linear_combine( - value_prev.to_le_bytes(), - randomness, - )) + Some(rlc::value(&value_prev.to_le_bytes(), randomness)) } Self::TxAccessListAccount { is_warm_prev, .. } | Self::TxAccessListAccountStorage { is_warm_prev, .. } => { @@ -610,10 +599,7 @@ impl Rw { match self { Self::AccountStorage { committed_value, .. - } => Some(RandomLinearCombination::random_linear_combine( - committed_value.to_le_bytes(), - randomness, - )), + } => Some(rlc::value(&committed_value.to_le_bytes(), randomness)), _ => None, } } diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index 2a5958c107..2a00af024a 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -85,11 +85,8 @@ impl From<&ExecError> for ExecutionState { OogError::ExtCodeCopy => ExecutionState::ErrorOutOfGasEXTCODECOPY, OogError::Sload => ExecutionState::ErrorOutOfGasSLOAD, OogError::Sstore => ExecutionState::ErrorOutOfGasSSTORE, - OogError::Call => ExecutionState::ErrorOutOfGasCALL, - OogError::CallCode => ExecutionState::ErrorOutOfGasCALLCODE, - OogError::DelegateCall => ExecutionState::ErrorOutOfGasDELEGATECALL, + OogError::Call => ExecutionState::ErrorOutOfGasCall, OogError::Create2 => ExecutionState::ErrorOutOfGasCREATE2, - OogError::StaticCall => ExecutionState::ErrorOutOfGasSTATICCALL, OogError::SelfDestruct => ExecutionState::ErrorOutOfGasSELFDESTRUCT, }, } diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 85028ee690..f58a2e714b 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -2,9 +2,7 @@ use bus_mapping::circuit_input_builder; use eth_types::{Address, Field, ToLittleEndian, ToScalar, ToWord, Word}; use halo2_proofs::circuit::Value; -use crate::{ - evm_circuit::util::RandomLinearCombination, table::TxContextFieldTag, util::Challenges, -}; +use crate::{evm_circuit::util::rlc, table::TxContextFieldTag, util::Challenges}; use super::{step::step_convert, Call, ExecStep}; @@ -63,12 +61,9 @@ impl Transaction { Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::GasPrice as u64)), Value::known(F::zero()), - challenges.evm_word().map(|evm_word| { - RandomLinearCombination::random_linear_combine( - self.gas_price.to_le_bytes(), - evm_word, - ) - }), + challenges + .evm_word() + .map(|challenge| rlc::value(&self.gas_price.to_le_bytes(), challenge)), ], [ Value::known(F::from(self.id as u64)), @@ -92,12 +87,9 @@ impl Transaction { Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::Value as u64)), Value::known(F::zero()), - challenges.evm_word().map(|evm_word| { - RandomLinearCombination::random_linear_combine( - self.value.to_le_bytes(), - evm_word, - ) - }), + challenges + .evm_word() + .map(|challenge| rlc::value(&self.value.to_le_bytes(), challenge)), ], [ Value::known(F::from(self.id as u64)),
id