Skip to main content

Input / Output (io)

The io module is the guest's only channel to the outside world. Inputs arrive as a sequential bytes written by the host and public outputs are committed into the proof during execution so they can be consumed after verification.

Overview

ZisK guests execute inside a zkVM that has no operating system, no filesystem, no networking, and no environment. The only data that crosses into the guest is whatever the host wrote into the input region before execution started, and the only data the guest can publish is whatever it commits into the proof's public-output region. The io module is the interface for both directions. It provides four primitives that map directly onto the underlying memory regions:

DirectionTypedRaw
Readio::read<T>() -> Tio::read_slice<'a>() -> &'a [u8]
Commitio::commit(value: &T)io::commit_slice(buf: &[u8])

Reading inputs

read<T>() -> T

Deserializes the next value from the input stream into T using bincode. The read order must match the order the values were written.

pub fn read<T: DeserializeOwned>() -> T

Internally this performs two reads from the input stream: first a u64 length header that says how many bytes the value occupies, then exactly that many payload bytes which are passed to bincode::deserialize to reconstruct T.

note

If no value is available to consume from the input stream, the program blocks waiting for one. It does not fail or return early.

Parameters

NameTypeDescription
Ttype parameterTarget type. Must implement serde::DeserializeOwned.

Returns

TypeDescription
TThe deserialized value read from the input stream.

Example

let data = io::read::<u64>();
let data: Vec<u8> = io::read();
warning

Read order must match write order exactly. A type mismatch produces a deserialization panic; a length mismatch is silent, leaving the next read out of sync


read_slice<'a>() -> &'a [u8]

Returns the payload of the next input frame as a raw byte slice without deserializing it. Use this when the data is already in a binary format the guest can interpret directly, or when bincode adds unwanted overhead.

// In the guest (zkvm): a zero-copy borrow of the input region
pub fn read_slice<'a>() -> &'a [u8]

// Off-target (native execution): an owned copy
pub fn read_slice() -> Box<[u8]>

Internally this performs two reads from the input stream: first a u64 length header that says how many bytes the value occupies, then exactly that many payload bytes are read and returned raw.

note

If no value is available to consume from the input stream, the program blocks waiting for one. It does not fail or return early.

Renamed in 1.0.0-beta

This function was previously read_input_slice. That name still exists as a deprecated alias and forwards to read_slice; prefer read_slice in new code.

Returns

TypeDescription
&[u8] (guest)Zero-copy borrow of the next input frame.
Box<[u8]> (native)Owned copy of the next input frame.

Example

// Read a 32-byte digest written via stdin.write_slice on the host.
let digest = io::read_slice();
assert_eq!(digest.len(), 32);

// Reinterpret raw bytes without copying.
let words: &[u64] = unsafe {
core::slice::from_raw_parts(
digest.as_ptr() as *const u64,
digest.len() / 8,
)
};

Committing outputs

commit(value: &T) -> ()

Serializes value with bincode and appends the result to the public output as 32-bit slots. Committed values are written into the proof and visible to the verifier. (The buffer is padded with zeros to the next 4-byte boundary if its length is not a multiple of 4).

pub fn commit<T: Serialize>(value: &T)

Parameters

NameTypeDescription
value&TReference to the value to commit. T must implement serde::Serialize.

Returns

Nothing. The serialized bytes are packed into the next available 32-bit slots of public values.

Example

let result: u64 = compute(&input);
io::commit(&result);

let summary = MySummary { ok: true, count: 42 };
io::commit(&summary);
warning

The verifier reads public values in the same order they were committed. Two callers reading the same proof must agree on both the order and the type of each commit.


commit_slice(value: &[u8]) -> ()

The raw-bytes counterpart to commit. Writes buf to the public output stream as a sequence of 32-bit little-endian words (the buffer is padded with zeros to the next 4-byte boundary if its length is not a multiple of 4).

pub fn commit_slice(buf: &[u8])

Parameters

NameTypeDescription
buf&[u8]Raw byte slice to write to the public output stream.

Returns

Nothing. Each consecutive 4-byte chunk of buf is written as a little-endian u32; the final chunk is zero-padded if buf.len() is not a multiple of 4.

Example

// Commit a digest without padding
let digest: [u8; 32] = Sha256::digest(&input).into();
io::commit_slice(&digest);

// Commit a array of bytes with padding
let encoded: [u8; 30] = [0u8; 30];
io::commit_slice(&encoded);
warning

The verifier reads public values in the same order they were committed. Two callers reading the same proof must agree on both the order and the type of each commit.


Cursor controls

Both directions are sequential: reads advance an input cursor and commits advance an output cursor. These helpers rewind those cursors — useful in tests or when re-reading the input from the start.

read_input_reset() -> ()

Reset the input cursor to the beginning so the next read / read_slice starts again from the first value.

pub fn read_input_reset()

write_output_reset() -> ()

Reset the output cursor to slot 0, discarding any previously committed public values.

pub fn write_output_reset()