Skip to content

gitteri/confidential-balances-exploration

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Confidential Balances Workshop

A comprehensive workshop for understanding and implementing Solana's Confidential Balances feature in Token-2022 (Token Extensions).

What are Confidential Balances?

Confidential Balances is a set of Token-2022 extensions that enable privacy on Solana asset transfers. Instead of all token amounts being visible on-chain, balances and transfer amounts are encrypted using advanced cryptographic techniques.

Token-2022 Extensions Involved

Confidential Balances uses the Token-2022 (Token Extensions) program, which allows modular features to be added to tokens.

Extension Extension Type Applied To Required Purpose
ConfidentialTransferMint ExtensionType(11) Mint Yes Configures mint-level settings (auditor, authority, auto-approval)
ConfidentialTransferAccount ExtensionType(12) Token Account Yes Stores encrypted balances and encryption keys
ConfidentialTransferFeeConfig ExtensionType(13) Mint Optional Enables confidential transfer fee calculation
ConfidentialMintBurn ExtensionType(33) Mint Optional Allows private token issuance (disables deposit/withdraw)

Key Points:

  • Extensions must be initialized at creation time (cannot be added later)
  • Account space must be allocated to fit extension data
  • Token-2022 Program ID: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Privacy Levels

Confidential Balances support varying degrees of configurable privacy:

  1. Disabled - No confidentiality (standard SPL tokens)
  2. Whitelisted - Only approved accounts can use confidential transfers
  3. Opt-in - Users choose to enable confidentiality
  4. Required - All transfers must be confidential

Cryptographic Foundations

The privacy is achieved through:

  • Twisted ElGamal Encryption - Homomorphic encryption enabling arithmetic on encrypted data (Curve25519/Ristretto)
  • AES-GCM-SIV - Authenticated encryption for efficient balance viewing by account owners
  • Pedersen Commitments - Binding, hiding commitments for zero-knowledge proofs
  • Sigma Protocols (ZKPs) - Proves validity without revealing amounts

ZK ElGamal Proof Program

Confidential transfers require zero-knowledge proofs verified by a dedicated Solana program:

  • Program ID: ZkE1Gama1Proof11111111111111111111111111111
  • Purpose: Verifies equality, range, and validity proofs on-chain
  • Integration: Token-2022 instructions reference proof context accounts

Repository Structure

.
├── src/                            # Core implementation
│   ├── configure.rs                # Configure accounts for confidential transfers
│   ├── deposit.rs                  # Deposit from public to confidential
│   ├── apply_pending.rs            # Apply pending to available balance
│   ├── withdraw.rs                 # Withdraw from confidential to public
│   ├── transfer.rs                 # Confidential transfer between accounts
│   └── bin/
│       └── demo-server.rs          # HTTP API wrapping the modules (used by the slide-deck demo)
├── examples/
│   ├── run_transfer.rs             # End-to-end transfer with balance display
│   └── get_balances.rs             # Query and decrypt all balance types
├── tests/
│   ├── integration_test.rs         # Integration tests for all operations
│   └── common/                     # Test utilities
├── docs/
│   ├── guides/
│   │   ├── product-guide.md        # High-level product overview
│   │   └── wallet-integration.md   # Guide for wallet developers
│   ├── reference/
│   │   ├── token-extensions.md     # Token-2022 program architecture
│   │   ├── cryptography.md         # Encryption & proof details
│   │   └── rust-deps.md            # Rust crate reference
│   └── FAQ.md                      # Troubleshooting & common issues
└── README.md                       # This file

Key Dependencies

Rust Crates

# Solana core. zk-sdk is at 6.0.1 because the deployed devnet ZK ElGamal Proof
# program now expects 6.0.1-format proofs.
solana-sdk = "3.0.0"
solana-client = "3.1.6"
solana-zk-sdk = "6.0.1"

# SPL Token-2022. proof-extraction stays at 0.5.1 (matches spl-token-2022's
# transitive zk-sdk 4.0) so we can name the legacy `ProofLocation` type at the
# instruction boundary. proof-generation is on 0.6.0 (zk-sdk 6.0.1) for the
# actual proof bytes.
spl-token-2022 = "10.0.0"
spl-token-client = "0.18.0"
spl-associated-token-account = "8.0.0"
spl-token-confidential-transfer-proof-generation = "0.6.0"
spl-token-confidential-transfer-proof-extraction = "0.5.1"

