> ## Documentation Index
> Fetch the complete documentation index at: https://docs.x0protocol.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# x0-zk-verifier

> On-chain Groth16 zero-knowledge proof verification for confidential transfers over the Ristretto255 curve.

**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 $P$ is correctly derived: $P = s \cdot H$ where $s$ is the secret scalar and $H$ 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 $a$ from encrypted balance $E$ is valid — i.e., the remaining balance is non-negative and bounded by $2^{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.

```rust theme={null}
verify_pubkey_validity(proof_data: Vec<u8>, elgamal_pubkey: [u8; 32])
```

### `verify_withdraw`

Verifies a withdrawal proof and records the verified amount.

```rust theme={null}
verify_withdraw(
    proof_data: Vec<u8>,
    amount: u64,
    new_decryptable_balance: [u8; 36]
)
```

### `verify_zero_balance`

Verifies that an encrypted balance is zero.

```rust theme={null}
verify_zero_balance(proof_data: Vec<u8>)
```

### `verify_transfer`

Verifies a confidential transfer proof and records the amount and recipient.

```rust theme={null}
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:

```rust theme={null}
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:

$$
\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:

$$
0 \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

| Code     | Name                      | Description                      |
| -------- | ------------------------- | -------------------------------- |
| `0x1700` | `ProofVerificationFailed` | ZK proof did not verify          |
| `0x1701` | `InvalidProofData`        | Proof data format invalid        |
| `0x1703` | `InvalidProofType`        | Unknown proof type               |
| `0x1704` | `ProofExpired`            | Proof context older than 300s    |
| `0x1710` | `AmountTooLarge`          | Amount exceeds $2^{48} - 1$      |
| `0x1711` | `InvalidElGamalPubkey`    | ElGamal public key invalid       |
| `0x1713` | `ProofSizeMismatch`       | Proof data wrong length          |
| `0x1720` | `ArithmeticOverflow`      | Integer overflow in verification |
