From fd107fefa064c3cb3496286c4a62acc07d79b3ab Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Thu, 31 Oct 2024 14:58:50 +0800 Subject: [PATCH 01/10] add p256VerifyAuxData --- bus-mapping/src/precompile.rs | 83 ++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/bus-mapping/src/precompile.rs b/bus-mapping/src/precompile.rs index edf2dc14b8..ea60d264dd 100644 --- a/bus-mapping/src/precompile.rs +++ b/bus-mapping/src/precompile.rs @@ -111,11 +111,26 @@ impl From for PrecompileCalls { 0x08 => Self::Bn128Pairing, 0x09 => Self::Blake2F, 0x100 => Self::P256Verify, - _ => unreachable!("precompile contracts only from 0x01 to 0x09"), + _ => unreachable!("precompile contracts only from 0x01 to 0x100"), } } } +/// size limit of modexp +pub const MODEXP_SIZE_LIMIT: usize = 32; +/// size of input limit +pub const MODEXP_INPUT_LIMIT: usize = 192; + +/// size of Bn128_Mul input limit +pub const BN128MUL_INPUT_LIMIT: usize = 96; +/// size of Bn128Add input limit +pub const BN128ADD_INPUT_LIMIT: usize = 128; + +/// size of p256Verify input limit +pub const P256VERIFY_INPUT_LIMIT: usize = 160; +/// size of Ecrecover input limit +pub const ECRECOVER_INPUT_LIMIT: usize = 128; + impl PrecompileCalls { /// Get the base gas cost for the precompile call. pub fn base_gas_cost(&self) -> GasCost { @@ -141,9 +156,11 @@ impl PrecompileCalls { /// Maximum length of input bytes considered for the precompile call. pub fn input_len(&self) -> Option { match self { - Self::Ecrecover | Self::Bn128Add => Some(128), - Self::Bn128Mul => Some(96), + Self::Ecrecover => Some(ECRECOVER_INPUT_LIMIT), + Self::Bn128Add => Some(BN128ADD_INPUT_LIMIT), + Self::Bn128Mul => Some(BN128MUL_INPUT_LIMIT), Self::Modexp => Some(MODEXP_INPUT_LIMIT), + Self::P256Verify => Some(P256VERIFY_INPUT_LIMIT), _ => None, } } @@ -174,7 +191,7 @@ impl EcrecoverAuxData { /// Create a new instance of ecrecover auxiliary data. pub fn new(input: &[u8], output: &[u8], return_bytes: &[u8]) -> Self { let mut resized_input = input.to_vec(); - resized_input.resize(128, 0u8); + resized_input.resize(ECRECOVER_INPUT_LIMIT, 0u8); let mut resized_output = output.to_vec(); resized_output.resize(32, 0u8); @@ -206,10 +223,52 @@ impl EcrecoverAuxData { } } -/// size limit of modexp -pub const MODEXP_SIZE_LIMIT: usize = 32; -/// size of input limit -pub const MODEXP_INPUT_LIMIT: usize = 192; +/// Auxiliary data for P256Verify +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct P256VerifyAuxData { + /// mas hash of the message being signed, can be any hash algorithm result. + pub msg_hash: Word, + /// r-component of signature. + pub sig_r: Word, + /// s-component of signature. + pub sig_s: Word, + /// x-component of public key. + pub pubkey_x: Word, + /// y-component of public key. + pub pubkey_y: Word, + /// Input bytes to the P256Verify call. + pub input_bytes: Vec, + /// Output bytes from the P256Verify call. + pub output_bytes: Vec, + /// Bytes returned to the caller from the P256Verify call. + pub return_bytes: Vec, +} + +impl P256VerifyAuxData { + /// Create a new instance of p256Verify auxiliary data. + pub fn new(input: &[u8], output: &[u8], return_bytes: &[u8]) -> Self { + let mut resized_input = input.to_vec(); + resized_input.resize(P256VERIFY_INPUT_LIMIT, 0u8); + let mut resized_output = output.to_vec(); + resized_output.resize(32, 0u8); + + // assert that output bytes is 32 bytes. + assert!(resized_output[0x01..0x20].iter().all(|&b| b == 0)); + // assert first byte is bool + assert!(resized_output[0x01] == 1u8 || resized_output[0x01] == 0); + + Self { + msg_hash: Word::from_big_endian(&resized_input[0x00..0x20]), + sig_r: Word::from_big_endian(&resized_input[0x20..0x40]), + sig_s: Word::from_big_endian(&resized_input[0x40..0x60]), + pubkey_x: Word::from_big_endian(&resized_input[0x60..0x80]), + pubkey_y: Word::from_big_endian(&resized_input[0x80..0xa0]), + input_bytes: input.to_vec(), + output_bytes: output.to_vec(), + return_bytes: return_bytes.to_vec(), + } + } +} /// Auxiliary data for Modexp #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -336,10 +395,10 @@ pub struct EcAddAuxData { } impl EcAddAuxData { - /// Create a new instance of ecrecover auxiliary data. + /// Create a new instance of EcAddAux auxiliary data. pub fn new(input: &[u8], output: &[u8], return_bytes: &[u8]) -> Self { let mut resized_input = input.to_vec(); - resized_input.resize(128, 0u8); + resized_input.resize(BN128ADD_INPUT_LIMIT, 0u8); let mut resized_output = output.to_vec(); resized_output.resize(64, 0u8); @@ -384,7 +443,7 @@ impl EcMulAuxData { /// Create a new instance of EcMul auxiliary data. pub fn new(input: &[u8], output: &[u8], return_bytes: &[u8]) -> Self { let mut resized_input = input.to_vec(); - resized_input.resize(96, 0u8); + resized_input.resize(BN128MUL_INPUT_LIMIT, 0u8); let mut resized_output = output.to_vec(); resized_output.resize(64, 0u8); @@ -457,6 +516,8 @@ pub enum PrecompileAuxData { EcMul(EcMulAuxData), /// EcPairing. EcPairing(Box>), + /// p256Verify + P256Verify(P256VerifyAuxData), } impl Default for PrecompileAuxData { From 9e9d094d350e7f058a7a2a132851f0aabe2c3f42 Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Thu, 31 Oct 2024 15:44:32 +0800 Subject: [PATCH 02/10] add p256 opt_data --- .../src/evm/opcodes/precompiles/mod.rs | 1 + .../evm/opcodes/precompiles/p256_verify.rs | 58 +++++++++++++++++++ eth-types/src/sign_types.rs | 5 ++ 3 files changed, 64 insertions(+) create mode 100644 bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs diff --git a/bus-mapping/src/evm/opcodes/precompiles/mod.rs b/bus-mapping/src/evm/opcodes/precompiles/mod.rs index 71f28d676d..db4ac9d0e1 100644 --- a/bus-mapping/src/evm/opcodes/precompiles/mod.rs +++ b/bus-mapping/src/evm/opcodes/precompiles/mod.rs @@ -14,6 +14,7 @@ mod ec_mul; mod ec_pairing; mod ecrecover; mod modexp; +mod p256_verify; use ec_add::opt_data as opt_data_ec_add; use ec_mul::opt_data as opt_data_ec_mul; diff --git a/bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs b/bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs new file mode 100644 index 0000000000..21d9a33a08 --- /dev/null +++ b/bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs @@ -0,0 +1,58 @@ +use eth_types::{ + sign_types::{biguint_to_32bytes_le, SignData, SECP256R1_Q}, + Bytes, ToBigEndian, ToLittleEndian, +}; +use halo2_proofs::halo2curves::{ + group::{ff::PrimeField, prime::PrimeCurveAffine}, + secp256r1::{Fq, Fp, Secp256r1Affine}, +}; +use halo2_proofs::arithmetic::CurveAffine; +use num::{BigUint, Integer}; + +use crate::{ + circuit_input_builder::PrecompileEvent, + precompile::{P256VerifyAuxData, PrecompileAuxData}, +}; + +pub(crate) fn opt_data( + input_bytes: &[u8], + output_bytes: &[u8], + return_bytes: &[u8], +) -> (Option, Option) { + let aux_data = P256VerifyAuxData::new(input_bytes, output_bytes, return_bytes); + + // We skip the validation through sig circuit if r or s was not in canonical form. + let opt_sig_r: Option = Fq::from_bytes(&aux_data.sig_r.to_le_bytes()).into(); + let opt_sig_s: Option = Fq::from_bytes(&aux_data.sig_s.to_le_bytes()).into(); + let opt_x: Option = Fp::from_bytes(&aux_data.pubkey_x.to_le_bytes()).into(); + let opt_y: Option = Fp::from_bytes(&aux_data.pubkey_y.to_le_bytes()).into(); + + if opt_sig_r.zip(opt_sig_s).is_none() { + return (None, Some(PrecompileAuxData::P256Verify(aux_data))); + } + if opt_x.zip(opt_y).is_none() { + return (None, Some(PrecompileAuxData::P256Verify(aux_data))); + } + + let pk = Secp256r1Affine::from_xy(opt_x.unwrap(), opt_y.unwrap()); + let sign_data = SignData:: { + signature: ( + Fq::from_bytes(&aux_data.sig_r.to_le_bytes()).unwrap(), + Fq::from_bytes(&aux_data.sig_s.to_le_bytes()).unwrap(), + // p256verify has no v field, set 0 + 0, + ), + pk: pk.unwrap(), + msg: Bytes::default(), + msg_hash: { + let msg_hash = BigUint::from_bytes_be(&aux_data.msg_hash.to_be_bytes()); + let msg_hash = msg_hash.mod_floor(&*SECP256R1_Q); + let msg_hash_le = biguint_to_32bytes_le(msg_hash); + Fq::from_repr(msg_hash_le).unwrap() + }, + }; + ( + Some(PrecompileEvent::P256Verify(sign_data)), + Some(PrecompileAuxData::P256Verify(aux_data)), + ) +} diff --git a/eth-types/src/sign_types.rs b/eth-types/src/sign_types.rs index 4e72321141..a03623cd77 100644 --- a/eth-types/src/sign_types.rs +++ b/eth-types/src/sign_types.rs @@ -303,6 +303,11 @@ pub fn recover_pk2( pub static SECP256K1_Q: LazyLock = LazyLock::new(|| BigUint::from_bytes_le(&(Fq_K1::zero() - Fq_K1::one()).to_repr()) + 1u64); +/// Secp256r1 Curve Scalar. Reference: Section 2.4.2 (parameter `n`) in "SEC 2: Recommended +/// Elliptic Curve Domain Parameters" document at http://www.secg.org/sec2-v2.pdf +pub static SECP256R1_Q: LazyLock = + LazyLock::new(|| BigUint::from_bytes_le(&(Fq_R1::zero() - Fq_R1::one()).to_repr()) + 1u64); + /// Helper function to convert a `CtOption` into an `Result`. Similar to /// `Option::ok_or`. pub fn ct_option_ok_or(v: CtOption, err: E) -> Result { From 17342cfd3ef40ad7e052c44bb9c7280796eecd89 Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Thu, 31 Oct 2024 17:15:45 +0800 Subject: [PATCH 03/10] add P256VerifyGadget --- bus-mapping/src/evm/opcodes/callop.rs | 1 + .../src/evm/opcodes/precompiles/mod.rs | 2 + .../evm/opcodes/precompiles/p256_verify.rs | 6 +- .../execution/precompiles/p256_verify.rs | 455 ++++++++++++++++++ zkevm-circuits/src/evm_circuit/step.rs | 3 +- 5 files changed, 463 insertions(+), 4 deletions(-) create mode 100644 zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs diff --git a/bus-mapping/src/evm/opcodes/callop.rs b/bus-mapping/src/evm/opcodes/callop.rs index 89d7a035eb..67535e1f53 100644 --- a/bus-mapping/src/evm/opcodes/callop.rs +++ b/bus-mapping/src/evm/opcodes/callop.rs @@ -1022,6 +1022,7 @@ pub mod tests { max_rws: 3000, ..Default::default() }, + // TODO: add p256verify test here ]; let call_ops = [ diff --git a/bus-mapping/src/evm/opcodes/precompiles/mod.rs b/bus-mapping/src/evm/opcodes/precompiles/mod.rs index db4ac9d0e1..5efe26ff3a 100644 --- a/bus-mapping/src/evm/opcodes/precompiles/mod.rs +++ b/bus-mapping/src/evm/opcodes/precompiles/mod.rs @@ -21,6 +21,7 @@ use ec_mul::opt_data as opt_data_ec_mul; use ec_pairing::opt_data as opt_data_ec_pairing; use ecrecover::opt_data as opt_data_ecrecover; use modexp::opt_data as opt_data_modexp; +use p256_verify::opt_data as opt_data_p256verify; pub fn gen_associated_ops( state: &mut CircuitInputStateRef, @@ -92,6 +93,7 @@ pub fn gen_ops( return_bytes: return_bytes.to_vec(), }), ), + PrecompileCalls::P256Verify => opt_data_p256verify(input_bytes, output_bytes, return_bytes), _ => { log::warn!("precompile {:?} unsupported in circuits", precompile); ( diff --git a/bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs b/bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs index 21d9a33a08..08a1eddc10 100644 --- a/bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs +++ b/bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs @@ -2,11 +2,11 @@ use eth_types::{ sign_types::{biguint_to_32bytes_le, SignData, SECP256R1_Q}, Bytes, ToBigEndian, ToLittleEndian, }; +use halo2_proofs::arithmetic::CurveAffine; use halo2_proofs::halo2curves::{ group::{ff::PrimeField, prime::PrimeCurveAffine}, - secp256r1::{Fq, Fp, Secp256r1Affine}, + secp256r1::{Fp, Fq, Secp256r1Affine}, }; -use halo2_proofs::arithmetic::CurveAffine; use num::{BigUint, Integer}; use crate::{ @@ -39,7 +39,7 @@ pub(crate) fn opt_data( signature: ( Fq::from_bytes(&aux_data.sig_r.to_le_bytes()).unwrap(), Fq::from_bytes(&aux_data.sig_s.to_le_bytes()).unwrap(), - // p256verify has no v field, set 0 + // p256verify has no v field, set 0 0, ), pk: pk.unwrap(), diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs new file mode 100644 index 0000000000..f351b0134e --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs @@ -0,0 +1,455 @@ +use crate::util::Field; +use bus_mapping::precompile::{PrecompileAuxData, PrecompileCalls}; +use eth_types::{evm_types::GasCost, word, ToLittleEndian, U256}; +use gadgets::util::{and, not, or, select, sum, Expr}; +use gadgets::ToScalar; +use halo2_proofs::{ + circuit::Value, + plonk::{Error, Expression}, +}; +use std::sync::LazyLock; + +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_MEMORY_ADDRESS, N_BYTES_WORD}, + step::ExecutionState, + util::{ + common_gadget::RestoreContextGadget, + constraint_builder::{ConstrainBuilderCommon, EVMConstraintBuilder}, + from_bytes, + math_gadget::{IsEqualGadget, IsZeroGadget, LtGadget, LtWordGadget, ModGadget}, + padding_gadget::PaddingGadget, + rlc, CachedRegion, Cell, RandomLinearCombination, Word, + }, + }, + table::CallContextFieldTag, + witness::{Block, Call, ExecStep, Transaction}, +}; + +// secp256r1 Fp +static FQ_MODULUS: LazyLock = + LazyLock::new(|| word!("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551")); + +#[derive(Clone, Debug)] +pub struct P256VerifyGadget { + input_bytes_rlc: Cell, + output_bytes_rlc: Cell, + return_bytes_rlc: Cell, + + pad_right: LtGadget, + padding: PaddingGadget, + + msg_hash_keccak_rlc: Cell, + sig_r_keccak_rlc: Cell, + sig_s_keccak_rlc: Cell, + // recovered_addr_keccak_rlc: RandomLinearCombination, + + msg_hash_raw: Word, + msg_hash: Word, + fq_modulus: Word, + msg_hash_mod: ModGadget, + + sig_r: Word, + sig_r_canonical: LtWordGadget, + sig_s: Word, + sig_s_canonical: LtWordGadget, + + pk_x: Word, + pk_x_canonical: LtWordGadget, + pk_y: Word, + pk_y_canonical: LtWordGadget, + + is_success: Cell, + callee_address: Cell, + is_root: Cell, + call_data_offset: Cell, + call_data_length: Cell, + return_data_offset: Cell, + return_data_length: Cell, + restore_context: RestoreContextGadget, +} + +impl ExecutionGadget for P256VerifyGadget { + const EXECUTION_STATE: ExecutionState = ExecutionState::PRECOMPILE_P256VERIFY; + + const NAME: &'static str = "P256VERIFY"; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + let (input_bytes_rlc, output_bytes_rlc, return_bytes_rlc) = ( + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + ); + let ( + recovered, + msg_hash_keccak_rlc, + sig_r_keccak_rlc, + sig_s_keccak_rlc, + recovered_addr_keccak_rlc, + ) = ( + cb.query_bool(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_keccak_rlc(), + ); + + let msg_hash_raw = cb.query_word_rlc(); + let msg_hash = cb.query_word_rlc(); + let fq_modulus = cb.query_word_rlc(); + let msg_hash_mod = ModGadget::construct(cb, [&msg_hash_raw, &fq_modulus, &msg_hash]); + + let sig_r = cb.query_word_rlc(); + let sig_r_canonical = LtWordGadget::construct(cb, &sig_r, &fq_modulus); + let sig_s = cb.query_word_rlc(); + let sig_s_canonical = LtWordGadget::construct(cb, &sig_s, &fq_modulus); + let r_s_canonical = and::expr([sig_r_canonical.expr(), sig_s_canonical.expr()]); + + cb.require_equal( + "msg hash cells assigned incorrectly", + msg_hash_keccak_rlc.expr(), + cb.keccak_rlc::( + msg_hash_raw + .cells + .iter() + .map(Expr::expr) + .collect::>>() + .try_into() + .expect("msg hash is 32 bytes"), + ), + ); + cb.require_equal( + "sig_r cells assigned incorrectly", + sig_r_keccak_rlc.expr(), + cb.keccak_rlc::( + sig_r + .cells + .iter() + .map(Expr::expr) + .collect::>>() + .try_into() + .expect("msg hash is 32 bytes"), + ), + ); + cb.require_equal( + "sig_s cells assigned incorrectly", + sig_s_keccak_rlc.expr(), + cb.keccak_rlc::( + sig_s + .cells + .iter() + .map(Expr::expr) + .collect::>>() + .try_into() + .expect("msg hash is 32 bytes"), + ), + ); + + cb.require_equal( + "Secp256r1::Fq modulus assigned correctly", + fq_modulus.expr(), + cb.word_rlc::(FQ_MODULUS.to_le_bytes().map(|b| b.expr())), + ); + + let [is_success, callee_address, is_root, call_data_offset, call_data_length, return_data_offset, return_data_length] = + [ + CallContextFieldTag::IsSuccess, + CallContextFieldTag::CalleeAddress, + CallContextFieldTag::IsRoot, + CallContextFieldTag::CallDataOffset, + CallContextFieldTag::CallDataLength, + CallContextFieldTag::ReturnDataOffset, + CallContextFieldTag::ReturnDataLength, + ] + .map(|tag| cb.call_context(None, tag)); + + let gas_cost = select::expr( + is_success.expr(), + GasCost::PRECOMPILE_ECRECOVER_BASE.expr(), + cb.curr.state.gas_left.expr(), + ); + + // lookup to the sign_verify table: + // + // || msg_hash | v | r | s | recovered_addr | recovered || + cb.condition( + and::expr([r_s_canonical.expr(), sig_v_valid.expr()]), + |cb| { + cb.sig_table_lookup( + msg_hash.expr(), + sig_v.cells[0].expr() - 27.expr(), + sig_r.expr(), + sig_s.expr(), + select::expr( + recovered.expr(), + from_bytes::expr(&recovered_addr_keccak_rlc.cells), + 0.expr(), + ), + recovered.expr(), + ); + }, + ); + cb.condition(not::expr(r_s_canonical.expr()), |cb| { + cb.require_zero( + "recovered == false if r or s not canonical", + recovered.expr(), + ); + }); + // cb.condition(not::expr(recovered.expr()), |cb| { + // cb.require_zero( + // "address == 0 if address could not be recovered", + // recovered_addr_keccak_rlc.expr(), + // ); + // }); + + cb.precompile_info_lookup( + cb.execution_state().as_u64().expr(), + callee_address.expr(), + cb.execution_state().precompile_base_gas_cost().expr(), + ); + + let required_input_len = 128.expr(); + let pad_right = LtGadget::construct(cb, call_data_length.expr(), required_input_len.expr()); + let padding = cb.condition(pad_right.expr(), |cb| { + PaddingGadget::construct( + cb, + input_bytes_rlc.expr(), + call_data_length.expr(), + required_input_len, + ) + }); + cb.condition(not::expr(pad_right.expr()), |cb| { + cb.require_equal( + "no padding implies padded bytes == input bytes", + padding.padded_rlc(), + input_bytes_rlc.expr(), + ); + }); + let (r_pow_32, r_pow_64, r_pow_96) = { + let challenges = cb.challenges().keccak_powers_of_randomness::<16>(); + let r_pow_16 = challenges[15].clone(); + let r_pow_32 = r_pow_16.square(); + let r_pow_64 = r_pow_32.expr().square(); + let r_pow_96 = r_pow_64.expr() * r_pow_32.expr(); + (r_pow_32, r_pow_64, r_pow_96) + }; + cb.require_equal( + "input bytes (RLC) = [msg_hash | sig_v_rlc | sig_r | sig_s]", + padding.padded_rlc(), + (msg_hash_keccak_rlc.expr() * r_pow_96) + + (sig_v_keccak_rlc.expr() * r_pow_64) + + (sig_r_keccak_rlc.expr() * r_pow_32) + + sig_s_keccak_rlc.expr(), + ); + // RLC of output bytes always equals RLC of the recovered address. + cb.require_equal( + "output bytes (RLC) = recovered address", + output_bytes_rlc.expr(), + recovered_addr_keccak_rlc.expr(), + ); + // If the address was not recovered, RLC(address) == RLC(output) == 0. + cb.condition(not::expr(recovered.expr()), |cb| { + cb.require_zero("output bytes == 0", output_bytes_rlc.expr()); + }); + + let restore_context = super::gen_restore_context( + cb, + is_root.expr(), + is_success.expr(), + gas_cost.expr(), + select::expr(recovered.expr(), 0x20.expr(), 0x00.expr()), // ReturnDataLength + ); + + Self { + input_bytes_rlc, + output_bytes_rlc, + return_bytes_rlc, + + pad_right, + padding, + + recovered, + msg_hash_keccak_rlc, + sig_v_keccak_rlc, + sig_r_keccak_rlc, + sig_s_keccak_rlc, + recovered_addr_keccak_rlc, + + msg_hash_raw, + msg_hash, + fq_modulus, + msg_hash_mod, + + sig_r, + sig_r_canonical, + sig_s, + sig_s_canonical, + + is_success, + callee_address, + is_root, + call_data_offset, + call_data_length, + return_data_offset, + return_data_length, + restore_context, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + if let Some(PrecompileAuxData::Ecrecover(aux_data)) = &step.aux_data { + self.input_bytes_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(aux_data.input_bytes.iter().rev(), r)), + )?; + self.output_bytes_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(aux_data.output_bytes.iter().rev(), r)), + )?; + self.return_bytes_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(aux_data.return_bytes.iter().rev(), r)), + )?; + let recovered = !aux_data.recovered_addr.is_zero(); + self.recovered + .assign(region, offset, Value::known(F::from(recovered as u64)))?; + self.msg_hash_keccak_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(&aux_data.msg_hash.to_le_bytes(), r)), + )?; + self.sig_v_keccak_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(&aux_data.sig_v.to_le_bytes(), r)), + )?; + self.sig_r_keccak_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(&aux_data.sig_r.to_le_bytes(), r)), + )?; + self.sig_s_keccak_rlc.assign( + region, + offset, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(&aux_data.sig_s.to_le_bytes(), r)), + )?; + for (word_rlc, value) in [ + (&self.msg_hash_raw, aux_data.msg_hash), + (&self.sig_r, aux_data.sig_r), + (&self.sig_s, aux_data.sig_s), + ] { + word_rlc.assign(region, offset, Some(value.to_le_bytes()))?; + } + let (quotient, remainder) = aux_data.msg_hash.div_mod(*FQ_MODULUS); + self.msg_hash + .assign(region, offset, Some(remainder.to_le_bytes()))?; + self.fq_modulus + .assign(region, offset, Some(FQ_MODULUS.to_le_bytes()))?; + self.msg_hash_mod.assign( + region, + offset, + aux_data.msg_hash, + *FQ_MODULUS, + remainder, + quotient, + )?; + self.sig_r_canonical + .assign(region, offset, aux_data.sig_r, *FQ_MODULUS)?; + self.sig_s_canonical + .assign(region, offset, aux_data.sig_s, *FQ_MODULUS)?; + // self.recovered_addr_keccak_rlc.assign( + // region, + // offset, + // Some({ + // let mut recovered_addr = aux_data.recovered_addr.to_fixed_bytes(); + // recovered_addr.reverse(); + // recovered_addr + // }), + // )?; + self.pad_right + .assign(region, offset, call.call_data_length.into(), 128.into())?; + self.padding.assign( + region, + offset, + PrecompileCalls::Ecrecover, + region + .challenges() + .keccak_input() + .map(|r| rlc::value(aux_data.input_bytes.iter().rev(), r)), + call.call_data_length, + region.challenges().keccak_input(), + )?; + } else { + log::error!("unexpected aux_data {:?} for ecrecover", step.aux_data); + return Err(Error::Synthesis); + } + + self.is_success.assign( + region, + offset, + Value::known(F::from(u64::from(call.is_success))), + )?; + self.callee_address.assign( + region, + offset, + Value::known(call.code_address.unwrap().to_scalar().unwrap()), + )?; + self.is_root + .assign(region, offset, Value::known(F::from(call.is_root as u64)))?; + self.call_data_offset.assign( + region, + offset, + Value::known(F::from(call.call_data_offset)), + )?; + self.call_data_length.assign( + region, + offset, + Value::known(F::from(call.call_data_length)), + )?; + self.return_data_offset.assign( + region, + offset, + Value::known(F::from(call.return_data_offset)), + )?; + self.return_data_length.assign( + region, + offset, + Value::known(F::from(call.return_data_length)), + )?; + self.restore_context + .assign(region, offset, block, call, step, 7) + } +} diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index cdb1411810..9db4b5142c 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -30,7 +30,7 @@ impl From for ExecutionState { PrecompileCalls::Bn128Mul => ExecutionState::PrecompileBn256ScalarMul, PrecompileCalls::Bn128Pairing => ExecutionState::PrecompileBn256Pairing, PrecompileCalls::Blake2F => ExecutionState::PrecompileBlake2f, - PrecompileCalls::P256Verify => todo!("P256Verify"), + PrecompileCalls::P256Verify => ExecutionState::PrecompileP256Verify, } } } @@ -141,6 +141,7 @@ pub enum ExecutionState { PrecompileBn256ScalarMul, PrecompileBn256Pairing, PrecompileBlake2f, + PrecompileP256Verify, } impl Default for ExecutionState { From 34e035f906f9fd62a91e3f61026f59b465e85588 Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Fri, 1 Nov 2024 14:43:12 +0800 Subject: [PATCH 04/10] add to mod & remove v --- .../src/evm_circuit/execution/precompiles/mod.rs | 3 +++ .../execution/precompiles/p256_verify.rs | 13 ++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs index b6cf969c1e..0c9d054de1 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs @@ -40,6 +40,9 @@ pub use identity::IdentityGadget; mod sha256; pub use sha256::SHA256Gadget; +mod p256_verify; +pub use p256_verify::P256VerifyGadget; + /// build RestoreContextGadget with consideration for root calling /// MUST be called after all rw has completed since we use `rw_counter_offset`` pub fn gen_restore_context( diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs index f351b0134e..1fd85926f0 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs @@ -27,7 +27,7 @@ use crate::{ witness::{Block, Call, ExecStep, Transaction}, }; -// secp256r1 Fp +// secp256r1 Fq static FQ_MODULUS: LazyLock = LazyLock::new(|| word!("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551")); @@ -82,13 +82,11 @@ impl ExecutionGadget for P256VerifyGadget { cb.query_cell_phase2(), ); let ( - recovered, msg_hash_keccak_rlc, sig_r_keccak_rlc, sig_s_keccak_rlc, recovered_addr_keccak_rlc, ) = ( - cb.query_bool(), cb.query_cell_phase2(), cb.query_cell_phase2(), cb.query_cell_phase2(), @@ -173,12 +171,12 @@ impl ExecutionGadget for P256VerifyGadget { // lookup to the sign_verify table: // // || msg_hash | v | r | s | recovered_addr | recovered || - cb.condition( - and::expr([r_s_canonical.expr(), sig_v_valid.expr()]), + cb.condition(r_s_canonical.expr(), |cb| { cb.sig_table_lookup( msg_hash.expr(), - sig_v.cells[0].expr() - 27.expr(), + // v set zero + 0.expr(), sig_r.expr(), sig_s.expr(), select::expr( @@ -190,6 +188,7 @@ impl ExecutionGadget for P256VerifyGadget { ); }, ); + // TODO: check x, y is canonical cb.condition(not::expr(r_s_canonical.expr()), |cb| { cb.require_zero( "recovered == false if r or s not canonical", @@ -209,7 +208,7 @@ impl ExecutionGadget for P256VerifyGadget { cb.execution_state().precompile_base_gas_cost().expr(), ); - let required_input_len = 128.expr(); + let required_input_len = 160.expr(); let pad_right = LtGadget::construct(cb, call_data_length.expr(), required_input_len.expr()); let padding = cb.condition(pad_right.expr(), |cb| { PaddingGadget::construct( From 025ddd7cdddc2806fc31021f8143568c3a5aaf58 Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Fri, 1 Nov 2024 17:13:28 +0800 Subject: [PATCH 05/10] add FQ_MODULUS --- .../execution/precompiles/p256_verify.rs | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs index 1fd85926f0..2e5e13d743 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs @@ -30,7 +30,11 @@ use crate::{ // secp256r1 Fq static FQ_MODULUS: LazyLock = LazyLock::new(|| word!("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551")); - + +// secp256r1 Fp +static FP_MODULUS: LazyLock = + LazyLock::new(|| word!("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff")); + #[derive(Clone, Debug)] pub struct P256VerifyGadget { input_bytes_rlc: Cell, @@ -44,12 +48,16 @@ pub struct P256VerifyGadget { sig_r_keccak_rlc: Cell, sig_s_keccak_rlc: Cell, // recovered_addr_keccak_rlc: RandomLinearCombination, + pubkey_x_keccak_rlc: Cell, + pubkey_y_keccak_rlc: Cell, msg_hash_raw: Word, msg_hash: Word, fq_modulus: Word, msg_hash_mod: ModGadget, + fp_modulus: Word, + sig_r: Word, sig_r_canonical: LtWordGadget, sig_s: Word, @@ -85,17 +93,19 @@ impl ExecutionGadget for P256VerifyGadget { msg_hash_keccak_rlc, sig_r_keccak_rlc, sig_s_keccak_rlc, - recovered_addr_keccak_rlc, + //recovered_addr_keccak_rlc, ) = ( cb.query_cell_phase2(), cb.query_cell_phase2(), cb.query_cell_phase2(), - cb.query_keccak_rlc(), + //cb.query_keccak_rlc(), ); let msg_hash_raw = cb.query_word_rlc(); let msg_hash = cb.query_word_rlc(); let fq_modulus = cb.query_word_rlc(); + let fp_modulus = cb.query_word_rlc(); + let msg_hash_mod = ModGadget::construct(cb, [&msg_hash_raw, &fq_modulus, &msg_hash]); let sig_r = cb.query_word_rlc(); @@ -104,6 +114,11 @@ impl ExecutionGadget for P256VerifyGadget { let sig_s_canonical = LtWordGadget::construct(cb, &sig_s, &fq_modulus); let r_s_canonical = and::expr([sig_r_canonical.expr(), sig_s_canonical.expr()]); + let pk_x = cb.query_word_rlc(); + let pk_y = cb.query_word_rlc(); + let pk_x_canonical = LtWordGadget::construct(cb, &pk_x, &fp_modulus); + let pk_y_canonical = LtWordGadget::construct(cb, &pk_y, &fp_modulus); + cb.require_equal( "msg hash cells assigned incorrectly", msg_hash_keccak_rlc.expr(), @@ -149,6 +164,11 @@ impl ExecutionGadget for P256VerifyGadget { fq_modulus.expr(), cb.word_rlc::(FQ_MODULUS.to_le_bytes().map(|b| b.expr())), ); + cb.require_equal( + "Secp256r1::Fp modulus assigned correctly", + fp_modulus.expr(), + cb.word_rlc::(FP_MODULUS.to_le_bytes().map(|b| b.expr())), + ); let [is_success, callee_address, is_root, call_data_offset, call_data_length, return_data_offset, return_data_length] = [ @@ -241,17 +261,13 @@ impl ExecutionGadget for P256VerifyGadget { + (sig_r_keccak_rlc.expr() * r_pow_32) + sig_s_keccak_rlc.expr(), ); - // RLC of output bytes always equals RLC of the recovered address. - cb.require_equal( - "output bytes (RLC) = recovered address", - output_bytes_rlc.expr(), - recovered_addr_keccak_rlc.expr(), - ); - // If the address was not recovered, RLC(address) == RLC(output) == 0. - cb.condition(not::expr(recovered.expr()), |cb| { - cb.require_zero("output bytes == 0", output_bytes_rlc.expr()); - }); - + // TODO: constrain output first byte is bool . + // cb.require_equal( + // "output bytes (RLC) = recovered address", + // output_bytes_rlc.expr(), + // recovered_addr_keccak_rlc.expr(), + // ); + let restore_context = super::gen_restore_context( cb, is_root.expr(), @@ -285,6 +301,10 @@ impl ExecutionGadget for P256VerifyGadget { sig_s, sig_s_canonical, + pk_x, + pk_x_canonical, + pk_y, + pk_y_canonical, is_success, callee_address, is_root, @@ -305,7 +325,7 @@ impl ExecutionGadget for P256VerifyGadget { call: &Call, step: &ExecStep, ) -> Result<(), Error> { - if let Some(PrecompileAuxData::Ecrecover(aux_data)) = &step.aux_data { + if let Some(PrecompileAuxData::P256Verify(aux_data)) = &step.aux_data { self.input_bytes_rlc.assign( region, offset, @@ -330,9 +350,7 @@ impl ExecutionGadget for P256VerifyGadget { .keccak_input() .map(|r| rlc::value(aux_data.return_bytes.iter().rev(), r)), )?; - let recovered = !aux_data.recovered_addr.is_zero(); - self.recovered - .assign(region, offset, Value::known(F::from(recovered as u64)))?; + // check is_valid of sig ? self.msg_hash_keccak_rlc.assign( region, offset, @@ -341,14 +359,7 @@ impl ExecutionGadget for P256VerifyGadget { .keccak_input() .map(|r| rlc::value(&aux_data.msg_hash.to_le_bytes(), r)), )?; - self.sig_v_keccak_rlc.assign( - region, - offset, - region - .challenges() - .keccak_input() - .map(|r| rlc::value(&aux_data.sig_v.to_le_bytes(), r)), - )?; + self.sig_r_keccak_rlc.assign( region, offset, @@ -377,6 +388,8 @@ impl ExecutionGadget for P256VerifyGadget { .assign(region, offset, Some(remainder.to_le_bytes()))?; self.fq_modulus .assign(region, offset, Some(FQ_MODULUS.to_le_bytes()))?; + self.fp_modulus + .assign(region, offset, Some(FP_MODULUS.to_le_bytes()))?; self.msg_hash_mod.assign( region, offset, From 049c627a206be49acdfad100915b3622898faabe Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Mon, 4 Nov 2024 10:12:07 +0800 Subject: [PATCH 06/10] constrain x y canonical --- zkevm-circuits/src/evm_circuit/execution.rs | 6 ++- .../execution/precompiles/p256_verify.rs | 42 ++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 934c299530..bef2558299 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -216,7 +216,7 @@ use pc::PcGadget; use pop::PopGadget; use precompiles::{ BasePrecompileGadget, EcAddGadget, EcMulGadget, EcPairingGadget, EcrecoverGadget, - IdentityGadget, ModExpGadget, SHA256Gadget, + IdentityGadget, ModExpGadget, SHA256Gadget, P256VerifyGadget, }; use push::PushGadget; use return_revert::ReturnRevertGadget; @@ -381,6 +381,7 @@ pub(crate) struct ExecutionConfig { precompile_bn128mul_gadget: Box>, precompile_bn128pairing_gadget: Box>, precompile_blake2f_gadget: Box>, + precompile_p256verify_gadget: Box>, } impl ExecutionConfig { @@ -1746,6 +1747,9 @@ impl ExecutionConfig { ExecutionState::PrecompileBlake2f => { assign_exec_step!(self.precompile_blake2f_gadget) } + ExecutionState::PrecompileP256Verify => { + assign_exec_step!(self.precompile_p256verify_gadget) + } } // Fill in the witness values for stored expressions diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs index 2e5e13d743..36229e1618 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs @@ -67,6 +67,7 @@ pub struct P256VerifyGadget { pk_x_canonical: LtWordGadget, pk_y: Word, pk_y_canonical: LtWordGadget, + is_valid: Cell, is_success: Cell, callee_address: Cell, @@ -90,11 +91,13 @@ impl ExecutionGadget for P256VerifyGadget { cb.query_cell_phase2(), ); let ( + is_valid, msg_hash_keccak_rlc, sig_r_keccak_rlc, sig_s_keccak_rlc, //recovered_addr_keccak_rlc, ) = ( + cb.query_bool(), cb.query_cell_phase2(), cb.query_cell_phase2(), cb.query_cell_phase2(), @@ -119,6 +122,8 @@ impl ExecutionGadget for P256VerifyGadget { let pk_x_canonical = LtWordGadget::construct(cb, &pk_x, &fp_modulus); let pk_y_canonical = LtWordGadget::construct(cb, &pk_y, &fp_modulus); + let x_y_canonical = and::expr([pk_x_canonical.expr(), pk_y_canonical.expr()]); + cb.require_equal( "msg hash cells assigned incorrectly", msg_hash_keccak_rlc.expr(), @@ -199,20 +204,25 @@ impl ExecutionGadget for P256VerifyGadget { 0.expr(), sig_r.expr(), sig_s.expr(), - select::expr( - recovered.expr(), - from_bytes::expr(&recovered_addr_keccak_rlc.cells), - 0.expr(), - ), - recovered.expr(), + // recovered addr set to 0. + 0.expr(), + is_valid.expr(), ); }, ); - // TODO: check x, y is canonical + // check r, s is canonical cb.condition(not::expr(r_s_canonical.expr()), |cb| { cb.require_zero( - "recovered == false if r or s not canonical", - recovered.expr(), + "is_valid == false if r or s not canonical", + is_valid.expr(), + ); + }); + + // check x, y is canonical + cb.condition(not::expr(x_y_canonical.expr()), |cb| { + cb.require_zero( + "is_valid == false if x or y not canonical", + is_valid.expr(), ); }); // cb.condition(not::expr(recovered.expr()), |cb| { @@ -283,13 +293,10 @@ impl ExecutionGadget for P256VerifyGadget { pad_right, padding, - - recovered, msg_hash_keccak_rlc, - sig_v_keccak_rlc, sig_r_keccak_rlc, sig_s_keccak_rlc, - recovered_addr_keccak_rlc, + //recovered_addr_keccak_rlc, msg_hash_raw, msg_hash, @@ -380,6 +387,8 @@ impl ExecutionGadget for P256VerifyGadget { (&self.msg_hash_raw, aux_data.msg_hash), (&self.sig_r, aux_data.sig_r), (&self.sig_s, aux_data.sig_s), + (&self.pk_x, aux_data.pubkey_x), + (&self.pk_y, aux_data.pubkey_y), ] { word_rlc.assign(region, offset, Some(value.to_le_bytes()))?; } @@ -402,6 +411,9 @@ impl ExecutionGadget for P256VerifyGadget { .assign(region, offset, aux_data.sig_r, *FQ_MODULUS)?; self.sig_s_canonical .assign(region, offset, aux_data.sig_s, *FQ_MODULUS)?; + // assign pk_x_canonical, pk_y_canonical + self.pk_x_canonical.assign(region, offset, aux_data.pubkey_x, *FP_MODULUS)?; + self.pk_y_canonical.assign(region, offset, aux_data.pubkey_y, *FP_MODULUS)?; // self.recovered_addr_keccak_rlc.assign( // region, // offset, @@ -416,7 +428,7 @@ impl ExecutionGadget for P256VerifyGadget { self.padding.assign( region, offset, - PrecompileCalls::Ecrecover, + PrecompileCalls::P256Verify, region .challenges() .keccak_input() @@ -425,7 +437,7 @@ impl ExecutionGadget for P256VerifyGadget { region.challenges().keccak_input(), )?; } else { - log::error!("unexpected aux_data {:?} for ecrecover", step.aux_data); + log::error!("unexpected aux_data {:?} for p256verify", step.aux_data); return Err(Error::Synthesis); } From fa2c940e4da0ac2e27f84e2e98e31bae2b702269 Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Mon, 4 Nov 2024 11:38:05 +0800 Subject: [PATCH 07/10] gas cost &comment x, y keccak --- zkevm-circuits/src/evm_circuit/execution.rs | 1 + .../execution/precompiles/p256_verify.rs | 20 ++++++++++--------- zkevm-circuits/src/sig_circuit.rs | 1 + 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index bef2558299..006a1d6834 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -678,6 +678,7 @@ impl ExecutionConfig { precompile_bn128mul_gadget: configure_gadget!(), precompile_bn128pairing_gadget: configure_gadget!(), precompile_blake2f_gadget: configure_gadget!(), + precompile_p256verify_gadget: configure_gadget!(), // step and presets step: step_curr, height_map, diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs index 36229e1618..85d6e8f97d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs @@ -47,9 +47,8 @@ pub struct P256VerifyGadget { msg_hash_keccak_rlc: Cell, sig_r_keccak_rlc: Cell, sig_s_keccak_rlc: Cell, - // recovered_addr_keccak_rlc: RandomLinearCombination, - pubkey_x_keccak_rlc: Cell, - pubkey_y_keccak_rlc: Cell, + // pubkey_x_keccak_rlc: Cell, + // pubkey_y_keccak_rlc: Cell, msg_hash_raw: Word, msg_hash: Word, @@ -80,7 +79,7 @@ pub struct P256VerifyGadget { } impl ExecutionGadget for P256VerifyGadget { - const EXECUTION_STATE: ExecutionState = ExecutionState::PRECOMPILE_P256VERIFY; + const EXECUTION_STATE: ExecutionState = ExecutionState::PrecompileP256Verify; const NAME: &'static str = "P256VERIFY"; @@ -189,18 +188,18 @@ impl ExecutionGadget for P256VerifyGadget { let gas_cost = select::expr( is_success.expr(), - GasCost::PRECOMPILE_ECRECOVER_BASE.expr(), + GasCost::PRECOMPILE_P256VERIFY.expr(), cb.curr.state.gas_left.expr(), ); // lookup to the sign_verify table: // - // || msg_hash | v | r | s | recovered_addr | recovered || + // || msg_hash | v(0) | r | s | recovered_addr(0) | is_valid || cb.condition(r_s_canonical.expr(), |cb| { cb.sig_table_lookup( msg_hash.expr(), - // v set zero + // v set to zero 0.expr(), sig_r.expr(), sig_s.expr(), @@ -267,7 +266,7 @@ impl ExecutionGadget for P256VerifyGadget { "input bytes (RLC) = [msg_hash | sig_v_rlc | sig_r | sig_s]", padding.padded_rlc(), (msg_hash_keccak_rlc.expr() * r_pow_96) - + (sig_v_keccak_rlc.expr() * r_pow_64) + + (0.expr() * r_pow_64) + (sig_r_keccak_rlc.expr() * r_pow_32) + sig_s_keccak_rlc.expr(), ); @@ -283,7 +282,8 @@ impl ExecutionGadget for P256VerifyGadget { is_root.expr(), is_success.expr(), gas_cost.expr(), - select::expr(recovered.expr(), 0x20.expr(), 0x00.expr()), // ReturnDataLength + // ReturnDataLength + select::expr(is_valid.expr(), 0x20.expr(), 0x00.expr()), ); Self { @@ -302,6 +302,7 @@ impl ExecutionGadget for P256VerifyGadget { msg_hash, fq_modulus, msg_hash_mod, + fp_modulus, sig_r, sig_r_canonical, @@ -312,6 +313,7 @@ impl ExecutionGadget for P256VerifyGadget { pk_x_canonical, pk_y, pk_y_canonical, + is_valid, is_success, callee_address, is_root, diff --git a/zkevm-circuits/src/sig_circuit.rs b/zkevm-circuits/src/sig_circuit.rs index c138fab3c0..38fd282f42 100644 --- a/zkevm-circuits/src/sig_circuit.rs +++ b/zkevm-circuits/src/sig_circuit.rs @@ -785,6 +785,7 @@ impl SigCircuit { // address is the random linear combination of the public key // it is fine to use a phase 1 gate here + // TODO: for p256verify no address field is needed, check if need to change to zero ? let address = ecdsa_chip.range.gate.inner_product( ctx, powers_of_256_cells[..20].to_vec(), From 8cac694e03f7ebdf18c6ede4058b923932e37e67 Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Mon, 4 Nov 2024 16:12:20 +0800 Subject: [PATCH 08/10] check & assign is_valid --- eth-types/src/sign_types.rs | 20 +++++ zkevm-circuits/src/evm_circuit/execution.rs | 2 +- .../execution/precompiles/p256_verify.rs | 74 +++++++++---------- 3 files changed, 58 insertions(+), 38 deletions(-) diff --git a/eth-types/src/sign_types.rs b/eth-types/src/sign_types.rs index a03623cd77..0ad856d87d 100644 --- a/eth-types/src/sign_types.rs +++ b/eth-types/src/sign_types.rs @@ -97,6 +97,26 @@ pub fn verify< r == r_candidate } +/// verify r1 signature from bytes representation. +pub fn verify_r1_bytes( + pub_key: (&[u8; 32], &[u8; 32]), + r: &[u8; 32], + s: &[u8; 32], + msg_hash: &[u8; 32], + // if pubkey is provided rather than from recovered , v is not necessary. + _v: Option, +) -> bool { + // Verify + let x = Fp_R1::from_bytes(pub_key.0); + let y = Fp_R1::from_bytes(pub_key.1); + let pk = Secp256r1Affine::from_xy(x.unwrap(), y.unwrap()).unwrap(); + let r = Fq_R1::from_bytes(r).unwrap(); + let s = Fq_R1::from_bytes(s).unwrap(); + let msg_hash = Fq_R1::from_bytes(msg_hash).unwrap(); + + verify(pk, r, s, msg_hash, None) +} + // convert Fp to Fq fn mod_n, Fq: PrimeField + FromUniformBytes<64>>(x: Fp) -> Fq { let mut x_repr = [0u8; 32]; diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 006a1d6834..37349a70d1 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -216,7 +216,7 @@ use pc::PcGadget; use pop::PopGadget; use precompiles::{ BasePrecompileGadget, EcAddGadget, EcMulGadget, EcPairingGadget, EcrecoverGadget, - IdentityGadget, ModExpGadget, SHA256Gadget, P256VerifyGadget, + IdentityGadget, ModExpGadget, P256VerifyGadget, SHA256Gadget, }; use push::PushGadget; use return_revert::ReturnRevertGadget; diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs index 85d6e8f97d..27fa95c11d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs @@ -1,6 +1,6 @@ use crate::util::Field; use bus_mapping::precompile::{PrecompileAuxData, PrecompileCalls}; -use eth_types::{evm_types::GasCost, word, ToLittleEndian, U256}; +use eth_types::{evm_types::GasCost, sign_types::verify_r1_bytes, word, ToLittleEndian, U256}; use gadgets::util::{and, not, or, select, sum, Expr}; use gadgets::ToScalar; use halo2_proofs::{ @@ -49,7 +49,6 @@ pub struct P256VerifyGadget { sig_s_keccak_rlc: Cell, // pubkey_x_keccak_rlc: Cell, // pubkey_y_keccak_rlc: Cell, - msg_hash_raw: Word, msg_hash: Word, fq_modulus: Word, @@ -120,7 +119,7 @@ impl ExecutionGadget for P256VerifyGadget { let pk_y = cb.query_word_rlc(); let pk_x_canonical = LtWordGadget::construct(cb, &pk_x, &fp_modulus); let pk_y_canonical = LtWordGadget::construct(cb, &pk_y, &fp_modulus); - + let x_y_canonical = and::expr([pk_x_canonical.expr(), pk_y_canonical.expr()]); cb.require_equal( @@ -195,34 +194,26 @@ impl ExecutionGadget for P256VerifyGadget { // lookup to the sign_verify table: // // || msg_hash | v(0) | r | s | recovered_addr(0) | is_valid || - cb.condition(r_s_canonical.expr(), - |cb| { - cb.sig_table_lookup( - msg_hash.expr(), - // v set to zero - 0.expr(), - sig_r.expr(), - sig_s.expr(), - // recovered addr set to 0. - 0.expr(), - is_valid.expr(), - ); - }, - ); - // check r, s is canonical - cb.condition(not::expr(r_s_canonical.expr()), |cb| { - cb.require_zero( - "is_valid == false if r or s not canonical", + cb.condition(r_s_canonical.expr(), |cb| { + cb.sig_table_lookup( + msg_hash.expr(), + // v set to zero + 0.expr(), + sig_r.expr(), + sig_s.expr(), + // recovered addr set to 0. + 0.expr(), is_valid.expr(), ); }); + // check r, s is canonical + cb.condition(not::expr(r_s_canonical.expr()), |cb| { + cb.require_zero("is_valid == false if r or s not canonical", is_valid.expr()); + }); // check x, y is canonical cb.condition(not::expr(x_y_canonical.expr()), |cb| { - cb.require_zero( - "is_valid == false if x or y not canonical", - is_valid.expr(), - ); + cb.require_zero("is_valid == false if x or y not canonical", is_valid.expr()); }); // cb.condition(not::expr(recovered.expr()), |cb| { // cb.require_zero( @@ -270,13 +261,9 @@ impl ExecutionGadget for P256VerifyGadget { + (sig_r_keccak_rlc.expr() * r_pow_32) + sig_s_keccak_rlc.expr(), ); - // TODO: constrain output first byte is bool . - // cb.require_equal( - // "output bytes (RLC) = recovered address", - // output_bytes_rlc.expr(), - // recovered_addr_keccak_rlc.expr(), - // ); - + // constrain output first byte is bool . + cb.require_boolean("output first byte is bool", output_bytes_rlc.expr()); + let restore_context = super::gen_restore_context( cb, is_root.expr(), @@ -297,7 +284,6 @@ impl ExecutionGadget for P256VerifyGadget { sig_r_keccak_rlc, sig_s_keccak_rlc, //recovered_addr_keccak_rlc, - msg_hash_raw, msg_hash, fq_modulus, @@ -359,7 +345,6 @@ impl ExecutionGadget for P256VerifyGadget { .keccak_input() .map(|r| rlc::value(aux_data.return_bytes.iter().rev(), r)), )?; - // check is_valid of sig ? self.msg_hash_keccak_rlc.assign( region, offset, @@ -368,7 +353,7 @@ impl ExecutionGadget for P256VerifyGadget { .keccak_input() .map(|r| rlc::value(&aux_data.msg_hash.to_le_bytes(), r)), )?; - + self.sig_r_keccak_rlc.assign( region, offset, @@ -414,8 +399,23 @@ impl ExecutionGadget for P256VerifyGadget { self.sig_s_canonical .assign(region, offset, aux_data.sig_s, *FQ_MODULUS)?; // assign pk_x_canonical, pk_y_canonical - self.pk_x_canonical.assign(region, offset, aux_data.pubkey_x, *FP_MODULUS)?; - self.pk_y_canonical.assign(region, offset, aux_data.pubkey_y, *FP_MODULUS)?; + self.pk_x_canonical + .assign(region, offset, aux_data.pubkey_x, *FP_MODULUS)?; + self.pk_y_canonical + .assign(region, offset, aux_data.pubkey_y, *FP_MODULUS)?; + // TODO: assign is_valid correctly + let pub_key_bytes = ( + &aux_data.pubkey_x.to_le_bytes(), + &aux_data.pubkey_y.to_le_bytes(), + ); + let r_bytes = aux_data.sig_r.to_le_bytes(); + let s_bytes = aux_data.sig_s.to_le_bytes(); + let msg_hash_bytes = aux_data.msg_hash.to_le_bytes(); + + let is_sig_valid = + verify_r1_bytes(pub_key_bytes, &r_bytes, &s_bytes, &msg_hash_bytes, None); + self.is_valid + .assign(region, offset, Value::known(F::from(is_sig_valid)))?; // self.recovered_addr_keccak_rlc.assign( // region, // offset, From 2ce126a48569c3746012a73e307e16946d484177 Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Mon, 4 Nov 2024 17:22:23 +0800 Subject: [PATCH 09/10] is_precompiled include p256 --- zkevm-circuits/src/evm_circuit/step.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index 9db4b5142c..f5723b4565 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -179,6 +179,7 @@ impl ExecutionState { | Self::PrecompileBlake2f | Self::ErrorOutOfGasPrecompile | Self::ErrorPrecompileFailed + | Self::PrecompileP256Verify ) } @@ -193,6 +194,7 @@ impl ExecutionState { Self::PrecompileBn256ScalarMul => PrecompileCalls::Bn128Mul, Self::PrecompileBn256Pairing => PrecompileCalls::Bn128Pairing, Self::PrecompileBlake2f => PrecompileCalls::Blake2F, + Self::PrecompileP256Verify => PrecompileCalls::P256Verify, _ => return GasCost(0), }) .base_gas_cost() From 70e70f25e5cddf0d77b6b1f65cf344de04d665d3 Mon Sep 17 00:00:00 2001 From: Dream Wu Date: Tue, 5 Nov 2024 09:50:42 +0800 Subject: [PATCH 10/10] add test vector --- .../execution/precompiles/p256_verify.rs | 247 +++++++++++++++++- 1 file changed, 240 insertions(+), 7 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs index 27fa95c11d..e0f683868d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs @@ -215,12 +215,6 @@ impl ExecutionGadget for P256VerifyGadget { cb.condition(not::expr(x_y_canonical.expr()), |cb| { cb.require_zero("is_valid == false if x or y not canonical", is_valid.expr()); }); - // cb.condition(not::expr(recovered.expr()), |cb| { - // cb.require_zero( - // "address == 0 if address could not be recovered", - // recovered_addr_keccak_rlc.expr(), - // ); - // }); cb.precompile_info_lookup( cb.execution_state().as_u64().expr(), @@ -403,7 +397,7 @@ impl ExecutionGadget for P256VerifyGadget { .assign(region, offset, aux_data.pubkey_x, *FP_MODULUS)?; self.pk_y_canonical .assign(region, offset, aux_data.pubkey_y, *FP_MODULUS)?; - // TODO: assign is_valid correctly + // assign is_valid let pub_key_bytes = ( &aux_data.pubkey_x.to_le_bytes(), &aux_data.pubkey_y.to_le_bytes(), @@ -479,3 +473,242 @@ impl ExecutionGadget for P256VerifyGadget { .assign(region, offset, block, call, step, 7) } } + +#[cfg(test)] +mod test { + use bus_mapping::{ + evm::{OpcodeId, PrecompileCallArgs}, + precompile::PrecompileCalls, + }; + use eth_types::{bytecode, word, ToWord}; + use mock::TestContext; + use rayon::{iter::ParallelIterator, prelude::IntoParallelRefIterator}; + use std::sync::LazyLock; + + use crate::test_util::CircuitTestBuilder; + + static TEST_VECTOR: LazyLock> = LazyLock::new(|| { + vec![ + PrecompileCallArgs { + name: "p256verify (padded bytes)", + setup_code: bytecode! { + // msg hash from 0x00 + PUSH32(word!("0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3")) + PUSH1(0x00) + MSTORE + // TODO: add x, y + // signature s from 0x60 + PUSH32(word!("0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada")) + PUSH1(0x40) + MSTORE + }, + // copy 101 bytes from memory addr 0. This should be sufficient to recover an + // address, but the signature is invalid (ecrecover does not care about this + // though) + call_data_offset: 0x00.into(), + call_data_length: 0x65.into(), + // return 32 bytes and write from memory addr 128 + ret_offset: 0x80.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::P256Verify.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "p256verify (valid sig)", + setup_code: bytecode! { + // msg hash from 0x00 + PUSH32(word!("0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3")) + PUSH1(0x00) + MSTORE + // x, y from 0x20 + PUSH1(28) + PUSH1(0x20) + MSTORE + // signature r from 0x40 + PUSH32(word!("0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608")) + PUSH1(0x40) + MSTORE + // signature s from 0x60 + PUSH32(word!("0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada")) + PUSH1(0x60) + MSTORE + }, + // copy 128 bytes from memory addr 0. Address is recovered and the signature is + // valid. + call_data_offset: 0x00.into(), + call_data_length: 0x80.into(), + // return 32 bytes and write from memory addr 128 + ret_offset: 0x80.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::P256Verify.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "p256verify (valid sig, extra input bytes)", + setup_code: bytecode! { + // msg hash from 0x00 + PUSH32(word!("0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3")) + PUSH1(0x00) + MSTORE + // signature v from 0x20 + PUSH1(28) + PUSH1(0x20) + MSTORE + // signature r from 0x40 + PUSH32(word!("0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608")) + PUSH1(0x40) + MSTORE + // signature s from 0x60 + PUSH32(word!("0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada")) + PUSH1(0x60) + MSTORE + }, + // copy 133 bytes from memory addr 0. Address is recovered and the signature is + // valid. The 5 bytes after the first 128 bytes are ignored. + call_data_offset: 0x00.into(), + call_data_length: 0x85.into(), + // return 32 bytes and write from memory addr 128 + ret_offset: 0x80.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::P256Verify.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "p256verify (overflowing msg_hash)", + setup_code: bytecode! { + // msg hash from 0x00 + PUSH32(word!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee")) + PUSH1(0x00) + MSTORE + // x,y from 0x20 + PUSH1(28) + PUSH1(0x20) + MSTORE + // signature r from 0x40 + PUSH32(word!("0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608")) + PUSH1(0x40) + MSTORE + // signature s from 0x60 + PUSH32(word!("0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada")) + PUSH1(0x60) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x80.into(), + ret_offset: 0x80.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::P256Verify.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "ecrecover (overflowing sig_r)", + setup_code: bytecode! { + // msg hash from 0x00 + PUSH32(word!("0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3")) + PUSH1(0x00) + MSTORE + // x, y from 0x20 + PUSH1(28) + PUSH1(0x20) + MSTORE + // signature r from 0x40 + PUSH32(word!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee")) + PUSH1(0x40) + MSTORE + // signature s from 0x60 + PUSH32(word!("0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada")) + PUSH1(0x60) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x80.into(), + ret_offset: 0x80.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::P256Verify.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "p256verify (overflowing sig_s)", + setup_code: bytecode! { + // msg hash from 0x00 + PUSH32(word!("0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3")) + PUSH1(0x00) + MSTORE + // x,y from 0x20 + PUSH1(28) + PUSH1(0x20) + MSTORE + // signature r from 0x40 + PUSH32(word!("0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608")) + PUSH1(0x40) + MSTORE + // signature s from 0x60 + PUSH32(word!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee")) + PUSH1(0x60) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x80.into(), + ret_offset: 0x80.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::P256Verify.address().to_word(), + ..Default::default() + }, + + PrecompileCallArgs { + name: "p256verify (overflowing x)", + setup_code: bytecode! { + // msg hash from 0x00 + PUSH32(word!("0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3")) + PUSH1(0x00) + MSTORE + // x,y from 0x20 + PUSH1(28) + PUSH1(0x20) + MSTORE + // signature r from 0x40 + PUSH32(word!("0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608")) + PUSH1(0x40) + MSTORE + // signature s from 0x60 + PUSH32(word!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee")) + PUSH1(0x60) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x80.into(), + ret_offset: 0x80.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::P256Verify.address().to_word(), + ..Default::default() + }, + PrecompileCallArgs { + name: "p256verify (overflowing y)", + setup_code: bytecode! { + // msg hash from 0x00 + PUSH32(word!("0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3")) + PUSH1(0x00) + MSTORE + // x,y from 0x20 + PUSH1(28) + PUSH1(0x20) + MSTORE + // signature r from 0x40 + PUSH32(word!("0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608")) + PUSH1(0x40) + MSTORE + // signature s from 0x60 + PUSH32(word!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee")) + PUSH1(0x60) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x80.into(), + ret_offset: 0x80.into(), + ret_size: 0x20.into(), + address: PrecompileCalls::P256Verify.address().to_word(), + ..Default::default() + }, + ] + }); +}