# Bypass-mode helpers (see "Bypass mode" below)
solana-zk-elgamal-proof-interface = "0.1.2"
solana-zk-sdk-pod = "0.1.1"
solana-address = "2.6"
bytemuck = "1.25"

Bypass mode (the 4.0 ↔ 6.0.1 boundary)

This split is a stopgap, not a design choice. spl-token-client and spl-token-2022 should be on the agave v4 beta / rc crates (which target solana-zk-sdk = 6.0.1 natively), but those Agave v4 crates haven't been published yet. Until they are, spl-token-2022 = 10.0.0 transitively pulls solana-zk-sdk = 4.0 for its public API while the deployed ZK ElGamal Proof program on devnet verifies 6.0.1-format proofs. This repo bridges the gap:

  1. Derive ElGamal + AES keys with solana-zk-sdk = 6.0.1 directly.
  2. Generate proofs with proof-generation = 0.6.0.
  3. Pre-verify each proof into a ProofContextState account.
  4. Reference those accounts in spl-token-2022's instruction builders via ProofLocation::ContextStateAccount — a phantom-typed Pubkey that carries no proof bytes, so the version mismatch never crosses the FFI.
  5. Cross the type boundary only at on-chain PODs (ElGamal pubkey/ciphertext, AES ciphertext) using zero-copy byte casts, since their wire format is identical between 4.0 and 6.0.1.

Every module in src/ follows this pattern; *Legacy aliases mark the 4.0 side of the boundary. Once the v4 crates are published, the bypass and the *Legacy aliases can be deleted and everything routes through 6.0.1 directly.

Quick Start

Prerequisites

  • Solana CLI 2.1.13+ (solana --version)
  • SPL Token CLI 5.1.0+ (spl-token --version)
  • Rust 1.70+

Running the Example Implementation

This repository includes a complete Rust implementation of all confidential transfer operations:

# Start local test validator
solana-test-validator --quiet --reset &

# Run all integration tests
cargo test --test integration_test

# Run a specific test
cargo test test_confidential_transfer_between_accounts -- --nocapture

# Run end-to-end transfer example (shows balance changes throughout)
SOLANA_RPC_URL=https://zk-edge.surfnet.dev:8899 \
PAYER_KEYPAIR=$(cat ~/.config/solana/id.json) \
cargo run --example run_transfer

# Query and display encrypted balances
SOLANA_RPC_URL=https://zk-edge.surfnet.dev:8899 \
MINT_ADDRESS=<mint> \
OWNER_KEYPAIR=$(cat ~/.config/solana/id.json) \
cargo run --example get_balances

Available Operations:

  • src/configure.rs - Configure token accounts for confidential transfers
  • src/deposit.rs - Deposit from public to confidential balance
  • src/apply_pending.rs - Apply pending balance to available balance
  • src/withdraw.rs - Withdraw from confidential to public balance
  • src/transfer.rs - Transfer confidentially between accounts (with proof context state accounts)

Examples:

  • examples/run_transfer.rs - Complete end-to-end transfer with balance display at each step
  • examples/get_balances.rs - Query and decrypt all balance types (public, pending, available)

All operations are tested in tests/integration_test.rs with complete end-to-end flows.

Try it with CLI

# Run the official confidential transfer example script
curl -sSf https://raw.githubusercontent.com/solana-program/token-2022/main/clients/cli/examples/confidential-transfer.sh | bash

Demo server (for the zkproof8 slide deck)

The demo-server binary wraps the modules above in a small HTTP API so a webapp deck can drive a live confidential transfer on stage. Single-tenant, in-memory, all keypairs in .env.

It's the backend for the zkproof8 talk slide deck: gitteri/zkproof8-talk — the webapp there calls these endpoints to drive the live transfer.

One-time setup:

