Skip to main content

risc0_steel/
lib.rs

1// Copyright 2026 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![cfg_attr(not(doctest), doc = include_str!("../../../README.md"))]
16#![deny(rustdoc::broken_intra_doc_links)]
17#![cfg_attr(docsrs, feature(doc_cfg))]
18
19/// Re-export of [alloy], provided to ensure that the correct version of the types used in the
20/// public API are available in case multiple versions of [alloy] are in use.
21#[cfg(feature = "host")]
22pub use alloy;
23pub use revm;
24
25use ::serde::{de::DeserializeOwned, Deserialize, Serialize};
26use alloy_evm::{Database, Evm, EvmError, IntoTxEnv};
27use alloy_primitives::{
28    uint, Address, BlockNumber, Bloom, Bytes, ChainId, Log, Sealable, Sealed, B256, U256,
29};
30use alloy_rpc_types::Filter;
31use alloy_sol_types::SolValue;
32use config::ChainSpec;
33use revm::{
34    context::{result::HaltReasonTr, BlockEnv},
35    Database as RevmDatabase,
36};
37use std::{error::Error, fmt, fmt::Debug};
38
39pub mod account;
40pub mod beacon;
41mod block;
42pub mod config;
43mod contract;
44pub mod ethereum;
45pub mod event;
46pub mod history;
47#[cfg(feature = "host")]
48pub mod host;
49mod merkle;
50mod mpt;
51pub mod serde;
52mod state;
53#[cfg(test)]
54mod test_utils;
55mod verifier;
56
57pub use account::Account;
58pub use beacon::BeaconInput;
59pub use block::BlockInput;
60pub use contract::{CallBuilder, Contract};
61pub use event::Event;
62pub use history::HistoryInput;
63pub use mpt::MerkleTrie;
64pub use state::{StateAccount, StateDb};
65pub use verifier::SteelVerifier;
66
67/// The serializable input to derive and validate an [EvmEnv] from.
68#[non_exhaustive]
69#[derive(Clone, Serialize, Deserialize)]
70pub enum EvmInput<F: EvmFactory> {
71    /// Input committing to the corresponding execution block hash.
72    Block(BlockInput<F>),
73    /// Input committing to the corresponding Beacon Chain block root.
74    Beacon(BeaconInput<F>),
75    /// Input recursively committing to multiple Beacon Chain block root.
76    History(HistoryInput<F>),
77}
78
79impl<F: EvmFactory> EvmInput<F> {
80    /// Converts the input into a [EvmEnv] for execution.
81    ///
82    /// This method verifies that the state matches the state root in the header and panics if not.
83    #[inline]
84    pub fn into_env(self, chain_spec: &ChainSpec<F::SpecId>) -> GuestEvmEnv<F> {
85        match self {
86            EvmInput::Block(input) => input.into_env(chain_spec),
87            EvmInput::Beacon(input) => input.into_env(chain_spec),
88            EvmInput::History(input) => input.into_env(chain_spec),
89        }
90    }
91}
92
93/// A trait linking the block header to a commitment.
94pub trait BlockHeaderCommit<H> {
95    /// Creates a verifiable [Commitment] of the `header`.
96    fn commit(self, header: &Sealed<H>, config_id: B256) -> Commitment;
97}
98
99/// A generalized input type consisting of a block-based input and a commitment wrapper.
100///
101/// The `commit` field provides a mechanism to generate a commitment to the block header
102/// contained within the `input` field.
103#[derive(Clone, Serialize, Deserialize)]
104pub struct ComposeInput<F: EvmFactory, C> {
105    input: BlockInput<F>,
106    commit: C,
107}
108
109impl<F: EvmFactory, C: BlockHeaderCommit<F::Header>> ComposeInput<F, C> {
110    /// Creates a new composed input from a [BlockInput] and a [BlockHeaderCommit].
111    pub const fn new(input: BlockInput<F>, commit: C) -> Self {
112        Self { input, commit }
113    }
114
115    /// Disassembles this `ComposeInput`, returning the underlying input and commitment creator.
116    pub fn into_parts(self) -> (BlockInput<F>, C) {
117        (self.input, self.commit)
118    }
119
120    /// Converts the input into a [EvmEnv] for verifiable state access in the guest.
121    pub fn into_env(self, chain_spec: &ChainSpec<F::SpecId>) -> GuestEvmEnv<F> {
122        let mut env = self.input.into_env(chain_spec);
123        env.commit = self.commit.commit(&env.header, env.commit.configID);
124
125        env
126    }
127}
128
129/// A database abstraction for the Steel EVM.
130pub trait EvmDatabase: RevmDatabase {
131    /// Retrieves all the logs matching the given [Filter].
132    ///
133    /// It returns an error, if the corresponding logs cannot be retrieved from DB.
134    /// The filter must match the block hash corresponding to the DB, it will panic otherwise.
135    fn logs(&mut self, filter: Filter) -> Result<Vec<Log>, <Self as RevmDatabase>::Error>;
136}
137
138/// Alias for readability, do not make public.
139pub(crate) type GuestEvmEnv<F> = EvmEnv<StateDb, F, Commitment>;
140
141/// Abstracts the creation and configuration of a specific EVM implementation.
142///
143/// This trait acts as a factory pattern, allowing generic code (like `Contract` and `CallBuilder`)
144/// to operate with different underlying EVM engines (e.g., `revm`) without being
145/// tightly coupled to a specific implementation. Implementers define the concrete types
146/// associated with their chosen EVM and provide the logic to instantiate it.
147pub trait EvmFactory {
148    /// The concrete EVM execution environment type created by this factory.
149    type Evm<DB: Database>: Evm<
150        DB = DB,
151        Tx = Self::Tx,
152        HaltReason = Self::HaltReason,
153        Error = Self::Error<DB::Error>,
154        Spec = Self::Spec,
155    >;
156    /// The transaction environment type compatible with `Self::Evm`.
157    type Tx: IntoTxEnv<Self::Tx> + Send + Sync + 'static;
158    /// The error type returned by `Self::Evm` during execution.
159    type Error<DBError: Error + Send + Sync + 'static>: EvmError;
160    /// The type representing reasons why `Self::Evm` might halt execution.
161    type HaltReason: HaltReasonTr + Send + Sync + 'static;
162    /// The EVM specification identifier (e.g., Shanghai, Cancun) used by `Self::Evm`.
163    type Spec: Ord + Serialize + Debug + Copy + Send + Sync + 'static;
164    /// The specification identifier (e.g., Shanghai, Cancun).
165    type SpecId: EvmSpecId + Into<Self::Spec> + Copy + Send + Sync + 'static;
166    /// The block header type providing execution context (e.g., timestamp, number, basefee).
167    type Header: EvmBlockHeader<SpecId = Self::SpecId>
168        + Clone
169        + Serialize
170        + DeserializeOwned
171        + Send
172        + Sync
173        + 'static;
174
175    /// Creates a new transaction environment instance for a basic call.
176    ///
177    /// Implementers should create an instance of `Self::Tx`,
178    /// populate it with the target `address` and input `data`, and apply appropriate
179    /// defaults for other transaction fields (like caller, value, gas limit, etc.)
180    /// required by the specific EVM implementation.
181    fn new_tx(address: Address, data: Bytes) -> Self::Tx;
182
183    /// Creates a new instance of the EVM defined by `Self::Evm`.
184    fn create_evm<DB: Database>(
185        db: DB,
186        chain_id: ChainId,
187        spec_id: Self::SpecId,
188        header: &Self::Header,
189    ) -> Self::Evm<DB>;
190}
191
192/// Represents the complete execution environment for EVM contract calls.
193///
194/// This struct encapsulates all necessary components to configure and run an EVM instance
195/// compatible with the specified [EvmFactory]. It serves as the primary context object passed
196/// around during EVM execution setup and interaction, both on the host (for preflight) and in the
197/// guest.
198pub struct EvmEnv<D, F: EvmFactory, C> {
199    /// The database instance providing EVM state (accounts, storage).
200    ///
201    /// This is wrapped in an `Option` because ownership might need to be temporarily
202    /// transferred during certain operations, particularly when moving execution into
203    /// a blocking task or thread on the host during preflight simulation.
204    db: Option<D>,
205    /// The Chain ID of the EVM network (EIP-155).
206    chain_id: ChainId,
207    /// The EVM specification identifier, representing the active hardfork (e.g., Shanghai,
208    /// Cancun).
209    spec_id: F::SpecId,
210    /// The sealed block header providing context for the current transaction execution.
211    header: Sealed<F::Header>,
212    /// Auxiliary context or commitment handler.
213    commit: C,
214}
215
216impl<D, F: EvmFactory, C> EvmEnv<D, F, C> {
217    /// Creates a new environment.
218    pub(crate) fn new(
219        db: D,
220        chain_spec: &ChainSpec<F::SpecId>,
221        header: Sealed<F::Header>,
222        commit: C,
223    ) -> Self {
224        let spec_id = *chain_spec
225            .active_fork(header.number(), header.timestamp())
226            .unwrap();
227        Self {
228            db: Some(db),
229            chain_id: chain_spec.chain_id,
230            spec_id,
231            header,
232            commit,
233        }
234    }
235
236    /// Returns the sealed header of the environment.
237    #[inline]
238    pub fn header(&self) -> &Sealed<F::Header> {
239        &self.header
240    }
241
242    pub(crate) fn db(&self) -> &D {
243        // safe unwrap: self cannot be borrowed without a DB
244        self.db.as_ref().unwrap()
245    }
246
247    #[cfg(feature = "host")]
248    pub(crate) fn db_mut(&mut self) -> &mut D {
249        // safe unwrap: self cannot be borrowed without a DB
250        self.db.as_mut().unwrap()
251    }
252}
253
254impl<D, F: EvmFactory> EvmEnv<D, F, Commitment> {
255    /// Returns the [Commitment] used to validate the environment.
256    #[inline]
257    pub fn commitment(&self) -> &Commitment {
258        &self.commit
259    }
260
261    /// Consumes and returns the [Commitment] used to validate the environment.
262    #[inline]
263    pub fn into_commitment(self) -> Commitment {
264        self.commit
265    }
266}
267
268/// Steel abstraction of the EVM specification identifier (e.g., London, Shanghai).
269pub trait EvmSpecId: Ord {
270    /// Whether EIP-4788 has been activated.
271    fn has_eip4788(&self) -> bool;
272    /// Whether EIP-2935 has been activated.
273    fn has_eip2935(&self) -> bool;
274    /// Converts the specification ID into an `u32`. This is used to compute [ChainSpec::digest()].
275    ///
276    /// This must return a unique integer for each distinct specification. Different chains can have
277    /// clashing specifications as long as their chain IDs are different.
278    fn to_u32(&self) -> u32;
279}
280
281/// An EVM abstraction of a block header.
282pub trait EvmBlockHeader: Sealable {
283    /// Associated type for the EVM specification identifier (e.g., London, Shanghai).
284    type SpecId: Copy;
285
286    /// Returns the hash of the parent block's header.
287    fn parent_hash(&self) -> &B256;
288    /// Returns the block number.
289    fn number(&self) -> BlockNumber;
290    /// Returns the block timestamp.
291    fn timestamp(&self) -> u64;
292    /// Returns the state root hash.
293    fn state_root(&self) -> &B256;
294    /// Returns the receipts root hash of the block.
295    fn receipts_root(&self) -> &B256;
296    /// Returns the logs bloom filter of the block
297    fn logs_bloom(&self) -> &Bloom;
298
299    /// Returns the EVM block environment equivalent to this block header.
300    fn to_block_env(&self, spec_id: Self::SpecId) -> BlockEnv;
301}
302
303// Keep everything in the Steel library private except the commitment.
304mod private {
305    use serde::{Deserialize, Serialize};
306
307    alloy_sol_types::sol! {
308        /// A Solidity struct representing a commitment used for validation within Steel proofs.
309        ///
310        /// This struct is used to commit to a specific claim, such as the hash of an execution block
311        /// or a beacon chain state root. It includes an identifier combining the claim type (version)
312        /// and a specific instance identifier (e.g., block number), the claim digest itself, and a
313        /// configuration ID to ensure the commitment is valid for the intended network configuration.
314        /// This structure is designed to be ABI-compatible with Solidity for on-chain verification.
315        #[derive(Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
316        struct Commitment {
317            /// Packed commitment identifier and version.
318            ///
319            /// This field encodes two distinct pieces of information into a single 256-bit unsigned integer:
320            /// 1.  **Version (Top 16 bits):** Bits `[255..240]` store a `u16` representing the type or version
321            ///     of the claim being made. See [CommitmentVersion] for defined values like
322            ///     `Block` or `Beacon`.
323            /// 2.  **Identifier (Bottom 64 bits):** Bits `[63..0]` store a `u64` value that uniquely identifies
324            ///     the specific instance of the claim. For example, for a `Block` commitment, this
325            ///     would be the block number. For a `Beacon` commitment, it would be the slot number.
326            ///
327            /// Use [Commitment::decode_id] to unpack this field into its constituent parts in Rust code.
328            /// The packing scheme ensures efficient storage and retrieval while maintaining compatibility
329            /// with Solidity's `uint256`.
330            ///
331            /// [CommitmentVersion]: crate::CommitmentVersion
332            uint256 id;
333
334            /// The cryptographic digest representing the core claim data.
335            ///
336            /// This is the actual data being attested to. The exact meaning depends on the `version` specified in the `id` field.
337            bytes32 digest;
338
339            /// A cryptographic digest identifying the network and prover configuration.
340            ///
341            /// This ID acts as a fingerprint of the context in which the commitment was generated,
342            /// including details like the Ethereum chain ID, active hard forks (part of the chain spec),
343            /// and potentially prover-specific settings. Verification must ensure this `configID`
344            /// matches the verifier's current environment configuration to prevent cross-chain or
345            /// misconfigured proof validation.
346            bytes32 configID;
347        }
348    }
349}
350
351// Publicly export only the Commitment struct definition generated by the sol! macro.
352pub use private::Commitment;
353
354/// Version identifier for a [Commitment], indicating the type of claim.
355///
356/// This enum defines the valid types of commitments that can be created and verified.
357/// The raw `u16` value of the enum variant is stored in the top 16 bits of the
358/// [Commitment::id] field.
359#[derive(Debug, Copy, Clone, PartialEq, Eq, enumn::N)]
360#[repr(u16)]
361#[non_exhaustive]
362pub enum CommitmentVersion {
363    /// Version 0: Commitment to an execution block hash indexed by its block number.
364    Block = 0,
365    /// Version 1: Commitment to a beacon block root indexed by its EIP-4788 child timestamp.
366    Beacon = 1,
367    /// Version 2: Commitment to a beacon block root indexed by its slot.
368    Consensus = 2,
369}
370
371impl Commitment {
372    /// The size in bytes of the ABI-encoded commitment (3 fields * 32 bytes/field = 96 bytes).
373    pub const ABI_ENCODED_SIZE: usize = 3 * 32;
374
375    /// Creates a new [Commitment] by packing the version and identifier into the `id` field.
376    #[inline]
377    pub const fn new(version: u16, id: u64, digest: B256, config_id: B256) -> Commitment {
378        Self {
379            id: Commitment::encode_id(id, version), // pack id and version
380            digest,
381            configID: config_id,
382        }
383    }
384
385    /// Decodes the packed `Commitment.id` field into the identifier part and the version.
386    ///
387    /// This function extracts the version from the top 16 bits and returns the remaining part of
388    /// the `id` field (which contains the instance identifier in its lower 64 bits) along with the
389    /// `u16` version.
390    #[inline]
391    pub fn decode_id(&self) -> (U256, u16) {
392        // define a mask to isolate the lower 240 bits (zeroing out the top 16 version bits)
393        let id_mask =
394            uint!(0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256);
395        let id_part = self.id & id_mask;
396
397        // extract the version by right-shifting the most significant limb (limbs[3]) by 48 bits
398        let version = (self.id.as_limbs()[3] >> 48) as u16;
399
400        (id_part, version)
401    }
402
403    /// ABI-encodes the commitment into a byte vector according to Solidity ABI specifications.
404    #[inline]
405    pub fn abi_encode(&self) -> Vec<u8> {
406        SolValue::abi_encode(self)
407    }
408
409    /// Packs a `u64` identifier and a `u16` version into a single `U256` value.
410    const fn encode_id(id: u64, version: u16) -> U256 {
411        U256::from_limbs([id, 0, 0, (version as u64) << 48])
412    }
413}
414
415impl Debug for Commitment {
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        let (id, version_code) = self.decode_id();
418        let version = match CommitmentVersion::n(version_code) {
419            Some(v) => format!("{v:?}"),
420            None => format!("Unknown({version_code:x})"),
421        };
422
423        f.debug_struct("Commitment")
424            .field("version", &version)
425            .field("id", &id)
426            .field("digest", &self.digest)
427            .field("configID", &self.configID)
428            .finish()
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435    use alloy_primitives::B256;
436
437    #[test]
438    fn size() {
439        let tests = vec![
440            Commitment::default(),
441            Commitment::new(
442                u16::MAX,
443                u64::MAX,
444                B256::repeat_byte(0xFF),
445                B256::repeat_byte(0xFF),
446            ),
447        ];
448        for test in tests {
449            assert_eq!(test.abi_encode().len(), Commitment::ABI_ENCODED_SIZE);
450        }
451    }
452
453    #[test]
454    fn versioned_id() {
455        let tests = vec![(u64::MAX, u16::MAX), (u64::MAX, 0), (0, u16::MAX), (0, 0)];
456        for test in tests {
457            let commit = Commitment::new(test.1, test.0, B256::default(), B256::default());
458            let (id, version) = commit.decode_id();
459            assert_eq!((id.to(), version), test);
460        }
461    }
462}