1use bytes::{Buf, Bytes};
2use ethrex_common::H160;
3use ethrex_common::utils::u256_from_big_endian_const;
4use ethrex_common::{
5 Address, H256, U256, types::Fork, types::Fork::*, utils::u256_from_big_endian,
6};
7use ethrex_crypto::{Crypto, CryptoError};
8use rustc_hash::FxHashMap;
9use std::borrow::Cow;
10use std::collections::{BTreeMap, BTreeSet, hash_map::Entry};
11use std::sync::RwLock;
12
13use crate::gas_cost::{MODEXP_STATIC_COST, P256_VERIFY_COST};
14use crate::vm::VMType;
15use crate::{
16 constants::VERSIONED_HASH_VERSION_KZG,
17 errors::{InternalError, PrecompileError, VMError},
18 gas_cost::{
19 self, BLAKE2F_ROUND_COST, BLS12_381_G1_K_DISCOUNT, BLS12_381_G1ADD_COST,
20 BLS12_381_G2_K_DISCOUNT, BLS12_381_G2ADD_COST, BLS12_381_MAP_FP_TO_G1_COST,
21 BLS12_381_MAP_FP2_TO_G2_COST, ECADD_COST, ECMUL_COST, G1_MUL_COST, G2_MUL_COST,
22 POINT_EVALUATION_COST,
23 },
24};
25
26pub const BLAKE2F_ELEMENT_SIZE: usize = 8;
27
28pub const SIZE_PRECOMPILES_PRE_CANCUN: u64 = 9;
29pub const SIZE_PRECOMPILES_CANCUN: u64 = 10;
30pub const SIZE_PRECOMPILES_PRAGUE: u64 = 17;
31
32pub const BLS12_381_G1_MSM_PAIR_LENGTH: usize = 160;
33pub const BLS12_381_G2_MSM_PAIR_LENGTH: usize = 288;
34pub const BLS12_381_PAIRING_CHECK_PAIR_LENGTH: usize = 384;
35
36const BLS12_381_FP2_VALID_INPUT_LENGTH: usize = 128;
37const BLS12_381_FP_VALID_INPUT_LENGTH: usize = 64;
38
39pub const FIELD_ELEMENT_WITHOUT_PADDING_LENGTH: usize = 48;
40pub const PADDED_FIELD_ELEMENT_SIZE_IN_BYTES: usize = 64;
41
42pub const G1_POINT_AT_INFINITY: [u8; 128] = [0_u8; 128];
43pub const G2_POINT_AT_INFINITY: [u8; 256] = [0_u8; 256];
44
45const FP2_ZERO_MAPPED_TO_G2: [u8; 256] = [
49 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 131, 32, 137, 110, 201, 238, 249, 213, 230,
50 25, 132, 141, 194, 156, 226, 102, 244, 19, 208, 45, 211, 29, 155, 157, 68, 236, 12, 121, 205,
51 97, 241, 139, 7, 93, 219, 166, 215, 189, 32, 183, 255, 39, 164, 179, 36, 191, 206, 0, 0, 0, 0,
52 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 103, 209, 33, 24, 181, 163, 91, 176, 45, 46, 134, 179,
53 235, 250, 126, 35, 65, 13, 185, 61, 227, 159, 176, 109, 112, 37, 250, 149, 233, 111, 250, 66,
54 138, 122, 39, 195, 174, 77, 212, 180, 11, 210, 81, 172, 101, 136, 146, 0, 0, 0, 0, 0, 0, 0, 0,
55 0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 224, 54, 68, 209, 162, 195, 33, 37, 107, 50, 70, 186, 210, 184,
56 149, 202, 209, 56, 144, 203, 230, 248, 93, 245, 81, 6, 160, 211, 52, 96, 79, 177, 67, 199, 160,
57 66, 216, 120, 0, 98, 113, 134, 91, 195, 89, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
58 4, 198, 151, 119, 164, 63, 11, 218, 7, 103, 157, 88, 5, 230, 63, 24, 207, 78, 14, 124, 97, 18,
59 172, 127, 112, 38, 109, 25, 155, 79, 118, 174, 39, 198, 38, 154, 60, 238, 189, 174, 48, 128,
60 110, 154, 118, 170, 223, 92,
61];
62
63pub struct Precompile {
64 pub address: H160,
65 pub name: &'static str,
66 pub active_since_fork: Fork,
67}
68
69pub const ECRECOVER: Precompile = Precompile {
70 address: H160([
71 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
72 0x00, 0x00, 0x00, 0x00, 0x01,
73 ]),
74 name: "ECREC",
75 active_since_fork: Paris,
76};
77
78pub const SHA2_256: Precompile = Precompile {
79 address: H160([
80 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
81 0x00, 0x00, 0x00, 0x00, 0x02,
82 ]),
83 name: "SHA256",
84 active_since_fork: Paris,
85};
86
87pub const RIPEMD_160: Precompile = Precompile {
88 address: H160([
89 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
90 0x00, 0x00, 0x00, 0x00, 0x03,
91 ]),
92 name: "RIPEMD160",
93 active_since_fork: Paris,
94};
95
96pub const IDENTITY: Precompile = Precompile {
97 address: H160([
98 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
99 0x00, 0x00, 0x00, 0x00, 0x04,
100 ]),
101 name: "ID",
102 active_since_fork: Paris,
103};
104
105pub const MODEXP: Precompile = Precompile {
106 address: H160([
107 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
108 0x00, 0x00, 0x00, 0x00, 0x05,
109 ]),
110 name: "MODEXP",
111 active_since_fork: Paris,
112};
113
114pub const ECADD: Precompile = Precompile {
115 address: H160([
116 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
117 0x00, 0x00, 0x00, 0x00, 0x06,
118 ]),
119 name: "BN254_ADD",
120 active_since_fork: Paris,
121};
122
123pub const ECMUL: Precompile = Precompile {
124 address: H160([
125 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
126 0x00, 0x00, 0x00, 0x00, 0x07,
127 ]),
128 name: "BN254_MUL",
129 active_since_fork: Paris,
130};
131
132pub const ECPAIRING: Precompile = Precompile {
133 address: H160([
134 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
135 0x00, 0x00, 0x00, 0x00, 0x08,
136 ]),
137 name: "BN254_PAIRING",
138 active_since_fork: Paris,
139};
140
141pub const BLAKE2F: Precompile = Precompile {
142 address: H160([
143 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
144 0x00, 0x00, 0x00, 0x00, 0x09,
145 ]),
146 name: "BLAKE2F",
147 active_since_fork: Paris,
148};
149
150pub const POINT_EVALUATION: Precompile = Precompile {
151 address: H160([
152 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
153 0x00, 0x00, 0x00, 0x00, 0x0a,
154 ]),
155 name: "KZG_POINT_EVALUATION",
156 active_since_fork: Cancun,
157};
158
159pub const BLS12_G1ADD: Precompile = Precompile {
160 address: H160([
161 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
162 0x00, 0x00, 0x00, 0x00, 0x0b,
163 ]),
164 name: "BLS12_G1ADD",
165 active_since_fork: Prague,
166};
167
168pub const BLS12_G1MSM: Precompile = Precompile {
169 address: H160([
170 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
171 0x00, 0x00, 0x00, 0x00, 0x0c,
172 ]),
173 name: "BLS12_G1MSM",
174 active_since_fork: Prague,
175};
176
177pub const BLS12_G2ADD: Precompile = Precompile {
178 address: H160([
179 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
180 0x00, 0x00, 0x00, 0x00, 0x0d,
181 ]),
182 name: "BLS12_G2ADD",
183 active_since_fork: Prague,
184};
185
186pub const BLS12_G2MSM: Precompile = Precompile {
187 address: H160([
188 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
189 0x00, 0x00, 0x00, 0x00, 0x0e,
190 ]),
191 name: "BLS12_G2MSM",
192 active_since_fork: Prague,
193};
194
195pub const BLS12_PAIRING_CHECK: Precompile = Precompile {
196 address: H160([
197 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
198 0x00, 0x00, 0x00, 0x00, 0x0f,
199 ]),
200 name: "BLS12_PAIRING_CHECK",
201 active_since_fork: Prague,
202};
203
204pub const BLS12_MAP_FP_TO_G1: Precompile = Precompile {
205 address: H160([
206 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
207 0x00, 0x00, 0x00, 0x00, 0x10,
208 ]),
209 name: "BLS12_MAP_FP_TO_G1",
210 active_since_fork: Prague,
211};
212
213pub const BLS12_MAP_FP2_TO_G2: Precompile = Precompile {
214 address: H160([
215 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
216 0x00, 0x00, 0x00, 0x00, 0x11,
217 ]),
218 name: "BLS12_MAP_FP2_TO_G2",
219 active_since_fork: Prague,
220};
221
222pub const P256VERIFY: Precompile = Precompile {
223 address: H160([
224 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
225 0x00, 0x00, 0x00, 0x01, 0x00,
226 ]),
227 name: "P256VERIFY",
228 active_since_fork: Osaka,
229};
230
231pub const EXECUTE: Precompile = Precompile {
241 address: H160([
242 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
243 0x00, 0x00, 0x00, 0x01, 0x01,
244 ]),
245 name: "EXECUTE",
246 active_since_fork: LStar,
247};
248
249pub const PRECOMPILES: [Precompile; 18] = [
250 ECRECOVER,
251 SHA2_256,
252 RIPEMD_160,
253 IDENTITY,
254 MODEXP,
255 ECADD,
256 ECMUL,
257 ECPAIRING,
258 BLAKE2F,
259 POINT_EVALUATION,
260 BLS12_G1ADD,
261 BLS12_G1MSM,
262 BLS12_G2ADD,
263 BLS12_G2MSM,
264 BLS12_PAIRING_CHECK,
265 BLS12_MAP_FP_TO_G1,
266 BLS12_MAP_FP2_TO_G2,
267 P256VERIFY,
268];
269
270pub fn precompiles_for_fork(fork: Fork) -> impl Iterator<Item = Precompile> {
271 PRECOMPILES
272 .into_iter()
273 .filter(move |precompile| precompile.active_since_fork <= fork)
274}
275
276pub fn is_precompile(address: &Address, fork: Fork, vm_type: VMType) -> bool {
277 if fork >= Fork::LStar && matches!(vm_type, VMType::L1) && *address == EXECUTE.address {
278 return true;
279 }
280 (matches!(vm_type, VMType::L2(_)) && *address == P256VERIFY.address)
281 || precompiles_for_fork(fork).any(|precompile| precompile.address == *address)
282}
283
284#[derive(Debug, Default, Clone, PartialEq, Eq)]
297pub struct PrecompileMoves {
298 suppressed: BTreeSet<Address>,
300 relocated: BTreeMap<Address, Address>,
302}
303
304impl PrecompileMoves {
305 pub fn from_parts(
312 moves: impl IntoIterator<Item = (Address, Address)>,
313 overridden: impl IntoIterator<Item = Address>,
314 ) -> Self {
315 let mut result = Self {
316 suppressed: overridden.into_iter().collect(),
317 relocated: BTreeMap::new(),
318 };
319 for (source, destination) in moves {
320 result.relocated.insert(destination, source);
321 }
322 result
323 }
324
325 pub fn is_empty(&self) -> bool {
326 self.suppressed.is_empty() && self.relocated.is_empty()
327 }
328
329 pub fn is_suppressed(&self, address: &Address) -> bool {
331 self.suppressed.contains(address)
332 }
333
334 pub fn source_for(&self, address: &Address) -> Option<Address> {
337 self.relocated.get(address).copied()
338 }
339}
340
341pub fn is_precompile_with_moves(
345 address: &Address,
346 fork: Fork,
347 vm_type: VMType,
348 moves: Option<&PrecompileMoves>,
349) -> bool {
350 let Some(moves) = moves else {
351 return is_precompile(address, fork, vm_type);
352 };
353 if let Some(source) = moves.source_for(address) {
358 return is_precompile(&source, fork, vm_type);
359 }
360 if moves.is_suppressed(address) {
361 return false;
362 }
363 is_precompile(address, fork, vm_type)
364}
365
366pub fn effective_precompile_address(address: Address, moves: Option<&PrecompileMoves>) -> Address {
369 moves
370 .and_then(|m| m.source_for(&address))
371 .unwrap_or(address)
372}
373
374pub const PRECOMPILE_CACHE_MAX_BYTES: usize = 128 * 1024 * 1024;
378
379#[derive(Default)]
380struct PrecompileCacheEntries {
381 map: FxHashMap<(Address, Bytes), (Bytes, u64)>,
382 used_bytes: usize,
384}
385
386#[derive(Default)]
393pub struct PrecompileCache {
394 cache: RwLock<PrecompileCacheEntries>,
395}
396
397impl PrecompileCache {
398 pub fn new() -> Self {
399 Self::default()
400 }
401
402 pub fn entry_size(calldata_len: usize, output_len: usize) -> usize {
406 calldata_len
407 .saturating_add(output_len)
408 .saturating_add(size_of::<((Address, Bytes), (Bytes, u64))>())
409 }
410
411 pub fn get(&self, address: &Address, calldata: &Bytes) -> Option<(Bytes, u64)> {
412 self.cache
416 .read()
417 .unwrap_or_else(|poisoned| poisoned.into_inner())
418 .map
419 .get(&(*address, calldata.clone()))
420 .cloned()
421 }
422
423 pub fn insert(&self, address: Address, calldata: Bytes, output: Bytes, gas_cost: u64) {
424 let entry_size = Self::entry_size(calldata.len(), output.len());
425 let mut guard = self
426 .cache
427 .write()
428 .unwrap_or_else(|poisoned| poisoned.into_inner());
429 let entries = &mut *guard;
430 let used_bytes = entries.used_bytes.saturating_add(entry_size);
431 if used_bytes > PRECOMPILE_CACHE_MAX_BYTES {
432 return;
433 }
434 if let Entry::Vacant(slot) = entries.map.entry((address, calldata)) {
437 slot.insert((output, gas_cost));
438 entries.used_bytes = used_bytes;
439 }
440 }
441}
442
443#[expect(clippy::as_conversions, clippy::indexing_slicing)]
444pub fn execute_precompile(
445 address: Address,
446 calldata: &Bytes,
447 gas_remaining: &mut u64,
448 fork: Fork,
449 cache: Option<&PrecompileCache>,
450 crypto: &dyn Crypto,
451 _stateless_validator: Option<&dyn crate::StatelessValidator>,
452) -> Result<Bytes, VMError> {
453 type PrecompileFn = fn(&Bytes, &mut u64, Fork, &dyn Crypto) -> Result<Bytes, VMError>;
454
455 const PRECOMPILES: [Option<PrecompileFn>; 512] = const {
456 let mut precompiles = [const { None }; 512];
457 precompiles[ECRECOVER.address.0[19] as usize] = Some(ecrecover as PrecompileFn);
458 precompiles[IDENTITY.address.0[19] as usize] = Some(identity as PrecompileFn);
459 precompiles[SHA2_256.address.0[19] as usize] = Some(sha2_256 as PrecompileFn);
460 precompiles[RIPEMD_160.address.0[19] as usize] = Some(ripemd_160 as PrecompileFn);
461 precompiles[MODEXP.address.0[19] as usize] = Some(modexp as PrecompileFn);
462 precompiles[ECADD.address.0[19] as usize] = Some(ecadd as PrecompileFn);
463 precompiles[ECMUL.address.0[19] as usize] = Some(ecmul as PrecompileFn);
464 precompiles[ECPAIRING.address.0[19] as usize] = Some(ecpairing as PrecompileFn);
465 precompiles[BLAKE2F.address.0[19] as usize] = Some(blake2f as PrecompileFn);
466 precompiles[POINT_EVALUATION.address.0[19] as usize] =
467 Some(point_evaluation as PrecompileFn);
468 precompiles[BLS12_G1ADD.address.0[19] as usize] = Some(bls12_g1add as PrecompileFn);
469 precompiles[BLS12_G1MSM.address.0[19] as usize] = Some(bls12_g1msm as PrecompileFn);
470 precompiles[BLS12_G2ADD.address.0[19] as usize] = Some(bls12_g2add as PrecompileFn);
471 precompiles[BLS12_G2MSM.address.0[19] as usize] = Some(bls12_g2msm as PrecompileFn);
472 precompiles[BLS12_PAIRING_CHECK.address.0[19] as usize] =
473 Some(bls12_pairing_check as PrecompileFn);
474 precompiles[BLS12_MAP_FP_TO_G1.address.0[19] as usize] =
475 Some(bls12_map_fp_to_g1 as PrecompileFn);
476 precompiles[BLS12_MAP_FP2_TO_G2.address.0[19] as usize] =
477 Some(bls12_map_fp2_to_g2 as PrecompileFn);
478 precompiles
479 [u16::from_be_bytes([P256VERIFY.address.0[18], P256VERIFY.address.0[19]]) as usize] =
480 Some(p_256_verify as PrecompileFn);
481 precompiles
482 };
483
484 if fork >= Fork::LStar && address == EXECUTE.address {
487 return crate::execute_precompile::execute_precompile(
488 calldata,
489 gas_remaining,
490 fork,
491 crypto,
492 _stateless_validator,
493 );
494 }
495
496 if address[0..18] != [0u8; 18] {
497 return Err(VMError::Internal(InternalError::InvalidPrecompileAddress));
498 }
499 let index = u16::from_be_bytes([address[18], address[19]]) as usize;
500
501 let precompile = PRECOMPILES
502 .get(index)
503 .copied()
504 .flatten()
505 .ok_or(VMError::Internal(InternalError::InvalidPrecompileAddress))?;
506
507 if address != IDENTITY.address
509 && let Some((output, gas_cost)) = cache.and_then(|c| c.get(&address, calldata))
510 {
511 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
512 return Ok(output);
513 }
514
515 #[cfg(feature = "perf_opcode_timings")]
516 let precompile_time_start = std::time::Instant::now();
517
518 let gas_before = *gas_remaining;
519 let result = precompile(calldata, gas_remaining, fork, crypto);
520
521 #[cfg(feature = "perf_opcode_timings")]
522 {
523 let time = precompile_time_start.elapsed();
524 let mut timings = crate::timings::PRECOMPILES_TIMINGS.lock().expect("poison");
525 timings.update(address, time);
526 }
527
528 if address != IDENTITY.address
530 && let Some(cache) = cache
531 && let Ok(output) = &result
532 {
533 let gas_cost = gas_before.saturating_sub(*gas_remaining);
534 cache.insert(address, calldata.clone(), output.clone(), gas_cost);
535 }
536
537 result
538}
539
540pub(crate) fn increase_precompile_consumed_gas(
542 gas_cost: u64,
543 gas_remaining: &mut u64,
544) -> Result<(), VMError> {
545 *gas_remaining = gas_remaining
546 .checked_sub(gas_cost)
547 .ok_or(PrecompileError::NotEnoughGas)?;
548 Ok(())
549}
550
551#[inline(always)]
554pub(crate) fn fill_with_zeros(calldata: &Bytes, target_len: usize) -> Bytes {
555 if calldata.len() >= target_len {
556 return calldata.clone();
558 }
559 let mut padded_calldata = calldata.to_vec();
560 padded_calldata.resize(target_len, 0);
561 padded_calldata.into()
562}
563
564fn crypto_error_to_precompile(e: CryptoError) -> VMError {
565 match e {
566 CryptoError::InvalidPoint(_) => PrecompileError::InvalidPoint.into(),
567 CryptoError::InvalidInput(_) => PrecompileError::ParsingInputError.into(),
568 CryptoError::InvalidSignature => PrecompileError::ParsingInputError.into(),
569 CryptoError::InvalidRecoveryId => PrecompileError::ParsingInputError.into(),
570 CryptoError::RecoveryFailed => PrecompileError::ParsingInputError.into(),
571 CryptoError::VerificationFailed => PrecompileError::ParsingInputError.into(),
572 CryptoError::Other(_) => PrecompileError::ParsingInputError.into(),
573 CryptoError::Unsupported(msg) => InternalError::Custom(msg.to_string()).into(),
577 }
578}
579
580fn bls12_g1_crypto_error(e: CryptoError) -> VMError {
583 match e {
584 CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G1PointNotInCurve.into(),
585 other => crypto_error_to_precompile(other),
586 }
587}
588
589fn bls12_g2_crypto_error(e: CryptoError) -> VMError {
592 match e {
593 CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G2PointNotInCurve.into(),
594 other => crypto_error_to_precompile(other),
595 }
596}
597
598fn bls12_msm_pairing_crypto_error(e: CryptoError) -> VMError {
602 match e {
603 CryptoError::InvalidPoint(_) => PrecompileError::ParsingInputError.into(),
604 other => crypto_error_to_precompile(other),
605 }
606}
607
608pub fn ecrecover(
618 calldata: &Bytes,
619 gas_remaining: &mut u64,
620 _fork: Fork,
621 crypto: &dyn Crypto,
622) -> Result<Bytes, VMError> {
623 use crate::gas_cost::ECRECOVER_COST;
624
625 increase_precompile_consumed_gas(ECRECOVER_COST, gas_remaining)?;
626
627 const INPUT_LEN: usize = 128;
628 const WORD: usize = 32;
629
630 let input = fill_with_zeros(calldata, INPUT_LEN);
631
632 #[expect(
633 clippy::indexing_slicing,
634 reason = "fill_with_zeros guarantees len >= 128"
635 )]
636 let raw_hash: &[u8] = &input[0..WORD];
637 #[expect(
638 clippy::indexing_slicing,
639 reason = "fill_with_zeros guarantees len >= 128"
640 )]
641 let raw_v: &[u8] = &input[WORD..WORD * 2];
642 #[expect(
643 clippy::indexing_slicing,
644 reason = "fill_with_zeros guarantees len >= 128"
645 )]
646 let raw_sig: &[u8] = &input[WORD * 2..WORD * 2 + 64];
647
648 let recid_byte: u8 = match u8::try_from(u256_from_big_endian(raw_v)) {
650 Ok(27) => 0,
651 Ok(28) => 1,
652 _ => return Ok(Bytes::new()),
653 };
654
655 let msg_hash: [u8; 32] = raw_hash
656 .try_into()
657 .map_err(|_| InternalError::TypeConversion)?;
658 let sig: [u8; 64] = raw_sig
659 .try_into()
660 .map_err(|_| InternalError::TypeConversion)?;
661
662 let pk_hash = match crypto.secp256k1_ecrecover(&sig, recid_byte, &msg_hash) {
663 Ok(h) => h,
664 Err(_) => return Ok(Bytes::new()),
665 };
666
667 let mut out = [0u8; 32];
669 out[12..32].copy_from_slice(&pk_hash[12..32]);
670
671 Ok(Bytes::copy_from_slice(&out))
672}
673
674pub fn identity(
676 calldata: &Bytes,
677 gas_remaining: &mut u64,
678 _fork: Fork,
679 _crypto: &dyn Crypto,
680) -> Result<Bytes, VMError> {
681 let gas_cost = gas_cost::identity(calldata.len())?;
682
683 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
684
685 Ok(calldata.clone())
686}
687
688pub fn sha2_256(
690 calldata: &Bytes,
691 gas_remaining: &mut u64,
692 _fork: Fork,
693 crypto: &dyn Crypto,
694) -> Result<Bytes, VMError> {
695 let gas_cost = gas_cost::sha2_256(calldata.len())?;
696
697 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
698
699 let digest = crypto.sha256(calldata);
700 Ok(Bytes::copy_from_slice(&digest))
701}
702
703pub fn ripemd_160(
705 calldata: &Bytes,
706 gas_remaining: &mut u64,
707 _fork: Fork,
708 crypto: &dyn Crypto,
709) -> Result<Bytes, VMError> {
710 let gas_cost = gas_cost::ripemd_160(calldata.len())?;
711
712 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
713
714 let result = crypto.ripemd160(calldata);
715 Ok(Bytes::copy_from_slice(&result))
716}
717
718#[expect(clippy::indexing_slicing, reason = "bounds checked at start")]
720pub fn modexp(
721 calldata: &Bytes,
722 gas_remaining: &mut u64,
723 fork: Fork,
724 crypto: &dyn Crypto,
725) -> Result<Bytes, VMError> {
726 let calldata = fill_with_zeros(calldata, 96);
728
729 if fork < Fork::Osaka {
731 let base_size_bytes: [u8; 32] = calldata[0..32].try_into()?;
732 let modulus_size_bytes: [u8; 32] = calldata[64..96].try_into()?;
733 const ZERO_BYTES: [u8; 32] = [0u8; 32];
734
735 if base_size_bytes == ZERO_BYTES && modulus_size_bytes == ZERO_BYTES {
736 increase_precompile_consumed_gas(MODEXP_STATIC_COST, gas_remaining)?;
738 return Ok(Bytes::new());
739 }
740 }
741
742 let base_size = u256_from_big_endian_const::<32>(calldata[0..32].try_into()?);
745 let modulus_size = u256_from_big_endian_const::<32>(calldata[64..96].try_into()?);
746 let exponent_size = u256_from_big_endian_const::<32>(calldata[32..64].try_into()?);
747
748 if fork >= Fork::Osaka {
749 if base_size > U256::from(1024) {
750 return Err(PrecompileError::ModExpBaseTooLarge.into());
751 }
752 if exponent_size > U256::from(1024) {
753 return Err(PrecompileError::ModExpExpTooLarge.into());
754 }
755 if modulus_size > U256::from(1024) {
756 return Err(PrecompileError::ModExpModulusTooLarge.into());
757 }
758 }
759
760 let base_size = usize::try_from(base_size).map_err(|_| PrecompileError::ParsingInputError)?;
762 let exponent_size =
763 usize::try_from(exponent_size).map_err(|_| PrecompileError::ParsingInputError)?;
764 let modulus_size =
765 usize::try_from(modulus_size).map_err(|_| PrecompileError::ParsingInputError)?;
766
767 let base_limit = base_size.checked_add(96).ok_or(InternalError::Overflow)?;
768
769 let exponent_limit = exponent_size
770 .checked_add(base_limit)
771 .ok_or(InternalError::Overflow)?;
772
773 let modulus_limit = modulus_size
774 .checked_add(exponent_limit)
775 .ok_or(InternalError::Overflow)?;
776
777 let b = get_slice_or_default(&calldata, 96, base_limit, base_size);
778 let e = get_slice_or_default(&calldata, base_limit, exponent_limit, exponent_size);
779 let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, modulus_size);
780
781 use malachite::Natural;
783 use malachite::base::num::conversion::traits::*;
784 let exp_first_32_bytes = e.get(0..32.min(exponent_size)).unwrap_or_default();
785 let exp_first_32 =
786 Natural::from_power_of_2_digits_desc(8u64, exp_first_32_bytes.iter().cloned())
787 .ok_or(InternalError::TypeConversion)?;
788
789 let gas_cost = gas_cost::modexp(&exp_first_32, base_size, exponent_size, modulus_size, fork)?;
790
791 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
792
793 if base_size == 0 && modulus_size == 0 {
794 return Ok(Bytes::new());
795 }
796
797 let result = crypto
798 .modexp(&b, &e, &m)
799 .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
800
801 let res_bytes = increase_left_pad(&Bytes::from(result), modulus_size);
802
803 Ok(res_bytes.slice(..modulus_size))
804}
805
806#[expect(clippy::indexing_slicing, reason = "bounds checked")]
811fn get_slice_or_default<'c>(
812 calldata: &'c Bytes,
813 lower_limit: usize,
814 upper_limit: usize,
815 size_to_expand: usize,
816) -> Cow<'c, [u8]> {
817 let upper_limit = calldata.len().min(upper_limit);
818 if let Some(data) = calldata.get(lower_limit..upper_limit)
819 && !data.is_empty()
820 {
821 if data.len() == size_to_expand {
822 return data.into();
823 }
824 let mut extended = vec![0u8; size_to_expand];
825 let copy_size = size_to_expand.min(data.len());
826 extended[..copy_size].copy_from_slice(&data[..copy_size]);
827 return extended.into();
828 }
829 Vec::new().into()
830}
831
832#[inline(always)]
834pub fn increase_left_pad(result: &Bytes, m_size: usize) -> Bytes {
835 #[expect(
836 clippy::arithmetic_side_effects,
837 clippy::indexing_slicing,
838 reason = "overflow checked with the if condition, bounds checked"
839 )]
840 if result.len() < m_size {
841 let mut padded_result = vec![0u8; m_size];
842 let size_diff = m_size - result.len();
843 padded_result[size_diff..].copy_from_slice(result);
844
845 padded_result.into()
846 } else {
847 result.clone()
849 }
850}
851
852pub fn ecadd(
854 calldata: &Bytes,
855 gas_remaining: &mut u64,
856 _fork: Fork,
857 crypto: &dyn Crypto,
858) -> Result<Bytes, VMError> {
859 let calldata = fill_with_zeros(calldata, 128);
861
862 increase_precompile_consumed_gas(ECADD_COST, gas_remaining)?;
863
864 let (Some(first_point), Some(second_point)) =
865 (parse_bn254_g1(&calldata, 0), parse_bn254_g1(&calldata, 64))
866 else {
867 return Err(InternalError::Slicing.into());
868 };
869 validate_bn254_g1_coords(&first_point)?;
870 validate_bn254_g1_coords(&second_point)?;
871
872 #[expect(clippy::indexing_slicing, reason = "calldata padded to 128 bytes")]
873 let result = crypto
874 .bn254_g1_add(&calldata[..64], &calldata[64..128])
875 .map_err(crypto_error_to_precompile)?;
876
877 Ok(Bytes::copy_from_slice(&result))
878}
879
880pub fn ecmul(
882 calldata: &Bytes,
883 gas_remaining: &mut u64,
884 _fork: Fork,
885 crypto: &dyn Crypto,
886) -> Result<Bytes, VMError> {
887 let calldata = fill_with_zeros(calldata, 96);
889 increase_precompile_consumed_gas(ECMUL_COST, gas_remaining)?;
890
891 let (Some(g1), Some(_scalar)) = (
892 parse_bn254_g1(&calldata, 0),
893 parse_bn254_scalar(&calldata, 64),
894 ) else {
895 return Err(InternalError::Slicing.into());
896 };
897 validate_bn254_g1_coords(&g1)?;
898
899 #[expect(clippy::indexing_slicing, reason = "calldata padded to 96 bytes")]
900 let result = crypto
901 .bn254_g1_mul(&calldata[..64], &calldata[64..96])
902 .map_err(crypto_error_to_precompile)?;
903
904 Ok(Bytes::copy_from_slice(&result))
905}
906
907const ALT_BN128_PRIME: U256 = U256([
908 0x3c208c16d87cfd47,
909 0x97816a916871ca8d,
910 0xb85045b68181585d,
911 0x30644e72e131a029,
912]);
913
914pub struct G1(U256, U256);
915impl G1 {
916 pub fn is_zero(&self) -> bool {
918 self.0.is_zero() && self.1.is_zero()
919 }
920}
921pub struct G2(U256, U256, U256, U256);
922impl G2 {
923 pub fn is_zero(&self) -> bool {
925 self.0.is_zero() && self.1.is_zero() && self.2.is_zero() && self.3.is_zero()
926 }
927}
928
929#[inline]
931fn parse_bn254_scalar(buf: &[u8], offset: usize) -> Option<U256> {
932 buf.get(offset..offset.checked_add(32)?)
933 .map(u256_from_big_endian)
934}
935
936#[inline]
938fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option<G1> {
939 let chunk = buf.get(offset..offset.checked_add(64)?)?;
940 let (x_bytes, y_bytes) = chunk.split_at_checked(32)?;
941 Some(G1(
942 u256_from_big_endian(x_bytes),
943 u256_from_big_endian(y_bytes),
944 ))
945}
946
947fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option<G2> {
949 let chunk = buf.get(offset..offset.checked_add(128)?)?;
950 let (g2_xy, rest) = chunk.split_at_checked(32)?;
951 let (g2_xx, rest) = rest.split_at_checked(32)?;
952 let (g2_yy, g2_yx) = rest.split_at_checked(32)?;
953 Some(G2(
954 u256_from_big_endian(g2_xx),
955 u256_from_big_endian(g2_xy),
956 u256_from_big_endian(g2_yx),
957 u256_from_big_endian(g2_yy),
958 ))
959}
960
961#[inline]
962fn validate_bn254_g1_coords(g1: &G1) -> Result<(), VMError> {
963 if g1.0 >= ALT_BN128_PRIME || g1.1 >= ALT_BN128_PRIME {
965 return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
966 }
967 Ok(())
968}
969
970#[inline]
971fn validate_bn254_g2_coords(g2: &G2) -> Result<(), VMError> {
972 if g2.0 >= ALT_BN128_PRIME
974 || g2.1 >= ALT_BN128_PRIME
975 || g2.2 >= ALT_BN128_PRIME
976 || g2.3 >= ALT_BN128_PRIME
977 {
978 return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
979 }
980 Ok(())
981}
982
983pub fn ecpairing(
985 calldata: &Bytes,
986 gas_remaining: &mut u64,
987 _fork: Fork,
988 crypto: &dyn Crypto,
989) -> Result<Bytes, VMError> {
990 if !calldata.len().is_multiple_of(192) {
992 return Err(PrecompileError::ParsingInputError.into());
993 }
994
995 let inputs_amount = calldata.len() / 192;
996 let gas_cost = gas_cost::ecpairing(inputs_amount)?;
997 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
998
999 let mut pairs: Vec<(&[u8], &[u8])> = Vec::new();
1000 for input in calldata.chunks_exact(192) {
1001 let (Some(g1), Some(g2)) = (parse_bn254_g1(input, 0), parse_bn254_g2(input, 64)) else {
1002 return Err(InternalError::Slicing.into());
1003 };
1004 validate_bn254_g1_coords(&g1)?;
1005 validate_bn254_g2_coords(&g2)?;
1006 #[expect(clippy::indexing_slicing, reason = "chunks_exact guarantees 192 bytes")]
1007 pairs.push((&input[..64], &input[64..192]));
1008 }
1009
1010 let pairing_check = if pairs.is_empty() {
1011 true
1012 } else {
1013 crypto
1014 .bn254_pairing_check(&pairs)
1015 .map_err(crypto_error_to_precompile)?
1016 };
1017
1018 let mut result = [0; 32];
1019 result[31] = u8::from(pairing_check);
1020 Ok(Bytes::from_owner(result))
1021}
1022
1023pub fn blake2f(
1025 calldata: &Bytes,
1026 gas_remaining: &mut u64,
1027 _fork: Fork,
1028 crypto: &dyn Crypto,
1029) -> Result<Bytes, VMError> {
1030 if calldata.len() != 213 {
1031 return Err(PrecompileError::ParsingInputError.into());
1032 }
1033
1034 let mut calldata = calldata.slice(0..213);
1035
1036 let rounds = calldata.get_u32();
1037
1038 let gas_cost = u64::from(rounds) * BLAKE2F_ROUND_COST;
1039 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1040
1041 let mut h = [0; 8];
1042
1043 h.copy_from_slice(&std::array::from_fn::<u64, 8, _>(|_| calldata.get_u64_le()));
1044
1045 let mut m = [0; 16];
1046
1047 m.copy_from_slice(&std::array::from_fn::<u64, 16, _>(|_| {
1048 calldata.get_u64_le()
1049 }));
1050
1051 let mut t = [0; 2];
1052 t.copy_from_slice(&std::array::from_fn::<u64, 2, _>(|_| calldata.get_u64_le()));
1053
1054 let f = calldata.get_u8();
1055 if f != 0 && f != 1 {
1056 return Err(PrecompileError::ParsingInputError.into());
1057 }
1058 let f = f == 1;
1059
1060 crypto.blake2_compress(rounds, &mut h, m, t, f);
1061
1062 Ok(Bytes::from_iter(
1063 h.into_iter().flat_map(|value| value.to_le_bytes()),
1064 ))
1065}
1066
1067fn kzg_commitment_to_versioned_hash(commitment_bytes: &[u8; 48], crypto: &dyn Crypto) -> H256 {
1069 let mut versioned_hash: [u8; 32] = crypto.sha256(commitment_bytes);
1070 versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
1071 versioned_hash.into()
1072}
1073
1074const POINT_EVALUATION_OUTPUT_BYTES: [u8; 64] = [
1075 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1077 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
1078 0x73, 0xED, 0xA7, 0x53, 0x29, 0x9D, 0x7D, 0x48, 0x33, 0x39, 0xD8, 0x08, 0x09, 0xA1, 0xD8, 0x05,
1080 0x53, 0xBD, 0xA4, 0x02, 0xFF, 0xFE, 0x5B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01,
1081];
1082
1083fn point_evaluation(
1085 calldata: &Bytes,
1086 gas_remaining: &mut u64,
1087 _fork: Fork,
1088 crypto: &dyn Crypto,
1089) -> Result<Bytes, VMError> {
1090 if calldata.len() != 192 {
1091 return Err(PrecompileError::ParsingInputError.into());
1092 }
1093
1094 let gas_cost = POINT_EVALUATION_COST;
1096 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1097
1098 let versioned_hash: [u8; 32] = calldata
1100 .get(..32)
1101 .ok_or(InternalError::Slicing)?
1102 .try_into()
1103 .map_err(|_| InternalError::TypeConversion)?;
1104
1105 let z: [u8; 32] = calldata
1106 .get(32..64)
1107 .ok_or(InternalError::Slicing)?
1108 .try_into()
1109 .map_err(|_| InternalError::TypeConversion)?;
1110
1111 let y: [u8; 32] = calldata
1112 .get(64..96)
1113 .ok_or(InternalError::Slicing)?
1114 .try_into()
1115 .map_err(|_| InternalError::TypeConversion)?;
1116
1117 let commitment: [u8; 48] = calldata
1118 .get(96..144)
1119 .ok_or(InternalError::Slicing)?
1120 .try_into()
1121 .map_err(|_| InternalError::TypeConversion)?;
1122
1123 let proof: [u8; 48] = calldata
1124 .get(144..192)
1125 .ok_or(InternalError::Slicing)?
1126 .try_into()
1127 .map_err(|_| InternalError::TypeConversion)?;
1128
1129 if kzg_commitment_to_versioned_hash(&commitment, crypto) != H256::from(versioned_hash) {
1133 return Err(PrecompileError::ParsingInputError.into());
1134 }
1135
1136 crypto
1138 .verify_kzg_proof(&z, &y, &commitment, &proof)
1139 .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
1140
1141 let output = POINT_EVALUATION_OUTPUT_BYTES.to_vec();
1144
1145 Ok(Bytes::from(output))
1146}
1147
1148pub fn p_256_verify(
1153 calldata: &Bytes,
1154 gas_remaining: &mut u64,
1155 _fork: Fork,
1156 crypto: &dyn Crypto,
1157) -> Result<Bytes, VMError> {
1158 increase_precompile_consumed_gas(P256_VERIFY_COST, gas_remaining)
1159 .map_err(|_| PrecompileError::NotEnoughGas)?;
1160
1161 if calldata.len() != 160 {
1163 return Ok(Bytes::new());
1164 }
1165
1166 #[expect(
1168 clippy::indexing_slicing,
1169 reason = "length of the calldata is checked before slicing"
1170 )]
1171 let msg: &[u8; 32] = calldata[0..32].try_into()?;
1172 #[expect(clippy::indexing_slicing, reason = "length checked")]
1173 let r: &[u8; 32] = calldata[32..64].try_into()?;
1174 #[expect(clippy::indexing_slicing, reason = "length checked")]
1175 let s: &[u8; 32] = calldata[64..96].try_into()?;
1176 #[expect(clippy::indexing_slicing, reason = "length checked")]
1177 let pk_x: &[u8; 32] = calldata[96..128].try_into()?;
1178 #[expect(clippy::indexing_slicing, reason = "length checked")]
1179 let pk_y: &[u8; 32] = calldata[128..160].try_into()?;
1180
1181 let mut sig_bytes = [0u8; 64];
1183 sig_bytes[..32].copy_from_slice(r);
1184 sig_bytes[32..].copy_from_slice(s);
1185
1186 let mut pk_bytes = [0u8; 64];
1187 pk_bytes[..32].copy_from_slice(pk_x);
1188 pk_bytes[32..].copy_from_slice(pk_y);
1189
1190 let success = crypto.secp256r1_verify(msg, &sig_bytes, &pk_bytes);
1191
1192 if success {
1195 const RESULT: [u8; 32] = [
1196 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,
1197 0, 0, 1,
1198 ];
1199 Ok(Bytes::from_static(&RESULT))
1200 } else {
1201 Ok(Bytes::new())
1202 }
1203}
1204
1205fn parse_bls12_padded_fp(padded: &[u8; 64]) -> Result<[u8; 48], VMError> {
1208 if padded[..16] != [0u8; 16] {
1209 return Err(PrecompileError::ParsingInputError.into());
1210 }
1211 let fp: [u8; 48] = padded[16..64]
1212 .try_into()
1213 .map_err(|_| InternalError::TypeConversion)?;
1214 Ok(fp)
1215}
1216
1217pub fn bls12_g1add(
1218 calldata: &Bytes,
1219 gas_remaining: &mut u64,
1220 _fork: Fork,
1221 crypto: &dyn Crypto,
1222) -> Result<Bytes, VMError> {
1223 let (x_data, calldata) = calldata
1224 .split_first_chunk::<128>()
1225 .ok_or(PrecompileError::ParsingInputError)?;
1226 let (y_data, calldata) = calldata
1227 .split_first_chunk::<128>()
1228 .ok_or(PrecompileError::ParsingInputError)?;
1229 if !calldata.is_empty() {
1230 return Err(PrecompileError::ParsingInputError.into());
1231 }
1232
1233 increase_precompile_consumed_gas(BLS12_381_G1ADD_COST, gas_remaining)
1235 .map_err(|_| PrecompileError::NotEnoughGas)?;
1236
1237 let ax = parse_bls12_padded_fp(
1239 x_data[..64]
1240 .try_into()
1241 .map_err(|_| InternalError::TypeConversion)?,
1242 )?;
1243 let ay = parse_bls12_padded_fp(
1244 x_data[64..128]
1245 .try_into()
1246 .map_err(|_| InternalError::TypeConversion)?,
1247 )?;
1248 let bx = parse_bls12_padded_fp(
1249 y_data[..64]
1250 .try_into()
1251 .map_err(|_| InternalError::TypeConversion)?,
1252 )?;
1253 let by = parse_bls12_padded_fp(
1254 y_data[64..128]
1255 .try_into()
1256 .map_err(|_| InternalError::TypeConversion)?,
1257 )?;
1258
1259 let result = crypto
1260 .bls12_381_g1_add((ax, ay), (bx, by))
1261 .map_err(bls12_g1_crypto_error)?;
1262
1263 let mut output = [0u8; 128];
1265 output[16..64].copy_from_slice(&result[..48]);
1266 output[80..128].copy_from_slice(&result[48..96]);
1267 Ok(Bytes::copy_from_slice(&output))
1268}
1269
1270pub fn bls12_g1msm(
1271 calldata: &Bytes,
1272 gas_remaining: &mut u64,
1273 _fork: Fork,
1274 crypto: &dyn Crypto,
1275) -> Result<Bytes, VMError> {
1276 if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G1_MSM_PAIR_LENGTH) {
1277 return Err(PrecompileError::ParsingInputError.into());
1278 }
1279
1280 let k = calldata.len() / BLS12_381_G1_MSM_PAIR_LENGTH;
1281 let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G1_K_DISCOUNT, G1_MUL_COST)?;
1282 increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1283
1284 #[allow(clippy::type_complexity)]
1285 let mut pairs: Vec<(([u8; 48], [u8; 48]), [u8; 32])> = Vec::with_capacity(k);
1286
1287 #[expect(
1288 clippy::arithmetic_side_effects,
1289 clippy::indexing_slicing,
1290 reason = "bounds checked"
1291 )]
1292 for i in 0..k {
1293 let point_offset = i * BLS12_381_G1_MSM_PAIR_LENGTH;
1294 let scalar_offset = point_offset + 128;
1295 let pair_end = scalar_offset + 32;
1296
1297 let point_bytes = &calldata[point_offset..scalar_offset];
1298 let scalar_bytes = &calldata[scalar_offset..pair_end];
1299
1300 let px = parse_bls12_padded_fp(
1301 point_bytes[..64]
1302 .try_into()
1303 .map_err(|_| InternalError::TypeConversion)?,
1304 )?;
1305 let py = parse_bls12_padded_fp(
1306 point_bytes[64..128]
1307 .try_into()
1308 .map_err(|_| InternalError::TypeConversion)?,
1309 )?;
1310 let scalar: [u8; 32] = scalar_bytes
1311 .try_into()
1312 .map_err(|_| InternalError::TypeConversion)?;
1313
1314 pairs.push(((px, py), scalar));
1315 }
1316
1317 let result = crypto
1318 .bls12_381_g1_msm(&pairs)
1319 .map_err(bls12_msm_pairing_crypto_error)?;
1320
1321 let mut output = [0u8; 128];
1323 output[16..64].copy_from_slice(&result[..48]);
1324 output[80..128].copy_from_slice(&result[48..96]);
1325 Ok(Bytes::copy_from_slice(&output))
1326}
1327
1328pub fn bls12_g2add(
1329 calldata: &Bytes,
1330 gas_remaining: &mut u64,
1331 _fork: Fork,
1332 crypto: &dyn Crypto,
1333) -> Result<Bytes, VMError> {
1334 let (x_data, calldata) = calldata
1335 .split_first_chunk::<256>()
1336 .ok_or(PrecompileError::ParsingInputError)?;
1337 let (y_data, calldata) = calldata
1338 .split_first_chunk::<256>()
1339 .ok_or(PrecompileError::ParsingInputError)?;
1340 if !calldata.is_empty() {
1341 return Err(PrecompileError::ParsingInputError.into());
1342 }
1343
1344 increase_precompile_consumed_gas(BLS12_381_G2ADD_COST, gas_remaining)
1346 .map_err(|_| PrecompileError::NotEnoughGas)?;
1347
1348 let ax0 = parse_bls12_padded_fp(
1351 x_data[0..64]
1352 .try_into()
1353 .map_err(|_| InternalError::TypeConversion)?,
1354 )?;
1355 let ax1 = parse_bls12_padded_fp(
1356 x_data[64..128]
1357 .try_into()
1358 .map_err(|_| InternalError::TypeConversion)?,
1359 )?;
1360 let ay0 = parse_bls12_padded_fp(
1361 x_data[128..192]
1362 .try_into()
1363 .map_err(|_| InternalError::TypeConversion)?,
1364 )?;
1365 let ay1 = parse_bls12_padded_fp(
1366 x_data[192..256]
1367 .try_into()
1368 .map_err(|_| InternalError::TypeConversion)?,
1369 )?;
1370
1371 let bx0 = parse_bls12_padded_fp(
1372 y_data[0..64]
1373 .try_into()
1374 .map_err(|_| InternalError::TypeConversion)?,
1375 )?;
1376 let bx1 = parse_bls12_padded_fp(
1377 y_data[64..128]
1378 .try_into()
1379 .map_err(|_| InternalError::TypeConversion)?,
1380 )?;
1381 let by0 = parse_bls12_padded_fp(
1382 y_data[128..192]
1383 .try_into()
1384 .map_err(|_| InternalError::TypeConversion)?,
1385 )?;
1386 let by1 = parse_bls12_padded_fp(
1387 y_data[192..256]
1388 .try_into()
1389 .map_err(|_| InternalError::TypeConversion)?,
1390 )?;
1391
1392 let result = crypto
1393 .bls12_381_g2_add((ax0, ax1, ay0, ay1), (bx0, bx1, by0, by1))
1394 .map_err(bls12_g2_crypto_error)?;
1395
1396 let mut output = [0u8; 256];
1400 output[16..64].copy_from_slice(&result[0..48]);
1401 output[80..128].copy_from_slice(&result[48..96]);
1402 output[144..192].copy_from_slice(&result[96..144]);
1403 output[208..256].copy_from_slice(&result[144..192]);
1404 Ok(Bytes::copy_from_slice(&output))
1405}
1406
1407pub fn bls12_g2msm(
1408 calldata: &Bytes,
1409 gas_remaining: &mut u64,
1410 _fork: Fork,
1411 crypto: &dyn Crypto,
1412) -> Result<Bytes, VMError> {
1413 if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G2_MSM_PAIR_LENGTH) {
1414 return Err(PrecompileError::ParsingInputError.into());
1415 }
1416
1417 let k = calldata.len() / BLS12_381_G2_MSM_PAIR_LENGTH;
1418 let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G2_K_DISCOUNT, G2_MUL_COST)?;
1419 increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1420
1421 #[allow(clippy::type_complexity)]
1422 let mut pairs: Vec<(([u8; 48], [u8; 48], [u8; 48], [u8; 48]), [u8; 32])> =
1423 Vec::with_capacity(k);
1424
1425 #[expect(
1426 clippy::indexing_slicing,
1427 clippy::arithmetic_side_effects,
1428 reason = "bounds checked"
1429 )]
1430 for i in 0..k {
1431 let point_offset = i * BLS12_381_G2_MSM_PAIR_LENGTH;
1432 let scalar_offset = point_offset + 256;
1433 let pair_end = scalar_offset + 32;
1434
1435 let point_bytes = &calldata[point_offset..scalar_offset];
1436 let scalar_bytes = &calldata[scalar_offset..pair_end];
1437
1438 let x0 = parse_bls12_padded_fp(
1439 point_bytes[0..64]
1440 .try_into()
1441 .map_err(|_| InternalError::TypeConversion)?,
1442 )?;
1443 let x1 = parse_bls12_padded_fp(
1444 point_bytes[64..128]
1445 .try_into()
1446 .map_err(|_| InternalError::TypeConversion)?,
1447 )?;
1448 let y0 = parse_bls12_padded_fp(
1449 point_bytes[128..192]
1450 .try_into()
1451 .map_err(|_| InternalError::TypeConversion)?,
1452 )?;
1453 let y1 = parse_bls12_padded_fp(
1454 point_bytes[192..256]
1455 .try_into()
1456 .map_err(|_| InternalError::TypeConversion)?,
1457 )?;
1458 let scalar: [u8; 32] = scalar_bytes
1459 .try_into()
1460 .map_err(|_| InternalError::TypeConversion)?;
1461
1462 pairs.push(((x0, x1, y0, y1), scalar));
1463 }
1464
1465 let result = crypto
1466 .bls12_381_g2_msm(&pairs)
1467 .map_err(bls12_msm_pairing_crypto_error)?;
1468
1469 let mut output = [0u8; 256];
1471 output[16..64].copy_from_slice(&result[0..48]);
1472 output[80..128].copy_from_slice(&result[48..96]);
1473 output[144..192].copy_from_slice(&result[96..144]);
1474 output[208..256].copy_from_slice(&result[144..192]);
1475 Ok(Bytes::copy_from_slice(&output))
1476}
1477
1478pub fn bls12_pairing_check(
1479 calldata: &Bytes,
1480 gas_remaining: &mut u64,
1481 _fork: Fork,
1482 crypto: &dyn Crypto,
1483) -> Result<Bytes, VMError> {
1484 if calldata.is_empty()
1485 || !calldata
1486 .len()
1487 .is_multiple_of(BLS12_381_PAIRING_CHECK_PAIR_LENGTH)
1488 {
1489 return Err(PrecompileError::ParsingInputError.into());
1490 }
1491
1492 let k = calldata.len() / BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1494 let gas_cost = gas_cost::bls12_pairing_check(k)?;
1495 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1496
1497 #[allow(clippy::type_complexity)]
1498 let mut pairs: Vec<(
1499 ([u8; 48], [u8; 48]),
1500 ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
1501 )> = Vec::with_capacity(k);
1502
1503 #[expect(
1504 clippy::indexing_slicing,
1505 clippy::arithmetic_side_effects,
1506 reason = "bounds checked"
1507 )]
1508 for i in 0..k {
1509 let g1_offset = i * BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1510 let g2_offset = g1_offset + 128;
1511 let pair_end = g2_offset + 256;
1512
1513 let g1_bytes = &calldata[g1_offset..g2_offset];
1514 let g2_bytes = &calldata[g2_offset..pair_end];
1515
1516 let g1x = parse_bls12_padded_fp(
1517 g1_bytes[0..64]
1518 .try_into()
1519 .map_err(|_| InternalError::TypeConversion)?,
1520 )?;
1521 let g1y = parse_bls12_padded_fp(
1522 g1_bytes[64..128]
1523 .try_into()
1524 .map_err(|_| InternalError::TypeConversion)?,
1525 )?;
1526
1527 let g2x0 = parse_bls12_padded_fp(
1528 g2_bytes[0..64]
1529 .try_into()
1530 .map_err(|_| InternalError::TypeConversion)?,
1531 )?;
1532 let g2x1 = parse_bls12_padded_fp(
1533 g2_bytes[64..128]
1534 .try_into()
1535 .map_err(|_| InternalError::TypeConversion)?,
1536 )?;
1537 let g2y0 = parse_bls12_padded_fp(
1538 g2_bytes[128..192]
1539 .try_into()
1540 .map_err(|_| InternalError::TypeConversion)?,
1541 )?;
1542 let g2y1 = parse_bls12_padded_fp(
1543 g2_bytes[192..256]
1544 .try_into()
1545 .map_err(|_| InternalError::TypeConversion)?,
1546 )?;
1547
1548 pairs.push(((g1x, g1y), (g2x0, g2x1, g2y0, g2y1)));
1549 }
1550
1551 let result = crypto
1552 .bls12_381_pairing_check(&pairs)
1553 .map_err(bls12_msm_pairing_crypto_error)?;
1554
1555 if result {
1556 let mut out = vec![0_u8; 31];
1557 out.push(1);
1558 Ok(Bytes::from(out))
1559 } else {
1560 Ok(Bytes::copy_from_slice(&[0_u8; 32]))
1561 }
1562}
1563
1564pub fn bls12_map_fp_to_g1(
1565 calldata: &Bytes,
1566 gas_remaining: &mut u64,
1567 _fork: Fork,
1568 crypto: &dyn Crypto,
1569) -> Result<Bytes, VMError> {
1570 if calldata.len() != BLS12_381_FP_VALID_INPUT_LENGTH {
1571 return Err(PrecompileError::ParsingInputError.into());
1572 }
1573
1574 increase_precompile_consumed_gas(BLS12_381_MAP_FP_TO_G1_COST, gas_remaining)?;
1576
1577 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1579 let fp = parse_bls12_padded_fp(
1580 calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1581 .try_into()
1582 .map_err(|_| InternalError::TypeConversion)?,
1583 )?;
1584
1585 let result = crypto
1586 .bls12_381_fp_to_g1(&fp)
1587 .map_err(crypto_error_to_precompile)?;
1588
1589 let mut output = [0u8; 128];
1591 output[16..64].copy_from_slice(&result[0..48]);
1592 output[80..128].copy_from_slice(&result[48..96]);
1593 Ok(Bytes::copy_from_slice(&output))
1594}
1595
1596pub fn bls12_map_fp2_to_g2(
1597 calldata: &Bytes,
1598 gas_remaining: &mut u64,
1599 _fork: Fork,
1600 crypto: &dyn Crypto,
1601) -> Result<Bytes, VMError> {
1602 if calldata.len() != BLS12_381_FP2_VALID_INPUT_LENGTH {
1603 return Err(PrecompileError::ParsingInputError.into());
1604 }
1605
1606 increase_precompile_consumed_gas(BLS12_381_MAP_FP2_TO_G2_COST, gas_remaining)?;
1608
1609 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1611 let c0 = parse_bls12_padded_fp(
1612 calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1613 .try_into()
1614 .map_err(|_| InternalError::TypeConversion)?,
1615 )?;
1616 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1617 let c1 = parse_bls12_padded_fp(
1618 calldata[PADDED_FIELD_ELEMENT_SIZE_IN_BYTES..BLS12_381_FP2_VALID_INPUT_LENGTH]
1619 .try_into()
1620 .map_err(|_| InternalError::TypeConversion)?,
1621 )?;
1622
1623 if c0 == [0u8; 48] && c1 == [0u8; 48] {
1624 return Ok(Bytes::copy_from_slice(&FP2_ZERO_MAPPED_TO_G2));
1625 }
1626
1627 let result = crypto
1628 .bls12_381_fp2_to_g2((c0, c1))
1629 .map_err(crypto_error_to_precompile)?;
1630
1631 let mut output = [0u8; 256];
1635 output[16..64].copy_from_slice(&result[0..48]);
1636 output[80..128].copy_from_slice(&result[48..96]);
1637 output[144..192].copy_from_slice(&result[96..144]);
1638 output[208..256].copy_from_slice(&result[144..192]);
1639 Ok(Bytes::copy_from_slice(&output))
1640}
1641
1642#[cfg(test)]
1643mod tests {
1644 use super::*;
1645 use ethrex_common::types::Fork;
1646
1647 #[test]
1649 fn execute_precompile_gated_by_lstar_and_l1() {
1650 let execute_addr = EXECUTE.address;
1651 assert!(!is_precompile(&execute_addr, Fork::Amsterdam, VMType::L1));
1653 assert!(!is_precompile(
1655 &execute_addr,
1656 Fork::LStar,
1657 VMType::L2(Default::default())
1658 ));
1659 assert!(is_precompile(&execute_addr, Fork::LStar, VMType::L1));
1661 }
1662}