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 71f28d676d..5efe26ff3a 100644 --- a/bus-mapping/src/evm/opcodes/precompiles/mod.rs +++ b/bus-mapping/src/evm/opcodes/precompiles/mod.rs @@ -14,12 +14,14 @@ 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; 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, @@ -91,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 new file mode 100644 index 0000000000..08a1eddc10 --- /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::arithmetic::CurveAffine; +use halo2_proofs::halo2curves::{ + group::{ff::PrimeField, prime::PrimeCurveAffine}, + secp256r1::{Fp, Fq, Secp256r1Affine}, +}; +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/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 { diff --git a/eth-types/src/sign_types.rs b/eth-types/src/sign_types.rs index 4e72321141..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]; @@ -303,6 +323,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 { diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 934c299530..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, + IdentityGadget, ModExpGadget, P256VerifyGadget, SHA256Gadget, }; 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 { @@ -677,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, @@ -1746,6 +1748,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/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 new file mode 100644 index 0000000000..e0f683868d --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/p256_verify.rs @@ -0,0 +1,714 @@ +use crate::util::Field; +use bus_mapping::precompile::{PrecompileAuxData, PrecompileCalls}; +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::{ + 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 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, + 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, + // 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, + sig_s_canonical: LtWordGadget, + + pk_x: Word, + pk_x_canonical: LtWordGadget, + pk_y: Word, + pk_y_canonical: LtWordGadget, + is_valid: Cell, + + 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::PrecompileP256Verify; + + 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 ( + 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(), + //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(); + 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()]); + + 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); + + 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(), + 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())), + ); + 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] = + [ + 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_P256VERIFY.expr(), + cb.curr.state.gas_left.expr(), + ); + + // 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", 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.precompile_info_lookup( + cb.execution_state().as_u64().expr(), + callee_address.expr(), + cb.execution_state().precompile_base_gas_cost().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( + 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) + + (0.expr() * r_pow_64) + + (sig_r_keccak_rlc.expr() * r_pow_32) + + sig_s_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(), + is_success.expr(), + gas_cost.expr(), + // ReturnDataLength + select::expr(is_valid.expr(), 0x20.expr(), 0x00.expr()), + ); + + Self { + input_bytes_rlc, + output_bytes_rlc, + return_bytes_rlc, + + pad_right, + padding, + msg_hash_keccak_rlc, + sig_r_keccak_rlc, + sig_s_keccak_rlc, + //recovered_addr_keccak_rlc, + msg_hash_raw, + msg_hash, + fq_modulus, + msg_hash_mod, + fp_modulus, + + sig_r, + sig_r_canonical, + sig_s, + sig_s_canonical, + + pk_x, + pk_x_canonical, + pk_y, + pk_y_canonical, + is_valid, + 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::P256Verify(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)), + )?; + 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_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), + (&self.pk_x, aux_data.pubkey_x), + (&self.pk_y, aux_data.pubkey_y), + ] { + 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.fp_modulus + .assign(region, offset, Some(FP_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)?; + // 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)?; + // assign is_valid + 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, + // 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::P256Verify, + 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 p256verify", 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) + } +} + +#[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() + }, + ] + }); +} diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index cdb1411810..f5723b4565 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 { @@ -178,6 +179,7 @@ impl ExecutionState { | Self::PrecompileBlake2f | Self::ErrorOutOfGasPrecompile | Self::ErrorPrecompileFailed + | Self::PrecompileP256Verify ) } @@ -192,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() 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(),