Your wallet is a glass house
Every token balance you have on Ethereum? Public. Every transfer you make? Public. That address you used to receive your paycheck? Anyone can see exactly how much you make, when you get paid, and where you spend it.
For individuals, that's creepy. For businesses, it's a disaster. Your competitors can watch your treasury. They can see when you're accumulating tokens before a big announcement. They can front-run your trades.
Mixing services help a bit, but they're clunky and regulators hate them. We wanted something better: a privacy pool where you can deposit, transfer, and withdraw tokens without anyone tracing the flow.
How the privacy pool works
NixProtocol uses a UTXO model, not encrypted ERC-20 balances. The concept is straightforward: you deposit tokens into the NixPool contract and receive private UTXO notes in return. These notes live in a Merkle tree on-chain, and only you know which ones are yours.
Each note is a commitment:
note = Poseidon2(value, owner_pubkey, salt)
The on-chain contract stores these commitments in a Merkle tree (depth 20, supporting roughly 1 million notes). Nobody can tell what value a note holds or who owns it just by looking at the commitment.
But wait, how do we know nobody's cheating?
This is where zero-knowledge proofs come in. Every transaction in the NixPool is authorized by an UltraHonk proof (compiled from Noir circuits via Barretenberg). No signatures needed. The ZK proof itself is the authorization.
For a transfer, the proof says:
- "I know the secret values behind two input notes that exist in the Merkle tree" (without revealing which ones)
- "I'm creating two new output notes whose values add up correctly" (no tokens created from thin air)
- "I'm publishing nullifiers for the input notes so they can't be spent again"
- "The ECIES-encrypted data attached to this transaction is correct" (for auditor compliance)
The smart contract verifies this proof. If it checks out, the transfer goes through. If not, it reverts.
Walking through the lifecycle
Deposit: entering the privacy pool
Alice wants to make 100 USDC private.
- She calls the deposit circuit on the NixPool contract, sending 100 USDC
- The contract creates a note commitment:
Poseidon2(100, alice_pubkey, random_salt) - This commitment gets inserted into the Merkle tree
- Alice stores the note details locally (value, salt, leaf index)
Her 100 USDC is now a private note. On-chain, it's just a hash. Nobody knows the value or who owns it.
Transfer: moving value privately
Alice wants to send Bob 50 USDC inside the privacy pool.
On Alice's device:
- She selects two of her notes as inputs (the protocol always uses 2-in/2-out)
- She creates two output notes: one for Bob (50 USDC) and one back to herself as change (50 USDC)
- She encrypts the output note details using ECIES so Bob can discover his note
- She generates an UltraHonk proof that everything's correct
On-chain:
- The contract receives the transaction with nullifiers, new commitments, the proof, and ECIES-encrypted data
- It verifies the proof
- It checks nullifiers haven't been used before (prevents double-spending)
- It inserts the two new note commitments into the Merkle tree
- Done. No one learned any amounts or identities.
Bob's side:
- Bob's wallet scans for new transactions
- He tries to decrypt the ECIES-encrypted data with his Grumpkin private key
- When decryption succeeds, he discovers a note meant for him
- He now knows he received 50 USDC and stores the note details locally
Withdraw: leaving the privacy pool
Bob wants to take his 50 USDC back to a regular ERC-20 balance. He consumes his note (publishes a nullifier, proves ownership via ZK proof) and the contract releases 50 USDC to his address.
Nullifiers: preventing double-spending
Every note can only be spent once. When you spend a note, you publish a nullifier derived from the note and your private key. The contract keeps a set of all used nullifiers. If a nullifier has been seen before, the transaction reverts.
The key insight: the nullifier is deterministic for a given note and owner, but you can't link a nullifier back to the note commitment it came from. This is what keeps the privacy pool private.
What about compliance?
"But won't regulators freak out about a privacy pool?"
We thought about this. Every transaction in the NixPool includes ECIES-encrypted data that a designated auditor can decrypt. The auditor holds a Grumpkin private key set at pool deployment. They can decrypt every transaction's details (sender, receiver, amounts) if needed for compliance.
This means:
- Regulators can audit the pool if legally required
- The auditor can trace funds for AML/KYC purposes
- But random strangers on the internet still can't see anything
You get privacy by default with compliance when you need it.
Real-world numbers
| What | Cost |
|---|---|
| Deposit into NixPool | ~3M gas |
| Private transfer (2-in/2-out) | ~3M gas, proof takes ~3 seconds to generate |
| Withdraw from NixPool | ~3M gas |
| Registration | ~2.8M gas |
UltraHonk proof verification is expensive - ~3M gas is significantly more than a regular ERC-20 transfer. That's the cost of real privacy: ZK proof verification is computationally heavy on-chain. On low-fee chains like Avalanche, this translates to cents rather than dollars, which makes it practical. For the privacy you get - hidden amounts, unlinkable transfers, and full sender/recipient confidentiality - we think it's worth it.
The bottom line
You shouldn't have to broadcast your financial life to participate in crypto. With the right cryptography, you can have all the benefits of blockchain (trustless, permissionless, verifiable) without giving up your privacy.
That's what we're building.