Deep dive into the cryptographic primitives powering Solana Confidential Balances.
Confidential Balances use three main cryptographic techniques:
| Technique | Purpose | Library |
|---|---|---|
| Twisted ElGamal | Homomorphic encryption of balances | solana-zk-sdk |
| Pedersen Commitments | Binding commitments for proofs | solana-zk-sdk |
| Sigma Protocols | Zero-knowledge proofs | solana-zk-sdk |
Standard ElGamal encrypts messages as group elements, making arithmetic impossible. Twisted ElGamal encrypts the discrete log of the message, enabling:
- Homomorphic addition:
Enc(a) + Enc(b) = Enc(a + b) - Scalar multiplication:
k * Enc(a) = Enc(k * a)
This allows updating encrypted balances without decryption.
Curve: Curve25519 (Ristretto group)
Key Generation:
secret_key s ← random scalar
public_key H = s * G (G is the generator)
Encryption of message m:
r ← random scalar (opening)
Commitment C = m * G + r * H_pedersen
Handle D = r * H
Ciphertext = (C, D)
Decryption:
m * G = C - s * D
m = discrete_log(m * G)
┌─────────────────────────────────────────────────────────────┐
│ ElGamal Ciphertext (64 bytes) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ Pedersen Commitment │ │ Decrypt Handle │ │
│ │ (32 bytes) │ │ (32 bytes) │ │
│ │ │ │ │ │
│ │ C = m*G + r*H │ │ D = r*PK │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
// Balance update after transfer
let new_sender_balance = current_balance - transfer_amount;
// Operates on encrypted values:
let new_sender_ct = sender_balance_ct - transfer_ct;
// Adding encrypted values
let total_ct = balance1_ct + balance2_ct;
// Decrypts to: balance1 + balance2Decryption requires solving discrete log: m * G = ?
For efficiency, decrypt_u32 uses a precomputed table for values 0 to 2^32. Values outside this range require expensive computation.
Practical implication: Token amounts should fit in 32 bits after decimal adjustment.
Pedersen commitments allow:
- Hiding: Commitment reveals nothing about the value
- Binding: Cannot change the committed value later
- Homomorphic: Can add commitments without revealing values
Generators: G (standard), H (nothing-up-my-sleeve point)
Commit(m, r):
C = m * G + r * H
where:
m = message (amount)
r = opening (randomness)
C = commitment
| Property | Guarantee |
|---|---|
| Hiding | Given C, cannot determine m without r |
| Binding | Cannot find (m', r') ≠ (m, r) with same C |
| Homomorphic | C(m₁, r₁) + C(m₂, r₂) = C(m₁+m₂, r₁+r₂) |
Transfer amount: 100 tokens
Sender commitment: C_s = 100*G + r_s*H
Receiver commitment: C_r = 100*G + r_r*H
Proof shows: C_s and C_r commit to same value
(without revealing 100)
All proofs use Sigma protocols (3-move interactive proofs made non-interactive via Fiat-Shamir):
┌──────────┐ commitment ┌──────────┐
│ Prover │ ──────────────────► │ Verifier │
│ │ │ │
│ │ ◄────────────────── │ │
│ │ challenge │ │
│ │ │ │
│ │ ──────────────────► │ │
│ │ response │ │
└──────────┘ └──────────┘
Non-interactive: challenge = Hash(commitment, context)
Proves: Prover knows secret key for a public key
Use case: Account configuration (proves encryption key is valid)
Statement: "I know s such that H = s * G"
Commitment: R = k * G (k random)
Challenge: c = Hash(H, R)
Response: z = k + c * s
Verify: z * G == R + c * H
Proves: A ciphertext encrypts zero
Use case: Prove account is empty for closing
Statement: "Ciphertext (C, D) encrypts 0"
If m = 0: C = r * H_ped and D = r * PK
Prove knowledge of r such that both equations hold.
Proves: ElGamal ciphertext and Pedersen commitment encode same value
Use case: Withdrawal proofs (link encrypted balance to public amount)
Statement: "C_elgamal encrypts same value as C_pedersen"
Proves: Two ciphertexts encrypt the same value
Use case: Transfer proofs (sender deduction = receiver credit)
Statement: "Ciphertext₁ and Ciphertext₂ encrypt the same m"
Proves: Committed value is in range [0, 2^n)
Use case: Prevent negative balances (prove balance ≥ 0)
Implementation: Bulletproofs-style aggregated range proof
Prove: 0 ≤ m < 2^64
Decompose: m = Σ mᵢ * 2^i where mᵢ ∈ {0, 1}
Prove each bit commitment is 0 or 1
Aggregate into single proof
Bit lengths used:
BatchedRangeProofU64: 64-bit rangeBatchedRangeProofU128: 128-bit range (for batched amounts)BatchedRangeProofU256: 256-bit range
Proves: Grouped ciphertext correctly encrypts under multiple keys
Use case: Transfer with auditor (encrypts amount for sender, receiver, auditor)
Statement: "All handles in grouped ciphertext are valid encryptions of same value"
Proves: Value is X% of base amount, capped at maximum
Use case: Fee calculation proofs
Statement: "fee = min(base * rate, cap)"
| Proof Type | Approximate Size |
|---|---|
| PubkeyValidity | ~64 bytes |
| ZeroCiphertext | ~96 bytes |
| CiphertextCommitmentEquality | ~128 bytes |
| CiphertextCiphertextEquality | ~192 bytes |
| BatchedRangeProofU64 | ~800 bytes |
| BatchedRangeProofU128 | ~1,400 bytes |
| GroupedCiphertext3HandlesValidity | ~224 bytes |
ElGamal decryption is expensive (discrete log). For efficient balance viewing by account owners, balances are also encrypted with AES.
Algorithm: AES-GCM-SIV (authenticated encryption with associated data)
Key derivation:
ae_key = HKDF(signature("AeKey"))
Encryption:
ciphertext = AES-GCM-SIV.Encrypt(ae_key, nonce, balance)
┌─────────────────────────────────────────────────────────────┐
│ Token Account Confidential State │
├─────────────────────────────────────────────────────────────┤
│ │
│ available_balance: ElGamal ciphertext │
│ (for homomorphic ops) │
│ │
│ decryptable_available_balance: AES ciphertext │
│ (for efficient viewing) │
│ │
└─────────────────────────────────────────────────────────────┘
Owner decrypts AES version for fast balance display.
Program uses ElGamal version for transfer operations.
| Data | Visibility |
|---|---|
| Transfer amounts | Encrypted (ElGamal) |
| Account balances | Encrypted (ElGamal + AES) |
| Sender/Receiver addresses | Public |
| Transaction timestamps | Public |
- Range proofs prevent negative balances
- Equality proofs ensure conservation (no token creation)
- Validity proofs ensure proper encryption
Optional auditor can decrypt all transfers:
Grouped ciphertext encrypts amount under:
1. Sender public key
2. Receiver public key
3. Auditor public key (optional)
Auditor can decrypt any transfer for compliance.
| Operation | Relative Cost |
|---|---|
| Key generation | Low |
| Encryption | Low |
| Decryption (u32) | Medium (table lookup) |
| Decryption (u64) | High (discrete log) |
| Range proof generation | Very High |
| Range proof verification | Medium |
| Equality proof generation | Medium |
| Equality proof verification | Low |
Proofs are verified by the ZK ElGamal Proof Program:
- Program ID:
ZkE1Gama1Proof11111111111111111111111111111 - Verification is ~10-50x cheaper than generation
- Large proofs stored in context state accounts