Skip to main content

ZiskStream

ZiskStream is the streaming counterpart to ZiskStdin. Instead of holding the entire input in memory before proving, it is backed by a live transport and pushes data to the prover as it becomes available.

Overview

ZiskStream mirrors the ZiskStdin write API but pushes bytes over a live transport instead of buffering everything in memory. Use it for inputs too large to materialize up front, or to feed the prover incrementally and reduce latency. It implements both Into<InputSource> (interchangeable with ZiskStdin in prove / execute) and Into<HintsSource>. Writes accumulate locally; the first flush() opens the transport and pushes pending bytes; the SDK calls finish() when the JobHandle resolves, after which the stream is reusable for the next job.


Type

pub struct ZiskStream { /* private */ }

Constructors

ZiskStream is created through one of five transport constructors, each suited to a different deployment.

unix

Open a Unix domain socket at an auto-assigned /tmp/ path.

pub fn unix() -> ZiskStream

Use this when the host and prover run on the same machine and you do not care about the socket location.

Example

use zisk_sdk::ZiskStream;

let stream = ZiskStream::unix();
println!("listening on {}", stream.uri());
// e.g. "unix:///tmp/zisk-input-3a7f.sock"

unix_external

Attach to a Unix domain socket that another process has already bound and is listening on. The SDK only writes through the existing socket; it does not create, bind, or unlink it.

pub fn unix_external(path: &str) -> ZiskStream

Parameters

NameTypeDescription
path&strFilesystem path of an existing, externally-managed socket.

Use this when a sidecar or supervising process owns the socket lifecycle and your client only needs to write to it.

unix_at

Create and bind a new Unix domain socket at an explicit path. The SDK owns the socket.

pub fn unix_at(path: &str) -> Result<ZiskStream>

Parameters

NameTypeDescription
path&strFilesystem path where the socket will be created.

Use this when you need to control the socket location. The socket starts listening immediately so the executor can connect as soon as it launches.

quic

Open a QUIC-over-UDP transport.

pub fn quic(uri: &str) -> Result<ZiskStream>

Parameters

NameTypeDescription
uri&strA quic://host:port URI. Pass :0 to let the OS pick a port.

Use this when host and prover run on different machines and you want a direct point-to-point transport.

Example

let stream = ZiskStream::quic("quic://127.0.0.1:0")?;

grpc

Open a gRPC push transport. Frames are pushed to the coordinator via the PushJobInput RPC.

pub fn grpc() -> ZiskStream

Use this in cluster and remote-prover deployments where the coordinator drives the connection between host and prover.


Write methods

write

Bincode-serialize a typed value and buffer it as one framed record: a u64 length header followed by the serialized payload, zero-padded to the next 8-byte boundary.

pub fn write<T: Serialize>(&self, data: &T)

The guest reads this record with io::read::<T>(), which consumes the header, the payload, and the trailing padding in one step.

write_slice

Buffer raw bytes as one framed record. Same framing as write (a u64 length header followed by the bytes, zero-padded to the next 8-byte boundary), but the payload is written verbatim instead of going through bincode.

pub fn write_slice(&self, data: &[u8])

The guest reads the record with io::read_slice(&mut buf), which consumes the header, the payload, and the trailing padding. Use this when the data is already in byte form.

write_bytes

Buffer raw bytes with no framing, no length header, no padding, no record boundary. The bytes are appended to the transport stream exactly as supplied.

pub fn write_bytes(&self, data: &[u8])

Useful when the guest performs a read_bytes whose size it already knows from another channel (e.g. derived from a separately-read count). Without a length prefix the guest is responsible for knowing how many bytes to consume.


Lifecycle methods

flush

Send every buffered byte over the transport. Blocks until the prover is connected; the first flush() after construction (or after a finish()) opens the transport implicitly.

pub fn flush(&self) -> Result<()>

Example

let stream = ZiskStream::unix();

stream.write(&3u64); // buffered locally
stream.flush()?; // opens transport, sends the record

stream.write(&"hi".to_string());
stream.flush()?; // reuses the open transport

reset

Discard buffered bytes that have not yet been sent. Bytes already flushed cannot be taken back.

pub fn reset(&self)

finish

Close the current transport and mark the stream as not-live. The SDK calls finish automatically when the JobHandle is awaited, so most callers do not need to invoke it directly.

pub fn finish(&self) -> Result<()>

Any flush() issued after finish() blocks until the next call re-opens the transport preventing accidental writes to a completed job.


Accessors

uri

Return the transport URI for the current stream.

pub fn uri(&self) -> &str

Useful for logging, or for handing the URI to a separate process that will connect on the other side of the transport.


Example of usage

ZiskStream is interchangeable with ZiskStdin at the call site:

use zisk_sdk::{GuestProgram, ProverClient, ZiskStream, 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().build()?;
client.setup(&PROGRAM).run()?.await?;

let stream = ZiskStream::unix();
stream.write(&3u64);

// Submit before the rest of the input has been produced
let job = client.prove(&PROGRAM, stream.clone()).run()?;

// Push records in as they arrive — the prover blocks on each
// read until the next flush lands
for chunk in producer.chunks() {
stream.write_slice(&chunk);
stream.flush()?;
}

let proof = job.await?;
Ok(())
}

The byte contract with the guest does not change — the host still has to write values in the order the guest reads them. Only when the bytes arrive differs. From the guest's perspective there is no difference between buffered and streamed input.