diff --git a/Cargo.lock b/Cargo.lock index ccfb1bc70..8ae37fcef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,28 +300,16 @@ dependencies = [ "radium 0.3.0", ] -[[package]] -name = "bitvec" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" -dependencies = [ - "funty 1.2.0", - "radium 0.6.2", - "tap", - "wyz 0.4.0", -] - [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "funty 2.0.0", + "funty", "radium 0.7.0", "tap", - "wyz 0.5.1", + "wyz", ] [[package]] @@ -2159,11 +2147,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "funty" -version = "1.2.0" -source = "git+https://github.com/ferrilab/funty/?rev=7ef0d890fbcd8b3def1635ac1a877fc298488446#7ef0d890fbcd8b3def1635ac1a877fc298488446" - [[package]] name = "funty" version = "2.0.0" @@ -2440,12 +2423,11 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "halo2-mpt-circuits" version = "0.1.0" -source = "git+https://github.com/scroll-tech/mpt-circuit.git?branch=scroll-dev-0111-halo2-upgrade#1aa8aa43289d9f29c2c8dbd5b2384f56bfd90fff" +source = "git+https://github.com/scroll-tech/mpt-circuit.git?branch=scroll-dev-0129#631d71fd4dbe070fee34416efbfd12241d78c62f" dependencies = [ "halo2_proofs", "hex", "lazy_static", - "log", "num-bigint", "poseidon-circuit", "rand 0.8.5", @@ -3908,9 +3890,9 @@ dependencies = [ [[package]] name = "poseidon-circuit" version = "0.1.0" -source = "git+https://github.com/scroll-tech/poseidon-circuit.git?branch=scroll-dev-0111-halo2-upgrade#b6761f8fc778c9851d874aa21fb3c4271071b717" +source = "git+https://github.com/scroll-tech/poseidon-circuit.git?branch=scroll-dev-0201#ceda6061aa5e5d09ca5dfd5d90c581eb54077fc0" dependencies = [ - "bitvec 0.22.3", + "bitvec 1.0.1", "halo2_proofs", "lazy_static", "thiserror", @@ -4070,12 +4052,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" -[[package]] -name = "radium" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" - [[package]] name = "radium" version = "0.7.0" @@ -6422,15 +6398,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wyz" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" -dependencies = [ - "tap", -] - [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index fe38ed5e3..f6dd3565d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,6 @@ members = [ "mock", ] -[patch.crates-io] -# temporary solution to funty@1.2.0 being yanked, tracking issue: https://github.com/ferrilab/funty/issues/7 -funty = { git = "https://github.com/ferrilab/funty/", rev = "7ef0d890fbcd8b3def1635ac1a877fc298488446" } - [patch."https://github.com/privacy-scaling-explorations/halo2.git"] halo2_proofs = { git = "https://github.com/scroll-tech/halo2.git", branch = "scroll-dev-0220" } diff --git a/eth-types/Cargo.toml b/eth-types/Cargo.toml index 79d7cc432..a374bb9ba 100644 --- a/eth-types/Cargo.toml +++ b/eth-types/Cargo.toml @@ -24,7 +24,7 @@ num = "0.4" num-bigint = { version = "0.4" } strum_macros = "0.24" strum = "0.24" -mpt-circuits = { package = "halo2-mpt-circuits", git = "https://github.com/scroll-tech/mpt-circuit.git", branch = "scroll-dev-0111-halo2-upgrade" } +mpt-circuits = { package = "halo2-mpt-circuits", git = "https://github.com/scroll-tech/mpt-circuit.git", branch = "scroll-dev-0129" } [features] default = ["warn-unimplemented"] warn-unimplemented = [] diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index befb78ee8..82780dafa 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -61,3 +61,4 @@ zktrie = [] enable-sign-verify = [] codehash = [] reject-eip2718 = [] +poseidon-codehash = [] diff --git a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs index fd5322177..a00e4e882 100644 --- a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs +++ b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs @@ -58,3 +58,15 @@ pub fn unroll_with_codehash(code_hash: U256, bytes: Vec) -> Unroll } UnrolledBytecode { bytes, rows } } + +/// re-export bytes wrapped in hash field +pub use super::circuit::to_poseidon_hash::HASHBLOCK_BYTES_IN_FIELD; +use crate::table::PoseidonTable; + +/// Apply default constants in mod +pub fn unroll_to_hash_input_default( + code: impl ExactSizeIterator, +) -> Vec<[F; PoseidonTable::INPUT_WIDTH]> { + use super::circuit::to_poseidon_hash::unroll_to_hash_input; + unroll_to_hash_input::(code) +} diff --git a/zkevm-circuits/src/bytecode_circuit/circuit.rs b/zkevm-circuits/src/bytecode_circuit/circuit.rs index 7740163b4..d13e3539a 100644 --- a/zkevm-circuits/src/bytecode_circuit/circuit.rs +++ b/zkevm-circuits/src/bytecode_circuit/circuit.rs @@ -19,6 +19,9 @@ use super::{ param::PUSH_TABLE_WIDTH, }; +/// An extended circuit for binding with poseidon +pub mod to_poseidon_hash; + #[cfg(feature = "onephase")] use halo2_proofs::plonk::FirstPhase as SecondPhase; #[cfg(not(feature = "onephase"))] @@ -755,6 +758,12 @@ impl BytecodeCircuit { } impl SubCircuit for BytecodeCircuit { + #[cfg(feature = "poseidon-codehash")] + type Config = to_poseidon_hash::ToHashBlockCircuitConfig< + F, + { to_poseidon_hash::HASHBLOCK_BYTES_IN_FIELD }, + >; + #[cfg(not(feature = "poseidon-codehash"))] type Config = BytecodeCircuitConfig; fn new_from_block(block: &witness::Block) -> Self { diff --git a/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs b/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs new file mode 100644 index 000000000..3baffb81f --- /dev/null +++ b/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs @@ -0,0 +1,752 @@ +use crate::{ + evm_circuit::util::{and, constraint_builder::BaseConstraintBuilder, not, or, rlc, select}, + table::{BytecodeFieldTag, DynamicTableColumns, KeccakTable, PoseidonTable}, + util::{Challenges, Expr, SubCircuitConfig}, +}; +use eth_types::Field; +use gadgets::is_zero::IsZeroChip; +use halo2_proofs::{ + circuit::{Layouter, Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, VirtualCells}, + poly::Rotation, +}; +use keccak256::EMPTY_HASH_LE; +use log::trace; +use std::vec; + +use super::super::bytecode_unroller::{BytecodeRow, UnrolledBytecode}; +use super::{BytecodeCircuitConfig, BytecodeCircuitConfigArgs}; + +/// specify byte in field for encoding bytecode +pub const HASHBLOCK_BYTES_IN_FIELD: usize = 16; + +#[derive(Clone, Debug)] +/// Bytecode circuit (for hash block) configuration +/// basically the BytecodeCircuit include two parts: +/// a) marking and proving bytcodetable for bytecodes +/// b) mapping the bytes to keccaktable +/// and we re-useing the a) part and put additional +/// controlling cols to enable lookup from poseidon table +pub struct ToHashBlockCircuitConfig { + base_conf: BytecodeCircuitConfig, + control_length: Column, + field_input: Column, + bytes_in_field_index: Column, + bytes_in_field_inv: Column, + is_field_border: Column, + padding_shift: Column, + field_index: Column, + field_index_inv: Column, + // External table + pub(crate) poseidon_table: PoseidonTable, + pub(crate) keccak_table: KeccakTable, +} + +impl ToHashBlockCircuitConfig { + pub(crate) fn configure( + meta: &mut ConstraintSystem, + base_conf: BytecodeCircuitConfig, + poseidon_table: PoseidonTable, + ) -> Self { + let base_conf_cl = base_conf.clone(); + let bytecode_table = base_conf.bytecode_table; + //TODO: does this col still used for storing poseidon hash? + let code_hash = bytecode_table.code_hash; + + let q_enable = base_conf.q_enable; //from 0 to last avaliable row + + let control_length = meta.advice_column(); + let field_input = meta.advice_column(); + let bytes_in_field_index = meta.advice_column(); + let bytes_in_field_inv = meta.advice_column(); + let is_field_border = meta.advice_column(); + let padding_shift = meta.advice_column(); + let field_index = meta.advice_column(); + let field_index_inv = meta.advice_column(); + + // some composited selectors are grepped from base + // Does the current row have bytecode field tag == Byte? + let is_row_tag_byte = + |meta: &mut VirtualCells| meta.query_advice(bytecode_table.tag, Rotation::cur()); + + // Does the current row have bytecode field tag == Length (Now header)? + let is_row_tag_length = |meta: &mut VirtualCells| { + not::expr(meta.query_advice(bytecode_table.tag, Rotation::cur())) + }; + + // Does the current row is final of a bytecode + let is_byte_to_header = |meta: &mut VirtualCells| { + and::expr(vec![ + meta.query_advice(bytecode_table.tag, Rotation::cur()), + not::expr(meta.query_advice(bytecode_table.tag, Rotation::next())), + ]) + }; + + meta.create_gate("always", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_boolean( + "is_field_border", + meta.query_advice(is_field_border, Rotation::cur()), + ); + + // Conditions: + // - always + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }); + + // current byte_in_field index is not the last one: i.e BYTES_IN_FIELD + let q_byte_in_field_not_last = |meta: &mut VirtualCells| { + (BYTES_IN_FIELD.expr() - meta.query_advice(bytes_in_field_index, Rotation::cur())) + * meta.query_advice(bytes_in_field_inv, Rotation::cur()) + }; + + // current field index is not the last one of the input: i.e + // PoseidonTable::INPUT_WIDTH + let q_field_not_last = |meta: &mut VirtualCells| { + (PoseidonTable::INPUT_WIDTH.expr() - meta.query_advice(field_index, Rotation::cur())) + * meta.query_advice(field_index_inv, Rotation::cur()) + }; + + meta.create_gate("field byte cycling", |meta| { + let mut cb = BaseConstraintBuilder::default(); + cb.condition(BYTES_IN_FIELD.expr() - meta.query_advice(bytes_in_field_index, Rotation::cur()), |cb|{ + cb.require_equal("q_byte_in_field_not_last = 1 except for BYTES_IN_FIELD", + 1.expr(), + q_byte_in_field_not_last(meta), + ) + }); + + cb.require_equal("is_field_border := !q_byte_in_field_not_last or is_byte_to_header", + meta.query_advice(is_field_border, Rotation::cur()), + or::expr(vec![ + not::expr(q_byte_in_field_not_last(meta)), + is_byte_to_header(meta), + ]), + ); + + cb.require_equal( + "byte_in_field_index := 1 if is_field_border_prev else (byte_in_field_index_prev + 1)", + meta.query_advice(bytes_in_field_index, Rotation::cur()), + select::expr( + meta.query_advice(is_field_border, Rotation::prev()), + 1.expr(), + meta.query_advice(bytes_in_field_index, Rotation::prev()) + 1.expr(), + ) + ); + + let shifted_byte = meta.query_advice(bytecode_table.value, Rotation::cur()) * + meta.query_advice(padding_shift, Rotation::cur()); + + cb.require_equal( + "field_input = byte * padding_shift if is_field_border_prev else field_input_prev + byte * padding_shift", + meta.query_advice(field_input, Rotation::cur()), + select::expr( + meta.query_advice(is_field_border, Rotation::prev()), + shifted_byte.clone(), + meta.query_advice(field_input, Rotation::prev()) + shifted_byte + ), + ); + + cb.condition(not::expr(meta.query_advice(is_field_border, Rotation::prev())), |cb|{ + + cb.require_equal( + "if field_continue (not is_field_border_prev) padding_shift := padding_shift_prev / 256", + meta.query_advice(padding_shift, Rotation::cur()) * 256.expr(), + meta.query_advice(padding_shift, Rotation::prev()), + ); + }); + + cb.condition(not::expr(q_byte_in_field_not_last(meta)), |cb|{ + + cb.require_equal( + "if !q_byte_in_field_not_last padding_shift := 1", + meta.query_advice(padding_shift, Rotation::cur()), + 1.expr(), + ); + }); + + // Conditions: + // - Byte tag + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + is_row_tag_byte(meta), + ])) + }); + + meta.create_gate("field input cycling", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.condition(PoseidonTable::INPUT_WIDTH.expr() - meta.query_advice(field_index, Rotation::cur()), |cb|{ + cb.require_equal("q_field_not_last = 1 except for PoseidonTable::INPUT_WIDTH", + 1.expr(), + q_field_not_last(meta), + ) + }); + + let q_input_continue = + (PoseidonTable::INPUT_WIDTH.expr() - meta.query_advice(field_index, Rotation::prev())) + * meta.query_advice(field_index_inv, Rotation::prev()); + + let q_input_border_last = and::expr([ + meta.query_advice(is_field_border, Rotation::prev()), + not::expr(q_input_continue), + ]); + + cb.require_equal( + "control_length := base.length - bytecode_table.index if q_input_border_last else control_length_prev", + meta.query_advice(control_length, Rotation::cur()), + select::expr( + q_input_border_last.clone(), + meta.query_advice(base_conf.length, Rotation::cur()) - + meta.query_advice(bytecode_table.index, Rotation::cur()), + meta.query_advice(control_length, Rotation::prev()) + ), + ); + + cb.condition(q_input_border_last.clone(), |cb|{ + cb.require_equal( + "field_index = 1 on q_input_border_last", + 1.expr(), + meta.query_advice(field_index, Rotation::cur()) + ) + }); + + cb.condition(not::expr(q_input_border_last), |cb|{ + cb.require_equal( + "field_index := if is_field_border_last then field_index_prev + 1 else field_index_prev", + meta.query_advice(field_index, Rotation::cur()), + select::expr( + meta.query_advice(is_field_border, Rotation::prev()), + meta.query_advice(field_index, Rotation::prev()) + 1.expr(), + meta.query_advice(field_index, Rotation::prev()), + ), + ) + }); + + // Conditions: + // - Byte tag + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + is_row_tag_byte(meta), + ])) + }); + + meta.create_gate("start of bytecode", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_zero( + "enforce not is_field_border", + meta.query_advice(is_field_border, Rotation::cur()), + ); + + // enforce the next bytes_in_field_index is 1 + cb.require_zero( + "enforce bytes_in_field_index is 0", + meta.query_advice(bytes_in_field_index, Rotation::cur()), + ); + + // enforce the next field_index is 1 + cb.require_equal( + "enforce field_index is 1", + 1.expr(), + meta.query_advice(field_index, Rotation::cur()), + ); + + // the next field_index is code_length (the starting of ctrl_length) + cb.require_equal( + "control_length := base.length - bytecode_table.index", + meta.query_advice(control_length, Rotation::cur()), + meta.query_advice(base_conf.length, Rotation::cur()) + - meta.query_advice(bytecode_table.index, Rotation::cur()), + ); + + // Conditions: + // - Length (Now Header) tag + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + is_row_tag_length(meta), + ])) + }); + + /* not need + meta.create_gate("padding", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_zero( + "enforce not is_field_border", + meta.query_advice(is_field_border, Rotation::cur()), + ); + // Conditions: + // - Padding + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(base_conf.padding, Rotation::cur()), + ])) + }); + */ + + let lookup_columns = [code_hash, field_input, control_length]; + let pick_hash_tbl_cols = |inp_i| { + let cols = poseidon_table.columns(); + [cols[0], cols[inp_i + 1], cols[cols.len() - 2]] + }; + + // we use a special selection exp for only 2 indexs + let field_selector = |meta: &mut VirtualCells| { + let field_index = meta.query_advice(field_index, Rotation::cur()) - 1.expr(); + [1.expr() - field_index.clone(), field_index] + }; + + // poseidon lookup: + // * PoseidonTable::INPUT_WIDTH lookups for each input field + // * PoseidonTable::INPUT_WIDTH -1 lookups for the padded zero input + // so we have 2*PoseidonTable::INPUT_WIDTH -1 lookups + for i in 0..PoseidonTable::INPUT_WIDTH { + meta.lookup_any("poseidon input", |meta| { + // Conditions: + // - On the row at **field border** (`is_field_border == 1`) + // - the field_index match current i + let enable = and::expr(vec![ + meta.query_advice(is_field_border, Rotation::cur()), + field_selector(meta)[i].clone(), + ]); + let mut constraints = Vec::new(); /*vec![( + enable.clone(), + meta.query_advice(keccak_table.is_enabled, Rotation::cur()), + )];*/ + for (l_col, tbl_col) in lookup_columns.into_iter().zip(pick_hash_tbl_cols(i)) { + constraints.push(( + enable.clone() * meta.query_advice(l_col, Rotation::cur()), + meta.query_advice(tbl_col, Rotation::cur()), + )) + } + constraints + }); + } + + //the canonical form should be `for i in 1..PoseidonTable::INPUT_WIDTH{...}` + meta.lookup_any("poseidon input padding zero for final", |meta| { + // Conditions: + // - On the row with the last byte (`is_byte_to_header == 1`) + // - Not padding + // - the (0 begin) field_index is 1 (for we have only 2 input field) + let enable = and::expr(vec![ + is_byte_to_header(meta), + 2.expr() - meta.query_advice(field_index, Rotation::cur()), + ]); + let mut constraints = Vec::new(); + for (l_exp, tbl_col) in [ + meta.query_advice(code_hash, Rotation::cur()), + 0.expr(), + meta.query_advice(control_length, Rotation::cur()), + ] + .into_iter() + .zip(pick_hash_tbl_cols(1)) + { + constraints.push(( + enable.clone() * l_exp, + meta.query_advice(tbl_col, Rotation::cur()), + )) + } + constraints + }); + + // re-export keccak table in extended config + let keccak_table = base_conf.keccak_table.clone(); + Self { + base_conf: base_conf_cl, + control_length, + field_input, + bytes_in_field_index, + bytes_in_field_inv, + is_field_border, + padding_shift, + field_index, + field_index_inv, + poseidon_table, + keccak_table, + } + } + + pub(crate) fn assign( + &self, + layouter: &mut impl Layouter, + size: usize, + witness: &[UnrolledBytecode], + challenges: &Challenges>, + ) -> Result<(), Error> { + self.assign_internal(layouter, size, witness, challenges, true) + } + + pub(crate) fn assign_internal( + &self, + layouter: &mut impl Layouter, + size: usize, + witness: &[UnrolledBytecode], + challenges: &Challenges>, + fail_fast: bool, + ) -> Result<(), Error> { + let base_conf = &self.base_conf; + let push_data_left_is_zero_chip = + IsZeroChip::construct(base_conf.push_data_left_is_zero.clone()); + + // Subtract the unusable rows from the size + assert!(size > base_conf.minimum_rows); + let last_row_offset = size - base_conf.minimum_rows + 1; + + trace!( + "size: {}, minimum_rows: {}, last_row_offset:{}", + size, + base_conf.minimum_rows, + last_row_offset + ); + + let empty_hash = challenges + .evm_word() + .map(|challenge| rlc::value(EMPTY_HASH_LE.as_ref(), challenge)); + + layouter.assign_region( + || "assign bytecode with poseidon hash extension", + |mut region| { + let mut offset = 0; + let mut row_input = F::zero(); + for bytecode in witness.iter() { + let bytecode_offset_begin = offset; + base_conf.assign_bytecode( + &mut region, + bytecode, + challenges, + &push_data_left_is_zero_chip, + empty_hash, + &mut offset, + last_row_offset, + fail_fast, + )?; + + for (idx, row) in bytecode.rows.iter().enumerate() { + // if the base_conf's assignment not fail fast, + // we also avoid the failure of "NotEnoughRowsAvailable" + // in prover creation (so bytecode_incomplete test could pass) + let offset = bytecode_offset_begin + idx; + if offset <= last_row_offset { + row_input = self.assign_extended_row( + &mut region, + offset, + row, + row_input, + bytecode.bytes.len(), + )?; + } + } + } + + // Padding + for idx in offset..=last_row_offset { + base_conf.set_padding_row( + &mut region, + &push_data_left_is_zero_chip, + empty_hash, + idx, + last_row_offset, + )?; + self.set_header_row(&mut region, 0, idx)?; + } + Ok(()) + }, + ) + } + + /// Assign a header row (at padding or start line of each bytecodes) + fn set_header_row( + &self, + region: &mut Region, + code_length: usize, + offset: usize, + ) -> Result<(), Error> { + for (name, column) in [ + ("control length header", self.control_length), + ("field input header", self.field_input), + ("bytes in field header", self.bytes_in_field_index), + ("bytes in field inv header", self.bytes_in_field_inv), + ("field border header", self.is_field_border), + ("padding shift header", self.padding_shift), + ("field index header", self.field_index), + ("field index inv header", self.field_index_inv), + ] { + region.assign_advice( + || format!("assign {name} {offset}"), + column, + offset, + || Value::known(F::zero()), + )?; + } + + for (name, column, val) in [ + ( + "control length header", + self.control_length, + F::from(code_length as u64), + ), + ( + "padding shift header", + self.padding_shift, + F::from(256 as u64).pow_vartime([BYTES_IN_FIELD as u64]), + ), + ("field index header", self.field_index, F::one()), + ] { + region.assign_advice( + || format!("assign {name} {offset}"), + column, + offset, + || Value::known(val), + )?; + } + + Ok(()) + } + + /// Assign a row, all of the value is determinded by current bytes progress + /// and the hash width + fn assign_extended_row( + &self, + region: &mut Region, + offset: usize, + row: &BytecodeRow, + input_prev: F, + code_length: usize, + ) -> Result { + let code_index = row.index.get_lower_128() as usize; + let tag = row.tag.get_lower_32(); + let row_input = match tag { + i if i == BytecodeFieldTag::Byte as u32 => { + let block_size = BYTES_IN_FIELD * PoseidonTable::INPUT_WIDTH; + + let prog_block = code_index / block_size; + let control_length = code_length - prog_block * block_size; + let bytes_in_field_index = (code_index + 1) % BYTES_IN_FIELD; + let field_border = bytes_in_field_index == 0; + let bytes_in_field_index = if field_border { + BYTES_IN_FIELD + } else { + bytes_in_field_index + }; + let bytes_in_field_index_inv_f = + F::from((BYTES_IN_FIELD - bytes_in_field_index) as u64) + .invert() + .unwrap_or(F::zero()); + let padding_shift_f = F::from(256 as u64) + .pow_vartime([(BYTES_IN_FIELD - bytes_in_field_index) as u64]); + let input_f = row.value * padding_shift_f + input_prev; + // relax field_border for code end + let field_border = field_border || code_index + 1 == code_length; + + let field_index = (code_index % block_size) / BYTES_IN_FIELD + 1; + let field_index_inv_f = F::from((PoseidonTable::INPUT_WIDTH - field_index) as u64) + .invert() + .unwrap_or(F::zero()); + + trace!( + "bytecode_extend.set_row({}): cl:{} inp:{:?} bif:{} br:{} pd:{:x} fi:{}", + offset, + control_length, + input_f, + bytes_in_field_index, + field_border, + padding_shift_f.get_lower_128(), + field_index, + ); + + for (tip, column, val) in [ + ( + "control length", + self.control_length, + F::from(control_length as u64), + ), + ("field input", self.field_input, input_f), + ( + "bytes in field", + self.bytes_in_field_index, + F::from(bytes_in_field_index as u64), + ), + ( + "bytes in field inv", + self.bytes_in_field_inv, + bytes_in_field_index_inv_f, + ), + ( + "field border", + self.is_field_border, + F::from(field_border as u64), + ), + ("padding shift", self.padding_shift, padding_shift_f), + ("field index", self.field_index, F::from(field_index as u64)), + ("field index inv", self.field_index_inv, field_index_inv_f), + ] { + region.assign_advice( + || format!("assign {} {}", tip, offset), + column, + offset, + || Value::known(val), + )?; + } + + if field_border { + F::zero() + } else { + input_f + } + } + i if i == BytecodeFieldTag::Header as u32 => { + trace!("bytecode_extend.set_header_row({offset}): cl:{code_length}",); + self.set_header_row(region, code_length, offset)?; + F::zero() + } + _ => unreachable!("unexpected tag number"), + }; + + Ok(row_input) + } + + /// re-export load fixed tables + pub(crate) fn load_aux_tables(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + self.base_conf.load_aux_tables(layouter) + } +} + +/// Circuit configuration arguments +pub struct ToHashBlockBytecodeCircuitConfigArgs { + /// arg for base config + pub base_args: BytecodeCircuitConfigArgs, + /// BytecodeTable + pub poseidon_table: PoseidonTable, +} + +impl SubCircuitConfig for ToHashBlockCircuitConfig { + type ConfigArgs = ToHashBlockBytecodeCircuitConfigArgs; + + /// Return a new BytecodeCircuitConfig + fn new( + meta: &mut ConstraintSystem, + Self::ConfigArgs { + base_args, + poseidon_table, + }: Self::ConfigArgs, + ) -> Self { + let base_conf = BytecodeCircuitConfig::new(meta, base_args); + Self::configure(meta, base_conf, poseidon_table) + } +} + +/// Get unrolled hash inputs as inputs to hash circuit +pub fn unroll_to_hash_input( + code: impl ExactSizeIterator, +) -> Vec<[F; INPUT_LEN]> { + use eth_types::U256; + + let fl_cnt = code.len() / BYTES_IN_FIELD; + let fl_cnt = if code.len() % BYTES_IN_FIELD != 0 { + fl_cnt + 1 + } else { + fl_cnt + }; + + let (msgs, _) = code + .chain(std::iter::repeat(0)) + .take(fl_cnt * BYTES_IN_FIELD) + .fold((Vec::new(), Vec::new()), |(mut msgs, mut cache), bt| { + cache.push(bt); + if cache.len() == BYTES_IN_FIELD { + let mut buf: [u8; 64] = [0; 64]; + U256::from_big_endian(&cache).to_little_endian(&mut buf[0..32]); + msgs.push(F::from_bytes_wide(&buf)); + cache.clear(); + } + (msgs, cache) + }); + + let input_cnt = msgs.len() / INPUT_LEN; + let input_cnt = if msgs.len() % INPUT_LEN != 0 { + input_cnt + 1 + } else { + input_cnt + }; + if input_cnt == 0 { + return Vec::new(); + } + + let (mut inputs, last) = msgs + .into_iter() + .chain(std::iter::repeat(F::zero())) + .take(input_cnt * INPUT_LEN) + .fold( + (Vec::new(), [None; INPUT_LEN]), + |(mut msgs, mut v_arr), f| { + if let Some(v) = v_arr.iter_mut().find(|v| v.is_none()) { + v.replace(f); + (msgs, v_arr) + } else { + msgs.push(v_arr.map(|v| v.unwrap())); + let mut v_arr = [None; INPUT_LEN]; + v_arr[0].replace(f); + (msgs, v_arr) + } + }, + ); + + inputs.push(last.map(|v| v.unwrap())); + inputs +} + +/// test module +#[cfg(any(feature = "test", test))] +#[cfg(test)] +pub mod tests { + use super::*; + //use super::super::tests::get_randomness; + //use crate::{bytecode_circuit::dev::test_bytecode_circuit_unrolled, + // util::DEFAULT_RAND}; use eth_types::Bytecode; + use halo2_proofs::halo2curves::bn256::Fr; + + #[test] + fn bytecode_unrolling_to_input() { + let bt = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + + let out = unroll_to_hash_input::(bt.iter().copied().take(6)); + assert_eq!(out.len(), 1); + assert_eq!(out[0][0], Fr::from(0x01020304)); + assert_eq!(out[0][1], Fr::from(0x05060000)); + + let out = unroll_to_hash_input::(bt.iter().copied().take(9)); + assert_eq!(out.len(), 2); + assert_eq!(out[0][0], Fr::from(0x010203)); + assert_eq!(out[0][1], Fr::from(0x040506)); + assert_eq!(out[1][0], Fr::from(0x070809)); + assert_eq!(out[1][1], Fr::zero()); + + let out = unroll_to_hash_input::(bt.iter().copied().take(12)); + assert_eq!(out.len(), 2); + assert_eq!(out[0][0], Fr::from(0x010203)); + assert_eq!(out[0][1], Fr::from(0x040506)); + assert_eq!(out[1][0], Fr::from(0x070809)); + assert_eq!(out[1][1], Fr::from(0x0A0B0C)); + + let out = unroll_to_hash_input::(bt.iter().copied().take(12)); + assert_eq!(out.len(), 2); + assert_eq!(out[0][0], Fr::from(0x010203)); + assert_eq!(out[0][1], Fr::from(0x040506)); + assert_eq!(out[0][2], Fr::from(0x070809)); + assert_eq!(out[1][0], Fr::from(0x0A0B0C)); + assert_eq!(out[1][1], Fr::zero()); + assert_eq!(out[1][2], Fr::zero()); + + let out = unroll_to_hash_input::(bt.iter().copied().take(14)); + assert_eq!(out.len(), 2); + assert_eq!(out[0][0], Fr::from(0x010203)); + assert_eq!(out[0][1], Fr::from(0x040506)); + assert_eq!(out[0][2], Fr::from(0x070809)); + assert_eq!(out[1][0], Fr::from(0x0A0B0C)); + assert_eq!(out[1][1], Fr::from(0x0D0E00)); + assert_eq!(out[1][2], Fr::zero()); + } +} diff --git a/zkevm-circuits/src/bytecode_circuit/dev.rs b/zkevm-circuits/src/bytecode_circuit/dev.rs index a8ac0b55a..e936fad20 100644 --- a/zkevm-circuits/src/bytecode_circuit/dev.rs +++ b/zkevm-circuits/src/bytecode_circuit/dev.rs @@ -1,5 +1,13 @@ use super::bytecode_unroller::{unroll, UnrolledBytecode}; -use super::circuit::{BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs}; +#[cfg(feature = "poseidon-codehash")] +use super::circuit::to_poseidon_hash::{ + ToHashBlockBytecodeCircuitConfigArgs, ToHashBlockCircuitConfig, HASHBLOCK_BYTES_IN_FIELD, +}; +#[cfg(not(feature = "poseidon-codehash"))] +use super::circuit::BytecodeCircuitConfig; +use super::circuit::{BytecodeCircuit, BytecodeCircuitConfigArgs}; +#[cfg(feature = "poseidon-codehash")] +use crate::table::PoseidonTable; use crate::table::{BytecodeTable, KeccakTable}; use crate::util::{Challenges, SubCircuit, SubCircuitConfig}; use eth_types::Field; @@ -10,8 +18,15 @@ use halo2_proofs::{ use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; use log::error; +#[cfg(feature = "poseidon-codehash")] +///alias for circuit config +pub type CircuitConfig = ToHashBlockCircuitConfig; +#[cfg(not(feature = "poseidon-codehash"))] +///alias for circuit config +pub type CircuitConfig = BytecodeCircuitConfig; + impl Circuit for BytecodeCircuit { - type Config = (BytecodeCircuitConfig, Challenges); + type Config = (CircuitConfig, Challenges); type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -22,17 +37,23 @@ impl Circuit for BytecodeCircuit { let bytecode_table = BytecodeTable::construct(meta); let keccak_table = KeccakTable::construct(meta); let challenges = Challenges::construct(meta); + #[cfg(feature = "poseidon-codehash")] + let poseidon_table = PoseidonTable::construct(meta); let config = { let challenges = challenges.exprs(meta); - BytecodeCircuitConfig::new( - meta, - BytecodeCircuitConfigArgs { - bytecode_table, - keccak_table, - challenges, - }, - ) + let args = BytecodeCircuitConfigArgs { + bytecode_table, + keccak_table, + challenges, + }; + #[cfg(feature = "poseidon-codehash")] + let args = ToHashBlockBytecodeCircuitConfigArgs { + base_args: args, + poseidon_table, + }; + + CircuitConfig::new(meta, args) }; (config, challenges) @@ -50,6 +71,13 @@ impl Circuit for BytecodeCircuit { self.bytecodes.iter().map(|b| &b.bytes), &challenges, )?; + #[cfg(feature = "poseidon-codehash")] + config.poseidon_table.dev_load( + &mut layouter, + self.bytecodes.iter().map(|b| &b.bytes), + &challenges, + )?; + self.synthesize_sub(&config, &challenges, &mut layouter)?; Ok(()) } diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index fd11cb874..6336c0fa3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -21,7 +21,7 @@ use crate::{ table::{AccountFieldTag, CallContextFieldTag, TxFieldTag as TxContextFieldTag}, util::Expr, }; -use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar}; +use eth_types::{Field, ToLittleEndian, ToScalar}; use ethers_core::utils::{get_contract_address, keccak256}; use gadgets::util::{expr_from_bytes, or, select}; use halo2_proofs::plonk::Error; @@ -170,8 +170,8 @@ impl ExecutionGadget for BeginTxGadget { intrinsic_gas_cost.expr(), select::expr( tx_is_create.expr(), - GasCost::CREATION_TX.expr(), - GasCost::TX.expr(), + eth_types::evm_types::GasCost::CREATION_TX.expr(), + eth_types::evm_types::GasCost::TX.expr(), ) + tx_call_data_gas_cost.expr(), ); let gas_left = tx_gas.expr() - intrinsic_gas_cost.expr(); diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index 542c7918c..0fa9313aa 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -25,6 +25,7 @@ pub mod exp_circuit; pub mod keccak_circuit; pub mod mpt_circuit; pub mod pi_circuit; +pub mod poseidon_circuit; pub mod rlp_circuit; pub mod state_circuit; pub mod super_circuit; diff --git a/zkevm-circuits/src/mpt_circuit.rs b/zkevm-circuits/src/mpt_circuit.rs index 353abac06..106746249 100644 --- a/zkevm-circuits/src/mpt_circuit.rs +++ b/zkevm-circuits/src/mpt_circuit.rs @@ -65,7 +65,11 @@ impl SubCircuit for MptCircuit { .map(|tr| AccountOp::try_from(tr).unwrap()), ); let (circuit, _) = eth_trie.to_circuits( - (block.circuits_params.max_rws / 3, None), + ( + // notice we do not use the accompanied hash circuit so just assign any size + 100usize, + Some(block.evm_circuit_pad_to), + ), &block.mpt_updates.proof_types, ); MptCircuit(circuit) @@ -123,7 +127,7 @@ impl Circuit for MptCircuit { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - let poseidon_table = PoseidonTable::construct(meta); + let poseidon_table = PoseidonTable::dev_construct(meta); let mpt_table = MptTable::construct(meta); let challenges = Challenges::construct(meta); @@ -149,7 +153,7 @@ impl Circuit for MptCircuit { mut layouter: impl Layouter, ) -> Result<(), Error> { let challenges = challenges.values(&mut layouter); - config.0.load_hash_table( + config.0.dev_load_hash_table( &mut layouter, self.0.ops.iter().flat_map(|op| op.hash_traces()), self.0.calcs, diff --git a/zkevm-circuits/src/poseidon_circuit.rs b/zkevm-circuits/src/poseidon_circuit.rs new file mode 100644 index 000000000..3efaf5a6e --- /dev/null +++ b/zkevm-circuits/src/poseidon_circuit.rs @@ -0,0 +1,173 @@ +//! wrapping of mpt-circuit +use crate::{ + bytecode_circuit::bytecode_unroller::{self, HASHBLOCK_BYTES_IN_FIELD}, + table::PoseidonTable, + util::{Challenges, SubCircuit, SubCircuitConfig}, + witness, +}; +use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Circuit, ConstraintSystem, Error}, +}; +use mpt_zktrie::hash::{Hashable, PoseidonHashChip, PoseidonHashConfig, PoseidonHashTable}; + +/// re-wrapping for mpt circuit +#[derive(Default, Clone, Debug)] +pub struct PoseidonCircuit(pub(crate) PoseidonHashTable, usize); + +/// Circuit configuration argumen ts +pub struct PoseidonCircuitConfigArgs { + /// PoseidonTable + pub poseidon_table: PoseidonTable, +} + +/// re-wrapping for poseidon config +#[derive(Debug, Clone)] +pub struct PoseidonCircuitConfig(pub(crate) PoseidonHashConfig); + +const HASH_BLOCK_STEP_SIZE: usize = HASHBLOCK_BYTES_IN_FIELD * PoseidonTable::INPUT_WIDTH; + +impl SubCircuitConfig for PoseidonCircuitConfig { + type ConfigArgs = PoseidonCircuitConfigArgs; + + fn new( + meta: &mut ConstraintSystem, + Self::ConfigArgs { poseidon_table }: Self::ConfigArgs, + ) -> Self { + let conf = PoseidonHashConfig::configure_sub(meta, poseidon_table.0, HASH_BLOCK_STEP_SIZE); + Self(conf) + } +} + +#[cfg(any(feature = "test", test))] +impl SubCircuit for PoseidonCircuit { + type Config = PoseidonCircuitConfig; + + fn new_from_block(block: &witness::Block) -> Self { + let max_hashes = block.evm_circuit_pad_to / F::hash_block_size(); + #[allow(unused_mut)] + let mut poseidon_table_data = PoseidonHashTable::default(); + // without any feature we just synthesis an empty poseidon circuit + #[cfg(feature = "zktrie")] + { + use mpt_zktrie::{operation::AccountOp, EthTrie}; + let mut eth_trie: EthTrie = Default::default(); + eth_trie.add_ops( + block + .mpt_updates + .smt_traces + .iter() + .map(|tr| AccountOp::try_from(tr).unwrap()), + ); + poseidon_table_data.constant_inputs_with_check(eth_trie.hash_traces()); + } + #[cfg(feature = "poseidon-codehash")] + { + use bytecode_unroller::unroll_to_hash_input_default; + for bytecode in block.bytecodes.values() { + // must skip empty bytecode + if !bytecode.bytes.is_empty() { + poseidon_table_data.stream_inputs( + &unroll_to_hash_input_default::(bytecode.bytes.iter().copied()), + bytecode.bytes.len() as u64, + HASH_BLOCK_STEP_SIZE, + ); + } + } + } + + Self(poseidon_table_data, max_hashes) + } + + fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + let acc = 0; + #[cfg(feature = "zktrie")] + let acc = { + let mut cnt = acc; + use mpt_zktrie::{operation::AccountOp, EthTrie}; + let mut eth_trie: EthTrie = Default::default(); + eth_trie.add_ops( + block + .mpt_updates + .smt_traces + .iter() + .map(|tr| AccountOp::try_from(tr).unwrap()), + ); + cnt += eth_trie.hash_traces().count(); + cnt + }; + #[cfg(feature = "poseidon-codehash")] + let acc = { + let mut cnt = acc; + use bytecode_unroller::unroll_to_hash_input_default; + for bytecode in block.bytecodes.values() { + cnt += unroll_to_hash_input_default::(bytecode.bytes.iter().copied()).len(); + } + cnt + }; + let acc = acc * F::hash_block_size(); + (acc, block.evm_circuit_pad_to.max(acc)) + } + + /// Make the assignments to the MptCircuit, notice it fill mpt table + /// but not fill hash table + fn synthesize_sub( + &self, + config: &Self::Config, + challenges: &Challenges>, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + // for single codehash we sitll use keccak256(nil) + use crate::evm_circuit::util::rlc; + use keccak256::EMPTY_HASH_LE; + let empty_hash = challenges + .evm_word() + .map(|challenge| rlc::value(EMPTY_HASH_LE.as_ref(), challenge)); + + let chip = + PoseidonHashChip::<_, { bytecode_unroller::HASHBLOCK_BYTES_IN_FIELD }>::construct( + config.0.clone(), + &self.0, + self.1, + false, + empty_hash.inner, + ); + + chip.load(layouter) + } + + /// powers of randomness for instance columns + fn instance(&self) -> Vec> { + vec![] + } +} + +#[cfg(any(feature = "test", test))] +impl Circuit for PoseidonCircuit { + type Config = (PoseidonCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self(Default::default(), self.1) + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let poseidon_table = PoseidonTable::construct(meta); + let challenges = Challenges::construct(meta); + + let config = + { PoseidonCircuitConfig::new(meta, PoseidonCircuitConfigArgs { poseidon_table }) }; + + (config, challenges) + } + + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + self.synthesize_sub(&config, &challenges, &mut layouter) + } +} diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index 9b4c694b3..516566eed 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -51,20 +51,23 @@ //! - [x] Tx Circuit //! - [ ] MPT Circuit -use crate::bytecode_circuit::circuit::{ - BytecodeCircuit, BytecodeCircuitConfig, BytecodeCircuitConfigArgs, +#[cfg(feature = "poseidon-codehash")] +use crate::bytecode_circuit::circuit::to_poseidon_hash::{ + ToHashBlockBytecodeCircuitConfigArgs, ToHashBlockCircuitConfig, HASHBLOCK_BYTES_IN_FIELD, }; +#[cfg(not(feature = "poseidon-codehash"))] +use crate::bytecode_circuit::circuit::BytecodeCircuitConfig; +use crate::bytecode_circuit::circuit::{BytecodeCircuit, BytecodeCircuitConfigArgs}; use crate::copy_circuit::{CopyCircuit, CopyCircuitConfig, CopyCircuitConfigArgs}; use crate::evm_circuit::{EvmCircuit, EvmCircuitConfig, EvmCircuitConfigArgs}; use crate::exp_circuit::{ExpCircuit, ExpCircuitConfig}; use crate::keccak_circuit::keccak_packed_multi::{ KeccakCircuit, KeccakCircuitConfig, KeccakCircuitConfigArgs, }; +use crate::poseidon_circuit::{PoseidonCircuit, PoseidonCircuitConfig, PoseidonCircuitConfigArgs}; #[cfg(feature = "zktrie")] use crate::mpt_circuit::{MptCircuit, MptCircuitConfig, MptCircuitConfigArgs}; -#[cfg(feature = "zktrie")] -use crate::table::PoseidonTable; #[cfg(not(feature = "onephase"))] use crate::util::Challenges; @@ -73,8 +76,8 @@ use crate::util::MockChallenges as Challenges; use crate::state_circuit::{StateCircuit, StateCircuitConfig, StateCircuitConfigArgs}; use crate::table::{ - BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, RlpTable, RwTable, - TxTable, + BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, PoseidonTable, RlpTable, + RwTable, TxTable, }; use crate::util::{circuit_stats, log2_ceil, SubCircuit, SubCircuitConfig}; @@ -101,12 +104,17 @@ pub struct SuperCircuitConfig { mpt_table: MptTable, rlp_table: RlpTable, tx_table: TxTable, + poseidon_table: PoseidonTable, evm_circuit: EvmCircuitConfig, state_circuit: StateCircuitConfig, tx_circuit: TxCircuitConfig, + #[cfg(not(feature = "poseidon-codehash"))] bytecode_circuit: BytecodeCircuitConfig, + #[cfg(feature = "poseidon-codehash")] + bytecode_circuit: ToHashBlockCircuitConfig, copy_circuit: CopyCircuitConfig, keccak_circuit: KeccakCircuitConfig, + poseidon_circuit: PoseidonCircuitConfig, pi_circuit: PiCircuitConfig, exp_circuit: ExpCircuitConfig, rlp_circuit: RlpCircuitConfig, @@ -153,10 +161,7 @@ impl SubCircuitConfig for SuperCircuitConfig { let mpt_table = MptTable::construct(meta); log_circuit_info(meta, "mpt table"); - - #[cfg(feature = "zktrie")] let poseidon_table = PoseidonTable::construct(meta); - #[cfg(feature = "zktrie")] log_circuit_info(meta, "poseidon table"); let bytecode_table = BytecodeTable::construct(meta); @@ -183,6 +188,9 @@ impl SubCircuitConfig for SuperCircuitConfig { ); log_circuit_info(meta, "keccak circuit"); + let poseidon_circuit = + PoseidonCircuitConfig::new(meta, PoseidonCircuitConfigArgs { poseidon_table }); + let rlp_circuit = RlpCircuitConfig::configure(meta, &rlp_table, &challenges); log_circuit_info(meta, "rlp circuit"); @@ -211,6 +219,7 @@ impl SubCircuitConfig for SuperCircuitConfig { ); log_circuit_info(meta, "tx circuit"); + #[cfg(not(feature = "poseidon-codehash"))] let bytecode_circuit = BytecodeCircuitConfig::new( meta, BytecodeCircuitConfigArgs { @@ -219,6 +228,19 @@ impl SubCircuitConfig for SuperCircuitConfig { challenges: challenges.clone(), }, ); + #[cfg(feature = "poseidon-codehash")] + let bytecode_circuit = ToHashBlockCircuitConfig::new( + meta, + ToHashBlockBytecodeCircuitConfigArgs { + base_args: BytecodeCircuitConfigArgs { + bytecode_table: bytecode_table.clone(), + keccak_table: keccak_table.clone(), + challenges: challenges.clone(), + }, + poseidon_table, + }, + ); + log_circuit_info(meta, "bytecode circuit"); let copy_circuit = CopyCircuitConfig::new( @@ -282,11 +304,13 @@ impl SubCircuitConfig for SuperCircuitConfig { mpt_table, tx_table, rlp_table, + poseidon_table, evm_circuit, state_circuit, copy_circuit, bytecode_circuit, keccak_circuit, + poseidon_circuit, pi_circuit, rlp_circuit, tx_circuit, @@ -322,6 +346,8 @@ pub struct SuperCircuit< pub exp_circuit: ExpCircuit, /// Keccak Circuit pub keccak_circuit: KeccakCircuit, + /// Poseidon hash Circuit + pub poseidon_circuit: PoseidonCircuit, /// Rlp Circuit pub rlp_circuit: RlpCircuit, /// Mpt Circuit @@ -405,6 +431,7 @@ impl< let copy_circuit = CopyCircuit::new_from_block_no_external(block); let exp_circuit = ExpCircuit::new_from_block(block); let keccak_circuit = KeccakCircuit::new_from_block(block); + let poseidon_circuit = PoseidonCircuit::new_from_block(block); let rlp_circuit = RlpCircuit::new_from_block(block); #[cfg(feature = "zktrie")] let mpt_circuit = MptCircuit::new_from_block(block); @@ -417,6 +444,7 @@ impl< copy_circuit, exp_circuit, keccak_circuit, + poseidon_circuit, rlp_circuit, #[cfg(feature = "zktrie")] mpt_circuit, @@ -456,6 +484,8 @@ impl< ) -> Result<(), Error> { self.keccak_circuit .synthesize_sub(&config.keccak_circuit, challenges, layouter)?; + self.poseidon_circuit + .synthesize_sub(&config.poseidon_circuit, challenges, layouter)?; self.bytecode_circuit .synthesize_sub(&config.bytecode_circuit, challenges, layouter)?; self.tx_circuit @@ -480,20 +510,9 @@ impl< .synthesize_sub(&config.rlp_circuit, challenges, layouter)?; // load both poseidon table and zktrie table #[cfg(feature = "zktrie")] - { - // TODO: wrap this as `poseidon_table.load` - config.mpt_circuit.0.load_hash_table( - layouter, - self.mpt_circuit - .0 - .ops - .iter() - .flat_map(|op| op.hash_traces()), - self.mpt_circuit.0.calcs, - )?; - self.mpt_circuit - .synthesize_sub(&config.mpt_circuit, challenges, layouter)?; - } + self.mpt_circuit + .synthesize_sub(&config.mpt_circuit, challenges, layouter)?; + Ok(()) } } diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index ff1027691..894d5a7a2 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -22,6 +22,7 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Error}, }; use halo2_proofs::{circuit::Layouter, poly::Rotation}; +use std::iter::repeat; #[cfg(feature = "onephase")] use halo2_proofs::plonk::FirstPhase as SecondPhase; @@ -612,8 +613,10 @@ impl MptTable { /// The Poseidon hash table shared between Hash Circuit, Mpt Circuit and /// Bytecode Circuit +/// the 5 cols represent [index(final hash of inputs), input0, input1, control, +/// heading mark] #[derive(Clone, Copy, Debug)] -pub struct PoseidonTable(pub [Column; 4]); +pub struct PoseidonTable(pub [Column; 5]); impl DynamicTableColumns for PoseidonTable { fn columns(&self) -> Vec> { @@ -622,9 +625,26 @@ impl DynamicTableColumns for PoseidonTable { } impl PoseidonTable { + /// the permutation width of current poseidon table + pub(crate) const WIDTH: usize = 3; + + /// the input width of current poseidon table + pub(crate) const INPUT_WIDTH: usize = Self::WIDTH - 1; + /// Construct a new PoseidonTable pub(crate) fn construct(meta: &mut ConstraintSystem) -> Self { - Self([0; 4].map(|_| meta.advice_column())) + Self([ + meta.advice_column_in(SecondPhase), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ]) + } + + /// Construct a new PoseidonTable for dev (no secondphase, mpt only) + pub(crate) fn dev_construct(meta: &mut ConstraintSystem) -> Self { + Self([0; 5].map(|_| meta.advice_column())) } pub(crate) fn assign( @@ -661,6 +681,97 @@ impl PoseidonTable { } Ok(()) } + + /// Provide this function for the case that we want to consume a poseidon + /// table but without running the full poseidon circuit + pub fn dev_load<'a, F: Field>( + &self, + layouter: &mut impl Layouter, + inputs: impl IntoIterator> + Clone, + challenges: &Challenges>, + ) -> Result<(), Error> { + use crate::bytecode_circuit::bytecode_unroller::{ + unroll_to_hash_input_default, HASHBLOCK_BYTES_IN_FIELD, + }; + + layouter.assign_region( + || "poseidon table", + |mut region| { + let mut offset = 0; + let poseidon_table_columns = self.columns(); + for column in poseidon_table_columns.iter().copied() { + region.assign_advice( + || "poseidon table all-zero row", + column, + offset, + || Value::known(F::zero()), + )?; + } + offset += 1; + let nil_hash = KeccakTable::assignments(&[], challenges)[0][3]; + for (column, value) in poseidon_table_columns + .iter() + .copied() + .zip(once(nil_hash).chain(repeat(Value::known(F::zero())))) + { + region.assign_advice( + || "poseidon table nil input row", + column, + offset, + || value, + )?; + } + offset += 1; + + for input in inputs.clone() { + let mut control_len = input.len(); + let mut first_row = true; + let ref_hash = KeccakTable::assignments(input, challenges)[0][3]; + for row in unroll_to_hash_input_default::(input.iter().copied()) { + assert_ne!( + control_len, + 0, + "must have enough len left (original size {})", + input.len() + ); + let block_size = HASHBLOCK_BYTES_IN_FIELD * row.len(); + + for (column, value) in poseidon_table_columns.iter().zip_eq( + once(ref_hash) + .chain(row.map(Value::known)) + .chain(once(Value::known(F::from(control_len as u64)))) + .chain(once(Value::known(if first_row { + F::one() + } else { + F::zero() + }))), + ) { + region.assign_advice( + || format!("poseidon table row {}", offset), + *column, + offset, + || value, + )?; + } + first_row = false; + offset += 1; + control_len = if control_len > block_size { + control_len - block_size + } else { + 0 + }; + } + assert_eq!( + control_len, + 0, + "should have exhaust all bytes (original size {})", + input.len() + ); + } + Ok(()) + }, + ) + } } /// Tag to identify the field in a Bytecode Table row diff --git a/zktrie/Cargo.toml b/zktrie/Cargo.toml index 1d49f41f5..c6547f724 100644 --- a/zktrie/Cargo.toml +++ b/zktrie/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" [dependencies] halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2022_09_10" } -mpt-circuits = { package = "halo2-mpt-circuits", git = "https://github.com/scroll-tech/mpt-circuit.git", branch = "scroll-dev-0111-halo2-upgrade" } +mpt-circuits = { package = "halo2-mpt-circuits", git = "https://github.com/scroll-tech/mpt-circuit.git", branch = "scroll-dev-0129" } zktrie = { git = "https://github.com/scroll-tech/zktrie.git", branch = "dev-1102" } bus-mapping = { path = "../bus-mapping" } eth-types = { path = "../eth-types" } diff --git a/zktrie/src/lib.rs b/zktrie/src/lib.rs index 69d601e4b..e3895c922 100644 --- a/zktrie/src/lib.rs +++ b/zktrie/src/lib.rs @@ -2,19 +2,15 @@ // #![deny(missing_docs)] +pub use mpt_circuits::hash; pub use mpt_circuits::operation; pub use mpt_circuits::serde; +pub use mpt_circuits::CommitmentIndexs; pub use mpt_circuits::EthTrie; pub use mpt_circuits::EthTrieCircuit; pub use mpt_circuits::EthTrieConfig; pub use mpt_circuits::MPTProofType; -/// the hash scheme (poseidon) used by mpt-zktrie -pub mod hash { - pub use mpt_circuits::hash::Hashable; - pub use mpt_circuits::HashCircuit; -} - //pub use mpt_circuits::hash; //use mpt_circuits::{hash::Hashable, operation::AccountOp, EthTrie, // EthTrieCircuit, HashCircuit, MPTProofType}; diff --git a/zktrie/src/state/builder.rs b/zktrie/src/state/builder.rs index c6afe5881..655bfe80c 100644 --- a/zktrie/src/state/builder.rs +++ b/zktrie/src/state/builder.rs @@ -220,127 +220,3 @@ pub(crate) fn verify_proof_leaf(inp: TrieProof, key_buf: &[u8; 32 inp } } - -/* -pub fn build_statedb_and_codedb(blocks: &[BlockTrace]) -> Result<(StateDB, CodeDB), anyhow::Error> { - let mut sdb = StateDB::new(); - let mut cdb = - CodeDB::new_with_code_hasher(Box::new(PoseidonCodeHash::new(POSEIDONHASH_BYTES_IN_FIELD))); - - // step1: insert proof into statedb - for block in blocks.iter().rev() { - let storage_trace = &block.storage_trace; - if let Some(acc_proofs) = &storage_trace.proofs { - for (addr, acc) in acc_proofs.iter() { - let acc_proof: mpt::AccountProof = acc.as_slice().try_into()?; - let acc = verify_proof_leaf(acc_proof, &mpt::extend_address_to_h256(addr)); - if acc.key.is_some() { - // a valid leaf - let (_, acc_mut) = sdb.get_account_mut(addr); - acc_mut.nonce = acc.data.nonce.into(); - acc_mut.code_hash = acc.data.code_hash; - acc_mut.balance = acc.data.balance; - } else { - // it is essential to set it as default (i.e. not existed account data) - sdb.set_account( - addr, - Account { - nonce: Default::default(), - balance: Default::default(), - storage: HashMap::new(), - code_hash: Default::default(), - }, - ); - } - } - } - - for (addr, s_map) in storage_trace.storage_proofs.iter() { - let (found, acc) = sdb.get_account_mut(addr); - if !found { - log::error!("missed address in proof field show in storage: {:?}", addr); - continue; - } - - for (k, val) in s_map { - let mut k_buf: [u8; 32] = [0; 32]; - k.to_big_endian(&mut k_buf[..]); - let val_proof: mpt::StorageProof = val.as_slice().try_into()?; - let val = verify_proof_leaf(val_proof, &k_buf); - - if val.key.is_some() { - // a valid leaf - acc.storage.insert(*k, *val.data.as_ref()); - // log::info!("set storage {:?} {:?} {:?}", addr, k, val.data); - } else { - // add 0 - acc.storage.insert(*k, Default::default()); - // log::info!("set empty storage {:?} {:?}", addr, k); - } - } - } - - // step2: insert code into codedb - // notice empty codehash always kept as keccak256(nil) - cdb.insert(Vec::new()); - - for execution_result in &block.execution_results { - if let Some(bytecode) = &execution_result.byte_code { - if execution_result.account_created.is_none() { - cdb.0.insert( - execution_result - .code_hash - .ok_or_else(|| anyhow!("empty code hash in result"))?, - decode_bytecode(bytecode)?.to_vec(), - ); - } - } - - for step in execution_result.exec_steps.iter().rev() { - if let Some(data) = &step.extra_data { - match step.op { - OpcodeId::CALL - | OpcodeId::CALLCODE - | OpcodeId::DELEGATECALL - | OpcodeId::STATICCALL => { - let callee_code = data.get_code_at(1); - trace_code(&mut cdb, step, &sdb, callee_code, 1); - } - OpcodeId::CREATE | OpcodeId::CREATE2 => { - // notice we do not need to insert code for CREATE, - // bustmapping do this job - } - OpcodeId::EXTCODESIZE | OpcodeId::EXTCODECOPY => { - let code = data.get_code_at(0); - trace_code(&mut cdb, step, &sdb, code, 0); - } - - _ => {} - } - } - } - } - } - - // A temporary fix: zkgeth do not trace 0 address if it is only refered as coinbase - // (For it is not the "real" coinbase address in PoA) but would still refer it for - // other reasons (like being transferred or called), in the other way, busmapping - // seems always refer it as coinbase (?) - // here we just add it as unexisted account and consider fix it in zkgeth later (always - // record 0 addr inside storageTrace field) - let (zero_coinbase_exist, _) = sdb.get_account(&Default::default()); - if !zero_coinbase_exist { - sdb.set_account( - &Default::default(), - Account { - nonce: Default::default(), - balance: Default::default(), - storage: HashMap::new(), - code_hash: Default::default(), - }, - ); - } - - Ok((sdb, cdb)) -} - */