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: u64,
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        hasher.finalize_into((&mut output[12..]).into());
216        output
217    }
218
219    /// BN254 elliptic curve addition.
220    #[inline]
221    fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileHalt> {
222        crate::bn254::crypto_backend::g1_point_add(p1, p2)
223    }
224
225    /// BN254 elliptic curve scalar multiplication.
226    #[inline]
227    fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileHalt> {
228        crate::bn254::crypto_backend::g1_point_mul(point, scalar)
229    }
230
231    /// BN254 pairing check.
232    #[inline]
233    fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result<bool, PrecompileHalt> {
234        crate::bn254::crypto_backend::pairing_check(pairs)
235    }
236
237    /// secp256k1 ECDSA signature recovery.
238    #[inline]
239    fn secp256k1_ecrecover(
240        &self,
241        sig: &[u8; 64],
242        recid: u8,
243        msg: &[u8; 32],
244    ) -> Result<[u8; 32], PrecompileHalt> {
245        crate::secp256k1::ecrecover_bytes(sig, recid, msg)
246            .ok_or(PrecompileHalt::Secp256k1RecoverFailed)
247    }
248
249    /// Modular exponentiation.
250    #[inline]
251    fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result<Vec<u8>, PrecompileHalt> {
252        Ok(crate::modexp::modexp(base, exp, modulus))
253    }
254
255    /// Blake2 compression function.
256    #[inline]
257    fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) {
258        crate::blake2::algo::compress(rounds as usize, h, m, t, f);
259    }
260
261    /// secp256r1 (P-256) signature verification.
262    #[inline]
263    fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool {
264        crate::secp256r1::verify_signature(msg, sig, pk).is_some()
265    }
266
267    /// KZG point evaluation.
268    #[inline]
269    fn verify_kzg_proof(
270        &self,
271        z: &[u8; 32],
272        y: &[u8; 32],
273        commitment: &[u8; 48],
274        proof: &[u8; 48],
275    ) -> Result<(), PrecompileHalt> {
276        if !crate::kzg_point_evaluation::verify_kzg_proof(commitment, z, y, proof) {
277            return Err(PrecompileHalt::BlobVerifyKzgProofFailed);
278        }
279
280        Ok(())
281    }
282
283    /// BLS12-381 G1 addition (returns 96-byte unpadded G1 point)
284    fn bls12_381_g1_add(&self, a: G1Point, b: G1Point) -> Result<[u8; 96], PrecompileHalt> {
285        crate::bls12_381::crypto_backend::p1_add_affine_bytes(a, b)
286    }
287
288    /// BLS12-381 G1 multi-scalar multiplication (returns 96-byte unpadded G1 point)
289    fn bls12_381_g1_msm(
290        &self,
291        pairs: &mut dyn Iterator<Item = Result<G1PointScalar, PrecompileHalt>>,
292    ) -> Result<[u8; 96], PrecompileHalt> {
293        crate::bls12_381::crypto_backend::p1_msm_bytes(pairs)
294    }
295
296    /// BLS12-381 G2 addition (returns 192-byte unpadded G2 point)
297    fn bls12_381_g2_add(&self, a: G2Point, b: G2Point) -> Result<[u8; 192], PrecompileHalt> {
298        crate::bls12_381::crypto_backend::p2_add_affine_bytes(a, b)
299    }
300
301    /// BLS12-381 G2 multi-scalar multiplication (returns 192-byte unpadded G2 point)
302    fn bls12_381_g2_msm(
303        &self,
304        pairs: &mut dyn Iterator<Item = Result<G2PointScalar, PrecompileHalt>>,
305    ) -> Result<[u8; 192], PrecompileHalt> {
306        crate::bls12_381::crypto_backend::p2_msm_bytes(pairs)
307    }
308
309    /// BLS12-381 pairing check.
310    fn bls12_381_pairing_check(
311        &self,
312        pairs: &[(G1Point, G2Point)],
313    ) -> Result<bool, PrecompileHalt> {
314        crate::bls12_381::crypto_backend::pairing_check_bytes(pairs)
315    }
316
317    /// BLS12-381 map field element to G1.
318    fn bls12_381_fp_to_g1(&self, fp: &[u8; 48]) -> Result<[u8; 96], PrecompileHalt> {
319        crate::bls12_381::crypto_backend::map_fp_to_g1_bytes(fp)
320    }
321
322    /// BLS12-381 map field element to G2.
323    fn bls12_381_fp2_to_g2(&self, fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], PrecompileHalt> {
324        crate::bls12_381::crypto_backend::map_fp2_to_g2_bytes(&fp2.0, &fp2.1)
325    }
326}
327
328/// Eth precompile function type. Takes input and gas limit, returns an Eth precompile result.
329///
330/// This is the function signature used by individual Ethereum precompile implementations.
331/// Use [`PrecompileFn`] for the higher-level type that returns [`PrecompileOutput`].
332pub type PrecompileEthFn = fn(&[u8], u64) -> EthPrecompileResult;
333
334/// Precompile function type. Takes input, gas limit and reservoir, returns a [`PrecompileResult`].
335///
336/// Returns `Ok(PrecompileOutput)` for successful execution or non-fatal halts,
337/// or `Err(PrecompileError)` for fatal/unrecoverable errors that should abort EVM execution.
338pub type PrecompileFn = fn(&[u8], u64, u64) -> PrecompileResult;
339
340/// Macro that generates a thin wrapper function converting a [`PrecompileEthFn`] into a [`PrecompileFn`].
341///
342/// Usage:
343/// ```ignore
344/// eth_precompile_fn!(my_precompile, my_eth_fn);
345/// ```
346/// Expands to:
347/// ```ignore
348/// fn my_precompile(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileOutput {
349///     call_eth_precompile(my_eth_fn, input, gas_limit, reservoir)
350/// }
351/// ```
352#[macro_export]
353macro_rules! eth_precompile_fn {
354    ($name:ident, $eth_fn:expr) => {
355        fn $name(input: &[u8], gas_limit: u64, reservoir: u64) -> $crate::PrecompileResult {
356            Ok($crate::call_eth_precompile(
357                $eth_fn, input, gas_limit, reservoir,
358            ))
359        }
360    };
361}
362
363/// Calls a [`PrecompileEthFn`] and wraps the result into a [`PrecompileOutput`].
364///
365/// Use this in wrapper functions to adapt an eth precompile to the [`PrecompileFn`] signature:
366/// ```ignore
367/// fn my_precompile(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileOutput {
368///     call_eth_precompile(my_eth_fn, input, gas_limit, reservoir)
369/// }
370/// ```
371#[inline]
372pub fn call_eth_precompile(
373    f: PrecompileEthFn,
374    input: &[u8],
375    gas_limit: u64,
376    reservoir: u64,
377) -> PrecompileOutput {
378    match f(input, gas_limit) {
379        Ok(output) => PrecompileOutput::new(output.gas_used, output.bytes, reservoir),
380        Err(halt) => PrecompileOutput::halt(halt, reservoir),
381    }
382}
383
384/// Non-fatal halt reasons for precompiles.
385///
386/// These represent conditions that halt precompile execution but do not abort
387/// the entire EVM transaction. They are expressed through [`PrecompileStatus::Halt`]
388/// at the provider level.
389#[derive(Clone, Debug, PartialEq, Eq, Hash)]
390pub enum PrecompileHalt {
391    /// out of gas is the main error. Others are here just for completeness
392    OutOfGas,
393    /// Blake2 errors
394    Blake2WrongLength,
395    /// Blake2 wrong final indicator flag
396    Blake2WrongFinalIndicatorFlag,
397    /// Modexp errors
398    ModexpExpOverflow,
399    /// Modexp base overflow
400    ModexpBaseOverflow,
401    /// Modexp mod overflow
402    ModexpModOverflow,
403    /// Modexp limit all input sizes.
404    ModexpEip7823LimitSize,
405    /// Bn254 errors
406    Bn254FieldPointNotAMember,
407    /// Bn254 affine g failed to create
408    Bn254AffineGFailedToCreate,
409    /// Bn254 pair length
410    Bn254PairLength,
411    // Blob errors
412    /// The input length is not exactly 192 bytes
413    BlobInvalidInputLength,
414    /// The commitment does not match the versioned hash
415    BlobMismatchedVersion,
416    /// The proof verification failed
417    BlobVerifyKzgProofFailed,
418    /// Non-canonical field element
419    NonCanonicalFp,
420    /// BLS12-381 G1 point not on curve
421    Bls12381G1NotOnCurve,
422    /// BLS12-381 G1 point not in correct subgroup
423    Bls12381G1NotInSubgroup,
424    /// BLS12-381 G2 point not on curve
425    Bls12381G2NotOnCurve,
426    /// BLS12-381 G2 point not in correct subgroup
427    Bls12381G2NotInSubgroup,
428    /// BLS12-381 scalar input length error
429    Bls12381ScalarInputLength,
430    /// BLS12-381 G1 add input length error
431    Bls12381G1AddInputLength,
432    /// BLS12-381 G1 msm input length error
433    Bls12381G1MsmInputLength,
434    /// BLS12-381 G2 add input length error
435    Bls12381G2AddInputLength,
436    /// BLS12-381 G2 msm input length error
437    Bls12381G2MsmInputLength,
438    /// BLS12-381 pairing input length error
439    Bls12381PairingInputLength,
440    /// BLS12-381 map fp to g1 input length error
441    Bls12381MapFpToG1InputLength,
442    /// BLS12-381 map fp2 to g2 input length error
443    Bls12381MapFp2ToG2InputLength,
444    /// BLS12-381 padding error
445    Bls12381FpPaddingInvalid,
446    /// BLS12-381 fp padding length error
447    Bls12381FpPaddingLength,
448    /// BLS12-381 g1 padding length error
449    Bls12381G1PaddingLength,
450    /// BLS12-381 g2 padding length error
451    Bls12381G2PaddingLength,
452    /// KZG invalid G1 point
453    KzgInvalidG1Point,
454    /// KZG G1 point not on curve
455    KzgG1PointNotOnCurve,
456    /// KZG G1 point not in correct subgroup
457    KzgG1PointNotInSubgroup,
458    /// KZG input length error
459    KzgInvalidInputLength,
460    /// secp256k1 ecrecover failed
461    Secp256k1RecoverFailed,
462    /// Catch-all variant for precompile halt reasons without a dedicated variant.
463    Other(Cow<'static, str>),
464}
465
466impl PrecompileHalt {
467    /// Returns another halt reason with the given message.
468    pub fn other(err: impl Into<String>) -> Self {
469        Self::Other(Cow::Owned(err.into()))
470    }
471
472    /// Returns another halt reason with the given static string.
473    pub const fn other_static(err: &'static str) -> Self {
474        Self::Other(Cow::Borrowed(err))
475    }
476
477    /// Returns `true` if the halt reason is out of gas.
478    pub const fn is_oog(&self) -> bool {
479        matches!(self, Self::OutOfGas)
480    }
481}
482
483impl core::error::Error for PrecompileHalt {}
484
485impl fmt::Display for PrecompileHalt {
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        let s = match self {
488            Self::OutOfGas => "out of gas",
489            Self::Blake2WrongLength => "wrong input length for blake2",
490            Self::Blake2WrongFinalIndicatorFlag => "wrong final indicator flag for blake2",
491            Self::ModexpExpOverflow => "modexp exp overflow",
492            Self::ModexpBaseOverflow => "modexp base overflow",
493            Self::ModexpModOverflow => "modexp mod overflow",
494            Self::ModexpEip7823LimitSize => "Modexp limit all input sizes.",
495            Self::Bn254FieldPointNotAMember => "field point not a member of bn254 curve",
496            Self::Bn254AffineGFailedToCreate => "failed to create affine g point for bn254 curve",
497            Self::Bn254PairLength => "bn254 invalid pair length",
498            Self::BlobInvalidInputLength => "invalid blob input length",
499            Self::BlobMismatchedVersion => "mismatched blob version",
500            Self::BlobVerifyKzgProofFailed => "verifying blob kzg proof failed",
501            Self::NonCanonicalFp => "non-canonical field element",
502            Self::Bls12381G1NotOnCurve => "bls12-381 g1 point not on curve",
503            Self::Bls12381G1NotInSubgroup => "bls12-381 g1 point not in correct subgroup",
504            Self::Bls12381G2NotOnCurve => "bls12-381 g2 point not on curve",
505            Self::Bls12381G2NotInSubgroup => "bls12-381 g2 point not in correct subgroup",
506            Self::Bls12381ScalarInputLength => "bls12-381 scalar input length error",
507            Self::Bls12381G1AddInputLength => "bls12-381 g1 add input length error",
508            Self::Bls12381G1MsmInputLength => "bls12-381 g1 msm input length error",
509            Self::Bls12381G2AddInputLength => "bls12-381 g2 add input length error",
510            Self::Bls12381G2MsmInputLength => "bls12-381 g2 msm input length error",
511            Self::Bls12381PairingInputLength => "bls12-381 pairing input length error",
512            Self::Bls12381MapFpToG1InputLength => "bls12-381 map fp to g1 input length error",
513            Self::Bls12381MapFp2ToG2InputLength => "bls12-381 map fp2 to g2 input length error",
514            Self::Bls12381FpPaddingInvalid => "bls12-381 fp 64 top bytes of input are not zero",
515            Self::Bls12381FpPaddingLength => "bls12-381 fp padding length error",
516            Self::Bls12381G1PaddingLength => "bls12-381 g1 padding length error",
517            Self::Bls12381G2PaddingLength => "bls12-381 g2 padding length error",
518            Self::KzgInvalidG1Point => "kzg invalid g1 point",
519            Self::KzgG1PointNotOnCurve => "kzg g1 point not on curve",
520            Self::KzgG1PointNotInSubgroup => "kzg g1 point not in correct subgroup",
521            Self::KzgInvalidInputLength => "kzg invalid input length",
522            Self::Secp256k1RecoverFailed => "secp256k1 signature recovery failed",
523            Self::Other(s) => s,
524        };
525        f.write_str(s)
526    }
527}
528
529/// Fatal precompile error type.
530///
531/// These errors represent unrecoverable conditions that abort the entire EVM
532/// transaction. They propagate as `EVMError::Custom`.
533///
534/// For non-fatal halt reasons (like out-of-gas or invalid input), see
535/// [`PrecompileHalt`] which is expressed through [`PrecompileStatus::Halt`].
536#[derive(Clone, Debug, PartialEq, Eq, Hash)]
537pub enum PrecompileError {
538    /// Unrecoverable error that halts EVM execution.
539    Fatal(String),
540    /// Unrecoverable error that halts EVM execution.
541    FatalAny(AnyError),
542}
543
544impl PrecompileError {
545    /// Returns `true` if the error is `Fatal` or `FatalAny`.
546    pub const fn is_fatal(&self) -> bool {
547        true
548    }
549}
550
551impl core::error::Error for PrecompileError {}
552
553impl fmt::Display for PrecompileError {
554    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555        match self {
556            Self::Fatal(s) => write!(f, "fatal: {s}"),
557            Self::FatalAny(s) => write!(f, "fatal: {s}"),
558        }
559    }
560}
561
562/// Default implementation of the Crypto trait using the existing crypto libraries.
563#[derive(Clone, Debug)]
564pub struct DefaultCrypto;
565
566impl Crypto for DefaultCrypto {}