Skip to main content

Proof

Proof bundles a cryptographic proof body, the public values the guest committed, and the program verification key that pins the proof to a specific program. Use it to verify, inspect outputs, and persist for later use.

Overview​

A Proof is what a proving run produces. It carries everything an independent process needs to verify the run — neither the guest ELF nor the original inputs are required. ProveResult derefs to Proof, so every method documented below is reachable directly on the result returned by client.prove(...).

You usually do not construct a Proof yourself. The prover hands you one through ProveResult, or you load a previously saved one with Proof::load(...).


Types​

Proof​

The top-level proof container.

pub struct Proof {
pub body: ProofBody,
pub program_vk: ProgramVK,
}
FieldTypeDescription
bodyProofBodyKind-tagged proof payload, including the publics.
program_vkProgramVKProgram verification key that pins the proof to a program.
note

Publics are not a field on Proof. They live inside the body at full u64 width, and the PublicValues view is derived on demand by publics(). This keeps the u32 view from ever disagreeing with the elements the proof actually committed to.

ProofKind​

Identifies the proof format. Derived from ProofBody via Proof::kind(), not stored on the proof.

pub enum ProofKind {
#[default]
VadcopFinal,
VadcopFinalMinimal,
Plonk,
}
VariantDescription
VadcopFinalDefault STARK output. Largest proof, fastest to generate.
VadcopFinalMinimalSmaller STARK obtained by wrapping VadcopFinal. Off-chain friendly.
PlonkSNARK-wrapped proof, constant size, EVM-verifiable on-chain.

ProofBody​

The kind-tagged proof payload stored in Proof::body. Both variants carry publics_full — the untruncated u64 field elements the proof committed to — because a recurser proof's publics exceed 32 bits and the recursion round-trip must use the exact committed values.

pub enum ProofBody {
Vadcop {
proof: Vec<u64>,
zisk_vk: Vec<u64>,
kind: VadcopKind,
hash: String,
publics_full: Vec<u64>,
},
Plonk {
proof_bytes: Vec<u8>,
plonk_vk: Box<PlonkVkBlob>,
publics: PublicValues,
publics_full: Vec<u64>,
rootc: Vec<u64>,
},
}

Vadcop — produced by VadcopFinal and VadcopFinalMinimal.

FieldTypeDescription
proofVec<u64>Proof, as a flat vector of field elements.
zisk_vkVec<u64>ZisK verification key.
kindVadcopKindWhich vadcop flavor this is, and the is_vadcop_final_proof flag value it implies.
hashStringHash family the proof was generated with.
publics_fullVec<u64>Flag-free program publics [program_vk(4) | inputs(64)], always 68, at full u64 width.

Plonk — produced by Plonk, for on-chain verification.

FieldTypeDescription
proof_bytesVec<u8>Serialized SNARK proof bytes.
plonk_vkBox<PlonkVkBlob>PLONK verification key blob. Boxed so the common Vadcop variant doesn't carry it inline.
publicsPublicValuesu32 view of the publics, matching the Solidity calldata layout.
publics_fullVec<u64>Full-width publics. The SNARK's publicsHash is computed over these, not the u32 view.
rootcVec<u64>The stamped rootCVadcopFinal committed into publicsHash.
On-chain calldata

rootc is what the on-chain publicsHash commits to, and it is not derivable from publics_full or from the verification key blob: it is the vadcop_final verkey for a plain proof, but the recurser verkey for an aggregated one. Read it from the Plonk body rather than reconstructing it. The hash preimage is program_vk (big-endian) ++ inputs (little-endian) ++ rootc (big-endian), SHA-256'd and then reduced modulo the BN254 scalar field.

VadcopKind​

Tags which flavor of vadcop proof a Vadcop body holds. It owns the is_vadcop_final_proof flag value, which is deliberately not stored in publics_full — STARK paths re-add it via VadcopKind::stark_publics.

