1use bytes::{Buf, Bytes};
2use ethrex_common::H160;
3use ethrex_common::utils::u256_from_big_endian_const;
4use ethrex_common::{
5 Address, H256, U256, types::Fork, types::Fork::*, utils::u256_from_big_endian,
6};
7use ethrex_crypto::{Crypto, CryptoError};
8use rustc_hash::FxHashMap;
9use std::borrow::Cow;
10use std::collections::{BTreeMap, BTreeSet};
11use std::sync::RwLock;
12
13use crate::gas_cost::{MODEXP_STATIC_COST, P256_VERIFY_COST};
14use crate::vm::VMType;
15use crate::{
16 constants::VERSIONED_HASH_VERSION_KZG,
17 errors::{InternalError, PrecompileError, VMError},
18 gas_cost::{
19 self, BLAKE2F_ROUND_COST, BLS12_381_G1_K_DISCOUNT, BLS12_381_G1ADD_COST,
20 BLS12_381_G2_K_DISCOUNT, BLS12_381_G2ADD_COST, BLS12_381_MAP_FP_TO_G1_COST,
21 BLS12_381_MAP_FP2_TO_G2_COST, ECADD_COST, ECMUL_COST, G1_MUL_COST, G2_MUL_COST,
22 POINT_EVALUATION_COST,
23 },
24};
25
26pub const BLAKE2F_ELEMENT_SIZE: usize = 8;
27
28pub const SIZE_PRECOMPILES_PRE_CANCUN: u64 = 9;
29pub const SIZE_PRECOMPILES_CANCUN: u64 = 10;
30pub const SIZE_PRECOMPILES_PRAGUE: u64 = 17;
31
32pub const BLS12_381_G1_MSM_PAIR_LENGTH: usize = 160;
33pub const BLS12_381_G2_MSM_PAIR_LENGTH: usize = 288;
34pub const BLS12_381_PAIRING_CHECK_PAIR_LENGTH: usize = 384;
35
36const BLS12_381_FP2_VALID_INPUT_LENGTH: usize = 128;
37const BLS12_381_FP_VALID_INPUT_LENGTH: usize = 64;
38
39pub const FIELD_ELEMENT_WITHOUT_PADDING_LENGTH: usize = 48;
40pub const PADDED_FIELD_ELEMENT_SIZE_IN_BYTES: usize = 64;
41
42pub const G1_POINT_AT_INFINITY: [u8; 128] = [0_u8; 128];
43pub const G2_POINT_AT_INFINITY: [u8; 256] = [0_u8; 256];
44
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 struct PrecompileCache {
376 cache: RwLock<FxHashMap<(Address, Bytes), (Bytes, u64)>>,
377}
378
379impl Default for PrecompileCache {
380 fn default() -> Self {
381 Self {
382 cache: RwLock::new(FxHashMap::default()),
383 }
384 }
385}
386
387impl PrecompileCache {
388 pub fn new() -> Self {
389 Self::default()
390 }
391
392 pub fn get(&self, address: &Address, calldata: &Bytes) -> Option<(Bytes, u64)> {
393 self.cache
397 .read()
398 .unwrap_or_else(|poisoned| poisoned.into_inner())
399 .get(&(*address, calldata.clone()))
400 .cloned()
401 }
402
403 pub fn insert(&self, address: Address, calldata: Bytes, output: Bytes, gas_cost: u64) {
404 self.cache
405 .write()
406 .unwrap_or_else(|poisoned| poisoned.into_inner())
407 .insert((address, calldata), (output, gas_cost));
408 }
409}
410
411#[expect(clippy::as_conversions, clippy::indexing_slicing)]
412pub fn execute_precompile(
413 address: Address,
414 calldata: &Bytes,
415 gas_remaining: &mut u64,
416 fork: Fork,
417 cache: Option<&PrecompileCache>,
418 crypto: &dyn Crypto,
419 _stateless_validator: Option<&dyn crate::StatelessValidator>,
420) -> Result<Bytes, VMError> {
421 type PrecompileFn = fn(&Bytes, &mut u64, Fork, &dyn Crypto) -> Result<Bytes, VMError>;
422
423 const PRECOMPILES: [Option<PrecompileFn>; 512] = const {
424 let mut precompiles = [const { None }; 512];
425 precompiles[ECRECOVER.address.0[19] as usize] = Some(ecrecover as PrecompileFn);
426 precompiles[IDENTITY.address.0[19] as usize] = Some(identity as PrecompileFn);
427 precompiles[SHA2_256.address.0[19] as usize] = Some(sha2_256 as PrecompileFn);
428 precompiles[RIPEMD_160.address.0[19] as usize] = Some(ripemd_160 as PrecompileFn);
429 precompiles[MODEXP.address.0[19] as usize] = Some(modexp as PrecompileFn);
430 precompiles[ECADD.address.0[19] as usize] = Some(ecadd as PrecompileFn);
431 precompiles[ECMUL.address.0[19] as usize] = Some(ecmul as PrecompileFn);
432 precompiles[ECPAIRING.address.0[19] as usize] = Some(ecpairing as PrecompileFn);
433 precompiles[BLAKE2F.address.0[19] as usize] = Some(blake2f as PrecompileFn);
434 precompiles[POINT_EVALUATION.address.0[19] as usize] =
435 Some(point_evaluation as PrecompileFn);
436 precompiles[BLS12_G1ADD.address.0[19] as usize] = Some(bls12_g1add as PrecompileFn);
437 precompiles[BLS12_G1MSM.address.0[19] as usize] = Some(bls12_g1msm as PrecompileFn);
438 precompiles[BLS12_G2ADD.address.0[19] as usize] = Some(bls12_g2add as PrecompileFn);
439 precompiles[BLS12_G2MSM.address.0[19] as usize] = Some(bls12_g2msm as PrecompileFn);
440 precompiles[BLS12_PAIRING_CHECK.address.0[19] as usize] =
441 Some(bls12_pairing_check as PrecompileFn);
442 precompiles[BLS12_MAP_FP_TO_G1.address.0[19] as usize] =
443 Some(bls12_map_fp_to_g1 as PrecompileFn);
444 precompiles[BLS12_MAP_FP2_TO_G2.address.0[19] as usize] =
445 Some(bls12_map_fp2_to_g2 as PrecompileFn);
446 precompiles
447 [u16::from_be_bytes([P256VERIFY.address.0[18], P256VERIFY.address.0[19]]) as usize] =
448 Some(p_256_verify as PrecompileFn);
449 precompiles
450 };
451
452 if fork >= Fork::LStar && address == EXECUTE.address {
455 return crate::execute_precompile::execute_precompile(
456 calldata,
457 gas_remaining,
458 fork,
459 crypto,
460 _stateless_validator,
461 );
462 }
463
464 if address[0..18] != [0u8; 18] {
465 return Err(VMError::Internal(InternalError::InvalidPrecompileAddress));
466 }
467 let index = u16::from_be_bytes([address[18], address[19]]) as usize;
468
469 let precompile = PRECOMPILES
470 .get(index)
471 .copied()
472 .flatten()
473 .ok_or(VMError::Internal(InternalError::InvalidPrecompileAddress))?;
474
475 if address != IDENTITY.address
477 && let Some((output, gas_cost)) = cache.and_then(|c| c.get(&address, calldata))
478 {
479 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
480 return Ok(output);
481 }
482
483 #[cfg(feature = "perf_opcode_timings")]
484 let precompile_time_start = std::time::Instant::now();
485
486 let gas_before = *gas_remaining;
487 let result = precompile(calldata, gas_remaining, fork, crypto);
488
489 #[cfg(feature = "perf_opcode_timings")]
490 {
491 let time = precompile_time_start.elapsed();
492 let mut timings = crate::timings::PRECOMPILES_TIMINGS.lock().expect("poison");
493 timings.update(address, time);
494 }
495
496 if address != IDENTITY.address
498 && let Some(cache) = cache
499 && let Ok(output) = &result
500 {
501 let gas_cost = gas_before.saturating_sub(*gas_remaining);
502 cache.insert(address, calldata.clone(), output.clone(), gas_cost);
503 }
504
505 result
506}
507
508pub(crate) fn increase_precompile_consumed_gas(
510 gas_cost: u64,
511 gas_remaining: &mut u64,
512) -> Result<(), VMError> {
513 *gas_remaining = gas_remaining
514 .checked_sub(gas_cost)
515 .ok_or(PrecompileError::NotEnoughGas)?;
516 Ok(())
517}
518
519#[inline(always)]
522pub(crate) fn fill_with_zeros(calldata: &Bytes, target_len: usize) -> Bytes {
523 if calldata.len() >= target_len {
524 return calldata.clone();
526 }
527 let mut padded_calldata = calldata.to_vec();
528 padded_calldata.resize(target_len, 0);
529 padded_calldata.into()
530}
531
532fn crypto_error_to_precompile(e: CryptoError) -> VMError {
533 match e {
534 CryptoError::InvalidPoint(_) => PrecompileError::InvalidPoint.into(),
535 CryptoError::InvalidInput(_) => PrecompileError::ParsingInputError.into(),
536 CryptoError::InvalidSignature => PrecompileError::ParsingInputError.into(),
537 CryptoError::InvalidRecoveryId => PrecompileError::ParsingInputError.into(),
538 CryptoError::RecoveryFailed => PrecompileError::ParsingInputError.into(),
539 CryptoError::VerificationFailed => PrecompileError::ParsingInputError.into(),
540 CryptoError::Other(_) => PrecompileError::ParsingInputError.into(),
541 CryptoError::Unsupported(msg) => InternalError::Custom(msg.to_string()).into(),
545 }
546}
547
548fn bls12_g1_crypto_error(e: CryptoError) -> VMError {
551 match e {
552 CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G1PointNotInCurve.into(),
553 other => crypto_error_to_precompile(other),
554 }
555}
556
557fn bls12_g2_crypto_error(e: CryptoError) -> VMError {
560 match e {
561 CryptoError::InvalidPoint(_) => PrecompileError::BLS12381G2PointNotInCurve.into(),
562 other => crypto_error_to_precompile(other),
563 }
564}
565
566fn bls12_msm_pairing_crypto_error(e: CryptoError) -> VMError {
570 match e {
571 CryptoError::InvalidPoint(_) => PrecompileError::ParsingInputError.into(),
572 other => crypto_error_to_precompile(other),
573 }
574}
575
576pub fn ecrecover(
586 calldata: &Bytes,
587 gas_remaining: &mut u64,
588 _fork: Fork,
589 crypto: &dyn Crypto,
590) -> Result<Bytes, VMError> {
591 use crate::gas_cost::ECRECOVER_COST;
592
593 increase_precompile_consumed_gas(ECRECOVER_COST, gas_remaining)?;
594
595 const INPUT_LEN: usize = 128;
596 const WORD: usize = 32;
597
598 let input = fill_with_zeros(calldata, INPUT_LEN);
599
600 #[expect(
601 clippy::indexing_slicing,
602 reason = "fill_with_zeros guarantees len >= 128"
603 )]
604 let raw_hash: &[u8] = &input[0..WORD];
605 #[expect(
606 clippy::indexing_slicing,
607 reason = "fill_with_zeros guarantees len >= 128"
608 )]
609 let raw_v: &[u8] = &input[WORD..WORD * 2];
610 #[expect(
611 clippy::indexing_slicing,
612 reason = "fill_with_zeros guarantees len >= 128"
613 )]
614 let raw_sig: &[u8] = &input[WORD * 2..WORD * 2 + 64];
615
616 let recid_byte: u8 = match u8::try_from(u256_from_big_endian(raw_v)) {
618 Ok(27) => 0,
619 Ok(28) => 1,
620 _ => return Ok(Bytes::new()),
621 };
622
623 let msg_hash: [u8; 32] = raw_hash
624 .try_into()
625 .map_err(|_| InternalError::TypeConversion)?;
626 let sig: [u8; 64] = raw_sig
627 .try_into()
628 .map_err(|_| InternalError::TypeConversion)?;
629
630 let pk_hash = match crypto.secp256k1_ecrecover(&sig, recid_byte, &msg_hash) {
631 Ok(h) => h,
632 Err(_) => return Ok(Bytes::new()),
633 };
634
635 let mut out = [0u8; 32];
637 out[12..32].copy_from_slice(&pk_hash[12..32]);
638
639 Ok(Bytes::copy_from_slice(&out))
640}
641
642pub fn identity(
644 calldata: &Bytes,
645 gas_remaining: &mut u64,
646 _fork: Fork,
647 _crypto: &dyn Crypto,
648) -> Result<Bytes, VMError> {
649 let gas_cost = gas_cost::identity(calldata.len())?;
650
651 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
652
653 Ok(calldata.clone())
654}
655
656pub fn sha2_256(
658 calldata: &Bytes,
659 gas_remaining: &mut u64,
660 _fork: Fork,
661 crypto: &dyn Crypto,
662) -> Result<Bytes, VMError> {
663 let gas_cost = gas_cost::sha2_256(calldata.len())?;
664
665 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
666
667 let digest = crypto.sha256(calldata);
668 Ok(Bytes::copy_from_slice(&digest))
669}
670
671pub fn ripemd_160(
673 calldata: &Bytes,
674 gas_remaining: &mut u64,
675 _fork: Fork,
676 crypto: &dyn Crypto,
677) -> Result<Bytes, VMError> {
678 let gas_cost = gas_cost::ripemd_160(calldata.len())?;
679
680 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
681
682 let result = crypto.ripemd160(calldata);
683 Ok(Bytes::copy_from_slice(&result))
684}
685
686#[expect(clippy::indexing_slicing, reason = "bounds checked at start")]
688pub fn modexp(
689 calldata: &Bytes,
690 gas_remaining: &mut u64,
691 fork: Fork,
692 crypto: &dyn Crypto,
693) -> Result<Bytes, VMError> {
694 let calldata = fill_with_zeros(calldata, 96);
696
697 if fork < Fork::Osaka {
699 let base_size_bytes: [u8; 32] = calldata[0..32].try_into()?;
700 let modulus_size_bytes: [u8; 32] = calldata[64..96].try_into()?;
701 const ZERO_BYTES: [u8; 32] = [0u8; 32];
702
703 if base_size_bytes == ZERO_BYTES && modulus_size_bytes == ZERO_BYTES {
704 increase_precompile_consumed_gas(MODEXP_STATIC_COST, gas_remaining)?;
706 return Ok(Bytes::new());
707 }
708 }
709
710 let base_size = u256_from_big_endian_const::<32>(calldata[0..32].try_into()?);
713 let modulus_size = u256_from_big_endian_const::<32>(calldata[64..96].try_into()?);
714 let exponent_size = u256_from_big_endian_const::<32>(calldata[32..64].try_into()?);
715
716 if fork >= Fork::Osaka {
717 if base_size > U256::from(1024) {
718 return Err(PrecompileError::ModExpBaseTooLarge.into());
719 }
720 if exponent_size > U256::from(1024) {
721 return Err(PrecompileError::ModExpExpTooLarge.into());
722 }
723 if modulus_size > U256::from(1024) {
724 return Err(PrecompileError::ModExpModulusTooLarge.into());
725 }
726 }
727
728 let base_size = usize::try_from(base_size).map_err(|_| PrecompileError::ParsingInputError)?;
730 let exponent_size =
731 usize::try_from(exponent_size).map_err(|_| PrecompileError::ParsingInputError)?;
732 let modulus_size =
733 usize::try_from(modulus_size).map_err(|_| PrecompileError::ParsingInputError)?;
734
735 let base_limit = base_size.checked_add(96).ok_or(InternalError::Overflow)?;
736
737 let exponent_limit = exponent_size
738 .checked_add(base_limit)
739 .ok_or(InternalError::Overflow)?;
740
741 let modulus_limit = modulus_size
742 .checked_add(exponent_limit)
743 .ok_or(InternalError::Overflow)?;
744
745 let b = get_slice_or_default(&calldata, 96, base_limit, base_size);
746 let e = get_slice_or_default(&calldata, base_limit, exponent_limit, exponent_size);
747 let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, modulus_size);
748
749 use malachite::Natural;
751 use malachite::base::num::conversion::traits::*;
752 let exp_first_32_bytes = e.get(0..32.min(exponent_size)).unwrap_or_default();
753 let exp_first_32 =
754 Natural::from_power_of_2_digits_desc(8u64, exp_first_32_bytes.iter().cloned())
755 .ok_or(InternalError::TypeConversion)?;
756
757 let gas_cost = gas_cost::modexp(&exp_first_32, base_size, exponent_size, modulus_size, fork)?;
758
759 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
760
761 if base_size == 0 && modulus_size == 0 {
762 return Ok(Bytes::new());
763 }
764
765 let result = crypto
766 .modexp(&b, &e, &m)
767 .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
768
769 let res_bytes = increase_left_pad(&Bytes::from(result), modulus_size);
770
771 Ok(res_bytes.slice(..modulus_size))
772}
773
774#[expect(clippy::indexing_slicing, reason = "bounds checked")]
779fn get_slice_or_default<'c>(
780 calldata: &'c Bytes,
781 lower_limit: usize,
782 upper_limit: usize,
783 size_to_expand: usize,
784) -> Cow<'c, [u8]> {
785 let upper_limit = calldata.len().min(upper_limit);
786 if let Some(data) = calldata.get(lower_limit..upper_limit)
787 && !data.is_empty()
788 {
789 if data.len() == size_to_expand {
790 return data.into();
791 }
792 let mut extended = vec![0u8; size_to_expand];
793 let copy_size = size_to_expand.min(data.len());
794 extended[..copy_size].copy_from_slice(&data[..copy_size]);
795 return extended.into();
796 }
797 Vec::new().into()
798}
799
800#[inline(always)]
802pub fn increase_left_pad(result: &Bytes, m_size: usize) -> Bytes {
803 #[expect(
804 clippy::arithmetic_side_effects,
805 clippy::indexing_slicing,
806 reason = "overflow checked with the if condition, bounds checked"
807 )]
808 if result.len() < m_size {
809 let mut padded_result = vec![0u8; m_size];
810 let size_diff = m_size - result.len();
811 padded_result[size_diff..].copy_from_slice(result);
812
813 padded_result.into()
814 } else {
815 result.clone()
817 }
818}
819
820pub fn ecadd(
822 calldata: &Bytes,
823 gas_remaining: &mut u64,
824 _fork: Fork,
825 crypto: &dyn Crypto,
826) -> Result<Bytes, VMError> {
827 let calldata = fill_with_zeros(calldata, 128);
829
830 increase_precompile_consumed_gas(ECADD_COST, gas_remaining)?;
831
832 let (Some(first_point), Some(second_point)) =
833 (parse_bn254_g1(&calldata, 0), parse_bn254_g1(&calldata, 64))
834 else {
835 return Err(InternalError::Slicing.into());
836 };
837 validate_bn254_g1_coords(&first_point)?;
838 validate_bn254_g1_coords(&second_point)?;
839
840 #[expect(clippy::indexing_slicing, reason = "calldata padded to 128 bytes")]
841 let result = crypto
842 .bn254_g1_add(&calldata[..64], &calldata[64..128])
843 .map_err(crypto_error_to_precompile)?;
844
845 Ok(Bytes::copy_from_slice(&result))
846}
847
848pub fn ecmul(
850 calldata: &Bytes,
851 gas_remaining: &mut u64,
852 _fork: Fork,
853 crypto: &dyn Crypto,
854) -> Result<Bytes, VMError> {
855 let calldata = fill_with_zeros(calldata, 96);
857 increase_precompile_consumed_gas(ECMUL_COST, gas_remaining)?;
858
859 let (Some(g1), Some(_scalar)) = (
860 parse_bn254_g1(&calldata, 0),
861 parse_bn254_scalar(&calldata, 64),
862 ) else {
863 return Err(InternalError::Slicing.into());
864 };
865 validate_bn254_g1_coords(&g1)?;
866
867 #[expect(clippy::indexing_slicing, reason = "calldata padded to 96 bytes")]
868 let result = crypto
869 .bn254_g1_mul(&calldata[..64], &calldata[64..96])
870 .map_err(crypto_error_to_precompile)?;
871
872 Ok(Bytes::copy_from_slice(&result))
873}
874
875const ALT_BN128_PRIME: U256 = U256([
876 0x3c208c16d87cfd47,
877 0x97816a916871ca8d,
878 0xb85045b68181585d,
879 0x30644e72e131a029,
880]);
881
882pub struct G1(U256, U256);
883impl G1 {
884 pub fn is_zero(&self) -> bool {
886 self.0.is_zero() && self.1.is_zero()
887 }
888}
889pub struct G2(U256, U256, U256, U256);
890impl G2 {
891 pub fn is_zero(&self) -> bool {
893 self.0.is_zero() && self.1.is_zero() && self.2.is_zero() && self.3.is_zero()
894 }
895}
896
897#[inline]
899fn parse_bn254_scalar(buf: &[u8], offset: usize) -> Option<U256> {
900 buf.get(offset..offset.checked_add(32)?)
901 .map(u256_from_big_endian)
902}
903
904#[inline]
906fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option<G1> {
907 let chunk = buf.get(offset..offset.checked_add(64)?)?;
908 let (x_bytes, y_bytes) = chunk.split_at_checked(32)?;
909 Some(G1(
910 u256_from_big_endian(x_bytes),
911 u256_from_big_endian(y_bytes),
912 ))
913}
914
915fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option<G2> {
917 let chunk = buf.get(offset..offset.checked_add(128)?)?;
918 let (g2_xy, rest) = chunk.split_at_checked(32)?;
919 let (g2_xx, rest) = rest.split_at_checked(32)?;
920 let (g2_yy, g2_yx) = rest.split_at_checked(32)?;
921 Some(G2(
922 u256_from_big_endian(g2_xx),
923 u256_from_big_endian(g2_xy),
924 u256_from_big_endian(g2_yx),
925 u256_from_big_endian(g2_yy),
926 ))
927}
928
929#[inline]
930fn validate_bn254_g1_coords(g1: &G1) -> Result<(), VMError> {
931 if g1.0 >= ALT_BN128_PRIME || g1.1 >= ALT_BN128_PRIME {
933 return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
934 }
935 Ok(())
936}
937
938#[inline]
939fn validate_bn254_g2_coords(g2: &G2) -> Result<(), VMError> {
940 if g2.0 >= ALT_BN128_PRIME
942 || g2.1 >= ALT_BN128_PRIME
943 || g2.2 >= ALT_BN128_PRIME
944 || g2.3 >= ALT_BN128_PRIME
945 {
946 return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
947 }
948 Ok(())
949}
950
951pub fn ecpairing(
953 calldata: &Bytes,
954 gas_remaining: &mut u64,
955 _fork: Fork,
956 crypto: &dyn Crypto,
957) -> Result<Bytes, VMError> {
958 if !calldata.len().is_multiple_of(192) {
960 return Err(PrecompileError::ParsingInputError.into());
961 }
962
963 let inputs_amount = calldata.len() / 192;
964 let gas_cost = gas_cost::ecpairing(inputs_amount)?;
965 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
966
967 let mut pairs: Vec<(&[u8], &[u8])> = Vec::new();
968 for input in calldata.chunks_exact(192) {
969 let (Some(g1), Some(g2)) = (parse_bn254_g1(input, 0), parse_bn254_g2(input, 64)) else {
970 return Err(InternalError::Slicing.into());
971 };
972 validate_bn254_g1_coords(&g1)?;
973 validate_bn254_g2_coords(&g2)?;
974 #[expect(clippy::indexing_slicing, reason = "chunks_exact guarantees 192 bytes")]
975 pairs.push((&input[..64], &input[64..192]));
976 }
977
978 let pairing_check = if pairs.is_empty() {
979 true
980 } else {
981 crypto
982 .bn254_pairing_check(&pairs)
983 .map_err(crypto_error_to_precompile)?
984 };
985
986 let mut result = [0; 32];
987 result[31] = u8::from(pairing_check);
988 Ok(Bytes::from_owner(result))
989}
990
991pub fn blake2f(
993 calldata: &Bytes,
994 gas_remaining: &mut u64,
995 _fork: Fork,
996 crypto: &dyn Crypto,
997) -> Result<Bytes, VMError> {
998 if calldata.len() != 213 {
999 return Err(PrecompileError::ParsingInputError.into());
1000 }
1001
1002 let mut calldata = calldata.slice(0..213);
1003
1004 let rounds = calldata.get_u32();
1005
1006 let gas_cost = u64::from(rounds) * BLAKE2F_ROUND_COST;
1007 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1008
1009 let mut h = [0; 8];
1010
1011 h.copy_from_slice(&std::array::from_fn::<u64, 8, _>(|_| calldata.get_u64_le()));
1012
1013 let mut m = [0; 16];
1014
1015 m.copy_from_slice(&std::array::from_fn::<u64, 16, _>(|_| {
1016 calldata.get_u64_le()
1017 }));
1018
1019 let mut t = [0; 2];
1020 t.copy_from_slice(&std::array::from_fn::<u64, 2, _>(|_| calldata.get_u64_le()));
1021
1022 let f = calldata.get_u8();
1023 if f != 0 && f != 1 {
1024 return Err(PrecompileError::ParsingInputError.into());
1025 }
1026 let f = f == 1;
1027
1028 crypto.blake2_compress(rounds, &mut h, m, t, f);
1029
1030 Ok(Bytes::from_iter(
1031 h.into_iter().flat_map(|value| value.to_le_bytes()),
1032 ))
1033}
1034
1035fn kzg_commitment_to_versioned_hash(commitment_bytes: &[u8; 48], crypto: &dyn Crypto) -> H256 {
1037 let mut versioned_hash: [u8; 32] = crypto.sha256(commitment_bytes);
1038 versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
1039 versioned_hash.into()
1040}
1041
1042const POINT_EVALUATION_OUTPUT_BYTES: [u8; 64] = [
1043 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1045 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
1046 0x73, 0xED, 0xA7, 0x53, 0x29, 0x9D, 0x7D, 0x48, 0x33, 0x39, 0xD8, 0x08, 0x09, 0xA1, 0xD8, 0x05,
1048 0x53, 0xBD, 0xA4, 0x02, 0xFF, 0xFE, 0x5B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01,
1049];
1050
1051fn point_evaluation(
1053 calldata: &Bytes,
1054 gas_remaining: &mut u64,
1055 _fork: Fork,
1056 crypto: &dyn Crypto,
1057) -> Result<Bytes, VMError> {
1058 if calldata.len() != 192 {
1059 return Err(PrecompileError::ParsingInputError.into());
1060 }
1061
1062 let gas_cost = POINT_EVALUATION_COST;
1064 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1065
1066 let versioned_hash: [u8; 32] = calldata
1068 .get(..32)
1069 .ok_or(InternalError::Slicing)?
1070 .try_into()
1071 .map_err(|_| InternalError::TypeConversion)?;
1072
1073 let z: [u8; 32] = calldata
1074 .get(32..64)
1075 .ok_or(InternalError::Slicing)?
1076 .try_into()
1077 .map_err(|_| InternalError::TypeConversion)?;
1078
1079 let y: [u8; 32] = calldata
1080 .get(64..96)
1081 .ok_or(InternalError::Slicing)?
1082 .try_into()
1083 .map_err(|_| InternalError::TypeConversion)?;
1084
1085 let commitment: [u8; 48] = calldata
1086 .get(96..144)
1087 .ok_or(InternalError::Slicing)?
1088 .try_into()
1089 .map_err(|_| InternalError::TypeConversion)?;
1090
1091 let proof: [u8; 48] = calldata
1092 .get(144..192)
1093 .ok_or(InternalError::Slicing)?
1094 .try_into()
1095 .map_err(|_| InternalError::TypeConversion)?;
1096
1097 if kzg_commitment_to_versioned_hash(&commitment, crypto) != H256::from(versioned_hash) {
1101 return Err(PrecompileError::ParsingInputError.into());
1102 }
1103
1104 crypto
1106 .verify_kzg_proof(&z, &y, &commitment, &proof)
1107 .map_err(|_| VMError::from(PrecompileError::ParsingInputError))?;
1108
1109 let output = POINT_EVALUATION_OUTPUT_BYTES.to_vec();
1112
1113 Ok(Bytes::from(output))
1114}
1115
1116pub fn p_256_verify(
1121 calldata: &Bytes,
1122 gas_remaining: &mut u64,
1123 _fork: Fork,
1124 crypto: &dyn Crypto,
1125) -> Result<Bytes, VMError> {
1126 increase_precompile_consumed_gas(P256_VERIFY_COST, gas_remaining)
1127 .map_err(|_| PrecompileError::NotEnoughGas)?;
1128
1129 if calldata.len() != 160 {
1131 return Ok(Bytes::new());
1132 }
1133
1134 #[expect(
1136 clippy::indexing_slicing,
1137 reason = "length of the calldata is checked before slicing"
1138 )]
1139 let msg: &[u8; 32] = calldata[0..32].try_into()?;
1140 #[expect(clippy::indexing_slicing, reason = "length checked")]
1141 let r: &[u8; 32] = calldata[32..64].try_into()?;
1142 #[expect(clippy::indexing_slicing, reason = "length checked")]
1143 let s: &[u8; 32] = calldata[64..96].try_into()?;
1144 #[expect(clippy::indexing_slicing, reason = "length checked")]
1145 let pk_x: &[u8; 32] = calldata[96..128].try_into()?;
1146 #[expect(clippy::indexing_slicing, reason = "length checked")]
1147 let pk_y: &[u8; 32] = calldata[128..160].try_into()?;
1148
1149 let mut sig_bytes = [0u8; 64];
1151 sig_bytes[..32].copy_from_slice(r);
1152 sig_bytes[32..].copy_from_slice(s);
1153
1154 let mut pk_bytes = [0u8; 64];
1155 pk_bytes[..32].copy_from_slice(pk_x);
1156 pk_bytes[32..].copy_from_slice(pk_y);
1157
1158 let success = crypto.secp256r1_verify(msg, &sig_bytes, &pk_bytes);
1159
1160 if success {
1163 const RESULT: [u8; 32] = [
1164 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1165 0, 0, 1,
1166 ];
1167 Ok(Bytes::from_static(&RESULT))
1168 } else {
1169 Ok(Bytes::new())
1170 }
1171}
1172
1173fn parse_bls12_padded_fp(padded: &[u8; 64]) -> Result<[u8; 48], VMError> {
1176 if padded[..16] != [0u8; 16] {
1177 return Err(PrecompileError::ParsingInputError.into());
1178 }
1179 let fp: [u8; 48] = padded[16..64]
1180 .try_into()
1181 .map_err(|_| InternalError::TypeConversion)?;
1182 Ok(fp)
1183}
1184
1185pub fn bls12_g1add(
1186 calldata: &Bytes,
1187 gas_remaining: &mut u64,
1188 _fork: Fork,
1189 crypto: &dyn Crypto,
1190) -> Result<Bytes, VMError> {
1191 let (x_data, calldata) = calldata
1192 .split_first_chunk::<128>()
1193 .ok_or(PrecompileError::ParsingInputError)?;
1194 let (y_data, calldata) = calldata
1195 .split_first_chunk::<128>()
1196 .ok_or(PrecompileError::ParsingInputError)?;
1197 if !calldata.is_empty() {
1198 return Err(PrecompileError::ParsingInputError.into());
1199 }
1200
1201 increase_precompile_consumed_gas(BLS12_381_G1ADD_COST, gas_remaining)
1203 .map_err(|_| PrecompileError::NotEnoughGas)?;
1204
1205 let ax = parse_bls12_padded_fp(
1207 x_data[..64]
1208 .try_into()
1209 .map_err(|_| InternalError::TypeConversion)?,
1210 )?;
1211 let ay = parse_bls12_padded_fp(
1212 x_data[64..128]
1213 .try_into()
1214 .map_err(|_| InternalError::TypeConversion)?,
1215 )?;
1216 let bx = parse_bls12_padded_fp(
1217 y_data[..64]
1218 .try_into()
1219 .map_err(|_| InternalError::TypeConversion)?,
1220 )?;
1221 let by = parse_bls12_padded_fp(
1222 y_data[64..128]
1223 .try_into()
1224 .map_err(|_| InternalError::TypeConversion)?,
1225 )?;
1226
1227 let result = crypto
1228 .bls12_381_g1_add((ax, ay), (bx, by))
1229 .map_err(bls12_g1_crypto_error)?;
1230
1231 let mut output = [0u8; 128];
1233 output[16..64].copy_from_slice(&result[..48]);
1234 output[80..128].copy_from_slice(&result[48..96]);
1235 Ok(Bytes::copy_from_slice(&output))
1236}
1237
1238pub fn bls12_g1msm(
1239 calldata: &Bytes,
1240 gas_remaining: &mut u64,
1241 _fork: Fork,
1242 crypto: &dyn Crypto,
1243) -> Result<Bytes, VMError> {
1244 if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G1_MSM_PAIR_LENGTH) {
1245 return Err(PrecompileError::ParsingInputError.into());
1246 }
1247
1248 let k = calldata.len() / BLS12_381_G1_MSM_PAIR_LENGTH;
1249 let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G1_K_DISCOUNT, G1_MUL_COST)?;
1250 increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1251
1252 #[allow(clippy::type_complexity)]
1253 let mut pairs: Vec<(([u8; 48], [u8; 48]), [u8; 32])> = Vec::with_capacity(k);
1254
1255 #[expect(
1256 clippy::arithmetic_side_effects,
1257 clippy::indexing_slicing,
1258 reason = "bounds checked"
1259 )]
1260 for i in 0..k {
1261 let point_offset = i * BLS12_381_G1_MSM_PAIR_LENGTH;
1262 let scalar_offset = point_offset + 128;
1263 let pair_end = scalar_offset + 32;
1264
1265 let point_bytes = &calldata[point_offset..scalar_offset];
1266 let scalar_bytes = &calldata[scalar_offset..pair_end];
1267
1268 let px = parse_bls12_padded_fp(
1269 point_bytes[..64]
1270 .try_into()
1271 .map_err(|_| InternalError::TypeConversion)?,
1272 )?;
1273 let py = parse_bls12_padded_fp(
1274 point_bytes[64..128]
1275 .try_into()
1276 .map_err(|_| InternalError::TypeConversion)?,
1277 )?;
1278 let scalar: [u8; 32] = scalar_bytes
1279 .try_into()
1280 .map_err(|_| InternalError::TypeConversion)?;
1281
1282 pairs.push(((px, py), scalar));
1283 }
1284
1285 let result = crypto
1286 .bls12_381_g1_msm(&pairs)
1287 .map_err(bls12_msm_pairing_crypto_error)?;
1288
1289 let mut output = [0u8; 128];
1291 output[16..64].copy_from_slice(&result[..48]);
1292 output[80..128].copy_from_slice(&result[48..96]);
1293 Ok(Bytes::copy_from_slice(&output))
1294}
1295
1296pub fn bls12_g2add(
1297 calldata: &Bytes,
1298 gas_remaining: &mut u64,
1299 _fork: Fork,
1300 crypto: &dyn Crypto,
1301) -> Result<Bytes, VMError> {
1302 let (x_data, calldata) = calldata
1303 .split_first_chunk::<256>()
1304 .ok_or(PrecompileError::ParsingInputError)?;
1305 let (y_data, calldata) = calldata
1306 .split_first_chunk::<256>()
1307 .ok_or(PrecompileError::ParsingInputError)?;
1308 if !calldata.is_empty() {
1309 return Err(PrecompileError::ParsingInputError.into());
1310 }
1311
1312 increase_precompile_consumed_gas(BLS12_381_G2ADD_COST, gas_remaining)
1314 .map_err(|_| PrecompileError::NotEnoughGas)?;
1315
1316 let ax0 = parse_bls12_padded_fp(
1319 x_data[0..64]
1320 .try_into()
1321 .map_err(|_| InternalError::TypeConversion)?,
1322 )?;
1323 let ax1 = parse_bls12_padded_fp(
1324 x_data[64..128]
1325 .try_into()
1326 .map_err(|_| InternalError::TypeConversion)?,
1327 )?;
1328 let ay0 = parse_bls12_padded_fp(
1329 x_data[128..192]
1330 .try_into()
1331 .map_err(|_| InternalError::TypeConversion)?,
1332 )?;
1333 let ay1 = parse_bls12_padded_fp(
1334 x_data[192..256]
1335 .try_into()
1336 .map_err(|_| InternalError::TypeConversion)?,
1337 )?;
1338
1339 let bx0 = parse_bls12_padded_fp(
1340 y_data[0..64]
1341 .try_into()
1342 .map_err(|_| InternalError::TypeConversion)?,
1343 )?;
1344 let bx1 = parse_bls12_padded_fp(
1345 y_data[64..128]
1346 .try_into()
1347 .map_err(|_| InternalError::TypeConversion)?,
1348 )?;
1349 let by0 = parse_bls12_padded_fp(
1350 y_data[128..192]
1351 .try_into()
1352 .map_err(|_| InternalError::TypeConversion)?,
1353 )?;
1354 let by1 = parse_bls12_padded_fp(
1355 y_data[192..256]
1356 .try_into()
1357 .map_err(|_| InternalError::TypeConversion)?,
1358 )?;
1359
1360 let result = crypto
1361 .bls12_381_g2_add((ax0, ax1, ay0, ay1), (bx0, bx1, by0, by1))
1362 .map_err(bls12_g2_crypto_error)?;
1363
1364 let mut output = [0u8; 256];
1368 output[16..64].copy_from_slice(&result[0..48]);
1369 output[80..128].copy_from_slice(&result[48..96]);
1370 output[144..192].copy_from_slice(&result[96..144]);
1371 output[208..256].copy_from_slice(&result[144..192]);
1372 Ok(Bytes::copy_from_slice(&output))
1373}
1374
1375pub fn bls12_g2msm(
1376 calldata: &Bytes,
1377 gas_remaining: &mut u64,
1378 _fork: Fork,
1379 crypto: &dyn Crypto,
1380) -> Result<Bytes, VMError> {
1381 if calldata.is_empty() || !calldata.len().is_multiple_of(BLS12_381_G2_MSM_PAIR_LENGTH) {
1382 return Err(PrecompileError::ParsingInputError.into());
1383 }
1384
1385 let k = calldata.len() / BLS12_381_G2_MSM_PAIR_LENGTH;
1386 let required_gas = gas_cost::bls12_msm(k, &BLS12_381_G2_K_DISCOUNT, G2_MUL_COST)?;
1387 increase_precompile_consumed_gas(required_gas, gas_remaining)?;
1388
1389 #[allow(clippy::type_complexity)]
1390 let mut pairs: Vec<(([u8; 48], [u8; 48], [u8; 48], [u8; 48]), [u8; 32])> =
1391 Vec::with_capacity(k);
1392
1393 #[expect(
1394 clippy::indexing_slicing,
1395 clippy::arithmetic_side_effects,
1396 reason = "bounds checked"
1397 )]
1398 for i in 0..k {
1399 let point_offset = i * BLS12_381_G2_MSM_PAIR_LENGTH;
1400 let scalar_offset = point_offset + 256;
1401 let pair_end = scalar_offset + 32;
1402
1403 let point_bytes = &calldata[point_offset..scalar_offset];
1404 let scalar_bytes = &calldata[scalar_offset..pair_end];
1405
1406 let x0 = parse_bls12_padded_fp(
1407 point_bytes[0..64]
1408 .try_into()
1409 .map_err(|_| InternalError::TypeConversion)?,
1410 )?;
1411 let x1 = parse_bls12_padded_fp(
1412 point_bytes[64..128]
1413 .try_into()
1414 .map_err(|_| InternalError::TypeConversion)?,
1415 )?;
1416 let y0 = parse_bls12_padded_fp(
1417 point_bytes[128..192]
1418 .try_into()
1419 .map_err(|_| InternalError::TypeConversion)?,
1420 )?;
1421 let y1 = parse_bls12_padded_fp(
1422 point_bytes[192..256]
1423 .try_into()
1424 .map_err(|_| InternalError::TypeConversion)?,
1425 )?;
1426 let scalar: [u8; 32] = scalar_bytes
1427 .try_into()
1428 .map_err(|_| InternalError::TypeConversion)?;
1429
1430 pairs.push(((x0, x1, y0, y1), scalar));
1431 }
1432
1433 let result = crypto
1434 .bls12_381_g2_msm(&pairs)
1435 .map_err(bls12_msm_pairing_crypto_error)?;
1436
1437 let mut output = [0u8; 256];
1439 output[16..64].copy_from_slice(&result[0..48]);
1440 output[80..128].copy_from_slice(&result[48..96]);
1441 output[144..192].copy_from_slice(&result[96..144]);
1442 output[208..256].copy_from_slice(&result[144..192]);
1443 Ok(Bytes::copy_from_slice(&output))
1444}
1445
1446pub fn bls12_pairing_check(
1447 calldata: &Bytes,
1448 gas_remaining: &mut u64,
1449 _fork: Fork,
1450 crypto: &dyn Crypto,
1451) -> Result<Bytes, VMError> {
1452 if calldata.is_empty()
1453 || !calldata
1454 .len()
1455 .is_multiple_of(BLS12_381_PAIRING_CHECK_PAIR_LENGTH)
1456 {
1457 return Err(PrecompileError::ParsingInputError.into());
1458 }
1459
1460 let k = calldata.len() / BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1462 let gas_cost = gas_cost::bls12_pairing_check(k)?;
1463 increase_precompile_consumed_gas(gas_cost, gas_remaining)?;
1464
1465 #[allow(clippy::type_complexity)]
1466 let mut pairs: Vec<(
1467 ([u8; 48], [u8; 48]),
1468 ([u8; 48], [u8; 48], [u8; 48], [u8; 48]),
1469 )> = Vec::with_capacity(k);
1470
1471 #[expect(
1472 clippy::indexing_slicing,
1473 clippy::arithmetic_side_effects,
1474 reason = "bounds checked"
1475 )]
1476 for i in 0..k {
1477 let g1_offset = i * BLS12_381_PAIRING_CHECK_PAIR_LENGTH;
1478 let g2_offset = g1_offset + 128;
1479 let pair_end = g2_offset + 256;
1480
1481 let g1_bytes = &calldata[g1_offset..g2_offset];
1482 let g2_bytes = &calldata[g2_offset..pair_end];
1483
1484 let g1x = parse_bls12_padded_fp(
1485 g1_bytes[0..64]
1486 .try_into()
1487 .map_err(|_| InternalError::TypeConversion)?,
1488 )?;
1489 let g1y = parse_bls12_padded_fp(
1490 g1_bytes[64..128]
1491 .try_into()
1492 .map_err(|_| InternalError::TypeConversion)?,
1493 )?;
1494
1495 let g2x0 = parse_bls12_padded_fp(
1496 g2_bytes[0..64]
1497 .try_into()
1498 .map_err(|_| InternalError::TypeConversion)?,
1499 )?;
1500 let g2x1 = parse_bls12_padded_fp(
1501 g2_bytes[64..128]
1502 .try_into()
1503 .map_err(|_| InternalError::TypeConversion)?,
1504 )?;
1505 let g2y0 = parse_bls12_padded_fp(
1506 g2_bytes[128..192]
1507 .try_into()
1508 .map_err(|_| InternalError::TypeConversion)?,
1509 )?;
1510 let g2y1 = parse_bls12_padded_fp(
1511 g2_bytes[192..256]
1512 .try_into()
1513 .map_err(|_| InternalError::TypeConversion)?,
1514 )?;
1515
1516 pairs.push(((g1x, g1y), (g2x0, g2x1, g2y0, g2y1)));
1517 }
1518
1519 let result = crypto
1520 .bls12_381_pairing_check(&pairs)
1521 .map_err(bls12_msm_pairing_crypto_error)?;
1522
1523 if result {
1524 let mut out = vec![0_u8; 31];
1525 out.push(1);
1526 Ok(Bytes::from(out))
1527 } else {
1528 Ok(Bytes::copy_from_slice(&[0_u8; 32]))
1529 }
1530}
1531
1532pub fn bls12_map_fp_to_g1(
1533 calldata: &Bytes,
1534 gas_remaining: &mut u64,
1535 _fork: Fork,
1536 crypto: &dyn Crypto,
1537) -> Result<Bytes, VMError> {
1538 if calldata.len() != BLS12_381_FP_VALID_INPUT_LENGTH {
1539 return Err(PrecompileError::ParsingInputError.into());
1540 }
1541
1542 increase_precompile_consumed_gas(BLS12_381_MAP_FP_TO_G1_COST, gas_remaining)?;
1544
1545 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1547 let fp = parse_bls12_padded_fp(
1548 calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1549 .try_into()
1550 .map_err(|_| InternalError::TypeConversion)?,
1551 )?;
1552
1553 let result = crypto
1554 .bls12_381_fp_to_g1(&fp)
1555 .map_err(crypto_error_to_precompile)?;
1556
1557 let mut output = [0u8; 128];
1559 output[16..64].copy_from_slice(&result[0..48]);
1560 output[80..128].copy_from_slice(&result[48..96]);
1561 Ok(Bytes::copy_from_slice(&output))
1562}
1563
1564pub fn bls12_map_fp2_to_g2(
1565 calldata: &Bytes,
1566 gas_remaining: &mut u64,
1567 _fork: Fork,
1568 crypto: &dyn Crypto,
1569) -> Result<Bytes, VMError> {
1570 if calldata.len() != BLS12_381_FP2_VALID_INPUT_LENGTH {
1571 return Err(PrecompileError::ParsingInputError.into());
1572 }
1573
1574 increase_precompile_consumed_gas(BLS12_381_MAP_FP2_TO_G2_COST, gas_remaining)?;
1576
1577 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1579 let c0 = parse_bls12_padded_fp(
1580 calldata[0..PADDED_FIELD_ELEMENT_SIZE_IN_BYTES]
1581 .try_into()
1582 .map_err(|_| InternalError::TypeConversion)?,
1583 )?;
1584 #[expect(clippy::indexing_slicing, reason = "bounds checked")]
1585 let c1 = parse_bls12_padded_fp(
1586 calldata[PADDED_FIELD_ELEMENT_SIZE_IN_BYTES..BLS12_381_FP2_VALID_INPUT_LENGTH]
1587 .try_into()
1588 .map_err(|_| InternalError::TypeConversion)?,
1589 )?;
1590
1591 if c0 == [0u8; 48] && c1 == [0u8; 48] {
1592 return Ok(Bytes::copy_from_slice(&FP2_ZERO_MAPPED_TO_G2));
1593 }
1594
1595 let result = crypto
1596 .bls12_381_fp2_to_g2((c0, c1))
1597 .map_err(crypto_error_to_precompile)?;
1598
1599 let mut output = [0u8; 256];
1603 output[16..64].copy_from_slice(&result[0..48]);
1604 output[80..128].copy_from_slice(&result[48..96]);
1605 output[144..192].copy_from_slice(&result[96..144]);
1606 output[208..256].copy_from_slice(&result[144..192]);
1607 Ok(Bytes::copy_from_slice(&output))
1608}
1609
1610#[cfg(test)]
1611mod tests {
1612 use super::*;
1613 use ethrex_common::types::Fork;
1614
1615 #[test]
1617 fn execute_precompile_gated_by_lstar_and_l1() {
1618 let execute_addr = EXECUTE.address;
1619 assert!(!is_precompile(&execute_addr, Fork::Amsterdam, VMType::L1));
1621 assert!(!is_precompile(
1623 &execute_addr,
1624 Fork::LStar,
1625 VMType::L2(Default::default())
1626 ));
1627 assert!(is_precompile(&execute_addr, Fork::LStar, VMType::L1));
1629 }
1630}