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};
24use core::fmt::Debug;
25
26use anyhow::Result;
27use borsh::{BorshDeserialize, BorshSerialize};
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    receipt_claim::Unknown,
44    serde::{from_slice, Error},
45    sha::{Digestible, Sha256},
46    Assumption, Assumptions, MaybePruned, Output, 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            return Err(VerificationError::VerifierParametersMismatch {
170                expected: self.inner.verifier_parameters(),
171                received: self.metadata.verifier_parameters,
172            });
173        }
174
175        tracing::debug!("Receipt::verify_with_context");
176        self.inner.verify_integrity_with_context(ctx)?;
177
178        // Check that the claim on the verified receipt matches what was expected. Since we have
179        // constrained all field in the ReceiptClaim, we can directly construct the expected digest
180        // and do not need to open the claim digest on the inner receipt.
181        let expected_claim = ReceiptClaim::ok(image_id, MaybePruned::Pruned(self.journal.digest()));
182        if expected_claim.digest() != self.inner.claim()?.digest() {
183            tracing::debug!(
184                "receipt claim does not match expected claim:\nreceipt: {:#?}\nexpected: {:#?}",
185                self.inner.claim()?,
186                expected_claim
187            );
188            return Err(VerificationError::ClaimDigestMismatch {
189                expected: expected_claim.digest(),
190                received: self.claim()?.digest(),
191            });
192        }
193
194        Ok(())
195    }
196
197    /// Verify the integrity of this receipt, ensuring the claim and journal
198    /// are attested to by the seal.
199    ///
200    /// This does not verify the success of the guest execution. In
201    /// particular, the guest could have exited with an error (e.g.
202    /// `ExitCode::Halted(1)`) or faulted state. It also does not check the
203    /// image ID, or otherwise constrain what guest was executed. After calling
204    /// this method, the caller should check the [ReceiptClaim] fields
205    /// relevant to their application. If you need to verify a successful
206    /// guest execution and access the journal, the `verify` function is
207    /// recommended.
208    pub fn verify_integrity_with_context(
209        &self,
210        ctx: &VerifierContext,
211    ) -> Result<(), VerificationError> {
212        if self.inner.verifier_parameters() != self.metadata.verifier_parameters {
213            return Err(VerificationError::VerifierParametersMismatch {
214                expected: self.inner.verifier_parameters(),
215                received: self.metadata.verifier_parameters,
216            });
217        }
218
219        tracing::debug!("Receipt::verify_integrity_with_context");
220        self.inner.verify_integrity_with_context(ctx)?;
221
222        // Check that self.journal is attested to by the inner receipt.
223        // We need to open the claim digest to do this, so it cannot be pruned.
224        let maybe_pruned_claim = self.inner.claim()?;
225        let claim = maybe_pruned_claim
226            .as_value()
227            .map_err(|_| VerificationError::ReceiptFormatError)?;
228
229        let expected_output = claim.exit_code.expects_output().then(|| Output {
230            journal: MaybePruned::Pruned(self.journal.digest()),
231            // TODO(#982): It would be reasonable for this method to allow integrity verification
232            // for receipts that have a non-empty assumptions list, but it is not supported here
233            // because we don't have a enough information to open the assumptions list unless we
234            // require it be empty.
235            assumptions: Assumptions(vec![]).into(),
236        });
237
238        if claim.output.digest() != expected_output.digest() {
239            let empty_output = claim.output.is_none() && self.journal.bytes.is_empty();
240            if !empty_output {
241                tracing::debug!(
242                    "journal: 0x{}, expected output digest: 0x{}, decoded output digest: 0x{}",
243                    hex::encode(&self.journal.bytes),
244                    hex::encode(expected_output.digest()),
245                    hex::encode(claim.output.digest()),
246                );
247                return Err(VerificationError::JournalDigestMismatch);
248            }
249            tracing::debug!("accepting zero digest for output of receipt with empty journal");
250        }
251
252        Ok(())
253    }
254
255    /// Extract the [ReceiptClaim] from this receipt.
256    pub fn claim(&self) -> Result<MaybePruned<ReceiptClaim>, VerificationError> {
257        self.inner.claim()
258    }
259
260    /// Total number of bytes used by the seals of this receipt.
261    pub fn seal_size(&self) -> usize {
262        self.inner.seal_size()
263    }
264}
265
266/// A record of the public commitments for a proven zkVM execution.
267///
268/// Public outputs, including commitments to important inputs, are written to the journal during
269/// zkVM execution. Along with an image ID, it constitutes the statement proven by a given
270/// [Receipt]
271#[derive(
272    Clone, Debug, Default, Deserialize, Serialize, PartialEq, BorshSerialize, BorshDeserialize,
273)]
274pub struct Journal {
275    /// The raw bytes of the journal.
276    pub bytes: Vec<u8>,
277}
278
279impl Journal {
280    /// Construct a new [Journal].
281    pub fn new(bytes: Vec<u8>) -> Self {
282        Self { bytes }
283    }
284
285    /// Decode the journal bytes by using the [risc0 deserializer](crate::serde).
286    pub fn decode<T: DeserializeOwned>(&self) -> Result<T, Error> {
287        from_slice(&self.bytes)
288    }
289}
290
291impl risc0_binfmt::Digestible for Journal {
292    fn digest<S: Sha256>(&self) -> Digest {
293        *S::hash_bytes(&self.bytes)
294    }
295}
296
297impl AsRef<[u8]> for Journal {
298    fn as_ref(&self) -> &[u8] {
299        &self.bytes
300    }
301}
302
303/// A lower level receipt, containing the cryptographic seal (i.e. zero-knowledge proof) and
304/// verification logic for a specific proof system and circuit. All inner receipt types are
305/// zero-knowledge proofs of execution for a RISC-V zkVM.
306#[derive(Clone, Debug, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
307#[cfg_attr(test, derive(PartialEq))]
308#[non_exhaustive]
309pub enum InnerReceipt {
310    /// A non-succinct [CompositeReceipt], made up of one inner receipt per segment.
311    Composite(CompositeReceipt),
312
313    /// A [SuccinctReceipt], proving arbitrarily long zkVM computions with a single STARK.
314    Succinct(SuccinctReceipt<ReceiptClaim>),
315
316    /// A [Groth16Receipt], proving arbitrarily long zkVM computions with a single Groth16 SNARK.
317    Groth16(Groth16Receipt<ReceiptClaim>),
318
319    /// A [FakeReceipt], with no cryptographic integrity, used only for development.
320    Fake(FakeReceipt<ReceiptClaim>),
321}
322
323impl InnerReceipt {
324    /// Verify the integrity of this receipt, ensuring the claim is attested to by the seal.
325    pub fn verify_integrity_with_context(
326        &self,
327        ctx: &VerifierContext,
328    ) -> Result<(), VerificationError> {
329        tracing::debug!("InnerReceipt::verify_integrity_with_context");
330        match self {
331            Self::Composite(inner) => inner.verify_integrity_with_context(ctx),
332            Self::Groth16(inner) => inner.verify_integrity_with_context(ctx),
333            Self::Succinct(inner) => inner.verify_integrity_with_context(ctx),
334            Self::Fake(inner) => inner.verify_integrity(),
335        }
336    }
337
338    /// Returns the [InnerReceipt::Composite] arm.
339    pub fn composite(&self) -> Result<&CompositeReceipt, VerificationError> {
340        if let Self::Composite(x) = self {
341            Ok(x)
342        } else {
343            Err(VerificationError::ReceiptFormatError)
344        }
345    }
346
347    /// Returns the [InnerReceipt::Groth16] arm.
348    pub fn groth16(&self) -> Result<&Groth16Receipt<ReceiptClaim>, VerificationError> {
349        if let Self::Groth16(x) = self {
350            Ok(x)
351        } else {
352            Err(VerificationError::ReceiptFormatError)
353        }
354    }
355
356    /// Returns the [InnerReceipt::Succinct] arm.
357    pub fn succinct(&self) -> Result<&SuccinctReceipt<ReceiptClaim>, VerificationError> {
358        if let Self::Succinct(x) = self {
359            Ok(x)
360        } else {
361            Err(VerificationError::ReceiptFormatError)
362        }
363    }
364
365    /// Extract the [ReceiptClaim] from this receipt.
366    pub fn claim(&self) -> Result<MaybePruned<ReceiptClaim>, VerificationError> {
367        match self {
368            Self::Composite(ref inner) => Ok(inner.claim()?.into()),
369            Self::Groth16(ref inner) => Ok(inner.claim.clone()),
370            Self::Succinct(ref inner) => Ok(inner.claim.clone()),
371            Self::Fake(ref inner) => Ok(inner.claim.clone()),
372        }
373    }
374
375    /// Return the digest of the verifier parameters struct for the appropriate receipt verifier.
376    pub fn verifier_parameters(&self) -> Digest {
377        match self {
378            Self::Composite(ref inner) => inner.verifier_parameters,
379            Self::Groth16(ref inner) => inner.verifier_parameters,
380            Self::Succinct(ref inner) => inner.verifier_parameters,
381            Self::Fake(_) => Digest::ZERO,
382        }
383    }
384
385    /// Total number of bytes used by the seals of this receipt.
386    pub fn seal_size(&self) -> usize {
387        match self {
388            Self::Composite(receipt) => receipt.seal_size(),
389            Self::Succinct(receipt) => receipt.seal_size(),
390            Self::Groth16(receipt) => receipt.seal_size(),
391            Self::Fake(_) => 0,
392        }
393    }
394}
395
396/// A fake receipt for testing and development.
397///
398/// This receipt is not valid and will fail verification unless the
399/// environment variable `RISC0_DEV_MODE` is set to `true`, in which case a
400/// pass-through 'verification' will be performed, but it *does not*
401/// represent any meaningful attestation of receipt's integrity.
402///
403/// This type solely exists to improve development experience, for further
404/// information about development-only mode see our [dev-mode
405/// documentation](https://dev.risczero.com/api/generating-proofs/dev-mode).
406#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
407#[cfg_attr(test, derive(PartialEq))]
408#[non_exhaustive]
409pub struct FakeReceipt<Claim>
410where
411    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
412{
413    /// Claim containing information about the computation that this receipt pretends to prove.
414    ///
415    /// The standard claim type is [ReceiptClaim], which represents a RISC-V zkVM execution.
416    pub claim: MaybePruned<Claim>,
417}
418
419impl<Claim> FakeReceipt<Claim>
420where
421    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
422{
423    /// Create a new [FakeReceipt] for the given claim.
424    pub fn new(claim: impl Into<MaybePruned<Claim>>) -> Self {
425        Self {
426            claim: claim.into(),
427        }
428    }
429
430    /// Pretend to verify the integrity of this receipt. If not in dev mode (i.e. the
431    /// RISC0_DEV_MODE environment variable is not set) this will always reject. When in dev mode,
432    /// this will always pass.
433    pub fn verify_integrity(&self) -> Result<(), VerificationError> {
434        #[cfg(feature = "std")]
435        if crate::is_dev_mode() {
436            return Ok(());
437        }
438        Err(VerificationError::InvalidProof)
439    }
440
441    /// Prunes the claim, retaining its digest, and converts into a [FakeReceipt] with an unknown
442    /// claim type. Can be used to get receipts of a uniform type across heterogeneous claims.
443    pub fn into_unknown(self) -> FakeReceipt<Unknown> {
444        FakeReceipt {
445            claim: MaybePruned::Pruned(self.claim.digest()),
446        }
447    }
448}
449
450/// Metadata providing context on the receipt.
451///
452/// It contains information about the proving system, SDK versions, and other information to help
453/// with interoperability. It is not cryptographically bound to the receipt, and should not be used
454/// for security-relevant decisions, such as choosing whether or not to accept a receipt based on
455/// it's stated version.
456#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
457#[non_exhaustive]
458pub struct ReceiptMetadata {
459    /// Information which can be used to decide whether a given verifier is compatible with this
460    /// receipt (i.e. that it may be able to verify it).
461    ///
462    /// It is intended to be used when there are multiple verifier implementations (e.g.
463    /// corresponding to multiple versions of a proof system or circuit) and it is ambiguous which
464    /// one should be used to attempt verification of a receipt.
465    pub verifier_parameters: Digest,
466}
467
468/// An assumption attached to a guest execution as a result of calling
469/// `env::verify` or `env::verify_integrity`.
470#[derive(Clone, Debug, Serialize, Deserialize)]
471pub enum AssumptionReceipt {
472    /// A [Receipt] for a proven assumption.
473    ///
474    /// Upon proving, this receipt will be used as proof of the assumption that results from a call
475    /// to `env::verify`, and the resulting receipt will be unconditional. As a result,
476    /// [Receipt::verify] will return true and the verifier will accept the receipt.
477    Proven(InnerAssumptionReceipt),
478
479    /// An [Assumption] that is not directly proven to be true.
480    ///
481    /// Proving an execution with an unresolved assumption will result in a conditional receipt. In
482    /// order for the verifier to accept a conditional receipt, they must be given a
483    /// [AssumptionReceipt] proving the assumption, or explicitly accept the assumption without
484    /// proof.
485    Unresolved(Assumption),
486}
487
488impl AssumptionReceipt {
489    /// Returns the digest of the claim for this [AssumptionReceipt].
490    pub fn claim_digest(&self) -> Result<Digest, VerificationError> {
491        match self {
492            Self::Proven(receipt) => Ok(receipt.claim_digest()?),
493            Self::Unresolved(assumption) => Ok(assumption.claim),
494        }
495    }
496}
497
498impl From<Receipt> for AssumptionReceipt {
499    /// Create a proven assumption from a [Receipt].
500    fn from(receipt: Receipt) -> Self {
501        Self::Proven(receipt.inner.into())
502    }
503}
504
505impl From<InnerReceipt> for AssumptionReceipt {
506    /// Create a proven assumption from a [InnerReceipt].
507    fn from(receipt: InnerReceipt) -> Self {
508        Self::Proven(receipt.into())
509    }
510}
511
512impl From<InnerAssumptionReceipt> for AssumptionReceipt {
513    /// Create a proven assumption from a [InnerAssumptionReceipt].
514    fn from(receipt: InnerAssumptionReceipt) -> Self {
515        Self::Proven(receipt)
516    }
517}
518
519impl<Claim> From<SuccinctReceipt<Claim>> for AssumptionReceipt
520where
521    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
522{
523    /// Create a proven assumption from a [InnerAssumptionReceipt].
524    fn from(receipt: SuccinctReceipt<Claim>) -> Self {
525        Self::Proven(InnerAssumptionReceipt::Succinct(receipt.into_unknown()))
526    }
527}
528
529impl From<Assumption> for AssumptionReceipt {
530    /// Create an unresolved assumption from an [Assumption].
531    fn from(assumption: Assumption) -> Self {
532        Self::Unresolved(assumption)
533    }
534}
535
536impl From<MaybePruned<ReceiptClaim>> for AssumptionReceipt {
537    /// Create an unresolved assumption from a [MaybePruned] [ReceiptClaim].
538    ///
539    /// The control root will be set to all zeroes, which means that the assumption must be
540    /// resolved with the same control root at the conditional receipt (i.e. that this assumption
541    /// is for the same version of zkVM as the receipt it is attached to).
542    fn from(claim: MaybePruned<ReceiptClaim>) -> Self {
543        Self::Unresolved(Assumption {
544            claim: claim.digest(),
545            control_root: Digest::ZERO,
546        })
547    }
548}
549
550impl From<ReceiptClaim> for AssumptionReceipt {
551    /// Create an unresolved assumption from a [ReceiptClaim].
552    ///
553    /// The control root will be set to all zeroes, which means that the assumption must be
554    /// resolved with the same control root at the conditional receipt (i.e. that this assumption
555    /// is for the same version of zkVM as the receipt it is attached to).
556    fn from(claim: ReceiptClaim) -> Self {
557        Self::Unresolved(Assumption {
558            claim: claim.digest(),
559            control_root: Digest::ZERO,
560        })
561    }
562}
563
564/// An enumeration of receipt types similar to [InnerReceipt], but for use in [AssumptionReceipt].
565/// Instead of proving only RISC-V execution with [ReceiptClaim], this type can prove any claim
566/// implemented by one of its inner types.
567#[derive(Clone, Debug, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
568#[cfg_attr(test, derive(PartialEq))]
569#[non_exhaustive]
570pub enum InnerAssumptionReceipt {
571    /// A non-succinct [CompositeReceipt], made up of one inner receipt per segment and assumption.
572    Composite(CompositeReceipt),
573
574    /// A [SuccinctReceipt], proving arbitrarily the claim with a single STARK.
575    Succinct(SuccinctReceipt<Unknown>),
576
577    /// A [Groth16Receipt], proving arbitrarily the claim with a single Groth16 SNARK.
578    Groth16(Groth16Receipt<Unknown>),
579
580    /// A [FakeReceipt], with no cryptographic integrity, used only for development.
581    Fake(FakeReceipt<Unknown>),
582}
583
584impl InnerAssumptionReceipt {
585    /// Verify the integrity of this receipt, ensuring the claim is attested to by the seal.
586    pub fn verify_integrity_with_context(
587        &self,
588        ctx: &VerifierContext,
589    ) -> Result<(), VerificationError> {
590        tracing::debug!("InnerAssumptionReceipt::verify_integrity_with_context");
591        match self {
592            Self::Composite(inner) => inner.verify_integrity_with_context(ctx),
593            Self::Groth16(inner) => inner.verify_integrity_with_context(ctx),
594            Self::Succinct(inner) => inner.verify_integrity_with_context(ctx),
595            Self::Fake(inner) => inner.verify_integrity(),
596        }
597    }
598
599    /// Returns the [InnerAssumptionReceipt::Composite] arm.
600    pub fn composite(&self) -> Result<&CompositeReceipt, VerificationError> {
601        if let Self::Composite(x) = self {
602            Ok(x)
603        } else {
604            Err(VerificationError::ReceiptFormatError)
605        }
606    }
607
608    /// Returns the [InnerAssumptionReceipt::Groth16] arm.
609    pub fn groth16(&self) -> Result<&Groth16Receipt<Unknown>, VerificationError> {
610        if let Self::Groth16(x) = self {
611            Ok(x)
612        } else {
613            Err(VerificationError::ReceiptFormatError)
614        }
615    }
616
617    /// Returns the [InnerAssumptionReceipt::Succinct] arm.
618    pub fn succinct(&self) -> Result<&SuccinctReceipt<Unknown>, VerificationError> {
619        if let Self::Succinct(x) = self {
620            Ok(x)
621        } else {
622            Err(VerificationError::ReceiptFormatError)
623        }
624    }
625
626    /// Extract the claim digest from this receipt.
627    ///
628    /// Note that only the claim digest is available because the claim type may be unknown.
629    pub fn claim_digest(&self) -> Result<Digest, VerificationError> {
630        match self {
631            Self::Composite(ref inner) => Ok(inner.claim()?.digest()),
632            Self::Groth16(ref inner) => Ok(inner.claim.digest()),
633            Self::Succinct(ref inner) => Ok(inner.claim.digest()),
634            Self::Fake(ref inner) => Ok(inner.claim.digest()),
635        }
636    }
637
638    /// Return the digest of the verifier parameters struct for the appropriate receipt verifier.
639    pub fn verifier_parameters(&self) -> Digest {
640        match self {
641            Self::Composite(ref inner) => inner.verifier_parameters,
642            Self::Groth16(ref inner) => inner.verifier_parameters,
643            Self::Succinct(ref inner) => inner.verifier_parameters,
644            Self::Fake(_) => Digest::ZERO,
645        }
646    }
647
648    /// Total number of bytes used by the seals of this receipt.
649    pub fn seal_size(&self) -> usize {
650        match self {
651            Self::Composite(receipt) => receipt.seal_size(),
652            Self::Succinct(receipt) => receipt.seal_size(),
653            Self::Groth16(receipt) => receipt.seal_size(),
654            Self::Fake(_) => 0,
655        }
656    }
657}
658
659impl From<InnerReceipt> for InnerAssumptionReceipt {
660    fn from(value: InnerReceipt) -> Self {
661        match value {
662            InnerReceipt::Composite(x) => InnerAssumptionReceipt::Composite(x),
663            InnerReceipt::Succinct(x) => InnerAssumptionReceipt::Succinct(x.into_unknown()),
664            InnerReceipt::Groth16(x) => InnerAssumptionReceipt::Groth16(x.into_unknown()),
665            InnerReceipt::Fake(x) => InnerAssumptionReceipt::Fake(x.into_unknown()),
666        }
667    }
668}
669
670/// Maximum segment size, as a power of two (po2) that the default verifier parameters will accept.
671///
672/// A default of 21 was selected to reach a target of 97 bits of security under our analysis. Using
673/// a po2 higher than 21 shows a degradation of 1 bit of security per po2, to 94 bits at po2 24.
674pub const DEFAULT_MAX_PO2: usize = 22;
675
676/// Context available to the verification process.
677#[non_exhaustive]
678pub struct VerifierContext {
679    /// A registry of hash functions to be used by the verification process.
680    pub suites: BTreeMap<String, HashSuite<BabyBear>>,
681
682    /// Parameters for verification of [SegmentReceipt].
683    pub segment_verifier_parameters: Option<SegmentReceiptVerifierParameters>,
684
685    /// Parameters for verification of [SuccinctReceipt].
686    pub succinct_verifier_parameters: Option<SuccinctReceiptVerifierParameters>,
687
688    /// Parameters for verification of [Groth16Receipt].
689    pub groth16_verifier_parameters: Option<Groth16ReceiptVerifierParameters>,
690}
691
692impl VerifierContext {
693    /// Create an empty [VerifierContext].
694    pub fn empty() -> Self {
695        Self {
696            suites: BTreeMap::default(),
697            segment_verifier_parameters: None,
698            succinct_verifier_parameters: None,
699            groth16_verifier_parameters: None,
700        }
701    }
702
703    /// Return the mapping of hash suites used in the default [VerifierContext].
704    pub fn default_hash_suites() -> BTreeMap<String, HashSuite<BabyBear>> {
705        BTreeMap::from([
706            ("blake2b".into(), Blake2bCpuHashSuite::new_suite()),
707            ("poseidon2".into(), Poseidon2HashSuite::new_suite()),
708            ("sha-256".into(), Sha256HashSuite::new_suite()),
709        ])
710    }
711
712    /// Construct a verifier context that will accept receipts with control any of the default
713    /// control ID associated with cycle counts as powers of two (po2) up to the given max
714    /// inclusive.
715    #[stability::unstable]
716    pub fn from_max_po2(po2_max: usize) -> Self {
717        Self {
718            suites: Self::default_hash_suites(),
719            segment_verifier_parameters: Some(SegmentReceiptVerifierParameters::default()),
720            succinct_verifier_parameters: Some(SuccinctReceiptVerifierParameters::from_max_po2(
721                po2_max,
722            )),
723            groth16_verifier_parameters: Some(Groth16ReceiptVerifierParameters::from_max_po2(
724                po2_max,
725            )),
726        }
727    }
728
729    /// Construct a verifier context that will accept receipts with control any of the default
730    /// control ID associated with cycle counts of all supported powers of two (po2).
731    #[stability::unstable]
732    pub fn all_po2s() -> Self {
733        Self::from_max_po2(risc0_zkp::MAX_CYCLES_PO2)
734    }
735
736    /// Return [VerifierContext] with the given map of hash suites.
737    pub fn with_suites(mut self, suites: BTreeMap<String, HashSuite<BabyBear>>) -> Self {
738        self.suites = suites;
739        self
740    }
741
742    /// Return [VerifierContext] with the given [SegmentReceiptVerifierParameters] set.
743    pub fn with_segment_verifier_parameters(
744        mut self,
745        params: SegmentReceiptVerifierParameters,
746    ) -> Self {
747        self.segment_verifier_parameters = Some(params);
748        self
749    }
750
751    /// Return [VerifierContext] with the given [SuccinctReceiptVerifierParameters] set.
752    pub fn with_succinct_verifier_parameters(
753        mut self,
754        params: SuccinctReceiptVerifierParameters,
755    ) -> Self {
756        self.succinct_verifier_parameters = Some(params);
757        self
758    }
759
760    /// Return [VerifierContext] with the given [Groth16ReceiptVerifierParameters] set.
761    pub fn with_groth16_verifier_parameters(
762        mut self,
763        params: Groth16ReceiptVerifierParameters,
764    ) -> Self {
765        self.groth16_verifier_parameters = Some(params);
766        self
767    }
768
769    /// Parameters for verification of [CompositeReceipt].
770    ///
771    /// Made up of the verifier parameters for each other receipt type. Returns none if any of the
772    /// composed verifier parameters are unavailable.
773    pub fn composite_verifier_parameters(&self) -> Option<CompositeReceiptVerifierParameters> {
774        Some(CompositeReceiptVerifierParameters {
775            segment: self.segment_verifier_parameters.as_ref()?.clone().into(),
776            succinct: self.succinct_verifier_parameters.as_ref()?.clone().into(),
777            groth16: self.groth16_verifier_parameters.as_ref()?.clone().into(),
778        })
779    }
780}
781
782impl Default for VerifierContext {
783    fn default() -> Self {
784        Self {
785            suites: Self::default_hash_suites(),
786            segment_verifier_parameters: Some(Default::default()),
787            succinct_verifier_parameters: Some(Default::default()),
788            groth16_verifier_parameters: Some(Default::default()),
789        }
790    }
791}
792
793#[cfg(test)]
794mod tests {
795    use super::{FakeReceipt, InnerReceipt, Receipt};
796    use crate::{
797        sha::{Digest, DIGEST_BYTES},
798        MaybePruned,
799    };
800    use risc0_zkp::verify::VerificationError;
801
802    #[test]
803    fn mangled_version_info_should_error() {
804        let mut mangled_receipt = Receipt::new(
805            InnerReceipt::Fake(FakeReceipt {
806                claim: MaybePruned::Pruned(Digest::ZERO),
807            }),
808            vec![],
809        );
810        let ones_digest = Digest::from([1u8; DIGEST_BYTES]);
811        mangled_receipt.metadata.verifier_parameters = ones_digest;
812
813        assert_eq!(
814            mangled_receipt.verify(Digest::ZERO).err().unwrap(),
815            VerificationError::VerifierParametersMismatch {
816                expected: Digest::ZERO,
817                received: ones_digest
818            }
819        );
820        assert_eq!(
821            mangled_receipt
822                .verify_with_context(&Default::default(), Digest::ZERO)
823                .err()
824                .unwrap(),
825            VerificationError::VerifierParametersMismatch {
826                expected: Digest::ZERO,
827                received: ones_digest
828            }
829        );
830        assert_eq!(
831            mangled_receipt
832                .verify_integrity_with_context(&Default::default())
833                .err()
834                .unwrap(),
835            VerificationError::VerifierParametersMismatch {
836                expected: Digest::ZERO,
837                received: ones_digest
838            }
839        );
840    }
841
842    #[test]
843    fn borsh_serde() {
844        use crate::ReceiptClaim;
845        use risc0_zkvm_methods::MULTI_TEST_ID;
846
847        let claim = ReceiptClaim::ok(MULTI_TEST_ID, vec![]);
848        let receipt = Receipt::new(
849            InnerReceipt::Fake(FakeReceipt {
850                claim: MaybePruned::Value(claim),
851            }),
852            vec![],
853        );
854        let encoded = borsh::to_vec(&receipt).unwrap();
855        let decoded: Receipt = borsh::from_slice(&encoded).unwrap();
856        assert_eq!(receipt, decoded);
857    }
858}