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.
Guest code is the program that executes inside the RISC Zero zkVM and gets proven. The guest code runs in a constrained RISC-V environment and produces cryptographic proofs of correct execution.
Basic Structure
A minimal guest program requires three components:
#![no_main]
#![no_std]
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
// Your guest code here
}
The #![no_main] and #![no_std] attributes are required when not using the std feature. These keep the guest binary lightweight for better performance.
Boilerplate Explained
#![no_std] - Excludes the Rust standard library to keep the guest code lightweight and performant
#![no_main] - Indicates this is not a standalone executable with a standard entry point
risc0_zkvm::guest::entry!(main) - Macro that designates which function to call when the host starts executing the guest code
Guest programs receive private inputs from the host using the env::read() function:
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
// Read a single value
let a: u64 = env::read();
let b: u64 = env::read();
// Compute with the inputs
let result = a.checked_mul(b).expect("Integer overflow");
}
For better performance, use env::read_slice() to read raw bytes without deserialization overhead:
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
// Read the length first
let len: usize = env::read();
// Allocate and read the slice
let mut data = vec![0u8; len];
env::read_slice(&mut data);
}
Performance matters in zkVM guest code. Use _slice variants when working with large amounts of data to reduce cycle count and proof generation time.
Committing to the Journal
The journal contains public outputs that are included in the receipt and available to verifiers:
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
let a: u64 = env::read();
let b: u64 = env::read();
if a == 1 || b == 1 {
panic!("Trivial factors");
}
let product = a.checked_mul(b).expect("Integer overflow");
// Commit the result to the journal (public output)
env::commit(&product);
}
Committing Slices
For performance-critical applications, commit slices directly:
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
let data = [1u8, 2, 3, 4];
// Commit raw bytes to the journal
env::commit_slice(&data);
}
Writing Private Output
Use env::write() to send private data back to the host (not included in the receipt):
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
let intermediate_result = 42;
// Send private data to the host
env::write(&intermediate_result);
}
Working with Complex Data Types
Guest programs support serialization with serde:
use risc0_zkvm::guest::env;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct Input {
value: u64,
metadata: String,
}
#[derive(Serialize)]
struct Output {
result: u64,
hash: [u8; 32],
}
risc0_zkvm::guest::entry!(main);
fn main() {
// Read structured input
let input: Input = env::read();
// Process
let output = Output {
result: input.value * 2,
hash: [0; 32],
};
// Commit structured output
env::commit(&output);
}
Real-World Example: JSON Processing
Here’s an example from the RISC Zero examples that processes JSON data:
use json::parse;
use risc0_zkvm::{
guest::env,
sha::{Impl, Sha256},
};
fn main() {
// Read JSON string from host
let data: String = env::read();
// Hash the input data
let sha = *Impl::hash_bytes(&data.as_bytes());
// Parse and extract critical data
let data = parse(&data).unwrap();
let proven_val = data["critical_data"].as_u32().unwrap();
// Commit both the hash and extracted value
let out = Outputs {
data: proven_val,
hash: sha,
};
env::commit(&out);
}
Debugging and Optimization
Logging
Use env::log() to print debug messages:
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
env::log("Starting computation");
let result = 42;
env::log(&format!("Result: {}", result));
}
Cycle Counting
Measure performance with env::cycle_count():
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
let start = env::cycle_count();
// Your computation
let result = expensive_computation();
let end = env::cycle_count();
env::log(&format!("Cycles: {}", end - start));
}
Warning: The cycle count is provided by the host and is not checked by the zkVM circuit. Use it only for debugging and optimization.
Advanced Features
File Descriptors
Access standard input/output streams directly:
use risc0_zkvm::guest::env;
use risc0_zkvm::guest::env::{Read, Write};
risc0_zkvm::guest::entry!(main);
fn main() {
let mut stdin = env::stdin();
let mut stdout = env::stdout();
let mut journal = env::journal();
// Use Read and Write traits
stdin.read(&my_struct);
stdout.write(&result);
journal.write(&public_output);
}
Pausing and Exiting
Control zkVM execution flow:
use risc0_zkvm::guest::env;
risc0_zkvm::guest::entry!(main);
fn main() {
// Pause execution (can be resumed later)
env::pause(0);
// Exit with status code
env::exit(0); // Success
// env::exit(1); // Error
}
Best Practices
Use _slice variants for large data transfers and avoid unnecessary allocations.
Check input validity at the start to fail fast if data is invalid.
Use Appropriate Data Types
Choose the smallest sufficient types (e.g., u32 vs u64) to reduce cycle count.
Use built-in cryptographic functions like SHA-256 and Keccak for better performance.
See Also