Zero-Knowledge Proofs
Tenzro uses Plonky3 STARKs over the KoalaBear field for verifiable computation. There is no trusted setup, no per-circuit ceremony, and no toxic waste — proofs verify in milliseconds with post-quantum-conjectured soundness.
Why STARKs, Why Plonky3
- Transparent setup: No CRS, no Powers of Tau ceremony, no per-circuit proving keys. Anyone can prove or verify with the public parameters.
- KoalaBear field: 31-bit prime
2^31 − 2^24 + 1with two-adicity 24 — fast SIMD arithmetic on commodity CPUs. - Poseidon2 + FRI: Hash-based commitments. No pairings, no elliptic curves.
- Post-quantum-conjectured: Soundness rests on collision-resistance of the hash, which is conjectured to hold against quantum adversaries.
- Compact proofs: ~64–128 KB; verification ~5–20 ms on commodity hardware.
- Hybrid ZK-in-TEE: Pair proofs with hardware attestation for dual verification.
Pre-Built AIRs
Three concrete Algebraic Intermediate Representations (AIRs) ship with the node. Each is keyed by a stable circuit_id string used by the on-chain commitment registry and the JSON-RPC verifier:
circuit_id: "inference" — InferenceAir
Proves an AI inference was computed correctly without revealing model weights or input data.
Private inputs: model_weights, input_data, computation_trace
circuit_id: "settlement" — SettlementAir
Proves payment settlement occurred correctly with the right fee calculation.
Private inputs: payer_signature, payee_address, timestamp
circuit_id: "identity" — IdentityAir
Proves identity ownership without revealing private keys or the full DID document.
Private inputs: private_key, credential_proof
Pinned Testnet Configuration
The whole network agrees on one Plonky3 config so proofs verify identically across nodes. The dev-tree config is pinned at git rev 32079474b1d31d9221656ae774afb322d2597db0:
use tenzro_zk::build_testnet_config;
let config = build_testnet_config();
// log_blowup = 1
// num_queries = 64
// query_pow = 16
// commit_pow = 8
// hash = Poseidon2 over KoalaBear
// commitment = FRIGenerating a Proof
use tenzro_zk::{Plonky3Prover, Proof, ProofType, build_testnet_config};
let config = build_testnet_config();
let prover = Plonky3Prover::new(config.clone(), "inference")?;
// Public inputs are field-element chunks (4 bytes LE per element).
let public_inputs: Vec<Vec<u8>> = vec![
model_hash_chunks, // Vec<u8> — 4-byte little-endian KoalaBear elements
input_hash_chunks,
output_hash_chunks,
];
let witness = /* private trace cells */;
// Produce a Proof envelope:
// { proof_bytes, public_inputs, proof_type=Plonky3, circuit_id, ... }
let proof: Proof = prover.prove(&public_inputs, &witness).await?;
assert_eq!(proof.proof_type, ProofType::Plonky3);
assert_eq!(proof.circuit_id, "inference");Verifying a Proof
A single generic dispatcher matches on the envelope's circuit_idand runs the right AIR's verifier against the pinned config. This is the same entry point used by web, MCP, and JSON-RPC handlers, plus the settlement engine:
use tenzro_zk::verify_proof_envelope;
let valid = verify_proof_envelope(&proof)?;
if valid {
println!("✓ Proof verified");
// Release escrow, accept inference result, mark settlement final.
} else {
println!("✗ Proof invalid");
// Reject result, slash provider, refund payer.
}Commitment-Attestation Model (On-Chain Verification)
STARK proofs are too large to verify inside an EVM precompile economically. Tenzro splits the work: validators run the full Plonky3 verifier off-EVM, then record a 32-byte commitment in the on-chain ZkCommitmentRegistry. The EVM ZK_VERIFY precompile becomes an O(1) HashSet lookup against that registry.
use tenzro_zk::compute_zk_commitment;
// Commitment hash, computed identically off-EVM and on-EVM:
// SHA-256( circuit_id ‖ proof_bytes ‖ Σ( len_le(pi) ‖ pi ) )
//
// where len_le(pi) is a 4-byte little-endian length prefix per public-input chunk.
let commitment: [u8; 32] = compute_zk_commitment(&proof);
// Validators publish the commitment after they accept the proof.
// EVM contracts can then call ZK_VERIFY (precompile address, see /docs/evm)
// with the commitment bytes and get true / false in O(1).JSON-RPC API
# Generate a proof
curl -X POST https://rpc.tenzro.network \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_createZkProof",
"params": [{
"circuit_id": "inference",
"proof_type": "plonky3",
"public_inputs": ["0x12345678", "0x9abcdef0", "0x..."],
"witness": { "...": "..." }
}],
"id": 1
}'
# Verify a proof
curl -X POST https://rpc.tenzro.network \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_verifyZkProof",
"params": [{ "proof": { "proof_bytes": "0x...", "public_inputs": [...], "proof_type": "plonky3", "circuit_id": "inference" } }],
"id": 2
}'
# List supported circuits
curl -X POST https://rpc.tenzro.network \
-H "Content-Type: application/json" \
-d '{ "jsonrpc": "2.0", "method": "tenzro_listCircuits", "id": 3 }'CLI
# List supported circuits
tenzro zk circuits
# Generate a proof
tenzro zk prove \
--circuit-id inference \
--proof-type plonky3 \
--public-inputs ./public_inputs.json \
--witness ./witness.json \
--output proof.bin
# Verify a proof
tenzro zk verify --proof proof.bin
# Generate keys (no-op for STARKs — no per-circuit setup; provided for parity)
tenzro zk keygen --circuit-id inferenceHybrid ZK-in-TEE
A TEE-resident prover runs the Plonky3 prover inside an enclave, then signs the resulting envelope with the enclave's hardware-rooted Ed25519 key. Verifiers check both the cryptographic proof and the hardware attestation:
use tenzro_zk::tee_integration::{
generate_tee_zk_proof,
verify_tee_zk_proof,
sign_tee_zk_proof,
verify_tee_zk_signature,
};
// Inside the enclave: generate proof and sign it with the TEE key.
let (proof, attestation) = generate_tee_zk_proof(
"inference",
&public_inputs,
&witness,
tee_provider,
).await?;
let signature = sign_tee_zk_proof(&proof, &attestation, tee_provider)?;
// Outside the enclave: verify both layers.
let proof_ok = verify_tee_zk_proof(&proof, &attestation)?;
let sig_ok = verify_tee_zk_signature(&proof, &attestation, &signature)?;
assert!(proof_ok && sig_ok);Use Cases
1. Verifiable AI Inference
// Provider returns inference output + Plonky3 proof.
let (result, proof) = model_provider.infer_with_proof(model_id, input).await?;
// User verifies before accepting.
let valid = tenzro_zk::verify_proof_envelope(&proof)?;
if valid {
// Release escrow, accept output.
} else {
// Reject and slash provider.
}2. Settlement Receipts
// Settlement engine emits a Plonky3 proof committing to (settlement_id, amount, fee).
let proof = settlement_engine.prove_settlement(&settlement)?;
// Counterparties (and any auditor) verify with one RPC call.
let valid = tenzro_zk::verify_proof_envelope(&proof)?;3. Credential / KYC Proofs
// Prove a credential is issued by a trusted issuer
// without revealing the full DID document.
let proof = identity_prover.prove(&PublicInputs {
did_hash: sha256(b"did:tenzro:human:..."),
credential_hash: sha256(&credential_bytes),
}, &witness).await?;Web Verification API
# Verify a Plonky3 proof via the public web API.
# Note: api.tenzro.network serves /verify/* directly — no /api/ prefix.
curl -X POST https://api.tenzro.network/verify/zk-proof \
-H "Content-Type: application/json" \
-d '{
"proof": {
"proof_bytes": "0x...",
"public_inputs": ["0x12345678", "0x9abcdef0"],
"proof_type": "plonky3",
"circuit_id": "inference"
}
}'
# Response
{
"valid": true,
"circuit_id": "inference",
"verification_time_ms": 12
}Performance
Circuit: InferenceAir (medium witness)
CPU proving (commodity x86_64):
- Single proof: low-seconds range (witness-dependent)
CPU verification:
- Per proof: 5–20 ms
Proof size:
- 64–128 KB envelope (proof_bytes + public_inputs)
On-chain check:
- ZK_VERIFY EVM precompile: O(1) HashSet lookup, ~3k gas
(the heavy verification ran off-EVM and was committed to the registry)