1use bytes::{Buf, Bytes};
2use ethrex_common::H160;
3use ethrex_common::seeded_hash::SeededHashMap;
4use ethrex_common::utils::u256_from_big_endian_const;
5use ethrex_common::{
6 Address, H256, U256, types::Fork, types::Fork::*, utils::u256_from_big_endian,
7};
8use ethrex_crypto::{Crypto, CryptoError};
9use rustc_hash::FxHashMap;
10use std::borrow::Cow;
11use std::collections::{BTreeMap, BTreeSet, hash_map::Entry};
12use std::sync::RwLock;
13
14use crate::gas_cost::{MODEXP_STATIC_COST, P256_VERIFY_COST};
15use crate::vm::VMType;
16use crate::{
17 constants::VERSIONED_HASH_VERSION_KZG,
18 errors::{ExceptionalHalt, InternalError, PrecompileError, VMError},
19 gas_cost::{
20 self, BLAKE2F_ROUND_COST, BLS12_381_G1_K_DISCOUNT, BLS12_381_G1ADD_COST,
21 BLS12_381_G2_K_DISCOUNT, BLS12_381_G2ADD_COST, BLS12_381_MAP_FP_TO_G1_COST,
22 BLS12_381_MAP_FP2_TO_G2_COST, ECADD_COST, ECMUL_COST, G1_MUL_COST, G2_MUL_COST,
23 POINT_EVALUATION_COST,
24 },
25};
26
27pub const BLAKE2F_ELEMENT_SIZE: usize = 8;
28
29pub const SIZE_PRECOMPILES_PRE_CANCUN: u64 = 9;
30pub const SIZE_PRECOMPILES_CANCUN: u64 = 10;
31pub const SIZE_PRECOMPILES_PRAGUE: u64 = 17;
32
33pub const BLS12_381_G1_MSM_PAIR_LENGTH: usize = 160;
34pub const BLS12_381_G2_MSM_PAIR_LENGTH: usize = 288;
35pub const BLS12_381_PAIRING_CHECK_PAIR_LENGTH: usize = 384;
36
37const BLS12_381_FP2_VALID_INPUT_LENGTH: usize = 128;
38const BLS12_381_FP_VALID_INPUT_LENGTH: usize = 64;
39
40pub const FIELD_ELEMENT_WITHOUT_PADDING_LENGTH: usize = 48;
41pub const PADDED_FIELD_ELEMENT_SIZE_IN_BYTES: usize = 64;
42
43pub const G1_POINT_AT_INFINITY: [u8; 128] = [0_u8; 128];
44pub const G2_POINT_AT_INFINITY: [u8; 256] = [0_u8; 256];
45
46const FP2_ZERO_MAPPED_TO_G2: [u8; 256] = [
50 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,
51 25, 132, 141, 194, 156, 226, 102, 244, 19, 208, 45, 211, 29, 155, 157, 68, 236, 12, 121, 205,
52 97, 241, 139, 7, 93, 219, 166, 215, 189, 32, 183, 255, 39, 164, 179, 36, 191, 206, 0, 0, 0, 0,
53 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,
54 235, 250, 126, 35, 65, 13, 185, 61, 227, 159, 176, 109, 112, 37, 250, 149, 233, 111, 250, 66,
55 138, 122, 39, 195, 174, 77, 212, 180, 11, 210, 81, 172, 101, 136, 146, 0, 0, 0, 0, 0, 0, 0, 0,
56 0, 0, 0, 0, 0, 0, 0, 0, 2, 96, 224, 54, 68, 209, 162, 195, 33, 37, 107, 50, 70, 186, 210, 184,
57 149, 202, 209, 56, 144, 203, 230, 248, 93, 245, 81, 6, 160, 211, 52, 96, 79, 177, 67, 199, 160,
58 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,
59 4, 198, 151, 119, 164, 63, 11, 218, 7, 103, 157, 88, 5, 230, 63, 24, 207, 78, 14, 124, 97, 18,
60 172, 127, 112, 38, 109, 25, 155, 79, 118, 174, 39, 198, 38, 154, 60, 238, 189, 174, 48, 128,
61 110, 154, 118, 170, 223, 92,
62];
63
64pub struct Precompile {
65 pub address: H160,
66 pub name: &'static str,
67 pub active_since_fork: Fork,
68}
69
70pub const ECRECOVER: Precompile = Precompile {
71 address: H160([
72 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
73 0x00, 0x00, 0x00, 0x00, 0x01,
74 ]),
75 name: "ECREC",
76 active_since_fork: Paris,
77};
78
79pub const SHA2_256: Precompile = Precompile {
80 address: H160([
81 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
82 0x00, 0x00, 0x00, 0x00, 0x02,
83 ]),
84 name: "SHA256",
85 active_since_fork: Paris,
86};
87
88pub const RIPEMD_160: Precompile = Precompile {
89 address: H160([
90 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
91 0x00, 0x00, 0x00, 0x00, 0x03,
92 ]),
93 name: "RIPEMD160",
94 active_since_fork: Paris,
95};
96
97pub const IDENTITY: Precompile = Precompile {
98 address: H160([
99 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
100 0x00, 0x00, 0x00, 0x00, 0x04,
101 ]),
102 name: "ID",
103 active_since_fork: Paris,
104};
105
106pub const MODEXP: Precompile = Precompile {
107 address: H160([
108 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
109 0x00, 0x00, 0x00, 0x00, 0x05,
110 ]),
111 name: "MODEXP",
112 active_since_fork: Paris,
113};
114
115pub const ECADD: Precompile = Precompile {
116 address: H160([
117 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
118 0x00, 0x00, 0x00, 0x00, 0x06,
119 ]),
120 name: "BN254_ADD",
121 active_since_fork: Paris,
122};
123
124pub const ECMUL: Precompile = Precompile {
125 address: H160([
126 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
127 0x00, 0x00, 0x00, 0x00, 0x07,
128 ]),
129 name: "BN254_MUL",
130 active_since_fork: Paris,
131};
132
133pub const ECPAIRING: Precompile = Precompile {
134 address: H160([
135 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
136 0x00, 0x00, 0x00, 0x00, 0x08,
137 ]),
138 name: "BN254_PAIRING",
139 active_since_fork: Paris,
140};
141
142pub const BLAKE2F: Precompile = Precompile {
143 address: H160([
144 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
145 0x00, 0x00, 0x00, 0x00, 0x09,
146 ]),
147 name: "BLAKE2F",
148 active_since_fork: Paris,
149};
150
151pub const POINT_EVALUATION: Precompile = Precompile {
152 address: H160([
153 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
154 0x00, 0x00, 0x00, 0x00, 0x0a,
155 ]),
156 name: "KZG_POINT_EVALUATION",
157 active_since_fork: Cancun,
158};
159
160pub const BLS12_G1ADD: Precompile = Precompile {
161 address: H160([
162 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
163 0x00, 0x00, 0x00, 0x00, 0x0b,
164 ]),
165 name: "BLS12_G1ADD",
166 active_since_fork: Prague,
167};
168
169pub const BLS12_G1MSM: Precompile = Precompile {
170 address: H160([
171 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
172 0x00, 0x00, 0x00, 0x00, 0x0c,
173 ]),
174 name: "BLS12_G1MSM",
175 active_since_fork: Prague,
176};
177
178pub const BLS12_G2ADD: Precompile = Precompile {
179 address: H160([
180 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
181 0x00, 0x00, 0x00, 0x00, 0x0d,
182 ]),
183 name: "BLS12_G2ADD",
184 active_since_fork: Prague,
185};
186
187pub const BLS12_G2MSM: Precompile = Precompile {
188 address: H160([
189 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
190 0x00, 0x00, 0x00, 0x00, 0x0e,
191 ]),
192 name: "BLS12_G2MSM",
193 active_since_fork: Prague,
194};
195
196pub const BLS12_PAIRING_CHECK: Precompile = Precompile {
197 address: H160([
198 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
199 0x00, 0x00, 0x00, 0x00, 0x0f,
200 ]),
201 name: "BLS12_PAIRING_CHECK",
202 active_since_fork: Prague,
203};
204
205pub const BLS12_MAP_FP_TO_G1: Precompile = Precompile {
206 address: H160([
207 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
208 0x00, 0x00, 0x00, 0x00, 0x10,
209 ]),
210 name: "BLS12_MAP_FP_TO_G1",
211 active_since_fork: Prague,
212};
213
214pub const BLS12_MAP_FP2_TO_G2: Precompile = Precompile {
215 address: H160([
216 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
217 0x00, 0x00, 0x00, 0x00, 0x11,
218 ]),
219 name: "BLS12_MAP_FP2_TO_G2",
220 active_since_fork: Prague,
221};
222
223pub const P256VERIFY: Precompile = Precompile {
224 address: H160([
225 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
226 0x00, 0x00, 0x00, 0x01, 0x00,
227 ]),
228 name: "P256VERIFY",
229 active_since_fork: Osaka,
230};
231
232pub const EXECUTE: Precompile = Precompile {
242 address: H160([
243 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
244 0x00, 0x00, 0x00, 0x01, 0x01,
245 ]),
246 name: "EXECUTE",
247 active_since_fork: LStar,
248};
249
250pub const PRECOMPILES: [Precompile; 18] = [
251 ECRECOVER,
252 SHA2_256,
253 RIPEMD_160,
254 IDENTITY,
255 MODEXP,
256 ECADD,
257 ECMUL,
258 ECPAIRING,
259 BLAKE2F,
260 POINT_EVALUATION,
261 BLS12_G1ADD,
262 BLS12_G1MSM,
263 BLS12_G2ADD,
264 BLS12_G2MSM,
265 BLS12_PAIRING_CHECK,
266 BLS12_MAP_FP_TO_G1,
267 BLS12_MAP_FP2_TO_G2,
268 P256VERIFY,
269];
270
271pub fn precompiles_for_fork(fork: Fork) -> impl Iterator<Item = Precompile> {
272 PRECOMPILES
273 .into_iter()
274 .filter(move |precompile| precompile.active_since_fork <= fork)
275}
276
277pub fn is_precompile(address: &Address, fork: Fork, vm_type: VMType) -> bool {
278 if fork >= Fork::LStar && matches!(vm_type, VMType::L1) && *address == EXECUTE.address {
279 return true;
280 }
281 (matches!(vm_type, VMType::L2(_)) && *address == P256VERIFY.address)
282 || precompiles_for_fork(fork).any(|precompile| precompile.address == *address)
283}
284
285#[derive(Debug, Default, Clone, PartialEq, Eq)]
298pub struct PrecompileMoves {
299 suppressed: BTreeSet<Address>,
301 relocated: BTreeMap<Address, Address>,
303}
304
305impl PrecompileMoves {
306 pub fn from_parts(
313 moves: impl IntoIterator<Item = (Address, Address)>,
314 overridden: impl IntoIterator<Item = Address>,
315 ) -> Self {
316 let mut result = Self {
317 suppressed: overridden.into_iter().collect(),
318 relocated: BTreeMap::new(),
319 };
320 for (source, destination) in moves {
321 result.relocated.insert(destination, source);
322 }
323 result
324 }
325
326 pub fn is_empty(&self) -> bool {
327 self.suppressed.is_empty() && self.relocated.is_empty()
328 }
329
330 pub fn is_suppressed(&self, address: &Address) -> bool {
332 self.suppressed.contains(address)
333 }
334
335 pub fn source_for(&self, address: &Address) -> Option<Address> {
338 self.relocated.get(address).copied()
339 }
340}
341
342pub fn is_precompile_with_moves(
346 address: &Address,
347 fork: Fork,
348 vm_type: VMType,
349 moves: Option<&PrecompileMoves>,
350) -> bool {
351 let Some(moves) = moves else {
352 return is_precompile(address, fork, vm_type);
353 };
354 if let Some(source) = moves.source_for(address) {
359 return is_precompile(&source, fork, vm_type);
360 }
361 if moves.is_suppressed(address) {
362 return false;
363 }
364 is_precompile(address, fork, vm_type)
365}
366
367pub fn effective_precompile_address(address: Address, moves: Option<&PrecompileMoves>) -> Address {
370 moves
371 .and_then(|m| m.source_for(&address))
372 .unwrap_or(address)
373}
374
375pub const PRECOMPILE_CACHE_MAX_BYTES: usize = 128 * 1024 * 1024;
379
380pub const PRECOMPILE_CACHE_MAX_INPUT_BYTES: usize = 4096;
386
387#[derive(Default)]
388struct PrecompileCacheEntries {
389 map: FxHashMap<Address, SeededHashMap<Bytes, (Bytes, u64)>>,
393 used_bytes: usize,
395}
396
397impl PrecompileCacheEntries {
398 fn contains(&self, address: &Address, calldata: &[u8]) -> bool {
399 self.map
400 .get(address)
401 .is_some_and(|calls| calls.contains_key(calldata))
402 }
403}
404
405pub struct PrecompileCache {
413 cache: RwLock<PrecompileCacheEntries>,
414 max_bytes: usize,
415}
416
417impl Default for PrecompileCache {
418 fn default() -> Self {
419 Self::with_max_bytes(PRECOMPILE_CACHE_MAX_BYTES)
420 }
421}
422
423impl PrecompileCache {
424 pub fn new() -> Self {
425 Self::default()
426 }
427
428 pub fn with_max_bytes(max_bytes: usize) -> Self {
430 Self {
431 cache: RwLock::default(),
432 max_bytes,
433 }
434 }
435
436 pub fn entry_size(calldata_len: usize, output_len: usize) -> usize {
441 calldata_len
442 .saturating_add(output_len)
443 .saturating_add(size_of::<(Bytes, (Bytes, u64))>())
444 }
445
446 pub fn get(&self, address: &Address, calldata: &[u8]) -> Option<(Bytes, u64)> {
447 let entries = self
451 .cache
452 .read()
453 .unwrap_or_else(|poisoned| poisoned.into_inner());
454 entries.map.get(address)?.get(calldata).cloned()
455 }
456
457 pub fn insert(&self, address: Address, calldata: &[u8], output: &[u8], gas_cost: u64) {
466 let entry_size = Self::entry_size(calldata.len(), output.len());
467 {
468 let entries = self
469 .cache
470 .read()
471 .unwrap_or_else(|poisoned| poisoned.into_inner());
472 if entries.used_bytes.saturating_add(entry_size) > self.max_bytes
473 || entries.contains(&address, calldata)
474 {
475 return;
476 }
477 }
478 let calldata = Bytes::copy_from_slice(calldata);
479 let output = Bytes::copy_from_slice(output);
480 let mut guard = self
481 .cache
482 .write()
483 .unwrap_or_else(|poisoned| poisoned.into_inner());
484 let entries = &mut *guard;
485 let used_bytes = entries.used_bytes.saturating_add(entry_size);
487 if used_bytes > self.max_bytes {
488 return;
489 }
490 if let Entry::Vacant(slot) = entries.map.entry(address).or_default().entry(calldata) {
493 slot.insert((output, gas_cost));
494 entries.used_bytes = used_bytes;
495 }
496 }
497}
498
499#[expect(clippy::as_conversions, clippy::indexing_slicing)]
500pub fn execute_precompile(
501 address: Address,
502 calldata: &[u8],
503 gas_remaining: &mut u64,
504 fork: Fork,
505 cache: Option<&PrecompileCache>,
506 crypto: &dyn Crypto,
507 _stateless_validator: Option<&dyn crate::StatelessValidator>,
508) -> Result<Bytes, VMError> {
509 type PrecompileFn = fn(&[u8], &mut u64, Fork, &dyn Crypto) -> Result<Bytes, VMError>;
510
511 const PRECOMPILES: [Option<PrecompileFn>; 512] = const {
512 let mut precompiles = [const { None }; 512];
513 precompiles[ECRECOVER.address.0[19] as usize] = Some(ecrecover as PrecompileFn);
514 precompiles[IDENTITY.address.0[19] as usize] = Some(identity as PrecompileFn);
515 precompiles[SHA2_256.address.0[19] as usize] = Some(sha2_256 as PrecompileFn);
516 precompiles[RIPEMD_160.address.0[19] as usize] = Some(ripemd_160 as PrecompileFn);
517 precompiles[MODEXP.address.0[19] as usize] = Some(modexp as PrecompileFn);
518 precompiles[ECADD.address.0[19] as usize] = Some(ecadd as PrecompileFn);
519 precompiles[ECMUL.address.0[19] as usize] = Some(ecmul as PrecompileFn);
520 precompiles[ECPAIRING.address.0[19] as usize] = Some(ecpairing as PrecompileFn);
521 precompiles[BLAKE2F.address.0[19] as usize] = Some(blake2f as PrecompileFn);
522 precompiles[POINT_EVALUATION.address.0[19] as usize] =
523 Some(point_evaluation as PrecompileFn);
524 precompiles[BLS12_G1ADD.address.0[19] as usize] = Some(bls12_g1add as PrecompileFn);
525 precompiles[BLS12_G1MSM.address.0[19] as usize] = Some(bls12_g1msm as PrecompileFn);
526 precompiles[BLS12_G2ADD.address.0[19] as usize] = Some(bls12_g2add as PrecompileFn);
527 precompiles[BLS12_G2MSM.address.0[19] as usize] = Some(bls12_g2msm as PrecompileFn);
528 precompiles[BLS12_PAIRING_CHECK.address.0[19] as usize] =
529 Some(bls12_pairing_check as PrecompileFn);
530 precompiles[BLS12_MAP_FP_TO_G1.address.0[19] as usize] =
531 Some(bls12_map_fp_to_g1 as PrecompileFn);
532 precompiles[BLS12_MAP_FP2_TO_G2.address.0[19] as usize] =
533 Some(bls12_map_fp2_to_g2 as PrecompileFn);
534 precompiles
535 [u16::from_be_bytes([P256VERIFY.address.0[18], P256VERIFY.address.0[19]]) as usize] =
536 Some(p_256_verify as PrecompileFn);
537 precompiles
538 };
539
540 if fork >= Fork::LStar && address == EXECUTE.address {
543 return crate::execute_precompile::execute_precompile(
544 calldata,
545 gas_remaining,
546 fork,
547 crypto,
548 _stateless_validator,
549 );
550 }
551
552 if address[0..18] != [0u8; 18] {
553 return Err(VMError::Internal(InternalError::InvalidPrecompileAddress));
554 }
555 let index = u16::from_be_bytes([address[18], address[19]]) as usize;
556
557 let precompile = PRECOMPILES
558 .get(index)
559 .copied()
560 .flatten()
561 .ok_or(VMError::Internal(InternalError::InvalidPrecompileAddress))?;
562
563 let cache = cache.filter(|_| {
565 address != IDENTITY.address && calldata.len() <= PRECOMPILE_CACHE_MAX_INPUT_BYTES
566 });
567 if let Some((output, gas_cost)) = cache.and_then(|c| c.get(&address, calldata)) {
568 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
569 return Ok(output);
570 }
571
572 #[cfg(feature = "perf_opcode_timings")]
573 let precompile_time_start = std::time::Instant::now();
574
575 let gas_before = *gas_remaining;
576 let result = precompile(calldata, gas_remaining, fork, crypto);
577
578 #[cfg(feature = "perf_opcode_timings")]
579 {
580 let time = precompile_time_start.elapsed();
581 let mut timings = crate::timings::PRECOMPILES_TIMINGS.lock().expect("poison");
582 timings.update(address, time);
583 }
584
585 if let Some(cache) = cache
587 && let Ok(output) = &result
588 {
589 let gas_cost = gas_before.saturating_sub(*gas_remaining);
590 cache.insert(address, calldata, output, gas_cost);
591 }
592
593 result
594}
595
596pub(crate) fn increase_precompile_consumed_gas(
598 gas_cost: u64,
599 gas_remaining: &mut u64,
600) -> Result<(), VMError> {
601 *gas_remaining = gas_remaining
602 .checked_sub(gas_cost)
603 .ok_or(PrecompileError::NotEnoughGas)?;
604 Ok(())
605}
606
607#[inline(always)]
610pub(crate) fn fill_with_zeros(calldata: &[u8], target_len: usize) -> Cow<'_, [u8]> {
611 if calldata.len() >= target_len {
612 return Cow::Borrowed(calldata);
613 }
614 let mut padded_calldata = calldata.to_vec();
615 padded_calldata.resize(target_len, 0);
616 Cow::Owned(padded_calldata)
617}
618
619fn crypto_error_to_precompile(e: CryptoError) -> VMError {
620 match e {
621 CryptoError::InvalidPoint(_) => PrecompileError::InvalidPoint.into(),
622 CryptoError::InvalidInput(_) => PrecompileError::ParsingInputError.into(),
623 CryptoError::InvalidSignature => PrecompileError::ParsingInputError.into(),
624 CryptoError::InvalidRecoveryId => PrecompileError::ParsingInputError.into(),
625 CryptoError::RecoveryFailed => PrecompileError::ParsingInputError.into(),
626 CryptoError::VerificationFailed => PrecompileError::ParsingInputError.into(),
627 CryptoError::Other(_) => PrecompileError::ParsingInputError.into(),
628 CryptoError::Unsupported(msg) => InternalError::Custom(msg.to_string()).into(),
632 }
633}
634
635fn bls12_g1_crypto_error(e: CryptoError) -> VMError {
638 match e {
639 CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G1PointNotInCurve.into(),
640 other => crypto_error_to_precompile(other),
641 }
642}
643
644fn bls12_g2_crypto_error(e: CryptoError) -> VMError {
647 match e {
648 CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G2PointNotInCurve.into(),
649 other => crypto_error_to_precompile(other),
650 }
651}
652
653fn bls12_msm_pairing_crypto_error(e: CryptoError) -> VMError {
657 match e {
658 CryptoError::InvalidPoint(_) => PrecompileError::ParsingInputError.into(),
659 other => crypto_error_to_precompile(other),
660 }
661}
662
663pub fn ecrecover(
673 calldata: &[u8],
674 gas_remaining: &mut u64,
675 _fork: Fork,
676 crypto: &dyn Crypto,
677) -> Result<Bytes, VMError> {
678 use crate::gas_cost::ECRECOVER_COST;
679
680 increase_precompile_consumed_gas(ECRECOVER_COST, gas_remaining)?;
681
682 const INPUT_LEN: usize = 128;
683 const WORD: usize = 32;
684
685 let input = fill_with_zeros(calldata, INPUT_LEN);
686
687 #[expect(
688 clippy::indexing_slicing,
689 reason = "fill_with_zeros guarantees len >= 128"
690 )]
691 let raw_hash: &[u8] = &input[0..WORD];
692 #[expect(
693 clippy::indexing_slicing,
694 reason = "fill_with_zeros guarantees len >= 128"
695 )]
696 let raw_v: &[u8] = &input[WORD..WORD * 2];
697 #[expect(
698 clippy::indexing_slicing,
699 reason = "fill_with_zeros guarantees len >= 128"
700 )]
701 let raw_sig: &[u8] = &input[WORD * 2..WORD * 2 + 64];
702
703 let recid_byte: u8 = match u8::try_from(u256_from_big_endian(raw_v)) {
705 Ok(27) => 0,
706 Ok(28) => 1,
707 _ => return Ok(Bytes::new()),
708 };
709
710 let msg_hash: [u8; 32] = raw_hash
711 .try_into()
712 .map_err(|_| InternalError::TypeConversion)?;
713 let sig: [u8; 64] = raw_sig
714 .try_into()
715 .map_err(|_| InternalError::TypeConversion)?;
716
717 let pk_hash = match crypto.secp256k1_ecrecover(&sig, recid_byte, &msg_hash) {
718 Ok(h) => h,
719 Err(_) => return Ok(Bytes::new()),
720 };
721
722 let mut out = [0u8; 32];
724 out[12..32].copy_from_slice(&pk_hash[12..32]);
725
726 Ok(Bytes::copy_from_slice(&out))
727}
728
729pub fn identity(
731 calldata: &[u8],
732 gas_remaining: &mut u64,
733 _fork: Fork,
734 _crypto: &dyn Crypto,
735) -> Result<Bytes, VMError> {
736 let gas_cost = gas_cost::identity(calldata.len())?;
737
738 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
739
740 Ok(Bytes::copy_from_slice(calldata))
741}
742
743pub fn sha2_256(
745 calldata: &[u8],
746 gas_remaining: &mut u64,
747 _fork: Fork,
748 crypto: &dyn Crypto,
749) -> Result<Bytes, VMError> {
750 let gas_cost = gas_cost::sha2_256(calldata.len())?;
751
752 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
753
754 let digest = crypto.sha256(calldata);
755 Ok(Bytes::copy_from_slice(&digest))
756}
757
758pub fn ripemd_160(
760 calldata: &[u8],
761 gas_remaining: &mut u64,
762 _fork: Fork,
763 crypto: &dyn Crypto,
764) -> Result<Bytes, VMError> {
765 let gas_cost = gas_cost::ripemd_160(calldata.len())?;
766
767 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
768
769 let result = crypto.ripemd160(calldata);
770 Ok(Bytes::copy_from_slice(&result))
771}
772
773#[expect(clippy::indexing_slicing, reason = "bounds checked at start")]
775pub fn modexp(
776 calldata: &[u8],
777 gas_remaining: &mut u64,
778 fork: Fork,
779 crypto: &dyn Crypto,
780) -> Result<Bytes, VMError> {
781 let calldata = fill_with_zeros(calldata, 96);
783
784 if fork < Fork::Osaka {
786 let base_size_bytes: [u8; 32] = calldata[0..32].try_into()?;
787 let modulus_size_bytes: [u8; 32] = calldata[64..96].try_into()?;
788 const ZERO_BYTES: [u8; 32] = [0u8; 32];
789
790 if base_size_bytes == ZERO_BYTES && modulus_size_bytes == ZERO_BYTES {
791 increase_precompile_consumed_gas(MODEXP_STATIC_COST, gas_remaining)?;
793 return Ok(Bytes::new());
794 }
795 }
796
797 let base_size = u256_from_big_endian_const::<32>(calldata[0..32].try_into()?);
800 let modulus_size = u256_from_big_endian_const::<32>(calldata[64..96].try_into()?);
801 let exponent_size = u256_from_big_endian_const::<32>(calldata[32..64].try_into()?);
802
803 if fork >= Fork::Osaka {
804 if base_size > U256::from(1024) {
805 return Err(PrecompileError::ModExpBaseTooLarge.into());
806 }
807 if exponent_size > U256::from(1024) {
808 return Err(PrecompileError::ModExpExpTooLarge.into());
809 }
810 if modulus_size > U256::from(1024) {
811 return Err(PrecompileError::ModExpModulusTooLarge.into());
812 }
813 }
814
815 let base_size = usize::try_from(base_size).map_err(|_| PrecompileError::ParsingInputError)?;
817 let exponent_size =
818 usize::try_from(exponent_size).map_err(|_| PrecompileError::ParsingInputError)?;
819 let modulus_size =
820 usize::try_from(modulus_size).map_err(|_| PrecompileError::ParsingInputError)?;
821
822 let exponent_start = base_size.saturating_add(96);
830 let exponent_head_size = exponent_size.min(32);
831 let exponent_head = get_slice_or_default(
832 &calldata,
833 exponent_start,
834 exponent_start.saturating_add(exponent_head_size),
835 exponent_head_size,
836 )?;
837
838 use malachite::Natural;
840 use malachite::base::num::conversion::traits::*;
841 let exp_first_32 = Natural::from_power_of_2_digits_desc(8u64, exponent_head.iter().cloned())
842 .ok_or(InternalError::TypeConversion)?;
843
844 let gas_cost = gas_cost::modexp(&exp_first_32, base_size, exponent_size, modulus_size, fork)?;
845
846 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
847
848 if base_size == 0 && modulus_size == 0 {
849 return Ok(Bytes::new());
850 }
851
852 let base_limit = base_size.checked_add(96).ok_or(InternalError::Overflow)?;
854
855 let exponent_limit = exponent_size
856 .checked_add(base_limit)
857 .ok_or(InternalError::Overflow)?;
858
859 let modulus_limit = modulus_size
860 .checked_add(exponent_limit)
861 .ok_or(InternalError::Overflow)?;
862
863 let b = get_slice_or_default(&calldata, 96, base_limit, base_size)?;
864 let e = get_slice_or_default(&calldata, base_limit, exponent_limit, exponent_size)?;
865 let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, modulus_size)?;
866
867 let result = crypto
868 .modexp(&b, &e, &m)
869 .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
870
871 let res_bytes = increase_left_pad(&Bytes::from(result), modulus_size);
872
873 Ok(res_bytes.slice(..modulus_size))
874}
875
876#[expect(clippy::indexing_slicing, reason = "bounds checked")]
884fn get_slice_or_default<'c>(
885 calldata: &'c [u8],
886 lower_limit: usize,
887 upper_limit: usize,
888 size_to_expand: usize,
889) -> Result<Cow<'c, [u8]>, VMError> {
890 let upper_limit = calldata.len().min(upper_limit);
891 if let Some(data) = calldata.get(lower_limit..upper_limit)
892 && !data.is_empty()
893 {
894 if data.len() == size_to_expand {
895 return Ok(data.into());
896 }
897 let mut extended = Vec::new();
898 extended
899 .try_reserve_exact(size_to_expand)
900 .map_err(|_| ExceptionalHalt::OutOfGas)?;
901 let copy_size = size_to_expand.min(data.len());
902 extended.extend_from_slice(&data[..copy_size]);
903 extended.resize(size_to_expand, 0);
904 return Ok(extended.into());
905 }
906 Ok(Vec::new().into())
907}
908
909#[inline(always)]
911pub fn increase_left_pad(result: &Bytes, m_size: usize) -> Bytes {
912 #[expect(
913 clippy::arithmetic_side_effects,
914 clippy::indexing_slicing,
915 reason = "overflow checked with the if condition, bounds checked"
916 )]
917 if result.len() < m_size {
918 let mut padded_result = vec![0u8; m_size];
919 let size_diff = m_size - result.len();
920 padded_result[size_diff..].copy_from_slice(result);
921
922 padded_result.into()
923 } else {
924 result.clone()
926 }
927}
928
929pub fn ecadd(
931 calldata: &[u8],
932 gas_remaining: &mut u64,
933 _fork: Fork,
934 crypto: &dyn Crypto,
935) -> Result<Bytes, VMError> {
936 let calldata = fill_with_zeros(calldata, 128);
938
939 increase_precompile_consumed_gas(ECADD_COST, gas_remaining)?;
940
941 let (Some(first_point), Some(second_point)) =
942 (parse_bn254_g1(&calldata, 0), parse_bn254_g1(&calldata, 64))
943 else {
944 return Err(InternalError::Slicing.into());
945 };
946 validate_bn254_g1_coords(&first_point)?;
947 validate_bn254_g1_coords(&second_point)?;
948
949 #[expect(clippy::indexing_slicing, reason = "calldata padded to 128 bytes")]
950 let result = crypto
951 .bn254_g1_add(&calldata[..64], &calldata[64..128])
952 .map_err(crypto_error_to_precompile)?;
953
954 Ok(Bytes::copy_from_slice(&result))
955}
956
957pub fn ecmul(
959 calldata: &[u8],
960 gas_remaining: &mut u64,
961 _fork: Fork,
962 crypto: &dyn Crypto,
963) -> Result<Bytes, VMError> {
964 let calldata = fill_with_zeros(calldata, 96);
966 increase_precompile_consumed_gas(ECMUL_COST, gas_remaining)?;
967
968 let (Some(g1), Some(_scalar)) = (
969 parse_bn254_g1(&calldata, 0),
970 parse_bn254_scalar(&calldata, 64),
971 ) else {
972 return Err(InternalError::Slicing.into());
973 };
974 validate_bn254_g1_coords(&g1)?;
975
976 #[expect(clippy::indexing_slicing, reason = "calldata padded to 96 bytes")]
977 let result = crypto
978 .bn254_g1_mul(&calldata[..64], &calldata[64..96])
979 .map_err(crypto_error_to_precompile)?;
980
981 Ok(Bytes::copy_from_slice(&result))
982}
983
984const ALT_BN128_PRIME: U256 = U256([
985 0x3c208c16d87cfd47,
986 0x97816a916871ca8d,
987 0xb85045b68181585d,
988 0x30644e72e131a029,
989]);
990
991pub struct G1(U256, U256);
992impl G1 {
993 pub fn is_zero(&self) -> bool {
995 self.0.is_zero() && self.1.is_zero()
996 }
997}
998pub struct G2(U256, U256, U256, U256);
999impl G2 {
1000 pub fn is_zero(&self) -> bool {
1002 self.0.is_zero() && self.1.is_zero() && self.2.is_zero() && self.3.is_zero()
1003 }
1004}
1005
1006#[inline]
1008fn parse_bn254_scalar(buf: &[u8], offset: usize) -> Option<U256> {
1009 buf.get(offset..offset.checked_add(32)?)
1010 .map(u256_from_big_endian)
1011}
1012
1013#[inline]
1015fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option<G1> {
1016 let chunk = buf.get(offset..offset.checked_add(64)?)?;
1017 let (x_bytes, y_bytes) = chunk.split_at_checked(32)?;
1018 Some(G1(
1019 u256_from_big_endian(x_bytes),
1020 u256_from_big_endian(y_bytes),
1021 ))
1022}
1023
1024fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option<G2> {
1026 let chunk = buf.get(offset..offset.checked_add(128)?)?;
1027 let (g2_xy, rest) = chunk.split_at_checked(32)?;
1028 let (g2_xx, rest) = rest.split_at_checked(32)?;
1029 let (g2_yy, g2_yx) = rest.split_at_checked(32)?;
1030 Some(G2(
1031 u256_from_big_endian(g2_xx),
1032 u256_from_big_endian(g2_xy),
1033 u256_from_big_endian(g2_yx),
1034 u256_from_big_endian(g2_yy),
1035 ))
1036}
1037
1038#[inline]
1039fn validate_bn254_g1_coords(g1: &G1) -> Result<(), VMError> {
1040 if g1.0 >= ALT_BN128_PRIME || g1.1 >= ALT_BN128_PRIME {
1042 return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
1043 }
1044 Ok(())
1045}
1046
1047#[inline]
1048fn validate_bn254_g2_coords(g2: &G2) -> Result<(), VMError> {
1049 if g2.0 >= ALT_BN128_PRIME
1051 || g2.1 >= ALT_BN128_PRIME
1052 || g2.2 >= ALT_BN128_PRIME
1053 || g2.3 >= ALT_BN128_PRIME
1054 {
1055 return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
1056 }
1057 Ok(())
1058}
1059
1060pub fn ecpairing(
1062 calldata: &[u8],
1063 gas_remaining: &mut u64,
1064 _fork: Fork,
1065 crypto: &dyn Crypto,
1066) -> Result<Bytes, VMError> {
1067 if !calldata.len().is_multiple_of(192) {
1069 return Err(PrecompileError::ParsingInputError.into());
1070 }
1071
1072 let inputs_amount = calldata.len() / 192;
1073 let gas_cost = gas_cost::ecpairing(inputs_amount)?;
1074 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1075
1076 let mut pairs: Vec<(&[u8], &[u8])> = Vec::new();
1077 for input in calldata.chunks_exact(192) {
1078 let (Some(g1), Some(g2)) = (parse_bn254_g1(input, 0), parse_bn254_g2(input, 64)) else {
1079 return Err(InternalError::Slicing.into());
1080 };
1081 validate_bn254_g1_coords(&g1)?;
1082 validate_bn254_g2_coords(&g2)?;
1083 #[expect(clippy::indexing_slicing, reason = "chunks_exact guarantees 192 bytes")]
1084 pairs.push((&input[..64], &input[64..192]));
1085 }
1086
1087 let pairing_check = if pairs.is_empty() {
1088 true
1089 } else {
1090 crypto
1091 .bn254_pairing_check(&pairs)
1092 .map_err(crypto_error_to_precompile)?
1093 };
1094
1095 let mut result = [0; 32];
1096 result[31] = u8::from(pairing_check);
1097 Ok(Bytes::from_owner(result))
1098}
1099
1100pub fn blake2f(
1102 calldata: &[u8],
1103 gas_remaining: &mut u64,
1104 _fork: Fork,
1105 crypto: &dyn Crypto,
1106) -> Result<Bytes, VMError> {
1107 if calldata.len() != 213 {
1108 return Err(PrecompileError::ParsingInputError.into());
1109 }
1110
1111 #[expect(clippy::indexing_slicing, reason = "length checked above")]
1112 let mut calldata = &calldata[..213];
1113
1114 let rounds = calldata.get_u32();
1115
1116 let gas_cost = u64::from(rounds) * BLAKE2F_ROUND_COST;
1117 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1118
1119 let mut h = [0; 8];
1120
1121 h.copy_from_slice(&std::array::from_fn::<u64, 8, _>(|_| calldata.get_u64_le()));
1122
1123 let mut m = [0; 16];
1124
1125 m.copy_from_slice(&std::array::from_fn::<u64, 16, _>(|_| {
1126 calldata.get_u64_le()
1127 }));
1128
1129 let mut t = [0; 2];
1130 t.copy_from_slice(&std::array::from_fn::<u64, 2, _>(|_| calldata.get_u64_le()));
1131
1132 let f = calldata.get_u8();
1133 if f != 0 && f != 1 {
1134 return Err(PrecompileError::ParsingInputError.into());
1135 }
1136 let f = f == 1;
1137
1138 crypto.blake2_compress(rounds, &mut h, m, t, f);
1139
1140 Ok(Bytes::from_iter(
1141 h.into_iter().flat_map(|value| value.to_le_bytes()),
1142 ))
1143}
1144
1145fn kzg_commitment_to_versioned_hash(commitment_bytes: &[u8; 48], crypto: &dyn Crypto) -> H256 {
1147 let mut versioned_hash: [u8; 32] = crypto.sha256(commitment_bytes);
1148 versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
1149 versioned_hash.into()
1150}
1151
1152const POINT_EVALUATION_OUTPUT_BYTES: [u8; 64] = [
1153 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1155 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
1156 0x73, 0xED, 0xA7, 0x53, 0x29, 0x9D, 0x7D, 0x48, 0x33, 0x39, 0xD8, 0x08, 0x09, 0xA1, 0xD8, 0x05,
1158 0x53, 0xBD, 0xA4, 0x02, 0xFF, 0xFE, 0x5B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01,
1159];
1160
1161fn point_evaluation(
1163 calldata: &[u8],
1164 gas_remaining: &mut u64,
1165 _fork: Fork,
1166 crypto: &dyn Crypto,
1167) -> Result<Bytes, VMError> {
1168 if calldata.len() != 192 {
1169 return Err(PrecompileError::ParsingInputError.into());
1170 }
1171
1172 let gas_cost = POINT_EVALUATION_COST;
1174 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1175
1176 let versioned_hash: [u8; 32] = calldata
1178 .get(..32)
1179 .ok_or(InternalError::Slicing)?
1180 .try_into()
1181 .map_err(|_| InternalError::TypeConversion)?;
1182
1183 let z: [u8; 32] = calldata
1184 .get(32..64)
1185 .ok_or(InternalError::Slicing)?
1186 .try_into()
1187 .map_err(|_| InternalError::TypeConversion)?;
1188
1189 let y: [u8; 32] = calldata
1190 .get(64..96)
1191 .ok_or(InternalError::Slicing)?
1192 .try_into()
1193 .map_err(|_| InternalError::TypeConversion)?;
1194
1195 let commitment: [u8; 48] = calldata
1196 .get(96..144)
1197 .ok_or(InternalError::Slicing)?
1198 .try_into()
1199 .map_err(|_| InternalError::TypeConversion)?;
1200
1201 let proof: [u8; 48] = calldata
1202 .get(144..192)
1203 .ok_or(InternalError::Slicing)?
1204 .try_into()
1205 .map_err(|_| InternalError::TypeConversion)?;
1206
1207 if kzg_commitment_to_versioned_hash(&commitment, crypto) != H256::from(versioned_hash) {
1211 return Err(PrecompileError::ParsingInputError.into());
1212 }
1213
1214 crypto
1216 .verify_kzg_proof(&z, &y, &commitment, &proof)
1217 .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
1218
1219 let output = POINT_EVALUATION_OUTPUT_BYTES.to_vec();
1222
1223 Ok(Bytes::from(output))
1224}
1225
1226pub fn p_256_verify(
1231 calldata: &[u8],
1232 gas_remaining: &mut u64,
1233 _fork: Fork,
1234 crypto: &dyn Crypto,
1235) -> Result<Bytes, VMError> {
1236 increase_precompile_consumed_gas(P256_VERIFY_COST, gas_remaining)
1237 .map_err(|_| PrecompileError::NotEnoughGas)?;
1238
1239 if calldata.len() != 160 {
1241 return Ok(Bytes::new());
1242 }
1243
1244 #[expect(
1246 clippy::indexing_slicing,
1247 reason = "length of the calldata is checked before slicing"
1248 )]
1249 let msg: &[u8; 32] = calldata[0..32].try_into()?;
1250 #[expect(clippy::indexing_slicing, reason = "length checked")]
1251 let r: &[u8; 32] = calldata[32..64].try_into()?;
1252 #[expect(clippy::indexing_slicing, reason = "length checked")]
1253 let s: &[u8; 32] = calldata[64..96].try_into()?;
1254 #[expect(clippy::indexing_slicing, reason = "length checked")]
1255 let pk_x: &[u8; 32] = calldata[96..128].try_into()?;
1256 #[expect(clippy::indexing_slicing, reason = "length checked")]
1257 let pk_y: &[u8; 32] = calldata[128..160].try_into()?;
1258
1259 let mut sig_bytes = [0u8; 64];
1261 sig_bytes[..32].copy_from_slice(r);
1262 sig_bytes[32..].copy_from_slice(s);
1263
1264 let mut pk_bytes = [0u8; 64];
1265 pk_bytes[..32].copy_from_slice(pk_x);
1266 pk_bytes[32..].copy_from_slice(pk_y);
1267
1268 let success = crypto.secp256r1_verify(msg, &sig_bytes, &pk_bytes);
1269
1270 if success {
1273 const RESULT: [u8; 32] = [
1274 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,
1275 0, 0, 1,
1276 ];
1277 Ok(Bytes::from_static(&RESULT))
1278 } else {
1279 Ok(Bytes::new())
1280 }
1281}
1282
1283fn parse_bls12_padded_fp(padded: &[u8; 64]) -> Result<[u8; 48], VMError> {
1286 if padded[..16] != [0u8; 16] {
1287 return Err(PrecompileError::ParsingInputError.into());
1288 }
1289 let fp: [u8; 48] = padded[16..64]
1290 .try_into()
1291 .map_err(|_| InternalError::TypeConversion)?;
1292 Ok(fp)
1293}
1294
1295pub fn bls12_g1add(
1296 calldata: &[u8],
1297 gas_remaining: &mut u64,
1298 _fork: Fork,
1299 crypto: &dyn Crypto,
1300) -> Result<Bytes, VMError> {
1301 let (x_data, calldata) = calldata
1302 .split_first_chunk::<128>()
1303 .ok_or(PrecompileError::ParsingInputError)?;
1304 let (y_data, calldata) = calldata
1305 .split_first_chunk::<128>()
1306 .ok_or(PrecompileError::ParsingInputError)?;
1307 if !calldata.is_empty() {
1308 return Err(PrecompileError::ParsingInputError.into());
1309 }
1310
1311 increase_precompile_consumed_gas(BLS12_381_G1ADD_COST, gas_remaining)
1313 .map_err(|_| PrecompileError::NotEnoughGas)?;
1314
1315 let ax = parse_bls12_padded_fp(
1317 x_data[..64]
1318 .try_into()
1319 .map_err(|_| InternalError::TypeConversion)?,
1320 )?;
1321 let ay = parse_bls12_padded_fp(
1322 x_data[64..128]
1323 .try_into()
1324 .map_err(|_| InternalError::TypeConversion)?,
1325 )?;
1326 let bx = parse_bls12_padded_fp(
1327 y_data[..64]
1328 .try_into()
1329 .map_err(|_| InternalError::TypeConversion)?,
1330 )?;
1331 let by = parse_bls12_padded_fp(
1332 y_data[64..128]
1333 .try_into()
1334 .map_err(|_| InternalError::TypeConversion)?,
1335 )?;
1336
1337 let result = crypto
1338 .bls12_381_g1_add((ax, ay), (bx, by))
1339 .map_err(bls12_g1_crypto_error)?;
1340
1341 let mut output = [0u8; 128];
1343 output[16..64].copy_from_slice(&result[..48]);
1344 output[80..128].copy_from_slice(&result[48..96]);
1345 Ok(Bytes::copy_from_slice(&output))
1346}
1347
1348pub fn bls12_g1msm(
1349 calldata: &[u8],
1350 gas_remaining: &mut u64,
1351 _fork: Fork,
1352 crypto: &dyn Crypto,
1353) -> Result<Bytes, VMError> {
1354 if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G1_MSM_PAIR_LENGTH) {
1355 return Err(PrecompileError::ParsingInputError.into());
1356 }
1357
1358 let k = calldata.len() / BLS12_381_G1_MSM_PAIR_LENGTH;
1359 let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G1_K_DISCOUNT, G1_MUL_COST)?;
1360 increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1361
1362 #[allow(clippy::type_complexity)]
1363 let mut pairs: Vec<(([u8; 48], [u8; 48]), [u8; 32])> = Vec::with_capacity(k);
1364
1365 #[expect(
1366 clippy::arithmetic_side_effects,
1367 clippy::indexing_slicing,
1368 reason = "bounds checked"
1369 )]
1370 for i in 0..k {
1371 let point_offset = i * BLS12_381_G1_MSM_PAIR_LENGTH;
1372 let scalar_offset = point_offset + 128;
1373 let pair_end = scalar_offset + 32;
1374
1375 let point_bytes = &calldata[point_offset..scalar_offset];
1376 let scalar_bytes = &calldata[scalar_offset..pair_end];
1377
1378 let px = parse_bls12_padded_fp(
1379 point_bytes[..64]
1380 .try_into()
1381 .map_err(|_| InternalError::TypeConversion)?,
1382 )?;
1383 let py = parse_bls12_padded_fp(
1384 point_bytes[64..128]
1385 .try_into()
1386 .map_err(|_| InternalError::TypeConversion)?,
1387 )?;
1388 let scalar: [u8; 32] = scalar_bytes
1389 .try_into()
1390 .map_err(|_| InternalError::TypeConversion)?;
1391
1392 pairs.push(((px, py), scalar));
1393 }
1394
1395 let result = crypto
1396 .bls12_381_g1_msm(&pairs)
1397 .map_err(bls12_msm_pairing_crypto_error)?;
1398
1399 let mut output = [0u8; 128];
1401 output[16..64].copy_from_slice(&result[..48]);
1402 output[80..128].copy_from_slice(&result[48..96]);
1403 Ok(Bytes::copy_from_slice(&output))
1404}
1405
1406pub fn bls12_g2add(
1407 calldata: &[u8],
1408 gas_remaining: &mut u64,
1409 _fork: Fork,
1410 crypto: &dyn Crypto,
1411) -> Result<Bytes, VMError> {
1412 let (x_data, calldata) = calldata
1413 .split_first_chunk::<256>()
1414 .ok_or(PrecompileError::ParsingInputError)?;
1415 let (y_data, calldata) = calldata
1416 .split_first_chunk::<256>()
1417 .ok_or(PrecompileError::ParsingInputError)?;
1418 if !calldata.is_empty() {
1419 return Err(PrecompileError::ParsingInputError.into());
1420 }
1421
1422 increase_precompile_consumed_gas(BLS12_381_G2ADD_COST, gas_remaining)
1424 .map_err(|_| PrecompileError::NotEnoughGas)?;
1425
1426 let ax0 = parse_bls12_padded_fp(
1429 x_data[0..64]
1430 .try_into()
1431 .map_err(|_| InternalError::TypeConversion)?,
1432 )?;
1433 let ax1 = parse_bls12_padded_fp(
1434 x_data[64..128]
1435 .try_into()
1436 .map_err(|_| InternalError::TypeConversion)?,
1437 )?;
1438 let ay0 = parse_bls12_padded_fp(
1439 x_data[128..192]
1440 .try_into()
1441 .map_err(|_| InternalError::TypeConversion)?,
1442 )?;
1443 let ay1 = parse_bls12_padded_fp(
1444 x_data[192..256]
1445 .try_into()
1446 .map_err(|_| InternalError::TypeConversion)?,
1447 )?;
1448
1449 let bx0 = parse_bls12_padded_fp(
1450 y_data[0..64]
1451 .try_into()
1452 .map_err(|_| InternalError::TypeConversion)?,
1453 )?;
1454 let bx1 = parse_bls12_padded_fp(
1455 y_data[64..128]
1456 .try_into()
1457 .map_err(|_| InternalError::TypeConversion)?,
1458 )?;
1459 let by0 = parse_bls12_padded_fp(
1460 y_data[128..192]
1461 .try_into()
1462 .map_err(|_| InternalError::TypeConversion)?,
1463 )?;
1464 let by1 = parse_bls12_padded_fp(
1465 y_data[192..256]
1466 .try_into()
1467 .map_err(|_| InternalError::TypeConversion)?,
1468 )?;
1469
1470 let result = crypto
1471 .bls12_381_g2_add((ax0, ax1, ay0, ay1), (bx0, bx1, by0, by1))
1472 .map_err(bls12_g2_crypto_error)?;
1473
1474 let mut output = [0u8; 256];
1478 output[16..64].copy_from_slice(&result[0..48]);
1479 output[80..128].copy_from_slice(&result[48..96]);
1480 output[144..192].copy_from_slice(&result[96..144]);
1481 output[208..256].copy_from_slice(&result[144..192]);
1482 Ok(Bytes::copy_from_slice(&output))
1483}
1484
1485pub fn bls12_g2msm(
1486 calldata: &[u8],
1487 gas_remaining: &mut u64,
1488 _fork: Fork,
1489 crypto: &dyn Crypto,
1490) -> Result<Bytes, VMError> {
1491 if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G2_MSM_PAIR_LENGTH) {
1492 return Err(PrecompileError::ParsingInputError.into());
1493 }
1494
1495 let k = calldata.len() / BLS12_381_G2_MSM_PAIR_LENGTH;
1496 let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G2_K_DISCOUNT, G2_MUL_COST)?;
1497 increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1498
1499 #[allow(clippy::type_complexity)]
1500 let mut pairs: Vec<(([u8; 48], [u8; 48], [u8; 48], [u8; 48]), [u8; 32])> =
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 point_offset = i * BLS12_381_G2_MSM_PAIR_LENGTH;
1510 let scalar_offset = point_offset + 256;
1511 let pair_end = scalar_offset + 32;
1512
1513 let point_bytes = &calldata[point_offset..scalar_offset];
1514 let scalar_bytes = &calldata[scalar_offset..pair_end];
1515
1516 let x0 = parse_bls12_padded_fp(
1517 point_bytes[0..64]
1518 .try_into()
1519 .map_err(|_| InternalError::TypeConversion)?,
1520 )?;
1521 let x1 = parse_bls12_padded_fp(
1522 point_bytes[64..128]
1523 .try_into()
1524 .map_err(|_| InternalError::TypeConversion)?,
1525 )?;
1526 let y0 = parse_bls12_padded_fp(
1527 point_bytes[128..192]
1528 .try_into()
1529 .map_err(|_| InternalError::TypeConversion)?,
1530 )?;
1531 let y1 = parse_bls12_padded_fp(
1532 point_bytes[192..256]
1533 .try_into()
1534 .map_err(|_| InternalError::TypeConversion)?,
1535 )?;
1536 let scalar: [u8; 32] = scalar_bytes
1537 .try_into()
1538 .map_err(|_| InternalError::TypeConversion)?;
1539
1540 pairs.push(((x0, x1, y0, y1), scalar));
1541 }
1542
1543 let result = crypto
1544 .bls12_381_g2_msm(&pairs)
1545 .map_err(bls12_msm_pairing_crypto_error)?;
1546
1547 let mut output = [0u8; 256];
1549 output[16..64].copy_from_slice(&result[0..48]);
1550 output[80..128].copy_from_slice(&result[48..96]);
1551 output[144..192].copy_from_slice(&result[96..144]);
1552 output[208..256].copy_from_slice(&result[144..192]);
1553 Ok(Bytes::copy_from_slice(&output))
1554}
1555
1556pub fn bls12_pairing_check(
1557 calldata: &[u8],
1558 gas_remaining: &mut u64,
1559 _fork: Fork,
1560 crypto: &dyn Crypto,
1561) -> Result<Bytes, VMError> {
1562 if calldata.is_empty()
1563 || !calldata
1564 .len()
1565 .is_multiple_of(BLS12_381_PAIRING_CHECK_PAIR_LENGTH)
1566 {
1567 return Err(PrecompileError::ParsingInputError.into());
1568 }
1569
1570 let k = calldata.len() / BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1572 let gas_cost = gas_cost::bls12_pairing_check(k)?;
1573 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1574
1575 #[allow(clippy::type_complexity)]
1576 let mut pairs: Vec<(
1577 ([u8; 48], [u8; 48]),
1578 ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
1579 )> = Vec::with_capacity(k);
1580
1581 #[expect(
1582 clippy::indexing_slicing,
1583 clippy::arithmetic_side_effects,
1584 reason = "bounds checked"
1585 )]
1586 for i in 0..k {
1587 let g1_offset = i * BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1588 let g2_offset = g1_offset + 128;
1589 let pair_end = g2_offset + 256;
1590
1591 let g1_bytes = &calldata[g1_offset..g2_offset];
1592 let g2_bytes = &calldata[g2_offset..pair_end];
1593
1594 let g1x = parse_bls12_padded_fp(
1595 g1_bytes[0..64]
1596 .try_into()
1597 .map_err(|_| InternalError::TypeConversion)?,
1598 )?;
1599 let g1y = parse_bls12_padded_fp(
1600 g1_bytes[64..128]
1601 .try_into()
1602 .map_err(|_| InternalError::TypeConversion)?,
1603 )?;
1604
1605 let g2x0 = parse_bls12_padded_fp(
1606 g2_bytes[0..64]
1607 .try_into()
1608 .map_err(|_| InternalError::TypeConversion)?,
1609 )?;
1610 let g2x1 = parse_bls12_padded_fp(
1611 g2_bytes[64..128]
1612 .try_into()
1613 .map_err(|_| InternalError::TypeConversion)?,
1614 )?;
1615 let g2y0 = parse_bls12_padded_fp(
1616 g2_bytes[128..192]
1617 .try_into()
1618 .map_err(|_| InternalError::TypeConversion)?,
1619 )?;
1620 let g2y1 = parse_bls12_padded_fp(
1621 g2_bytes[192..256]
1622 .try_into()
1623 .map_err(|_| InternalError::TypeConversion)?,
1624 )?;
1625
1626 pairs.push(((g1x, g1y), (g2x0, g2x1, g2y0, g2y1)));
1627 }
1628
1629 let result = crypto
1630 .bls12_381_pairing_check(&pairs)
1631 .map_err(bls12_msm_pairing_crypto_error)?;
1632
1633 if result {
1634 let mut out = vec![0_u8; 31];
1635 out.push(1);
1636 Ok(Bytes::from(out))
1637 } else {
1638 Ok(Bytes::copy_from_slice(&[0_u8; 32]))
1639 }
1640}
1641
1642pub fn bls12_map_fp_to_g1(
1643 calldata: &[u8],
1644 gas_remaining: &mut u64,
1645 _fork: Fork,
1646 crypto: &dyn Crypto,
1647) -> Result<Bytes, VMError> {
1648 if calldata.len() != BLS12_381_FP_VALID_INPUT_LENGTH {
1649 return Err(PrecompileError::ParsingInputError.into());
1650 }
1651
1652 increase_precompile_consumed_gas(BLS12_381_MAP_FP_TO_G1_COST, gas_remaining)?;
1654
1655 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1657 let fp = parse_bls12_padded_fp(
1658 calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1659 .try_into()
1660 .map_err(|_| InternalError::TypeConversion)?,
1661 )?;
1662
1663 let result = crypto
1664 .bls12_381_fp_to_g1(&fp)
1665 .map_err(crypto_error_to_precompile)?;
1666
1667 let mut output = [0u8; 128];
1669 output[16..64].copy_from_slice(&result[0..48]);
1670 output[80..128].copy_from_slice(&result[48..96]);
1671 Ok(Bytes::copy_from_slice(&output))
1672}
1673
1674pub fn bls12_map_fp2_to_g2(
1675 calldata: &[u8],
1676 gas_remaining: &mut u64,
1677 _fork: Fork,
1678 crypto: &dyn Crypto,
1679) -> Result<Bytes, VMError> {
1680 if calldata.len() != BLS12_381_FP2_VALID_INPUT_LENGTH {
1681 return Err(PrecompileError::ParsingInputError.into());
1682 }
1683
1684 increase_precompile_consumed_gas(BLS12_381_MAP_FP2_TO_G2_COST, gas_remaining)?;
1686
1687 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1689 let c0 = parse_bls12_padded_fp(
1690 calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1691 .try_into()
1692 .map_err(|_| InternalError::TypeConversion)?,
1693 )?;
1694 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1695 let c1 = parse_bls12_padded_fp(
1696 calldata[PADDED_FIELD_ELEMENT_SIZE_IN_BYTES..BLS12_381_FP2_VALID_INPUT_LENGTH]
1697 .try_into()
1698 .map_err(|_| InternalError::TypeConversion)?,
1699 )?;
1700
1701 if c0 == [0u8; 48] && c1 == [0u8; 48] {
1702 return Ok(Bytes::copy_from_slice(&FP2_ZERO_MAPPED_TO_G2));
1703 }
1704
1705 let result = crypto
1706 .bls12_381_fp2_to_g2((c0, c1))
1707 .map_err(crypto_error_to_precompile)?;
1708
1709 let mut output = [0u8; 256];
1713 output[16..64].copy_from_slice(&result[0..48]);
1714 output[80..128].copy_from_slice(&result[48..96]);
1715 output[144..192].copy_from_slice(&result[96..144]);
1716 output[208..256].copy_from_slice(&result[144..192]);
1717 Ok(Bytes::copy_from_slice(&output))
1718}
1719
1720#[cfg(test)]
1721#[allow(clippy::unwrap_used, clippy::arithmetic_side_effects)]
1722mod tests {
1723 use super::*;
1724 use crate::errors::ExceptionalHalt;
1725 use ethrex_common::types::Fork;
1726 use ethrex_crypto::NativeCrypto;
1727
1728 fn modexp_calldata(base_len: u64, exp_len: u64, mod_len: u64, operands: &[u8]) -> Bytes {
1732 let mut calldata = Vec::with_capacity(96 + operands.len());
1733 for len in [base_len, exp_len, mod_len] {
1734 calldata.extend_from_slice(&[0u8; 24]);
1735 calldata.extend_from_slice(&len.to_be_bytes());
1736 }
1737 calldata.extend_from_slice(operands);
1738 Bytes::from(calldata)
1739 }
1740
1741 #[test]
1747 fn modexp_pre_osaka_huge_declared_length_halts_with_out_of_gas() {
1748 for huge in [1u64 << 62, 1u64 << 33] {
1752 for (base_len, exp_len, mod_len) in [(huge, 1, 1), (1, huge, 1), (1, 1, huge)] {
1756 let calldata = modexp_calldata(base_len, exp_len, mod_len, &[1, 1, 1]);
1757 let mut gas_remaining = 30_000_000;
1758 let result = modexp(&calldata, &mut gas_remaining, Fork::Prague, &NativeCrypto);
1759 assert!(
1760 matches!(
1761 result,
1762 Err(VMError::ExceptionalHalt(
1763 ExceptionalHalt::OutOfGas
1764 | ExceptionalHalt::Precompile(PrecompileError::NotEnoughGas)
1765 ))
1766 ),
1767 "lengths ({base_len}, {exp_len}, {mod_len}) must halt with out-of-gas, got {result:?}"
1768 );
1769 }
1770 }
1771 }
1772
1773 #[test]
1776 fn modexp_eip198_example_gas_and_output() {
1777 let mut exponent = [0xffu8; 32];
1779 exponent[27..].copy_from_slice(&[0xfe, 0xff, 0xff, 0xfc, 0x2e]);
1780 let mut modulus = [0xffu8; 32];
1781 modulus[27..].copy_from_slice(&[0xfe, 0xff, 0xff, 0xfc, 0x2f]);
1782 let mut operands = vec![0x03];
1783 operands.extend_from_slice(&exponent);
1784 operands.extend_from_slice(&modulus);
1785 let calldata = modexp_calldata(1, 32, 32, &operands);
1786
1787 let mut gas_remaining = 100_000;
1788 let output = modexp(&calldata, &mut gas_remaining, Fork::Prague, &NativeCrypto).unwrap();
1789
1790 let mut expected = [0u8; 32];
1791 expected[31] = 1;
1792 assert_eq!(output.as_ref(), expected);
1793 assert_eq!(100_000 - gas_remaining, 1360);
1794 }
1795
1796 #[test]
1801 fn modexp_truncated_calldata_prices_zero_padded_exponent_head() {
1802 let calldata = modexp_calldata(1, 32, 32, &[0x03, 0x80]);
1803
1804 let mut gas_remaining = 100_000;
1805 let output = modexp(&calldata, &mut gas_remaining, Fork::Prague, &NativeCrypto).unwrap();
1806
1807 assert_eq!(100_000 - gas_remaining, 1360);
1809 assert_eq!(output.as_ref(), [0u8; 32]);
1810 }
1811
1812 #[test]
1818 fn modexp_unsatisfiable_operand_allocation_halts_with_out_of_gas() {
1819 let calldata = modexp_calldata(1, 1 << 61, 1, &[1, 1, 1]);
1820 let mut gas_remaining = u64::MAX;
1821 let result = modexp(&calldata, &mut gas_remaining, Fork::Prague, &NativeCrypto);
1822 assert!(
1823 matches!(
1824 result,
1825 Err(VMError::ExceptionalHalt(ExceptionalHalt::OutOfGas))
1826 ),
1827 "an operand the host cannot allocate must halt with out-of-gas, got {result:?}"
1828 );
1829 }
1830
1831 #[test]
1833 fn execute_precompile_gated_by_lstar_and_l1() {
1834 let execute_addr = EXECUTE.address;
1835 assert!(!is_precompile(&execute_addr, Fork::Amsterdam, VMType::L1));
1837 assert!(!is_precompile(
1839 &execute_addr,
1840 Fork::LStar,
1841 VMType::L2(Default::default())
1842 ));
1843 assert!(is_precompile(&execute_addr, Fork::LStar, VMType::L1));
1845 }
1846}
1847
1848#[cfg(test)]
1849mod seeded_calldata_tests {
1850 use super::*;
1851 use std::hash::BuildHasher;
1852
1853 #[track_caller]
1856 fn assert_seeded<S: BuildHasher + Default>(state: &S) {
1857 let calldata = Bytes::from_static(&[0x5a; 32]);
1858 assert_ne!(state.hash_one(&calldata), S::default().hash_one(&calldata));
1859 }
1860
1861 #[test]
1864 fn precompile_results_are_seeded() {
1865 let mut entries = PrecompileCacheEntries::default();
1866 assert_seeded(entries.map.entry(Address::zero()).or_default().hasher());
1867 }
1868}