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::{BabyBear, BabyBearElem, BabyBearExtElem},
38    hal::{CircuitHal, Hal},
39    verify::ReadIOP,
40};
41use serde::Serialize;
42
43use crate::{
44    receipt::{
45        merkle::{MerkleGroup, MerkleProof},
46        SegmentReceipt, SuccinctReceipt, SuccinctReceiptVerifierParameters,
47    },
48    receipt_claim::{Assumption, MaybePruned, Merge, UnionClaim},
49    sha::Digestible,
50    ProverOpts, ReceiptClaim, Unknown,
51};
52
53use risc0_circuit_recursion::prove::Program;
54
55/// Number of rows to use for the recursion circuit witness as a power of 2.
56pub const RECURSION_PO2: usize = 18;
57
58pub(crate) type ZkrRegistryEntry = Box<dyn Fn() -> Result<Program> + Send + 'static>;
59
60pub(crate) type ZkrRegistry = BTreeMap<Digest, ZkrRegistryEntry>;
61
62/// A registry to look up programs by control ID.
63pub(crate) static ZKR_REGISTRY: Mutex<ZkrRegistry> = Mutex::new(BTreeMap::new());
64
65/// Run the lift program to transform an rv32im segment receipt into a recursion receipt.
66///
67/// The lift program verifies the rv32im circuit STARK proof inside the recursion circuit,
68/// resulting in a recursion circuit STARK proof. This recursion proof has a single
69/// constant-time verification procedure, with respect to the original segment length, and is then
70/// used as the input to all other recursion programs (e.g. join, resolve, and identity_p254).
71pub fn lift(segment_receipt: &SegmentReceipt) -> Result<SuccinctReceipt<ReceiptClaim>> {
72    tracing::debug!("Proving lift: claim = {:#?}", segment_receipt.claim);
73    let opts = ProverOpts::succinct();
74    let mut prover = Prover::new_lift(segment_receipt, opts.clone())?;
75
76    let receipt = prover.prover.run()?;
77    let mut out_stream: VecDeque<u32> = VecDeque::new();
78    out_stream.extend(receipt.output.iter());
79    let claim_decoded = ReceiptClaim::decode(&mut out_stream)?;
80    tracing::debug!("Proving lift finished: decoded claim = {claim_decoded:#?}");
81
82    // Include an inclusion proof for control_id to allow verification against a root.
83    let control_inclusion_proof = MerkleGroup::new(opts.control_ids.clone())?
84        .get_proof(&prover.control_id, opts.hash_suite()?.hashfn.as_ref())?;
85    Ok(SuccinctReceipt {
86        seal: receipt.seal,
87        hashfn: opts.hashfn,
88        control_id: prover.control_id,
89        control_inclusion_proof,
90        claim: claim_decoded.merge(&segment_receipt.claim)?.into(),
91        verifier_parameters: SuccinctReceiptVerifierParameters::default().digest(),
92    })
93}
94
95/// Run the join program to compress two receipts of the same session into one.
96///
97/// By repeated application of the join program, any number of receipts for execution spans within
98/// the same session can be compressed into a single receipt for the entire session.
99pub fn join(
100    a: &SuccinctReceipt<ReceiptClaim>,
101    b: &SuccinctReceipt<ReceiptClaim>,
102) -> Result<SuccinctReceipt<ReceiptClaim>> {
103    tracing::debug!("Proving join: a.claim = {:#?}", a.claim);
104    tracing::debug!("Proving join: b.claim = {:#?}", b.claim);
105
106    let opts = ProverOpts::succinct();
107    let mut prover = Prover::new_join(a, b, opts.clone())?;
108    let receipt = prover.prover.run()?;
109    let mut out_stream: VecDeque<u32> = VecDeque::new();
110    out_stream.extend(receipt.output.iter());
111
112    // Construct the expected claim that should have result from the join.
113    let ab_claim = ReceiptClaim {
114        pre: a.claim.as_value()?.pre.clone(),
115        post: b.claim.as_value()?.post.clone(),
116        exit_code: b.claim.as_value()?.exit_code,
117        input: a.claim.as_value()?.input.clone(),
118        output: b.claim.as_value()?.output.clone(),
119    };
120
121    let claim_decoded = ReceiptClaim::decode(&mut out_stream)?;
122    tracing::debug!("Proving join finished: decoded claim = {claim_decoded:#?}");
123
124    // Include an inclusion proof for control_id to allow verification against a root.
125    let control_inclusion_proof = MerkleGroup::new(opts.control_ids.clone())?
126        .get_proof(&prover.control_id, opts.hash_suite()?.hashfn.as_ref())?;
127    Ok(SuccinctReceipt {
128        seal: receipt.seal,
129        hashfn: opts.hashfn,
130        control_id: prover.control_id,
131        control_inclusion_proof,
132        claim: claim_decoded.merge(&ab_claim)?.into(),
133        verifier_parameters: SuccinctReceiptVerifierParameters::default().digest(),
134    })
135}
136
137/// Run the union program to compress two succinct receipts into one.
138///
139/// By repeated application of the union program, any number of succinct
140/// receipts can be compressed into a single receipt.
141pub fn union(
142    a: &SuccinctReceipt<Unknown>,
143    b: &SuccinctReceipt<Unknown>,
144) -> Result<SuccinctReceipt<UnionClaim>> {
145    let a_assumption = Assumption {
146        claim: a.claim.digest(),
147        control_root: a.control_root()?,
148    }
149    .digest();
150    let b_assumption = Assumption {
151        claim: b.claim.digest(),
152        control_root: b.control_root()?,
153    }
154    .digest();
155
156    let ((left_assumption, left_receipt), (right_assumption, right_receipt)) =
157        if a_assumption <= b_assumption {
158            ((a_assumption, a), (b_assumption, b))
159        } else {
160            ((b_assumption, b), (a_assumption, a))
161        };
162    tracing::debug!("Proving union: left assumption = {:#?}", left_assumption);
163    tracing::debug!("Proving union: right assumption = {:#?}", right_assumption);
164
165    let opts = ProverOpts::succinct();
166    let mut prover = Prover::new_union(left_receipt, right_receipt, opts.clone())?;
167    let receipt = prover.prover.run()?;
168
169    let claim = UnionClaim {
170        left: left_assumption,
171        right: right_assumption,
172    };
173
174    // Include an inclusion proof for control_id to allow verification against a root.
175    let control_inclusion_proof = MerkleGroup::new(opts.control_ids.clone())?
176        .get_proof(&prover.control_id, opts.hash_suite()?.hashfn.as_ref())?;
177    Ok(SuccinctReceipt {
178        seal: receipt.seal,
179        hashfn: opts.hashfn,
180        control_id: prover.control_id,
181        control_inclusion_proof,
182        claim: MaybePruned::Value(claim),
183        verifier_parameters: SuccinctReceiptVerifierParameters::default().digest(),
184    })
185}
186
187/// Run the resolve program to remove an assumption from a conditional receipt upon verifying a
188/// receipt proving the validity of the assumption.
189///
190/// By applying the resolve program, a conditional receipt (i.e. a receipt for an execution using
191/// the `env::verify` API to logically verify a receipt) can be made into an unconditional receipt.
192pub fn resolve<Claim>(
193    conditional: &SuccinctReceipt<ReceiptClaim>,
194    assumption: &SuccinctReceipt<Claim>,
195) -> Result<SuccinctReceipt<ReceiptClaim>>
196where
197    Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
198{
199    tracing::debug!(
200        "Proving resolve: conditional.claim = {:#?}",
201        conditional.claim,
202    );
203    tracing::debug!(
204        "Proving resolve: assumption.claim = {:#?}",
205        assumption.claim,
206    );
207
208    // Construct the resolved claim by copying the conditional receipt claim and resolving
209    // the head assumption. If this fails, then so would the resolve program.
210    let mut resolved_claim = conditional
211        .claim
212        .as_value()
213        .context("conditional receipt claim is pruned")?
214        .clone();
215    // Open the assumptions on the output of the claim and remove the first assumption.
216    // NOTE: Prover::new_resolve will check that the assumption can actually be resolved with the
217    // given receipts.
218    resolved_claim
219        .output
220        .as_value_mut()
221        .context("conditional receipt output is pruned")?
222        .as_mut()
223        .ok_or_else(|| anyhow!("conditional receipt has empty output and no assumptions"))?
224        .assumptions
225        .as_value_mut()
226        .context("conditional receipt assumptions are pruned")?
227        .0
228        .drain(..1)
229        .next()
230        .ok_or_else(|| anyhow!("cannot resolve assumption from receipt with no assumptions"))?;
231
232    let opts = ProverOpts::succinct();
233    let mut prover = Prover::new_resolve(conditional, assumption, opts.clone())?;
234    let receipt = prover.prover.run()?;
235    let mut out_stream: VecDeque<u32> = VecDeque::new();
236    out_stream.extend(receipt.output.iter());
237
238    let claim_decoded = ReceiptClaim::decode(&mut out_stream)?;
239    tracing::debug!("Proving resolve finished: decoded claim = {claim_decoded:#?}");
240
241    // Include an inclusion proof for control_id to allow verification against a root.
242    let control_inclusion_proof = MerkleGroup::new(opts.control_ids.clone())?
243        .get_proof(&prover.control_id, opts.hash_suite()?.hashfn.as_ref())?;
244    Ok(SuccinctReceipt {
245        seal: receipt.seal,
246        hashfn: opts.hashfn,
247        control_id: prover.control_id,
248        control_inclusion_proof,
249        claim: claim_decoded.merge(&resolved_claim)?.into(),
250        verifier_parameters: SuccinctReceiptVerifierParameters::default().digest(),
251    })
252}
253
254/// Prove the verification of a recursion receipt using the Poseidon254 hash function for FRI.
255///
256/// The identity_p254 program is used as the last step in the prover pipeline before running the
257/// Groth16 prover. In Groth16 over BN254, it is much more efficient to verify a STARK that was
258/// produced with Poseidon over the BN254 base field compared to using Poseidon over BabyBear.
259pub fn identity_p254(a: &SuccinctReceipt<ReceiptClaim>) -> Result<SuccinctReceipt<ReceiptClaim>> {
260    let opts = ProverOpts::succinct()
261        .with_hashfn("poseidon_254".to_string())
262        .with_control_ids(vec![BN254_IDENTITY_CONTROL_ID]);
263
264    let mut prover = Prover::new_identity(a, opts.clone())?;
265    let receipt = prover.prover.run()?;
266    let mut out_stream: VecDeque<u32> = VecDeque::new();
267    out_stream.extend(receipt.output.iter());
268    let claim = MaybePruned::Value(ReceiptClaim::decode(&mut out_stream)?).merge(&a.claim)?;
269
270    // Include an inclusion proof for control_id to allow verification against a root.
271    let hashfn = opts.hash_suite()?.hashfn;
272    let control_inclusion_proof = MerkleGroup::new(opts.control_ids.clone())?
273        .get_proof(&prover.control_id, hashfn.as_ref())?;
274    let control_root = control_inclusion_proof.root(&prover.control_id, hashfn.as_ref());
275    let params = SuccinctReceiptVerifierParameters {
276        control_root,
277        inner_control_root: Some(a.control_root()?),
278        proof_system_info: PROOF_SYSTEM_INFO,
279        circuit_info: CircuitImpl::CIRCUIT_INFO,
280    };
281    Ok(SuccinctReceipt {
282        seal: receipt.seal,
283        hashfn: opts.hashfn,
284        control_id: prover.control_id,
285        control_inclusion_proof,
286        claim,
287        verifier_parameters: params.digest(),
288    })
289}
290
291/// Prove the specified program identified by the `control_id` using the specified `input`.
292pub fn prove_zkr(
293    program: Program,
294    control_id: &Digest,
295    allowed_control_ids: Vec<Digest>,
296    input: &[u8],
297) -> Result<SuccinctReceipt<Unknown>> {
298    let opts = ProverOpts::succinct().with_control_ids(allowed_control_ids);
299    let mut prover = Prover::new(program, *control_id, opts.clone());
300    prover.add_input(bytemuck::cast_slice(input));
301
302    tracing::debug!("Running prover");
303    let receipt = prover.run()?;
304
305    tracing::trace!("zkr receipt: {receipt:?}");
306
307    // Read the claim digest from the second of the global output slots.
308    let claim_digest: Digest = read_sha_halfs(&mut VecDeque::from_iter(
309        bytemuck::checked::cast_slice::<_, BabyBearElem>(
310            &receipt.seal[DIGEST_SHORTS..2 * DIGEST_SHORTS],
311        )
312        .iter()
313        .copied()
314        .map(u32::from),
315    ))?;
316
317    let hashfn = opts.hash_suite()?.hashfn;
318    let control_inclusion_proof =
319        MerkleGroup::new(opts.control_ids.clone())?.get_proof(control_id, hashfn.as_ref())?;
320
321    Ok(SuccinctReceipt {
322        seal: receipt.seal,
323        hashfn: opts.hashfn,
324        control_id: *control_id,
325        control_inclusion_proof,
326        claim: MaybePruned::<Unknown>::Pruned(claim_digest),
327        verifier_parameters: SuccinctReceiptVerifierParameters::default().digest(),
328    })
329}
330
331/// Prove the specified program identified by the `control_id` using the specified `input`.
332pub fn prove_registered_zkr(
333    control_id: &Digest,
334    allowed_control_ids: Vec<Digest>,
335    input: &[u8],
336) -> Result<SuccinctReceipt<Unknown>> {
337    let zkr = get_registered_zkr(control_id)?;
338    prove_zkr(zkr, control_id, allowed_control_ids, input)
339}
340
341/// Registers a function to retrieve a recursion program (zkr) based on a control id.
342pub fn register_zkr(
343    control_id: &Digest,
344    get_program_fn: impl Fn() -> Result<Program> + Send + 'static,
345) -> Option<ZkrRegistryEntry> {
346    let mut registry = ZKR_REGISTRY.lock().unwrap();
347    registry.insert(*control_id, Box::new(get_program_fn))
348}
349
350/// Returns a registered ZKR program, or an error if not found.
351pub fn get_registered_zkr(control_id: &Digest) -> Result<Program> {
352    let registry = ZKR_REGISTRY.lock().unwrap();
353    registry
354        .get(control_id)
355        .map(|f| f())
356        .unwrap_or_else(|| bail!("Control id {control_id} unregistered"))
357}
358
359/// Prove the test_recursion_circuit. This is useful for testing purposes.
360///
361/// digest1 will be passed through to the first of the output globals, as the "inner control root".
362/// digest1 and digest2 will be used to calculate a "claim digest", placed in the second output.
363#[cfg(test)]
364pub fn test_zkr(
365    digest1: &Digest,
366    digest2: &Digest,
367    po2: usize,
368) -> Result<SuccinctReceipt<crate::receipt_claim::Unknown>> {
369    use risc0_circuit_recursion::prove::zkr::get_zkr;
370    use risc0_zkp::core::hash::poseidon2::Poseidon2HashSuite;
371
372    let program = get_zkr("test_recursion_circuit.zkr", po2)?;
373    let suite = Poseidon2HashSuite::new_suite();
374    let control_id = program.compute_control_id(suite.clone());
375    let opts = ProverOpts::succinct().with_control_ids(vec![control_id]);
376
377    let mut prover = Prover::new(program, control_id, opts.clone());
378    prover.add_input_digest(digest1, DigestKind::Poseidon2);
379    prover.add_input_digest(digest2, DigestKind::Poseidon2);
380
381    let receipt = prover.run()?;
382
383    // Read the claim digest from the second of the global output slots.
384    let claim_digest = read_sha_halfs(&mut VecDeque::from_iter(
385        bytemuck::checked::cast_slice::<_, BabyBearElem>(
386            &receipt.seal[DIGEST_SHORTS..2 * DIGEST_SHORTS],
387        )
388        .iter()
389        .copied()
390        .map(u32::from),
391    ))?;
392
393    // Include an inclusion proof for control_id to allow verification against a root.
394    let hashfn = opts.hash_suite()?.hashfn;
395    let control_inclusion_proof = MerkleGroup::new(opts.control_ids.clone())?
396        .get_proof(&prover.control_id, hashfn.as_ref())?;
397    let control_root = control_inclusion_proof.root(&prover.control_id, hashfn.as_ref());
398    let params = SuccinctReceiptVerifierParameters {
399        control_root,
400        inner_control_root: Some(digest1.to_owned()),
401        proof_system_info: PROOF_SYSTEM_INFO,
402        circuit_info: CircuitImpl::CIRCUIT_INFO,
403    };
404    Ok(SuccinctReceipt {
405        seal: receipt.seal,
406        hashfn: suite.name,
407        control_id: prover.control_id,
408        control_inclusion_proof,
409        claim: MaybePruned::Pruned(claim_digest),
410        verifier_parameters: params.digest(),
411    })
412}
413
414/// Prover for zkVM use of the recursion circuit.
415pub struct Prover {
416    prover: risc0_circuit_recursion::prove::Prover,
417    control_id: Digest,
418}
419
420impl Prover {
421    pub(crate) fn new(program: Program, control_id: Digest, opts: ProverOpts) -> Self {
422        Self {
423            prover: risc0_circuit_recursion::prove::Prover::new(program, &opts.hashfn),
424            control_id,
425        }
426    }
427
428    /// Returns the control id of the recursion VM program being proven.
429    pub fn control_id(&self) -> &Digest {
430        &self.control_id
431    }
432
433    /// Initialize a recursion prover with the test recursion program. This program is used in
434    /// testing the basic correctness of the recursion circuit.
435    pub fn new_test_recursion_circuit(digests: [&Digest; 2], opts: ProverOpts) -> Result<Self> {
436        let (program, control_id) = zkr::test_recursion_circuit(&opts.hashfn)?;
437        let mut prover = Prover::new(program, control_id, opts);
438
439        for digest in digests {
440            prover.add_input_digest(digest, DigestKind::Poseidon2);
441        }
442
443        Ok(prover)
444    }
445
446    /// Initialize a recursion prover with the lift program to transform an rv32im segment receipt
447    /// into a recursion receipt.
448    ///
449    /// The lift program is verifies the rv32im circuit STARK proof inside the recursion circuit,
450    /// resulting in a recursion circuit STARK proof. This recursion proof has a single
451    /// constant-time verification procedure, with respect to the original segment length, and is
452    /// then used as the input to all other recursion programs (e.g. join, resolve, and
453    /// identity_p254).
454    pub fn new_lift(segment: &SegmentReceipt, opts: ProverOpts) -> Result<Self> {
455        ensure!(
456            segment.hashfn == "poseidon2",
457            "lift recursion program only supports poseidon2 hashfn; received {}",
458            segment.hashfn
459        );
460
461        let inner_hash_suite = hash_suite_from_name(&segment.hashfn)
462            .ok_or_else(|| anyhow!("unsupported hash function: {}", segment.hashfn))?;
463        let allowed_ids = MerkleGroup::new(opts.control_ids.clone())?;
464        let merkle_root = allowed_ids.calc_root(inner_hash_suite.hashfn.as_ref());
465
466        let out_size = risc0_circuit_rv32im::CircuitImpl::OUTPUT_SIZE;
467
468        ensure!(
469            segment.seal[0] == RV32IM_SEAL_VERSION,
470            "seal version mismatch"
471        );
472
473        let seal = &segment.seal[1..];
474
475        // Read the output fields in the rv32im seal to get the po2. We need this po2 to chose
476        // which lift program we are going to run.
477        let mut iop = ReadIOP::new(seal, inner_hash_suite.rng.as_ref());
478        iop.read_field_elem_slice::<BabyBearElem>(out_size);
479        let po2 = *iop.read_u32s(1).first().unwrap() as usize;
480
481        // Instantiate the prover with the lift recursion program and its control ID.
482        let (program, control_id) = zkr::lift(po2, &opts.hashfn)?;
483        let mut prover = Prover::new(program, control_id, opts);
484
485        prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
486        prover.add_input(seal);
487
488        Ok(prover)
489    }
490
491    /// Initialize a recursion prover with the union program to compress two
492    /// succinct receipts into one.
493    ///
494    /// By repeated application of the union program, any number of succinct
495    /// receipts can be compressed into a single receipt.
496    pub fn new_union(
497        a: &SuccinctReceipt<Unknown>,
498        b: &SuccinctReceipt<Unknown>,
499        opts: ProverOpts,
500    ) -> Result<Self> {
501        ensure!(
502            a.hashfn == "poseidon2",
503            "union recursion program only supports poseidon2 hashfn; received {}",
504            a.hashfn
505        );
506        ensure!(
507            b.hashfn == "poseidon2",
508            "union recursion program only supports poseidon2 hashfn; received {}",
509            b.hashfn
510        );
511        let hash_suite = Poseidon2HashSuite::new_suite();
512        let allowed_ids = MerkleGroup::new(opts.control_ids.clone())?;
513        let merkle_root = allowed_ids.calc_root(hash_suite.hashfn.as_ref());
514
515        let (program, control_id) = zkr::union(&opts.hashfn)?;
516        let mut prover = Prover::new(program, control_id, opts);
517
518        prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
519        prover.add_succinct_receipt(a)?;
520        prover.add_succinct_receipt(b)?;
521        Ok(prover)
522    }
523
524    /// Initialize a recursion prover with the join program to compress two receipts of the same
525    /// session into one.
526    ///
527    /// By repeated application of the join program, any number of receipts for execution spans
528    /// within the same session can be compressed into a single receipt for the entire session.
529    pub fn new_join(
530        a: &SuccinctReceipt<ReceiptClaim>,
531        b: &SuccinctReceipt<ReceiptClaim>,
532        opts: ProverOpts,
533    ) -> Result<Self> {
534        ensure!(
535            a.hashfn == "poseidon2",
536            "join recursion program only supports poseidon2 hashfn; received {}",
537            a.hashfn
538        );
539        ensure!(
540            b.hashfn == "poseidon2",
541            "join recursion program only supports poseidon2 hashfn; received {}",
542            b.hashfn
543        );
544
545        let (program, control_id) = zkr::join(&opts.hashfn)?;
546        let mut prover = Prover::new(program, control_id, opts);
547
548        // Determine the control root from the receipts themselves, and ensure they are equal. If
549        // the determined control root does not match what the downstream verifier expects, they
550        // will reject.
551        let merkle_root = a.control_root()?;
552        ensure!(
553            merkle_root == b.control_root()?,
554            "merkle roots for a and b do not match: {} != {}",
555            merkle_root,
556            b.control_root()?
557        );
558
559        prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
560        prover.add_segment_receipt(a)?;
561        prover.add_segment_receipt(b)?;
562        Ok(prover)
563    }
564
565    /// Initialize a recursion prover with the resolve program to remove an assumption from a
566    /// conditional receipt upon verifying a receipt proving the validity of the assumption.
567    ///
568    /// By applying the resolve program, a conditional receipt (i.e. a receipt for an execution
569    /// using the `env::verify` API to logically verify a receipt) can be made into an
570    /// unconditional receipt.
571    pub fn new_resolve<Claim>(
572        cond: &SuccinctReceipt<ReceiptClaim>,
573        assum: &SuccinctReceipt<Claim>,
574        opts: ProverOpts,
575    ) -> Result<Self>
576    where
577        Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
578    {
579        ensure!(
580            cond.hashfn == "poseidon2",
581            "resolve recursion program only supports poseidon2 hashfn; received {}",
582            cond.hashfn
583        );
584        ensure!(
585            assum.hashfn == "poseidon2",
586            "resolve recursion program only supports poseidon2 hashfn; received {}",
587            assum.hashfn
588        );
589
590        // Load the resolve predicate as a Program and construct the prover.
591        let (program, control_id) = zkr::resolve(&opts.hashfn)?;
592        let mut prover = Prover::new(program, control_id, opts);
593
594        // Load the input values needed by the predicate.
595        // Resolve predicate needs both seals as input, and the journal and assumptions tail digest
596        // to compute the opening of the conditional receipt claim to the first assumption.
597        prover.add_input_digest(&cond.control_root()?, DigestKind::Poseidon2);
598        prover.add_segment_receipt(cond)?;
599
600        let output = cond
601            .claim
602            .as_value()
603            .context("cannot resolve conditional receipt with pruned claim")?
604            .output
605            .as_value()
606            .context("cannot resolve conditional receipt with pruned output")?
607            .as_ref()
608            .ok_or_else(|| anyhow!("cannot resolve conditional receipt with no output"))?
609            .clone();
610
611        // Unwrap the MaybePruned assumptions list and resolve the corroborated assumption,
612        // removing the head and leaving the tail of the list.
613        let assumptions = output
614            .assumptions
615            .value()
616            .context("cannot resolve conditional receipt with pruned assumptions")?;
617        let head: Assumption = assumptions
618            .0
619            .first()
620            .ok_or_else(|| anyhow!("cannot resolve conditional receipt with no assumptions"))?
621            .as_value()
622            .context("cannot resolve conditional receipt with pruned head assumption")?
623            .clone();
624
625        // Ensure that the assumption receipt can resolve the assumption.
626        ensure!(
627            head.claim == assum.claim.digest(),
628            "assumption receipt claim does not match head of assumptions list"
629        );
630        let expected_root = match head.control_root == Digest::ZERO {
631            true => cond.control_root()?,
632            false => head.control_root,
633        };
634        ensure!(
635            expected_root == assum.control_root()?,
636            "assumption receipt control root does not match head of assumptions list"
637        );
638
639        let mut assumptions_tail = assumptions;
640        assumptions_tail.resolve(&head.digest())?;
641
642        prover.add_assumption_receipt(head, assum)?;
643        prover.add_input_digest(&assumptions_tail.digest(), DigestKind::Sha256);
644        prover.add_input_digest(&output.journal.digest(), DigestKind::Sha256);
645        Ok(prover)
646    }
647
648    /// Prove the verification of a recursion receipt, applying no changes to [ReceiptClaim].
649    ///
650    /// The primary use for this program is to transform the receipt itself, e.g. using a different
651    /// hash function for FRI. See [identity_p254] for more information.
652    pub fn new_identity(a: &SuccinctReceipt<ReceiptClaim>, opts: ProverOpts) -> Result<Self> {
653        ensure!(
654            a.hashfn == "poseidon2",
655            "identity recursion program only supports poseidon2 hashfn; received {}",
656            a.hashfn
657        );
658
659        let (program, control_id) = zkr::identity(&opts.hashfn)?;
660        let mut prover = Prover::new(program, control_id, opts);
661
662        prover.add_input_digest(&a.control_root()?, DigestKind::Poseidon2);
663        prover.add_segment_receipt(a)?;
664        Ok(prover)
665    }
666
667    pub(crate) fn add_input(&mut self, input: &[u32]) {
668        self.prover.add_input(input)
669    }
670
671    /// Add a digest to the input for the recursion program.
672    fn add_input_digest(&mut self, digest: &Digest, kind: DigestKind) {
673        self.prover.add_input_digest(digest, kind)
674    }
675
676    /// Add a recursion seal (i.e. STARK proof) to input tape of the recursion program.
677    pub fn add_seal(
678        &mut self,
679        seal: &[u32],
680        control_id: &Digest,
681        control_inclusion_proof: &MerkleProof,
682    ) -> Result<()> {
683        tracing::debug!("Control ID = {:?}", control_id);
684        self.add_input(seal);
685        tracing::debug!("index = {:?}", control_inclusion_proof.index);
686        self.add_input(bytemuck::cast_slice(&[BabyBearElem::new(
687            control_inclusion_proof.index,
688        )]));
689        for digest in &control_inclusion_proof.digests {
690            tracing::debug!("path = {:?}", digest);
691            self.add_input_digest(digest, DigestKind::Poseidon2);
692        }
693        Ok(())
694    }
695
696    /// Add a receipt covering some generic claim. Do not include any claim information.
697    fn add_assumption_receipt<Claim>(
698        &mut self,
699        assumption: Assumption,
700        receipt: &SuccinctReceipt<Claim>,
701    ) -> Result<()>
702    where
703        Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
704    {
705        self.add_seal(
706            &receipt.seal,
707            &receipt.control_id,
708            &receipt.control_inclusion_proof,
709        )?;
710        // Resolve program expects an additional boolean to tell it when the control root is zero.
711        let zero_root = BabyBearElem::new((assumption.control_root == Digest::ZERO) as u32);
712        self.add_input(bytemuck::cast_slice(&[zero_root]));
713        Ok(())
714    }
715
716    /// Add a receipt covering rv32im execution, and include the first level of ReceiptClaim.
717    fn add_segment_receipt(&mut self, a: &SuccinctReceipt<ReceiptClaim>) -> Result<()> {
718        self.add_seal(&a.seal, &a.control_id, &a.control_inclusion_proof)?;
719        let mut data = Vec::<u32>::new();
720        a.claim.as_value()?.encode(&mut data)?;
721        let data_fp: Vec<BabyBearElem> = data.iter().map(|x| BabyBearElem::new(*x)).collect();
722        self.add_input(bytemuck::cast_slice(&data_fp));
723        Ok(())
724    }
725
726    /// Add a receipt for a succinct receipt
727    fn add_succinct_receipt<Claim>(&mut self, a: &SuccinctReceipt<Claim>) -> Result<()>
728    where
729        Claim: risc0_binfmt::Digestible + Debug + Clone + Serialize,
730    {
731        self.add_seal(&a.seal, &a.control_id, &a.control_inclusion_proof)?;
732        // Union program expects an additional boolean to indicate that control root is zero.
733        let zero_root = BabyBearElem::new((a.control_root()? == Digest::ZERO) as u32);
734        self.add_input(bytemuck::cast_slice(&[zero_root]));
735        Ok(())
736    }
737
738    /// Run the prover, producing a receipt of execution for the recursion circuit over the loaded
739    /// program and input.
740    pub fn run(&mut self) -> Result<RecursionReceipt> {
741        self.prover.run()
742    }
743
744    /// Run the prover, producing a receipt of execution for the recursion circuit over the loaded
745    /// program and input, using the specified HAL.
746    pub fn run_with_hal<H, C>(&mut self, hal: &H, circuit_hal: &C) -> Result<RecursionReceipt>
747    where
748        H: Hal<Field = BabyBear, Elem = BabyBearElem, ExtElem = BabyBearExtElem>,
749        C: CircuitHal<H>,
750    {
751        self.prover.run_with_hal(hal, circuit_hal)
752    }
753}