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
44const 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 EXECUTE: Precompile = Precompile {
240 address: H160([
241 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
242 0x00, 0x00, 0x00, 0x01, 0x01,
243 ]),
244 name: "EXECUTE",
245 active_since_fork: LStar,
246};
247
248pub const PRECOMPILES: [Precompile; 18] = [
249 ECRECOVER,
250 SHA2_256,
251 RIPEMD_160,
252 IDENTITY,
253 MODEXP,
254 ECADD,
255 ECMUL,
256 ECPAIRING,
257 BLAKE2F,
258 POINT_EVALUATION,
259 BLS12_G1ADD,
260 BLS12_G1MSM,
261 BLS12_G2ADD,
262 BLS12_G2MSM,
263 BLS12_PAIRING_CHECK,
264 BLS12_MAP_FP_TO_G1,
265 BLS12_MAP_FP2_TO_G2,
266 P256VERIFY,
267];
268
269pub fn precompiles_for_fork(fork: Fork) -> impl Iterator<Item = Precompile> {
270 PRECOMPILES
271 .into_iter()
272 .filter(move |precompile| precompile.active_since_fork <= fork)
273}
274
275pub fn is_precompile(address: &Address, fork: Fork, vm_type: VMType) -> bool {
276 if fork >= Fork::LStar && matches!(vm_type, VMType::L1) && *address == EXECUTE.address {
277 return true;
278 }
279 (matches!(vm_type, VMType::L2(_)) && *address == P256VERIFY.address)
280 || precompiles_for_fork(fork).any(|precompile| precompile.address == *address)
281}
282
283pub struct PrecompileCache {
285 cache: RwLock<FxHashMap<(Address, Bytes), (Bytes, u64)>>,
286}
287
288impl Default for PrecompileCache {
289 fn default() -> Self {
290 Self {
291 cache: RwLock::new(FxHashMap::default()),
292 }
293 }
294}
295
296impl PrecompileCache {
297 pub fn new() -> Self {
298 Self::default()
299 }
300
301 pub fn get(&self, address: &Address, calldata: &Bytes) -> Option<(Bytes, u64)> {
302 self.cache
306 .read()
307 .unwrap_or_else(|poisoned| poisoned.into_inner())
308 .get(&(*address, calldata.clone()))
309 .cloned()
310 }
311
312 pub fn insert(&self, address: Address, calldata: Bytes, output: Bytes, gas_cost: u64) {
313 self.cache
314 .write()
315 .unwrap_or_else(|poisoned| poisoned.into_inner())
316 .insert((address, calldata), (output, gas_cost));
317 }
318}
319
320#[expect(clippy::as_conversions, clippy::indexing_slicing)]
321pub fn execute_precompile(
322 address: Address,
323 calldata: &Bytes,
324 gas_remaining: &mut u64,
325 fork: Fork,
326 cache: Option<&PrecompileCache>,
327 crypto: &dyn Crypto,
328 _stateless_validator: Option<&dyn crate::StatelessValidator>,
329) -> Result<Bytes, VMError> {
330 type PrecompileFn = fn(&Bytes, &mut u64, Fork, &dyn Crypto) -> Result<Bytes, VMError>;
331
332 const PRECOMPILES: [Option<PrecompileFn>; 512] = const {
333 let mut precompiles = [const { None }; 512];
334 precompiles[ECRECOVER.address.0[19] as usize] = Some(ecrecover as PrecompileFn);
335 precompiles[IDENTITY.address.0[19] as usize] = Some(identity as PrecompileFn);
336 precompiles[SHA2_256.address.0[19] as usize] = Some(sha2_256 as PrecompileFn);
337 precompiles[RIPEMD_160.address.0[19] as usize] = Some(ripemd_160 as PrecompileFn);
338 precompiles[MODEXP.address.0[19] as usize] = Some(modexp as PrecompileFn);
339 precompiles[ECADD.address.0[19] as usize] = Some(ecadd as PrecompileFn);
340 precompiles[ECMUL.address.0[19] as usize] = Some(ecmul as PrecompileFn);
341 precompiles[ECPAIRING.address.0[19] as usize] = Some(ecpairing as PrecompileFn);
342 precompiles[BLAKE2F.address.0[19] as usize] = Some(blake2f as PrecompileFn);
343 precompiles[POINT_EVALUATION.address.0[19] as usize] =
344 Some(point_evaluation as PrecompileFn);
345 precompiles[BLS12_G1ADD.address.0[19] as usize] = Some(bls12_g1add as PrecompileFn);
346 precompiles[BLS12_G1MSM.address.0[19] as usize] = Some(bls12_g1msm as PrecompileFn);
347 precompiles[BLS12_G2ADD.address.0[19] as usize] = Some(bls12_g2add as PrecompileFn);
348 precompiles[BLS12_G2MSM.address.0[19] as usize] = Some(bls12_g2msm as PrecompileFn);
349 precompiles[BLS12_PAIRING_CHECK.address.0[19] as usize] =
350 Some(bls12_pairing_check as PrecompileFn);
351 precompiles[BLS12_MAP_FP_TO_G1.address.0[19] as usize] =
352 Some(bls12_map_fp_to_g1 as PrecompileFn);
353 precompiles[BLS12_MAP_FP2_TO_G2.address.0[19] as usize] =
354 Some(bls12_map_fp2_to_g2 as PrecompileFn);
355 precompiles
356 [u16::from_be_bytes([P256VERIFY.address.0[18], P256VERIFY.address.0[19]]) as usize] =
357 Some(p_256_verify as PrecompileFn);
358 precompiles
359 };
360
361 if fork >= Fork::LStar && address == EXECUTE.address {
364 return crate::execute_precompile::execute_precompile(
365 calldata,
366 gas_remaining,
367 fork,
368 crypto,
369 _stateless_validator,
370 );
371 }
372
373 if address[0..18] != [0u8; 18] {
374 return Err(VMError::Internal(InternalError::InvalidPrecompileAddress));
375 }
376 let index = u16::from_be_bytes([address[18], address[19]]) as usize;
377
378 let precompile = PRECOMPILES
379 .get(index)
380 .copied()
381 .flatten()
382 .ok_or(VMError::Internal(InternalError::InvalidPrecompileAddress))?;
383
384 if address != IDENTITY.address
386 && let Some((output, gas_cost)) = cache.and_then(|c| c.get(&address, calldata))
387 {
388 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
389 return Ok(output);
390 }
391
392 #[cfg(feature = "perf_opcode_timings")]
393 let precompile_time_start = std::time::Instant::now();
394
395 let gas_before = *gas_remaining;
396 let result = precompile(calldata, gas_remaining, fork, crypto);
397
398 #[cfg(feature = "perf_opcode_timings")]
399 {
400 let time = precompile_time_start.elapsed();
401 let mut timings = crate::timings::PRECOMPILES_TIMINGS.lock().expect("poison");
402 timings.update(address, time);
403 }
404
405 if address != IDENTITY.address
407 && let Some(cache) = cache
408 && let Ok(output) = &result
409 {
410 let gas_cost = gas_before.saturating_sub(*gas_remaining);
411 cache.insert(address, calldata.clone(), output.clone(), gas_cost);
412 }
413
414 result
415}
416
417pub(crate) fn increase_precompile_consumed_gas(
419 gas_cost: u64,
420 gas_remaining: &mut u64,
421) -> Result<(), VMError> {
422 *gas_remaining = gas_remaining
423 .checked_sub(gas_cost)
424 .ok_or(PrecompileError::NotEnoughGas)?;
425 Ok(())
426}
427
428#[inline(always)]
431pub(crate) fn fill_with_zeros(calldata: &Bytes, target_len: usize) -> Bytes {
432 if calldata.len() >= target_len {
433 return calldata.clone();
435 }
436 let mut padded_calldata = calldata.to_vec();
437 padded_calldata.resize(target_len, 0);
438 padded_calldata.into()
439}
440
441fn crypto_error_to_precompile(e: CryptoError) -> VMError {
442 match e {
443 CryptoError::InvalidPoint(_) => PrecompileError::InvalidPoint.into(),
444 CryptoError::InvalidInput(_) => PrecompileError::ParsingInputError.into(),
445 CryptoError::InvalidSignature => PrecompileError::ParsingInputError.into(),
446 CryptoError::InvalidRecoveryId => PrecompileError::ParsingInputError.into(),
447 CryptoError::RecoveryFailed => PrecompileError::ParsingInputError.into(),
448 CryptoError::VerificationFailed => PrecompileError::ParsingInputError.into(),
449 CryptoError::Other(_) => PrecompileError::ParsingInputError.into(),
450 CryptoError::Unsupported(msg) => InternalError::Custom(msg.to_string()).into(),
454 }
455}
456
457fn bls12_g1_crypto_error(e: CryptoError) -> VMError {
460 match e {
461 CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G1PointNotInCurve.into(),
462 other => crypto_error_to_precompile(other),
463 }
464}
465
466fn bls12_g2_crypto_error(e: CryptoError) -> VMError {
469 match e {
470 CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G2PointNotInCurve.into(),
471 other => crypto_error_to_precompile(other),
472 }
473}
474
475fn bls12_msm_pairing_crypto_error(e: CryptoError) -> VMError {
479 match e {
480 CryptoError::InvalidPoint(_) => PrecompileError::ParsingInputError.into(),
481 other => crypto_error_to_precompile(other),
482 }
483}
484
485pub fn ecrecover(
495 calldata: &Bytes,
496 gas_remaining: &mut u64,
497 _fork: Fork,
498 crypto: &dyn Crypto,
499) -> Result<Bytes, VMError> {
500 use crate::gas_cost::ECRECOVER_COST;
501
502 increase_precompile_consumed_gas(ECRECOVER_COST, gas_remaining)?;
503
504 const INPUT_LEN: usize = 128;
505 const WORD: usize = 32;
506
507 let input = fill_with_zeros(calldata, INPUT_LEN);
508
509 #[expect(
510 clippy::indexing_slicing,
511 reason = "fill_with_zeros guarantees len >= 128"
512 )]
513 let raw_hash: &[u8] = &input[0..WORD];
514 #[expect(
515 clippy::indexing_slicing,
516 reason = "fill_with_zeros guarantees len >= 128"
517 )]
518 let raw_v: &[u8] = &input[WORD..WORD * 2];
519 #[expect(
520 clippy::indexing_slicing,
521 reason = "fill_with_zeros guarantees len >= 128"
522 )]
523 let raw_sig: &[u8] = &input[WORD * 2..WORD * 2 + 64];
524
525 let recid_byte: u8 = match u8::try_from(u256_from_big_endian(raw_v)) {
527 Ok(27) => 0,
528 Ok(28) => 1,
529 _ => return Ok(Bytes::new()),
530 };
531
532 let msg_hash: [u8; 32] = raw_hash
533 .try_into()
534 .map_err(|_| InternalError::TypeConversion)?;
535 let sig: [u8; 64] = raw_sig
536 .try_into()
537 .map_err(|_| InternalError::TypeConversion)?;
538
539 let pk_hash = match crypto.secp256k1_ecrecover(&sig, recid_byte, &msg_hash) {
540 Ok(h) => h,
541 Err(_) => return Ok(Bytes::new()),
542 };
543
544 let mut out = [0u8; 32];
546 out[12..32].copy_from_slice(&pk_hash[12..32]);
547
548 Ok(Bytes::copy_from_slice(&out))
549}
550
551pub fn identity(
553 calldata: &Bytes,
554 gas_remaining: &mut u64,
555 _fork: Fork,
556 _crypto: &dyn Crypto,
557) -> Result<Bytes, VMError> {
558 let gas_cost = gas_cost::identity(calldata.len())?;
559
560 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
561
562 Ok(calldata.clone())
563}
564
565pub fn sha2_256(
567 calldata: &Bytes,
568 gas_remaining: &mut u64,
569 _fork: Fork,
570 crypto: &dyn Crypto,
571) -> Result<Bytes, VMError> {
572 let gas_cost = gas_cost::sha2_256(calldata.len())?;
573
574 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
575
576 let digest = crypto.sha256(calldata);
577 Ok(Bytes::copy_from_slice(&digest))
578}
579
580pub fn ripemd_160(
582 calldata: &Bytes,
583 gas_remaining: &mut u64,
584 _fork: Fork,
585 crypto: &dyn Crypto,
586) -> Result<Bytes, VMError> {
587 let gas_cost = gas_cost::ripemd_160(calldata.len())?;
588
589 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
590
591 let result = crypto.ripemd160(calldata);
592 Ok(Bytes::copy_from_slice(&result))
593}
594
595#[expect(clippy::indexing_slicing, reason = "bounds checked at start")]
597pub fn modexp(
598 calldata: &Bytes,
599 gas_remaining: &mut u64,
600 fork: Fork,
601 crypto: &dyn Crypto,
602) -> Result<Bytes, VMError> {
603 let calldata = fill_with_zeros(calldata, 96);
605
606 if fork < Fork::Osaka {
608 let base_size_bytes: [u8; 32] = calldata[0..32].try_into()?;
609 let modulus_size_bytes: [u8; 32] = calldata[64..96].try_into()?;
610 const ZERO_BYTES: [u8; 32] = [0u8; 32];
611
612 if base_size_bytes == ZERO_BYTES && modulus_size_bytes == ZERO_BYTES {
613 increase_precompile_consumed_gas(MODEXP_STATIC_COST, gas_remaining)?;
615 return Ok(Bytes::new());
616 }
617 }
618
619 let base_size = u256_from_big_endian_const::<32>(calldata[0..32].try_into()?);
622 let modulus_size = u256_from_big_endian_const::<32>(calldata[64..96].try_into()?);
623 let exponent_size = u256_from_big_endian_const::<32>(calldata[32..64].try_into()?);
624
625 if fork >= Fork::Osaka {
626 if base_size > U256::from(1024) {
627 return Err(PrecompileError::ModExpBaseTooLarge.into());
628 }
629 if exponent_size > U256::from(1024) {
630 return Err(PrecompileError::ModExpExpTooLarge.into());
631 }
632 if modulus_size > U256::from(1024) {
633 return Err(PrecompileError::ModExpModulusTooLarge.into());
634 }
635 }
636
637 let base_size = usize::try_from(base_size).map_err(|_| PrecompileError::ParsingInputError)?;
639 let exponent_size =
640 usize::try_from(exponent_size).map_err(|_| PrecompileError::ParsingInputError)?;
641 let modulus_size =
642 usize::try_from(modulus_size).map_err(|_| PrecompileError::ParsingInputError)?;
643
644 let base_limit = base_size.checked_add(96).ok_or(InternalError::Overflow)?;
645
646 let exponent_limit = exponent_size
647 .checked_add(base_limit)
648 .ok_or(InternalError::Overflow)?;
649
650 let modulus_limit = modulus_size
651 .checked_add(exponent_limit)
652 .ok_or(InternalError::Overflow)?;
653
654 let b = get_slice_or_default(&calldata, 96, base_limit, base_size);
655 let e = get_slice_or_default(&calldata, base_limit, exponent_limit, exponent_size);
656 let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, modulus_size);
657
658 use malachite::Natural;
660 use malachite::base::num::conversion::traits::*;
661 let exp_first_32_bytes = e.get(0..32.min(exponent_size)).unwrap_or_default();
662 let exp_first_32 =
663 Natural::from_power_of_2_digits_desc(8u64, exp_first_32_bytes.iter().cloned())
664 .ok_or(InternalError::TypeConversion)?;
665
666 let gas_cost = gas_cost::modexp(&exp_first_32, base_size, exponent_size, modulus_size, fork)?;
667
668 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
669
670 if base_size == 0 && modulus_size == 0 {
671 return Ok(Bytes::new());
672 }
673
674 let result = crypto
675 .modexp(&b, &e, &m)
676 .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
677
678 let res_bytes = increase_left_pad(&Bytes::from(result), modulus_size);
679
680 Ok(res_bytes.slice(..modulus_size))
681}
682
683#[expect(clippy::indexing_slicing, reason = "bounds checked")]
688fn get_slice_or_default<'c>(
689 calldata: &'c Bytes,
690 lower_limit: usize,
691 upper_limit: usize,
692 size_to_expand: usize,
693) -> Cow<'c, [u8]> {
694 let upper_limit = calldata.len().min(upper_limit);
695 if let Some(data) = calldata.get(lower_limit..upper_limit)
696 && !data.is_empty()
697 {
698 if data.len() == size_to_expand {
699 return data.into();
700 }
701 let mut extended = vec![0u8; size_to_expand];
702 let copy_size = size_to_expand.min(data.len());
703 extended[..copy_size].copy_from_slice(&data[..copy_size]);
704 return extended.into();
705 }
706 Vec::new().into()
707}
708
709#[inline(always)]
711pub fn increase_left_pad(result: &Bytes, m_size: usize) -> Bytes {
712 #[expect(
713 clippy::arithmetic_side_effects,
714 clippy::indexing_slicing,
715 reason = "overflow checked with the if condition, bounds checked"
716 )]
717 if result.len() < m_size {
718 let mut padded_result = vec![0u8; m_size];
719 let size_diff = m_size - result.len();
720 padded_result[size_diff..].copy_from_slice(result);
721
722 padded_result.into()
723 } else {
724 result.clone()
726 }
727}
728
729pub fn ecadd(
731 calldata: &Bytes,
732 gas_remaining: &mut u64,
733 _fork: Fork,
734 crypto: &dyn Crypto,
735) -> Result<Bytes, VMError> {
736 let calldata = fill_with_zeros(calldata, 128);
738
739 increase_precompile_consumed_gas(ECADD_COST, gas_remaining)?;
740
741 let (Some(first_point), Some(second_point)) =
742 (parse_bn254_g1(&calldata, 0), parse_bn254_g1(&calldata, 64))
743 else {
744 return Err(InternalError::Slicing.into());
745 };
746 validate_bn254_g1_coords(&first_point)?;
747 validate_bn254_g1_coords(&second_point)?;
748
749 #[expect(clippy::indexing_slicing, reason = "calldata padded to 128 bytes")]
750 let result = crypto
751 .bn254_g1_add(&calldata[..64], &calldata[64..128])
752 .map_err(crypto_error_to_precompile)?;
753
754 Ok(Bytes::copy_from_slice(&result))
755}
756
757pub fn ecmul(
759 calldata: &Bytes,
760 gas_remaining: &mut u64,
761 _fork: Fork,
762 crypto: &dyn Crypto,
763) -> Result<Bytes, VMError> {
764 let calldata = fill_with_zeros(calldata, 96);
766 increase_precompile_consumed_gas(ECMUL_COST, gas_remaining)?;
767
768 let (Some(g1), Some(_scalar)) = (
769 parse_bn254_g1(&calldata, 0),
770 parse_bn254_scalar(&calldata, 64),
771 ) else {
772 return Err(InternalError::Slicing.into());
773 };
774 validate_bn254_g1_coords(&g1)?;
775
776 #[expect(clippy::indexing_slicing, reason = "calldata padded to 96 bytes")]
777 let result = crypto
778 .bn254_g1_mul(&calldata[..64], &calldata[64..96])
779 .map_err(crypto_error_to_precompile)?;
780
781 Ok(Bytes::copy_from_slice(&result))
782}
783
784const ALT_BN128_PRIME: U256 = U256([
785 0x3c208c16d87cfd47,
786 0x97816a916871ca8d,
787 0xb85045b68181585d,
788 0x30644e72e131a029,
789]);
790
791pub struct G1(U256, U256);
792impl G1 {
793 pub fn is_zero(&self) -> bool {
795 self.0.is_zero() && self.1.is_zero()
796 }
797}
798pub struct G2(U256, U256, U256, U256);
799impl G2 {
800 pub fn is_zero(&self) -> bool {
802 self.0.is_zero() && self.1.is_zero() && self.2.is_zero() && self.3.is_zero()
803 }
804}
805
806#[inline]
808fn parse_bn254_scalar(buf: &[u8], offset: usize) -> Option<U256> {
809 buf.get(offset..offset.checked_add(32)?)
810 .map(u256_from_big_endian)
811}
812
813#[inline]
815fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option<G1> {
816 let chunk = buf.get(offset..offset.checked_add(64)?)?;
817 let (x_bytes, y_bytes) = chunk.split_at_checked(32)?;
818 Some(G1(
819 u256_from_big_endian(x_bytes),
820 u256_from_big_endian(y_bytes),
821 ))
822}
823
824fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option<G2> {
826 let chunk = buf.get(offset..offset.checked_add(128)?)?;
827 let (g2_xy, rest) = chunk.split_at_checked(32)?;
828 let (g2_xx, rest) = rest.split_at_checked(32)?;
829 let (g2_yy, g2_yx) = rest.split_at_checked(32)?;
830 Some(G2(
831 u256_from_big_endian(g2_xx),
832 u256_from_big_endian(g2_xy),
833 u256_from_big_endian(g2_yx),
834 u256_from_big_endian(g2_yy),
835 ))
836}
837
838#[inline]
839fn validate_bn254_g1_coords(g1: &G1) -> Result<(), VMError> {
840 if g1.0 >= ALT_BN128_PRIME || g1.1 >= ALT_BN128_PRIME {
842 return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
843 }
844 Ok(())
845}
846
847#[inline]
848fn validate_bn254_g2_coords(g2: &G2) -> Result<(), VMError> {
849 if g2.0 >= ALT_BN128_PRIME
851 || g2.1 >= ALT_BN128_PRIME
852 || g2.2 >= ALT_BN128_PRIME
853 || g2.3 >= ALT_BN128_PRIME
854 {
855 return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
856 }
857 Ok(())
858}
859
860pub fn ecpairing(
862 calldata: &Bytes,
863 gas_remaining: &mut u64,
864 _fork: Fork,
865 crypto: &dyn Crypto,
866) -> Result<Bytes, VMError> {
867 if !calldata.len().is_multiple_of(192) {
869 return Err(PrecompileError::ParsingInputError.into());
870 }
871
872 let inputs_amount = calldata.len() / 192;
873 let gas_cost = gas_cost::ecpairing(inputs_amount)?;
874 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
875
876 let mut pairs: Vec<(&[u8], &[u8])> = Vec::new();
877 for input in calldata.chunks_exact(192) {
878 let (Some(g1), Some(g2)) = (parse_bn254_g1(input, 0), parse_bn254_g2(input, 64)) else {
879 return Err(InternalError::Slicing.into());
880 };
881 validate_bn254_g1_coords(&g1)?;
882 validate_bn254_g2_coords(&g2)?;
883 #[expect(clippy::indexing_slicing, reason = "chunks_exact guarantees 192 bytes")]
884 pairs.push((&input[..64], &input[64..192]));
885 }
886
887 let pairing_check = if pairs.is_empty() {
888 true
889 } else {
890 crypto
891 .bn254_pairing_check(&pairs)
892 .map_err(crypto_error_to_precompile)?
893 };
894
895 let mut result = [0; 32];
896 result[31] = u8::from(pairing_check);
897 Ok(Bytes::from_owner(result))
898}
899
900pub fn blake2f(
902 calldata: &Bytes,
903 gas_remaining: &mut u64,
904 _fork: Fork,
905 crypto: &dyn Crypto,
906) -> Result<Bytes, VMError> {
907 if calldata.len() != 213 {
908 return Err(PrecompileError::ParsingInputError.into());
909 }
910
911 let mut calldata = calldata.slice(0..213);
912
913 let rounds = calldata.get_u32();
914
915 let gas_cost = u64::from(rounds) * BLAKE2F_ROUND_COST;
916 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
917
918 let mut h = [0; 8];
919
920 h.copy_from_slice(&std::array::from_fn::<u64, 8, _>(|_| calldata.get_u64_le()));
921
922 let mut m = [0; 16];
923
924 m.copy_from_slice(&std::array::from_fn::<u64, 16, _>(|_| {
925 calldata.get_u64_le()
926 }));
927
928 let mut t = [0; 2];
929 t.copy_from_slice(&std::array::from_fn::<u64, 2, _>(|_| calldata.get_u64_le()));
930
931 let f = calldata.get_u8();
932 if f != 0 && f != 1 {
933 return Err(PrecompileError::ParsingInputError.into());
934 }
935 let f = f == 1;
936
937 crypto.blake2_compress(rounds, &mut h, m, t, f);
938
939 Ok(Bytes::from_iter(
940 h.into_iter().flat_map(|value| value.to_le_bytes()),
941 ))
942}
943
944fn kzg_commitment_to_versioned_hash(commitment_bytes: &[u8; 48], crypto: &dyn Crypto) -> H256 {
946 let mut versioned_hash: [u8; 32] = crypto.sha256(commitment_bytes);
947 versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
948 versioned_hash.into()
949}
950
951const POINT_EVALUATION_OUTPUT_BYTES: [u8; 64] = [
952 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
954 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
955 0x73, 0xED, 0xA7, 0x53, 0x29, 0x9D, 0x7D, 0x48, 0x33, 0x39, 0xD8, 0x08, 0x09, 0xA1, 0xD8, 0x05,
957 0x53, 0xBD, 0xA4, 0x02, 0xFF, 0xFE, 0x5B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01,
958];
959
960fn point_evaluation(
962 calldata: &Bytes,
963 gas_remaining: &mut u64,
964 _fork: Fork,
965 crypto: &dyn Crypto,
966) -> Result<Bytes, VMError> {
967 if calldata.len() != 192 {
968 return Err(PrecompileError::ParsingInputError.into());
969 }
970
971 let gas_cost = POINT_EVALUATION_COST;
973 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
974
975 let versioned_hash: [u8; 32] = calldata
977 .get(..32)
978 .ok_or(InternalError::Slicing)?
979 .try_into()
980 .map_err(|_| InternalError::TypeConversion)?;
981
982 let z: [u8; 32] = calldata
983 .get(32..64)
984 .ok_or(InternalError::Slicing)?
985 .try_into()
986 .map_err(|_| InternalError::TypeConversion)?;
987
988 let y: [u8; 32] = calldata
989 .get(64..96)
990 .ok_or(InternalError::Slicing)?
991 .try_into()
992 .map_err(|_| InternalError::TypeConversion)?;
993
994 let commitment: [u8; 48] = calldata
995 .get(96..144)
996 .ok_or(InternalError::Slicing)?
997 .try_into()
998 .map_err(|_| InternalError::TypeConversion)?;
999
1000 let proof: [u8; 48] = calldata
1001 .get(144..192)
1002 .ok_or(InternalError::Slicing)?
1003 .try_into()
1004 .map_err(|_| InternalError::TypeConversion)?;
1005
1006 if kzg_commitment_to_versioned_hash(&commitment, crypto) != H256::from(versioned_hash) {
1010 return Err(PrecompileError::ParsingInputError.into());
1011 }
1012
1013 crypto
1015 .verify_kzg_proof(&z, &y, &commitment, &proof)
1016 .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
1017
1018 let output = POINT_EVALUATION_OUTPUT_BYTES.to_vec();
1021
1022 Ok(Bytes::from(output))
1023}
1024
1025pub fn p_256_verify(
1030 calldata: &Bytes,
1031 gas_remaining: &mut u64,
1032 _fork: Fork,
1033 crypto: &dyn Crypto,
1034) -> Result<Bytes, VMError> {
1035 increase_precompile_consumed_gas(P256_VERIFY_COST, gas_remaining)
1036 .map_err(|_| PrecompileError::NotEnoughGas)?;
1037
1038 if calldata.len() != 160 {
1040 return Ok(Bytes::new());
1041 }
1042
1043 #[expect(
1045 clippy::indexing_slicing,
1046 reason = "length of the calldata is checked before slicing"
1047 )]
1048 let msg: &[u8; 32] = calldata[0..32].try_into()?;
1049 #[expect(clippy::indexing_slicing, reason = "length checked")]
1050 let r: &[u8; 32] = calldata[32..64].try_into()?;
1051 #[expect(clippy::indexing_slicing, reason = "length checked")]
1052 let s: &[u8; 32] = calldata[64..96].try_into()?;
1053 #[expect(clippy::indexing_slicing, reason = "length checked")]
1054 let pk_x: &[u8; 32] = calldata[96..128].try_into()?;
1055 #[expect(clippy::indexing_slicing, reason = "length checked")]
1056 let pk_y: &[u8; 32] = calldata[128..160].try_into()?;
1057
1058 let mut sig_bytes = [0u8; 64];
1060 sig_bytes[..32].copy_from_slice(r);
1061 sig_bytes[32..].copy_from_slice(s);
1062
1063 let mut pk_bytes = [0u8; 64];
1064 pk_bytes[..32].copy_from_slice(pk_x);
1065 pk_bytes[32..].copy_from_slice(pk_y);
1066
1067 let success = crypto.secp256r1_verify(msg, &sig_bytes, &pk_bytes);
1068
1069 if success {
1072 const RESULT: [u8; 32] = [
1073 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,
1074 0, 0, 1,
1075 ];
1076 Ok(Bytes::from_static(&RESULT))
1077 } else {
1078 Ok(Bytes::new())
1079 }
1080}
1081
1082fn parse_bls12_padded_fp(padded: &[u8; 64]) -> Result<[u8; 48], VMError> {
1085 if padded[..16] != [0u8; 16] {
1086 return Err(PrecompileError::ParsingInputError.into());
1087 }
1088 let fp: [u8; 48] = padded[16..64]
1089 .try_into()
1090 .map_err(|_| InternalError::TypeConversion)?;
1091 Ok(fp)
1092}
1093
1094pub fn bls12_g1add(
1095 calldata: &Bytes,
1096 gas_remaining: &mut u64,
1097 _fork: Fork,
1098 crypto: &dyn Crypto,
1099) -> Result<Bytes, VMError> {
1100 let (x_data, calldata) = calldata
1101 .split_first_chunk::<128>()
1102 .ok_or(PrecompileError::ParsingInputError)?;
1103 let (y_data, calldata) = calldata
1104 .split_first_chunk::<128>()
1105 .ok_or(PrecompileError::ParsingInputError)?;
1106 if !calldata.is_empty() {
1107 return Err(PrecompileError::ParsingInputError.into());
1108 }
1109
1110 increase_precompile_consumed_gas(BLS12_381_G1ADD_COST, gas_remaining)
1112 .map_err(|_| PrecompileError::NotEnoughGas)?;
1113
1114 let ax = parse_bls12_padded_fp(
1116 x_data[..64]
1117 .try_into()
1118 .map_err(|_| InternalError::TypeConversion)?,
1119 )?;
1120 let ay = parse_bls12_padded_fp(
1121 x_data[64..128]
1122 .try_into()
1123 .map_err(|_| InternalError::TypeConversion)?,
1124 )?;
1125 let bx = parse_bls12_padded_fp(
1126 y_data[..64]
1127 .try_into()
1128 .map_err(|_| InternalError::TypeConversion)?,
1129 )?;
1130 let by = parse_bls12_padded_fp(
1131 y_data[64..128]
1132 .try_into()
1133 .map_err(|_| InternalError::TypeConversion)?,
1134 )?;
1135
1136 let result = crypto
1137 .bls12_381_g1_add((ax, ay), (bx, by))
1138 .map_err(bls12_g1_crypto_error)?;
1139
1140 let mut output = [0u8; 128];
1142 output[16..64].copy_from_slice(&result[..48]);
1143 output[80..128].copy_from_slice(&result[48..96]);
1144 Ok(Bytes::copy_from_slice(&output))
1145}
1146
1147pub fn bls12_g1msm(
1148 calldata: &Bytes,
1149 gas_remaining: &mut u64,
1150 _fork: Fork,
1151 crypto: &dyn Crypto,
1152) -> Result<Bytes, VMError> {
1153 if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G1_MSM_PAIR_LENGTH) {
1154 return Err(PrecompileError::ParsingInputError.into());
1155 }
1156
1157 let k = calldata.len() / BLS12_381_G1_MSM_PAIR_LENGTH;
1158 let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G1_K_DISCOUNT, G1_MUL_COST)?;
1159 increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1160
1161 #[allow(clippy::type_complexity)]
1162 let mut pairs: Vec<(([u8; 48], [u8; 48]), [u8; 32])> = Vec::with_capacity(k);
1163
1164 #[expect(
1165 clippy::arithmetic_side_effects,
1166 clippy::indexing_slicing,
1167 reason = "bounds checked"
1168 )]
1169 for i in 0..k {
1170 let point_offset = i * BLS12_381_G1_MSM_PAIR_LENGTH;
1171 let scalar_offset = point_offset + 128;
1172 let pair_end = scalar_offset + 32;
1173
1174 let point_bytes = &calldata[point_offset..scalar_offset];
1175 let scalar_bytes = &calldata[scalar_offset..pair_end];
1176
1177 let px = parse_bls12_padded_fp(
1178 point_bytes[..64]
1179 .try_into()
1180 .map_err(|_| InternalError::TypeConversion)?,
1181 )?;
1182 let py = parse_bls12_padded_fp(
1183 point_bytes[64..128]
1184 .try_into()
1185 .map_err(|_| InternalError::TypeConversion)?,
1186 )?;
1187 let scalar: [u8; 32] = scalar_bytes
1188 .try_into()
1189 .map_err(|_| InternalError::TypeConversion)?;
1190
1191 pairs.push(((px, py), scalar));
1192 }
1193
1194 let result = crypto
1195 .bls12_381_g1_msm(&pairs)
1196 .map_err(bls12_msm_pairing_crypto_error)?;
1197
1198 let mut output = [0u8; 128];
1200 output[16..64].copy_from_slice(&result[..48]);
1201 output[80..128].copy_from_slice(&result[48..96]);
1202 Ok(Bytes::copy_from_slice(&output))
1203}
1204
1205pub fn bls12_g2add(
1206 calldata: &Bytes,
1207 gas_remaining: &mut u64,
1208 _fork: Fork,
1209 crypto: &dyn Crypto,
1210) -> Result<Bytes, VMError> {
1211 let (x_data, calldata) = calldata
1212 .split_first_chunk::<256>()
1213 .ok_or(PrecompileError::ParsingInputError)?;
1214 let (y_data, calldata) = calldata
1215 .split_first_chunk::<256>()
1216 .ok_or(PrecompileError::ParsingInputError)?;
1217 if !calldata.is_empty() {
1218 return Err(PrecompileError::ParsingInputError.into());
1219 }
1220
1221 increase_precompile_consumed_gas(BLS12_381_G2ADD_COST, gas_remaining)
1223 .map_err(|_| PrecompileError::NotEnoughGas)?;
1224
1225 let ax0 = parse_bls12_padded_fp(
1228 x_data[0..64]
1229 .try_into()
1230 .map_err(|_| InternalError::TypeConversion)?,
1231 )?;
1232 let ax1 = parse_bls12_padded_fp(
1233 x_data[64..128]
1234 .try_into()
1235 .map_err(|_| InternalError::TypeConversion)?,
1236 )?;
1237 let ay0 = parse_bls12_padded_fp(
1238 x_data[128..192]
1239 .try_into()
1240 .map_err(|_| InternalError::TypeConversion)?,
1241 )?;
1242 let ay1 = parse_bls12_padded_fp(
1243 x_data[192..256]
1244 .try_into()
1245 .map_err(|_| InternalError::TypeConversion)?,
1246 )?;
1247
1248 let bx0 = parse_bls12_padded_fp(
1249 y_data[0..64]
1250 .try_into()
1251 .map_err(|_| InternalError::TypeConversion)?,
1252 )?;
1253 let bx1 = parse_bls12_padded_fp(
1254 y_data[64..128]
1255 .try_into()
1256 .map_err(|_| InternalError::TypeConversion)?,
1257 )?;
1258 let by0 = parse_bls12_padded_fp(
1259 y_data[128..192]
1260 .try_into()
1261 .map_err(|_| InternalError::TypeConversion)?,
1262 )?;
1263 let by1 = parse_bls12_padded_fp(
1264 y_data[192..256]
1265 .try_into()
1266 .map_err(|_| InternalError::TypeConversion)?,
1267 )?;
1268
1269 let result = crypto
1270 .bls12_381_g2_add((ax0, ax1, ay0, ay1), (bx0, bx1, by0, by1))
1271 .map_err(bls12_g2_crypto_error)?;
1272
1273 let mut output = [0u8; 256];
1277 output[16..64].copy_from_slice(&result[0..48]);
1278 output[80..128].copy_from_slice(&result[48..96]);
1279 output[144..192].copy_from_slice(&result[96..144]);
1280 output[208..256].copy_from_slice(&result[144..192]);
1281 Ok(Bytes::copy_from_slice(&output))
1282}
1283
1284pub fn bls12_g2msm(
1285 calldata: &Bytes,
1286 gas_remaining: &mut u64,
1287 _fork: Fork,
1288 crypto: &dyn Crypto,
1289) -> Result<Bytes, VMError> {
1290 if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G2_MSM_PAIR_LENGTH) {
1291 return Err(PrecompileError::ParsingInputError.into());
1292 }
1293
1294 let k = calldata.len() / BLS12_381_G2_MSM_PAIR_LENGTH;
1295 let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G2_K_DISCOUNT, G2_MUL_COST)?;
1296 increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1297
1298 #[allow(clippy::type_complexity)]
1299 let mut pairs: Vec<(([u8; 48], [u8; 48], [u8; 48], [u8; 48]), [u8; 32])> =
1300 Vec::with_capacity(k);
1301
1302 #[expect(
1303 clippy::indexing_slicing,
1304 clippy::arithmetic_side_effects,
1305 reason = "bounds checked"
1306 )]
1307 for i in 0..k {
1308 let point_offset = i * BLS12_381_G2_MSM_PAIR_LENGTH;
1309 let scalar_offset = point_offset + 256;
1310 let pair_end = scalar_offset + 32;
1311
1312 let point_bytes = &calldata[point_offset..scalar_offset];
1313 let scalar_bytes = &calldata[scalar_offset..pair_end];
1314
1315 let x0 = parse_bls12_padded_fp(
1316 point_bytes[0..64]
1317 .try_into()
1318 .map_err(|_| InternalError::TypeConversion)?,
1319 )?;
1320 let x1 = parse_bls12_padded_fp(
1321 point_bytes[64..128]
1322 .try_into()
1323 .map_err(|_| InternalError::TypeConversion)?,
1324 )?;
1325 let y0 = parse_bls12_padded_fp(
1326 point_bytes[128..192]
1327 .try_into()
1328 .map_err(|_| InternalError::TypeConversion)?,
1329 )?;
1330 let y1 = parse_bls12_padded_fp(
1331 point_bytes[192..256]
1332 .try_into()
1333 .map_err(|_| InternalError::TypeConversion)?,
1334 )?;
1335 let scalar: [u8; 32] = scalar_bytes
1336 .try_into()
1337 .map_err(|_| InternalError::TypeConversion)?;
1338
1339 pairs.push(((x0, x1, y0, y1), scalar));
1340 }
1341
1342 let result = crypto
1343 .bls12_381_g2_msm(&pairs)
1344 .map_err(bls12_msm_pairing_crypto_error)?;
1345
1346 let mut output = [0u8; 256];
1348 output[16..64].copy_from_slice(&result[0..48]);
1349 output[80..128].copy_from_slice(&result[48..96]);
1350 output[144..192].copy_from_slice(&result[96..144]);
1351 output[208..256].copy_from_slice(&result[144..192]);
1352 Ok(Bytes::copy_from_slice(&output))
1353}
1354
1355pub fn bls12_pairing_check(
1356 calldata: &Bytes,
1357 gas_remaining: &mut u64,
1358 _fork: Fork,
1359 crypto: &dyn Crypto,
1360) -> Result<Bytes, VMError> {
1361 if calldata.is_empty()
1362 || !calldata
1363 .len()
1364 .is_multiple_of(BLS12_381_PAIRING_CHECK_PAIR_LENGTH)
1365 {
1366 return Err(PrecompileError::ParsingInputError.into());
1367 }
1368
1369 let k = calldata.len() / BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1371 let gas_cost = gas_cost::bls12_pairing_check(k)?;
1372 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1373
1374 #[allow(clippy::type_complexity)]
1375 let mut pairs: Vec<(
1376 ([u8; 48], [u8; 48]),
1377 ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
1378 )> = Vec::with_capacity(k);
1379
1380 #[expect(
1381 clippy::indexing_slicing,
1382 clippy::arithmetic_side_effects,
1383 reason = "bounds checked"
1384 )]
1385 for i in 0..k {
1386 let g1_offset = i * BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1387 let g2_offset = g1_offset + 128;
1388 let pair_end = g2_offset + 256;
1389
1390 let g1_bytes = &calldata[g1_offset..g2_offset];
1391 let g2_bytes = &calldata[g2_offset..pair_end];
1392
1393 let g1x = parse_bls12_padded_fp(
1394 g1_bytes[0..64]
1395 .try_into()
1396 .map_err(|_| InternalError::TypeConversion)?,
1397 )?;
1398 let g1y = parse_bls12_padded_fp(
1399 g1_bytes[64..128]
1400 .try_into()
1401 .map_err(|_| InternalError::TypeConversion)?,
1402 )?;
1403
1404 let g2x0 = parse_bls12_padded_fp(
1405 g2_bytes[0..64]
1406 .try_into()
1407 .map_err(|_| InternalError::TypeConversion)?,
1408 )?;
1409 let g2x1 = parse_bls12_padded_fp(
1410 g2_bytes[64..128]
1411 .try_into()
1412 .map_err(|_| InternalError::TypeConversion)?,
1413 )?;
1414 let g2y0 = parse_bls12_padded_fp(
1415 g2_bytes[128..192]
1416 .try_into()
1417 .map_err(|_| InternalError::TypeConversion)?,
1418 )?;
1419 let g2y1 = parse_bls12_padded_fp(
1420 g2_bytes[192..256]
1421 .try_into()
1422 .map_err(|_| InternalError::TypeConversion)?,
1423 )?;
1424
1425 pairs.push(((g1x, g1y), (g2x0, g2x1, g2y0, g2y1)));
1426 }
1427
1428 let result = crypto
1429 .bls12_381_pairing_check(&pairs)
1430 .map_err(bls12_msm_pairing_crypto_error)?;
1431
1432 if result {
1433 let mut out = vec![0_u8; 31];
1434 out.push(1);
1435 Ok(Bytes::from(out))
1436 } else {
1437 Ok(Bytes::copy_from_slice(&[0_u8; 32]))
1438 }
1439}
1440
1441pub fn bls12_map_fp_to_g1(
1442 calldata: &Bytes,
1443 gas_remaining: &mut u64,
1444 _fork: Fork,
1445 crypto: &dyn Crypto,
1446) -> Result<Bytes, VMError> {
1447 if calldata.len() != BLS12_381_FP_VALID_INPUT_LENGTH {
1448 return Err(PrecompileError::ParsingInputError.into());
1449 }
1450
1451 increase_precompile_consumed_gas(BLS12_381_MAP_FP_TO_G1_COST, gas_remaining)?;
1453
1454 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1456 let fp = parse_bls12_padded_fp(
1457 calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1458 .try_into()
1459 .map_err(|_| InternalError::TypeConversion)?,
1460 )?;
1461
1462 let result = crypto
1463 .bls12_381_fp_to_g1(&fp)
1464 .map_err(crypto_error_to_precompile)?;
1465
1466 let mut output = [0u8; 128];
1468 output[16..64].copy_from_slice(&result[0..48]);
1469 output[80..128].copy_from_slice(&result[48..96]);
1470 Ok(Bytes::copy_from_slice(&output))
1471}
1472
1473pub fn bls12_map_fp2_to_g2(
1474 calldata: &Bytes,
1475 gas_remaining: &mut u64,
1476 _fork: Fork,
1477 crypto: &dyn Crypto,
1478) -> Result<Bytes, VMError> {
1479 if calldata.len() != BLS12_381_FP2_VALID_INPUT_LENGTH {
1480 return Err(PrecompileError::ParsingInputError.into());
1481 }
1482
1483 increase_precompile_consumed_gas(BLS12_381_MAP_FP2_TO_G2_COST, gas_remaining)?;
1485
1486 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1488 let c0 = parse_bls12_padded_fp(
1489 calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1490 .try_into()
1491 .map_err(|_| InternalError::TypeConversion)?,
1492 )?;
1493 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1494 let c1 = parse_bls12_padded_fp(
1495 calldata[PADDED_FIELD_ELEMENT_SIZE_IN_BYTES..BLS12_381_FP2_VALID_INPUT_LENGTH]
1496 .try_into()
1497 .map_err(|_| InternalError::TypeConversion)?,
1498 )?;
1499
1500 if c0 == [0u8; 48] && c1 == [0u8; 48] {
1501 return Ok(Bytes::copy_from_slice(&FP2_ZERO_MAPPED_TO_G2));
1502 }
1503
1504 let result = crypto
1505 .bls12_381_fp2_to_g2((c0, c1))
1506 .map_err(crypto_error_to_precompile)?;
1507
1508 let mut output = [0u8; 256];
1512 output[16..64].copy_from_slice(&result[0..48]);
1513 output[80..128].copy_from_slice(&result[48..96]);
1514 output[144..192].copy_from_slice(&result[96..144]);
1515 output[208..256].copy_from_slice(&result[144..192]);
1516 Ok(Bytes::copy_from_slice(&output))
1517}
1518
1519#[cfg(test)]
1520mod tests {
1521 use super::*;
1522 use ethrex_common::types::Fork;
1523
1524 #[test]
1526 fn execute_precompile_gated_by_lstar_and_l1() {
1527 let execute_addr = EXECUTE.address;
1528 assert!(!is_precompile(&execute_addr, Fork::Amsterdam, VMType::L1));
1530 assert!(!is_precompile(
1532 &execute_addr,
1533 Fork::LStar,
1534 VMType::L2(Default::default())
1535 ));
1536 assert!(is_precompile(&execute_addr, Fork::LStar, VMType::L1));
1538 }
1539}