risc0_ethereum_contracts/
receipt.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
15use alloy_primitives::Bytes;
16use risc0_aggregation::{
17    decode_set_inclusion_seal, SetInclusionDecodingError, SetInclusionEncodingError,
18    SetInclusionReceipt,
19};
20use risc0_zkvm::{sha::Digest, FakeReceipt, InnerReceipt, ReceiptClaim};
21use serde::{Deserialize, Serialize};
22use thiserror::Error;
23
24use crate::{
25    encode_seal,
26    groth16::decode_groth16_seal,
27    selector::{Selector, SelectorError, SelectorType},
28};
29
30/// Extension of the base [risc0_zkvm::Receipt] type.
31#[derive(Clone, Debug, Deserialize, Serialize)]
32pub enum Receipt {
33    Base(Box<risc0_zkvm::Receipt>),
34    SetInclusion(Box<SetInclusionReceipt<ReceiptClaim>>),
35}
36
37impl Receipt {
38    /// Encode the receipt as a seal.
39    pub fn abi_encode_seal(&self) -> Result<Vec<u8>, SetInclusionEncodingError> {
40        match self {
41            Receipt::Base(receipt) => {
42                encode_seal(receipt).map_err(|_| SetInclusionEncodingError::UnsupportedReceipt)
43            }
44            Receipt::SetInclusion(receipt) => receipt.abi_encode_seal(),
45        }
46    }
47
48    /// Get the receipt if it is a base [risc0_zkvm::Receipt].
49    pub fn receipt(&self) -> Option<&risc0_zkvm::Receipt> {
50        match self {
51            Receipt::Base(receipt) => Some(receipt),
52            _ => None,
53        }
54    }
55
56    /// Get the receipt if it is a set inclusion receipt.
57    pub fn set_inclusion_receipt(&self) -> Option<&SetInclusionReceipt<ReceiptClaim>> {
58        match self {
59            Receipt::SetInclusion(receipt) => Some(receipt),
60            _ => None,
61        }
62    }
63}
64
65/// Errors that can occur when decoding a seal.
66#[derive(Debug, Error)]
67#[non_exhaustive]
68pub enum DecodingError {
69    #[error("Seal too short")]
70    SealTooShort,
71    #[error("Unsupported selector {0:?}")]
72    UnsupportedSelector([u8; 4]),
73    #[error("Selector error: {0}")]
74    SelectorError(#[from] SelectorError),
75    #[error("Decoding error: {0}")]
76    SetInclusionError(#[from] SetInclusionDecodingError),
77    #[error("Decoding error: {0}")]
78    Anyhow(#[from] anyhow::Error),
79}
80
81/// Decode a seal into a receipt.
82pub fn decode_seal(
83    seal: Bytes,
84    image_id: impl Into<Digest>,
85    journal: impl Into<Vec<u8>>,
86) -> Result<Receipt, DecodingError> {
87    let journal = journal.into();
88    let claim = ReceiptClaim::ok(image_id, journal.clone());
89    decode_seal_with_claim(seal, claim, journal)
90}
91
92/// Decode a seal into a receipt.
93pub fn decode_seal_with_claim(
94    seal: Bytes,
95    claim: ReceiptClaim,
96    journal: impl Into<Vec<u8>>,
97) -> Result<Receipt, DecodingError> {
98    if seal.len() < 4 {
99        return Err(DecodingError::SealTooShort);
100    }
101    let selector = [seal[0], seal[1], seal[2], seal[3]];
102    let selector = Selector::from_bytes(selector)
103        .ok_or_else(|| DecodingError::UnsupportedSelector(selector))?;
104    match selector.get_type() {
105        SelectorType::FakeReceipt => {
106            let receipt = risc0_zkvm::Receipt::new(
107                InnerReceipt::Fake(FakeReceipt::new(claim)),
108                journal.into(),
109            );
110            Ok(Receipt::Base(Box::new(receipt)))
111        }
112        SelectorType::Groth16 => {
113            let verifier_parameters = selector.verifier_parameters_digest()?;
114            let receipt =
115                decode_groth16_seal(seal, claim, journal.into(), Some(verifier_parameters))?;
116            Ok(Receipt::Base(Box::new(receipt)))
117        }
118        SelectorType::SetVerifier => {
119            let verifier_parameters = selector.verifier_parameters_digest()?;
120            let receipt = decode_set_inclusion_seal(&seal, claim, verifier_parameters)?;
121            Ok(Receipt::SetInclusion(Box::new(receipt)))
122        }
123    }
124}