1use context_interface::{cfg::gas::GasTracker, result::AnyError};
4use core::fmt::{self, Debug};
5use primitives::{Bytes, OnceLock};
6use std::{borrow::Cow, boxed::Box, string::String, vec::Vec};
7
8use crate::bls12_381::{G1Point, G1PointScalar, G2Point, G2PointScalar};
9
10static CRYPTO: OnceLock<Box<dyn Crypto>> = OnceLock::new();
12
13pub fn install_crypto<C: Crypto + 'static>(crypto: C) -> bool {
15 CRYPTO.set(Box::new(crypto)).is_ok()
16}
17
18pub fn crypto() -> &'static dyn Crypto {
20 CRYPTO.get_or_init(|| Box::new(DefaultCrypto)).as_ref()
21}
22
23pub type EthPrecompileResult = Result<EthPrecompileOutput, PrecompileHalt>;
27
28pub type PrecompileResult = Result<PrecompileOutput, PrecompileError>;
33
34#[derive(Clone, Debug, PartialEq, Eq, Hash)]
39pub struct EthPrecompileOutput {
40 pub gas_used: u64,
42 pub bytes: Bytes,
44}
45
46impl EthPrecompileOutput {
47 pub const fn new(gas_used: u64, bytes: Bytes) -> Self {
49 Self { gas_used, bytes }
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq, Hash)]
55pub enum PrecompileStatus {
56 Success,
58 Revert,
60 Halt(PrecompileHalt),
62}
63
64impl PrecompileStatus {
65 #[inline]
67 pub const fn is_success_or_revert(&self) -> bool {
68 matches!(self, PrecompileStatus::Success | PrecompileStatus::Revert)
69 }
70
71 #[inline]
73 pub const fn is_revert_or_halt(&self) -> bool {
74 matches!(self, PrecompileStatus::Revert | PrecompileStatus::Halt(_))
75 }
76
77 #[inline]
79 pub const fn halt_reason(&self) -> Option<&PrecompileHalt> {
80 match &self {
81 PrecompileStatus::Halt(reason) => Some(reason),
82 _ => None,
83 }
84 }
85
86 #[inline]
88 pub const fn is_success(&self) -> bool {
89 matches!(self, PrecompileStatus::Success)
90 }
91
92 #[inline]
94 pub const fn is_revert(&self) -> bool {
95 matches!(self, PrecompileStatus::Revert)
96 }
97
98 #[inline]
100 pub const fn is_halt(&self) -> bool {
101 matches!(self, PrecompileStatus::Halt(_))
102 }
103}
104
105#[derive(Clone, Debug, PartialEq, Eq, Hash)]
110pub struct PrecompileOutput {
111 pub status: PrecompileStatus,
113 pub gas_used: u64,
115 pub gas_refunded: i64,
117 pub state_gas_used: i64,
119 pub state_gas_spilled: u64,
127 pub reservoir: u64,
129 pub bytes: Bytes,
131}
132
133impl PrecompileOutput {
134 pub fn from_eth_result(result: EthPrecompileResult, reservoir: u64) -> Self {
136 match result {
137 Ok(output) => Self::new(output.gas_used, output.bytes, reservoir),
138 Err(halt) => Self::halt(halt, reservoir),
139 }
140 }
141 pub const fn new(gas_used: u64, bytes: Bytes, reservoir: u64) -> Self {
143 Self {
144 status: PrecompileStatus::Success,
145 gas_used,
146 gas_refunded: 0,
147 state_gas_used: 0,
148 state_gas_spilled: 0,
149 reservoir,
150 bytes,
151 }
152 }
153
154 pub const fn halt(reason: PrecompileHalt, reservoir: u64) -> Self {
156 Self {
157 status: PrecompileStatus::Halt(reason),
158 gas_used: 0,
159 gas_refunded: 0,
160 state_gas_used: 0,
161 state_gas_spilled: 0,
162 reservoir,
163 bytes: Bytes::new(),
164 }
165 }
166
167 pub const fn revert(gas_used: u64, bytes: Bytes, reservoir: u64) -> Self {
169 Self {
170 status: PrecompileStatus::Revert,
171 gas_used,
172 gas_refunded: 0,
173 state_gas_used: 0,
174 state_gas_spilled: 0,
175 reservoir,
176 bytes,
177 }
178 }
179
180 pub const fn from_gas_tracker(
187 status: PrecompileStatus,
188 bytes: Bytes,
189 tracker: GasTracker,
190 ) -> Self {
191 let mut output = Self {
192 status,
193 gas_used: 0,
194 gas_refunded: 0,
195 state_gas_used: 0,
196 state_gas_spilled: 0,
197 reservoir: 0,
198 bytes,
199 };
200 output.set_gas(tracker);
201 output
202 }
203
204 pub const fn set_gas(&mut self, tracker: GasTracker) {
211 self.gas_used = tracker.limit().saturating_sub(tracker.remaining());
212 self.gas_refunded = tracker.refunded();
213 self.state_gas_used = tracker.state_gas_spent();
214 self.state_gas_spilled = tracker.state_gas_spilled();
215 self.reservoir = tracker.reservoir();
216 }
217
218 pub const fn to_gas_tracker(&self, gas_limit: u64) -> GasTracker {
224 let mut tracker = GasTracker::new_used_gas(gas_limit, self.gas_used, self.reservoir);
225 tracker.set_refunded(self.gas_refunded);
226 tracker.set_state_gas_spent(self.state_gas_used);
227 tracker.set_state_gas_spilled(self.state_gas_spilled);
228 tracker
229 }
230
231 pub const fn is_success(&self) -> bool {
233 matches!(self.status, PrecompileStatus::Success)
234 }
235
236 #[deprecated(note = "use `is_success` instead")]
238 pub const fn is_ok(&self) -> bool {
239 self.is_success()
240 }
241
242 pub const fn is_revert(&self) -> bool {
244 matches!(self.status, PrecompileStatus::Revert)
245 }
246
247 pub const fn is_halt(&self) -> bool {
249 matches!(self.status, PrecompileStatus::Halt(_))
250 }
251
252 #[inline]
254 pub const fn halt_reason(&self) -> Option<&PrecompileHalt> {
255 self.status.halt_reason()
256 }
257}
258
259pub trait Crypto: Send + Sync + Debug {
261 #[inline]
263 fn sha256(&self, input: &[u8]) -> [u8; 32] {
264 use sha2::Digest;
265 let output = sha2::Sha256::digest(input);
266 output.into()
267 }
268
269 #[inline]
271 fn ripemd160(&self, input: &[u8]) -> [u8; 32] {
272 use ripemd::Digest;
273 let mut hasher = ripemd::Ripemd160::new();
274 hasher.update(input);
275
276 let mut output = [0u8; 32];
277 let hash: &mut [u8; 20] = (&mut output[12..]).try_into().unwrap();
278 hasher.finalize_into(hash.into());
279 output
280 }
281
282 #[inline]
284 fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileHalt> {
285 crate::bn254::crypto_backend::g1_point_add(p1, p2)
286 }
287
288 #[inline]
290 fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileHalt> {
291 crate::bn254::crypto_backend::g1_point_mul(point, scalar)
292 }
293
294 #[inline]
296 fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result<bool, PrecompileHalt> {
297 crate::bn254::crypto_backend::pairing_check(pairs)
298 }
299
300 #[inline]
302 fn secp256k1_ecrecover(
303 &self,
304 sig: &[u8; 64],
305 recid: u8,
306 msg: &[u8; 32],
307 ) -> Result<[u8; 32], PrecompileHalt> {
308 crate::secp256k1::ecrecover_bytes(sig, recid, msg)
309 .ok_or(PrecompileHalt::Secp256k1RecoverFailed)
310 }
311
312 #[inline]
314 fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result<Vec<u8>, PrecompileHalt> {
315 Ok(crate::modexp::modexp(base, exp, modulus))
316 }
317
318 #[inline]
320 fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) {
321 crate::blake2::compress(rounds, h, m, t, f);
322 }
323
324 #[inline]
326 fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool {
327 crate::secp256r1::verify_signature(msg, sig, pk).is_some()
328 }
329
330 #[inline]
332 fn verify_kzg_proof(
333 &self,
334 z: &[u8; 32],
335 y: &[u8; 32],
336 commitment: &[u8; 48],
337 proof: &[u8; 48],
338 ) -> Result<(), PrecompileHalt> {
339 if !crate::kzg_point_evaluation::verify_kzg_proof(commitment, z, y, proof) {
340 return Err(PrecompileHalt::BlobVerifyKzgProofFailed);
341 }
342
343 Ok(())
344 }
345
346 fn bls12_381_g1_add(&self, a: G1Point, b: G1Point) -> Result<[u8; 96], PrecompileHalt> {
348 crate::bls12_381::crypto_backend::p1_add_affine_bytes(a, b)
349 }
350
351 fn bls12_381_g1_msm(
353 &self,
354 pairs: &mut dyn Iterator<Item = Result<G1PointScalar, PrecompileHalt>>,
355 ) -> Result<[u8; 96], PrecompileHalt> {
356 crate::bls12_381::crypto_backend::p1_msm_bytes(pairs)
357 }
358
359 fn bls12_381_g2_add(&self, a: G2Point, b: G2Point) -> Result<[u8; 192], PrecompileHalt> {
361 crate::bls12_381::crypto_backend::p2_add_affine_bytes(a, b)
362 }
363
364 fn bls12_381_g2_msm(
366 &self,
367 pairs: &mut dyn Iterator<Item = Result<G2PointScalar, PrecompileHalt>>,
368 ) -> Result<[u8; 192], PrecompileHalt> {
369 crate::bls12_381::crypto_backend::p2_msm_bytes(pairs)
370 }
371
372 fn bls12_381_pairing_check(
374 &self,
375 pairs: &[(G1Point, G2Point)],
376 ) -> Result<bool, PrecompileHalt> {
377 crate::bls12_381::crypto_backend::pairing_check_bytes(pairs)
378 }
379
380 fn bls12_381_fp_to_g1(&self, fp: &[u8; 48]) -> Result<[u8; 96], PrecompileHalt> {
382 crate::bls12_381::crypto_backend::map_fp_to_g1_bytes(fp)
383 }
384
385 fn bls12_381_fp2_to_g2(&self, fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], PrecompileHalt> {
387 crate::bls12_381::crypto_backend::map_fp2_to_g2_bytes(&fp2.0, &fp2.1)
388 }
389}
390
391pub type PrecompileEthFn = fn(&[u8], u64) -> EthPrecompileResult;
396
397pub type PrecompileFn = fn(&[u8], u64, u64) -> PrecompileResult;
402
403#[macro_export]
416macro_rules! eth_precompile_fn {
417 ($name:ident, $eth_fn:expr) => {
418 fn $name(input: &[u8], gas_limit: u64, reservoir: u64) -> $crate::PrecompileResult {
419 Ok($crate::call_eth_precompile(
420 $eth_fn, input, gas_limit, reservoir,
421 ))
422 }
423 };
424}
425
426#[inline]
435pub fn call_eth_precompile(
436 f: PrecompileEthFn,
437 input: &[u8],
438 gas_limit: u64,
439 reservoir: u64,
440) -> PrecompileOutput {
441 match f(input, gas_limit) {
442 Ok(output) => PrecompileOutput::new(output.gas_used, output.bytes, reservoir),
443 Err(halt) => PrecompileOutput::halt(halt, reservoir),
444 }
445}
446
447#[derive(Clone, Debug, PartialEq, Eq, Hash)]
453pub enum PrecompileHalt {
454 OutOfGas,
456 Blake2WrongLength,
458 Blake2WrongFinalIndicatorFlag,
460 ModexpExpOverflow,
462 ModexpBaseOverflow,
464 ModexpModOverflow,
466 ModexpEip7823LimitSize,
468 Bn254FieldPointNotAMember,
470 Bn254AffineGFailedToCreate,
472 Bn254PairLength,
474 BlobInvalidInputLength,
477 BlobMismatchedVersion,
479 BlobVerifyKzgProofFailed,
481 NonCanonicalFp,
483 Bls12381G1NotOnCurve,
485 Bls12381G1NotInSubgroup,
487 Bls12381G2NotOnCurve,
489 Bls12381G2NotInSubgroup,
491 Bls12381ScalarInputLength,
493 Bls12381G1AddInputLength,
495 Bls12381G1MsmInputLength,
497 Bls12381G2AddInputLength,
499 Bls12381G2MsmInputLength,
501 Bls12381PairingInputLength,
503 Bls12381MapFpToG1InputLength,
505 Bls12381MapFp2ToG2InputLength,
507 Bls12381FpPaddingInvalid,
509 Bls12381FpPaddingLength,
511 Bls12381G1PaddingLength,
513 Bls12381G2PaddingLength,
515 KzgInvalidG1Point,
517 KzgG1PointNotOnCurve,
519 KzgG1PointNotInSubgroup,
521 KzgInvalidInputLength,
523 Secp256k1RecoverFailed,
525 Other(Cow<'static, str>),
527}
528
529impl PrecompileHalt {
530 pub fn other(err: impl Into<String>) -> Self {
532 Self::Other(Cow::Owned(err.into()))
533 }
534
535 pub const fn other_static(err: &'static str) -> Self {
537 Self::Other(Cow::Borrowed(err))
538 }
539
540 pub const fn is_oog(&self) -> bool {
542 matches!(self, Self::OutOfGas)
543 }
544}
545
546impl core::error::Error for PrecompileHalt {}
547
548impl fmt::Display for PrecompileHalt {
549 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550 let s = match self {
551 Self::OutOfGas => "out of gas",
552 Self::Blake2WrongLength => "wrong input length for blake2",
553 Self::Blake2WrongFinalIndicatorFlag => "wrong final indicator flag for blake2",
554 Self::ModexpExpOverflow => "modexp exp overflow",
555 Self::ModexpBaseOverflow => "modexp base overflow",
556 Self::ModexpModOverflow => "modexp mod overflow",
557 Self::ModexpEip7823LimitSize => "Modexp limit all input sizes.",
558 Self::Bn254FieldPointNotAMember => "field point not a member of bn254 curve",
559 Self::Bn254AffineGFailedToCreate => "failed to create affine g point for bn254 curve",
560 Self::Bn254PairLength => "bn254 invalid pair length",
561 Self::BlobInvalidInputLength => "invalid blob input length",
562 Self::BlobMismatchedVersion => "mismatched blob version",
563 Self::BlobVerifyKzgProofFailed => "verifying blob kzg proof failed",
564 Self::NonCanonicalFp => "non-canonical field element",
565 Self::Bls12381G1NotOnCurve => "bls12-381 g1 point not on curve",
566 Self::Bls12381G1NotInSubgroup => "bls12-381 g1 point not in correct subgroup",
567 Self::Bls12381G2NotOnCurve => "bls12-381 g2 point not on curve",
568 Self::Bls12381G2NotInSubgroup => "bls12-381 g2 point not in correct subgroup",
569 Self::Bls12381ScalarInputLength => "bls12-381 scalar input length error",
570 Self::Bls12381G1AddInputLength => "bls12-381 g1 add input length error",
571 Self::Bls12381G1MsmInputLength => "bls12-381 g1 msm input length error",
572 Self::Bls12381G2AddInputLength => "bls12-381 g2 add input length error",
573 Self::Bls12381G2MsmInputLength => "bls12-381 g2 msm input length error",
574 Self::Bls12381PairingInputLength => "bls12-381 pairing input length error",
575 Self::Bls12381MapFpToG1InputLength => "bls12-381 map fp to g1 input length error",
576 Self::Bls12381MapFp2ToG2InputLength => "bls12-381 map fp2 to g2 input length error",
577 Self::Bls12381FpPaddingInvalid => "bls12-381 fp 64 top bytes of input are not zero",
578 Self::Bls12381FpPaddingLength => "bls12-381 fp padding length error",
579 Self::Bls12381G1PaddingLength => "bls12-381 g1 padding length error",
580 Self::Bls12381G2PaddingLength => "bls12-381 g2 padding length error",
581 Self::KzgInvalidG1Point => "kzg invalid g1 point",
582 Self::KzgG1PointNotOnCurve => "kzg g1 point not on curve",
583 Self::KzgG1PointNotInSubgroup => "kzg g1 point not in correct subgroup",
584 Self::KzgInvalidInputLength => "kzg invalid input length",
585 Self::Secp256k1RecoverFailed => "secp256k1 signature recovery failed",
586 Self::Other(s) => s,
587 };
588 f.write_str(s)
589 }
590}
591
592#[derive(Clone, Debug, PartialEq, Eq, Hash)]
600pub enum PrecompileError {
601 Fatal(String),
603 FatalAny(AnyError),
605}
606
607impl PrecompileError {
608 pub const fn is_fatal(&self) -> bool {
610 true
611 }
612}
613
614impl core::error::Error for PrecompileError {}
615
616impl fmt::Display for PrecompileError {
617 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618 match self {
619 Self::Fatal(s) => write!(f, "fatal: {s}"),
620 Self::FatalAny(s) => write!(f, "fatal: {s}"),
621 }
622 }
623}
624
625#[derive(Clone, Debug)]
627pub struct DefaultCrypto;
628
629impl Crypto for DefaultCrypto {}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
638 fn gas_tracker_round_trip() {
639 let mut tracker = GasTracker::new(100, 100, 10);
642 assert!(tracker.record_regular_cost(10));
643 assert!(tracker.record_state_cost(30));
644 tracker.record_refund(5);
645 assert_eq!(
646 (tracker.remaining(), tracker.reservoir()),
647 (70, 0),
648 "10 regular + 20 spilled state gas"
649 );
650
651 let output =
652 PrecompileOutput::from_gas_tracker(PrecompileStatus::Success, Bytes::new(), tracker);
653 assert_eq!(output.gas_used, 30);
654 assert_eq!(output.gas_refunded, 5);
655 assert_eq!(output.state_gas_used, 30);
656 assert_eq!(output.state_gas_spilled, 20);
657 assert_eq!(output.reservoir, 0);
658
659 assert_eq!(output.to_gas_tracker(tracker.limit()), tracker);
660
661 let mut existing = PrecompileOutput::revert(1, Bytes::from_static(b"out"), 999);
663 existing.set_gas(tracker);
664 assert_eq!(existing.status, PrecompileStatus::Revert);
665 assert_eq!(existing.bytes, Bytes::from_static(b"out"));
666 assert_eq!(
667 (
668 existing.gas_used,
669 existing.gas_refunded,
670 existing.state_gas_used,
671 existing.state_gas_spilled,
672 existing.reservoir
673 ),
674 (30, 5, 30, 20, 0)
675 );
676 }
677
678 #[test]
680 fn to_gas_tracker_saturates_on_overspend() {
681 let mut output = PrecompileOutput::new(0, Bytes::new(), 0);
682 output.gas_used = u64::MAX;
683 assert_eq!(output.to_gas_tracker(100).remaining(), 0);
684 }
685}