risc0_zkvm/host/client/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
15#[cfg(feature = "bonsai")]
16pub(crate) mod bonsai;
17pub(crate) mod external;
18#[cfg(feature = "prove")]
19pub(crate) mod local;
20
21use std::{path::PathBuf, rc::Rc};
22
23use anyhow::{anyhow, Result};
24use risc0_circuit_recursion::control_id::ALLOWED_CONTROL_IDS;
25use risc0_zkp::core::digest::Digest;
26use serde::{Deserialize, Serialize};
27
28#[cfg(feature = "bonsai")]
29use {self::bonsai::BonsaiProver, crate::is_dev_mode};
30
31use self::external::ExternalProver;
32
33use crate::{
34    get_version, host::prove_info::ProveInfo, receipt::DEFAULT_MAX_PO2, ExecutorEnv, Receipt,
35    SessionInfo, VerifierContext,
36};
37
38/// A Prover can execute a given ELF binary and produce a
39/// [Receipt] that can be used to verify correct computation.
40///
41/// # Usage
42/// To produce a proof, you must minimally provide an [ExecutorEnv] and an ELF
43/// binary. See the [risc0_build](https://docs.rs/risc0-build/*/risc0_build)
44/// crate for more information on producing ELF binaries from Rust source code.
45///
46/// ```rust
47/// use risc0_zkvm::{
48///     default_prover,
49///     ExecutorEnv,
50///     ProverOpts,
51///     VerifierContext,
52/// };
53/// use risc0_zkvm_methods::FIB_ELF;
54///
55/// # #[cfg(not(feature = "cuda"))]
56/// # {
57/// // A straightforward case with an ELF binary
58/// let env = ExecutorEnv::builder().write(&20u32).unwrap().build().unwrap();
59/// let receipt = default_prover().prove(env, FIB_ELF).unwrap();
60///
61/// // Or you can specify a context and options
62/// // Here we are using ProverOpts::succinct() to get a constant size proof through recursion.
63/// let env = ExecutorEnv::builder().write(&20u32).unwrap().build().unwrap();
64/// let opts = ProverOpts::succinct();
65/// let receipt = default_prover().prove_with_opts(env, FIB_ELF, &opts).unwrap();
66/// # }
67/// ```
68pub trait Prover {
69    /// Return a name for this [Prover].
70    fn get_name(&self) -> String;
71
72    /// Prove zkVM execution of the specified ELF binary.
73    ///
74    /// Use this method unless you have need to configure the prover options or verifier context.
75    /// Default [VerifierContext] and [ProverOpts] will be used.
76    fn prove(&self, env: ExecutorEnv<'_>, elf: &[u8]) -> Result<ProveInfo> {
77        let opts = ProverOpts::default();
78        let ctx = VerifierContext::default();
79        self.prove_with_ctx(env, &ctx, elf, &opts)
80    }
81
82    /// Prove zkVM execution of the specified ELF binary and using the specified [ProverOpts].
83    ///
84    /// Use this method when you want to specify the receipt type you would like (e.g. groth16 or
85    /// succinct), or if you need to tweak other parameter in [ProverOpts].
86    ///
87    /// Default [VerifierContext] will be used.
88    fn prove_with_opts(
89        &self,
90        env: ExecutorEnv<'_>,
91        elf: &[u8],
92        opts: &ProverOpts,
93    ) -> Result<ProveInfo> {
94        let ctx = VerifierContext::default();
95        self.prove_with_ctx(env, &ctx, elf, opts)
96    }
97
98    /// Prove zkVM execution of the specified ELF binary and using the specified [VerifierContext]
99    /// and [ProverOpts].
100    ///
101    /// Use this method if you are using non-standard verification parameters. The
102    /// [VerifierContext] specified here should match what you expect the verifier to use in your
103    /// application.
104    fn prove_with_ctx(
105        &self,
106        env: ExecutorEnv<'_>,
107        ctx: &VerifierContext,
108        elf: &[u8],
109        opts: &ProverOpts,
110    ) -> Result<ProveInfo>;
111
112    /// Compress a [Receipt], proving the same computation using a smaller representation.
113    ///
114    /// Proving will, by default, produce a [CompositeReceipt](crate::CompositeReceipt), which
115    /// may contain an arbitrary number of receipts assembled into segments and assumptions.
116    /// Together, these receipts collectively prove a top-level
117    /// [ReceiptClaim](crate::ReceiptClaim). This function can be used to compress all of the constituent
118    /// receipts of a [CompositeReceipt](crate::CompositeReceipt) into a single
119    /// [SuccinctReceipt](crate::SuccinctReceipt) or [Groth16Receipt](crate::Groth16Receipt) that proves the same top-level claim.
120    ///
121    /// Compression from [Groth16Receipt](crate::CompositeReceipt) to
122    /// [SuccinctReceipt](crate::SuccinctReceipt) is accomplished by iterative application of the
123    /// recursion programs including lift, join, and resolve.
124    ///
125    /// Compression from [SuccinctReceipt](crate::SuccinctReceipt) to
126    /// [Groth16Receipt](crate::Groth16Receipt) is accomplished by running a Groth16 recursive
127    /// verifier, referred to as the "STARK-to-SNARK" operation.
128    ///
129    /// NOTE: Compression to [Groth16Receipt](crate::Groth16Receipt) is currently only supported on
130    /// x86 hosts, and requires Docker to be installed. See issue
131    /// [#1749](https://github.com/risc0/risc0/issues/1749) for more information.
132    ///
133    /// If the receipt is already at least as compressed as the requested compression level (e.g.
134    /// it is already succinct or Groth16 and a succinct receipt is required) this function is a
135    /// no-op. As a result, it is idempotent.
136    fn compress(&self, opts: &ProverOpts, receipt: &Receipt) -> Result<Receipt>;
137}
138
139/// An Executor can execute a given ELF binary.
140pub trait Executor {
141    /// Execute the specified ELF binary.
142    ///
143    /// This only executes the program and does not generate a receipt.
144    fn execute(&self, env: ExecutorEnv<'_>, elf: &[u8]) -> Result<SessionInfo>;
145}
146
147/// Options to configure a [Prover].
148#[derive(Clone, Debug, Serialize, Deserialize)]
149#[non_exhaustive]
150pub struct ProverOpts {
151    /// Identifier of the hash function to use for the STARK proving protocol.
152    pub hashfn: String,
153
154    /// When false, only prove execution sessions that end in a successful
155    /// [crate::ExitCode] (i.e. `Halted(0)` or `Paused(0)`).
156    /// When set to true, any completed execution session will be proven, including indicated
157    /// errors (e.g. `Halted(1)`) and sessions ending in `Fault`.
158    pub prove_guest_errors: bool,
159
160    /// Kind of receipt to be generated by the prover.
161    pub receipt_kind: ReceiptKind,
162
163    /// List of control IDs to enable for recursion proving.
164    ///
165    /// This list is used to construct the control root, which commits to the set of recursion
166    /// programs that are allowed to run and is a key field in the
167    /// [SuccinctReceiptVerifierParameters][crate::SuccinctReceiptVerifierParameters].
168    pub control_ids: Vec<Digest>,
169
170    /// Maximum cycle count, as a power of two (po2) that these prover options support.
171    pub(crate) max_segment_po2: usize,
172}
173
174/// An enumeration of receipt kinds that can be requested to be generated.
175#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
176#[non_exhaustive]
177pub enum ReceiptKind {
178    /// Request that a [CompositeReceipt][crate::CompositeReceipt] be generated.
179    ///
180    /// Composite receipts are made up of a receipt for every segment in a zkVM execution, and
181    /// every assumption. They are linear in size with respect to the execution length.
182    Composite,
183
184    /// Request that a [SuccinctReceipt][crate::SuccinctReceipt] be generated.
185    ///
186    /// Succinct receipts are constant in size, with respect to the execution length.
187    ///
188    Succinct,
189
190    /// Request that a [Groth16Receipt][crate::Groth16Receipt] be generated.
191    ///
192    /// Groth16 receipts are proven using Groth16, are constant in size, and are the smallest
193    /// available receipt format. A Groth16 receipt can be serialized to a few hundred bytes.
194    Groth16,
195}
196
197impl Default for ProverOpts {
198    /// Return [ProverOpts] that are intended to work for most applications.
199    ///
200    /// Proof generated with these options may be linear in size with the execution length, but
201    /// can be compressed using the [Prover::compress] methods.
202    fn default() -> Self {
203        Self {
204            hashfn: "poseidon2".to_string(),
205            prove_guest_errors: false,
206            receipt_kind: ReceiptKind::Composite,
207            control_ids: ALLOWED_CONTROL_IDS.to_vec(),
208            max_segment_po2: DEFAULT_MAX_PO2,
209        }
210    }
211}
212
213impl ProverOpts {
214    /// Construct a [ProverOpts] ready to prove segments with up to the given max cycle count as a
215    /// power of two (po2). All fields are equal to the default expect where they need to be
216    /// adjusted to support a larger po2.
217    ///
218    /// NOTE: If the po2 used to prove is greater than the targeted verifier supports,
219    /// [DEFAULT_MAX_PO2] by default, receipts will be rejected by the verifier.
220    #[stability::unstable]
221    pub fn from_max_po2(po2_max: usize) -> Self {
222        Self {
223            hashfn: "poseidon2".to_string(),
224            prove_guest_errors: false,
225            receipt_kind: ReceiptKind::Composite,
226            control_ids: crate::receipt::succinct::allowed_control_ids("poseidon2", po2_max)
227                .unwrap()
228                .collect(),
229            max_segment_po2: po2_max,
230        }
231    }
232
233    /// Construct a verifier context that will accept receipts with control any of the default
234    /// control ID associated with cycle counts of all supported powers of two (po2).
235    #[stability::unstable]
236    pub fn all_po2s() -> Self {
237        Self::from_max_po2(risc0_zkp::MAX_CYCLES_PO2)
238    }
239
240    /// Choose the fastest prover options. Receipt will be linear in length of the execution,
241    /// and does not support compression via recursion.
242    pub fn fast() -> Self {
243        Self {
244            hashfn: "poseidon2".to_string(),
245            prove_guest_errors: false,
246            receipt_kind: ReceiptKind::Composite,
247            control_ids: Vec::new(),
248            max_segment_po2: DEFAULT_MAX_PO2,
249        }
250    }
251
252    /// Choose the prover that generates composite receipts, linear in the length of the execution,
253    /// and supports compression via recursion.
254    pub fn composite() -> Self {
255        Self {
256            hashfn: "poseidon2".to_string(),
257            prove_guest_errors: false,
258            receipt_kind: ReceiptKind::Composite,
259            control_ids: ALLOWED_CONTROL_IDS.to_vec(),
260            max_segment_po2: DEFAULT_MAX_PO2,
261        }
262    }
263
264    /// Choose the prover that generates succinct receipts, which are constant size in the length
265    /// of execution.
266    pub fn succinct() -> Self {
267        Self {
268            hashfn: "poseidon2".to_string(),
269            prove_guest_errors: false,
270            receipt_kind: ReceiptKind::Succinct,
271            control_ids: ALLOWED_CONTROL_IDS.to_vec(),
272            max_segment_po2: DEFAULT_MAX_PO2,
273        }
274    }
275
276    /// Choose the prover that generates Groth16 receipts which are constant size in the length of
277    /// the execution and small enough to verify on blockchains, like Ethereum.
278    ///
279    /// Only supported for x86_64 Linux with Docker installed.
280    pub fn groth16() -> Self {
281        Self {
282            hashfn: "poseidon2".to_string(),
283            prove_guest_errors: false,
284            receipt_kind: ReceiptKind::Groth16,
285            control_ids: ALLOWED_CONTROL_IDS.to_vec(),
286            max_segment_po2: DEFAULT_MAX_PO2,
287        }
288    }
289
290    /// Return [ProverOpts] with the hashfn set to the given value.
291    pub fn with_hashfn(self, hashfn: String) -> Self {
292        Self {
293            hashfn: hashfn.to_owned(),
294            ..self
295        }
296    }
297
298    /// Return [ProverOpts] with prove_guest_errors set to the given value.
299    pub fn with_prove_guest_errors(self, prove_guest_errors: bool) -> Self {
300        Self {
301            prove_guest_errors,
302            ..self
303        }
304    }
305
306    /// Return [ProverOpts] with the receipt_kind set to the given value.
307    pub fn with_receipt_kind(self, receipt_kind: ReceiptKind) -> Self {
308        Self {
309            receipt_kind,
310            ..self
311        }
312    }
313
314    /// Return [ProverOpts] with the control_ids set to the given value.
315    pub fn with_control_ids(self, control_ids: Vec<Digest>) -> Self {
316        Self {
317            control_ids,
318            ..self
319        }
320    }
321
322    /// Return [ProverOpts] with the max_segment_po2 set to the given value.
323    #[stability::unstable]
324    pub fn with_segment_po2_max(self, max_segment_po2: usize) -> Self {
325        Self {
326            max_segment_po2,
327            ..self
328        }
329    }
330
331    #[cfg(feature = "prove")]
332    pub(crate) fn hash_suite(
333        &self,
334    ) -> Result<risc0_zkp::core::hash::HashSuite<risc0_zkp::field::baby_bear::BabyBear>> {
335        risc0_zkp::core::hash::hash_suite_from_name(&self.hashfn)
336            .ok_or_else(|| anyhow::anyhow!("unsupported hash suite: {}", self.hashfn))
337    }
338}
339
340/// Return a default [Prover] based on environment variables and feature flags.
341///
342/// The `RISC0_PROVER` environment variable, if specified, will select the
343/// following [Prover] implementation:
344/// * `bonsai`: [BonsaiProver] to prove on Bonsai.
345/// * `local`: LocalProver to prove locally in-process. Note: this
346///   requires the `prove` feature flag.
347/// * `ipc`: [ExternalProver] to prove using an `r0vm` sub-process. Note: `r0vm`
348///   must be installed. To specify the path to `r0vm`, use `RISC0_SERVER_PATH`.
349///
350/// If `RISC0_PROVER` is not specified, the following rules are used to select a
351/// [Prover]:
352/// * [BonsaiProver] if the `BONSAI_API_URL` and `BONSAI_API_KEY` environment
353///   variables are set unless `RISC0_DEV_MODE` is enabled.
354/// * LocalProver if the `prove` feature flag is enabled.
355/// * [ExternalProver] otherwise.
356pub fn default_prover() -> Rc<dyn Prover> {
357    let explicit = std::env::var("RISC0_PROVER").unwrap_or_default();
358    if !explicit.is_empty() {
359        return match explicit.to_lowercase().as_str() {
360            #[cfg(feature = "bonsai")]
361            "bonsai" => Rc::new(BonsaiProver::new("bonsai")),
362            "ipc" => Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap())),
363            #[cfg(feature = "prove")]
364            "local" => Rc::new(self::local::LocalProver::new("local")),
365            _ => unimplemented!("Unsupported prover: {explicit}"),
366        };
367    }
368
369    #[cfg(feature = "bonsai")]
370    {
371        if !is_dev_mode()
372            && std::env::var("BONSAI_API_URL").is_ok()
373            && std::env::var("BONSAI_API_KEY").is_ok()
374        {
375            return Rc::new(BonsaiProver::new("bonsai"));
376        }
377    }
378
379    if cfg!(feature = "prove") {
380        #[cfg(feature = "prove")]
381        return Rc::new(self::local::LocalProver::new("local"));
382    }
383
384    Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap()))
385}
386
387/// Return a default [Executor] based on environment variables and feature
388/// flags.
389///
390/// The `RISC0_EXECUTOR` environment variable, if specified, will select the
391/// following [Executor] implementation:
392/// * `local`: LocalProver to execute locally in-process. Note: this is
393///   only available when the `prove` feature is enabled.
394/// * `ipc`: [ExternalProver] to execute using an `r0vm` sub-process. Note:
395///   `r0vm` must be installed. To specify the path to `r0vm`, use
396///   `RISC0_SERVER_PATH`.
397///
398/// If `RISC0_EXECUTOR` is not specified, the following rules are used to select
399/// an [Executor]:
400/// * LocalProver if the `prove` feature flag is enabled.
401/// * [ExternalProver] otherwise.
402pub fn default_executor() -> Rc<dyn Executor> {
403    let explicit = std::env::var("RISC0_EXECUTOR").unwrap_or_default();
404    if !explicit.is_empty() {
405        return match explicit.to_lowercase().as_str() {
406            "ipc" => Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap())),
407            #[cfg(feature = "prove")]
408            "local" => Rc::new(self::local::LocalProver::new("local")),
409            _ => unimplemented!("Unsupported executor: {explicit}"),
410        };
411    }
412
413    if cfg!(feature = "prove") {
414        #[cfg(feature = "prove")]
415        return Rc::new(self::local::LocalProver::new("local"));
416    }
417
418    Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap()))
419}
420
421/// Return a local [Executor].
422#[cfg(feature = "prove")]
423pub fn local_executor() -> Rc<dyn Executor> {
424    Rc::new(self::local::LocalProver::new("local"))
425}
426
427pub(crate) fn get_r0vm_path() -> Result<PathBuf> {
428    if let Ok(path) = std::env::var("RISC0_SERVER_PATH") {
429        let path = PathBuf::from(path);
430        if path.is_file() {
431            return Ok(path);
432        }
433    }
434
435    let mut version = get_version().map_err(|err| anyhow!(err))?;
436    tracing::debug!("version: {version}");
437
438    if let Ok(rzup) = rzup::Rzup::new() {
439        if let Ok(dir) = rzup.get_version_dir(&rzup::Component::R0Vm, &version) {
440            return Ok(dir.join("r0vm"));
441        }
442
443        // Try again, but with these fields stripped
444        version.patch = 0;
445        version.build = Default::default();
446
447        if let Ok(dir) = rzup.get_version_dir(&rzup::Component::R0Vm, &version) {
448            return Ok(dir.join("r0vm"));
449        }
450    }
451
452    Ok("r0vm".into())
453}