Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Prover

The zkm_sdk crate provides the tools for proof generation. Its ProverClient lets you:

  • generate the proving and verifying keys with setup();
  • execute a program without proving it with execute();
  • generate proofs with prove();
  • verify proofs with verify().

When generating Groth16 or PLONK proofs, the ProverClient downloads the circuit artifacts of the current release (the proving key from the trusted setup, the verifying key and the Solidity verifier) on first use with try_install_circuit_artifacts(), and caches them in ~/.zkm/circuits/{groth16,plonk}/<version> (for example v2.0.0).

Example: Fibonacci

The following code uses zkm_sdk in a host program.

use zkm_sdk::{include_elf, utils, ProverClient, ZKMProofWithPublicValues, ZKMStdin};

/// The ELF we want to execute inside the zkVM.
const ELF: &[u8] = include_elf!("fibonacci");

fn main() {
    utils::setup_logger();

    let n = 1000u32;

    let mut stdin = ZKMStdin::new();
    stdin.write(&n);

    let client = ProverClient::new();

    let (_, report) = client.execute(ELF, &stdin).run().unwrap();
    println!("executed program with {} cycles", report.total_instruction_count());

    let (pk, vk) = client.setup(ELF);
    let mut proof = client.prove(&pk, stdin).run().unwrap();

    println!("generated proof");

    let _ = proof.public_values.read::<u32>();
    let a = proof.public_values.read::<u32>();
    let b = proof.public_values.read::<u32>();

    println!("a: {}", a);
    println!("b: {}", b);

    client.verify(&proof, &vk).expect("verification failed");

    proof.save("proof-with-pis.bin").expect("saving proof failed");
    let deserialized_proof =
        ZKMProofWithPublicValues::load("proof-with-pis.bin").expect("loading proof failed");

    client.verify(&deserialized_proof, &vk).expect("verification failed");

    println!("successfully generated and verified proof for the program!")
}

Proof Types

The proof mode is chosen on the prove() builder. The proof itself is a ZKMProof:

#![allow(unused)]
fn main() {
pub enum ZKMProof {
    /// A proof generated by the core proof mode.
    ///
    /// The proof size scales linearly with the number of cycles.
    Core(Vec<ShardProof<CoreSC>>),
    /// A proof generated by the compress proof mode.
    ///
    /// The proof size is constant, regardless of the number of cycles.
    Compressed(Box<ZKMReduceProof<InnerSC>>),
    /// A proof generated by the Plonk proof mode.
    Plonk(PlonkBn254Proof),
    /// A proof generated by the Groth16 proof mode.
    Groth16(Groth16Bn254Proof),
    /// A proof generated by the DV-SNARK proof mode.
    DvSnark(DvSnarkBn254Proof),
    /// Compressed-proof-to-Groth16 conversion.
    CompressToGroth16,
}
}

A proof is returned as a ZKMProofWithPublicValues, which bundles the proof, the committed public values and the Ziren version, and can be written and read back with save() and load().

Core Proof (Default)

The default mode produces one STARK proof per shard; the total size grows linearly with the length of the execution.

#![allow(unused)]
fn main() {
let client = ProverClient::new();
client.prove(&pk, stdin).run().unwrap();
}

Compressed Proof

The compressed mode aggregates the shard proofs by recursion into a single STARK proof of constant size. It is verified natively (for example with zkm_verifier::StarkVerifier), but is too large for on-chain verification.

#![allow(unused)]
fn main() {
let client = ProverClient::new();
client.prove(&pk, stdin).compressed().run().unwrap();
}

The Groth16 mode wraps the compressed proof into a Groth16 proof over BN254 of 260 bytes (a 4-byte verifier selector and 8 field elements), verifiable on-chain.

#![allow(unused)]
fn main() {
let client = ProverClient::new();
client.prove(&pk, stdin).groth16().run().unwrap();
}

PLONK Proof

The PLONK mode wraps the compressed proof into a PLONK proof over BN254 of about 868 bytes, also verifiable on-chain. PLONK uses a universal setup (the Aztec Ignition SRS) instead of a circuit-specific trusted setup ceremony.

#![allow(unused)]
fn main() {
let client = ProverClient::new();
client.prove(&pk, stdin).plonk().run().unwrap();
}

Other Modes and Options

  • compress_to_groth16() converts an existing compressed proof into a Groth16 proof. The input stream must hold exactly one compressed proof (written with stdin.write_proof) and the bincode-encoded public values as its only input.
  • The builder also accepts shard_size(), shard_batch_size(), cycle_limit(), with_hook(), and timeout() (network prover only).

Immutable Wrap Verifying Key

By default the Groth16 circuit is specific to one Ziren release. In the immutable-wrap-vk mode (ZKM_IMM_WRAP_VK=1 when building the guest and proving), a single Groth16 verifying key serves all releases: the guest hashes its public values with BLAKE3 instead of SHA-256, and the commitment and start pc of the release's partial STARK verifying key are hashed into the program key hash. zkm-build then builds the guest with its imm-wrap-vk feature, so the guest's Cargo.toml must forward it:

