Skip to main content

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
ParameterTypeDescription
urlimpl 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:

FinisherReturnsUse 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.

Embedded-only

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

SetterDefaultDescription
.with_hints()disabledEnable hint-accelerated witness generation. Requires the Assembly executor on the client.
.emulator_only()disabledGenerate only the setup needed by the emulator, skipping Assembly-backend artifacts.
.timeout(Duration)no timeoutCancel setup if it takes longer than this.
.output_dir(PathBuf)ZisK cache dirDirectory 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:

SetterDefaultDescription
.hints(impl Into<HintsSource>)noneAttach a hints source for this execute call.
.executor(ExecutorKind)client defaultOverride the executor (Emulator or Assembly) for this single request.
.timeout(Duration)no timeoutCancel 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:

SetterDefaultDescription
.hints(impl Into<HintsSource>)noneAttach a hints source. Requires .with_hints() at setup time and the Assembly executor.
.executor(ExecutorKind)client defaultOverride the executor for this single request.
.timeout(Duration)no timeoutCancel proving if it takes longer than this.
.wrap(ProofKind)VadcopFinalMinimal (embedded) / coordinator-setSet the proof wrapping mode.
.on(JobEvent, callback)no callbackRegister 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:

SetterDefaultDescription
.with_publics(PublicValues)from the proofOverride the public values used for the wrapped proof. Embedded-only (see below).
.with_program_vk(ProgramVK)from the proofOverride the program verification key. Embedded-only (see below).
.timeout(Duration)no timeoutCancel 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:

SetterDefaultDescription
.debug_info(impl Into<Option<String>>)disabledEnable debug-info output. Pass None for the default path, or Some(path) to write elsewhere.
.timeout(Duration)no timeoutCancel 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(())
}