diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index ac89b562ce..9ad43afc3a 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -6,7 +6,7 @@ use eth_types::{ GethExecStep, H256, }; use gadgets::impl_expr; -use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; +use halo2_proofs::plonk::Expression; use strum_macros::EnumIter; /// An execution step of the EVM. @@ -201,7 +201,7 @@ pub enum NumberOrHash { /// Defines a copy event associated with EVM opcodes such as CALLDATACOPY, /// CODECOPY, CREATE, etc. More information: -/// https://github.com/privacy-scaling-explorations/zkevm-specs/blob/master/specs/copy-proof.md. +/// . #[derive(Clone, Debug)] pub struct CopyEvent { /// Represents the start address at the source of the copy event. diff --git a/circuit-benchmarks/src/evm_circuit.rs b/circuit-benchmarks/src/evm_circuit.rs index 9fb695fc2e..36bcd17b7b 100644 --- a/circuit-benchmarks/src/evm_circuit.rs +++ b/circuit-benchmarks/src/evm_circuit.rs @@ -6,6 +6,7 @@ use halo2_proofs::{ plonk::{Circuit, ConstraintSystem, Error, Expression}, }; use zkevm_circuits::evm_circuit::{witness::Block, EvmCircuit}; +use zkevm_circuits::table::{BlockTable, BytecodeTable, RwTable, TxTable}; #[derive(Debug, Default)] pub struct TestCircuit { @@ -21,10 +22,10 @@ impl Circuit for TestCircuit { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let tx_table = [(); 4].map(|_| meta.advice_column()); - let rw_table = [(); 11].map(|_| meta.advice_column()); - let bytecode_table = [(); 5].map(|_| meta.advice_column()); - let block_table = [(); 3].map(|_| meta.advice_column()); + let tx_table = TxTable::construct(meta); + let rw_table = RwTable::construct(meta); + let bytecode_table = BytecodeTable::construct(meta); + let block_table = BlockTable::construct(meta); let copy_table = [(); 11].map(|_| meta.advice_column()); // Use constant expression to mock constant instance column for a more // reasonable benchmark. diff --git a/gadgets/src/util.rs b/gadgets/src/util.rs index a5a7959595..974fc0eadf 100644 --- a/gadgets/src/util.rs +++ b/gadgets/src/util.rs @@ -126,7 +126,7 @@ pub trait Expr { #[macro_export] macro_rules! impl_expr { ($type:ty) => { - impl $crate::util::Expr for $type { + impl $crate::util::Expr for $type { #[inline] fn expr(&self) -> Expression { Expression::Constant(F::from(*self as u64)) @@ -134,7 +134,7 @@ macro_rules! impl_expr { } }; ($type:ty, $method:path) => { - impl $crate::util::Expr for $type { + impl $crate::util::Expr for $type { #[inline] fn expr(&self) -> Expression { Expression::Constant(F::from($method(self) as u64)) diff --git a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs index c96355b6c1..a19783826b 100644 --- a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs +++ b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs @@ -1,33 +1,27 @@ use crate::{ - evm_circuit::{ - table::BytecodeFieldTag, - util::{ - and, constraint_builder::BaseConstraintBuilder, not, or, select, - RandomLinearCombination, - }, + evm_circuit::util::{ + and, constraint_builder::BaseConstraintBuilder, not, or, select, RandomLinearCombination, }, + table::{BytecodeFieldTag, BytecodeTable, DynamicTableColumns, KeccakTable}, util::Expr, }; use bus_mapping::evm::OpcodeId; -use eth_types::Field; -use gadgets::{ - evm_word::encode, - is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, -}; +use eth_types::{Field, ToLittleEndian, Word}; +use gadgets::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; use halo2_proofs::{ circuit::{Layouter, Region}, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector, VirtualCells}, poly::Rotation, }; use keccak256::plain::Keccak; -use std::{convert::TryInto, vec}; +use std::vec; -use super::param::{KECCAK_WIDTH, PUSH_TABLE_WIDTH}; +use super::param::PUSH_TABLE_WIDTH; /// Public data for the bytecode #[derive(Clone, Debug, PartialEq)] pub(crate) struct BytecodeRow { - hash: F, + code_hash: F, tag: F, index: F, is_code: F, @@ -36,26 +30,22 @@ pub(crate) struct BytecodeRow { /// Unrolled bytecode #[derive(Clone, Debug, PartialEq)] -pub(crate) struct UnrolledBytecode { +pub struct UnrolledBytecode { bytes: Vec, rows: Vec>, } #[derive(Clone, Debug)] pub struct Config { - r: F, + randomness: Expression, minimum_rows: usize, q_enable: Column, q_first: Column, q_last: Selector, - hash: Column, - tag: Column, - index: Column, - is_code: Column, - value: Column, + bytecode_table: BytecodeTable, push_rindex: Column, - hash_rlc: Column, - hash_length: Column, + hash_input_rlc: Column, + code_length: Column, byte_push_size: Column, is_final: Column, padding: Column, @@ -64,29 +54,29 @@ pub struct Config { length_inv: Column, length_is_zero: IsZeroConfig, push_table: [Column; PUSH_TABLE_WIDTH], - keccak_table: [Column; KECCAK_WIDTH], + keccak_table: KeccakTable, } impl Config { - pub(crate) fn configure(meta: &mut ConstraintSystem, r: F) -> Self { + pub(crate) fn configure( + meta: &mut ConstraintSystem, + randomness: Expression, + bytecode_table: BytecodeTable, + keccak_table: KeccakTable, + ) -> Self { let q_enable = meta.fixed_column(); let q_first = meta.fixed_column(); let q_last = meta.selector(); - let hash = meta.advice_column(); - let tag = meta.advice_column(); - let index = meta.advice_column(); - let is_code = meta.advice_column(); - let value = meta.advice_column(); + let value = bytecode_table.value; let push_rindex = meta.advice_column(); - let hash_rlc = meta.advice_column(); - let hash_length = meta.advice_column(); + let hash_input_rlc = meta.advice_column(); + 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()); - let keccak_table = array_init::array_init(|_| meta.advice_column()); // A byte is an opcode when `push_rindex == 0` on the previous row, // else it's push data. @@ -106,7 +96,7 @@ impl Config { let is_row_tag_length = |meta: &mut VirtualCells| { and::expr(vec![ not::expr(meta.query_advice(padding, Rotation::cur())), - not::expr(meta.query_advice(tag, Rotation::cur())), + not::expr(meta.query_advice(bytecode_table.tag, Rotation::cur())), ]) }; @@ -114,7 +104,7 @@ impl Config { let is_row_tag_byte = |meta: &mut VirtualCells| { and::expr(vec![ not::expr(meta.query_advice(padding, Rotation::cur())), - meta.query_advice(tag, Rotation::cur()), + meta.query_advice(bytecode_table.tag, Rotation::cur()), ]) }; @@ -144,22 +134,22 @@ impl Config { 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(tag, 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(index, Rotation::cur()), + meta.query_advice(bytecode_table.index, Rotation::cur()), select::expr( is_prev_row_tag_length(meta), 0.expr(), - meta.query_advice(index, Rotation::prev()) + 1.expr(), + meta.query_advice(bytecode_table.index, Rotation::prev()) + 1.expr(), ), ); cb.require_equal( "is_code := push_rindex_prev == 0", - meta.query_advice(is_code, Rotation::cur()), + meta.query_advice(bytecode_table.is_code, Rotation::cur()), select::expr( is_prev_row_tag_length(meta), 1.expr(), @@ -167,20 +157,20 @@ impl Config { ), ); cb.require_equal( - "hash_rlc := hash_rlc_prev * r + byte", - meta.query_advice(hash_rlc, Rotation::cur()), - meta.query_advice(hash_rlc, Rotation::prev()) * r + "hash_input_rlc := hash_input_rlc_prev * randomness + byte", + meta.query_advice(hash_input_rlc, Rotation::cur()), + meta.query_advice(hash_input_rlc, Rotation::prev()) * randomness.clone() + meta.query_advice(value, Rotation::cur()), ); cb.require_equal( - "hash needs to remain the same", - meta.query_advice(hash, Rotation::cur()), - meta.query_advice(hash, Rotation::prev()), + "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( - "hash_length needs to remain the same", - meta.query_advice(hash_length, Rotation::cur()), - meta.query_advice(hash_length, Rotation::prev()), + "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", @@ -200,7 +190,7 @@ impl Config { 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(tag, Rotation::next()), + meta.query_advice(bytecode_table.tag, Rotation::next()), select::expr( length_is_zero.clone().is_zero_expression, select::expr( @@ -212,17 +202,20 @@ impl Config { ), ); cb.require_equal( - "if row.tag == tag.Length: value == row.hash_length", + "if row.tag == tag.Length: value == row.code_length", meta.query_advice(value, Rotation::cur()), - meta.query_advice(hash_length, Rotation::cur()), + meta.query_advice(code_length, Rotation::cur()), ); - cb.condition(length_is_zero.clone().is_zero_expression, |cb| { - cb.require_equal( - "if length == 0: hash == RLC(EMPTY_HASH, randomness)", - meta.query_advice(hash, Rotation::cur()), - Expression::Constant(keccak(&[], r)), - ); - }); + // 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 @@ -254,9 +247,9 @@ impl Config { 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 hash_length", - meta.query_advice(index, Rotation::cur()) + 1.expr(), - meta.query_advice(hash_length, Rotation::cur()), + "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: @@ -285,7 +278,7 @@ impl Config { "push_rindex := is_code ? byte_push_size : push_rindex_prev - 1", meta.query_advice(push_rindex, Rotation::cur()), select::expr( - meta.query_advice(is_code, Rotation::cur()), + 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(), ), @@ -310,7 +303,7 @@ impl Config { ])) }); - // The hash is checked on the latest row because only then have + // 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. @@ -354,31 +347,30 @@ impl Config { meta.query_advice(is_final, Rotation::cur()), not::expr(meta.query_advice(padding, Rotation::cur())), ]); - let lookup_columns = vec![hash_rlc, hash_length, hash]; - let mut constraints = vec![]; - for i in 0..KECCAK_WIDTH { + 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(keccak_table[i], Rotation::cur()), + meta.query_advice(*column, Rotation::cur()), )) } constraints }); Config { - r, + randomness, minimum_rows: meta.minimum_rows(), q_enable, q_first, q_last, - hash, - tag, - index, - is_code, - value, + bytecode_table, push_rindex, - hash_rlc, - hash_length, + hash_input_rlc, + code_length, byte_push_size, is_final, padding, @@ -393,9 +385,10 @@ impl Config { pub(crate) fn assign( &self, - mut layouter: impl Layouter, + layouter: &mut impl Layouter, size: usize, witness: &[UnrolledBytecode], + randomness: F, ) -> 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()); @@ -408,13 +401,12 @@ impl Config { |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_rlc = F::zero(); - let hash_length = F::from(bytecode.bytes.len() as u64); + let mut hash_input_rlc = F::zero(); + let code_length = F::from(bytecode.bytes.len() as u64); for (idx, row) in bytecode.rows.iter().enumerate() { // Track which byte is an opcode and which is push // data @@ -426,7 +418,7 @@ impl Config { } else { push_rindex - 1 }; - hash_rlc = hash_rlc * self.r + row.value; + hash_input_rlc = hash_input_rlc * randomness + row.value; } // Set the data for this row @@ -438,14 +430,14 @@ impl Config { offset, true, offset == last_row_offset, - row.hash, + row.code_hash, row.tag, row.index, row.is_code, row.value, push_rindex, - hash_rlc, - hash_length, + hash_input_rlc, + code_length, F::from(byte_push_size as u64), idx == bytecode.bytes.len(), false, @@ -495,14 +487,14 @@ impl Config { offset: usize, enable: bool, last: bool, - hash: F, + code_hash: F, tag: F, index: F, is_code: F, value: F, push_rindex: u64, - hash_rlc: F, - hash_length: F, + hash_input_rlc: F, + code_length: F, byte_push_size: F, is_final: bool, padding: bool, @@ -531,14 +523,14 @@ impl Config { // Advices for (name, column, value) in &[ - ("hash", self.hash, hash), - ("tag", self.tag, tag), - ("index", self.index, index), - ("is_code", self.is_code, is_code), - ("value", self.value, value), + ("code_hash", self.bytecode_table.code_hash, code_hash), + ("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)), - ("hash_rlc", self.hash_rlc, hash_rlc), - ("hash_length", self.hash_length, hash_length), + ("hash_input_rlc", self.hash_input_rlc, hash_input_rlc), + ("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)), @@ -555,20 +547,17 @@ impl Config { push_rindex_is_zero_chip.assign(region, offset, Some(push_rindex_prev))?; // length_is_zero chip - length_is_zero_chip.assign(region, offset, Some(hash_length))?; + length_is_zero_chip.assign(region, offset, Some(code_length))?; Ok(()) } - pub(crate) fn load( - &self, - layouter: &mut impl Layouter, - bytecodes: &[UnrolledBytecode], - ) -> Result<(), Error> { + /// load fixed tables + pub(crate) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { // push table: BYTE -> NUM_PUSHED: - // [0, OpcodeId::PUSH1[ -> 0 + // [0, OpcodeId::PUSH1] -> 0 // [OpcodeId::PUSH1, OpcodeId::PUSH32] -> [1..32] - // ]OpcodeId::PUSH32, 256[ -> 0 + // [OpcodeId::PUSH32, 256] -> 0 layouter.assign_region( || "push table", |mut region| { @@ -590,38 +579,14 @@ impl Config { }, )?; - // keccak table - layouter.assign_region( - || "keccak table", - |mut region| { - for (offset, bytecode) in bytecodes.iter().map(|v| v.bytes.clone()).enumerate() { - let hash: F = keccak(&bytecode[..], self.r); - let rlc: F = linear_combine(bytecode.clone(), self.r); - let size = F::from(bytecode.len() as u64); - for (name, column, value) in &[ - ("rlc", self.keccak_table[0], rlc), - ("size", self.keccak_table[1], size), - ("hash", self.keccak_table[2], hash), - ] { - region.assign_advice( - || format!("Keccak table assign {} {}", name, offset), - *column, - offset, - || Ok(*value), - )?; - } - } - Ok(()) - }, - )?; Ok(()) } } -fn unroll(bytes: Vec, r: F) -> UnrolledBytecode { - let hash = keccak(&bytes[..], r); +pub fn unroll(bytes: Vec, randomness: F) -> UnrolledBytecode { + let code_hash = keccak(&bytes[..], randomness); let mut rows = vec![BytecodeRow:: { - hash, + code_hash, tag: F::from(BytecodeFieldTag::Length as u64), index: F::zero(), is_code: F::zero(), @@ -639,7 +604,7 @@ fn unroll(bytes: Vec, r: F) -> UnrolledBytecode { }; rows.push(BytecodeRow:: { - hash, + code_hash, tag: F::from(BytecodeFieldTag::Byte as u64), index: F::from(index as u64), is_code: F::from(is_code as u64), @@ -661,10 +626,13 @@ fn get_push_size(byte: u8) -> u64 { } } -fn keccak(msg: &[u8], r: F) -> F { +fn keccak(msg: &[u8], randomness: F) -> F { let mut keccak = Keccak::default(); keccak.update(msg); - RandomLinearCombination::::random_linear_combine(keccak.digest().try_into().unwrap(), r) + RandomLinearCombination::::random_linear_combine( + Word::from_big_endian(keccak.digest().as_slice()).to_le_bytes(), + randomness, + ) } fn into_words(message: &[u8]) -> Vec { @@ -680,13 +648,10 @@ fn into_words(message: &[u8]) -> Vec { words } -fn linear_combine(bytes: Vec, r: F) -> F { - encode(bytes.into_iter(), r) -} - #[cfg(test)] mod tests { use super::*; + use crate::util::power_of_randomness_from_instance; use eth_types::{Bytecode, Word}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, @@ -699,12 +664,11 @@ mod tests { struct MyCircuit { bytecodes: Vec>, size: usize, + randomness: F, } - impl MyCircuit { - fn r() -> F { - F::from(123456) - } + fn get_randomness() -> F { + F::from(123456) } impl Circuit for MyCircuit { @@ -716,7 +680,12 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - Config::configure(meta, MyCircuit::r()) + let bytecode_table = BytecodeTable::construct(meta); + + let randomness = power_of_randomness_from_instance::<_, 1>(meta); + let keccak_table = KeccakTable::construct(meta); + + Config::configure(meta, randomness[0].clone(), bytecode_table, keccak_table) } fn synthesize( @@ -724,19 +693,28 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - config.load(&mut layouter, &self.bytecodes)?; - config.assign(layouter, self.size, &self.bytecodes)?; + config.load(&mut layouter)?; + config.keccak_table.load( + &mut layouter, + self.bytecodes.iter().map(|b| b.bytes.as_slice()), + self.randomness, + )?; + config.assign(&mut layouter, self.size, &self.bytecodes, self.randomness)?; Ok(()) } } - fn verify(k: u32, bytecodes: Vec>, success: bool) { + fn verify(k: u32, bytecodes: Vec>, randomness: F, success: bool) { let circuit = MyCircuit:: { bytecodes, size: 2usize.pow(k), + randomness, }; - let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); + let num_rows = 1 << k; + const NUM_BLINDING_ROWS: usize = 7 - 1; + let instance = vec![vec![randomness; num_rows - NUM_BLINDING_ROWS]]; + let prover = MockProver::::run(k, &circuit, instance).unwrap(); let result = prover.verify(); if let Err(failures) = &result { for failure in failures.iter() { @@ -750,7 +728,7 @@ mod tests { #[test] fn bytecode_unrolling() { let k = 10; - let r = MyCircuit::r(); + let randomness = get_randomness(); let mut rows = vec![]; let mut bytecode = Bytecode::default(); // First add all non-push bytes, which should all be seen as code @@ -758,7 +736,7 @@ mod tests { if !is_push(byte) { bytecode.write(byte, true); rows.push(BytecodeRow { - hash: Fr::zero(), + code_hash: Fr::zero(), tag: Fr::from(BytecodeFieldTag::Byte as u64), index: Fr::from(rows.len() as u64), is_code: Fr::from(true as u64), @@ -771,7 +749,7 @@ mod tests { let data_byte = OpcodeId::PUSH32.as_u8(); bytecode.push(n, Word::from_little_endian(&vec![data_byte; n][..])); rows.push(BytecodeRow { - hash: Fr::zero(), + code_hash: Fr::zero(), tag: Fr::from(BytecodeFieldTag::Byte as u64), index: Fr::from(rows.len() as u64), is_code: Fr::from(true as u64), @@ -779,7 +757,7 @@ mod tests { }); for _ in 0..n { rows.push(BytecodeRow { - hash: Fr::zero(), + code_hash: Fr::zero(), tag: Fr::from(BytecodeFieldTag::Byte as u64), index: Fr::from(rows.len() as u64), is_code: Fr::from(false as u64), @@ -787,15 +765,15 @@ mod tests { }); } } - // Set the hash of the complete bytecode in the rows - let hash = keccak(&bytecode.to_vec()[..], r); + // Set the code_hash of the complete bytecode in the rows + let code_hash = keccak(&bytecode.to_vec()[..], randomness); for row in rows.iter_mut() { - row.hash = hash; + row.code_hash = code_hash; } rows.insert( 0, BytecodeRow { - hash, + code_hash, tag: Fr::from(BytecodeFieldTag::Length as u64), index: Fr::zero(), is_code: Fr::zero(), @@ -803,7 +781,7 @@ mod tests { }, ); // Unroll the bytecode - let unrolled = unroll(bytecode.to_vec(), r); + let unrolled = unroll(bytecode.to_vec(), randomness); // Check if the bytecode was unrolled correctly assert_eq!( UnrolledBytecode { @@ -813,97 +791,114 @@ mod tests { unrolled, ); // Verify the unrolling in the circuit - verify::(k, vec![unrolled], true); + verify::(k, vec![unrolled], randomness, true); } /// Tests a fully empty circuit #[test] fn bytecode_empty() { let k = 9; - let r = MyCircuit::r(); - verify::(k, vec![unroll(vec![], r)], true); + let randomness = get_randomness(); + verify::(k, vec![unroll(vec![], randomness)], randomness, true); } #[test] fn bytecode_simple() { let k = 9; - let r = MyCircuit::r(); + let randomness = get_randomness(); let bytecodes = vec![ - unroll(vec![7u8], r), - unroll(vec![6u8], r), - unroll(vec![5u8], r), + unroll(vec![7u8], randomness), + unroll(vec![6u8], randomness), + unroll(vec![5u8], randomness), ]; - verify::(k, bytecodes, true); + verify::(k, bytecodes, randomness, true); } /// Tests a fully full circuit #[test] fn bytecode_full() { let k = 9; - let r = MyCircuit::r(); - verify::(k, vec![unroll(vec![7u8; 2usize.pow(k) - 7], r)], true); + let randomness = get_randomness(); + verify::( + k, + vec![unroll(vec![7u8; 2usize.pow(k) - 7], randomness)], + randomness, + true, + ); } /// Tests a circuit with incomplete bytecode #[test] fn bytecode_incomplete() { let k = 9; - let r = MyCircuit::r(); - verify::(k, vec![unroll(vec![7u8; 2usize.pow(k) + 1], r)], false); + let randomness = get_randomness(); + verify::( + k, + vec![unroll(vec![7u8; 2usize.pow(k) + 1], randomness)], + randomness, + false, + ); } /// Tests multiple bytecodes in a single circuit #[test] fn bytecode_push() { let k = 9; - let r = MyCircuit::r(); + let randomness = get_randomness(); verify::( k, vec![ - unroll(vec![], r), - unroll(vec![OpcodeId::PUSH32.as_u8()], r), - unroll(vec![OpcodeId::PUSH32.as_u8(), OpcodeId::ADD.as_u8()], r), - unroll(vec![OpcodeId::ADD.as_u8(), OpcodeId::PUSH32.as_u8()], r), + unroll(vec![], randomness), + unroll(vec![OpcodeId::PUSH32.as_u8()], randomness), + unroll( + vec![OpcodeId::PUSH32.as_u8(), OpcodeId::ADD.as_u8()], + randomness, + ), + unroll( + vec![OpcodeId::ADD.as_u8(), OpcodeId::PUSH32.as_u8()], + randomness, + ), unroll( vec![ OpcodeId::ADD.as_u8(), OpcodeId::PUSH32.as_u8(), OpcodeId::ADD.as_u8(), ], - r, + randomness, ), ], + randomness, true, ); } - /// Test invalid hash data + /// Test invalid code_hash data #[test] fn bytecode_invalid_hash_data() { let k = 9; - let r = MyCircuit::r(); + let randomness = get_randomness(); let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode, r); - verify::(k, vec![unrolled.clone()], true); - // Change the hash on the first position + let unrolled = unroll(bytecode, randomness); + verify::(k, vec![unrolled.clone()], randomness, true); + // Change the code_hash on the first position { let mut invalid = unrolled.clone(); - invalid.rows[0].hash += Fr::from(1u64); - verify::(k, vec![invalid], false); + invalid.rows[0].code_hash += Fr::from(1u64); + verify::(k, vec![invalid], randomness, false); } - // Change the hash on another position + // Change the code_hash on another position { let mut invalid = unrolled.clone(); - invalid.rows[4].hash += Fr::from(1u64); - verify::(k, vec![invalid], false); + invalid.rows[4].code_hash += Fr::from(1u64); + verify::(k, vec![invalid], randomness, false); } - // Change all the hashes so it doesn't match the keccak lookup hash + // 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.hash = Fr::one(); + row.code_hash = Fr::one(); } - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } } @@ -912,23 +907,23 @@ mod tests { #[ignore] fn bytecode_invalid_index() { let k = 9; - let r = MyCircuit::r(); + let randomness = get_randomness(); let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode, r); - verify::(k, vec![unrolled.clone()], true); + let unrolled = unroll(bytecode, randomness); + verify::(k, vec![unrolled.clone()], randomness, true); // Start the index at 1 { let mut invalid = unrolled.clone(); for row in invalid.rows.iter_mut() { row.index += Fr::one(); } - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } // Don't increment an index once { let mut invalid = unrolled; invalid.rows.last_mut().unwrap().index -= Fr::one(); - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } } @@ -936,27 +931,27 @@ mod tests { #[test] fn bytecode_invalid_byte_data() { let k = 9; - let r = MyCircuit::r(); + let randomness = get_randomness(); let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode, r); - verify::(k, vec![unrolled.clone()], true); + let unrolled = unroll(bytecode, randomness); + verify::(k, vec![unrolled.clone()], randomness, true); // Change the first byte { let mut invalid = unrolled.clone(); invalid.rows[1].value = Fr::from(9u64); - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } // Change a byte on another position { let mut invalid = unrolled.clone(); invalid.rows[5].value = Fr::from(6u64); - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } // Set a byte value out of range { let mut invalid = unrolled; invalid.rows[3].value = Fr::from(256u64); - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } } @@ -964,7 +959,7 @@ mod tests { #[test] fn bytecode_invalid_is_code() { let k = 9; - let r = MyCircuit::r(); + let randomness = get_randomness(); let bytecode = vec![ OpcodeId::ADD.as_u8(), OpcodeId::PUSH1.as_u8(), @@ -974,25 +969,25 @@ mod tests { OpcodeId::ADD.as_u8(), OpcodeId::PUSH6.as_u8(), ]; - let unrolled = unroll(bytecode, r); - verify::(k, vec![unrolled.clone()], true); + let unrolled = unroll(bytecode, randomness); + verify::(k, vec![unrolled.clone()], randomness, 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(); - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } // Mark the 4rd byte as data (is code) { let mut invalid = unrolled.clone(); invalid.rows[4].is_code = Fr::zero(); - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } // Mark the 7th byte as code (is data for the PUSH7) { let mut invalid = unrolled; invalid.rows[7].is_code = Fr::one(); - verify::(k, vec![invalid], false); + verify::(k, vec![invalid], randomness, false); } } } diff --git a/zkevm-circuits/src/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs index ffa10bb130..97c57cb93b 100644 --- a/zkevm-circuits/src/copy_circuit.rs +++ b/zkevm-circuits/src/copy_circuit.rs @@ -5,22 +5,44 @@ use bus_mapping::circuit_input_builder::{CopyDataType, CopyEvent, CopyStep, NumberOrHash}; use eth_types::{Field, ToAddress, ToScalar, U256}; use gadgets::{ - binary_number::{BinaryNumberChip, BinaryNumberConfig}, + binary_number::BinaryNumberChip, less_than::{LtChip, LtConfig, LtInstruction}, util::{and, not, or, Expr}, }; use halo2_proofs::{ circuit::{Layouter, Region}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector, VirtualCells}, + plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Selector}, poly::Rotation, }; -use crate::evm_circuit::{ - table::{BytecodeFieldTag, LookupTable, RwTableTag, TxContextFieldTag, TxLogFieldTag}, - util::{constraint_builder::BaseConstraintBuilder, RandomLinearCombination}, - witness::Block, +use crate::{ + evm_circuit::{ + util::{constraint_builder::BaseConstraintBuilder, RandomLinearCombination}, + witness::Block, + }, + table::{ + BytecodeFieldTag, CopyTable, LookupTable, RwTableTag, TxContextFieldTag, TxLogFieldTag, + }, }; +/// Encode the type `NumberOrHash` into a field element +pub fn number_or_hash_to_field(v: &NumberOrHash, randomness: F) -> F { + match v { + NumberOrHash::Number(n) => F::from(*n as u64), + NumberOrHash::Hash(h) => { + // since code hash in the bytecode table is represented in + // the little-endian form, we reverse the big-endian bytes + // of H256. + let le_bytes = { + let mut b = h.to_fixed_bytes(); + b.reverse(); + b + }; + RandomLinearCombination::random_linear_combine(le_bytes, randomness) + } + } +} + /// The rw table shared between evm circuit and state circuit #[derive(Clone, Copy, Debug)] pub struct CopyCircuit { @@ -29,22 +51,11 @@ pub struct CopyCircuit { /// Whether this row denotes a step. A read row is a step and a write row is /// not. pub q_step: Selector, - /// Whether the row is the first read-write pair for a copy event. - pub is_first: Column, /// Whether the row is the last read-write pair for a copy event. pub is_last: Column, - /// The relevant ID for the read-write row, represented as a random linear - /// combination. The ID may be one of the below: - /// 1. Call ID/Caller ID for CopyDataType::Memory - /// 2. RLC encoding of bytecode hash for CopyDataType::Bytecode - /// 3. Transaction ID for CopyDataType::TxCalldata, CopyDataType::TxLog - pub id: Column, - /// The source/destination address for this copy step. - pub addr: Column, - /// The end of the source buffer for the copy event. - pub src_addr_end: Column, - /// The number of bytes left to be copied. - pub bytes_left: Column, + /// The Copy Table contains the columns that are exposed via the lookup + /// expressions + pub copy_table: CopyTable, /// The value copied in this copy step. pub value: Column, /// In case of a bytecode tag, this denotes whether or not the copied byte @@ -52,38 +63,12 @@ pub struct CopyCircuit { pub is_code: Column, /// Whether the row is padding. pub is_pad: Column, - /// The associated read-write counter for this row. - pub rw_counter: Column, - /// Decrementing counter denoting reverse read-write counter. - pub rwc_inc_left: Column, - /// Binary chip to constrain the copy table conditionally depending on the - /// current row's tag, whether it is Bytecode, Memory, TxCalldata or - /// TxLog. - pub tag: BinaryNumberConfig, /// Lt chip to check: src_addr < src_addr_end. /// Since `src_addr` and `src_addr_end` are u64, 8 bytes are sufficient for /// the Lt chip. pub addr_lt_addr_end: LtConfig, } -impl LookupTable for CopyCircuit { - fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { - vec![ - meta.query_advice(self.is_first, Rotation::cur()), - meta.query_advice(self.id, Rotation::cur()), // src_id - self.tag.value(Rotation::cur())(meta), // src_tag - meta.query_advice(self.id, Rotation::next()), // dst_id - self.tag.value(Rotation::next())(meta), // dst_tag - meta.query_advice(self.addr, Rotation::cur()), // src_addr - meta.query_advice(self.src_addr_end, Rotation::cur()), // src_addr_end - meta.query_advice(self.addr, Rotation::next()), // dst_addr - meta.query_advice(self.bytes_left, Rotation::cur()), // length - meta.query_advice(self.rw_counter, Rotation::cur()), // rw_counter - meta.query_advice(self.rwc_inc_left, Rotation::cur()), // rwc_inc_left - ] - } -} - impl CopyCircuit { /// Configure the Copy Circuit constraining read-write steps and doing /// appropriate lookups to the Tx Table, RW Table and Bytecode Table. @@ -92,22 +77,22 @@ impl CopyCircuit { tx_table: &dyn LookupTable, rw_table: &dyn LookupTable, bytecode_table: &dyn LookupTable, + copy_table: CopyTable, + q_enable: Column, ) -> Self { - let q_enable = meta.fixed_column(); let q_step = meta.complex_selector(); - let is_first = meta.advice_column(); let is_last = meta.advice_column(); - let id = meta.advice_column(); - let addr = meta.advice_column(); - let src_addr_end = meta.advice_column(); - let bytes_left = meta.advice_column(); let value = meta.advice_column(); let is_code = meta.advice_column(); let is_pad = meta.advice_column(); - let rw_counter = meta.advice_column(); - let rwc_inc_left = meta.advice_column(); - - let tag = BinaryNumberChip::configure(meta, q_enable); + let is_first = copy_table.is_first; + let id = copy_table.id; + let addr = copy_table.addr; + let src_addr_end = copy_table.src_addr_end; + let bytes_left = copy_table.bytes_left; + let rw_counter = copy_table.rw_counter; + let rwc_inc_left = copy_table.rwc_inc_left; + let tag = copy_table.tag; let addr_lt_addr_end = LtChip::configure( meta, @@ -327,19 +312,12 @@ impl CopyCircuit { Self { q_enable, q_step, - is_first, is_last, - id, - addr, - src_addr_end, - bytes_left, value, is_code, is_pad, - rw_counter, - rwc_inc_left, - tag, addr_lt_addr_end, + copy_table, } } @@ -349,7 +327,7 @@ impl CopyCircuit { layouter: &mut impl Layouter, block: &Block, ) -> Result<(), Error> { - let tag_chip = BinaryNumberChip::construct(self.tag); + let tag_chip = BinaryNumberChip::construct(self.copy_table.tag); let lt_chip = LtChip::construct(self.addr_lt_addr_end); layouter.assign_region( @@ -410,7 +388,7 @@ impl CopyCircuit { // is_first region.assign_advice( || format!("assign is_first {}", offset), - self.is_first, + self.copy_table.is_first, offset, || Ok(if step_idx == 0 { F::one() } else { F::zero() }), )?; @@ -430,29 +408,14 @@ impl CopyCircuit { // id region.assign_advice( || format!("assign id {}", offset), - self.id, + self.copy_table.id, offset, - || { - Ok(match id { - NumberOrHash::Number(n) => F::from(*n as u64), - NumberOrHash::Hash(h) => { - // since code hash in the bytecode table is represented in - // the little-endian form, we reverse the big-endian bytes - // of H256. - let le_bytes = { - let mut b = h.to_fixed_bytes(); - b.reverse(); - b - }; - RandomLinearCombination::random_linear_combine(le_bytes, randomness) - } - }) - }, + || Ok(number_or_hash_to_field(id, randomness)), )?; // addr region.assign_advice( || format!("assign addr {}", offset), - self.addr, + self.copy_table.addr, offset, || { Ok(match copy_step.tag { @@ -491,14 +454,14 @@ impl CopyCircuit { // rw_counter region.assign_advice( || format!("assign rw_counter {}", offset), - self.rw_counter, + self.copy_table.rw_counter, offset, || Ok(F::from(copy_step.rwc.0 as u64)), )?; // rwc_inc_left region.assign_advice( || format!("assign rwc_inc_left {}", offset), - self.rwc_inc_left, + self.copy_table.rwc_inc_left, offset, || Ok(F::from(copy_step.rwc_inc_left)), )?; @@ -509,14 +472,14 @@ impl CopyCircuit { // src_addr_end region.assign_advice( || format!("assign src_addr_end {}", offset), - self.src_addr_end, + self.copy_table.src_addr_end, offset, || Ok(F::from(copy_event.src_addr_end)), )?; // bytes_left region.assign_advice( || format!("assign bytes_left {}", offset), - self.bytes_left, + self.copy_table.bytes_left, offset, || Ok(F::from(bytes_left)), )?; @@ -542,7 +505,7 @@ impl CopyCircuit { // is_first region.assign_advice( || format!("assign is_first {}", offset), - self.is_first, + self.copy_table.is_first, offset, || Ok(F::zero()), )?; @@ -556,28 +519,28 @@ impl CopyCircuit { // id region.assign_advice( || format!("assign id {}", offset), - self.id, + self.copy_table.id, offset, || Ok(F::zero()), )?; // addr region.assign_advice( || format!("assign addr {}", offset), - self.addr, + self.copy_table.addr, offset, || Ok(F::zero()), )?; // src_addr_end region.assign_advice( || format!("assign src_addr_end {}", offset), - self.src_addr_end, + self.copy_table.src_addr_end, offset, || Ok(F::zero()), )?; // bytes_left region.assign_advice( || format!("assign bytes_left {}", offset), - self.bytes_left, + self.copy_table.bytes_left, offset, || Ok(F::zero()), )?; @@ -605,14 +568,14 @@ impl CopyCircuit { // rw_counter region.assign_advice( || format!("assign rw_counter {}", offset), - self.rw_counter, + self.copy_table.rw_counter, offset, || Ok(F::zero()), )?; // rwc_inc_left region.assign_advice( || format!("assign rwc_inc_left {}", offset), - self.rwc_inc_left, + self.copy_table.rwc_inc_left, offset, || Ok(F::zero()), )?; @@ -624,6 +587,7 @@ impl CopyCircuit { #[cfg(test)] mod tests { + use super::*; use bus_mapping::{ circuit_input_builder::{CircuitInputBuilder, CopyDataType}, mock::BlockData, @@ -633,143 +597,24 @@ mod tests { use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, dev::{MockProver, VerifyFailure}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, + plonk::{Circuit, ConstraintSystem}, }; - use itertools::Itertools; use mock::TestContext; use rand::{prelude::SliceRandom, Rng}; use crate::{ - evm_circuit::witness::{block_convert, Block, Bytecode, RwMap, Transaction}, - rw_table::RwTable, + evm_circuit::witness::{block_convert, Block}, + table::{BytecodeTable, RwTable, TxTable}, }; - use super::CopyCircuit; - #[derive(Clone)] struct MyConfig { - tx_table: [Column; 4], + tx_table: TxTable, rw_table: RwTable, - bytecode_table: [Column; 5], + bytecode_table: BytecodeTable, copy_table: CopyCircuit, } - impl MyConfig { - fn load_txs( - &self, - layouter: &mut impl Layouter, - txs: &[Transaction], - randomness: F, - ) -> Result<(), Error> { - layouter.assign_region( - || "tx table", - |mut region| { - let mut offset = 0; - for column in self.tx_table { - region.assign_advice( - || "tx table all-zero row", - column, - offset, - || Ok(F::zero()), - )?; - } - offset += 1; - - for tx in txs.iter() { - for row in tx.table_assignments(randomness) { - for (column, value) in self.tx_table.iter().zip_eq(row) { - region.assign_advice( - || format!("tx table row {}", offset), - *column, - offset, - || Ok(value), - )?; - } - offset += 1; - } - } - Ok(()) - }, - ) - } - - fn load_rws( - &self, - layouter: &mut impl Layouter, - rws: &RwMap, - randomness: F, - ) -> Result<(), Error> { - layouter.assign_region( - || "rw table", - |mut region| { - let mut offset = 0; - self.rw_table - .assign(&mut region, offset, &Default::default())?; - offset += 1; - - let mut rows = rws - .0 - .values() - .flat_map(|rws| rws.iter()) - .collect::>(); - - rows.sort_by_key(|a| a.rw_counter()); - let mut expected_rw_counter = 1; - for rw in rows { - assert!(rw.rw_counter() == expected_rw_counter); - expected_rw_counter += 1; - - self.rw_table.assign( - &mut region, - offset, - &rw.table_assignment(randomness), - )?; - offset += 1; - } - Ok(()) - }, - ) - } - - fn load_bytecodes<'a>( - &self, - layouter: &mut impl Layouter, - bytecodes: impl IntoIterator + Clone, - randomness: F, - ) -> Result<(), Error> { - layouter.assign_region( - || "bytecode table", - |mut region| { - let mut offset = 0; - for column in self.bytecode_table { - region.assign_advice( - || "bytecode table all-zero row", - column, - offset, - || Ok(F::zero()), - )?; - } - offset += 1; - - for bytecode in bytecodes.clone() { - for row in bytecode.table_assignments(randomness) { - for (column, value) in self.bytecode_table.iter().zip_eq(row) { - region.assign_advice( - || format!("bytecode table row {}", offset), - *column, - offset, - || Ok(value), - )?; - } - offset += 1; - } - } - Ok(()) - }, - ) - } - } - #[derive(Default)] struct MyCircuit { block: Block, @@ -790,10 +635,19 @@ mod tests { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let tx_table = [(); 4].map(|_| meta.advice_column()); + let tx_table = TxTable::construct(meta); let rw_table = RwTable::construct(meta); - let bytecode_table = [(); 5].map(|_| meta.advice_column()); - let copy_table = CopyCircuit::configure(meta, &tx_table, &rw_table, &bytecode_table); + let bytecode_table = BytecodeTable::construct(meta); + let q_enable = meta.fixed_column(); + let copy_table = CopyTable::construct(meta, q_enable); + let copy_table = CopyCircuit::configure( + meta, + &tx_table, + &rw_table, + &bytecode_table, + copy_table, + q_enable, + ); MyConfig { tx_table, @@ -808,9 +662,13 @@ mod tests { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), halo2_proofs::plonk::Error> { - config.load_txs(&mut layouter, &self.block.txs, self.block.randomness)?; - config.load_rws(&mut layouter, &self.block.rws, self.block.randomness)?; - config.load_bytecodes( + config + .tx_table + .load(&mut layouter, &self.block.txs, self.block.randomness)?; + config + .rw_table + .load(&mut layouter, &self.block.rws, self.block.randomness)?; + config.bytecode_table.load( &mut layouter, self.block.bytecodes.values(), self.block.randomness, diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 12a1df79b1..e955198a6e 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -11,10 +11,11 @@ pub(crate) mod util; pub mod table; pub mod witness; +use crate::table::LookupTable; use eth_types::Field; use execution::ExecutionConfig; use itertools::Itertools; -use table::{FixedTableTag, LookupTable}; +use table::FixedTableTag; use witness::Block; /// EvmCircuit implements verification of execution trace of a block. @@ -145,23 +146,16 @@ impl EvmCircuit { #[cfg(any(feature = "test", test))] pub mod test { use crate::{ - copy_circuit::CopyCircuit, - evm_circuit::{ - table::FixedTableTag, - witness::{Block, BlockContext, Bytecode, RwMap, Transaction}, - EvmCircuit, - }, - rw_table::RwTable, - util::Expr, + evm_circuit::{table::FixedTableTag, witness::Block, EvmCircuit}, + table::{BlockTable, BytecodeTable, CopyTable, RwTable, TxTable}, + util::power_of_randomness_from_instance, }; use eth_types::{Field, Word}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, dev::{MockProver, VerifyFailure}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, - poly::Rotation, + plonk::{Circuit, ConstraintSystem, Error}, }; - use itertools::Itertools; use rand::{ distributions::uniform::{SampleRange, SampleUniform}, random, thread_rng, Rng, @@ -190,167 +184,14 @@ pub mod test { #[derive(Clone)] pub struct TestCircuitConfig { - tx_table: [Column; 4], + tx_table: TxTable, rw_table: RwTable, - bytecode_table: [Column; 5], - block_table: [Column; 3], - copy_table: CopyCircuit, + bytecode_table: BytecodeTable, + block_table: BlockTable, + copy_table: CopyTable, pub evm_circuit: EvmCircuit, } - impl TestCircuitConfig { - fn load_txs( - &self, - layouter: &mut impl Layouter, - txs: &[Transaction], - randomness: F, - ) -> Result<(), Error> { - layouter.assign_region( - || "tx table", - |mut region| { - let mut offset = 0; - for column in self.tx_table { - region.assign_advice( - || "tx table all-zero row", - column, - offset, - || Ok(F::zero()), - )?; - } - offset += 1; - - for tx in txs.iter() { - for row in tx.table_assignments(randomness) { - for (column, value) in self.tx_table.iter().zip_eq(row) { - region.assign_advice( - || format!("tx table row {}", offset), - *column, - offset, - || Ok(value), - )?; - } - offset += 1; - } - } - Ok(()) - }, - ) - } - - fn load_rws( - &self, - layouter: &mut impl Layouter, - rws: &RwMap, - randomness: F, - ) -> Result<(), Error> { - layouter.assign_region( - || "rw table", - |mut region| { - let mut offset = 0; - self.rw_table - .assign(&mut region, offset, &Default::default())?; - offset += 1; - - let mut rows = rws - .0 - .values() - .flat_map(|rws| rws.iter()) - .collect::>(); - - rows.sort_by_key(|a| a.rw_counter()); - let mut expected_rw_counter = 1; - for rw in rows { - assert!(rw.rw_counter() == expected_rw_counter); - expected_rw_counter += 1; - - self.rw_table.assign( - &mut region, - offset, - &rw.table_assignment(randomness), - )?; - offset += 1; - } - Ok(()) - }, - ) - } - - fn load_bytecodes<'a>( - &self, - layouter: &mut impl Layouter, - bytecodes: impl IntoIterator + Clone, - randomness: F, - ) -> Result<(), Error> { - layouter.assign_region( - || "bytecode table", - |mut region| { - let mut offset = 0; - for column in self.bytecode_table { - region.assign_advice( - || "bytecode table all-zero row", - column, - offset, - || Ok(F::zero()), - )?; - } - offset += 1; - - for bytecode in bytecodes.clone() { - for row in bytecode.table_assignments(randomness) { - for (column, value) in self.bytecode_table.iter().zip_eq(row) { - region.assign_advice( - || format!("bytecode table row {}", offset), - *column, - offset, - || Ok(value), - )?; - } - offset += 1; - } - } - Ok(()) - }, - ) - } - - fn load_block( - &self, - layouter: &mut impl Layouter, - block: &BlockContext, - randomness: F, - ) -> Result<(), Error> { - layouter.assign_region( - || "block table", - |mut region| { - let mut offset = 0; - for column in self.block_table { - region.assign_advice( - || "block table all-zero row", - column, - offset, - || Ok(F::zero()), - )?; - } - offset += 1; - - for row in block.table_assignments(randomness) { - for (column, value) in self.block_table.iter().zip_eq(row) { - region.assign_advice( - || format!("block table row {}", offset), - *column, - offset, - || Ok(value), - )?; - } - offset += 1; - } - - Ok(()) - }, - ) - } - } - #[derive(Default)] pub struct TestCircuit { block: Block, @@ -375,29 +216,23 @@ pub mod test { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let tx_table = [(); 4].map(|_| meta.advice_column()); + let tx_table = TxTable::construct(meta); let rw_table = RwTable::construct(meta); - let bytecode_table = [(); 5].map(|_| meta.advice_column()); - let block_table = [(); 3].map(|_| meta.advice_column()); - let copy_table = CopyCircuit::configure(meta, &tx_table, &rw_table, &bytecode_table); - - // This gate is used just to get the array of expressions from the power of - // randomness instance column, so that later on we don't need to query - // columns everywhere, and can pass the power of randomness array - // expression everywhere. The gate itself doesn't add any constraints. - let power_of_randomness = { - let columns = [(); 31].map(|_| meta.instance_column()); - let mut power_of_randomness = None; - - meta.create_gate("", |meta| { - power_of_randomness = - Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); - - [0.expr()] - }); - - power_of_randomness.unwrap() - }; + let bytecode_table = BytecodeTable::construct(meta); + let block_table = BlockTable::construct(meta); + let q_copy_table = meta.fixed_column(); + let copy_table = CopyTable::construct(meta, q_copy_table); + + let power_of_randomness = power_of_randomness_from_instance(meta); + let evm_circuit = EvmCircuit::configure( + meta, + power_of_randomness, + &tx_table, + &rw_table, + &bytecode_table, + &block_table, + ©_table, + ); Self::Config { tx_table, @@ -405,15 +240,7 @@ pub mod test { bytecode_table, block_table, copy_table, - evm_circuit: EvmCircuit::configure( - meta, - power_of_randomness, - &tx_table, - &rw_table, - &bytecode_table, - &block_table, - ©_table, - ), + evm_circuit, } } @@ -426,15 +253,23 @@ pub mod test { .evm_circuit .load_fixed_table(&mut layouter, self.fixed_table_tags.clone())?; config.evm_circuit.load_byte_table(&mut layouter)?; - config.load_txs(&mut layouter, &self.block.txs, self.block.randomness)?; - config.load_rws(&mut layouter, &self.block.rws, self.block.randomness)?; - config.load_bytecodes( + config + .tx_table + .load(&mut layouter, &self.block.txs, self.block.randomness)?; + config + .rw_table + .load(&mut layouter, &self.block.rws, self.block.randomness)?; + config.bytecode_table.load( &mut layouter, self.block.bytecodes.values(), self.block.randomness, )?; - config.load_block(&mut layouter, &self.block.context, self.block.randomness)?; - config.copy_table.assign_block(&mut layouter, &self.block)?; + config + .block_table + .load(&mut layouter, &self.block.context, self.block.randomness)?; + config + .copy_table + .load(&mut layouter, &self.block, self.block.randomness)?; config .evm_circuit .assign_block_exact(&mut layouter, &self.block) diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 9804a145ba..f6f2e1daf9 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -3,13 +3,14 @@ use crate::{ evm_circuit::{ param::{MAX_STEP_HEIGHT, STEP_WIDTH}, step::{ExecutionState, Step}, - table::{LookupTable, Table}, + table::Table, util::{ constraint_builder::{BaseConstraintBuilder, ConstraintBuilder}, rlc, CellType, }, witness::{Block, Call, ExecStep, Transaction}, }, + table::LookupTable, util::Expr, }; use eth_types::Field; diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index 7a3c655c41..51d2b9df8a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::N_BYTES_GAS, step::ExecutionState, - table::{AccountFieldTag, CallContextFieldTag, TxContextFieldTag}, util::{ common_gadget::TransferWithGasFeeGadget, constraint_builder::{ @@ -15,6 +14,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{AccountFieldTag, CallContextFieldTag, TxFieldTag as TxContextFieldTag}, util::Expr, }; use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar}; diff --git a/zkevm-circuits/src/evm_circuit/execution/block_ctx.rs b/zkevm-circuits/src/evm_circuit/execution/block_ctx.rs index 56115f070c..35791e8164 100644 --- a/zkevm-circuits/src/evm_circuit/execution/block_ctx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/block_ctx.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_U64, N_BYTES_WORD}, step::ExecutionState, - table::BlockContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -11,6 +10,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::BlockContextFieldTag, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/call.rs b/zkevm-circuits/src/evm_circuit/execution/call.rs index 69382d0c3f..0d70afd62c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/call.rs +++ b/zkevm-circuits/src/evm_circuit/execution/call.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_GAS, N_BYTES_MEMORY_WORD_SIZE}, step::ExecutionState, - table::{AccountFieldTag, CallContextFieldTag}, util::{ common_gadget::TransferGadget, constraint_builder::{ @@ -20,6 +19,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{AccountFieldTag, CallContextFieldTag}, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs index 2bad1c1bc7..cea102572d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::{N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE}, step::ExecutionState, - table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ @@ -16,6 +15,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::CallContextFieldTag, util::Expr, }; use bus_mapping::{circuit_input_builder::CopyDataType, evm::OpcodeId}; diff --git a/zkevm-circuits/src/evm_circuit/execution/calldataload.rs b/zkevm-circuits/src/evm_circuit/execution/calldataload.rs index a4742b4d67..f392c3f75f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldataload.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldataload.rs @@ -8,7 +8,6 @@ use crate::{ evm_circuit::{ param::{N_BYTES_MEMORY_ADDRESS, N_BYTES_WORD}, step::ExecutionState, - table::{CallContextFieldTag, TxContextFieldTag}, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -18,6 +17,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{CallContextFieldTag, TxContextFieldTag}, util::Expr, }; diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs index c8ef0af926..52345b9704 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::N_BYTES_CALLDATASIZE, step::ExecutionState, - table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -11,6 +10,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::CallContextFieldTag, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/caller.rs b/zkevm-circuits/src/evm_circuit/execution/caller.rs index 2bb0e22fa7..fc6577b62e 100644 --- a/zkevm-circuits/src/evm_circuit/execution/caller.rs +++ b/zkevm-circuits/src/evm_circuit/execution/caller.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::N_BYTES_ACCOUNT_ADDRESS, step::ExecutionState, - table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -11,6 +10,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::CallContextFieldTag, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/callvalue.rs b/zkevm-circuits/src/evm_circuit/execution/callvalue.rs index 989a158d09..47e2ed0153 100644 --- a/zkevm-circuits/src/evm_circuit/execution/callvalue.rs +++ b/zkevm-circuits/src/evm_circuit/execution/callvalue.rs @@ -2,7 +2,6 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -10,6 +9,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::CallContextFieldTag, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/chainid.rs b/zkevm-circuits/src/evm_circuit/execution/chainid.rs index 184fb4c843..a7fe6b0228 100644 --- a/zkevm-circuits/src/evm_circuit/execution/chainid.rs +++ b/zkevm-circuits/src/evm_circuit/execution/chainid.rs @@ -2,7 +2,6 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - table::BlockContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -10,6 +9,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::BlockContextFieldTag, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs index 7dab0555db..de430475dc 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs @@ -3,10 +3,6 @@ use crate::{ execution::ExecutionGadget, param::N_BYTES_GAS, step::ExecutionState, - table::{ - BlockContextFieldTag, CallContextFieldTag, RwTableTag, TxContextFieldTag, - TxReceiptFieldTag, - }, util::{ common_gadget::UpdateBalanceGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -18,6 +14,9 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{ + BlockContextFieldTag, CallContextFieldTag, RwTableTag, TxContextFieldTag, TxReceiptFieldTag, + }, util::Expr, }; use eth_types::{evm_types::MAX_REFUND_QUOTIENT_OF_GAS_USED, Field, ToScalar}; diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs index 64a712e749..2852f9788c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs +++ b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::N_BYTES_ACCOUNT_ADDRESS, step::ExecutionState, - table::{AccountFieldTag, CallContextFieldTag}, util::{ common_gadget::SameContextGadget, constraint_builder::{ @@ -15,6 +14,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{AccountFieldTag, CallContextFieldTag}, util::Expr, }; use eth_types::{evm_types::GasCost, Field, ToAddress, ToScalar, U256}; diff --git a/zkevm-circuits/src/evm_circuit/execution/gasprice.rs b/zkevm-circuits/src/evm_circuit/execution/gasprice.rs index 40afd712d3..0b5342d555 100644 --- a/zkevm-circuits/src/evm_circuit/execution/gasprice.rs +++ b/zkevm-circuits/src/evm_circuit/execution/gasprice.rs @@ -2,7 +2,6 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - table::{CallContextFieldTag, TxContextFieldTag}, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -10,6 +9,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{CallContextFieldTag, TxContextFieldTag}, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/logs.rs b/zkevm-circuits/src/evm_circuit/execution/logs.rs index a5b7bd961f..e1bc4ac7e3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/logs.rs +++ b/zkevm-circuits/src/evm_circuit/execution/logs.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::N_BYTES_MEMORY_WORD_SIZE, step::ExecutionState, - table::{CallContextFieldTag, RwTableTag, TxLogFieldTag}, util::{ common_gadget::SameContextGadget, constraint_builder::{ @@ -15,6 +14,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{CallContextFieldTag, RwTableTag, TxLogFieldTag}, util::Expr, }; use array_init::array_init; diff --git a/zkevm-circuits/src/evm_circuit/execution/origin.rs b/zkevm-circuits/src/evm_circuit/execution/origin.rs index 798fa1dbb4..e6a43cae63 100644 --- a/zkevm-circuits/src/evm_circuit/execution/origin.rs +++ b/zkevm-circuits/src/evm_circuit/execution/origin.rs @@ -3,7 +3,6 @@ use crate::{ execution::ExecutionGadget, param::N_BYTES_ACCOUNT_ADDRESS, step::ExecutionState, - table::{CallContextFieldTag, TxContextFieldTag}, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -11,6 +10,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{CallContextFieldTag, TxContextFieldTag}, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs b/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs index 42805a46f2..06b62a3b69 100644 --- a/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs @@ -2,7 +2,6 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - table::{AccountFieldTag, CallContextFieldTag}, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, @@ -10,6 +9,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::{AccountFieldTag, CallContextFieldTag}, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/execution/sload.rs b/zkevm-circuits/src/evm_circuit/execution/sload.rs index b39b998946..d17b29915c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sload.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sload.rs @@ -2,7 +2,6 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ @@ -12,6 +11,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::CallContextFieldTag, util::Expr, }; use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar}; diff --git a/zkevm-circuits/src/evm_circuit/execution/sstore.rs b/zkevm-circuits/src/evm_circuit/execution/sstore.rs index 599e90c871..81558315b1 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sstore.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sstore.rs @@ -2,7 +2,6 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ @@ -13,6 +12,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::CallContextFieldTag, util::Expr, }; diff --git a/zkevm-circuits/src/evm_circuit/execution/stop.rs b/zkevm-circuits/src/evm_circuit/execution/stop.rs index a76cd3c28a..14c2b88ed3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/stop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/stop.rs @@ -2,7 +2,6 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - table::CallContextFieldTag, util::{ common_gadget::RestoreContextGadget, constraint_builder::{ @@ -14,6 +13,7 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + table::CallContextFieldTag, util::Expr, }; use bus_mapping::evm::OpcodeId; diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 53838c08c3..0815cc466e 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -1,31 +1,10 @@ -use crate::{evm_circuit::step::ExecutionState, impl_expr}; -use halo2_proofs::{ - arithmetic::FieldExt, - plonk::{Advice, Column, Expression, Fixed, VirtualCells}, - poly::Rotation, -}; +use crate::evm_circuit::step::ExecutionState; +use crate::impl_expr; +pub use crate::table::TxContextFieldTag; +use eth_types::Field; +use halo2_proofs::plonk::Expression; use strum::IntoEnumIterator; -use strum_macros::{EnumCount, EnumIter}; - -pub trait LookupTable { - fn table_exprs(&self, meta: &mut VirtualCells) -> Vec>; -} - -impl LookupTable for [Column; W] { - fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { - self.iter() - .map(|column| meta.query_advice(*column, Rotation::cur())) - .collect() - } -} - -impl LookupTable for [Column; W] { - fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { - self.iter() - .map(|column| meta.query_fixed(*column, Rotation::cur())) - .collect() - } -} +use strum_macros::EnumIter; #[derive(Clone, Copy, Debug, EnumIter)] pub enum FixedTableTag { @@ -44,9 +23,10 @@ pub enum FixedTableTag { ResponsibleOpcode, Pow2, } +impl_expr!(FixedTableTag); impl FixedTableTag { - pub fn build(&self) -> Box> { + pub fn build(&self) -> Box> { let tag = F::from(*self as u64); match self { Self::Zero => Box::new((0..1).map(move |_| [tag, F::zero(), F::zero(), F::zero()])), @@ -115,138 +95,6 @@ impl FixedTableTag { } } -#[derive(Clone, Copy, Debug)] -pub enum TxContextFieldTag { - Nonce = 1, - Gas, - GasPrice, - CallerAddress, - CalleeAddress, - IsCreate, - Value, - CallDataLength, - CallDataGasCost, - CallData, -} - -// Keep the sequence consistent with OpcodeId for scalar -#[derive(Clone, Copy, Debug)] -pub enum BlockContextFieldTag { - Coinbase = 1, - Timestamp, - Number, - Difficulty, - GasLimit, - BaseFee = 8, - BlockHash, - ChainId, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)] -pub enum RwTableTag { - Start = 1, - Stack, - Memory, - AccountStorage, - TxAccessListAccount, - TxAccessListAccountStorage, - TxRefund, - Account, - AccountDestructed, - CallContext, - TxLog, - TxReceipt, -} - -impl RwTableTag { - pub fn is_reversible(self) -> bool { - matches!( - self, - RwTableTag::TxAccessListAccount - | RwTableTag::TxAccessListAccountStorage - | RwTableTag::TxRefund - | RwTableTag::Account - | RwTableTag::AccountStorage - | RwTableTag::AccountDestructed - ) - } -} - -impl From for usize { - fn from(t: RwTableTag) -> Self { - t as usize - } -} - -#[derive(Clone, Copy, Debug, EnumIter)] -pub enum AccountFieldTag { - Nonce = 1, - Balance, - CodeHash, -} - -#[derive(Clone, Copy, Debug)] -pub enum BytecodeFieldTag { - Length, - Byte, - Padding, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)] -pub enum TxLogFieldTag { - Address = 1, - Topic, - Data, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, EnumCount)] -pub enum TxReceiptFieldTag { - PostStateOrStatus = 1, - CumulativeGasUsed, - LogLength, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)] -pub enum CallContextFieldTag { - RwCounterEndOfReversion = 1, - CallerId, - TxId, - Depth, - CallerAddress, - CalleeAddress, - CallDataOffset, - CallDataLength, - ReturnDataOffset, - ReturnDataLength, - Value, - IsSuccess, - IsPersistent, - IsStatic, - - LastCalleeId, - LastCalleeReturnDataOffset, - LastCalleeReturnDataLength, - - IsRoot, - IsCreate, - CodeHash, - ProgramCounter, - StackPointer, - GasLeft, - MemorySize, - ReversibleWriteCounter, -} - -impl_expr!(FixedTableTag); -impl_expr!(TxContextFieldTag); -impl_expr!(RwTableTag); -impl_expr!(AccountFieldTag); -impl_expr!(BytecodeFieldTag); -impl_expr!(CallContextFieldTag); -impl_expr!(BlockContextFieldTag); -impl_expr!(TxLogFieldTag); -impl_expr!(TxReceiptFieldTag); - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, EnumIter)] pub(crate) enum Table { Fixed, @@ -357,7 +205,7 @@ pub(crate) enum Lookup { Conditional(Expression, Box>), } -impl Lookup { +impl Lookup { pub(crate) fn conditional(self, condition: Expression) -> Self { Self::Conditional(condition, self.into()) } diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index fc40c1d4c7..b0b582f6c1 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -408,8 +408,12 @@ pub(crate) mod rlc { rlc } - pub(crate) fn value(values: &[u8], randomness: F) -> F { - values.iter().rev().fold(F::zero(), |acc, value| { + pub(crate) fn value<'a, F: FieldExt, I>(values: I, randomness: F) -> F + where + I: IntoIterator, + ::IntoIter: DoubleEndedIterator, + { + values.into_iter().rev().fold(F::zero(), |acc, value| { acc * randomness + F::from(*value as u64) }) } diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index 9c0d2e2f86..269eb18cd7 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -2,7 +2,7 @@ use super::CachedRegion; use crate::{ evm_circuit::{ param::N_BYTES_GAS, - table::{AccountFieldTag, CallContextFieldTag, FixedTableTag, Lookup}, + table::{FixedTableTag, Lookup}, util::{ constraint_builder::{ ConstraintBuilder, ReversionInfo, StepStateTransition, @@ -13,6 +13,7 @@ use crate::{ }, witness::{Block, Call, ExecStep}, }, + table::{AccountFieldTag, CallContextFieldTag}, util::Expr, }; use eth_types::{Field, ToLittleEndian, ToScalar, U256}; diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 621d35a9fe..c42ffde713 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -2,20 +2,19 @@ use crate::{ evm_circuit::{ param::STACK_CAPACITY, step::{ExecutionState, Step}, - table::{ - AccountFieldTag, BytecodeFieldTag, CallContextFieldTag, FixedTableTag, Lookup, - RwTableTag, Table, TxContextFieldTag, TxLogFieldTag, TxReceiptFieldTag, - }, + table::{FixedTableTag, Lookup, Table}, util::{Cell, RandomLinearCombination, Word}, }, + table::{ + AccountFieldTag, BytecodeFieldTag, CallContextFieldTag, RwTableTag, TxContextFieldTag, + TxLogFieldTag, TxReceiptFieldTag, + }, util::Expr, }; -use halo2_proofs::{ - arithmetic::FieldExt, - plonk::{ - Error, - Expression::{self, Constant}, - }, +use eth_types::Field; +use halo2_proofs::plonk::{ + Error, + Expression::{self, Constant}, }; use std::convert::TryInto; @@ -42,7 +41,7 @@ impl Default for Transition { } #[derive(Default)] -pub(crate) struct StepStateTransition { +pub(crate) struct StepStateTransition { pub(crate) rw_counter: Transition>, pub(crate) call_id: Transition>, pub(crate) is_root: Transition>, @@ -56,7 +55,7 @@ pub(crate) struct StepStateTransition { pub(crate) log_id: Transition>, } -impl StepStateTransition { +impl StepStateTransition { pub(crate) fn new_context() -> Self { Self { program_counter: Transition::To(0.expr()), @@ -100,7 +99,7 @@ pub(crate) struct ReversionInfo { reversible_write_counter: Expression, } -impl ReversionInfo { +impl ReversionInfo { pub(crate) fn rw_counter_end_of_reversion(&self) -> Expression { self.rw_counter_end_of_reversion.expr() } @@ -143,7 +142,7 @@ pub struct BaseConstraintBuilder { pub condition: Option>, } -impl BaseConstraintBuilder { +impl BaseConstraintBuilder { pub(crate) fn new(max_degree: usize) -> Self { BaseConstraintBuilder { constraints: Vec::new(), @@ -250,7 +249,7 @@ pub(crate) struct ConstraintBuilder<'a, F> { stored_expressions: Vec>, } -impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { +impl<'a, F: Field> ConstraintBuilder<'a, F> { pub(crate) fn new( curr: Step, next: Step, diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 79ee115df1..b658d04462 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -1,12 +1,14 @@ #![allow(missing_docs)] -use crate::evm_circuit::{ - param::{N_BYTES_WORD, STACK_CAPACITY}, - step::ExecutionState, +use crate::{ + evm_circuit::{ + param::{N_BYTES_WORD, STACK_CAPACITY}, + step::ExecutionState, + util::RandomLinearCombination, + }, table::{ AccountFieldTag, BlockContextFieldTag, BytecodeFieldTag, CallContextFieldTag, RwTableTag, TxContextFieldTag, TxLogFieldTag, TxReceiptFieldTag, }, - util::RandomLinearCombination, }; use bus_mapping::{ diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index 3ece83aa54..147998f69b 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -19,8 +19,9 @@ pub mod bytecode_circuit; pub mod copy_circuit; pub mod evm_circuit; -pub mod rw_table; pub mod state_circuit; +pub mod super_circuit; +pub mod table; #[cfg(test)] pub mod test_util; pub mod tx_circuit; diff --git a/zkevm-circuits/src/rw_table.rs b/zkevm-circuits/src/rw_table.rs deleted file mode 100644 index c08c05dce3..0000000000 --- a/zkevm-circuits/src/rw_table.rs +++ /dev/null @@ -1,83 +0,0 @@ -#![allow(missing_docs)] -use halo2_proofs::{ - arithmetic::FieldExt, - circuit::Region, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, VirtualCells}, - poly::Rotation, -}; - -use crate::evm_circuit::{table::LookupTable, witness::RwRow}; - -/// The rw table shared between evm circuit and state circuit -#[derive(Clone, Copy)] -pub struct RwTable { - pub rw_counter: Column, - pub is_write: Column, - pub tag: Column, - pub key1: Column, - pub key2: Column, - pub key3: Column, - pub key4: Column, - pub value: Column, - pub value_prev: Column, - pub aux1: Column, - pub aux2: Column, -} - -impl LookupTable for RwTable { - fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { - vec![ - meta.query_advice(self.rw_counter, Rotation::cur()), - meta.query_advice(self.is_write, Rotation::cur()), - meta.query_advice(self.tag, Rotation::cur()), - meta.query_advice(self.key1, Rotation::cur()), - meta.query_advice(self.key2, Rotation::cur()), - meta.query_advice(self.key3, Rotation::cur()), - meta.query_advice(self.key4, Rotation::cur()), - meta.query_advice(self.value, Rotation::cur()), - meta.query_advice(self.value_prev, Rotation::cur()), - meta.query_advice(self.aux1, Rotation::cur()), - meta.query_advice(self.aux2, Rotation::cur()), - ] - } -} -impl RwTable { - pub fn construct(meta: &mut ConstraintSystem) -> Self { - Self { - rw_counter: meta.advice_column(), - is_write: meta.advice_column(), - tag: meta.advice_column(), - key1: meta.advice_column(), - key2: meta.advice_column(), - key3: meta.advice_column(), - key4: meta.advice_column(), - value: meta.advice_column(), - value_prev: meta.advice_column(), - aux1: meta.advice_column(), - aux2: meta.advice_column(), - } - } - pub fn assign( - &self, - region: &mut Region<'_, F>, - offset: usize, - row: &RwRow, - ) -> Result<(), Error> { - for (column, value) in [ - (self.rw_counter, row.rw_counter), - (self.is_write, row.is_write), - (self.tag, row.tag), - (self.key1, row.key1), - (self.key2, row.key2), - (self.key3, row.key3), - (self.key4, row.key4), - (self.value, row.value), - (self.value_prev, row.value_prev), - (self.aux1, row.aux1), - (self.aux2, row.aux2), - ] { - region.assign_advice(|| "assign rw row on rw table", column, offset, || Ok(value))?; - } - Ok(()) - } -} diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 70be026150..4484930190 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -7,10 +7,12 @@ mod random_linear_combination; #[cfg(test)] mod test; -use crate::evm_circuit::{ - param::N_BYTES_WORD, +use crate::{ + evm_circuit::{ + param::N_BYTES_WORD, + witness::{Rw, RwMap}, + }, table::RwTableTag, - witness::{Rw, RwMap}, }; use constraint_builder::{ConstraintBuilder, Queries}; use eth_types::{Address, Field}; diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index 42ccc54187..f5a84b7611 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -3,12 +3,11 @@ use super::{ random_linear_combination::Queries as RlcQueries, N_LIMBS_ACCOUNT_ADDRESS, N_LIMBS_ID, N_LIMBS_RW_COUNTER, }; -use crate::evm_circuit::{ - param::N_BYTES_WORD, +use crate::util::Expr; +use crate::{ + evm_circuit::{param::N_BYTES_WORD, util::not}, table::{AccountFieldTag, RwTableTag}, - util::not, }; -use crate::util::Expr; use eth_types::Field; use gadgets::binary_number::BinaryNumberConfig; use halo2_proofs::plonk::Expression; diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index dafb736ca1..13967385c0 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -6,7 +6,6 @@ use crate::{ }; use eth_types::{Field, ToBigEndian}; use gadgets::binary_number::{AsBits, BinaryNumberChip, BinaryNumberConfig}; -use halo2_proofs::arithmetic::FieldExt; use halo2_proofs::{ circuit::Region, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Instance, VirtualCells}, diff --git a/zkevm-circuits/src/state_circuit/lookups.rs b/zkevm-circuits/src/state_circuit/lookups.rs index d90ee9e670..70eeaca760 100644 --- a/zkevm-circuits/src/state_circuit/lookups.rs +++ b/zkevm-circuits/src/state_circuit/lookups.rs @@ -1,4 +1,4 @@ -use crate::evm_circuit::table::CallContextFieldTag; +use crate::table::CallContextFieldTag; use eth_types::Field; use halo2_proofs::{ circuit::Layouter, diff --git a/zkevm-circuits/src/state_circuit/test.rs b/zkevm-circuits/src/state_circuit/test.rs index 781e778511..96ffe9c713 100644 --- a/zkevm-circuits/src/state_circuit/test.rs +++ b/zkevm-circuits/src/state_circuit/test.rs @@ -1,7 +1,7 @@ use super::{StateCircuit, StateConfig}; -use crate::evm_circuit::{ +use crate::{ + evm_circuit::witness::{Rw, RwMap}, table::{AccountFieldTag, CallContextFieldTag, RwTableTag, TxLogFieldTag, TxReceiptFieldTag}, - witness::{Rw, RwMap}, }; use bus_mapping::operation::{ MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs new file mode 100644 index 0000000000..187102ae5b --- /dev/null +++ b/zkevm-circuits/src/super_circuit.rs @@ -0,0 +1,414 @@ +//! The Super Circuit is a circuit that contains all the circuits of the +//! zkEVM in order to achieve two things: +//! - Check the correct integration between circuits via the shared lookup +//! tables, to verify that the table layouts match. +//! - Allow having a single circuit setup for which a proof can be generated +//! that would be verified under a single aggregation circuit for the first +//! milestone. +//! +//! The current implementation contains the following circuits: +//! +//! - [x] EVM Circuit +//! - [ ] State Circuit +//! - [x] Tx Circuit +//! - [x] Bytecode Circuit +//! - [ ] Copy Circuit +//! - [ ] Keccak Circuit +//! - [ ] MPT Circuit +//! - [ ] PublicInputs Circuit +//! +//! And the following shared tables, with the circuits that use them: +//! +//! - [x] Copy Table +//! - [ ] Copy Circuit +//! - [x] EVM Circuit +//! - [ ] Rw Table +//! - [ ] State Circuit +//! - [ ] EVM Circuit +//! - [ ] Copy Circuit +//! - [x] Tx Table +//! - [x] Tx Circuit +//! - [x] EVM Circuit +//! - [ ] Copy Circuit +//! - [ ] PublicInputs Circuit +//! - [x] Bytecode Table +//! - [x] Bytecode Circuit +//! - [x] EVM Circuit +//! - [ ] Copy Circuit +//! - [ ] Block Table +//! - [ ] EVM Circuit +//! - [ ] PublicInputs Circuit +//! - [ ] MPT Table +//! - [ ] MPT Circuit +//! - [ ] State Circuit +//! - [x] Keccak Table +//! - [ ] Keccak Circuit +//! - [ ] EVM Circuit +//! - [x] Bytecode Circuit +//! - [x] Tx Circuit +//! - [ ] MPT Circuit + +use crate::tx_circuit::{self, TxCircuit, TxCircuitConfig}; + +use crate::bytecode_circuit::bytecode_unroller::{ + unroll, Config as BytecodeConfig, UnrolledBytecode, +}; + +use crate::evm_circuit::{table::FixedTableTag, witness::Block, EvmCircuit}; +use crate::table::{BlockTable, BytecodeTable, CopyTable, KeccakTable, RwTable, TxTable}; +use crate::util::power_of_randomness_from_instance; +use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, +}; + +/// Configuration of the Super Circuit +#[derive(Clone)] +pub struct SuperCircuitConfig { + tx_table: TxTable, + rw_table: RwTable, + bytecode_table: BytecodeTable, + block_table: BlockTable, + keccak_table: KeccakTable, + copy_table: CopyTable, + evm_circuit: EvmCircuit, + tx_circuit: TxCircuitConfig, + bytecode_circuit: BytecodeConfig, +} + +/// The Super Circuit contains all the zkEVM circuits +#[derive(Default)] +pub struct SuperCircuit { + // EVM Circuit + block: Block, + fixed_table_tags: Vec, + // Tx Circuit + tx_circuit: TxCircuit, + // Bytecode Circuit + // bytecodes: Vec>, + bytecode_size: usize, +} + +impl + SuperCircuit +{ + /// Return the number of rows required to verify a given block + pub fn get_num_rows_required(block: &Block) -> usize { + let mut cs = ConstraintSystem::default(); + let config = Self::configure(&mut cs); + config.evm_circuit.get_num_rows_required(block) + } +} + +impl Circuit + for SuperCircuit +{ + type Config = SuperCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let tx_table = TxTable::construct(meta); + let rw_table = RwTable::construct(meta); + let bytecode_table = BytecodeTable::construct(meta); + let block_table = BlockTable::construct(meta); + let keccak_table = KeccakTable::construct(meta); + let q_copy_table = meta.fixed_column(); + let copy_table = CopyTable::construct(meta, q_copy_table); + + let power_of_randomness = power_of_randomness_from_instance(meta); + let evm_circuit = EvmCircuit::configure( + meta, + power_of_randomness[..31].to_vec().try_into().unwrap(), + &tx_table, + &rw_table, + &bytecode_table, + &block_table, + ©_table, + ); + + Self::Config { + tx_table: tx_table.clone(), + rw_table, + bytecode_table: bytecode_table.clone(), + block_table, + keccak_table: keccak_table.clone(), + copy_table, + evm_circuit, + tx_circuit: TxCircuitConfig::new( + meta, + power_of_randomness.clone(), + tx_table, + keccak_table.clone(), + ), + bytecode_circuit: BytecodeConfig::configure( + meta, + power_of_randomness[0].clone(), + bytecode_table, + keccak_table, + ), + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // --- EVM Circuit --- + config + .evm_circuit + .load_fixed_table(&mut layouter, self.fixed_table_tags.clone())?; + config.evm_circuit.load_byte_table(&mut layouter)?; + config + .rw_table + .load(&mut layouter, &self.block.rws, self.block.randomness)?; + config + .block_table + .load(&mut layouter, &self.block.context, self.block.randomness)?; + config + .copy_table + .load(&mut layouter, &self.block, self.block.randomness)?; + config + .evm_circuit + .assign_block(&mut layouter, &self.block)?; + // --- Tx Circuit --- + self.tx_circuit.assign(&config.tx_circuit, &mut layouter)?; + // --- Bytecode Circuit --- + let bytecodes: Vec> = self + .block + .bytecodes + .iter() + .map(|(_, b)| unroll(b.bytes.clone(), self.block.randomness)) + .collect(); + config.bytecode_circuit.load(&mut layouter)?; + config.bytecode_circuit.assign( + &mut layouter, + self.bytecode_size, + &bytecodes, + self.block.randomness, + )?; + // --- Keccak Table --- + let mut keccak_inputs = Vec::new(); + // Lookups from TxCircuit + keccak_inputs.extend_from_slice(&tx_circuit::keccak_inputs( + &self.tx_circuit.txs, + self.block.context.chain_id.as_u64(), + )?); + // Lookups from BytecodeCircuit + for bytecode in self.block.bytecodes.values() { + keccak_inputs.push(bytecode.bytes.clone()); + } + // Load Keccak Table + config.keccak_table.load( + &mut layouter, + keccak_inputs.iter().map(|b| b.as_slice()), + self.block.randomness, + )?; + Ok(()) + } +} + +#[cfg(test)] +pub mod test { + use eth_types::{Address, U64}; + use ethers_core::{types::TransactionRequest, utils::keccak256}; + use ethers_signers::LocalWallet; + use std::collections::HashMap; + // Testing function + pub fn sign_txs( + txs: &mut [eth_types::Transaction], + chain_id: u64, + wallets: &HashMap, + ) { + for tx in txs.iter_mut() { + let wallet = wallets.get(&tx.from).unwrap(); + let req = TransactionRequest::new() + .from(tx.from) + .to(tx.to.unwrap()) + .nonce(tx.nonce) + .value(tx.value) + .data(tx.input.clone()) + .gas(tx.gas) + .gas_price(tx.gas_price.unwrap()); + let tx_rlp = req.rlp(chain_id); + let sighash = keccak256(tx_rlp.as_ref()).into(); + let sig = wallet.sign_hash(sighash, true); + tx.v = U64::from(sig.v); + tx.r = sig.r; + tx.s = sig.s; + } + } +} + +#[cfg(test)] +mod super_circuit_tests { + use super::test::*; + use super::*; + use crate::{evm_circuit::witness::block_convert, tx_circuit::sign_verify::POW_RAND_SIZE}; + use bus_mapping::mock::BlockData; + use eth_types::{ + address, bytecode, + geth_types::{self, GethData}, + Word, + }; + use ethers_signers::{LocalWallet, Signer}; + use group::{Curve, Group}; + use halo2_proofs::arithmetic::{CurveAffine, Field as Halo2Field}; + use halo2_proofs::dev::{MockProver, VerifyFailure}; + use halo2_proofs::pairing::bn256::Fr; + use mock::{TestContext, MOCK_CHAIN_ID}; + use rand::SeedableRng; + use rand_chacha::ChaCha20Rng; + use secp256k1::Secp256k1Affine; + use std::collections::HashMap; + use strum::IntoEnumIterator; + + struct Inputs { + block: Block, + txs: Vec, + aux_generator: Secp256k1Affine, + } + + fn run_test_circuit( + inputs: Inputs, + fixed_table_tags: Vec, + ) -> Result<(), Vec> { + let Inputs { + block, + txs, + aux_generator, + } = inputs; + + let log2_ceil = |n| u32::BITS - (n as u32).leading_zeros() - (n & (n - 1) == 0) as u32; + + let num_rows_required_for_steps = + SuperCircuit::::get_num_rows_required(&block); + + let k = log2_ceil( + 64 + fixed_table_tags + .iter() + .map(|tag| tag.build::().count()) + .sum::(), + ); + let bytecodes_len = block + .bytecodes + .iter() + .map(|(_, bytecode)| bytecode.bytes.len()) + .sum::(); + let k = k.max(log2_ceil(64 + bytecodes_len)); + let k = k.max(log2_ceil(64 + num_rows_required_for_steps)); + let k = k + 1; + log::debug!("evm circuit uses k = {}", k); + + let mut instance: Vec> = (1..POW_RAND_SIZE + 1) + .map(|exp| vec![block.randomness.pow(&[exp as u64, 0, 0, 0]); (1 << k) - 64]) + .collect(); + // SignVerifyChip -> ECDSAChip -> MainGate instance column + instance.push(vec![]); + + let chain_id = block.context.chain_id; + let tx_circuit = TxCircuit::new(aux_generator, block.randomness, chain_id.as_u64(), txs); + let circuit = SuperCircuit:: { + block, + fixed_table_tags, + tx_circuit, + // Instead of using 1 << k - NUM_BLINDING_ROWS, we use a much smaller number of enabled + // rows for the Bytecode Circuit because otherwise it penalizes significantly the + // MockProver verification time. + bytecode_size: bytecodes_len + 64, + }; + let prover = MockProver::::run(k, &circuit, instance).unwrap(); + prover.verify() + } + + fn run_test_circuit_complete_fixed_table( + inputs: Inputs, + ) -> Result<(), Vec> { + const MAX_TXS: usize = 1; + const MAX_CALLDATA: usize = 32; + + run_test_circuit::(inputs, FixedTableTag::iter().collect()) + } + + // High memory usage test. Run in serial with: + // `cargo test [...] skip_ -- --ignored --test-threads 1` + // NOTE: This test is not run as part of CI because it requires more memory than + // is available in github workers and so it gets killed before completion. + #[ignore] + #[test] + fn skip_test_super_circuit() { + let mut rng = ChaCha20Rng::seed_from_u64(2); + + let chain_id = (*MOCK_CHAIN_ID).as_u64(); + + let bytecode = bytecode! { + GAS + STOP + }; + + let wallet_a = LocalWallet::new(&mut rng).with_chain_id(chain_id); + + let addr_a = wallet_a.address(); + let addr_b = address!("0x000000000000000000000000000000000000BBBB"); + + let mut wallets = HashMap::new(); + wallets.insert(wallet_a.address(), wallet_a); + + let mut block: GethData = TestContext::<2, 1>::new( + None, + |accs| { + accs[0] + .address(addr_b) + .balance(Word::from(1u64 << 20)) + .code(bytecode); + accs[1].address(addr_a).balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .gas(Word::from(1_000_000u64)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(); + + sign_txs(&mut block.eth_block.transactions, chain_id, &wallets); + let txs = block + .eth_block + .transactions + .iter() + .map(geth_types::Transaction::from_eth_tx) + .collect(); + + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + assert_eq!(builder.block.chain_id.as_u64(), chain_id); + + builder + .handle_block(&block.eth_block, &block.geth_traces) + .expect("could not handle block tx"); + let mut block = block_convert(&builder.block, &builder.code_db); + block.randomness = Fr::random(&mut rng); + + let aux_generator = + ::CurveExt::random(&mut rng).to_affine(); + let inputs = Inputs { + block, + txs, + aux_generator, + }; + + let res = run_test_circuit_complete_fixed_table(inputs); + if let Err(err) = res { + eprintln!("Verification failures:"); + eprintln!("{:#?}", err); + panic!("Failed verification"); + } + } +} diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs new file mode 100644 index 0000000000..e6e820ca4b --- /dev/null +++ b/zkevm-circuits/src/table.rs @@ -0,0 +1,874 @@ +//! Table definitions used cross-circuits + +use crate::copy_circuit::number_or_hash_to_field; +use crate::evm_circuit::witness::RwRow; +use crate::evm_circuit::{ + util::{rlc, RandomLinearCombination}, + witness::{Block, BlockContext, Bytecode, RwMap, Transaction}, +}; +use crate::impl_expr; +use bus_mapping::circuit_input_builder::{CopyDataType, CopyEvent}; +use eth_types::{Field, ToAddress, ToLittleEndian, ToScalar, Word, U256}; +use gadgets::binary_number::{BinaryNumberChip, BinaryNumberConfig}; +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::Region, + plonk::{Advice, Column, ConstraintSystem, Error}, +}; +use halo2_proofs::{circuit::Layouter, plonk::*, poly::Rotation}; +use itertools::Itertools; +use keccak256::plain::Keccak; +use strum_macros::{EnumCount, EnumIter}; + +/// Trait used for dynamic tables. Used to get an automatic implementation of +/// the LookupTable trait where each `table_expr` is a query to each column at +/// `Rotation::cur`. +pub trait DynamicTableColumns { + /// Returns the list of advice columns following the table order. + fn columns(&self) -> Vec>; +} + +/// Trait used to define lookup tables +pub trait LookupTable { + /// Return the list of expressions used to define the lookup table. + fn table_exprs(&self, meta: &mut VirtualCells) -> Vec>; +} + +impl LookupTable for T { + fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { + self.columns() + .iter() + .map(|column| meta.query_advice(*column, Rotation::cur())) + .collect() + } +} + +impl> + Clone, const W: usize> LookupTable for [C; W] { + fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { + self.iter() + .map(|column| meta.query_any(column.clone(), Rotation::cur())) + .collect() + } +} + +/// Tag used to identify each field in the transaction in a row of the +/// transaction table. +#[derive(Clone, Copy, Debug)] +pub enum TxFieldTag { + /// Unused tag + Null = 0, + /// Nonce + Nonce, + /// Gas + Gas, + /// GasPrice + GasPrice, + /// CallerAddress + CallerAddress, + /// CalleeAddress + CalleeAddress, + /// IsCreate + IsCreate, + /// Value + Value, + /// CallDataLength + CallDataLength, + /// Gas cost for transaction call data (4 for byte == 0, 16 otherwise) + CallDataGasCost, + /// TxSignHash: Hash of the transaction without the signature, used for + /// signing. + TxSignHash, + /// CallData + CallData, +} +impl_expr!(TxFieldTag); + +/// Alias for TxFieldTag used by EVM Circuit +pub type TxContextFieldTag = TxFieldTag; + +/// Table that contains the fields of all Transactions in a block +#[derive(Clone, Debug)] +pub struct TxTable { + /// Tx ID + pub tx_id: Column, + /// Tag (TxContextFieldTag) + pub tag: Column, + /// Index for Tag = CallData + pub index: Column, + /// Value + pub value: Column, +} + +impl TxTable { + /// Construct a new TxTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + tx_id: meta.advice_column(), + tag: meta.advice_column(), + index: meta.advice_column(), + value: meta.advice_column(), + } + } + + /// Assign the `TxTable` from a list of block `Transaction`s, followig the + /// same layout that the Tx Circuit uses. + pub fn load( + &self, + layouter: &mut impl Layouter, + txs: &[Transaction], + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "tx table", + |mut region| { + let mut offset = 0; + for column in self.columns() { + region.assign_advice( + || "tx table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + let tx_table_columns = self.columns(); + for tx in txs.iter() { + for row in tx.table_assignments(randomness) { + for (column, value) in tx_table_columns.iter().zip_eq(row) { + region.assign_advice( + || format!("tx table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } +} + +impl DynamicTableColumns for TxTable { + fn columns(&self) -> Vec> { + vec![self.tx_id, self.tag, self.index, self.value] + } +} + +/// Tag to identify the operation type in a RwTable row +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)] +pub enum RwTableTag { + /// Start (used for padding) + Start = 1, + /// Stack operation + Stack, + /// Memory operation + Memory, + /// Account Storage operation + AccountStorage, + /// Tx Access List Account operation + TxAccessListAccount, + /// Tx Access List Account Storage operation + TxAccessListAccountStorage, + /// Tx Refund operation + TxRefund, + /// Account operation + Account, + /// Account Destructed operation + AccountDestructed, + /// Call Context operation + CallContext, + /// Tx Log operation + TxLog, + /// Tx Receipt operation + TxReceipt, +} +impl_expr!(RwTableTag); + +impl RwTableTag { + /// Returns true if the RwTable operation is reversible + pub fn is_reversible(self) -> bool { + matches!( + self, + RwTableTag::TxAccessListAccount + | RwTableTag::TxAccessListAccountStorage + | RwTableTag::TxRefund + | RwTableTag::Account + | RwTableTag::AccountStorage + | RwTableTag::AccountDestructed + ) + } +} + +impl From for usize { + fn from(t: RwTableTag) -> Self { + t as usize + } +} + +/// Tag for an AccountField in RwTable +#[derive(Clone, Copy, Debug, EnumIter)] +pub enum AccountFieldTag { + /// Nonce field + Nonce = 1, + /// Balance field + Balance, + /// CodeHash field + CodeHash, +} +impl_expr!(AccountFieldTag); + +/// Tag for a TxLogField in RwTable +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)] +pub enum TxLogFieldTag { + /// Address field + Address = 1, + /// Topic field + Topic, + /// Data field + Data, +} +impl_expr!(TxLogFieldTag); + +/// Tag for a TxReceiptField in RwTable +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, EnumCount)] +pub enum TxReceiptFieldTag { + /// Tx result + PostStateOrStatus = 1, + /// CumulativeGasUsed in the tx + CumulativeGasUsed, + /// Number of logs in the tx + LogLength, +} +impl_expr!(TxReceiptFieldTag); + +/// Tag for a CallContextField in RwTable +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)] +pub enum CallContextFieldTag { + /// RwCounterEndOfReversion + RwCounterEndOfReversion = 1, + /// CallerId + CallerId, + /// TxId + TxId, + /// Depth + Depth, + /// CallerAddress + CallerAddress, + /// CalleeAddress + CalleeAddress, + /// CallDataOffset + CallDataOffset, + /// CallDataLength + CallDataLength, + /// ReturnDataOffset + ReturnDataOffset, + /// ReturnDataLength + ReturnDataLength, + /// Value + Value, + /// IsSuccess + IsSuccess, + /// IsPersistent + IsPersistent, + /// IsStatic + IsStatic, + + /// LastCalleeId + LastCalleeId, + /// LastCalleeReturnDataOffset + LastCalleeReturnDataOffset, + /// LastCalleeReturnDataLength + LastCalleeReturnDataLength, + + /// IsRoot + IsRoot, + /// IsCreate + IsCreate, + /// CodeHash + CodeHash, + /// ProgramCounter + ProgramCounter, + /// StackPointer + StackPointer, + /// GasLeft + GasLeft, + /// MemorySize + MemorySize, + /// ReversibleWriteCounter + ReversibleWriteCounter, +} +impl_expr!(CallContextFieldTag); + +/// The RwTable shared between EVM Circuit and State Circuit, which contains +/// traces of the EVM state operations. +#[derive(Clone, Copy)] +pub struct RwTable { + /// Read Write Counter + pub rw_counter: Column, + /// Is Write + pub is_write: Column, + /// Tag + pub tag: Column, + /// Key1 (Id) + pub key1: Column, + /// Key2 (Address) + pub key2: Column, + /// Key3 (FieldTag) + pub key3: Column, + /// Key3 (StorageKey) + pub key4: Column, + /// Value + pub value: Column, + /// Value Previous + pub value_prev: Column, + /// Aux1 (Committed Value) + pub aux1: Column, + /// Aux2 + pub aux2: Column, +} + +impl DynamicTableColumns for RwTable { + fn columns(&self) -> Vec> { + vec![ + self.rw_counter, + self.is_write, + self.tag, + self.key1, + self.key2, + self.key3, + self.key4, + self.value, + self.value_prev, + self.aux1, + self.aux2, + ] + } +} +impl RwTable { + /// Construct a new RwTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + rw_counter: meta.advice_column(), + is_write: meta.advice_column(), + tag: meta.advice_column(), + key1: meta.advice_column(), + key2: meta.advice_column(), + key3: meta.advice_column(), + key4: meta.advice_column(), + value: meta.advice_column(), + value_prev: meta.advice_column(), + aux1: meta.advice_column(), + aux2: meta.advice_column(), + } + } + /// Assign a `RwRow` at offset into the `RwTable` + pub fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + row: &RwRow, + ) -> Result<(), Error> { + for (column, value) in [ + (self.rw_counter, row.rw_counter), + (self.is_write, row.is_write), + (self.tag, row.tag), + (self.key1, row.key1), + (self.key2, row.key2), + (self.key3, row.key3), + (self.key4, row.key4), + (self.value, row.value), + (self.value_prev, row.value_prev), + (self.aux1, row.aux1), + (self.aux2, row.aux2), + ] { + region.assign_advice(|| "assign rw row on rw table", column, offset, || Ok(value))?; + } + Ok(()) + } + + /// Assign the `RwTable` from a `RwMap`, followig the same + /// table layout that the State Circuit uses. + pub fn load( + &self, + layouter: &mut impl Layouter, + rws: &RwMap, + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "rw table", + |mut region| { + let mut offset = 0; + self.assign(&mut region, offset, &Default::default())?; + offset += 1; + + let mut rows = rws + .0 + .values() + .flat_map(|rws| rws.iter()) + .collect::>(); + + rows.sort_by_key(|a| a.rw_counter()); + let mut expected_rw_counter = 1; + for rw in rows { + assert!(rw.rw_counter() == expected_rw_counter); + expected_rw_counter += 1; + + self.assign(&mut region, offset, &rw.table_assignment(randomness))?; + offset += 1; + } + Ok(()) + }, + ) + } +} + +/// Tag to identify the field in a Bytecode Table row +#[derive(Clone, Copy, Debug)] +pub enum BytecodeFieldTag { + /// Length field + Length, + /// Byte field + Byte, + /// Padding field + Padding, +} +impl_expr!(BytecodeFieldTag); + +/// Table with Bytecode indexed by its Code Hash +#[derive(Clone, Debug)] +pub struct BytecodeTable { + /// Code Hash + pub code_hash: Column, + /// Tag + pub tag: Column, + /// Index + pub index: Column, + /// Is Code is true when the byte is not an argument to a PUSH* instruction. + pub is_code: Column, + /// Value + pub value: Column, +} + +impl BytecodeTable { + /// Construct a new BytecodeTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + code_hash: meta.advice_column(), + tag: meta.advice_column(), + index: meta.advice_column(), + is_code: meta.advice_column(), + value: meta.advice_column(), + } + } + + /// Assign the `BytecodeTable` from a list of bytecodes, followig the same + /// table layout that the Bytecode Circuit uses. + pub fn load<'a, F: Field>( + &self, + layouter: &mut impl Layouter, + bytecodes: impl IntoIterator + Clone, + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "bytecode table", + |mut region| { + let mut offset = 0; + for column in self.columns() { + region.assign_advice( + || "bytecode table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + let bytecode_table_columns = self.columns(); + for bytecode in bytecodes.clone() { + for row in bytecode.table_assignments(randomness) { + for (column, value) in bytecode_table_columns.iter().zip_eq(row) { + region.assign_advice( + || format!("bytecode table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } +} + +impl DynamicTableColumns for BytecodeTable { + fn columns(&self) -> Vec> { + vec![ + self.code_hash, + self.tag, + self.index, + self.is_code, + self.value, + ] + } +} + +/// Tag to identify the field in a Block Table row +// Keep the sequence consistent with OpcodeId for scalar +#[derive(Clone, Copy, Debug)] +pub enum BlockContextFieldTag { + /// Coinbase field + Coinbase = 1, + /// Timestamp field + Timestamp, + /// Number field + Number, + /// Difficulty field + Difficulty, + /// Gas Limit field + GasLimit, + /// Base Fee field + BaseFee = 8, + /// Block Hash field + BlockHash, + /// Chain ID field. Although this is not a field in the block header, we + /// add it here for convenience. + ChainId, +} +impl_expr!(BlockContextFieldTag); + +/// Table with Block header fields +#[derive(Clone, Debug)] +pub struct BlockTable { + /// Tag + pub tag: Column, + /// Index + pub index: Column, + /// Value + pub value: Column, +} + +impl BlockTable { + /// Construct a new BlockTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + tag: meta.advice_column(), + index: meta.advice_column(), + value: meta.advice_column(), + } + } + + /// Assign the `BlockTable` from a `BlockContext`. + pub fn load( + &self, + layouter: &mut impl Layouter, + block: &BlockContext, + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "block table", + |mut region| { + let mut offset = 0; + for column in self.columns() { + region.assign_advice( + || "block table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + let block_table_columns = self.columns(); + for row in block.table_assignments(randomness) { + for (column, value) in block_table_columns.iter().zip_eq(row) { + region.assign_advice( + || format!("block table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + + Ok(()) + }, + ) + } +} + +impl DynamicTableColumns for BlockTable { + fn columns(&self) -> Vec> { + vec![self.tag, self.index, self.value] + } +} + +/// Keccak Table, used to verify keccak hashing from RLC'ed input. +#[derive(Clone, Debug)] +pub struct KeccakTable { + /// True when the row is enabled + pub is_enabled: Column, + /// Byte array input as `RLC(reversed(input))` + pub input_rlc: Column, // RLC of input bytes + /// Byte array input length + pub input_len: Column, + /// RLC of the hash result + pub output_rlc: Column, // RLC of hash of input bytes +} + +impl KeccakTable { + /// Construct a new KeccakTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + is_enabled: meta.advice_column(), + input_rlc: meta.advice_column(), + input_len: meta.advice_column(), + output_rlc: meta.advice_column(), + } + } + + /// Generate the keccak table assignments from a byte array input. + pub fn assignments(input: &[u8], randomness: F) -> Vec<[F; 4]> { + let input_rlc: F = rlc::value(input.iter().rev(), randomness); + let input_len = F::from(input.len() as u64); + let mut keccak = Keccak::default(); + keccak.update(input); + let output = keccak.digest(); + let output_rlc = RandomLinearCombination::::random_linear_combine( + Word::from_big_endian(output.as_slice()).to_le_bytes(), + randomness, + ); + + vec![[F::one(), input_rlc, input_len, output_rlc]] + } + + // NOTE: For now, the input_rlc of the keccak is defined as + // `RLC(reversed(input))` for convenience of the circuits that do the lookups. + // This allows calculating the `input_rlc` after all the inputs bytes have been + // layed out via the pattern `acc[i] = acc[i-1] * r + value[i]`. + /// Assign the `KeccakTable` from a list hashing inputs, followig the same + /// table layout that the Keccak Circuit uses. + pub fn load<'a, F: Field>( + &self, + layouter: &mut impl Layouter, + inputs: impl IntoIterator + Clone, + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "keccak table", + |mut region| { + let mut offset = 0; + for column in self.columns() { + region.assign_advice( + || "keccak table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + let keccak_table_columns = self.columns(); + for input in inputs.clone() { + for row in Self::assignments(input, randomness) { + // let mut column_index = 0; + for (column, value) in keccak_table_columns.iter().zip_eq(row) { + region.assign_advice( + || format!("keccak table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } +} + +impl DynamicTableColumns for KeccakTable { + fn columns(&self) -> Vec> { + vec![ + self.is_enabled, + self.input_rlc, + self.input_len, + self.output_rlc, + ] + } +} + +/// Copy Table, used to verify copies of byte chunks between Memory, Bytecode, +/// TxLogs and TxCallData. +#[derive(Clone, Copy, Debug)] +pub struct CopyTable { + /// Whether the row is the first read-write pair for a copy event. + pub is_first: Column, + /// The relevant ID for the read-write row, represented as a random linear + /// combination. The ID may be one of the below: + /// 1. Call ID/Caller ID for CopyDataType::Memory + /// 2. RLC encoding of bytecode hash for CopyDataType::Bytecode + /// 3. Transaction ID for CopyDataType::TxCalldata, CopyDataType::TxLog + pub id: Column, + /// The source/destination address for this copy step. Can be memory + /// address, byte index in the bytecode, tx call data, and tx log data. + pub addr: Column, + /// The end of the source buffer for the copy event. Any data read from an + /// address greater than or equal to this value will be 0. + pub src_addr_end: Column, + /// The number of bytes left to be copied. + pub bytes_left: Column, + /// The associated read-write counter for this row. + pub rw_counter: Column, + /// Decrementing counter denoting reverse read-write counter. + pub rwc_inc_left: Column, + /// Binary chip to constrain the copy table conditionally depending on the + /// current row's tag, whether it is Bytecode, Memory, TxCalldata or + /// TxLog. + pub tag: BinaryNumberConfig, +} + +impl CopyTable { + /// Construct a new CopyTable + pub fn construct(meta: &mut ConstraintSystem, q_enable: Column) -> Self { + Self { + is_first: meta.advice_column(), + id: meta.advice_column(), + tag: BinaryNumberChip::configure(meta, q_enable), + addr: meta.advice_column(), + src_addr_end: meta.advice_column(), + bytes_left: meta.advice_column(), + rw_counter: meta.advice_column(), + rwc_inc_left: meta.advice_column(), + } + } + + /// Generate the copy table assignments from a copy event. + pub fn assignments( + copy_event: &CopyEvent, + randomness: F, + ) -> Vec<(CopyDataType, [F; 7])> { + let mut assignments = Vec::new(); + for (step_idx, copy_step) in copy_event.steps.iter().enumerate() { + // is_first + let is_first = if step_idx == 0 { F::one() } else { F::zero() }; + // id + let id = { + let id = if copy_step.rw.is_read() { + ©_event.src_id + } else { + ©_event.dst_id + }; + number_or_hash_to_field(id, randomness) + }; + // addr + let addr = match copy_step.tag { + CopyDataType::TxLog => { + let addr = (U256::from(copy_step.addr) + + (U256::from(TxLogFieldTag::Data as u64) << 32) + + (U256::from(copy_event.log_id.unwrap()) << 48)) + .to_address(); + addr.to_scalar().unwrap() + } + _ => F::from(copy_step.addr), + }; + assignments.push(( + copy_step.tag, + [ + is_first, + id, + addr, + F::from(copy_event.src_addr_end), // src_addr_end + F::from(copy_event.length - step_idx as u64 / 2), // bytes_left + F::from(copy_step.rwc.0 as u64), // rw_counter + F::from(copy_step.rwc_inc_left), // rw_inc_left + ], + )); + } + assignments + } + + /// Assign the `CopyTable` from a `Block`. + pub fn load( + &self, + layouter: &mut impl Layouter, + block: &Block, + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "copy table", + |mut region| { + let mut offset = 0; + for column in self.columns() { + region.assign_advice( + || "copy table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + let tag_chip = BinaryNumberChip::construct(self.tag); + let copy_table_columns = self.columns(); + for copy_event in block.copy_events.values() { + for (tag, row) in Self::assignments(copy_event, randomness) { + for (column, value) in copy_table_columns.iter().zip_eq(row) { + region.assign_advice( + || format!("copy table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + tag_chip.assign(&mut region, offset, &tag)?; + offset += 1; + } + } + + Ok(()) + }, + ) + } +} + +impl CopyTable { + fn columns(&self) -> Vec> { + vec![ + self.is_first, + self.id, + self.addr, + self.src_addr_end, + self.bytes_left, + self.rw_counter, + self.rwc_inc_left, + ] + } +} + +impl LookupTable for CopyTable { + fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { + vec![ + meta.query_advice(self.is_first, Rotation::cur()), + meta.query_advice(self.id, Rotation::cur()), // src_id + self.tag.value(Rotation::cur())(meta), // src_tag + meta.query_advice(self.id, Rotation::next()), // dst_id + self.tag.value(Rotation::next())(meta), // dst_tag + meta.query_advice(self.addr, Rotation::cur()), // src_addr + meta.query_advice(self.src_addr_end, Rotation::cur()), // src_addr_end + meta.query_advice(self.addr, Rotation::next()), // dst_addr + meta.query_advice(self.bytes_left, Rotation::cur()), // length + meta.query_advice(self.rw_counter, Rotation::cur()), // rw_counter + meta.query_advice(self.rwc_inc_left, Rotation::cur()), // rwc_inc_left + ] + } +} diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index a5de278299..642330cd6b 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -6,7 +6,8 @@ pub mod sign_verify; -use crate::util::{random_linear_combine_word as rlc, Expr}; +use crate::table::{KeccakTable, TxFieldTag, TxTable}; +use crate::util::{power_of_randomness_from_instance, random_linear_combine_word as rlc}; use eth_types::{ geth_types::Transaction, Address, Field, ToBigEndian, ToLittleEndian, ToScalar, Word, }; @@ -14,8 +15,7 @@ use ff::PrimeField; use group::GroupEncoding; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, SimpleFloorPlanner}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, - poly::Rotation, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression}, }; use itertools::Itertools; use lazy_static::lazy_static; @@ -23,6 +23,7 @@ use libsecp256k1; use log::error; use num::Integer; use num_bigint::BigUint; +// use rand_core::RngCore; use rlp::RlpStream; use secp256k1::Secp256k1Affine; use sha3::{Digest, Keccak256}; @@ -85,6 +86,21 @@ fn ct_option_ok_or(v: CtOption, err: E) -> Result { Option::::from(v).ok_or(err) } +/// Return all the keccak inputs that the Tx Circuit requires. +pub fn keccak_inputs(txs: &[Transaction], chain_id: u64) -> Result>, Error> { + let mut inputs = Vec::new(); + let sign_datas: Vec = txs + .iter() + .map(|tx| tx_to_sign_data(tx, chain_id)) + .try_collect()?; + // Keccak inputs from SignVerify Chip + let sign_verify_inputs = sign_verify::keccak_inputs(&sign_datas); + inputs.extend_from_slice(&sign_verify_inputs); + // NOTE: We don't verify the Tx Hash in the circuit yet, so we don't have more + // hash inputs. + Ok(inputs) +} + fn tx_to_sign_data(tx: &Transaction, chain_id: u64) -> Result { let sig_r_le = tx.r.to_le_bytes(); let sig_s_le = tx.s.to_le_bytes(); @@ -134,37 +150,6 @@ fn tx_to_sign_data(tx: &Transaction, chain_id: u64) -> Result { }) } -// TODO: Deduplicate with -// `zkevm-circuits/src/evm_circuit/table.rs::TxContextFieldTag`. -/// Tag used to identify each field in the transaction in a row of the -/// transaction table. -#[derive(Clone, Copy, Debug)] -pub enum TxFieldTag { - /// Unused tag - Null = 0, - /// Nonce - Nonce, - /// Gas - Gas, - /// GasPrice - GasPrice, - /// CallerAddress - CallerAddress, - /// CalleeAddress - CalleeAddress, - /// IsCreate - IsCreate, - /// Value - Value, - /// CallDataLength - CallDataLength, - /// TxSignHash: Hash of the transaction without the signature, used for - /// signing. - TxSignHash, - /// CallData - CallData, -} - /// Config for TxCircuit #[derive(Clone, Debug)] pub struct TxCircuitConfig { @@ -173,35 +158,25 @@ pub struct TxCircuitConfig { index: Column, value: Column, sign_verify: SignVerifyConfig, + keccak_table: KeccakTable, _marker: PhantomData, } impl TxCircuitConfig { - fn new(meta: &mut ConstraintSystem) -> Self { - let tx_id = meta.advice_column(); - let tag = meta.advice_column(); - let index = meta.advice_column(); - let value = meta.advice_column(); + /// Return a new TxCircuitConfig + pub fn new( + meta: &mut ConstraintSystem, + power_of_randomness: [Expression; sign_verify::POW_RAND_SIZE], + tx_table: TxTable, + keccak_table: KeccakTable, + ) -> Self { + let tx_id = tx_table.tx_id; + let tag = tx_table.tag; + let index = tx_table.index; + let value = tx_table.value; meta.enable_equality(value); - // This gate is used just to get the array of expressions from the power of - // randomness instance column, so that later on we don't need to query - // columns everywhere, and can pass the power of randomness array - // expression everywhere. The gate itself doesn't add any constraints. - let power_of_randomness = { - let columns = [(); sign_verify::POW_RAND_SIZE].map(|_| meta.instance_column()); - let mut power_of_randomness = None; - - meta.create_gate("power of randomness", |meta| { - power_of_randomness = - Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); - - [0.expr()] - }); - - power_of_randomness.unwrap() - }; - let sign_verify = SignVerifyConfig::new(meta, power_of_randomness); + let sign_verify = SignVerifyConfig::new(meta, power_of_randomness, keccak_table.clone()); Self { tx_id, @@ -209,6 +184,7 @@ impl TxCircuitConfig { index, value, sign_verify, + keccak_table, _marker: PhantomData, } } @@ -232,7 +208,7 @@ impl TxCircuitConfig { } /// Tx Circuit for verifying transaction signatures -#[derive(Default)] +#[derive(Clone, Default)] pub struct TxCircuit { /// SignVerify chip pub sign_verify: SignVerifyChip, @@ -244,24 +220,33 @@ pub struct TxCircuit pub chain_id: u64, } -impl Circuit - for TxCircuit +impl + TxCircuit { - type Config = TxCircuitConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - TxCircuitConfig::new(meta) + /// Return a new TxCircuit + pub fn new( + aux_generator: Secp256k1Affine, + randomness: F, + chain_id: u64, + txs: Vec, + ) -> Self { + TxCircuit:: { + sign_verify: SignVerifyChip { + aux_generator, + window_size: 2, + _marker: PhantomData, + }, + randomness, + txs, + chain_id, + } } - fn synthesize( + /// Make the assignments to the TxCircuit + pub fn assign( &self, - config: Self::Config, - mut layouter: impl Layouter, + config: &TxCircuitConfig, + layouter: &mut impl Layouter, ) -> Result<(), Error> { assert!(self.txs.len() <= MAX_TXS); let sign_datas: Vec = self @@ -274,12 +259,9 @@ impl Circuit }) }) .try_collect()?; - let assigned_sig_verifs = self.sign_verify.assign( - &config.sign_verify, - &mut layouter, - self.randomness, - &sign_datas, - )?; + let assigned_sig_verifs = + self.sign_verify + .assign(&config.sign_verify, layouter, self.randomness, &sign_datas)?; layouter.assign_region( || "tx table", @@ -305,10 +287,7 @@ impl Circuit TxFieldTag::Nonce, rlc(tx.nonce.to_le_bytes(), self.randomness), ), - ( - TxFieldTag::Gas, - rlc(tx.gas_limit.to_le_bytes(), self.randomness), - ), + (TxFieldTag::Gas, F::from(tx.gas_limit.as_u64())), ( TxFieldTag::GasPrice, rlc(tx.gas_price.to_le_bytes(), self.randomness), @@ -333,6 +312,15 @@ impl Circuit TxFieldTag::CallDataLength, F::from(tx.call_data.0.len() as u64), ), + ( + TxFieldTag::CallDataGasCost, + F::from( + tx.call_data + .0 + .iter() + .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }), + ), + ), ( TxFieldTag::TxSignHash, *msg_hash_rlc_value.unwrap_or(&F::zero()), @@ -391,6 +379,45 @@ impl Circuit } } +impl Circuit + for TxCircuit +{ + type Config = TxCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let tx_table = TxTable { + tx_id: meta.advice_column(), + tag: meta.advice_column(), + index: meta.advice_column(), + value: meta.advice_column(), + }; + + let power_of_randomness = power_of_randomness_from_instance(meta); + let keccak_table = KeccakTable::construct(meta); + TxCircuitConfig::new(meta, power_of_randomness, tx_table, keccak_table) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + self.assign(&config, &mut layouter)?; + config.keccak_table.load( + &mut layouter, + keccak_inputs(&self.txs, self.chain_id)? + .iter() + .map(|b| b.as_slice()), + self.randomness, + ) + } +} + #[cfg(test)] mod tx_circuit_tests { use super::*; diff --git a/zkevm-circuits/src/tx_circuit/sign_verify.rs b/zkevm-circuits/src/tx_circuit/sign_verify.rs index 64c82061c4..4eb2bded7d 100644 --- a/zkevm-circuits/src/tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/tx_circuit/sign_verify.rs @@ -6,14 +6,16 @@ use crate::{ evm_circuit::util::{not, RandomLinearCombination, Word}, + table::KeccakTable, util::Expr, }; use ecc::{EccConfig, GeneralEccChip}; use ecdsa::ecdsa::{AssignedEcdsaSig, AssignedPublicKey, EcdsaChip}; +use eth_types::{self, Field}; use gadgets::is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}; -use group::{ff::Field, prime::PrimeCurveAffine, Curve}; +use group::{ff::Field as GroupField, prime::PrimeCurveAffine, Curve}; use halo2_proofs::{ - arithmetic::{BaseExt, Coordinates, CurveAffine, FieldExt}, + arithmetic::{BaseExt, Coordinates, CurveAffine}, circuit::{AssignedCell, Layouter, Region}, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, @@ -46,8 +48,8 @@ pub const VERIF_HEIGHT: usize = 1; /// Auxiliary Gadget to verify a that a message hash is signed by the public /// key corresponding to an Ethereum Address. -#[derive(Default, Debug)] -pub struct SignVerifyChip { +#[derive(Clone, Default, Debug)] +pub struct SignVerifyChip { /// Aux generator for EccChip pub aux_generator: Secp256k1Affine, /// Window size for EccChip @@ -56,11 +58,6 @@ pub struct SignVerifyChip { pub _marker: PhantomData, } -const KECCAK_IS_ENABLED: usize = 0; -const KECCAK_INPUT_RLC: usize = 1; -const KECCAK_INPUT_LEN: usize = 2; -const KECCAK_OUTPUT_RLC: usize = 3; - const NUMBER_OF_LIMBS: usize = 4; const BIT_LEN_LIMB: usize = 72; @@ -75,9 +72,37 @@ pub(crate) fn pk_bytes_swap_endianness(pk: &[T]) -> [T; 64] { pk_swap } +pub(crate) fn pk_bytes_le(pk: &Secp256k1Affine) -> [u8; 64] { + let pk_coord = Option::>::from(pk.coordinates()).expect("point is the identity"); + let mut pk_le = [0u8; 64]; + pk_coord + .x() + .write(&mut Cursor::new(&mut pk_le[..32])) + .expect("cannot write bytes to array"); + pk_coord + .y() + .write(&mut Cursor::new(&mut pk_le[32..])) + .expect("cannot write bytes to array"); + pk_le +} + +pub(crate) fn keccak_inputs(sigs: &[SignData]) -> Vec> { + let mut inputs = Vec::new(); + for sig in sigs { + let pk_le = pk_bytes_le(&sig.pk); + let pk_be = pk_bytes_swap_endianness(&pk_le); + inputs.push(pk_be.to_vec()); + } + // Padding signature + let pk_le = pk_bytes_le(&SignData::default().pk); + let pk_be = pk_bytes_swap_endianness(&pk_le); + inputs.push(pk_be.to_vec()); + inputs +} + /// Return an expression that builds an integer element in the field from the /// `bytes` in big endian. -fn int_from_bytes_be(bytes: &[Expression]) -> Expression { +fn int_from_bytes_be(bytes: &[Expression]) -> Expression { // sum_{i = 0}^{N} bytes[i] * 256^i let mut res = 0u8.expr(); for (i, byte) in bytes.iter().rev().enumerate() { @@ -88,7 +113,7 @@ fn int_from_bytes_be(bytes: &[Expression]) -> Expression { /// Constraint equality (using copy constraints) between `src` integer bytes and /// `dst` integer bytes. Then assign the `dst` values from `src`. -fn copy_integer_bytes_le( +fn copy_integer_bytes_le( region: &mut Region<'_, F>, name: &str, src: &[AssignedValue; 32], @@ -109,7 +134,7 @@ fn copy_integer_bytes_le( /// SignVerify Configuration #[derive(Debug, Clone)] -pub(crate) struct SignVerifyConfig { +pub(crate) struct SignVerifyConfig { q_enable: Selector, pk_hash: [Column; 32], // When address is 0, we disable the signature verification by using a dummy pk, msg_hash and @@ -129,13 +154,14 @@ pub(crate) struct SignVerifyConfig { power_of_randomness: [Expression; POW_RAND_SIZE], // [is_enabled, input_rlc, input_len, output_rlc] - keccak_table: [Column; 4], + keccak_table: KeccakTable, } -impl SignVerifyConfig { +impl SignVerifyConfig { pub(crate) fn new( meta: &mut ConstraintSystem, power_of_randomness: [Expression; POW_RAND_SIZE], + keccak_table: KeccakTable, ) -> Self { let q_enable = meta.complex_selector(); @@ -164,9 +190,6 @@ impl SignVerifyConfig { // is_not_padding == address != 0 let is_not_padding = not::expr(address_is_zero.is_zero_expression.clone()); - // lookup keccak table - let keccak_table = [(); 4].map(|_| meta.advice_column()); - // Ref. spec SignVerifyChip 1. Verify that keccak(pub_key_bytes) = pub_key_hash // by keccak table lookup, where pub_key_bytes is built from the pub_key // in the ecdsa_chip @@ -177,13 +200,11 @@ impl SignVerifyConfig { let mut table_map = Vec::new(); // Column 0: is_enabled - let keccak_is_enabled = - meta.query_advice(keccak_table[KECCAK_IS_ENABLED], Rotation::cur()); + let keccak_is_enabled = meta.query_advice(keccak_table.is_enabled, Rotation::cur()); table_map.push((selector.clone(), keccak_is_enabled)); // Column 1: input_rlc (pk_rlc) - let keccak_input_rlc = - meta.query_advice(keccak_table[KECCAK_INPUT_RLC], Rotation::cur()); + let keccak_input_rlc = meta.query_advice(keccak_table.input_rlc, Rotation::cur()); let pk_le: [Expression; 64] = pk .map(|coord| coord.map(|c| meta.query_advice(c, Rotation::cur()))) .iter() @@ -192,22 +213,25 @@ impl SignVerifyConfig { .collect::>>() .try_into() .expect("vector to array of size 64"); - let pk_be = pk_bytes_swap_endianness(&pk_le); + let mut pk_be = pk_bytes_swap_endianness(&pk_le); + pk_be.reverse(); let pk_rlc = RandomLinearCombination::random_linear_combine_expr(pk_be, &power_of_randomness); table_map.push((selector.clone() * pk_rlc, keccak_input_rlc)); // Column 2: input_len (64) - let keccak_input_len = - meta.query_advice(keccak_table[KECCAK_INPUT_LEN], Rotation::cur()); + let keccak_input_len = meta.query_advice(keccak_table.input_len, Rotation::cur()); table_map.push((selector.clone() * 64usize.expr(), keccak_input_len)); // Column 3: output_rlc (pk_hash_rlc) - let keccak_output_rlc = - meta.query_advice(keccak_table[KECCAK_OUTPUT_RLC], Rotation::cur()); - let pk_hash = pk_hash.map(|c| meta.query_advice(c, Rotation::cur())); - let pk_hash_rlc = - RandomLinearCombination::random_linear_combine_expr(pk_hash, &power_of_randomness); + let keccak_output_rlc = meta.query_advice(keccak_table.output_rlc, Rotation::cur()); + let mut pk_hash_rev = pk_hash.map(|c| meta.query_advice(c, Rotation::cur())); + pk_hash_rev.reverse(); // Ethereum decodes pk_hash into a Word as big endian, but + // `random_linear_combine_expr` expects LSB first. + let pk_hash_rlc = RandomLinearCombination::random_linear_combine_expr( + pk_hash_rev, + &power_of_randomness, + ); table_map.push((selector * pk_hash_rlc, keccak_output_rlc)); table_map @@ -270,7 +294,7 @@ pub(crate) struct KeccakAux { output: [u8; 32], } -impl SignVerifyConfig { +impl SignVerifyConfig { pub(crate) fn load_range(&self, layouter: &mut impl Layouter) -> Result<(), Error> { let bit_len_lookup = BIT_LEN_LIMB / NUMBER_OF_LOOKUP_LIMBS; let range_chip = RangeChip::::new(self.range_config.clone(), bit_len_lookup); @@ -280,67 +304,6 @@ impl SignVerifyConfig { Ok(()) } - fn keccak_assign_row( - &self, - region: &mut Region<'_, F>, - offset: usize, - is_enabled: F, - input_rlc: F, - input_len: usize, - output_rlc: F, - ) -> Result<(), Error> { - for (name, column, value) in &[ - ("is_enabled", self.keccak_table[0], is_enabled), - ("input_rlc", self.keccak_table[1], input_rlc), - ("input_len", self.keccak_table[2], F::from(input_len as u64)), - ("output_rlc", self.keccak_table[3], output_rlc), - ] { - region.assign_advice( - || format!("Keccak table assign {} {}", name, offset), - *column, - offset, - || Ok(*value), - )?; - } - Ok(()) - } - - pub(crate) fn load_keccak( - &self, - layouter: &mut impl Layouter, - auxs: Vec, - randomness: F, - ) -> Result<(), Error> { - layouter.assign_region( - || "keccak table", - |mut region| { - let mut offset = 0; - - // All zero row to allow simulating a disabled lookup. - self.keccak_assign_row(&mut region, offset, F::zero(), F::zero(), 0, F::zero())?; - offset += 1; - - for aux in &auxs { - let KeccakAux { input, output } = aux; - let input_rlc = - RandomLinearCombination::random_linear_combine(*input, randomness); - let output_rlc = Word::random_linear_combine(*output, randomness); - self.keccak_assign_row( - &mut region, - offset, - F::one(), - input_rlc, - input.len(), - output_rlc, - )?; - offset += 1; - } - Ok(()) - }, - )?; - Ok(()) - } - pub(crate) fn ecc_chip_config(&self) -> EccConfig { EccConfig::new(self.range_config.clone(), self.main_gate_config.clone()) } @@ -350,20 +313,20 @@ impl SignVerifyConfig { } } -pub(crate) struct AssignedECDSA { +pub(crate) struct AssignedECDSA { pk_x_le: [AssignedValue; 32], pk_y_le: [AssignedValue; 32], msg_hash_le: [AssignedValue; 32], } #[derive(Debug)] -pub(crate) struct AssignedSignatureVerify { +pub(crate) struct AssignedSignatureVerify { pub(crate) address: AssignedCell, pub(crate) msg_hash_rlc: AssignedCell, } // Returns assigned constants [256^1, 256^2, .., 256^{n-1}] -fn assign_pows_256( +fn assign_pows_256( ctx: &mut RegionCtx<'_, '_, F>, main_gate: &MainGate, n: usize, @@ -378,7 +341,7 @@ fn assign_pows_256( // Return an array of bytes that corresponds to the little endian representation // of the integer, adding the constraints to verify the correctness of the // conversion (byte range check included). -fn integer_to_bytes_le( +fn integer_to_bytes_le( ctx: &mut RegionCtx<'_, '_, F>, main_gate: &MainGate, range_chip: &RangeChip, @@ -412,7 +375,7 @@ fn integer_to_bytes_le( /// Helper structure pass around references to all the chips required for an /// ECDSA veficication. -struct ChipsRef<'a, F: FieldExt, const NUMBER_OF_LIMBS: usize, const BIT_LEN_LIMB: usize> { +struct ChipsRef<'a, F: Field, const NUMBER_OF_LIMBS: usize, const BIT_LEN_LIMB: usize> { main_gate: &'a MainGate, range_chip: &'a RangeChip, ecc_chip: &'a GeneralEccChip, @@ -420,7 +383,7 @@ struct ChipsRef<'a, F: FieldExt, const NUMBER_OF_LIMBS: usize, const BIT_LEN_LIM ecdsa_chip: &'a EcdsaChip, } -impl SignVerifyChip { +impl SignVerifyChip { fn assign_aux( &self, region: &mut Region<'_, F>, @@ -556,19 +519,8 @@ impl SignVerifyChip { )?; // Assign pk - let pk_coord = - Option::>::from(pk.coordinates()).expect("point is the identity"); - let mut pk_x_le = [0u8; 32]; - let mut pk_y_le = [0u8; 32]; - pk_coord - .x() - .write(&mut Cursor::new(&mut pk_x_le[..])) - .expect("cannot write bytes to array"); - pk_coord - .y() - .write(&mut Cursor::new(&mut pk_y_le[..])) - .expect("cannot write bytes to array"); - for (i, byte) in pk_x_le.iter().enumerate() { + let pk_le = pk_bytes_le(&pk); + for (i, byte) in pk_le[..32].iter().enumerate() { region.assign_advice( || format!("pk x byte {}", i), config.pk[0][i], @@ -576,7 +528,7 @@ impl SignVerifyChip { || Ok(F::from(*byte as u64)), )?; } - for (i, byte) in pk_y_le.iter().enumerate() { + for (i, byte) in pk_le[32..].iter().enumerate() { region.assign_advice( || format!("pk y byte {}", i), config.pk[1][i], @@ -585,9 +537,6 @@ impl SignVerifyChip { )?; } - let mut pk_le = [0u8; 64]; - pk_le[..32].copy_from_slice(&pk_x_le); - pk_le[32..].copy_from_slice(&pk_y_le); let pk_be = pk_bytes_swap_endianness(&pk_le); let mut keccak = Keccak::default(); keccak.update(&pk_be); @@ -724,7 +673,6 @@ impl SignVerifyChip { }, )?; - config.load_keccak(layouter, keccak_auxs, randomness)?; config.load_range(layouter)?; Ok(assigned_sig_verifs) @@ -792,7 +740,7 @@ impl Default for SignData { } } -fn pub_key_hash_to_address(pk_hash: &[u8]) -> F { +fn pub_key_hash_to_address(pk_hash: &[u8]) -> F { pk_hash[32 - 20..] .iter() .fold(F::zero(), |acc, b| acc * F::from(256) + F::from(*b as u64)) @@ -801,6 +749,7 @@ fn pub_key_hash_to_address(pk_hash: &[u8]) -> F { #[cfg(test)] mod sign_verify_tests { use super::*; + use crate::util::power_of_randomness_from_instance; use group::Group; use halo2_proofs::{ circuit::SimpleFloorPlanner, dev::MockProver, pairing::bn256::Fr, plonk::Circuit, @@ -810,43 +759,28 @@ mod sign_verify_tests { use rand_xorshift::XorShiftRng; #[derive(Clone, Debug)] - struct TestCircuitSignVerifyConfig { + struct TestCircuitSignVerifyConfig { sign_verify: SignVerifyConfig, } - impl TestCircuitSignVerifyConfig { + impl TestCircuitSignVerifyConfig { pub(crate) fn new(meta: &mut ConstraintSystem) -> Self { - // This gate is used just to get the array of expressions from the power of - // randomness instance column, so that later on we don't need to query - // columns everywhere, and can pass the power of randomness array - // expression everywhere. The gate itself doesn't add any constraints. - let power_of_randomness = { - let columns = [(); POW_RAND_SIZE].map(|_| meta.instance_column()); - let mut power_of_randomness = None; - - meta.create_gate("power of randomness", |meta| { - power_of_randomness = - Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); + let power_of_randomness = power_of_randomness_from_instance(meta); + let keccak_table = KeccakTable::construct(meta); - [0.expr()] - }); - - power_of_randomness.unwrap() - }; - - let sign_verify = SignVerifyConfig::new(meta, power_of_randomness); + let sign_verify = SignVerifyConfig::new(meta, power_of_randomness, keccak_table); TestCircuitSignVerifyConfig { sign_verify } } } #[derive(Default)] - struct TestCircuitSignVerify { + struct TestCircuitSignVerify { sign_verify: SignVerifyChip, randomness: F, signatures: Vec, } - impl Circuit for TestCircuitSignVerify { + impl Circuit for TestCircuitSignVerify { type Config = TestCircuitSignVerifyConfig; type FloorPlanner = SimpleFloorPlanner; @@ -869,11 +803,16 @@ mod sign_verify_tests { self.randomness, &self.signatures, )?; + config.sign_verify.keccak_table.load( + &mut layouter, + keccak_inputs(&self.signatures).iter().map(|b| b.as_slice()), + self.randomness, + )?; Ok(()) } } - fn run(k: u32, signatures: Vec) { + fn run(k: u32, signatures: Vec) { let mut rng = XorShiftRng::seed_from_u64(2); let aux_generator = ::CurveExt::random(&mut rng).to_affine(); diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index 43fdd6564b..5a650be787 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -1,8 +1,36 @@ //! Common utility traits and functions. use eth_types::Field; +use halo2_proofs::{ + plonk::{ConstraintSystem, Expression}, + poly::Rotation, +}; pub use gadgets::util::Expr; pub(crate) fn random_linear_combine_word(bytes: [u8; 32], randomness: F) -> F { crate::evm_circuit::util::Word::random_linear_combine(bytes, randomness) } + +/// Query N instances at current rotation and return their expressions. This +/// function is used to get the power of randomness (passed as +/// instances) in our tests. +pub fn power_of_randomness_from_instance( + meta: &mut ConstraintSystem, +) -> [Expression; N] { + // This gate is used just to get the array of expressions from the power of + // randomness instance column, so that later on we don't need to query + // columns everywhere, and can pass the power of randomness array + // expression everywhere. The gate itself doesn't add any constraints. + + let columns = [(); N].map(|_| meta.instance_column()); + let mut power_of_randomness = None; + + meta.create_gate("power of randomness from instance", |meta| { + power_of_randomness = + Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); + + [0.expr()] + }); + + power_of_randomness.unwrap() +}