ProverClient
ProverClient is the entry-point namespace from which you obtain a
configured client for the ZisK proving pipeline. Call
ProverClient::embedded() for an in-process client or
ProverClient::remote(url) for one that talks to a coordinator,
finalize the returned builder with .build(), and use the resulting
EmbeddedClient or RemoteClient to drive setup, execute,
prove, upload, and wrap_proof. Each call returns a request
builder that you configure and submit with .run(), yielding a
JobHandle that you .await to collect the result.
Overview
ProverClient itself is a zero-sized namespace struct exposing two
constructors. The actual work happens on the typed clients the
constructors produce:
pub struct ProverClient;
impl ProverClient {
pub fn embedded() -> EmbeddedClientBuilder;
pub fn remote(url: impl Into<String>) -> RemoteClientBuilder;
}
Both clients expose the same five-method pipeline (setup,
execute, prove, upload, wrap_proof) with identical
signatures.
A typical proving run looks like this:
use zisk_sdk::{ProverClient, ZiskStdin};
let client = ProverClient::embedded().build()?;
client.setup(&PROGRAM).run()?.await?;
let stdin = ZiskStdin::new();
stdin.write(&42u32);
let result = client.prove(&PROGRAM, stdin).run()?.await?;
result.save_proof("./proof.bin")?;
This page documents the ProverClient namespace and the two backend
clients.
ProverClient
The ProverClient struct is purely a namespace; it is never
instantiated. Its two static-style methods return builders for the
two backend kinds:
pub struct ProverClient;
impl ProverClient {
pub fn embedded() -> EmbeddedClientBuilder;
pub fn remote(url: impl Into<String>) -> RemoteClientBuilder;
}
For the full configuration surface returned by each constructor (executor selection, GPU, key paths, timeouts, runtime options) see Client builders.
embedded
Create a builder for an in-process client.
pub fn embedded() -> EmbeddedClientBuilder
Defaults to the emulator executor and CPU device.
use zisk_sdk::ProverClient;
// Default: emulator + CPU
let client = ProverClient::embedded().build()?;
remote
Create a builder targeting a remote proving coordinator at the given URL.
pub fn remote(url: impl Into<String>) -> RemoteClientBuilder
| Parameter | Type | Description |
|---|---|---|
url | impl Into<String> | Full base URL of the remote coordinator. |
use std::time::Duration;
use zisk_sdk::ProverClient;
let client = ProverClient::remote("http://coordinator:50051")
.build()?;
EmbeddedClient & RemoteClient
Both clients implement the same pipeline surface with identical
method signatures. EmbeddedClient is constructed by
EmbeddedClientBuilder::build() and runs the prover in-process;
RemoteClient is constructed by RemoteClientBuilder::build() and
delegates to a coordinator over gRPC.
pub struct EmbeddedClient { /* ... */ }
pub struct RemoteClient { /* ... */ }
The only asymmetry is verify_constraints, an embedded-only
extension trait. Everything else listed below applies to both clients.
Methods
Each method takes the program and an input source by reference and
returns a request builder. Configure with the setters listed below
(all optional), then submit with one of two finishers — .run() for
async code, .run_sync() when you don't have an async runtime. See
Submitting: run vs run_sync for the
difference.
Submitting: run vs run_sync
Every operation builder offers two finishers:
| Finisher | Returns | Use when |
|---|---|---|
.run() | Result<JobHandle<T>> | You are in an async context. .await the handle to get T; it also exposes .cancel()/.job_id(). |
.run_sync() | Result<T> | You have no async runtime. Blocks until the job completes and returns the result directly. |
// Async — await the JobHandle
let result = client.prove(&PROGRAM, stdin.clone()).run()?.await?;
// Sync — blocks, returns the result directly (no .await)
let result = client.prove(&PROGRAM, stdin).run_sync()?;
.run_sync() is available on every operation — setup, execute,
prove, wrap_proof, and the embedded-only verify_constraints.
.run_sync() is implemented for EmbeddedClient only.
RemoteClient is async-only: submit remote jobs with .run() and
.await the returned JobHandle. Calling .run_sync() on a remote
client is a compile-time error.
upload is already synchronous — .run() returns UploadResult
directly, with no JobHandle and no .run_sync() variant.
upload
Register the ELF with the coordinator. On EmbeddedClient this is
a no-op since the ELF is already local. The builder takes no
setters. Returns an UploadResult directly — not a JobHandle,
because uploading is a synchronous accept/reject with no
long-running job to watch.
pub fn upload<'a>(&'a self, program: &'a GuestProgram)
-> UploadRequest<'a, Self>;
let upload = client.upload(&program).run()?;
println!("registered hash_id: {}", upload.hash_id());
setup
Derive the proving key for the program. Returns
Result<JobHandle<SetupResult>>.
pub fn setup<'a>(&'a self, program: &'a GuestProgram)
-> SetupRequest<'a, Self>;
Configure with the following setters before submitting:
| Setter | Default | Description |
|---|---|---|
.with_hints() | disabled | Enable hint-accelerated witness generation. Requires the Assembly executor on the client. |
.emulator_only() | disabled | Generate only the setup needed by the emulator, skipping Assembly-backend artifacts. |
.timeout(Duration) | no timeout | Cancel setup if it takes longer than this. |
.output_dir(PathBuf) | ZisK cache dir | Directory where the verkey file is written when setup completes. |
use std::path::PathBuf;
use std::time::Duration;
client.setup(&program)
.with_hints()
.output_dir(PathBuf::from("./keys"))
.timeout(Duration::from_secs(300))
.run()?
.await?;
execute
Run the guest without generating a proof. Returns
Result<JobHandle<ExecuteResult>>. The input source accepts both
ZiskStdin and ZiskStream.
pub fn execute<'a>(
&'a self,
program: &'a GuestProgram,
stdin: impl Into<InputSource>,
) -> ExecuteRequest<'a, Self>;
Configure with the following setters before submitting:
| Setter | Default | Description |
|---|---|---|
.hints(impl Into<HintsSource>) | none | Attach a hints source for this execute call. |
.executor(ExecutorKind) | client default | Override the executor (Emulator or Assembly) for this single request. |
.timeout(Duration) | no timeout | Cancel execution if it takes longer than this. |
use std::time::Duration;
use zisk_sdk::ExecutorKind;
let exec = client
.execute(&program, stdin)
.executor(ExecutorKind::Assembly)
.timeout(Duration::from_secs(10))
.run()?
.await?;
prove
Generate a proof for a guest run. Returns
Result<JobHandle<ProveResult>>.
pub fn prove<'a>(
&'a self,
program: &'a GuestProgram,
stdin: impl Into<InputSource>,
) -> ProveRequest<'a, Self>;
Configure with the following setters before submitting:
| Setter | Default | Description |
|---|---|---|
.hints(impl Into<HintsSource>) | none | Attach a hints source. Requires .with_hints() at setup time and the Assembly executor. |
.executor(ExecutorKind) | client default | Override the executor for this single request. |
.timeout(Duration) | no timeout | Cancel proving if it takes longer than this. |
.wrap(ProofKind) | VadcopFinalMinimal (embedded) / coordinator-set | Set the proof wrapping mode. |
.on(JobEvent, callback) | no callback | Register a pre-submission event callback. Use JobEvent::All to receive every event. |
Callbacks registered via .on(...) before .run() are guaranteed
to see every event, including ones that fire between submission and
the first post-submission .on(...) on the returned JobHandle.
use std::time::Duration;
use zisk_sdk::{JobEvent, ProofKind};
let proof = client
.prove(&program, stdin)
.wrap(ProofKind::Plonk)
.timeout(Duration::from_secs(300))
.on(JobEvent::All, |e| println!("event: {e:?}"))
.run()?
.await?;
wrap_proof
Convert an existing proof into another ProofKind without
re-executing the guest. Returns Result<JobHandle<ProveResult>> —
wrapped proofs share the same result shape as freshly generated ones.
pub fn wrap_proof<'a>(
&'a self,
proof: &'a Proof,
proof_kind: ProofKind,
) -> WrapRequest<'a, Self>;
Configure with the following setters before submitting:
| Setter | Default | Description |
|---|---|---|
.with_publics(PublicValues) | from the proof | Override the public values used for the wrapped proof. Embedded-only (see below). |
.with_program_vk(ProgramVK) | from the proof | Override the program verification key. Embedded-only (see below). |
.timeout(Duration) | no timeout | Cancel the wrap job if it takes longer than this. |
use zisk_sdk::ProofKind;
let plonk = client
.wrap_proof(&stark_proof, ProofKind::Plonk)
.run()?
.await?;
:::note RemoteClient gotcha
with_publics and with_program_vk are honoured only by
EmbeddedClient::wrap_proof. RemoteClient::wrap_proof ignores
them — the coordinator always re-derives both from the wrapped
proof.
:::
verify_constraints (embedded-only)
EmbeddedClient also gains a verify_constraints method when the
VerifyConstraintsExtension trait is brought into scope. The
constraint checker runs the guest, builds the witness, and checks
every arithmetic constraint without generating a proof — the
fastest way to validate that a program and its inputs produce a
consistent execution trace. Returns
Result<JobHandle<VerifyConstraintsResult>>.
fn verify_constraints<'a>(
&'a self,
program: &'a GuestProgram,
stdin: ZiskStdin,
) -> VerifyConstraintsRequest<'a, Self>;
Configure with the following setters before submitting:
| Setter | Default | Description |
|---|---|---|
.debug_info(impl Into<Option<String>>) | disabled | Enable debug-info output. Pass None for the default path, or Some(path) to write elsewhere. |
.timeout(Duration) | no timeout | Cancel the constraint check if it takes longer than this. |
:::note Extension-trait gating
verify_constraints is provided by the VerifyConstraintsExtension
trait, not as an inherent method on EmbeddedClient. The trait is
sealed to internal types and is not implemented for
RemoteClient — calling it on a remote client is a compile-time
error. Unlike execute and prove, stdin is taken by value
(ZiskStdin), not by impl Into<InputSource> — the constraint
checker does not stream inputs.
:::
use std::time::Duration;
use zisk_sdk::VerifyConstraintsExtension;
let result = client
.verify_constraints(&program, stdin)
.timeout(Duration::from_secs(60))
.run()?
.await?;
Full pipeline example
use std::time::Duration;
use zisk_sdk::{
GuestProgram, JobEvent, ProofKind, ProverClient, ZiskStdin, load_program,
};
static PROGRAM: GuestProgram = load_program!(
"target/riscv64ima-zisk-zkvm-elf/release/my-guest"
);
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ProverClient::embedded().assembly().build()?;
// One-time setup per ELF
client.setup(&PROGRAM).run()?.await?;
// Validate the input cheaply before paying for proving
let stdin = ZiskStdin::new();
stdin.write(&vec![10u32, 20, 30]);
let exec = client
.execute(&PROGRAM, stdin.clone())
.timeout(Duration::from_secs(10))
.run()?
.await?;
println!("execute steps: {}", exec.get_execution_steps());
// Generate the proof, streaming progress events
let result = client
.prove(&PROGRAM, stdin)
.wrap(ProofKind::VadcopFinalMinimal)
.on(JobEvent::All, |e| println!("event: {e:?}"))
.run()?
.await?;
// The result derefs to ProveOutput — full access to its methods
result.verify()?;
result.save_proof("./proof.bin")?;
println!("proving time: {} ms", result.get_proving_time());
Ok(())
}