risc0_zkvm/receipt/
segment.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use alloc::{collections::BTreeSet, string::String, vec::Vec};
16
17use anyhow::Result;
18use borsh::{BorshDeserialize, BorshSerialize};
19use derive_more::Debug;
20use risc0_binfmt::{tagged_iter, tagged_struct, Digestible, PovwNonce};
21use risc0_zkp::{
22    adapter::{CircuitInfo as _, ProtocolInfo, PROOF_SYSTEM_INFO},
23    core::{digest::Digest, hash::sha::Sha256},
24    verify::VerificationError,
25};
26use serde::{Deserialize, Serialize};
27
28use super::VerifierContext;
29use crate::{sha, ReceiptClaim};
30
31/// A receipt attesting to the execution of a Segment.
32///
33/// A SegmentReceipt attests that a Segment was executed in a manner
34/// consistent with the [ReceiptClaim] included in the receipt.
35#[derive(Clone, Debug, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
36#[cfg_attr(test, derive(PartialEq))]
37#[non_exhaustive]
38pub struct SegmentReceipt {
39    /// The cryptographic data attesting to the validity of the code execution.
40    ///
41    /// This data is used by the ZKP Verifier (as called by
42    /// [SegmentReceipt::verify_integrity_with_context]) to cryptographically prove that this
43    /// Segment was faithfully executed. It is largely opaque cryptographic data, but contains a
44    /// non-opaque claim component, which can be conveniently accessed with
45    /// [SegmentReceipt::claim].
46    #[debug("{} bytes", self.get_seal_bytes().len())]
47    pub seal: Vec<u32>,
48
49    /// Segment index within the [Receipt](crate::Receipt)
50    pub index: u32,
51
52    /// Name of the hash function used to create this receipt.
53    pub hashfn: String,
54
55    /// A digest of the verifier parameters that can be used to verify this receipt.
56    ///
57    /// Acts as a fingerprint to identity differing proof system or circuit versions between a
58    /// prover and a verifier. Is not intended to contain the full verifier parameters, which must
59    /// be provided by a trusted source (e.g. packaged with the verifier code).
60    pub verifier_parameters: Digest,
61
62    /// [ReceiptClaim] containing information about the execution that this receipt proves.
63    pub claim: ReceiptClaim,
64}
65
66impl SegmentReceipt {
67    /// Verify the integrity of this receipt, ensuring the claim is attested
68    /// to by the seal.
69    pub fn verify_integrity_with_context(
70        &self,
71        ctx: &VerifierContext,
72    ) -> Result<(), VerificationError> {
73        let params = ctx
74            .segment_verifier_parameters
75            .as_ref()
76            .ok_or(VerificationError::VerifierParametersMissing)?;
77
78        // NOTE: We do not check the verifier_parameters digest here, which is used to detect
79        // version mismatches and display a friendlier error than "invalid proof", because there
80        // exist multiple versions of the verifier context that can verify a given receipt. This is
81        // because the segment receipt verifier context contains a set of control IDs,
82        // and will verify as long as the control ID found in is the set. Many sets can contain the
83        // same control ID.
84
85        // Check that the proof system and circuit info strings match what is implemented by this
86        // function. Info strings are used a version identifiers, and this verify implementation
87        // supports exactly one proof systema and circuit version at a time.
88        if params.proof_system_info != PROOF_SYSTEM_INFO {
89            return Err(VerificationError::ProofSystemInfoMismatch {
90                expected: PROOF_SYSTEM_INFO,
91                received: params.proof_system_info,
92            });
93        }
94
95        let expected = risc0_circuit_rv32im::CircuitImpl::CIRCUIT_INFO;
96
97        if params.circuit_info != expected {
98            return Err(VerificationError::CircuitInfoMismatch {
99                expected,
100                received: params.circuit_info,
101            });
102        }
103
104        if self.hashfn != "poseidon2" {
105            return Err(VerificationError::InvalidHashSuite);
106        }
107
108        tracing::debug!("SegmentReceipt::verify_integrity_with_context");
109        risc0_circuit_rv32im::verify(&self.seal)?;
110        let decoded_claim = ReceiptClaim::decode_from_seal_v2(&self.seal, None)
111            .or(Err(VerificationError::ReceiptFormatError))?;
112
113        // Receipt is consistent with the claim encoded on the seal. Now check against the
114        // claim on the struct.
115        if decoded_claim.digest::<sha::Impl>() != self.claim.digest::<sha::Impl>() {
116            tracing::debug!(
117                "decoded segment receipt claim does not match claim field:\ndecoded: {:#?},\nexpected: {:#?}",
118                decoded_claim,
119                self.claim,
120            );
121            return Err(VerificationError::ClaimDigestMismatch {
122                expected: self.claim.digest::<sha::Impl>(),
123                received: decoded_claim.digest::<sha::Impl>(),
124            });
125        }
126        Ok(())
127    }
128
129    /// Return the seal for this receipt, as a vector of bytes.
130    pub fn get_seal_bytes(&self) -> Vec<u8> {
131        self.seal.iter().flat_map(|x| x.to_le_bytes()).collect()
132    }
133
134    /// Number of bytes used by the seal for this receipt.
135    pub fn seal_size(&self) -> usize {
136        core::mem::size_of_val(self.seal.as_slice())
137    }
138
139    /// Extracts the PoVW nonce from this segment receipt's seal.
140    pub fn povw_nonce(&self) -> anyhow::Result<PovwNonce> {
141        risc0_circuit_rv32im::decode_povw_nonce(&self.seal)
142    }
143}
144
145/// Verifier parameters used to verify a [SegmentReceipt].
146#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
147pub struct SegmentReceiptVerifierParameters {
148    /// Set of control ID with which the receipt is expected to verify.
149    pub control_ids: BTreeSet<Digest>,
150
151    /// Protocol info string distinguishing the proof system under which the receipt should verify.
152    pub proof_system_info: ProtocolInfo,
153
154    /// Protocol info string distinguishing circuit with which the receipt should verify.
155    pub circuit_info: ProtocolInfo,
156}
157
158impl Digestible for SegmentReceiptVerifierParameters {
159    /// Hash the [SegmentReceiptVerifierParameters] to get a digest of the struct.
160    fn digest<S: Sha256>(&self) -> Digest {
161        tagged_struct::<S>(
162            "risc0.SegmentReceiptVerifierParameters",
163            &[
164                tagged_iter::<S>("risc0.ControlIdSet", self.control_ids.iter()),
165                *S::hash_bytes(&self.proof_system_info.0),
166                *S::hash_bytes(&self.circuit_info.0),
167            ],
168            &[],
169        )
170    }
171}
172
173impl Default for SegmentReceiptVerifierParameters {
174    /// Default set of parameters used to verify a [SegmentReceipt].
175    fn default() -> Self {
176        Self {
177            control_ids: BTreeSet::default(),
178            proof_system_info: PROOF_SYSTEM_INFO,
179            circuit_info: risc0_circuit_rv32im::CircuitImpl::CIRCUIT_INFO,
180        }
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::SegmentReceiptVerifierParameters;
187    use crate::sha::Digestible;
188    use risc0_zkp::core::digest::digest;
189
190    // Check that the verifier parameters has a stable digest (and therefore a stable value). This
191    // struct encodes parameters used in verification, and so this value should be updated if and
192    // only if a change to the verifier parameters is expected. Updating the verifier parameters
193    // will result in incompatibility with previous versions.
194    #[test]
195    fn segment_receipt_verifier_parameters_is_stable() {
196        assert_eq!(
197            SegmentReceiptVerifierParameters::default().digest(),
198            digest!("e7300130165ebe00f68f9301530de9d068d6f6c06f1c17817a5f7d64ce6c635d")
199        );
200    }
201}