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 default;
18pub(crate) mod external;
19#[cfg(feature = "prove")]
20pub(crate) mod local;
21pub(crate) mod opts;
22
23use core::ops::Deref;
24use std::{path::PathBuf, rc::Rc};
25
26use anyhow::{anyhow, Result};
27
28#[cfg(feature = "bonsai")]
29use self::bonsai::BonsaiProver;
30
31use self::{default::DefaultProver, external::ExternalProver, opts::ProverOpts};
32
33use crate::{
34 get_version, host::prove_info::ProveInfo, ExecutorEnv, Receipt, SessionInfo, VerifierContext,
35};
36
37/// A Prover can execute a given ELF binary and produce a
38/// [Receipt] that can be used to verify correct computation.
39///
40/// # Usage
41/// To produce a proof, you must minimally provide an [ExecutorEnv] and an ELF
42/// binary. See the [risc0_build](https://docs.rs/risc0-build/*/risc0_build)
43/// crate for more information on producing ELF binaries from Rust source code.
44///
45/// ```rust
46/// use risc0_zkvm::{
47/// default_prover,
48/// ExecutorEnv,
49/// ProverOpts,
50/// VerifierContext,
51/// };
52/// use risc0_zkvm_methods::FIB_ELF;
53///
54/// # #[cfg(not(feature = "cuda"))]
55/// # {
56/// // A straightforward case with an ELF binary
57/// let env = ExecutorEnv::builder().write(&20u32).unwrap().build().unwrap();
58/// let receipt = default_prover().prove(env, FIB_ELF).unwrap();
59///
60/// // Or you can specify a context and options
61/// // Here we are using ProverOpts::succinct() to get a constant size proof through recursion.
62/// let env = ExecutorEnv::builder().write(&20u32).unwrap().build().unwrap();
63/// let opts = ProverOpts::succinct();
64/// let receipt = default_prover().prove_with_opts(env, FIB_ELF, &opts).unwrap();
65/// # }
66/// ```
67pub trait Prover {
68 /// Return a name for this [Prover].
69 fn get_name(&self) -> String;
70
71 /// Prove zkVM execution of the specified ELF binary.
72 ///
73 /// Use this method unless you have need to configure the prover options or verifier context.
74 /// Default [VerifierContext] and [ProverOpts] will be used.
75 fn prove(&self, env: ExecutorEnv<'_>, elf: &[u8]) -> Result<ProveInfo> {
76 let opts = ProverOpts::default();
77 let ctx = VerifierContext::default();
78 self.prove_with_ctx(env, &ctx, elf, &opts)
79 }
80
81 /// Prove zkVM execution of the specified ELF binary and using the specified [ProverOpts].
82 ///
83 /// Use this method when you want to specify the receipt type you would like (e.g. groth16 or
84 /// succinct), or if you need to tweak other parameter in [ProverOpts].
85 ///
86 /// Default [VerifierContext] will be used.
87 fn prove_with_opts(
88 &self,
89 env: ExecutorEnv<'_>,
90 elf: &[u8],
91 opts: &ProverOpts,
92 ) -> Result<ProveInfo> {
93 let ctx = VerifierContext::default().with_dev_mode(opts.dev_mode());
94 self.prove_with_ctx(env, &ctx, elf, opts)
95 }
96
97 /// Prove zkVM execution of the specified ELF binary and using the specified [VerifierContext]
98 /// and [ProverOpts].
99 ///
100 /// Use this method if you are using non-standard verification parameters. The
101 /// [VerifierContext] specified here should match what you expect the verifier to use in your
102 /// application.
103 fn prove_with_ctx(
104 &self,
105 env: ExecutorEnv<'_>,
106 ctx: &VerifierContext,
107 elf: &[u8],
108 opts: &ProverOpts,
109 ) -> Result<ProveInfo>;
110
111 /// Compress a [Receipt], proving the same computation using a smaller representation.
112 ///
113 /// Proving will, by default, produce a [CompositeReceipt](crate::CompositeReceipt), which
114 /// may contain an arbitrary number of receipts assembled into segments and assumptions.
115 /// Together, these receipts collectively prove a top-level
116 /// [ReceiptClaim](crate::ReceiptClaim). This function can be used to compress all of the constituent
117 /// receipts of a [CompositeReceipt](crate::CompositeReceipt) into a single
118 /// [SuccinctReceipt](crate::SuccinctReceipt) or [Groth16Receipt](crate::Groth16Receipt) that proves the same top-level claim.
119 ///
120 /// Compression from [Groth16Receipt](crate::CompositeReceipt) to
121 /// [SuccinctReceipt](crate::SuccinctReceipt) is accomplished by iterative application of the
122 /// recursion programs including lift, join, and resolve.
123 ///
124 /// Compression from [SuccinctReceipt](crate::SuccinctReceipt) to
125 /// [Groth16Receipt](crate::Groth16Receipt) is accomplished by running a Groth16 recursive
126 /// verifier, referred to as the "STARK-to-SNARK" operation.
127 ///
128 /// NOTE: Compression to [Groth16Receipt](crate::Groth16Receipt) requires
129 /// Docker to be installed. See issue
130 /// [#1749](https://github.com/risc0/risc0/issues/1749) for more information.
131 ///
132 /// If the receipt is already at least as compressed as the requested compression level (e.g.
133 /// it is already succinct or Groth16 and a succinct receipt is required) this function is a
134 /// no-op. As a result, it is idempotent.
135 fn compress(&self, opts: &ProverOpts, receipt: &Receipt) -> Result<Receipt>;
136}
137
138// Implementation of Prover for `Rc<Prover>` which allows it to satisfy trait bounds for Prover.
139impl<P: Prover + ?Sized> Prover for Rc<P> {
140 fn get_name(&self) -> String {
141 self.deref().get_name()
142 }
143
144 fn prove_with_ctx(
145 &self,
146 env: ExecutorEnv<'_>,
147 ctx: &VerifierContext,
148 elf: &[u8],
149 opts: &ProverOpts,
150 ) -> Result<ProveInfo> {
151 self.deref().prove_with_ctx(env, ctx, elf, opts)
152 }
153
154 fn compress(&self, opts: &ProverOpts, receipt: &Receipt) -> Result<Receipt> {
155 self.deref().compress(opts, receipt)
156 }
157}
158
159/// An Executor can execute a given ELF binary.
160pub trait Executor {
161 /// Execute the specified ELF binary.
162 ///
163 /// This only executes the program and does not generate a receipt.
164 fn execute(&self, env: ExecutorEnv<'_>, elf: &[u8]) -> Result<SessionInfo>;
165}
166
167/// Return a default [Prover] based on environment variables and feature flags.
168///
169/// The `RISC0_PROVER` environment variable, if specified, will select the
170/// following [Prover] implementation:
171/// * `bonsai`: [BonsaiProver] to prove on Bonsai.
172/// * `local`: LocalProver to prove locally in-process. Note: this
173/// requires the `prove` feature flag.
174/// * `ipc`: [ExternalProver] to prove using an `r0vm` sub-process. Note: `r0vm`
175/// must be installed. To specify the path to `r0vm`, use `RISC0_SERVER_PATH`.
176///
177/// If `RISC0_PROVER` is not specified, the following rules are used to select a
178/// [Prover]:
179/// * [BonsaiProver] if the `BONSAI_API_URL` and `BONSAI_API_KEY` environment
180/// variables are set unless `RISC0_DEV_MODE` is enabled.
181/// * LocalProver if the `prove` feature flag is enabled.
182/// * [ExternalProver] otherwise.
183pub fn default_prover() -> Rc<dyn Prover> {
184 let explicit = std::env::var("RISC0_PROVER").unwrap_or_default();
185 if !explicit.is_empty() {
186 return match explicit.to_lowercase().as_str() {
187 "actor" => Rc::new(DefaultProver::new(get_r0vm_path().unwrap()).unwrap()),
188 #[cfg(feature = "bonsai")]
189 "bonsai" => Rc::new(BonsaiProver::new("bonsai")),
190 "ipc" => Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap())),
191 #[cfg(feature = "prove")]
192 "local" => Rc::new(self::local::LocalProver::new("local")),
193 _ => unimplemented!("Unsupported prover: {explicit}"),
194 };
195 }
196
197 #[cfg(feature = "bonsai")]
198 {
199 if !crate::is_dev_mode_enabled_via_environment()
200 && std::env::var("BONSAI_API_URL").is_ok()
201 && std::env::var("BONSAI_API_KEY").is_ok()
202 {
203 return Rc::new(BonsaiProver::new("bonsai"));
204 }
205 }
206
207 if cfg!(feature = "prove") {
208 #[cfg(feature = "prove")]
209 return Rc::new(self::local::LocalProver::new("local"));
210 }
211
212 Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap()))
213}
214
215/// Return a default [Executor] based on environment variables and feature
216/// flags.
217///
218/// The `RISC0_EXECUTOR` environment variable, if specified, will select the
219/// following [Executor] implementation:
220/// * `local`: LocalProver to execute locally in-process. Note: this is
221/// only available when the `prove` feature is enabled.
222/// * `ipc`: [ExternalProver] to execute using an `r0vm` sub-process. Note:
223/// `r0vm` must be installed. To specify the path to `r0vm`, use
224/// `RISC0_SERVER_PATH`.
225///
226/// If `RISC0_EXECUTOR` is not specified, the following rules are used to select
227/// an [Executor]:
228/// * LocalProver if the `prove` feature flag is enabled.
229/// * [ExternalProver] otherwise.
230pub fn default_executor() -> Rc<dyn Executor> {
231 let explicit = std::env::var("RISC0_EXECUTOR").unwrap_or_default();
232 if !explicit.is_empty() {
233 return match explicit.to_lowercase().as_str() {
234 "ipc" => Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap())),
235 #[cfg(feature = "prove")]
236 "local" => Rc::new(self::local::LocalProver::new("local")),
237 _ => unimplemented!("Unsupported executor: {explicit}"),
238 };
239 }
240
241 if cfg!(feature = "prove") {
242 #[cfg(feature = "prove")]
243 return Rc::new(self::local::LocalProver::new("local"));
244 }
245
246 Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap()))
247}
248
249/// Return a local [Executor].
250#[cfg(feature = "prove")]
251pub fn local_executor() -> Rc<dyn Executor> {
252 Rc::new(self::local::LocalProver::new("local"))
253}
254
255pub(crate) fn get_r0vm_path() -> Result<PathBuf> {
256 if let Ok(path) = std::env::var("RISC0_SERVER_PATH") {
257 let path = PathBuf::from(path);
258 if path.is_file() {
259 return Ok(path);
260 }
261 }
262
263 let mut version = get_version().map_err(|err| anyhow!(err))?;
264 tracing::debug!("version: {version}");
265
266 if let Ok(rzup) = rzup::Rzup::new() {
267 if let Ok(dir) = rzup.get_version_dir(&rzup::Component::R0Vm, &version) {
268 return Ok(dir.join("r0vm"));
269 }
270
271 // Try again, but with these fields stripped
272 version.patch = 0;
273 version.build = Default::default();
274
275 if let Ok(dir) = rzup.get_version_dir(&rzup::Component::R0Vm, &version) {
276 return Ok(dir.join("r0vm"));
277 }
278 }
279
280 Ok("r0vm".into())
281}