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.

CircomCircuit​

The circuit-side sibling of GuestProgram: a typed, client-independent handle to user-supplied Circom source. Where GuestProgram carries a compiled guest ELF, CircomCircuit carries a Circom template body that gets injected verbatim into a generated circuit — used by the aggregation / recurser API for its AggregatePublics and NormalizePublics templates.

pub struct CircomCircuit {
pub name: Cow<'static, str>,
pub source: Cow<'static, str>,
}
FieldTypeDescription
nameCow<'static, str>Display name for diagnostics — the load path or file stem.
sourceCow<'static, str>The Circom template source, injected verbatim.
Constructor / methodDescription
CircomCircuit::new_static(name, source)Build from static strings. const-compatible; used by load_circuit!.
CircomCircuit::from_source(name, source)Build from an in-memory string, for circuits generated at runtime.
CircomCircuit::from_path(path)Read a circuit body from a file at runtime. Returns Result<Self>.
.name() / .source()Borrow the display name / template source.

Builder APIs take impl Into<CircomCircuit>, and From<&CircomCircuit> is implemented, so a static circuit can be passed by reference without an explicit .clone().

load_circuit!​

Embed a Circom template body at compile time. The path resolves relative to the file containing the invocation, the same rule include_str! uses.

use zisk_sdk::{load_circuit, CircomCircuit};

static AGGREGATE: CircomCircuit = load_circuit!("circuits/aggregate_publics.circom");

For dynamic loading from a path known only at runtime, use CircomCircuit::from_path instead.


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