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