risc0_zkvm/receipt.rs
1// Copyright 2024 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 = 21;
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::from_max_po2(
720 po2_max,
721 )),
722 succinct_verifier_parameters: Some(SuccinctReceiptVerifierParameters::from_max_po2(
723 po2_max,
724 )),
725 groth16_verifier_parameters: Some(Groth16ReceiptVerifierParameters::from_max_po2(
726 po2_max,
727 )),
728 }
729 }
730
731 /// Construct a verifier context that will accept receipts with control any of the default
732 /// control ID associated with cycle counts of all supported powers of two (po2).
733 #[stability::unstable]
734 pub fn all_po2s() -> Self {
735 Self::from_max_po2(risc0_zkp::MAX_CYCLES_PO2)
736 }
737
738 /// Return [VerifierContext] with the given map of hash suites.
739 pub fn with_suites(mut self, suites: BTreeMap<String, HashSuite<BabyBear>>) -> Self {
740 self.suites = suites;
741 self
742 }
743
744 /// Return [VerifierContext] with the given [SegmentReceiptVerifierParameters] set.
745 pub fn with_segment_verifier_parameters(
746 mut self,
747 params: SegmentReceiptVerifierParameters,
748 ) -> Self {
749 self.segment_verifier_parameters = Some(params);
750 self
751 }
752
753 /// Return [VerifierContext] with the given [SuccinctReceiptVerifierParameters] set.
754 pub fn with_succinct_verifier_parameters(
755 mut self,
756 params: SuccinctReceiptVerifierParameters,
757 ) -> Self {
758 self.succinct_verifier_parameters = Some(params);
759 self
760 }
761
762 /// Return [VerifierContext] with the given [Groth16ReceiptVerifierParameters] set.
763 pub fn with_groth16_verifier_parameters(
764 mut self,
765 params: Groth16ReceiptVerifierParameters,
766 ) -> Self {
767 self.groth16_verifier_parameters = Some(params);
768 self
769 }
770
771 /// Parameters for verification of [CompositeReceipt].
772 ///
773 /// Made up of the verifier parameters for each other receipt type. Returns none if any of the
774 /// composed verifier parameters are unavailable.
775 pub fn composite_verifier_parameters(&self) -> Option<CompositeReceiptVerifierParameters> {
776 Some(CompositeReceiptVerifierParameters {
777 segment: self.segment_verifier_parameters.as_ref()?.clone().into(),
778 succinct: self.succinct_verifier_parameters.as_ref()?.clone().into(),
779 groth16: self.groth16_verifier_parameters.as_ref()?.clone().into(),
780 })
781 }
782}
783
784impl Default for VerifierContext {
785 fn default() -> Self {
786 Self {
787 suites: Self::default_hash_suites(),
788 segment_verifier_parameters: Some(Default::default()),
789 succinct_verifier_parameters: Some(Default::default()),
790 groth16_verifier_parameters: Some(Default::default()),
791 }
792 }
793}
794
795#[cfg(test)]
796mod tests {
797 use super::{FakeReceipt, InnerReceipt, Receipt};
798 use crate::{
799 sha::{Digest, DIGEST_BYTES},
800 MaybePruned,
801 };
802 use risc0_zkp::verify::VerificationError;
803
804 #[test]
805 fn mangled_version_info_should_error() {
806 let mut mangled_receipt = Receipt::new(
807 InnerReceipt::Fake(FakeReceipt {
808 claim: MaybePruned::Pruned(Digest::ZERO),
809 }),
810 vec![],
811 );
812 let ones_digest = Digest::from([1u8; DIGEST_BYTES]);
813 mangled_receipt.metadata.verifier_parameters = ones_digest;
814
815 assert_eq!(
816 mangled_receipt.verify(Digest::ZERO).err().unwrap(),
817 VerificationError::VerifierParametersMismatch {
818 expected: Digest::ZERO,
819 received: ones_digest
820 }
821 );
822 assert_eq!(
823 mangled_receipt
824 .verify_with_context(&Default::default(), Digest::ZERO)
825 .err()
826 .unwrap(),
827 VerificationError::VerifierParametersMismatch {
828 expected: Digest::ZERO,
829 received: ones_digest
830 }
831 );
832 assert_eq!(
833 mangled_receipt
834 .verify_integrity_with_context(&Default::default())
835 .err()
836 .unwrap(),
837 VerificationError::VerifierParametersMismatch {
838 expected: Digest::ZERO,
839 received: ones_digest
840 }
841 );
842 }
843
844 #[test]
845 fn borsh_serde() {
846 use crate::ReceiptClaim;
847 use risc0_zkvm_methods::MULTI_TEST_ID;
848
849 let claim = ReceiptClaim::ok(MULTI_TEST_ID, vec![]);
850 let receipt = Receipt::new(
851 InnerReceipt::Fake(FakeReceipt {
852 claim: MaybePruned::Value(claim),
853 }),
854 vec![],
855 );
856 let encoded = borsh::to_vec(&receipt).unwrap();
857 let decoded: Receipt = borsh::from_slice(&encoded).unwrap();
858 assert_eq!(receipt, decoded);
859 }
860}