risc0_steel/
lib.rs

1// Copyright 2025 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, doc_auto_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::Spec>) -> 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::Spec>) -> 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 block header type providing execution context (e.g., timestamp, number, basefee).
165    type Header: EvmBlockHeader<Spec = Self::Spec>
166        + Clone
167        + Serialize
168        + DeserializeOwned
169        + Send
170        + Sync
171        + 'static;
172
173    /// Creates a new transaction environment instance for a basic call.
174    ///
175    /// Implementers should create an instance of `Self::Tx`,
176    /// populate it with the target `address` and input `data`, and apply appropriate
177    /// defaults for other transaction fields (like caller, value, gas limit, etc.)
178    /// required by the specific EVM implementation.
179    fn new_tx(address: Address, data: Bytes) -> Self::Tx;
180
181    /// Creates a new instance of the EVM defined by `Self::Evm`.
182    fn create_evm<DB: Database>(
183        db: DB,
184        chain_id: ChainId,
185        spec: Self::Spec,
186        header: &Self::Header,
187    ) -> Self::Evm<DB>;
188}
189
190/// Represents the complete execution environment for EVM contract calls.
191///
192/// This struct encapsulates all necessary components to configure and run an EVM instance
193/// compatible with the specified [EvmFactory]. It serves as the primary context object passed
194/// around during EVM execution setup and interaction, both on the host (for preflight) and in the
195/// guest.
196pub struct EvmEnv<D, F: EvmFactory, C> {
197    /// The database instance providing EVM state (accounts, storage).
198    ///
199    /// This is wrapped in an `Option` because ownership might need to be temporarily
200    /// transferred during certain operations, particularly when moving execution into
201    /// a blocking task or thread on the host during preflight simulation.
202    db: Option<D>,
203    /// The Chain ID of the EVM network (EIP-155).
204    chain_id: ChainId,
205    /// The EVM specification identifier, representing the active hardfork (e.g., Shanghai,
206    /// Cancun).
207    spec: F::Spec,
208    /// The sealed block header providing context for the current transaction execution.
209    header: Sealed<F::Header>,
210    /// Auxiliary context or commitment handler.
211    commit: C,
212}
213
214impl<D, F: EvmFactory, C> EvmEnv<D, F, C> {
215    /// Creates a new environment.
216    pub(crate) fn new(
217        db: D,
218        chain_spec: &ChainSpec<F::Spec>,
219        header: Sealed<F::Header>,
220        commit: C,
221    ) -> Self {
222        let spec = *chain_spec
223            .active_fork(header.number(), header.timestamp())
224            .unwrap();
225        Self {
226            db: Some(db),
227            chain_id: chain_spec.chain_id,
228            spec,
229            header,
230            commit,
231        }
232    }
233
234    /// Returns the sealed header of the environment.
235    #[inline]
236    pub fn header(&self) -> &Sealed<F::Header> {
237        &self.header
238    }
239
240    pub(crate) fn db(&self) -> &D {
241        // safe unwrap: self cannot be borrowed without a DB
242        self.db.as_ref().unwrap()
243    }
244
245    #[cfg(feature = "host")]
246    pub(crate) fn db_mut(&mut self) -> &mut D {
247        // safe unwrap: self cannot be borrowed without a DB
248        self.db.as_mut().unwrap()
249    }
250}
251
252impl<D, F: EvmFactory> EvmEnv<D, F, Commitment> {
253    /// Returns the [Commitment] used to validate the environment.
254    #[inline]
255    pub fn commitment(&self) -> &Commitment {
256        &self.commit
257    }
258
259    /// Consumes and returns the [Commitment] used to validate the environment.
260    #[inline]
261    pub fn into_commitment(self) -> Commitment {
262        self.commit
263    }
264}
265
266/// An EVM abstraction of a block header.
267pub trait EvmBlockHeader: Sealable {
268    /// Associated type for the EVM specification (e.g., London, Shanghai)
269    type Spec: Copy;
270
271    /// Returns the hash of the parent block's header.
272    fn parent_hash(&self) -> &B256;
273    /// Returns the block number.
274    fn number(&self) -> BlockNumber;
275    /// Returns the block timestamp.
276    fn timestamp(&self) -> u64;
277    /// Returns the state root hash.
278    fn state_root(&self) -> &B256;
279    /// Returns the receipts root hash of the block.
280    fn receipts_root(&self) -> &B256;
281    /// Returns the logs bloom filter of the block
282    fn logs_bloom(&self) -> &Bloom;
283
284    /// Returns the EVM block environment equivalent to this block header.
285    fn to_block_env(&self, spec: Self::Spec) -> BlockEnv;
286}
287
288// Keep everything in the Steel library private except the commitment.
289mod private {
290    alloy_sol_types::sol! {
291        /// A Solidity struct representing a commitment used for validation within Steel proofs.
292        ///
293        /// This struct is used to commit to a specific claim, such as the hash of an execution block
294        /// or a beacon chain state root. It includes an identifier combining the claim type (version)
295        /// and a specific instance identifier (e.g., block number), the claim digest itself, and a
296        /// configuration ID to ensure the commitment is valid for the intended network configuration.
297        /// This structure is designed to be ABI-compatible with Solidity for on-chain verification.
298        #[derive(Default, PartialEq, Eq, Hash)]
299        struct Commitment {
300            /// Packed commitment identifier and version.
301            ///
302            /// This field encodes two distinct pieces of information into a single 256-bit unsigned integer:
303            /// 1.  **Version (Top 16 bits):** Bits `[255..240]` store a `u16` representing the type or version
304            ///     of the claim being made. See [CommitmentVersion] for defined values like
305            ///     `Block` or `Beacon`.
306            /// 2.  **Identifier (Bottom 64 bits):** Bits `[63..0]` store a `u64` value that uniquely identifies
307            ///     the specific instance of the claim. For example, for a `Block` commitment, this
308            ///     would be the block number. For a `Beacon` commitment, it would be the slot number.
309            ///
310            /// Use [Commitment::decode_id] to unpack this field into its constituent parts in Rust code.
311            /// The packing scheme ensures efficient storage and retrieval while maintaining compatibility
312            /// with Solidity's `uint256`.
313            ///
314            /// [CommitmentVersion]: crate::CommitmentVersion
315            uint256 id;
316
317            /// The cryptographic digest representing the core claim data.
318            ///
319            /// This is the actual data being attested to. The exact meaning depends on the `version` specified in the `id` field.
320            bytes32 digest;
321
322            /// A cryptographic digest identifying the network and prover configuration.
323            ///
324            /// This ID acts as a fingerprint of the context in which the commitment was generated,
325            /// including details like the Ethereum chain ID, active hard forks (part of the chain spec),
326            /// and potentially prover-specific settings. Verification must ensure this `configID`
327            /// matches the verifier's current environment configuration to prevent cross-chain or
328            /// misconfigured proof validation.
329            bytes32 configID;
330        }
331    }
332}
333
334// Publicly export only the Commitment struct definition generated by the sol! macro.
335pub use private::Commitment;
336
337/// Version identifier for a [Commitment], indicating the type of claim.
338///
339/// This enum defines the valid types of commitments that can be created and verified.
340/// The raw `u16` value of the enum variant is stored in the top 16 bits of the
341/// [Commitment::id] field.
342#[derive(Debug, Copy, Clone, PartialEq, Eq, enumn::N)]
343#[repr(u16)]
344#[non_exhaustive]
345pub enum CommitmentVersion {
346    /// Version 0: Commitment to an execution block hash indexed by its block number.
347    Block = 0,
348    /// Version 1: Commitment to a beacon block root indexed by its EIP-4788 child timestamp.
349    Beacon = 1,
350    /// Version 2: Commitment to a beacon block root indexed by its slot.
351    Consensus = 2,
352}
353
354impl Commitment {
355    /// The size in bytes of the ABI-encoded commitment (3 fields * 32 bytes/field = 96 bytes).
356    pub const ABI_ENCODED_SIZE: usize = 3 * 32;
357
358    /// Creates a new [Commitment] by packing the version and identifier into the `id` field.
359    #[inline]
360    pub const fn new(version: u16, id: u64, digest: B256, config_id: B256) -> Commitment {
361        Self {
362            id: Commitment::encode_id(id, version), // pack id and version
363            digest,
364            configID: config_id,
365        }
366    }
367
368    /// Decodes the packed `Commitment.id` field into the identifier part and the version.
369    ///
370    /// This function extracts the version from the top 16 bits and returns the remaining part of
371    /// the `id` field (which contains the instance identifier in its lower 64 bits) along with the
372    /// `u16` version.
373    #[inline]
374    pub fn decode_id(&self) -> (U256, u16) {
375        // define a mask to isolate the lower 240 bits (zeroing out the top 16 version bits)
376        let id_mask =
377            uint!(0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256);
378        let id_part = self.id & id_mask;
379
380        // extract the version by right-shifting the most significant limb (limbs[3]) by 48 bits
381        let version = (self.id.as_limbs()[3] >> 48) as u16;
382
383        (id_part, version)
384    }
385
386    /// ABI-encodes the commitment into a byte vector according to Solidity ABI specifications.
387    #[inline]
388    pub fn abi_encode(&self) -> Vec<u8> {
389        SolValue::abi_encode(self)
390    }
391
392    /// Packs a `u64` identifier and a `u16` version into a single `U256` value.
393    const fn encode_id(id: u64, version: u16) -> U256 {
394        U256::from_limbs([id, 0, 0, (version as u64) << 48])
395    }
396}
397
398impl Debug for Commitment {
399    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400        let (id, version_code) = self.decode_id();
401        let version = match CommitmentVersion::n(version_code) {
402            Some(v) => format!("{v:?}"),
403            None => format!("Unknown({version_code:x})"),
404        };
405
406        f.debug_struct("Commitment")
407            .field("version", &version)
408            .field("id", &id)
409            .field("digest", &self.digest)
410            .field("configID", &self.configID)
411            .finish()
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418    use alloy_primitives::B256;
419
420    #[test]
421    fn size() {
422        let tests = vec![
423            Commitment::default(),
424            Commitment::new(
425                u16::MAX,
426                u64::MAX,
427                B256::repeat_byte(0xFF),
428                B256::repeat_byte(0xFF),
429            ),
430        ];
431        for test in tests {
432            assert_eq!(test.abi_encode().len(), Commitment::ABI_ENCODED_SIZE);
433        }
434    }
435
436    #[test]
437    fn versioned_id() {
438        let tests = vec![(u64::MAX, u16::MAX), (u64::MAX, 0), (0, u16::MAX), (0, 0)];
439        for test in tests {
440            let commit = Commitment::new(test.1, test.0, B256::default(), B256::default());
441            let (id, version) = commit.decode_id();
442            assert_eq!((id.to(), version), test);
443        }
444    }
445}