Docs
/concepts
/Privacy Model

Privacy Model

How Veil achieves privacy

Privacy Model

Veil uses a combination of cryptographic primitives to ensure privacy for transactions on Solana.

Core Mechanisms

1. Shielding (Depositing)

When you shield assets, you convert public tokens into private commitments:

Process:

  1. User specifies amount to shield and generates a random blinding factor (32 bytes)
  2. SDK computes Pedersen commitment: C=amountG+blindingHC = amount \cdot G + blinding \cdot H (on BN254 G1)
  3. Commitment CC is sent to the Solana program along with the public token transfer
  4. Program verifies the token transfer and adds CC to the on-chain Merkle tree
  5. User stores the amount, blinding, and leaf_index locally 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 CC (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):

  1. User selects a note to spend (knows secret, amount, blinding, leaf_index)
  2. Derives spending_key = Poseidon(secret, "NYX_SPENDING_KEY")
  3. Computes nullifier = Poseidon(spending_key, Hash(leaf_index || "NYX_NULLIFIER"))
  4. Generates new output_blinding for the recipient's commitment
  5. Constructs Merkle proof showing input commitment exists in tree
  6. 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
  7. Encrypts note data for recipient using ECDH + ChaCha20-Poly1305
  8. Submits proof, nullifier, new commitment, and encrypted note to Solana

On-Chain (Solana Program):

  1. Verifies zkSNARK proof (~200k compute units)
  2. Checks nullifier has never been seen (PDA doesn't exist)
  3. Creates nullifier PDA to mark it as spent
  4. Adds new commitment to Merkle tree
  5. 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:

  1. Similar to private transfer proof generation
  2. Instead of creating a new private commitment, specify a public recipient address
  3. zkSNARK proves valid note ownership and correct nullifier
  4. 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

Privacy Guarantees

Veil provides strong privacy guarantees backed by cryptographic primitives:

Privacy PropertyImplementationCryptographic Basis
Amount PrivacyHidden in Pedersen commitmentsPerfectly hiding (information-theoretic)
Sender PrivacyUnlinkable nullifiersPoseidon hash provides pseudorandomness
Recipient PrivacyNew commitment appears randomCannot link to sender or amount without secret
Transaction Graph PrivacyNo visible links between inputs/outputsZero-knowledge proofs + unlinkable nullifiers
Double-Spend PreventionPDA-based nullifier trackingEach nullifier can only be recorded once
Amount IntegrityzkSNARK constraint enforcementCircuit proves input_amount = output_amount

Security Guarantees

FeatureImplementationDetails
Double-spend preventionNullifier PDA with Anchor init constraintAttempting to reuse a nullifier fails at PDA creation
Front-running protection30-root history windowProofs valid for any of the last 30 roots (~5-10 minutes)
Amount integrityzkSNARK circuit constraintsMathematically enforced, not trust-based
Membership proofMerkle path verification in circuitDepth-20 Poseidon Merkle tree
UnlinkabilityCircuit-safe nullifier derivationTwo-step Poseidon hashing prevents correlation
Recipient DiscoveryECDH + ChaCha20-Poly1305 encryptionOnly recipient can decrypt note data
Forward SecrecyEphemeral keys for note encryptionCompromising long-term key doesn't reveal past notes

Privacy Limitations

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)

Front-Running Protection

Veil maintains a 30-root history buffer to protect against front-running attacks:

The Problem:

  1. User generates proof against current Merkle root R1
  2. Proof generation takes 5-10 seconds
  3. Another transaction is included, updating root to R2
  4. 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: u8

During verification, the program checks if the proof's merkle_root matches any of the 30 stored roots.

Anonymity Set Size

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: