Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/risc0/risc0/llms.txt

Use this file to discover all available pages before exploring further.

The host is the untrusted machine that runs the zkVM, executes guest programs, and generates cryptographic proofs. The host sets up the execution environment, provides inputs, and collects outputs.

Overview

In a zkVM application, the host performs these key steps:
  1. Build an execution environment with inputs
  2. Execute the guest program
  3. Generate a proof (receipt)
  4. Verify the receipt (or pass it to a third party)

Basic Host Program

Here’s a minimal host program that executes a guest and generates a proof:
use risc0_zkvm::{default_prover, ExecutorEnv};
use hello_world_methods::MULTIPLY_ELF;

fn main() {
    // Build the execution environment
    let env = ExecutorEnv::builder().build().unwrap();
    
    // Get the default prover
    let prover = default_prover();
    
    // Prove execution of the guest program
    let receipt = prover.prove(env, MULTIPLY_ELF).unwrap().receipt;
    
    println!("Proof generated successfully!");
}

Building the Execution Environment

The ExecutorEnv is where you configure the zkVM execution and provide inputs:

Writing Input Data

use risc0_zkvm::ExecutorEnv;

let env = ExecutorEnv::builder()
    .write(&17u64).unwrap()  // First input
    .write(&23u64).unwrap()  // Second input
    .build()
    .unwrap();

Writing Slices for Performance

use risc0_zkvm::ExecutorEnv;

let data = vec![1u8, 2, 3, 4, 5];

let env = ExecutorEnv::builder()
    .write_slice(&data)
    .build()
    .unwrap();

Writing Frames

Frames include a length header and are more efficient for large data:
use risc0_zkvm::ExecutorEnv;

let payload = b"large data payload";

let env = ExecutorEnv::builder()
    .write_frame(payload)
    .build()
    .unwrap();

Complete Example with I/O

Here’s a real example from the hello-world demo:
use hello_world_methods::MULTIPLY_ELF;
use risc0_zkvm::{ExecutorEnv, Receipt, default_prover};

pub fn multiply(a: u64, b: u64) -> (Receipt, u64) {
    // Build environment with inputs
    let env = ExecutorEnv::builder()
        .write(&a).unwrap()
        .write(&b).unwrap()
        .build()
        .unwrap();

    // Get the prover
    let prover = default_prover();

    // Execute and prove
    let receipt = prover.prove(env, MULTIPLY_ELF).unwrap().receipt;

    // Extract output from the journal
    let c: u64 = receipt.journal.decode().expect(
        "Journal output should deserialize into the same types (& order) that it was written",
    );

    println!("I know the factors of {c}, and I can prove it!");

    (receipt, c)
}

Configuring the Executor Environment

Environment Variables

use risc0_zkvm::ExecutorEnv;
use std::collections::HashMap;

let mut vars = HashMap::new();
vars.insert("VAR1".to_string(), "VALUE1".to_string());
vars.insert("VAR2".to_string(), "VALUE2".to_string());

let env = ExecutorEnv::builder()
    .env_vars(vars)
    .build()
    .unwrap();
Or add variables individually:
let env = ExecutorEnv::builder()
    .env_var("DEBUG", "true")
    .env_var("LOG_LEVEL", "info")
    .build()
    .unwrap();

Command-Line Arguments

use risc0_zkvm::ExecutorEnv;

let env = ExecutorEnv::builder()
    .args(&[
        "program".to_string(),
        "--flag".to_string(),
        "value".to_string(),
    ])
    .build()
    .unwrap();

POSIX I/O (stdin/stdout/stderr)

use risc0_zkvm::ExecutorEnv;
use std::io::Cursor;

let input_data = b"input from stdin";
let input = Cursor::new(input_data.to_vec());

let mut stdout_buf = Vec::new();
let mut stderr_buf = Vec::new();

let env = ExecutorEnv::builder()
    .stdin(input)
    .stdout(&mut stdout_buf)
    .stderr(&mut stderr_buf)
    .build()
    .unwrap();

Performance Tuning

Segment Limits

Control memory usage by setting segment size:
use risc0_zkvm::ExecutorEnv;

let env = ExecutorEnv::builder()
    .segment_limit_po2(20)  // 2^20 cycles per segment
    .build()
    .unwrap();
Lowering the segment limit by 1 roughly cuts memory consumption in half. The default is tuned for common hardware.

Session Limits

Limit total execution cycles:
use risc0_zkvm::ExecutorEnv;

let env = ExecutorEnv::builder()
    .session_limit(Some(32 * 1024 * 1024))  // 32M cycles
    .build()
    .unwrap();

Keccak Configuration

use risc0_zkvm::ExecutorEnv;

let env = ExecutorEnv::builder()
    .keccak_max_po2(18).unwrap()
    .build()
    .unwrap();

