risc0_zkvm/host/client/
env.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//! This module defines the [ExecutorEnv] and [ExecutorEnvBuilder].
16
17use std::{
18    cell::RefCell,
19    collections::HashMap,
20    io::{BufRead, BufReader, Cursor, Read, Write},
21    mem,
22    path::{Path, PathBuf},
23    rc::Rc,
24    sync::Arc,
25};
26
27use anyhow::{bail, Result};
28use bytemuck::Pod;
29use bytes::Bytes;
30use risc0_binfmt::PovwJobId;
31use risc0_circuit_keccak::{KeccakState, KECCAK_PO2_RANGE};
32use risc0_zkp::core::digest::Digest;
33use risc0_zkvm_platform::{self, fileno};
34use serde::Serialize;
35use tempfile::TempDir;
36
37use crate::{
38    host::client::{
39        posix_io::PosixIo,
40        slice_io::{slice_io_from_fn, SliceIo, SliceIoTable},
41    },
42    serde::to_vec,
43    AssumptionReceipt, TraceCallback,
44};
45
46/// A builder pattern used to construct an [ExecutorEnv].
47#[derive(Default)]
48pub struct ExecutorEnvBuilder<'a> {
49    inner: ExecutorEnv<'a>,
50}
51
52#[allow(dead_code)]
53#[derive(Clone)]
54pub enum SegmentPath {
55    TempDir(Arc<TempDir>),
56    Path(PathBuf),
57}
58
59impl SegmentPath {
60    pub(crate) fn path(&self) -> &Path {
61        match self {
62            Self::TempDir(dir) => dir.path(),
63            Self::Path(path) => path.as_path(),
64        }
65    }
66}
67
68/// A Keccak proof request.
69#[derive(Clone, serde::Serialize, serde::Deserialize)]
70pub struct ProveKeccakRequest {
71    /// The digest of the claim that this keccak input is expected to produce.
72    pub claim_digest: Digest,
73
74    /// The requested size of the keccak proof, in powers of 2.
75    pub po2: usize,
76
77    /// The control root which identifies a particular keccak circuit revision.
78    pub control_root: Digest,
79
80    /// Input transcript to provide to the keccak circuit.
81    pub input: Vec<KeccakState>,
82}
83
84/// A trait that supports the ability to be notified of proof requests
85/// on-demand.
86pub trait CoprocessorCallback {
87    /// Request that a keccak proof is produced.
88    fn prove_keccak(&mut self, request: ProveKeccakRequest) -> Result<()>;
89}
90
91pub type CoprocessorCallbackRef<'a> = Rc<RefCell<dyn CoprocessorCallback + 'a>>;
92
93/// Container for assumptions in the executor environment.
94#[derive(Default)]
95pub(crate) struct AssumptionReceipts(pub(crate) Vec<AssumptionReceipt>);
96
97/// The [Executor][crate::Executor] is configured from this object.
98///
99/// The executor environment holds configuration details that inform how the
100/// guest environment is set up prior to guest program execution.
101#[derive(Default)]
102pub struct ExecutorEnv<'a> {
103    pub(crate) env_vars: HashMap<String, String>,
104    pub(crate) args: Vec<String>,
105    pub(crate) keccak_max_po2: Option<u32>,
106    pub(crate) segment_limit_po2: Option<u32>,
107    pub(crate) session_limit: Option<u64>,
108    pub(crate) posix_io: Rc<RefCell<PosixIo<'a>>>,
109    pub(crate) slice_io: Rc<RefCell<SliceIoTable<'a>>>,
110    pub(crate) input: Vec<u8>,
111    pub(crate) trace: Vec<Rc<RefCell<dyn TraceCallback + 'a>>>,
112    pub(crate) assumptions: Rc<RefCell<AssumptionReceipts>>,
113    pub(crate) segment_path: Option<SegmentPath>,
114    pub(crate) pprof_out: Option<PathBuf>,
115    pub(crate) input_digest: Option<Digest>,
116    pub(crate) coprocessor: Option<CoprocessorCallbackRef<'a>>,
117    pub(crate) povw_job_id: Option<PovwJobId>,
118}
119
120impl<'a> ExecutorEnv<'a> {
121    /// Construct a [ExecutorEnvBuilder].
122    ///
123    /// # Example
124    ///
125    /// ```
126    /// use risc0_zkvm::ExecutorEnv;
127    ///
128    /// let env = ExecutorEnv::builder().build();
129    /// ```
130    pub fn builder() -> ExecutorEnvBuilder<'a> {
131        ExecutorEnvBuilder::default()
132    }
133}
134
135impl<'a> ExecutorEnvBuilder<'a> {
136    /// Finalize this builder to construct an [ExecutorEnv].
137    ///
138    /// # Example
139    ///
140    /// ```
141    /// use risc0_zkvm::ExecutorEnv;
142    ///
143    /// let env = ExecutorEnv::builder().build().unwrap();
144    /// ```
145    ///
146    /// After calling `build`, the [ExecutorEnvBuilder] will be reset to
147    /// default.
148    pub fn build(&mut self) -> Result<ExecutorEnv<'a>> {
149        let mut inner = mem::take(&mut self.inner);
150
151        if !inner.input.is_empty() {
152            let reader = Cursor::new(inner.input.clone());
153            inner
154                .posix_io
155                .borrow_mut()
156                .with_read_fd(fileno::STDIN, reader);
157        }
158
159        if inner.pprof_out.is_none() {
160            if let Ok(env_var) = std::env::var("RISC0_PPROF_OUT") {
161                inner.pprof_out = Some(env_var.into());
162            }
163        }
164
165        if let Ok(po2) = std::env::var("RISC0_KECCAK_PO2") {
166            let po2 = po2.parse::<u32>()?;
167            if !KECCAK_PO2_RANGE.contains(&(po2 as usize)) {
168                bail!(
169                    "invalid keccak po2 {po2}. Expected range: {:?}",
170                    KECCAK_PO2_RANGE
171                );
172            }
173            inner.keccak_max_po2 = Some(po2);
174        }
175
176        Ok(inner)
177    }
178
179    /// Set a segment limit, specified in powers of 2 cycles.
180    ///
181    /// Lowering this value will reduce the memory consumption of the prover. Memory consumption is
182    /// roughly linear with the segment size, so lowering this value by 1 will cut memory
183    /// consumpton by about half.
184    ///
185    /// The default value is chosen to be performant on commonly used hardware. Tuning this value,
186    /// either up or down, may result in better proving performance.
187    ///
188    /// Given value must be between [risc0_zkp::MIN_CYCLES_PO2] and
189    /// [risc0_zkp::MAX_CYCLES_PO2] (inclusive).
190    pub fn segment_limit_po2(&mut self, limit: u32) -> &mut Self {
191        self.inner.segment_limit_po2 = Some(limit);
192        self
193    }
194
195    /// Set a segment limit for keccak proofs, specified in powers of 2 cycles.
196    ///
197    /// Lowering this value will reduce the memory consumption of the prover. Memory consumption is
198    /// roughly linear with the segment size, so lowering this value by 1 will cut memory
199    /// consumpton by about half.
200    ///
201    /// The default value is chosen to be performant on commonly used hardware. Tuning this value,
202    /// either up or down, may result in better proving performance.
203    ///
204    /// Given value must be within [risc0_circuit_keccak::KECCAK_PO2_RANGE]
205    pub fn keccak_max_po2(&mut self, limit: u32) -> Result<&mut Self> {
206        if !KECCAK_PO2_RANGE.contains(&(limit as usize)) {
207            bail!(
208                "invalid keccak po2 {limit}. Expected range: {:?}",
209                KECCAK_PO2_RANGE
210            );
211        }
212        self.inner.keccak_max_po2 = Some(limit);
213        Ok(self)
214    }
215
216    /// Set a session limit, specified in number of cycles.
217    ///
218    /// # Example
219    ///
220    /// ```
221    /// use risc0_zkvm::ExecutorEnv;
222    ///
223    /// let env = ExecutorEnv::builder()
224    ///     .session_limit(Some(32 * 1024 * 1024)) // 32M cycles
225    ///     .build()
226    ///     .unwrap();
227    /// ```
228    pub fn session_limit(&mut self, limit: Option<u64>) -> &mut Self {
229        self.inner.session_limit = limit;
230        self
231    }
232
233    /// Add environment variables to the guest environment.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// use std::collections::HashMap;
239    /// use risc0_zkvm::ExecutorEnv;
240    ///
241    /// let mut vars = HashMap::new();
242    /// vars.insert("VAR1".to_string(), "SOME_VALUE".to_string());
243    /// vars.insert("VAR2".to_string(), "SOME_VALUE".to_string());
244    ///
245    /// let env = ExecutorEnv::builder()
246    ///     .env_vars(vars)
247    ///     .build()
248    ///     .unwrap();
249    /// ```
250    pub fn env_vars(&mut self, vars: HashMap<String, String>) -> &mut Self {
251        self.inner.env_vars = vars;
252        self
253    }
254
255    /// Add an argument array to the guest environment.
256    ///
257    /// # Example
258    /// ```
259    /// # use risc0_zkvm::ExecutorEnv;
260    ///
261    /// let env = ExecutorEnv::builder()
262    ///     .args(&["grep".to_string(), "-c".to_string(), "foo".to_string(), "-".to_string()])
263    ///     .build()
264    ///     .unwrap();
265    /// ```
266    pub fn args(&mut self, args: &[String]) -> &mut Self {
267        self.inner.args.extend_from_slice(args);
268        self
269    }
270
271    /// Add an environment variable to the guest environment.
272    ///
273    /// # Example
274    ///
275    /// ```
276    /// use risc0_zkvm::ExecutorEnv;
277    ///
278    /// let env = ExecutorEnv::builder()
279    ///     .env_var("VAR1", "SOME_VALUE")
280    ///     .build()
281    ///     .unwrap();
282    /// ```
283    pub fn env_var(&mut self, name: &str, val: &str) -> &mut Self {
284        self.inner
285            .env_vars
286            .insert(name.to_string(), val.to_string());
287        self
288    }
289
290    /// Write input data to the zkVM guest stdin.
291    ///
292    /// This function will serialize `data` using a zkVM-optimized codec that
293    /// can be deserialized in the guest with a corresponding `env::read` with
294    /// the same data type.
295    ///
296    /// # Example
297    ///
298    /// ```
299    /// use risc0_zkvm::ExecutorEnv;
300    /// use serde::Serialize;
301    ///
302    /// #[derive(Serialize)]
303    /// struct Input {
304    ///     a: u32,
305    ///     b: u32,
306    /// }
307    ///
308    /// let input1 = Input{ a: 1, b: 2 };
309    /// let input2 = Input{ a: 3, b: 4 };
310    /// let env = ExecutorEnv::builder()
311    ///     .write(&input1).unwrap()
312    ///     .write(&input2).unwrap()
313    ///     .build()
314    ///     .unwrap();
315    /// ```
316    pub fn write<T: Serialize>(&mut self, data: &T) -> Result<&mut Self> {
317        Ok(self.write_slice(&to_vec(data)?))
318    }
319
320    /// Write input data to the zkVM guest stdin.
321    ///
322    /// This function writes a slice directly to the underlying buffer. A
323    /// corresponding `env::read_slice` can be used within the guest to read the
324    /// data.
325    ///
326    /// # Example
327    ///
328    /// ```
329    /// use risc0_zkvm::ExecutorEnv;
330    ///
331    /// let slice1 = [0, 1, 2, 3];
332    /// let slice2 = [3, 2, 1, 0];
333    /// let env = ExecutorEnv::builder()
334    ///     .write_slice(&slice1)
335    ///     .write_slice(&slice2)
336    ///     .build()
337    ///     .unwrap();
338    /// ```
339    pub fn write_slice<T: Pod>(&mut self, slice: &[T]) -> &mut Self {
340        self.inner
341            .input
342            .extend_from_slice(bytemuck::cast_slice(slice));
343        self
344    }
345
346    /// Write a frame to the zkVM guest via stdin.
347    ///
348    /// A frame contains a length header along with the payload. Reading a frame
349    /// can be more efficient than deserializing a message on-demand. On-demand
350    /// deserialization can cause many syscalls, whereas a frame will only have
351    /// two.
352    pub fn write_frame(&mut self, payload: &[u8]) -> &mut Self {
353        let len = payload.len() as u32;
354        self.inner.input.extend_from_slice(&len.to_le_bytes());
355        self.inner.input.extend_from_slice(payload);
356        self
357    }
358
359    /// Add a posix-style standard input.
360    pub fn stdin(&mut self, reader: impl Read + 'a) -> &mut Self {
361        self.read_fd(fileno::STDIN, BufReader::new(reader))
362    }
363
364    /// Add a posix-style standard output.
365    pub fn stdout(&mut self, writer: impl Write + 'a) -> &mut Self {
366        self.write_fd(fileno::STDOUT, writer)
367    }
368
369    /// Add a posix-style standard error.
370    pub fn stderr(&mut self, writer: impl Write + 'a) -> &mut Self {
371        self.write_fd(fileno::STDERR, writer)
372    }
373
374    /// Add a posix-style file descriptor for reading.
375    pub fn read_fd(&mut self, fd: u32, reader: impl BufRead + 'a) -> &mut Self {
376        self.inner.posix_io.borrow_mut().with_read_fd(fd, reader);
377        self
378    }
379
380    /// Add a posix-style file descriptor for writing.
381    pub fn write_fd(&mut self, fd: u32, writer: impl Write + 'a) -> &mut Self {
382        self.inner.posix_io.borrow_mut().with_write_fd(fd, writer);
383        self
384    }
385
386    /// Add a handler for simple I/O handling.
387    pub fn slice_io(&mut self, channel: &str, handler: impl SliceIo + 'a) -> &mut Self {
388        self.inner
389            .slice_io
390            .borrow_mut()
391            .with_handler(channel, handler);
392        self
393    }
394
395    /// Add a handler for simple I/O handling.
396    pub fn io_callback<C: AsRef<str>>(
397        &mut self,
398        channel: C,
399        callback: impl Fn(Bytes) -> Result<Bytes> + 'a,
400    ) -> &mut Self {
401        self.inner
402            .slice_io
403            .borrow_mut()
404            .with_handler(channel.as_ref(), slice_io_from_fn(callback));
405        self
406    }
407
408    /// Add an [AssumptionReceipt] to the [ExecutorEnv], for use in [composition].
409    ///
410    /// During execution, when the guest calls `env::verify` or `env::verify_integrity`, this
411    /// collection will be searched for an [AssumptionReceipt] that corresponds the verification
412    /// call.
413    ///
414    /// Either a [crate::Receipt] or a [crate::ReceiptClaim] can be provided. If a [crate::Receipt]
415    /// is provided, then an [AssumptionReceipt::Proven] will be added to the [ExecutorEnv]
416    /// and the [crate::Receipt] generated by proving will be unconditional.
417    ///
418    /// [composition]: https://dev.risczero.com/terminology#composition
419    pub fn add_assumption(&mut self, assumption: impl Into<AssumptionReceipt>) -> &mut Self {
420        self.inner
421            .assumptions
422            .borrow_mut()
423            .0
424            .push(assumption.into());
425        self
426    }
427
428    /// Add a callback handler for raw trace messages.
429    pub fn trace_callback(&mut self, callback: impl TraceCallback + 'a) -> &mut Self {
430        self.inner.trace.push(Rc::new(RefCell::new(callback)));
431        self
432    }
433
434    /// Set the path where segments will be stored.
435    pub fn segment_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
436        self.inner.segment_path = Some(SegmentPath::Path(path.as_ref().to_path_buf()));
437        self
438    }
439
440    /// Enable the profiler and output results to the specified path.
441    pub fn enable_profiler<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
442        self.inner.pprof_out = Some(path.as_ref().to_path_buf());
443        self
444    }
445
446    /// Set the input digest.
447    pub fn input_digest(&mut self, digest: Digest) -> &mut Self {
448        self.inner.input_digest = Some(digest);
449        self
450    }
451
452    /// Add a callback for coprocessor requests.
453    pub fn coprocessor_callback(&mut self, callback: impl CoprocessorCallback + 'a) -> &mut Self {
454        self.inner.coprocessor = Some(Rc::new(RefCell::new(callback)));
455        self
456    }
457
458    /// Add a callback for coprocessor requests.
459    pub fn coprocessor_callback_ref(&mut self, callback: CoprocessorCallbackRef<'a>) -> &mut Self {
460        self.inner.coprocessor = Some(callback);
461        self
462    }
463
464    /// Return [ProverOpts][crate::ProverOpts] with proof of verifiable work (PoVW) enabled, and the specified work
465    /// log identifer and job number as the base for PoVW nonces assigned to each segment.
466    ///
467    /// ```
468    /// # use risc0_zkvm::ExecutorEnv;
469    /// use ruint::uint;
470    ///
471    /// let work_log_id = uint!(0xC2A2379b379da8C076d51520C4f6a2fc5AAE3d1e_U160);
472    /// ExecutorEnv::builder().povw((work_log_id, rand::random()));
473    /// ```
474    ///
475    /// See also [PovwJobId]
476    pub fn povw(&mut self, povw_job_id: impl Into<PovwJobId>) -> &mut Self {
477        self.inner.povw_job_id = Some(povw_job_id.into());
478        self
479    }
480}