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