Skip to content

Commit 5bc0f9e

Browse files
ilitteriazteca1998
andauthored
feat(l1): reintroduce ecrecover implementation using secp256k1 for L1 (#4924)
Co-authored-by: Esteve Soler Arderiu <[email protected]>
1 parent 6e16e8b commit 5bc0f9e

File tree

2 files changed

+73
-6
lines changed

2 files changed

+73
-6
lines changed

crates/vm/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ path = "./lib.rs"
3131

3232
[features]
3333
default = []
34+
3435
c-kzg = ["ethrex-levm/c-kzg", "ethrex-common/c-kzg"]
35-
debug = ["ethrex-levm/debug"]
36+
3637
sp1 = ["ethrex-levm/sp1"]
37-
risc0 = ["ethrex-levm/risc0"]
38+
risc0 = ["ethrex-levm/risc0", "c-kzg"]
39+
40+
debug = ["ethrex-levm/debug"]
3841

3942
[lints.clippy]
4043
unwrap_used = "deny"

crates/vm/levm/src/precompiles.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ use bls12_381::{
77
};
88
use bytes::{Buf, Bytes};
99
use ethrex_common::H160;
10-
use ethrex_common::utils::{keccak, u256_from_big_endian_const};
10+
use ethrex_common::utils::u256_from_big_endian_const;
1111
use ethrex_common::{
1212
Address, H256, U256, serde_utils::bool, types::Fork, types::Fork::*,
1313
utils::u256_from_big_endian,
1414
};
1515
use ethrex_crypto::{blake2f::blake2b_f, kzg::verify_kzg_proof};
16-
use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
1716
use k256::elliptic_curve::Field;
1817
use lambdaworks_math::cyclic_group::IsGroup;
1918
use lambdaworks_math::elliptic_curve::short_weierstrass::curves::bn_254::curve::{
@@ -53,8 +52,8 @@ use crate::{
5352
gas_cost::{
5453
self, BLAKE2F_ROUND_COST, BLS12_381_G1_K_DISCOUNT, BLS12_381_G1ADD_COST,
5554
BLS12_381_G2_K_DISCOUNT, BLS12_381_G2ADD_COST, BLS12_381_MAP_FP_TO_G1_COST,
56-
BLS12_381_MAP_FP2_TO_G2_COST, ECADD_COST, ECMUL_COST, ECRECOVER_COST, G1_MUL_COST,
57-
G2_MUL_COST, POINT_EVALUATION_COST,
55+
BLS12_381_MAP_FP2_TO_G2_COST, ECADD_COST, ECMUL_COST, G1_MUL_COST, G2_MUL_COST,
56+
POINT_EVALUATION_COST,
5857
},
5958
};
6059
use lambdaworks_math::elliptic_curve::short_weierstrass::curves::bls12_381::curve::{
@@ -375,6 +374,65 @@ pub(crate) fn fill_with_zeros(calldata: &Bytes, target_len: usize) -> Bytes {
375374
padded_calldata.into()
376375
}
377376

377+
#[cfg(all(not(feature = "sp1"), not(feature = "risc0")))]
378+
pub fn ecrecover(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result<Bytes, VMError> {
379+
use sha3::Keccak256;
380+
381+
use crate::gas_cost::ECRECOVER_COST;
382+
383+
increase_precompile_consumed_gas(ECRECOVER_COST, gas_remaining)?;
384+
385+
const INPUT_LEN: usize = 128;
386+
const WORD: usize = 32;
387+
388+
let input = fill_with_zeros(calldata, INPUT_LEN);
389+
390+
// len(raw_hash) == 32, len(raw_v) == 32, len(raw_sig) == 64
391+
let (raw_hash, tail) = input.split_at(WORD);
392+
let (raw_v, raw_sig) = tail.split_at(WORD);
393+
394+
// EVM expects v ∈ {27, 28}. Anything else is invalid → empty return.
395+
let recovery_id_byte = match u8::try_from(u256_from_big_endian(raw_v)) {
396+
Ok(27) => 0_i32,
397+
Ok(28) => 1_i32,
398+
_ => return Ok(Bytes::new()),
399+
};
400+
401+
// Recovery id from the adjusted byte.
402+
let Ok(recovery_id) = secp256k1::ecdsa::RecoveryId::try_from(recovery_id_byte) else {
403+
return Ok(Bytes::new());
404+
};
405+
406+
let Ok(recoverable_signature) =
407+
secp256k1::ecdsa::RecoverableSignature::from_compact(raw_sig, recovery_id)
408+
else {
409+
return Ok(Bytes::new());
410+
};
411+
412+
let message = secp256k1::Message::from_digest(
413+
raw_hash
414+
.try_into()
415+
.map_err(|_err| InternalError::msg("Invalid message length for ecrecover"))?,
416+
);
417+
418+
let Ok(public_key) = recoverable_signature.recover(&message) else {
419+
return Ok(Bytes::new());
420+
};
421+
422+
// We need to take the 64 bytes from the public key (discarding the first pos of the slice)
423+
let public_key_hash = Keccak256::digest(&public_key.serialize_uncompressed()[1..]);
424+
425+
// Address is the last 20 bytes of the hash.
426+
#[expect(clippy::indexing_slicing)]
427+
let recovered_address_bytes = &public_key_hash[12..];
428+
429+
let mut out = [0u8; 32];
430+
431+
out[12..32].copy_from_slice(recovered_address_bytes);
432+
433+
Ok(Bytes::copy_from_slice(&out))
434+
}
435+
378436
/// ## ECRECOVER precompile.
379437
/// Elliptic curve digital signature algorithm (ECDSA) public key recovery function.
380438
///
@@ -384,7 +442,13 @@ pub(crate) fn fill_with_zeros(calldata: &Bytes, target_len: usize) -> Bytes {
384442
/// [64..128): r||s (64 bytes)
385443
///
386444
/// Returns the recovered address.
445+
#[cfg(any(feature = "sp1", feature = "risc0"))]
387446
pub fn ecrecover(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result<Bytes, VMError> {
447+
use ethrex_common::utils::keccak;
448+
use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
449+
450+
use crate::gas_cost::ECRECOVER_COST;
451+
388452
increase_precompile_consumed_gas(ECRECOVER_COST, gas_remaining)?;
389453

390454
const INPUT_LEN: usize = 128;

0 commit comments

Comments
 (0)