Skip to main content

revm_precompile/
interface.rs

1//! Interface for the precompiles. It contains the precompile result type,
2//! the precompile output type, and the precompile error type.
3use context_interface::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
10/// Global crypto provider instance
11static CRYPTO: OnceLock<Box<dyn Crypto>> = OnceLock::new();
12
13/// Install a custom crypto provider globally.
14pub fn install_crypto<C: Crypto + 'static>(crypto: C) -> bool {
15    CRYPTO.set(Box::new(crypto)).is_ok()
16}
17
18/// Get the installed crypto provider, or the default if none is installed.
19pub fn crypto() -> &'static dyn Crypto {
20    CRYPTO.get_or_init(|| Box::new(DefaultCrypto)).as_ref()
21}
22
23/// A precompile operation result type for individual Ethereum precompile functions.
24///
25/// Returns either `Ok(EthPrecompileOutput)` or `Err(PrecompileHalt)`.
26pub type EthPrecompileResult = Result<EthPrecompileOutput, PrecompileHalt>;
27
28/// A precompile operation result type for the precompile provider.
29///
30/// Returns either `Ok(PrecompileOutput)` or `Err(PrecompileError)`.
31/// `PrecompileError` only represents fatal errors that abort EVM execution.
32pub type PrecompileResult = Result<PrecompileOutput, PrecompileError>;
33
34/// Simple precompile execution output used by individual Ethereum precompile functions.
35///
36/// Contains only the gas used and output bytes. For the richer output type
37/// with state gas accounting and halt support, see [`PrecompileOutput`].
38#[derive(Clone, Debug, PartialEq, Eq, Hash)]
39pub struct EthPrecompileOutput {
40    /// Gas used by the precompile.
41    pub gas_used: u64,
42    /// Output bytes
43    pub bytes: Bytes,
44}
45
46impl EthPrecompileOutput {
47    /// Returns new precompile output with the given gas used and output bytes.
48    pub const fn new(gas_used: u64, bytes: Bytes) -> Self {
49        Self { gas_used, bytes }
50    }
51}
52
53/// Status of a precompile execution.
54#[derive(Clone, Debug, PartialEq, Eq, Hash)]
55pub enum PrecompileStatus {
56    /// Precompile executed successfully.
57    Success,
58    /// Precompile reverted (non-fatal, returns remaining gas).
59    Revert,
60    /// Precompile halted with a specific reason.
61    Halt(PrecompileHalt),
62}
63
64impl PrecompileStatus {
65    /// Returns `true` if the precompile execution was successful or reverted.
66    #[inline]
67    pub const fn is_success_or_revert(&self) -> bool {
68        matches!(self, PrecompileStatus::Success | PrecompileStatus::Revert)
69    }
70
71    /// Returns `true` if the precompile execution was reverted or halted.
72    #[inline]
73    pub const fn is_revert_or_halt(&self) -> bool {
74        matches!(self, PrecompileStatus::Revert | PrecompileStatus::Halt(_))
75    }
76
77    /// Returns the halt reason if the precompile halted, `None` otherwise.
78    #[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    /// Returns `true` if the precompile execution was successful.
87    #[inline]
88    pub const fn is_success(&self) -> bool {
89        matches!(self, PrecompileStatus::Success)
90    }
91
92    /// Returns `true` if the precompile reverted.
93    #[inline]
94    pub const fn is_revert(&self) -> bool {
95        matches!(self, PrecompileStatus::Revert)
96    }
97
98    /// Returns `true` if the precompile halted.
99    #[inline]
100    pub const fn is_halt(&self) -> bool {
101        matches!(self, PrecompileStatus::Halt(_))
102    }
103}
104
105/// Rich precompile execution output with gas accounting and status support.
106///
107/// This is the output type used at the precompile provider level. It can express
108/// successful execution, reverts, and halts (non-fatal errors like out-of-gas).
109#[derive(Clone, Debug, PartialEq, Eq, Hash)]
110pub struct PrecompileOutput {
111    /// Status of the precompile execution.
112    pub status: PrecompileStatus,
113    /// Regular gas used by the precompile.
114    pub gas_used: u64,
115    /// Gas refunded by the precompile.
116    pub gas_refunded: i64,
117    /// State gas used by the precompile.
118    pub state_gas_used: i64,
119    /// Reservoir gas for EIP-8037.
120    pub reservoir: u64,
121    /// Output bytes.
122    pub bytes: Bytes,
123}
124
125impl PrecompileOutput {
126    /// Returns a new precompile output from an Ethereum precompile result.
127    pub fn from_eth_result(result: EthPrecompileResult, reservoir: u64) -> Self {
128        match result {
129            Ok(output) => Self::new(output.gas_used, output.bytes, reservoir),
130            Err(halt) => Self::halt(halt, reservoir),
131        }
132    }
133    /// Returns a new successful precompile output.
134    pub const fn new(gas_used: u64, bytes: Bytes, reservoir: u64) -> Self {
135        Self {
136            status: PrecompileStatus::Success,
137            gas_used,
138            gas_refunded: 0,
139            state_gas_used: 0,
140            reservoir,
141            bytes,
142        }
143    }
144
145    /// Returns a new halted precompile output with the given halt reason.
146    pub const fn halt(reason: PrecompileHalt, reservoir: u64) -> Self {
147        Self {
148            status: PrecompileStatus::Halt(reason),
149            gas_used: 0,
150            gas_refunded: 0,
151            state_gas_used: 0,
152            reservoir,
153            bytes: Bytes::new(),
154        }
155    }
156
157    /// Returns a new reverted precompile output.
158    pub const fn revert(gas_used: u64, bytes: Bytes, reservoir: u64) -> Self {
159        Self {
160            status: PrecompileStatus::Revert,
161            gas_used,
162            gas_refunded: 0,
163            state_gas_used: 0,
164            reservoir,
165            bytes,
166        }
167    }
168
169    /// Returns `true` if the precompile execution was successful.
170    pub const fn is_success(&self) -> bool {
171        matches!(self.status, PrecompileStatus::Success)
172    }
173
174    /// Returns `true` if the precompile execution was successful.
175    #[deprecated(note = "use `is_success` instead")]
176    pub const fn is_ok(&self) -> bool {
177        self.is_success()
178    }
179
180    /// Returns `true` if the precompile reverted.
181    pub const fn is_revert(&self) -> bool {
182        matches!(self.status, PrecompileStatus::Revert)
183    }
184
185    /// Returns `true` if the precompile halted.
186    pub const fn is_halt(&self) -> bool {
187        matches!(self.status, PrecompileStatus::Halt(_))
188    }
189
190    /// Returns the halt reason if the precompile halted, `None` otherwise.
191    #[inline]
192    pub const fn halt_reason(&self) -> Option<&PrecompileHalt> {
193        self.status.halt_reason()
194    }
195}
196
197/// Crypto operations trait for precompiles.
198pub trait Crypto: Send + Sync + Debug {
199    /// Compute SHA-256 hash
200    #[inline]
201    fn sha256(&self, input: &[u8]) -> [u8; 32] {
202        use sha2::Digest;
203        let output = sha2::Sha256::digest(input);
204        output.into()
205    }
206
207    /// Compute RIPEMD-160 hash
208    #[inline]
209    fn ripemd160(&self, input: &[u8]) -> [u8; 32] {
210        use ripemd::Digest;
211        let mut hasher = ripemd::Ripemd160::new();
212        hasher.update(input);
213
214        let mut output = [0u8; 32];
215        let hash: &mut [u8; 20] = (&mut output[12..]).try_into().unwrap();
216        hasher.finalize_into(hash.into());
217        output
218    }
219
220    /// BN254 elliptic curve addition.
221    #[inline]
222    fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileHalt> {
223        crate::bn254::crypto_backend::g1_point_add(p1, p2)
224    }
225
226    /// BN254 elliptic curve scalar multiplication.
227    #[inline]
228    fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileHalt> {
229        crate::bn254::crypto_backend::g1_point_mul(point, scalar)
230    }
231
232    /// BN254 pairing check.
233    #[inline]
234    fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result<bool, PrecompileHalt> {
235        crate::bn254::crypto_backend::pairing_check(pairs)
236    }
237
238    /// secp256k1 ECDSA signature recovery.
239    #[inline]
240    fn secp256k1_ecrecover(
241        &self,
242        sig: &[u8; 64],
243        recid: u8,
244        msg: &[u8; 32],
245    ) -> Result<[u8; 32], PrecompileHalt> {
246        crate::secp256k1::ecrecover_bytes(sig, recid, msg)
247            .ok_or(PrecompileHalt::Secp256k1RecoverFailed)
248    }
249
250    /// Modular exponentiation.
251    #[inline]
252    fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result<Vec<u8>, PrecompileHalt> {
253        Ok(crate::modexp::modexp(base, exp, modulus))
254    }
255
256    /// Blake2 compression function.
257    #[inline]
258    fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) {
259        crate::blake2::compress(rounds, h, m, t, f);
260    }
261
262    /// secp256r1 (P-256) signature verification.
263    #[inline]
264    fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool {
265        crate::secp256r1::verify_signature(msg, sig, pk).is_some()
266    }
267
268    /// KZG point evaluation.
269    #[inline]
270    fn verify_kzg_proof(
271        &self,
272        z: &[u8; 32],
273        y: &[u8; 32],
274        commitment: &[u8; 48],
275        proof: &[u8; 48],
276    ) -> Result<(), PrecompileHalt> {
277        if !crate::kzg_point_evaluation::verify_kzg_proof(commitment, z, y, proof) {
278            return Err(PrecompileHalt::BlobVerifyKzgProofFailed);
279        }
280
281        Ok(())
282    }
283
284    /// BLS12-381 G1 addition (returns 96-byte unpadded G1 point)
285    fn bls12_381_g1_add(&self, a: G1Point, b: G1Point) -> Result<[u8; 96], PrecompileHalt> {
286        crate::bls12_381::crypto_backend::p1_add_affine_bytes(a, b)
287    }
288
289    /// BLS12-381 G1 multi-scalar multiplication (returns 96-byte unpadded G1 point)
290    fn bls12_381_g1_msm(
291        &self,
292        pairs: &mut dyn Iterator<Item = Result<G1PointScalar, PrecompileHalt>>,
293    ) -> Result<[u8; 96], PrecompileHalt> {
294        crate::bls12_381::crypto_backend::p1_msm_bytes(pairs)
295    }
296
297    /// BLS12-381 G2 addition (returns 192-byte unpadded G2 point)
298    fn bls12_381_g2_add(&self, a: G2Point, b: G2Point) -> Result<[u8; 192], PrecompileHalt> {
299        crate::bls12_381::crypto_backend::p2_add_affine_bytes(a, b)
300    }
301
302    /// BLS12-381 G2 multi-scalar multiplication (returns 192-byte unpadded G2 point)
303    fn bls12_381_g2_msm(
304        &self,
305        pairs: &mut dyn Iterator<Item = Result<G2PointScalar, PrecompileHalt>>,
306    ) -> Result<[u8; 192], PrecompileHalt> {
307        crate::bls12_381::crypto_backend::p2_msm_bytes(pairs)
308    }
309
310    /// BLS12-381 pairing check.
311    fn bls12_381_pairing_check(
312        &self,
313        pairs: &[(G1Point, G2Point)],
314    ) -> Result<bool, PrecompileHalt> {
315        crate::bls12_381::crypto_backend::pairing_check_bytes(pairs)
316    }
317
318    /// BLS12-381 map field element to G1.
319    fn bls12_381_fp_to_g1(&self, fp: &[u8; 48]) -> Result<[u8; 96], PrecompileHalt> {
320        crate::bls12_381::crypto_backend::map_fp_to_g1_bytes(fp)
321    }
322
323    /// BLS12-381 map field element to G2.
324    fn bls12_381_fp2_to_g2(&self, fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], PrecompileHalt> {
325        crate::bls12_381::crypto_backend::map_fp2_to_g2_bytes(&fp2.0, &fp2.1)
326    }
327}
328
329/// Eth precompile function type. Takes input and gas limit, returns an Eth precompile result.
330///
331/// This is the function signature used by individual Ethereum precompile implementations.
332/// Use [`PrecompileFn`] for the higher-level type that returns [`PrecompileOutput`].
333pub type PrecompileEthFn = fn(&[u8], u64) -> EthPrecompileResult;
334
335/// Precompile function type. Takes input, gas limit and reservoir, returns a [`PrecompileResult`].
336///
337/// Returns `Ok(PrecompileOutput)` for successful execution or non-fatal halts,
338/// or `Err(PrecompileError)` for fatal/unrecoverable errors that should abort EVM execution.
339pub type PrecompileFn = fn(&[u8], u64, u64) -> PrecompileResult;
340
341/// Macro that generates a thin wrapper function converting a [`PrecompileEthFn`] into a [`PrecompileFn`].
342///
343/// Usage:
344/// ```ignore
345/// eth_precompile_fn!(my_precompile, my_eth_fn);
346/// ```
347/// Expands to:
348/// ```ignore
349/// fn my_precompile(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileOutput {
350///     call_eth_precompile(my_eth_fn, input, gas_limit, reservoir)
351/// }
352/// ```
353#[macro_export]
354macro_rules! eth_precompile_fn {
355    ($name:ident, $eth_fn:expr) => {
356        fn $name(input: &[u8], gas_limit: u64, reservoir: u64) -> $crate::PrecompileResult {
357            Ok($crate::call_eth_precompile(
358                $eth_fn, input, gas_limit, reservoir,
359            ))
360        }
361    };
362}
363
364/// Calls a [`PrecompileEthFn`] and wraps the result into a [`PrecompileOutput`].
365///
366/// Use this in wrapper functions to adapt an eth precompile to the [`PrecompileFn`] signature:
367/// ```ignore
368/// fn my_precompile(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileOutput {
369///     call_eth_precompile(my_eth_fn, input, gas_limit, reservoir)
370/// }
371/// ```
372#[inline]
373pub fn call_eth_precompile(
374    f: PrecompileEthFn,
375    input: &[u8],
376    gas_limit: u64,
377    reservoir: u64,
378) -> PrecompileOutput {
379    match f(input, gas_limit) {
380        Ok(output) => PrecompileOutput::new(output.gas_used, output.bytes, reservoir),
381        Err(halt) => PrecompileOutput::halt(halt, reservoir),
382    }
383}
384
385/// Non-fatal halt reasons for precompiles.
386///
387/// These represent conditions that halt precompile execution but do not abort
388/// the entire EVM transaction. They are expressed through [`PrecompileStatus::Halt`]
389/// at the provider level.
390#[derive(Clone, Debug, PartialEq, Eq, Hash)]
391pub enum PrecompileHalt {
392    /// out of gas is the main error. Others are here just for completeness
393    OutOfGas,
394    /// Blake2 errors
395    Blake2WrongLength,
396    /// Blake2 wrong final indicator flag
397    Blake2WrongFinalIndicatorFlag,
398    /// Modexp errors
399    ModexpExpOverflow,
400    /// Modexp base overflow
401    ModexpBaseOverflow,
402    /// Modexp mod overflow
403    ModexpModOverflow,
404    /// Modexp limit all input sizes.
405    ModexpEip7823LimitSize,
406    /// Bn254 errors
407    Bn254FieldPointNotAMember,
408    /// Bn254 affine g failed to create
409    Bn254AffineGFailedToCreate,
410    /// Bn254 pair length
411    Bn254PairLength,
412    // Blob errors
413    /// The input length is not exactly 192 bytes
414    BlobInvalidInputLength,
415    /// The commitment does not match the versioned hash
416    BlobMismatchedVersion,
417    /// The proof verification failed
418    BlobVerifyKzgProofFailed,
419    /// Non-canonical field element
420    NonCanonicalFp,
421    /// BLS12-381 G1 point not on curve
422    Bls12381G1NotOnCurve,
423    /// BLS12-381 G1 point not in correct subgroup
424    Bls12381G1NotInSubgroup,
425    /// BLS12-381 G2 point not on curve
426    Bls12381G2NotOnCurve,
427    /// BLS12-381 G2 point not in correct subgroup
428    Bls12381G2NotInSubgroup,
429    /// BLS12-381 scalar input length error
430    Bls12381ScalarInputLength,
431    /// BLS12-381 G1 add input length error
432    Bls12381G1AddInputLength,
433    /// BLS12-381 G1 msm input length error
434    Bls12381G1MsmInputLength,
435    /// BLS12-381 G2 add input length error
436    Bls12381G2AddInputLength,
437    /// BLS12-381 G2 msm input length error
438    Bls12381G2MsmInputLength,
439    /// BLS12-381 pairing input length error
440    Bls12381PairingInputLength,
441    /// BLS12-381 map fp to g1 input length error
442    Bls12381MapFpToG1InputLength,
443    /// BLS12-381 map fp2 to g2 input length error
444    Bls12381MapFp2ToG2InputLength,
445    /// BLS12-381 padding error
446    Bls12381FpPaddingInvalid,
447    /// BLS12-381 fp padding length error
448    Bls12381FpPaddingLength,
449    /// BLS12-381 g1 padding length error
450    Bls12381G1PaddingLength,
451    /// BLS12-381 g2 padding length error
452    Bls12381G2PaddingLength,
453    /// KZG invalid G1 point
454    KzgInvalidG1Point,
455    /// KZG G1 point not on curve
456    KzgG1PointNotOnCurve,
457    /// KZG G1 point not in correct subgroup
458    KzgG1PointNotInSubgroup,
459    /// KZG input length error
460    KzgInvalidInputLength,
461    /// secp256k1 ecrecover failed
462    Secp256k1RecoverFailed,
463    /// Catch-all variant for precompile halt reasons without a dedicated variant.
464    Other(Cow<'static, str>),
465}
466
467impl PrecompileHalt {
468    /// Returns another halt reason with the given message.
469    pub fn other(err: impl Into<String>) -> Self {
470        Self::Other(Cow::Owned(err.into()))
471    }
472
473    /// Returns another halt reason with the given static string.
474    pub const fn other_static(err: &'static str) -> Self {
475        Self::Other(Cow::Borrowed(err))
476    }
477
478    /// Returns `true` if the halt reason is out of gas.
479    pub const fn is_oog(&self) -> bool {
480        matches!(self, Self::OutOfGas)
481    }
482}
483
484impl core::error::Error for PrecompileHalt {}
485
486impl fmt::Display for PrecompileHalt {
487    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488        let s = match self {
489            Self::OutOfGas => "out of gas",
490            Self::Blake2WrongLength => "wrong input length for blake2",
491            Self::Blake2WrongFinalIndicatorFlag => "wrong final indicator flag for blake2",
492            Self::ModexpExpOverflow => "modexp exp overflow",
493            Self::ModexpBaseOverflow => "modexp base overflow",
494            Self::ModexpModOverflow => "modexp mod overflow",
495            Self::ModexpEip7823LimitSize => "Modexp limit all input sizes.",
496            Self::Bn254FieldPointNotAMember => "field point not a member of bn254 curve",
497            Self::Bn254AffineGFailedToCreate => "failed to create affine g point for bn254 curve",
498            Self::Bn254PairLength => "bn254 invalid pair length",
499            Self::BlobInvalidInputLength => "invalid blob input length",
500            Self::BlobMismatchedVersion => "mismatched blob version",
501            Self::BlobVerifyKzgProofFailed => "verifying blob kzg proof failed",
502            Self::NonCanonicalFp => "non-canonical field element",
503            Self::Bls12381G1NotOnCurve => "bls12-381 g1 point not on curve",
504            Self::Bls12381G1NotInSubgroup => "bls12-381 g1 point not in correct subgroup",
505            Self::Bls12381G2NotOnCurve => "bls12-381 g2 point not on curve",
506            Self::Bls12381G2NotInSubgroup => "bls12-381 g2 point not in correct subgroup",
507            Self::Bls12381ScalarInputLength => "bls12-381 scalar input length error",
508            Self::Bls12381G1AddInputLength => "bls12-381 g1 add input length error",
509            Self::Bls12381G1MsmInputLength => "bls12-381 g1 msm input length error",
510            Self::Bls12381G2AddInputLength => "bls12-381 g2 add input length error",
511            Self::Bls12381G2MsmInputLength => "bls12-381 g2 msm input length error",
512            Self::Bls12381PairingInputLength => "bls12-381 pairing input length error",
513            Self::Bls12381MapFpToG1InputLength => "bls12-381 map fp to g1 input length error",
514            Self::Bls12381MapFp2ToG2InputLength => "bls12-381 map fp2 to g2 input length error",
515            Self::Bls12381FpPaddingInvalid => "bls12-381 fp 64 top bytes of input are not zero",
516            Self::Bls12381FpPaddingLength => "bls12-381 fp padding length error",
517            Self::Bls12381G1PaddingLength => "bls12-381 g1 padding length error",
518            Self::Bls12381G2PaddingLength => "bls12-381 g2 padding length error",
519            Self::KzgInvalidG1Point => "kzg invalid g1 point",
520            Self::KzgG1PointNotOnCurve => "kzg g1 point not on curve",
521            Self::KzgG1PointNotInSubgroup => "kzg g1 point not in correct subgroup",
522            Self::KzgInvalidInputLength => "kzg invalid input length",
523            Self::Secp256k1RecoverFailed => "secp256k1 signature recovery failed",
524            Self::Other(s) => s,
525        };
526        f.write_str(s)
527    }
528}
529
530/// Fatal precompile error type.
531///
532/// These errors represent unrecoverable conditions that abort the entire EVM
533/// transaction. They propagate as `EVMError::Custom`.
534///
535/// For non-fatal halt reasons (like out-of-gas or invalid input), see
536/// [`PrecompileHalt`] which is expressed through [`PrecompileStatus::Halt`].
537#[derive(Clone, Debug, PartialEq, Eq, Hash)]
538pub enum PrecompileError {
539    /// Unrecoverable error that halts EVM execution.
540    Fatal(String),
541    /// Unrecoverable error that halts EVM execution.
542    FatalAny(AnyError),
543}
544
545impl PrecompileError {
546    /// Returns `true` if the error is `Fatal` or `FatalAny`.
547    pub const fn is_fatal(&self) -> bool {
548        true
549    }
550}
551
552impl core::error::Error for PrecompileError {}
553
554impl fmt::Display for PrecompileError {
555    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556        match self {
557            Self::Fatal(s) => write!(f, "fatal: {s}"),
558            Self::FatalAny(s) => write!(f, "fatal: {s}"),
559        }
560    }
561}
562
563/// Default implementation of the Crypto trait using the existing crypto libraries.
564#[derive(Clone, Debug)]
565pub struct DefaultCrypto;
566
567impl Crypto for DefaultCrypto {}