CORE FEATURE

Privacy Pool

A ZK-UTXO privacy pool — deposit any amount, withdraw with a FFLONK proof. Split withdrawals prevent amount fingerprinting. No fixed denominations required.

The Fan-In Problem

Stealth transfers give every payment a unique, unlinkable address. But once you claim those payments, all roads lead to your real wallet. An observer watching the claim address sees 10 inbound transactions from 10 different stealth addresses — and immediately knows those wallets belong to you.

DustPool V2 solves this with a ZK-UTXO model. You deposit any amount — ETH or ERC-20 tokens — into a shared pool. Each deposit creates a UTXO-style note with a Poseidon commitment. To withdraw, you generate a FFLONK proof (no trusted setup) that proves you own valid notes without revealing which ones. The 2-in-2-out circuit consumes input notes and creates change notes, just like Bitcoin's UTXO model but with full privacy.

How the Privacy Pool Works

PRIVACY POOL / MERKLE TREE
ROOTdepdepdepdepdep
ANONYMITY SET
1,048,576
DEPTH 20 · 2²⁰ ≈ 1,048,576 LEAVES
GROTH16BN254
  1. 01

    Deposit and create a UTXO note

    Your browser generates a random blinding factor and computes a Poseidon commitment: C = Poseidon(ownerPubKey, amount, asset, chainId, blinding). Call DustPoolV2.deposit(commitment) or depositERC20(token, amount, commitment). The commitment is added to the relayer's off-chain Merkle tree. Your note (amount, blinding, asset, commitment) is encrypted with AES-256-GCM and stored in IndexedDB — not plaintext localStorage.
  2. 02

    Notes accumulate (UTXO model)

    Each deposit creates an independent note. Unlike V1's fixed-amount mixer, you can deposit any amount at any time. Notes are like Bitcoin UTXOs — they have a specific value and are consumed whole when spent. The “change” from a partial withdrawal becomes a new output note.
  3. 03

    Generate a FFLONK proof (in-browser)

    The browser runs the 2-in-2-out transaction circuit (~12,400 constraints) to produce a FFLONK proof. Two input notes are consumed, two output notes are created (one for the withdrawal amount, one for change). Public signals: merkleRoot, null0, null1, outC0, outC1, pubAmount, pubAsset, recipient, chainId. The FFLONK system requires no trusted setup ceremony.
  4. 04

    Submit to relayer for on-chain verification

    The proof is sent to the relayer (same-origin Next.js API at /api/v2/withdraw). The relayer screens the recipient against the Chainalysis sanctions oracle, then submits to DustPoolV2.withdraw(). The contract verifies the FFLONK proof, checks nullifier freshness, validates chainId binding, confirms solvency, marks nullifiers spent, and transfers funds.
  5. 05

    Split withdrawals for denomination privacy (optional)

    To prevent amount fingerprinting, use the 2-in-8-out split circuit (~32,074 constraints). The denomination engine automatically breaks your withdrawal into common ETH chunks (10, 5, 3, 2, 1, 0.5, 0.3, etc.). Each chunk is submitted as a separate transaction with randomized timing — an observer sees only standard-looking amounts with no pattern.

Circuit Details

PropertyValue
Proof systemFFLONK (no trusted setup, BN254 curve)
Hash functionPoseidon (ZK-friendly, ~5 constraints per hash)
Transaction circuit2-in-2-out, ~12,400 constraints, 9 public signals
Split circuit2-in-8-out, ~32,074 constraints, 15 public signals
Merkle tree depth20 (2²⁰ ≈ 1,048,576 leaves)
Merkle tree locationOff-chain, relayer-maintained (verified via root history)
Proving environmentIn-browser via snarkjs + WASM
Proof generation time~2–3 seconds (transaction), ~4–5 seconds (split)
Gas for verification~220,000 gas (FFLONK, 22% cheaper than Groth16)
Double-spend preventionNullifier = Poseidon(nullifierKey, leafIndex), stored on-chain
Commitment structurePoseidon(ownerPubKey, amount, asset, chainId, blinding)
Note encryptionAES-256-GCM via Web Crypto API, key = SHA-256(spendingKey)

Anonymity Set

The anonymity set is the number of deposits in the Merkle tree at the time of withdrawal. A larger set means a withdrawal could correspond to any of more possible deposits, reducing the probability of correct guessing.

Best Practice

Wait until the pool has accumulated a reasonable number of deposits before withdrawing. The dashboard shows the current tree size. Withdrawing immediately after depositing offers minimal privacy benefit.

Root History

The contract maintains a history of past Merkle roots. You can prove membership against any root that was valid when you deposited — you don't need to re-deposit if new deposits change the root.

Security Notes

Notes are encrypted in IndexedDB. V2 encrypts deposit notes with AES-256-GCM (key derived from your spending key). Even if someone accesses your browser storage, they cannot read note data without your stealth keys. Export and back up notes from the Settings page.

Compliance screening is built-in. The Chainalysis oracle screens every depositor address. A 1-hour cooldown after deposit restricts withdrawals to the original depositor's address — giving compliance systems time to flag suspicious activity.

Denomination privacy via split withdrawals. Instead of fixed denominations, the split circuit breaks withdrawals into common amounts automatically. This prevents amount-based correlation while supporting arbitrary deposit sizes.

Chain ID binding prevents cross-chain replay. Every proof includes the chain ID as a public signal. A proof generated for Ethereum Sepolia cannot be replayed on Thanos Sepolia or any other chain.

FFLONKPoseidon HashZK-UTXOBN254snarkjsMerkle Depth 20ChainalysisAES-256-GCM