risc0_zkvm/host/client/prove/
mod.rs

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