[features]
imm-wrap-vk = ["zkm-zkvm/imm-wrap-vk"]

Such proofs are verified with Groth16Verifier::verify_by_imm_groth16_vk, and their artifacts live in ~/.zkm/circuits/groth16/imm-wrap-vk/<version>. See the imm-wrap-vk-add example.

Hardware Acceleration

GPU Acceleration

Ziren provides a CUDA-based GPU prover, which proves with much lower latency and better cost-performance than the CPU prover.

Software Requirements

Hardware Requirements

  • Processor: 4-core CPU or higher
  • System memory: 16 GB RAM or higher
  • Graphics card: 24 GB VRAM or higher

The NVIDIA GPU must have a compute capability of at least 8.6. You can check yours in the official NVIDIA documentation.

Usage

Build a CUDA client in one of two ways:

  • Option A (environment variable): use ProverClient::new() with ZKM_PROVER=cuda.
  • Option B (direct method): use ProverClient::cuda().

By default the client starts the GPU prover in a Docker container. Because the container receives the private input stream, the image must be pinned by digest:

export ZKM_GPU_IMAGE=projectzkm/ziren-gpu@sha256:<digest>   # a reviewed image digest

A mutable tag such as projectzkm/ziren-gpu:latest is refused unless ZKM_ALLOW_MUTABLE_GPU_IMAGE=1 is set, which is meant for local development only. Further options:

export CUDA_VISIBLE_DEVICE_INDEX=<index>   # GPU the container uses
export CUDA_PORT=<port>                    # port of the container's prover server
export CUDA_RUN_DOCKER=false               # connect to an already running GPU server instead
export CUDA_ENDPOINT=<url>                 # its endpoint (default: http://localhost:3000/twirp/)

With the client built, generate proofs with the standard methods.

CPU Acceleration

Ziren supports AVX2/AVX512 acceleration on x86 CPUs through Plonky3.

Check your CPU's AVX support with:

grep avx /proc/cpuinfo

and look for avx2 or avx512 in the output.

To enable AVX2, add these flags to your RUSTFLAGS environment variable:

RUSTFLAGS="-C target-cpu=native" cargo run --release

To enable AVX512, add these flags to your RUSTFLAGS environment variable:

RUSTFLAGS="-C target-cpu=native -C target-feature=+avx512f" cargo run --release

Network Prover

The ZKM Proof Network proves programs remotely. The SDK's network prover (the network feature of zkm-sdk) produces compressed and Groth16 proofs, and converts compressed proofs to Groth16 (compress_to_groth16()); it does not produce core or PLONK proofs.

A network proof passes through several stages (queuing, splitting, proving and finalizing), and each stage may take a different amount of time.

Requirements

  • Register your address to gain access.
  • A client certificate and key issued for the network, and the certificate of the CA that signs the network's certificate.
  • SDK dependency: add zkm-sdk with the network feature to your Cargo.toml:
zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren", features = ["network"] }

Environment Variable Setup

Before running your application, export the following environment variables:

export ZKM_PRIVATE_KEY=<your_private_key>       # Private key corresponding to your registered public key
export SSL_CERT_PATH=<path_to_ssl_certificate>  # Path to the SSL client certificate (e.g., ssl.pem)
export SSL_KEY_PATH=<path_to_ssl_key>           # Path to the SSL client private key (e.g., ssl.key)
export CA_CERT_PATH=<path_to_ca_certificate>    # Required when SSL_CERT_PATH/SSL_KEY_PATH are set

The repository's crates/sdk/tool directory contains a certgen.sh script and a test CA (ca.pem, ca.key). The test CA's private key is public, so it is only for local testing: the SDK uses it only when ZKM_ALLOW_INSECURE_TEST_CA=1 is set, and never for real witness data (see INSECURE-TEST-PKI.md there).

Optional: customize the network prover's behavior:

export SHARD_SIZE=<shard_size>              # Shard (segment) size requested from the network
export MAX_PROVER_NUM=<max_prover_num>      # Maximum number of provers to use in parallel
export SINGLE_NODE=<true|false>             # Whether to use a single node for proving (default: false)
export ZKM_PROOF_POLL_INTERVAL=<seconds>    # How often to poll for the proof status

To use your own proof network endpoint:

export ENDPOINT=<proof_network_endpoint>    # Proof network endpoint (default: https://152.32.186.45:20002)
export DOMAIN_NAME=<domain_name>            # TLS domain name (default: "stage")

Example

The following host uses the network prover:

use zkm_sdk::{include_elf, utils, ProverClient, ZKMStdin};

const FIBONACCI_ELF: &[u8] = include_elf!("fibonacci");

fn main() {
    utils::setup_logger();

    let mut stdin = ZKMStdin::new();
    stdin.write(&10u32);

    // Create a network client (or set ZKM_PROVER=network and use ProverClient::new()).
    let client = ProverClient::network();
    let (pk, vk) = client.setup(FIBONACCI_ELF);
    let proof = client.prove(&pk, stdin).groth16().run().unwrap();
    client.verify(&proof, &vk).unwrap();
}