Skip to content
This repository was archived by the owner on Apr 18, 2025. It is now read-only.

Commit 68d18f9

Browse files
authored
ecrecover soundness (address not recovered) (#930)
* wip: preliminary work sig circuit/lookp * feat: handle panics and edge cases in sig circuit * fix: handle v not boolean * fix: fq modulus not assigned and bus-mapping handle overflowing r/s * fix: sig_v handle invalid values (lookup) and is_valid dev_load fix * refactor (review comment) * fix: no sig table lookup for invalid sig_v * remove wrong test case (assertion fail)
1 parent 47ff003 commit 68d18f9

File tree

13 files changed

+734
-296
lines changed

13 files changed

+734
-296
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bus-mapping/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ poseidon-circuit = { git = "https://github.com/scroll-tech/poseidon-circuit.git"
2020
itertools = "0.10"
2121
lazy_static = "1.4"
2222
log = "0.4.14"
23+
num = "0.4"
2324
rand = { version = "0.8", optional = true }
2425
serde = {version = "1.0.130", features = ["derive"] }
2526
serde_json = "1.0.66"
Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use eth_types::{
2-
sign_types::{recover_pk, SignData},
2+
sign_types::{biguint_to_32bytes_le, recover_pk, SignData, SECP256K1_Q},
33
Bytes, ToBigEndian, ToLittleEndian,
44
};
5-
use halo2_proofs::halo2curves::secp256k1::Fq;
5+
use ethers_core::k256::elliptic_curve::PrimeField;
6+
use halo2_proofs::halo2curves::{
7+
group::prime::PrimeCurveAffine,
8+
secp256k1::{Fq, Secp256k1Affine},
9+
};
10+
use num::{BigUint, Integer};
611

712
use crate::{
813
circuit_input_builder::PrecompileEvent,
@@ -23,41 +28,41 @@ pub(crate) fn opt_data(
2328
});
2429
let aux_data = EcrecoverAuxData::new(input_bytes, output_bytes);
2530

26-
// only if sig_v was a valid recovery ID, then we proceed to populate the ecrecover events.
31+
// We skip the validation through sig circuit if r or s was not in canonical form.
32+
let opt_sig_r: Option<Fq> = Fq::from_bytes(&aux_data.sig_r.to_le_bytes()).into();
33+
let opt_sig_s: Option<Fq> = Fq::from_bytes(&aux_data.sig_s.to_le_bytes()).into();
34+
if opt_sig_r.zip(opt_sig_s).is_none() {
35+
return (None, Some(PrecompileAuxData::Ecrecover(aux_data)));
36+
}
37+
2738
if let Some(sig_v) = aux_data.recovery_id() {
28-
if let Ok(recovered_pk) = recover_pk(
39+
let recovered_pk = recover_pk(
2940
sig_v,
3041
&aux_data.sig_r,
3142
&aux_data.sig_s,
3243
&aux_data.msg_hash.to_be_bytes(),
33-
) {
34-
let sign_data = SignData {
35-
signature: (
36-
Fq::from_bytes(&aux_data.sig_r.to_le_bytes()).unwrap(),
37-
Fq::from_bytes(&aux_data.sig_s.to_le_bytes()).unwrap(),
38-
sig_v,
39-
),
40-
pk: recovered_pk,
41-
msg: Bytes::default(),
42-
msg_hash: Fq::from_bytes(&aux_data.msg_hash.to_le_bytes()).unwrap(),
43-
};
44-
assert_eq!(aux_data.recovered_addr, sign_data.get_addr());
45-
(
46-
Some(PrecompileEvent::Ecrecover(sign_data)),
47-
Some(PrecompileAuxData::Ecrecover(aux_data)),
48-
)
49-
} else {
50-
log::warn!(
51-
"could not recover pubkey. ecrecover aux_data={:?}",
52-
aux_data
53-
);
54-
(None, Some(PrecompileAuxData::Ecrecover(aux_data)))
55-
}
44+
)
45+
.unwrap_or(Secp256k1Affine::identity());
46+
let sign_data = SignData {
47+
signature: (
48+
Fq::from_bytes(&aux_data.sig_r.to_le_bytes()).unwrap(),
49+
Fq::from_bytes(&aux_data.sig_s.to_le_bytes()).unwrap(),
50+
sig_v,
51+
),
52+
pk: recovered_pk,
53+
msg: Bytes::default(),
54+
msg_hash: {
55+
let msg_hash = BigUint::from_bytes_be(&aux_data.msg_hash.to_be_bytes());
56+
let msg_hash = msg_hash.mod_floor(&*SECP256K1_Q);
57+
let msg_hash_le = biguint_to_32bytes_le(msg_hash);
58+
Fq::from_repr(msg_hash_le).unwrap()
59+
},
60+
};
61+
(
62+
Some(PrecompileEvent::Ecrecover(sign_data)),
63+
Some(PrecompileAuxData::Ecrecover(aux_data)),
64+
)
5665
} else {
57-
log::warn!(
58-
"invalid recoveryId for ecrecover. sig_v={:?}",
59-
aux_data.sig_v
60-
);
6166
(None, Some(PrecompileAuxData::Ecrecover(aux_data)))
6267
}
6368
}

eth-types/src/sign_types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use halo2_proofs::{
1515
halo2curves::{
1616
group::{
1717
ff::{Field as GroupField, PrimeField},
18+
prime::PrimeCurveAffine,
1819
Curve,
1920
},
2021
secp256k1::{self, Secp256k1Affine},
@@ -101,6 +102,9 @@ pub fn get_dummy_tx() -> (TransactionRequest, Signature) {
101102
impl SignData {
102103
/// Recover address of the signature
103104
pub fn get_addr(&self) -> Address {
105+
if self.pk == Secp256k1Affine::identity() {
106+
return Address::zero();
107+
}
104108
let pk_le = pk_bytes_le(&self.pk);
105109
let pk_be = pk_bytes_swap_endianness(&pk_le);
106110
let pk_hash = keccak256(pk_be);

zkevm-circuits/src/ecc_circuit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -622,10 +622,10 @@ impl<F: Field, const XI_0: i64> EccCircuit<F, XI_0> {
622622
let infinity = EcPoint::construct(
623623
ecc_chip
624624
.field_chip()
625-
.load_private(ctx, Value::known(0.into())),
625+
.load_constant(ctx, fe_to_biguint(&Fq::zero())),
626626
ecc_chip
627627
.field_chip()
628-
.load_private(ctx, Value::known(0.into())),
628+
.load_constant(ctx, fe_to_biguint(&Fq::zero())),
629629
);
630630
// for invalid case, take a random point.
631631
let dummy_g1 = ecc_chip.load_random_point::<G1Affine>(ctx);

0 commit comments

Comments
 (0)