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(); }
Groth16 Proof (Recommended)
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 withstdin.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(), andtimeout()(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
- CUDA 12
- Docker with the NVIDIA Container Toolkit; the user must be allowed to run
docker.
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()withZKM_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-sdkwith thenetworkfeature to yourCargo.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(); }