ProveKit is a zero-knowledge proof system toolkit by the World Foundation. It compiles Noir programs to R1CS constraints and generates/verifies WHIR proofs. The codebase is ~95% Rust with a Go recursive verifier.
Noir Circuit (.acir)
↓ [r1cs-compiler]
R1CS (A, B, C matrices) + Witness Builders
↓ [prover]
1. Witness solving (layered: w1 → challenges → w2)
2. R1CS compression (postcard blob, reduces peak memory)
3. W1 commitment (Skyscraper/SHA256/Keccak/Blake3 Merkle tree)
4. W2 commitment (if multi-challenge circuit)
5. WHIR sumcheck
↓
NoirProof { public_inputs, whir_r1cs_proof }
↓ [verifier]
1. Fiat-Shamir transcript replay
2. Commitment verification
3. Sumcheck verification
4. Public input binding check
↓
Accept / Reject
Core proof system (Rust):
provekit/common— Shared types: R1CS, SparseMatrix, Interner, PrefixCovector, WitnessBuilders, HashConfig, serializationprovekit/r1cs-compiler— Noir ACIR → R1CS compilation with optimizations (binop batching, range check batching, spread table caching)provekit/prover— WHIR proving: witness solving, memory compression, commitment, sumcheckprovekit/verifier— WHIR verification: transcript replay, sumcheck check, public input binding
Cryptographic primitives:
skyscraper/core— Custom BN254 hash engine with SIMD-accelerated field arithmetic (aarch64). Registered globally at startup.ntt— Number Theoretic Transform for polynomial evaluation/interpolation. Supports interleaved polynomials.poseidon2— Poseidon2 hash function (BN254-specific). Used in R1CS compilation for Poseidon2 black box calls.
Tooling:
tooling/cli— Main CLI for prove/verify commandstooling/provekit-ffi— C-compatible FFI bindings (iOS, Android, Python, Swift, Kotlin)tooling/provekit-gnark— gnark integration for Go interoptooling/provekit-bench— Benchmarking utilitiestooling/verifier-server— HTTP server combining Rust API + Go verifier
Go recursive verifier (recursive-verifier/):
- Takes WHIR proof and produces Groth16 proof for on-chain verification via gnark
- CLI (
cmd/cli/) and HTTP server (cmd/server/) modes - R1CS must match the WHIR proof being verified; PK/VK must be generated together
These invariants are critical for soundness. Violations can produce unsound proofs or verification failures.
For all constraints i: (A[i] · w) * (B[i] · w) = C[i] · w (mod BN254 prime)
Changes to noir_to_r1cs(), WitnessBuilder variants, or R1CS optimization passes must preserve this.
Prover and verifier must construct identical Fiat-Shamir transcripts. The domain separator is derived from the serialized WhirR1CSScheme. Any change to proof structure, commitment ordering, or message sequencing breaks transcript consistency and causes verification failure.
public_inputs[i] == witness[1 + i] for all i < num_public_inputs
The witness at position 0 is the constant 1. make_public_weight and compute_public_eval use n = num_public_inputs + 1 to account for this. Off-by-one here is a soundness vulnerability (see PR #321).
Witness builders execute in layers. All builders depending on a Challenge must be in a later layer than the challenge source. Within a layer, execution order is irrelevant. Layers of type Inverse use Montgomery's batch inversion trick (single field inverse + multiplications). Violating layer ordering causes panics in solve_witness_vec().
prover_message: Absorbed into Fiat-Shamir transcript — verifier derives challenges from it. Use for values that must be transcript-bound.prover_hint_ark: NOT absorbed into transcript — prover sends it but it doesn't affect challenges. Use only for values independently verified by WHIR (e.g., committed polynomial evaluations).
Misusing prover_hint_ark for a value that should be transcript-bound is a soundness vulnerability — a malicious prover can substitute arbitrary values without detection.
provekit_common::register_ntt(); // Must be called once at startupRegisters the WHIR ArkNtt and Skyscraper hash engine globally. Forgetting this causes runtime panics.
pub struct R1CS {
pub num_public_inputs: usize,
pub interner: Interner, // Deduplicates field elements in matrices
pub a: SparseMatrix, // Left constraint matrix
pub b: SparseMatrix, // Middle constraint matrix
pub c: SparseMatrix, // Right constraint matrix
}Uses delta-encoded column indices within rows (reduces serialized size ~30-50%). Key operations: set(), iter_row(), transpose(), multiply() (parallel via rayon).
Memory-optimized covectors for zero-padded vectors. PrefixCovector stores only the non-zero prefix; OffsetCovector stores weights at an offset. Both implement LinearForm for MLE evaluation.
pub struct NoirProof {
pub public_inputs: PublicInputs,
pub whir_r1cs_proof: WhirR1CSProof, // narg_string (raw bytes) + hints
}Serialized as postcard binary (.np) or JSON (.json). Hash config stored as 1-byte header.
~15+ variants: Constant, Sum, Product, Inverse, Challenge, DigitalDecomposition, SpiceWitnesses (RAM/ROM), LogUpInverse, BinOpLookupDenominator, etc. Each variant knows how to compute its witness value from prior witnesses.
Runtime-selectable: Skyscraper (default, custom optimized), SHA256, Keccak, Blake3. Determines Merkle tree hash and Fiat-Shamir sponge.
# Rust (requires nightly)
cargo fmt --all --check
cargo clippy --all-targets --all-features --verbose
cargo build --all-targets --all-features --verbose
cargo test --no-fail-fast --all-features --verbose --lib --tests --bins
cargo test --doc --all-features --verbose
cargo doc --workspace --all-features --no-deps --document-private-items # with RUSTDOCFLAGS="--cfg doc_cfg -D warnings"
# Go (recursive-verifier/)
cd recursive-verifier && go build ./... && go test -v -cover ./...Toolchain: Rust nightly (nightly-2026-03-04), edition 2021, rust-version 1.85. Go 1.24.
CI runs on ubuntu-24.04-arm. End-to-end tests run on self-hosted ARM64 (provekit-build).
- Formatting:
cargo fmt. CI enforces. - Linting:
cargo clippywith workspace-level lints:warnonperf,complexity,style,correctness,suspicious.denyonmissing_safety_doc. - Error handling:
anyhow::Result<T>with.context("description"). Useensure!()for invariant checks,bail!()for early returns. No.unwrap()or.expect()in library code — only in tests and CLI entry points. - Unsafe code: All
unsafeblocks must have// SAFETY:comment explaining the invariant. Avoidunsafein proof system core (provekit/*) without team review. - Documentation: All
pubitems need///doc comments. CI runscargo doc -D warnings. - Tracing: Use
#[instrument(skip_all)]fromtracingfor function tracing. Do not log large objects. - Parallelism: Use
rayonfor data parallelism. Do not spawn raw threads. - Dependencies: Declare in root
Cargo.toml[workspace.dependencies], reference with{ workspace = true }in crateCargo.toml. - Testing:
#[test_case]for parameterized tests,proptest/quickcheckfor property-based tests. Integration tests compile Noir programs end-to-end.
gofmt,go vet,golangci-lintmust pass.go mod tidymust produce no diff.
- All
*const c_charparameters must be valid null-terminated UTF-8. pk_init()must be called exactly once before any prove/verify.pk_configure_memory()must be called beforepk_init()if using custom allocator.- All returned
PKBufmust be freed exactly once viapk_free_buf(). - All FFI functions wrap Rust code with
catch_panic()to prevent unwinding across the FFI boundary. - Dual allocator modes: callback-based (host delegates alloc/dealloc) or mmap-based (swap-to-disk).
- Proof format:
NoirProofserialized via postcard (.np) or serde_json (.json). Hash config stored as 1-byte header. - Proof scheme (
.pkp): Serialized Prover/Verifier containing R1CS, witness builders, WHIR config. - SparseMatrix: Uses delta-encoded column indices on disk.
encode_col_deltas/decode_col_deltasmust roundtrip correctly. - Breaking changes: Do not change serialization formats without migration plan — deployed verifiers and mobile clients depend on compatibility.
This is a cryptographic proof system. Correctness and soundness are critical.
- Never skip or weaken constraint checks in prover/verifier code without explicit justification.
- Sensitive field elements must use
zeroizefor cleanup. - Do not introduce
unsafecode inprovekit/*without team review. - FFI boundaries must validate all input pointers and lengths.
- Do not change serialization formats without migration plan.
- All field arithmetic is mod BN254 prime. Montgomery representation used internally by arkworks — transparent to users.
When reviewing PRs, check for:
- Soundness: Changes to constraint generation (
noir_to_r1cs), witness builders, prover transcript messages (prover_messagevsprover_hint_ark), or verification logic must not weaken soundness. Scrutinize for missing constraints, wrong witness layer ordering, or transcript inconsistencies. - Public input binding: Any change to
make_public_weight,compute_public_eval, orverify_public_input_bindingmust account for the constant-1 witness at position 0 (n = num_public_inputs + 1). - Transcript consistency: Prover and verifier must produce identical Fiat-Shamir transcripts. Check that new prover messages are mirrored in verifier, and vice versa.
- Unsafe code: New
unsafeblocks must have// SAFETY:comments. Prefer safe alternatives. Extra scrutiny forskyscraper/SIMD code. - Error handling: No
.unwrap()in library code. Useanyhow::Resultwithcontext()/ensure!()/bail!(). - Public API docs: All new
pubitems need///doc comments. - Test coverage: New functionality must include tests. Bug fixes must include regression tests. Property-based tests (
proptest/quickcheck) preferred for mathematical operations. - Workspace deps: Dependencies must be in root
Cargo.toml[workspace.dependencies]. - Serialization: Changes to serialized types (
NoirProof,R1CS,SparseMatrix,WhirR1CSProof) must maintain backward compatibility or include migration. - Performance: Changes to hot paths (NTT, polynomial ops, sparse matrix multiply, sumcheck inner loop) must not regress performance. Use
#[instrument]for tracing. - FFI safety: Changes to
provekit-ffimust maintain panic safety (catch_panic), pointer validation, andPKBuflifetime correctness. - Clippy compliance: All clippy warnings resolved. Workspace enforces
warnonperf,complexity,style,correctness,suspicious. - Formatting:
cargo fmtfor Rust,gofmtfor Go. No exceptions.
- Branch names:
username/description(e.g.,px/fix-verifier). - Keep PRs focused — one logical change per PR.
- CI must pass before merge. Do not bypass branch protection without justification.