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, }