From 86e929855bcf976ff3911a270ce1065690093d5f Mon Sep 17 00:00:00 2001 From: johntaiko Date: Thu, 1 Jun 2023 12:39:33 +0800 Subject: [PATCH 01/20] feat: add anchor transaction circuit --- Cargo.lock | 5 +- gadgets/src/is_equal.rs | 247 +++++++++++ gadgets/src/lib.rs | 1 + zkevm-circuits/Cargo.toml | 18 +- zkevm-circuits/src/anchor_tx_circuit.rs | 417 +++++++++++++++++ .../src/anchor_tx_circuit/sign_verify.rs | 418 ++++++++++++++++++ zkevm-circuits/src/lib.rs | 1 + zkevm-circuits/src/table.rs | 3 + zkevm-circuits/src/table/pi_table.rs | 18 + zkevm-circuits/src/table/tx_table.rs | 6 + zkevm-circuits/src/tx_circuit.rs | 17 +- zkevm-circuits/src/witness/block.rs | 5 +- zkevm-circuits/src/witness/taiko.rs | 9 +- 13 files changed, 1153 insertions(+), 12 deletions(-) create mode 100644 gadgets/src/is_equal.rs create mode 100644 zkevm-circuits/src/anchor_tx_circuit.rs create mode 100644 zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs create mode 100644 zkevm-circuits/src/table/pi_table.rs diff --git a/Cargo.lock b/Cargo.lock index 32193ce582..6299cbba9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3029,9 +3029,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" [[package]] name = "opaque-debug" @@ -5328,6 +5328,7 @@ dependencies = [ "mock", "num", "num-bigint", + "once_cell", "pretty_assertions", "rand", "rand_chacha", diff --git a/gadgets/src/is_equal.rs b/gadgets/src/is_equal.rs new file mode 100644 index 0000000000..8a4a16007b --- /dev/null +++ b/gadgets/src/is_equal.rs @@ -0,0 +1,247 @@ +//! IsEqual chip can be used to check equality of two expressions. + +use eth_types::Field; +use halo2_proofs::{ + circuit::{Chip, Region, Value}, + plonk::{ConstraintSystem, Error, Expression, VirtualCells}, +}; + +use super::is_zero::{IsZeroChip, IsZeroInstruction}; + +/// Instruction that the IsEqual chip needs to implement. +pub trait IsEqualInstruction { + /// Assign lhs and rhs witnesses to the IsEqual chip's region. + fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + lhs: Value, + rhs: Value, + ) -> Result<(), Error>; +} + +/// Config for the IsEqual chip. +#[derive(Clone, Debug)] +pub struct IsEqualConfig { + /// Stores an IsZero chip. + pub is_zero_chip: IsZeroChip, + /// Expression that denotes whether the chip evaluated to equal or not. + pub is_equal_expression: Expression, +} + +/// Chip that compares equality between two expressions. +#[derive(Clone, Debug)] +pub struct IsEqualChip { + /// Config for the IsEqual chip. + pub(crate) config: IsEqualConfig, +} + +impl IsEqualChip { + /// Configure the IsEqual chip. + pub fn configure( + meta: &mut ConstraintSystem, + q_enable: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, + lhs: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, + rhs: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, + ) -> IsEqualConfig { + let value = |meta: &mut VirtualCells| lhs(meta) - rhs(meta); + let value_inv = meta.advice_column(); + + let is_zero_config = IsZeroChip::configure(meta, q_enable, value, value_inv); + let is_equal_expression = is_zero_config.is_zero_expression.clone(); + + IsEqualConfig { + is_zero_chip: IsZeroChip::construct(is_zero_config), + is_equal_expression, + } + } + + /// Construct an IsEqual chip given a config. + pub fn construct(config: IsEqualConfig) -> Self { + Self { config } + } +} + +impl IsEqualInstruction for IsEqualChip { + fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + lhs: Value, + rhs: Value, + ) -> Result<(), Error> { + self.config.is_zero_chip.assign(region, offset, lhs - rhs)?; + + Ok(()) + } +} + +impl Chip for IsEqualChip { + type Config = IsEqualConfig; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use eth_types::Field; + use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + dev::MockProver, + halo2curves::bn256::Fr as Fp, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Selector, VirtualCells}, + poly::Rotation, + }; + use rand::Rng; + + use super::{IsEqualChip, IsEqualConfig, IsEqualInstruction}; + use crate::util::Expr; + + #[derive(Clone, Debug)] + struct TestCircuitConfig { + q_enable: Selector, + value: Column, + check: Column, + is_equal: IsEqualConfig, + } + + #[derive(Default)] + struct TestCircuit { + values: Vec, + checks: Vec, + _marker: PhantomData, + } + + impl Circuit for TestCircuit { + type Config = TestCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let q_enable = meta.complex_selector(); + let value = meta.advice_column(); + let check = meta.advice_column(); + + let lhs = |meta: &mut VirtualCells| meta.query_advice(value, Rotation::cur()); + let rhs = |_meta: &mut VirtualCells| RHS.expr(); + + let is_equal = + IsEqualChip::configure(meta, |meta| meta.query_selector(q_enable), lhs, rhs); + + let config = Self::Config { + q_enable, + value, + check, + is_equal, + }; + + meta.create_gate("check is_equal", |meta| { + let q_enable = meta.query_selector(q_enable); + + let check = meta.query_advice(check, Rotation::cur()); + + vec![q_enable * (config.is_equal.is_equal_expression.clone() - check)] + }); + + config + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = IsEqualChip::construct(config.is_equal.clone()); + + layouter.assign_region( + || "witness", + |mut region| { + let checks = self.checks.clone(); + + for (idx, (value, check)) in self.values.iter().cloned().zip(checks).enumerate() + { + region.assign_advice( + || "value", + config.value, + idx + 1, + || Value::known(F::from(value)), + )?; + region.assign_advice( + || "check", + config.check, + idx + 1, + || Value::known(F::from(check as u64)), + )?; + config.q_enable.enable(&mut region, idx + 1)?; + chip.assign( + &mut region, + idx + 1, + Value::known(F::from(value)), + Value::known(F::from(RHS)), + )?; + } + + Ok(()) + }, + ) + } + } + + macro_rules! try_test { + ($values:expr, $checks:expr, $rhs:expr, $is_ok_or_err:ident) => { + let k = usize::BITS - $values.len().leading_zeros() + 2; + let circuit = TestCircuit:: { + values: $values, + checks: $checks, + _marker: PhantomData, + }; + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); + assert!(prover.verify().$is_ok_or_err()); + }; + } + + fn random() -> u64 { + rand::thread_rng().gen::() + } + + #[test] + fn is_equal_gadget() { + try_test!( + vec![random(), 123, random(), 123, 123, random()], + vec![false, true, false, true, true, false], + 123, + is_ok + ); + try_test!( + vec![random(), 321321, 321321, random()], + vec![false, true, true, false], + 321321, + is_ok + ); + try_test!( + vec![random(), random(), random(), 1846123], + vec![false, false, false, true], + 1846123, + is_ok + ); + try_test!( + vec![123, 234, 345, 456], + vec![true, true, false, false], + 234, + is_err + ); + } +} diff --git a/gadgets/src/lib.rs b/gadgets/src/lib.rs index a7150262ee..5f2a85b922 100644 --- a/gadgets/src/lib.rs +++ b/gadgets/src/lib.rs @@ -13,6 +13,7 @@ pub mod batched_is_zero; pub mod binary_number; +pub mod is_equal; pub mod is_zero; pub mod less_than; pub mod mul_add; diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index cc1ca23fd6..e156705bc6 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -8,7 +8,9 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", features = ["circuit-params"], tag = "v2023_04_20" } +halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", features = [ + "circuit-params", +], tag = "v2023_04_20" } num = "0.4" sha3 = "0.10" array-init = "2.0.0" @@ -24,18 +26,22 @@ rand_xorshift = "0.3" rand = "0.8" itertools = "0.10.3" lazy_static = "1.4" -keccak256 = { path = "../keccak256"} +keccak256 = { path = "../keccak256" } log = "0.4" env_logger = "0.9" ecdsa = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } -ecc = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } -maingate = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } -integer = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } +ecc = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } +maingate = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } +integer = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } libsecp256k1 = "0.7" num-bigint = { version = "0.4" } rand_chacha = "0.3" -snark-verifier = { git = "https://github.com/privacy-scaling-explorations/snark-verifier", tag = "v2023_04_20", default-features = false, features = ["loader_halo2", "system_halo2"] } +snark-verifier = { git = "https://github.com/privacy-scaling-explorations/snark-verifier", tag = "v2023_04_20", default-features = false, features = [ + "loader_halo2", + "system_halo2", +] } cli-table = { version = "0.4", optional = true } +once_cell = "1.17.1" [dev-dependencies] bus-mapping = { path = "../bus-mapping", features = ["test"] } diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs new file mode 100644 index 0000000000..c0372efdbe --- /dev/null +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -0,0 +1,417 @@ +//! Anchor circuit implementation. + +mod sign_verify; +use crate::{ + evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, + table::{PiFieldTag, PiTable, TxFieldTag, TxTable}, + tx_circuit::TX_LEN, + util::{Challenges, SubCircuit, SubCircuitConfig}, + witness::{self, Taiko}, +}; +use eth_types::{geth_types::Transaction, Field, ToScalar}; +use gadgets::util::Expr; +use halo2_proofs::{ + circuit::{Layouter, Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector}, + poly::Rotation, +}; +use sign_verify::SignVerifyConfig; +use std::marker::PhantomData; + +// The first of txlist is the anchor tx +const ANCHOR_TX_ID: usize = 0; +const ANCHOR_TX_VALUE: u64 = 0; +const ANCHOR_TX_IS_CREATE: bool = false; +const ANCHOR_TX_GAS_PRICE: u64 = 0; +// TODO: calculate the method_signature +const ANCHOR_TX_METHOD_SIGNATURE: u32 = 0; +const MAX_DEGREE: usize = 10; +const BYTE_POW_BASE: u64 = 1 << 8; + +// anchor(bytes32,bytes32,uint64,uint64) = method_signature(4B)+1st(32B)+2nd(32B)+3rd(8B)+4th(8B) +const ANCHOR_CALL_DATA_LEN: usize = 84; + +struct CallData { + start: usize, + end: usize, +} + +/// Config for AnchorTxCircuit +#[derive(Clone, Debug)] +pub struct AnchorTxCircuitConfig { + tx_table: TxTable, + pi_table: PiTable, + + q_anchor: Selector, + // the anchor transaction fixed fields + // Gas, GasPrice, CallerAddress, CalleeAddress, IsCreate, Value, CallDataLength, + // 2 rows: 0, tag, 0, value + anchor: Column, + + // check: method_signature, l1Hash, l1SignalRoot, l1Height, parentGasUsed + q_call_data_start: Selector, + q_call_data_step: Selector, + q_call_data_end: Selector, + call_data_rlc_acc: Column, + call_data_tag: Column, + + sign_verify: SignVerifyConfig, +} + +/// Circuit configuration arguments +pub struct AnchorTxCircuitConfigArgs { + /// TxTable + pub tx_table: TxTable, + /// PiTable + pub pi_table: PiTable, + /// Challenges + pub challenges: Challenges>, +} + +impl SubCircuitConfig for AnchorTxCircuitConfig { + type ConfigArgs = AnchorTxCircuitConfigArgs; + + /// Return a new TxCircuitConfig + fn new( + meta: &mut ConstraintSystem, + Self::ConfigArgs { + tx_table, + pi_table, + challenges, + }: Self::ConfigArgs, + ) -> Self { + let q_anchor = meta.complex_selector(); + let anchor = meta.fixed_column(); + + let q_call_data_start = meta.complex_selector(); + let q_call_data_step = meta.complex_selector(); + let q_call_data_end = meta.complex_selector(); + let call_data_rlc_acc = meta.advice_column_in(SecondPhase); + let call_data_tag = meta.fixed_column(); + let sign_verify = SignVerifyConfig::configure(meta, tx_table.clone(), &challenges); + + // anchor transaction constants + meta.lookup_any("anchor fixed fields", |meta| { + let q_anchor = meta.query_selector(q_anchor); + let tx_id = ANCHOR_TX_ID.expr(); + let tag = meta.query_fixed(anchor, Rotation(0)); + let index = 0.expr(); + let value = meta.query_fixed(anchor, Rotation(1)); + vec![ + ( + q_anchor.expr() * tx_id, + meta.query_advice(tx_table.tx_id, Rotation::cur()), + ), + ( + q_anchor.expr() * tag, + meta.query_fixed(tx_table.tag, Rotation::cur()), + ), + ( + q_anchor.expr() * index, + meta.query_advice(tx_table.index, Rotation::cur()), + ), + ( + q_anchor * value, + meta.query_advice(tx_table.value, Rotation::cur()), + ), + ] + }); + + // call data + meta.create_gate( + "call_data_rlc_acc[i+1] = call_data_rlc_acc[i] * randomness + call_data[i+1]", + |meta| { + let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); + + let q_call_data_step = meta.query_selector(q_call_data_step); + let call_data_rlc_acc_next = meta.query_advice(call_data_rlc_acc, Rotation::next()); + let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); + let call_data_next = meta.query_advice(tx_table.value, Rotation::next()); + let randomness = challenges.evm_word(); + cb.require_equal( + "call_data_rlc_acc[i+1] = call_data_rlc_acc[i] * randomness + call_data[i+1]", + call_data_rlc_acc_next, + call_data_rlc_acc * randomness + call_data_next, + ); + cb.gate(q_call_data_step) + }, + ); + meta.create_gate("call_data_acc[0] = call_data[0]", |meta| { + let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); + + let q_call_data_start = meta.query_selector(q_call_data_start); + let call_data_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); + let call_data = meta.query_advice(tx_table.value, Rotation::cur()); + + cb.require_equal("call_data_acc[0] = call_data[0]", call_data_acc, call_data); + cb.gate(q_call_data_start) + }); + meta.lookup_any("call data in pi_table", |meta| { + let q_call_data_end = meta.query_selector(q_call_data_end); + let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); + let call_data_tag = meta.query_fixed(call_data_tag, Rotation::cur()); + + vec![ + ( + q_call_data_end.expr() * call_data_tag, + meta.query_fixed(pi_table.tag, Rotation::cur()), + ), + ( + q_call_data_end * call_data_rlc_acc, + meta.query_advice(pi_table.value, Rotation::cur()), + ), + ] + }); + + Self { + tx_table, + pi_table, + + q_anchor, + anchor, + + q_call_data_start, + q_call_data_step, + q_call_data_end, + call_data_rlc_acc, + call_data_tag, + sign_verify, + } + } +} + +impl AnchorTxCircuitConfig { + fn assign_anchor_tx( + &self, + region: &mut Region<'_, F>, + _anchor_tx: &Transaction, + taiko: &Taiko, + _challenges: &Challenges>, + ) -> Result<(), Error> { + // Gas, GasPrice, CallerAddress, CalleeAddress, IsCreate, Value, CallDataLength, + let mut offset = 0; + for (tag, value) in [ + ( + TxFieldTag::Gas, + Value::known(F::from(taiko.anchor_gas_cost)), + ), + ( + TxFieldTag::GasPrice, + Value::known(F::from(ANCHOR_TX_GAS_PRICE)), + ), + ( + TxFieldTag::CallerAddress, + Value::known( + taiko + .anchor_from + .to_scalar() + .expect("anchor_tx.from too big"), + ), + ), + ( + TxFieldTag::CalleeAddress, + Value::known(taiko.anchor_to.to_scalar().expect("anchor_tx.to too big")), + ), + ( + TxFieldTag::IsCreate, + Value::known(F::from(ANCHOR_TX_IS_CREATE as u64)), + ), + (TxFieldTag::Value, Value::known(F::from(ANCHOR_TX_VALUE))), + ( + TxFieldTag::CallDataLength, + Value::known(F::from(ANCHOR_CALL_DATA_LEN as u64)), + ), + ] { + self.q_anchor.enable(region, offset)?; + region.assign_fixed( + || "tag", + self.anchor, + offset, + || Value::known(F::from(tag as u64)), + )?; + offset += 1; + region.assign_fixed(|| "anchor", self.anchor, offset, || value)?; + offset += 1; + } + Ok(()) + } + + fn assign_call_data( + &self, + region: &mut Region<'_, F>, + anchor_tx: &Transaction, + call_data: &CallData, + challenges: &Challenges>, + ) -> Result<(), Error> { + let mut offset = call_data.start; + let randomness = challenges.evm_word(); + // // check: method_signature, l1Hash, l1SignalRoot, l1Height, parentGasUsed + // q_call_data_start: Selector, + // q_call_data_step: Selector, + // q_call_data_end: Selector, + // call_data_rlc_acc: Column, + // call_data_tag: Column, + for (annotation, len, value, tag) in [ + ( + "method_signature", + 4, + &anchor_tx.call_data[..4], + PiFieldTag::MethodSign, + ), + ( + "l1_hash", + 32, + &anchor_tx.call_data[4..36], + PiFieldTag::L1Hash, + ), + ( + "l1_signal_root", + 32, + &anchor_tx.call_data[36..68], + PiFieldTag::L1SignalRoot, + ), + ( + "l1_height", + 8, + &anchor_tx.call_data[68..76], + PiFieldTag::L1Height, + ), + ( + "parent_gas_used", + 8, + &anchor_tx.call_data[76..84], + PiFieldTag::ParentGasUsed, + ), + ] { + let mut rlc_acc = Value::known(F::ZERO); + for idx in 0..len { + let row_offset = offset + idx; + rlc_acc = rlc_acc * randomness + Value::known(F::from(value[idx] as u64)); + region.assign_advice( + || annotation, + self.call_data_rlc_acc, + row_offset, + || rlc_acc, + )?; + region.assign_fixed( + || annotation, + self.call_data_tag, + row_offset, + || Value::known(F::from(tag as u64)), + )?; + // setup selector + if idx == 0 { + self.q_call_data_start.enable(region, row_offset)?; + } + // the last offset of field + if idx == len - 1 { + self.q_call_data_end.enable(region, row_offset)?; + } else { + self.q_call_data_step.enable(region, row_offset)?; + } + } + offset += len; + } + todo!() + } + + fn assign( + &self, + layouter: &mut impl Layouter, + anchor_tx: &Transaction, + chain_id: u64, + taiko: &Taiko, + call_data: &CallData, + challenges: &Challenges>, + ) -> Result<(), Error> { + self.sign_verify + .assign(layouter, anchor_tx, chain_id, challenges)?; + layouter.assign_region( + || "anchor transaction", + |ref mut region| { + self.assign_anchor_tx(region, anchor_tx, taiko, challenges)?; + self.assign_call_data(region, anchor_tx, call_data, challenges)?; + Ok(()) + }, + ) + } +} + +/// Anchor Transaction Circuit for verifying anchor transaction +#[derive(Clone, Default, Debug)] +pub struct AnchorTxCircuit { + max_txs: usize, + anchor_tx: Transaction, + chain_id: u64, + taiko: Taiko, + _marker: PhantomData, +} + +impl AnchorTxCircuit { + /// Return a new TxCircuit + pub fn new(max_txs: usize, anchor_tx: Transaction, chain_id: u64, taiko: Taiko) -> Self { + AnchorTxCircuit { + max_txs, + anchor_tx, + chain_id, + taiko, + _marker: PhantomData, + } + } + + fn call_data_start(&self) -> usize { + self.max_txs * TX_LEN + 1 // empty row + } + + fn call_data_end(&self) -> usize { + self.call_data_start() + ANCHOR_CALL_DATA_LEN + } +} + +impl SubCircuit for AnchorTxCircuit { + type Config = AnchorTxCircuitConfig; + + fn unusable_rows() -> usize { + 0 + } + + fn new_from_block(block: &witness::Block) -> Self { + Self::new( + block.circuits_params.max_txs, + block + .eth_block + .transactions + .iter() + .map(|tx| tx.into()) + .next() + .unwrap(), + block.context.chain_id.as_u64(), + block.taiko.clone(), + ) + } + + /// Make the assignments to the TxCircuit + fn synthesize_sub( + &self, + config: &Self::Config, + challenges: &Challenges>, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + let call_data = CallData { + start: self.call_data_start(), + end: self.call_data_end(), + }; + config.assign( + layouter, + &self.anchor_tx, + self.chain_id, + &self.taiko, + &call_data, + challenges, + ) + } + + fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + (0, 0) + } +} diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs new file mode 100644 index 0000000000..0a93a07978 --- /dev/null +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -0,0 +1,418 @@ +use crate::{ + evm_circuit::util::{ + constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, + split_u256_limb64, + }, + table::{TxFieldTag, TxTable}, + util::Challenges, +}; +use eth_types::{geth_types::Transaction, Field, ToBigEndian, Word, U256}; +use gadgets::{ + is_equal::IsEqualChip, + mul_add::{MulAddChip, MulAddConfig}, + util::{split_u256, Expr}, +}; +use halo2_proofs::{ + circuit::{Layouter, Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector}, + poly::Rotation, +}; +use log::error; +use once_cell::sync::Lazy; + +const ANCHOR_TX_ID: usize = 0; +const MAX_DEGREE: usize = 10; +const BYTE_POW_BASE: u64 = 1 << 8; + +// 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 +const GX1: Word = U256([ + 0x59F2815B16F81798, + 0x029BFCDB2DCE28D9, + 0x55A06295CE870B07, + 0x79BE667EF9DCBBAC, +]); +static GX1_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX1)); + +// 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 +const GX2: Word = U256([ + 0xabac09b95c709ee5, + 0x5c778e4b8cef3ca7, + 0x3045406e95c07cd8, + 0xc6047f9441ed7d6d, +]); +static GX2_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX2)); + +// 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 +const N: Word = U256([ + 0xbfd25e8cd0364141, + 0xbaaedce6af48a03b, + 0xfffffffffffffffe, + 0xffffffffffffffff, +]); +static N_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&N)); + +// private key 0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38 +// GX1 * PRIVATEKEY(mod N) = 0x4341adf5a780b4a87939938fd7a032f6e6664c7da553c121d3b4947429639122 +const GX1_MUL_PRIVATEKEY: Word = U256([ + 0xd3b4947429639122, + 0xe6664c7da553c121, + 0x7939938fd7a032f6, + 0x4341adf5a780b4a8, +]); +static GX1_MUL_PRIVATEKEY_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX1_MUL_PRIVATEKEY)); +static GX1_MUL_PRIVATEKEY_LIMB64: Lazy<[U256; 4]> = + Lazy::new(|| split_u256_limb64(&GX1_MUL_PRIVATEKEY)); + +// GX2 * PRIVATEKEY(mod N) = 0x4a43b192ca74cab200d6c086df90fb729abca9e52d38b8fa0beb4eafe70956de +const GX2_MUL_PRIVATEKEY: Word = U256([ + 0x0beb4eafe70956de, + 0x9abca9e52d38b8fa, + 0x00d6c086df90fb72, + 0x4a43b192ca74cab2, +]); +static GX2_MUL_PRIVATEKEY_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX2_MUL_PRIVATEKEY)); + +// # How to check the signature +// 1. IF r == GX1 OR r == GX2 +// 2. IF r == GX2 THEN IF r == GX1 THEN s == 0 +// 3. IF s == 0 THEN (GX1_MUL_PRIVATEKEY + msg_hash) == N +// => IF r == GX2 THEN GX1_MUL_PRIVATEKEY * 1 + msg_hash == N +// +// # The layout +// - msg_hash (c) +// - SigR + +#[derive(Debug, Clone)] +pub(crate) struct SignVerifyConfig { + tx_table: TxTable, + + q_sign_start: Selector, + q_sign_step: Selector, + q_sign_end: Selector, + tag: Column, + sign: Column, + sign_rlc_acc: Column, + // split u256 to low and high + q_u128_start: Selector, + q_u128_step: Selector, + q_u128_end: Selector, + sign_u128_acc: Column, + + q_check: Selector, + mul_add: MulAddConfig, +} + +impl SignVerifyConfig { + pub(crate) fn configure( + meta: &mut ConstraintSystem, + tx_table: TxTable, + challenges: &Challenges>, + ) -> Self { + let q_sign_start = meta.complex_selector(); + let q_sign_step = meta.complex_selector(); + let q_sign_end = meta.complex_selector(); + let tag = meta.fixed_column(); + let sign = meta.advice_column(); + let sign_rlc_acc = meta.advice_column_in(SecondPhase); + + let q_u128_start = meta.complex_selector(); + let q_u128_step = meta.complex_selector(); + let q_u128_end = meta.complex_selector(); + let sign_u128_acc = meta.advice_column(); + + let q_check = meta.complex_selector(); + let mul_add = MulAddChip::configure(meta, |meta| meta.query_selector(q_check)); + + let gx1_rlc = crate::evm_circuit::util::rlc::expr( + GX1.to_be_bytes() + .map(|v| Expression::Constant(F::from(v as u64))) + .as_ref(), + challenges.evm_word(), + ); + + let gx2_rlc = crate::evm_circuit::util::rlc::expr( + GX2.to_be_bytes() + .map(|v| Expression::Constant(F::from(v as u64))) + .as_ref(), + challenges.evm_word(), + ); + let is_equal_gx2 = IsEqualChip::configure( + meta, + |meta| meta.query_selector(q_check), + |meta| meta.query_advice(sign_rlc_acc, Rotation(64)), // SigR == GX2 + |_| gx2_rlc.expr(), + ); + + // signature rlc + meta.create_gate( + "sign_rlc_acc[i+1] = sign_rlc_acc[i] * randomness + sign[i+1]", + |meta| { + let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); + + let q_sign_step = meta.query_selector(q_sign_step); + let sign_rlc_acc_next = meta.query_advice(sign_rlc_acc, Rotation::next()); + let sign_rlc_acc = meta.query_advice(sign_rlc_acc, Rotation::cur()); + let sign = meta.query_advice(sign, Rotation::next()); + let randomness = challenges.evm_word(); + cb.require_equal( + "sign_rlc_acc[i+1] = sign_rlc_acc[i] * randomness + sign[i+1]", + sign_rlc_acc_next, + sign_rlc_acc * randomness + sign, + ); + cb.gate(q_sign_step) + }, + ); + meta.create_gate("sign_rlc_acc[0] = sign[0]", |meta| { + let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); + + let q_sign_start = meta.query_selector(q_sign_start); + let sign_rlc_acc = meta.query_advice(sign_rlc_acc, Rotation::cur()); + let sign = meta.query_advice(sign, Rotation::cur()); + + cb.require_equal("sign_rlc_acc[0] = sign[0]", sign_rlc_acc, sign); + cb.gate(q_sign_start) + }); + meta.lookup_any("sign_r or msg_hash in tx_table", |meta| { + let q_sign_end = meta.query_selector(q_sign_end); + + let tx_id = ANCHOR_TX_ID.expr(); + let tag = meta.query_fixed(tag, Rotation::cur()); + let index = 0.expr(); + let value = meta.query_advice(sign_rlc_acc, Rotation::cur()); + vec![ + ( + q_sign_end.expr() * tx_id, + meta.query_advice(tx_table.tx_id, Rotation::cur()), + ), + ( + q_sign_end.expr() * tag, + meta.query_fixed(tx_table.tag, Rotation::cur()), + ), + ( + q_sign_end.expr() * index, + meta.query_advice(tx_table.index, Rotation::cur()), + ), + ( + q_sign_end * value, + meta.query_advice(tx_table.value, Rotation::cur()), + ), + ] + }); + // signature u128 + meta.create_gate( + "sign_u128_acc[i+1] = sign_u128_acc[i] * BYTE_POW_BASE + sign[i+1]", + |meta| { + let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); + + let q_u128_step = meta.query_selector(q_u128_step); + let sign_u128_acc_next = meta.query_advice(sign_u128_acc, Rotation::next()); + let sign_u128_acc = meta.query_advice(sign_u128_acc, Rotation::cur()); + let sign_next = meta.query_advice(sign, Rotation::next()); + cb.require_equal( + "sign_u128_acc[i+1] = sign_u128_acc[i] * BYTE_POW_BASE + sign[i+1]", + sign_u128_acc_next, + sign_u128_acc * BYTE_POW_BASE.expr() + sign_next, + ); + cb.gate(q_u128_step) + }, + ); + meta.create_gate("sign_u128_acc[start] = sign[start]", |meta| { + let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); + + let q_u128_start = meta.query_selector(q_u128_start); + let sign_u128_acc = meta.query_advice(sign_u128_acc, Rotation::cur()); + let sign = meta.query_advice(sign, Rotation::cur()); + + cb.require_equal("sign_u128_acc[start] = sign[start]", sign_u128_acc, sign); + cb.gate(q_u128_start) + }); + + // check SigR + meta.create_gate( + "IF r == GX2 THEN a(GX1_MUL_PRIVATEKEY) * b(1) + c(msg_hash) == d(N)", + |meta| { + let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); + + let q_check = meta.query_selector(q_check); + + let sign_rlc_acc = meta.query_advice(sign_rlc_acc, Rotation(64)); + + cb.require_in_set("r in (GX1, GX2)", sign_rlc_acc, vec![gx1_rlc, gx2_rlc]); + + cb.condition(is_equal_gx2.is_equal_expression, |cb| { + // a == GX1_MUL_PRIVATEKEY + let (a_limb0, a_limb1, a_limb2, a_limb3) = mul_add.a_limbs_cur(meta); + let a_limb = GX1_MUL_PRIVATEKEY_LIMB64 + .map(|v| Expression::Constant(F::from(v.as_u64()))); + cb.require_equal("a_limb0", a_limb0, a_limb[0].expr()); + cb.require_equal("a_limb1", a_limb1, a_limb[1].expr()); + cb.require_equal("a_limb2", a_limb2, a_limb[2].expr()); + cb.require_equal("a_limb3", a_limb3, a_limb[3].expr()); + + // b == 1 + let (b_limb0, b_limb1, b_limb2, b_limb3) = mul_add.b_limbs_cur(meta); + let b_limb = split_u256_limb64(&U256::one()) + .map(|v| Expression::Constant(F::from(v.as_u64()))); + cb.require_equal("b_limb0", b_limb0, b_limb[0].expr()); + cb.require_equal("b_limb1", b_limb1, b_limb[1].expr()); + cb.require_equal("b_limb2", b_limb2, b_limb[2].expr()); + cb.require_equal("b_limb3", b_limb3, b_limb[3].expr()); + + // c == msg_hash + let c_lo_hi0 = meta.query_advice(sign_u128_acc, Rotation(16)); + let c_lo_hi1 = meta.query_advice(sign_u128_acc, Rotation(32)); + let (c_lo_cur, c_hi_cur) = mul_add.c_lo_hi_cur(meta); + cb.require_equal("c_lo_cur", c_lo_hi0, c_lo_cur); + cb.require_equal("c_hi_cur", c_lo_hi1, c_hi_cur); + + // d == N + let (d_lo_cur, d_hi_cur) = mul_add.c_lo_hi_cur(meta); + let d_lo_cur_expr = Expression::Constant(F::from_u128(N_LO_HI.0.as_u128())); + let d_hi_cur_expr = Expression::Constant(F::from_u128(N_LO_HI.1.as_u128())); + + cb.require_equal("d_lo_cur", d_lo_cur_expr, d_lo_cur); + cb.require_equal("d_hi_cur", d_hi_cur_expr, d_hi_cur); + }); + cb.gate(q_check) + }, + ); + + Self { + tx_table, + + q_sign_start, + q_sign_step, + q_sign_end, + tag, + sign, + sign_rlc_acc, + + q_u128_start, + q_u128_step, + q_u128_end, + sign_u128_acc, + + q_check, + mul_add, + } + } + + fn assign_field( + &self, + region: &mut Region<'_, F>, + _annotation: &'static str, + offset: &mut usize, + tag: TxFieldTag, + value: [u8; 32], + need_check: bool, + challenges: &Challenges>, + ) -> Result<(), Error> { + let mut rlc_acc = Value::known(F::ZERO); + let randomness = challenges.evm_word(); + + let mut assign_u128 = |offset: &mut usize, value: &[u8]| -> Result<(), Error> { + let mut u128_acc = Value::known(F::ZERO); + for (idx, byte) in value.iter().enumerate() { + let row_offset = *offset + idx; + u128_acc = u128_acc * Value::known(F::from(BYTE_POW_BASE as u64)) + + Value::known(F::from(*byte as u64)); + region.assign_advice( + || "sign_u128_acc", + self.sign_u128_acc, + row_offset, + || u128_acc, + )?; + // setup selector + if idx == 0 { + self.q_u128_start.enable(region, row_offset)?; + } + // the last offset of field + if idx == 15 { + self.q_u128_end.enable(region, row_offset)?; + } else { + self.q_u128_step.enable(region, row_offset)?; + } + } + Ok(()) + }; + + let mut assign_u128_offset = *offset; + assign_u128(&mut assign_u128_offset, &value[..16])?; + assign_u128(&mut assign_u128_offset, &value[16..])?; + + for (idx, byte) in value.iter().enumerate() { + let row_offset = *offset + idx; + region.assign_advice( + || "sign", + self.sign, + row_offset, + || Value::known(F::from(*byte as u64)), + )?; + region.assign_fixed( + || "tag", + self.tag, + row_offset, + || Value::known(F::from(tag as u64)), + )?; + + rlc_acc = rlc_acc * randomness + Value::known(F::from(*byte as u64)); + region.assign_advice(|| "sign_rlc_acc", self.sign_rlc_acc, row_offset, || rlc_acc)?; + // setup selector + if idx == 0 { + self.q_sign_start.enable(region, row_offset)?; + if need_check { + self.q_check.enable(region, row_offset)?; + } + } + // the last offset of field + if idx == 31 { + self.q_sign_end.enable(region, row_offset)?; + } else { + self.q_sign_step.enable(region, row_offset)?; + } + } + *offset += 32; + Ok(()) + } + + fn load_mul_add(&self, region: &mut Region<'_, F>, msg_hash: Word) -> Result<(), Error> { + let chip = MulAddChip::construct(self.mul_add.clone()); + chip.assign(region, 0, [GX1_MUL_PRIVATEKEY, U256::one(), msg_hash, N]) + } + + pub(crate) fn assign( + &self, + layouter: &mut impl Layouter, + anchor_tx: &Transaction, + chain_id: u64, + challenges: &Challenges>, + ) -> Result<(), Error> { + layouter.assign_region( + || "anchor sign verify", + |ref mut region| { + let sign_data = anchor_tx.sign_data(chain_id).map_err(|e| { + error!("tx_to_sign_data error for tx {:?}", e); + Error::Synthesis + })?; + let msg_hash = U256::from_big_endian(sign_data.msg_hash.to_bytes().as_ref()); + self.load_mul_add(region, msg_hash)?; + let mut offset = 0; + for (annotation, tag, need_check, value) in [ + ("msg_hash", TxFieldTag::TxSignHash, true, msg_hash), + ("sign_r", TxFieldTag::SigR, false, anchor_tx.r), + ] { + self.assign_field( + region, + annotation, + &mut offset, + tag, + value.to_be_bytes(), + need_check, + challenges, + )?; + } + Ok(()) + }, + ) + } +} diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index f4f0bb8cd7..23d1b5cff4 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -32,6 +32,7 @@ pub mod table; #[cfg(any(feature = "test", test))] pub mod test_util; +pub mod anchor_tx_circuit; pub mod tx_circuit; pub mod util; pub mod witness; diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index c9ba47c01a..4c21c84c46 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -38,6 +38,8 @@ pub(crate) mod exp_table; pub(crate) mod keccak_table; /// mpt table pub(crate) mod mpt_table; +/// pi table +pub(crate) mod pi_table; /// rw table pub(crate) mod rw_table; /// tx table @@ -50,6 +52,7 @@ pub(crate) use exp_table::ExpTable; pub(crate) use keccak_table::KeccakTable; pub(crate) use mpt_table::{MPTProofType, MptTable}; +pub(crate) use pi_table::{PiFieldTag, PiTable}; pub(crate) use rw_table::RwTable; pub(crate) use tx_table::{ TxContextFieldTag, TxFieldTag, TxLogFieldTag, TxReceiptFieldTag, TxTable, diff --git a/zkevm-circuits/src/table/pi_table.rs b/zkevm-circuits/src/table/pi_table.rs new file mode 100644 index 0000000000..03066bb894 --- /dev/null +++ b/zkevm-circuits/src/table/pi_table.rs @@ -0,0 +1,18 @@ +use super::*; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PiFieldTag { + Null = 0, + MethodSign, + L1Hash, + L1SignalRoot, + L1Height, + ParentGasUsed, +} +impl_expr!(PiFieldTag); + +#[derive(Clone, Debug)] +pub struct PiTable { + pub tag: Column, + pub value: Column, +} diff --git a/zkevm-circuits/src/table/tx_table.rs b/zkevm-circuits/src/table/tx_table.rs index 017007a614..e6b37a8566 100644 --- a/zkevm-circuits/src/table/tx_table.rs +++ b/zkevm-circuits/src/table/tx_table.rs @@ -29,6 +29,12 @@ pub enum TxFieldTag { TxSignHash, /// CallData CallData, + /// Signature field V. + SigV, + /// Signature field R. + SigR, + /// Signature field S. + SigS, } impl_expr!(TxFieldTag); diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index ffd60993aa..fa90e9108a 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -30,10 +30,10 @@ use std::marker::PhantomData; /// Number of static fields per tx: [nonce, gas, gas_price, /// caller_address, callee_address, is_create, value, call_data_length, -/// call_data_gas_cost, tx_sign_hash]. +/// call_data_gas_cost, tx_sign_hash, r, s, v]. /// Note that call data bytes are layed out in the TxTable after all the static /// fields arranged by txs. -pub(crate) const TX_LEN: usize = 10; +pub(crate) const TX_LEN: usize = 13; /// Config for TxCircuit #[derive(Clone, Debug)] @@ -243,6 +243,19 @@ impl TxCircuit { TxFieldTag::TxSignHash, assigned_sig_verif.msg_hash_rlc.value().copied(), ), + (TxFieldTag::SigV, Value::known(F::from(tx.v))), + ( + TxFieldTag::SigR, + challenges + .evm_word() + .map(|challenge| rlc(tx.r.to_le_bytes(), challenge)), + ), + ( + TxFieldTag::SigS, + challenges + .evm_word() + .map(|challenge| rlc(tx.s.to_le_bytes(), challenge)), + ), ] { let assigned_cell = config.assign_row(&mut region, offset, i + 1, tag, 0, value)?; diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index 920cd909b0..37cad6aac2 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -13,7 +13,7 @@ use bus_mapping::{ use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; use halo2_proofs::circuit::Value; -use super::{tx::tx_convert, Bytecode, ExecStep, Rw, RwMap, Transaction}; +use super::{taiko::Taiko, tx::tx_convert, Bytecode, ExecStep, Rw, RwMap, Transaction}; // TODO: Remove fields that are duplicated in`eth_block` /// Block is the struct used by all circuits, which contains all the needed @@ -51,6 +51,8 @@ pub struct Block { pub keccak_inputs: Vec>, /// Original Block from geth pub eth_block: eth_types::Block, + /// Taiko witness + pub taiko: Taiko, } impl Block { @@ -251,5 +253,6 @@ pub fn block_convert( prev_state_root: block.prev_state_root, keccak_inputs: circuit_input_builder::keccak_inputs(block, code_db)?, eth_block: block.eth_block.clone(), + taiko: Taiko::default(), }) } diff --git a/zkevm-circuits/src/witness/taiko.rs b/zkevm-circuits/src/witness/taiko.rs index c394870d8e..5fdcc40340 100644 --- a/zkevm-circuits/src/witness/taiko.rs +++ b/zkevm-circuits/src/witness/taiko.rs @@ -3,7 +3,7 @@ use eth_types::{Address, Hash, H256}; /// Taiko witness -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Taiko { /// l1 signal service address pub l1_signal_service: Address, @@ -33,4 +33,11 @@ pub struct Taiko { pub max_transactions_per_block: u64, /// maxBytesPerTxList pub max_bytes_per_tx_list: u64, + + /// anchor gas cost + pub anchor_gas_cost: u64, + /// anchor from + pub anchor_from: Address, + /// anchor to + pub anchor_to: Address, } From ef370f15cef8af734fa3be14805536e6c9da71ae Mon Sep 17 00:00:00 2001 From: johntaiko Date: Thu, 1 Jun 2023 21:30:35 +0800 Subject: [PATCH 02/20] chore: improve comment --- zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 0a93a07978..9d5819a103 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -74,9 +74,14 @@ static GX2_MUL_PRIVATEKEY_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&G // # How to check the signature // 1. IF r == GX1 OR r == GX2 -// 2. IF r == GX2 THEN IF r == GX1 THEN s == 0 +// 2. IF r == GX2 THEN MUST WHEN r == GX1 AND s == 0 // 3. IF s == 0 THEN (GX1_MUL_PRIVATEKEY + msg_hash) == N -// => IF r == GX2 THEN GX1_MUL_PRIVATEKEY * 1 + msg_hash == N +// => IF r == GX2 THEN GX1_MUL_PRIVATEKEY + msg_hash == N +// In mul_add chip, we have a * b + c == d +// => a == GX1_MUL_PRIVATEKEY +// => b == 1 +// => c == msg_hash +// => d == N // // # The layout // - msg_hash (c) From c504d320e56e56c09bfab80bd7e6888930da1f29 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Thu, 1 Jun 2023 21:36:41 +0800 Subject: [PATCH 03/20] fix: clippy complain --- zkevm-circuits/src/anchor_tx_circuit.rs | 23 ++++++------------- .../src/anchor_tx_circuit/sign_verify.rs | 3 ++- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index c0372efdbe..b9273860a8 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -251,42 +251,33 @@ impl AnchorTxCircuitConfig { // q_call_data_end: Selector, // call_data_rlc_acc: Column, // call_data_tag: Column, - for (annotation, len, value, tag) in [ + for (annotation, value, tag) in [ ( "method_signature", - 4, &anchor_tx.call_data[..4], PiFieldTag::MethodSign, ), - ( - "l1_hash", - 32, - &anchor_tx.call_data[4..36], - PiFieldTag::L1Hash, - ), + ("l1_hash", &anchor_tx.call_data[4..36], PiFieldTag::L1Hash), ( "l1_signal_root", - 32, &anchor_tx.call_data[36..68], PiFieldTag::L1SignalRoot, ), ( "l1_height", - 8, &anchor_tx.call_data[68..76], PiFieldTag::L1Height, ), ( "parent_gas_used", - 8, &anchor_tx.call_data[76..84], PiFieldTag::ParentGasUsed, ), ] { let mut rlc_acc = Value::known(F::ZERO); - for idx in 0..len { + for (idx, byte) in value.iter().enumerate() { let row_offset = offset + idx; - rlc_acc = rlc_acc * randomness + Value::known(F::from(value[idx] as u64)); + rlc_acc = rlc_acc * randomness + Value::known(F::from(*byte as u64)); region.assign_advice( || annotation, self.call_data_rlc_acc, @@ -304,13 +295,13 @@ impl AnchorTxCircuitConfig { self.q_call_data_start.enable(region, row_offset)?; } // the last offset of field - if idx == len - 1 { + if idx == value.len() - 1 { self.q_call_data_end.enable(region, row_offset)?; } else { self.q_call_data_step.enable(region, row_offset)?; } } - offset += len; + offset += value.len(); } todo!() } @@ -411,7 +402,7 @@ impl SubCircuit for AnchorTxCircuit { ) } - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + fn min_num_rows_block(_block: &witness::Block) -> (usize, usize) { (0, 0) } } diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 9d5819a103..481ce9db08 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -302,6 +302,7 @@ impl SignVerifyConfig { } } + #[allow(clippy::too_many_arguments)] fn assign_field( &self, region: &mut Region<'_, F>, @@ -319,7 +320,7 @@ impl SignVerifyConfig { let mut u128_acc = Value::known(F::ZERO); for (idx, byte) in value.iter().enumerate() { let row_offset = *offset + idx; - u128_acc = u128_acc * Value::known(F::from(BYTE_POW_BASE as u64)) + u128_acc = u128_acc * Value::known(F::from(BYTE_POW_BASE)) + Value::known(F::from(*byte as u64)); region.assign_advice( || "sign_u128_acc", From 83b16186a0ff5f07d8b5cd1cba65d9da4b2ec286 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 12 Jun 2023 17:06:43 +0800 Subject: [PATCH 04/20] feat: add tests --- zkevm-circuits/src/anchor_tx_circuit.rs | 60 ++++++++------ zkevm-circuits/src/anchor_tx_circuit/dev.rs | 83 +++++++++++++++++++ .../src/anchor_tx_circuit/sign_verify.rs | 28 ++++++- zkevm-circuits/src/anchor_tx_circuit/test.rs | 81 ++++++++++++++++++ zkevm-circuits/src/pi_circuit2.rs | 4 +- zkevm-circuits/src/table/pi_table.rs | 52 ++++++++++++ zkevm-circuits/src/witness/taiko.rs | 83 +++++++++++++++++-- 7 files changed, 357 insertions(+), 34 deletions(-) create mode 100644 zkevm-circuits/src/anchor_tx_circuit/dev.rs create mode 100644 zkevm-circuits/src/anchor_tx_circuit/test.rs diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index b9273860a8..8631abb493 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -1,6 +1,13 @@ //! Anchor circuit implementation. +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +mod dev; +#[cfg(any(feature = "test", test, feature = "test-circuits"))] +pub use dev::TestAnchorTxCircuit; mod sign_verify; +#[cfg(any(feature = "test", test))] +mod test; + use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, table::{PiFieldTag, PiTable, TxFieldTag, TxTable}, @@ -18,6 +25,8 @@ use halo2_proofs::{ use sign_verify::SignVerifyConfig; use std::marker::PhantomData; +use self::sign_verify::GOLDEN_TOUCH_ADDRESS; + // The first of txlist is the anchor tx const ANCHOR_TX_ID: usize = 0; const ANCHOR_TX_VALUE: u64 = 0; @@ -25,7 +34,7 @@ const ANCHOR_TX_IS_CREATE: bool = false; const ANCHOR_TX_GAS_PRICE: u64 = 0; // TODO: calculate the method_signature const ANCHOR_TX_METHOD_SIGNATURE: u32 = 0; -const MAX_DEGREE: usize = 10; +const MAX_DEGREE: usize = 9; const BYTE_POW_BASE: u64 = 1 << 8; // anchor(bytes32,bytes32,uint64,uint64) = method_signature(4B)+1st(32B)+2nd(32B)+3rd(8B)+4th(8B) @@ -42,11 +51,11 @@ pub struct AnchorTxCircuitConfig { tx_table: TxTable, pi_table: PiTable, - q_anchor: Selector, + q_tag: Selector, // the anchor transaction fixed fields // Gas, GasPrice, CallerAddress, CalleeAddress, IsCreate, Value, CallDataLength, // 2 rows: 0, tag, 0, value - anchor: Column, + tag: Column, // check: method_signature, l1Hash, l1SignalRoot, l1Height, parentGasUsed q_call_data_start: Selector, @@ -80,8 +89,8 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { challenges, }: Self::ConfigArgs, ) -> Self { - let q_anchor = meta.complex_selector(); - let anchor = meta.fixed_column(); + let q_tag = meta.complex_selector(); + let tag = meta.fixed_column(); let q_call_data_start = meta.complex_selector(); let q_call_data_step = meta.complex_selector(); @@ -92,11 +101,11 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { // anchor transaction constants meta.lookup_any("anchor fixed fields", |meta| { - let q_anchor = meta.query_selector(q_anchor); + let q_anchor = meta.query_selector(q_tag); let tx_id = ANCHOR_TX_ID.expr(); - let tag = meta.query_fixed(anchor, Rotation(0)); + let value = meta.query_fixed(tag, Rotation::next()); + let tag = meta.query_fixed(tag, Rotation::cur()); let index = 0.expr(); - let value = meta.query_fixed(anchor, Rotation(1)); vec![ ( q_anchor.expr() * tx_id, @@ -167,8 +176,8 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { tx_table, pi_table, - q_anchor, - anchor, + q_tag, + tag, q_call_data_start, q_call_data_step, @@ -202,15 +211,14 @@ impl AnchorTxCircuitConfig { ( TxFieldTag::CallerAddress, Value::known( - taiko - .anchor_from + GOLDEN_TOUCH_ADDRESS .to_scalar() .expect("anchor_tx.from too big"), ), ), ( TxFieldTag::CalleeAddress, - Value::known(taiko.anchor_to.to_scalar().expect("anchor_tx.to too big")), + Value::known(taiko.l2_contract.to_scalar().expect("anchor_tx.to too big")), ), ( TxFieldTag::IsCreate, @@ -222,15 +230,15 @@ impl AnchorTxCircuitConfig { Value::known(F::from(ANCHOR_CALL_DATA_LEN as u64)), ), ] { - self.q_anchor.enable(region, offset)?; + self.q_tag.enable(region, offset)?; region.assign_fixed( || "tag", - self.anchor, + self.tag, offset, || Value::known(F::from(tag as u64)), )?; offset += 1; - region.assign_fixed(|| "anchor", self.anchor, offset, || value)?; + region.assign_fixed(|| "anchor", self.tag, offset, || value)?; offset += 1; } Ok(()) @@ -245,12 +253,6 @@ impl AnchorTxCircuitConfig { ) -> Result<(), Error> { let mut offset = call_data.start; let randomness = challenges.evm_word(); - // // check: method_signature, l1Hash, l1SignalRoot, l1Height, parentGasUsed - // q_call_data_start: Selector, - // q_call_data_step: Selector, - // q_call_data_end: Selector, - // call_data_rlc_acc: Column, - // call_data_tag: Column, for (annotation, value, tag) in [ ( "method_signature", @@ -350,6 +352,13 @@ impl AnchorTxCircuit { } } + /// Return the minimum number of rows required to prove an input of a + /// particular size. + pub(crate) fn min_num_rows() -> usize { + let rows_sign_verify = SignVerifyConfig::::min_num_rows(); + std::cmp::max(ANCHOR_CALL_DATA_LEN, rows_sign_verify) + } + fn call_data_start(&self) -> usize { self.max_txs * TX_LEN + 1 // empty row } @@ -363,7 +372,9 @@ impl SubCircuit for AnchorTxCircuit { type Config = AnchorTxCircuitConfig; fn unusable_rows() -> usize { - 0 + // No column queried at more than 1 distinct rotations, so returns 5 as + // minimum unusable row. + 5 } fn new_from_block(block: &witness::Block) -> Self { @@ -392,6 +403,7 @@ impl SubCircuit for AnchorTxCircuit { start: self.call_data_start(), end: self.call_data_end(), }; + // the first transaction is the anchor transaction config.assign( layouter, &self.anchor_tx, @@ -403,6 +415,6 @@ impl SubCircuit for AnchorTxCircuit { } fn min_num_rows_block(_block: &witness::Block) -> (usize, usize) { - (0, 0) + (Self::min_num_rows(), Self::min_num_rows()) } } diff --git a/zkevm-circuits/src/anchor_tx_circuit/dev.rs b/zkevm-circuits/src/anchor_tx_circuit/dev.rs new file mode 100644 index 0000000000..3c620bd144 --- /dev/null +++ b/zkevm-circuits/src/anchor_tx_circuit/dev.rs @@ -0,0 +1,83 @@ +pub use super::AnchorTxCircuit; +use crate::{ + anchor_tx_circuit::{AnchorTxCircuitConfig, AnchorTxCircuitConfigArgs}, + table::{PiTable, TxTable}, + util::{Challenges, SubCircuit, SubCircuitConfig}, + witness::{self, Taiko}, +}; +use eth_types::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, +}; + +/// Test circuit for the anchor tx circuit. +#[derive(Clone, Debug, Default)] +pub struct TestAnchorTxCircuit { + txs: Vec, + taiko: Taiko, + max_calldata: usize, + circuit: AnchorTxCircuit, +} + +impl TestAnchorTxCircuit { + /// Create a new test circuit from a block. + pub fn new_from_block(block: &witness::Block) -> Self { + TestAnchorTxCircuit { + txs: block.txs.clone(), + taiko: block.taiko.clone(), + max_calldata: block.circuits_params.max_calldata, + circuit: AnchorTxCircuit::new_from_block(block), + } + } +} + +impl Circuit for TestAnchorTxCircuit { + type Config = (AnchorTxCircuitConfig, Challenges); + type FloorPlanner = SimpleFloorPlanner; + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let tx_table = TxTable::construct(meta); + let pi_table = PiTable::construct(meta); + let challenges = Challenges::construct(meta); + + let config = { + let challenges = challenges.exprs(meta); + AnchorTxCircuitConfig::new( + meta, + AnchorTxCircuitConfigArgs { + tx_table, + pi_table, + challenges, + }, + ) + }; + + (config, challenges) + } + + fn synthesize( + &self, + (config, challenges): Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + config.tx_table.load( + &mut layouter, + &self.txs[..], + self.circuit.max_txs, + self.max_calldata, + &challenges, + )?; + config + .pi_table + .load(&mut layouter, &self.taiko, &challenges)?; + self.circuit + .synthesize_sub(&config, &challenges, &mut layouter) + } +} diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 481ce9db08..28d3b1068e 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -6,7 +6,8 @@ use crate::{ table::{TxFieldTag, TxTable}, util::Challenges, }; -use eth_types::{geth_types::Transaction, Field, ToBigEndian, Word, U256}; +use eth_types::{geth_types::Transaction, Address, Field, ToBigEndian, Word, U256}; +use ethers_signers::LocalWallet; use gadgets::{ is_equal::IsEqualChip, mul_add::{MulAddChip, MulAddConfig}, @@ -19,11 +20,28 @@ use halo2_proofs::{ }; use log::error; use once_cell::sync::Lazy; +use std::str::FromStr; const ANCHOR_TX_ID: usize = 0; -const MAX_DEGREE: usize = 10; +const MAX_DEGREE: usize = 9; const BYTE_POW_BASE: u64 = 1 << 8; +pub(crate) static GOLDEN_TOUCH_ADDRESS: Lazy
= + Lazy::new(|| Address::from_str("0x0000777735367b36bC9B61C50022d9D0700dB4Ec").unwrap()); + +// 0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38 +pub(crate) const GOLDEN_TOUCH_PRIVATEKEY: Word = U256([ + 0xacc1bf426222ce38, + 0x414054aefe1ef9ae, + 0xf3ce3ead0069c1af, + 0x92954368afd3caa1, +]); + +pub(crate) static GOLDEN_TOUCH_WALLET: Lazy = Lazy::new(|| { + LocalWallet::from_str("0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38") + .unwrap() +}); + // 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 const GX1: Word = U256([ 0x59F2815B16F81798, @@ -386,6 +404,12 @@ impl SignVerifyConfig { chip.assign(region, 0, [GX1_MUL_PRIVATEKEY, U256::one(), msg_hash, N]) } + /// Return the minimum number of rows required to prove an input of a + /// particular size. + pub(crate) fn min_num_rows() -> usize { + 64 // msg_hash(32B) + sign_r(32B) + } + pub(crate) fn assign( &self, layouter: &mut impl Layouter, diff --git a/zkevm-circuits/src/anchor_tx_circuit/test.rs b/zkevm-circuits/src/anchor_tx_circuit/test.rs new file mode 100644 index 0000000000..5cf79d817f --- /dev/null +++ b/zkevm-circuits/src/anchor_tx_circuit/test.rs @@ -0,0 +1,81 @@ +#![allow(unused_imports)] +use super::{ + sign_verify::{GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_PRIVATEKEY, GOLDEN_TOUCH_WALLET}, + *, +}; +use crate::{ + util::{log2_ceil, unusable_rows}, + witness::{block_convert, Block}, +}; +use bus_mapping::{ + circuit_input_builder::{CircuitInputBuilder, CircuitsParams}, + mock::BlockData, +}; +use eth_types::{address, geth_types::GethData, ToWord, Word}; +use halo2_proofs::{ + dev::{MockProver, VerifyFailure}, + halo2curves::bn256::Fr, +}; +use itertools::Itertools; +use mock::{AddrOrWallet, TestContext}; + +#[test] +fn tx_circuit_unusable_rows() { + assert_eq!( + AnchorTxCircuit::::unusable_rows(), + unusable_rows::>(()), + ) +} + +fn run(block: &Block) -> Result<(), Vec> { + let k = + log2_ceil(AnchorTxCircuit::::unusable_rows() + AnchorTxCircuit::::min_num_rows()); + let circuit = TestAnchorTxCircuit::::new_from_block(block); + + let prover = match MockProver::run(k, &circuit, vec![vec![]]) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + prover.verify() +} + +const GAS_LIMIT: u64 = 1500000; + +fn gen_block( + max_txs: usize, + max_call_data: usize, + taiko: Taiko, +) -> Block { + let block: GethData = TestContext::<1, NUM_TXS>::new( + None, + |_accs| {}, + |mut txs, _accs| { + txs[0] + .gas(GAS_LIMIT.to_word()) + .gas_price(ANCHOR_TX_GAS_PRICE.to_word()) + .from(GOLDEN_TOUCH_WALLET.clone()) + .to(taiko.l2_contract) + .value(ANCHOR_TX_VALUE.to_word()); + }, + |block, _tx| block, + ) + .unwrap() + .into(); + let mut circuits_params = CircuitsParams::default(); + circuits_params.max_txs = max_txs; + circuits_params.max_calldata = max_call_data; + let mut builder = BlockData::new_from_geth_data_with_params(block.clone(), circuits_params) + .new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + let mut block = block_convert::(&builder.block, &builder.code_db).unwrap(); + block.taiko = taiko; + block +} + +#[test] +fn test() { + let block = gen_block::<2>(2, 100, Taiko::default()); + assert_eq!(run::(&block), Ok(())); +} diff --git a/zkevm-circuits/src/pi_circuit2.rs b/zkevm-circuits/src/pi_circuit2.rs index 0486ee1e79..7f10949b57 100644 --- a/zkevm-circuits/src/pi_circuit2.rs +++ b/zkevm-circuits/src/pi_circuit2.rs @@ -22,7 +22,7 @@ use halo2_proofs::{ }; use std::marker::PhantomData; -const MAX_DEGREE: usize = 10; +const MAX_DEGREE: usize = 9; const MIN_DEGREE: usize = 8; const RPI_CELL_IDX: usize = 0; const RPI_RLC_ACC_CELL_IDX: usize = 1; @@ -158,7 +158,7 @@ impl PublicData { l1_signal_service: taiko.l1_signal_service.to_word(), l2_signal_service: taiko.l2_signal_service.to_word(), l2_contract: taiko.l2_contract.to_word(), - meta_hash: taiko.meta_hash.to_word(), + meta_hash: taiko.meta_hash.hash().to_word(), block_hash: taiko.block_hash.to_word(), parent_hash: taiko.parent_hash.to_word(), signal_root: taiko.signal_root.to_word(), diff --git a/zkevm-circuits/src/table/pi_table.rs b/zkevm-circuits/src/table/pi_table.rs index 03066bb894..ef2d35c387 100644 --- a/zkevm-circuits/src/table/pi_table.rs +++ b/zkevm-circuits/src/table/pi_table.rs @@ -1,3 +1,5 @@ +use crate::witness::Taiko; + use super::*; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -16,3 +18,53 @@ pub struct PiTable { pub tag: Column, pub value: Column, } + +impl PiTable { + /// Construct a new TxTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + tag: meta.fixed_column(), + value: meta.advice_column_in(SecondPhase), + } + } + + /// Assign the `TxTable` from a list of block `Transaction`s, followig the + /// same layout that the Tx Circuit uses. + pub fn load( + &self, + layouter: &mut impl Layouter, + taiko: &Taiko, + challenges: &Challenges>, + ) -> Result<(), Error> { + layouter.assign_region( + || "pi table", + |mut region| { + let randomness = challenges.evm_word(); + for (offset, [tag, value]) in + taiko.table_assignments(randomness).into_iter().enumerate() + { + region.assign_fixed(|| "tag", self.tag, offset, || tag)?; + region.assign_advice(|| "value", self.value, offset, || value)?; + } + Ok(()) + }, + ) + } +} + +impl LookupTable for PiTable { + fn columns(&self) -> Vec> { + vec![self.tag.into(), self.value.into()] + } + + fn annotations(&self) -> Vec { + vec![String::from("tag"), String::from("value")] + } + + fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { + vec![ + meta.query_fixed(self.tag, Rotation::cur()), + meta.query_advice(self.value, Rotation::cur()), + ] + } +} diff --git a/zkevm-circuits/src/witness/taiko.rs b/zkevm-circuits/src/witness/taiko.rs index 5fdcc40340..71c58acb07 100644 --- a/zkevm-circuits/src/witness/taiko.rs +++ b/zkevm-circuits/src/witness/taiko.rs @@ -1,6 +1,11 @@ //! extra witness for taiko circuits -use eth_types::{Address, Hash, H256}; +use crate::{evm_circuit::util::rlc, table::PiFieldTag}; +use eth_types::{Address, Field, Hash, ToLittleEndian, ToWord, H256}; +use halo2_proofs::circuit::Value; + +// TODO: calculate the method_signature +const ANCHOR_TX_METHOD_SIGNATURE: u32 = 0; /// Taiko witness #[derive(Debug, Default, Clone)] @@ -12,7 +17,7 @@ pub struct Taiko { /// l2 contract address pub l2_contract: Address, /// meta hash - pub meta_hash: Hash, + pub meta_hash: MetaHash, /// block hash value pub block_hash: Hash, /// the parent block hash @@ -36,8 +41,74 @@ pub struct Taiko { /// anchor gas cost pub anchor_gas_cost: u64, - /// anchor from - pub anchor_from: Address, - /// anchor to - pub anchor_to: Address, +} + +#[derive(Debug, Default, Clone)] +pub struct MetaHash { + /// meta id + pub id: u64, + /// meta timestamp + pub timestamp: u64, + /// l1 block height + pub l1_height: u64, + /// l1 block hash + pub l1_hash: Hash, + /// l1 block mix hash + pub l1_mix_hash: Hash, + /// deposits processed + pub deposits_processed: Hash, + /// tx list hash + pub tx_list_hash: Hash, + /// tx list byte start + pub tx_list_byte_start: u32, // u24 + /// tx list byte end + pub tx_list_byte_end: u32, // u24 + /// gas limit + pub gas_limit: u32, + /// beneficiary + pub beneficiary: Address, + /// treasury + pub treasury: Address, +} + +impl MetaHash { + pub fn hash(&self) -> Hash { + todo!() + } +} + +impl Taiko { + /// Assignments for pi table + pub fn table_assignments(&self, randomness: Value) -> [[Value; 2]; 6] { + [ + [ + Value::known(F::from(PiFieldTag::Null as u64)), + Value::known(F::ZERO), + ], + [ + Value::known(F::from(PiFieldTag::MethodSign as u64)), + Value::known(F::from(ANCHOR_TX_METHOD_SIGNATURE as u64)), + ], + [ + Value::known(F::from(PiFieldTag::L1Hash as u64)), + randomness.map(|randomness| { + rlc::value(&self.meta_hash.l1_hash.to_word().to_le_bytes(), randomness) + }), + ], + [ + Value::known(F::from(PiFieldTag::L1SignalRoot as u64)), + randomness.map(|randomness| { + rlc::value(&self.signal_root.to_word().to_le_bytes(), randomness) + }), + ], + [ + Value::known(F::from(PiFieldTag::L1Height as u64)), + Value::known(F::from(self.meta_hash.l1_height)), + ], + [ + Value::known(F::from(PiFieldTag::ParentGasUsed as u64)), + Value::known(F::from(self.parent_gas_used as u64)), + ], + ] + } } From 839152c04ab3ddd1b8ce32632a407e5e7ef823a8 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 12 Jun 2023 17:22:42 +0800 Subject: [PATCH 05/20] chore: update method signature --- zkevm-circuits/src/witness/taiko.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zkevm-circuits/src/witness/taiko.rs b/zkevm-circuits/src/witness/taiko.rs index 71c58acb07..7e11ddbf6a 100644 --- a/zkevm-circuits/src/witness/taiko.rs +++ b/zkevm-circuits/src/witness/taiko.rs @@ -4,8 +4,7 @@ use crate::{evm_circuit::util::rlc, table::PiFieldTag}; use eth_types::{Address, Field, Hash, ToLittleEndian, ToWord, H256}; use halo2_proofs::circuit::Value; -// TODO: calculate the method_signature -const ANCHOR_TX_METHOD_SIGNATURE: u32 = 0; +const ANCHOR_TX_METHOD_SIGNATURE: u32 = 0x3d384a4b; /// Taiko witness #[derive(Debug, Default, Clone)] From 2e1821a4c72c71dab50d9c1565de6b66e817d777 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 12 Jun 2023 17:55:09 +0800 Subject: [PATCH 06/20] fix: clippy --- zkevm-circuits/src/anchor_tx_circuit/test.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit/test.rs b/zkevm-circuits/src/anchor_tx_circuit/test.rs index 5cf79d817f..604c4a06bc 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/test.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/test.rs @@ -41,11 +41,7 @@ fn run(block: &Block) -> Result<(), Vec> { const GAS_LIMIT: u64 = 1500000; -fn gen_block( - max_txs: usize, - max_call_data: usize, - taiko: Taiko, -) -> Block { +fn gen_block(max_txs: usize, max_calldata: usize, taiko: Taiko) -> Block { let block: GethData = TestContext::<1, NUM_TXS>::new( None, |_accs| {}, @@ -61,9 +57,11 @@ fn gen_block( ) .unwrap() .into(); - let mut circuits_params = CircuitsParams::default(); - circuits_params.max_txs = max_txs; - circuits_params.max_calldata = max_call_data; + let circuits_params = CircuitsParams { + max_txs, + max_calldata, + ..Default::default() + }; let mut builder = BlockData::new_from_geth_data_with_params(block.clone(), circuits_params) .new_circuit_input_builder(); builder From 6eab07b6877e8843d0a182669d1db14829ddbcca Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 12 Jun 2023 18:14:49 +0800 Subject: [PATCH 07/20] feat: support meta hash --- zkevm-circuits/src/pi_circuit2.rs | 13 +-------- zkevm-circuits/src/witness.rs | 2 +- zkevm-circuits/src/witness/taiko.rs | 44 +++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/zkevm-circuits/src/pi_circuit2.rs b/zkevm-circuits/src/pi_circuit2.rs index 7f10949b57..e487a10500 100644 --- a/zkevm-circuits/src/pi_circuit2.rs +++ b/zkevm-circuits/src/pi_circuit2.rs @@ -135,18 +135,7 @@ impl PublicData { /// create PublicData from block and taiko pub fn new(block: &witness::Block, taiko: &witness::Taiko) -> Self { - // left shift x by n bits - fn left_shift(x: T, n: u32) -> Word { - assert!(n < 256); - if n < 128 { - return x.to_word() * Word::from(2u128.pow(n)); - } - let mut bits = [0; 32]; - bits[..16].copy_from_slice(2u128.pow(n - 128).to_be_bytes().as_ref()); - bits[16..].copy_from_slice(0u128.to_be_bytes().as_ref()); - x.to_word() * Word::from(&bits[..]) - } - + use witness::left_shift; let field9 = left_shift(taiko.prover, 96) + left_shift(taiko.parent_gas_used as u64, 64) + left_shift(taiko.gas_used as u64, 32); diff --git a/zkevm-circuits/src/witness.rs b/zkevm-circuits/src/witness.rs index edea9c3c7f..e6c57370db 100644 --- a/zkevm-circuits/src/witness.rs +++ b/zkevm-circuits/src/witness.rs @@ -15,4 +15,4 @@ mod tx; pub use bus_mapping::circuit_input_builder::Call; pub use tx::Transaction; mod taiko; -pub use taiko::Taiko; +pub use taiko::{left_shift, MetaHash, Taiko}; diff --git a/zkevm-circuits/src/witness/taiko.rs b/zkevm-circuits/src/witness/taiko.rs index 7e11ddbf6a..036af91a52 100644 --- a/zkevm-circuits/src/witness/taiko.rs +++ b/zkevm-circuits/src/witness/taiko.rs @@ -1,8 +1,11 @@ //! extra witness for taiko circuits +use std::iter; + use crate::{evm_circuit::util::rlc, table::PiFieldTag}; -use eth_types::{Address, Field, Hash, ToLittleEndian, ToWord, H256}; +use eth_types::{Address, Field, Hash, ToBigEndian, ToLittleEndian, ToWord, Word, H256}; use halo2_proofs::circuit::Value; +use keccak256::plain::Keccak; const ANCHOR_TX_METHOD_SIGNATURE: u32 = 0x3d384a4b; @@ -42,6 +45,7 @@ pub struct Taiko { pub anchor_gas_cost: u64, } +/// l1 meta hash #[derive(Debug, Default, Clone)] pub struct MetaHash { /// meta id @@ -70,9 +74,45 @@ pub struct MetaHash { pub treasury: Address, } +/// left shift x by n bits +pub fn left_shift(x: T, n: u32) -> Word { + assert!(n < 256); + if n < 128 { + return x.to_word() * Word::from(2u128.pow(n)); + } + let mut bits = [0; 32]; + bits[..16].copy_from_slice(2u128.pow(n - 128).to_be_bytes().as_ref()); + bits[16..].copy_from_slice(0u128.to_be_bytes().as_ref()); + x.to_word() * Word::from(&bits[..]) +} + impl MetaHash { + /// get the hash of meta hash pub fn hash(&self) -> Hash { - todo!() + let field0 = left_shift(self.id as u64, 192) + + left_shift(self.timestamp as u64, 128) + + left_shift(self.l1_height as u64, 64); + + let field5 = left_shift(self.tx_list_byte_start as u64, 232) + + left_shift(self.tx_list_byte_end as u64, 208) + + left_shift(self.gas_limit as u64, 176) + + left_shift(self.beneficiary, 16); + + let field6 = left_shift(self.treasury, 96); + + let input: Vec = iter::empty() + .chain(field0.to_be_bytes()) + .chain(self.l1_hash.to_fixed_bytes()) + .chain(self.l1_mix_hash.to_fixed_bytes()) + .chain(self.deposits_processed.to_fixed_bytes()) + .chain(self.tx_list_hash.to_fixed_bytes()) + .chain(field5.to_be_bytes()) + .chain(field6.to_be_bytes()) + .collect(); + let mut keccak = Keccak::default(); + keccak.update(&input); + let output = keccak.digest(); + Hash::from_slice(&output) } } From b16ad373fc90eba2df9f1a117eb64c7f6b4325ca Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 12 Jun 2023 21:31:45 +0800 Subject: [PATCH 08/20] chore: improve the comment --- .../src/anchor_tx_circuit/sign_verify.rs | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 28d3b1068e..aa6443f8ea 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -1,3 +1,42 @@ +//! # How to check the signature +//! +//! 1. IF r == GX1 OR r == GX2 +//! 2. IF r == GX2 THEN MUST WHEN r == GX1 AND s == 0 +//! 3. IF s == 0 THEN GX1_MUL_PRIVATEKEY + msg_hash == N +//! +//! So, IF r == GX2 THEN GX1_MUL_PRIVATEKEY + msg_hash == N +//! +//! ## Why we only need to prove the equation: GX1_MUL_PRIVATEKEY + msg_hash == N +//! +//! based on the algorithm of [taiko-mono](https://github.com/taikoxyz/taiko-mono/blob/ad26803e5bcbcc76b812084b7bd08f45992e59dd/packages/protocol/contracts/libs/LibAnchorSignature.sol#L68) +//! +//! ### The formula of signature with K = 1 +//! +//! ``` +//! s = (GX1 * GOLDEN_TOUCH_PRIVATEKEY + msg_hash) (mod N) (K = 1) +//! ``` +//! +//! #### Formula deformation +//! +//! ``` +//! s = (GX1 * GOLDEN_TOUCH_PRIVATEKEY (mod N) + msg_hash (mod N)) (mod N) +//! ``` +//! +//! - Our `GX1_MUL_PRIVATEKEY` is equal to `GX1 * GOLDEN_TOUCH_PRIVATEKEY (mod N)` +//! - Our `msg_hash` has already been (mod N) in [zkevm-circuit](https://github.com/taikoxyz/zkevm-circuits/blob/839152c04ab3ddd1b8ce32632a407e5e7ef823a8/eth-types/src/geth_types.rs#L236) +//! +//! ```rust +//! let msg_hash = msg_hash.mod_floor(&*SECP256K1_Q); +//! ``` +//! +//! ### Summary +//! +//! ``` +//! because: 0 < GX1_MUL_PRIVATEKEY + msg_hash < 2N +//! need prove: (GX1_MUL_PRIVATEKEY + msg_hash) (mod N) == 0 +//! so: GX1_MUL_PRIVATEKEY + msg_hash == N +//! ``` + use crate::{ evm_circuit::util::{ constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, @@ -90,18 +129,13 @@ const GX2_MUL_PRIVATEKEY: Word = U256([ ]); static GX2_MUL_PRIVATEKEY_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX2_MUL_PRIVATEKEY)); -// # How to check the signature -// 1. IF r == GX1 OR r == GX2 -// 2. IF r == GX2 THEN MUST WHEN r == GX1 AND s == 0 -// 3. IF s == 0 THEN (GX1_MUL_PRIVATEKEY + msg_hash) == N -// => IF r == GX2 THEN GX1_MUL_PRIVATEKEY + msg_hash == N // In mul_add chip, we have a * b + c == d // => a == GX1_MUL_PRIVATEKEY // => b == 1 // => c == msg_hash // => d == N // -// # The layout +// # The circuit layout // - msg_hash (c) // - SigR From c29e776d493382f123fa0d6fc4c8b059a78adb6c Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 12 Jun 2023 21:38:45 +0800 Subject: [PATCH 09/20] chore: use LookupTable for readable --- zkevm-circuits/src/anchor_tx_circuit.rs | 17 ++++------- .../src/anchor_tx_circuit/sign_verify.rs | 30 +++++++------------ 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index 8631abb493..5b14d39421 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -10,7 +10,7 @@ mod test; use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, - table::{PiFieldTag, PiTable, TxFieldTag, TxTable}, + table::{LookupTable, PiFieldTag, PiTable, TxFieldTag, TxTable}, tx_circuit::TX_LEN, util::{Challenges, SubCircuit, SubCircuitConfig}, witness::{self, Taiko}, @@ -160,16 +160,11 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); let call_data_tag = meta.query_fixed(call_data_tag, Rotation::cur()); - vec![ - ( - q_call_data_end.expr() * call_data_tag, - meta.query_fixed(pi_table.tag, Rotation::cur()), - ), - ( - q_call_data_end * call_data_rlc_acc, - meta.query_advice(pi_table.value, Rotation::cur()), - ), - ] + [call_data_tag, call_data_rlc_acc] + .into_iter() + .zip(pi_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (q_call_data_end.expr() * arg, table)) + .collect::>() }); Self { diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index aa6443f8ea..40c2fd0b07 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -42,7 +42,7 @@ use crate::{ constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, split_u256_limb64, }, - table::{TxFieldTag, TxTable}, + table::{LookupTable, TxFieldTag, TxTable}, util::Challenges, }; use eth_types::{geth_types::Transaction, Address, Field, ToBigEndian, Word, U256}; @@ -219,6 +219,7 @@ impl SignVerifyConfig { cb.gate(q_sign_step) }, ); + meta.create_gate("sign_rlc_acc[0] = sign[0]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); @@ -229,6 +230,7 @@ impl SignVerifyConfig { cb.require_equal("sign_rlc_acc[0] = sign[0]", sign_rlc_acc, sign); cb.gate(q_sign_start) }); + meta.lookup_any("sign_r or msg_hash in tx_table", |meta| { let q_sign_end = meta.query_selector(q_sign_end); @@ -236,25 +238,14 @@ impl SignVerifyConfig { let tag = meta.query_fixed(tag, Rotation::cur()); let index = 0.expr(); let value = meta.query_advice(sign_rlc_acc, Rotation::cur()); - vec![ - ( - q_sign_end.expr() * tx_id, - meta.query_advice(tx_table.tx_id, Rotation::cur()), - ), - ( - q_sign_end.expr() * tag, - meta.query_fixed(tx_table.tag, Rotation::cur()), - ), - ( - q_sign_end.expr() * index, - meta.query_advice(tx_table.index, Rotation::cur()), - ), - ( - q_sign_end * value, - meta.query_advice(tx_table.value, Rotation::cur()), - ), - ] + + [tx_id, tag, index, value] + .into_iter() + .zip(tx_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (q_sign_end.expr() * arg, table)) + .collect::>() }); + // signature u128 meta.create_gate( "sign_u128_acc[i+1] = sign_u128_acc[i] * BYTE_POW_BASE + sign[i+1]", @@ -273,6 +264,7 @@ impl SignVerifyConfig { cb.gate(q_u128_step) }, ); + meta.create_gate("sign_u128_acc[start] = sign[start]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); From 54bb92cd754b2db117b82762349338e817f23b59 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 19 Jun 2023 18:09:01 +0800 Subject: [PATCH 10/20] improve: readable --- zkevm-circuits/src/anchor_tx_circuit.rs | 137 ++++++++++-------- .../src/anchor_tx_circuit/sign_verify.rs | 83 ++++------- zkevm-circuits/src/anchor_tx_circuit/test.rs | 135 +++++++++++++++-- zkevm-circuits/src/util.rs | 8 + zkevm-circuits/src/witness/block.rs | 2 +- zkevm-circuits/src/witness/taiko.rs | 25 +++- zkevm-circuits/src/witness/tx.rs | 57 +++++++- 7 files changed, 306 insertions(+), 141 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index 5b14d39421..aa5c658d2e 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -13,13 +13,16 @@ use crate::{ table::{LookupTable, PiFieldTag, PiTable, TxFieldTag, TxTable}, tx_circuit::TX_LEN, util::{Challenges, SubCircuit, SubCircuitConfig}, - witness::{self, Taiko}, + witness::{self, Taiko, Transaction}, }; -use eth_types::{geth_types::Transaction, Field, ToScalar}; -use gadgets::util::Expr; +use eth_types::{Field, ToScalar}; +use gadgets::util::{select, Expr}; use halo2_proofs::{ circuit::{Layouter, Region, Value}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector}, + plonk::{ + Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector, + ThirdPhase, + }, poly::Rotation, }; use sign_verify::SignVerifyConfig; @@ -28,7 +31,7 @@ use std::marker::PhantomData; use self::sign_verify::GOLDEN_TOUCH_ADDRESS; // The first of txlist is the anchor tx -const ANCHOR_TX_ID: usize = 0; +const ANCHOR_TX_ID: usize = 1; const ANCHOR_TX_VALUE: u64 = 0; const ANCHOR_TX_IS_CREATE: bool = false; const ANCHOR_TX_GAS_PRICE: u64 = 0; @@ -56,6 +59,7 @@ pub struct AnchorTxCircuitConfig { // Gas, GasPrice, CallerAddress, CalleeAddress, IsCreate, Value, CallDataLength, // 2 rows: 0, tag, 0, value tag: Column, + use_rlc: Column, // check: method_signature, l1Hash, l1SignalRoot, l1Height, parentGasUsed q_call_data_start: Selector, @@ -91,6 +95,7 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { ) -> Self { let q_tag = meta.complex_selector(); let tag = meta.fixed_column(); + let use_rlc = meta.fixed_column(); let q_call_data_start = meta.complex_selector(); let q_call_data_step = meta.complex_selector(); @@ -102,33 +107,21 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { // anchor transaction constants meta.lookup_any("anchor fixed fields", |meta| { let q_anchor = meta.query_selector(q_tag); - let tx_id = ANCHOR_TX_ID.expr(); - let value = meta.query_fixed(tag, Rotation::next()); - let tag = meta.query_fixed(tag, Rotation::cur()); - let index = 0.expr(); - vec![ - ( - q_anchor.expr() * tx_id, - meta.query_advice(tx_table.tx_id, Rotation::cur()), - ), - ( - q_anchor.expr() * tag, - meta.query_fixed(tx_table.tag, Rotation::cur()), - ), - ( - q_anchor.expr() * index, - meta.query_advice(tx_table.index, Rotation::cur()), - ), - ( - q_anchor * value, - meta.query_advice(tx_table.value, Rotation::cur()), - ), + [ + ANCHOR_TX_ID.expr(), + meta.query_fixed(tag, Rotation::cur()), + 0.expr(), + meta.query_fixed(tag, Rotation::next()), ] + .into_iter() + .zip(tx_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (q_anchor.expr() * arg, table)) + .collect() }); // call data meta.create_gate( - "call_data_rlc_acc[i+1] = call_data_rlc_acc[i] * randomness + call_data[i+1]", + "call_data_rlc_acc[i+1] = call_data_rlc_acc[i] * t + call_data[i+1]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); @@ -136,25 +129,33 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { let call_data_rlc_acc_next = meta.query_advice(call_data_rlc_acc, Rotation::next()); let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); let call_data_next = meta.query_advice(tx_table.value, Rotation::next()); + let use_rlc = meta.query_fixed(use_rlc, Rotation::cur()); let randomness = challenges.evm_word(); + let t = select::expr(use_rlc, randomness, BYTE_POW_BASE.expr()); cb.require_equal( - "call_data_rlc_acc[i+1] = call_data_rlc_acc[i] * randomness + call_data[i+1]", + "call_data_rlc_acc[i+1] = call_data_rlc_acc[i] * t + call_data[i+1]", call_data_rlc_acc_next, - call_data_rlc_acc * randomness + call_data_next, + call_data_rlc_acc * t + call_data_next, ); cb.gate(q_call_data_step) }, ); - meta.create_gate("call_data_acc[0] = call_data[0]", |meta| { + + meta.create_gate("call_data_rlc_acc[0] = call_data[0]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); let q_call_data_start = meta.query_selector(q_call_data_start); - let call_data_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); + let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); let call_data = meta.query_advice(tx_table.value, Rotation::cur()); - cb.require_equal("call_data_acc[0] = call_data[0]", call_data_acc, call_data); + cb.require_equal( + "call_data_rlc_acc[0] = call_data[0]", + call_data_rlc_acc, + call_data, + ); cb.gate(q_call_data_start) }); + meta.lookup_any("call data in pi_table", |meta| { let q_call_data_end = meta.query_selector(q_call_data_end); let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); @@ -173,6 +174,7 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { q_tag, tag, + use_rlc, q_call_data_start, q_call_data_step, @@ -247,7 +249,23 @@ impl AnchorTxCircuitConfig { challenges: &Challenges>, ) -> Result<(), Error> { let mut offset = call_data.start; - let randomness = challenges.evm_word(); + // for idx in 0..offset { + // // fill zero + // region.assign_advice( + // || "zero value", + // self.call_data_rlc_acc, + // idx, + // || Value::known(F::ZERO), + // )?; + // region.assign_fixed( + // || "zero value", + // self.call_data_tag, + // idx, + // || Value::known(F::ZERO), + // )?; + // region.assign_fixed(|| "zero value", self.use_rlc, idx, || Value::known(F::ZERO))?; + // } + for (annotation, value, tag) in [ ( "method_signature", @@ -272,9 +290,14 @@ impl AnchorTxCircuitConfig { ), ] { let mut rlc_acc = Value::known(F::ZERO); + let (use_rlc, t) = if value.len() * 8 > F::CAPACITY as usize { + (Value::known(F::ONE), challenges.evm_word()) + } else { + (Value::known(F::ZERO), Value::known(F::from(BYTE_POW_BASE))) + }; for (idx, byte) in value.iter().enumerate() { let row_offset = offset + idx; - rlc_acc = rlc_acc * randomness + Value::known(F::from(*byte as u64)); + rlc_acc = rlc_acc * t + Value::known(F::from(*byte as u64)); region.assign_advice( || annotation, self.call_data_rlc_acc, @@ -287,6 +310,7 @@ impl AnchorTxCircuitConfig { row_offset, || Value::known(F::from(tag as u64)), )?; + region.assign_fixed(|| annotation, self.use_rlc, row_offset, || use_rlc)?; // setup selector if idx == 0 { self.q_call_data_start.enable(region, row_offset)?; @@ -300,20 +324,18 @@ impl AnchorTxCircuitConfig { } offset += value.len(); } - todo!() + Ok(()) } fn assign( &self, layouter: &mut impl Layouter, anchor_tx: &Transaction, - chain_id: u64, taiko: &Taiko, call_data: &CallData, challenges: &Challenges>, ) -> Result<(), Error> { - self.sign_verify - .assign(layouter, anchor_tx, chain_id, challenges)?; + self.sign_verify.assign(layouter, anchor_tx, challenges)?; layouter.assign_region( || "anchor transaction", |ref mut region| { @@ -330,18 +352,16 @@ impl AnchorTxCircuitConfig { pub struct AnchorTxCircuit { max_txs: usize, anchor_tx: Transaction, - chain_id: u64, taiko: Taiko, _marker: PhantomData, } impl AnchorTxCircuit { /// Return a new TxCircuit - pub fn new(max_txs: usize, anchor_tx: Transaction, chain_id: u64, taiko: Taiko) -> Self { + pub fn new(max_txs: usize, anchor_tx: Transaction, taiko: Taiko) -> Self { AnchorTxCircuit { max_txs, anchor_tx, - chain_id, taiko, _marker: PhantomData, } @@ -349,17 +369,17 @@ impl AnchorTxCircuit { /// Return the minimum number of rows required to prove an input of a /// particular size. - pub(crate) fn min_num_rows() -> usize { + pub(crate) fn min_num_rows(max_txs: usize) -> usize { let rows_sign_verify = SignVerifyConfig::::min_num_rows(); - std::cmp::max(ANCHOR_CALL_DATA_LEN, rows_sign_verify) + std::cmp::max(Self::call_data_end(max_txs), rows_sign_verify) } - fn call_data_start(&self) -> usize { - self.max_txs * TX_LEN + 1 // empty row + fn call_data_start(max_txs: usize) -> usize { + max_txs * TX_LEN + 1 // empty row } - fn call_data_end(&self) -> usize { - self.call_data_start() + ANCHOR_CALL_DATA_LEN + fn call_data_end(max_txs: usize) -> usize { + Self::call_data_start(max_txs) + ANCHOR_CALL_DATA_LEN } } @@ -367,7 +387,7 @@ impl SubCircuit for AnchorTxCircuit { type Config = AnchorTxCircuitConfig; fn unusable_rows() -> usize { - // No column queried at more than 1 distinct rotations, so returns 5 as + // No column queried at more than 2 distinct rotations, so returns 5 as // minimum unusable row. 5 } @@ -375,14 +395,7 @@ impl SubCircuit for AnchorTxCircuit { fn new_from_block(block: &witness::Block) -> Self { Self::new( block.circuits_params.max_txs, - block - .eth_block - .transactions - .iter() - .map(|tx| tx.into()) - .next() - .unwrap(), - block.context.chain_id.as_u64(), + block.txs.iter().next().unwrap().clone(), block.taiko.clone(), ) } @@ -395,21 +408,23 @@ impl SubCircuit for AnchorTxCircuit { layouter: &mut impl Layouter, ) -> Result<(), Error> { let call_data = CallData { - start: self.call_data_start(), - end: self.call_data_end(), + start: Self::call_data_start(self.max_txs), + end: Self::call_data_end(self.max_txs), }; // the first transaction is the anchor transaction config.assign( layouter, &self.anchor_tx, - self.chain_id, &self.taiko, &call_data, challenges, ) } - fn min_num_rows_block(_block: &witness::Block) -> (usize, usize) { - (Self::min_num_rows(), Self::min_num_rows()) + fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + ( + Self::min_num_rows(block.circuits_params.max_txs), + Self::min_num_rows(block.circuits_params.max_txs), + ) } } diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 40c2fd0b07..beab9b2e2c 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -44,8 +44,9 @@ use crate::{ }, table::{LookupTable, TxFieldTag, TxTable}, util::Challenges, + witness::Transaction, }; -use eth_types::{geth_types::Transaction, Address, Field, ToBigEndian, Word, U256}; +use eth_types::{address, word, Address, Field, ToBigEndian, Word, U256}; use ethers_signers::LocalWallet; use gadgets::{ is_equal::IsEqualChip, @@ -57,24 +58,18 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector}, poly::Rotation, }; -use log::error; use once_cell::sync::Lazy; use std::str::FromStr; -const ANCHOR_TX_ID: usize = 0; const MAX_DEGREE: usize = 9; const BYTE_POW_BASE: u64 = 1 << 8; pub(crate) static GOLDEN_TOUCH_ADDRESS: Lazy
= - Lazy::new(|| Address::from_str("0x0000777735367b36bC9B61C50022d9D0700dB4Ec").unwrap()); + Lazy::new(|| address!("0x0000777735367b36bC9B61C50022d9D0700dB4Ec")); // 0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38 -pub(crate) const GOLDEN_TOUCH_PRIVATEKEY: Word = U256([ - 0xacc1bf426222ce38, - 0x414054aefe1ef9ae, - 0xf3ce3ead0069c1af, - 0x92954368afd3caa1, -]); +pub(crate) static GOLDEN_TOUCH_PRIVATEKEY: Lazy = + Lazy::new(|| word!("0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38")); pub(crate) static GOLDEN_TOUCH_WALLET: Lazy = Lazy::new(|| { LocalWallet::from_str("0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38") @@ -82,51 +77,31 @@ pub(crate) static GOLDEN_TOUCH_WALLET: Lazy = Lazy::new(|| { }); // 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 -const GX1: Word = U256([ - 0x59F2815B16F81798, - 0x029BFCDB2DCE28D9, - 0x55A06295CE870B07, - 0x79BE667EF9DCBBAC, -]); +pub(crate) static GX1: Lazy = + Lazy::new(|| word!("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")); static GX1_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX1)); // 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 -const GX2: Word = U256([ - 0xabac09b95c709ee5, - 0x5c778e4b8cef3ca7, - 0x3045406e95c07cd8, - 0xc6047f9441ed7d6d, -]); +pub(crate) static GX2: Lazy = + Lazy::new(|| word!("0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5")); static GX2_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX2)); // 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 -const N: Word = U256([ - 0xbfd25e8cd0364141, - 0xbaaedce6af48a03b, - 0xfffffffffffffffe, - 0xffffffffffffffff, -]); +static N: Lazy = + Lazy::new(|| word!("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")); static N_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&N)); // private key 0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38 // GX1 * PRIVATEKEY(mod N) = 0x4341adf5a780b4a87939938fd7a032f6e6664c7da553c121d3b4947429639122 -const GX1_MUL_PRIVATEKEY: Word = U256([ - 0xd3b4947429639122, - 0xe6664c7da553c121, - 0x7939938fd7a032f6, - 0x4341adf5a780b4a8, -]); +static GX1_MUL_PRIVATEKEY: Lazy = + Lazy::new(|| word!("0x4341adf5a780b4a87939938fd7a032f6e6664c7da553c121d3b4947429639122")); static GX1_MUL_PRIVATEKEY_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX1_MUL_PRIVATEKEY)); static GX1_MUL_PRIVATEKEY_LIMB64: Lazy<[U256; 4]> = Lazy::new(|| split_u256_limb64(&GX1_MUL_PRIVATEKEY)); // GX2 * PRIVATEKEY(mod N) = 0x4a43b192ca74cab200d6c086df90fb729abca9e52d38b8fa0beb4eafe70956de -const GX2_MUL_PRIVATEKEY: Word = U256([ - 0x0beb4eafe70956de, - 0x9abca9e52d38b8fa, - 0x00d6c086df90fb72, - 0x4a43b192ca74cab2, -]); +static GX2_MUL_PRIVATEKEY: Lazy = + Lazy::new(|| word!("0x4a43b192ca74cab200d6c086df90fb729abca9e52d38b8fa0beb4eafe70956de")); static GX2_MUL_PRIVATEKEY_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX2_MUL_PRIVATEKEY)); // In mul_add chip, we have a * b + c == d @@ -196,7 +171,7 @@ impl SignVerifyConfig { let is_equal_gx2 = IsEqualChip::configure( meta, |meta| meta.query_selector(q_check), - |meta| meta.query_advice(sign_rlc_acc, Rotation(64)), // SigR == GX2 + |meta| meta.query_advice(sign_rlc_acc, Rotation::cur()), // SigR == GX2 |_| gx2_rlc.expr(), ); @@ -234,7 +209,7 @@ impl SignVerifyConfig { meta.lookup_any("sign_r or msg_hash in tx_table", |meta| { let q_sign_end = meta.query_selector(q_sign_end); - let tx_id = ANCHOR_TX_ID.expr(); + let tx_id = super::ANCHOR_TX_ID.expr(); let tag = meta.query_fixed(tag, Rotation::cur()); let index = 0.expr(); let value = meta.query_advice(sign_rlc_acc, Rotation::cur()); @@ -383,6 +358,7 @@ impl SignVerifyConfig { self.q_u128_step.enable(region, row_offset)?; } } + *offset += 16; Ok(()) }; @@ -410,13 +386,13 @@ impl SignVerifyConfig { // setup selector if idx == 0 { self.q_sign_start.enable(region, row_offset)?; - if need_check { - self.q_check.enable(region, row_offset)?; - } } // the last offset of field if idx == 31 { self.q_sign_end.enable(region, row_offset)?; + if need_check { + // self.q_check.enable(region, row_offset)?; + } } else { self.q_sign_step.enable(region, row_offset)?; } @@ -427,7 +403,11 @@ impl SignVerifyConfig { fn load_mul_add(&self, region: &mut Region<'_, F>, msg_hash: Word) -> Result<(), Error> { let chip = MulAddChip::construct(self.mul_add.clone()); - chip.assign(region, 0, [GX1_MUL_PRIVATEKEY, U256::one(), msg_hash, N]) + chip.assign( + region, + 0, + [GX1_MUL_PRIVATEKEY.clone(), U256::one(), msg_hash, N.clone()], + ) } /// Return the minimum number of rows required to prove an input of a @@ -440,22 +420,17 @@ impl SignVerifyConfig { &self, layouter: &mut impl Layouter, anchor_tx: &Transaction, - chain_id: u64, challenges: &Challenges>, ) -> Result<(), Error> { layouter.assign_region( || "anchor sign verify", |ref mut region| { - let sign_data = anchor_tx.sign_data(chain_id).map_err(|e| { - error!("tx_to_sign_data error for tx {:?}", e); - Error::Synthesis - })?; - let msg_hash = U256::from_big_endian(sign_data.msg_hash.to_bytes().as_ref()); + let msg_hash = U256::from_big_endian(&anchor_tx.tx_sign_hash.to_fixed_bytes()); self.load_mul_add(region, msg_hash)?; let mut offset = 0; for (annotation, tag, need_check, value) in [ - ("msg_hash", TxFieldTag::TxSignHash, true, msg_hash), - ("sign_r", TxFieldTag::SigR, false, anchor_tx.r), + ("msg_hash", TxFieldTag::TxSignHash, false, msg_hash), + ("sign_r", TxFieldTag::SigR, true, anchor_tx.r), ] { self.assign_field( region, diff --git a/zkevm-circuits/src/anchor_tx_circuit/test.rs b/zkevm-circuits/src/anchor_tx_circuit/test.rs index 604c4a06bc..5ae213c343 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/test.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/test.rs @@ -1,6 +1,8 @@ #![allow(unused_imports)] +use std::collections::HashMap; + use super::{ - sign_verify::{GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_PRIVATEKEY, GOLDEN_TOUCH_WALLET}, + sign_verify::{GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_PRIVATEKEY, GOLDEN_TOUCH_WALLET, GX1, GX2}, *, }; use crate::{ @@ -11,13 +13,39 @@ use bus_mapping::{ circuit_input_builder::{CircuitInputBuilder, CircuitsParams}, mock::BlockData, }; -use eth_types::{address, geth_types::GethData, ToWord, Word}; +use eth_types::{ + address, bytecode, + geth_types::{GethData, Transaction}, + sign_types::{biguint_to_32bytes_le, ct_option_ok_or, sign, SignData, SECP256K1_Q}, + word, Address, Field, ToBigEndian, ToLittleEndian, ToWord, Word, U256, +}; +use ethers_core::types::TransactionRequest; +use ethers_signers::{LocalWallet, Signer}; +use gadgets::{ + is_equal::IsEqualChip, + mul_add::{MulAddChip, MulAddConfig}, + util::{split_u256, Expr}, +}; use halo2_proofs::{ + arithmetic::Field as _, + circuit::{Layouter, Region, Value}, dev::{MockProver, VerifyFailure}, - halo2curves::bn256::Fr, + halo2curves::{ + bn256::Fr, + ff::PrimeField, + group::Curve, + secp256k1::{self, Secp256k1Affine}, + }, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector}, + poly::Rotation, }; use itertools::Itertools; -use mock::{AddrOrWallet, TestContext}; +use log::error; +use mock::{AddrOrWallet, TestContext, MOCK_CHAIN_ID}; +use num::Integer; +use num_bigint::BigUint; +use once_cell::sync::Lazy; +use sha3::{Digest, Keccak256}; #[test] fn tx_circuit_unusable_rows() { @@ -27,31 +55,108 @@ fn tx_circuit_unusable_rows() { ) } +pub(crate) fn anchor_sign( + anchor_tx: &Transaction, + chain_id: u64, +) -> Result { + // msg = rlp([nonce, gasPrice, gas, to, value, data, sig_v, r, s]) + let req: TransactionRequest = anchor_tx.into(); + let msg = req.chain_id(chain_id).rlp(); + let msg_hash: [u8; 32] = Keccak256::digest(&msg) + .as_slice() + .to_vec() + .try_into() + .expect("hash length isn't 32 bytes"); + // msg_hash = msg_hash % q + let msg_hash = BigUint::from_bytes_be(msg_hash.as_slice()); + let msg_hash = msg_hash.mod_floor(&*SECP256K1_Q); + let msg_hash_le = biguint_to_32bytes_le(msg_hash); + let msg_hash = ct_option_ok_or( + secp256k1::Fq::from_repr(msg_hash_le), + libsecp256k1::Error::InvalidMessage, + )?; + let k1 = secp256k1::Fq::ONE; + let sk = ct_option_ok_or( + secp256k1::Fq::from_repr(GOLDEN_TOUCH_PRIVATEKEY.to_le_bytes()), + libsecp256k1::Error::InvalidSecretKey, + )?; + let generator = Secp256k1Affine::generator(); + let pk = generator * sk; + let pk = pk.to_affine(); + let (mut sig_r, mut sig_s) = sign(k1, sk, msg_hash); + let gx1 = ct_option_ok_or( + secp256k1::Fq::from_repr(GX1.to_le_bytes()), + libsecp256k1::Error::InvalidSignature, + )?; + assert!(sig_r == gx1); + if sig_s == secp256k1::Fq::ZERO { + let k2 = secp256k1::Fq::ONE + secp256k1::Fq::ONE; + (sig_r, sig_s) = sign(k2, sk, msg_hash); + let gx2 = ct_option_ok_or( + secp256k1::Fq::from_repr(GX2.to_le_bytes()), + libsecp256k1::Error::InvalidSignature, + )?; + assert!(sig_r == gx2); + } + Ok(SignData { + signature: (sig_r, sig_s), + pk, + msg_hash, + }) +} + fn run(block: &Block) -> Result<(), Vec> { - let k = - log2_ceil(AnchorTxCircuit::::unusable_rows() + AnchorTxCircuit::::min_num_rows()); + let k = log2_ceil( + AnchorTxCircuit::::unusable_rows() + + AnchorTxCircuit::::min_num_rows(block.circuits_params.max_txs), + ); let circuit = TestAnchorTxCircuit::::new_from_block(block); - let prover = match MockProver::run(k, &circuit, vec![vec![]]) { + let prover = match MockProver::run(k + 1, &circuit, vec![]) { Ok(prover) => prover, Err(e) => panic!("{:#?}", e), }; prover.verify() } -const GAS_LIMIT: u64 = 1500000; - fn gen_block(max_txs: usize, max_calldata: usize, taiko: Taiko) -> Block { - let block: GethData = TestContext::<1, NUM_TXS>::new( + let chain_id = (*MOCK_CHAIN_ID).as_u64(); + let mut wallets = HashMap::new(); + wallets.insert( + GOLDEN_TOUCH_ADDRESS.clone(), + GOLDEN_TOUCH_WALLET.clone().with_chain_id(chain_id), + ); + + let code = bytecode! { + PUSH1(0x01) // value + PUSH1(0x02) // key + SSTORE + + PUSH3(0xbb) + }; + + let block: GethData = TestContext::<2, NUM_TXS>::new( None, - |_accs| {}, + |accs| { + accs[0] + .address(GOLDEN_TOUCH_ADDRESS.clone()) + .balance(Word::from(1u64 << 20)); + accs[1].address(taiko.l2_contract).code(code); + }, |mut txs, _accs| { txs[0] - .gas(GAS_LIMIT.to_word()) + .gas(taiko.anchor_gas_cost.to_word()) .gas_price(ANCHOR_TX_GAS_PRICE.to_word()) - .from(GOLDEN_TOUCH_WALLET.clone()) + .from(GOLDEN_TOUCH_ADDRESS.clone()) .to(taiko.l2_contract) + .input(taiko.anchor_call()) + .nonce(0) .value(ANCHOR_TX_VALUE.to_word()); + let tx: Transaction = txs[0].to_owned().into(); + let sig_data = anchor_sign(&tx, chain_id).unwrap(); + let sig_r = U256::from_little_endian(sig_data.signature.0.to_bytes().as_slice()); + let sig_s = U256::from_little_endian(sig_data.signature.1.to_bytes().as_slice()); + txs[0].sig_data((2712, sig_r, sig_s)); }, |block, _tx| block, ) @@ -74,6 +179,8 @@ fn gen_block(max_txs: usize, max_calldata: usize, taiko: T #[test] fn test() { - let block = gen_block::<2>(2, 100, Taiko::default()); + let mut taiko = Taiko::default(); + taiko.anchor_gas_cost = 150000; + let block = gen_block::<1>(2, 100, taiko); assert_eq!(run::(&block), Ok(())); } diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index 2a20e42e34..894beaedb7 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -30,6 +30,14 @@ pub(crate) fn random_linear_combine_word(bytes: [u8; 32], randomness: rlc::value(&bytes, randomness) } +pub(crate) fn rlc_be_bytes(bytes: &[u8], rand: Value) -> Value { + rand.map(|rand| { + bytes + .iter() + .fold(F::ZERO, |acc, byte| acc * rand + F::from(*byte as u64)) + }) +} + /// All challenges used in `SuperCircuit`. #[derive(Default, Clone, Copy, Debug)] pub struct Challenges { diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index 37cad6aac2..e9fef72bc6 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -233,7 +233,7 @@ pub fn block_convert( .txs() .iter() .enumerate() - .map(|(idx, tx)| tx_convert(tx, idx + 1)) + .map(|(idx, tx)| tx_convert(tx, block.chain_id.as_u64(), idx + 1)) .collect(), end_block_not_last: block.block_steps.end_block_not_last.clone(), end_block_last: block.block_steps.end_block_last.clone(), diff --git a/zkevm-circuits/src/witness/taiko.rs b/zkevm-circuits/src/witness/taiko.rs index 036af91a52..a81bd141a1 100644 --- a/zkevm-circuits/src/witness/taiko.rs +++ b/zkevm-circuits/src/witness/taiko.rs @@ -2,11 +2,12 @@ use std::iter; -use crate::{evm_circuit::util::rlc, table::PiFieldTag}; -use eth_types::{Address, Field, Hash, ToBigEndian, ToLittleEndian, ToWord, Word, H256}; +use crate::{evm_circuit::util::rlc, table::PiFieldTag, util::rlc_be_bytes}; +use eth_types::{Address, Bytes, Field, Hash, ToBigEndian, ToLittleEndian, ToWord, Word, H256}; use halo2_proofs::circuit::Value; use keccak256::plain::Keccak; +// hash(anchor) const ANCHOR_TX_METHOD_SIGNATURE: u32 = 0x3d384a4b; /// Taiko witness @@ -117,6 +118,18 @@ impl MetaHash { } impl Taiko { + /// gen anchor call + // anchor(l1_hash,signal_root,l1_height,parent_gas_used) + pub fn anchor_call(&self) -> Bytes { + let mut result = Vec::new(); + result.extend_from_slice(&ANCHOR_TX_METHOD_SIGNATURE.to_be_bytes()); + result.extend_from_slice(&self.meta_hash.l1_hash.to_fixed_bytes()); + result.extend_from_slice(&self.signal_root.to_fixed_bytes()); + result.extend_from_slice(&self.meta_hash.l1_height.to_be_bytes()); + result.extend_from_slice(&(self.parent_gas_used as u64).to_be_bytes()); + result.into() + } + /// Assignments for pi table pub fn table_assignments(&self, randomness: Value) -> [[Value; 2]; 6] { [ @@ -130,15 +143,11 @@ impl Taiko { ], [ Value::known(F::from(PiFieldTag::L1Hash as u64)), - randomness.map(|randomness| { - rlc::value(&self.meta_hash.l1_hash.to_word().to_le_bytes(), randomness) - }), + rlc_be_bytes(&self.meta_hash.l1_hash.to_fixed_bytes(), randomness), ], [ Value::known(F::from(PiFieldTag::L1SignalRoot as u64)), - randomness.map(|randomness| { - rlc::value(&self.signal_root.to_word().to_le_bytes(), randomness) - }), + rlc_be_bytes(&self.signal_root.to_fixed_bytes(), randomness), ], [ Value::known(F::from(PiFieldTag::L1Height as u64)), diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 38be201d3f..4e60685823 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -1,8 +1,14 @@ use bus_mapping::circuit_input_builder; -use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; +use eth_types::{ + sign_types::SignData, Address, Field, ToBigEndian, ToLittleEndian, ToScalar, Word, H256, U256, +}; use halo2_proofs::circuit::Value; -use crate::{evm_circuit::util::rlc, table::TxContextFieldTag, util::Challenges}; +use crate::{ + evm_circuit::util::rlc, + table::TxContextFieldTag, + util::{rlc_be_bytes, Challenges}, +}; use super::{Call, ExecStep}; @@ -35,6 +41,14 @@ pub struct Transaction { pub calls: Vec, /// The steps executioned in the transaction pub steps: Vec, + /// "v" value of the transaction signature + pub v: u64, + /// "r" value of the transaction signature + pub r: Word, + /// "s" value of the transaction signature + pub s: Word, + /// tx sign hash + pub tx_sign_hash: H256, } impl Transaction { @@ -103,6 +117,30 @@ impl Transaction { Value::known(F::ZERO), Value::known(F::from(self.call_data_gas_cost)), ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::TxSignHash as u64)), + Value::known(F::ZERO), + rlc_be_bytes(&self.tx_sign_hash.to_fixed_bytes(), challenges.evm_word()), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::SigV as u64)), + Value::known(F::ZERO), + Value::known(F::from(self.v)), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::SigR as u64)), + Value::known(F::ZERO), + rlc_be_bytes(&self.r.to_be_bytes(), challenges.evm_word()), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::SigS as u64)), + Value::known(F::ZERO), + rlc_be_bytes(&self.s.to_be_bytes(), challenges.evm_word()), + ], ]; let tx_calldata = self .call_data @@ -121,7 +159,16 @@ impl Transaction { } } -pub(super) fn tx_convert(tx: &circuit_input_builder::Transaction, id: usize) -> Transaction { +pub(super) fn tx_convert( + tx: &circuit_input_builder::Transaction, + chain_id: u64, + id: usize, +) -> Transaction { + let sign_data: SignData = tx + .tx + .sign_data(chain_id) + .expect("Error computing tx_sign_hash"); + let tx_sign_hash = H256::from(&sign_data.msg_hash.to_bytes()); Transaction { id, nonce: tx.tx.nonce.as_u64(), @@ -136,5 +183,9 @@ pub(super) fn tx_convert(tx: &circuit_input_builder::Transaction, id: usize) -> call_data_gas_cost: tx.tx.call_data_gas_cost(), calls: tx.calls().to_vec(), steps: tx.steps().to_vec(), + v: tx.tx.v, + r: tx.tx.r, + s: tx.tx.s, + tx_sign_hash, } } From fd9105f8ee943aa86a1fdb212250d3819bae57b4 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 19 Jun 2023 21:26:28 +0800 Subject: [PATCH 11/20] fix: query columns cross different regions --- gadgets/src/is_equal.rs | 4 +- zkevm-circuits/src/anchor_tx_circuit.rs | 169 +++++++++++++++--- zkevm-circuits/src/anchor_tx_circuit/dev.rs | 7 - .../src/anchor_tx_circuit/sign_verify.rs | 28 +-- zkevm-circuits/src/anchor_tx_circuit/test.rs | 2 +- zkevm-circuits/src/witness/taiko.rs | 4 +- zkevm-circuits/src/witness/tx.rs | 2 +- 7 files changed, 165 insertions(+), 51 deletions(-) diff --git a/gadgets/src/is_equal.rs b/gadgets/src/is_equal.rs index 8a4a16007b..b7faac3427 100644 --- a/gadgets/src/is_equal.rs +++ b/gadgets/src/is_equal.rs @@ -3,7 +3,7 @@ use eth_types::Field; use halo2_proofs::{ circuit::{Chip, Region, Value}, - plonk::{ConstraintSystem, Error, Expression, VirtualCells}, + plonk::{ConstraintSystem, Error, Expression, SecondPhase, VirtualCells}, }; use super::is_zero::{IsZeroChip, IsZeroInstruction}; @@ -45,7 +45,7 @@ impl IsEqualChip { rhs: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, ) -> IsEqualConfig { let value = |meta: &mut VirtualCells| lhs(meta) - rhs(meta); - let value_inv = meta.advice_column(); + let value_inv = meta.advice_column_in(SecondPhase); let is_zero_config = IsZeroChip::configure(meta, q_enable, value, value_inv); let is_equal_expression = is_zero_config.is_zero_expression.clone(); diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index aa5c658d2e..2279c4c3a6 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -10,7 +10,7 @@ mod test; use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, - table::{LookupTable, PiFieldTag, PiTable, TxFieldTag, TxTable}, + table::{LookupTable, PiFieldTag, PiTable, TxContextFieldTag, TxFieldTag, TxTable}, tx_circuit::TX_LEN, util::{Challenges, SubCircuit, SubCircuitConfig}, witness::{self, Taiko, Transaction}, @@ -19,10 +19,7 @@ use eth_types::{Field, ToScalar}; use gadgets::util::{select, Expr}; use halo2_proofs::{ circuit::{Layouter, Region, Value}, - plonk::{ - Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector, - ThirdPhase, - }, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, SecondPhase, Selector}, poly::Rotation, }; use sign_verify::SignVerifyConfig; @@ -106,7 +103,7 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { // anchor transaction constants meta.lookup_any("anchor fixed fields", |meta| { - let q_anchor = meta.query_selector(q_tag); + let q_tag = meta.query_selector(q_tag); [ ANCHOR_TX_ID.expr(), meta.query_fixed(tag, Rotation::cur()), @@ -115,7 +112,7 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { ] .into_iter() .zip(tx_table.table_exprs(meta).into_iter()) - .map(|(arg, table)| (q_anchor.expr() * arg, table)) + .map(|(arg, table)| (q_tag.expr() * arg, table)) .collect() }); @@ -130,7 +127,7 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); let call_data_next = meta.query_advice(tx_table.value, Rotation::next()); let use_rlc = meta.query_fixed(use_rlc, Rotation::cur()); - let randomness = challenges.evm_word(); + let randomness = challenges.lookup_input(); let t = select::expr(use_rlc, randomness, BYTE_POW_BASE.expr()); cb.require_equal( "call_data_rlc_acc[i+1] = call_data_rlc_acc[i] * t + call_data[i+1]", @@ -249,23 +246,6 @@ impl AnchorTxCircuitConfig { challenges: &Challenges>, ) -> Result<(), Error> { let mut offset = call_data.start; - // for idx in 0..offset { - // // fill zero - // region.assign_advice( - // || "zero value", - // self.call_data_rlc_acc, - // idx, - // || Value::known(F::ZERO), - // )?; - // region.assign_fixed( - // || "zero value", - // self.call_data_tag, - // idx, - // || Value::known(F::ZERO), - // )?; - // region.assign_fixed(|| "zero value", self.use_rlc, idx, || Value::known(F::ZERO))?; - // } - for (annotation, value, tag) in [ ( "method_signature", @@ -327,10 +307,127 @@ impl AnchorTxCircuitConfig { Ok(()) } + fn load_tx_table( + &self, + region: &mut Region<'_, F>, + txs: &[Transaction], + max_txs: usize, + max_calldata: usize, + challenges: &Challenges>, + ) -> Result<(), Error> { + assert!( + txs.len() <= max_txs, + "txs.len() <= max_txs: txs.len()={}, max_txs={}", + txs.len(), + max_txs + ); + let sum_txs_calldata = txs.iter().map(|tx| tx.call_data.len()).sum(); + assert!( + sum_txs_calldata <= max_calldata, + "sum_txs_calldata <= max_calldata: sum_txs_calldata={}, max_calldata={}", + sum_txs_calldata, + max_calldata, + ); + + fn assign_row( + region: &mut Region<'_, F>, + offset: usize, + advice_columns: &[Column], + tag: &Column, + row: &[Value; 4], + msg: &str, + ) -> Result<(), Error> { + for (index, column) in advice_columns.iter().enumerate() { + region.assign_advice( + || format!("tx table {} row {}", msg, offset), + *column, + offset, + || row[if index > 0 { index + 1 } else { index }], + )?; + } + region.assign_fixed( + || format!("tx table {} row {}", msg, offset), + *tag, + offset, + || row[1], + )?; + Ok(()) + } + + let mut offset = 0; + let advice_columns = [ + self.tx_table.tx_id, + self.tx_table.index, + self.tx_table.value, + ]; + assign_row( + region, + offset, + &advice_columns, + &self.tx_table.tag, + &[(); 4].map(|_| Value::known(F::ZERO)), + "all-zero", + )?; + offset += 1; + + // Tx Table contains an initial region that has a size parametrized by max_txs + // with all the tx data except for calldata, and then a second + // region that has a size parametrized by max_calldata with all + // the tx calldata. This is required to achieve a constant fixed column tag + // regardless of the number of input txs or the calldata size of each tx. + let mut calldata_assignments: Vec<[Value; 4]> = Vec::new(); + // Assign Tx data (all tx fields except for calldata) + let padding_txs: Vec<_> = (txs.len()..max_txs) + .map(|i| Transaction { + id: i + 1, + ..Default::default() + }) + .collect(); + for tx in txs.iter().chain(padding_txs.iter()) { + let [tx_data, tx_calldata] = tx.table_assignments(*challenges); + for row in tx_data { + assign_row( + region, + offset, + &advice_columns, + &self.tx_table.tag, + &row, + "", + )?; + offset += 1; + } + calldata_assignments.extend(tx_calldata.iter()); + } + // Assign Tx calldata + let padding_calldata = (sum_txs_calldata..max_calldata).map(|_| { + [ + Value::known(F::ZERO), + Value::known(F::from(TxContextFieldTag::CallData as u64)), + Value::known(F::ZERO), + Value::known(F::ZERO), + ] + }); + for row in calldata_assignments.into_iter().chain(padding_calldata) { + assign_row( + region, + offset, + &advice_columns, + &self.tx_table.tag, + &row, + "", + )?; + offset += 1; + } + Ok(()) + } + fn assign( &self, layouter: &mut impl Layouter, anchor_tx: &Transaction, + txs: &[Transaction], + max_txs: usize, + max_calldata: usize, taiko: &Taiko, call_data: &CallData, challenges: &Challenges>, @@ -339,6 +436,7 @@ impl AnchorTxCircuitConfig { layouter.assign_region( || "anchor transaction", |ref mut region| { + self.load_tx_table(region, txs, max_txs, max_calldata, challenges)?; self.assign_anchor_tx(region, anchor_tx, taiko, challenges)?; self.assign_call_data(region, anchor_tx, call_data, challenges)?; Ok(()) @@ -351,17 +449,27 @@ impl AnchorTxCircuitConfig { #[derive(Clone, Default, Debug)] pub struct AnchorTxCircuit { max_txs: usize, + max_calldata: usize, anchor_tx: Transaction, + txs: Vec, taiko: Taiko, _marker: PhantomData, } impl AnchorTxCircuit { /// Return a new TxCircuit - pub fn new(max_txs: usize, anchor_tx: Transaction, taiko: Taiko) -> Self { + pub fn new( + max_txs: usize, + max_calldata: usize, + anchor_tx: Transaction, + txs: Vec, + taiko: Taiko, + ) -> Self { AnchorTxCircuit { max_txs, + max_calldata, anchor_tx, + txs, taiko, _marker: PhantomData, } @@ -387,15 +495,17 @@ impl SubCircuit for AnchorTxCircuit { type Config = AnchorTxCircuitConfig; fn unusable_rows() -> usize { - // No column queried at more than 2 distinct rotations, so returns 5 as + // No column queried at more than 3 distinct rotations, so returns 5 as // minimum unusable row. - 5 + 6 } fn new_from_block(block: &witness::Block) -> Self { Self::new( block.circuits_params.max_txs, + block.circuits_params.max_calldata, block.txs.iter().next().unwrap().clone(), + block.txs.clone(), block.taiko.clone(), ) } @@ -415,6 +525,9 @@ impl SubCircuit for AnchorTxCircuit { config.assign( layouter, &self.anchor_tx, + &self.txs, + self.max_txs, + self.max_calldata, &self.taiko, &call_data, challenges, diff --git a/zkevm-circuits/src/anchor_tx_circuit/dev.rs b/zkevm-circuits/src/anchor_tx_circuit/dev.rs index 3c620bd144..d71314a07e 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/dev.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/dev.rs @@ -67,13 +67,6 @@ impl Circuit for TestAnchorTxCircuit { mut layouter: impl Layouter, ) -> Result<(), Error> { let challenges = challenges.values(&mut layouter); - config.tx_table.load( - &mut layouter, - &self.txs[..], - self.circuit.max_txs, - self.max_calldata, - &challenges, - )?; config .pi_table .load(&mut layouter, &self.taiko, &challenges)?; diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index beab9b2e2c..69d39a7ebd 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -46,10 +46,10 @@ use crate::{ util::Challenges, witness::Transaction, }; -use eth_types::{address, word, Address, Field, ToBigEndian, Word, U256}; +use eth_types::{address, word, Address, Field, ToBigEndian, ToLittleEndian, Word, U256}; use ethers_signers::LocalWallet; use gadgets::{ - is_equal::IsEqualChip, + is_equal::{IsEqualChip, IsEqualConfig, IsEqualInstruction}, mul_add::{MulAddChip, MulAddConfig}, util::{split_u256, Expr}, }; @@ -132,6 +132,7 @@ pub(crate) struct SignVerifyConfig { q_check: Selector, mul_add: MulAddConfig, + is_equal_gx2: IsEqualConfig, } impl SignVerifyConfig { @@ -156,14 +157,14 @@ impl SignVerifyConfig { let mul_add = MulAddChip::configure(meta, |meta| meta.query_selector(q_check)); let gx1_rlc = crate::evm_circuit::util::rlc::expr( - GX1.to_be_bytes() + GX1.to_le_bytes() .map(|v| Expression::Constant(F::from(v as u64))) .as_ref(), challenges.evm_word(), ); let gx2_rlc = crate::evm_circuit::util::rlc::expr( - GX2.to_be_bytes() + GX2.to_le_bytes() .map(|v| Expression::Constant(F::from(v as u64))) .as_ref(), challenges.evm_word(), @@ -171,7 +172,7 @@ impl SignVerifyConfig { let is_equal_gx2 = IsEqualChip::configure( meta, |meta| meta.query_selector(q_check), - |meta| meta.query_advice(sign_rlc_acc, Rotation::cur()), // SigR == GX2 + |meta| meta.query_advice(sign_rlc_acc, Rotation(63)), // SigR == GX2 |_| gx2_rlc.expr(), ); @@ -259,11 +260,11 @@ impl SignVerifyConfig { let q_check = meta.query_selector(q_check); - let sign_rlc_acc = meta.query_advice(sign_rlc_acc, Rotation(64)); + let sign_rlc_acc = meta.query_advice(sign_rlc_acc, Rotation(63)); cb.require_in_set("r in (GX1, GX2)", sign_rlc_acc, vec![gx1_rlc, gx2_rlc]); - cb.condition(is_equal_gx2.is_equal_expression, |cb| { + cb.condition(is_equal_gx2.is_equal_expression.expr(), |cb| { // a == GX1_MUL_PRIVATEKEY let (a_limb0, a_limb1, a_limb2, a_limb3) = mul_add.a_limbs_cur(meta); let a_limb = GX1_MUL_PRIVATEKEY_LIMB64 @@ -318,6 +319,7 @@ impl SignVerifyConfig { q_check, mul_add, + is_equal_gx2, } } @@ -390,13 +392,17 @@ impl SignVerifyConfig { // the last offset of field if idx == 31 { self.q_sign_end.enable(region, row_offset)?; - if need_check { - // self.q_check.enable(region, row_offset)?; - } } else { self.q_sign_step.enable(region, row_offset)?; } } + if need_check { + let gx2_rlc = randomness.map(|randomness| { + crate::evm_circuit::util::rlc::value(&GX2.to_le_bytes(), randomness) + }); + let chip = IsEqualChip::construct(self.is_equal_gx2.clone()); + chip.assign(region, 0, rlc_acc, gx2_rlc)?; + } *offset += 32; Ok(()) } @@ -425,6 +431,8 @@ impl SignVerifyConfig { layouter.assign_region( || "anchor sign verify", |ref mut region| { + self.q_check.enable(region, 0)?; + let msg_hash = U256::from_big_endian(&anchor_tx.tx_sign_hash.to_fixed_bytes()); self.load_mul_add(region, msg_hash)?; let mut offset = 0; diff --git a/zkevm-circuits/src/anchor_tx_circuit/test.rs b/zkevm-circuits/src/anchor_tx_circuit/test.rs index 5ae213c343..9b7992e260 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/test.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/test.rs @@ -112,7 +112,7 @@ fn run(block: &Block) -> Result<(), Vec> { ); let circuit = TestAnchorTxCircuit::::new_from_block(block); - let prover = match MockProver::run(k + 1, &circuit, vec![]) { + let prover = match MockProver::run(k + 3, &circuit, vec![]) { Ok(prover) => prover, Err(e) => panic!("{:#?}", e), }; diff --git a/zkevm-circuits/src/witness/taiko.rs b/zkevm-circuits/src/witness/taiko.rs index a81bd141a1..e97d152930 100644 --- a/zkevm-circuits/src/witness/taiko.rs +++ b/zkevm-circuits/src/witness/taiko.rs @@ -2,8 +2,8 @@ use std::iter; -use crate::{evm_circuit::util::rlc, table::PiFieldTag, util::rlc_be_bytes}; -use eth_types::{Address, Bytes, Field, Hash, ToBigEndian, ToLittleEndian, ToWord, Word, H256}; +use crate::{table::PiFieldTag, util::rlc_be_bytes}; +use eth_types::{Address, Bytes, Field, Hash, ToBigEndian, ToWord, Word, H256}; use halo2_proofs::circuit::Value; use keccak256::plain::Keccak; diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 4e60685823..1d3132d7a6 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -1,6 +1,6 @@ use bus_mapping::circuit_input_builder; use eth_types::{ - sign_types::SignData, Address, Field, ToBigEndian, ToLittleEndian, ToScalar, Word, H256, U256, + sign_types::SignData, Address, Field, ToBigEndian, ToLittleEndian, ToScalar, Word, H256, }; use halo2_proofs::circuit::Value; From 1ed28913be837bdecc60de585b31b4d8dda15136 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Mon, 19 Jun 2023 23:17:46 +0800 Subject: [PATCH 12/20] fix: sign verify test case --- .../src/anchor_tx_circuit/sign_verify.rs | 206 +++++++++--------- 1 file changed, 106 insertions(+), 100 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 69d39a7ebd..b7b7836415 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -125,10 +125,10 @@ pub(crate) struct SignVerifyConfig { sign: Column, sign_rlc_acc: Column, // split u256 to low and high - q_u128_start: Selector, - q_u128_step: Selector, - q_u128_end: Selector, - sign_u128_acc: Column, + q_u64_start: Selector, + q_u64_step: Selector, + q_u64_end: Selector, + sign_u64_acc: Column, q_check: Selector, mul_add: MulAddConfig, @@ -148,13 +148,10 @@ impl SignVerifyConfig { let sign = meta.advice_column(); let sign_rlc_acc = meta.advice_column_in(SecondPhase); - let q_u128_start = meta.complex_selector(); - let q_u128_step = meta.complex_selector(); - let q_u128_end = meta.complex_selector(); - let sign_u128_acc = meta.advice_column(); - - let q_check = meta.complex_selector(); - let mul_add = MulAddChip::configure(meta, |meta| meta.query_selector(q_check)); + let q_u64_start = meta.complex_selector(); + let q_u64_step = meta.complex_selector(); + let q_u64_end = meta.complex_selector(); + let sign_u64_acc = meta.advice_column(); let gx1_rlc = crate::evm_circuit::util::rlc::expr( GX1.to_le_bytes() @@ -169,6 +166,7 @@ impl SignVerifyConfig { .as_ref(), challenges.evm_word(), ); + let q_check = meta.complex_selector(); let is_equal_gx2 = IsEqualChip::configure( meta, |meta| meta.query_selector(q_check), @@ -176,6 +174,10 @@ impl SignVerifyConfig { |_| gx2_rlc.expr(), ); + let mul_add = MulAddChip::configure(meta, |meta| { + is_equal_gx2.is_equal_expression.expr() * meta.query_selector(q_check) + }); + // signature rlc meta.create_gate( "sign_rlc_acc[i+1] = sign_rlc_acc[i] * randomness + sign[i+1]", @@ -222,39 +224,39 @@ impl SignVerifyConfig { .collect::>() }); - // signature u128 + // signature u64 meta.create_gate( - "sign_u128_acc[i+1] = sign_u128_acc[i] * BYTE_POW_BASE + sign[i+1]", + "sign_u64_acc[i+1] = sign_u64_acc[i] * BYTE_POW_BASE + sign[i+1]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - let q_u128_step = meta.query_selector(q_u128_step); - let sign_u128_acc_next = meta.query_advice(sign_u128_acc, Rotation::next()); - let sign_u128_acc = meta.query_advice(sign_u128_acc, Rotation::cur()); + let q_u64_step = meta.query_selector(q_u64_step); + let sign_u64_acc_next = meta.query_advice(sign_u64_acc, Rotation::next()); + let sign_u64_acc = meta.query_advice(sign_u64_acc, Rotation::cur()); let sign_next = meta.query_advice(sign, Rotation::next()); cb.require_equal( - "sign_u128_acc[i+1] = sign_u128_acc[i] * BYTE_POW_BASE + sign[i+1]", - sign_u128_acc_next, - sign_u128_acc * BYTE_POW_BASE.expr() + sign_next, + "sign_u64_acc[i+1] = sign_u64_acc[i] * BYTE_POW_BASE + sign[i+1]", + sign_u64_acc_next, + sign_u64_acc * BYTE_POW_BASE.expr() + sign_next, ); - cb.gate(q_u128_step) + cb.gate(q_u64_step) }, ); - meta.create_gate("sign_u128_acc[start] = sign[start]", |meta| { + meta.create_gate("sign_u64_acc[start] = sign[start]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - let q_u128_start = meta.query_selector(q_u128_start); - let sign_u128_acc = meta.query_advice(sign_u128_acc, Rotation::cur()); + let q_u64_start = meta.query_selector(q_u64_start); + let sign_u64_acc = meta.query_advice(sign_u64_acc, Rotation::cur()); let sign = meta.query_advice(sign, Rotation::cur()); - cb.require_equal("sign_u128_acc[start] = sign[start]", sign_u128_acc, sign); - cb.gate(q_u128_start) + cb.require_equal("sign_u64_acc[start] = sign[start]", sign_u64_acc, sign); + cb.gate(q_u64_start) }); // check SigR meta.create_gate( - "IF r == GX2 THEN a(GX1_MUL_PRIVATEKEY) * b(1) + c(msg_hash) == d(N)", + "IF r == GX2 THEN a(msg_hash) * b(1) + c(GX1_MUL_PRIVATEKEY) == d(N)", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); @@ -264,40 +266,45 @@ impl SignVerifyConfig { cb.require_in_set("r in (GX1, GX2)", sign_rlc_acc, vec![gx1_rlc, gx2_rlc]); - cb.condition(is_equal_gx2.is_equal_expression.expr(), |cb| { - // a == GX1_MUL_PRIVATEKEY - let (a_limb0, a_limb1, a_limb2, a_limb3) = mul_add.a_limbs_cur(meta); - let a_limb = GX1_MUL_PRIVATEKEY_LIMB64 - .map(|v| Expression::Constant(F::from(v.as_u64()))); - cb.require_equal("a_limb0", a_limb0, a_limb[0].expr()); - cb.require_equal("a_limb1", a_limb1, a_limb[1].expr()); - cb.require_equal("a_limb2", a_limb2, a_limb[2].expr()); - cb.require_equal("a_limb3", a_limb3, a_limb[3].expr()); - - // b == 1 - let (b_limb0, b_limb1, b_limb2, b_limb3) = mul_add.b_limbs_cur(meta); - let b_limb = split_u256_limb64(&U256::one()) - .map(|v| Expression::Constant(F::from(v.as_u64()))); - cb.require_equal("b_limb0", b_limb0, b_limb[0].expr()); - cb.require_equal("b_limb1", b_limb1, b_limb[1].expr()); - cb.require_equal("b_limb2", b_limb2, b_limb[2].expr()); - cb.require_equal("b_limb3", b_limb3, b_limb[3].expr()); - - // c == msg_hash - let c_lo_hi0 = meta.query_advice(sign_u128_acc, Rotation(16)); - let c_lo_hi1 = meta.query_advice(sign_u128_acc, Rotation(32)); - let (c_lo_cur, c_hi_cur) = mul_add.c_lo_hi_cur(meta); - cb.require_equal("c_lo_cur", c_lo_hi0, c_lo_cur); - cb.require_equal("c_hi_cur", c_lo_hi1, c_hi_cur); - - // d == N - let (d_lo_cur, d_hi_cur) = mul_add.c_lo_hi_cur(meta); - let d_lo_cur_expr = Expression::Constant(F::from_u128(N_LO_HI.0.as_u128())); - let d_hi_cur_expr = Expression::Constant(F::from_u128(N_LO_HI.1.as_u128())); - - cb.require_equal("d_lo_cur", d_lo_cur_expr, d_lo_cur); - cb.require_equal("d_hi_cur", d_hi_cur_expr, d_hi_cur); - }); + // a == msg_hash + let (a_limbs_cur0, a_limbs_cur1, a_limbs_cur2, a_limbs_cur3) = + mul_add.a_limbs_cur(meta); + + // c == msg_hash + let a_limb0 = meta.query_advice(sign_u64_acc, Rotation(31)); + let a_limb1 = meta.query_advice(sign_u64_acc, Rotation(23)); + let a_limb2 = meta.query_advice(sign_u64_acc, Rotation(15)); + let a_limb3 = meta.query_advice(sign_u64_acc, Rotation(7)); + cb.require_equal("a_limb0", a_limbs_cur0, a_limb0); + cb.require_equal("a_limb1", a_limbs_cur1, a_limb1); + cb.require_equal("a_limb2", a_limbs_cur2, a_limb2); + cb.require_equal("a_limb3", a_limbs_cur3, a_limb3); + + // b == 1 + let (b_limb0, b_limb1, b_limb2, b_limb3) = mul_add.b_limbs_cur(meta); + let b_limb = split_u256_limb64(&U256::one()) + .map(|v| Expression::Constant(F::from(v.as_u64()))); + cb.require_equal("b_limb0", b_limb0, b_limb[0].expr()); + cb.require_equal("b_limb1", b_limb1, b_limb[1].expr()); + cb.require_equal("b_limb2", b_limb2, b_limb[2].expr()); + cb.require_equal("b_limb3", b_limb3, b_limb[3].expr()); + + // c == GX1_MUL_PRIVATEKEY + let c_lo_hi0 = + Expression::Constant(F::from_u128(GX1_MUL_PRIVATEKEY_LO_HI.0.as_u128())); + let c_lo_hi1 = + Expression::Constant(F::from_u128(GX1_MUL_PRIVATEKEY_LO_HI.1.as_u128())); + let (c_lo_cur, c_hi_cur) = mul_add.c_lo_hi_cur(meta); + cb.require_equal("c_lo_cur", c_lo_hi0, c_lo_cur); + cb.require_equal("c_hi_cur", c_lo_hi1, c_hi_cur); + + // d == N + let (d_lo_cur, d_hi_cur) = mul_add.d_lo_hi_cur(meta); + let d_lo_cur_expr = Expression::Constant(F::from_u128(N_LO_HI.0.as_u128())); + let d_hi_cur_expr = Expression::Constant(F::from_u128(N_LO_HI.1.as_u128())); + + cb.require_equal("d_lo_cur", d_lo_cur_expr, d_lo_cur); + cb.require_equal("d_hi_cur", d_hi_cur_expr, d_hi_cur); cb.gate(q_check) }, ); @@ -312,10 +319,10 @@ impl SignVerifyConfig { sign, sign_rlc_acc, - q_u128_start, - q_u128_step, - q_u128_end, - sign_u128_acc, + q_u64_start, + q_u64_step, + q_u64_end, + sign_u64_acc, q_check, mul_add, @@ -331,42 +338,43 @@ impl SignVerifyConfig { offset: &mut usize, tag: TxFieldTag, value: [u8; 32], - need_check: bool, challenges: &Challenges>, - ) -> Result<(), Error> { + ) -> Result, Error> { let mut rlc_acc = Value::known(F::ZERO); let randomness = challenges.evm_word(); - let mut assign_u128 = |offset: &mut usize, value: &[u8]| -> Result<(), Error> { - let mut u128_acc = Value::known(F::ZERO); + let mut assign_u64 = |offset: &mut usize, value: &[u8]| -> Result<(), Error> { + let mut u64_acc = Value::known(F::ZERO); for (idx, byte) in value.iter().enumerate() { let row_offset = *offset + idx; - u128_acc = u128_acc * Value::known(F::from(BYTE_POW_BASE)) + u64_acc = u64_acc * Value::known(F::from(BYTE_POW_BASE)) + Value::known(F::from(*byte as u64)); region.assign_advice( - || "sign_u128_acc", - self.sign_u128_acc, + || "sign_u64_acc", + self.sign_u64_acc, row_offset, - || u128_acc, + || u64_acc, )?; // setup selector if idx == 0 { - self.q_u128_start.enable(region, row_offset)?; + self.q_u64_start.enable(region, row_offset)?; } // the last offset of field - if idx == 15 { - self.q_u128_end.enable(region, row_offset)?; + if idx == 7 { + self.q_u64_end.enable(region, row_offset)?; } else { - self.q_u128_step.enable(region, row_offset)?; + self.q_u64_step.enable(region, row_offset)?; } } - *offset += 16; + *offset += 8; Ok(()) }; - let mut assign_u128_offset = *offset; - assign_u128(&mut assign_u128_offset, &value[..16])?; - assign_u128(&mut assign_u128_offset, &value[16..])?; + let mut assign_u64_offset = *offset; + assign_u64(&mut assign_u64_offset, &value[..8])?; + assign_u64(&mut assign_u64_offset, &value[8..16])?; + assign_u64(&mut assign_u64_offset, &value[16..24])?; + assign_u64(&mut assign_u64_offset, &value[24..])?; for (idx, byte) in value.iter().enumerate() { let row_offset = *offset + idx; @@ -396,15 +404,8 @@ impl SignVerifyConfig { self.q_sign_step.enable(region, row_offset)?; } } - if need_check { - let gx2_rlc = randomness.map(|randomness| { - crate::evm_circuit::util::rlc::value(&GX2.to_le_bytes(), randomness) - }); - let chip = IsEqualChip::construct(self.is_equal_gx2.clone()); - chip.assign(region, 0, rlc_acc, gx2_rlc)?; - } *offset += 32; - Ok(()) + Ok(rlc_acc) } fn load_mul_add(&self, region: &mut Region<'_, F>, msg_hash: Word) -> Result<(), Error> { @@ -412,7 +413,7 @@ impl SignVerifyConfig { chip.assign( region, 0, - [GX1_MUL_PRIVATEKEY.clone(), U256::one(), msg_hash, N.clone()], + [msg_hash, U256::one(), GX1_MUL_PRIVATEKEY.clone(), N.clone()], ) } @@ -437,18 +438,23 @@ impl SignVerifyConfig { self.load_mul_add(region, msg_hash)?; let mut offset = 0; for (annotation, tag, need_check, value) in [ - ("msg_hash", TxFieldTag::TxSignHash, false, msg_hash), - ("sign_r", TxFieldTag::SigR, true, anchor_tx.r), + ( + "msg_hash", + TxFieldTag::TxSignHash, + false, + anchor_tx.tx_sign_hash.to_fixed_bytes(), + ), + ("sign_r", TxFieldTag::SigR, true, anchor_tx.r.to_be_bytes()), ] { - self.assign_field( - region, - annotation, - &mut offset, - tag, - value.to_be_bytes(), - need_check, - challenges, - )?; + let rlc_acc = + self.assign_field(region, annotation, &mut offset, tag, value, challenges)?; + if need_check { + let gx2_rlc = challenges.evm_word().map(|randomness| { + crate::evm_circuit::util::rlc::value(&GX2.to_le_bytes(), randomness) + }); + let chip = IsEqualChip::construct(self.is_equal_gx2.clone()); + chip.assign(region, 0, rlc_acc, gx2_rlc)?; + } } Ok(()) }, From 08f3d46865e5c78536077f0c1894ee462438ae3f Mon Sep 17 00:00:00 2001 From: johntaiko Date: Tue, 20 Jun 2023 00:54:58 +0800 Subject: [PATCH 13/20] fix: add r == GX2 test case --- zkevm-circuits/src/anchor_tx_circuit.rs | 8 +- zkevm-circuits/src/anchor_tx_circuit/dev.rs | 8 +- .../src/anchor_tx_circuit/sign_verify.rs | 14 ++- zkevm-circuits/src/anchor_tx_circuit/test.rs | 86 +++++++++++++++---- zkevm-circuits/src/witness/tx.rs | 4 +- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index 2279c4c3a6..8ed0e923a8 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -432,7 +432,6 @@ impl AnchorTxCircuitConfig { call_data: &CallData, challenges: &Challenges>, ) -> Result<(), Error> { - self.sign_verify.assign(layouter, anchor_tx, challenges)?; layouter.assign_region( || "anchor transaction", |ref mut region| { @@ -441,7 +440,8 @@ impl AnchorTxCircuitConfig { self.assign_call_data(region, anchor_tx, call_data, challenges)?; Ok(()) }, - ) + )?; + self.sign_verify.assign(layouter, anchor_tx, challenges) } } @@ -495,9 +495,9 @@ impl SubCircuit for AnchorTxCircuit { type Config = AnchorTxCircuitConfig; fn unusable_rows() -> usize { - // No column queried at more than 3 distinct rotations, so returns 5 as + // No column queried at more than 7 distinct rotations, so returns 10 as // minimum unusable row. - 6 + 10 } fn new_from_block(block: &witness::Block) -> Self { diff --git a/zkevm-circuits/src/anchor_tx_circuit/dev.rs b/zkevm-circuits/src/anchor_tx_circuit/dev.rs index d71314a07e..6a785a1a65 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/dev.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/dev.rs @@ -5,7 +5,7 @@ use crate::{ util::{Challenges, SubCircuit, SubCircuitConfig}, witness::{self, Taiko}, }; -use eth_types::Field; +use eth_types::{Field, H256}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, plonk::{Circuit, ConstraintSystem, Error}, @@ -30,6 +30,12 @@ impl TestAnchorTxCircuit { circuit: AnchorTxCircuit::new_from_block(block), } } + + /// Modify the sign hash for test + pub fn sign_hash(&mut self, hash: H256) { + self.circuit.anchor_tx.tx_sign_hash = hash; + self.circuit.txs[0].tx_sign_hash = hash; + } } impl Circuit for TestAnchorTxCircuit { diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index b7b7836415..73493abfb5 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -87,13 +87,13 @@ pub(crate) static GX2: Lazy = static GX2_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX2)); // 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 -static N: Lazy = +pub(crate) static N: Lazy = Lazy::new(|| word!("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")); static N_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&N)); // private key 0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38 // GX1 * PRIVATEKEY(mod N) = 0x4341adf5a780b4a87939938fd7a032f6e6664c7da553c121d3b4947429639122 -static GX1_MUL_PRIVATEKEY: Lazy = +pub(crate) static GX1_MUL_PRIVATEKEY: Lazy = Lazy::new(|| word!("0x4341adf5a780b4a87939938fd7a032f6e6664c7da553c121d3b4947429639122")); static GX1_MUL_PRIVATEKEY_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX1_MUL_PRIVATEKEY)); static GX1_MUL_PRIVATEKEY_LIMB64: Lazy<[U256; 4]> = @@ -410,11 +410,7 @@ impl SignVerifyConfig { fn load_mul_add(&self, region: &mut Region<'_, F>, msg_hash: Word) -> Result<(), Error> { let chip = MulAddChip::construct(self.mul_add.clone()); - chip.assign( - region, - 0, - [msg_hash, U256::one(), GX1_MUL_PRIVATEKEY.clone(), N.clone()], - ) + chip.assign(region, 0, [msg_hash, U256::one(), *GX1_MUL_PRIVATEKEY, *N]) } /// Return the minimum number of rows required to prove an input of a @@ -434,7 +430,7 @@ impl SignVerifyConfig { |ref mut region| { self.q_check.enable(region, 0)?; - let msg_hash = U256::from_big_endian(&anchor_tx.tx_sign_hash.to_fixed_bytes()); + let msg_hash = U256::from_little_endian(&anchor_tx.tx_sign_hash.to_fixed_bytes()); self.load_mul_add(region, msg_hash)?; let mut offset = 0; for (annotation, tag, need_check, value) in [ @@ -442,7 +438,7 @@ impl SignVerifyConfig { "msg_hash", TxFieldTag::TxSignHash, false, - anchor_tx.tx_sign_hash.to_fixed_bytes(), + msg_hash.to_be_bytes(), ), ("sign_r", TxFieldTag::SigR, true, anchor_tx.r.to_be_bytes()), ] { diff --git a/zkevm-circuits/src/anchor_tx_circuit/test.rs b/zkevm-circuits/src/anchor_tx_circuit/test.rs index 9b7992e260..7de03f5dd3 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/test.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/test.rs @@ -2,7 +2,10 @@ use std::collections::HashMap; use super::{ - sign_verify::{GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_PRIVATEKEY, GOLDEN_TOUCH_WALLET, GX1, GX2}, + sign_verify::{ + GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_PRIVATEKEY, GOLDEN_TOUCH_WALLET, GX1, + GX1_MUL_PRIVATEKEY, GX2, N, + }, *, }; use crate::{ @@ -17,7 +20,7 @@ use eth_types::{ address, bytecode, geth_types::{GethData, Transaction}, sign_types::{biguint_to_32bytes_le, ct_option_ok_or, sign, SignData, SECP256K1_Q}, - word, Address, Field, ToBigEndian, ToLittleEndian, ToWord, Word, U256, + word, Address, Field, ToBigEndian, ToLittleEndian, ToWord, Word, H256, U256, }; use ethers_core::types::TransactionRequest; use ethers_signers::{LocalWallet, Signer}; @@ -41,7 +44,7 @@ use halo2_proofs::{ }; use itertools::Itertools; use log::error; -use mock::{AddrOrWallet, TestContext, MOCK_CHAIN_ID}; +use mock::{AddrOrWallet, MockTransaction, TestContext, MOCK_CHAIN_ID}; use num::Integer; use num_bigint::BigUint; use once_cell::sync::Lazy; @@ -105,13 +108,15 @@ pub(crate) fn anchor_sign( }) } -fn run(block: &Block) -> Result<(), Vec> { +fn run(block: &Block, sign_hash: Option) -> Result<(), Vec> { let k = log2_ceil( AnchorTxCircuit::::unusable_rows() + AnchorTxCircuit::::min_num_rows(block.circuits_params.max_txs), ); - let circuit = TestAnchorTxCircuit::::new_from_block(block); - + let mut circuit = TestAnchorTxCircuit::::new_from_block(block); + if let Some(sign_hash) = sign_hash { + circuit.sign_hash(sign_hash); + } let prover = match MockProver::run(k + 3, &circuit, vec![]) { Ok(prover) => prover, Err(e) => panic!("{:#?}", e), @@ -119,11 +124,16 @@ fn run(block: &Block) -> Result<(), Vec> { prover.verify() } -fn gen_block(max_txs: usize, max_calldata: usize, taiko: Taiko) -> Block { +fn gen_block( + max_txs: usize, + max_calldata: usize, + taiko: Taiko, + extra_func_tx: fn(&mut MockTransaction), +) -> Block { let chain_id = (*MOCK_CHAIN_ID).as_u64(); let mut wallets = HashMap::new(); wallets.insert( - GOLDEN_TOUCH_ADDRESS.clone(), + *GOLDEN_TOUCH_ADDRESS, GOLDEN_TOUCH_WALLET.clone().with_chain_id(chain_id), ); @@ -139,7 +149,7 @@ fn gen_block(max_txs: usize, max_calldata: usize, taiko: T None, |accs| { accs[0] - .address(GOLDEN_TOUCH_ADDRESS.clone()) + .address(*GOLDEN_TOUCH_ADDRESS) .balance(Word::from(1u64 << 20)); accs[1].address(taiko.l2_contract).code(code); }, @@ -147,16 +157,12 @@ fn gen_block(max_txs: usize, max_calldata: usize, taiko: T txs[0] .gas(taiko.anchor_gas_cost.to_word()) .gas_price(ANCHOR_TX_GAS_PRICE.to_word()) - .from(GOLDEN_TOUCH_ADDRESS.clone()) + .from(*GOLDEN_TOUCH_ADDRESS) .to(taiko.l2_contract) .input(taiko.anchor_call()) .nonce(0) .value(ANCHOR_TX_VALUE.to_word()); - let tx: Transaction = txs[0].to_owned().into(); - let sig_data = anchor_sign(&tx, chain_id).unwrap(); - let sig_r = U256::from_little_endian(sig_data.signature.0.to_bytes().as_slice()); - let sig_s = U256::from_little_endian(sig_data.signature.1.to_bytes().as_slice()); - txs[0].sig_data((2712, sig_r, sig_s)); + extra_func_tx(txs[0]); }, |block, _tx| block, ) @@ -177,10 +183,52 @@ fn gen_block(max_txs: usize, max_calldata: usize, taiko: T block } +fn correct_sign(tx: &mut MockTransaction) { + let chain_id = (*MOCK_CHAIN_ID).as_u64(); + let _tx: Transaction = tx.to_owned().into(); + let sig_data = anchor_sign(&_tx, chain_id).unwrap(); + let sig_r = U256::from_little_endian(sig_data.signature.0.to_bytes().as_slice()); + let sig_s = U256::from_little_endian(sig_data.signature.1.to_bytes().as_slice()); + tx.sig_data((2712, sig_r, sig_s)); +} + #[test] fn test() { - let mut taiko = Taiko::default(); - taiko.anchor_gas_cost = 150000; - let block = gen_block::<1>(2, 100, taiko); - assert_eq!(run::(&block), Ok(())); + let taiko = Taiko { + anchor_gas_cost: 150000, + ..Default::default() + }; + let block = gen_block::<1>(2, 100, taiko, correct_sign); + assert_eq!(run::(&block, None), Ok(())); +} + +fn sign_r_is_gx2(tx: &mut MockTransaction) { + let msg_hash = *N - *GX1_MUL_PRIVATEKEY; + let msg_hash = ct_option_ok_or( + secp256k1::Fq::from_repr(msg_hash.to_le_bytes()), + libsecp256k1::Error::InvalidMessage, + ) + .unwrap(); + let k2 = secp256k1::Fq::ONE + secp256k1::Fq::ONE; + let sk = ct_option_ok_or( + secp256k1::Fq::from_repr(GOLDEN_TOUCH_PRIVATEKEY.to_le_bytes()), + libsecp256k1::Error::InvalidSecretKey, + ) + .unwrap(); + let (sig_r, sig_s) = sign(k2, sk, msg_hash); + let sig_r = U256::from_little_endian(sig_r.to_bytes().as_slice()); + let sig_s = U256::from_little_endian(sig_s.to_bytes().as_slice()); + tx.sig_data((2712, sig_r, sig_s)); +} + +#[test] +fn test_when_sign_r_is_gx2() { + let taiko = Taiko { + anchor_gas_cost: 150000, + ..Default::default() + }; + let msg_hash = *N - *GX1_MUL_PRIVATEKEY; + let msg_hash = H256::from(msg_hash.to_le_bytes()); + let block = gen_block::<1>(2, 100, taiko, sign_r_is_gx2); + assert_eq!(run::(&block, Some(msg_hash)), Ok(())); } diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 1d3132d7a6..3df92c3edb 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -121,7 +121,9 @@ impl Transaction { Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxSignHash as u64)), Value::known(F::ZERO), - rlc_be_bytes(&self.tx_sign_hash.to_fixed_bytes(), challenges.evm_word()), + challenges + .evm_word() + .map(|challenge| rlc::value(&self.tx_sign_hash.to_fixed_bytes(), challenge)), ], [ Value::known(F::from(self.id as u64)), From 3a43132ad480e16b31248ef38f693a938df3d4c5 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Tue, 20 Jun 2023 12:14:28 +0800 Subject: [PATCH 14/20] chore: clippy complain --- zkevm-circuits/src/anchor_tx_circuit.rs | 3 ++- zkevm-circuits/src/witness/taiko.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index 8ed0e923a8..b37e94390b 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -421,6 +421,7 @@ impl AnchorTxCircuitConfig { Ok(()) } + #[allow(clippy::too_many_arguments)] fn assign( &self, layouter: &mut impl Layouter, @@ -504,7 +505,7 @@ impl SubCircuit for AnchorTxCircuit { Self::new( block.circuits_params.max_txs, block.circuits_params.max_calldata, - block.txs.iter().next().unwrap().clone(), + block.txs.first().unwrap().clone(), block.txs.clone(), block.taiko.clone(), ) diff --git a/zkevm-circuits/src/witness/taiko.rs b/zkevm-circuits/src/witness/taiko.rs index e97d152930..ec419aa684 100644 --- a/zkevm-circuits/src/witness/taiko.rs +++ b/zkevm-circuits/src/witness/taiko.rs @@ -90,9 +90,9 @@ pub fn left_shift(x: T, n: u32) -> Word { impl MetaHash { /// get the hash of meta hash pub fn hash(&self) -> Hash { - let field0 = left_shift(self.id as u64, 192) - + left_shift(self.timestamp as u64, 128) - + left_shift(self.l1_height as u64, 64); + let field0 = left_shift(self.id, 192) + + left_shift(self.timestamp, 128) + + left_shift(self.l1_height, 64); let field5 = left_shift(self.tx_list_byte_start as u64, 232) + left_shift(self.tx_list_byte_end as u64, 208) From 615dd255bc4663539df83ee48378b08fb3d4d515 Mon Sep 17 00:00:00 2001 From: Brechtpd Date: Tue, 20 Jun 2023 20:09:57 +0200 Subject: [PATCH 15/20] Some misc small improvements --- zkevm-circuits/src/anchor_tx_circuit.rs | 98 ++++--- .../src/anchor_tx_circuit/sign_verify.rs | 240 +++++++++--------- zkevm-circuits/src/anchor_tx_circuit/test.rs | 44 ++-- 3 files changed, 197 insertions(+), 185 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index b37e94390b..fc6a5fc810 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -27,17 +27,22 @@ use std::marker::PhantomData; use self::sign_verify::GOLDEN_TOUCH_ADDRESS; -// The first of txlist is the anchor tx +// The anchor tx is the first tx const ANCHOR_TX_ID: usize = 1; const ANCHOR_TX_VALUE: u64 = 0; const ANCHOR_TX_IS_CREATE: bool = false; const ANCHOR_TX_GAS_PRICE: u64 = 0; -// TODO: calculate the method_signature -const ANCHOR_TX_METHOD_SIGNATURE: u32 = 0; const MAX_DEGREE: usize = 9; const BYTE_POW_BASE: u64 = 1 << 8; -// anchor(bytes32,bytes32,uint64,uint64) = method_signature(4B)+1st(32B)+2nd(32B)+3rd(8B)+4th(8B) +// function anchor( +// bytes32 l1Hash, +// bytes32 l1SignalRoot, +// uint64 l1Height, +// uint64 parentGasUsed +// ) +// anchor(bytes32,bytes32,uint64,uint64) = +// method_signature(4B)+l1Hash(32B)+l1SignalRoot(32B)+l1Height(8B)+parentGasUsed(8B) const ANCHOR_CALL_DATA_LEN: usize = 84; struct CallData { @@ -59,11 +64,11 @@ pub struct AnchorTxCircuitConfig { use_rlc: Column, // check: method_signature, l1Hash, l1SignalRoot, l1Height, parentGasUsed - q_call_data_start: Selector, - q_call_data_step: Selector, - q_call_data_end: Selector, - call_data_rlc_acc: Column, - call_data_tag: Column, + q_call_data_part_start: Selector, + q_call_data_part_step: Selector, + q_call_data_part_end: Selector, + call_data_part_rlc_acc: Column, + call_data_part_tag: Column, sign_verify: SignVerifyConfig, } @@ -94,14 +99,17 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { let tag = meta.fixed_column(); let use_rlc = meta.fixed_column(); - let q_call_data_start = meta.complex_selector(); - let q_call_data_step = meta.complex_selector(); - let q_call_data_end = meta.complex_selector(); - let call_data_rlc_acc = meta.advice_column_in(SecondPhase); - let call_data_tag = meta.fixed_column(); + let q_call_data_part_start = meta.complex_selector(); + let q_call_data_part_step = meta.complex_selector(); + let q_call_data_part_end = meta.complex_selector(); + let call_data_part_rlc_acc = meta.advice_column_in(SecondPhase); + let call_data_part_tag = meta.fixed_column(); let sign_verify = SignVerifyConfig::configure(meta, tx_table.clone(), &challenges); - // anchor transaction constants + // Verify the constant values of the anchor tx in the tx table. + // The tag and its corresponding constant value are stored next to each other vertically. + // (if the tag is at row i, its value is at row i + 1). + // Because of this, this lookup is enabled every other row. meta.lookup_any("anchor fixed fields", |meta| { let q_tag = meta.query_selector(q_tag); [ @@ -116,15 +124,16 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { .collect() }); - // call data + // RLC/decode the calldata (per part) of the anchor tx (all bytes except the first one) meta.create_gate( "call_data_rlc_acc[i+1] = call_data_rlc_acc[i] * t + call_data[i+1]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - let q_call_data_step = meta.query_selector(q_call_data_step); - let call_data_rlc_acc_next = meta.query_advice(call_data_rlc_acc, Rotation::next()); - let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); + let q_call_data_step = meta.query_selector(q_call_data_part_step); + let call_data_rlc_acc_next = + meta.query_advice(call_data_part_rlc_acc, Rotation::next()); + let call_data_rlc_acc = meta.query_advice(call_data_part_rlc_acc, Rotation::cur()); let call_data_next = meta.query_advice(tx_table.value, Rotation::next()); let use_rlc = meta.query_fixed(use_rlc, Rotation::cur()); let randomness = challenges.lookup_input(); @@ -137,12 +146,12 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { cb.gate(q_call_data_step) }, ); - + // RLC/decode the calldata (per part) of the anchor tx (first byte) meta.create_gate("call_data_rlc_acc[0] = call_data[0]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - let q_call_data_start = meta.query_selector(q_call_data_start); - let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); + let q_call_data_start = meta.query_selector(q_call_data_part_start); + let call_data_rlc_acc = meta.query_advice(call_data_part_rlc_acc, Rotation::cur()); let call_data = meta.query_advice(tx_table.value, Rotation::cur()); cb.require_equal( @@ -153,10 +162,12 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { cb.gate(q_call_data_start) }); + // After RLC/decode of an input in the calldata, verify that the value matches the expected + // value in the public input table. meta.lookup_any("call data in pi_table", |meta| { - let q_call_data_end = meta.query_selector(q_call_data_end); - let call_data_rlc_acc = meta.query_advice(call_data_rlc_acc, Rotation::cur()); - let call_data_tag = meta.query_fixed(call_data_tag, Rotation::cur()); + let q_call_data_end = meta.query_selector(q_call_data_part_end); + let call_data_rlc_acc = meta.query_advice(call_data_part_rlc_acc, Rotation::cur()); + let call_data_tag = meta.query_fixed(call_data_part_tag, Rotation::cur()); [call_data_tag, call_data_rlc_acc] .into_iter() @@ -173,18 +184,18 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { tag, use_rlc, - q_call_data_start, - q_call_data_step, - q_call_data_end, - call_data_rlc_acc, - call_data_tag, + q_call_data_part_start, + q_call_data_part_step, + q_call_data_part_end, + call_data_part_rlc_acc, + call_data_part_tag, sign_verify, } } } impl AnchorTxCircuitConfig { - fn assign_anchor_tx( + fn assign_anchor_tx_values( &self, region: &mut Region<'_, F>, _anchor_tx: &Transaction, @@ -270,6 +281,7 @@ impl AnchorTxCircuitConfig { ), ] { let mut rlc_acc = Value::known(F::ZERO); + // Use RLC encoding if the input doesn't fit within the field let (use_rlc, t) = if value.len() * 8 > F::CAPACITY as usize { (Value::known(F::ONE), challenges.evm_word()) } else { @@ -277,29 +289,35 @@ impl AnchorTxCircuitConfig { }; for (idx, byte) in value.iter().enumerate() { let row_offset = offset + idx; + + // RLC/Decode bytes + region.assign_fixed(|| annotation, self.use_rlc, row_offset, || use_rlc)?; rlc_acc = rlc_acc * t + Value::known(F::from(*byte as u64)); region.assign_advice( || annotation, - self.call_data_rlc_acc, + self.call_data_part_rlc_acc, row_offset, || rlc_acc, )?; + + // Set the tag for this input region.assign_fixed( || annotation, - self.call_data_tag, + self.call_data_part_tag, row_offset, || Value::known(F::from(tag as u64)), )?; - region.assign_fixed(|| annotation, self.use_rlc, row_offset, || use_rlc)?; - // setup selector + + // Always enable the `start` selector at the first byte if idx == 0 { - self.q_call_data_start.enable(region, row_offset)?; + self.q_call_data_part_start.enable(region, row_offset)?; } - // the last offset of field + // If we're at the last byte, enable the `end` selector. + // Otherwise enable the `step` selector. if idx == value.len() - 1 { - self.q_call_data_end.enable(region, row_offset)?; + self.q_call_data_part_end.enable(region, row_offset)?; } else { - self.q_call_data_step.enable(region, row_offset)?; + self.q_call_data_part_step.enable(region, row_offset)?; } } offset += value.len(); @@ -437,7 +455,7 @@ impl AnchorTxCircuitConfig { || "anchor transaction", |ref mut region| { self.load_tx_table(region, txs, max_txs, max_calldata, challenges)?; - self.assign_anchor_tx(region, anchor_tx, taiko, challenges)?; + self.assign_anchor_tx_values(region, anchor_tx, taiko, challenges)?; self.assign_call_data(region, anchor_tx, call_data, challenges)?; Ok(()) }, diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 73493abfb5..eec91d10a5 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -40,7 +40,7 @@ use crate::{ evm_circuit::util::{ constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, - split_u256_limb64, + rlc, split_u256_limb64, }, table::{LookupTable, TxFieldTag, TxTable}, util::Challenges, @@ -104,31 +104,30 @@ static GX2_MUL_PRIVATEKEY: Lazy = Lazy::new(|| word!("0x4a43b192ca74cab200d6c086df90fb729abca9e52d38b8fa0beb4eafe70956de")); static GX2_MUL_PRIVATEKEY_LO_HI: Lazy<(U256, U256)> = Lazy::new(|| split_u256(&GX2_MUL_PRIVATEKEY)); -// In mul_add chip, we have a * b + c == d -// => a == GX1_MUL_PRIVATEKEY -// => b == 1 -// => c == msg_hash -// => d == N -// // # The circuit layout // - msg_hash (c) // - SigR +// +// We don't have to verify `s` and `v` of the signature, because the signature needs to be valid and +// and those values are fixed if `r` is fixed. +// `msg_hash` is calculated and verified in the tx circuit like any other transaction. #[derive(Debug, Clone)] pub(crate) struct SignVerifyConfig { tx_table: TxTable, - q_sign_start: Selector, - q_sign_step: Selector, - q_sign_end: Selector, + q_sig_start: Selector, + q_sig_step: Selector, + q_sig_end: Selector, tag: Column, - sign: Column, - sign_rlc_acc: Column, - // split u256 to low and high + sig: Column, + sig_rlc_acc: Column, + + // split u256 into 4 64bit limbs q_u64_start: Selector, q_u64_step: Selector, q_u64_end: Selector, - sign_u64_acc: Column, + sig_u64_acc: Column, q_check: Selector, mul_add: MulAddConfig, @@ -141,188 +140,188 @@ impl SignVerifyConfig { tx_table: TxTable, challenges: &Challenges>, ) -> Self { - let q_sign_start = meta.complex_selector(); - let q_sign_step = meta.complex_selector(); - let q_sign_end = meta.complex_selector(); + let q_sig_start = meta.complex_selector(); + let q_sig_step = meta.complex_selector(); + let q_sig_end = meta.complex_selector(); let tag = meta.fixed_column(); - let sign = meta.advice_column(); - let sign_rlc_acc = meta.advice_column_in(SecondPhase); + let sig = meta.advice_column(); + let sig_rlc_acc = meta.advice_column_in(SecondPhase); let q_u64_start = meta.complex_selector(); let q_u64_step = meta.complex_selector(); let q_u64_end = meta.complex_selector(); - let sign_u64_acc = meta.advice_column(); + let sig_u64_acc = meta.advice_column(); - let gx1_rlc = crate::evm_circuit::util::rlc::expr( + // RLC of GX1 bytes + let gx1_rlc = rlc::expr( GX1.to_le_bytes() .map(|v| Expression::Constant(F::from(v as u64))) .as_ref(), challenges.evm_word(), ); - - let gx2_rlc = crate::evm_circuit::util::rlc::expr( + // RLC of GX2 bytes + let gx2_rlc = rlc::expr( GX2.to_le_bytes() .map(|v| Expression::Constant(F::from(v as u64))) .as_ref(), challenges.evm_word(), ); + + // Check if R == GX2 let q_check = meta.complex_selector(); let is_equal_gx2 = IsEqualChip::configure( meta, |meta| meta.query_selector(q_check), - |meta| meta.query_advice(sign_rlc_acc, Rotation(63)), // SigR == GX2 + |meta| meta.query_advice(sig_rlc_acc, Rotation(63)), // SigR == GX2 |_| gx2_rlc.expr(), ); - + // Only enable the mul_add constraints when r == GX2 let mul_add = MulAddChip::configure(meta, |meta| { is_equal_gx2.is_equal_expression.expr() * meta.query_selector(q_check) }); - // signature rlc + // RLC the signature data (per part) (all bytes except the first one) meta.create_gate( - "sign_rlc_acc[i+1] = sign_rlc_acc[i] * randomness + sign[i+1]", + "sig_rlc_acc[i+1] = sig_rlc_acc[i] * randomness + sig[i+1]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - let q_sign_step = meta.query_selector(q_sign_step); - let sign_rlc_acc_next = meta.query_advice(sign_rlc_acc, Rotation::next()); - let sign_rlc_acc = meta.query_advice(sign_rlc_acc, Rotation::cur()); - let sign = meta.query_advice(sign, Rotation::next()); + let q_sig_step = meta.query_selector(q_sig_step); + let sig_rlc_acc_next = meta.query_advice(sig_rlc_acc, Rotation::next()); + let sig_rlc_acc = meta.query_advice(sig_rlc_acc, Rotation::cur()); + let sign = meta.query_advice(sig, Rotation::next()); let randomness = challenges.evm_word(); cb.require_equal( - "sign_rlc_acc[i+1] = sign_rlc_acc[i] * randomness + sign[i+1]", - sign_rlc_acc_next, - sign_rlc_acc * randomness + sign, + "sig_rlc_acc[i+1] = sig_rlc_acc[i] * randomness + sign[i+1]", + sig_rlc_acc_next, + sig_rlc_acc * randomness + sign, ); - cb.gate(q_sign_step) + cb.gate(q_sig_step) }, ); - - meta.create_gate("sign_rlc_acc[0] = sign[0]", |meta| { + // RLC the signature data (per part) (first byte) + meta.create_gate("sig_rlc_acc[0] = sign[0]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - let q_sign_start = meta.query_selector(q_sign_start); - let sign_rlc_acc = meta.query_advice(sign_rlc_acc, Rotation::cur()); - let sign = meta.query_advice(sign, Rotation::cur()); + let q_sig_start = meta.query_selector(q_sig_start); + let sig_rlc_acc = meta.query_advice(sig_rlc_acc, Rotation::cur()); + let sign = meta.query_advice(sig, Rotation::cur()); - cb.require_equal("sign_rlc_acc[0] = sign[0]", sign_rlc_acc, sign); - cb.gate(q_sign_start) + cb.require_equal("sig_rlc_acc[0] = sign[0]", sig_rlc_acc, sign); + cb.gate(q_sig_start) }); - meta.lookup_any("sign_r or msg_hash in tx_table", |meta| { - let q_sign_end = meta.query_selector(q_sign_end); + // Make sure that `sig_r` and `msg_hash` have the correct value by looking up their RLCd + // representations in the tx table. + meta.lookup_any("sig_r or msg_hash in tx_table", |meta| { + let q_sig_end = meta.query_selector(q_sig_end); let tx_id = super::ANCHOR_TX_ID.expr(); let tag = meta.query_fixed(tag, Rotation::cur()); let index = 0.expr(); - let value = meta.query_advice(sign_rlc_acc, Rotation::cur()); + let value = meta.query_advice(sig_rlc_acc, Rotation::cur()); [tx_id, tag, index, value] .into_iter() .zip(tx_table.table_exprs(meta).into_iter()) - .map(|(arg, table)| (q_sign_end.expr() * arg, table)) + .map(|(arg, table)| (q_sig_end.expr() * arg, table)) .collect::>() }); - // signature u64 + // Decode the 4 64bit limbs of msg_hash and R (all bytes except the first one) meta.create_gate( - "sign_u64_acc[i+1] = sign_u64_acc[i] * BYTE_POW_BASE + sign[i+1]", + "sig_u64_acc[i+1] = sig_u64_acc[i] * BYTE_POW_BASE + sig[i+1]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); let q_u64_step = meta.query_selector(q_u64_step); - let sign_u64_acc_next = meta.query_advice(sign_u64_acc, Rotation::next()); - let sign_u64_acc = meta.query_advice(sign_u64_acc, Rotation::cur()); - let sign_next = meta.query_advice(sign, Rotation::next()); + let sig_u64_acc_next = meta.query_advice(sig_u64_acc, Rotation::next()); + let sig_u64_acc = meta.query_advice(sig_u64_acc, Rotation::cur()); + let sig_next = meta.query_advice(sig, Rotation::next()); cb.require_equal( - "sign_u64_acc[i+1] = sign_u64_acc[i] * BYTE_POW_BASE + sign[i+1]", - sign_u64_acc_next, - sign_u64_acc * BYTE_POW_BASE.expr() + sign_next, + "sig_u64_acc[i+1] = sig_u64_acc[i] * BYTE_POW_BASE + sig[i+1]", + sig_u64_acc_next, + sig_u64_acc * BYTE_POW_BASE.expr() + sig_next, ); cb.gate(q_u64_step) }, ); - - meta.create_gate("sign_u64_acc[start] = sign[start]", |meta| { + // Decode the 4 64bit limbs of msg_hash and R (first byte) + meta.create_gate("sig_u64_acc[start] = sig[start]", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); let q_u64_start = meta.query_selector(q_u64_start); - let sign_u64_acc = meta.query_advice(sign_u64_acc, Rotation::cur()); - let sign = meta.query_advice(sign, Rotation::cur()); + let sig_u64_acc = meta.query_advice(sig_u64_acc, Rotation::cur()); + let sig = meta.query_advice(sig, Rotation::cur()); - cb.require_equal("sign_u64_acc[start] = sign[start]", sign_u64_acc, sign); + cb.require_equal("sig_u64_acc[start] = sig[start]", sig_u64_acc, sig); cb.gate(q_u64_start) }); - // check SigR + // Check that R has the expected value meta.create_gate( "IF r == GX2 THEN a(msg_hash) * b(1) + c(GX1_MUL_PRIVATEKEY) == d(N)", |meta| { let mut cb = BaseConstraintBuilder::new(MAX_DEGREE); - let q_check = meta.query_selector(q_check); - - let sign_rlc_acc = meta.query_advice(sign_rlc_acc, Rotation(63)); - - cb.require_in_set("r in (GX1, GX2)", sign_rlc_acc, vec![gx1_rlc, gx2_rlc]); + // R needs to be either GX1 or Gx2 + let sig_rlc_acc = meta.query_advice(sig_rlc_acc, Rotation(63)); + cb.require_in_set("r in (GX1, GX2)", sig_rlc_acc, vec![gx1_rlc, gx2_rlc]); + // In mul_add chip, we have a * b + c == d + // => a == msg_hash + // => b == 1 + // => c == GX1_MUL_PRIVATEKEY + // => d == N + // Check that the inputs a, b, and c into mul_add are correct // a == msg_hash - let (a_limbs_cur0, a_limbs_cur1, a_limbs_cur2, a_limbs_cur3) = - mul_add.a_limbs_cur(meta); - - // c == msg_hash - let a_limb0 = meta.query_advice(sign_u64_acc, Rotation(31)); - let a_limb1 = meta.query_advice(sign_u64_acc, Rotation(23)); - let a_limb2 = meta.query_advice(sign_u64_acc, Rotation(15)); - let a_limb3 = meta.query_advice(sign_u64_acc, Rotation(7)); - cb.require_equal("a_limb0", a_limbs_cur0, a_limb0); - cb.require_equal("a_limb1", a_limbs_cur1, a_limb1); - cb.require_equal("a_limb2", a_limbs_cur2, a_limb2); - cb.require_equal("a_limb3", a_limbs_cur3, a_limb3); - + let a = mul_add.a_limbs_cur(meta); + cb.require_equal("a.0", a.0, meta.query_advice(sig_u64_acc, Rotation(31))); + cb.require_equal("a.1", a.1, meta.query_advice(sig_u64_acc, Rotation(23))); + cb.require_equal("a.2", a.2, meta.query_advice(sig_u64_acc, Rotation(15))); + cb.require_equal("a.3", a.3, meta.query_advice(sig_u64_acc, Rotation(7))); // b == 1 - let (b_limb0, b_limb1, b_limb2, b_limb3) = mul_add.b_limbs_cur(meta); - let b_limb = split_u256_limb64(&U256::one()) + let b = mul_add.b_limbs_cur(meta); + let one = split_u256_limb64(&U256::one()) .map(|v| Expression::Constant(F::from(v.as_u64()))); - cb.require_equal("b_limb0", b_limb0, b_limb[0].expr()); - cb.require_equal("b_limb1", b_limb1, b_limb[1].expr()); - cb.require_equal("b_limb2", b_limb2, b_limb[2].expr()); - cb.require_equal("b_limb3", b_limb3, b_limb[3].expr()); - + cb.require_equal("b.0", b.0, one[0].expr()); + cb.require_equal("b.1", b.1, one[1].expr()); + cb.require_equal("b.2", b.2, one[2].expr()); + cb.require_equal("b.3", b.3, one[3].expr()); // c == GX1_MUL_PRIVATEKEY - let c_lo_hi0 = + let gx1_mul_privatekey_0 = Expression::Constant(F::from_u128(GX1_MUL_PRIVATEKEY_LO_HI.0.as_u128())); - let c_lo_hi1 = + let gx1_mul_privatekey_1 = Expression::Constant(F::from_u128(GX1_MUL_PRIVATEKEY_LO_HI.1.as_u128())); - let (c_lo_cur, c_hi_cur) = mul_add.c_lo_hi_cur(meta); - cb.require_equal("c_lo_cur", c_lo_hi0, c_lo_cur); - cb.require_equal("c_hi_cur", c_lo_hi1, c_hi_cur); - - // d == N - let (d_lo_cur, d_hi_cur) = mul_add.d_lo_hi_cur(meta); - let d_lo_cur_expr = Expression::Constant(F::from_u128(N_LO_HI.0.as_u128())); - let d_hi_cur_expr = Expression::Constant(F::from_u128(N_LO_HI.1.as_u128())); - - cb.require_equal("d_lo_cur", d_lo_cur_expr, d_lo_cur); - cb.require_equal("d_hi_cur", d_hi_cur_expr, d_hi_cur); - cb.gate(q_check) + let c = mul_add.c_lo_hi_cur(meta); + cb.require_equal("c.0", c.0, gx1_mul_privatekey_0); + cb.require_equal("c.1", c.1, gx1_mul_privatekey_1); + + // Now finally check that d == N + let n_0 = Expression::Constant(F::from_u128(N_LO_HI.0.as_u128())); + let n_1 = Expression::Constant(F::from_u128(N_LO_HI.1.as_u128())); + let d = mul_add.d_lo_hi_cur(meta); + cb.require_equal("d.0", d.0, n_0); + cb.require_equal("d.1", d.1, n_1); + + cb.gate(meta.query_selector(q_check)) }, ); Self { tx_table, - q_sign_start, - q_sign_step, - q_sign_end, + q_sig_start, + q_sig_step, + q_sig_end, tag, - sign, - sign_rlc_acc, + sig, + sig_rlc_acc, q_u64_start, q_u64_step, q_u64_end, - sign_u64_acc, + sig_u64_acc, q_check, mul_add, @@ -349,12 +348,7 @@ impl SignVerifyConfig { let row_offset = *offset + idx; u64_acc = u64_acc * Value::known(F::from(BYTE_POW_BASE)) + Value::known(F::from(*byte as u64)); - region.assign_advice( - || "sign_u64_acc", - self.sign_u64_acc, - row_offset, - || u64_acc, - )?; + region.assign_advice(|| "sig_u64_acc", self.sig_u64_acc, row_offset, || u64_acc)?; // setup selector if idx == 0 { self.q_u64_start.enable(region, row_offset)?; @@ -379,8 +373,8 @@ impl SignVerifyConfig { for (idx, byte) in value.iter().enumerate() { let row_offset = *offset + idx; region.assign_advice( - || "sign", - self.sign, + || "sig", + self.sig, row_offset, || Value::known(F::from(*byte as u64)), )?; @@ -392,16 +386,16 @@ impl SignVerifyConfig { )?; rlc_acc = rlc_acc * randomness + Value::known(F::from(*byte as u64)); - region.assign_advice(|| "sign_rlc_acc", self.sign_rlc_acc, row_offset, || rlc_acc)?; + region.assign_advice(|| "sig_rlc_acc", self.sig_rlc_acc, row_offset, || rlc_acc)?; // setup selector if idx == 0 { - self.q_sign_start.enable(region, row_offset)?; + self.q_sig_start.enable(region, row_offset)?; } // the last offset of field if idx == 31 { - self.q_sign_end.enable(region, row_offset)?; + self.q_sig_end.enable(region, row_offset)?; } else { - self.q_sign_step.enable(region, row_offset)?; + self.q_sig_step.enable(region, row_offset)?; } } *offset += 32; @@ -433,21 +427,21 @@ impl SignVerifyConfig { let msg_hash = U256::from_little_endian(&anchor_tx.tx_sign_hash.to_fixed_bytes()); self.load_mul_add(region, msg_hash)?; let mut offset = 0; - for (annotation, tag, need_check, value) in [ + for (annotation, tag, do_check_equal_to_gx2, value) in [ ( "msg_hash", TxFieldTag::TxSignHash, false, msg_hash.to_be_bytes(), ), - ("sign_r", TxFieldTag::SigR, true, anchor_tx.r.to_be_bytes()), + ("sig_r", TxFieldTag::SigR, true, anchor_tx.r.to_be_bytes()), ] { let rlc_acc = self.assign_field(region, annotation, &mut offset, tag, value, challenges)?; - if need_check { - let gx2_rlc = challenges.evm_word().map(|randomness| { - crate::evm_circuit::util::rlc::value(&GX2.to_le_bytes(), randomness) - }); + if do_check_equal_to_gx2 { + let gx2_rlc = challenges + .evm_word() + .map(|randomness| rlc::value(&GX2.to_le_bytes(), randomness)); let chip = IsEqualChip::construct(self.is_equal_gx2.clone()); chip.assign(region, 0, rlc_acc, gx2_rlc)?; } diff --git a/zkevm-circuits/src/anchor_tx_circuit/test.rs b/zkevm-circuits/src/anchor_tx_circuit/test.rs index 7de03f5dd3..bb07cbbc57 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/test.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/test.rs @@ -50,14 +50,6 @@ use num_bigint::BigUint; use once_cell::sync::Lazy; use sha3::{Digest, Keccak256}; -#[test] -fn tx_circuit_unusable_rows() { - assert_eq!( - AnchorTxCircuit::::unusable_rows(), - unusable_rows::>(()), - ) -} - pub(crate) fn anchor_sign( anchor_tx: &Transaction, chain_id: u64, @@ -183,7 +175,7 @@ fn gen_block( block } -fn correct_sign(tx: &mut MockTransaction) { +fn sign_tx(tx: &mut MockTransaction) { let chain_id = (*MOCK_CHAIN_ID).as_u64(); let _tx: Transaction = tx.to_owned().into(); let sig_data = anchor_sign(&_tx, chain_id).unwrap(); @@ -192,17 +184,7 @@ fn correct_sign(tx: &mut MockTransaction) { tx.sig_data((2712, sig_r, sig_s)); } -#[test] -fn test() { - let taiko = Taiko { - anchor_gas_cost: 150000, - ..Default::default() - }; - let block = gen_block::<1>(2, 100, taiko, correct_sign); - assert_eq!(run::(&block, None), Ok(())); -} - -fn sign_r_is_gx2(tx: &mut MockTransaction) { +fn sign_tx_r_is_gx2(tx: &mut MockTransaction) { let msg_hash = *N - *GX1_MUL_PRIVATEKEY; let msg_hash = ct_option_ok_or( secp256k1::Fq::from_repr(msg_hash.to_le_bytes()), @@ -222,13 +204,31 @@ fn sign_r_is_gx2(tx: &mut MockTransaction) { } #[test] -fn test_when_sign_r_is_gx2() { +fn anchor_tx_circuit_unusable_rows() { + assert_eq!( + AnchorTxCircuit::::unusable_rows(), + unusable_rows::>(()), + ) +} + +#[test] +fn anchor_test() { + let taiko = Taiko { + anchor_gas_cost: 150000, + ..Default::default() + }; + let block = gen_block::<1>(2, 100, taiko, sign_tx); + assert_eq!(run::(&block, None), Ok(())); +} + +#[test] +fn anchor_test_when_sign_r_is_gx2() { let taiko = Taiko { anchor_gas_cost: 150000, ..Default::default() }; let msg_hash = *N - *GX1_MUL_PRIVATEKEY; let msg_hash = H256::from(msg_hash.to_le_bytes()); - let block = gen_block::<1>(2, 100, taiko, sign_r_is_gx2); + let block = gen_block::<1>(2, 100, taiko, sign_tx_r_is_gx2); assert_eq!(run::(&block, Some(msg_hash)), Ok(())); } From 2633c2960ccca8a17cfff26473ca9f4d46dfcdf7 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Wed, 21 Jun 2023 20:00:55 +0800 Subject: [PATCH 16/20] improve: code clean --- zkevm-circuits/Cargo.toml | 4 +- zkevm-circuits/src/anchor_tx_circuit.rs | 127 ++---------------- zkevm-circuits/src/anchor_tx_circuit/dev.rs | 5 +- .../src/anchor_tx_circuit/sign_verify.rs | 20 ++- zkevm-circuits/src/pi_circuit2.rs | 37 +++-- zkevm-circuits/src/table.rs | 2 + zkevm-circuits/src/table/byte_table.rs | 48 +++++++ zkevm-circuits/src/table/tx_table.rs | 113 +++++++++------- 8 files changed, 159 insertions(+), 197 deletions(-) create mode 100644 zkevm-circuits/src/table/byte_table.rs diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index e156705bc6..8e71c6840f 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -8,9 +8,7 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", features = [ - "circuit-params", -], tag = "v2023_04_20" } +halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", features = ["circuit-params",], tag = "v2023_04_20" } num = "0.4" sha3 = "0.10" array-init = "2.0.0" diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index fc6a5fc810..99d625b2fc 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -10,7 +10,7 @@ mod test; use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, - table::{LookupTable, PiFieldTag, PiTable, TxContextFieldTag, TxFieldTag, TxTable}, + table::{byte_table::ByteTable, LookupTable, PiFieldTag, PiTable, TxFieldTag, TxTable}, tx_circuit::TX_LEN, util::{Challenges, SubCircuit, SubCircuitConfig}, witness::{self, Taiko, Transaction}, @@ -55,6 +55,7 @@ struct CallData { pub struct AnchorTxCircuitConfig { tx_table: TxTable, pi_table: PiTable, + byte_table: ByteTable, q_tag: Selector, // the anchor transaction fixed fields @@ -79,6 +80,8 @@ pub struct AnchorTxCircuitConfigArgs { pub tx_table: TxTable, /// PiTable pub pi_table: PiTable, + /// ByteTable + pub byte_table: ByteTable, /// Challenges pub challenges: Challenges>, } @@ -92,6 +95,7 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { Self::ConfigArgs { tx_table, pi_table, + byte_table, challenges, }: Self::ConfigArgs, ) -> Self { @@ -104,7 +108,8 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { let q_call_data_part_end = meta.complex_selector(); let call_data_part_rlc_acc = meta.advice_column_in(SecondPhase); let call_data_part_tag = meta.fixed_column(); - let sign_verify = SignVerifyConfig::configure(meta, tx_table.clone(), &challenges); + let sign_verify = + SignVerifyConfig::configure(meta, tx_table.clone(), byte_table.clone(), &challenges); // Verify the constant values of the anchor tx in the tx table. // The tag and its corresponding constant value are stored next to each other vertically. @@ -179,6 +184,7 @@ impl SubCircuitConfig for AnchorTxCircuitConfig { Self { tx_table, pi_table, + byte_table, q_tag, tag, @@ -325,120 +331,6 @@ impl AnchorTxCircuitConfig { Ok(()) } - fn load_tx_table( - &self, - region: &mut Region<'_, F>, - txs: &[Transaction], - max_txs: usize, - max_calldata: usize, - challenges: &Challenges>, - ) -> Result<(), Error> { - assert!( - txs.len() <= max_txs, - "txs.len() <= max_txs: txs.len()={}, max_txs={}", - txs.len(), - max_txs - ); - let sum_txs_calldata = txs.iter().map(|tx| tx.call_data.len()).sum(); - assert!( - sum_txs_calldata <= max_calldata, - "sum_txs_calldata <= max_calldata: sum_txs_calldata={}, max_calldata={}", - sum_txs_calldata, - max_calldata, - ); - - fn assign_row( - region: &mut Region<'_, F>, - offset: usize, - advice_columns: &[Column], - tag: &Column, - row: &[Value; 4], - msg: &str, - ) -> Result<(), Error> { - for (index, column) in advice_columns.iter().enumerate() { - region.assign_advice( - || format!("tx table {} row {}", msg, offset), - *column, - offset, - || row[if index > 0 { index + 1 } else { index }], - )?; - } - region.assign_fixed( - || format!("tx table {} row {}", msg, offset), - *tag, - offset, - || row[1], - )?; - Ok(()) - } - - let mut offset = 0; - let advice_columns = [ - self.tx_table.tx_id, - self.tx_table.index, - self.tx_table.value, - ]; - assign_row( - region, - offset, - &advice_columns, - &self.tx_table.tag, - &[(); 4].map(|_| Value::known(F::ZERO)), - "all-zero", - )?; - offset += 1; - - // Tx Table contains an initial region that has a size parametrized by max_txs - // with all the tx data except for calldata, and then a second - // region that has a size parametrized by max_calldata with all - // the tx calldata. This is required to achieve a constant fixed column tag - // regardless of the number of input txs or the calldata size of each tx. - let mut calldata_assignments: Vec<[Value; 4]> = Vec::new(); - // Assign Tx data (all tx fields except for calldata) - let padding_txs: Vec<_> = (txs.len()..max_txs) - .map(|i| Transaction { - id: i + 1, - ..Default::default() - }) - .collect(); - for tx in txs.iter().chain(padding_txs.iter()) { - let [tx_data, tx_calldata] = tx.table_assignments(*challenges); - for row in tx_data { - assign_row( - region, - offset, - &advice_columns, - &self.tx_table.tag, - &row, - "", - )?; - offset += 1; - } - calldata_assignments.extend(tx_calldata.iter()); - } - // Assign Tx calldata - let padding_calldata = (sum_txs_calldata..max_calldata).map(|_| { - [ - Value::known(F::ZERO), - Value::known(F::from(TxContextFieldTag::CallData as u64)), - Value::known(F::ZERO), - Value::known(F::ZERO), - ] - }); - for row in calldata_assignments.into_iter().chain(padding_calldata) { - assign_row( - region, - offset, - &advice_columns, - &self.tx_table.tag, - &row, - "", - )?; - offset += 1; - } - Ok(()) - } - #[allow(clippy::too_many_arguments)] fn assign( &self, @@ -454,7 +346,8 @@ impl AnchorTxCircuitConfig { layouter.assign_region( || "anchor transaction", |ref mut region| { - self.load_tx_table(region, txs, max_txs, max_calldata, challenges)?; + self.tx_table + .load_with_region(region, txs, max_txs, max_calldata, challenges)?; self.assign_anchor_tx_values(region, anchor_tx, taiko, challenges)?; self.assign_call_data(region, anchor_tx, call_data, challenges)?; Ok(()) diff --git a/zkevm-circuits/src/anchor_tx_circuit/dev.rs b/zkevm-circuits/src/anchor_tx_circuit/dev.rs index 6a785a1a65..4e72967e4c 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/dev.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/dev.rs @@ -1,7 +1,7 @@ pub use super::AnchorTxCircuit; use crate::{ anchor_tx_circuit::{AnchorTxCircuitConfig, AnchorTxCircuitConfigArgs}, - table::{PiTable, TxTable}, + table::{byte_table::ByteTable, PiTable, TxTable}, util::{Challenges, SubCircuit, SubCircuitConfig}, witness::{self, Taiko}, }; @@ -50,6 +50,7 @@ impl Circuit for TestAnchorTxCircuit { fn configure(meta: &mut ConstraintSystem) -> Self::Config { let tx_table = TxTable::construct(meta); let pi_table = PiTable::construct(meta); + let byte_table = ByteTable::construct(meta); let challenges = Challenges::construct(meta); let config = { @@ -59,6 +60,7 @@ impl Circuit for TestAnchorTxCircuit { AnchorTxCircuitConfigArgs { tx_table, pi_table, + byte_table, challenges, }, ) @@ -76,6 +78,7 @@ impl Circuit for TestAnchorTxCircuit { config .pi_table .load(&mut layouter, &self.taiko, &challenges)?; + config.byte_table.load(&mut layouter)?; self.circuit .synthesize_sub(&config, &challenges, &mut layouter) } diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index eec91d10a5..9c67351e77 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -1,7 +1,7 @@ //! # How to check the signature //! //! 1. IF r == GX1 OR r == GX2 -//! 2. IF r == GX2 THEN MUST WHEN r == GX1 AND s == 0 +//! 2. IF r == GX2 THEN MUST CHECK IF s == 0 when r = GX1 //! 3. IF s == 0 THEN GX1_MUL_PRIVATEKEY + msg_hash == N //! //! So, IF r == GX2 THEN GX1_MUL_PRIVATEKEY + msg_hash == N @@ -42,7 +42,7 @@ use crate::{ constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, rlc, split_u256_limb64, }, - table::{LookupTable, TxFieldTag, TxTable}, + table::{byte_table::ByteTable, LookupTable, TxFieldTag, TxTable}, util::Challenges, witness::Transaction, }; @@ -51,7 +51,7 @@ use ethers_signers::LocalWallet; use gadgets::{ is_equal::{IsEqualChip, IsEqualConfig, IsEqualInstruction}, mul_add::{MulAddChip, MulAddConfig}, - util::{split_u256, Expr}, + util::{or, split_u256, Expr}, }; use halo2_proofs::{ circuit::{Layouter, Region, Value}, @@ -138,6 +138,7 @@ impl SignVerifyConfig { pub(crate) fn configure( meta: &mut ConstraintSystem, tx_table: TxTable, + byte_table: ByteTable, challenges: &Challenges>, ) -> Self { let q_sig_start = meta.complex_selector(); @@ -308,6 +309,19 @@ impl SignVerifyConfig { }, ); + // is byte + meta.lookup_any("sig is byte", |meta| { + let q_sig_step = meta.query_selector(q_sig_start); + let q_sig_end = meta.query_selector(q_sig_end); + let is_field = or::expr([q_sig_step, q_sig_end]); + let rpi_field_bytes = meta.query_advice(sig, Rotation::cur()); + [rpi_field_bytes] + .into_iter() + .zip(byte_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (is_field.expr() * arg, table)) + .collect::>() + }); + Self { tx_table, diff --git a/zkevm-circuits/src/pi_circuit2.rs b/zkevm-circuits/src/pi_circuit2.rs index e487a10500..c0d14a471d 100644 --- a/zkevm-circuits/src/pi_circuit2.rs +++ b/zkevm-circuits/src/pi_circuit2.rs @@ -2,7 +2,7 @@ use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, - table::{BlockTable, KeccakTable}, + table::{byte_table::ByteTable, BlockTable, KeccakTable, LookupTable}, util::{random_linear_combine_word as rlc, Challenges, SubCircuit, SubCircuitConfig}, witness, }; @@ -207,7 +207,7 @@ pub struct PiCircuitConfig { q_start: Selector, q_not_end: Selector, - is_byte: Column, + byte_table: ByteTable, pi: Column, // keccak_hi, keccak_lo @@ -226,6 +226,8 @@ pub struct PiCircuitConfigArgs { pub block_table: BlockTable, /// KeccakTable pub keccak_table: KeccakTable, + /// ByteTable + pub byte_table: ByteTable, /// Challenges pub challenges: Challenges>, } @@ -239,6 +241,7 @@ impl SubCircuitConfig for PiCircuitConfig { Self::ConfigArgs { block_table, keccak_table, + byte_table, challenges, }: Self::ConfigArgs, ) -> Self { @@ -250,7 +253,6 @@ impl SubCircuitConfig for PiCircuitConfig { let q_field_end = meta.complex_selector(); let q_start = meta.complex_selector(); let q_not_end = meta.complex_selector(); - let is_byte = meta.fixed_column(); let is_field_rlc = meta.fixed_column(); let pi = meta.instance_column(); @@ -325,9 +327,11 @@ impl SubCircuitConfig for PiCircuitConfig { let q_field_end = meta.query_selector(q_field_end); let is_field = or::expr([q_field_step, q_field_end]); let rpi_field_bytes = meta.query_advice(rpi_field_bytes, Rotation::cur()); - - let is_byte = meta.query_fixed(is_byte, Rotation::cur()); - vec![(is_field * rpi_field_bytes, is_byte)] + [rpi_field_bytes] + .into_iter() + .zip(byte_table.table_exprs(meta).into_iter()) + .map(|(arg, table)| (is_field.expr() * arg, table)) + .collect::>() }); Self { @@ -340,7 +344,7 @@ impl SubCircuitConfig for PiCircuitConfig { q_start, q_not_end, - is_byte, + byte_table, is_field_rlc, pi, // keccak_hi, keccak_lo @@ -643,21 +647,7 @@ impl SubCircuit for PiCircuit { challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { - layouter.assign_region( - || "is_byte table", - |mut region| { - for i in 0..(1 << 8) { - region.assign_fixed( - || format!("row_{}", i), - config.is_byte, - i, - || Value::known(F::from(i as u64)), - )?; - } - - Ok(()) - }, - )?; + config.byte_table.load(layouter)?; config.assign(layouter, &self.public_data, challenges) } } @@ -686,6 +676,7 @@ impl Circuit for PiTestCircuit { fn configure(meta: &mut ConstraintSystem) -> Self::Config { let block_table = BlockTable::construct(meta); let keccak_table = KeccakTable::construct(meta); + let byte_table = ByteTable::construct(meta); let challenges = Challenges::construct(meta); let challenge_exprs = challenges.exprs(meta); // let challenge_exprs = Challenges::mock(100.expr(), 100.expr()); @@ -696,6 +687,7 @@ impl Circuit for PiTestCircuit { PiCircuitConfigArgs { block_table, keccak_table, + byte_table, challenges: challenge_exprs, }, ), @@ -717,6 +709,7 @@ impl Circuit for PiTestCircuit { config .keccak_table .dev_load(&mut layouter, vec![&public_data.rpi_bytes()], &challenges)?; + config.byte_table.load(&mut layouter)?; self.0.synthesize_sub(&config, &challenges, &mut layouter) } diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 4c21c84c46..223fe24d5d 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -28,6 +28,8 @@ use strum_macros::{EnumCount, EnumIter}; /// block table pub(crate) mod block_table; +/// byte table +pub(crate) mod byte_table; /// bytecode table pub(crate) mod bytecode_table; /// copy Table diff --git a/zkevm-circuits/src/table/byte_table.rs b/zkevm-circuits/src/table/byte_table.rs new file mode 100644 index 0000000000..43c9d36391 --- /dev/null +++ b/zkevm-circuits/src/table/byte_table.rs @@ -0,0 +1,48 @@ +use super::*; + +/// Is byte table +#[derive(Clone, Debug)] +pub struct ByteTable { + range256: Column, +} + +impl ByteTable { + /// construct the table + pub fn construct(meta: &mut ConstraintSystem) -> Self { + let range256 = meta.fixed_column(); + Self { range256 } + } + + /// load table + pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_region( + || "is_byte table", + |mut region| { + for i in 0..(1 << 8) { + region.assign_fixed( + || format!("row_{}", i), + self.range256, + i, + || Value::known(F::from(i as u64)), + )?; + } + + Ok(()) + }, + ) + } +} + +impl LookupTable for ByteTable { + fn columns(&self) -> Vec> { + vec![self.range256.into()] + } + + fn annotations(&self) -> Vec { + vec![String::from("range256")] + } + + fn table_exprs(&self, meta: &mut VirtualCells) -> Vec> { + vec![meta.query_fixed(self.range256, Rotation::cur())] + } +} diff --git a/zkevm-circuits/src/table/tx_table.rs b/zkevm-circuits/src/table/tx_table.rs index e6b37a8566..64ea31506b 100644 --- a/zkevm-circuits/src/table/tx_table.rs +++ b/zkevm-circuits/src/table/tx_table.rs @@ -98,6 +98,22 @@ impl TxTable { max_txs: usize, max_calldata: usize, challenges: &Challenges>, + ) -> Result<(), Error> { + layouter.assign_region( + || "tx table", + |mut region| self.load_with_region(&mut region, txs, max_txs, max_calldata, challenges), + ) + } + + /// Assign the `TxTable` from a list of block `Transaction`s, followig the + /// same layout that the Tx Circuit uses. + pub fn load_with_region( + &self, + region: &mut Region<'_, F>, + txs: &[Transaction], + max_txs: usize, + max_calldata: usize, + challenges: &Challenges>, ) -> Result<(), Error> { assert!( txs.len() <= max_txs, @@ -138,58 +154,53 @@ impl TxTable { Ok(()) } - layouter.assign_region( - || "tx table", - |mut region| { - let mut offset = 0; - let advice_columns = [self.tx_id, self.index, self.value]; - assign_row( - &mut region, - offset, - &advice_columns, - &self.tag, - &[(); 4].map(|_| Value::known(F::ZERO)), - "all-zero", - )?; - offset += 1; + let mut offset = 0; + let advice_columns = [self.tx_id, self.index, self.value]; + assign_row( + region, + offset, + &advice_columns, + &self.tag, + &[(); 4].map(|_| Value::known(F::ZERO)), + "all-zero", + )?; + offset += 1; - // Tx Table contains an initial region that has a size parametrized by max_txs - // with all the tx data except for calldata, and then a second - // region that has a size parametrized by max_calldata with all - // the tx calldata. This is required to achieve a constant fixed column tag - // regardless of the number of input txs or the calldata size of each tx. - let mut calldata_assignments: Vec<[Value; 4]> = Vec::new(); - // Assign Tx data (all tx fields except for calldata) - let padding_txs: Vec<_> = (txs.len()..max_txs) - .map(|i| Transaction { - id: i + 1, - ..Default::default() - }) - .collect(); - for tx in txs.iter().chain(padding_txs.iter()) { - let [tx_data, tx_calldata] = tx.table_assignments(*challenges); - for row in tx_data { - assign_row(&mut region, offset, &advice_columns, &self.tag, &row, "")?; - offset += 1; - } - calldata_assignments.extend(tx_calldata.iter()); - } - // Assign Tx calldata - let padding_calldata = (sum_txs_calldata..max_calldata).map(|_| { - [ - Value::known(F::ZERO), - Value::known(F::from(TxContextFieldTag::CallData as u64)), - Value::known(F::ZERO), - Value::known(F::ZERO), - ] - }); - for row in calldata_assignments.into_iter().chain(padding_calldata) { - assign_row(&mut region, offset, &advice_columns, &self.tag, &row, "")?; - offset += 1; - } - Ok(()) - }, - ) + // Tx Table contains an initial region that has a size parametrized by max_txs + // with all the tx data except for calldata, and then a second + // region that has a size parametrized by max_calldata with all + // the tx calldata. This is required to achieve a constant fixed column tag + // regardless of the number of input txs or the calldata size of each tx. + let mut calldata_assignments: Vec<[Value; 4]> = Vec::new(); + // Assign Tx data (all tx fields except for calldata) + let padding_txs: Vec<_> = (txs.len()..max_txs) + .map(|i| Transaction { + id: i + 1, + ..Default::default() + }) + .collect(); + for tx in txs.iter().chain(padding_txs.iter()) { + let [tx_data, tx_calldata] = tx.table_assignments(*challenges); + for row in tx_data { + assign_row(region, offset, &advice_columns, &self.tag, &row, "")?; + offset += 1; + } + calldata_assignments.extend(tx_calldata.iter()); + } + // Assign Tx calldata + let padding_calldata = (sum_txs_calldata..max_calldata).map(|_| { + [ + Value::known(F::ZERO), + Value::known(F::from(TxContextFieldTag::CallData as u64)), + Value::known(F::ZERO), + Value::known(F::ZERO), + ] + }); + for row in calldata_assignments.into_iter().chain(padding_calldata) { + assign_row(region, offset, &advice_columns, &self.tag, &row, "")?; + offset += 1; + } + Ok(()) } } From 385f06c883636a946ad356e2fed29e78dcd22b27 Mon Sep 17 00:00:00 2001 From: johntaiko Date: Wed, 21 Jun 2023 20:26:07 +0800 Subject: [PATCH 17/20] fix(IsEqual): alloc value_inv with closure --- gadgets/src/is_equal.rs | 15 ++++++++++----- zkevm-circuits/src/anchor_tx_circuit.rs | 3 +++ .../src/anchor_tx_circuit/sign_verify.rs | 1 + zkevm-circuits/src/table/byte_table.rs | 2 +- zkevm-circuits/src/table/tx_table.rs | 1 + 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/gadgets/src/is_equal.rs b/gadgets/src/is_equal.rs index b7faac3427..60c9cb7db8 100644 --- a/gadgets/src/is_equal.rs +++ b/gadgets/src/is_equal.rs @@ -3,7 +3,7 @@ use eth_types::Field; use halo2_proofs::{ circuit::{Chip, Region, Value}, - plonk::{ConstraintSystem, Error, Expression, SecondPhase, VirtualCells}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, SecondPhase, VirtualCells}, }; use super::is_zero::{IsZeroChip, IsZeroInstruction}; @@ -40,13 +40,13 @@ impl IsEqualChip { /// Configure the IsEqual chip. pub fn configure( meta: &mut ConstraintSystem, + value_inv: impl FnOnce(&mut ConstraintSystem) -> Column, q_enable: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, lhs: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, rhs: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, ) -> IsEqualConfig { let value = |meta: &mut VirtualCells| lhs(meta) - rhs(meta); - let value_inv = meta.advice_column_in(SecondPhase); - + let value_inv = value_inv(meta); let is_zero_config = IsZeroChip::configure(meta, q_enable, value, value_inv); let is_equal_expression = is_zero_config.is_zero_expression.clone(); @@ -138,8 +138,13 @@ mod tests { let lhs = |meta: &mut VirtualCells| meta.query_advice(value, Rotation::cur()); let rhs = |_meta: &mut VirtualCells| RHS.expr(); - let is_equal = - IsEqualChip::configure(meta, |meta| meta.query_selector(q_enable), lhs, rhs); + let is_equal = IsEqualChip::configure( + meta, + |meta| meta.advice_column(), + |meta| meta.query_selector(q_enable), + lhs, + rhs, + ); let config = Self::Config { q_enable, diff --git a/zkevm-circuits/src/anchor_tx_circuit.rs b/zkevm-circuits/src/anchor_tx_circuit.rs index 99d625b2fc..b2ecbe578c 100644 --- a/zkevm-circuits/src/anchor_tx_circuit.rs +++ b/zkevm-circuits/src/anchor_tx_circuit.rs @@ -346,6 +346,9 @@ impl AnchorTxCircuitConfig { layouter.assign_region( || "anchor transaction", |ref mut region| { + // halo2 doesn't support create gates between different regions, + // so we need to load TxTable in the same region in order to create + // gate with TxTable's column self.tx_table .load_with_region(region, txs, max_txs, max_calldata, challenges)?; self.assign_anchor_tx_values(region, anchor_tx, taiko, challenges)?; diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 9c67351e77..59ac638321 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -172,6 +172,7 @@ impl SignVerifyConfig { let q_check = meta.complex_selector(); let is_equal_gx2 = IsEqualChip::configure( meta, + |meta| meta.advice_column_in(SecondPhase), |meta| meta.query_selector(q_check), |meta| meta.query_advice(sig_rlc_acc, Rotation(63)), // SigR == GX2 |_| gx2_rlc.expr(), diff --git a/zkevm-circuits/src/table/byte_table.rs b/zkevm-circuits/src/table/byte_table.rs index 43c9d36391..801df04235 100644 --- a/zkevm-circuits/src/table/byte_table.rs +++ b/zkevm-circuits/src/table/byte_table.rs @@ -1,6 +1,6 @@ use super::*; -/// Is byte table +/// byte table stores the value from 0 to 255 which is used to check whether the input is a byte #[derive(Clone, Debug)] pub struct ByteTable { range256: Column, diff --git a/zkevm-circuits/src/table/tx_table.rs b/zkevm-circuits/src/table/tx_table.rs index 64ea31506b..9d7cb74c91 100644 --- a/zkevm-circuits/src/table/tx_table.rs +++ b/zkevm-circuits/src/table/tx_table.rs @@ -107,6 +107,7 @@ impl TxTable { /// Assign the `TxTable` from a list of block `Transaction`s, followig the /// same layout that the Tx Circuit uses. + /// NOTE: This function is used by passing the region directly pub fn load_with_region( &self, region: &mut Region<'_, F>, From 64088fa9df61fb4c984404bd28443777085bfaae Mon Sep 17 00:00:00 2001 From: johntaiko Date: Thu, 22 Jun 2023 08:21:16 +0800 Subject: [PATCH 18/20] fix: clippy complain --- gadgets/src/is_equal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gadgets/src/is_equal.rs b/gadgets/src/is_equal.rs index 60c9cb7db8..839d015fad 100644 --- a/gadgets/src/is_equal.rs +++ b/gadgets/src/is_equal.rs @@ -3,7 +3,7 @@ use eth_types::Field; use halo2_proofs::{ circuit::{Chip, Region, Value}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, SecondPhase, VirtualCells}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, VirtualCells}, }; use super::is_zero::{IsZeroChip, IsZeroInstruction}; From 860f63b77a57cbccc41232d5f7e5fb1cd69af84e Mon Sep 17 00:00:00 2001 From: Brechtpd Date: Thu, 22 Jun 2023 12:47:53 +0200 Subject: [PATCH 19/20] Revert some formatting only changes --- zkevm-circuits/Cargo.toml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 8e71c6840f..263f9b377b 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", features = ["circuit-params",], tag = "v2023_04_20" } +halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", features = ["circuit-params"], tag = "v2023_04_20" } num = "0.4" sha3 = "0.10" array-init = "2.0.0" @@ -24,20 +24,17 @@ rand_xorshift = "0.3" rand = "0.8" itertools = "0.10.3" lazy_static = "1.4" -keccak256 = { path = "../keccak256" } +keccak256 = { path = "../keccak256"} log = "0.4" env_logger = "0.9" ecdsa = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } -ecc = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } -maingate = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } -integer = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } +ecc = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } +maingate = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } +integer = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2023_04_20" } libsecp256k1 = "0.7" num-bigint = { version = "0.4" } rand_chacha = "0.3" -snark-verifier = { git = "https://github.com/privacy-scaling-explorations/snark-verifier", tag = "v2023_04_20", default-features = false, features = [ - "loader_halo2", - "system_halo2", -] } +snark-verifier = { git = "https://github.com/privacy-scaling-explorations/snark-verifier", tag = "v2023_04_20", default-features = false, features = ["loader_halo2", "system_halo2"] } cli-table = { version = "0.4", optional = true } once_cell = "1.17.1" @@ -61,4 +58,4 @@ stats = ["warn-unimplemented", "dep:cli-table"] [[bin]] name = "stats" -required-features = ["stats"] +required-features = ["stats"] \ No newline at end of file From ceff858f3464127d7699b5afb9b3908dda41afc6 Mon Sep 17 00:00:00 2001 From: Brechtpd Date: Thu, 22 Jun 2023 12:48:34 +0200 Subject: [PATCH 20/20] Simplify byte lookup --- zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs index 59ac638321..5db43201ef 100644 --- a/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/anchor_tx_circuit/sign_verify.rs @@ -51,7 +51,7 @@ use ethers_signers::LocalWallet; use gadgets::{ is_equal::{IsEqualChip, IsEqualConfig, IsEqualInstruction}, mul_add::{MulAddChip, MulAddConfig}, - util::{or, split_u256, Expr}, + util::{split_u256, Expr}, }; use halo2_proofs::{ circuit::{Layouter, Region, Value}, @@ -310,16 +310,13 @@ impl SignVerifyConfig { }, ); - // is byte - meta.lookup_any("sig is byte", |meta| { - let q_sig_step = meta.query_selector(q_sig_start); - let q_sig_end = meta.query_selector(q_sig_end); - let is_field = or::expr([q_sig_step, q_sig_end]); + // Range constraint all bytes + meta.lookup_any("ensure all bytes are actually byte values", |meta| { let rpi_field_bytes = meta.query_advice(sig, Rotation::cur()); [rpi_field_bytes] .into_iter() .zip(byte_table.table_exprs(meta).into_iter()) - .map(|(arg, table)| (is_field.expr() * arg, table)) + .map(|(arg, table)| (arg, table)) .collect::>() });