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