Skip to main content

Prove, verify, and check deployability

This tutorial takes a registered circuit through the full developer loop: build a witness, generate a proof, verify it, wrap it in the canonical envelope, and finally ask whether the verifier could be deployed on a given chain. It uses the zerostyl-sdk crate and the zerostyl-orbit CLI.

You'll use: zerostyl-sdk, zerostyl-prove, zerostyl-orbit. Time: ~15 minutes.

Running from the monorepo

This tutorial runs from a clone of the repository: it uses the zerostyl-prove CLI (built from source) and the zk_private_demo worked example at examples/zk_private_demo/. The zerostyl-sdk crate itself is published on crates.io.

1. Build a witness

WitnessBuilder assembles the JSON a circuit descriptor expects (decimal or 0x-hex strings, arrays for Merkle paths), with optional schema checking:

use zerostyl_sdk::{WitnessBuilder, ZeroStyl};

let witness = WitnessBuilder::new()
.set_u64("collateral", 500_000)
.set_u64("collateral_nonce", 42)
.set_u64("threshold", 100_000)
.build();

2. Register the circuit and mock-prove first

mock_prove runs the constraint system without generating a real proof — instant, and the fastest way to catch a bad witness before paying for keygen:

let mut client = ZeroStyl::new()?; // default cache dir
client.register(zk_private_demo::descriptor())?;

let circuit = client.circuit("deposit")?;
let report = circuit.mock_prove(&witness)?;
assert!(report.satisfied, "constraints unsatisfied: {:?}", report.failures);

If constraints fail, jump to Debug a failing witness before proving.

3. Prove and verify

let artifact = circuit.prove(&witness)?; // real halo2 KZG proof
assert!(circuit.verify(&artifact.bytes, &artifact.public_inputs_json)?);

ProofArtifact carries the raw proof bytes and the public_inputs_json (the 32-byte little-endian field representations the verifier consumes).

The same thing from the CLI

zerostyl-prove generate --circuit state_mask --witnesses witness.json --output proof.bin
zerostyl-prove verify --circuit state_mask --proof proof.bin --inputs public_inputs.json
ZeroStyl Prover — circuit: state_mask k: 10
Proof: 2720 bytes → proof.bin
Public inputs → public_inputs.json
Done.

ZeroStyl Verifier — circuit: state_mask k: 10
Proof is VALID
Done.

4. Seal the proof for transport

The canonical envelope stamps the proof with the circuit id so a reader can tell which circuit it belongs to before verifying. It is for off-chain storage/transport — on-chain verifiers expect the raw bytes:

let sealed = circuit.seal(&artifact); // magic + circuit id + version + proof
let raw = circuit.open(&sealed)?; // rejects a proof sealed for another circuit
assert_eq!(raw, artifact.bytes);

5. Check deployability per chain

A real verifier for this proof is large. Before you plan a deployment, ask zerostyl-orbit whether it fits a chain — and whether that chain has the precompiles on-chain verification needs:

zerostyl-orbit matrix --wasm zerostyl_verifier.wasm --proving-system halo2_kzg
Artifact: 91.1 KB compressed / 338.3 KB decompressed

arbitrum-sepolia: NOT deployable
[OVER] compressed (on-chain code): 91.1 KB / 24.0 KB (67.1 KB over)
[OVER] decompressed WASM: 338.3 KB / 256.0 KB (82.3 KB over)

arbitrum-sepolia: Halo2Kzg on-chain verification precompiles available
[ok ] 0x06 ecAdd (bn256)
[ok ] 0x07 ecMul (bn256)
[ok ] 0x08 ecPairing (bn256)

This quantifies the honest state of the toolkit: the BN254 pairing precompile a KZG verifier needs is available, but the verifier is ~4× over the 24 KB compressed limit — so on-chain verification is a size problem, not a precompile problem. A small contract, by contrast, passes:

arbitrum-sepolia: DEPLOYABLE
[ok ] compressed (on-chain code): 12.0 KB / 24.0 KB (12.0 KB headroom)
[ok ] decompressed WASM: 40.0 KB / 256.0 KB (216.0 KB headroom)

Scaffold a custom Orbit chain profile (with the caps raised to the 96 KB / 256 KB maximums) with zerostyl-orbit init --output my-chain.toml, then pass --chain my-chain.toml.

Next steps