pub enum VadcopKind {
#[default]
Final,
Recurser,
Minimal,
}
VariantPublics layoutFlag at index 0Meaning
Final[flag | vk(4) | inputs(64)] (69)1Raw ZisK vadcop_final proof — a recursion leaf.
Recurser[flag | vk(4) | inputs(64)] (69)0Output of a recurser fold (an aggregated proof).
Minimal[vk(4) | inputs(64)] (68)noneMinimal proof; the compressed circuit strips it.
MethodDescription
.flag() -> Option<u64>The flag value at public index 0, or None for Minimal.
.is_minimal() -> boolWhether this is the minimal (compressed) proof.
.from_publics_full(&[u64])Classify a raw publics vector as it arrives from the prover, before normalization.
.stark_publics(&[u64]) -> Vec<u64>Rebuild the STARK public vector, re-adding the flag for Final / Recurser.
note

VadcopKind is defined in zisk_common and is not re-exported by zisk_sdk. The SDK re-exports Proof, ProofBody, ProofKind, PublicValues, ProgramVK, PlonkVkBlob, and PlonkVkey; reach for zisk_common directly if you need to name this type. Most callers don't — use Proof::kind(), which maps Minimal to ProofKind::VadcopFinalMinimal and both other variants to ProofKind::VadcopFinal.

PublicValues​

Holds the public outputs the guest committed. Internally a byte buffer plus an atomic read cursor: each read advances the cursor by the number of bytes consumed. Reads take &self because the cursor is atomic.

pub struct PublicValues {
/* private */
}

The encoding follows serde rules: read::<T>() uses bincode for typed reads; read_slice operates on raw bytes; read_abi::<T>() decodes Solidity ABI–encoded values.

ProgramVK​

The program verification key. Re-exported from zisk_common. It is derived from the guest ELF (see GuestProgram::vk) and is what an independent verifier pins a proof to.

pub struct ProgramVK { /* private */ }

Constructors​

Proof::load​

Load a previously saved proof from disk.

pub fn load(path: impl AsRef<Path>) -> Result<Proof>
NameTypeDescription
pathimpl AsRef<Path>Path to the proof file on disk.

The file must have been written by Proof::save (or by tooling using the same encoding).

use zisk_sdk::Proof;

let proof = Proof::load("my-path/proof.bin")?;
proof.verify()?;

Methods​

save​

Write the proof to disk. The parent directory must already exist; existing files are overwritten.

pub fn save(&self, path: impl AsRef<Path>) -> Result<()>
NameTypeDescription
pathimpl AsRef<Path>Destination path for the proof file.
proof.save("my-path/proof.bin")?;

kind​

Return the proof's ProofKind, derived from the body discriminant: a Vadcop body with VadcopKind::Minimal maps to VadcopFinalMinimal, any other Vadcop body to VadcopFinal, and a Plonk body to Plonk.

pub fn kind(&self) -> ProofKind
match proof.kind() {
ProofKind::VadcopFinal => println!("Vadcop final proof"),
ProofKind::VadcopFinalMinimal => println!("Vadcop minimal proof"),
ProofKind::Plonk => println!("Plonk SNARK proof"),
}

is_empty​

Return true if the proof carries no proof body (a default / placeholder proof).

pub fn is_empty(&self) -> bool

publics / publics_full​

publics() derives the u32 PublicValues view from whatever the body holds: for Vadcop it truncates each full-width public to its low 32 bits (the guest / Solidity ABI width), and for Plonk it returns the stored u32 publics unchanged. Because it is the single derivation point, the view can never disagree with the underlying source of truth.

publics_full() returns the untruncated u64 publics [program_vk(4) | inputs(64)] — what the proof actually committed to, and what the recursion round-trip needs. It returns None for a Plonk proof, which has no u64 form of its own.

pub fn publics(&self) -> PublicValues
pub fn publics_full(&self) -> Option<&[u64]>
// u32 view — what the guest wrote and the ABI expects
let publics = proof.publics();

