Skip to main content

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,
}
FieldTypeDescription
program_idProgramIdIdentifies the program by name and ELF hash.
elfElfThe 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>,
}
FieldTypeDescription
nameCow<'static, str>Human-readable name, derived from the ELF path.
hash_idCow<'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]>,
}
FieldTypeDescription
dataCow<'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>");
ArgumentTypeDescription
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 variableSet byContains
ZISK_ELF_<name>cargo-zisk buildAbsolute path to the compiled ELF.
ZISK_ELF_HASH_<name>cargo-zisk buildblake3 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:

bash
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:

host/build.rs
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.:

bash
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

NameTypeDescription
uri&strPlain 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

TypeDescription
Result<GuestProgram>A GuestProgram ready for setup, upload, and prove.

Errors

ConditionError
File not foundWrapped std::io::Error
Permission deniedWrapped 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

NameTypeDescription
nameimpl Into<String>Human-readable program name stored in ProgramId.
elf_dataVec<u8>Raw ELF binary bytes to embed in the GuestProgram.

Returns

TypeDescription
GuestProgramA 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

TypeDescription
&[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

TypeDescription
&strThe 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

TypeDescription
&strHex-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

TypeDescription
&ProgramIdThe 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

TypeDescription
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

NameTypeDescription
hash_modeHashModeThe hash mode used to derive the VK.

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

NameTypeDescription
program&GuestProgramThe compiled guest program to execute.
stdinZiskStdinInput data made available to the guest's stdin.
profilingOption<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

TypeDescription
Result<()>Ok(()) if emulation succeeds, or an error describing a failure.

Errors

ConditionError
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()))?;