From ecbc924a0fd00516a9949a6c579982311f959529 Mon Sep 17 00:00:00 2001 From: KimiWu Date: Thu, 25 Jan 2024 17:52:01 +0900 Subject: [PATCH] feat: using sign_verify for sig_circuit, compilation error free --- bus-mapping/src/circuit_input_builder.rs | 8 + .../src/integration_test_circuits.rs | 3 + testool/src/statetest/executor.rs | 2 + zkevm-circuits/src/root_circuit/test.rs | 1 + zkevm-circuits/src/sig_circuit.rs | 1548 ++++++++--------- zkevm-circuits/src/sig_circuit/dev.rs | 12 +- zkevm-circuits/src/sig_circuit/ecdsa.rs | 226 --- zkevm-circuits/src/sig_circuit/test.rs | 333 ++-- zkevm-circuits/src/sig_circuit/utils.rs | 178 +- zkevm-circuits/src/super_circuit/test.rs | 3 + 10 files changed, 985 insertions(+), 1329 deletions(-) delete mode 100644 zkevm-circuits/src/sig_circuit/ecdsa.rs diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 7033c092fb4..23e40a8be20 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -76,6 +76,11 @@ pub struct FixedCParams { /// calculated, so the same circuit will not be able to prove different /// witnesses. pub max_keccak_rows: usize, + /// This number indicate what 100% usage means, for example if we can support up to 2 + /// ecPairing inside circuit, and max_vertical_circuit_rows is set to 1_000_000, + /// then if there is 1 ecPairing in the input, we will return 500_000 as the "row usage" + /// for the ec circuit. + pub max_vertical_circuit_rows: usize, } /// Unset Circuits Parameters @@ -117,6 +122,7 @@ impl Default for FixedCParams { max_bytecode: 512, max_evm_rows: 0, max_keccak_rows: 0, + max_vertical_circuit_rows: 0, } } } @@ -435,6 +441,7 @@ impl CircuitInputBuilder { // When the evm circuit receives a 0 value it dynamically computes the minimum // number of rows necessary. let max_evm_rows = 0; + let max_vertical_circuit_rows = 0; // Similarly, computing the number of rows for the Keccak circuit requires // constants that cannot be accessed from here (NUM_ROUNDS and KECCAK_ROWS). // With a 0 value the keccak circuit computes dynamically the minimum number of rows @@ -450,6 +457,7 @@ impl CircuitInputBuilder { max_bytecode, max_evm_rows, max_keccak_rows, + max_vertical_circuit_rows, } }; let mut cib = CircuitInputBuilder:: { diff --git a/integration-tests/src/integration_test_circuits.rs b/integration-tests/src/integration_test_circuits.rs index dc8c30f2d2e..2c83f5ef841 100644 --- a/integration-tests/src/integration_test_circuits.rs +++ b/integration-tests/src/integration_test_circuits.rs @@ -66,6 +66,8 @@ const MAX_EVM_ROWS: usize = 10000; const MAX_EXP_STEPS: usize = 1000; const MAX_KECCAK_ROWS: usize = 38000; +/// MAX_VERTICAL_CIRCUIT_ROWS +const MAX_VERTICAL_CIRCUIT_ROWS: usize = 0; const CIRCUITS_PARAMS: FixedCParams = FixedCParams { max_rws: MAX_RWS, @@ -77,6 +79,7 @@ const CIRCUITS_PARAMS: FixedCParams = FixedCParams { max_evm_rows: MAX_EVM_ROWS, max_exp_steps: MAX_EXP_STEPS, max_keccak_rows: MAX_KECCAK_ROWS, + max_vertical_circuit_rows: MAX_VERTICAL_CIRCUIT_ROWS, }; const EVM_CIRCUIT_DEGREE: u32 = 18; diff --git a/testool/src/statetest/executor.rs b/testool/src/statetest/executor.rs index a0b2dd98fd8..4cce1b680a5 100644 --- a/testool/src/statetest/executor.rs +++ b/testool/src/statetest/executor.rs @@ -337,6 +337,7 @@ pub fn run_test( max_evm_rows: 0, max_exp_steps: 5000, max_keccak_rows: 0, + max_vertical_circuit_rows: 0, }; let block_data = BlockData::new_from_geth_data_with_params(geth_data, circuits_params); @@ -375,6 +376,7 @@ pub fn run_test( max_bytecode: 512, max_evm_rows: 0, max_keccak_rows: 0, + max_vertical_circuit_rows: 0, }; let (k, circuit, instance, _builder) = SuperCircuit::::build(geth_data, circuits_params, Fr::from(0x100)).unwrap(); diff --git a/zkevm-circuits/src/root_circuit/test.rs b/zkevm-circuits/src/root_circuit/test.rs index 1270751165c..72158390506 100644 --- a/zkevm-circuits/src/root_circuit/test.rs +++ b/zkevm-circuits/src/root_circuit/test.rs @@ -32,6 +32,7 @@ fn test_root_circuit() { max_bytecode: 512, max_evm_rows: 0, max_keccak_rows: 0, + max_vertical_circuit_rows: 0, }; let (k, circuit, instance, _) = SuperCircuit::<_>::build(block_1tx(), circuits_params, TEST_MOCK_RANDOMNESS.into()) diff --git a/zkevm-circuits/src/sig_circuit.rs b/zkevm-circuits/src/sig_circuit.rs index 68a1e9e11b9..a37e90e21a6 100644 --- a/zkevm-circuits/src/sig_circuit.rs +++ b/zkevm-circuits/src/sig_circuit.rs @@ -1,13 +1,8 @@ //! Circuit to verify multiple ECDSA secp256k1 signatures. -// This module uses halo2-ecc's ecdsa chip -// - to prove the correctness of secp signatures -// - to compute the RLC in circuit -// - to perform keccak lookup table -// + // Naming notes: // - *_be: Big-Endian bytes // - *_le: Little-Endian bytes - #[cfg(any(feature = "test", test, feature = "test-circuits"))] mod dev; #[cfg(any(feature = "test", test, feature = "test-circuits"))] @@ -15,738 +10,794 @@ mod test; use crate::{ evm_circuit::{ - util::{not, rlc}, + util::{from_bytes, not, rlc}, EvmCircuit, }, keccak_circuit::KeccakCircuit, - sig_circuit::ecdsa::ecdsa_verify_no_pubkey_check, table::{KeccakTable, SigTable}, - util::{Challenges, Expr, SubCircuit, SubCircuitConfig}, + util::{word::Word, Challenges, Expr, SubCircuit, SubCircuitConfig}, }; +use ecc::{maingate, EccConfig, GeneralEccChip}; +use ecdsa::ecdsa::{AssignedEcdsaSig, AssignedPublicKey, EcdsaChip}; use eth_types::{ - self, + self, keccak256, sign_types::{pk_bytes_le, pk_bytes_swap_endianness, SignData}, Field, }; -use halo2_base::{ - gates::{range::RangeConfig, GateInstructions, RangeInstructions}, - utils::modulus, - AssignedValue, Context, QuantumCell, SKIP_FIRST_PASS, -}; -use halo2_ecc::{ - bigint::CRTInteger, - ecc::EccChip, - fields::{ - fp::{FpConfig, FpStrategy}, - FieldChip, - }, -}; - -mod ecdsa; -mod utils; -#[cfg(any(feature = "test", test, feature = "test-circuits"))] -pub(crate) use utils::*; - use halo2_proofs::{ - circuit::{Layouter, Value}, - halo2curves::secp256k1::{Fp, Fq, Secp256k1Affine}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + circuit::{AssignedCell, Cell, Layouter, Value}, + halo2curves::{ff::PrimeField, secp256k1, secp256k1::Secp256k1Affine}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, SecondPhase, Selector}, poly::Rotation, }; +use integer::{AssignedInteger, IntegerChip, IntegerInstructions, Range}; -use ethers_core::utils::keccak256; use itertools::Itertools; use log::error; +use maingate::{ + AssignedValue, MainGate, MainGateConfig, MainGateInstructions, RangeChip, RangeConfig, + RangeInstructions, RegionCtx, +}; +use num::Integer; use std::{iter, marker::PhantomData}; -/// Circuit configuration arguments +mod utils; +use utils::*; +/// Auxiliary Gadget to verify a that a message hash is signed by the public +/// key corresponding to an Ethereum Address. +#[derive(Clone, Debug)] +pub struct SigCircuit { + /// Signatures + pub signatures: Vec, + /// Aux generator for EccChip + pub aux_generator: Secp256k1Affine, + /// Window size for EccChip + pub window_size: usize, + /// Max number of verifications + pub max_verif: usize, + /// Marker + pub _marker: PhantomData, +} + +impl SubCircuit for SigCircuit { + type Config = SigCircuitConfig; + + fn new_from_block(block: &crate::witness::Block) -> Self { + assert!(block.circuits_params.max_txs <= MAX_NUM_SIG); + + SigCircuit { + signatures: block.get_sign_data(true), + aux_generator: Secp256k1Affine::default(), + window_size: 4, + max_verif: MAX_NUM_SIG, + _marker: Default::default(), + } + } + + // Since sig circuit / halo2-lib use vertical cell assignment, + // so the returned pair is consisted of same values + fn min_num_rows_block(block: &crate::witness::Block) -> (usize, usize) { + let ecdsa_verif_count = + block.txs.iter().count() + block.precompile_events.get_ecrecover_events().len(); + // Reserve one ecdsa verification for padding tx such that the bad case in which some tx + // calls MAX_NUM_SIG - 1 ecrecover precompile won't happen. If that case happens, the sig + // circuit won't have more space for the padding tx's ECDSA verification. Then the + // prover won't be able to produce any valid proof. + let max_num_verif = MAX_NUM_SIG - 1; + + let row_num = if block.circuits_params.max_vertical_circuit_rows == 0 { + Self::min_num_rows(ecdsa_verif_count) + } else { + block.circuits_params.max_vertical_circuit_rows + }; + // Instead of showing actual minimum row usage, + // halo2-lib based circuits use min_row_num to represent a percentage of total-used capacity + // This functionality allows l2geth to decide if additional ops can be added. + let min_row_num = (row_num / max_num_verif) * ecdsa_verif_count; + + (min_row_num, row_num) + } + + /// Returns number of unusable rows of the SubCircuit, which should be + /// `meta.blinding_factors() + 1`. + fn unusable_rows() -> usize { + [ + KeccakCircuit::::unusable_rows(), + EvmCircuit::::unusable_rows(), + // may include additional subcircuits here + ] + .into_iter() + .max() + .unwrap() + } + + fn synthesize_sub( + &self, + config: &Self::Config, + challenges: &Challenges>, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + config.load_range(layouter)?; + self.assign(config, layouter, &self.signatures, challenges)?; + Ok(()) + } +} + +impl Default for SigCircuit { + fn default() -> Self { + Self { + signatures: vec![], + aux_generator: Secp256k1Affine::default(), + window_size: 4, + max_verif: 0, + _marker: PhantomData::default(), + } + } +} + +const NUMBER_OF_LIMBS: usize = 4; +const BIT_LEN_LIMB: usize = 72; +const BIT_LEN_LAST_LIMB: usize = 256 - (NUMBER_OF_LIMBS - 1) * BIT_LEN_LIMB; + +/// Arguments accepted to configure the EccCircuitConfig. +#[derive(Clone, Debug)] pub struct SigCircuitConfigArgs { - /// KeccakTable - pub keccak_table: KeccakTable, /// SigTable - pub sig_table: SigTable, - /// Challenges + sig_table: SigTable, + /// Keccak + keccak_table: KeccakTable, + + /// zkEVM challenge API. pub challenges: Challenges>, } /// SignVerify Configuration #[derive(Debug, Clone)] -pub struct SigCircuitConfig -where - F: Field + halo2_base::utils::ScalarField, -{ - /// ECDSA - ecdsa_config: FpChip, - // ecdsa_config: FpConfig, - /// An advice column to store RLC witnesses - rlc_column: Column, - /// selector for keccak lookup table - q_keccak: Selector, - /// Used to lookup pk->pk_hash(addr) - keccak_table: KeccakTable, - /// The exposed table to be used by tx circuit and ecrecover +pub struct SigCircuitConfig { + // ECDSA + main_gate_config: MainGateConfig, + range_config: RangeConfig, + // RLC + q_rlc_keccak_input: Selector, + rlc: Column, + /// SigTable sig_table: SigTable, + // Keccak + q_keccak: Selector, + _keccak_table: KeccakTable, + + _marker: PhantomData, } -impl SubCircuitConfig for SigCircuitConfig -where - F: Field + halo2_base::utils::ScalarField, -{ +impl SubCircuitConfig for SigCircuitConfig { type ConfigArgs = SigCircuitConfigArgs; - /// Return a new SigConfig fn new( meta: &mut ConstraintSystem, Self::ConfigArgs { - keccak_table, sig_table, - challenges: _, + keccak_table, + challenges, }: Self::ConfigArgs, ) -> Self { - #[cfg(feature = "onephase")] - let num_advice = [calc_required_advices(MAX_NUM_SIG)]; - #[cfg(not(feature = "onephase"))] - // need an additional phase 2 column/basic gate to hold the witnesses during RLC - // computations - let num_advice = [calc_required_advices(MAX_NUM_SIG), 1]; - - let num_lookup_advice = [calc_required_lookup_advices(MAX_NUM_SIG)]; - - #[cfg(feature = "onephase")] - log::info!("configuring ECDSA chip with single phase"); - #[cfg(not(feature = "onephase"))] - log::info!("configuring ECDSA chip with multiple phases"); - - // halo2-ecc's ECDSA config - // - // get the following parameters by running - // `cargo test --release --package zkevm-circuits --lib sig_circuit::test::sign_verify -- - // --nocapture` - // - num_advice: 56 - // - num_lookup_advice: 8 - // - num_fixed: 1 - // - lookup_bits: 19 - // - limb_bits: 88 - // - num_limbs: 3 - // - // TODO: make those parameters tunable from a config file - let ecdsa_config = FpConfig::configure( + // ECDSA config + let (rns_base, rns_scalar) = + GeneralEccChip::::rns(); + let main_gate_config = MainGate::::configure(meta); + let range_config = RangeChip::::configure( meta, - FpStrategy::Simple, - &num_advice, - &num_lookup_advice, - 1, - LOG_TOTAL_NUM_ROWS - 1, - 88, - 3, - modulus::(), - 0, - LOG_TOTAL_NUM_ROWS, // maximum k of the chip + &main_gate_config, + vec![BIT_LEN_LIMB / NUMBER_OF_LIMBS, 8], + [rns_base.overflow_lengths(), rns_scalar.overflow_lengths()].concat(), ); - // we need one phase 2 column to store RLC results - #[cfg(feature = "onephase")] - let rlc_column = meta.advice_column_in(halo2_proofs::plonk::FirstPhase); - #[cfg(not(feature = "onephase"))] - let rlc_column = meta.advice_column_in(halo2_proofs::plonk::SecondPhase); + // RLC + let q_rlc_keccak_input = meta.selector(); + let rlc = meta.advice_column_in(SecondPhase); + meta.enable_equality(rlc); - meta.enable_equality(rlc_column); + Self::configure_rlc( + meta, + "keccak_input_rlc", + main_gate_config.clone(), + q_rlc_keccak_input, + rlc, + challenges.keccak_input(), + ); + // sig_table meta.enable_equality(sig_table.recovered_addr); - meta.enable_equality(sig_table.sig_r_rlc); - meta.enable_equality(sig_table.sig_s_rlc); + meta.enable_equality(sig_table.sig_r.lo()); + meta.enable_equality(sig_table.sig_r.hi()); + meta.enable_equality(sig_table.sig_s.lo()); + meta.enable_equality(sig_table.sig_s.hi()); meta.enable_equality(sig_table.sig_v); meta.enable_equality(sig_table.is_valid); - meta.enable_equality(sig_table.msg_hash_rlc); + meta.enable_equality(sig_table.msg_hash.lo()); + meta.enable_equality(sig_table.msg_hash.hi()); - // Ref. spec SignVerifyChip 1. Verify that keccak(pub_key_bytes) = pub_key_hash + // Ref. spec SigCircuit 1. Verify that keccak(pub_key_bytes) = pub_key_hash // by keccak table lookup, where pub_key_bytes is built from the pub_key // in the ecdsa_chip. let q_keccak = meta.complex_selector(); - - meta.lookup_any("keccak lookup table", |meta| { + meta.lookup_any("keccak", |meta| { // When address is 0, we disable the signature verification by using a dummy pk, - // msg_hash and signature which is not constrained to match msg_hash_rlc nor - // the address. + // msg_hash and signature which is not constrained to match msg_hash nor the address. // Layout: - // | q_keccak | rlc | - // | -------- | --------------- | - // | 1 | is_address_zero | - // | | pk_rlc | - // | | pk_hash_rlc | + // | q_keccak | a | b | c | rlc | + // | -------- | --------------- |--------- | --------- | ------- | + // | 1 | is_addr_zero | word_lo | word_hi | pk_rlc | let q_keccak = meta.query_selector(q_keccak); - let is_address_zero = meta.query_advice(rlc_column, Rotation::cur()); + let is_address_zero = meta.query_advice(main_gate_config.advices()[0], Rotation::cur()); let is_enable = q_keccak * not::expr(is_address_zero); - + let word_lo = meta.query_advice(main_gate_config.advices()[1], Rotation::cur()); + let word_hi = meta.query_advice(main_gate_config.advices()[2], Rotation::cur()); let input = [ is_enable.clone(), - is_enable.clone(), - is_enable.clone() * meta.query_advice(rlc_column, Rotation(1)), + is_enable.clone() * meta.query_advice(rlc, Rotation::cur()), is_enable.clone() * 64usize.expr(), - is_enable * meta.query_advice(rlc_column, Rotation(2)), + is_enable.clone() * word_lo, + is_enable * word_hi, ]; let table = [ - meta.query_fixed(keccak_table.q_enable, Rotation::cur()), - meta.query_advice(keccak_table.is_final, Rotation::cur()), - meta.query_advice(keccak_table.input_rlc, Rotation::cur()), - meta.query_advice(keccak_table.input_len, Rotation::cur()), - meta.query_advice(keccak_table.output_rlc, Rotation::cur()), - ]; + keccak_table.is_enabled, + keccak_table.input_rlc, + keccak_table.input_len, + keccak_table.output.lo(), + keccak_table.output.hi(), + ] + .map(|column| meta.query_advice(column, Rotation::cur())); input.into_iter().zip(table).collect() }); Self { - ecdsa_config, - keccak_table, - sig_table, + range_config, + main_gate_config, + q_rlc_keccak_input, + rlc, q_keccak, - rlc_column, + _keccak_table: keccak_table, + sig_table, + _marker: PhantomData, } } } -impl SigCircuitConfig { +impl SigCircuitConfig { + fn configure_rlc( + meta: &mut ConstraintSystem, + name: &'static str, + main_gate_config: MainGateConfig, + q_rlc: Selector, + rlc: Column, + challenge: Expression, + ) { + // Layout (take input with length 12 as an example) + // | q_rlc | rlc | a | b | c | + // d | e | | ----- | --------------------------------------------------- | + // ----- | ----- | ----- | ------ | ------ | | 1 | + // 0 | 0 | 0 | 0 | be[0] | be[1] | | 1 | + // be[0]*r^1 + be[1] | be[2] | be[3] | be[4] | be[5] | be[6] | | 1 | be[0]* + // r^6 + be[1]*r^5 + ... + be[5]*r^1 + be[6] | be[7] | be[8] | be[9] | be[10] | be[11] | + // | 0 | be[0]*r^11 + be[1]*r^10 + ... + be[10]*r^1 + be[11] | | | | + // | | + // + // Note that the first row of zeros will be enforced by copy constraint. + meta.create_gate(name, |meta| { + let q_rlc = meta.query_selector(q_rlc); + let [a, b, c, d, e] = main_gate_config + .advices() + .map(|column| meta.query_advice(column, Rotation::cur())); + let [rlc, rlc_next] = [Rotation::cur(), Rotation::next()] + .map(|rotation| meta.query_advice(rlc, rotation)); + let inputs = [e, d, c, b, a, rlc]; + + vec![q_rlc * (rlc_next - rlc::expr(&inputs, challenge))] + }); + } + pub(crate) fn load_range(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - self.ecdsa_config.range.load_lookup_table(layouter) + let range_chip = RangeChip::::new(self.range_config.clone()); + range_chip.load_table(layouter) } -} -/// Verify a message hash is signed by the public -/// key corresponding to an Ethereum Address. -#[derive(Clone, Debug, Default)] -pub struct SigCircuit { - /// Max number of verifications - pub max_verif: usize, - /// Without padding - pub signatures: Vec, - /// Marker - pub _marker: PhantomData, + pub(crate) fn ecc_chip_config(&self) -> EccConfig { + EccConfig::new(self.range_config.clone(), self.main_gate_config.clone()) + } } -impl SubCircuit for SigCircuit { - type Config = SigCircuitConfig; +/// Term provides a wrapper of possible assigned cell with value or unassigned +/// value. It's similar to `AssignedCell` but with explicitly set value. +/// +/// The reason to use `Term` instead of `AssignedCell` is because the value of +/// `AssignedCell` will always be `Value::unknown()` if the columns is not in +/// current phase, even the value assigned is not. And this behavior is due to +/// the fact that the `to` function in `assign_fixed` and `assign_advice` is +/// `FnMut` and will be guaranteed to be only called once. +#[derive(Clone, Debug)] +pub(crate) enum Term { + Assigned(Cell, Value), + _Unassigned(Value), +} - fn new_from_block(block: &crate::witness::Block) -> Self { - assert!(block.circuits_params.max_txs <= MAX_NUM_SIG); +impl Term { + fn assigned(cell: Cell, value: Value) -> Self { + Self::Assigned(cell, value) + } - SigCircuit { - max_verif: MAX_NUM_SIG, - signatures: block.get_sign_data(true), - _marker: Default::default(), + fn cell(&self) -> Option { + match self { + Self::Assigned(cell, _) => Some(*cell), + Self::_Unassigned(_) => None, } } - /// Returns number of unusable rows of the SubCircuit, which should be - /// `meta.blinding_factors() + 1`. - fn unusable_rows() -> usize { - [ - KeccakCircuit::::unusable_rows(), - EvmCircuit::::unusable_rows(), - // may include additional subcircuits here - ] - .into_iter() - .max() - .unwrap() + fn value(&self) -> Value { + match self { + Self::Assigned(_, value) => *value, + Self::_Unassigned(value) => *value, + } } +} - fn synthesize_sub( - &self, - config: &Self::Config, - challenges: &Challenges>, - layouter: &mut impl Layouter, - ) -> Result<(), Error> { - config.ecdsa_config.range.load_lookup_table(layouter)?; - self.assign(config, layouter, &self.signatures, challenges)?; - Ok(()) - } +pub(crate) struct AssignedECDSA { + pk_x_le: [AssignedValue; 32], + pk_y_le: [AssignedValue; 32], + msg_hash_le: [AssignedValue; 32], - // Since sig circuit / halo2-lib use veticle cell assignment, - // so the returned pair is consisted of same values - fn min_num_rows_block(block: &crate::witness::Block) -> (usize, usize) { - let row_num = if block.circuits_params.max_vertical_circuit_rows == 0 { - Self::min_num_rows() - } else { - block.circuits_params.max_vertical_circuit_rows - }; + r_le: [AssignedValue; 32], + s_le: [AssignedValue; 32], + v: AssignedValue, +} - let ecdsa_verif_count = block - .txs - .iter() - .filter(|tx| !tx.tx_type.is_l1_msg()) - .count() - + block.precompile_events.get_ecrecover_events().len(); - // Reserve one ecdsa verification for padding tx such that the bad case in which some tx - // calls MAX_NUM_SIG - 1 ecrecover precompile won't happen. If that case happens, the sig - // circuit won't have more space for the padding tx's ECDSA verification. Then the - // prover won't be able to produce any valid proof. - let max_num_verif = MAX_NUM_SIG - 1; +#[derive(Debug)] +pub(crate) struct AssignedSignatureVerify { + pub(crate) address: AssignedValue, + pub(crate) msg_hash: Word>, - // Instead of showing actual minimum row usage, - // halo2-lib based circuits use min_row_num to represent a percentage of total-used capacity - // This functionality allows l2geth to decide if additional ops can be added. - let min_row_num = (row_num / max_num_verif) * ecdsa_verif_count; + r: Word>, + s: Word>, + v: AssignedValue, - (min_row_num, row_num) - } + is_valid: AssignedValue, } -impl SigCircuit { - /// Return a new SigCircuit - pub fn new(max_verif: usize) -> Self { - Self { - max_verif, - signatures: Vec::new(), - _marker: PhantomData, - } - } +// Return an array of bytes that corresponds to the little endian representation +// of the integer, adding the constraints to verify the correctness of the +// conversion (byte range check included). +fn integer_to_bytes_le( + ctx: &mut RegionCtx<'_, F>, + range_chip: &RangeChip, + int: &AssignedInteger, +) -> Result<[AssignedValue; 32], Error> { + let bytes = int + .limbs() + .iter() + .zip_eq([BIT_LEN_LIMB, BIT_LEN_LIMB, BIT_LEN_LIMB, BIT_LEN_LAST_LIMB]) + .map(|(limb, bit_len)| { + range_chip + .decompose(ctx, limb.as_ref().value().copied(), 8, bit_len) + .map(|(_, byte)| byte) + }) + .collect::, _>>()? + .into_iter() + .flatten() + .collect_vec(); + Ok(bytes.try_into().unwrap()) +} + +/// Helper structure pass around references to all the chips required for an +/// ECDSA verification. +struct ChipsRef<'a, F: Field, const NUMBER_OF_LIMBS: usize, const BIT_LEN_LIMB: usize> { + main_gate: &'a MainGate, + range_chip: &'a RangeChip, + ecc_chip: &'a GeneralEccChip, + scalar_chip: &'a IntegerChip, + base_chip: &'a IntegerChip, + ecdsa_chip: &'a EcdsaChip, +} +impl SigCircuit { /// Return the minimum number of rows required to prove an input of a /// particular size. - pub fn min_num_rows() -> usize { - // SigCircuit can't determine usable rows independently. - // Instead, the blinding area is determined by other advise columns with most counts of - // rotation queries. This value is typically determined by either the Keccak or EVM - // circuit. - - // the cells are allocated vertically, i.e., given a TOTAL_NUM_ROWS * NUM_ADVICE - // matrix, the allocator will try to use all the cells in the first column, then - // the second column, etc. - - let max_blinding_factor = Self::unusable_rows() - 1; + pub fn min_num_rows(num_verif: usize) -> usize { + // The values rows_ecc_chip_aux, rows_ecdsa_chip_verification and + // rows_ecdsa_chip_verification have been obtained from log debugs while running + // the tx circuit with max_txs=1. For example: + // `RUST_LOG=debug RUST_BACKTRACE=1 cargo test tx_circuit_1tx_1max_tx --release + // --all-features -- --nocapture` + // The value rows_range_chip_table has been obtained by patching the halo2 + // library to report the number of rows used in the range chip table + // region. TODO: Figure out a way to get these numbers automatically. + let rows_range_chip_table = 295188; + let rows_ecc_chip_aux = 226; + let rows_ecdsa_chip_verification = 104471; + let rows_signature_address_verify = 76; + std::cmp::max( + rows_range_chip_table, + (rows_ecc_chip_aux + rows_ecdsa_chip_verification + rows_signature_address_verify) + * num_verif, + ) + } - // same formula as halo2-lib's FlexGate - (1 << LOG_TOTAL_NUM_ROWS) - (max_blinding_factor + 3) + fn assign_aux( + &self, + ctx: &mut RegionCtx<'_, F>, + ecc_chip: &mut GeneralEccChip, + ) -> Result<(), Error> { + ecc_chip.assign_aux_generator(ctx, Value::known(self.aux_generator))?; + ecc_chip.assign_aux(ctx, self.window_size, 2)?; + Ok(()) } -} -impl SigCircuit { - /// Verifies the ecdsa relationship. I.e., prove that the signature - /// is (in)valid or not under the given public key and the message hash in - /// the circuit. Does not enforce the signature is valid. - /// - /// Returns the cells for - /// - public keys - /// - message hashes - /// - a boolean whether the signature is correct or not - /// - /// WARNING: this circuit does not enforce the returned value to be true - /// make sure the caller checks this result! fn assign_ecdsa( &self, - ctx: &mut Context, - ecdsa_chip: &FpChip, + ctx: &mut RegionCtx, + chips: &ChipsRef, sign_data: &SignData, - ) -> Result>, Error> { - let gate = ecdsa_chip.gate(); - let zero = gate.load_zero(ctx); - + ) -> Result, Error> { let SignData { signature, pk, - msg: _, msg_hash, } = sign_data; - let (sig_r, sig_s, v) = signature; - - // build ecc chip from Fp chip - let ecc_chip = EccChip::>::construct(ecdsa_chip.clone()); - let pk_assigned = ecc_chip.load_private(ctx, (Value::known(pk.x), Value::known(pk.y))); - let pk_is_valid = ecc_chip.is_on_curve_or_infinity::(ctx, &pk_assigned); - gate.assert_is_const(ctx, &pk_is_valid, F::one()); - - // build Fq chip from Fp chip - let fq_chip = FqChip::construct(ecdsa_chip.range.clone(), 88, 3, modulus::()); - let integer_r = - fq_chip.load_private(ctx, FqChip::::fe_to_witness(&Value::known(*sig_r))); - let integer_s = - fq_chip.load_private(ctx, FqChip::::fe_to_witness(&Value::known(*sig_s))); - let msg_hash = - fq_chip.load_private(ctx, FqChip::::fe_to_witness(&Value::known(*msg_hash))); - - // returns the verification result of ecdsa signature - // - // WARNING: this circuit does not enforce the returned value to be true - // make sure the caller checks this result! - let (sig_is_valid, pk_is_zero, y_coord, y_coord_is_zero) = - ecdsa_verify_no_pubkey_check::( - &ecc_chip.field_chip, - ctx, - &pk_assigned, - &integer_r, - &integer_s, - &msg_hash, - 4, - 4, - ); + let (sig_r, sig_s, _) = signature; + + let ChipsRef { + main_gate: _, + range_chip, + ecc_chip, + scalar_chip, + ecdsa_chip, + base_chip, + } = chips; + + let integer_r = ecc_chip.new_unassigned_scalar(Value::known(*sig_r)); + let integer_s = ecc_chip.new_unassigned_scalar(Value::known(*sig_s)); + let msg_hash = ecc_chip.new_unassigned_scalar(Value::known(*msg_hash)); + + let r_assigned = scalar_chip.assign_integer(ctx, integer_r, Range::Remainder)?; + let s_assigned = scalar_chip.assign_integer(ctx, integer_s, Range::Remainder)?; + let sig = AssignedEcdsaSig { + r: r_assigned.clone(), + s: s_assigned.clone(), + }; + let r_le = integer_to_bytes_le(ctx, range_chip, &r_assigned)?; + let s_le = integer_to_bytes_le(ctx, range_chip, &s_assigned)?; - // ======================================= - // constrains v == y.is_oddness() - // ======================================= - assert!(*v == 0 || *v == 1, "v is not boolean"); + let pk_in_circuit = ecc_chip.assign_point(ctx, Value::known(*pk))?; + let pk_assigned = AssignedPublicKey { + point: pk_in_circuit, + }; + let msg_hash = scalar_chip.assign_integer(ctx, msg_hash, Range::Remainder)?; - // we constrain: - // - v + 2*tmp = y where y is already range checked (88 bits) - // - v is a binary - // - tmp is also < 88 bits (this is crucial otherwise tmp may wrap around and break - // soundness) + // Convert (msg_hash, pk_x, pk_y) integers to little endian bytes + let msg_hash_le = integer_to_bytes_le(ctx, range_chip, &msg_hash)?; + let pk_x = pk_assigned.point.x(); + let pk_x_le = integer_to_bytes_le(ctx, range_chip, pk_x)?; + let pk_y = pk_assigned.point.y(); + let pk_y_le = integer_to_bytes_le(ctx, range_chip, pk_y)?; - let assigned_y_is_odd = gate.load_witness(ctx, Value::known(F::from(*v as u64))); - gate.assert_bit(ctx, assigned_y_is_odd); + // Ref. spec SigCircuit 4. Verify the ECDSA signature + ecdsa_chip.verify(ctx, &sig, &pk_assigned, &msg_hash)?; - // the last 88 bits of y - let assigned_y_limb = &y_coord.limbs()[0]; - let mut y_value = F::zero(); - assigned_y_limb.value().map(|&x| y_value = x); + // assign the sign of y-coordinate which is `v` + let v = base_chip.sign(ctx, pk_y)?; - // y_tmp = (y_value - y_last_bit)/2 - let y_tmp = (y_value - F::from(*v as u64)) * F::TWO_INV; - let assigned_y_tmp = gate.load_witness(ctx, Value::known(y_tmp)); - - // y_tmp_double = (y_value - y_last_bit) - let y_tmp_double = gate.mul( - ctx, - QuantumCell::Existing(assigned_y_tmp), - QuantumCell::Constant(F::from(2)), - ); - let y_rec = gate.add( - ctx, - QuantumCell::Existing(y_tmp_double), - QuantumCell::Existing(assigned_y_is_odd), - ); - let y_is_ok = gate.is_equal( - ctx, - QuantumCell::Existing(*assigned_y_limb), - QuantumCell::Existing(y_rec), - ); - - // last step we want to constrain assigned_y_tmp is 87 bits - let assigned_y_tmp = gate.select( - ctx, - QuantumCell::Existing(zero), - QuantumCell::Existing(assigned_y_tmp), - QuantumCell::Existing(y_coord_is_zero), - ); - ecc_chip - .field_chip - .range - .range_check(ctx, &assigned_y_tmp, 87); - - let y_coord_not_zero = gate.not(ctx, QuantumCell::Existing(y_coord_is_zero)); - let sig_is_valid = gate.and_many( - ctx, - vec![ - QuantumCell::Existing(sig_is_valid), - QuantumCell::Existing(y_is_ok), - QuantumCell::Existing(y_coord_not_zero), - ], - ); + // TODO: Update once halo2wrong supports the following methods: + // - `IntegerChip::assign_integer_from_bytes_le` + // - `GeneralEccChip::assign_point_from_bytes_le` Ok(AssignedECDSA { - pk: pk_assigned, - pk_is_zero, - msg_hash, - integer_r, - integer_s, - v: assigned_y_is_odd, - sig_is_valid, + pk_x_le, + pk_y_le, + msg_hash_le, + r_le, + s_le, + v, }) } - fn enable_keccak_lookup( + #[allow(clippy::too_many_arguments)] + fn assign_rlc_le( &self, config: &SigCircuitConfig, - ctx: &mut Context, - offset: usize, - is_address_zero: &AssignedValue, - pk_rlc: &AssignedValue, - pk_hash_rlc: &AssignedValue, - ) -> Result<(), Error> { - log::trace!("keccak lookup"); - - // Layout: - // | q_keccak | rlc | - // | -------- | --------------- | - // | 1 | is_address_zero | - // | | pk_rlc | - // | | pk_hash_rlc | - config.q_keccak.enable(&mut ctx.region, offset)?; - - // is_address_zero - let tmp_cell = ctx.region.assign_advice( - || "is_address_zero", - config.rlc_column, - offset, - || is_address_zero.value, - )?; - ctx.region - .constrain_equal(is_address_zero.cell, tmp_cell.cell())?; - - // pk_rlc - let tmp_cell = ctx.region.assign_advice( - || "pk_rlc", - config.rlc_column, - offset + 1, - || pk_rlc.value, - )?; - ctx.region.constrain_equal(pk_rlc.cell, tmp_cell.cell())?; - - // pk_hash_rlc - let tmp_cell = ctx.region.assign_advice( - || "pk_hash_rlc", - config.rlc_column, - offset + 2, - || pk_hash_rlc.value, - )?; - ctx.region - .constrain_equal(pk_hash_rlc.cell, tmp_cell.cell())?; + ctx: &mut RegionCtx, + chips: &ChipsRef, + name: &str, + q_rlc: Selector, + challenge: Value, + inputs_le: impl IntoIterator>, + ) -> Result, Error> { + let zero = chips.main_gate.assign_constant(ctx, F::ZERO)?; + let columns = config.main_gate_config.advices(); + let inputs_le = inputs_le.into_iter().collect_vec(); + let inputs_be = iter::repeat_with(|| Term::assigned(zero.cell(), Value::known(F::ZERO))) + .take(Integer::next_multiple_of(&inputs_le.len(), &columns.len()) - inputs_le.len()) + .chain(inputs_le.into_iter().rev()) + .collect_vec(); - log::trace!("finished keccak lookup"); - Ok(()) - } + let mut rlc = Value::known(F::ZERO); + for (chunk_idx, chunk) in inputs_be.chunks_exact(columns.len()).enumerate() { + ctx.enable(q_rlc)?; + let assigned_rlc = ctx.assign_advice(|| "{name}_rlc[{chunk_idx}]", config.rlc, rlc)?; + for ((idx, column), term) in (chunk_idx * chunk.len()..).zip(columns).zip(chunk) { + let copied = + ctx.assign_advice(|| format!("{name}_byte[{idx}]"), column, term.value())?; + if let Some(cell) = term.cell() { + ctx.constrain_equal(cell, copied.cell())?; + } + } + if chunk_idx == 0 { + ctx.constrain_equal(zero.cell(), assigned_rlc.cell())?; + } + rlc = iter::once(rlc) + .chain(chunk.iter().map(|term| term.value())) + .fold(Value::known(F::ZERO), |acc, input| acc * challenge + input); + ctx.next(); + } - /// Input the signature data, - /// Output the cells for byte decomposition of the keys and messages - fn sign_data_decomposition( - &self, - ctx: &mut Context, - ecdsa_chip: &FpChip, - sign_data: &SignData, - assigned_data: &AssignedECDSA>, - ) -> Result, Error> { - // build ecc chip from Fp chip - let ecc_chip = EccChip::>::construct(ecdsa_chip.clone()); - - let zero = ecdsa_chip.range.gate.load_zero(ctx); - - // ================================================ - // step 0. powers of aux parameters - // ================================================ - let powers_of_256 = - iter::successors(Some(F::one()), |coeff| Some(F::from(256) * coeff)).take(32); - let powers_of_256_cells = powers_of_256 - .map(|x| QuantumCell::Constant(x)) - .collect_vec(); + let assigned_rlc = ctx.assign_advice(|| "{name}_rlc", config.rlc, rlc)?; + ctx.next(); - // ================================================ - // pk hash cells - // ================================================ - let pk_le = pk_bytes_le(&sign_data.pk); - let pk_be = pk_bytes_swap_endianness(&pk_le); - let pk_hash = keccak256(pk_be).map(|byte| Value::known(F::from(byte as u64))); - - log::trace!("pk hash {:0x?}", pk_hash); - let pk_hash_cells = pk_hash - .iter() - .map(|&x| QuantumCell::Witness(x)) - .rev() - .collect_vec(); + Ok(assigned_rlc) + } - // address is the random linear combination of the public key - // it is fine to use a phase 1 gate here - let address = ecdsa_chip.range.gate.inner_product( - ctx, - powers_of_256_cells[..20].to_vec(), - pk_hash_cells[..20].to_vec(), - ); - let address = ecdsa_chip.range.gate.select( - ctx, - QuantumCell::Existing(zero), - QuantumCell::Existing(address), - QuantumCell::Existing(assigned_data.pk_is_zero), - ); - let is_address_zero = ecdsa_chip.range.gate.is_equal( - ctx, - QuantumCell::Existing(address), - QuantumCell::Existing(zero), - ); - log::trace!("address: {:?}", address.value()); - - // ================================================ - // message hash cells - // ================================================ - - let assert_crt = |ctx: &mut Context, - bytes: [u8; 32], - crt_integer: &CRTInteger| - -> Result<_, Error> { - let byte_cells: Vec> = bytes - .iter() - .map(|&x| QuantumCell::Witness(Value::known(F::from(x as u64)))) - .collect_vec(); - self.assert_crt_int_byte_repr( - ctx, - &ecdsa_chip.range, - crt_integer, - &byte_cells, - &powers_of_256_cells, - )?; - Ok(byte_cells) + fn enable_keccak_lookup( + &self, + config: &SigCircuitConfig, + ctx: &mut RegionCtx, + is_address_zero: &AssignedCell, + pk_rlc: &AssignedCell, + pk_hash: &Word>, + ) -> Result<(), Error> { + let copy = |ctx: &mut RegionCtx, name, column, assigned: &AssignedCell| { + let copied = ctx.assign_advice(|| name, column, assigned.value().copied())?; + ctx.constrain_equal(assigned.cell(), copied.cell())?; + Ok::<_, Error>(()) }; - // assert the assigned_msg_hash_le is the right decomposition of msg_hash - // msg_hash is an overflowing integer with 3 limbs, of sizes 88, 88, and 80 - let assigned_msg_hash_le = - assert_crt(ctx, sign_data.msg_hash.to_bytes(), &assigned_data.msg_hash)?; - - // ================================================ - // pk cells - // ================================================ - let pk_x_le = sign_data - .pk - .x - .to_bytes() - .iter() - .map(|&x| QuantumCell::Witness(Value::known(F::from_u128(x as u128)))) - .collect_vec(); - let pk_y_le = sign_data - .pk - .y - .to_bytes() - .iter() - .map(|&y| QuantumCell::Witness(Value::known(F::from_u128(y as u128)))) - .collect_vec(); - let pk_assigned = ecc_chip.load_private( - ctx, - (Value::known(sign_data.pk.x), Value::known(sign_data.pk.y)), - ); - - self.assert_crt_int_byte_repr( + ctx.enable(config.q_keccak)?; + copy( ctx, - &ecdsa_chip.range, - &pk_assigned.x, - &pk_x_le, - &powers_of_256_cells, - )?; - self.assert_crt_int_byte_repr( - ctx, - &ecdsa_chip.range, - &pk_assigned.y, - &pk_y_le, - &powers_of_256_cells, + "is_address_zero", + config.main_gate_config.advices()[0], + is_address_zero, )?; - - let assigned_pk_le_selected = [pk_y_le, pk_x_le].concat(); - log::trace!("finished data decomposition"); - - let r_cells = assert_crt( + copy(ctx, "pk_rlc", config.rlc, pk_rlc)?; + copy( ctx, - sign_data.signature.0.to_bytes(), - &assigned_data.integer_r, + "pk_hash_lo", + config.main_gate_config.advices()[1], + &pk_hash.lo(), )?; - let s_cells = assert_crt( + copy( ctx, - sign_data.signature.1.to_bytes(), - &assigned_data.integer_s, + "pk_hash_hi", + config.main_gate_config.advices()[2], + &pk_hash.hi(), )?; + ctx.next(); - Ok(SignDataDecomposed { - pk_hash_cells, - msg_hash_cells: assigned_msg_hash_le, - pk_cells: assigned_pk_le_selected, - address, - is_address_zero, - r_cells, - s_cells, - }) + Ok(()) } #[allow(clippy::too_many_arguments)] - fn assign_sig_verify( + fn assign_signature_verify( &self, - ctx: &mut Context, - rlc_chip: &RangeConfig, - sign_data: &SignData, - sign_data_decomposed: &SignDataDecomposed, + config: &SigCircuitConfig, + ctx: &mut RegionCtx, + chips: &ChipsRef, + sign_data: Option<&SignData>, + assigned_ecdsa: &AssignedECDSA, challenges: &Challenges>, - assigned_ecdsa: &AssignedECDSA>, - ) -> Result<([AssignedValue; 3], AssignedSignatureVerify), Error> { - // ================================================ - // step 0. powers of aux parameters - // ================================================ - let evm_challenge_powers = iter::successors(Some(Value::known(F::one())), |coeff| { - Some(challenges.evm_word() * coeff) - }) - .take(32) - .map(|x| QuantumCell::Witness(x)) - .collect_vec(); + ) -> Result, Error> { + let main_gate = chips.main_gate; + let range_chip = chips.range_chip; - log::trace!("evm challenge: {:?} ", challenges.evm_word()); + let (padding, sign_data) = match sign_data { + Some(sign_data) => (false, sign_data.clone()), + None => (true, SignData::default()), + }; - let keccak_challenge_powers = iter::successors(Some(Value::known(F::one())), |coeff| { - Some(challenges.keccak_input() * coeff) - }) - .take(64) - .map(|x| QuantumCell::Witness(x)) - .collect_vec(); - // ================================================ - // step 1 random linear combination of message hash - // ================================================ - // Ref. spec SignVerifyChip 3. Verify that the signed message in the ecdsa_chip - // with RLC encoding corresponds to msg_hash_rlc - let msg_hash_rlc = rlc_chip.gate.inner_product( - ctx, - sign_data_decomposed - .msg_hash_cells - .iter() - .take(32) - .cloned() - .collect_vec(), - evm_challenge_powers.clone(), - ); + let pk_be = pk_bytes_swap_endianness(&pk_bytes_le(&sign_data.pk)); + let mut pk_hash = (!padding).then(|| keccak256(&pk_be)).unwrap_or_default(); + pk_hash.reverse(); - log::trace!("assigned msg hash rlc: {:?}", msg_hash_rlc.value()); + let powers_of_256 = iter::successors(Some(F::ONE), |coeff| Some(F::from(256) * coeff)) + .take(16) + .collect_vec(); - // ================================================ - // step 2 random linear combination of pk - // ================================================ - let pk_rlc = rlc_chip.gate.inner_product( - ctx, - sign_data_decomposed.pk_cells.clone(), - keccak_challenge_powers, - ); - log::trace!("pk rlc: {:?}", pk_rlc.value()); + // Ref. spec SigCircuit 2. Verify that the first 20 bytes of the + // pub_key_hash equal the address + let (address_cells, pk_hash_cells) = { + // Diagram of byte decomposition of little-endian pk_hash, and how address is built + // from it: + // + // byte 0 15 16 20 21 32 + // [ address_lo ] [ address_hi ] [ ] + // [ pk_hash_lo ] [ pk_hash_hi ] + + let pk_hash_lo_bytes = &pk_hash[..16]; + let pk_hash_hi_bytes = &pk_hash[16..]; + let pk_hash_lo = from_bytes::value::(pk_hash_lo_bytes); + let pk_hash_hi = from_bytes::value::(pk_hash_hi_bytes); + // Assign all bytes of pk_hash to cells which are range constrained to be 8 bits. Then + // constrain the lower 16 cell bytes to build the lo cell, and the higher 16 bytes to + // build the hi cell. + let (pk_hash_cell_lo, pk_hash_lo_cell_bytes) = + range_chip.decompose(ctx, Value::known(pk_hash_lo), 8, 128)?; + let (pk_hash_cell_hi, pk_hash_hi_cell_bytes) = + range_chip.decompose(ctx, Value::known(pk_hash_hi), 8, 128)?; + + // Take the 20 lowest assigned byte cells of pk_hash and constrain them to build + // address. We don't need lo/hi for an address so we concat assigned pk_hash bytes here. + let powers_of_256_20_bytes = + iter::successors(Some(F::ONE), |coeff| Some(F::from(256) * coeff)) + .take(20) + .collect_vec(); + let pk_hash_bytes = &iter::empty() + .chain(pk_hash_lo_cell_bytes) + .chain(pk_hash_hi_cell_bytes) + .collect_vec()[0..20]; + let (address_cell, _) = main_gate.decompose( + ctx, + &pk_hash_bytes + .iter() + .zip_eq(&powers_of_256_20_bytes) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + // let (address_cell_lo, _) = main_gate.decompose( + // ctx, + // &pk_hash_lo_cell_bytes + // .iter() + // .zip_eq(&powers_of_256) + // .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + // .collect_vec(), + // F::ZERO, + // |_, _| Ok(()), + // )?; + // let (address_cell_hi, _) = main_gate.decompose( + // ctx, + // &pk_hash_hi_cell_bytes + // .iter() + // .take(N_BYTES_ACCOUNT_ADDRESS - 16) + // .zip(&powers_of_256) + // .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + // .collect_vec(), + // F::ZERO, + // |_, _| Ok(()), + // )?; + + (address_cell, Word::new([pk_hash_cell_lo, pk_hash_cell_hi])) + }; - // ================================================ - // step 3 random linear combination of pk_hash - // ================================================ - let pk_hash_rlc = rlc_chip.gate.inner_product( - ctx, - sign_data_decomposed.pk_hash_cells.clone(), - evm_challenge_powers.clone(), - ); + let is_address_zero = main_gate.is_zero(ctx, &address_cells)?; + // let iz_zero_lo = main_gate.is_zero(ctx, &address_cells.lo())?; + // let is_address_zero = main_gate.and(ctx, &iz_zero_lo, &iz_zero_hi)?; - // step 4: r,s rlc - let r_rlc = rlc_chip.gate.inner_product( - ctx, - sign_data_decomposed.r_cells.clone(), - evm_challenge_powers.clone(), - ); - let s_rlc = rlc_chip.gate.inner_product( - ctx, - sign_data_decomposed.s_cells.clone(), - evm_challenge_powers, - ); + // Ref. spec SigCircuit 3. Verify that the signed message in the ecdsa_chip + // corresponds to msg_hash + let msg_hash_cells = { + let msg_hash_lo_cell_bytes = &assigned_ecdsa.msg_hash_le[..16]; + let msg_hash_hi_cell_bytes = &assigned_ecdsa.msg_hash_le[16..]; + let (msg_hash_cell_lo, _) = main_gate.decompose( + ctx, + &msg_hash_lo_cell_bytes + .iter() + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + let (msg_hash_cell_hi, _) = main_gate.decompose( + ctx, + &msg_hash_hi_cell_bytes + .iter() + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + + Word::new([msg_hash_cell_lo, msg_hash_cell_hi]) + }; - log::trace!("pk hash rlc halo2ecc: {:?}", pk_hash_rlc.value()); - log::trace!("finished sign verify"); - let to_be_keccak_checked = [sign_data_decomposed.is_address_zero, pk_rlc, pk_hash_rlc]; - let assigned_sig_verif = AssignedSignatureVerify { - address: sign_data_decomposed.address, - msg_len: sign_data.msg.len(), - msg_rlc: challenges - .keccak_input() - .map(|r| rlc::value(sign_data.msg.iter().rev(), r)), - msg_hash_rlc, - sig_is_valid: assigned_ecdsa.sig_is_valid, - r_rlc, - s_rlc, - v: assigned_ecdsa.v, + let pk_rlc = { + let assigned_pk_le = iter::empty() + .chain(&assigned_ecdsa.pk_y_le) + .chain(&assigned_ecdsa.pk_x_le); + let pk_le = iter::empty() + .chain(sign_data.pk.y.to_bytes()) + .chain(sign_data.pk.x.to_bytes()) + .map(|byte| Value::known(F::from(byte as u64))); + self.assign_rlc_le( + config, + ctx, + chips, + "pk_hash", + config.q_rlc_keccak_input, + challenges.keccak_input(), + assigned_pk_le + .zip(pk_le) + .map(|(assigned, byte)| Term::assigned(assigned.cell(), byte)), + )? }; - Ok((to_be_keccak_checked, assigned_sig_verif)) + + let (r, s) = { + let r_lo_cell_bytes = &assigned_ecdsa.r_le[..16]; + let r_hi_cell_bytes = &assigned_ecdsa.r_le[16..]; + let s_lo_cell_bytes = &assigned_ecdsa.s_le[..16]; + let s_hi_cell_bytes = &assigned_ecdsa.s_le[16..]; + + let (r_cell_lo, _) = main_gate.decompose( + ctx, + &r_lo_cell_bytes + .iter() + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + let (r_cell_hi, _) = main_gate.decompose( + ctx, + &r_hi_cell_bytes + .iter() + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + + let (s_cell_lo, _) = main_gate.decompose( + ctx, + &s_lo_cell_bytes + .iter() + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + let (s_cell_hi, _) = main_gate.decompose( + ctx, + &s_hi_cell_bytes + .iter() + .zip_eq(&powers_of_256) + .map(|(cell, coeff)| maingate::Term::Assigned(cell, *coeff)) + .collect_vec(), + F::ZERO, + |_, _| Ok(()), + )?; + + ( + Word::new([r_cell_lo, r_cell_hi]), + Word::new([s_cell_lo, s_cell_hi]), + ) + }; + + // TODO fix it + let is_valid = main_gate.and(ctx, &pk_rlc, &pk_rlc)?; + + self.enable_keccak_lookup(config, ctx, &is_address_zero, &pk_rlc, &pk_hash_cells)?; + Ok(AssignedSignatureVerify { + address: address_cells, + msg_hash: msg_hash_cells, + r, + s, + v: assigned_ecdsa.v.clone(), + is_valid, + }) } - /// Assign witness data to the sig circuit. pub(crate) fn assign( &self, config: &SigCircuitConfig, @@ -762,162 +813,150 @@ impl SigCircuit { ); return Err(Error::Synthesis); } - let mut first_pass = SKIP_FIRST_PASS; - let ecdsa_chip = &config.ecdsa_config; + let main_gate = MainGate::new(config.main_gate_config.clone()); + let range_chip = RangeChip::new(config.range_config.clone()); + let mut ecc_chip = GeneralEccChip::::new( + config.ecc_chip_config(), + ); + let cloned_ecc_chip = ecc_chip.clone(); + let scalar_chip = cloned_ecc_chip.scalar_field_chip(); + let base_chip = cloned_ecc_chip.base_field_chip(); - let assigned_sig_verifs = layouter.assign_region( - || "ecdsa chip verification", + layouter.assign_region( + || "ecc chip aux", |region| { - if first_pass { - first_pass = false; - return Ok(vec![]); - } + let mut ctx = RegionCtx::new(region, 0); + self.assign_aux(&mut ctx, &mut ecc_chip)?; + log::debug!("ecc chip aux: {} rows", ctx.offset()); + Ok(()) + }, + )?; - let mut ctx = ecdsa_chip.new_context(region); + let ecdsa_chip = EcdsaChip::new(ecc_chip.clone()); - // ================================================ - // step 1: assert the signature is valid in circuit - // ================================================ - let assigned_ecdsas = signatures - .iter() - .chain(std::iter::repeat(&SignData::default())) - .take(self.max_verif) - .map(|sign_data| self.assign_ecdsa(&mut ctx, ecdsa_chip, sign_data)) - .collect::>>, Error>>()?; - - // ================================================ - // step 2: decompose the keys and messages - // ================================================ - let sign_data_decomposed = signatures - .iter() - .chain(std::iter::repeat(&SignData::default())) - .take(self.max_verif) - .zip_eq(assigned_ecdsas.iter()) - .map(|(sign_data, assigned_ecdsa)| { - self.sign_data_decomposition( - &mut ctx, - ecdsa_chip, - sign_data, - assigned_ecdsa, - ) - }) - .collect::>, Error>>()?; - - // IMPORTANT: Move to Phase2 before RLC - log::info!("before proceeding to the next phase"); - - #[cfg(not(feature = "onephase"))] - { - // finalize the current lookup table before moving to next phase - ecdsa_chip.finalize(&mut ctx); - ctx.print_stats(&["ECDSA context"]); - ctx.next_phase(); + let chips = ChipsRef { + main_gate: &main_gate, + range_chip: &range_chip, + ecc_chip: &ecc_chip, + scalar_chip, + base_chip, + ecdsa_chip: &ecdsa_chip, + }; + + let assigned_ecdsas = layouter.assign_region( + || "ecdsa chip verification", + |region| { + let mut assigned_ecdsas = Vec::new(); + let mut ctx = RegionCtx::new(region, 0); + for i in 0..self.max_verif { + let signature = if i < signatures.len() { + signatures[i].clone() + } else { + // padding (enabled when address == 0) + SignData::default() + }; + let assigned_ecdsa = self.assign_ecdsa(&mut ctx, &chips, &signature)?; + assigned_ecdsas.push(assigned_ecdsa); } + log::debug!("ecdsa chip verification: {} rows", ctx.offset()); + Ok(assigned_ecdsas) + }, + )?; - // ================================================ - // step 3: compute RLC of keys and messages - // ================================================ - let (assigned_keccak_values, assigned_sig_values): ( - Vec<[AssignedValue; 3]>, - Vec>, - ) = signatures - .iter() - .chain(std::iter::repeat(&SignData::default())) - .take(self.max_verif) - .zip_eq(assigned_ecdsas.iter()) - .zip_eq(sign_data_decomposed.iter()) - .map(|((sign_data, assigned_ecdsa), sign_data_decomp)| { - self.assign_sig_verify( - &mut ctx, - &ecdsa_chip.range, - sign_data, - sign_data_decomp, - challenges, - assigned_ecdsa, - ) - }) - .collect::; 3], AssignedSignatureVerify)>, - Error, - >>()? - .into_iter() - .unzip(); - - // ================================================ - // step 4: deferred keccak checks - // ================================================ - for (i, [is_address_zero, pk_rlc, pk_hash_rlc]) in - assigned_keccak_values.iter().enumerate() - { - let offset = i * 3; - self.enable_keccak_lookup( + let assigned_sig_verifs = layouter.assign_region( + || "signature address verify", + |region| { + let mut assigned_sig_verifs = Vec::new(); + let mut ctx = RegionCtx::new(region, 0); + for (i, assigned_ecdsa) in assigned_ecdsas.iter().enumerate() { + let sign_data = signatures.get(i); // None when padding (enabled when address == 0) + let assigned_sig_verif = self.assign_signature_verify( config, &mut ctx, - offset, - is_address_zero, - pk_rlc, - pk_hash_rlc, + &chips, + sign_data, + assigned_ecdsa, + challenges, )?; + assigned_sig_verifs.push(assigned_sig_verif); } - - // IMPORTANT: this assigns all constants to the fixed columns - // IMPORTANT: this copies cells to the lookup advice column to perform range - // check lookups - // This is not optional. - let lookup_cells = ecdsa_chip.finalize(&mut ctx); - log::info!("total number of lookup cells: {}", lookup_cells); - - ctx.print_stats(&["ECDSA context"]); - Ok(assigned_sig_values) + log::debug!("signature address verify: {} rows", ctx.offset()); + Ok(assigned_sig_verifs) }, )?; - // TODO: is this correct? layouter.assign_region( || "expose sig table", |mut region| { - // step 5: export as a lookup table + // export as a lookup table for (idx, assigned_sig_verif) in assigned_sig_verifs.iter().enumerate() { region.assign_fixed( || "assign sig_table selector", config.sig_table.q_enable, idx, - || Value::known(F::one()), + || Value::known(F::ONE), )?; - assigned_sig_verif - .v - .copy_advice(&mut region, config.sig_table.sig_v, idx); + assigned_sig_verif.v.copy_advice( + || "v", + &mut region, + config.sig_table.sig_v, + idx, + )?; - assigned_sig_verif.r_rlc.copy_advice( + assigned_sig_verif.r.lo().copy_advice( + || "r_lo", &mut region, - config.sig_table.sig_r_rlc, + config.sig_table.sig_r.lo(), idx, - ); + )?; - assigned_sig_verif.s_rlc.copy_advice( + assigned_sig_verif.r.hi().copy_advice( + || "r_hi", &mut region, - config.sig_table.sig_s_rlc, + config.sig_table.sig_r.lo(), idx, - ); + )?; + + assigned_sig_verif.s.lo().copy_advice( + || "s_lo", + &mut region, + config.sig_table.sig_s.lo(), + idx, + )?; + assigned_sig_verif.s.hi().copy_advice( + || "s_hi", + &mut region, + config.sig_table.sig_s.hi(), + idx, + )?; assigned_sig_verif.address.copy_advice( + || "address", &mut region, config.sig_table.recovered_addr, idx, - ); + )?; - assigned_sig_verif.sig_is_valid.copy_advice( + assigned_sig_verif.is_valid.copy_advice( + || "is_valid", &mut region, config.sig_table.is_valid, idx, - ); + )?; - assigned_sig_verif.msg_hash_rlc.copy_advice( + assigned_sig_verif.msg_hash.lo().copy_advice( + || "msg hash lo", + &mut region, + config.sig_table.msg_hash.lo(), + idx, + )?; + assigned_sig_verif.msg_hash.hi().copy_advice( + || "msg hash hi", &mut region, - config.sig_table.msg_hash_rlc, + config.sig_table.msg_hash.hi(), idx, - ); + )?; } Ok(()) }, @@ -925,83 +964,4 @@ impl SigCircuit { Ok(assigned_sig_verifs) } - - /// Assert an CRTInteger's byte representation is correct. - /// inputs - /// - crt_int with 3 limbs [88, 88, 80] - /// - byte representation of the integer - /// - a sequence of [1, 2^8, 2^16, ...] - /// - a overriding flag that sets output to 0 if set - fn assert_crt_int_byte_repr( - &self, - ctx: &mut Context, - range_chip: &RangeConfig, - crt_int: &CRTInteger, - byte_repr: &[QuantumCell], - powers_of_256: &[QuantumCell], - ) -> Result<(), Error> { - // length of byte representation is 32 - assert_eq!(byte_repr.len(), 32); - // need to support decomposition of up to 88 bits - assert!(powers_of_256.len() >= 11); - - let flex_gate_chip = &range_chip.gate; - - // apply the overriding flag - let limb1_value = crt_int.truncation.limbs[0]; - let limb2_value = crt_int.truncation.limbs[1]; - let limb3_value = crt_int.truncation.limbs[2]; - - // assert the byte_repr is the right decomposition of overflow_int - // overflow_int is an overflowing integer with 3 limbs, of sizes 88, 88, and 80 - // we reconstruct the three limbs from the bytes repr, and - // then enforce equality with the CRT integer - let limb1_recover = flex_gate_chip.inner_product( - ctx, - byte_repr[0..11].to_vec(), - powers_of_256[0..11].to_vec(), - ); - let limb2_recover = flex_gate_chip.inner_product( - ctx, - byte_repr[11..22].to_vec(), - powers_of_256[0..11].to_vec(), - ); - let limb3_recover = flex_gate_chip.inner_product( - ctx, - byte_repr[22..].to_vec(), - powers_of_256[0..10].to_vec(), - ); - flex_gate_chip.assert_equal( - ctx, - QuantumCell::Existing(limb1_value), - QuantumCell::Existing(limb1_recover), - ); - flex_gate_chip.assert_equal( - ctx, - QuantumCell::Existing(limb2_value), - QuantumCell::Existing(limb2_recover), - ); - flex_gate_chip.assert_equal( - ctx, - QuantumCell::Existing(limb3_value), - QuantumCell::Existing(limb3_recover), - ); - log::trace!( - "limb 1 \ninput {:?}\nreconstructed {:?}", - limb1_value.value(), - limb1_recover.value() - ); - log::trace!( - "limb 2 \ninput {:?}\nreconstructed {:?}", - limb2_value.value(), - limb2_recover.value() - ); - log::trace!( - "limb 3 \ninput {:?}\nreconstructed {:?}", - limb3_value.value(), - limb3_recover.value() - ); - - Ok(()) - } } diff --git a/zkevm-circuits/src/sig_circuit/dev.rs b/zkevm-circuits/src/sig_circuit/dev.rs index 57de814e559..dddc3d0f0dd 100644 --- a/zkevm-circuits/src/sig_circuit/dev.rs +++ b/zkevm-circuits/src/sig_circuit/dev.rs @@ -7,12 +7,12 @@ use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; /// SigCircuitTesterConfig #[derive(Clone, Debug)] -pub struct SigCircuitTesterConfig { +pub struct SigCircuitTesterConfig { sign_verify: SigCircuitConfig, challenges: crate::util::Challenges, } -impl SigCircuitTesterConfig { +impl SigCircuitTesterConfig { pub(crate) fn new(meta: &mut ConstraintSystem) -> Self { let keccak_table = KeccakTable::construct(meta); let sig_table = SigTable::construct(meta); @@ -21,9 +21,9 @@ impl SigCircuitTesterConfig { let sign_verify = SigCircuitConfig::new( meta, SigCircuitConfigArgs { + sig_table, keccak_table, challenges: challenges_expr, - sig_table, }, ); @@ -34,7 +34,7 @@ impl SigCircuitTesterConfig { } } -impl Circuit for SigCircuit { +impl Circuit for SigCircuit { type Config = SigCircuitTesterConfig; type FloorPlanner = SimpleFloorPlanner; type Params = (); @@ -52,9 +52,9 @@ impl Circuit for SigCircuit { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - let challenges = config.challenges.values(&layouter); + let challenges = config.challenges.values(&mut layouter); self.synthesize_sub(&config.sign_verify, &challenges, &mut layouter)?; - config.sign_verify.keccak_table.dev_load( + config.sign_verify._keccak_table.dev_load( &mut layouter, &keccak_inputs_sign_verify(&self.signatures), &challenges, diff --git a/zkevm-circuits/src/sig_circuit/ecdsa.rs b/zkevm-circuits/src/sig_circuit/ecdsa.rs deleted file mode 100644 index 4921b4391ac..00000000000 --- a/zkevm-circuits/src/sig_circuit/ecdsa.rs +++ /dev/null @@ -1,226 +0,0 @@ -//! This module implements the ECDSA circuit. Modified from -//! - -use halo2_base::{ - gates::{GateInstructions, RangeInstructions}, - utils::{fe_to_biguint, modulus, CurveAffineExt}, - AssignedValue, Context, - QuantumCell::Existing, -}; -use halo2_ecc::{ - bigint::{big_less_than, CRTInteger}, - ecc::{ec_add_unequal, ec_sub_unequal, fixed_base, scalar_multiply, EcPoint, EccChip}, - fields::{fp::FpConfig, FieldChip, PrimeField, Selectable}, -}; - -// CF is the coordinate field of GA -// SF is the scalar field of GA -// p = coordinate field modulus -// n = scalar field modulus -// Only valid when p is very close to n in size (e.g. for Secp256k1) -// returns -// - if the signature is valid -// - the y coordinate for rG (will be used for ECRecovery later) -#[allow(clippy::too_many_arguments)] -pub(crate) fn ecdsa_verify_no_pubkey_check( - base_chip: &FpConfig, - ctx: &mut Context, - pubkey: &EcPoint as FieldChip>::FieldPoint>, - r: &CRTInteger, - s: &CRTInteger, - msghash: &CRTInteger, - var_window_bits: usize, - fixed_window_bits: usize, -) -> ( - AssignedValue, - AssignedValue, - CRTInteger, - AssignedValue, -) -where - GA: CurveAffineExt, -{ - let ecc_chip = EccChip::>::construct(base_chip.clone()); - let scalar_chip = FpConfig::::construct( - base_chip.range.clone(), - base_chip.limb_bits, - base_chip.num_limbs, - modulus::(), - ); - let n = scalar_chip.load_constant(ctx, scalar_chip.p.to_biguint().unwrap()); - - // check whether the pubkey is (0, 0), i.e. in the case of ecrecover, no pubkey could be - // recovered. - let (is_pubkey_zero, is_pubkey_not_zero) = { - let is_pubkey_x_zero = ecc_chip.field_chip().is_zero(ctx, &pubkey.x); - let is_pubkey_y_zero = ecc_chip.field_chip().is_zero(ctx, &pubkey.y); - let is_pubkey_zero = ecc_chip.field_chip().range().gate().and( - ctx, - Existing(is_pubkey_x_zero), - Existing(is_pubkey_y_zero), - ); - ( - is_pubkey_zero, - ecc_chip - .field_chip() - .range() - .gate() - .not(ctx, Existing(is_pubkey_zero)), - ) - }; - - // check r,s are in [1, n - 1] - let r_is_zero = scalar_chip.is_soft_zero(ctx, r); - let r_in_range = scalar_chip.is_soft_nonzero(ctx, r); - let r_is_valid = base_chip - .range() - .gate() - .or(ctx, Existing(r_is_zero), Existing(r_in_range)); - let s_is_zero = scalar_chip.is_soft_zero(ctx, s); - let s_in_range = scalar_chip.is_soft_nonzero(ctx, s); - let s_is_valid = base_chip - .range() - .gate() - .or(ctx, Existing(s_is_zero), Existing(s_in_range)); - - // load required constants - let zero = scalar_chip.load_constant(ctx, FpConfig::::fe_to_constant(SF::zero())); - let one = scalar_chip.load_constant(ctx, FpConfig::::fe_to_constant(SF::one())); - let neg_one = scalar_chip.load_constant(ctx, FpConfig::::fe_to_constant(-SF::one())); - - // compute u1 = m * s^{-1} mod n - let s2 = scalar_chip.select(ctx, &one, s, &s_is_zero); - let u1 = scalar_chip.divide(ctx, msghash, &s2); - let u1 = scalar_chip.select(ctx, &zero, &u1, &s_is_zero); - let u1_is_one = { - let diff = scalar_chip.sub_no_carry(ctx, &u1, &one); - let diff = scalar_chip.carry_mod(ctx, &diff); - scalar_chip.is_zero(ctx, &diff) - }; - - // compute u2 = r * s^{-1} mod n - let u2 = scalar_chip.divide(ctx, r, &s2); - let u2 = scalar_chip.select(ctx, &zero, &u2, &s_is_zero); - - // u3 = 1 if u1 == 1 - // u3 = -1 if u1 != 1 - // this ensures u1 + u3 != 0 - let u3 = scalar_chip.select(ctx, &one, &neg_one, &u1_is_one); - - let u1_plus_u3 = scalar_chip.add_no_carry(ctx, &u1, &u3); - let u1_plus_u3 = scalar_chip.carry_mod(ctx, &u1_plus_u3); - - // compute (u1+u3) * G - let u1u3_mul = fixed_base::scalar_multiply::( - base_chip, - ctx, - &GA::generator(), - &u1_plus_u3.truncation.limbs, - base_chip.limb_bits, - fixed_window_bits, - ); - // compute u2 * pubkey - let u2_prime = scalar_chip.select(ctx, &one, &u2, &s_is_zero); - let pubkey_prime = ecc_chip.load_random_point::(ctx); - let pubkey_prime = ecc_chip.select(ctx, &pubkey_prime, pubkey, &is_pubkey_zero); - let u2_mul = scalar_multiply::( - base_chip, - ctx, - &pubkey_prime, - &u2_prime.truncation.limbs, - base_chip.limb_bits, - var_window_bits, - ); - let point_at_infinity = EcPoint::construct( - ecc_chip - .field_chip() - .load_constant(ctx, fe_to_biguint(&CF::zero())), - ecc_chip - .field_chip() - .load_constant(ctx, fe_to_biguint(&CF::zero())), - ); - let u2_is_zero = - base_chip - .range() - .gate() - .or(ctx, Existing(s_is_zero), Existing(is_pubkey_zero)); - let u2_mul = ecc_chip.select(ctx, &point_at_infinity, &u2_mul, &u2_is_zero); - // compute u3*G this is directly assigned for G so no scalar_multiply is required - let u3_mul = { - let generator = GA::generator(); - let neg_generator = -generator; - let generator = ecc_chip.assign_constant_point(ctx, generator); - let neg_generator = ecc_chip.assign_constant_point(ctx, neg_generator); - ecc_chip.select(ctx, &generator, &neg_generator, &u1_is_one) - }; - - // compute u2 * pubkey + u3 * G - base_chip.enforce_less_than_p(ctx, u2_mul.x()); - base_chip.enforce_less_than_p(ctx, u3_mul.x()); - let u2_pk_u3_g = ec_add_unequal(base_chip, ctx, &u2_mul, &u3_mul, false); - - // check - // - (u1 + u3) * G - // - u2 * pubkey + u3 * G - // are not equal - let u1_u2_x_eq = ecc_chip.is_equal(ctx, &u1u3_mul, &u2_pk_u3_g); - let u1_u2_not_eq = base_chip.range.gate().not(ctx, Existing(u1_u2_x_eq)); - - // compute (x1, y1) = u1 * G + u2 * pubkey and check (r mod n) == x1 as integers - // which is basically u1u3_mul + u2_mul - u3_mul - // WARNING: For optimization reasons, does not reduce x1 mod n, which is - // invalid unless p is very close to n in size. - // - // WARNING: this may be trigger errors if: - // - u1u3_mul == u2_mul - // - // if r is sampled truly from random then this will not happen - // to completely ensure the correctness we may need to sample u3 from random, but it is quite - // costly. - let sum = ec_add_unequal(base_chip, ctx, &u1u3_mul, &u2_mul, false); - - // safe: we have already checked u1.G + u2.pk != 0 - // so u1.G + u3.G + u2.pk != u3.G - let sum = ec_sub_unequal(base_chip, ctx, &sum, &u3_mul, false); - let equal_check = base_chip.is_equal(ctx, &sum.x, r); - - // TODO: maybe the big_less_than is optional? - let u1_small = big_less_than::assign::( - base_chip.range(), - ctx, - &u1.truncation, - &n.truncation, - base_chip.limb_bits, - base_chip.limb_bases[1], - ); - let u2_small = big_less_than::assign::( - base_chip.range(), - ctx, - &u2.truncation, - &n.truncation, - base_chip.limb_bits, - base_chip.limb_bases[1], - ); - - // check - // - (r in [0, n - 1]) - // - (s in [0, n - 1]) - // - (u1_mul != - u2_mul) - // - (r == x1 mod n) - // - pk != (0, 0) - let res = base_chip.range().gate().and_many( - ctx, - vec![ - Existing(r_is_valid), - Existing(s_is_valid), - Existing(u1_small), - Existing(u2_small), - Existing(u1_u2_not_eq), - Existing(equal_check), - Existing(is_pubkey_not_zero), - ], - ); - - let y_is_zero = scalar_chip.is_soft_zero(ctx, &sum.y); - (res, is_pubkey_zero, sum.y, y_is_zero) -} diff --git a/zkevm-circuits/src/sig_circuit/test.rs b/zkevm-circuits/src/sig_circuit/test.rs index 7fe97c10133..9edd15b6f48 100644 --- a/zkevm-circuits/src/sig_circuit/test.rs +++ b/zkevm-circuits/src/sig_circuit/test.rs @@ -1,216 +1,113 @@ -use eth_types::{ - sign_types::{sign, SignData}, - Field, -}; +use super::*; +use crate::util::Challenges; +use bus_mapping::circuit_input_builder::keccak_inputs_sign_verify; +use eth_types::sign_types::sign; use halo2_proofs::{ arithmetic::Field as HaloField, + circuit::SimpleFloorPlanner, dev::MockProver, halo2curves::{ bn256::Fr, - group::Curve, - secp256k1::{self, Secp256k1Affine}, + group::{Curve, Group}, + CurveAffine, }, + plonk::Circuit, }; -use rand::{Rng, RngCore}; -use std::marker::PhantomData; - -use crate::sig_circuit::SigCircuit; - -#[test] -fn test_edge_cases() { - use super::utils::LOG_TOTAL_NUM_ROWS; - use eth_types::{ - sign_types::{biguint_to_32bytes_le, recover_pk, SECP256K1_Q}, - word, ToBigEndian, ToLittleEndian, Word, - }; - use ethers_core::k256::elliptic_curve::PrimeField; - use halo2_proofs::halo2curves::secp256k1::Fq; - use num::{BigUint, Integer}; - use rand::SeedableRng; - use rand_xorshift::XorShiftRng; - use snark_verifier::util::arithmetic::PrimeCurveAffine; - - let mut rng = XorShiftRng::seed_from_u64(1); +use rand::{RngCore, SeedableRng}; +use rand_xorshift::XorShiftRng; - // helper - let to_sig = |(r, s, v): (Word, Word, u8)| -> (Fq, Fq, u8) { - ( - Fq::from_bytes(&r.to_le_bytes()).unwrap(), - Fq::from_bytes(&s.to_le_bytes()).unwrap(), - v, - ) - }; - - // Vec<(msg_hash, r, s, v)> - // - // good data for ecrecover is (big-endian): - // - msg_hash: 0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3 - // - r: 0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608 - // - s: 0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada - // - v: 28, i.e. v == 1 for sig circuit - let good_ecrecover_data = ( - word!("0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3"), - word!("0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608"), - word!("0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada"), - 1u8, - ); - let ecrecover_data = vec![ - // 1. good data - good_ecrecover_data, - // 2. msg_hash == 0 - ( - Word::zero(), - good_ecrecover_data.1, - good_ecrecover_data.2, - good_ecrecover_data.3, - ), - // 3. r == 0 - ( - good_ecrecover_data.0, - Word::zero(), - good_ecrecover_data.2, - good_ecrecover_data.3, - ), - // 4. s == 0 - ( - good_ecrecover_data.0, - good_ecrecover_data.1, - Word::zero(), - good_ecrecover_data.3, - ), - // 5. r == 0 and s == 0 - ( - good_ecrecover_data.0, - Word::zero(), - Word::zero(), - good_ecrecover_data.3, - ), - // 6. random r and s for random msg hash - { - let mut bytes = [0u8; 32]; - rng.fill(&mut bytes[..]); - let msg_hash = Word::from_big_endian(&bytes); - rng.fill(&mut bytes[..]); - let r = Word::from_big_endian(&bytes); - rng.fill(&mut bytes[..]); - let s = Word::from_big_endian(&bytes); - (msg_hash, r, s, 0u8) - }, - // 7. v == 0 when v should be 1 - ( - good_ecrecover_data.0, - good_ecrecover_data.1, - good_ecrecover_data.2, - 1 - good_ecrecover_data.3, - ), - // 8. msg_hash outside FQ::MODULUS - ( - Word::MAX, - good_ecrecover_data.1, - good_ecrecover_data.2, - good_ecrecover_data.3, - ), - ]; - let signatures = ecrecover_data - .iter() - .map(|&(msg_hash, r, s, v)| SignData { - signature: to_sig((r, s, v)), - pk: recover_pk(v, &r, &s, &msg_hash.to_be_bytes()) - .unwrap_or(Secp256k1Affine::identity()), - msg_hash: { - let msg_hash = BigUint::from_bytes_be(&msg_hash.to_be_bytes()); - let msg_hash = msg_hash.mod_floor(&*SECP256K1_Q); - let msg_hash_le = biguint_to_32bytes_le(msg_hash); - secp256k1::Fq::from_repr(msg_hash_le).unwrap() - }, - ..Default::default() - }) - .collect(); - log::debug!("signatures="); - log::debug!("{:#?}", signatures); - - run::(LOG_TOTAL_NUM_ROWS as u32, 8, signatures); +#[derive(Clone, Debug)] +struct TestCircuitSignVerifyConfig { + sign_verify: SigCircuitConfig, + challenges: Challenges, } -#[test] -fn sign_verify() { - use super::utils::LOG_TOTAL_NUM_ROWS; - use crate::sig_circuit::utils::MAX_NUM_SIG; - use rand::SeedableRng; - use rand_xorshift::XorShiftRng; - use sha3::{Digest, Keccak256}; - let mut rng = XorShiftRng::seed_from_u64(1); - - // msg_hash == 0 - { - log::debug!("testing for msg_hash = 0"); - let mut signatures = Vec::new(); +impl TestCircuitSignVerifyConfig { + pub(crate) fn new(meta: &mut ConstraintSystem) -> Self { + let keccak_table = KeccakTable::construct(meta); + let sig_table = SigTable::construct(meta); + let challenges = Challenges::construct(meta); + + let sign_verify = { + let challenges = challenges.exprs(meta); + SigCircuitConfig::new( + meta, + SigCircuitConfigArgs { + keccak_table, + sig_table, + challenges, + }, + ) + }; + + TestCircuitSignVerifyConfig { + sign_verify, + challenges, + } + } +} - let (sk, pk) = gen_key_pair(&mut rng); - let msg = gen_msg(&mut rng); - let msg_hash = secp256k1::Fq::zero(); - let (r, s, v) = sign_with_rng(&mut rng, sk, msg_hash); - signatures.push(SignData { - signature: (r, s, v), - pk, - msg: msg.into(), - msg_hash, - }); +#[derive(Default)] +struct TestCircuitSignVerify { + sign_verify: SigCircuit, +} - let k = LOG_TOTAL_NUM_ROWS as u32; - run::(k, 1, signatures); +impl Circuit for TestCircuitSignVerify { + type Config = TestCircuitSignVerifyConfig; + type FloorPlanner = SimpleFloorPlanner; + type Params = (); - log::debug!("end of testing for msg_hash = 0"); + fn without_witnesses(&self) -> Self { + Self::default() } - // msg_hash == 1 - { - log::debug!("testing for msg_hash = 1"); - let mut signatures = Vec::new(); - - let (sk, pk) = gen_key_pair(&mut rng); - let msg = gen_msg(&mut rng); - let msg_hash = secp256k1::Fq::one(); - let (r, s, v) = sign_with_rng(&mut rng, sk, msg_hash); - signatures.push(SignData { - signature: (r, s, v), - pk, - msg: msg.into(), - msg_hash, - }); - let k = LOG_TOTAL_NUM_ROWS as u32; - run::(k, 1, signatures); + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + TestCircuitSignVerifyConfig::new(meta) + } - log::debug!("end of testing for msg_hash = 1"); + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let challenges = config.challenges.values(&mut layouter); + + self.sign_verify.assign( + &config.sign_verify, + &mut layouter, + &self.sign_verify.signatures, + &challenges, + )?; + config.sign_verify._keccak_table.dev_load( + &mut layouter, + &keccak_inputs_sign_verify(&self.sign_verify.signatures), + &challenges, + )?; + config.sign_verify.load_range(&mut layouter)?; + Ok(()) } - // random msg_hash - let max_sigs = [1, 16, MAX_NUM_SIG]; - for max_sig in max_sigs.iter() { - log::debug!("testing for {} signatures", max_sig); - let mut signatures = Vec::new(); - for _ in 0..*max_sig { - let (sk, pk) = gen_key_pair(&mut rng); - let msg = gen_msg(&mut rng); - let msg_hash: [u8; 32] = Keccak256::digest(&msg) - .as_slice() - .to_vec() - .try_into() - .expect("hash length isn't 32 bytes"); - let msg_hash = secp256k1::Fq::from_bytes(&msg_hash).unwrap(); - let (r, s, v) = sign_with_rng(&mut rng, sk, msg_hash); - signatures.push(SignData { - signature: (r, s, v), - pk, - msg: msg.into(), - msg_hash, - }); - } +} - let k = LOG_TOTAL_NUM_ROWS as u32; - run::(k, *max_sig, signatures); +fn run(k: u32, max_verif: usize, signatures: Vec) { + let mut rng = XorShiftRng::seed_from_u64(2); + let aux_generator = ::CurveExt::random(&mut rng).to_affine(); + + // SigCircuit -> ECDSAChip -> MainGate instance column + let circuit = TestCircuitSignVerify:: { + sign_verify: SigCircuit { + signatures, + aux_generator, + window_size: 4, + max_verif, + _marker: PhantomData, + }, + }; - log::debug!("end of testing for {} signatures", max_sig); - } + let prover = match MockProver::run(k, &circuit, vec![vec![]]) { + Ok(prover) => prover, + Err(e) => panic!("{:#?}", e), + }; + prover.assert_satisfied_par(); } // Generate a test key pair @@ -229,15 +126,7 @@ fn gen_msg_hash(rng: impl RngCore) -> secp256k1::Fq { secp256k1::Fq::random(rng) } -// Generate a test message. -fn gen_msg(mut rng: impl RngCore) -> Vec { - let msg_len: usize = rng.gen_range(0..128); - let mut msg = vec![0; msg_len]; - rng.fill_bytes(&mut msg); - msg -} - -// Returns (r, s, v) +// Returns (r, s) fn sign_with_rng( rng: impl RngCore, sk: secp256k1::Fq, @@ -247,17 +136,31 @@ fn sign_with_rng( sign(randomness, sk, msg_hash) } -fn run(k: u32, max_verif: usize, signatures: Vec) { - // SignVerifyChip -> ECDSAChip -> MainGate instance column - let circuit = SigCircuit:: { - max_verif, - signatures, - _marker: PhantomData, - }; +#[test] +fn sign_verify() { + // Vectors using `XorShiftRng::seed_from_u64(1)` + // sk: 0x771bd7bf6c6414b9370bb8559d46e1cedb479b1836ea3c2e59a54c343b0d0495 + // pk: ( + // 0x8e31a3586d4c8de89d4e0131223ecfefa4eb76215f68a691ae607757d6256ede, + // 0xc76fdd462294a7eeb8ff3f0f698eb470f32085ba975801dbe446ed8e0b05400b + // ) + // pk_hash: d90e2e9d267cbcfd94de06fa7adbe6857c2c733025c0b8938a76beeefc85d6c7 + // addr: 0x7adbe6857c2c733025c0b8938a76beeefc85d6c7 + let mut rng = XorShiftRng::seed_from_u64(1); + const MAX_VERIF: usize = 3; + const NUM_SIGS: usize = 2; + let mut signatures = Vec::new(); + for _ in 0..NUM_SIGS { + let (sk, pk) = gen_key_pair(&mut rng); + let msg_hash = gen_msg_hash(&mut rng); + let sig = sign_with_rng(&mut rng, sk, msg_hash); + signatures.push(SignData { + signature: sig, + pk, + msg_hash, + }); + } - let prover = match MockProver::run(k, &circuit, vec![]) { - Ok(prover) => prover, - Err(e) => panic!("{e:#?}"), - }; - assert_eq!(prover.verify(), Ok(())); + let k = 19; + run::(k, MAX_VERIF, signatures); } diff --git a/zkevm-circuits/src/sig_circuit/utils.rs b/zkevm-circuits/src/sig_circuit/utils.rs index 9e8715dc74b..a0c7babef8c 100644 --- a/zkevm-circuits/src/sig_circuit/utils.rs +++ b/zkevm-circuits/src/sig_circuit/utils.rs @@ -1,99 +1,101 @@ -use eth_types::Field; -use halo2_base::{AssignedValue, QuantumCell}; -use halo2_ecc::{ - bigint::CRTInteger, - ecc::EcPoint, - fields::{fp::FpConfig, FieldChip}, -}; -use halo2_proofs::{ - circuit::Value, - halo2curves::secp256k1::{Fp, Fq}, -}; +// use eth_types::Field; +// use halo2_base::{AssignedValue, QuantumCell}; +// use halo2_ecc::{ +// bigint::CRTInteger, +// ecc::EcPoint, +// fields::{fp::FpConfig, FieldChip}, +// }; +// use halo2_proofs::{ +// circuit::Value, +// halo2curves::secp256k1::{Fp, Fq}, +// }; + +// use crate::util::word::Word; // Hard coded parameters. // FIXME: allow for a configurable param. pub(super) const MAX_NUM_SIG: usize = 128; // Each ecdsa signature requires 461540 cells -pub(super) const CELLS_PER_SIG: usize = 461540; -// Each ecdsa signature requires 63489 lookup cells -pub(super) const LOOKUP_CELLS_PER_SIG: usize = 63489; -// Total number of rows allocated for ecdsa chip -pub(super) const LOG_TOTAL_NUM_ROWS: usize = 20; -// Max number of columns allowed -pub(super) const COLUMN_NUM_LIMIT: usize = 58; -// Max number of lookup columns allowed -pub(super) const LOOKUP_COLUMN_NUM_LIMIT: usize = 9; +// pub(super) const CELLS_PER_SIG: usize = 461540; +// // Each ecdsa signature requires 63489 lookup cells +// pub(super) const LOOKUP_CELLS_PER_SIG: usize = 63489; +// // Total number of rows allocated for ecdsa chip +// pub(super) const LOG_TOTAL_NUM_ROWS: usize = 20; +// // Max number of columns allowed +// pub(super) const COLUMN_NUM_LIMIT: usize = 58; +// // Max number of lookup columns allowed +// pub(super) const LOOKUP_COLUMN_NUM_LIMIT: usize = 9; -pub(super) fn calc_required_advices(num_verif: usize) -> usize { - let mut num_adv = 1; - let total_cells = num_verif * CELLS_PER_SIG; - let row_num = 1 << LOG_TOTAL_NUM_ROWS; - while num_adv < COLUMN_NUM_LIMIT { - if num_adv * row_num > total_cells { - log::debug!( - "ecdsa chip uses {} advice columns for {} signatures", - num_adv, - num_verif - ); - return num_adv; - } - num_adv += 1; - } - panic!("the required advice columns exceeds {COLUMN_NUM_LIMIT} for {num_verif} signatures"); -} +// pub(super) fn calc_required_advices(num_verif: usize) -> usize { +// let mut num_adv = 1; +// let total_cells = num_verif * CELLS_PER_SIG; +// let row_num = 1 << LOG_TOTAL_NUM_ROWS; +// while num_adv < COLUMN_NUM_LIMIT { +// if num_adv * row_num > total_cells { +// log::debug!( +// "ecdsa chip uses {} advice columns for {} signatures", +// num_adv, +// num_verif +// ); +// return num_adv; +// } +// num_adv += 1; +// } +// panic!("the required advice columns exceeds {COLUMN_NUM_LIMIT} for {num_verif} signatures"); +// } -pub(super) fn calc_required_lookup_advices(num_verif: usize) -> usize { - let mut num_adv = 1; - let total_cells = num_verif * LOOKUP_CELLS_PER_SIG; - let row_num = 1 << LOG_TOTAL_NUM_ROWS; - while num_adv < LOOKUP_COLUMN_NUM_LIMIT { - if num_adv * row_num > total_cells { - log::debug!( - "ecdsa chip uses {} lookup advice columns for {} signatures", - num_adv, - num_verif - ); - return num_adv; - } - num_adv += 1; - } - panic!("the required lookup advice columns exceeds {LOOKUP_COLUMN_NUM_LIMIT} for {num_verif} signatures"); -} +// pub(super) fn calc_required_lookup_advices(num_verif: usize) -> usize { +// let mut num_adv = 1; +// let total_cells = num_verif * LOOKUP_CELLS_PER_SIG; +// let row_num = 1 << LOG_TOTAL_NUM_ROWS; +// while num_adv < LOOKUP_COLUMN_NUM_LIMIT { +// if num_adv * row_num > total_cells { +// log::debug!( +// "ecdsa chip uses {} lookup advice columns for {} signatures", +// num_adv, +// num_verif +// ); +// return num_adv; +// } +// num_adv += 1; +// } +// panic!("the required lookup advice columns exceeds {LOOKUP_COLUMN_NUM_LIMIT} for {num_verif} +// signatures"); } -/// Chip to handle overflow integers of ECDSA::Fq, the scalar field -pub(super) type FqChip = FpConfig; -/// Chip to handle ECDSA::Fp, the base field -pub(super) type FpChip = FpConfig; +// /// Chip to handle overflow integers of ECDSA::Fq, the scalar field +// pub(super) type FqChip = FpConfig; +// /// Chip to handle ECDSA::Fp, the base field +// pub(super) type FpChip = FpConfig; -pub(crate) struct AssignedECDSA> { - pub(super) pk: EcPoint, - pub(super) pk_is_zero: AssignedValue, - pub(super) msg_hash: CRTInteger, - pub(super) integer_r: CRTInteger, - pub(super) integer_s: CRTInteger, - pub(super) v: AssignedValue, - pub(super) sig_is_valid: AssignedValue, -} +// pub(crate) struct AssignedECDSA> { +// pub(super) pk: EcPoint, +// pub(super) pk_is_zero: AssignedValue, +// pub(super) msg_hash: CRTInteger, +// pub(super) integer_r: CRTInteger, +// pub(super) integer_s: CRTInteger, +// pub(super) v: AssignedValue, +// pub(super) sig_is_valid: AssignedValue, +// } -#[derive(Debug, Clone)] -pub(crate) struct AssignedSignatureVerify { - pub(crate) address: AssignedValue, - pub(crate) msg_len: usize, - pub(crate) msg_rlc: Value, - pub(crate) msg_hash_rlc: AssignedValue, - pub(crate) r_rlc: AssignedValue, - pub(crate) s_rlc: AssignedValue, - pub(crate) v: AssignedValue, - pub(crate) sig_is_valid: AssignedValue, -} +// #[derive(Debug, Clone)] +// pub(crate) struct AssignedSignatureVerify { +// pub(crate) address: AssignedValue, +// pub(crate) msg_len: usize, +// pub(crate) msg: Value, +// pub(crate) msg_hash: AssignedValue, +// pub(crate) r: Word>, +// pub(crate) s: Word>, +// pub(crate) v: AssignedValue, +// pub(crate) sig_is_valid: AssignedValue, +// } -pub(super) struct SignDataDecomposed { - pub(super) pk_hash_cells: Vec>, - pub(super) msg_hash_cells: Vec>, - pub(super) pk_cells: Vec>, - pub(super) address: AssignedValue, - pub(super) is_address_zero: AssignedValue, - pub(super) r_cells: Vec>, - pub(super) s_cells: Vec>, - // v: AssignedValue<'v, F>, // bool -} +// pub(super) struct SignDataDecomposed { +// pub(super) pk_hash_cells: Vec>, +// pub(super) msg_hash_cells: Vec>, +// pub(super) pk_cells: Vec>, +// pub(super) address: AssignedValue, +// pub(super) is_address_zero: AssignedValue, +// pub(super) r_cells: Vec>, +// pub(super) s_cells: Vec>, +// // v: AssignedValue<'v, F>, // bool +// } diff --git a/zkevm-circuits/src/super_circuit/test.rs b/zkevm-circuits/src/super_circuit/test.rs index dd9b4aaf137..0e217c8efe4 100644 --- a/zkevm-circuits/src/super_circuit/test.rs +++ b/zkevm-circuits/src/super_circuit/test.rs @@ -139,6 +139,7 @@ fn serial_test_super_circuit_1tx_1max_tx() { max_bytecode: 512, max_evm_rows: 0, max_keccak_rows: 0, + max_vertical_circuit_rows: 0, }; test_super_circuit(block, circuits_params, Fr::from(TEST_MOCK_RANDOMNESS)); } @@ -156,6 +157,7 @@ fn serial_test_super_circuit_1tx_2max_tx() { max_bytecode: 512, max_evm_rows: 0, max_keccak_rows: 0, + max_vertical_circuit_rows: 0, }; test_super_circuit(block, circuits_params, Fr::from(TEST_MOCK_RANDOMNESS)); } @@ -173,6 +175,7 @@ fn serial_test_super_circuit_2tx_2max_tx() { max_bytecode: 512, max_evm_rows: 0, max_keccak_rows: 0, + max_vertical_circuit_rows: 0, }; test_super_circuit(block, circuits_params, Fr::from(TEST_MOCK_RANDOMNESS)); }