risc0_ethereum_contracts/
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#![deny(rustdoc::broken_intra_doc_links)]
16#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
17
18pub mod groth16;
19
20/// Re-export of [alloy], provided to ensure that the correct version of the types used in the
21/// public API are available in case multiple versions of [alloy] are in use.
22///
23/// Because [alloy] is a v0.x crate, it is not covered under the semver policy of this crate.
24pub use alloy;
25
26// NOTE: Placing the cfg directly on the `pub mod` statement doesn't work when tried with Rust 1.81
27cfg_if::cfg_if! {
28    if #[cfg(feature = "unstable")] {
29        pub mod set_verifier;
30        pub mod event_query;
31        pub mod receipt;
32        pub mod selector;
33    }
34}
35
36use core::str::FromStr;
37
38use anyhow::{bail, Result};
39use risc0_zkvm::{sha::Digestible, InnerReceipt};
40
41#[cfg(not(target_os = "zkvm"))]
42use alloy::{primitives::Bytes, sol_types::SolInterface, transports::TransportError};
43
44alloy::sol!(
45    #![sol(rpc, all_derives)]
46    "src/IRiscZeroVerifier.sol"
47);
48
49alloy::sol!(
50    #![sol(rpc, all_derives)]
51    "src/IRiscZeroSetVerifier.sol"
52);
53
54#[cfg(not(target_os = "zkvm"))]
55pub use IRiscZeroSetVerifier::IRiscZeroSetVerifierErrors;
56
57#[cfg(not(target_os = "zkvm"))]
58#[derive(thiserror::Error, Debug)]
59pub enum Error {
60    #[error("SetVerifier error: {0:?}")]
61    SetVerifierError(IRiscZeroSetVerifierErrors),
62
63    #[error("contract error: {0}")]
64    ContractError(alloy::contract::Error),
65
66    #[error("decoding error: {0}")]
67    DecodingError(#[from] DecodingError),
68}
69
70#[cfg(not(target_os = "zkvm"))]
71#[derive(thiserror::Error, Debug)]
72pub enum DecodingError {
73    #[error("missing data, code: {0} msg: {1}")]
74    MissingData(i64, String),
75
76    #[error("error creating bytes from string")]
77    BytesFromStrError,
78
79    #[error("abi decoder error: {0} - {1}")]
80    Abi(alloy::sol_types::Error, Bytes),
81}
82
83/// Encode the seal of the given receipt for use with EVM smart contract verifiers.
84///
85/// Appends the verifier selector, determined from the first 4 bytes of the verifier parameters
86/// including the Groth16 verification key and the control IDs that commit to the RISC Zero
87/// circuits.
88pub fn encode_seal(receipt: &risc0_zkvm::Receipt) -> Result<Vec<u8>> {
89    let seal = match receipt.inner.clone() {
90        InnerReceipt::Fake(receipt) => {
91            let seal = receipt.claim.digest().as_bytes().to_vec();
92            let selector = &[0xFFu8; 4];
93            // Create a new vector with the capacity to hold both selector and seal
94            let mut selector_seal = Vec::with_capacity(selector.len() + seal.len());
95            selector_seal.extend_from_slice(selector);
96            selector_seal.extend_from_slice(&seal);
97            selector_seal
98        }
99        InnerReceipt::Groth16(receipt) => {
100            let selector = &receipt.verifier_parameters.as_bytes()[..4];
101            // Create a new vector with the capacity to hold both selector and seal
102            let mut selector_seal = Vec::with_capacity(selector.len() + receipt.seal.len());
103            selector_seal.extend_from_slice(selector);
104            selector_seal.extend_from_slice(receipt.seal.as_ref());
105            selector_seal
106        }
107        _ => bail!("Unsupported receipt type"),
108        // TODO(victor): Add set verifier seal here.
109    };
110    Ok(seal)
111}
112
113#[cfg(not(target_os = "zkvm"))]
114fn decode_contract_err<T: SolInterface>(err: alloy::contract::Error) -> Result<T, Error> {
115    match err {
116        alloy::contract::Error::TransportError(TransportError::ErrorResp(ts_err)) => {
117            let Some(data) = ts_err.data else {
118                return Err(
119                    DecodingError::MissingData(ts_err.code, ts_err.message.to_string()).into(),
120                );
121            };
122
123            let data = data.get().trim_matches('"');
124
125            let Ok(data) = Bytes::from_str(data) else {
126                return Err(DecodingError::BytesFromStrError.into());
127            };
128
129            let decoded_error = match T::abi_decode(&data, true) {
130                Ok(res) => res,
131                Err(err) => {
132                    return Err(DecodingError::Abi(err, data).into());
133                }
134            };
135
136            Ok(decoded_error)
137        }
138        _ => Err(Error::ContractError(err)),
139    }
140}
141
142#[cfg(not(target_os = "zkvm"))]
143impl IRiscZeroSetVerifierErrors {
144    pub fn decode_error(err: alloy::contract::Error) -> Error {
145        match decode_contract_err(err) {
146            Ok(res) => Error::SetVerifierError(res),
147            Err(decode_err) => decode_err,
148        }
149    }
150}