Tenzro Testnet is live. Get testnet TNZO

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.

Public inputs: model_hash, input_hash, output_hash
Private inputs: model_weights, input_data, computation_trace

SettlementProofCircuit

Proves payment settlement occurred correctly with proper fee calculation.

Public inputs: settlement_id, amount, fee
Private inputs: payer_signature, payee_address, timestamp

IdentityProofCircuit

Proves identity ownership without revealing private keys or full DID document.

Public inputs: did_hash, credential_hash
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 uncompressed

Proof 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 amount

3. 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 DID

Web 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)