Privacy Model
How Veil achieves privacy
Veil uses a combination of cryptographic primitives to ensure privacy for transactions on Solana.
1. Shielding (Depositing)
When you shield assets, you convert public tokens into private commitments:
Process:
- User specifies
amountto shield and generates a randomblindingfactor (32 bytes) - SDK computes Pedersen commitment: (on BN254 G1)
- Commitment is sent to the Solana program along with the public token transfer
- Program verifies the token transfer and adds to the on-chain Merkle tree
- User stores the
amount,blinding, andleaf_indexlocally to reconstruct the note later
Privacy Properties:
- Amount is perfectly hidden (information-theoretic) by the blinding factor
- Commitment is publicly visible but reveals nothing about the amount
- Only the user knows the secret data needed to spend this note later
On-Chain Visibility:
- Commitment value (32 bytes)
- Merkle tree insertion (leaf index increments)
- Public token transfer into the pool
2. Private Transfer
The core privacy operation - spending a note to create a new one for a recipient:
Off-Chain (Client-Side):
- User selects a note to spend (knows
secret,amount,blinding,leaf_index) - Derives
spending_key = Poseidon(secret, "NYX_SPENDING_KEY") - Computes
nullifier = Poseidon(spending_key, Hash(leaf_index || "NYX_NULLIFIER")) - Generates new
output_blindingfor the recipient's commitment - Constructs Merkle proof showing input commitment exists in tree
- Generates zkSNARK proof (~5-10 seconds) demonstrating:
- Input commitment exists in Merkle tree
- Nullifier is correctly derived
- Output commitment preserves the amount
- All operations follow protocol rules
- Encrypts note data for recipient using ECDH + ChaCha20-Poly1305
- Submits proof, nullifier, new commitment, and encrypted note to Solana
On-Chain (Solana Program):
- Verifies zkSNARK proof (~200k compute units)
- Checks nullifier has never been seen (PDA doesn't exist)
- Creates nullifier PDA to mark it as spent
- Adds new commitment to Merkle tree
- Updates root history (maintains last 30 roots)
Privacy Properties:
- No one can determine which commitment was spent (unlinkability)
- Amount remains hidden throughout the transfer
- Recipient can discover their note by attempting to decrypt the encrypted note data
- Transaction graph is completely broken - observers cannot link inputs to outputs
On-Chain Visibility:
- Nullifier (32 bytes, appears random)
- New commitment (32 bytes, appears random)
- zkSNARK proof (256 bytes)
- Encrypted note (96 bytes)
3. Unshielding (Withdrawing)
Similar to a private transfer, but outputs to a public Solana address:
Process:
- Similar to private transfer proof generation
- Instead of creating a new private commitment, specify a public recipient address
- zkSNARK proves valid note ownership and correct nullifier
- On-chain program verifies proof, marks nullifier as spent, and transfers tokens to recipient
Privacy Trade-offs:
- Reveals the withdrawal amount and recipient address
- Observers can see that "someone" withdrew X tokens
- Cannot link the withdrawal to the original deposit (still privacy-preserving for sender identity)
- Creates a potential graph analysis vector if recipient is known
Veil provides strong privacy guarantees backed by cryptographic primitives:
| Privacy Property | Implementation | Cryptographic Basis |
|---|---|---|
| Amount Privacy | Hidden in Pedersen commitments | Perfectly hiding (information-theoretic) |
| Sender Privacy | Unlinkable nullifiers | Poseidon hash provides pseudorandomness |
| Recipient Privacy | New commitment appears random | Cannot link to sender or amount without secret |
| Transaction Graph Privacy | No visible links between inputs/outputs | Zero-knowledge proofs + unlinkable nullifiers |
| Double-Spend Prevention | PDA-based nullifier tracking | Each nullifier can only be recorded once |
| Amount Integrity | zkSNARK constraint enforcement | Circuit proves input_amount = output_amount |
| Feature | Implementation | Details |
|---|---|---|
| Double-spend prevention | Nullifier PDA with Anchor init constraint | Attempting to reuse a nullifier fails at PDA creation |
| Front-running protection | 30-root history window | Proofs valid for any of the last 30 roots (~5-10 minutes) |
| Amount integrity | zkSNARK circuit constraints | Mathematically enforced, not trust-based |
| Membership proof | Merkle path verification in circuit | Depth-20 Poseidon Merkle tree |
| Unlinkability | Circuit-safe nullifier derivation | Two-step Poseidon hashing prevents correlation |
| Recipient Discovery | ECDH + ChaCha20-Poly1305 encryption | Only recipient can decrypt note data |
| Forward Secrecy | Ephemeral keys for note encryption | Compromising long-term key doesn't reveal past notes |
While Veil provides strong cryptographic privacy, users should be aware of these limitations:
1. Timing Analysis
Risk: Observers can correlate deposits and withdrawals by timing.
- If Alice deposits 100 SOL and Bob withdraws 100 SOL moments later, pattern analysis may suggest a link
- Larger anonymity sets and time delays improve privacy
Mitigation:
- Wait before withdrawing
- Use standard denomination amounts (e.g., 1, 10, 100 SOL)
- Perform multiple transactions to mix timing
2. Amount Pattern Detection
Risk: Unique amounts can be fingerprints.
- Depositing exactly 123.456789 SOL and withdrawing the same amount reveals linkage
- Even with hidden amounts, the rarity of specific values is a risk
Mitigation:
- Round to common denominations
- Split large amounts into multiple notes
- Wait for larger anonymity sets before withdrawal
3. Graph Analysis at Boundaries
Risk: Deposit and withdrawal points are public.
- The act of shielding reveals your address and amount
- Unshielding reveals recipient address and amount
- Only the private transfer operations are fully hidden
Mitigation:
- Use multiple addresses for shielding/unshielding
- Employ relayers to hide IP and submission metadata
- Perform multiple internal transfers before unshielding
4. IP Address Exposure
Risk: Submitting transactions reveals your IP to RPC nodes.
- RPC providers can log submission IPs
- Network-level observers can correlate IPs with transaction timing
Mitigation:
- Use the relayer network (see Relayers)
- Connect through VPN/Tor for additional IP privacy
- Relayers submit on your behalf, breaking the IP-to-transaction link
5. Side-Channel Attacks
Risk: Information leakage through non-cryptographic channels.
- Proof generation timing may leak information about secret values
- Memory access patterns during cryptographic operations
- Browser/device fingerprinting when using web interfaces
Mitigation:
- Constant-time implementations in Rust core
- Use trusted local environments for sensitive operations
- Hardware wallets for secret key management (future)
Veil maintains a 30-root history buffer to protect against front-running attacks:
The Problem:
- User generates proof against current Merkle root
R1 - Proof generation takes 5-10 seconds
- Another transaction is included, updating root to
R2 - User's proof is now invalid (references old root)
The Solution:
- Solana program stores the last 30 Merkle roots in a circular buffer
- Proofs are valid for any root in this history window
- With ~2 second block times, this provides ~1 minute of validity
- Sufficient time for proof generation and submission without racing against tree updates
Implementation:
root_history: [MerkleRoot; 30]
current_root_index: u8During verification, the program checks if the proof's merkle_root matches any of the 30 stored roots.
Privacy strength depends on the anonymity set - the number of indistinguishable commitments:
Current Status:
- Merkle tree depth: 20 levels (~1 million potential leaves)
- Active commitments: Depends on usage
- Larger sets = stronger privacy
Best Practices:
- More users = better privacy for everyone
- Wait for pool to grow before making privacy-critical transactions
- Contribute to the anonymity set by using the protocol regularly
See also:
- Note Encryption - How recipients discover their notes
- Relayers - IP privacy and gas abstraction
- zkSNARKs - Cryptographic proof system details
On This Page
- Privacy Model
- Core Mechanisms
- 1. Shielding (Depositing)
- 2. Private Transfer
- 3. Unshielding (Withdrawing)
- Privacy Guarantees
- Security Guarantees
- Privacy Limitations
- 1. Timing Analysis
- 2. Amount Pattern Detection
- 3. Graph Analysis at Boundaries
- 4. IP Address Exposure
- 5. Side-Channel Attacks
- Front-Running Protection
- Anonymity Set Size