Proof Composition

Use verified receipts as assumptions in new proofs:
use risc0_zkvm::{ExecutorEnv, Receipt};

fn compose_proofs(previous_receipt: Receipt) -> Receipt {
    let env = ExecutorEnv::builder()
        .add_assumption(previous_receipt).unwrap()
        .build()
        .unwrap();
    
    // The guest can now call env::verify() on this receipt
    let prover = default_prover();
    prover.prove(env, COMPOSE_ELF).unwrap().receipt
}
CompositeReceipt cannot be used directly as an assumption. Compress it first using ProverOpts::succinct().

Verifying Receipts

Once you have a receipt, verify it against the expected image ID:
use hello_world_methods::{MULTIPLY_ELF, MULTIPLY_ID};
use risc0_zkvm::{default_prover, ExecutorEnv};

fn main() {
    // Generate proof
    let env = ExecutorEnv::builder().build().unwrap();
    let prover = default_prover();
    let receipt = prover.prove(env, MULTIPLY_ELF).unwrap().receipt;
    
    // Verify the receipt
    receipt.verify(MULTIPLY_ID).expect(
        "Code you have proven should successfully verify"
    );
    
    println!("Receipt verified successfully!");
}

Extracting Journal Data

use risc0_zkvm::Receipt;

fn extract_output(receipt: Receipt) -> u64 {
    // Decode journal output
    let output: u64 = receipt.journal.decode().expect(
        "Failed to decode journal"
    );
    output
}

Decoding Multiple Values

use serde::{Deserialize, Serialize};
use risc0_zkvm::Receipt;

#[derive(Serialize, Deserialize)]
struct Output {
    result: u64,
    hash: [u8; 32],
}

fn extract_structured(receipt: Receipt) -> Output {
    receipt.journal.decode().expect("Failed to decode")
}

Prover Options

Customize proof generation:
use risc0_zkvm::{ExecutorEnv, ProverOpts, ReceiptKind, default_prover};

let env = ExecutorEnv::builder().build().unwrap();
let prover = default_prover();

// Generate different receipt types
let opts = ProverOpts::succinct();  // Compressed proof
let receipt = prover
    .prove_with_opts(env, MULTIPLY_ELF, &opts)
    .unwrap()
    .receipt;

Error Handling

use risc0_zkvm::{ExecutorEnv, default_prover};
use anyhow::Result;

fn prove_with_error_handling() -> Result<()> {
    let env = ExecutorEnv::builder()
        .write(&42u64)?
        .build()?;
    
    let prover = default_prover();
    
    match prover.prove(env, MULTIPLY_ELF) {
        Ok(prove_info) => {
            println!("Proof generated successfully!");
            prove_info.receipt.verify(MULTIPLY_ID)?;
            Ok(())
        }
        Err(e) => {
            eprintln!("Proving failed: {}", e);
            Err(e)
        }
    }
}

Advanced Features

Trace Callbacks

Monitor execution with custom callbacks:
use risc0_zkvm::{ExecutorEnv, TraceCallback, TraceEvent};

struct MyTracer;

impl TraceCallback for MyTracer {
    fn trace_callback(&mut self, event: TraceEvent) {
        // Process trace events
        println!("Trace: {:?}", event);
    }
}

let env = ExecutorEnv::builder()
    .trace_callback(MyTracer)
    .build()
    .unwrap();

Profiling

Enable profiler output:
use risc0_zkvm::ExecutorEnv;

let env = ExecutorEnv::builder()
    .enable_profiler("/tmp/profile.pb")
    .build()
    .unwrap();

Custom Segment Storage

use risc0_zkvm::ExecutorEnv;
use std::path::Path;

let env = ExecutorEnv::builder()
    .segment_path("/custom/segment/path")
    .build()
    .unwrap();

Image IDs and ELF Binaries

When you build guest methods with risc0-build, it generates constants:
// Auto-generated in methods/src/lib.rs
pub const MULTIPLY_ELF: &[u8] = include_bytes!("...");
pub const MULTIPLY_ID: [u32; 8] = [...];
pub const MULTIPLY_PATH: &str = "...";
  • MULTIPLY_ELF - The compiled guest binary
  • MULTIPLY_ID - The image ID used for verification
  • MULTIPLY_PATH - Path to the ELF file

Best Practices

1
Use the Builder Pattern
2
Always use ExecutorEnv::builder() for maximum flexibility and clarity.
3
Handle Errors Properly
4
Use Result<T> and the ? operator for clean error propagation.
5
Verify Receipts
6
Always verify receipts after generation, even in development, to catch issues early.
7
Match Input/Output Order
8
Ensure the order of write() calls in the host matches read() calls in the guest.
9
Consider Receipt Size
10
Use ProverOpts::succinct() when receipt size matters (e.g., on-chain verification).

See Also