Solana Devnet · Privacy Vault

Zero-knowledge private payments on Solana

Sunflower shields SOL using Twisted ElGamal on Ristretto255, sigma proofs verified on-chain (same family as Token-2022 confidential transfers), and an optional Arcium MXE hybrid path for confidential compute. Real tokens sit in an SPL vault; balances are encrypted ciphertexts — not SNARKs, but rigorous elliptic-curve zero-knowledge.

ElGamalSigma NIZKHomomorphicArcium MXEAnchorRust prover

Dual-path architecture

Two parallel privacy designs share the same frontend. Path A is the account model you use in the simple wallet; Path B adds an Umbra-style UTXO mixer with Merkle commitments.

PATH A — ACCOUNT MODEL

ElGamal shielded accounts

  • • SPL SOL in vault_authority ATA
  • • Per-user ShieldedAccount PDA with ciphertext_lo / ciphertext_hi
  • shield · transfer_private · unshield
  • • Pending credits merged via apply_pending_balance

Program: DNgdrd77NG3z17F5wJJFuGYHKk9dF9HccKKpCQSXos1S

PATH B — UTXO MIXER

Commitments + nullifiers

  • • SHA256 commitments in depth-10 Merkle tree (1,024 leaves)
  • deposit_utxo → SPL into pool + insert leaf
  • claim_utxo with Merkle proof + nullifier spend
  • • No ElGamal sigma proofs on this path — hash privacy instead

See advanced /dev panel for full mixer flow.

Cryptography & proofs

Privacy comes from encrypting 48-bit amounts as two 16-bit ElGamal chunks, then proving statements about ciphertexts without revealing keys or amounts. Proofs are sigma protocols made non-interactive via Fiat–Shamir — verified with solana-curve25519 syscalls on-chain.

functionsTwisted ElGamal encryption

Curve: Ristretto255 · G = basepoint · H = HashToPoint("privacy_vault_H_generator_v1")

Keygen:    sk ← random,  pk = sk · G
Encrypt m: d = r · G,  e = m · H + r · pk   →  Ciphertext (d, e)  [64 bytes]
Homomorphic: Enc(m1) + Enc(m2) = Enc(m1 + m2)
Balance:     ciphertext_lo ← amount & 0xFFFF
             ciphertext_hi ← amount >> 16

PubkeyValidityProof

Prover knows sk such that pk = sk · G

s · G == R + c · pk

Used in: initialize_shielded_account, shield, transfer_private

CiphertextEqualityProof

Two ciphertexts under different keys encrypt the same amount m

Four Ristretto equations (r1…r4, s1…s3)

Used in: Private send — amount hidden, balance math enforced homomorphically

Private transfer verification

After sigma proofs pass, the program enforces homomorphic balance consistency (not itself a ZK proof):

new_source + transfer == old_source   (for lo and hi chunks)

Off-chain: crypto_helper plan-transfer → CiphertextEqualityProof + new ciphertexts
On-chain:  transfer_private verifies proofs + updates encrypted balances
Amount:    NOT visible on-chain during private send

End-to-end pipeline

Four execution zones. The browser never sends your ElGamal secret key to the server; heavy math runs in Rust via Next.js API routes calling crypto_helper.

01
Browser

Phantom signs txs · ElGamal sk/pk in sessionStorage · decrypt balance locally

02
On-chain

privacy_vault program · vault_authority SPL custody · ShieldedAccount PDAs

03
Off-chain

crypto_helper: encrypt, prove_pubkey_validity, prove_ciphertext_equality, decrypt-balance

04
Arcium MXE

plan_transfer_prep circuit · hybrid path with transfer_private_arcium + replay ID

Arcium Bridge: 7Rj21r9ujnVCjHzNXahP3wYPJ8i9zzBcYpanyxGGjrtK

MXE account: 7Ab8P5AzcH7F8HhPXPXbGLtXHUwfCUwbcGA9TQWpjr4t · cluster offset 456

Technology stack

token

Solana / Anchor

On-chain program + PDAs

lock

Ristretto255 ElGamal

Twisted ElGamal ciphertexts

verified_user

Sigma proofs

Fiat–Shamir NIZKs (Token-2022 family)

memory

crypto_helper (Rust)

Off-chain prover via Next.js API

hub

Arcium MXE

Confidential compute on devnet cluster 456

account_tree

UTXO mixer (Path B)

SHA256 commitments + Merkle depth 10

enhanced_encryption

Try it on devnet

Connect Phantom on Devnet, shield SOL, send privately with sigma-verified transfers, and decrypt your balance only in your browser. Full cryptography details in finalreport.md.

Launch Privacy Vault