# Generate a fresh .env with five keypairs (PAYER / MINT / SENDER / RECEIVER / AUDITOR)
cargo run --bin demo-server -- generate-env > .env

# The output prints PAYER pubkey to stderr — fund it.
solana airdrop 5 <PAYER_PUBKEY> --url https://api.devnet.solana.com

Run the server:

cargo run --bin demo-server
# listens on http://localhost:8088

Endpoints:

Method Path Body Notes
GET /demo/health { ok, validator_reachable, mint, port, rpc_url }
GET /demo/state full ledger snapshot for the four-column slide
POST /demo/init idempotent: mint if missing, configure ATAs, top up sender
POST /demo/transfer { "amount_ui": 250000 } opt. runs the full confidential transfer flow
POST /demo/apply-pending { "account": "sender"|"receiver" } moves pending balance to available

SOLANA_RPC_URL selects devnet or local (surfpool, etc). All demo state resets when keypairs in .env are rotated; soft reset on devnet just re-runs /demo/init.

Core Operations Flow

┌─────────────────────────────────────────────────────────────┐
│                    CONFIDENTIAL TRANSFER FLOW               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Sender                                     Recipient       │
│    │                                           │            │
│    │  1. Deposit (public → pending)            │            │
│    │  ──────────────────────────►              │            │
│    │                                           │            │
│    │  2. Apply (pending → available)           │            │
│    │  ──────────────────────────►              │            │
│    │                                           │            │
│    │  3. Transfer (with ZK proofs)             │            │
│    │  ─────────────────────────────────────────►            │
│    │                                           │            │
│    │                              4. Apply     │            │
│    │                              (pending →   │            │
│    │                               available)  │            │
│    │                                           │            │
│    │                              5. Withdraw  │            │
│    │                              (available → │            │
│    │                               public)     │            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Key Concepts

Balance Types

Balance Type Visibility Purpose
Public Visible on-chain Standard SPL token balance
Pending Encrypted Incoming transfers waiting to be applied
Available Encrypted Usable confidential balance for transfers

Encryption Keys

Each confidential token account has two encryption keys derived from the owner's signature:

  1. ElGamal Keypair - Used for transfer encryption (derived from signing "ElGamalSecretKey")
  2. AES Key - Used for balance decryption (derived from signing "AeKey")

ZK Proofs Required for Transfers

Proof Type Purpose Size
Equality Proof Proves two ciphertexts encrypt the same value Small
Ciphertext Validity Proves ciphertexts are properly generated Small
Range Proof Proves value is in range [0, u64::MAX] Large

Proof Context State Accounts: To avoid transaction size limitations, each proof is pre-verified into a temporary on-chain account and the transfer instruction references it via ProofLocation::ContextStateAccount. The implementation in src/transfer.rs packs the full flow into 3 transactions:

  1. Tx 1: create all three proof accounts (equality / validity / range) and verify the validity proof.
  2. Tx 2: verify the range proof on its own — ~1006-byte ix, the binding constraint on transaction size.
  3. Tx 3: verify the equality proof, run inner_transfer, and close all three proof accounts to reclaim rent.

The proof accounts use the payer (not the sender) as the context-state authority. This keeps the sender out of the verify txs' account_keys, saving 32 bytes per tx — the difference between fitting and overflowing the 1232-byte legacy tx-size limit on the range-verify tx. The sender still signs Tx 3 because it's the transfer's token-account authority.

Resources

Official Documentation

Guides & Tutorials

Code Repositories

Sample Transactions (Devnet)

Documentation

Guides

  1. Product Guide - Understanding the product from a high level
  2. Wallet Integration - Integration patterns for wallet developers

Technical Reference

  1. Token Extensions Architecture - Token-2022 program-level details
  2. Cryptography Reference - Deep dive into the crypto primitives
  3. Rust Dependencies - Using the Rust crates
  4. JS/WASM Clients - JavaScript and WASM SDK reference

Troubleshooting

  1. FAQ & Troubleshooting - Common issues and solutions

License

This workshop material is provided for educational purposes.

About

Learn Solana Confidential Balances: Complete workshop covering Token-2022 extensions, zero-knowledge proofs, and private transfers with Rust/JS examples

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages