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