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