Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: implement evm circuit for p256 precompile (eip7212) #1441

Draft
wants to merge 10 commits into
base: feat/eip-7212
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bus-mapping/src/evm/opcodes/callop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,7 @@ pub mod tests {
max_rws: 3000,
..Default::default()
},
// TODO: add p256verify test here
];

let call_ops = [
Expand Down
3 changes: 3 additions & 0 deletions bus-mapping/src/evm/opcodes/precompiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
(
Expand Down
58 changes: 58 additions & 0 deletions bus-mapping/src/evm/opcodes/precompiles/p256_verify.rs
Original file line number Diff line number Diff line change
@@ -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<PrecompileEvent>, Option<PrecompileAuxData>) {
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> = Fq::from_bytes(&aux_data.sig_r.to_le_bytes()).into();
let opt_sig_s: Option<Fq> = Fq::from_bytes(&aux_data.sig_s.to_le_bytes()).into();
let opt_x: Option<Fp> = Fp::from_bytes(&aux_data.pubkey_x.to_le_bytes()).into();
let opt_y: Option<Fp> = 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::<Fq, Secp256r1Affine> {
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)),
)
}
83 changes: 72 additions & 11 deletions bus-mapping/src/precompile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,26 @@ impl From<u64> 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 {
Expand All @@ -141,9 +156,11 @@ impl PrecompileCalls {
/// Maximum length of input bytes considered for the precompile call.
pub fn input_len(&self) -> Option<usize> {
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,
}
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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<u8>,
/// Output bytes from the P256Verify call.
pub output_bytes: Vec<u8>,
/// Bytes returned to the caller from the P256Verify call.
pub return_bytes: Vec<u8>,
}

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)]
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -457,6 +516,8 @@ pub enum PrecompileAuxData {
EcMul(EcMulAuxData),
/// EcPairing.
EcPairing(Box<Result<EcPairingAuxData, EcPairingError>>),
/// p256Verify
P256Verify(P256VerifyAuxData),
}

impl Default for PrecompileAuxData {
Expand Down
25 changes: 25 additions & 0 deletions eth-types/src/sign_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
) -> 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<Fp: PrimeField<Repr = [u8; 32]>, Fq: PrimeField + FromUniformBytes<64>>(x: Fp) -> Fq {
let mut x_repr = [0u8; 32];
Expand Down Expand Up @@ -303,6 +323,11 @@ pub fn recover_pk2(
pub static SECP256K1_Q: LazyLock<BigUint> =
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<BigUint> =
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<T, E>(v: CtOption<T>, err: E) -> Result<T, E> {
Expand Down
7 changes: 6 additions & 1 deletion zkevm-circuits/src/evm_circuit/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -381,6 +381,7 @@ pub(crate) struct ExecutionConfig<F> {
precompile_bn128mul_gadget: Box<EcMulGadget<F>>,
precompile_bn128pairing_gadget: Box<EcPairingGadget<F>>,
precompile_blake2f_gadget: Box<BasePrecompileGadget<F, { ExecutionState::PrecompileBlake2f }>>,
precompile_p256verify_gadget: Box<P256VerifyGadget<F>>,
}

impl<F: Field> ExecutionConfig<F> {
Expand Down Expand Up @@ -677,6 +678,7 @@ impl<F: Field> ExecutionConfig<F> {
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,
Expand Down Expand Up @@ -1746,6 +1748,9 @@ impl<F: Field> ExecutionConfig<F> {
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
Expand Down
3 changes: 3 additions & 0 deletions zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<F: Field>(
Expand Down
Loading
Loading