Skip to main content
Program ID: zQWSrznKgcK8aHA4ry7xbSCdP36FqgUHj766YM3pwre x0-zk-verifier provides on-chain verification of zero-knowledge proofs generated by the x0-zk-proofs WASM library. It validates Groth16 proofs over the Ristretto255 curve for confidential token operations.

Problem Statement

Token-2022’s ConfidentialTransfer extension encrypts balances using Twisted ElGamal, but the protocol still needs to enforce correctness constraints:
  1. Public key validity — The ElGamal public key is correctly derived from a secret scalar
  2. Zero balance — An encrypted balance is actually zero (required for account closure)
  3. Withdraw correctness — A withdrawal amount is valid against the encrypted balance
  4. Transfer correctness — Encrypted sender/receiver ciphertexts are consistent
Each of these requires a zero-knowledge proof that reveals nothing about the underlying amounts.

Proof Types

PubkeyValidityProof

Proves that an ElGamal public key PP is correctly derived: P=sHP = s \cdot H where ss is the secret scalar and HH is the Ristretto255 generator. Size: 64 bytes

ZeroBalanceProof

Proves that an ElGamal ciphertext encrypts the value 0 without revealing the randomness. Size: 96 bytes

WithdrawProof

Proves that withdrawing amount aa from encrypted balance EE is valid — i.e., the remaining balance is non-negative and bounded by 24812^{48} - 1. Size: 160 bytes

TransferProof

Proves that a confidential transfer correctly re-encrypts the amount under the recipient’s public key, and the sender’s remaining balance is non-negative. Size: 288 bytes

Instructions

verify_pubkey_validity

Verifies an ElGamal public key validity proof and stores the result in a ProofContext PDA.
verify_pubkey_validity(proof_data: Vec<u8>, elgamal_pubkey: [u8; 32])

verify_withdraw

Verifies a withdrawal proof and records the verified amount.
verify_withdraw(
    proof_data: Vec<u8>,
    amount: u64,
    new_decryptable_balance: [u8; 36]
)

verify_zero_balance

Verifies that an encrypted balance is zero.
verify_zero_balance(proof_data: Vec<u8>)

verify_transfer

Verifies a confidential transfer proof and records the amount and recipient.
verify_transfer(
    proof_data: Vec<u8>,
    amount: u64,
    recipient: Pubkey
)

State Account

ProofContext

Each verified proof creates a ProofContext PDA that serves as an on-chain receipt:
pub struct ProofContext {
    pub version: u8,
    pub proof_type: ProofType,
    pub verified: bool,
    pub owner: Pubkey,
    pub verified_at: i64,
    pub amount: Option<u64>,
    pub recipient: Option<Pubkey>,
    pub elgamal_pubkey: Option<[u8; 32]>,
    pub mint: Pubkey,
    pub token_account: Pubkey,
    pub bump: u8,
}
PDA Seeds: ["proof-context", owner, mint]

Proof Freshness

Proof contexts expire after 300 seconds (PROOF_CONTEXT_FRESHNESS_SECONDS). The is_fresh() method checks: current_timeverified_atΔproof=300s\text{current\_time} - \text{verified\_at} \leq \Delta_{\text{proof}} = 300\text{s} This prevents replay attacks — a proof verified 5 minutes ago cannot be used to authorize a new transfer.

Amount Bounds

All proof amounts are bounded by: 0a2481=281,474,976,710,6550 \leq a \leq 2^{48} - 1 = 281{,}474{,}976{,}710{,}655 This constraint comes from the Token-2022 ConfidentialTransfer extension’s use of 48-bit range proofs.

Verification Flow

Client                     x0-zk-proofs (WASM)         Solana
  │                              │                        │
  │── generate_withdraw_proof ──▶│                        │
  │◀── { proofData, newBal } ───│                        │
  │                              │                        │
  │── verify_withdraw(proof) ────────────────────────────▶│
  │                              │         x0-zk-verifier │
  │                              │    ┌── verify proof ───┤
  │                              │    │   store context   │
  │◀──────────── tx signature ───────────────────────────│
  │                              │                        │
  │── withdraw(amount) ─────────────────────────────────▶│
  │                              │         x0-token       │
  │                              │    ┌── check context ──┤
  │                              │    │   is_fresh()?     │
  │                              │    │   execute         │
  │◀──────────── confirmed ──────────────────────────────│

Error Codes

CodeNameDescription
0x1700ProofVerificationFailedZK proof did not verify
0x1701InvalidProofDataProof data format invalid
0x1703InvalidProofTypeUnknown proof type
0x1704ProofExpiredProof context older than 300s
0x1710AmountTooLargeAmount exceeds 24812^{48} - 1
0x1711InvalidElGamalPubkeyElGamal public key invalid
0x1713ProofSizeMismatchProof data wrong length
0x1720ArithmeticOverflowInteger overflow in verification
Last modified on February 8, 2026