Zero-Knowledge Proofs
Tenzro uses Groth16 SNARKs on BN254 for efficient zero-knowledge proofs, with GPU-accelerated proving and hybrid ZK-in-TEE execution for maximum verifiability.
ZK Proof System
- Groth16: Succinct SNARKs with constant-size proofs (~128 bytes)
- BN254 Curve: ~100-bit security level (post-exTNFS), efficient pairing operations
- GPU Acceleration: 10-100x faster proof generation with CUDA/Metal
- Hybrid ZK-in-TEE: Combine cryptographic proofs with hardware attestation
Pre-Built Circuits
InferenceVerificationCircuit
Proves that AI inference was computed correctly without revealing model weights or input data.
Private inputs: model_weights, input_data, computation_trace
SettlementProofCircuit
Proves payment settlement occurred correctly with proper fee calculation.
Private inputs: payer_signature, payee_address, timestamp
IdentityProofCircuit
Proves identity ownership without revealing private keys or full DID document.
Private inputs: private_key, credential_proof
BGM17 MPC Trusted Setup Ceremony
Groth16 requires a circuit-specific Common Reference String (CRS). If a single party generates the CRS and keeps the toxic waste, they can forge proofs. Tenzro uses the BGM17 protocol (Bowe-Gabizon-Miers) to generate the CRS through a multi-party computation ceremony: as long as at least one participant destroys their secret contribution, the resulting parameters are sound. No single party needs to be trusted.
The ceremony runs in two phases. Phase 1 generates a universal Powers of Tau accumulator that is circuit-independent. Phase 2specializes the Phase 1 output for a specific circuit. Tenzro's implementation deterministically seeds Phase 2 from the hash of the final Phase 1 accumulator, so MPC randomness from Phase 1 is provably carried into the circuit-specific proving and verifying keys.
Phase 1: Powers of Tau
use tenzro_zk::ceremony::{CeremonyCoordinator, CeremonyPhase};
// Coordinator initializes the Phase 1 accumulator (universal SRS)
let mut coordinator = CeremonyCoordinator::new(
CeremonyPhase::PowersOfTau,
max_circuit_constraints: 1_000_000,
)?;
// Each participant contributes entropy sequentially
for participant_id in 0..ceremony_participants {
// Participant downloads current accumulator, adds their randomness,
// produces a proof of contribution, and destroys their secret
let contribution = participant.contribute(
&coordinator.current_accumulator(),
)?;
// Coordinator verifies pairing check: e(g^a, h) = e(g, h^a)
coordinator.accept_contribution(contribution)?;
}
// Finalize with a public random beacon (e.g., a future Bitcoin block hash)
// to prevent last-contributor attacks
let phase1_srs = coordinator.finalize_phase1(random_beacon)?;Phase 2: Circuit Specialization
// Phase 2 is deterministically seeded from the Phase 1 accumulator hash
// so MPC randomness flows into the circuit-specific CRS
let mut phase2 = CeremonyCoordinator::phase2_from_phase1(
&phase1_srs,
CircuitType::InferenceVerification,
)?;
// Same MPC flow: each participant contributes, coordinator verifies
for participant_id in 0..ceremony_participants {
let contribution = participant.contribute(
&phase2.current_accumulator(),
)?;
phase2.accept_contribution(contribution)?;
}
// Finalize produces the circuit-specific proving and verifying keys
let (proving_key, verifying_key) = phase2.finalize_phase2(
random_beacon,
)?;
// The full transcript (every contribution + proof of contribution)
// is persisted for public audit
let transcript = phase2.transcript();
transcript.save("ceremony_transcript.bin")?;Security Properties
- 1-of-N trust: As long as one participant honestly destroys their secret, the CRS is sound
- Pairing-based verification: Every contribution is checked via BN254 pairings before acceptance
- Random beacon finalization: Prevents the last contributor from biasing the output
- Full auditability: Coordinator state machine persists every contribution with its proof, enabling post-hoc verification by anyone
- Deterministic Phase 2 seeding: Phase 2 initialization derives from the Phase 1 accumulator hash, ensuring no fresh unverifiable randomness is introduced between phases
Proof Generation
Basic Proof
use tenzro_zk::{CircuitType, ProofSystem, PublicInputs};
// Setup (one-time, generates proving/verifying keys)
let proof_system = ProofSystem::setup(
CircuitType::InferenceVerification,
)?;
// Generate proof
let public_inputs = PublicInputs {
model_hash: vec![0x12, 0x34, ...],
input_hash: vec![0x56, 0x78, ...],
output_hash: vec![0xAB, 0xCD, ...],
};
let witness = /* private computation trace */;
let proof = proof_system.prove(
&public_inputs,
&witness,
).await?;
// Proof is ~128 bytes (constant size)GPU-Accelerated Proving
The GpuProvingEngine is designed to amortize the fixed overhead of setting up pairing computations across many proofs. It supports batch generation up to 100 proofs per call and circuits up to 1,000,000 constraints. Batched proofs can be aggregated into a single succinct commitment via a Merkle tree and then compressed at one of four levels:
use tenzro_zk::{GpuProvingEngine, CompressionLevel};
let engine = GpuProvingEngine::new()
.with_max_batch_size(100)
.with_max_constraints(1_000_000)?;
// Batch proof generation (up to 100 proofs)
let batch_result = engine.batch_prove(
circuit_type,
&public_inputs_batch,
&witnesses_batch,
CompressionLevel::Medium,
).await?;
// BatchResult carries:
// - individual proofs
// - a Merkle root over (proof, public_inputs) leaves
// - aggregate compression ratio
// - proving_time_ms (per-batch + per-proof breakdown)
let proofs = batch_result.proofs;
let merkle_root = batch_result.merkle_root;
let stats = batch_result.stats;Aggregate Verification
Instead of verifying 100 proofs independently, a verifier can verify the Merkle root commitment once and then use Merkle inclusion proofs to check any subset on demand. This reduces on-chain verification cost from O(N) pairings to O(log N) hashes per proof:
// Verifier checks the aggregate commitment
let agg_valid = engine.verify_batch_commitment(
&batch_result.merkle_root,
&batch_result.merkle_proof,
).await?;
// Then per-proof inclusion can be verified cheaply
for (i, proof) in batch_result.proofs.iter().enumerate() {
let inclusion_ok = engine.verify_inclusion(
&batch_result.merkle_root,
&batch_result.inclusion_proofs[i],
proof,
&public_inputs_batch[i],
)?;
assert!(inclusion_ok);
}
// Performance:
// - CPU: ~1 proof/second
// - GPU (NVIDIA H100): ~100 proofs/second
// - Aggregate verification of 100 proofs: ~10ms (vs ~400ms independently)
// - Compression reduces proof size by 30-70%Proof Verification
// Verify proof on-chain or via API
let is_valid = proof_system.verify(
&proof,
&public_inputs,
).await?;
if is_valid {
println!("✓ Proof verified");
// Release escrow, accept inference result, etc.
} else {
println!("✗ Proof invalid");
// Reject result, slash provider, etc.
}Hybrid ZK-in-TEE
Combine ZK proofs with TEE attestation for maximum security and verifiability:
use tenzro_zk::HybridZkTee;
use tenzro_tee::IntelTdxProvider;
// Generate proof inside TEE
let hybrid_system = HybridZkTee::new(
proof_system,
IntelTdxProvider::new()?,
);
let (proof, tee_attestation) = hybrid_system
.prove_in_tee(
circuit_type,
&public_inputs,
&witness,
)
.await?;
// Verifier checks:
// 1. ZK proof is cryptographically valid
// 2. TEE attestation confirms proof was generated in genuine TEE
// 3. TEE measurements match expected values
let valid = hybrid_system.verify_hybrid(
&proof,
&tee_attestation,
&public_inputs,
).await?;Benefits of ZK-in-TEE
- Dual Verification: Both cryptographic proof and hardware attestation
- Protected Setup: Proving keys generated and stored in TEE
- Confidential Computation: Witness data never leaves TEE
- Auditability: TEE measurements prove correct prover software
Proof Compression
GPU proving engine supports multi-level compression for bandwidth-constrained environments:
enum CompressionLevel {
None, // ~128 bytes, no compression
Light, // ~90 bytes, 30% reduction, minimal CPU overhead
Medium, // ~65 bytes, 50% reduction, balanced
Heavy, // ~40 bytes, 70% reduction, higher CPU cost
}
// Example: compress 100 proofs
let compressed_proofs = engine.batch_prove(
circuit_type,
&inputs,
&witnesses,
CompressionLevel::Heavy,
).await?;
// Result: 40 bytes * 100 = 4 KB (vs 128 bytes * 100 = 12.8 KB uncompressed)
// Verification time: +10-20% vs uncompressedProof Types
Tenzro supports multiple proof systems (with Groth16 as the default):
Groth16 (Default)
- Constant-size proofs (~128 bytes)
- Fast verification (~1-5ms)
- Requires trusted setup (per circuit)
PlonK (Experimental)
- Universal setup (same parameters for all circuits)
- Larger proofs (~400-800 bytes)
- Slower verification (~10-20ms)
Halo2 (Experimental)
- No trusted setup required
- Recursive proof composition
- Higher proving cost
STARK
- Transparent (no trusted setup)
- Post-quantum secure
- Large proof size (~100-200 KB)
Use Cases
1. Verifiable AI Inference
// Provider generates inference + proof
let (result, proof) = model_provider.infer_with_proof(
model_id,
input,
).await?;
// User verifies before accepting result
let valid = zk_system.verify(
CircuitType::InferenceVerification,
&proof,
&PublicInputs {
model_hash: model.hash(),
input_hash: hash(&input),
output_hash: hash(&result),
},
).await?;
if valid {
// Accept result, release payment
} else {
// Reject result, slash provider
}2. Private Transactions
// Prove transaction validity without revealing amounts
let proof = zk_system.prove(
CircuitType::PrivateTransfer,
&PublicInputs {
sender_commitment: commit(&sender),
receiver_commitment: commit(&receiver),
nullifier: nullify(&prev_state),
},
&Witness {
sender_private_key,
amount,
receiver_address,
},
).await?;
// Validators verify without knowing sender, receiver, or amount3. Credential Verification
// Prove KYC tier without revealing full identity
let proof = zk_system.prove(
CircuitType::IdentityProof,
&PublicInputs {
min_kyc_tier: 2,
age_requirement: 18,
},
&Witness {
did: "did:tenzro:human:...",
kyc_tier: 3,
age: 25,
credential_proof,
},
).await?;
// Service provider verifies requirements met without seeing full DIDWeb Verification API
# Verify ZK proof via HTTP
curl -X POST http://localhost:8080/verify/zk-proof \
-H "Content-Type: application/json" \
-d '{
"proof": "0xabc123...",
"public_inputs": ["0x123", "0x456"],
"circuit_type": "InferenceVerification"
}'
# Response
{
"valid": true,
"circuit_type": "InferenceVerification",
"verification_time_ms": 45
}Performance Benchmarks
Circuit: InferenceVerificationCircuit
Constraints: 1,000,000
CPU Proving (Intel Xeon):
- Single proof: 1.2s
- Batch (10): 12s
GPU Proving (NVIDIA H100):
- Single proof: 120ms (10x faster)
- Batch (100): 1.5s (80x faster)
Verification:
- CPU: 3-5ms
- Proof size: 128 bytes (uncompressed)
- Proof size: 40 bytes (heavy compression)