Skip to main content

ethrex_levm/
precompiles.rs

1use bytes::{Buf, Bytes};
2use ethrex_common::H160;
3use ethrex_common::utils::u256_from_big_endian_const;
4use ethrex_common::{
5    Address, H256, U256, types::Fork, types::Fork::*, utils::u256_from_big_endian,
6};
7use ethrex_crypto::{Crypto, CryptoError};
8use rustc_hash::FxHashMap;
9use std::borrow::Cow;
10use std::collections::{BTreeMap, BTreeSet};
11use std::sync::RwLock;
12
13use crate::gas_cost::{MODEXP_STATIC_COST, P256_VERIFY_COST};
14use crate::vm::VMType;
15use crate::{
16    constants::VERSIONED_HASH_VERSION_KZG,
17    errors::{InternalError, PrecompileError, VMError},
18    gas_cost::{
19        self, BLAKE2F_ROUND_COST, BLS12_381_G1_K_DISCOUNT, BLS12_381_G1ADD_COST,
20        BLS12_381_G2_K_DISCOUNT, BLS12_381_G2ADD_COST, BLS12_381_MAP_FP_TO_G1_COST,
21        BLS12_381_MAP_FP2_TO_G2_COST, ECADD_COST, ECMUL_COST, G1_MUL_COST, G2_MUL_COST,
22        POINT_EVALUATION_COST,
23    },
24};
25
26pub const BLAKE2F_ELEMENT_SIZE: usize = 8;
27
28pub const SIZE_PRECOMPILES_PRE_CANCUN: u64 = 9;
29pub const SIZE_PRECOMPILES_CANCUN: u64 = 10;
30pub const SIZE_PRECOMPILES_PRAGUE: u64 = 17;
31
32pub const BLS12_381_G1_MSM_PAIR_LENGTH: usize = 160;
33pub const BLS12_381_G2_MSM_PAIR_LENGTH: usize = 288;
34pub const BLS12_381_PAIRING_CHECK_PAIR_LENGTH: usize = 384;
35
36const BLS12_381_FP2_VALID_INPUT_LENGTH: usize = 128;
37const BLS12_381_FP_VALID_INPUT_LENGTH: usize = 64;
38
39pub const FIELD_ELEMENT_WITHOUT_PADDING_LENGTH: usize = 48;
40pub const PADDED_FIELD_ELEMENT_SIZE_IN_BYTES: usize = 64;
41
42pub const G1_POINT_AT_INFINITY: [u8; 128] = [0_u8; 128];
43pub const G2_POINT_AT_INFINITY: [u8; 256] = [0_u8; 256];
44
45/// Precomputed result for MAP_FP2_TO_G2 when input is (Fp::zero(), Fp::zero()).
46/// The bls12_381 crate's map_to_curve does not handle the zero Fp2 case correctly,
47/// so we return the known-correct padded result directly.
48const FP2_ZERO_MAPPED_TO_G2: [u8; 256] = [
49    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 131, 32, 137, 110, 201, 238, 249, 213, 230,
50    25, 132, 141, 194, 156, 226, 102, 244, 19, 208, 45, 211, 29, 155, 157, 68, 236, 12, 121, 205,
51    97, 241, 139, 7, 93, 219, 166, 215, 189, 32, 183, 255, 39, 164, 179, 36, 191, 206, 0, 0, 0, 0,
52    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 103, 209, 33, 24, 181, 163, 91, 176, 45, 46, 134, 179,
53    235, 250, 126, 35, 65, 13, 185, 61, 227, 159, 176, 109, 112, 37, 250, 149, 233, 111, 250, 66,
54    138, 122, 39, 195, 174, 77, 212, 180, 11, 210, 81, 172, 101, 136, 146, 0, 0, 0, 0, 0, 0, 0, 0,
55    0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 224, 54, 68, 209, 162, 195, 33, 37, 107, 50, 70, 186, 210, 184,
56    149, 202, 209, 56, 144, 203, 230, 248, 93, 245, 81, 6, 160, 211, 52, 96, 79, 177, 67, 199, 160,
57    66, 216, 120, 0, 98, 113, 134, 91, 195, 89, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
58    4, 198, 151, 119, 164, 63, 11, 218, 7, 103, 157, 88, 5, 230, 63, 24, 207, 78, 14, 124, 97, 18,
59    172, 127, 112, 38, 109, 25, 155, 79, 118, 174, 39, 198, 38, 154, 60, 238, 189, 174, 48, 128,
60    110, 154, 118, 170, 223, 92,
61];
62
63pub struct Precompile {
64    pub address: H160,
65    pub name: &'static str,
66    pub active_since_fork: Fork,
67}
68
69pub const ECRECOVER: Precompile = Precompile {
70    address: H160([
71        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
72        0x00, 0x00, 0x00, 0x00, 0x01,
73    ]),
74    name: "ECREC",
75    active_since_fork: Paris,
76};
77
78pub const SHA2_256: Precompile = Precompile {
79    address: H160([
80        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
81        0x00, 0x00, 0x00, 0x00, 0x02,
82    ]),
83    name: "SHA256",
84    active_since_fork: Paris,
85};
86
87pub const RIPEMD_160: Precompile = Precompile {
88    address: H160([
89        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
90        0x00, 0x00, 0x00, 0x00, 0x03,
91    ]),
92    name: "RIPEMD160",
93    active_since_fork: Paris,
94};
95
96pub const IDENTITY: Precompile = Precompile {
97    address: H160([
98        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
99        0x00, 0x00, 0x00, 0x00, 0x04,
100    ]),
101    name: "ID",
102    active_since_fork: Paris,
103};
104
105pub const MODEXP: Precompile = Precompile {
106    address: H160([
107        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
108        0x00, 0x00, 0x00, 0x00, 0x05,
109    ]),
110    name: "MODEXP",
111    active_since_fork: Paris,
112};
113
114pub const ECADD: Precompile = Precompile {
115    address: H160([
116        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
117        0x00, 0x00, 0x00, 0x00, 0x06,
118    ]),
119    name: "BN254_ADD",
120    active_since_fork: Paris,
121};
122
123pub const ECMUL: Precompile = Precompile {
124    address: H160([
125        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
126        0x00, 0x00, 0x00, 0x00, 0x07,
127    ]),
128    name: "BN254_MUL",
129    active_since_fork: Paris,
130};
131
132pub const ECPAIRING: Precompile = Precompile {
133    address: H160([
134        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
135        0x00, 0x00, 0x00, 0x00, 0x08,
136    ]),
137    name: "BN254_PAIRING",
138    active_since_fork: Paris,
139};
140
141pub const BLAKE2F: Precompile = Precompile {
142    address: H160([
143        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
144        0x00, 0x00, 0x00, 0x00, 0x09,
145    ]),
146    name: "BLAKE2F",
147    active_since_fork: Paris,
148};
149
150pub const POINT_EVALUATION: Precompile = Precompile {
151    address: H160([
152        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
153        0x00, 0x00, 0x00, 0x00, 0x0a,
154    ]),
155    name: "KZG_POINT_EVALUATION",
156    active_since_fork: Cancun,
157};
158
159pub const BLS12_G1ADD: Precompile = Precompile {
160    address: H160([
161        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
162        0x00, 0x00, 0x00, 0x00, 0x0b,
163    ]),
164    name: "BLS12_G1ADD",
165    active_since_fork: Prague,
166};
167
168pub const BLS12_G1MSM: Precompile = Precompile {
169    address: H160([
170        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
171        0x00, 0x00, 0x00, 0x00, 0x0c,
172    ]),
173    name: "BLS12_G1MSM",
174    active_since_fork: Prague,
175};
176
177pub const BLS12_G2ADD: Precompile = Precompile {
178    address: H160([
179        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
180        0x00, 0x00, 0x00, 0x00, 0x0d,
181    ]),
182    name: "BLS12_G2ADD",
183    active_since_fork: Prague,
184};
185
186pub const BLS12_G2MSM: Precompile = Precompile {
187    address: H160([
188        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
189        0x00, 0x00, 0x00, 0x00, 0x0e,
190    ]),
191    name: "BLS12_G2MSM",
192    active_since_fork: Prague,
193};
194
195pub const BLS12_PAIRING_CHECK: Precompile = Precompile {
196    address: H160([
197        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
198        0x00, 0x00, 0x00, 0x00, 0x0f,
199    ]),
200    name: "BLS12_PAIRING_CHECK",
201    active_since_fork: Prague,
202};
203
204pub const BLS12_MAP_FP_TO_G1: Precompile = Precompile {
205    address: H160([
206        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
207        0x00, 0x00, 0x00, 0x00, 0x10,
208    ]),
209    name: "BLS12_MAP_FP_TO_G1",
210    active_since_fork: Prague,
211};
212
213pub const BLS12_MAP_FP2_TO_G2: Precompile = Precompile {
214    address: H160([
215        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
216        0x00, 0x00, 0x00, 0x00, 0x11,
217    ]),
218    name: "BLS12_MAP_FP2_TO_G2",
219    active_since_fork: Prague,
220};
221
222pub const P256VERIFY: Precompile = Precompile {
223    address: H160([
224        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
225        0x00, 0x00, 0x00, 0x01, 0x00,
226    ]),
227    name: "P256VERIFY",
228    active_since_fork: Osaka,
229};
230
231/// EXECUTE precompile address (0x0101) for Native Rollups (EIP-8079).
232/// Active only on L1 at or above `Fork::LStar`. The L1-only + LStar gate is
233/// enforced in `is_precompile` (the `matches!(vm_type, VMType::L1)` check);
234/// `execute_precompile`'s dispatch only re-checks `fork >= LStar` and relies on
235/// `is_precompile` having already rejected the L2 case at the call site.
236/// NOTE: `EXECUTE` is intentionally NOT in the `PRECOMPILES` array, so its
237/// `active_since_fork` below is never consulted by `precompiles_for_fork` — the
238/// gating is entirely the hand-written checks above. The field is set for
239/// documentation only.
240pub const EXECUTE: Precompile = Precompile {
241    address: H160([
242        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
243        0x00, 0x00, 0x00, 0x01, 0x01,
244    ]),
245    name: "EXECUTE",
246    active_since_fork: LStar,
247};
248
249pub const PRECOMPILES: [Precompile; 18] = [
250    ECRECOVER,
251    SHA2_256,
252    RIPEMD_160,
253    IDENTITY,
254    MODEXP,
255    ECADD,
256    ECMUL,
257    ECPAIRING,
258    BLAKE2F,
259    POINT_EVALUATION,
260    BLS12_G1ADD,
261    BLS12_G1MSM,
262    BLS12_G2ADD,
263    BLS12_G2MSM,
264    BLS12_PAIRING_CHECK,
265    BLS12_MAP_FP_TO_G1,
266    BLS12_MAP_FP2_TO_G2,
267    P256VERIFY,
268];
269
270pub fn precompiles_for_fork(fork: Fork) -> impl Iterator<Item = Precompile> {
271    PRECOMPILES
272        .into_iter()
273        .filter(move |precompile| precompile.active_since_fork <= fork)
274}
275
276pub fn is_precompile(address: &Address, fork: Fork, vm_type: VMType) -> bool {
277    if fork >= Fork::LStar && matches!(vm_type, VMType::L1) && *address == EXECUTE.address {
278        return true;
279    }
280    (matches!(vm_type, VMType::L2(_)) && *address == P256VERIFY.address)
281        || precompiles_for_fork(fork).any(|precompile| precompile.address == *address)
282}
283
284/// Precompile-dispatch changes requested by a geth State Override Set. Simulation-only:
285/// this is `None` on every consensus path, in which case precompile dispatch is
286/// bit-identical to [`is_precompile`].
287///
288/// Two independent effects, both matching geth's `StateOverride.Apply`:
289///
290/// - `movePrecompileToAddress` makes a destination address dispatch the precompile that
291///   lives at the named source.
292/// - **Any** overridden address stops dispatching as a precompile. geth does
293///   `delete(precompiles, addr)` for every address in the override set, not only the
294///   sources of a move, so overriding `0x04`'s balance alone takes the identity precompile
295///   out of the active set and leaves an ordinary account behind.
296#[derive(Debug, Default, Clone, PartialEq, Eq)]
297pub struct PrecompileMoves {
298    /// Overridden addresses, which no longer dispatch as precompiles.
299    suppressed: BTreeSet<Address>,
300    /// Destination -> the original precompile address whose implementation runs there.
301    relocated: BTreeMap<Address, Address>,
302}
303
304impl PrecompileMoves {
305    /// Build from the `(precompile_address, destination_address)` relocations and the full
306    /// set of overridden addresses.
307    ///
308    /// A later pair wins if two moves name the same destination, which is also what
309    /// geth's `StateOverride.Apply` does: it assigns `precompiles[dest]` per move, in
310    /// address order, and only refuses a destination that is itself overridden.
311    pub fn from_parts(
312        moves: impl IntoIterator<Item = (Address, Address)>,
313        overridden: impl IntoIterator<Item = Address>,
314    ) -> Self {
315        let mut result = Self {
316            suppressed: overridden.into_iter().collect(),
317            relocated: BTreeMap::new(),
318        };
319        for (source, destination) in moves {
320            result.relocated.insert(destination, source);
321        }
322        result
323    }
324
325    pub fn is_empty(&self) -> bool {
326        self.suppressed.is_empty() && self.relocated.is_empty()
327    }
328
329    /// True if `address` is overridden and so no longer dispatches as a precompile.
330    pub fn is_suppressed(&self, address: &Address) -> bool {
331        self.suppressed.contains(address)
332    }
333
334    /// The address whose precompile implementation should run when `address` is
335    /// called, or `None` if `address` is not a relocation destination.
336    pub fn source_for(&self, address: &Address) -> Option<Address> {
337        self.relocated.get(address).copied()
338    }
339}
340
341/// [`is_precompile`], honoring any simulation-only precompile relocations.
342///
343/// With `moves == None` this is exactly [`is_precompile`] — the consensus path.
344pub fn is_precompile_with_moves(
345    address: &Address,
346    fork: Fork,
347    vm_type: VMType,
348    moves: Option<&PrecompileMoves>,
349) -> bool {
350    let Some(moves) = moves else {
351        return is_precompile(address, fork, vm_type);
352    };
353    // A destination wins over the address's own identity: `movePrecompileToAddress`
354    // may legitimately target an address that is itself a precompile. A destination
355    // cannot also be overridden — the RPC layer rejects that, as geth does — so this
356    // never has to arbitrate between a relocation and a suppression.
357    if let Some(source) = moves.source_for(address) {
358        return is_precompile(&source, fork, vm_type);
359    }
360    if moves.is_suppressed(address) {
361        return false;
362    }
363    is_precompile(address, fork, vm_type)
364}
365
366/// The address whose precompile implementation should execute for a call to
367/// `address`. Identity mapping unless `address` is a relocation destination.
368pub fn effective_precompile_address(address: Address, moves: Option<&PrecompileMoves>) -> Address {
369    moves
370        .and_then(|m| m.source_for(&address))
371        .unwrap_or(address)
372}
373
374/// Per-block cache for precompile results shared between warmer and executor.
375pub struct PrecompileCache {
376    cache: RwLock<FxHashMap<(Address, Bytes), (Bytes, u64)>>,
377}
378
379impl Default for PrecompileCache {
380    fn default() -> Self {
381        Self {
382            cache: RwLock::new(FxHashMap::default()),
383        }
384    }
385}
386
387impl PrecompileCache {
388    pub fn new() -> Self {
389        Self::default()
390    }
391
392    pub fn get(&self, address: &Address, calldata: &Bytes) -> Option<(Bytes, u64)> {
393        // Graceful degradation: if the lock is poisoned (a thread panicked while
394        // holding it), skip the cache rather than propagating the panic. The cache
395        // is a pure optimization — missing it only costs a recomputation.
396        self.cache
397            .read()
398            .unwrap_or_else(|poisoned| poisoned.into_inner())
399            .get(&(*address, calldata.clone()))
400            .cloned()
401    }
402
403    pub fn insert(&self, address: Address, calldata: Bytes, output: Bytes, gas_cost: u64) {
404        self.cache
405            .write()
406            .unwrap_or_else(|poisoned| poisoned.into_inner())
407            .insert((address, calldata), (output, gas_cost));
408    }
409}
410
411#[expect(clippy::as_conversions, clippy::indexing_slicing)]
412pub fn execute_precompile(
413    address: Address,
414    calldata: &Bytes,
415    gas_remaining: &mut u64,
416    fork: Fork,
417    cache: Option<&PrecompileCache>,
418    crypto: &dyn Crypto,
419    _stateless_validator: Option<&dyn crate::StatelessValidator>,
420) -> Result<Bytes, VMError> {
421    type PrecompileFn = fn(&Bytes, &mut u64, Fork, &dyn Crypto) -> Result<Bytes, VMError>;
422
423    const PRECOMPILES: [Option<PrecompileFn>; 512] = const {
424        let mut precompiles = [const { None }; 512];
425        precompiles[ECRECOVER.address.0[19] as usize] = Some(ecrecover as PrecompileFn);
426        precompiles[IDENTITY.address.0[19] as usize] = Some(identity as PrecompileFn);
427        precompiles[SHA2_256.address.0[19] as usize] = Some(sha2_256 as PrecompileFn);
428        precompiles[RIPEMD_160.address.0[19] as usize] = Some(ripemd_160 as PrecompileFn);
429        precompiles[MODEXP.address.0[19] as usize] = Some(modexp as PrecompileFn);
430        precompiles[ECADD.address.0[19] as usize] = Some(ecadd as PrecompileFn);
431        precompiles[ECMUL.address.0[19] as usize] = Some(ecmul as PrecompileFn);
432        precompiles[ECPAIRING.address.0[19] as usize] = Some(ecpairing as PrecompileFn);
433        precompiles[BLAKE2F.address.0[19] as usize] = Some(blake2f as PrecompileFn);
434        precompiles[POINT_EVALUATION.address.0[19] as usize] =
435            Some(point_evaluation as PrecompileFn);
436        precompiles[BLS12_G1ADD.address.0[19] as usize] = Some(bls12_g1add as PrecompileFn);
437        precompiles[BLS12_G1MSM.address.0[19] as usize] = Some(bls12_g1msm as PrecompileFn);
438        precompiles[BLS12_G2ADD.address.0[19] as usize] = Some(bls12_g2add as PrecompileFn);
439        precompiles[BLS12_G2MSM.address.0[19] as usize] = Some(bls12_g2msm as PrecompileFn);
440        precompiles[BLS12_PAIRING_CHECK.address.0[19] as usize] =
441            Some(bls12_pairing_check as PrecompileFn);
442        precompiles[BLS12_MAP_FP_TO_G1.address.0[19] as usize] =
443            Some(bls12_map_fp_to_g1 as PrecompileFn);
444        precompiles[BLS12_MAP_FP2_TO_G2.address.0[19] as usize] =
445            Some(bls12_map_fp2_to_g2 as PrecompileFn);
446        precompiles
447            [u16::from_be_bytes([P256VERIFY.address.0[18], P256VERIFY.address.0[19]]) as usize] =
448            Some(p_256_verify as PrecompileFn);
449        precompiles
450    };
451
452    // EXECUTE precompile is dispatched before the const table (LStar runtime gate;
453    // L1-only enforcement is at the is_precompile call site).
454    if fork >= Fork::LStar && address == EXECUTE.address {
455        return crate::execute_precompile::execute_precompile(
456            calldata,
457            gas_remaining,
458            fork,
459            crypto,
460            _stateless_validator,
461        );
462    }
463
464    if address[0..18] != [0u8; 18] {
465        return Err(VMError::Internal(InternalError::InvalidPrecompileAddress));
466    }
467    let index = u16::from_be_bytes([address[18], address[19]]) as usize;
468
469    let precompile = PRECOMPILES
470        .get(index)
471        .copied()
472        .flatten()
473        .ok_or(VMError::Internal(InternalError::InvalidPrecompileAddress))?;
474
475    // Check cache (skip identity -- copy is cheaper than lookup)
476    if address != IDENTITY.address
477        && let Some((output, gas_cost)) = cache.and_then(|c| c.get(&address, calldata))
478    {
479        increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
480        return Ok(output);
481    }
482
483    #[cfg(feature = "perf_opcode_timings")]
484    let precompile_time_start = std::time::Instant::now();
485
486    let gas_before = *gas_remaining;
487    let result = precompile(calldata, gas_remaining, fork, crypto);
488
489    #[cfg(feature = "perf_opcode_timings")]
490    {
491        let time = precompile_time_start.elapsed();
492        let mut timings = crate::timings::PRECOMPILES_TIMINGS.lock().expect("poison");
493        timings.update(address, time);
494    }
495
496    // Cache result on success (skip identity)
497    if address != IDENTITY.address
498        && let Some(cache) = cache
499        && let Ok(output) = &result
500    {
501        let gas_cost = gas_before.saturating_sub(*gas_remaining);
502        cache.insert(address, calldata.clone(), output.clone(), gas_cost);
503    }
504
505    result
506}
507
508/// Consumes gas and if it's higher than the gas limit returns an error.
509pub(crate) fn increase_precompile_consumed_gas(
510    gas_cost: u64,
511    gas_remaining: &mut u64,
512) -> Result<(), VMError> {
513    *gas_remaining = gas_remaining
514        .checked_sub(gas_cost)
515        .ok_or(PrecompileError::NotEnoughGas)?;
516    Ok(())
517}
518
519/// When slice length is less than `target_len`, the rest is filled with zeros. If slice length is
520/// more than `target_len`, the excess bytes are kept.
521#[inline(always)]
522pub(crate) fn fill_with_zeros(calldata: &Bytes, target_len: usize) -> Bytes {
523    if calldata.len() >= target_len {
524        // this clone is cheap (Arc)
525        return calldata.clone();
526    }
527    let mut padded_calldata = calldata.to_vec();
528    padded_calldata.resize(target_len, 0);
529    padded_calldata.into()
530}
531
532fn crypto_error_to_precompile(e: CryptoError) -> VMError {
533    match e {
534        CryptoError::InvalidPoint(_) => PrecompileError::InvalidPoint.into(),
535        CryptoError::InvalidInput(_) => PrecompileError::ParsingInputError.into(),
536        CryptoError::InvalidSignature => PrecompileError::ParsingInputError.into(),
537        CryptoError::InvalidRecoveryId => PrecompileError::ParsingInputError.into(),
538        CryptoError::RecoveryFailed => PrecompileError::ParsingInputError.into(),
539        CryptoError::VerificationFailed => PrecompileError::ParsingInputError.into(),
540        CryptoError::Other(_) => PrecompileError::ParsingInputError.into(),
541        // Misconfiguration rather than bad input: the BLS12-381 backend is
542        // unavailable (host built without `blst`, or a provider failed to
543        // override the trait default). Surface it as an internal error.
544        CryptoError::Unsupported(msg) => InternalError::Custom(msg.to_string()).into(),
545    }
546}
547
548/// Map crypto errors for BLS12-381 G1 operations (add).
549/// The old code returned BLS12381G1PointNotInCurve for invalid G1 points.
550fn bls12_g1_crypto_error(e: CryptoError) -> VMError {
551    match e {
552        CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G1PointNotInCurve.into(),
553        other => crypto_error_to_precompile(other),
554    }
555}
556
557/// Map crypto errors for BLS12-381 G2 operations (add).
558/// The old code returned BLS12381G2PointNotInCurve for invalid G2 points.
559fn bls12_g2_crypto_error(e: CryptoError) -> VMError {
560    match e {
561        CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G2PointNotInCurve.into(),
562        other => crypto_error_to_precompile(other),
563    }
564}
565
566/// Map crypto errors for BLS12-381 MSM and pairing operations.
567/// The old code used `G1Affine::from_uncompressed` (checked), which returned
568/// ParsingInputError for any invalid point (not on curve or not in subgroup).
569fn bls12_msm_pairing_crypto_error(e: CryptoError) -> VMError {
570    match e {
571        CryptoError::InvalidPoint(_) => PrecompileError::ParsingInputError.into(),
572        other => crypto_error_to_precompile(other),
573    }
574}
575
576/// ## ECRECOVER precompile.
577/// Elliptic curve digital signature algorithm (ECDSA) public key recovery function.
578///
579/// Input is 128 bytes (padded with zeros if shorter):
580///   [0..32)  : keccak-256 hash (message digest)
581///   [32..64) : v (27 or 28)
582///   [64..128): r||s (64 bytes)
583///
584/// Returns the recovered address.
585pub fn ecrecover(
586    calldata: &Bytes,
587    gas_remaining: &mut u64,
588    _fork: Fork,
589    crypto: &dyn Crypto,
590) -> Result<Bytes, VMError> {
591    use crate::gas_cost::ECRECOVER_COST;
592
593    increase_precompile_consumed_gas(ECRECOVER_COST, gas_remaining)?;
594
595    const INPUT_LEN: usize = 128;
596    const WORD: usize = 32;
597
598    let input = fill_with_zeros(calldata, INPUT_LEN);
599
600    #[expect(
601        clippy::indexing_slicing,
602        reason = "fill_with_zeros guarantees len >= 128"
603    )]
604    let raw_hash: &[u8] = &input[0..WORD];
605    #[expect(
606        clippy::indexing_slicing,
607        reason = "fill_with_zeros guarantees len >= 128"
608    )]
609    let raw_v: &[u8] = &input[WORD..WORD * 2];
610    #[expect(
611        clippy::indexing_slicing,
612        reason = "fill_with_zeros guarantees len >= 128"
613    )]
614    let raw_sig: &[u8] = &input[WORD * 2..WORD * 2 + 64];
615
616    // EVM expects v ∈ {27, 28}. Anything else is invalid → empty return.
617    let recid_byte: u8 = match u8::try_from(u256_from_big_endian(raw_v)) {
618        Ok(27) => 0,
619        Ok(28) => 1,
620        _ => return Ok(Bytes::new()),
621    };
622
623    let msg_hash: [u8; 32] = raw_hash
624        .try_into()
625        .map_err(|_| InternalError::TypeConversion)?;
626    let sig: [u8; 64] = raw_sig
627        .try_into()
628        .map_err(|_| InternalError::TypeConversion)?;
629
630    let pk_hash = match crypto.secp256k1_ecrecover(&sig, recid_byte, &msg_hash) {
631        Ok(h) => h,
632        Err(_) => return Ok(Bytes::new()),
633    };
634
635    // Address is the last 20 bytes of the keccak hash of the public key.
636    let mut out = [0u8; 32];
637    out[12..32].copy_from_slice(&pk_hash[12..32]);
638
639    Ok(Bytes::copy_from_slice(&out))
640}
641
642/// Returns the calldata received
643pub fn identity(
644    calldata: &Bytes,
645    gas_remaining: &mut u64,
646    _fork: Fork,
647    _crypto: &dyn Crypto,
648) -> Result<Bytes, VMError> {
649    let gas_cost = gas_cost::identity(calldata.len())?;
650
651    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
652
653    Ok(calldata.clone())
654}
655
656/// Returns the calldata hashed by sha2-256 algorithm
657pub fn sha2_256(
658    calldata: &Bytes,
659    gas_remaining: &mut u64,
660    _fork: Fork,
661    crypto: &dyn Crypto,
662) -> Result<Bytes, VMError> {
663    let gas_cost = gas_cost::sha2_256(calldata.len())?;
664
665    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
666
667    let digest = crypto.sha256(calldata);
668    Ok(Bytes::copy_from_slice(&digest))
669}
670
671/// Returns the calldata hashed by ripemd-160 algorithm, padded by zeros at left
672pub fn ripemd_160(
673    calldata: &Bytes,
674    gas_remaining: &mut u64,
675    _fork: Fork,
676    crypto: &dyn Crypto,
677) -> Result<Bytes, VMError> {
678    let gas_cost = gas_cost::ripemd_160(calldata.len())?;
679
680    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
681
682    let result = crypto.ripemd160(calldata);
683    Ok(Bytes::copy_from_slice(&result))
684}
685
686/// Returns the result of the module-exponentiation operation
687#[expect(clippy::indexing_slicing, reason = "bounds checked at start")]
688pub fn modexp(
689    calldata: &Bytes,
690    gas_remaining: &mut u64,
691    fork: Fork,
692    crypto: &dyn Crypto,
693) -> Result<Bytes, VMError> {
694    // If calldata does not reach the required length, we should fill the rest with zeros
695    let calldata = fill_with_zeros(calldata, 96);
696
697    // Defer converting to a U256 after the zero check.
698    if fork < Fork::Osaka {
699        let base_size_bytes: [u8; 32] = calldata[0..32].try_into()?;
700        let modulus_size_bytes: [u8; 32] = calldata[64..96].try_into()?;
701        const ZERO_BYTES: [u8; 32] = [0u8; 32];
702
703        if base_size_bytes == ZERO_BYTES && modulus_size_bytes == ZERO_BYTES {
704            // On Berlin or newer there is a floor cost for the modexp precompile
705            increase_precompile_consumed_gas(MODEXP_STATIC_COST, gas_remaining)?;
706            return Ok(Bytes::new());
707        }
708    }
709
710    // The try_into are infallible and the compiler optimizes them out, even without unsafe.
711    // https://godbolt.org/z/h8rW8M3c4
712    let base_size = u256_from_big_endian_const::<32>(calldata[0..32].try_into()?);
713    let modulus_size = u256_from_big_endian_const::<32>(calldata[64..96].try_into()?);
714    let exponent_size = u256_from_big_endian_const::<32>(calldata[32..64].try_into()?);
715
716    if fork >= Fork::Osaka {
717        if base_size > U256::from(1024) {
718            return Err(PrecompileError::ModExpBaseTooLarge.into());
719        }
720        if exponent_size > U256::from(1024) {
721            return Err(PrecompileError::ModExpExpTooLarge.into());
722        }
723        if modulus_size > U256::from(1024) {
724            return Err(PrecompileError::ModExpModulusTooLarge.into());
725        }
726    }
727
728    // Because on some cases conversions to usize exploded before the check of the zero value could be done
729    let base_size = usize::try_from(base_size).map_err(|_| PrecompileError::ParsingInputError)?;
730    let exponent_size =
731        usize::try_from(exponent_size).map_err(|_| PrecompileError::ParsingInputError)?;
732    let modulus_size =
733        usize::try_from(modulus_size).map_err(|_| PrecompileError::ParsingInputError)?;
734
735    let base_limit = base_size.checked_add(96).ok_or(InternalError::Overflow)?;
736
737    let exponent_limit = exponent_size
738        .checked_add(base_limit)
739        .ok_or(InternalError::Overflow)?;
740
741    let modulus_limit = modulus_size
742        .checked_add(exponent_limit)
743        .ok_or(InternalError::Overflow)?;
744
745    let b = get_slice_or_default(&calldata, 96, base_limit, base_size);
746    let e = get_slice_or_default(&calldata, base_limit, exponent_limit, exponent_size);
747    let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, modulus_size);
748
749    // Gas computation uses malachite Natural to compute bit length of exponent
750    use malachite::Natural;
751    use malachite::base::num::conversion::traits::*;
752    let exp_first_32_bytes = e.get(0..32.min(exponent_size)).unwrap_or_default();
753    let exp_first_32 =
754        Natural::from_power_of_2_digits_desc(8u64, exp_first_32_bytes.iter().cloned())
755            .ok_or(InternalError::TypeConversion)?;
756
757    let gas_cost = gas_cost::modexp(&exp_first_32, base_size, exponent_size, modulus_size, fork)?;
758
759    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
760
761    if base_size == 0 && modulus_size == 0 {
762        return Ok(Bytes::new());
763    }
764
765    let result = crypto
766        .modexp(&b, &e, &m)
767        .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
768
769    let res_bytes = increase_left_pad(&Bytes::from(result), modulus_size);
770
771    Ok(res_bytes.slice(..modulus_size))
772}
773
774/// This function returns the slice between the lower and upper limit of the calldata (as a vector),
775/// padding with zeros at the end if necessary.
776///
777/// Uses Cow so that the best case of no resizing doesn't require an allocation.
778#[expect(clippy::indexing_slicing, reason = "bounds checked")]
779fn get_slice_or_default<'c>(
780    calldata: &'c Bytes,
781    lower_limit: usize,
782    upper_limit: usize,
783    size_to_expand: usize,
784) -> Cow<'c, [u8]> {
785    let upper_limit = calldata.len().min(upper_limit);
786    if let Some(data) = calldata.get(lower_limit..upper_limit)
787        && !data.is_empty()
788    {
789        if data.len() == size_to_expand {
790            return data.into();
791        }
792        let mut extended = vec![0u8; size_to_expand];
793        let copy_size = size_to_expand.min(data.len());
794        extended[..copy_size].copy_from_slice(&data[..copy_size]);
795        return extended.into();
796    }
797    Vec::new().into()
798}
799
800/// If the result size is less than needed, pads left with zeros.
801#[inline(always)]
802pub fn increase_left_pad(result: &Bytes, m_size: usize) -> Bytes {
803    #[expect(
804        clippy::arithmetic_side_effects,
805        clippy::indexing_slicing,
806        reason = "overflow checked with the if condition, bounds checked"
807    )]
808    if result.len() < m_size {
809        let mut padded_result = vec![0u8; m_size];
810        let size_diff = m_size - result.len();
811        padded_result[size_diff..].copy_from_slice(result);
812
813        padded_result.into()
814    } else {
815        // this clone is cheap (Arc)
816        result.clone()
817    }
818}
819
820/// Makes a point addition on the elliptic curve 'alt_bn128'
821pub fn ecadd(
822    calldata: &Bytes,
823    gas_remaining: &mut u64,
824    _fork: Fork,
825    crypto: &dyn Crypto,
826) -> Result<Bytes, VMError> {
827    // If calldata does not reach the required length, we should fill the rest with zeros
828    let calldata = fill_with_zeros(calldata, 128);
829
830    increase_precompile_consumed_gas(ECADD_COST, gas_remaining)?;
831
832    let (Some(first_point), Some(second_point)) =
833        (parse_bn254_g1(&calldata, 0), parse_bn254_g1(&calldata, 64))
834    else {
835        return Err(InternalError::Slicing.into());
836    };
837    validate_bn254_g1_coords(&first_point)?;
838    validate_bn254_g1_coords(&second_point)?;
839
840    #[expect(clippy::indexing_slicing, reason = "calldata padded to 128 bytes")]
841    let result = crypto
842        .bn254_g1_add(&calldata[..64], &calldata[64..128])
843        .map_err(crypto_error_to_precompile)?;
844
845    Ok(Bytes::copy_from_slice(&result))
846}
847
848/// Makes a scalar multiplication on the elliptic curve 'alt_bn128'
849pub fn ecmul(
850    calldata: &Bytes,
851    gas_remaining: &mut u64,
852    _fork: Fork,
853    crypto: &dyn Crypto,
854) -> Result<Bytes, VMError> {
855    // If calldata does not reach the required length, we should fill the rest with zeros
856    let calldata = fill_with_zeros(calldata, 96);
857    increase_precompile_consumed_gas(ECMUL_COST, gas_remaining)?;
858
859    let (Some(g1), Some(_scalar)) = (
860        parse_bn254_g1(&calldata, 0),
861        parse_bn254_scalar(&calldata, 64),
862    ) else {
863        return Err(InternalError::Slicing.into());
864    };
865    validate_bn254_g1_coords(&g1)?;
866
867    #[expect(clippy::indexing_slicing, reason = "calldata padded to 96 bytes")]
868    let result = crypto
869        .bn254_g1_mul(&calldata[..64], &calldata[64..96])
870        .map_err(crypto_error_to_precompile)?;
871
872    Ok(Bytes::copy_from_slice(&result))
873}
874
875const ALT_BN128_PRIME: U256 = U256([
876    0x3c208c16d87cfd47,
877    0x97816a916871ca8d,
878    0xb85045b68181585d,
879    0x30644e72e131a029,
880]);
881
882pub struct G1(U256, U256);
883impl G1 {
884    /// According to EIP-197, the point at infinity (also called neutral element of G1 or zero) is encoded as (0, 0)
885    pub fn is_zero(&self) -> bool {
886        self.0.is_zero() && self.1.is_zero()
887    }
888}
889pub struct G2(U256, U256, U256, U256);
890impl G2 {
891    /// According to EIP-197, the point at infinity (also called neutral element of G2 or zero) is encoded as (0, 0, 0, 0)
892    pub fn is_zero(&self) -> bool {
893        self.0.is_zero() && self.1.is_zero() && self.2.is_zero() && self.3.is_zero()
894    }
895}
896
897/// Parses 32 bytes as BN254 scalar
898#[inline]
899fn parse_bn254_scalar(buf: &[u8], offset: usize) -> Option<U256> {
900    buf.get(offset..offset.checked_add(32)?)
901        .map(u256_from_big_endian)
902}
903
904/// Parses 64 bytes as a BN254 G1 point
905#[inline]
906fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option<G1> {
907    let chunk = buf.get(offset..offset.checked_add(64)?)?;
908    let (x_bytes, y_bytes) = chunk.split_at_checked(32)?;
909    Some(G1(
910        u256_from_big_endian(x_bytes),
911        u256_from_big_endian(y_bytes),
912    ))
913}
914
915/// Parses 128 bytes as a BN254 G2 point
916fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option<G2> {
917    let chunk = buf.get(offset..offset.checked_add(128)?)?;
918    let (g2_xy, rest) = chunk.split_at_checked(32)?;
919    let (g2_xx, rest) = rest.split_at_checked(32)?;
920    let (g2_yy, g2_yx) = rest.split_at_checked(32)?;
921    Some(G2(
922        u256_from_big_endian(g2_xx),
923        u256_from_big_endian(g2_xy),
924        u256_from_big_endian(g2_yx),
925        u256_from_big_endian(g2_yy),
926    ))
927}
928
929#[inline]
930fn validate_bn254_g1_coords(g1: &G1) -> Result<(), VMError> {
931    // check each element is in field
932    if g1.0 >= ALT_BN128_PRIME || g1.1 >= ALT_BN128_PRIME {
933        return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
934    }
935    Ok(())
936}
937
938#[inline]
939fn validate_bn254_g2_coords(g2: &G2) -> Result<(), VMError> {
940    // check each element is in field
941    if g2.0 >= ALT_BN128_PRIME
942        || g2.1 >= ALT_BN128_PRIME
943        || g2.2 >= ALT_BN128_PRIME
944        || g2.3 >= ALT_BN128_PRIME
945    {
946        return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
947    }
948    Ok(())
949}
950
951/// Performs a bilinear pairing on points on the elliptic curve 'alt_bn128', returns 1 on success and 0 on failure
952pub fn ecpairing(
953    calldata: &Bytes,
954    gas_remaining: &mut u64,
955    _fork: Fork,
956    crypto: &dyn Crypto,
957) -> Result<Bytes, VMError> {
958    // The input must always be a multiple of 192 (6 32-byte values)
959    if !calldata.len().is_multiple_of(192) {
960        return Err(PrecompileError::ParsingInputError.into());
961    }
962
963    let inputs_amount = calldata.len() / 192;
964    let gas_cost = gas_cost::ecpairing(inputs_amount)?;
965    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
966
967    let mut pairs: Vec<(&[u8], &[u8])> = Vec::new();
968    for input in calldata.chunks_exact(192) {
969        let (Some(g1), Some(g2)) = (parse_bn254_g1(input, 0), parse_bn254_g2(input, 64)) else {
970            return Err(InternalError::Slicing.into());
971        };
972        validate_bn254_g1_coords(&g1)?;
973        validate_bn254_g2_coords(&g2)?;
974        #[expect(clippy::indexing_slicing, reason = "chunks_exact guarantees 192 bytes")]
975        pairs.push((&input[..64], &input[64..192]));
976    }
977
978    let pairing_check = if pairs.is_empty() {
979        true
980    } else {
981        crypto
982            .bn254_pairing_check(&pairs)
983            .map_err(crypto_error_to_precompile)?
984    };
985
986    let mut result = [0; 32];
987    result[31] = u8::from(pairing_check);
988    Ok(Bytes::from_owner(result))
989}
990
991/// Returns the result of Blake2 hashing algorithm given a certain parameters from the calldata.
992pub fn blake2f(
993    calldata: &Bytes,
994    gas_remaining: &mut u64,
995    _fork: Fork,
996    crypto: &dyn Crypto,
997) -> Result<Bytes, VMError> {
998    if calldata.len() != 213 {
999        return Err(PrecompileError::ParsingInputError.into());
1000    }
1001
1002    let mut calldata = calldata.slice(0..213);
1003
1004    let rounds = calldata.get_u32();
1005
1006    let gas_cost = u64::from(rounds) * BLAKE2F_ROUND_COST;
1007    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1008
1009    let mut h = [0; 8];
1010
1011    h.copy_from_slice(&std::array::from_fn::<u64, 8, _>(|_| calldata.get_u64_le()));
1012
1013    let mut m = [0; 16];
1014
1015    m.copy_from_slice(&std::array::from_fn::<u64, 16, _>(|_| {
1016        calldata.get_u64_le()
1017    }));
1018
1019    let mut t = [0; 2];
1020    t.copy_from_slice(&std::array::from_fn::<u64, 2, _>(|_| calldata.get_u64_le()));
1021
1022    let f = calldata.get_u8();
1023    if f != 0 && f != 1 {
1024        return Err(PrecompileError::ParsingInputError.into());
1025    }
1026    let f = f == 1;
1027
1028    crypto.blake2_compress(rounds, &mut h, m, t, f);
1029
1030    Ok(Bytes::from_iter(
1031        h.into_iter().flat_map(|value| value.to_le_bytes()),
1032    ))
1033}
1034
1035/// Converts the provided commitment to match the provided versioned_hash.
1036fn kzg_commitment_to_versioned_hash(commitment_bytes: &[u8; 48], crypto: &dyn Crypto) -> H256 {
1037    let mut versioned_hash: [u8; 32] = crypto.sha256(commitment_bytes);
1038    versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
1039    versioned_hash.into()
1040}
1041
1042const POINT_EVALUATION_OUTPUT_BYTES: [u8; 64] = [
1043    // Big endian FIELD_ELEMENTS_PER_BLOB bytes
1044    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1045    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
1046    // Big endian BLS_MODULUS bytes
1047    0x73, 0xED, 0xA7, 0x53, 0x29, 0x9D, 0x7D, 0x48, 0x33, 0x39, 0xD8, 0x08, 0x09, 0xA1, 0xD8, 0x05,
1048    0x53, 0xBD, 0xA4, 0x02, 0xFF, 0xFE, 0x5B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01,
1049];
1050
1051/// Makes verifications on the received point, proof and commitment, if true returns a constant value
1052fn point_evaluation(
1053    calldata: &Bytes,
1054    gas_remaining: &mut u64,
1055    _fork: Fork,
1056    crypto: &dyn Crypto,
1057) -> Result<Bytes, VMError> {
1058    if calldata.len() != 192 {
1059        return Err(PrecompileError::ParsingInputError.into());
1060    }
1061
1062    // Consume gas
1063    let gas_cost = POINT_EVALUATION_COST;
1064    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1065
1066    // Parse inputs
1067    let versioned_hash: [u8; 32] = calldata
1068        .get(..32)
1069        .ok_or(InternalError::Slicing)?
1070        .try_into()
1071        .map_err(|_| InternalError::TypeConversion)?;
1072
1073    let z: [u8; 32] = calldata
1074        .get(32..64)
1075        .ok_or(InternalError::Slicing)?
1076        .try_into()
1077        .map_err(|_| InternalError::TypeConversion)?;
1078
1079    let y: [u8; 32] = calldata
1080        .get(64..96)
1081        .ok_or(InternalError::Slicing)?
1082        .try_into()
1083        .map_err(|_| InternalError::TypeConversion)?;
1084
1085    let commitment: [u8; 48] = calldata
1086        .get(96..144)
1087        .ok_or(InternalError::Slicing)?
1088        .try_into()
1089        .map_err(|_| InternalError::TypeConversion)?;
1090
1091    let proof: [u8; 48] = calldata
1092        .get(144..192)
1093        .ok_or(InternalError::Slicing)?
1094        .try_into()
1095        .map_err(|_| InternalError::TypeConversion)?;
1096
1097    // Perform the evaluation
1098
1099    // This checks if the commitment is equal to the versioned hash
1100    if kzg_commitment_to_versioned_hash(&commitment, crypto) != H256::from(versioned_hash) {
1101        return Err(PrecompileError::ParsingInputError.into());
1102    }
1103
1104    // This verifies the proof from a point (z, y) and a commitment
1105    crypto
1106        .verify_kzg_proof(&z, &y, &commitment, &proof)
1107        .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
1108
1109    // The first 32 bytes consist of the number of field elements in the blob, and the
1110    // other 32 bytes consist of the modulus used in the BLS signature scheme.
1111    let output = POINT_EVALUATION_OUTPUT_BYTES.to_vec();
1112
1113    Ok(Bytes::from(output))
1114}
1115
1116/// Signature verification in the "secp256r1" elliptic curve
1117/// If the verification succeeds, returns 1 in a 32-bit big-endian format.
1118/// If the verification fails, returns an empty `Bytes` object.
1119/// Implemented following https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7951.md
1120pub fn p_256_verify(
1121    calldata: &Bytes,
1122    gas_remaining: &mut u64,
1123    _fork: Fork,
1124    crypto: &dyn Crypto,
1125) -> Result<Bytes, VMError> {
1126    increase_precompile_consumed_gas(P256_VERIFY_COST, gas_remaining)
1127        .map_err(|_| PrecompileError::NotEnoughGas)?;
1128
1129    // Validate input data length is 160 bytes
1130    if calldata.len() != 160 {
1131        return Ok(Bytes::new());
1132    }
1133
1134    // Parse parameters
1135    #[expect(
1136        clippy::indexing_slicing,
1137        reason = "length of the calldata is checked before slicing"
1138    )]
1139    let msg: &[u8; 32] = calldata[0..32].try_into()?;
1140    #[expect(clippy::indexing_slicing, reason = "length checked")]
1141    let r: &[u8; 32] = calldata[32..64].try_into()?;
1142    #[expect(clippy::indexing_slicing, reason = "length checked")]
1143    let s: &[u8; 32] = calldata[64..96].try_into()?;
1144    #[expect(clippy::indexing_slicing, reason = "length checked")]
1145    let pk_x: &[u8; 32] = calldata[96..128].try_into()?;
1146    #[expect(clippy::indexing_slicing, reason = "length checked")]
1147    let pk_y: &[u8; 32] = calldata[128..160].try_into()?;
1148
1149    // Build 64-byte sig (r||s) and 64-byte pk (x||y)
1150    let mut sig_bytes = [0u8; 64];
1151    sig_bytes[..32].copy_from_slice(r);
1152    sig_bytes[32..].copy_from_slice(s);
1153
1154    let mut pk_bytes = [0u8; 64];
1155    pk_bytes[..32].copy_from_slice(pk_x);
1156    pk_bytes[32..].copy_from_slice(pk_y);
1157
1158    let success = crypto.secp256r1_verify(msg, &sig_bytes, &pk_bytes);
1159
1160    // If the verification succeeds, returns 1 in a 32-bit big-endian format.
1161    // If the verification fails, returns an empty `Bytes` object.
1162    if success {
1163        const RESULT: [u8; 32] = [
1164            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1165            0, 0, 1,
1166        ];
1167        Ok(Bytes::from_static(&RESULT))
1168    } else {
1169        Ok(Bytes::new())
1170    }
1171}
1172
1173/// Parse a 64-byte padded BLS12-381 field element into a 48-byte unpadded element.
1174/// The first 16 bytes must be zero (padding). Returns error if padding is invalid.
1175fn parse_bls12_padded_fp(padded: &[u8; 64]) -> Result<[u8; 48], VMError> {
1176    if padded[..16] != [0u8; 16] {
1177        return Err(PrecompileError::ParsingInputError.into());
1178    }
1179    let fp: [u8; 48] = padded[16..64]
1180        .try_into()
1181        .map_err(|_| InternalError::TypeConversion)?;
1182    Ok(fp)
1183}
1184
1185pub fn bls12_g1add(
1186    calldata: &Bytes,
1187    gas_remaining: &mut u64,
1188    _fork: Fork,
1189    crypto: &dyn Crypto,
1190) -> Result<Bytes, VMError> {
1191    let (x_data, calldata) = calldata
1192        .split_first_chunk::<128>()
1193        .ok_or(PrecompileError::ParsingInputError)?;
1194    let (y_data, calldata) = calldata
1195        .split_first_chunk::<128>()
1196        .ok_or(PrecompileError::ParsingInputError)?;
1197    if !calldata.is_empty() {
1198        return Err(PrecompileError::ParsingInputError.into());
1199    }
1200
1201    // Apply precompile gas cost.
1202    increase_precompile_consumed_gas(BLS12_381_G1ADD_COST, gas_remaining)
1203        .map_err(|_| PrecompileError::NotEnoughGas)?;
1204
1205    // Parse two 128-byte padded G1 points into 48-byte unpadded coordinates.
1206    let ax = parse_bls12_padded_fp(
1207        x_data[..64]
1208            .try_into()
1209            .map_err(|_| InternalError::TypeConversion)?,
1210    )?;
1211    let ay = parse_bls12_padded_fp(
1212        x_data[64..128]
1213            .try_into()
1214            .map_err(|_| InternalError::TypeConversion)?,
1215    )?;
1216    let bx = parse_bls12_padded_fp(
1217        y_data[..64]
1218            .try_into()
1219            .map_err(|_| InternalError::TypeConversion)?,
1220    )?;
1221    let by = parse_bls12_padded_fp(
1222        y_data[64..128]
1223            .try_into()
1224            .map_err(|_| InternalError::TypeConversion)?,
1225    )?;
1226
1227    let result = crypto
1228        .bls12_381_g1_add((ax, ay), (bx, by))
1229        .map_err(bls12_g1_crypto_error)?;
1230
1231    // Re-pad the 96-byte unpadded result (x||y each 48 bytes) to 128 bytes.
1232    let mut output = [0u8; 128];
1233    output[16..64].copy_from_slice(&result[..48]);
1234    output[80..128].copy_from_slice(&result[48..96]);
1235    Ok(Bytes::copy_from_slice(&output))
1236}
1237
1238pub fn bls12_g1msm(
1239    calldata: &Bytes,
1240    gas_remaining: &mut u64,
1241    _fork: Fork,
1242    crypto: &dyn Crypto,
1243) -> Result<Bytes, VMError> {
1244    if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G1_MSM_PAIR_LENGTH) {
1245        return Err(PrecompileError::ParsingInputError.into());
1246    }
1247
1248    let k = calldata.len() / BLS12_381_G1_MSM_PAIR_LENGTH;
1249    let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G1_K_DISCOUNT, G1_MUL_COST)?;
1250    increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1251
1252    #[allow(clippy::type_complexity)]
1253    let mut pairs: Vec<(([u8; 48], [u8; 48]), [u8; 32])> = Vec::with_capacity(k);
1254
1255    #[expect(
1256        clippy::arithmetic_side_effects,
1257        clippy::indexing_slicing,
1258        reason = "bounds checked"
1259    )]
1260    for i in 0..k {
1261        let point_offset = i * BLS12_381_G1_MSM_PAIR_LENGTH;
1262        let scalar_offset = point_offset + 128;
1263        let pair_end = scalar_offset + 32;
1264
1265        let point_bytes = &calldata[point_offset..scalar_offset];
1266        let scalar_bytes = &calldata[scalar_offset..pair_end];
1267
1268        let px = parse_bls12_padded_fp(
1269            point_bytes[..64]
1270                .try_into()
1271                .map_err(|_| InternalError::TypeConversion)?,
1272        )?;
1273        let py = parse_bls12_padded_fp(
1274            point_bytes[64..128]
1275                .try_into()
1276                .map_err(|_| InternalError::TypeConversion)?,
1277        )?;
1278        let scalar: [u8; 32] = scalar_bytes
1279            .try_into()
1280            .map_err(|_| InternalError::TypeConversion)?;
1281
1282        pairs.push(((px, py), scalar));
1283    }
1284
1285    let result = crypto
1286        .bls12_381_g1_msm(&pairs)
1287        .map_err(bls12_msm_pairing_crypto_error)?;
1288
1289    // Re-pad output: 96-byte result → 128-byte padded
1290    let mut output = [0u8; 128];
1291    output[16..64].copy_from_slice(&result[..48]);
1292    output[80..128].copy_from_slice(&result[48..96]);
1293    Ok(Bytes::copy_from_slice(&output))
1294}
1295
1296pub fn bls12_g2add(
1297    calldata: &Bytes,
1298    gas_remaining: &mut u64,
1299    _fork: Fork,
1300    crypto: &dyn Crypto,
1301) -> Result<Bytes, VMError> {
1302    let (x_data, calldata) = calldata
1303        .split_first_chunk::<256>()
1304        .ok_or(PrecompileError::ParsingInputError)?;
1305    let (y_data, calldata) = calldata
1306        .split_first_chunk::<256>()
1307        .ok_or(PrecompileError::ParsingInputError)?;
1308    if !calldata.is_empty() {
1309        return Err(PrecompileError::ParsingInputError.into());
1310    }
1311
1312    // Apply precompile gas cost.
1313    increase_precompile_consumed_gas(BLS12_381_G2ADD_COST, gas_remaining)
1314        .map_err(|_| PrecompileError::NotEnoughGas)?;
1315
1316    // Parse two 256-byte padded G2 points into four 48-byte unpadded coordinates each.
1317    // G2 point layout: x_0(64) || x_1(64) || y_0(64) || y_1(64) = 256 bytes
1318    let ax0 = parse_bls12_padded_fp(
1319        x_data[0..64]
1320            .try_into()
1321            .map_err(|_| InternalError::TypeConversion)?,
1322    )?;
1323    let ax1 = parse_bls12_padded_fp(
1324        x_data[64..128]
1325            .try_into()
1326            .map_err(|_| InternalError::TypeConversion)?,
1327    )?;
1328    let ay0 = parse_bls12_padded_fp(
1329        x_data[128..192]
1330            .try_into()
1331            .map_err(|_| InternalError::TypeConversion)?,
1332    )?;
1333    let ay1 = parse_bls12_padded_fp(
1334        x_data[192..256]
1335            .try_into()
1336            .map_err(|_| InternalError::TypeConversion)?,
1337    )?;
1338
1339    let bx0 = parse_bls12_padded_fp(
1340        y_data[0..64]
1341            .try_into()
1342            .map_err(|_| InternalError::TypeConversion)?,
1343    )?;
1344    let bx1 = parse_bls12_padded_fp(
1345        y_data[64..128]
1346            .try_into()
1347            .map_err(|_| InternalError::TypeConversion)?,
1348    )?;
1349    let by0 = parse_bls12_padded_fp(
1350        y_data[128..192]
1351            .try_into()
1352            .map_err(|_| InternalError::TypeConversion)?,
1353    )?;
1354    let by1 = parse_bls12_padded_fp(
1355        y_data[192..256]
1356            .try_into()
1357            .map_err(|_| InternalError::TypeConversion)?,
1358    )?;
1359
1360    let result = crypto
1361        .bls12_381_g2_add((ax0, ax1, ay0, ay1), (bx0, bx1, by0, by1))
1362        .map_err(bls12_g2_crypto_error)?;
1363
1364    // Re-pad the 192-byte unpadded result to 256 bytes.
1365    // Unpadded: x_0(48) || x_1(48) || y_0(48) || y_1(48) = 192 bytes
1366    // Padded output: x_0_padded(64) || x_1_padded(64) || y_0_padded(64) || y_1_padded(64) = 256 bytes
1367    let mut output = [0u8; 256];
1368    output[16..64].copy_from_slice(&result[0..48]);
1369    output[80..128].copy_from_slice(&result[48..96]);
1370    output[144..192].copy_from_slice(&result[96..144]);
1371    output[208..256].copy_from_slice(&result[144..192]);
1372    Ok(Bytes::copy_from_slice(&output))
1373}
1374
1375pub fn bls12_g2msm(
1376    calldata: &Bytes,
1377    gas_remaining: &mut u64,
1378    _fork: Fork,
1379    crypto: &dyn Crypto,
1380) -> Result<Bytes, VMError> {
1381    if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G2_MSM_PAIR_LENGTH) {
1382        return Err(PrecompileError::ParsingInputError.into());
1383    }
1384
1385    let k = calldata.len() / BLS12_381_G2_MSM_PAIR_LENGTH;
1386    let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G2_K_DISCOUNT, G2_MUL_COST)?;
1387    increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1388
1389    #[allow(clippy::type_complexity)]
1390    let mut pairs: Vec<(([u8; 48], [u8; 48], [u8; 48], [u8; 48]), [u8; 32])> =
1391        Vec::with_capacity(k);
1392
1393    #[expect(
1394        clippy::indexing_slicing,
1395        clippy::arithmetic_side_effects,
1396        reason = "bounds checked"
1397    )]
1398    for i in 0..k {
1399        let point_offset = i * BLS12_381_G2_MSM_PAIR_LENGTH;
1400        let scalar_offset = point_offset + 256;
1401        let pair_end = scalar_offset + 32;
1402
1403        let point_bytes = &calldata[point_offset..scalar_offset];
1404        let scalar_bytes = &calldata[scalar_offset..pair_end];
1405
1406        let x0 = parse_bls12_padded_fp(
1407            point_bytes[0..64]
1408                .try_into()
1409                .map_err(|_| InternalError::TypeConversion)?,
1410        )?;
1411        let x1 = parse_bls12_padded_fp(
1412            point_bytes[64..128]
1413                .try_into()
1414                .map_err(|_| InternalError::TypeConversion)?,
1415        )?;
1416        let y0 = parse_bls12_padded_fp(
1417            point_bytes[128..192]
1418                .try_into()
1419                .map_err(|_| InternalError::TypeConversion)?,
1420        )?;
1421        let y1 = parse_bls12_padded_fp(
1422            point_bytes[192..256]
1423                .try_into()
1424                .map_err(|_| InternalError::TypeConversion)?,
1425        )?;
1426        let scalar: [u8; 32] = scalar_bytes
1427            .try_into()
1428            .map_err(|_| InternalError::TypeConversion)?;
1429
1430        pairs.push(((x0, x1, y0, y1), scalar));
1431    }
1432
1433    let result = crypto
1434        .bls12_381_g2_msm(&pairs)
1435        .map_err(bls12_msm_pairing_crypto_error)?;
1436
1437    // Re-pad the 192-byte unpadded result to 256 bytes.
1438    let mut output = [0u8; 256];
1439    output[16..64].copy_from_slice(&result[0..48]);
1440    output[80..128].copy_from_slice(&result[48..96]);
1441    output[144..192].copy_from_slice(&result[96..144]);
1442    output[208..256].copy_from_slice(&result[144..192]);
1443    Ok(Bytes::copy_from_slice(&output))
1444}
1445
1446pub fn bls12_pairing_check(
1447    calldata: &Bytes,
1448    gas_remaining: &mut u64,
1449    _fork: Fork,
1450    crypto: &dyn Crypto,
1451) -> Result<Bytes, VMError> {
1452    if calldata.is_empty()
1453        || !calldata
1454            .len()
1455            .is_multiple_of(BLS12_381_PAIRING_CHECK_PAIR_LENGTH)
1456    {
1457        return Err(PrecompileError::ParsingInputError.into());
1458    }
1459
1460    // GAS
1461    let k = calldata.len() / BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1462    let gas_cost = gas_cost::bls12_pairing_check(k)?;
1463    increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1464
1465    #[allow(clippy::type_complexity)]
1466    let mut pairs: Vec<(
1467        ([u8; 48], [u8; 48]),
1468        ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
1469    )> = Vec::with_capacity(k);
1470
1471    #[expect(
1472        clippy::indexing_slicing,
1473        clippy::arithmetic_side_effects,
1474        reason = "bounds checked"
1475    )]
1476    for i in 0..k {
1477        let g1_offset = i * BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1478        let g2_offset = g1_offset + 128;
1479        let pair_end = g2_offset + 256;
1480
1481        let g1_bytes = &calldata[g1_offset..g2_offset];
1482        let g2_bytes = &calldata[g2_offset..pair_end];
1483
1484        let g1x = parse_bls12_padded_fp(
1485            g1_bytes[0..64]
1486                .try_into()
1487                .map_err(|_| InternalError::TypeConversion)?,
1488        )?;
1489        let g1y = parse_bls12_padded_fp(
1490            g1_bytes[64..128]
1491                .try_into()
1492                .map_err(|_| InternalError::TypeConversion)?,
1493        )?;
1494
1495        let g2x0 = parse_bls12_padded_fp(
1496            g2_bytes[0..64]
1497                .try_into()
1498                .map_err(|_| InternalError::TypeConversion)?,
1499        )?;
1500        let g2x1 = parse_bls12_padded_fp(
1501            g2_bytes[64..128]
1502                .try_into()
1503                .map_err(|_| InternalError::TypeConversion)?,
1504        )?;
1505        let g2y0 = parse_bls12_padded_fp(
1506            g2_bytes[128..192]
1507                .try_into()
1508                .map_err(|_| InternalError::TypeConversion)?,
1509        )?;
1510        let g2y1 = parse_bls12_padded_fp(
1511            g2_bytes[192..256]
1512                .try_into()
1513                .map_err(|_| InternalError::TypeConversion)?,
1514        )?;
1515
1516        pairs.push(((g1x, g1y), (g2x0, g2x1, g2y0, g2y1)));
1517    }
1518
1519    let result = crypto
1520        .bls12_381_pairing_check(&pairs)
1521        .map_err(bls12_msm_pairing_crypto_error)?;
1522
1523    if result {
1524        let mut out = vec![0_u8; 31];
1525        out.push(1);
1526        Ok(Bytes::from(out))
1527    } else {
1528        Ok(Bytes::copy_from_slice(&[0_u8; 32]))
1529    }
1530}
1531
1532pub fn bls12_map_fp_to_g1(
1533    calldata: &Bytes,
1534    gas_remaining: &mut u64,
1535    _fork: Fork,
1536    crypto: &dyn Crypto,
1537) -> Result<Bytes, VMError> {
1538    if calldata.len() != BLS12_381_FP_VALID_INPUT_LENGTH {
1539        return Err(PrecompileError::ParsingInputError.into());
1540    }
1541
1542    // GAS
1543    increase_precompile_consumed_gas(BLS12_381_MAP_FP_TO_G1_COST, gas_remaining)?;
1544
1545    // Parse the 64-byte padded field element into 48-byte unpadded.
1546    #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1547    let fp = parse_bls12_padded_fp(
1548        calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1549            .try_into()
1550            .map_err(|_| InternalError::TypeConversion)?,
1551    )?;
1552
1553    let result = crypto
1554        .bls12_381_fp_to_g1(&fp)
1555        .map_err(crypto_error_to_precompile)?;
1556
1557    // Re-pad the 96-byte unpadded G1 result to 128 bytes.
1558    let mut output = [0u8; 128];
1559    output[16..64].copy_from_slice(&result[0..48]);
1560    output[80..128].copy_from_slice(&result[48..96]);
1561    Ok(Bytes::copy_from_slice(&output))
1562}
1563
1564pub fn bls12_map_fp2_to_g2(
1565    calldata: &Bytes,
1566    gas_remaining: &mut u64,
1567    _fork: Fork,
1568    crypto: &dyn Crypto,
1569) -> Result<Bytes, VMError> {
1570    if calldata.len() != BLS12_381_FP2_VALID_INPUT_LENGTH {
1571        return Err(PrecompileError::ParsingInputError.into());
1572    }
1573
1574    // GAS
1575    increase_precompile_consumed_gas(BLS12_381_MAP_FP2_TO_G2_COST, gas_remaining)?;
1576
1577    // Parse the two 64-byte padded field elements into 48-byte unpadded.
1578    #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1579    let c0 = parse_bls12_padded_fp(
1580        calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1581            .try_into()
1582            .map_err(|_| InternalError::TypeConversion)?,
1583    )?;
1584    #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1585    let c1 = parse_bls12_padded_fp(
1586        calldata[PADDED_FIELD_ELEMENT_SIZE_IN_BYTES..BLS12_381_FP2_VALID_INPUT_LENGTH]
1587            .try_into()
1588            .map_err(|_| InternalError::TypeConversion)?,
1589    )?;
1590
1591    if c0 == [0u8; 48] && c1 == [0u8; 48] {
1592        return Ok(Bytes::copy_from_slice(&FP2_ZERO_MAPPED_TO_G2));
1593    }
1594
1595    let result = crypto
1596        .bls12_381_fp2_to_g2((c0, c1))
1597        .map_err(crypto_error_to_precompile)?;
1598
1599    // Re-pad the 192-byte unpadded G2 result to 256 bytes.
1600    // Unpadded: x_0(48) || x_1(48) || y_0(48) || y_1(48) = 192 bytes
1601    // Padded output: x_0_padded(64) || x_1_padded(64) || y_0_padded(64) || y_1_padded(64) = 256 bytes
1602    let mut output = [0u8; 256];
1603    output[16..64].copy_from_slice(&result[0..48]);
1604    output[80..128].copy_from_slice(&result[48..96]);
1605    output[144..192].copy_from_slice(&result[96..144]);
1606    output[208..256].copy_from_slice(&result[144..192]);
1607    Ok(Bytes::copy_from_slice(&output))
1608}
1609
1610#[cfg(test)]
1611mod tests {
1612    use super::*;
1613    use ethrex_common::types::Fork;
1614
1615    /// I3 gate test: EXECUTE precompile is L1-only and only active since LStar.
1616    #[test]
1617    fn execute_precompile_gated_by_lstar_and_l1() {
1618        let execute_addr = EXECUTE.address;
1619        // Pre-LStar L1: not a precompile.
1620        assert!(!is_precompile(&execute_addr, Fork::Amsterdam, VMType::L1));
1621        // LStar on L2: not a precompile (L1-only, I3).
1622        assert!(!is_precompile(
1623            &execute_addr,
1624            Fork::LStar,
1625            VMType::L2(Default::default())
1626        ));
1627        // LStar on L1: is a precompile.
1628        assert!(is_precompile(&execute_addr, Fork::LStar, VMType::L1));
1629    }
1630}