diff --git a/Cargo.lock b/Cargo.lock index d4746e56aa..bacdd56dca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1561,8 +1561,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5925cba515ac18eb5c798ddf6069cc33ae00916cb08ae64194364a1b35c100b" +source = "git+https://github.com/ggkitsas/ethers-rs/?rev=6059806c17fa1a1d7534044bcdbecfef869392e1#6059806c17fa1a1d7534044bcdbecfef869392e1" dependencies = [ "arrayvec", "bytes", @@ -2160,7 +2159,6 @@ dependencies = [ [[package]] name = "halo2_proofs" version = "0.2.0" -source = "git+https://github.com/privacy-scaling-explorations/halo2.git?tag=v2023_04_20#be955686f86eb618f55d2320c0e042485b313d22" dependencies = [ "blake2b_simd", "ff 0.13.0", @@ -5396,6 +5394,7 @@ version = "0.1.0" dependencies = [ "array-init", "bus-mapping", + "bytes", "cli-table", "ctor", "ecc", diff --git a/Cargo.toml b/Cargo.toml index be096fc2fb..9c8e96fcbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ [patch.crates-io] halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_04_20" } +ethers-core = {git = "https://github.com/ggkitsas/ethers-rs/", rev = "6059806c17fa1a1d7534044bcdbecfef869392e1"} # Definition of benchmarks profile to use. [profile.bench] diff --git a/gadgets/src/less_than.rs b/gadgets/src/less_than.rs index 49285951da..f5bd562471 100644 --- a/gadgets/src/less_than.rs +++ b/gadgets/src/less_than.rs @@ -45,6 +45,24 @@ impl LtConfig { pub fn is_lt(&self, meta: &mut VirtualCells, rotation: Option) -> Expression { meta.query_advice(self.lt, rotation.unwrap_or_else(Rotation::cur)) } + + fn annotations(&self) -> Vec { + [ + vec![String::from("lt"), String::from("u8")], + (0..N_BYTES).map(|i| format!("diff byte #{}", i)).collect(), + ] + .concat() + } + /// Annotates columns of an LtChip embedded within a circuit region. + pub fn annotate_columns_in_region(&self, region: &mut Region) { + let annotations = self.annotations(); + region.name_column(|| &annotations[0], self.lt); + region.name_column(|| &annotations[1], self.u8); + self.diff + .iter() + .zip(self.annotations().iter().skip(2)) + .for_each(|(&col, ann)| region.name_column(|| ann, col)) + } } /// Chip that compares lhs < rhs. diff --git a/mock/src/block.rs b/mock/src/block.rs index 4123312933..8ae3c249c1 100644 --- a/mock/src/block.rs +++ b/mock/src/block.rs @@ -1,7 +1,7 @@ //! Mock Block definition and builder related methods. use crate::{MockTransaction, MOCK_BASEFEE, MOCK_CHAIN_ID, MOCK_DIFFICULTY, MOCK_GASLIMIT}; -use eth_types::{Address, Block, Bytes, Hash, Transaction, Word, H64, U64}; +use eth_types::{Address, Block, Bytes, Hash, Transaction, Word, H256, H64, U64}; use ethers_core::types::{Bloom, OtherFields}; #[derive(Clone, Debug)] @@ -31,6 +31,7 @@ pub struct MockBlock { size: Word, mix_hash: Hash, nonce: H64, + withdrawals_root: H256, // This field is handled here as we assume that all block txs have the same ChainId. // Also, the field is stored in the block_table since we don't have a chain_config // structure/table. @@ -63,6 +64,7 @@ impl Default for MockBlock { mix_hash: Hash::zero(), nonce: H64::zero(), chain_id: *MOCK_CHAIN_ID, + withdrawals_root: H256::zero(), } } } @@ -96,6 +98,7 @@ impl From for Block { mix_hash: Some(mock.mix_hash), nonce: Some(mock.nonce), base_fee_per_gas: Some(mock.base_fee_per_gas), + withdrawals_root: Some(mock.withdrawals_root), other: OtherFields::default(), } } @@ -126,6 +129,7 @@ impl From for Block<()> { mix_hash: Some(mock.mix_hash), nonce: Some(mock.nonce), base_fee_per_gas: Some(mock.base_fee_per_gas), + withdrawals_root: Some(mock.withdrawals_root), other: OtherFields::default(), } } diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 6eee0266c5..05d36aea90 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -38,6 +38,8 @@ snark-verifier = { git = "https://github.com/brechtpd/snark-verifier.git", branc snark-verifier-sdk = { git = "https://github.com/brechtpd/snark-verifier.git", branch = "feat/add-sdk", default-features = false, features = ["loader_halo2", "loader_evm", "parallel", "display", "halo2_circuit_params"] } cli-table = { version = "0.4", optional = true } once_cell = "1.17.1" +bytes = "1.4.0" +hex = "0.4.3" [dev-dependencies] bus-mapping = { path = "../bus-mapping", features = ["test"] } diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 93ea3d51ca..ce960949c2 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -13,7 +13,7 @@ use crate::{ }; use bus_mapping::{operation::Target, state_db::EMPTY_CODE_HASH_LE}; use eth_types::Field; -use gadgets::util::not; +use gadgets::util::{and, not}; use halo2_proofs::{ circuit::Value, plonk::{ @@ -180,15 +180,12 @@ pub(crate) trait ConstrainBuilderCommon { pub struct BaseConstraintBuilder { pub constraints: Vec<(&'static str, Expression)>, pub max_degree: usize, - pub condition: Option>, + conditions: Vec>, } impl ConstrainBuilderCommon for BaseConstraintBuilder { fn add_constraint(&mut self, name: &'static str, constraint: Expression) { - let constraint = match &self.condition { - Some(condition) => condition.clone() * constraint, - None => constraint, - }; + let constraint = self.get_condition_expr() * constraint; self.validate_degree(constraint.degree(), name); self.constraints.push((name, constraint)); } @@ -199,25 +196,49 @@ impl BaseConstraintBuilder { BaseConstraintBuilder { constraints: Vec::new(), max_degree, - condition: None, + conditions: Vec::new(), + } + } + + pub(crate) fn get_condition(&self) -> Option> { + if self.conditions.is_empty() { + None + } else { + Some(and::expr(self.conditions.iter())) } } + fn get_condition_expr(&self) -> Expression { + self.get_condition().unwrap_or_else(|| 1.expr()) + } + pub(crate) fn condition( &mut self, condition: Expression, constraint: impl FnOnce(&mut Self) -> R, ) -> R { - debug_assert!( - self.condition.is_none(), - "Nested condition is not supported" - ); - self.condition = Some(condition); + self.conditions.push(condition); let ret = constraint(self); - self.condition = None; + self.conditions.pop(); ret } + fn condition_expr_opt(&self) -> Option> { + let mut iter = self.conditions.iter(); + let first = match iter.next() { + Some(e) => e, + None => return None, + }; + Some(iter.fold(first.clone(), |acc, e| acc * e.clone())) + } + + fn condition_expr(&self) -> Expression { + match self.condition_expr_opt() { + Some(condition) => condition, + None => 1.expr(), + } + } + pub(crate) fn validate_degree(&self, degree: usize, name: &'static str) { if self.max_degree > 0 { debug_assert!( diff --git a/zkevm-circuits/src/table/block_table.rs b/zkevm-circuits/src/table/block_table.rs index 443e993673..839b83e20b 100644 --- a/zkevm-circuits/src/table/block_table.rs +++ b/zkevm-circuits/src/table/block_table.rs @@ -2,7 +2,7 @@ use super::*; /// Tag to identify the field in a Block Table row // Keep the sequence consistent with OpcodeId for scalar -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BlockContextFieldTag { /// Coinbase field Coinbase = 1, @@ -21,6 +21,28 @@ pub enum BlockContextFieldTag { /// Chain ID field. Although this is not a field in the block header, we /// add it here for convenience. ChainId, + /// Beneficiary field + Beneficiary, + /// StateRoot field + StateRoot, + /// TX Root field + TransactionsRoot, + /// Receipts Root field + ReceiptsRoot, + /// Gas Used field + GasUsed, + /// Mix Hash field + MixHash, + /// Withdrawals Root field + WithdrawalsRoot, + /// Previous Hashes field + PreviousHash, + /// Previous Hashes hi part + PreviousHashHi, + /// Previous Hashes lo part + PreviousHashLo, + /// None for the all zeros row needed in block table + None, } impl_expr!(BlockContextFieldTag); @@ -49,7 +71,7 @@ impl BlockTable { pub fn load( &self, layouter: &mut impl Layouter, - block: &BlockContext, + block_context: &BlockContext, randomness: Value, ) -> Result<(), Error> { layouter.assign_region( @@ -67,7 +89,7 @@ impl BlockTable { offset += 1; let block_table_columns = >::advice_columns(self); - for row in block.table_assignments(randomness) { + for row in block_context.table_assignments(randomness) { for (&column, value) in block_table_columns.iter().zip_eq(row) { region.assign_advice( || format!("block table row {}", offset), diff --git a/zkevm-circuits/src/table/keccak_table.rs b/zkevm-circuits/src/table/keccak_table.rs index 5eaff3f30d..4bf977901b 100644 --- a/zkevm-circuits/src/table/keccak_table.rs +++ b/zkevm-circuits/src/table/keccak_table.rs @@ -1,5 +1,13 @@ -use super::*; +use crate::table::LookupTable; +use super::*; +/// 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>; +} /// Keccak Table, used to verify keccak hashing from RLC'ed input. #[derive(Clone, Debug)] pub struct KeccakTable { @@ -144,3 +152,144 @@ impl KeccakTable { ] } } + +/// Keccak Table, used to verify keccak hashing from hi/lo input. +#[derive(Clone, Debug)] +pub struct KeccakTable2 { + /// 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, + /// hash high 16 bytes + pub output_hi: Column, + /// hash low 16 bytes + pub output_lo: Column, +} + +impl LookupTable for KeccakTable2 { + fn columns(&self) -> Vec> { + vec![ + self.is_enabled.into(), + self.input_rlc.into(), + self.input_len.into(), + self.output_hi.into(), + self.output_lo.into(), + ] + } + + fn annotations(&self) -> Vec { + vec![ + String::from("is_enabled"), + String::from("input_rlc"), + String::from("input_len"), + String::from("output_hi"), + String::from("output_lo"), + ] + } +} + +impl KeccakTable2 { + /// Construct a new KeccakTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + is_enabled: meta.advice_column(), + input_rlc: meta.advice_column_in(SecondPhase), + input_len: meta.advice_column(), + output_hi: meta.advice_column(), + output_lo: meta.advice_column(), + } + } + + /// Generate the keccak table assignments from a byte array input. + pub fn assignments( + input: &[u8], + challenges: &Challenges>, + ) -> Vec<[Value; 5]> { + const BYTE_POW_BASE: u64 = 1 << 8; + let input_rlc = challenges + .keccak_input() + .map(|challenge| rlc::value(input.iter().rev(), challenge)); + let input_len = F::from(input.len() as u64); + let mut keccak = Keccak::default(); + keccak.update(input); + let output = keccak.digest(); + + let output_hi = output.iter().take(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + + let output_lo = output.iter().skip(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + + vec![[ + Value::known(F::ONE), + input_rlc, + Value::known(input_len), + Value::known(output_hi), + Value::known(output_lo), + ]] + } + + /// Assign a table row for keccak table + pub fn assign_row( + &self, + region: &mut Region, + offset: usize, + values: [Value; 4], + ) -> Result<(), Error> { + for (column, value) in LookupTable::::columns(self).iter().zip(values.iter()) { + region.assign_advice( + || format!("assign {}", offset), + TryInto::>::try_into(*column).unwrap(), + offset, + || *value, + )?; + } + Ok(()) + } + + /// Provide this function for the case that we want to consume a keccak + /// table but without running the full keccak circuit + pub fn dev_load<'a, F: Field>( + &self, + layouter: &mut impl Layouter, + inputs: impl IntoIterator> + Clone, + challenges: &Challenges>, + ) -> Result<(), Error> { + layouter.assign_region( + || "keccak table", + |mut region| { + let mut offset = 0; + for column in LookupTable::::columns(self) { + region.assign_advice( + || "keccak table all-zero row", + TryInto::>::try_into(column).unwrap(), + offset, + || Value::known(F::ZERO), + )?; + } + offset += 1; + + let keccak_table_columns = LookupTable::::columns(self); + for input in inputs.clone() { + for row in Self::assignments(input, challenges) { + // let mut column_index = 0; + for (column, value) in keccak_table_columns.iter().zip_eq(row) { + region.assign_advice( + || format!("keccak table row {}", offset), + TryInto::>::try_into(*column).unwrap(), + offset, + || value, + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } +} diff --git a/zkevm-circuits/src/taiko_pi_circuit.rs b/zkevm-circuits/src/taiko_pi_circuit.rs index 125e35c1ff..cfa67a932b 100644 --- a/zkevm-circuits/src/taiko_pi_circuit.rs +++ b/zkevm-circuits/src/taiko_pi_circuit.rs @@ -1,14 +1,28 @@ //! Use the hash value as public input. - use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, - table::{byte_table::ByteTable, BlockContextFieldTag, BlockTable, KeccakTable, LookupTable}, + table::{ + byte_table::ByteTable, keccak_table::KeccakTable2, BlockContextFieldTag, BlockTable, + KeccakTable, LookupTable, + }, util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}, witness::{self, BlockContext}, }; -use eth_types::{Address, Field, ToBigEndian, ToWord, Word, H256}; -use ethers_core::utils::keccak256; -use gadgets::util::{or, select, Expr}; +use eth_types::{ + Address, Bytes, Field, ToBigEndian, ToLittleEndian, ToScalar, ToWord, Word, H160, H256, +}; +use ethers_core::{ + types::U256, + utils::{ + keccak256, + rlp::{Encodable, RlpStream}, + }, +}; +use gadgets::{ + is_zero::IsZeroChip, + less_than::{LtChip, LtConfig, LtInstruction}, + util::{and, not, or, select, Expr}, +}; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{ @@ -17,8 +31,29 @@ use halo2_proofs::{ }, poly::Rotation, }; +use itertools::Itertools; use std::marker::PhantomData; +use lazy_static::lazy_static; +lazy_static! { + static ref OMMERS_HASH: H256 = H256::from_slice( + &hex::decode("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(), + ); +} + +/// BlockValues +#[derive(Clone, Default, Debug)] +pub struct BlockValues { + coinbase: Address, + gas_limit: u64, + number: u64, + timestamp: u64, + difficulty: Word, + base_fee: Word, // NOTE: BaseFee was added by EIP-1559 and is ignored in legacy headers. + chain_id: u64, + history_hashes: Vec, +} + const MAX_DEGREE: usize = 9; const RPI_CELL_IDX: usize = 0; const RPI_RLC_ACC_CELL_IDX: usize = 1; @@ -27,9 +62,99 @@ const RPI_BYTES_LEN: usize = 32 * 10; // 10 fields * 32B + lo(16B) + hi(16B) + keccak(32B) const USED_ROWS: usize = RPI_BYTES_LEN + 64; +/// Fixed by the spec +const EXTRA_LEN: usize = 2; +const ZERO_BYTE_GAS_COST: u64 = 4; +const NONZERO_BYTE_GAS_COST: u64 = 16; + +// The total number of previous blocks for which to check the hash chain +const PREVIOUS_BLOCKS_NUM: usize = 256; +// This is the number of entries each block occupies in the block_table, which +// is equal to the number of header fields per block (coinbase, timestamp, +// number, difficulty, gas_limit, base_fee, blockhash, beneficiary, state_root, +// transactions_root, receipts_root, gas_used, mix_hash, withdrawals_root) +const BLOCK_LEN_IN_TABLE: usize = 15; +// previous hashes in rlc, lo and hi +// + zero +const BLOCK_TABLE_MISC_LEN: usize = PREVIOUS_BLOCKS_NUM * 3 + 1; +// Total number of entries in the block table: +// + (block fields num) * (total number of blocks) +// + misc entries +const TOTAL_BLOCK_TABLE_LEN: usize = + (BLOCK_LEN_IN_TABLE * (PREVIOUS_BLOCKS_NUM + 1)) + BLOCK_TABLE_MISC_LEN; + +const OLDEST_BLOCK_NUM: usize = 0; +const CURRENT_BLOCK_NUM: usize = PREVIOUS_BLOCKS_NUM; + +const WORD_SIZE: usize = 32; +const U64_SIZE: usize = 8; +const ADDRESS_SIZE: usize = 20; + +const RLP_HDR_NOT_SHORT: u64 = 0x81; + +// Maximum size of block header fields in bytes +const PARENT_HASH_SIZE: usize = WORD_SIZE; +const OMMERS_HASH_SIZE: usize = WORD_SIZE; +const BENEFICIARY_SIZE: usize = ADDRESS_SIZE; +const STATE_ROOT_SIZE: usize = WORD_SIZE; +const TX_ROOT_SIZE: usize = WORD_SIZE; +const RECEIPTS_ROOT_SIZE: usize = WORD_SIZE; +const LOGS_BLOOM_SIZE: usize = 256; +const DIFFICULTY_SIZE: usize = 1; +const NUMBER_SIZE: usize = U64_SIZE; +const GAS_LIMIT_SIZE: usize = WORD_SIZE; +const GAS_USED_SIZE: usize = WORD_SIZE; +const TIMESTAMP_SIZE: usize = WORD_SIZE; +const EXTRA_DATA_SIZE: usize = 1; +const MIX_HASH_SIZE: usize = WORD_SIZE; +const NONCE_SIZE: usize = U64_SIZE; +const BASE_FEE_SIZE: usize = WORD_SIZE; +const WITHDRAWALS_ROOT_SIZE: usize = WORD_SIZE; + +// Helper contants for the offset calculations below +const PARENT_HASH_RLP_LEN: usize = PARENT_HASH_SIZE + 1; +const OMMERS_HASH_RLP_LEN: usize = OMMERS_HASH_SIZE + 1; +const BENEFICIARY_RLP_LEN: usize = BENEFICIARY_SIZE + 1; +const STATE_ROOT_RLP_LEN: usize = STATE_ROOT_SIZE + 1; +const TX_ROOT_RLP_LEN: usize = TX_ROOT_SIZE + 1; +const RECEIPTS_ROOT_RLP_LEN: usize = RECEIPTS_ROOT_SIZE + 1; +const LOGS_BLOOM_RLP_LEN: usize = LOGS_BLOOM_SIZE + 3; +const DIFFICULTY_RLP_LEN: usize = DIFFICULTY_SIZE; +const NUMBER_RLP_LEN: usize = NUMBER_SIZE + 1; +const GAS_LIMIT_RLP_LEN: usize = GAS_LIMIT_SIZE + 1; +const GAS_USED_RLP_LEN: usize = GAS_USED_SIZE + 1; +const TIMESTAMP_RLP_LEN: usize = TIMESTAMP_SIZE + 1; +const EXTRA_DATA_RLP_LEN: usize = EXTRA_DATA_SIZE; +const MIX_HASH_RLP_LEN: usize = MIX_HASH_SIZE + 1; +const NONCE_RLP_LEN: usize = NONCE_SIZE + 1; +const BASE_FEE_RLP_LEN: usize = BASE_FEE_SIZE + 1; +const WITHDRAWALS_ROOT_RLP_LEN: usize = WITHDRAWALS_ROOT_SIZE; + +// Row offsets where the value of block header fields start (after their RLP +// header) +const PARENT_HASH_RLP_OFFSET: usize = 4; +const BENEFICIARY_RLP_OFFSET: usize = + PARENT_HASH_RLP_OFFSET + PARENT_HASH_RLP_LEN + OMMERS_HASH_RLP_LEN; +const STATE_ROOT_RLP_OFFSET: usize = BENEFICIARY_RLP_OFFSET + BENEFICIARY_RLP_LEN; +const TX_ROOT_RLP_OFFSET: usize = STATE_ROOT_RLP_OFFSET + STATE_ROOT_RLP_LEN; +const RECEIPTS_ROOT_RLP_OFFSET: usize = TX_ROOT_RLP_OFFSET + TX_ROOT_RLP_LEN; +const NUMBER_RLP_OFFSET: usize = + RECEIPTS_ROOT_RLP_OFFSET + RECEIPTS_ROOT_RLP_LEN + LOGS_BLOOM_RLP_LEN + DIFFICULTY_RLP_LEN; +const GAS_LIMIT_RLP_OFFSET: usize = NUMBER_RLP_OFFSET + NUMBER_RLP_LEN; +const GAS_USED_RLP_OFFSET: usize = GAS_LIMIT_RLP_OFFSET + GAS_LIMIT_RLP_LEN; +const TIMESTAMP_RLP_OFFSET: usize = GAS_USED_RLP_OFFSET + GAS_USED_RLP_LEN; +const MIX_HASH_RLP_OFFSET: usize = TIMESTAMP_RLP_OFFSET + TIMESTAMP_RLP_LEN + EXTRA_DATA_RLP_LEN; +const BASE_FEE_RLP_OFFSET: usize = MIX_HASH_RLP_OFFSET + MIX_HASH_RLP_LEN + NONCE_RLP_LEN; +const WITHDRAWALS_ROOT_RLP_OFFSET: usize = BASE_FEE_RLP_OFFSET + BASE_FEE_RLP_LEN; +const BLOCKHASH_TOTAL_ROWS: usize = WITHDRAWALS_ROOT_RLP_OFFSET + WITHDRAWALS_ROOT_RLP_LEN; + +// Absolute row number of the row where the LSB of the total RLP length is +// located +const TOTAL_LENGTH_OFFSET: i32 = 2; + /// PublicData contains all the values that the PiCircuit receives as input -#[derive(Debug, Clone, Default)] -pub struct PublicData { +#[derive(Debug, Clone)] +pub struct PublicData { /// l1 signal service address pub l1_signal_service: Word, /// l2 signal service address @@ -39,9 +164,9 @@ pub struct PublicData { /// meta hash pub meta_hash: Word, /// block hash value - pub block_hash: Word, + pub block_hash: H256, /// the parent block hash - pub parent_hash: Word, + pub parent_hash: H256, /// signal root pub signal_root: Word, /// extra message @@ -57,20 +182,91 @@ pub struct PublicData { prover: Address, // parent block gas used parent_gas_used: u32, - // block gas used - gas_used: u32, + /// block gas used + pub gas_used: u32, // blockMaxGasLimit block_max_gas_limit: u64, // maxTransactionsPerBlock: u64, max_transactions_per_block: u64, // maxBytesPerTxList: u64, max_bytes_per_tx_list: u64, - - block_context: BlockContext, + /// block_context + pub block_context: BlockContext, chain_id: Word, + + /// Block State Root + pub state_root: H256, + /// The author + pub beneficiary: Address, + /// Transactions Root + pub transactions_root: H256, + /// Receipts Root + pub receipts_root: H256, + /// Mix Hash + pub mix_hash: H256, + /// Withdrawals Root + pub withdrawals_root: H256, + + /// All data of the past 256 blocks + pub previous_blocks: Vec>, + /// RLPs of the past 256 blocks + pub previous_blocks_rlp: Vec, + /// History hashes contains the most recent 256 block hashes in history, + /// where the latest one is at history_hashes[history_hashes.len() - 1]. + pub history_hashes: Vec, + + blockhash_blk_hdr_rlp: Bytes, + blockhash_rlp_hash_hi: F, + blockhash_rlp_hash_lo: F, +} + +fn rlp_opt(rlp: &mut RlpStream, opt: &Option) { + if let Some(inner) = opt { + rlp.append(inner); + } else { + rlp.append(&""); + } } -impl PublicData { +impl Default for PublicData { + fn default() -> Self { + PublicData { + parent_hash: H256::default(), + beneficiary: Address::default(), + transactions_root: H256::default(), + receipts_root: H256::default(), + gas_used: u32::default(), + mix_hash: H256::default(), + withdrawals_root: H256::default(), + previous_blocks: vec![], + previous_blocks_rlp: vec![], + block_hash: H256::default(), + blockhash_blk_hdr_rlp: Bytes::default(), + blockhash_rlp_hash_hi: F::default(), + blockhash_rlp_hash_lo: F::default(), + + l1_signal_service: U256::default(), + l2_signal_service: U256::default(), + l2_contract: U256::default(), + meta_hash: U256::default(), + signal_root: U256::default(), + graffiti: U256::default(), + field9: U256::default(), + field10: U256::default(), + prover: H160::default(), + parent_gas_used: u32::default(), + block_max_gas_limit: u64::default(), + max_transactions_per_block: u64::default(), + max_bytes_per_tx_list: u64::default(), + block_context: BlockContext::default(), + chain_id: U256::default(), + history_hashes: vec![], + state_root: H256::default(), + } + } +} + +impl PublicData { fn assignments(&self) -> [(&'static str, Option, [u8; 32]); 10] { [ ( @@ -88,12 +284,12 @@ impl PublicData { ( "parent_hash", Some(self.block_context.number - 1), - self.parent_hash.to_be_bytes(), + self.parent_hash.to_fixed_bytes(), ), ( "block_hash", Some(self.block_context.number), - self.block_hash.to_be_bytes(), + self.block_hash.to_fixed_bytes(), ), ("signal_root", None, self.signal_root.to_be_bytes()), ("graffiti", None, self.graffiti.to_be_bytes()), @@ -115,12 +311,21 @@ impl PublicData { self.assignments().iter().flat_map(|v| v.2).collect() } - fn default() -> Self { - Self::new::(&witness::Block::default()) + fn default() -> Self { + Self::new(&witness::Block::default()) } /// create PublicData from block and taiko - pub fn new(block: &witness::Block) -> Self { + pub fn new(block: &witness::Block) -> Self { + assert!(block.context.number >= U256::from(0x100)); + let (blockhash_blk_hdr_rlp, blockhash_rlp_hash_hi, blockhash_rlp_hash_lo, _) = + Self::get_block_header_rlp_from_block(block); + + // Only initializing `previous_blocks` and `previous_blocks_rlp` here + // these values are set outside of `new` + let previous_blocks = vec![witness::Block::::default(); PREVIOUS_BLOCKS_NUM]; + let previous_blocks_rlp = vec![Bytes::default(); PREVIOUS_BLOCKS_NUM]; + use witness::left_shift; let field9 = left_shift(block.protocol_instance.prover, 96) + left_shift(block.protocol_instance.parent_gas_used as u64, 64) @@ -134,8 +339,8 @@ impl PublicData { l2_signal_service: block.protocol_instance.l2_signal_service.to_word(), l2_contract: block.protocol_instance.l2_contract.to_word(), meta_hash: block.protocol_instance.meta_hash.hash().to_word(), - block_hash: block.protocol_instance.block_hash.to_word(), - parent_hash: block.protocol_instance.parent_hash.to_word(), + block_hash: block.protocol_instance.block_hash, + parent_hash: block.protocol_instance.parent_hash, signal_root: block.protocol_instance.signal_root.to_word(), graffiti: block.protocol_instance.graffiti.to_word(), prover: block.protocol_instance.prover, @@ -148,6 +353,37 @@ impl PublicData { field10, block_context: block.context.clone(), chain_id: block.context.chain_id, + beneficiary: block.eth_block.author.unwrap_or_else(H160::zero), + transactions_root: block.eth_block.transactions_root, + receipts_root: block.eth_block.receipts_root, + mix_hash: block.eth_block.mix_hash.unwrap_or_else(H256::zero), + withdrawals_root: block.eth_block.withdrawals_root.unwrap_or_else(H256::zero), + previous_blocks, + previous_blocks_rlp, + blockhash_blk_hdr_rlp, + blockhash_rlp_hash_hi, + blockhash_rlp_hash_lo, + history_hashes: block.context.history_hashes.clone(), + state_root: block.eth_block.state_root, + } + } + + /// Returns struct with values for the block table + pub fn get_block_table_values(&self) -> BlockValues { + let history_hashes = [ + vec![U256::zero(); PREVIOUS_BLOCKS_NUM - self.history_hashes.len()], + self.history_hashes.to_vec(), + ] + .concat(); + BlockValues { + coinbase: self.block_context.coinbase, + gas_limit: self.block_context.gas_limit, + number: self.block_context.number.as_u64(), + timestamp: self.block_context.timestamp.as_u64(), + difficulty: self.block_context.difficulty, + base_fee: self.block_context.base_fee, + chain_id: self.chain_id.as_u64(), + history_hashes, } } @@ -156,6 +392,77 @@ impl PublicData { let rpi_keccak = keccak256(rpi_bytes); H256(rpi_keccak) } + + fn split_hash(hash: [u8; 32]) -> (F, F) { + let hi = hash.iter().take(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + + let lo = hash.iter().skip(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + (hi, lo) + } + + fn get_block_header_rlp_from_block(block: &witness::Block) -> (Bytes, F, F, H256) { + let mut stream = RlpStream::new(); + stream.begin_unbounded_list(); + stream + .append(&block.eth_block.parent_hash) + .append(&*OMMERS_HASH) + .append(&block.eth_block.author.unwrap_or_else(H160::zero)) + .append(&block.eth_block.state_root) + .append(&block.eth_block.transactions_root) + .append(&block.eth_block.receipts_root) + .append(&vec![0u8; LOGS_BLOOM_SIZE]) // logs_bloom is all zeros + .append(&block.context.difficulty) + .append(&block.context.number.low_u64()) + .append(&U256::from(block.context.gas_limit)) + .append(&U256::from(block.protocol_instance.gas_used)) + .append(&block.context.timestamp); + rlp_opt(&mut stream, &None::); // extra_data = "" + stream + .append(&block.eth_block.mix_hash.unwrap_or_else(H256::zero)) + .append(&vec![0u8; NONCE_SIZE]) // nonce = 0 + .append(&block.context.base_fee) + .append(&block.eth_block.withdrawals_root.unwrap_or_else(H256::zero)); + + stream.finalize_unbounded_list(); + let out: bytes::Bytes = stream.out().into(); + let rlp_bytes: Bytes = out.into(); + let hash = keccak256(&rlp_bytes); + let (hi, lo) = Self::split_hash(hash); + let hash_res = H256::from(hash); + (rlp_bytes, hi, lo, hash_res) + } +} + +#[derive(Debug, Clone)] +struct BlockhashColumns { + blk_hdr_rlp: Column, + blk_hdr_rlp_inv: Column, + blk_hdr_rlp_const: Column, + q_blk_hdr_rlp: Selector, + q_blk_hdr_rlp_const: Selector, + blk_hdr_rlp_len_calc: Column, + blk_hdr_rlp_len_calc_inv: Column, + blk_hdr_reconstruct_value: Column, + blk_hdr_reconstruct_hi_lo: Column, + q_hi: Column, + q_lo: Column, + block_table_tag_blockhash: Column, + block_table_index_blockhash: Column, + q_reconstruct: Column, + q_number: Column, + q_parent_hash: Selector, + q_var_field_256: Column, + q_blk_hdr_rlc_start: Selector, + q_blk_hdr_rlp_end: Selector, + blk_hdr_rlc_acc: Column, + blk_hdr_do_rlc_acc: Column, + q_lookup_blockhash: Selector, + blk_hdr_is_leading_zero: Column, + blk_hdr_blockhash: Column, } /// Config for PiCircuit @@ -175,12 +482,18 @@ pub struct TaikoPiCircuitConfig { q_keccak: Selector, keccak_table: KeccakTable, + keccak_table2: KeccakTable2, // External tables q_block_table: Selector, block_index: Column, block_table: BlockTable, + block_table_blockhash: BlockTable, + q_start: Selector, + fixed_u8: Column, + rlp_is_short: LtConfig, + blockhash_cols: BlockhashColumns, _marker: PhantomData, } @@ -188,12 +501,16 @@ pub struct TaikoPiCircuitConfig { pub struct TaikoPiCircuitConfigArgs { /// BlockTable pub block_table: BlockTable, - /// KeccakTable - pub keccak_table: KeccakTable, + /// BlockTable for blockhash + pub block_table_blockhash: BlockTable, // TODO: merge the block tables /// ByteTable pub byte_table: ByteTable, /// Challenges pub challenges: Challenges>, + /// KeccakTable + pub keccak_table: KeccakTable, // TODO: merge the keccak tables + /// KeccakTable + pub keccak_table2: KeccakTable2, } impl SubCircuitConfig for TaikoPiCircuitConfig { @@ -204,7 +521,9 @@ impl SubCircuitConfig for TaikoPiCircuitConfig { meta: &mut ConstraintSystem, Self::ConfigArgs { block_table, + block_table_blockhash, keccak_table, + keccak_table2, byte_table, challenges, }: Self::ConfigArgs, @@ -223,6 +542,65 @@ impl SubCircuitConfig for TaikoPiCircuitConfig { let q_block_table = meta.complex_selector(); let block_index = meta.advice_column(); + let q_start = meta.complex_selector(); + let fixed_u8 = meta.fixed_column(); + // Block hash + let blk_hdr_rlp = meta.advice_column(); + let blk_hdr_rlp_inv = meta.advice_column(); + let blk_hdr_rlp_const = meta.fixed_column(); + let q_blk_hdr_rlp = meta.complex_selector(); + let q_blk_hdr_rlp_end = meta.complex_selector(); + let q_blk_hdr_rlp_const = meta.complex_selector(); + + let blk_hdr_rlp_len_calc = meta.advice_column(); + let blk_hdr_rlp_len_calc_inv = meta.advice_column(); + let blk_hdr_reconstruct_value = meta.advice_column_in(SecondPhase); + let blk_hdr_reconstruct_hi_lo = meta.advice_column(); + let block_table_tag_blockhash = meta.fixed_column(); + let block_table_index_blockhash = meta.fixed_column(); + let q_reconstruct = meta.fixed_column(); + let blk_hdr_is_leading_zero = meta.advice_column(); + + // Selectors for header fields. + let q_number = meta.fixed_column(); + let q_parent_hash = meta.complex_selector(); + let q_var_field_256 = meta.fixed_column(); + let q_hi = meta.fixed_column(); + let q_lo = meta.fixed_column(); + + let q_blk_hdr_rlc_start = meta.complex_selector(); + let blk_hdr_do_rlc_acc = meta.advice_column_in(SecondPhase); + let blk_hdr_rlc_acc = meta.advice_column_in(SecondPhase); + let q_lookup_blockhash = meta.complex_selector(); + let blk_hdr_blockhash = meta.advice_column(); + // self.blockhash_cols.block_table_tag + let blockhash_cols = BlockhashColumns { + blk_hdr_rlp, + blk_hdr_rlp_inv, + blk_hdr_rlp_const, + q_blk_hdr_rlp, + q_blk_hdr_rlp_const, + blk_hdr_rlp_len_calc, + blk_hdr_rlp_len_calc_inv, + blk_hdr_reconstruct_value, + blk_hdr_reconstruct_hi_lo, + q_hi, + q_lo, + q_reconstruct, + block_table_tag_blockhash, + block_table_index_blockhash, + q_number, + q_parent_hash, + q_var_field_256, + q_blk_hdr_rlc_start, + q_blk_hdr_rlp_end, + blk_hdr_rlc_acc, + blk_hdr_do_rlc_acc, + q_lookup_blockhash, + blk_hdr_is_leading_zero, + blk_hdr_blockhash, + }; + meta.enable_equality(rpi_field_bytes); meta.enable_equality(rpi_field_bytes_acc); meta.enable_equality(rpi_rlc_acc); @@ -278,21 +656,6 @@ impl SubCircuitConfig for TaikoPiCircuitConfig { .collect::>() }); - // in block table - meta.lookup_any("in block table", |meta| { - let q_block_table = meta.query_selector(q_block_table); - let block_index = meta.query_advice(block_index, Rotation::cur()); - let block_hash = meta.query_advice(rpi_field_bytes_acc, Rotation::cur()); - [ - BlockContextFieldTag::BlockHash.expr(), - block_index, - block_hash, - ] - .into_iter() - .zip(block_table.table_exprs(meta).into_iter()) - .map(|(arg, table)| (q_block_table.expr() * arg, table)) - .collect::>() - }); // is byte meta.lookup_any("is_byte", |meta| { let q_field_step = meta.query_selector(q_field_start); @@ -306,6 +669,442 @@ impl SubCircuitConfig for TaikoPiCircuitConfig { .collect::>() }); + // Block hash checks in three parts: + // 1. RLP checks + // 2. RLC calculation + // 3. Keccak lookup + + // Check if the RLP byte is 0 + let rlp_is_zero = IsZeroChip::configure( + meta, + |meta| meta.query_selector(q_blk_hdr_rlp), + |meta| meta.query_advice(blk_hdr_rlp, Rotation::cur()), + blk_hdr_rlp_inv, + ); + + // Check if the length is 0 + let length_is_zero = IsZeroChip::configure( + meta, + |meta| meta.query_selector(q_blk_hdr_rlp), + |meta| meta.query_advice(blk_hdr_rlp_len_calc, Rotation::cur()), + blk_hdr_rlp_len_calc_inv, + ); + + // Check if the RLP byte is short (byte < 81) + let rlp_is_short = LtChip::configure( + meta, + |meta| meta.query_selector(q_blk_hdr_rlp), + |meta| meta.query_advice(blk_hdr_rlp, Rotation::cur()), + |_| RLP_HDR_NOT_SHORT.expr(), + ); + + // Check that all RLP bytes are within [0, 255] + meta.lookup_any("Block header RLP: byte range checks", |meta| { + let block_header_rlp_byte = meta.query_advice(blk_hdr_rlp, Rotation::cur()); + let fixed_u8_table = meta.query_fixed(fixed_u8, Rotation::cur()); + + vec![(block_header_rlp_byte, fixed_u8_table)] + }); + + meta.create_gate("Block header", |meta| { + let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); + + let q_enabled = meta.query_selector(q_blk_hdr_rlp); + let q_const = meta.query_selector(q_blk_hdr_rlp_const); + let q_rlc_start = meta.query_selector(q_blk_hdr_rlc_start); + let q_rlc_end = meta.query_selector(q_blk_hdr_rlp_end); + let byte = meta.query_advice(blk_hdr_rlp, Rotation::cur()); + let byte_next = meta.query_advice(blk_hdr_rlp, Rotation::next()); + let const_byte = meta.query_fixed(blk_hdr_rlp_const, Rotation::cur()); + let length = meta.query_advice(blk_hdr_rlp_len_calc, Rotation::cur()); + let length_next = meta.query_advice(blk_hdr_rlp_len_calc, Rotation::next()); + let is_leading_zero = meta.query_advice(blk_hdr_is_leading_zero, Rotation::cur()); + let is_leading_zero_next = meta.query_advice(blk_hdr_is_leading_zero, Rotation::next()); + let q_reconstruct_cur = meta.query_fixed(q_reconstruct, Rotation::cur()); + let q_reconstruct_next = meta.query_fixed(q_reconstruct, Rotation::next()); + let do_rlc_acc = meta.query_advice(blk_hdr_do_rlc_acc, Rotation::cur()); + let rlc_acc = meta.query_advice(blk_hdr_rlc_acc, Rotation::cur()); + let rlc_acc_next = meta.query_advice(blk_hdr_rlc_acc, Rotation::next()); + let q_hi_next = meta.query_fixed(q_hi, Rotation::next()); + let q_lo_cur = meta.query_fixed(q_lo, Rotation::cur()); + let q_lo_next = meta.query_fixed(q_lo, Rotation::next()); + + // Check all RLP bytes that are constant against their expected value + cb.condition(q_const, |cb| { + cb.require_equal( + "RLP constant byte values are correct", + byte.expr(), + const_byte.expr(), + ); + }); + + // 1. Block header RLP + + cb.condition(q_enabled.expr(), |cb| { + // Make sure that the length starts from 0 + let q_number_or_field_256 = or::expr([ + meta.query_fixed(q_number, Rotation::cur()), + meta.query_fixed(q_var_field_256, Rotation::cur()), + ]); + cb.condition(not::expr(q_number_or_field_256), |cb| { + cb.require_zero("length default value is zero", length.expr()); + }); + + // `is_leading_zero` needs to be boolean + cb.require_boolean("is_leading_zero boolean", is_leading_zero.expr()); + // `q_rlc_acc` needs to be boolean + cb.require_boolean("q_rlc_acc boolean", do_rlc_acc.expr()); + + // Covers a corner case where MSB bytes can be skipped by annotating them as + // leading zeroes. This can occur when `blk_hdr_is_leading_zero` + // is set to 0 wrongly (the actual byte value is non-zero) + cb.condition(not::expr(rlp_is_zero.expr()), |cb| { + cb.require_zero("Leading zeros cannot be skipped", is_leading_zero.expr()); + }); + }); + + // Check leading zeros are actually leading zeros + for q_field in [q_number, q_var_field_256] { + let q_field_prev = meta.query_fixed(q_field, Rotation::prev()); + let q_field = meta.query_fixed(q_field, Rotation::cur()); + cb.condition(and::expr([is_leading_zero.expr(), q_field]), |cb| { + // Leading byte is actually zero + cb.require_zero("Leading zero is actually zero", byte.expr()); + + // Loading zeros needs to be continuous, except at the beginning of the field + let is_leading_zero_prev = + meta.query_advice(blk_hdr_is_leading_zero, Rotation::prev()); + cb.require_equal( + "Leading zeros must be continuous or we are at the begining of the field", + 1.expr(), + or::expr([is_leading_zero_prev, not::expr(q_field_prev)]), + ); + }); + } + + // Length checks for all variable length fields: + // 1. len = 0 for leading zeros + // 2. len = len_prev + 1 otherwise + // 3. total_len = 0 if value <= 0x80 + let rlp_is_short_next = rlp_is_short.is_lt(meta, Some(Rotation::next())); + for (q_value, var_size) in [(q_number, NUMBER_SIZE), (q_var_field_256, WORD_SIZE)] { + let q_field = meta.query_fixed(q_value, Rotation::cur()); + let q_field_next = meta.query_fixed(q_value, Rotation::next()); + // Only check while we're processing the field + cb.condition(q_field.expr(), |cb| { + // Length needs to remain zero when skipping over leading zeros + cb.condition(is_leading_zero.expr(), |cb| { + cb.require_zero("Length is zero on a leading zero", length.expr()); + }); + + // The length needs to increment when + // - not a leading zero + // - the total length is not 0 + // We know the total length is 0 when the length is currently 0 and the field + // ends on the next row + let is_total_len_zero = + and::expr([not::expr(q_field_next.expr()), length_is_zero.expr()]); + let do_increment_length = and::expr([ + not::expr(is_leading_zero.expr()), + not::expr(is_total_len_zero.expr()), + ]); + let length_prev = meta.query_advice(blk_hdr_rlp_len_calc, Rotation::prev()); + cb.require_equal( + "len = len_prev + do_increment_length", + length.expr(), + length_prev.expr() + do_increment_length, + ); + + // The length is also set to 0 when the RLP encoding is short (single RLP byte + // encoding) + cb.condition( + and::expr([ + rlp_is_short_next.clone(), + length_is_zero.expr(), + not::expr(q_field_next.expr()), + ]), + |cb| { + cb.require_zero( + "Length is set to zero for short values", + length_next.expr(), + ); + }, + ); + }); + + // Check the RLP encoding when on the next row the field value begins + cb.condition( + and::expr([not::expr(q_field.clone()), q_field_next.expr()]), + |cb| { + let length = + meta.query_advice(blk_hdr_rlp_len_calc, Rotation(var_size as i32)); + cb.require_equal("RLP length", byte.expr(), 0x80.expr() + length.expr()); + }, + ); + } + + // Check total length of RLP stream. + // For the block header, the total RLP length is always two bytes long and only + // the LSB fluctuates: + // - Minimum total length: lengths of all the fixed size fields + all the RLP headers = + // 527 bytes (0x020F) + // - Maximum total length: minimum total length + (maximum length of variable size + // field) = 527 + 4*32+1*8 = 663 (0x0297) + // - Actual total length: minimum total length + length of all variable size fields + // (number, gas_limit, gas_used, timestamp, base fee). + cb.condition(q_rlc_start.expr(), |cb| { + let mut get_len = |offset: usize| { + meta.query_advice( + blk_hdr_rlp_len_calc, + // The length of a field is located at its last row + // Since the `offset` given is the first byte of the next field, we need to + // remove 1 row to target the last byte of the actual field + Rotation((offset - 1).try_into().unwrap()), + ) + }; + let number_len = get_len(NUMBER_RLP_OFFSET + NUMBER_SIZE); + let gas_limit_len = get_len(GAS_LIMIT_RLP_OFFSET + GAS_LIMIT_SIZE); + let gas_used_len = get_len(GAS_USED_RLP_OFFSET + GAS_USED_SIZE); + let timestamp_len = get_len(TIMESTAMP_RLP_OFFSET + TIMESTAMP_SIZE); + let base_fee_len = get_len(BASE_FEE_RLP_OFFSET + BASE_FEE_SIZE); + // Only check the LSB of the length (the MSB is always 0x02!). + cb.require_equal( + "total_len", + meta.query_advice(blk_hdr_rlp, Rotation(TOTAL_LENGTH_OFFSET)), + 0x0F.expr() + + number_len + + gas_limit_len + + gas_used_len + + timestamp_len + + base_fee_len, + ); + }); + + // Leading zeros artificical headers are not part of the RLC calculation + let q_number_next = meta.query_fixed(q_number, Rotation::next()); + let q_number_after_next = meta.query_fixed(q_number, Rotation(2)); + let q_var_field_256_next = meta.query_fixed(q_var_field_256, Rotation::next()); + let q_var_field_256_after_next = meta.query_fixed(q_var_field_256, Rotation(2)); + let is_number_header = + and::expr([not::expr(q_number_next.expr()), q_number_after_next.expr()]); + let is_var_field_header = and::expr([ + not::expr(q_var_field_256_next.expr()), + q_var_field_256_after_next.expr(), + ]); + let is_number_zero = + meta.query_advice(blk_hdr_is_leading_zero, Rotation((NUMBER_SIZE + 1) as i32)); + let is_var_field_zero = + meta.query_advice(blk_hdr_is_leading_zero, Rotation((WORD_SIZE + 1) as i32)); + let rlp_short_or_zero = rlp_is_short.is_lt(meta, Some(Rotation::next())); + // Artificial headers exist for header fields with short values greater than + // zero + let is_artificial_header = and::expr([ + rlp_short_or_zero.expr(), + or::expr([ + and::expr([is_number_header, not::expr(is_number_zero.expr())]), + and::expr([is_var_field_header, not::expr(is_var_field_zero.expr())]), + ]), + ]); + let no_rlc = or::expr([is_leading_zero_next.expr(), is_artificial_header]); + + let do_rlc_val = select::expr(no_rlc, 0.expr(), 1.expr()); + cb.condition(q_enabled.expr(), |cb| { + cb.require_equal( + "skip leading zeros and artifical headers in RLC ", + meta.query_advice(blk_hdr_do_rlc_acc, Rotation::cur()), + do_rlc_val, + ); + }); + + // Decode RLC and Hi/Lo field values + for (selector, values, r_mul) in [ + ( + q_hi_next.expr(), + q_reconstruct_cur.expr(), + 2_u64.pow(8).expr(), + ), + (q_lo_next.expr(), q_lo_cur, 2_u64.pow(8).expr()), + ( + and::expr([ + q_reconstruct_next.expr(), + not::expr(q_hi_next.expr()), + not::expr(q_lo_next.expr()), + ]), + q_reconstruct_cur.expr(), + challenges.evm_word().expr(), + ), + ] { + cb.condition(selector, |cb| { + let decode = meta.query_advice(blk_hdr_reconstruct_value, Rotation::cur()); + let decode_next = + meta.query_advice(blk_hdr_reconstruct_value, Rotation::next()); + // For the first byte start from scratch and just copy over the next byte + let r = select::expr(values, r_mul, 0.expr()); + cb.require_equal( + "decode rlc fields and hi/lo values", + decode_next, + decode * r + byte_next.expr(), + ); + }); + } + + // 2. Check RLC of RLP'd block header + // Accumulate only bytes that have q_blk_hdr_rlp AND + // NOT(blk_hdr_is_leading_zero) and skip RLP headers if value is <0x80 + cb.condition(q_rlc_start.expr(), |cb| { + cb.require_equal("rlc_acc = byte", rlc_acc.expr(), byte.expr()); + }); + cb.condition( + and::expr([q_enabled.expr(), not::expr(q_rlc_end.expr())]), + |cb| { + // RLC encode the bytes, but skip over leading zeros + let r = select::expr( + do_rlc_acc.expr(), + challenges.keccak_input().expr(), + 1.expr(), + ); + let byte_value = select::expr(do_rlc_acc.expr(), byte_next.expr(), 0.expr()); + cb.require_equal( + "rlc_acc_next = rlc_acc * r + next_byte", + rlc_acc_next.expr(), + rlc_acc.expr() * r + byte_value, + ); + }, + ); + + cb.gate(1.expr()) + }); + + meta.lookup_any( + "Block header: Check RLC of field values except of `q_parent_hash`", + |meta| { + let q_sel = and::expr([ + meta.query_fixed(q_reconstruct, Rotation::cur()), + not::expr(meta.query_fixed(q_reconstruct, Rotation::next())), + // We exclude `parent_hash` as it is dealt with in its own lookup + not::expr(meta.query_selector(q_parent_hash)), + ]); + vec![ + ( + q_sel.expr() * meta.query_fixed(block_table_tag_blockhash, Rotation::cur()), + meta.query_advice(block_table_blockhash.tag, Rotation::cur()), + ), + ( + q_sel.expr() + * meta.query_fixed(block_table_index_blockhash, Rotation::cur()), + meta.query_advice(block_table_blockhash.index, Rotation::cur()), + ), + ( + q_sel.expr() + * meta.query_advice(blk_hdr_reconstruct_value, Rotation::cur()), + meta.query_advice(block_table_blockhash.value, Rotation::cur()), + ), + ] + }, + ); + + // 3. Check block header hash + meta.lookup_any("blockhash lookup keccak", |meta| { + let q_blk_hdr_rlp_end = meta.query_selector(q_blk_hdr_rlp_end); + let blk_hdr_rlc = meta.query_advice(blk_hdr_rlc_acc, Rotation::cur()); + // The total RLP length is the RLP list length (0x200 + blk_hdr_rlp[2]) + // + 3 bytes for the RLP list header + let blk_hdr_rlp_num_bytes = 0x200.expr() + + meta.query_advice( + blk_hdr_rlp, + Rotation(-(BLOCKHASH_TOTAL_ROWS as i32) + 1 + 2), + ) + + 3.expr(); + let blk_hdr_hash_hi = meta.query_advice(blk_hdr_blockhash, Rotation::cur()); + let blk_hdr_hash_lo = meta.query_advice(blk_hdr_blockhash, Rotation::prev()); + vec![ + ( + q_blk_hdr_rlp_end.expr(), + meta.query_advice(keccak_table2.is_enabled, Rotation::cur()), + ), + ( + q_blk_hdr_rlp_end.expr() * blk_hdr_rlc, + meta.query_advice(keccak_table2.input_rlc, Rotation::cur()), + ), + ( + q_blk_hdr_rlp_end.expr() * blk_hdr_rlp_num_bytes, + meta.query_advice(keccak_table2.input_len, Rotation::cur()), + ), + ( + q_blk_hdr_rlp_end.expr() * blk_hdr_hash_hi, + meta.query_advice(keccak_table2.output_hi, Rotation::cur()), + ), + ( + q_blk_hdr_rlp_end * blk_hdr_hash_lo, + meta.query_advice(keccak_table2.output_lo, Rotation::cur()), + ), + ] + }); + + for rotation in [0, -1] { + // hi blockhash is in current offset, lo blockhash in in previous + meta.lookup_any( + "Block header: Check hi/lo parts of block hashes against previous hashes", + |meta| { + let q_blk_hdr_rlp_end = meta.query_selector(q_blk_hdr_rlp_end); + let blk_hdr_hash_hi = meta.query_advice(blk_hdr_blockhash, Rotation(rotation)); + let q_lookup_blockhash = meta.query_selector(q_lookup_blockhash); + let tag = meta.query_fixed(block_table_tag_blockhash, Rotation(rotation - 1)); + let index = meta.query_fixed(block_table_index_blockhash, Rotation::cur()); + let q_sel = and::expr([q_blk_hdr_rlp_end, q_lookup_blockhash]); + vec![ + ( + q_sel.expr() * tag, + meta.query_advice(block_table_blockhash.tag, Rotation::cur()), + ), + ( + q_sel.expr() * index, + meta.query_advice(block_table_blockhash.index, Rotation::cur()), + ), + ( + q_sel.expr() * blk_hdr_hash_hi, + meta.query_advice(block_table_blockhash.value, Rotation::cur()), + ), + ] + }, + ); + } + + // Check all parent_hash fields against previous_hashes in block table + for (cur, hi_lo) in [(q_hi, 0), (q_lo, 1)] { + meta.lookup_any("Block header: Check parent hashes hi/lo", |meta| { + let tag = meta.query_fixed(block_table_tag_blockhash, Rotation::cur()); + let index = + meta.query_fixed(block_table_index_blockhash, Rotation::cur()) - 1.expr(); + let q_cur = meta.query_fixed(cur, Rotation::cur()); + let q_lo_next = meta.query_fixed(q_lo, Rotation::next()); + + let next_cond = if hi_lo == 0 { + q_lo_next + } else { + not::expr(q_lo_next) + }; + + let q_sel = and::expr([q_cur, next_cond, meta.query_selector(q_parent_hash)]); + + vec![ + ( + q_sel.expr() * tag, + meta.query_advice(block_table_blockhash.tag, Rotation::cur()), + ), + ( + q_sel.expr() * index, + meta.query_advice(block_table_blockhash.index, Rotation::cur()), + ), + ( + q_sel.expr() + * meta.query_advice(blk_hdr_reconstruct_value, Rotation::cur()), + meta.query_advice(block_table_blockhash.value, Rotation::cur()), + ), + ] + }); + } + Self { rpi_field_bytes, rpi_field_bytes_acc, @@ -321,12 +1120,18 @@ impl SubCircuitConfig for TaikoPiCircuitConfig { q_keccak, keccak_table, + keccak_table2, q_block_table, block_index, block_table, _marker: PhantomData, + q_start, + fixed_u8, + rlp_is_short, + blockhash_cols, + block_table_blockhash, } } } @@ -342,7 +1147,7 @@ impl TaikoPiCircuitConfig { rpi_rlc_acc: &mut Value, challenges: &Challenges>, keccak_hi_lo: bool, - block_number: Option, + block_offset: Option, ) -> Result>, Error> { let len = field_bytes.len(); let mut field_rlc_acc = Value::known(F::ZERO); @@ -399,7 +1204,7 @@ impl TaikoPiCircuitConfig { self.q_field_end.enable(region, row_offset)?; cells[RPI_CELL_IDX] = Some(rpi_cell); cells[RPI_RLC_ACC_CELL_IDX] = Some(rpi_rlc_acc_cell); - if let Some(block_number) = block_number { + if let Some(block_number) = block_offset { self.q_block_table.enable(region, row_offset)?; region.assign_advice( || "block_index", @@ -417,19 +1222,1057 @@ impl TaikoPiCircuitConfig { Ok(cells.into_iter().map(|cell| cell.unwrap()).collect()) } + #[allow(clippy::type_complexity)] + fn get_block_header_rlp_from_public_data( + public_data: &PublicData, + randomness: Value, + ) -> (Vec, Vec, Vec, Vec>, Value, Value) { + // RLP encode the block header data + let mut stream = RlpStream::new(); + stream.begin_unbounded_list(); + stream + .append(&public_data.parent_hash) + .append(&*OMMERS_HASH) + .append(&public_data.beneficiary) + .append(&public_data.state_root) + .append(&public_data.transactions_root) + .append(&public_data.receipts_root) + .append(&vec![0u8; LOGS_BLOOM_SIZE]) // logs_bloom is all zeros + .append(&public_data.block_context.difficulty) + .append(&public_data.block_context.number.as_u64()) + .append(&U256::from(public_data.block_context.gas_limit)) + // .append(&(public_data.block_context.gas_limit)) + .append(&public_data.gas_used) + .append(&public_data.block_context.timestamp); + rlp_opt(&mut stream, &None::); // extra_data = "" + stream + .append(&public_data.mix_hash) + .append(&vec![0u8; 8]) // nonce = 0 + .append(&public_data.block_context.base_fee) + .append(&public_data.withdrawals_root); + stream.finalize_unbounded_list(); + let mut bytes: Vec = stream.out().into(); + + // Calculate the block hash + let hash = keccak256(&bytes); + let hash_hi = hash.iter().take(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + let hash_lo = hash.iter().skip(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + + let mut leading_zeros: Vec = vec![0; bytes.len()]; + let mut blk_hdr_do_rlc_acc: Vec = vec![1; bytes.len()]; + let mut blk_hdr_rlc_acc: Vec> = vec![]; + + // Calculate the RLC of the bytes + bytes.iter().map(|b| Value::known(F::from(*b as u64))).fold( + Value::known(F::ZERO), + |mut rlc_acc, byte| { + rlc_acc = rlc_acc * randomness + byte; + blk_hdr_rlc_acc.push(rlc_acc); + rlc_acc + }, + ); + + // Handles leading zeros, short values and calculates the values for + // `blk_hdr_is_leading_zero` and `blk_hdr_rlc_acc` + let block = &public_data.block_context; + for (field, offset, zeros_bias) in [ + (U256::from(block.number.as_u64()), NUMBER_RLP_OFFSET, 32 - 8), + (block.gas_limit.into(), GAS_LIMIT_RLP_OFFSET, 0), + (public_data.gas_used.into(), GAS_USED_RLP_OFFSET, 0), + (block.timestamp, TIMESTAMP_RLP_OFFSET, 0), + (block.base_fee, BASE_FEE_RLP_OFFSET, 0), + ] + .iter() + { + // If the field has a short value then there is no RLP header. + // We need add an artificial RLP header with field length of one (0x80) to align + // the field. + // When the field is zero, it is represented by 0x80, + // which just so happens to be the value of the artificial header we need, + // thus we skip adding it. + // The field's value for the circuit will still be zero due to + // the leading zeros padding filling up the whole field. + if *field <= U256::from(0x80) { + if *field != U256::zero() { + bytes.insert(offset - 1, 0x80); + // Skipping artificial header for RLC. Since we accumulate the next byte in + // gates, we denote the skip one row earlier + blk_hdr_do_rlc_acc.insert(offset - 2, 0); + // Copy the current RLC when skipping + blk_hdr_rlc_acc.insert(offset - 1, blk_hdr_rlc_acc[offset - 2]); + } + leading_zeros.insert(offset - 1, 0); + } + + // Pad the field with the required amount of leading zeros + let num_leading_zeros = ((field.leading_zeros() / 8) - zeros_bias) as usize; + bytes.splice(offset..offset, vec![0; num_leading_zeros]); + leading_zeros.splice(offset..offset, vec![1; num_leading_zeros]); + // Skipping leading zeros for RLC. Since we accumulate the next byte in gates, + // we denote the skip one row earlier + blk_hdr_do_rlc_acc.splice(offset - 1..offset - 1, vec![0; num_leading_zeros]); + // Copy the current RLC when skipping + blk_hdr_rlc_acc.splice( + offset..offset, + vec![blk_hdr_rlc_acc[*offset - 1]; num_leading_zeros], + ); + } + + ( + bytes, + leading_zeros, + blk_hdr_do_rlc_acc, + blk_hdr_rlc_acc, + Value::known(hash_hi), + Value::known(hash_lo), + ) + } + + // Assigns all columns relevant to the blockhash checks + fn assign_block_hash_calc( + &self, + region: &mut Region<'_, F>, + public_data: &PublicData, + block_offset: usize, + challenges: &Challenges>, + ) { + let randomness = challenges.evm_word(); + // Current block is the exception, it sits on offset zero but hash block number + // = CURRENT_BLOCK_NUM The rest blocks are following, with their block + // number being one less from their position + let blk_offset = if block_offset == CURRENT_BLOCK_NUM { + 0 + } else { + (block_offset + 1) * BLOCKHASH_TOTAL_ROWS + }; + + self.blockhash_cols + .q_blk_hdr_rlc_start + .enable(region, blk_offset) + .unwrap(); + self.blockhash_cols + .q_blk_hdr_rlp_end + .enable(region, blk_offset + BLOCKHASH_TOTAL_ROWS - 1) + .unwrap(); + + region + .assign_fixed( + || "block_table_index_blockhash", + self.blockhash_cols.block_table_index_blockhash, + blk_offset + BLOCKHASH_TOTAL_ROWS - 1, + || Value::known(F::from(block_offset as u64)), + ) + .unwrap(); + + // We use the previous row for the `PreviousHashHi` tag as in this row + // `WithdrawalRoot` is set too + region + .assign_fixed( + || "block_table_tag_blockhash", + self.blockhash_cols.block_table_tag_blockhash, + blk_offset + BLOCKHASH_TOTAL_ROWS - 2, + || Value::known(F::from(BlockContextFieldTag::PreviousHashHi as u64)), + ) + .unwrap(); + + region + .assign_fixed( + || "block_table_index_blockhash", + self.blockhash_cols.block_table_index_blockhash, + blk_offset + BLOCKHASH_TOTAL_ROWS - 2, + || Value::known(F::from(block_offset as u64)), + ) + .unwrap(); + + // We need to push `PreviousHashLo` tag up one row since `PreviousHashHi` + // uses the current row + region + .assign_fixed( + || "block_table_tag_blockhash", + self.blockhash_cols.block_table_tag_blockhash, + blk_offset + BLOCKHASH_TOTAL_ROWS - 3, + || Value::known(F::from(BlockContextFieldTag::PreviousHashLo as u64)), + ) + .unwrap(); + + region + .assign_fixed( + || "block_table_index_blockhash", + self.blockhash_cols.block_table_index_blockhash, + blk_offset + BLOCKHASH_TOTAL_ROWS - 3, + || Value::known(F::from(block_offset as u64)), + ) + .unwrap(); + + if block_offset != CURRENT_BLOCK_NUM { + self.blockhash_cols + .q_lookup_blockhash + .enable(region, blk_offset + BLOCKHASH_TOTAL_ROWS - 1) + .unwrap(); + } + + let ( + block_header_rlp_byte, + leading_zeros, + blk_hdr_do_rlc_acc, + blk_hdr_rlc_acc, + blk_hdr_hash_hi, + blk_hdr_hash_lo, + ) = Self::get_block_header_rlp_from_public_data(public_data, challenges.keccak_input()); + + // Construct all the constant values of the block header. + // `c()` is for constant values, `v()` is for variable values. + let c = |value| (true, value); + let v = || (false, 123456); + let rlp_const: Vec<(bool, u64)> = [ + vec![c(0xF9), c(0x02), v()], // RLP list header + vec![c(0xA0)], + vec![v(); PARENT_HASH_SIZE], // Parent hash + vec![c(0xA0)], + (*OMMERS_HASH) + .as_bytes() + .iter() + .map(|b| c(*b as u64)) + .collect(), // Ommers hash + vec![c(0x94)], + vec![v(); BENEFICIARY_SIZE], // Beneficiary + vec![c(0xA0)], + vec![v(); STATE_ROOT_SIZE], // State root + vec![c(0xA0)], + vec![v(); TX_ROOT_SIZE], // Tx root + vec![c(0xA0)], + vec![v(); RECEIPTS_ROOT_SIZE], // Receipt root + vec![c(0xB9), c(0x01), c(0x00)], + vec![v(); LOGS_BLOOM_SIZE], // Bloom filter + vec![c(0x80)], // Difficulty + vec![v(); 1 + NUMBER_SIZE], // number + vec![v(); 1 + GAS_LIMIT_SIZE], // Gas limit + vec![v(); 1 + GAS_USED_SIZE], // Gas used + vec![v(); 1 + TIMESTAMP_SIZE], // Timestamp + vec![c(0x80)], // Extra data + vec![c(0xA0)], + vec![v(); MIX_HASH_SIZE], // Mix hash + vec![c(0x88)], + vec![v(); NONCE_SIZE], // Nonce + vec![v(); 1 + BASE_FEE_SIZE], // Base fee + vec![c(0xA0)], + vec![v(); WITHDRAWALS_ROOT_SIZE], // Withdrawals Root + ] + .concat(); + + for (offset, rlp_byte) in block_header_rlp_byte.iter().enumerate() { + let absolute_offset = blk_offset + offset; + region + .assign_advice( + || "blk_hdr_rlp", + self.blockhash_cols.blk_hdr_rlp, + absolute_offset, + || Value::known(F::from(*rlp_byte as u64)), + ) + .unwrap(); + region + .assign_advice( + || "blk_hdr_rlp_inv", + self.blockhash_cols.blk_hdr_rlp_inv, + absolute_offset, + || Value::known(F::from((*rlp_byte) as u64).invert().unwrap_or(F::ZERO)), + ) + .unwrap(); + region + .assign_advice( + || "blk_hdr_do_rlc_acc", + self.blockhash_cols.blk_hdr_do_rlc_acc, + absolute_offset, + || Value::known(F::from(blk_hdr_do_rlc_acc[offset] as u64)), + ) + .unwrap(); + region + .assign_advice( + || "blk_hdr_rlc_acc", + self.blockhash_cols.blk_hdr_rlc_acc, + absolute_offset, + || blk_hdr_rlc_acc[offset], + ) + .unwrap(); + region + .assign_advice( + || "blk_hdr_is_leading_zero", + self.blockhash_cols.blk_hdr_is_leading_zero, + absolute_offset, + || Value::known(F::from(leading_zeros[offset] as u64)), + ) + .unwrap(); + + self.blockhash_cols + .q_blk_hdr_rlp + .enable(region, absolute_offset) + .unwrap(); + } + + // Calculate reconstructed values + let mut reconstructed_values: Vec>> = vec![]; + for (index, value) in [ + // parent_hash hi + public_data.parent_hash.as_fixed_bytes()[0..PARENT_HASH_SIZE / 2].iter(), + // parent_hash lo + public_data.parent_hash.as_fixed_bytes()[PARENT_HASH_SIZE / 2..PARENT_HASH_SIZE].iter(), + public_data.beneficiary.as_fixed_bytes().iter(), + public_data.state_root.as_fixed_bytes().iter(), + public_data.transactions_root.as_fixed_bytes().iter(), + public_data.receipts_root.as_fixed_bytes().iter(), + public_data + .block_context + .number + .as_u64() + .to_be_bytes() + .iter(), + U256::from(public_data.block_context.gas_limit) + .to_be_bytes() + .iter(), + U256::from(public_data.gas_used).to_be_bytes().iter(), + public_data.block_context.timestamp.to_be_bytes().iter(), + public_data.mix_hash.as_fixed_bytes().iter(), + public_data.block_context.base_fee.to_be_bytes().iter(), + public_data.withdrawals_root.as_fixed_bytes().iter(), + ] + .iter() + .enumerate() + { + reconstructed_values.push( + value + .clone() + .scan(Value::known(F::ZERO), |acc, &x| { + *acc = if index <= 1 { + let mut acc_shifted = *acc; + for _ in 0..8 { + acc_shifted = acc_shifted * Value::known(F::from(2)); + } + acc_shifted + } else { + *acc * randomness + } + Value::known(F::from(x as u64)); + Some(*acc) + }) + .collect::>>(), + ); + } + + for (offset, (v, q)) in rlp_const.iter().enumerate() { + let absolute_offset = blk_offset + offset; + region + .assign_fixed( + || "blk_hdr_rlp_const", + self.blockhash_cols.blk_hdr_rlp_const, + absolute_offset, + || Value::known(F::from(*v as u64)), + ) + .unwrap(); + if *q == 1 { + self.blockhash_cols + .q_blk_hdr_rlp_const + .enable(region, absolute_offset) + .unwrap(); + } + } + + let mut length_calc = F::ZERO; + for (field_num, (name, base_offset, is_reconstruct)) in [ + ("parent_hash hi", PARENT_HASH_RLP_OFFSET, true), + ( + "parent_hash lo", + PARENT_HASH_RLP_OFFSET + PARENT_HASH_SIZE / 2, + true, + ), + ("beneficiary", BENEFICIARY_RLP_OFFSET, true), + ("state_root", STATE_ROOT_RLP_OFFSET, true), + ("tx_root", TX_ROOT_RLP_OFFSET, true), + ("receipts_root", RECEIPTS_ROOT_RLP_OFFSET, true), + ("number", NUMBER_RLP_OFFSET, true), + ("gas_limit", GAS_LIMIT_RLP_OFFSET, false), + ("gas_used", GAS_USED_RLP_OFFSET, false), + ("timestamp", TIMESTAMP_RLP_OFFSET, false), + ("mix_hash", MIX_HASH_RLP_OFFSET, true), + ("base_fee_per_gas", BASE_FEE_RLP_OFFSET, false), + ("withdrawals_root", WITHDRAWALS_ROOT_RLP_OFFSET, true), + ] + .iter() + .enumerate() + { + for (offset, val) in reconstructed_values[field_num].iter().enumerate() { + let absolute_offset = blk_offset + base_offset + offset; + let is_parent_hash_hi = *name == "parent_hash hi"; + let is_parent_hash_lo = *name == "parent_hash lo"; + let is_parent_hash = is_parent_hash_hi || is_parent_hash_lo; + + // `q_parent_hash` enables the lookup of parent_hash against the past 256 block + // hashes We skip this check for the oldest block as we don't + // have its parent block hash to compare it with + if block_offset != OLDEST_BLOCK_NUM { + if is_parent_hash { + self.blockhash_cols + .q_parent_hash + .enable(region, absolute_offset) + .unwrap(); + } + if is_parent_hash_hi { + region + .assign_fixed( + || "parent hash q_hi", + self.blockhash_cols.q_hi, + absolute_offset, + || Value::known(F::ONE), + ) + .unwrap(); + } else if is_parent_hash_lo { + region + .assign_fixed( + || "parent hash q_lo", + self.blockhash_cols.q_lo, + absolute_offset, + || Value::known(F::ONE), + ) + .unwrap(); + } + } + + region + .assign_advice( + || "reconstruct_value for ".to_string() + name, + self.blockhash_cols.blk_hdr_reconstruct_value, + absolute_offset, + || *val, + ) + .unwrap(); + + if *is_reconstruct && !(is_parent_hash && block_offset == OLDEST_BLOCK_NUM) { + region + .assign_fixed( + || "q_reconstruct for ".to_string() + name, + self.blockhash_cols.q_reconstruct, + absolute_offset, + || Value::known(F::ONE), + ) + .unwrap(); + } + + if [ + GAS_LIMIT_RLP_OFFSET, + GAS_USED_RLP_OFFSET, + TIMESTAMP_RLP_OFFSET, + BASE_FEE_RLP_OFFSET, + NUMBER_RLP_OFFSET, + ] + .contains(base_offset) + { + let field_size: usize; + let field_lead_zeros_num: u32; + let gas_limit = &U256::from(public_data.block_context.gas_limit); + let gas_used = &U256::from(public_data.gas_used); + + match *base_offset { + GAS_LIMIT_RLP_OFFSET => { + (field_size, field_lead_zeros_num) = + (GAS_LIMIT_RLP_LEN - 1, gas_limit.leading_zeros() / 8) + } + GAS_USED_RLP_OFFSET => { + (field_size, field_lead_zeros_num) = + (GAS_USED_RLP_LEN - 1, gas_used.leading_zeros() / 8) + } + TIMESTAMP_RLP_OFFSET => { + (field_size, field_lead_zeros_num) = ( + TIMESTAMP_RLP_LEN - 1, + &public_data.block_context.timestamp.leading_zeros() / 8, + ) + } + BASE_FEE_RLP_OFFSET => { + (field_size, field_lead_zeros_num) = ( + BASE_FEE_RLP_LEN - 1, + &public_data.block_context.base_fee.leading_zeros() / 8, + ) + } + _ => { + (field_size, field_lead_zeros_num) = ( + NUMBER_RLP_LEN - 1, + &public_data.block_context.number.as_u64().leading_zeros() / 8, + ) + } + } + + if (offset < field_lead_zeros_num as usize) + || // short RLP values have 0 length + (offset == field_size - 1 + && length_calc == F::ZERO + && block_header_rlp_byte[base_offset + offset] <= 0x80) + { + length_calc = F::ZERO; + } else { + length_calc = F::from(offset as u64 - field_lead_zeros_num as u64 + 1); + } + + region + .assign_advice( + || "length of ".to_string() + name, + self.blockhash_cols.blk_hdr_rlp_len_calc, + absolute_offset, + || Value::known(length_calc), + ) + .unwrap(); + + region + .assign_advice( + || "inverse length of ".to_string() + name, + self.blockhash_cols.blk_hdr_rlp_len_calc_inv, + absolute_offset, + || Value::known(length_calc.invert().unwrap_or(F::ZERO)), + ) + .unwrap(); + + let selector = if *base_offset == NUMBER_RLP_OFFSET { + self.blockhash_cols.q_number + } else { + self.blockhash_cols.q_var_field_256 + }; + region + .assign_fixed( + || "q_number and q_var_field_256", + selector, + absolute_offset, + || Value::known(F::ONE), + ) + .unwrap(); + } + } + } + + // Set the block table tags for fields with only one index + for (offset, tag) in [ + ( + PARENT_HASH_RLP_OFFSET + PARENT_HASH_SIZE / 2, + BlockContextFieldTag::PreviousHashHi, + ), + ( + PARENT_HASH_RLP_OFFSET + PARENT_HASH_SIZE, + BlockContextFieldTag::PreviousHashLo, + ), + ( + BENEFICIARY_RLP_OFFSET + BENEFICIARY_SIZE, + BlockContextFieldTag::Beneficiary, + ), + ( + STATE_ROOT_RLP_OFFSET + STATE_ROOT_SIZE, + BlockContextFieldTag::StateRoot, + ), + ( + TX_ROOT_RLP_OFFSET + TX_ROOT_SIZE, + BlockContextFieldTag::TransactionsRoot, + ), + ( + RECEIPTS_ROOT_RLP_OFFSET + RECEIPTS_ROOT_SIZE, + BlockContextFieldTag::ReceiptsRoot, + ), + ( + NUMBER_RLP_OFFSET + NUMBER_SIZE, + BlockContextFieldTag::Number, + ), + ( + GAS_LIMIT_RLP_OFFSET + GAS_LIMIT_SIZE, + BlockContextFieldTag::GasLimit, + ), + ( + GAS_USED_RLP_OFFSET + GAS_USED_SIZE, + BlockContextFieldTag::GasUsed, + ), + ( + TIMESTAMP_RLP_OFFSET + TIMESTAMP_SIZE, + BlockContextFieldTag::Timestamp, + ), + ( + MIX_HASH_RLP_OFFSET + MIX_HASH_SIZE, + BlockContextFieldTag::MixHash, + ), + ( + BASE_FEE_RLP_OFFSET + BASE_FEE_SIZE, + BlockContextFieldTag::BaseFee, + ), + ( + WITHDRAWALS_ROOT_RLP_OFFSET + WITHDRAWALS_ROOT_SIZE, + BlockContextFieldTag::WithdrawalsRoot, + ), + ] + .iter() + { + let absolute_offset = blk_offset + offset - 1; + region + .assign_fixed( + || "block_table_tag_blockhash", + self.blockhash_cols.block_table_tag_blockhash, + absolute_offset, + || Value::known(F::from(*tag as u64)), + ) + .unwrap(); + + region + .assign_fixed( + || "block_table_index_blockhash", + self.blockhash_cols.block_table_index_blockhash, + absolute_offset, + || Value::known(F::from(block_offset as u64)), + ) + .unwrap(); + } + + // Determines if it is a short RLP value + let lt_chip = LtChip::construct(self.rlp_is_short); + for (offset, &byte) in block_header_rlp_byte.iter().enumerate() { + lt_chip + .assign( + region, + blk_offset + offset, + F::from(byte as u64), + F::from(RLP_HDR_NOT_SHORT), + ) + .unwrap(); + } + + // Set the block header hash parts + region + .assign_advice( + || "blk_hdr_hash_hi", + self.blockhash_cols.blk_hdr_blockhash, + blk_offset + BLOCKHASH_TOTAL_ROWS - 1, + || blk_hdr_hash_hi, + ) + .unwrap(); + region + .assign_advice( + || "blk_hdr_hash_lo", + self.blockhash_cols.blk_hdr_blockhash, + blk_offset + BLOCKHASH_TOTAL_ROWS - 2, + || blk_hdr_hash_lo, + ) + .unwrap(); + } + + #[allow(clippy::type_complexity)] + fn assign_block_table( + &self, + region: &mut Region<'_, F>, + public_data: &PublicData, + block_offset: usize, + test_public_data: &Option>, + challenges: &Challenges>, + ) -> Result<(), Error> { + // When in negative testing, we need to bypass the actual public_data with some + // wrong test data + let pb = test_public_data.as_ref().unwrap_or(public_data); + let block_values = pb.get_block_table_values(); + let randomness = challenges.evm_word(); + self.q_start.enable(region, 0)?; + + let base_offset = if block_offset == CURRENT_BLOCK_NUM { + 0 + } else { + BLOCK_LEN_IN_TABLE * (block_offset + 1) + BLOCK_TABLE_MISC_LEN + }; + + let mut block_data: Vec<(&str, BlockContextFieldTag, usize, Value)> = vec![ + ( + "coinbase", + BlockContextFieldTag::Coinbase, + block_offset, + Value::known(block_values.coinbase.to_scalar().unwrap()), + ), + ( + "timestamp", + BlockContextFieldTag::Timestamp, + block_offset, + Value::known(F::from(block_values.timestamp)), + ), + ( + "number", + BlockContextFieldTag::Number, + block_offset, + randomness.map(|randomness| { + rlc( + [0; 32 - NUMBER_SIZE] + .into_iter() + .chain(block_values.number.to_be_bytes().into_iter()) + .rev() + .collect::>() + .try_into() + .unwrap(), + randomness, + ) + }), + ), + ( + "difficulty", + BlockContextFieldTag::Difficulty, + block_offset, + randomness.map(|randomness| rlc(block_values.difficulty.to_le_bytes(), randomness)), + ), + ( + "gas_limit", + BlockContextFieldTag::GasLimit, + block_offset, + Value::known(F::from(block_values.gas_limit)), + ), + ( + "base_fee", + BlockContextFieldTag::BaseFee, + block_offset, + randomness.map(|randomness| rlc(block_values.base_fee.to_le_bytes(), randomness)), + ), + ( + "chain_id", + BlockContextFieldTag::ChainId, + block_offset, + Value::known(F::from(block_values.chain_id)), + ), + ( + "beneficiary", + BlockContextFieldTag::Beneficiary, + block_offset, + randomness.map(|randomness| { + rlc( + ([0u8; 32 - BENEFICIARY_SIZE] + .into_iter() + .chain(pb.beneficiary.to_fixed_bytes().into_iter())) + .rev() + .collect::>() + .try_into() + .unwrap(), + randomness, + ) + }), + ), + ( + "state_root", + BlockContextFieldTag::StateRoot, + block_offset, + randomness.map(|randomness| { + rlc( + pb.state_root + .to_fixed_bytes() + .into_iter() + .rev() + .collect::>() + .try_into() + .unwrap(), + randomness, + ) + }), + ), + ( + "transactions_root", + BlockContextFieldTag::TransactionsRoot, + block_offset, + randomness.map(|randomness| { + rlc( + pb.transactions_root + .to_fixed_bytes() + .into_iter() + .rev() + .collect::>() + .try_into() + .unwrap(), + randomness, + ) + }), + ), + ( + "receipts_root", + BlockContextFieldTag::ReceiptsRoot, + block_offset, + randomness.map(|randomness| { + rlc( + pb.receipts_root + .to_fixed_bytes() + .into_iter() + .rev() + .collect::>() + .try_into() + .unwrap(), + randomness, + ) + }), + ), + ( + "gas_used", + BlockContextFieldTag::GasUsed, + block_offset, + Value::known(F::from(pb.gas_used as u64)), + ), + ( + "mix_hash", + BlockContextFieldTag::MixHash, + block_offset, + randomness.map(|randomness| { + rlc( + pb.mix_hash + .to_fixed_bytes() + .into_iter() + .rev() + .collect::>() + .try_into() + .unwrap(), + randomness, + ) + }), + ), + ( + "withdrawals_root", + BlockContextFieldTag::WithdrawalsRoot, + block_offset, + randomness.map(|randomness| { + rlc( + pb.withdrawals_root + .to_fixed_bytes() + .into_iter() + .rev() + .collect::>() + .try_into() + .unwrap(), + randomness, + ) + }), + ), + ]; + + // The following need to be added only once in block table + if block_offset == CURRENT_BLOCK_NUM { + block_data.extend_from_slice( + block_values + .history_hashes + .iter() + .enumerate() + .map(|(i, h)| { + ( + "prev_hash", + BlockContextFieldTag::PreviousHash, + i, + randomness.map(|randomness| rlc(h.to_le_bytes(), randomness)), + ) + }) + .collect_vec() + .as_slice(), + ); + block_data.extend_from_slice( + block_values + .history_hashes + .iter() + .enumerate() + .map(|(i, h)| { + ( + "prev_hash hi", + BlockContextFieldTag::PreviousHashHi, + i, + Value::known( + h.to_be_bytes().iter().take(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }), + ), + ) + }) + .collect_vec() + .as_slice(), + ); + block_data.extend_from_slice( + block_values + .history_hashes + .iter() + .enumerate() + .map(|(i, h)| { + ( + "prev_hash lo", + BlockContextFieldTag::PreviousHashLo, + i, + Value::known( + h.to_be_bytes().iter().skip(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }), + ), + ) + }) + .collect_vec() + .as_slice(), + ); + block_data.extend_from_slice(&[( + "zero", + BlockContextFieldTag::None, + 0, + Value::known(F::ZERO), + )]); + } + + let mut chain_id_cell = vec![]; + for (offset, (name, tag, idx, val)) in block_data.into_iter().enumerate() { + let absolute_offset = base_offset + offset; + self.q_block_table.enable(region, absolute_offset)?; + region.assign_advice( + || name, + self.block_table_blockhash.tag, + absolute_offset, + || Value::known(F::from(tag as u64)), + )?; + region.assign_advice( + || name, + self.block_table_blockhash.index, + absolute_offset, + || Value::known(F::from(idx as u64)), + )?; + + let cell = region.assign_advice( + || name, + self.block_table_blockhash.value, + absolute_offset, + || val, + )?; + if name == "chain_id" { + chain_id_cell.push(cell); + } + } + + Ok(()) + } + fn assign( &self, layouter: &mut impl Layouter, - public_data: &PublicData, + public_data: &PublicData, + test_public_data: &Option>, challenges: &Challenges>, ) -> Result<(), Error> { + let lt_chip: LtChip = LtChip::construct(self.rlp_is_short); + lt_chip.load(layouter)?; + let pi = layouter.assign_region( || "region 0", |ref mut region| { + // avoid the humongous amount of rubbish halo2 errors produced when circuit is + // failing + for i in 0..BLOCKHASH_TOTAL_ROWS * (PREVIOUS_BLOCKS_NUM + 1) { + for field in [ + self.blockhash_cols.blk_hdr_reconstruct_value, + self.blockhash_cols.blk_hdr_is_leading_zero, + self.blockhash_cols.blk_hdr_rlp_inv, + self.blockhash_cols.blk_hdr_rlp_len_calc, + self.blockhash_cols.blk_hdr_rlp_len_calc_inv, + self.blockhash_cols.blk_hdr_reconstruct_value, + self.blockhash_cols.blk_hdr_reconstruct_hi_lo, + self.blockhash_cols.blk_hdr_rlc_acc, + self.blockhash_cols.blk_hdr_do_rlc_acc, + self.blockhash_cols.blk_hdr_blockhash, + self.block_index, + self.rpi_field_bytes, + self.rpi_field_bytes_acc, + self.rpi_rlc_acc, + self.block_index, + self.rpi_field_bytes, + self.rpi_field_bytes_acc, + ] { + region + .assign_advice(|| "0", field, i, || Value::known(F::ZERO)) + .unwrap(); + } + + for field in [ + self.blockhash_cols.blk_hdr_rlp_const, + self.blockhash_cols.q_hi, + self.blockhash_cols.q_lo, + self.blockhash_cols.block_table_tag_blockhash, + self.blockhash_cols.block_table_index_blockhash, + self.blockhash_cols.q_reconstruct, + self.blockhash_cols.q_number, + self.blockhash_cols.q_var_field_256, + self.is_field_rlc, + ] { + region + .assign_fixed(|| "0", field, i, || Value::known(F::ZERO)) + .unwrap(); + } + } + + // Annotate columns + self.block_table.annotate_columns_in_region(region); + self.block_table_blockhash + .annotate_columns_in_region(region); + self.rlp_is_short.annotate_columns_in_region(region); + region.name_column(|| "rpi_field_bytes", self.rpi_field_bytes); + region.name_column(|| "rpi_field_bytes_acc", self.rpi_field_bytes_acc); + region.name_column(|| "is_field_rlc", self.is_field_rlc); + region.name_column(|| "block_index", self.block_index); + region.name_column( + || "blockhash_cols.blk_hdr_rlp_len_calc", + self.blockhash_cols.blk_hdr_rlp_len_calc, + ); + region.name_column( + || "blockhash_cols.blk_hdr_rlp_len_calc_inv", + self.blockhash_cols.blk_hdr_rlp_len_calc_inv, + ); + region.name_column( + || "blockhash_cols.blk_hdr_reconstruct_value", + self.blockhash_cols.blk_hdr_reconstruct_value, + ); + region.name_column( + || "blockhash_cols.blk_hdr_reconstruct_hi_lo", + self.blockhash_cols.blk_hdr_reconstruct_hi_lo, + ); + region.name_column( + || "blockhash_cols.blk_hdr_rlc_acc", + self.blockhash_cols.blk_hdr_rlc_acc, + ); + region.name_column( + || "blockhash_cols.blk_hdr_do_rlc_acc", + self.blockhash_cols.blk_hdr_do_rlc_acc, + ); + region.name_column( + || "blockhash_cols.blk_hdr_is_leading_zero", + self.blockhash_cols.blk_hdr_is_leading_zero, + ); + region.name_column(|| "rpi_rlc_acc", self.rpi_rlc_acc); + region.name_column(|| "Public_Inputs", self.pi); + region.name_column(|| "fixed_u8", self.fixed_u8); + + // Assign current block + self.assign_block_hash_calc(region, public_data, CURRENT_BLOCK_NUM, challenges); + self.assign_block_table( + region, + public_data, + CURRENT_BLOCK_NUM, + test_public_data, + challenges, + )?; + + for (block_offset, prev_block) in public_data.previous_blocks + [0..PREVIOUS_BLOCKS_NUM] + .iter() + .enumerate() + { + let prev_public_data = PublicData::new(prev_block); + self.assign_block_hash_calc( + region, + &prev_public_data, + block_offset, + challenges, + ); + self.assign_block_table( + region, + public_data, + block_offset, + test_public_data, + challenges, + )?; + } + let mut rpi_rlc_acc = Value::known(F::ZERO); let mut offset = 0; let mut rpi_rlc_acc_cell = None; - for (annotation, block_number, field_bytes) in public_data.assignments() { + for (annotation, block_offset, field_bytes) in public_data.assignments() { let cells = self.assign_pi_field( region, &mut offset, @@ -438,7 +2281,7 @@ impl TaikoPiCircuitConfig { &mut rpi_rlc_acc, challenges, false, - block_number, + block_offset, )?; rpi_rlc_acc_cell = Some(cells[RPI_RLC_ACC_CELL_IDX].clone()); } @@ -515,18 +2358,64 @@ impl TaikoPiCircuitConfig { #[derive(Clone, Default, Debug)] pub struct TaikoPiCircuit { /// PublicInputs data known by the verifier - pub public_data: PublicData, + pub public_data: PublicData, + /// Test public data + pub test_public_data: Option>, _marker: PhantomData, } impl TaikoPiCircuit { /// Creates a new TaikoPiCircuit - pub fn new(public_data: PublicData) -> Self { + pub fn new(public_data: PublicData, test_public_data: Option>) -> Self { Self { public_data, + test_public_data, _marker: PhantomData, } } + + fn split_hash(hash: [u8; 32]) -> (F, F) { + let hi = hash.iter().take(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + + let lo = hash.iter().skip(16).fold(F::ZERO, |acc, byte| { + acc * F::from(BYTE_POW_BASE) + F::from(*byte as u64) + }); + (hi, lo) + } + + fn get_block_header_rlp_from_block(block: &witness::Block) -> (Bytes, F, F, H256) { + let mut stream = RlpStream::new(); + stream.begin_unbounded_list(); + stream + .append(&block.eth_block.parent_hash) + .append(&*OMMERS_HASH) + .append(&block.eth_block.author.unwrap_or_else(H160::zero)) + .append(&block.eth_block.state_root) + .append(&block.eth_block.transactions_root) + .append(&block.eth_block.receipts_root) + .append(&vec![0u8; LOGS_BLOOM_SIZE]) // logs_bloom is all zeros + .append(&block.context.difficulty) + .append(&block.context.number.low_u64()) + .append(&U256::from(block.context.gas_limit)) + .append(&U256::from(block.protocol_instance.gas_used)) + .append(&block.context.timestamp); + rlp_opt(&mut stream, &None::); // extra_data = "" + stream + .append(&block.eth_block.mix_hash.unwrap_or_else(H256::zero)) + .append(&vec![0u8; NONCE_SIZE]) // nonce = 0 + .append(&block.context.base_fee) + .append(&block.eth_block.withdrawals_root.unwrap_or_else(H256::zero)); + + stream.finalize_unbounded_list(); + let out: bytes::Bytes = stream.out().into(); + let rlp_bytes: Bytes = out.into(); + let hash = keccak256(&rlp_bytes); + let (hi, lo) = Self::split_hash(hash); + let hash_res = H256::from(hash); + (rlp_bytes, hi, lo, hash_res) + } } impl SubCircuit for TaikoPiCircuit { @@ -543,7 +2432,7 @@ impl SubCircuit for TaikoPiCircuit { } fn new_from_block(block: &witness::Block) -> Self { - TaikoPiCircuit::new(PublicData::new(block)) + TaikoPiCircuit::new(PublicData::new(block), None) } /// Compute the public inputs for this circuit. @@ -576,8 +2465,29 @@ impl SubCircuit for TaikoPiCircuit { challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { + layouter.assign_region( + || "fixed u8 table", + |mut region| { + for i in 0..(1 << 8) { + region.assign_fixed( + || format!("row_{}", i), + config.fixed_u8, + i, + || Value::known(F::from(i as u64)), + )?; + } + + Ok(()) + }, + )?; + config.byte_table.load(layouter)?; - config.assign(layouter, &self.public_data, challenges) + config.assign( + layouter, + &self.public_data, + &self.test_public_data, + challenges, + ) } } @@ -604,7 +2514,9 @@ impl Circuit for TaikoPiTestCircuit { fn configure(meta: &mut ConstraintSystem) -> Self::Config { let block_table = BlockTable::construct(meta); + let block_table_blockhash = BlockTable::construct(meta); let keccak_table = KeccakTable::construct(meta); + let keccak_table2 = KeccakTable2::construct(meta); let byte_table = ByteTable::construct(meta); let challenges = Challenges::construct(meta); let challenge_exprs = challenges.exprs(meta); @@ -613,7 +2525,9 @@ impl Circuit for TaikoPiTestCircuit { meta, TaikoPiCircuitConfigArgs { block_table, + block_table_blockhash, keccak_table, + keccak_table2, byte_table, challenges: challenge_exprs, }, @@ -640,6 +2554,17 @@ impl Circuit for TaikoPiTestCircuit { .dev_load(&mut layouter, vec![&public_data.rpi_bytes()], &challenges)?; config.byte_table.load(&mut layouter)?; + let pr_bl: Vec> = public_data + .previous_blocks_rlp + .iter() + .map(|a| a.to_vec()) + .collect(); + let cur_bl = public_data.blockhash_blk_hdr_rlp.to_vec(); + let all = pr_bl.iter().chain(vec![&cur_bl]); + config + .keccak_table2 + .dev_load(&mut layouter, all, &challenges)?; + self.0.synthesize_sub(&config, &challenges, &mut layouter) } } @@ -649,7 +2574,7 @@ mod taiko_pi_circuit_test { use super::*; - use eth_types::ToScalar; + use eth_types::{ToScalar, H64, U64}; use halo2_proofs::{ dev::{MockProver, VerifyFailure}, halo2curves::bn256::Fr, @@ -666,49 +2591,67 @@ mod taiko_pi_circuit_test { fn run( k: u32, - public_data: PublicData, + public_data: PublicData, pi: Option>>, + test_public_data: Option>, ) -> Result<(), Vec> { - let circuit = TaikoPiTestCircuit::(TaikoPiCircuit::new(public_data)); + let circuit = TaikoPiTestCircuit::(TaikoPiCircuit::new(public_data, test_public_data)); let public_inputs = pi.unwrap_or_else(|| circuit.0.instance()); let prover = match MockProver::run(k, &circuit, public_inputs) { Ok(prover) => prover, Err(e) => panic!("{:#?}", e), }; - prover.verify() - } - - fn mock_public_data() -> PublicData { - let mut public_data = PublicData::default::(); - public_data.meta_hash = OMMERS_HASH.to_word(); - public_data.block_hash = OMMERS_HASH.to_word(); - public_data.block_context.block_hash = OMMERS_HASH.to_word(); - public_data.block_context.history_hashes = vec![Default::default(); 256]; - public_data.block_context.number = 300.into(); - public_data + // prover.verify() + let res: Result<(), Vec> = prover.verify(); + let mut curated_res = Vec::new(); + if res.is_err() { + let errors = res.as_ref().err().unwrap(); + for error in errors.iter() { + match error { + VerifyFailure::CellNotAssigned { .. } => (), + _ => curated_res.push(<&halo2_proofs::dev::VerifyFailure>::clone(&error)), + }; + } + if !curated_res.is_empty() { + return res; + } + } + Ok(()) } #[test] fn test_default_pi() { - let public_data = mock_public_data(); + let (block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; - let k = 17; - assert_eq!(run::(k, public_data, None), Ok(())); + let k = 18; + assert_eq!(run::(k, public_data, None, None), Ok(())); } #[test] fn test_fail_pi_hash() { - let public_data = mock_public_data(); + let (block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; - let k = 17; - match run::(k, public_data, Some(vec![vec![Fr::zero(), Fr::one()]])) { + let k = 18; + match run::( + k, + public_data, + Some(vec![vec![Fr::zero(), Fr::one()]]), + None, + ) { Ok(_) => unreachable!("this case must fail"), Err(errs) => { - assert_eq!(errs.len(), 4); + assert_eq!(errs.len(), 2810); for err in errs { match err { VerifyFailure::Permutation { .. } => return, + VerifyFailure::CellNotAssigned { .. } => return, _ => unreachable!("unexpected error"), } } @@ -718,7 +2661,11 @@ mod taiko_pi_circuit_test { #[test] fn test_fail_pi_prover() { - let mut public_data = mock_public_data(); + let (block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; + let address_bytes = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ]; @@ -726,18 +2673,20 @@ mod taiko_pi_circuit_test { public_data.prover = Address::from_slice(&address_bytes); let prover: Fr = public_data.prover.to_scalar().unwrap(); - let k = 17; + let k = 18; match run::( k, public_data, Some(vec![vec![prover, Fr::zero(), Fr::one()]]), + None, ) { Ok(_) => unreachable!("this case must fail"), Err(errs) => { - assert_eq!(errs.len(), 4); + assert_eq!(errs.len(), 2810); for err in errs { match err { VerifyFailure::Permutation { .. } => return, + VerifyFailure::CellNotAssigned { .. } => return, _ => unreachable!("unexpected error"), } } @@ -747,30 +2696,282 @@ mod taiko_pi_circuit_test { #[test] fn test_simple_pi() { - let mut public_data = mock_public_data(); + let (block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; + let chain_id = 1337u64; public_data.chain_id = Word::from(chain_id); - let k = 17; - assert_eq!(run::(k, public_data, None), Ok(())); + let k = 18; + assert_eq!(run::(k, public_data, None, None), Ok(())); + } + + fn get_block_header_rlp_from_block(block: &witness::Block) -> (H256, Bytes) { + let mut stream = RlpStream::new(); + stream.begin_unbounded_list(); + stream + .append(&block.eth_block.parent_hash) + .append(&*OMMERS_HASH) + .append(&block.eth_block.author.unwrap_or_else(H160::zero)) + .append(&block.eth_block.state_root) + .append(&block.eth_block.transactions_root) + .append(&block.eth_block.receipts_root) + .append(&vec![0u8; LOGS_BLOOM_SIZE]) // logs_bloom is all zeros + .append(&block.context.difficulty) + .append(&block.context.number.low_u64()) + .append(&block.context.gas_limit) + .append(&U256::from(block.protocol_instance.gas_used)) + .append(&block.context.timestamp); + rlp_opt(&mut stream, &None::); // extra_data = "" + stream + .append(&block.eth_block.mix_hash.unwrap_or_else(H256::zero)) + .append(&vec![0u8; NONCE_SIZE]) // nonce = 0 + .append(&block.context.base_fee) + .append(&block.eth_block.withdrawals_root.unwrap_or_else(H256::zero)); + + stream.finalize_unbounded_list(); + let out: bytes::Bytes = stream.out().into(); + let rlp_bytes: Bytes = out.into(); + let hash = keccak256(&rlp_bytes); + (hash.into(), rlp_bytes) + } + + fn default_test_block() -> ( + witness::Block, + Address, + Vec>, + Vec, + ) { + let mut current_block = witness::Block::::default(); + + current_block.context.history_hashes = vec![U256::zero(); PREVIOUS_BLOCKS_NUM]; + let mut previous_blocks: Vec> = + vec![witness::Block::::default(); PREVIOUS_BLOCKS_NUM]; + let mut previous_blocks_rlp: Vec = vec![Bytes::default(); PREVIOUS_BLOCKS_NUM]; + let mut past_block_hash = H256::zero(); + let mut past_block_rlp: Bytes; + for i in 0..PREVIOUS_BLOCKS_NUM { + let mut past_block = witness::Block::::default(); + past_block.context.number = U256::from(0x100); + + past_block.eth_block.parent_hash = past_block_hash; + past_block.protocol_instance.parent_hash = past_block_hash; + (past_block_hash, past_block_rlp) = get_block_header_rlp_from_block(&past_block); + + current_block.context.history_hashes[i] = U256::from(past_block_hash.as_bytes()); + previous_blocks[i] = past_block.clone(); + previous_blocks_rlp[i] = past_block_rlp.clone(); + } + + let prover = current_block.protocol_instance.prover; + // Populate current block + current_block.eth_block.parent_hash = past_block_hash; + current_block.protocol_instance.parent_hash = past_block_hash; + current_block.eth_block.author = Some(prover); + current_block.eth_block.state_root = H256::zero(); + current_block.eth_block.transactions_root = H256::zero(); + current_block.eth_block.receipts_root = H256::zero(); + current_block.eth_block.logs_bloom = Some([0; LOGS_BLOOM_SIZE].into()); + current_block.eth_block.difficulty = U256::from(0); + current_block.eth_block.number = Some(U64::from(0x100)); + current_block.context.number = U256::from(0x100); + current_block.eth_block.gas_limit = U256::from(0); + current_block.protocol_instance.block_max_gas_limit = 0; + current_block.eth_block.gas_used = U256::from(0); + current_block.protocol_instance.gas_used = 0; + current_block.eth_block.timestamp = U256::from(0); + current_block.context.timestamp = U256::from(0); + current_block.eth_block.extra_data = eth_types::Bytes::from([0; 0]); + current_block.eth_block.mix_hash = Some(H256::zero()); + current_block.eth_block.nonce = Some(H64::from([0, 0, 0, 0, 0, 0, 0, 0])); + current_block.eth_block.base_fee_per_gas = Some(U256::from(0)); + current_block.eth_block.withdrawals_root = Some(H256::zero()); + + (current_block, prover, previous_blocks, previous_blocks_rlp) } #[test] - fn test_verify() { - let mut block = witness::Block::::default(); + fn test_blockhash_verify() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + let k = 18; - block.eth_block.parent_hash = *OMMERS_HASH; - block.eth_block.hash = Some(*OMMERS_HASH); - block.protocol_instance.block_hash = *OMMERS_HASH; - block.protocol_instance.parent_hash = *OMMERS_HASH; - block.context.history_hashes = vec![OMMERS_HASH.to_word()]; - block.context.block_hash = OMMERS_HASH.to_word(); - block.context.number = 300.into(); + let (mut block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + block.context.number = U256::from(0x100); - let public_data = PublicData::new(&block); + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; - let k = 17; + assert_eq!(run::(k, public_data, None, None), Ok(())); + } + + #[test] + fn test_blockhash_calc_short_values() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + let k = 18; + + let (mut block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + block.context.number = U256::from(0x100); + block.context.gas_limit = 0x76; + block.protocol_instance.gas_used = 0x77; + block.context.timestamp = U256::from(0x78); + block.context.base_fee = U256::from(0x79); + + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; + + assert_eq!(run::(k, public_data, None, None), Ok(())); + } + + #[test] + fn test_blockhash_calc_one_byte_non_short_values() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + let k = 18; + + let (mut block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + block.context.number = U256::from(0x100); + block.context.gas_limit = RLP_HDR_NOT_SHORT; + block.protocol_instance.gas_used = RLP_HDR_NOT_SHORT as u32; + block.context.timestamp = U256::from(RLP_HDR_NOT_SHORT); + block.context.base_fee = U256::from(RLP_HDR_NOT_SHORT); + + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; + + assert_eq!(run::(k, public_data, None, None), Ok(())); + } + + #[test] + fn test_blockhash_calc_one_byte_non_short_values_2() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + let k = 18; + + let (mut block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + block.context.number = U256::from(0x100); + block.context.gas_limit = 0xFF; + block.protocol_instance.gas_used = 0xff; + block.context.timestamp = U256::from(0xFF); + block.context.base_fee = U256::from(0xFF); + + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; + + assert_eq!(run::(k, public_data, None, None), Ok(())); + } - assert_eq!(run::(k, public_data, None), Ok(())); + #[test] + fn test_blockhash_calc_leading_zeros() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + let k = 18; + + let (mut block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + block.context.number = U256::from(0x100); + block.context.gas_limit = 0x0000919191919191; + block.protocol_instance.gas_used = 0x92 << (2 * 8); + block.context.timestamp = U256::from(0x93) << (7 * 8); + block.context.base_fee = U256::from(0x94) << (26 * 8); + + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; + + assert_eq!(run::(k, public_data, None, None), Ok(())); + } + + #[test] + fn test_blockhash_calc_max_lengths() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + let k = 18; + + let (mut block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + + block.context.number = U256::from(0x100); + block.context.gas_limit = 0x9191919191919191; + block.protocol_instance.block_max_gas_limit = 0x9191919191919191; + + block.eth_block.gas_used = U256::from(0x92); // << (31 * 8); + block.protocol_instance.gas_used = 0x92; // << (31 * 8); + + block.context.timestamp = U256::from(0x93); // << (31 * 8); + block.context.base_fee = U256::from(0x94) << (31 * 8); + + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; + + assert_eq!(run::(k, public_data, None, None), Ok(())); + } + + #[test] + fn test_blockhash_calc_fail_lookups() { + const MAX_TXS: usize = 8; + const MAX_CALLDATA: usize = 200; + let k = 18; + + let (mut block, _, previous_blocks, previous_blocks_rlp) = default_test_block(); + + block.eth_block.state_root = H256::from_slice( + &hex::decode("21223344dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49349") + .unwrap(), + ); + block.eth_block.transactions_root = H256::from_slice( + &hex::decode("31223344dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49350") + .unwrap(), + ); + block.eth_block.receipts_root = H256::from_slice( + &hex::decode("41223344dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49351") + .unwrap(), + ); + block.eth_block.logs_bloom = Some([0; LOGS_BLOOM_SIZE].into()); + block.eth_block.extra_data = eth_types::Bytes::from([0; 0]); + block.eth_block.mix_hash = Some(H256::from_slice( + &hex::decode("51223344dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49352") + .unwrap(), + )); + block.context.number = U256::from(0x9090909090909090_u128); + block.context.gas_limit = 0x9191919191919191; + block.protocol_instance.gas_used = 0x92 << (3 * 8); + block.context.timestamp = U256::from(0); + block.context.base_fee = U256::from(0x94) << (31 * 8); + block.eth_block.withdrawals_root = Some(H256::from_slice( + &hex::decode("61223344dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49353") + .unwrap(), + )); + + let mut public_data = PublicData::new(&block); + public_data.previous_blocks = previous_blocks; + public_data.previous_blocks_rlp = previous_blocks_rlp; + + let (mut test_block, _, test_previous_blocks, test_previous_blocks_rlp) = + default_test_block(); + test_block.context.number = U256::from(0x100); + let mut test_public_data = PublicData::new(&test_block); + test_public_data.previous_blocks = test_previous_blocks; + test_public_data.previous_blocks_rlp = test_previous_blocks_rlp; + + match run::(k, public_data, None, Some(test_public_data)) { + Ok(_) => unreachable!("this case must fail"), + Err(errs) => { + // assert_eq!(errs.len(), 14); + for err in errs { + match err { + VerifyFailure::Lookup { .. } => return, + VerifyFailure::CellNotAssigned { .. } => return, + _ => unreachable!("unexpected error"), + } + } + } + } } } diff --git a/zkevm-circuits/src/taiko_super_circuit.rs b/zkevm-circuits/src/taiko_super_circuit.rs index 650700be56..34ba9111b7 100644 --- a/zkevm-circuits/src/taiko_super_circuit.rs +++ b/zkevm-circuits/src/taiko_super_circuit.rs @@ -6,7 +6,10 @@ pub mod test; use crate::{ anchor_tx_circuit::{AnchorTxCircuit, AnchorTxCircuitConfig, AnchorTxCircuitConfigArgs}, - table::{byte_table::ByteTable, BlockTable, KeccakTable, PiTable, TxTable}, + table::{ + byte_table::ByteTable, keccak_table::KeccakTable2, BlockTable, KeccakTable, PiTable, + TxTable, + }, taiko_pi_circuit::{TaikoPiCircuit, TaikoPiCircuitConfig, TaikoPiCircuitConfigArgs}, util::{log2_ceil, Challenges, SubCircuit, SubCircuitConfig}, witness::{block_convert, Block, ProtocolInstance}, @@ -53,7 +56,9 @@ impl SubCircuitConfig for SuperCircuitConfig { let tx_table = TxTable::construct(meta); let pi_table = PiTable::construct(meta); let block_table = BlockTable::construct(meta); + let block_table_blockhash = BlockTable::construct(meta); let keccak_table = KeccakTable::construct(meta); + let keccak_table2 = KeccakTable2::construct(meta); let byte_table = ByteTable::construct(meta); let pi_circuit = TaikoPiCircuitConfig::new( @@ -61,8 +66,10 @@ impl SubCircuitConfig for SuperCircuitConfig { TaikoPiCircuitConfigArgs { block_table: block_table.clone(), keccak_table: keccak_table.clone(), + keccak_table2, byte_table: byte_table.clone(), challenges: challenges.clone(), + block_table_blockhash, }, ); diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index 99ba2dfd56..4973f0d4c5 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -1,5 +1,4 @@ //! Testing utilities - use crate::{ evm_circuit::{cached::EvmCircuitCached, EvmCircuit}, state_circuit::StateCircuit,