GuestProgram
GuestProgram wraps a compiled guest ELF binary and the metadata
the prover needs to identify and prove it. It is the required
argument for setup, upload, execute, and prove operations
on ProverClient, and for the emulator-only zisk_sdk::run
helper.
Overview
Every ZisK proving run starts with a GuestProgram: the raw ELF
bytes the prover re-executes to build the trace, plus a ProgramId
(name + blake3 hash of those bytes) that pins a program to the exact
binary that produced it. Change the ELF and the hash changes too,
invalidating any previously generated program ids.
Types
GuestProgram
pub struct GuestProgram {
pub program_id: ProgramId,
pub elf: Elf,
}
| Field | Type | Description |
|---|---|---|
program_id | ProgramId | Identifies the program by name and ELF hash. |
elf | Elf | The compiled ELF bytes (static or heap-allocated). |
ProgramId
Uniquely identifies a guest program. Constructed automatically by
load_program!, from_uri, and from_bytes.
pub struct ProgramId {
pub name: Cow<'static, str>,
pub hash_id: Cow<'static, str>,
}
| Field | Type | Description |
|---|---|---|
name | Cow<'static, str> | Human-readable name, derived from the ELF path. |
hash_id | Cow<'static, str> | Hex-encoded blake3 hash of the ELF bytes. |
ProgramId implements Display as "name:hash_id":
let id = program.program_id();
println!("{id}"); // e.g. "hash-guest:3a7f2c…"
Use the hash_id to verify that the elf id on disk matches the
binary you are about to prove.
Elf
pub struct Elf {
pub data: Cow<'static, [u8]>,
}
| Field | Type | Description |
|---|---|---|
data | Cow<'static, [u8]> | Static bytes when embedded, heap-allocated at runtime. |
When the ELF is embedded via load_program! the bytes live in the binary's
read-only data segment with no heap allocation. When loaded at runtime via
from_uri or from_bytes, the bytes are heap-allocated and owned.
Constructors
load_program!
Embed a compiled guest program into the host binary at compile time. This is the recommended default.
static PROGRAM: GuestProgram = load_program!("<crate-name>");
| Argument | Type | Description |
|---|---|---|
| Program name | &str (literal) | String literal matching the guest crate's exported name. |
The macro reads two environment variables set by the ZisK build system, both suffixed with the literal you pass in:
| Environment variable | Set by | Contains |
|---|---|---|
ZISK_ELF_<name> | cargo-zisk build | Absolute path to the compiled ELF. |
ZISK_ELF_HASH_<name> | cargo-zisk build | blake3 hash of the ELF bytes. |
<name> is concatenated verbatim with the literal you pass. Both
variables must be present at compile time; if either is missing the
macro produces a compile error.
Populating the environment variables
There are three ways to set these variables before the host is compiled. The first two go through the ZisK CLI and handle the hash for you; the third lets you wire the variables up yourself.
Through cargo-zisk build
Run cargo-zisk build --release before compiling the host. The
build tool computes the hash, builds the guest, and exports the
variables in your current shell:
cargo-zisk build --release
cargo run --release
Through build.rs
Add a build script to the host crate so that Cargo rebuilds the guest and re-exports the variables automatically:
use zisk_sdk::build_program;
fn main() {
build_program("../guest");
}
With this in place a single cargo run --release always compiles
the guest first, exports the variables, then compiles the host.
Manually
Export the variables yourself before invoking cargo build. Useful
when the ELF lives outside the typical workspace layout — CI
artifacts, non-Cargo build systems, etc.:
export ZISK_ELF_hash_guest=/abs/path/to/hash-guest
export ZISK_ELF_HASH_hash_guest=$(b3sum /abs/path/to/hash-guest | cut -d' ' -f1)
cargo build --release
The suffix on the variable names must match the load_program!
argument character-for-character.
from_uri
Load a GuestProgram at runtime from a local file path or a
file:// URI. The ELF bytes are read from disk, the blake3 hash is
computed, and the program name is derived from the file stem of the
path.
pub fn from_uri(uri: &str) -> Result<GuestProgram>
Parameters
| Name | Type | Description |
|---|---|---|
uri | &str | Plain file path or file:// URI on disk. |
Only local file paths and file:// URIs are supported. http:// and
https:// schemes return an error; any other scheme is rejected as
unknown.
Returns
| Type | Description |
|---|---|
Result<GuestProgram> | A GuestProgram ready for setup, upload, and prove. |
Errors
| Condition | Error |
|---|---|
| File not found | Wrapped std::io::Error |
| Permission denied | Wrapped std::io::Error |
URI scheme is http:// or https:// | "HTTP loading not yet implemented" |
| URI scheme is anything else | "Unknown URI scheme" |
Example
use zisk_sdk::GuestProgram;
// From a plain file path
let program = GuestProgram::from_uri(
"target/riscv64ima-zisk-zkvm-elf/release/hash-guest",
)?;
// From a file:// URI
let program = GuestProgram::from_uri(
"file:///home/user/programs/hash-guest",
)?;
// Selecting a program at runtime based on user input
let path = std::env::args().nth(1).expect("usage: host <elf-path>");
let program = GuestProgram::from_uri(&path)?;
from_bytes
Build a GuestProgram directly from ELF bytes already in memory,
without touching the filesystem. The blake3 hash is computed from the
provided bytes; the caller supplies the program name.
pub fn from_bytes(name: impl Into<String>, elf_data: Vec<u8>) -> GuestProgram
Parameters
| Name | Type | Description |
|---|---|---|
name | impl Into<String> | Human-readable program name stored in ProgramId. |
elf_data | Vec<u8> | Raw ELF binary bytes to embed in the GuestProgram. |
Returns
| Type | Description |
|---|---|
GuestProgram | A new program with the supplied bytes and a freshly computed hash. |
Unlike from_uri, this constructor is infallible: any byte vector is
hashed and wrapped. If the bytes are not a valid ELF the program will
still be created, but downstream operations (setup, prove,
emulation) will fail when they attempt to parse it.
Example
use zisk_sdk::GuestProgram;
// ELF fetched from an arbitrary source (network, archive, database…)
let elf_bytes: Vec<u8> = fetch_elf_from_registry("hash-guest@1.2.3")?;
let program = GuestProgram::from_bytes("hash-guest", elf_bytes);
println!("{} -> {}", program.name(), program.hash());
Methods
elf
Return the raw ELF bytes of the guest program.
pub fn elf(&self) -> &[u8]
Returns
| Type | Description |
|---|---|
&[u8] | The raw ELF binary byte slice. |
Use this when you need to inspect or forward the ELF bytes directly — for example, to compute an independent hash, transmit the binary to a remote system, or write it to disk:
let bytes = program.elf();
println!("ELF size: {} bytes", bytes.len());
// Write to a temporary file
std::fs::write("/tmp/guest.elf", bytes)?;
name
Return the human-readable name of the guest program.
pub fn name(&self) -> &str
Returns
| Type | Description |
|---|---|
&str | The program name, derived from the ELF path or crate name. |
The name is used in log messages and in the Display output of
ProgramId. It is not guaranteed to be globally unique — two
different builds of the same crate produce the same name but a
different hash_id:
println!("Proving: {}", program.name());
// e.g. "Proving: hash-guest"
hash
Return the blake3 hash of the ELF binary as a lowercase hex string.
pub fn hash(&self) -> &str
Returns
| Type | Description |
|---|---|
&str | Hex-encoded blake3 hash of the ELF bytes. |
println!("ELF hash: {}", program.hash());
// e.g. "ELF hash: 3a7f2c8d…"
// Guard against stale proving keys
if stored_hash != program.hash() {
client.setup(&PROGRAM).run()?;
}
program_id
Return a reference to the ProgramId of the guest program.
pub fn program_id(&self) -> &ProgramId
Returns
| Type | Description |
|---|---|
&ProgramId | The program identity (name + blake3 hash). |
ProgramId implements Display as "name:hash_id", which is useful
for structured logging or building cache keys:
let id = program.program_id();
// Structured logging
println!("program_id={id}");
// e.g. "program_id=hash-guest:3a7f2c8d…"
// Cache key for proving key storage
let proving_key_path = format!("keys/{}", id);
vk
Return the program verification key for this guest. The VK is derived from the ELF and is what an independent verifier needs to confirm a proof was produced by this exact program.
pub fn vk(&self) -> Result<ProgramVK>
Returns
| Type | Description |
|---|---|
Result<ProgramVK> | The program verification key, or an error if it cannot be derived. |
Use the returned ProgramVK with Proof::with_program_vk(...) when
the verifier holds the canonical VK separately from the proof file
and wants to pin verification to that exact program.
Example
let vk = PROGRAM.vk()?;
// Later, pin a proof to that VK at verify time
proof.with_program_vk(&vk).verify()?;
vk() derives the key with the default HashMode::Poseidon1. To
derive it under a different hash mode, use vk_with_mode.
vk_with_mode
Derive the program verification key under an explicit hash mode,
rather than the default HashMode::Poseidon1 that vk() uses.
pub fn vk_with_mode(&self, hash_mode: HashMode) -> Result<ProgramVK>
Parameters
| Name | Type | Description |
|---|---|---|
hash_mode | HashMode | The hash mode used to derive the VK. |
Related utility
run
Run the ZisK emulator against a GuestProgram without going through
a ProverClient. This is the fastest way to validate that a guest
executes correctly .
pub fn run(
program: &GuestProgram,
stdin: ZiskStdin,
profiling: Option<ProfilingMode>,
) -> Result<()>
Parameters
| Name | Type | Description |
|---|---|---|
program | &GuestProgram | The compiled guest program to execute. |
stdin | ZiskStdin | Input data made available to the guest's stdin. |
profiling | Option<ProfilingMode> | Some(mode) to enable profiler output; None for a plain emulation run. |
When profiling is Some, the emulator writes profiler output to
the path configured by the selected ProfilingMode and prints that
path to stdout on success. The function temporarily extracts the
embedded ELF to a file in the system temp directory so the profiler
can resolve symbols; the file is removed before run returns.
Returns
| Type | Description |
|---|---|
Result<()> | Ok(()) if emulation succeeds, or an error describing a failure. |
Errors
| Condition | Error |
|---|---|
| ELF cannot be converted to a ZisK ROM | "Failed to convert ELF to ZISK ROM" |
| Emulator returns an error during execution | "Emulation failed" (also printed to stderr) |
Example
use zisk_sdk::{load_program, run, GuestProgram, ZiskStdin};
static PROGRAM: GuestProgram = load_program!("hash-guest");
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut stdin = ZiskStdin::new();
stdin.write(&42u64);
// Plain emulation: no profiling, no proof.
run(&PROGRAM, stdin, None)?;
Ok(())
}
To enable profiling, pass a ProfilingMode value. The profiler
output path is printed to stdout when run succeeds:
use zisk_sdk::{run, ProfilingMode, ZiskStdin};
let stdin = ZiskStdin::new();
run(&PROGRAM, stdin, Some(ProfilingMode::default()))?;