// Full-width elements, when they matter (recurser proofs exceed 32 bits)
if let Some(full) = proof.publics_full() {
println!("committed {} field elements", full.len());
}

get_publics​

Alias for publics(). Returns the derived PublicValues by value — it is a fresh view, not a borrow of something stored on the proof.

pub fn get_publics(&self) -> PublicValues
let publics = proof.get_publics();
let nonce: u64 = publics.read::<u64>()?;
let digest: [u8; 32] = {
let mut out = [0u8; 32];
publics.read_slice(&mut out);
out
};

get_program_vk​

Return the program verification key that pins this proof to its program. Use it to compare against a trusted key held out-of-band.

pub fn get_program_vk(&self) -> &ProgramVK
let vk = proof.get_program_vk();
assert_eq!(vk, &trusted_vk);

get_proof_bytes / get_proof_u64​

Return the serialized proof payload, either as raw bytes or as u64 words. The encoding varies by ProofKind. Use these to inspect or forward the proof body; for the typed structure use the public body field directly.

pub fn get_proof_bytes(&self) -> Result<Vec<u8>>
pub fn get_proof_u64(&self) -> Result<Vec<u64>>
let bytes = proof.get_proof_bytes()?;
println!("proof bytes: {}", bytes.len());

match &proof.body {
ProofBody::Vadcop { proof, .. } => println!("vadcop words: {}", proof.len()),
ProofBody::Plonk { proof_bytes, .. } => println!("plonk bytes: {}", proof_bytes.len()),
}

get_vadcop_final_proof​

Return the VADCOP final proof structure. Errors if the proof is a PLONK proof rather than a VADCOP one.

pub fn get_vadcop_final_proof(&self) -> Result<VadcopFinalProof>

verify​

Verify the proof against the public values and program verification key already stored on it.

pub fn verify(&self) -> Result<()>
proof.verify()?;
println!("proof verified");

with_publics / with_program_vk​

Begin a verify chain that overrides the embedded public values or program verification key. Both return a ZiskVerifyBuilder you can keep chaining before finishing with .verify().

pub fn with_publics<'a>(&'a self, publics: &'a PublicValues) -> ZiskVerifyBuilder<'a>
pub fn with_program_vk<'a>(&'a self, program_vk: &'a ProgramVK) -> ZiskVerifyBuilder<'a>
// Pin verification to an externally-held program VK
let vk = PROGRAM.vk()?;
proof.with_program_vk(&vk).verify()?;

PublicValues methods​

The cursor is shared via an AtomicUsize, so reads take &self even though they advance the cursor. Call head to rewind before re-reading.

read​

Decode the next bincode-encoded value from the buffer and advance the cursor by the bytes consumed.

pub fn read<T>(&self) -> Result<T>
where
T: serde::Serialize + serde::de::DeserializeOwned,
let count: u64 = publics.read::<u64>()?;
let digest: [u8; 32] = publics.read::<[u8; 32]>()?;

read_slice​

Copy the next slice.len() bytes from the buffer into slice. The cursor advances by slice.len().

pub fn read_slice(&self, slice: &mut [u8])
NameTypeDescription
slice&mut [u8]Destination buffer; its length is the count.
let mut tag = [0u8; 32];
publics.read_slice(&mut tag);

read_abi​

Decode the next value using Solidity ABI rules instead of bincode. Use this when the guest committed its publics with write_abi for on-chain consumption.

pub fn read_abi<T>(&self) -> Result<T>
where
T: alloy_sol_types::SolValue
+ From<<T::SolType as alloy_sol_types::SolType>::RustType>,

Reset the read cursor back to the beginning of the buffer so the next read starts from the first value again.

pub fn head(&self)
let a: u64 = publics.read()?;
publics.head(); // rewind
let a_again: u64 = publics.read()?;