risc0_zkvm/host/recursion/prove/
mod.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
15pub mod zkr;
16
17use std::{
18    collections::{BTreeMap, VecDeque},
19    fmt::Debug,
20    sync::Mutex,
21};
22
23use anyhow::{anyhow, bail, ensure, Context, Result};
24use risc0_binfmt::read_sha_halfs;
25use risc0_circuit_recursion::{
26    control_id::BN254_IDENTITY_CONTROL_ID,
27    prove::{DigestKind, RecursionReceipt},
28    CircuitImpl,
29};
30use risc0_circuit_rv32im::RV32IM_SEAL_VERSION;
31use risc0_zkp::{
32    adapter::{CircuitInfo, PROOF_SYSTEM_INFO},
33    core::{
34        digest::{Digest, DIGEST_SHORTS},
35        hash::{hash_suite_from_name, poseidon2::Poseidon2HashSuite},
36    },
37    field::baby_bear::BabyBearElem,
38    verify::ReadIOP,
39};
40
41use crate::{
42    claim::{
43        merge::Merge,
44        receipt::{Assumption, UnionClaim},
45        Unknown,
46    },
47    receipt::{
48        merkle::{MerkleGroup, MerkleProof},
49        SegmentReceipt, SuccinctReceipt, SuccinctReceiptVerifierParameters,
50    },
51    sha::Digestible,
52    Assumptions, MaybePruned, Output, ProverOpts, ReceiptClaim, WorkClaim,
53};
54
55use risc0_circuit_recursion::prove::Program;
56
57/// Number of rows to use for the recursion circuit witness as a power of 2.
58pub const RECURSION_PO2: usize = 18;
59
60pub(crate) type ZkrRegistryEntry = Box<dyn Fn() -> Result<Program> + Send + 'static>;
61
62pub(crate) type ZkrRegistry = BTreeMap<Digest, ZkrRegistryEntry>;
63
64/// A registry to look up programs by control ID.
65pub(crate) static ZKR_REGISTRY: Mutex<ZkrRegistry> = Mutex::new(BTreeMap::new());
66
67/// Run the lift program to transform an rv32im segment receipt into a recursion receipt.
68///
69/// The lift program verifies the rv32im circuit STARK proof inside the recursion circuit,
70/// resulting in a recursion circuit STARK proof. This recursion proof has a single
71/// constant-time verification procedure, with respect to the original segment length, and is then
72/// used as the input to all other recursion programs (e.g. join, resolve, and identity_p254).
73pub fn lift(segment_receipt: &SegmentReceipt) -> Result<SuccinctReceipt<ReceiptClaim>> {
74    tracing::debug!("Proving lift: claim = {:#?}", segment_receipt.claim);
75    let mut prover = Prover::new_lift(segment_receipt, ProverOpts::succinct())?;
76
77    let receipt = prover.prover.run()?;
78    let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
79    tracing::debug!("Proving lift finished: decoded claim = {claim_decoded:#?}");
80
81    let claim = claim_decoded.merge(&segment_receipt.claim)?;
82
83    make_succinct_receipt(prover, receipt, claim)
84}
85
86/// Run the lift program to create a succinct work claim receipt from a segment receipt.
87///
88/// Similar to [`lift`], but additionally tracks verifiable work by computing the work value
89/// from the segment proof and embedding it in the resulting work claim.
90pub fn lift_povw(
91    segment_receipt: &SegmentReceipt,
92) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>> {
93    tracing::debug!("Proving lift_povw: claim = {:#?}", segment_receipt.claim);
94    let mut prover = Prover::new_lift_povw(segment_receipt, ProverOpts::succinct())?;
95
96    let receipt = prover.prover.run()?;
97    let mut out_stream = receipt.out_stream();
98    tracing::debug!("Proving lift_povw finished: out = {out_stream:?}");
99    let claim_decoded = WorkClaim::<ReceiptClaim>::decode_from_seal(&mut out_stream)?;
100    tracing::debug!("Proving lift_povw finished: decoded claim = {claim_decoded:#?}");
101
102    // Merge the full claim from the segment receipt into the decoded work claim.
103    let mut claim = claim_decoded;
104    claim
105        .claim
106        .merge_with(&segment_receipt.claim.clone().into())
107        .context("failed to merge segment receipt claim into decode claim")?;
108
109    make_succinct_receipt(prover, receipt, claim)
110}
111
112/// Run the join program to compress two receipts of the same session into one.
113///
114/// By repeated application of the join program, any number of receipts for execution spans within
115/// the same session can be compressed into a single receipt for the entire session.
116pub fn join(
117    a: &SuccinctReceipt<ReceiptClaim>,
118    b: &SuccinctReceipt<ReceiptClaim>,
119) -> Result<SuccinctReceipt<ReceiptClaim>> {
120    tracing::debug!("Proving join: a.claim = {:#?}", a.claim);
121    tracing::debug!("Proving join: b.claim = {:#?}", b.claim);
122
123    let mut prover = Prover::new_join(a, b, ProverOpts::succinct())?;
124    let receipt = prover.prover.run()?;
125
126    let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
127    tracing::debug!("Proving join finished: decoded claim = {claim_decoded:#?}");
128
129    // Compute the expected claim and merge it with the decoded claim, checking that they match.
130    let claim = claim_decoded.merge(&a.claim.join(&b.claim)?.value()?)?;
131
132    make_succinct_receipt(prover, receipt, claim)
133}
134
135/// Run the join program to compress two work claim receipts of the same session into one.
136///
137/// Similar to [`join`], but operates on work claim receipts and combines their work values
138/// while ensuring the consumed nonce ranges are disjoint.
139pub fn join_povw(
140    a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
141    b: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
142) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>> {
143    tracing::debug!("Proving join_povw: a.claim = {:#?}", a.claim);
144    tracing::debug!("Proving join_povw: b.claim = {:#?}", b.claim);
145
146    let mut prover = Prover::new_join_povw(a, b, false, ProverOpts::succinct())?;
147    let receipt = prover.prover.run()?;
148
149    let claim_decoded = WorkClaim::<ReceiptClaim>::decode_from_seal(&mut receipt.out_stream())?;
150    tracing::debug!("Proving join_povw finished: decoded claim = {claim_decoded:#?}");
151
152    // Compute the expected claim and merge it with the decoded claim, checking that they match.
153    let claim = claim_decoded.merge(&a.claim.join(&b.claim)?.value()?)?;
154
155    make_succinct_receipt(prover, receipt, claim)
156}
157
158/// Run the join_unwrap program to compress two work claim receipts into a regular receipt.
159///
160/// This combines the functionality of [`join_povw`] and [`unwrap_povw`] in a single recursion
161/// step, with reduced latency relative to running a separate unwrap.
162pub fn join_unwrap_povw(
163    a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
164    b: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
165) -> Result<SuccinctReceipt<ReceiptClaim>> {
166    tracing::debug!("Proving join_unwrap_povw: a.claim = {:#?}", a.claim);
167    tracing::debug!("Proving join_unwrap_povw: b.claim = {:#?}", b.claim);
168
169    let mut prover = Prover::new_join_povw(a, b, true, ProverOpts::succinct())?;
170    let receipt = prover.prover.run()?;
171
172    let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
173    tracing::debug!("Proving join_unwrap_povw finished: decoded claim = {claim_decoded:#?}");
174
175    // Compute the expected claim and merge it with the decoded claim, checking that they match.
176    let claim = claim_decoded.merge(&a.claim.join(&b.claim)?.value()?.claim.value()?)?;
177
178    make_succinct_receipt(prover, receipt, claim)
179}
180
181/// Run the union program to compress two succinct receipts into one.
182///
183/// By repeated application of the union program, any number of succinct
184/// receipts can be compressed into a single receipt.
185pub fn union(
186    a: &SuccinctReceipt<Unknown>,
187    b: &SuccinctReceipt<Unknown>,
188) -> Result<SuccinctReceipt<UnionClaim>> {
189    // NOTE: This will run into issues if the assumption is made with a control root of zero. Right
190    // now, this is only used for keccak so this issue has not been hit.
191    let a_assumption = a.to_assumption(false)?.digest();
192    let b_assumption = b.to_assumption(false)?.digest();
193
194    let ((left_assumption, left_receipt), (right_assumption, right_receipt)) =
195        if a_assumption <= b_assumption {
196            ((a_assumption, a), (b_assumption, b))
197        } else {
198            ((b_assumption, b), (a_assumption, a))
199        };
200
201    tracing::debug!("Proving union: left assumption = {:#?}", left_assumption);
202    tracing::debug!("Proving union: right assumption = {:#?}", right_assumption);
203
204    let mut prover = Prover::new_union(left_receipt, right_receipt, ProverOpts::succinct())?;
205    let receipt = prover.prover.run()?;
206
207    let claim = UnionClaim {
208        left: left_assumption,
209        right: right_assumption,
210    };
211
212    make_succinct_receipt(prover, receipt, claim)
213}
214
215/// Run the resolve program to remove an assumption from a conditional receipt upon verifying a
216/// receipt proving the validity of the assumption.
217///
218/// By applying the resolve program, a conditional receipt (i.e. a receipt for an execution using
219/// the `env::verify` API to logically verify a receipt) can be made into an unconditional receipt.
220pub fn resolve<Claim>(
221    conditional: &SuccinctReceipt<ReceiptClaim>,
222    assumption: &SuccinctReceipt<Claim>,
223) -> Result<SuccinctReceipt<ReceiptClaim>>
224where
225    Claim: risc0_binfmt::Digestible + Debug,
226{
227    tracing::debug!(
228        "Proving resolve: conditional.claim = {:#?}",
229        conditional.claim,
230    );
231    tracing::debug!(
232        "Proving resolve: assumption.claim = {:#?}",
233        assumption.claim,
234    );
235
236    let mut prover = Prover::new_resolve(conditional, assumption, ProverOpts::succinct())?;
237    let receipt = prover.prover.run()?;
238    let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
239    tracing::debug!("Proving resolve finished: decoded claim = {claim_decoded:#?}");
240
241    let claim = conditional
242        .claim
243        .as_value()
244        .context("conditional receipt claim is pruned")?
245        .resolve(&assumption.claim)
246        .context("failed to compute resolved claim")?
247        .merge(&claim_decoded)
248        .context("failed to merge resolved and decoded claims")?;
249
250    make_succinct_receipt(prover, receipt, claim)
251}
252
253/// Run the resolve program to remove an assumption from a conditional work claim receipt.
254///
255/// Similar to [`resolve`], but operates on work claim receipts while preserving the work value
256/// from the conditional receipt.
257pub fn resolve_povw<Claim>(
258    conditional: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
259    assumption: &SuccinctReceipt<Claim>,
260) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>>
261where
262    Claim: risc0_binfmt::Digestible + Debug,
263{
264    tracing::debug!(
265        "Proving resolve_povw: conditional.claim = {:#?}",
266        conditional.claim,
267    );
268    tracing::debug!(
269        "Proving resolve_povw: assumption.claim = {:#?}",
270        assumption.claim,
271    );
272
273    let mut prover =
274        Prover::new_resolve_povw(conditional, assumption, false, ProverOpts::succinct())?;
275    let receipt = prover.prover.run()?;
276    let claim_decoded = WorkClaim::<ReceiptClaim>::decode_from_seal(&mut receipt.out_stream())?;
277    tracing::debug!("Proving resolve_povw finished: decoded claim = {claim_decoded:#?}");
278
279    let claim = conditional
280        .claim
281        .as_value()
282        .context("conditional receipt povw claim is pruned")?
283        .resolve(&assumption.claim)
284        .context("failed to compute resolved claim")?
285        .merge(&claim_decoded)
286        .context("failed to merge resolved and decoded claims")?;
287
288    make_succinct_receipt(prover, receipt, claim)
289}
290
291/// Run the resolve_unwrap program to remove an assumption from a conditional work claim receipt.
292///
293/// This combines the functionality of [`resolve_povw`] and [`unwrap_povw`] in a single recursion
294/// step, with reduced latency relative to running a separate unwrap.
295pub fn resolve_unwrap_povw<Claim>(
296    conditional: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
297    assumption: &SuccinctReceipt<Claim>,
298) -> Result<SuccinctReceipt<ReceiptClaim>>
299where
300    Claim: risc0_binfmt::Digestible + Debug,
301{
302    tracing::debug!(
303        "Proving resolve_unwrap_povw: conditional.claim = {:#?}",
304        conditional.claim,
305    );
306    tracing::debug!(
307        "Proving resolve_unwrap_povw: assumption.claim = {:#?}",
308        assumption.claim,
309    );
310
311    let mut prover =
312        Prover::new_resolve_povw(conditional, assumption, true, ProverOpts::succinct())?;
313    let receipt = prover.prover.run()?;
314    let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
315    tracing::debug!("Proving resolve_unwrap_povw finished: decoded claim = {claim_decoded:#?}");
316
317    let claim = conditional
318        .claim
319        .as_value()
320        .context("conditional receipt povw claim is pruned")?
321        .claim
322        .as_value()
323        .context("conditional receipt claim is pruned")?
324        .resolve(&assumption.claim)
325        .context("failed to compute resolved claim")?
326        .merge(&claim_decoded)
327        .context("failed to merge resolved and decoded claims")?;
328
329    make_succinct_receipt(prover, receipt, claim)
330}
331
332/// Run the unwrap program to convert a work claim receipt to a regular receipt claim.
333///
334/// This removes the work tracking wrapper from a receipt, producing a standard receipt
335/// that can be sent to verifiers of the underlying claim.
336pub fn unwrap_povw(
337    a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
338) -> Result<SuccinctReceipt<ReceiptClaim>> {
339    tracing::debug!("Proving unwrap_povw: a.claim = {:#?}", a.claim);
340
341    let mut prover = Prover::new_unwrap_povw(a, ProverOpts::succinct())?;
342    let receipt = prover.prover.run()?;
343
344    let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
345    tracing::debug!("Proving unwrap_povw finished: decoded claim = {claim_decoded:#?}");
346
347    // Compute the expected claim and merge it with the decoded claim, checking that they match.
348    let claim = MaybePruned::Value(claim_decoded).merge(&a.claim.as_value()?.claim)?;
349
350    make_succinct_receipt(prover, receipt, claim)
351}
352
353/// Prove the verification of a recursion receipt using the Poseidon254 hash function for FRI.
354///
355/// The identity_p254 program is used as the last step in the prover pipeline before running the
356/// Groth16 prover. In Groth16 over BN254, it is much more efficient to verify a STARK that was
357/// produced with Poseidon over the BN254 base field compared to using Poseidon over BabyBear.
358pub fn identity_p254(
359    inner: &SuccinctReceipt<ReceiptClaim>,
360) -> Result<SuccinctReceipt<ReceiptClaim>> {
361    tracing::debug!("identity_p254");
362
363    let opts = ProverOpts::succinct()
364        .with_hashfn("poseidon_254".to_string())
365        .with_control_ids(vec![BN254_IDENTITY_CONTROL_ID]);
366    let mut prover = Prover::new_identity(inner, opts)?;
367    let receipt = prover.prover.run()?;
368    let claim =
369        MaybePruned::Value(ReceiptClaim::decode(&mut receipt.out_stream())?).merge(&inner.claim)?;
370
371    // Include an inclusion proof for control_id to allow verification against a root.
372    let hashfn = prover.opts.hash_suite()?.hashfn;
373    let control_inclusion_proof = prover.control_inclusion_proof()?;
374    let control_root = control_inclusion_proof.root(&prover.control_id, hashfn.as_ref());
375    let params = SuccinctReceiptVerifierParameters {
376        control_root,
377        inner_control_root: Some(inner.control_root()?),
378        proof_system_info: PROOF_SYSTEM_INFO,
379        circuit_info: CircuitImpl::CIRCUIT_INFO,
380    };
381
382    Ok(SuccinctReceipt {
383        seal: receipt.seal,
384        hashfn: prover.opts.hashfn.clone(),
385        control_id: prover.control_id,
386        control_inclusion_proof,
387        claim,
388        verifier_parameters: params.digest(),
389    })
390}
391
392/// Prove the specified program identified by the `control_id` using the specified `input`.
393pub fn prove_zkr(
394    program: Program,
395    control_id: &Digest,
396    allowed_control_ids: Vec<Digest>,
397    input: &[u8],
398) -> Result<SuccinctReceipt<Unknown>> {
399    let opts = ProverOpts::succinct().with_control_ids(allowed_control_ids);
400    let mut prover = Prover::new(program, *control_id, opts.clone());
401    prover.add_input(bytemuck::cast_slice(input));
402
403    tracing::debug!("Running prover");
404    let receipt = prover.run()?;
405
406    tracing::trace!("zkr receipt: {receipt:?}");
407
408    // Read the claim digest from the second of the global output slots.
409    let claim_digest: Digest = read_sha_halfs(&mut VecDeque::from_iter(
410        bytemuck::checked::cast_slice::<_, BabyBearElem>(
411            &receipt.seal[DIGEST_SHORTS..2 * DIGEST_SHORTS],
412        )
413        .iter()
414        .copied()
415        .map(u32::from),
416    ))?;
417
418    let hashfn = opts.hash_suite()?.hashfn;
419    let control_group = MerkleGroup::new(opts.control_ids.clone())?;
420    let control_root = control_group.calc_root(hashfn.as_ref());
421    let control_inclusion_proof = control_group.get_proof(control_id, hashfn.as_ref())?;
422
423    let verifier_parameters = SuccinctReceiptVerifierParameters {
424        control_root,
425        inner_control_root: None,
426        proof_system_info: PROOF_SYSTEM_INFO,
427        circuit_info: CircuitImpl::CIRCUIT_INFO,
428    }
429    .digest();
430
431    Ok(SuccinctReceipt {
432        seal: receipt.seal,
433        hashfn: opts.hashfn,
434        control_id: *control_id,
435        control_inclusion_proof,
436        claim: MaybePruned::<Unknown>::Pruned(claim_digest),
437        verifier_parameters,
438    })
439}
440
441/// Prove the specified program identified by the `control_id` using the specified `input`.
442pub fn prove_registered_zkr(
443    control_id: &Digest,
444    allowed_control_ids: Vec<Digest>,
445    input: &[u8],
446) -> Result<SuccinctReceipt<Unknown>> {
447    let zkr = get_registered_zkr(control_id)?;
448    prove_zkr(zkr, control_id, allowed_control_ids, input)
449}
450
451/// Registers a function to retrieve a recursion program (zkr) based on a control id.
452pub fn register_zkr(
453    control_id: &Digest,
454    get_program_fn: impl Fn() -> Result<Program> + Send + 'static,
455) -> Option<ZkrRegistryEntry> {
456    let mut registry = ZKR_REGISTRY.lock().unwrap();
457    registry.insert(*control_id, Box::new(get_program_fn))
458}
459
460/// Returns a registered ZKR program, or an error if not found.
461pub fn get_registered_zkr(control_id: &Digest) -> Result<Program> {
462    let registry = ZKR_REGISTRY.lock().unwrap();
463    registry
464        .get(control_id)
465        .map(|f| f())
466        .unwrap_or_else(|| bail!("Control id {control_id} unregistered"))
467}
468
469/// Private utility to make a SuccinctReceipt from a RecursionReceipt, the Prover and the Claim.
470fn make_succinct_receipt<Claim>(
471    prover: Prover,
472    receipt: RecursionReceipt,
473    claim: impl Into<MaybePruned<Claim>>,
474) -> Result<SuccinctReceipt<Claim>> {
475    Ok(SuccinctReceipt {
476        seal: receipt.seal,
477        hashfn: prover.opts.hashfn.clone(),
478        control_id: prover.control_id,
479        control_inclusion_proof: prover.control_inclusion_proof()?,
480        claim: claim.into(),
481        // TODO(victor): This should be derived from the ProverOpts instead of being default.
482        verifier_parameters: SuccinctReceiptVerifierParameters::default().digest(),
483    })
484}
485
486/// Prove the test_recursion_circuit. This is useful for testing purposes.
487///
488/// digest1 will be passed through to the first of the output globals, as the "inner control root".
489/// digest1 and digest2 will be used to calculate a "claim digest", placed in the second output.
490#[cfg(test)]
491pub fn test_zkr(
492    digest1: &Digest,
493    digest2: &Digest,
494    po2: usize,
495) -> Result<SuccinctReceipt<crate::claim::Unknown>> {
496    use risc0_circuit_recursion::prove::zkr::get_zkr;
497    use risc0_zkp::core::hash::poseidon2::Poseidon2HashSuite;
498
499    let program = get_zkr("test_recursion_circuit.zkr", po2)?;
500    let suite = Poseidon2HashSuite::new_suite();
501    let control_id = program.compute_control_id(suite.clone()).unwrap();
502    let opts = ProverOpts::succinct().with_control_ids(vec![control_id]);
503
504    let mut prover = Prover::new(program, control_id, opts.clone());
505    prover.add_input_digest(digest1, DigestKind::Poseidon2);
506    prover.add_input_digest(digest2, DigestKind::Poseidon2);
507
508    let receipt = prover.run()?;
509
510    // Read the claim digest from the second of the global output slots.
511    let claim_digest = read_sha_halfs(&mut VecDeque::from_iter(
512        bytemuck::checked::cast_slice::<_, BabyBearElem>(
513            &receipt.seal[DIGEST_SHORTS..2 * DIGEST_SHORTS],
514        )
515        .iter()
516        .copied()
517        .map(u32::from),
518    ))?;
519
520    // Include an inclusion proof for control_id to allow verification against a root.
521    let hashfn = opts.hash_suite()?.hashfn;
522    let control_inclusion_proof = MerkleGroup::new(opts.control_ids.clone())?
523        .get_proof(&prover.control_id, hashfn.as_ref())?;
524    let control_root = control_inclusion_proof.root(&prover.control_id, hashfn.as_ref());
525    let params = SuccinctReceiptVerifierParameters {
526        control_root,
527        inner_control_root: Some(digest1.to_owned()),
528        proof_system_info: PROOF_SYSTEM_INFO,
529        circuit_info: CircuitImpl::CIRCUIT_INFO,
530    };
531    Ok(SuccinctReceipt {
532        seal: receipt.seal,
533        hashfn: suite.name,
534        control_id: prover.control_id,
535        control_inclusion_proof,
536        claim: MaybePruned::Pruned(claim_digest),
537        verifier_parameters: params.digest(),
538    })
539}
540
541/// Prover for zkVM use of the recursion circuit.
542pub struct Prover {
543    prover: risc0_circuit_recursion::prove::Prover,
544    control_id: Digest,
545    opts: ProverOpts,
546}
547
548/// Utility macro to compress repeated checks that a receipt uses the poseidon2 hash.
549macro_rules! ensure_poseidon2 {
550    ($receipt:expr) => {
551        ensure!(
552            $receipt.hashfn == "poseidon2",
553            "recursion programs only supports poseidon2 hashfn; received {}",
554            $receipt.hashfn
555        );
556    };
557}
558
559impl Prover {
560    pub(crate) fn new(program: Program, control_id: Digest, opts: ProverOpts) -> Self {
561        Self {
562            prover: risc0_circuit_recursion::prove::Prover::new(program, &opts.hashfn),
563            control_id,
564            opts,
565        }
566    }
567
568    /// Returns the control id of the recursion VM program being proven.
569    pub fn control_id(&self) -> &Digest {
570        &self.control_id
571    }
572
573    /// Returns a Merkle inclusion proof of this prover's control ID in the set of allowed IDs.
574    pub fn control_inclusion_proof(&self) -> Result<MerkleProof> {
575        let hashfn = self
576            .opts
577            .hash_suite()
578            .context("ProverOpts contains invalid hashfn")?
579            .hashfn;
580        MerkleGroup::new(self.opts.control_ids.clone())?
581            .get_proof(&self.control_id, hashfn.as_ref())
582    }
583
584    /// Initialize a recursion prover with the test recursion program. This program is used in
585    /// testing the basic correctness of the recursion circuit.
586    pub fn new_test_recursion_circuit(digests: [&Digest; 2], opts: ProverOpts) -> Result<Self> {
587        let (program, control_id) = zkr::test_recursion_circuit(&opts.hashfn)?;
588        let mut prover = Prover::new(program, control_id, opts);
589
590        for digest in digests {
591            prover.add_input_digest(digest, DigestKind::Poseidon2);
592        }
593
594        Ok(prover)
595    }
596
597    /// Initialize a recursion prover with the lift program to transform an rv32im segment receipt
598    /// into a recursion receipt.
599    ///
600    /// The lift program is verifies the rv32im circuit STARK proof inside the recursion circuit,
601    /// resulting in a recursion circuit STARK proof. This recursion proof has a single
602    /// constant-time verification procedure, with respect to the original segment length, and is
603    /// then used as the input to all other recursion programs (e.g. join, resolve, and
604    /// identity_p254).
605    pub fn new_lift(segment: &SegmentReceipt, opts: ProverOpts) -> Result<Self> {
606        Self::new_lift_inner(segment, opts, false)
607    }
608
609    /// Create a prover job for the lift program that produces a work claim receipt.
610    ///
611    /// Similar to [`Self::new_lift`], but produces a work claim receipt that tracks
612    /// verifiable work by computing the work value from the segment proof.
613    pub fn new_lift_povw(segment: &SegmentReceipt, opts: ProverOpts) -> Result<Self> {
614        Self::new_lift_inner(segment, opts, true)
615    }
616
617    /// Instantiate a lift program, with the option of PoVW or not. Note that these programs
618    /// produce different output but have the same inputs and so share the same logic here.
619    fn new_lift_inner(segment: &SegmentReceipt, opts: ProverOpts, povw: bool) -> Result<Self> {
620        ensure_poseidon2!(segment);
621
622        let inner_hash_suite = hash_suite_from_name(&segment.hashfn)
623            .ok_or_else(|| anyhow!("unsupported hash function: {}", segment.hashfn))?;
624        let allowed_ids = MerkleGroup::new(opts.control_ids.clone())?;
625        let merkle_root = allowed_ids.calc_root(inner_hash_suite.hashfn.as_ref());
626
627        let out_size = risc0_circuit_rv32im::CircuitImpl::OUTPUT_SIZE;
628
629        ensure!(
630            segment.seal[0] == RV32IM_SEAL_VERSION,
631            "seal version mismatch"
632        );
633
634        let seal = &segment.seal[1..];
635
636        // Read the output fields in the rv32im seal to get the po2. We need this po2 to chose
637        // which lift program we are going to run.
638        let mut iop = ReadIOP::new(seal, inner_hash_suite.rng.as_ref());
639        iop.read_field_elem_slice::<BabyBearElem>(out_size);
640        let po2 = *iop.read_u32s(1).first().unwrap() as usize;
641
642        // Instantiate the prover with the lift recursion program and its control ID.
643        let (program, control_id) = match povw {
644            false => zkr::lift(po2, &opts.hashfn)?,
645            true => zkr::lift_povw(po2, &opts.hashfn)?,
646        };
647        let mut prover = Prover::new(program, control_id, opts);
648
649        prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
650        prover.add_input(seal);
651
652        Ok(prover)
653    }
654
655    /// Initialize a recursion prover with the union program to compress two
656    /// succinct receipts into one.
657    ///
658    /// By repeated application of the union program, any number of succinct
659    /// receipts can be compressed into a single receipt.
660    pub fn new_union(
661        a: &SuccinctReceipt<Unknown>,
662        b: &SuccinctReceipt<Unknown>,
663        opts: ProverOpts,
664    ) -> Result<Self> {
665        ensure_poseidon2!(a);
666        ensure_poseidon2!(b);
667
668        let hash_suite = Poseidon2HashSuite::new_suite();
669        let allowed_ids = MerkleGroup::new(opts.control_ids.clone())?;
670        let merkle_root = allowed_ids.calc_root(hash_suite.hashfn.as_ref());
671
672        let (program, control_id) = zkr::union(&opts.hashfn)?;
673        let mut prover = Prover::new(program, control_id, opts);
674
675        prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
676        prover.add_succinct_generic_receipt(a)?;
677        prover.add_succinct_generic_receipt(b)?;
678        Ok(prover)
679    }
680
681    /// Initialize a recursion prover with the join program to compress two receipts of the same
682    /// session into one.
683    ///
684    /// By repeated application of the join program, any number of receipts for execution spans
685    /// within the same session can be compressed into a single receipt for the entire session.
686    pub fn new_join(
687        a: &SuccinctReceipt<ReceiptClaim>,
688        b: &SuccinctReceipt<ReceiptClaim>,
689        opts: ProverOpts,
690    ) -> Result<Self> {
691        ensure_poseidon2!(a);
692        ensure_poseidon2!(b);
693
694        let (program, control_id) = zkr::join(&opts.hashfn)?;
695        let mut prover = Prover::new(program, control_id, opts);
696
697        // Determine the control root from the receipts themselves, and ensure they are equal. If
698        // the determined control root does not match what the downstream verifier expects, they
699        // will reject.
700        let merkle_root = a.control_root()?;
701        ensure!(
702            merkle_root == b.control_root()?,
703            "merkle roots for a and b do not match: {} != {}",
704            merkle_root,
705            b.control_root()?
706        );
707
708        prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
709        prover.add_succinct_rv32im_receipt(a)?;
710        prover.add_succinct_rv32im_receipt(b)?;
711        Ok(prover)
712    }
713
714    /// Create a prover job for the join program that operates on work claim receipts.
715    ///
716    /// Similar to [`Self::new_join`], but operates on work claim receipts and combines their
717    /// work values while ensuring the consumed nonce ranges are disjoint. The `unwrap` parameter
718    /// determines whether the result is unwrapped to a regular receipt claim.
719    pub fn new_join_povw(
720        a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
721        b: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
722        unwrap: bool,
723        opts: ProverOpts,
724    ) -> Result<Self> {
725        ensure_poseidon2!(a);
726        ensure_poseidon2!(b);
727
728        let (program, control_id) = match unwrap {
729            false => zkr::join_povw(&opts.hashfn)?,
730            true => zkr::join_unwrap_povw(&opts.hashfn)?,
731        };
732        let mut prover = Prover::new(program, control_id, opts);
733
734        // Determine the control root from the receipts themselves, and ensure they are equal. If
735        // the determined control root does not match what the downstream verifier expects, they
736        // will reject.
737        let merkle_root = a.control_root()?;
738        ensure!(
739            merkle_root == b.control_root()?,
740            "merkle roots for a and b do not match: {} != {}",
741            merkle_root,
742            b.control_root()?
743        );
744
745        prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
746        prover.add_succinct_work_claim_rv32im_receipt(a)?;
747        prover.add_succinct_work_claim_rv32im_receipt(b)?;
748        Ok(prover)
749    }
750
751    /// Initialize a recursion prover with the resolve program to remove an assumption from a
752    /// conditional receipt upon verifying a receipt proving the validity of the assumption.
753    ///
754    /// By applying the resolve program, a conditional receipt (i.e. a receipt for an execution
755    /// using the `env::verify` API to logically verify a receipt) can be made into an
756    /// unconditional receipt.
757    pub fn new_resolve<Claim>(
758        cond: &SuccinctReceipt<ReceiptClaim>,
759        assum: &SuccinctReceipt<Claim>,
760        opts: ProverOpts,
761    ) -> Result<Self>
762    where
763        Claim: risc0_binfmt::Digestible + Debug,
764    {
765        ensure_poseidon2!(cond);
766        ensure_poseidon2!(assum);
767
768        // Load the resolve predicate as a Program and construct the prover.
769        let (program, control_id) = zkr::resolve(&opts.hashfn)?;
770        let mut prover = Prover::new(program, control_id, opts);
771
772        // Load the input values needed by the predicate.
773        // Resolve predicate needs both seals as input, and the journal and assumptions tail digest
774        // to compute the opening of the conditional receipt claim to the first assumption.
775        prover.add_input_digest(&cond.control_root()?, DigestKind::Poseidon2);
776        prover.add_succinct_rv32im_receipt(cond)?;
777
778        let (head, tail, output) = check_resolve_assumption(&cond.claim, &assum.claim)?;
779
780        // Ensure that the assumption receipt has the right control root.
781        let expected_root = match head.control_root == Digest::ZERO {
782            true => cond.control_root()?,
783            false => head.control_root,
784        };
785        ensure!(
786            expected_root == assum.control_root()?,
787            "assumption receipt control root does not match head of assumptions list"
788        );
789
790        prover.add_assumption_receipt(head, assum)?;
791        prover.add_input_digest(&tail.digest(), DigestKind::Sha256);
792        prover.add_input_digest(&output.journal.digest(), DigestKind::Sha256);
793        Ok(prover)
794    }
795
796    /// Create a prover job for the resolve program that operates on work claim receipts.
797    ///
798    /// Similar to [`Self::new_resolve`], but operates on work claim receipts while preserving
799    /// the work value from the conditional receipt. The `unwrap` parameter determines whether
800    /// the result is unwrapped to a regular receipt claim.
801    pub fn new_resolve_povw<Claim>(
802        cond: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
803        assum: &SuccinctReceipt<Claim>,
804        unwrap: bool,
805        opts: ProverOpts,
806    ) -> Result<Self>
807    where
808        Claim: risc0_binfmt::Digestible + Debug,
809    {
810        ensure_poseidon2!(cond);
811        ensure_poseidon2!(assum);
812
813        // Load the resolve predicate as a Program and construct the prover.
814        let (program, control_id) = match unwrap {
815            false => zkr::resolve_povw(&opts.hashfn)?,
816            true => zkr::resolve_unwrap_povw(&opts.hashfn)?,
817        };
818        let mut prover = Prover::new(program, control_id, opts);
819
820        // Load the input values needed by the predicate.
821        // Resolve predicate needs both seals as input, and the journal and assumptions tail digest
822        // to compute the opening of the conditional receipt claim to the first assumption.
823        prover.add_input_digest(&cond.control_root()?, DigestKind::Poseidon2);
824        prover.add_succinct_work_claim_rv32im_receipt(cond)?;
825
826        let cond_claim = &cond
827            .claim
828            .as_value()
829            .context("cannot resolve assumption receipt with pruned work claim")?
830            .claim;
831        let (head, tail, output) = check_resolve_assumption(cond_claim, &assum.claim)?;
832
833        // Ensure that the assumption receipt has the right control root.
834        let expected_root = match head.control_root == Digest::ZERO {
835            true => cond.control_root()?,
836            false => head.control_root,
837        };
838        ensure!(
839            expected_root == assum.control_root()?,
840            "assumption receipt control root does not match head of assumptions list"
841        );
842
843        prover.add_assumption_receipt(head, assum)?;
844        prover.add_input_digest(&tail.digest(), DigestKind::Sha256);
845        prover.add_input_digest(&output.journal.digest(), DigestKind::Sha256);
846        Ok(prover)
847    }
848
849    /// Prove the verification of a recursion receipt, applying no changes to [ReceiptClaim].
850    ///
851    /// The primary use for this program is to transform the receipt itself, e.g. using a different
852    /// hash function for FRI. See [identity_p254] for more information.
853    pub fn new_identity(a: &SuccinctReceipt<ReceiptClaim>, opts: ProverOpts) -> Result<Self> {
854        ensure_poseidon2!(a);
855
856        let (program, control_id) = zkr::identity(&opts.hashfn)?;
857        let mut prover = Prover::new(program, control_id, opts);
858
859        prover.add_input_digest(&a.control_root()?, DigestKind::Poseidon2);
860        prover.add_succinct_rv32im_receipt(a)?;
861        Ok(prover)
862    }
863
864    /// Create a prover job for the unwrap program that converts a work claim receipt to a
865    /// regular receipt.
866    ///
867    /// This removes the work tracking wrapper from a receipt, producing a standard receipt
868    /// that can be sent to verifiers of the underlying claim.
869    pub fn new_unwrap_povw(
870        a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
871        opts: ProverOpts,
872    ) -> Result<Self> {
873        ensure_poseidon2!(a);
874
875        let (program, control_id) = zkr::unwrap_povw(&opts.hashfn)?;
876        let mut prover = Prover::new(program, control_id, opts);
877
878        prover.add_input_digest(&a.control_root()?, DigestKind::Poseidon2);
879        prover.add_succinct_work_claim_rv32im_receipt(a)?;
880        Ok(prover)
881    }
882
883    pub(crate) fn add_input(&mut self, input: &[u32]) {
884        self.prover.add_input(input)
885    }
886
887    /// Add a digest to the input for the recursion program.
888    fn add_input_digest(&mut self, digest: &Digest, kind: DigestKind) {
889        self.prover.add_input_digest(digest, kind)
890    }
891
892    /// Add a recursion seal (i.e. STARK proof) to input tape of the recursion program.
893    pub fn add_seal(
894        &mut self,
895        seal: &[u32],
896        control_id: &Digest,
897        control_inclusion_proof: &MerkleProof,
898    ) -> Result<()> {
899        tracing::debug!("Control ID = {:?}", control_id);
900        self.add_input(seal);
901        tracing::debug!("index = {:?}", control_inclusion_proof.index);
902        self.add_input(bytemuck::cast_slice(&[BabyBearElem::new(
903            control_inclusion_proof.index,
904        )]));
905        for digest in &control_inclusion_proof.digests {
906            tracing::debug!("path = {:?}", digest);
907            self.add_input_digest(digest, DigestKind::Poseidon2);
908        }
909        Ok(())
910    }
911
912    /// Add a receipt covering some generic claim. Do not include any claim information.
913    fn add_assumption_receipt<Claim>(
914        &mut self,
915        assumption: Assumption,
916        receipt: &SuccinctReceipt<Claim>,
917    ) -> Result<()> {
918        self.add_seal(
919            &receipt.seal,
920            &receipt.control_id,
921            &receipt.control_inclusion_proof,
922        )?;
923        // Resolve program expects an additional boolean to tell it when the control root is zero.
924        let zero_root = BabyBearElem::new((assumption.control_root == Digest::ZERO) as u32);
925        self.add_input(bytemuck::cast_slice(&[zero_root]));
926        Ok(())
927    }
928
929    fn add_succinct_work_claim_rv32im_receipt(
930        &mut self,
931        a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
932    ) -> Result<()> {
933        self.add_seal(&a.seal, &a.control_id, &a.control_inclusion_proof)?;
934        let mut data = Vec::<u32>::new();
935        a.claim.as_value()?.encode_to_seal(&mut data)?;
936        let data_fp: Vec<BabyBearElem> = data.iter().map(|x| BabyBearElem::new(*x)).collect();
937        self.add_input(bytemuck::cast_slice(&data_fp));
938        Ok(())
939    }
940
941    /// Add a receipt covering rv32im execution, and include the first level of ReceiptClaim.
942    fn add_succinct_rv32im_receipt(&mut self, a: &SuccinctReceipt<ReceiptClaim>) -> Result<()> {
943        self.add_seal(&a.seal, &a.control_id, &a.control_inclusion_proof)?;
944        let mut data = Vec::<u32>::new();
945        a.claim.as_value()?.encode(&mut data)?;
946        let data_fp: Vec<BabyBearElem> = data.iter().map(|x| BabyBearElem::new(*x)).collect();
947        self.add_input(bytemuck::cast_slice(&data_fp));
948        Ok(())
949    }
950
951    /// Add a receipt for a succinct receipt
952    fn add_succinct_generic_receipt<Claim>(&mut self, a: &SuccinctReceipt<Claim>) -> Result<()> {
953        self.add_seal(&a.seal, &a.control_id, &a.control_inclusion_proof)?;
954        // Union program expects an additional boolean to indicate that control root is zero.
955        let zero_root = BabyBearElem::new((a.control_root()? == Digest::ZERO) as u32);
956        self.add_input(bytemuck::cast_slice(&[zero_root]));
957        Ok(())
958    }
959
960    /// Run the prover, producing a receipt of execution for the recursion circuit over the loaded
961    /// program and input.
962    pub fn run(&mut self) -> Result<RecursionReceipt> {
963        self.prover.run()
964    }
965}
966
967fn check_resolve_assumption<Claim>(
968    cond: &MaybePruned<ReceiptClaim>,
969    assum: &MaybePruned<Claim>,
970) -> Result<(Assumption, Assumptions, Output)>
971where
972    Claim: risc0_binfmt::Digestible + Debug,
973{
974    let output = cond
975        .as_value()
976        .context("cannot resolve conditional receipt with pruned claim")?
977        .output
978        .as_value()
979        .context("cannot resolve conditional receipt with pruned output")?
980        .as_ref()
981        .ok_or_else(|| anyhow!("cannot resolve conditional receipt with no output"))?
982        .clone();
983
984    // Unwrap the MaybePruned assumptions list and resolve the corroborated assumption,
985    // removing the head and leaving the tail of the list.
986    let assumptions = output
987        .assumptions
988        .clone()
989        .value()
990        .context("cannot resolve conditional receipt with pruned assumptions")?;
991    let head: Assumption = assumptions
992        .0
993        .first()
994        .ok_or_else(|| anyhow!("cannot resolve conditional receipt with no assumptions"))?
995        .as_value()
996        .context("cannot resolve conditional receipt with pruned head assumption")?
997        .clone();
998
999    ensure!(
1000        head.claim == assum.digest(),
1001        "assumption receipt claim does not match head of assumptions list"
1002    );
1003
1004    let mut assumptions_tail = assumptions;
1005    assumptions_tail.resolve(&head.digest())?;
1006
1007    Ok((head, assumptions_tail, output))
1008}