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}