risc0_zkvm/
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
15//! Manages the output and cryptographic data for a proven computation.
16
17pub(crate) mod composite;
18pub(crate) mod groth16;
19pub(crate) mod merkle;
20pub(crate) mod segment;
21pub(crate) mod succinct;
22
23use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
24
25use anyhow::Result;
26use borsh::{BorshDeserialize, BorshSerialize};
27use derive_more::Debug;
28use risc0_core::field::baby_bear::BabyBear;
29use risc0_zkp::{
30    core::{
31        digest::Digest,
32        hash::{
33            blake2b::Blake2bCpuHashSuite, poseidon2::Poseidon2HashSuite, sha::Sha256HashSuite,
34            HashSuite,
35        },
36    },
37    verify::VerificationError,
38};
39use serde::{de::DeserializeOwned, Deserialize, Serialize};
40
41// Make succinct receipt available through this `receipt` module.
42use crate::{
43    claim::Unknown,
44    serde::{from_slice, Error},
45    sha::{Digestible, Sha256},
46    Assumption, Assumptions, MaybePruned, Output, PrunedValueError, ReceiptClaim,
47};
48
49pub use self::groth16::{Groth16Receipt, Groth16ReceiptVerifierParameters};
50
51pub use self::{
52    composite::{CompositeReceipt, CompositeReceiptVerifierParameters},
53    segment::{SegmentReceipt, SegmentReceiptVerifierParameters},
54    succinct::{SuccinctReceipt, SuccinctReceiptVerifierParameters},
55};
56
57/// A receipt attesting to the execution of a guest program.
58///
59/// A Receipt is a zero-knowledge proof of computation. It attests that the
60/// [Receipt::journal] was produced by executing a guest program based on a
61/// specified memory image. This image is _not_ included in the receipt; the
62/// verifier must provide an
63/// [ImageID](https://dev.risczero.com/terminology#image-id), a cryptographic
64/// hash corresponding to the expected image.
65///
66/// A prover can provide a Receipt to an untrusting party to convince them that
67/// the results contained within the Receipt (in the [Receipt::journal]) came
68/// from running specific code. Conversely, a verifier can inspect a receipt to
69/// confirm that its results must have been generated from the expected code,
70/// even when this code was run by an untrusted source.
71///
72/// # Example
73///
74/// To create a [Receipt] attesting to the faithful execution of your code, run
75/// one of the `prove` functions from a `Prover`.
76///
77/// ```rust
78/// # #[cfg(feature = "prove")]
79/// use risc0_zkvm::{default_prover, ExecutorEnv};
80/// use risc0_zkvm_methods::FIB_ELF;
81///
82/// # #[cfg(not(feature = "cuda"))]
83/// # #[cfg(feature = "prove")]
84/// # {
85/// let env = ExecutorEnv::builder().write_slice(&[20]).build().unwrap();
86/// let receipt = default_prover().prove(env, FIB_ELF).unwrap();
87/// # }
88/// ```
89///
90/// To confirm that a [Receipt] was honestly generated, use [Receipt::verify]
91/// and supply the ImageID of the code that should have been executed as a
92/// parameter. (See
93/// [risc0_build](https://docs.rs/risc0-build/latest/risc0_build/) for more
94/// information about how ImageIDs are generated.)
95///
96/// ```rust
97/// use risc0_zkvm::Receipt;
98/// # #[cfg(feature = "prove")]
99/// # use risc0_zkvm::{default_prover, ExecutorEnv};
100/// # use risc0_zkvm_methods::{FIB_ELF, FIB_ID};
101///
102/// # #[cfg(not(feature = "cuda"))]
103/// # #[cfg(feature = "prove")]
104/// # {
105/// # let env = ExecutorEnv::builder().write_slice(&[20]).build().unwrap();
106/// # let receipt = default_prover().prove(env, FIB_ELF).unwrap().receipt;
107/// receipt.verify(FIB_ID).unwrap();
108/// # }
109/// ```
110///
111/// The public outputs of the [Receipt] are contained in the [Receipt::journal].
112/// You can use [Journal::decode] to deserialize the journal as typed and
113/// structured data, or access the [Journal::bytes] directly.
114#[derive(Clone, Debug, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
115#[cfg_attr(test, derive(PartialEq))]
116pub struct Receipt {
117    /// The polymorphic [InnerReceipt].
118    pub inner: InnerReceipt,
119
120    /// The public commitment written by the guest.
121    ///
122    /// This data is cryptographically authenticated in [Receipt::verify].
123    pub journal: Journal,
124
125    /// Metadata providing context on the receipt, about the proving system, SDK versions, and other
126    /// information to help with interoperability. It is not cryptographically bound to the receipt,
127    /// and should not be used for security-relevant decisions, such as choosing whether or not to
128    /// accept a receipt based on it's stated version.
129    pub metadata: ReceiptMetadata,
130}
131
132impl Receipt {
133    /// Construct a new Receipt
134    pub fn new(inner: InnerReceipt, journal: Vec<u8>) -> Self {
135        let metadata = ReceiptMetadata {
136            verifier_parameters: inner.verifier_parameters(),
137        };
138        Self {
139            inner,
140            journal: Journal::new(journal),
141            metadata,
142        }
143    }
144
145    /// Verify that this receipt proves a successful execution of the zkVM from
146    /// the given `image_id`.
147    ///
148    /// Uses the zero-knowledge proof system to verify the seal, and decodes the proven
149    /// [ReceiptClaim]. This method additionally ensures that the guest exited with a successful
150    /// status code (i.e. `Halted(0)`), the image ID is as expected, and the journal has not been
151    /// tampered with.
152    pub fn verify(&self, image_id: impl Into<Digest>) -> Result<(), VerificationError> {
153        self.verify_with_context(&VerifierContext::default(), image_id)
154    }
155
156    /// Verify that this receipt proves a successful execution of the zkVM from the given
157    /// `image_id`.
158    ///
159    /// Uses the zero-knowledge proof system to verify the seal, and decodes the proven
160    /// [ReceiptClaim]. This method additionally ensures that the guest exited with a successful
161    /// status code (i.e. `Halted(0)`), the image ID is as expected, and the journal has not been
162    /// tampered with.
163    pub fn verify_with_context(
164        &self,
165        ctx: &VerifierContext,
166        image_id: impl Into<Digest>,
167    ) -> Result<(), VerificationError> {
168        if self.inner.verifier_parameters() != self.metadata.verifier_parameters {
169            // inner verifier_parameters do not match metadata.verifier_parameters.
170            // This is an internal inconsistency in the receipt struct.
171            return Err(VerificationError::ReceiptFormatError);
172        }
173
174        tracing::debug!("Receipt::verify_with_context");
175        self.inner.verify_integrity_with_context(ctx)?;
176
177        // Check that the claim on the verified receipt matches what was expected. Since we have
178        // constrained all field in the ReceiptClaim, we can directly construct the expected digest
179        // and do not need to open the claim digest on the inner receipt.
180        let expected_claim = ReceiptClaim::ok(image_id, MaybePruned::Pruned(self.journal.digest()));
181        if expected_claim.digest() != self.inner.claim()?.digest() {
182            tracing::debug!(
183                "receipt claim does not match expected claim:\nreceipt: {:#?}\nexpected: {:#?}",
184                self.inner.claim()?,
185                expected_claim
186            );
187            return Err(VerificationError::ClaimDigestMismatch {
188                expected: expected_claim.digest(),
189                received: self.claim()?.digest(),
190            });
191        }
192
193        Ok(())
194    }
195
196    /// Verify the integrity of this receipt, ensuring the claim and journal
197    /// are attested to by the seal.
198    ///
199    /// This does not verify the success of the guest execution. In
200    /// particular, the guest could have exited with an error (e.g.
201    /// `ExitCode::Halted(1)`) or faulted state. It also does not check the
202    /// image ID, or otherwise constrain what guest was executed. After calling
203    /// this method, the caller should check the [ReceiptClaim] fields
204    /// relevant to their application. If you need to verify a successful
205    /// guest execution and access the journal, the `verify` function is
206    /// recommended.
207    pub fn verify_integrity_with_context(
208        &self,
209        ctx: &VerifierContext,
210    ) -> Result<(), VerificationError> {
211        if self.inner.verifier_parameters() != self.metadata.verifier_parameters {
212            // inner verifier_parameters do not match metadata.verifier_parameters.
213            // This is an internal inconsistency in the receipt struct.
214            return Err(VerificationError::ReceiptFormatError);
215        }
216
217        tracing::debug!("Receipt::verify_integrity_with_context");
218        self.inner.verify_integrity_with_context(ctx)?;
219
220        // Check that self.journal is attested to by the inner receipt.
221        // We need to open the claim digest to do this, so it cannot be pruned.
222        let maybe_pruned_claim = self.inner.claim()?;
223        let claim = maybe_pruned_claim
224            .as_value()
225            .map_err(|_| VerificationError::ReceiptFormatError)?;
226
227        let expected_output = claim.exit_code.expects_output().then(|| Output {
228            journal: MaybePruned::Pruned(self.journal.digest()),
229            // TODO(#982): It would be reasonable for this method to allow integrity verification
230            // for receipts that have a non-empty assumptions list, but it is not supported here
231            // because we don't have a enough information to open the assumptions list unless we
232            // require it be empty.
233            assumptions: Assumptions(vec![]).into(),
234        });
235
236        if claim.output.digest() != expected_output.digest() {
237            let empty_output = claim.output.is_none() && self.journal.bytes.is_empty();
238            if !empty_output {
239                tracing::debug!(
240                    "journal: 0x{}, expected output digest: 0x{}, decoded output digest: 0x{}",
241                    hex::encode(&self.journal.bytes),
242                    hex::encode(expected_output.digest()),
243                    hex::encode(claim.output.digest()),
244                );
245                return Err(VerificationError::JournalDigestMismatch);
246            }
247            tracing::debug!("accepting zero digest for output of receipt with empty journal");
248        }
249
250        Ok(())
251    }
252
253    /// Extract the [ReceiptClaim] from this receipt.
254    pub fn claim(&self) -> Result<MaybePruned<ReceiptClaim>, VerificationError> {
255        self.inner.claim()
256    }
257
258    /// Total number of bytes used by the seals of this receipt.
259    pub fn seal_size(&self) -> usize {
260        self.inner.seal_size()
261    }
262}
263
264/// A record of the public commitments for a proven zkVM execution.
265///
266/// Public outputs, including commitments to important inputs, are written to the journal during
267/// zkVM execution. Along with an image ID, it constitutes the statement proven by a given
268/// [Receipt]
269#[derive(
270    Clone, Debug, Default, Deserialize, Serialize, PartialEq, BorshSerialize, BorshDeserialize,
271)]
272pub struct Journal {
273    /// The raw bytes of the journal.
274    #[debug("{} bytes", bytes.len())]
275    pub bytes: Vec<u8>,
276}
277
278impl Journal {
279    /// Construct a new [Journal].
280    pub fn new(bytes: Vec<u8>) -> Self {
281        Self { bytes }
282    }
283
284    /// Decode the journal bytes by using the [risc0 deserializer](crate::serde).
285    pub fn decode<T: DeserializeOwned>(&self) -> Result<T, Error> {
286        from_slice(&self.bytes)
287    }
288}
289
290impl risc0_binfmt::Digestible for Journal {
291    fn digest<S: Sha256>(&self) -> Digest {
292        *S::hash_bytes(&self.bytes)
293    }
294}
295
296impl AsRef<[u8]> for Journal {
297    fn as_ref(&self) -> &[u8] {
298        &self.bytes
299    }
300}
301
302/// A lower level receipt, containing the cryptographic seal (i.e. zero-knowledge proof) and
303/// verification logic for a specific proof system and circuit. All inner receipt types are
304/// zero-knowledge proofs of execution for a RISC-V zkVM.
305#[derive(Clone, Debug, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
306#[cfg_attr(test, derive(PartialEq))]
307#[non_exhaustive]
308pub enum InnerReceipt {
309    /// A non-succinct [CompositeReceipt], made up of one inner receipt per segment.
310    Composite(CompositeReceipt),
311
312    /// A [SuccinctReceipt], proving arbitrarily long zkVM computions with a single STARK.
313    Succinct(SuccinctReceipt<ReceiptClaim>),
314
315    /// A [Groth16Receipt], proving arbitrarily long zkVM computions with a single Groth16 SNARK.
316    Groth16(Groth16Receipt<ReceiptClaim>),
317
318    /// A [FakeReceipt], with no cryptographic integrity, used only for development.
319    Fake(FakeReceipt<ReceiptClaim>),
320}
321
322impl InnerReceipt {
323    /// Verify the integrity of this receipt, ensuring the claim is attested
324    /// to by the seal.
325    pub fn verify_integrity(&self) -> Result<(), VerificationError> {
326        self.verify_integrity_with_context(&VerifierContext::default())
327    }
328
329    /// Verify the integrity of this receipt, ensuring the claim is attested to by the seal.
330    pub fn verify_integrity_with_context(
331        &self,
332        ctx: &VerifierContext,
333    ) -> Result<(), VerificationError> {
334        tracing::debug!("InnerReceipt::verify_integrity_with_context");
335        match self {
336            Self::Composite(inner) => inner.verify_integrity_with_context(ctx),
337            Self::Groth16(inner) => inner.verify_integrity_with_context(ctx),
338            Self::Succinct(inner) => inner.verify_integrity_with_context(ctx),
339            Self::Fake(inner) => inner.verify_integrity_with_context(ctx),
340        }
341    }
342
343    /// Returns the [InnerReceipt::Composite] arm.
344    pub fn composite(&self) -> Result<&CompositeReceipt, VerificationError> {
345        if let Self::Composite(x) = self {
346            Ok(x)
347        } else {
348            Err(VerificationError::ReceiptFormatError)
349        }
350    }
351
352    /// Returns the [InnerReceipt::Groth16] arm.
353    pub fn groth16(&self) -> Result<&Groth16Receipt<ReceiptClaim>, VerificationError> {
354        if let Self::Groth16(x) = self {
355            Ok(x)
356        } else {
357            Err(VerificationError::ReceiptFormatError)
358        }
359    }
360
361    /// Returns the [InnerReceipt::Succinct] arm.
362    pub fn succinct(&self) -> Result<&SuccinctReceipt<ReceiptClaim>, VerificationError> {
363        if let Self::Succinct(x) = self {
364            Ok(x)
365        } else {
366            Err(VerificationError::ReceiptFormatError)
367        }
368    }
369
370    /// Extract the [ReceiptClaim] from this receipt.
371    pub fn claim(&self) -> Result<MaybePruned<ReceiptClaim>, VerificationError> {
372        match self {
373            Self::Composite(ref inner) => Ok(inner.claim()?.into()),
374            Self::Groth16(ref inner) => Ok(inner.claim.clone()),
375            Self::Succinct(ref inner) => Ok(inner.claim.clone()),
376            Self::Fake(ref inner) => Ok(inner.claim.clone()),
377        }
378    }
379
380    /// Return the digest of the verifier parameters struct for the appropriate receipt verifier.
381    pub fn verifier_parameters(&self) -> Digest {
382        match self {
383            Self::Composite(ref inner) => inner.verifier_parameters,
384            Self::Groth16(ref inner) => inner.verifier_parameters,
385            Self::Succinct(ref inner) => inner.verifier_parameters,
386            Self::Fake(_) => Digest::ZERO,
387        }
388    }
389
390    /// Total number of bytes used by the seals of this receipt.
391    pub fn seal_size(&self) -> usize {
392        match self {
393            Self::Composite(receipt) => receipt.seal_size(),
394            Self::Succinct(receipt) => receipt.seal_size(),
395            Self::Groth16(receipt) => receipt.seal_size(),
396            Self::Fake(_) => 0,
397        }
398    }
399}
400
401impl From<CompositeReceipt> for InnerReceipt {
402    fn from(receipt: CompositeReceipt) -> Self {
403        Self::Composite(receipt)
404    }
405}
406
407impl From<SuccinctReceipt<ReceiptClaim>> for InnerReceipt {
408    fn from(receipt: SuccinctReceipt<ReceiptClaim>) -> Self {
409        Self::Succinct(receipt)
410    }
411}
412
413impl From<Groth16Receipt<ReceiptClaim>> for InnerReceipt {
414    fn from(receipt: Groth16Receipt<ReceiptClaim>) -> Self {
415        Self::Groth16(receipt)
416    }
417}
418
419impl From<FakeReceipt<ReceiptClaim>> for InnerReceipt {
420    fn from(receipt: FakeReceipt<ReceiptClaim>) -> Self {
421        Self::Fake(receipt)
422    }
423}
424
425/// A receipt that can hold different types of cryptographic proofs for a given claim.
426#[derive(Clone, Debug, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
427#[cfg_attr(test, derive(PartialEq))]
428#[non_exhaustive]
429pub enum GenericReceipt<Claim> {
430    /// A [SuccinctReceipt], proving the claim a STARK produced by the recursion VM.
431    Succinct(SuccinctReceipt<Claim>),
432
433    /// A [Groth16Receipt], proving the claim a Groth16 SNARK.
434    Groth16(Groth16Receipt<Claim>),
435
436    /// A [FakeReceipt], with no cryptographic integrity, used only for development.
437    Fake(FakeReceipt<Claim>),
438}
439
440impl<Claim> GenericReceipt<Claim> {
441    /// Verify the integrity of this receipt, ensuring the claim is attested
442    /// to by the seal.
443    pub fn verify_integrity(&self) -> Result<(), VerificationError>
444    where
445        Claim: risc0_binfmt::Digestible + core::fmt::Debug,
446    {
447        self.verify_integrity_with_context(&VerifierContext::default())
448    }
449
450    /// Verify the integrity of this receipt, ensuring the claim is attested to by the seal.
451    pub fn verify_integrity_with_context(
452        &self,
453        ctx: &VerifierContext,
454    ) -> Result<(), VerificationError>
455    where
456        Claim: risc0_binfmt::Digestible + core::fmt::Debug,
457    {
458        tracing::debug!("InnerReceipt::verify_integrity_with_context");
459        match self {
460            Self::Groth16(inner) => inner.verify_integrity_with_context(ctx),
461            Self::Succinct(inner) => inner.verify_integrity_with_context(ctx),
462            Self::Fake(inner) => inner.verify_integrity_with_context(ctx),
463        }
464    }
465
466    /// Returns the [InnerReceipt::Groth16] arm.
467    pub fn groth16(&self) -> Result<&Groth16Receipt<Claim>, VerificationError> {
468        if let Self::Groth16(x) = self {
469            Ok(x)
470        } else {
471            Err(VerificationError::ReceiptFormatError)
472        }
473    }
474
475    /// Returns the [InnerReceipt::Succinct] arm.
476    pub fn succinct(&self) -> Result<&SuccinctReceipt<Claim>, VerificationError> {
477        if let Self::Succinct(x) = self {
478            Ok(x)
479        } else {
480            Err(VerificationError::ReceiptFormatError)
481        }
482    }
483
484    /// Extract the [ReceiptClaim] from this receipt.
485    pub fn claim(&self) -> MaybePruned<Claim>
486    where
487        Claim: Clone,
488    {
489        match self {
490            Self::Groth16(ref inner) => inner.claim.clone(),
491            Self::Succinct(ref inner) => inner.claim.clone(),
492            Self::Fake(ref inner) => inner.claim.clone(),
493        }
494    }
495
496    /// Return the digest of the verifier parameters struct for the appropriate receipt verifier.
497    pub fn verifier_parameters(&self) -> Digest {
498        match self {
499            Self::Groth16(ref inner) => inner.verifier_parameters,
500            Self::Succinct(ref inner) => inner.verifier_parameters,
501            Self::Fake(_) => Digest::ZERO,
502        }
503    }
504
505    /// Total number of bytes used by the seals of this receipt.
506    pub fn seal_size(&self) -> usize {
507        match self {
508            Self::Succinct(receipt) => receipt.seal_size(),
509            Self::Groth16(receipt) => receipt.seal_size(),
510            Self::Fake(_) => 0,
511        }
512    }
513
514    /// Prunes the claim, retaining its digest, and converts into a [GenericReceipt] with an unknown
515    /// claim type. Can be used to get receipts of a uniform type across heterogeneous claims.
516    pub fn into_unknown(self) -> GenericReceipt<Unknown>
517    where
518        Claim: risc0_binfmt::Digestible,
519    {
520        match self {
521            Self::Succinct(receipt) => receipt.into_unknown().into(),
522            Self::Groth16(receipt) => receipt.into_unknown().into(),
523            Self::Fake(receipt) => receipt.into_unknown().into(),
524        }
525    }
526}
527
528impl<Claim> From<SuccinctReceipt<Claim>> for GenericReceipt<Claim> {
529    fn from(receipt: SuccinctReceipt<Claim>) -> Self {
530        Self::Succinct(receipt)
531    }
532}
533
534impl<Claim> From<Groth16Receipt<Claim>> for GenericReceipt<Claim> {
535    fn from(receipt: Groth16Receipt<Claim>) -> Self {
536        Self::Groth16(receipt)
537    }
538}
539
540impl<Claim> From<FakeReceipt<Claim>> for GenericReceipt<Claim> {
541    fn from(receipt: FakeReceipt<Claim>) -> Self {
542        Self::Fake(receipt)
543    }
544}
545
546/// A fake receipt for testing and development.
547///
548/// This receipt is not valid and will fail verification unless the
549/// environment variable `RISC0_DEV_MODE` is set to `true`, in which case a
550/// pass-through 'verification' will be performed, but it *does not*
551/// represent any meaningful attestation of receipt's integrity.
552///
553/// This type solely exists to improve development experience, for further
554/// information about development-only mode see our [dev-mode
555/// documentation](https://dev.risczero.com/api/generating-proofs/dev-mode).
556#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
557#[cfg_attr(test, derive(PartialEq))]
558#[non_exhaustive]
559pub struct FakeReceipt<Claim> {
560    /// Claim containing information about the computation that this receipt pretends to prove.
561    ///
562    /// The standard claim type is [ReceiptClaim], which represents a RISC-V zkVM execution.
563    pub claim: MaybePruned<Claim>,
564}
565
566impl<Claim> FakeReceipt<Claim> {
567    /// Create a new [FakeReceipt] for the given claim.
568    pub fn new(claim: impl Into<MaybePruned<Claim>>) -> Self {
569        Self {
570            claim: claim.into(),
571        }
572    }
573
574    /// Old verify function, always returns [`VerificationError::InvalidProof`].
575    #[deprecated(note = "Use verify_integrity_with_context instead")]
576    pub fn verify_integrity(&self) -> Result<(), VerificationError> {
577        Err(VerificationError::InvalidProof)
578    }
579
580    /// Pretend to verify the integrity of this receipt. If not in dev mode (see
581    /// [`VerifierContext::with_dev_mode`], or the `RISC0_DEV_MODE` environment variable) this will
582    /// always reject. When in dev mode, this will always pass.
583    pub fn verify_integrity_with_context(
584        &self,
585        ctx: &VerifierContext,
586    ) -> Result<(), VerificationError> {
587        if ctx.dev_mode() {
588            assert!(cfg!(not(feature = "disable-dev-mode")));
589            Ok(())
590        } else {
591            Err(VerificationError::InvalidProof)
592        }
593    }
594
595    /// Prunes the claim, retaining its digest, and converts into a [FakeReceipt] with an unknown
596    /// claim type. Can be used to get receipts of a uniform type across heterogeneous claims.
597    pub fn into_unknown(self) -> FakeReceipt<Unknown>
598    where
599        Claim: risc0_binfmt::Digestible,
600    {
601        FakeReceipt {
602            claim: MaybePruned::Pruned(self.claim.digest()),
603        }
604    }
605}
606
607impl TryFrom<FakeReceipt<ReceiptClaim>> for Receipt {
608    type Error = PrunedValueError;
609
610    /// Try to create a [Receipt] from a [FakeReceipt]. In order to succeed, the jounal must be
611    /// populated on the receipt claim (i.e. it cannot be pruned).
612    fn try_from(fake_receipt: FakeReceipt<ReceiptClaim>) -> Result<Self, Self::Error> {
613        // Attempt to copy the journal from the receipt claim, returning an error if pruned.
614        let journal = fake_receipt
615            .claim
616            .as_value()?
617            .output
618            .as_value()?
619            .as_ref()
620            .map(|output| Ok(output.journal.as_value()?.clone()))
621            .transpose()?
622            .unwrap_or_default();
623        Ok(Receipt::new(InnerReceipt::Fake(fake_receipt), journal))
624    }
625}
626
627/// Metadata providing context on the receipt.
628///
629/// It contains information about the proving system, SDK versions, and other information to help
630/// with interoperability. It is not cryptographically bound to the receipt, and should not be used
631/// for security-relevant decisions, such as choosing whether or not to accept a receipt based on
632/// it's stated version.
633#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
634#[non_exhaustive]
635pub struct ReceiptMetadata {
636    /// Information which can be used to decide whether a given verifier is compatible with this
637    /// receipt (i.e. that it may be able to verify it).
638    ///
639    /// It is intended to be used when there are multiple verifier implementations (e.g.
640    /// corresponding to multiple versions of a proof system or circuit) and it is ambiguous which
641    /// one should be used to attempt verification of a receipt.
642    pub verifier_parameters: Digest,
643}
644
645/// An assumption attached to a guest execution as a result of calling
646/// `env::verify` or `env::verify_integrity`.
647#[derive(Clone, Debug, Serialize, Deserialize)]
648pub enum AssumptionReceipt {
649    /// A [Receipt] for a proven assumption.
650    ///
651    /// Upon proving, this receipt will be used as proof of the assumption that results from a call
652    /// to `env::verify`, and the resulting receipt will be unconditional. As a result,
653    /// [Receipt::verify] will return true and the verifier will accept the receipt.
654    Proven(InnerAssumptionReceipt),
655
656    /// An [Assumption] that is not directly proven to be true.
657    ///
658    /// Proving an execution with an unresolved assumption will result in a conditional receipt. In
659    /// order for the verifier to accept a conditional receipt, they must be given a
660    /// [AssumptionReceipt] proving the assumption, or explicitly accept the assumption without
661    /// proof.
662    Unresolved(Assumption),
663}
664
665impl AssumptionReceipt {
666    /// Returns the digest of the claim for this [AssumptionReceipt].
667    pub fn claim_digest(&self) -> Result<Digest, VerificationError> {
668        match self {
669            Self::Proven(receipt) => Ok(receipt.claim_digest()?),
670            Self::Unresolved(assumption) => Ok(assumption.claim),
671        }
672    }
673}
674
675impl From<Receipt> for AssumptionReceipt {
676    /// Create a proven assumption from a [Receipt].
677    fn from(receipt: Receipt) -> Self {
678        Self::Proven(receipt.inner.into())
679    }
680}
681
682impl<Claim> From<GenericReceipt<Claim>> for AssumptionReceipt
683where
684    Claim: risc0_binfmt::Digestible,
685{
686    /// Create a proven assumption from a [GenericReceipt].
687    fn from(receipt: GenericReceipt<Claim>) -> Self {
688        Self::Proven(receipt.into())
689    }
690}
691
692impl From<InnerReceipt> for AssumptionReceipt {
693    /// Create a proven assumption from a [InnerReceipt].
694    fn from(receipt: InnerReceipt) -> Self {
695        Self::Proven(receipt.into())
696    }
697}
698
699impl From<InnerAssumptionReceipt> for AssumptionReceipt {
700    /// Create a proven assumption from a [InnerAssumptionReceipt].
701    fn from(receipt: InnerAssumptionReceipt) -> Self {
702        Self::Proven(receipt)
703    }
704}
705
706impl<Claim> From<SuccinctReceipt<Claim>> for AssumptionReceipt
707where
708    Claim: risc0_binfmt::Digestible,
709{
710    /// Create a proven assumption from a [SuccinctReceipt].
711    fn from(receipt: SuccinctReceipt<Claim>) -> Self {
712        Self::Proven(InnerAssumptionReceipt::Succinct(receipt.into_unknown()))
713    }
714}
715
716impl<Claim> From<FakeReceipt<Claim>> for AssumptionReceipt
717where
718    Claim: risc0_binfmt::Digestible,
719{
720    /// Create a fake proven assumption from a [FakeReceipt].
721    fn from(receipt: FakeReceipt<Claim>) -> Self {
722        Self::Proven(InnerAssumptionReceipt::Fake(receipt.into_unknown()))
723    }
724}
725
726impl From<Assumption> for AssumptionReceipt {
727    /// Create an unresolved assumption from an [Assumption].
728    fn from(assumption: Assumption) -> Self {
729        Self::Unresolved(assumption)
730    }
731}
732
733impl From<MaybePruned<ReceiptClaim>> for AssumptionReceipt {
734    /// Create an unresolved assumption from a [MaybePruned] [ReceiptClaim].
735    ///
736    /// The control root will be set to all zeroes, which means that the assumption must be
737    /// resolved with the same control root at the conditional receipt (i.e. that this assumption
738    /// is for the same version of zkVM as the receipt it is attached to).
739    fn from(claim: MaybePruned<ReceiptClaim>) -> Self {
740        Self::Unresolved(Assumption {
741            claim: claim.digest(),
742            control_root: Digest::ZERO,
743        })
744    }
745}
746
747impl From<ReceiptClaim> for AssumptionReceipt {
748    /// Create an unresolved assumption from a [ReceiptClaim].
749    ///
750    /// The control root will be set to all zeroes, which means that the assumption must be
751    /// resolved with the same control root at the conditional receipt (i.e. that this assumption
752    /// is for the same version of zkVM as the receipt it is attached to).
753    fn from(claim: ReceiptClaim) -> Self {
754        Self::Unresolved(Assumption {
755            claim: claim.digest(),
756            control_root: Digest::ZERO,
757        })
758    }
759}
760
761/// An enumeration of receipt types similar to [InnerReceipt], but for use in [AssumptionReceipt].
762/// Instead of proving only RISC-V execution with [ReceiptClaim], this type can prove any claim
763/// implemented by one of its inner types.
764#[derive(Clone, Debug, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
765#[cfg_attr(test, derive(PartialEq))]
766#[non_exhaustive]
767pub enum InnerAssumptionReceipt {
768    /// A non-succinct [CompositeReceipt], made up of one inner receipt per segment and assumption.
769    Composite(CompositeReceipt),
770
771    /// A [SuccinctReceipt], proving arbitrarily the claim with a single STARK.
772    Succinct(SuccinctReceipt<Unknown>),
773
774    /// A [Groth16Receipt], proving arbitrarily the claim with a single Groth16 SNARK.
775    Groth16(Groth16Receipt<Unknown>),
776
777    /// A [FakeReceipt], with no cryptographic integrity, used only for development.
778    Fake(FakeReceipt<Unknown>),
779}
780
781impl InnerAssumptionReceipt {
782    /// Verify the integrity of this receipt, ensuring the claim is attested to by the seal.
783    pub fn verify_integrity_with_context(
784        &self,
785        ctx: &VerifierContext,
786    ) -> Result<(), VerificationError> {
787        tracing::debug!("InnerAssumptionReceipt::verify_integrity_with_context");
788        match self {
789            Self::Composite(inner) => inner.verify_integrity_with_context(ctx),
790            Self::Groth16(inner) => inner.verify_integrity_with_context(ctx),
791            Self::Succinct(inner) => inner.verify_integrity_with_context(ctx),
792            Self::Fake(inner) => inner.verify_integrity_with_context(ctx),
793        }
794    }
795
796    /// Returns the [InnerAssumptionReceipt::Composite] arm.
797    pub fn composite(&self) -> Result<&CompositeReceipt, VerificationError> {
798        if let Self::Composite(x) = self {
799            Ok(x)
800        } else {
801            Err(VerificationError::ReceiptFormatError)
802        }
803    }
804
805    /// Returns the [InnerAssumptionReceipt::Groth16] arm.
806    pub fn groth16(&self) -> Result<&Groth16Receipt<Unknown>, VerificationError> {
807        if let Self::Groth16(x) = self {
808            Ok(x)
809        } else {
810            Err(VerificationError::ReceiptFormatError)
811        }
812    }
813
814    /// Returns the [InnerAssumptionReceipt::Succinct] arm.
815    pub fn succinct(&self) -> Result<&SuccinctReceipt<Unknown>, VerificationError> {
816        if let Self::Succinct(x) = self {
817            Ok(x)
818        } else {
819            Err(VerificationError::ReceiptFormatError)
820        }
821    }
822
823    /// Extract the claim digest from this receipt.
824    ///
825    /// Note that only the claim digest is available because the claim type may be unknown.
826    pub fn claim_digest(&self) -> Result<Digest, VerificationError> {
827        match self {
828            Self::Composite(ref inner) => Ok(inner.claim()?.digest()),
829            Self::Groth16(ref inner) => Ok(inner.claim.digest()),
830            Self::Succinct(ref inner) => Ok(inner.claim.digest()),
831            Self::Fake(ref inner) => Ok(inner.claim.digest()),
832        }
833    }
834
835    /// Return the digest of the verifier parameters struct for the appropriate receipt verifier.
836    pub fn verifier_parameters(&self) -> Digest {
837        match self {
838            Self::Composite(ref inner) => inner.verifier_parameters,
839            Self::Groth16(ref inner) => inner.verifier_parameters,
840            Self::Succinct(ref inner) => inner.verifier_parameters,
841            Self::Fake(_) => Digest::ZERO,
842        }
843    }
844
845    /// Total number of bytes used by the seals of this receipt.
846    pub fn seal_size(&self) -> usize {
847        match self {
848            Self::Composite(receipt) => receipt.seal_size(),
849            Self::Succinct(receipt) => receipt.seal_size(),
850            Self::Groth16(receipt) => receipt.seal_size(),
851            Self::Fake(_) => 0,
852        }
853    }
854}
855
856impl From<InnerReceipt> for InnerAssumptionReceipt {
857    fn from(value: InnerReceipt) -> Self {
858        match value {
859            InnerReceipt::Composite(x) => InnerAssumptionReceipt::Composite(x),
860            InnerReceipt::Succinct(x) => InnerAssumptionReceipt::Succinct(x.into_unknown()),
861            InnerReceipt::Groth16(x) => InnerAssumptionReceipt::Groth16(x.into_unknown()),
862            InnerReceipt::Fake(x) => InnerAssumptionReceipt::Fake(x.into_unknown()),
863        }
864    }
865}
866
867impl<Claim> From<GenericReceipt<Claim>> for InnerAssumptionReceipt
868where
869    Claim: risc0_binfmt::Digestible,
870{
871    fn from(value: GenericReceipt<Claim>) -> Self {
872        match value {
873            GenericReceipt::Succinct(x) => InnerAssumptionReceipt::Succinct(x.into_unknown()),
874            GenericReceipt::Groth16(x) => InnerAssumptionReceipt::Groth16(x.into_unknown()),
875            GenericReceipt::Fake(x) => InnerAssumptionReceipt::Fake(x.into_unknown()),
876        }
877    }
878}
879
880/// Maximum segment size, as a power of two (po2) that the default verifier parameters will accept.
881///
882/// A default of 21 was selected to reach a target of 97 bits of security under our analysis. Using
883/// a po2 higher than 21 shows a degradation of 1 bit of security per po2, to 94 bits at po2 24.
884pub const DEFAULT_MAX_PO2: usize = 22;
885
886/// Context available to the verification process.
887#[non_exhaustive]
888pub struct VerifierContext {
889    /// A registry of hash functions to be used by the verification process.
890    pub suites: BTreeMap<String, HashSuite<BabyBear>>,
891
892    /// Parameters for verification of [SegmentReceipt].
893    pub segment_verifier_parameters: Option<SegmentReceiptVerifierParameters>,
894
895    /// Parameters for verification of [SuccinctReceipt].
896    pub succinct_verifier_parameters: Option<SuccinctReceiptVerifierParameters>,
897
898    /// Parameters for verification of [Groth16Receipt].
899    pub groth16_verifier_parameters: Option<Groth16ReceiptVerifierParameters>,
900
901    /// Whether or not dev-mode is enabled. If enabled, fake receipts will verify successfully.
902    pub(crate) dev_mode: bool,
903}
904
905impl VerifierContext {
906    /// Create an empty [VerifierContext].
907    pub fn empty() -> Self {
908        Self {
909            suites: BTreeMap::default(),
910            segment_verifier_parameters: None,
911            succinct_verifier_parameters: None,
912            groth16_verifier_parameters: None,
913            dev_mode: crate::is_dev_mode_enabled_via_environment(),
914        }
915    }
916
917    /// Return the mapping of hash suites used in the default [VerifierContext].
918    pub fn default_hash_suites() -> BTreeMap<String, HashSuite<BabyBear>> {
919        BTreeMap::from([
920            ("blake2b".into(), Blake2bCpuHashSuite::new_suite()),
921            ("poseidon2".into(), Poseidon2HashSuite::new_suite()),
922            ("sha-256".into(), Sha256HashSuite::new_suite()),
923        ])
924    }
925
926    /// Construct a verifier context that will accept receipts with control any of the default
927    /// control ID associated with cycle counts as powers of two (po2) up to the given max
928    /// inclusive.
929    #[stability::unstable]
930    pub fn from_max_po2(po2_max: usize) -> Self {
931        Self {
932            suites: Self::default_hash_suites(),
933            segment_verifier_parameters: Some(SegmentReceiptVerifierParameters::default()),
934            succinct_verifier_parameters: Some(SuccinctReceiptVerifierParameters::from_max_po2(
935                po2_max,
936            )),
937            groth16_verifier_parameters: Some(Groth16ReceiptVerifierParameters::from_max_po2(
938                po2_max,
939            )),
940            dev_mode: crate::is_dev_mode_enabled_via_environment(),
941        }
942    }
943
944    /// Construct a verifier context that will accept receipts with control any of the default
945    /// control ID associated with cycle counts of all supported powers of two (po2).
946    #[stability::unstable]
947    pub fn all_po2s() -> Self {
948        Self::from_max_po2(risc0_zkp::MAX_CYCLES_PO2)
949    }
950
951    /// Return [VerifierContext] with the given map of hash suites.
952    pub fn with_suites(mut self, suites: BTreeMap<String, HashSuite<BabyBear>>) -> Self {
953        self.suites = suites;
954        self
955    }
956
957    /// Return [VerifierContext] with the given [SegmentReceiptVerifierParameters] set.
958    pub fn with_segment_verifier_parameters(
959        mut self,
960        params: SegmentReceiptVerifierParameters,
961    ) -> Self {
962        self.segment_verifier_parameters = Some(params);
963        self
964    }
965
966    /// Return [VerifierContext] with the given [SuccinctReceiptVerifierParameters] set.
967    pub fn with_succinct_verifier_parameters(
968        mut self,
969        params: SuccinctReceiptVerifierParameters,
970    ) -> Self {
971        self.succinct_verifier_parameters = Some(params);
972        self
973    }
974
975    /// Return [VerifierContext] with the given [Groth16ReceiptVerifierParameters] set.
976    pub fn with_groth16_verifier_parameters(
977        mut self,
978        params: Groth16ReceiptVerifierParameters,
979    ) -> Self {
980        self.groth16_verifier_parameters = Some(params);
981        self
982    }
983
984    /// Return [VerifierContext] with is_dev_mode enabled or disabled.
985    pub fn with_dev_mode(mut self, dev_mode: bool) -> Self {
986        if cfg!(feature = "disable-dev-mode") && dev_mode {
987            panic!("zkVM: Inconsistent settings -- please resolve. \
988                The RISC0_DEV_MODE environment variable is set but dev mode has been disabled by feature flag.");
989        }
990        self.dev_mode = dev_mode;
991        self
992    }
993
994    /// Returns `true` if dev-mode is enabled.
995    pub fn dev_mode(&self) -> bool {
996        self.dev_mode
997    }
998
999    /// Parameters for verification of [CompositeReceipt].
1000    ///
1001    /// Made up of the verifier parameters for each other receipt type. Returns none if any of the
1002    /// composed verifier parameters are unavailable.
1003    pub fn composite_verifier_parameters(&self) -> Option<CompositeReceiptVerifierParameters> {
1004        Some(CompositeReceiptVerifierParameters {
1005            segment: self.segment_verifier_parameters.as_ref()?.clone().into(),
1006            succinct: self.succinct_verifier_parameters.as_ref()?.clone().into(),
1007            groth16: self.groth16_verifier_parameters.as_ref()?.clone().into(),
1008        })
1009    }
1010}
1011
1012impl Default for VerifierContext {
1013    fn default() -> Self {
1014        Self {
1015            suites: Self::default_hash_suites(),
1016            segment_verifier_parameters: Some(Default::default()),
1017            succinct_verifier_parameters: Some(Default::default()),
1018            groth16_verifier_parameters: Some(Default::default()),
1019            dev_mode: crate::is_dev_mode_enabled_via_environment(),
1020        }
1021    }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026    use super::{FakeReceipt, InnerReceipt, Receipt};
1027    use crate::{
1028        sha::{Digest, DIGEST_BYTES},
1029        MaybePruned,
1030    };
1031    use risc0_zkp::verify::VerificationError;
1032
1033    #[test]
1034    fn mangled_version_info_should_error() {
1035        let mut mangled_receipt = Receipt::new(
1036            InnerReceipt::Fake(FakeReceipt {
1037                claim: MaybePruned::Pruned(Digest::ZERO),
1038            }),
1039            vec![],
1040        );
1041        let ones_digest = Digest::from([1u8; DIGEST_BYTES]);
1042        mangled_receipt.metadata.verifier_parameters = ones_digest;
1043
1044        assert_eq!(
1045            mangled_receipt.verify(Digest::ZERO).err().unwrap(),
1046            VerificationError::ReceiptFormatError
1047        );
1048        assert_eq!(
1049            mangled_receipt
1050                .verify_with_context(&Default::default(), Digest::ZERO)
1051                .err()
1052                .unwrap(),
1053            VerificationError::ReceiptFormatError
1054        );
1055        assert_eq!(
1056            mangled_receipt
1057                .verify_integrity_with_context(&Default::default())
1058                .err()
1059                .unwrap(),
1060            VerificationError::ReceiptFormatError
1061        );
1062    }
1063
1064    #[test]
1065    fn borsh_serde() {
1066        use crate::ReceiptClaim;
1067        use risc0_zkvm_methods::MULTI_TEST_ID;
1068
1069        let claim = ReceiptClaim::ok(MULTI_TEST_ID, vec![]);
1070        let receipt = Receipt::new(
1071            InnerReceipt::Fake(FakeReceipt {
1072                claim: MaybePruned::Value(claim),
1073            }),
1074            vec![],
1075        );
1076        let encoded = borsh::to_vec(&receipt).unwrap();
1077        let decoded: Receipt = borsh::from_slice(&encoded).unwrap();
1078        assert_eq!(receipt, decoded);
1079    }
1080}