diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index 4e022339e8..688d45e162 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -28,6 +28,15 @@ use halo2_proofs::{ plonk::Expression, }; +use halo2_proofs::halo2curves::{ + // secp256k1 curve + secp256k1::{Fp as Fp_K1, Fq as Fq_K1, Secp256k1Affine}, + // p256 curve + secp256r1::{Fp as Fp_R1, Fq as Fq_R1, Secp256r1Affine}, + Coordinates, + //CurveAffine, +}; + /// An execution step of the EVM. #[derive(Clone, Debug)] pub struct ExecStep { @@ -905,7 +914,7 @@ pub struct PrecompileEvents { impl PrecompileEvents { /// Get all ecrecover events. - pub fn get_ecrecover_events(&self) -> Vec { + pub fn get_ecrecover_events(&self) -> Vec> { self.events .iter() .filter_map(|e| { @@ -988,13 +997,28 @@ impl PrecompileEvents { .cloned() .collect() } + + /// Get all p256 verify events. + pub fn get_p256_verify_events(&self) -> Vec> { + self.events + .iter() + .filter_map(|e: &PrecompileEvent| { + if let PrecompileEvent::P256Verify(sign_data) = e { + Some(sign_data) + } else { + None + } + }) + .cloned() + .collect() + } } /// I/O from a precompiled contract call. #[derive(Clone, Debug)] pub enum PrecompileEvent { /// Represents the I/O from Ecrecover call. - Ecrecover(SignData), + Ecrecover(SignData), /// Represents the I/O from EcAdd call. EcAdd(EcAddOp), /// Represents the I/O from EcMul call. @@ -1005,6 +1029,8 @@ pub enum PrecompileEvent { ModExp(BigModExp), /// Represents the I/O from SHA256 call. SHA256(SHA256), + /// Represents the I/O from P256Verify call. + P256Verify(SignData), } impl Default for PrecompileEvent { diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 9c534e8589..f9dd7aee12 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -11,7 +11,10 @@ use ethers_core::types::{ transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, Eip2930TransactionRequest, NameOrAddress, TransactionRequest, H256, }; -use halo2curves::{group::ff::PrimeField, secp256k1::Fq}; +use halo2curves::{ + group::ff::PrimeField, + secp256k1::{Fq as Fq_K1, Secp256k1Affine}, +}; use num::Integer; use num_bigint::BigUint; use serde::{Serialize, Serializer}; @@ -356,12 +359,13 @@ impl From<&Transaction> for TransactionRequest { } impl Transaction { + /// secp256k1 method: /// Return the SignData associated with this Transaction. - pub fn sign_data(&self) -> Result { + pub fn sign_data(&self) -> Result, Error> { let sig_r_le = self.r.to_le_bytes(); let sig_s_le = self.s.to_le_bytes(); - let sig_r = ct_option_ok_or(Fq::from_repr(sig_r_le), Error::Signature)?; - let sig_s = ct_option_ok_or(Fq::from_repr(sig_s_le), Error::Signature)?; + let sig_r = ct_option_ok_or(Fq_K1::from_repr(sig_r_le), Error::Signature)?; + let sig_s = ct_option_ok_or(Fq_K1::from_repr(sig_s_le), Error::Signature)?; let msg = self.rlp_unsigned_bytes.clone().into(); let msg_hash: [u8; 32] = Keccak256::digest(&msg) .as_slice() @@ -374,7 +378,7 @@ impl Transaction { let msg_hash = BigUint::from_bytes_be(msg_hash.as_slice()); let msg_hash = msg_hash.mod_floor(&*SECP256K1_Q); let msg_hash_le = biguint_to_32bytes_le(msg_hash); - let msg_hash = ct_option_ok_or(Fq::from_repr(msg_hash_le), Error::Signature)?; + let msg_hash = ct_option_ok_or(Fq_K1::from_repr(msg_hash_le), Error::Signature)?; Ok(SignData { signature: (sig_r, sig_s, v), pk, diff --git a/eth-types/src/sign_types.rs b/eth-types/src/sign_types.rs index e81f9109c7..f312b19e0e 100644 --- a/eth-types/src/sign_types.rs +++ b/eth-types/src/sign_types.rs @@ -19,29 +19,45 @@ use halo2curves::{ group::{ ff::{Field as GroupField, PrimeField}, prime::PrimeCurveAffine, - Curve, + Curve, GroupEncoding, }, - secp256k1::{Fp, Fq, Secp256k1Affine}, - Coordinates, CurveAffine, + + // secp256k1 curve + secp256k1::{Fp as Fp_K1, Fq as Fq_K1, Secp256k1Affine}, + // p256 curve + secp256r1::{Fp as Fp_R1, Fq as Fq_R1, Secp256r1Affine}, + Coordinates, + CurveAffine, + CurveAffineExt, }; use num_bigint::BigUint; use sha3::digest::generic_array::GenericArray; use std::sync::LazyLock; use subtle::CtOption; -/// Do a secp256k1 signature with a given randomness value. -pub fn sign(randomness: Fq, sk: Fq, msg_hash: Fq) -> (Fq, Fq, u8) { +/// Do a secp256k1 or secp256r1 signature with a given randomness value. +/// FromUniformBytes<64> refers to https://github.com/scroll-tech/halo2curves/blob/v0.1.0/src/secp256k1/fq.rs#L287 +/// and https://github.com/scroll-tech/halo2curves/blob/v0.1.0/src/secp256r1/fq.rs#L283 +pub fn sign< + Fp: PrimeField, + Fq: PrimeField + FromUniformBytes<64>, + Affine: CurveAffine + std::ops::Mul + CurveAffineExt, +>( + randomness: Fq, + sk: Fq, + msg_hash: Fq, +) -> (Fq, Fq, u8) { let randomness_inv = Option::::from(randomness.invert()).expect("cannot invert randomness"); - let generator = Secp256k1Affine::generator(); + let generator = Affine::generator(); let sig_point = generator * randomness; - let sig_v: bool = sig_point.to_affine().y.is_odd().into(); + let sig_v: bool = sig_point.to_affine().into_coordinates().1.is_odd().into(); let x = *Option::>::from(sig_point.to_affine().coordinates()) .expect("point is the identity") .x(); let mut x_bytes = [0u8; 64]; - x_bytes[..32].copy_from_slice(&x.to_bytes()); + x_bytes[..32].copy_from_slice(&x.to_repr()); let sig_r = Fq::from_uniform_bytes(&x_bytes); // get x cordinate (E::Base) on E::Scalar @@ -52,12 +68,13 @@ pub fn sign(randomness: Fq, sk: Fq, msg_hash: Fq) -> (Fq, Fq, u8) { /// Signature data required by the SignVerify Chip as input to verify a /// signature. #[derive(Clone, Debug)] -pub struct SignData { +pub struct SignData { /// Secp256k1 signature point (r, s, v) /// v must be 0 or 1 pub signature: (Fq, Fq, u8), - /// Secp256k1 public key - pub pk: Secp256k1Affine, + /// Secp256k1 or Secp256r1 public key + /// + pub pk: Affine, /// Message being hashed before signing. pub msg: Bytes, /// Hash of the message that is being signed @@ -99,7 +116,7 @@ pub fn get_dummy_tx() -> (TransactionRequest, Signature) { (tx, sig) } -impl SignData { +impl SignData { /// Recover address of the signature pub fn get_addr(&self) -> Address { if self.pk.is_identity().into() { @@ -110,7 +127,18 @@ impl SignData { } } -static SIGN_DATA_DEFAULT: LazyLock = LazyLock::new(|| { +impl SignData { + /// Recover address of the signature + pub fn get_addr(&self) -> Address { + if self.pk.is_identity().into() { + return Address::zero(); + } + let pk_hash = keccak256(pk_bytes_swap_endianness(&pk_bytes_le_p256(&self.pk))); + Address::from_slice(&pk_hash[12..]) + } +} + +static SIGN_DATA_DEFAULT: LazyLock> = LazyLock::new(|| { let (tx_req, sig) = get_dummy_tx(); let tx = Transaction { tx_type: TxType::PreEip155, @@ -132,7 +160,8 @@ static SIGN_DATA_DEFAULT: LazyLock = LazyLock::new(|| { sign_data }); -impl Default for SignData { +// Default for secp256k1 +impl Default for SignData { // Hardcoded valid signature corresponding to a hardcoded private key and // message hash generated from "nothing up my sleeve" values to make the // ECDSA chip pass the constraints, to be use for padding signature @@ -183,11 +212,11 @@ pub fn recover_pk2( debug_assert_eq!(public_key[0], 0x04); let pk_le = pk_bytes_swap_endianness(&public_key[1..]); let x = ct_option_ok_or( - Fp::from_bytes(pk_le[..32].try_into().unwrap()), + Fp_K1::from_bytes(pk_le[..32].try_into().unwrap()), Error::Signature, )?; let y = ct_option_ok_or( - Fp::from_bytes(pk_le[32..].try_into().unwrap()), + Fp_K1::from_bytes(pk_le[32..].try_into().unwrap()), Error::Signature, )?; ct_option_ok_or(Secp256k1Affine::from_xy(x, y), Error::Signature) @@ -196,7 +225,7 @@ pub fn recover_pk2( /// Secp256k1 Curve Scalar. Reference: Section 2.4.1 (parameter `n`) in "SEC 2: Recommended /// Elliptic Curve Domain Parameters" document at http://www.secg.org/sec2-v2.pdf pub static SECP256K1_Q: LazyLock = - LazyLock::new(|| BigUint::from_bytes_le(&(Fq::zero() - Fq::one()).to_repr()) + 1u64); + LazyLock::new(|| BigUint::from_bytes_le(&(Fq_K1::zero() - Fq_K1::one()).to_repr()) + 1u64); /// Helper function to convert a `CtOption` into an `Result`. Similar to /// `Option::ok_or`. @@ -223,3 +252,29 @@ pub fn pk_bytes_le(pk: &Secp256k1Affine) -> [u8; 64] { pk_le[32..].copy_from_slice(&pk_coord.y().to_bytes()); pk_le } + +/// Return both secp256k1 and secp256r1 public key (x, y) coordinates in little endian bytes. +pub fn pk_bytes_le_generic< + Fp: PrimeField, // + GroupEncoding, // 32 bytes for secp256k1 and secp256r1 curve + Affine: CurveAffine, +>( + pk: &Affine, +) -> [u8; 64] { + let pk_coord = Option::>::from(pk.coordinates()).expect("point is the identity"); + let mut pk_le = [0u8; 64]; + //pk_le[..32].copy_from_slice(&pk_coord.x().to_bytes()); + //pk_le[32..].copy_from_slice(&pk_coord.y().to_bytes()); + pk_le[..32].copy_from_slice(&pk_coord.x().to_repr()); + pk_le[32..].copy_from_slice(&pk_coord.y().to_repr()); + pk_le +} + +// TODO: refactor to generic type: `pk_bytes_le_(pk: &Affine)` +/// Return the secp256k1 public key (x, y) coordinates in little endian bytes. +pub fn pk_bytes_le_p256(pk: &Secp256r1Affine) -> [u8; 64] { + let pk_coord = Option::>::from(pk.coordinates()).expect("point is the identity"); + let mut pk_le = [0u8; 64]; + pk_le[..32].copy_from_slice(&pk_coord.x().to_bytes()); + pk_le[32..].copy_from_slice(&pk_coord.y().to_bytes()); + pk_le +} diff --git a/zkevm-circuits/src/sig_circuit.rs b/zkevm-circuits/src/sig_circuit.rs index 9c67446efc..6548c1d877 100644 --- a/zkevm-circuits/src/sig_circuit.rs +++ b/zkevm-circuits/src/sig_circuit.rs @@ -1,4 +1,4 @@ -//! Circuit to verify multiple ECDSA secp256k1 signatures. +//! Circuit to verify multiple ECDSA secp256k1 and secp256r1 signatures. // // This module uses halo2-ecc's ecdsa chip // - to prove the correctness of secp signatures @@ -26,13 +26,15 @@ use crate::{ }; use eth_types::{ self, - sign_types::{pk_bytes_le, pk_bytes_swap_endianness, SignData}, + sign_types::{pk_bytes_le, pk_bytes_le_generic, pk_bytes_swap_endianness, SignData}, }; +use ff::PrimeField; use halo2_base::{ gates::{range::RangeConfig, GateInstructions, RangeInstructions}, - utils::modulus, + utils::{modulus, CurveAffineExt}, AssignedValue, Context, QuantumCell, SKIP_FIRST_PASS, }; + use halo2_ecc::{ bigint::CRTInteger, ecc::EccChip, @@ -41,6 +43,7 @@ use halo2_ecc::{ FieldChip, }, }; +use halo2_proofs::{arithmetic::CurveAffine, halo2curves::bls12_381::Fp}; mod ecdsa; mod utils; @@ -49,12 +52,16 @@ pub(crate) use utils::*; use halo2_proofs::{ circuit::{Layouter, Value}, - halo2curves::secp256k1::{Fp, Fq, Secp256k1Affine}, + halo2curves::group::GroupEncoding, + // secp256k1 curve + halo2curves::secp256k1::{Fp as Fp_K1, Fq as Fq_K1, Secp256k1Affine}, + // p256 curve + halo2curves::secp256r1::{Fp as Fp_R1, Fq as Fq_R1, Secp256r1Affine}, plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, poly::Rotation, }; -use ethers_core::utils::keccak256; +use ethers_core::{k256::elliptic_curve::AffinePoint, utils::keccak256}; use itertools::Itertools; use log::error; use std::{iter, marker::PhantomData}; @@ -72,8 +79,10 @@ pub struct SigCircuitConfigArgs { /// SignVerify Configuration #[derive(Debug, Clone)] pub struct SigCircuitConfig { - /// ECDSA - ecdsa_config: FpChip, + /// secp256k1 + ecdsa_k1_config: FpChipK1, + /// secp256r1 + ecdsa_r1_config: FpChipR1, /// An advice column to store RLC witnesses rlc_column: Column, /// selector for keccak lookup table @@ -123,7 +132,22 @@ impl SubCircuitConfig for SigCircuitConfig { // - num_limbs: 3 // // TODO: make those parameters tunable from a config file - let ecdsa_config = FpConfig::configure( + let ecdsa_k1_config = FpConfig::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 + ); + + // TODO: check if ecdsa_r1_config parameters need to be tuned. + let ecdsa_r1_config = FpConfig::configure( meta, FpStrategy::Simple, &num_advice, @@ -132,7 +156,7 @@ impl SubCircuitConfig for SigCircuitConfig { LOG_TOTAL_NUM_ROWS - 1, 88, 3, - modulus::(), + modulus::(), 0, LOG_TOTAL_NUM_ROWS, // maximum k of the chip ); @@ -190,7 +214,8 @@ impl SubCircuitConfig for SigCircuitConfig { }); Self { - ecdsa_config, + ecdsa_k1_config, + ecdsa_r1_config, rlc_column, q_keccak, keccak_table, @@ -201,7 +226,8 @@ impl SubCircuitConfig for SigCircuitConfig { impl SigCircuitConfig { pub(crate) fn load_range(&self, layouter: &mut impl Layouter) -> Result<(), Error> { - self.ecdsa_config.range.load_lookup_table(layouter) + self.ecdsa_k1_config.range.load_lookup_table(layouter)?; + self.ecdsa_r1_config.range.load_lookup_table(layouter) } } @@ -211,8 +237,10 @@ impl SigCircuitConfig { pub struct SigCircuit { /// Max number of verifications pub max_verif: usize, - /// Without padding - pub signatures: Vec, + /// Without padding Secp256k1 signatures + pub signatures_k1: Vec>, + /// Without padding Secp256k1 signatures + pub signatures_r1: Vec>, /// Marker pub _marker: PhantomData, } @@ -225,7 +253,8 @@ impl SubCircuit for SigCircuit { SigCircuit { max_verif: MAX_NUM_SIG, - signatures: block.get_sign_data(true), + signatures_k1: block.get_sign_data(true), + signatures_r1: block.get_sign_data_p256(true), _marker: Default::default(), } } @@ -249,8 +278,16 @@ impl SubCircuit for SigCircuit { challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { - config.ecdsa_config.range.load_lookup_table(layouter)?; - self.assign(config, layouter, &self.signatures, challenges)?; + config.ecdsa_k1_config.range.load_lookup_table(layouter)?; + config.ecdsa_r1_config.range.load_lookup_table(layouter)?; + self.assign( + config, + layouter, + &self.signatures_k1, + &self.signatures_r1, + challenges, + )?; + // TODO: assign signatures_r1? Ok(()) } @@ -289,7 +326,8 @@ impl SigCircuit { pub fn new(max_verif: usize) -> Self { Self { max_verif, - signatures: Vec::new(), + signatures_k1: Vec::new(), + signatures_r1: Vec::new(), _marker: PhantomData, } } @@ -328,9 +366,11 @@ impl SigCircuit { fn assign_ecdsa( &self, ctx: &mut Context, - ecdsa_chip: &FpChip, - sign_data: &SignData, - ) -> Result>, Error> { + ecdsa_chip: &FpChipK1, + sign_data: &SignData, + // TODO: refactor method `assign_ecdsa` to `assign_ecdsa` + // or add more one parameter `sign_data_r1` + ) -> Result>, Error> { let gate = ecdsa_chip.gate(); let zero = gate.load_zero(ctx); @@ -343,26 +383,173 @@ impl SigCircuit { let (sig_r, sig_s, v) = signature; // build ecc chip from Fp chip - let ecc_chip = EccChip::>::construct(ecdsa_chip.clone()); + 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::()); + // TODO: check if need to add new fq_chip_r + let fq_chip = FqChipK1::construct(ecdsa_chip.range.clone(), 88, 3, modulus::()); let integer_r = - fq_chip.load_private(ctx, FqChip::::fe_to_witness(&Value::known(*sig_r))); + fq_chip.load_private(ctx, FqChipK1::::fe_to_witness(&Value::known(*sig_r))); let integer_s = - fq_chip.load_private(ctx, FqChip::::fe_to_witness(&Value::known(*sig_s))); + fq_chip.load_private(ctx, FqChipK1::::fe_to_witness(&Value::known(*sig_s))); let msg_hash = - fq_chip.load_private(ctx, FqChip::::fe_to_witness(&Value::known(*msg_hash))); + fq_chip.load_private(ctx, FqChipK1::::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) = - ecdsa_verify_no_pubkey_check::( + // add new p256 curve `ecdsa_verify_no_pubkey_check` + ecdsa_verify_no_pubkey_check::( + &ecc_chip.field_chip, + ctx, + &pk_assigned, + &integer_r, + &integer_s, + &msg_hash, + 4, + 4, + ); + + // ======================================= + // constrains v == y.is_oddness() + // ======================================= + assert!(*v == 0 || *v == 1, "v is not boolean"); + + // 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) + + let assigned_y_is_odd = gate.load_witness(ctx, Value::known(F::from(*v as u64))); + gate.assert_bit(ctx, assigned_y_is_odd); + + // 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); + + // 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(pk_is_zero), + ); + ecc_chip + .field_chip + .range + .range_check(ctx, &assigned_y_tmp, 87); + + let pk_not_zero = gate.not(ctx, QuantumCell::Existing(pk_is_zero)); + let sig_is_valid = gate.and_many( + ctx, + vec![ + QuantumCell::Existing(sig_is_valid), + QuantumCell::Existing(y_is_ok), + QuantumCell::Existing(pk_not_zero), + ], + ); + + Ok(AssignedECDSA { + pk: pk_assigned, + pk_is_zero, + msg_hash, + integer_r, + integer_s, + v: assigned_y_is_odd, + sig_is_valid, + }) + } + + // this method try to support both Secp256k1 and Secp256r1 by using generic type. + // FpChip: can be FpChipK1 or FpChipR1 + // Fq: can be Fq_K1 or Fq_R1 + // Affine can be Secp256k1Affine or Secp256r1Affine + //fn assign_ecdsa_generic, Fq: PrimeField, Affine: CurveAffine + CurveAffineExt>( + fn assign_ecdsa_generic< + Fp: PrimeField + halo2_base::utils::ScalarField, + Fq: PrimeField + halo2_base::utils::ScalarField, + Affine: CurveAffine + CurveAffineExt, + >( + &self, + ctx: &mut Context, + ecdsa_chip: &FpConfig, + sign_data: &SignData, + // TODO: refactor method `assign_ecdsa` to `assign_ecdsa` + // or add more one parameter `sign_data_r1` + ) -> Result>, Error> + where + Affine::Base: ff::PrimeField, + { + let gate = ecdsa_chip.gate(); + let zero = gate.load_zero(ctx); + + 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()); + // match pk { + // Secp256k1Affine { x, y } => println!("k1 affine"), + // Secp256R1Affine { x, y } => println!("k1 affine"), + // _ => panic!("found unknown PK type, not Secp256k1Affine or Secp256R1Affine"), + // } + let (x, y) = pk.into_coordinates(); + let pk_assigned = ecc_chip.load_private(ctx, (Value::known(x), Value::known(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 + // TODO: check if need to add new fq_chip_r + let fq_chip = FqChipK1::construct(ecdsa_chip.range().clone(), 88, 3, modulus::()); + let integer_r = + fq_chip.load_private(ctx, FpConfig::::fe_to_witness(&Value::known(*sig_r))); + let integer_s = + fq_chip.load_private(ctx, FpConfig::::fe_to_witness(&Value::known(*sig_s))); + let msg_hash = fq_chip.load_private( + ctx, + FpConfig::::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) = + // add new p256 curve `ecdsa_verify_no_pubkey_check` + ecdsa_verify_no_pubkey_check::( &ecc_chip.field_chip, ctx, &pk_assigned, @@ -503,12 +690,14 @@ impl SigCircuit { fn sign_data_decomposition( &self, ctx: &mut Context, - ecdsa_chip: &FpChip, - sign_data: &SignData, - assigned_data: &AssignedECDSA>, + ecdsa_chip: &FpChipK1, + sign_data: &SignData, + //TODO: refactor this method to sign_data_decomposition + // or just add new parameter `sign_data_r1` + assigned_data: &AssignedECDSA>, ) -> Result, Error> { // build ecc chip from Fp chip - let ecc_chip = EccChip::>::construct(ecdsa_chip.clone()); + let ecc_chip = EccChip::>::construct(ecdsa_chip.clone()); let zero = ecdsa_chip.range.gate.load_zero(ctx); @@ -644,15 +833,270 @@ impl SigCircuit { }) } + // this helper aims to handle both k1 and r1 signatures by generic type. + fn sign_data_decomposition_generic< + Fp: PrimeField + halo2_base::utils::ScalarField, + Fq: PrimeField + halo2_base::utils::ScalarField, + Affine: CurveAffine + CurveAffineExt, + >( + &self, + ctx: &mut Context, + //ecdsa_chip: &FpChipK1, + ecdsa_chip: &FpConfig, + sign_data: &SignData, + //TODO: refactor this method to sign_data_decomposition + // or just add new parameter `sign_data_r1` + assigned_data: &AssignedECDSA>, + ) -> Result, Error> + where + Affine::Base: ff::PrimeField, + { + // 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(); + + // ================================================ + // pk hash cellsreset + // ================================================ + let pk_le = pk_bytes_le_generic(&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(); + + // 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) + }; + + // 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_repr(), &assigned_data.msg_hash)?; + + // ================================================ + // pk cells + // ================================================ + let (x, y) = sign_data.pk.into_coordinates(); + + let pk_x_le = x + .to_repr() + .iter() + .map(|&x| QuantumCell::Witness(Value::known(F::from_u128(x as u128)))) + .collect_vec(); + let pk_y_le = y + //.to_bytes() + .to_repr() + .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(x), Value::known(y))); + + self.assert_crt_int_byte_repr( + 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, + )?; + + let assigned_pk_le_selected = [pk_y_le, pk_x_le].concat(); + log::trace!("finished data decomposition"); + + let r_cells = assert_crt( + ctx, + //sign_data.signature.0.to_bytes(), + sign_data.signature.0.to_repr(), + &assigned_data.integer_r, + )?; + let s_cells = assert_crt( + ctx, + //sign_data.signature.1.to_bytes(), + sign_data.signature.1.to_repr(), + &assigned_data.integer_s, + )?; + + 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, + }) + } + #[allow(clippy::too_many_arguments)] fn assign_sig_verify( &self, ctx: &mut Context, rlc_chip: &RangeConfig, - sign_data: &SignData, + sign_data: &SignData, + // TODO: add sign_data_r1 + sign_data_decomposed: &SignDataDecomposed, + 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(); + + log::trace!("evm challenge: {:?} ", challenges.evm_word()); + + 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(), + ); + + log::trace!("assigned msg hash rlc: {:?}", msg_hash_rlc.value()); + + // ================================================ + // 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()); + + // ================================================ + // 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(), + ); + + // 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, + ); + + 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, + }; + Ok((to_be_keccak_checked, assigned_sig_verif)) + } + + // this helper support both secp256k1 snf secp256r1 + #[allow(clippy::too_many_arguments)] + fn assign_sig_verify_generic< + Fp: PrimeField + halo2_base::utils::ScalarField, + Fq: PrimeField + halo2_base::utils::ScalarField, + Affine: CurveAffine + CurveAffineExt, + >( + &self, + ctx: &mut Context, + rlc_chip: &RangeConfig, + sign_data: &SignData, sign_data_decomposed: &SignDataDecomposed, challenges: &Challenges>, - assigned_ecdsa: &AssignedECDSA>, + assigned_ecdsa: &AssignedECDSA>, + //assigned_ecdsa: &AssignedECDSA>, ) -> Result<([AssignedValue; 3], AssignedSignatureVerify), Error> { // ================================================ // step 0. powers of aux parameters @@ -744,19 +1188,23 @@ impl SigCircuit { &self, config: &SigCircuitConfig, layouter: &mut impl Layouter, - signatures: &[SignData], + signatures_k1: &[SignData], + signatures_r1: &[SignData], + // TODO: refactor method `assign` to `assign` + // or add more one parameter `sign_data_r1` challenges: &Challenges>, ) -> Result>, Error> { - if signatures.len() > self.max_verif { + if (signatures_k1.len() + signatures_r1.len()) > self.max_verif { error!( "signatures.len() = {} > max_verif = {}", - signatures.len(), + signatures_k1.len() + signatures_r1.len(), self.max_verif ); return Err(Error::Synthesis); } let mut first_pass = SKIP_FIRST_PASS; - let ecdsa_chip = &config.ecdsa_config; + let ecdsa_k1_chip = &config.ecdsa_k1_config; + let ecdsa_r1_chip = &config.ecdsa_r1_config; let assigned_sig_verifs = layouter.assign_region( || "ecdsa chip verification", @@ -766,30 +1214,53 @@ impl SigCircuit { return Ok(vec![]); } - let mut ctx = ecdsa_chip.new_context(region); + let mut ctx = ecdsa_k1_chip.new_context(region); + // don't need ecdsa_r1_chip.new_context(region) when they share the same region? // ================================================ // step 1: assert the signature is valid in circuit // ================================================ - let assigned_ecdsas = signatures + let assigned_ecdsas_k1 = signatures_k1 .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>>()?; + .map(|sign_data| self.assign_ecdsa_generic(&mut ctx, ecdsa_k1_chip, sign_data)) + .collect::>>, Error>>()?; + + let assigned_ecdsas_r1 = signatures_r1 + .iter() + //.chain(std::iter::repeat(&SignData::default())) + //.take(self.max_verif) + .map(|sign_data| self.assign_ecdsa_generic(&mut ctx, ecdsa_r1_chip, sign_data)) + .collect::>>, Error>>()?; // ================================================ // step 2: decompose the keys and messages // ================================================ - let sign_data_decomposed = signatures + let sign_data_k1_decomposed = signatures_k1 .iter() .chain(std::iter::repeat(&SignData::default())) .take(self.max_verif) - .zip_eq(assigned_ecdsas.iter()) + .zip_eq(assigned_ecdsas_k1.iter()) + .map(|(sign_data, assigned_ecdsa)| { + self.sign_data_decomposition_generic( + &mut ctx, + ecdsa_k1_chip, + sign_data, + assigned_ecdsa, + ) + }) + .collect::>, Error>>()?; + + let sign_data_r1_decomposed = signatures_r1 + .iter() + //.chain(std::iter::repeat(&SignData::default())) + //.take(self.max_verif) + .zip_eq(assigned_ecdsas_r1.iter()) .map(|(sign_data, assigned_ecdsa)| { - self.sign_data_decomposition( + self.sign_data_decomposition_generic( &mut ctx, - ecdsa_chip, + ecdsa_r1_chip, sign_data, assigned_ecdsa, ) @@ -802,7 +1273,8 @@ impl SigCircuit { #[cfg(not(feature = "onephase"))] { // finalize the current lookup table before moving to next phase - ecdsa_chip.finalize(&mut ctx); + ecdsa_k1_chip.finalize(&mut ctx); + ecdsa_r1_chip.finalize(&mut ctx); ctx.print_stats(&["ECDSA context"]); ctx.next_phase(); } @@ -813,16 +1285,16 @@ impl SigCircuit { let (assigned_keccak_values, assigned_sig_values): ( Vec<[AssignedValue; 3]>, Vec>, - ) = signatures + ) = signatures_k1 .iter() .chain(std::iter::repeat(&SignData::default())) .take(self.max_verif) - .zip_eq(assigned_ecdsas.iter()) - .zip_eq(sign_data_decomposed.iter()) + .zip_eq(assigned_ecdsas_k1.iter()) + .zip_eq(sign_data_k1_decomposed.iter()) .map(|((sign_data, assigned_ecdsa), sign_data_decomp)| { - self.assign_sig_verify( + self.assign_sig_verify_generic( &mut ctx, - &ecdsa_chip.range, + &ecdsa_k1_chip.range, sign_data, sign_data_decomp, challenges, @@ -857,7 +1329,7 @@ impl SigCircuit { // 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); + let lookup_cells = ecdsa_k1_chip.finalize(&mut ctx); log::info!("total number of lookup cells: {}", lookup_cells); ctx.print_stats(&["ECDSA context"]); diff --git a/zkevm-circuits/src/sig_circuit/dev.rs b/zkevm-circuits/src/sig_circuit/dev.rs index 1555db56c7..b8deb66257 100644 --- a/zkevm-circuits/src/sig_circuit/dev.rs +++ b/zkevm-circuits/src/sig_circuit/dev.rs @@ -57,7 +57,7 @@ impl Circuit for SigCircuit { self.synthesize_sub(&config.sign_verify, &challenges, &mut layouter)?; config.sign_verify.keccak_table.dev_load( &mut layouter, - &keccak_inputs_sign_verify(&self.signatures), + &keccak_inputs_sign_verify(&self.signatures_k1), &challenges, )?; /* diff --git a/zkevm-circuits/src/sig_circuit/test.rs b/zkevm-circuits/src/sig_circuit/test.rs index 394ce11837..7484bed905 100644 --- a/zkevm-circuits/src/sig_circuit/test.rs +++ b/zkevm-circuits/src/sig_circuit/test.rs @@ -6,6 +6,7 @@ use halo2_proofs::{ halo2curves::{ group::Curve, secp256k1::{self, Secp256k1Affine}, + secp256r1::{self, Secp256r1Affine}, }, }; use rand::{Rng, RngCore}; @@ -18,7 +19,7 @@ fn test_edge_cases() { sign_types::{biguint_to_32bytes_le, recover_pk2, SECP256K1_Q}, word, ToBigEndian, ToLittleEndian, Word, }; - use halo2_proofs::halo2curves::{bn256::Fr, group::ff::PrimeField, secp256k1::Fq}; + use halo2_proofs::halo2curves::{bn256::Fr, group::ff::PrimeField, secp256k1::Fq, secp256r1}; use num::{BigUint, Integer}; use rand::SeedableRng; use rand_xorshift::XorShiftRng; @@ -144,11 +145,12 @@ fn test_edge_cases() { log::debug!("signatures="); log::debug!("{:#?}", signatures); - run::(LOG_TOTAL_NUM_ROWS as u32, 10, signatures); + run::(LOG_TOTAL_NUM_ROWS as u32, 10, signatures, vec![]); } +// test for secp256k1 signatures #[test] -fn sign_verify() { +fn sign_k1_verify() { use super::utils::LOG_TOTAL_NUM_ROWS; use crate::sig_circuit::utils::MAX_NUM_SIG; use halo2_proofs::halo2curves::bn256::Fr; @@ -162,7 +164,7 @@ fn sign_verify() { log::debug!("testing for msg_hash = 0"); let mut signatures = Vec::new(); - let (sk, pk) = gen_key_pair(&mut rng); + let (sk, pk) = gen_key_pair_k1(&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); @@ -174,7 +176,7 @@ fn sign_verify() { }); let k = LOG_TOTAL_NUM_ROWS as u32; - run::(k, 1, signatures); + run::(k, 1, signatures, vec![]); log::debug!("end of testing for msg_hash = 0"); } @@ -183,7 +185,7 @@ fn sign_verify() { log::debug!("testing for msg_hash = 1"); let mut signatures = Vec::new(); - let (sk, pk) = gen_key_pair(&mut rng); + let (sk, pk) = gen_key_pair_k1(&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); @@ -195,7 +197,7 @@ fn sign_verify() { }); let k = LOG_TOTAL_NUM_ROWS as u32; - run::(k, 1, signatures); + run::(k, 1, signatures, vec![]); log::debug!("end of testing for msg_hash = 1"); } @@ -205,7 +207,7 @@ fn sign_verify() { 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 (sk, pk) = gen_key_pair_k1(&mut rng); let msg = gen_msg(&mut rng); let msg_hash: [u8; 32] = Keccak256::digest(&msg) .as_slice() @@ -223,14 +225,159 @@ fn sign_verify() { } let k = LOG_TOTAL_NUM_ROWS as u32; - run::(k, *max_sig, signatures); + run::(k, *max_sig, signatures, vec![]); + + log::debug!("end of testing for {} signatures", max_sig); + } +} + +// test for secp256r1 signatures +#[test] +fn p256_sign_verify() { + use super::utils::LOG_TOTAL_NUM_ROWS; + use crate::sig_circuit::utils::MAX_NUM_SIG; + use halo2_proofs::halo2curves::bn256::Fr; + 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(); + + let (sk, pk) = gen_key_pair_r1(&mut rng); + let msg = gen_msg(&mut rng); + let msg_hash = secp256r1::Fq::zero(); + let (r, s, v) = sign_r1_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, vec![], signatures); + + log::debug!("end of testing for msg_hash = 0"); + } + // msg_hash == 1 + { + log::debug!("testing for msg_hash = 1"); + let mut signatures = Vec::new(); + + let (sk, pk) = gen_key_pair_r1(&mut rng); + let msg = gen_msg(&mut rng); + let msg_hash = secp256r1::Fq::one(); + let (r, s, v) = sign_r1_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, vec![], signatures); + + log::debug!("end of testing for msg_hash = 1"); + } + + // random msg_hash + //let max_sigs = [1, 16, MAX_NUM_SIG]; enable this line after testing + let max_sigs = [1]; + + 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_r1(&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 = secp256r1::Fq::from_bytes(&msg_hash).unwrap(); + //let msg_hash = secp256r1::Fq::one(); + let (r, s, v) = sign_r1_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, vec![], signatures); log::debug!("end of testing for {} signatures", max_sig); } + +} + +// test for both secp256k1 and secp256r1 signatures +#[test] +fn sign_verify() { + use super::utils::LOG_TOTAL_NUM_ROWS; + use crate::sig_circuit::utils::MAX_NUM_SIG; + use halo2_proofs::halo2curves::bn256::Fr; + use rand::SeedableRng; + use rand_xorshift::XorShiftRng; + use sha3::{Digest, Keccak256}; + let mut rng = XorShiftRng::seed_from_u64(1); + + // random msg_hash + //let max_sigs = [1, 16, MAX_NUM_SIG]; + let max_sigs = [1]; + + for max_sig in max_sigs.iter() { + // max_sig secp256k1 and max_sig secp256r1 signatures + log::debug!("testing for {} signatures", 2 * max_sig); + let mut signatures_k1: Vec> = Vec::new(); + let mut signatures_r1: Vec> = Vec::new(); + for _ in 0..*max_sig { + let (sk_k1, pk_k1) = gen_key_pair_k1(&mut rng); + let (sk_r1, pk_r1) = gen_key_pair_r1(&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_k1 = secp256k1::Fq::from_bytes(&msg_hash).unwrap(); + let msg_hash_r1 = secp256r1::Fq::from_bytes(&msg_hash).unwrap(); + + //let msg_hash = secp256r1::Fq::one(); + let (r_k1, s_k1, v_k1) = sign_with_rng(&mut rng, sk_k1, msg_hash_k1); + let (r_r1, s_r1, v_r1) = sign_r1_with_rng(&mut rng, sk_r1, msg_hash_r1); + signatures_k1.push(SignData { + signature: (r_k1, s_k1, v_k1), + pk: pk_k1, + msg: msg.clone().into(), + msg_hash: msg_hash_k1, + }); + signatures_r1.push(SignData { + signature: (r_r1, s_r1, v_r1), + pk: pk_r1, + msg: msg.into(), + msg_hash: msg_hash_r1, + }); + } + + let k = LOG_TOTAL_NUM_ROWS as u32; + run::(k, 2 * (*max_sig), signatures_k1, signatures_r1); + + log::debug!("end of testing for {} signatures", 2 * max_sig); + } } -// Generate a test key pair -fn gen_key_pair(rng: impl RngCore) -> (secp256k1::Fq, Secp256k1Affine) { +// Generate a test key pair for secp256k1 +fn gen_key_pair_k1(rng: impl RngCore) -> (secp256k1::Fq, Secp256k1Affine) { // generate a valid signature let generator = Secp256k1Affine::generator(); let sk = secp256k1::Fq::random(rng); @@ -240,6 +387,17 @@ fn gen_key_pair(rng: impl RngCore) -> (secp256k1::Fq, Secp256k1Affine) { (sk, pk) } +// Generate a test key pair for secp256r1 +fn gen_key_pair_r1(rng: impl RngCore) -> (secp256r1::Fq, Secp256r1Affine) { + // generate a valid signature + let generator = Secp256r1Affine::generator(); + let sk = secp256r1::Fq::random(rng); + let pk = generator * sk; + let pk = pk.to_affine(); + + (sk, pk) +} + // Generate a test message hash fn gen_msg_hash(rng: impl RngCore) -> secp256k1::Fq { secp256k1::Fq::random(rng) @@ -253,21 +411,39 @@ fn gen_msg(mut rng: impl RngCore) -> Vec { msg } -// Returns (r, s, v) +// Returns (r, s, v) of secp256k1 fn sign_with_rng( rng: impl RngCore, sk: secp256k1::Fq, msg_hash: secp256k1::Fq, ) -> (secp256k1::Fq, secp256k1::Fq, u8) { let randomness = secp256k1::Fq::random(rng); - sign(randomness, sk, msg_hash) + + sign::(randomness, sk, msg_hash) +} + +// Returns (r, s, v) of secp256r1 +fn sign_r1_with_rng( + rng: impl RngCore, + sk: secp256r1::Fq, + msg_hash: secp256r1::Fq, +) -> (secp256r1::Fq, secp256r1::Fq, u8) { + let randomness = secp256r1::Fq::random(rng); + + sign::(randomness, sk, msg_hash) } -fn run(k: u32, max_verif: usize, signatures: Vec) { +fn run( + k: u32, + max_verif: usize, + signatures_k1: Vec>, + signatures_r1: Vec>, +) { // SignVerifyChip -> ECDSAChip -> MainGate instance column let circuit = SigCircuit:: { max_verif, - signatures, + signatures_k1, + signatures_r1, _marker: PhantomData, }; diff --git a/zkevm-circuits/src/sig_circuit/utils.rs b/zkevm-circuits/src/sig_circuit/utils.rs index 3716228ba3..8b47f44a59 100644 --- a/zkevm-circuits/src/sig_circuit/utils.rs +++ b/zkevm-circuits/src/sig_circuit/utils.rs @@ -7,7 +7,8 @@ use halo2_ecc::{ }; use halo2_proofs::{ circuit::Value, - halo2curves::secp256k1::{Fp, Fq}, + halo2curves::secp256k1::{Fp as Fp_K1, Fq as Fq_K1}, + halo2curves::secp256r1::{Fp as Fp_R1, Fq as Fq_R1}, }; // Hard coded parameters. @@ -61,9 +62,11 @@ pub(super) fn calc_required_lookup_advices(num_verif: usize) -> usize { } /// 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(super) type FqChipK1 = FpConfig; +/// Chip to handle ECDSA(secp256k1)::Fp, the base field +pub(super) type FpChipK1 = FpConfig; +/// Chip to handle ECDSA(secp256r1)::Fp, the base field +pub(super) type FpChipR1 = FpConfig; pub(crate) struct AssignedECDSA> { pub(super) pk: EcPoint, diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 2643eda4ec..62eb7707c6 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -32,6 +32,10 @@ use gadgets::{ use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, Value}, halo2curves::bn256::{Fq, G1Affine}, + halo2curves::{ + secp256k1::{self, Secp256k1Affine}, + secp256r1::{self, Secp256r1Affine}, + }, plonk::{Advice, Any, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, poly::Rotation, }; @@ -2508,7 +2512,7 @@ impl RlpFsmRlpTable { } } -/// The sig table is used to verify signatures, used in tx circuit and ecrecover precompile. +/// The sig table is used to verify signatures, used in tx circuit and ecrecover & p256_verify precompiles. #[derive(Clone, Copy, Debug)] pub struct SigTable { /// Indicates whether or not the gates are enabled on the current row. @@ -2551,30 +2555,17 @@ impl SigTable { layouter.assign_region( || "sig table (dev load)", |mut region| { - let signatures: Vec = block.get_sign_data(false); - - let evm_word = challenges.evm_word(); - for (offset, sign_data) in signatures.iter().enumerate() { - let msg_hash_rlc = evm_word.map(|challenge| { - rlc::value( - sign_data.msg_hash.to_bytes().iter().collect_vec(), - challenge, - ) - }); - let sig_r_rlc = evm_word.map(|challenge| { - rlc::value( - sign_data.signature.0.to_bytes().iter().collect_vec(), - challenge, - ) - }); - let sig_s_rlc = evm_word.map(|challenge| { - rlc::value( - sign_data.signature.1.to_bytes().iter().collect_vec(), - challenge, - ) - }); - let sig_v = Value::known(F::from(sign_data.signature.2 as u64)); - let recovered_addr = Value::known(sign_data.get_addr().to_scalar().unwrap()); + let signatures_k1 = block.get_sign_data(false); + let signatures_r1 = block.get_sign_data_p256(false); + + // TODO: connect signatures_r1 in following loop. + let signatures = + Self::combine_signatures(&signatures_k1, &signatures_r1, challenges); + for ( + offset, + (msg_hash_rlc, sig_r_rlc, sig_s_rlc, sig_v, recovered_addr, is_valid), + ) in signatures.iter().enumerate() + { region.assign_fixed( || format!("sig table q_enable {offset}"), self.q_enable, @@ -2587,17 +2578,13 @@ impl SigTable { ("sig_r_rlc", self.sig_r_rlc, sig_r_rlc), ("sig_s_rlc", self.sig_s_rlc, sig_s_rlc), ("recovered_addr", self.recovered_addr, recovered_addr), - ( - "is_valid", - self.is_valid, - Value::known(F::from(!sign_data.get_addr().is_zero())), - ), + ("is_valid", self.is_valid, is_valid), ] { region.assign_advice( || format!("sig table {column_name} {offset}"), column, offset, - || value, + || *value, )?; } } @@ -2608,6 +2595,87 @@ impl SigTable { Ok(()) } + + /// Combine secp256k1 signatures and secp256r1 signatures + pub fn combine_signatures( + signatures_k1: &Vec>, + signatures_r1: &Vec>, + challenges: &Challenges>, + ) -> Vec<(Value, Value, Value, Value, Value, Value)> { + let mut sig_table_items = vec![]; + let evm_word = challenges.evm_word(); + + // refactor to more uniform method to replace following two loops. + // let construct_sig_table_items = + // |signatures: &Vec>| -> Vec<(Value, Value, Value, Value, Value, Value)> { + // }; + + for (offset, sign_data) in signatures_k1.iter().enumerate() { + let msg_hash_rlc = evm_word.map(|challenge| { + rlc::value( + sign_data.msg_hash.to_bytes().iter().collect_vec(), + challenge, + ) + }); + let sig_r_rlc = evm_word.map(|challenge| { + rlc::value( + sign_data.signature.0.to_bytes().iter().collect_vec(), + challenge, + ) + }); + let sig_s_rlc = evm_word.map(|challenge| { + rlc::value( + sign_data.signature.1.to_bytes().iter().collect_vec(), + challenge, + ) + }); + let sig_v = Value::known(F::from(sign_data.signature.2 as u64)); + let recovered_addr = Value::known(sign_data.get_addr().to_scalar().unwrap()); + let is_valid = Value::known(F::from(!sign_data.get_addr().is_zero())); + sig_table_items.push(( + msg_hash_rlc, + sig_r_rlc, + sig_s_rlc, + sig_v, + recovered_addr, + is_valid, + )); + } + + for (offset, sign_data) in signatures_r1.iter().enumerate() { + let msg_hash_rlc = evm_word.map(|challenge| { + rlc::value( + sign_data.msg_hash.to_bytes().iter().collect_vec(), + challenge, + ) + }); + let sig_r_rlc = evm_word.map(|challenge| { + rlc::value( + sign_data.signature.0.to_bytes().iter().collect_vec(), + challenge, + ) + }); + let sig_s_rlc = evm_word.map(|challenge| { + rlc::value( + sign_data.signature.1.to_bytes().iter().collect_vec(), + challenge, + ) + }); + let sig_v = Value::known(F::from(sign_data.signature.2 as u64)); + let recovered_addr = Value::known(sign_data.get_addr().to_scalar().unwrap()); + let is_valid = Value::known(F::from(!sign_data.get_addr().is_zero())); + sig_table_items.push(( + msg_hash_rlc, + sig_r_rlc, + sig_s_rlc, + sig_v, + recovered_addr, + is_valid, + )); + } + + sig_table_items + } } impl LookupTable for SigTable { diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index d7cea0f8bd..1ec53d6c3e 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -63,6 +63,10 @@ use gadgets::{ }; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Region, Value}, + halo2curves::{ + secp256k1::{self, Secp256k1Affine}, + secp256r1::{self, Secp256r1Affine}, + }, plonk::{Advice, Column, ConstraintSystem, Error, Expression, VirtualCells}, poly::Rotation, }; @@ -3012,7 +3016,9 @@ impl TxCircuitConfig { region: &mut Region<'_, F>, offset: &mut usize, tx: &Transaction, - sign_data: &SignData, + sign_data: &SignData, + // TODO: refactor method `assign_fixed_rows` to `assign_fixed_rows` + // or add more one parameter `assign_fixed_rows_r1` next_tx: Option<&Transaction>, total_l1_popped_before: u64, num_all_txs_acc: u64, @@ -4032,7 +4038,7 @@ impl TxCircuit { .collect::>>(); inputs.extend_from_slice(&hash_datas); - let sign_datas: Vec = self + let sign_datas = self .txs .iter() .chain(iter::once(&padding_tx)) @@ -4047,7 +4053,8 @@ impl TxCircuit { }) } }) - .collect::, Error>>()?; + // TODO: add p256 signature here ? + .collect::>, Error>>()?; // Keccak inputs from SignVerify Chip let sign_verify_inputs = keccak_inputs_sign_verify(&sign_datas); inputs.extend_from_slice(&sign_verify_inputs); @@ -4165,7 +4172,8 @@ impl TxCircuit { challenges: &crate::util::Challenges>, layouter: &mut impl Layouter, start_l1_queue_index: u64, - sign_datas: Vec, + sign_datas: Vec>, + // TODO: add `sign_datas_r1" ? padding_txs: &[Transaction], ) -> Result>, Error> { config.tx_rom_table.load(layouter)?; @@ -4459,7 +4467,7 @@ impl SubCircuit for TxCircuit { tx }) .collect::>(); - let sign_datas: Vec = self + let sign_datas = self .txs .iter() .chain(padding_txs.iter()) @@ -4473,7 +4481,7 @@ impl SubCircuit for TxCircuit { }) } }) - .collect::, Error>>()?; + .collect::>, Error>>()?; // check if tx.caller_address == recovered_pk let recovered_pks = keccak_inputs_sign_verify(&sign_datas) @@ -4519,7 +4527,7 @@ pub(crate) fn get_sign_data( txs: &[Transaction], max_txs: usize, chain_id: usize, -) -> Result, halo2_proofs::plonk::Error> { +) -> Result>, halo2_proofs::plonk::Error> { let padding_txs = (txs.len()..max_txs) .map(|i| { let mut tx = Transaction::dummy(chain_id as u64); @@ -4527,7 +4535,7 @@ pub(crate) fn get_sign_data( tx }) .collect::>(); - let signatures: Vec = txs + let signatures = txs .iter() .chain(padding_txs.iter()) .map(|tx| { @@ -4542,7 +4550,8 @@ pub(crate) fn get_sign_data( }) } }) - .collect::, halo2_proofs::plonk::Error>>()?; + // TODO: add p256 signatures here ? + .collect::>, halo2_proofs::plonk::Error>>()?; Ok(signatures) } diff --git a/zkevm-circuits/src/tx_circuit/dev.rs b/zkevm-circuits/src/tx_circuit/dev.rs index 9afdbbc252..dce1c2612d 100644 --- a/zkevm-circuits/src/tx_circuit/dev.rs +++ b/zkevm-circuits/src/tx_circuit/dev.rs @@ -120,7 +120,8 @@ impl TxCircuitTester { TxCircuitTester:: { sig_circuit: SigCircuit { max_verif: max_txs, - signatures: get_sign_data(&txs, max_txs, chain_id as usize).unwrap(), + signatures_k1: get_sign_data(&txs, max_txs, chain_id as usize).unwrap(), + signatures_r1: vec![], _marker: PhantomData, }, tx_circuit: TxCircuit::new(max_txs, max_calldata, chain_id, start_l1_queue_index, txs), diff --git a/zkevm-circuits/src/tx_circuit/test.rs b/zkevm-circuits/src/tx_circuit/test.rs index cbabcf3834..624cd27367 100644 --- a/zkevm-circuits/src/tx_circuit/test.rs +++ b/zkevm-circuits/src/tx_circuit/test.rs @@ -178,7 +178,9 @@ fn run( let circuit = TxCircuitTester:: { sig_circuit: SigCircuit { max_verif: max_txs, - signatures: get_sign_data(&txs, max_txs, chain_id as usize).unwrap(), + signatures_k1: get_sign_data(&txs, max_txs, chain_id as usize).unwrap(), + // TODO: check if need to add p256 signatures here. + signatures_r1: vec![], _marker: PhantomData, }, tx_circuit: TxCircuit::new(max_txs, max_calldata, chain_id, start_l1_queue_index, txs), diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index 559084b7d4..2c409bc37b 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -19,6 +19,10 @@ use bus_mapping::{ Error, }; use eth_types::{sign_types::SignData, Address, ToLittleEndian, Word, H256, U256}; +use halo2_proofs::halo2curves::{ + secp256k1::{self, Secp256k1Affine}, + secp256r1::{self, Secp256r1Affine}, +}; use halo2_proofs::{circuit::Value, halo2curves::bn256::Fr}; use itertools::Itertools; @@ -130,15 +134,18 @@ impl Block { } /// Get signature (witness) from the block for tx signatures and ecRecover calls. - pub(crate) fn get_sign_data(&self, padding: bool) -> Vec { - let mut signatures: Vec = self + pub(crate) fn get_sign_data( + &self, + padding: bool, + ) -> Vec> { + let mut signatures = self .txs .iter() // Since L1Msg tx does not have signature, it do not need to do lookup into sig table .filter(|tx| !tx.tx_type.is_l1_msg()) .map(|tx| tx.sign_data()) .filter_map(|res| res.ok()) - .collect::>(); + .collect::>>(); signatures.extend_from_slice(&self.precompile_events.get_ecrecover_events()); if padding && self.txs.len() < self.circuits_params.max_txs { // padding tx's sign data @@ -147,6 +154,14 @@ impl Block { signatures } + /// Get signature (witness) from the block's p256Veirfy calls. + pub(crate) fn get_sign_data_p256( + &self, + padding: bool, + ) -> Vec> { + self.precompile_events.get_p256_verify_events() + } + /// Get EcAdd operations from all precompiled contract calls in this block. pub(crate) fn get_ec_add_ops(&self) -> Vec { self.precompile_events.get_ec_add_events() diff --git a/zkevm-circuits/src/witness/keccak.rs b/zkevm-circuits/src/witness/keccak.rs index 3b5884be96..681eae2b96 100644 --- a/zkevm-circuits/src/witness/keccak.rs +++ b/zkevm-circuits/src/witness/keccak.rs @@ -5,6 +5,10 @@ use eth_types::{ ToBigEndian, ToWord, Word, H256, }; use ethers_core::utils::keccak256; +use halo2_proofs::halo2curves::{ + secp256k1::{self, Secp256k1Affine}, + secp256r1::{self, Secp256r1Affine}, +}; use itertools::Itertools; use super::{Block, BlockContexts, Transaction}; @@ -68,7 +72,10 @@ pub fn keccak_inputs(block: &Block) -> Result>, Error> { /// Generate the keccak inputs required by the SignVerify Chip from the /// signature datas. -pub fn keccak_inputs_sign_verify(sigs: &[SignData]) -> Vec> { +/// TODO: check if need to support p256 SignData type later. +pub fn keccak_inputs_sign_verify( + sigs: &[SignData], +) -> Vec> { let mut inputs = Vec::new(); let dummy_sign_data = SignData::default(); for sig in sigs.iter().chain(std::iter::once(&dummy_sign_data)) { @@ -186,7 +193,7 @@ pub fn keccak_inputs_tx_circuit(txs: &[Transaction]) -> Result>, Err .collect::>(); inputs.push(chunk_txbytes); - let sign_datas: Vec = txs + let sign_datas: Vec> = txs .iter() .enumerate() .filter(|(i, tx)| { diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 42522d257a..b23de2c71b 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -31,7 +31,7 @@ use ethers_core::{ use gadgets::ToScalar; use halo2_proofs::{ circuit::Value, - halo2curves::{group::ff::PrimeField, secp256k1}, + halo2curves::{group::ff::PrimeField, secp256k1, secp256r1}, }; use num::Integer; use num_bigint::BigUint; @@ -136,7 +136,8 @@ impl Transaction { } /// Sign data - pub fn sign_data(&self) -> Result { + /// TODO: check this method is used to sign p256 data later. + pub fn sign_data(&self) -> Result, Error> { if self.r.is_zero() && self.s.is_zero() && self.v == 0 { return Ok(SignData::default()); }