Privacy Pool Overview
The b402 privacy pool is an on-chain privacy system that uses zero-knowledge proofs to shield token balances. Once tokens are inside the privacy pool contract, they can be transferred privately and withdrawn to any address, breaking the on-chain link between sender and recipient. The system has three operations:| Operation | Direction | What Happens |
|---|---|---|
| Shield | Public to private | Deposit tokens into the privacy pool |
| Transact | Private to private | Transfer tokens within the pool (ZK proof) |
| Unshield | Private to public | Withdraw tokens from the pool to any address |
| Contract | Address |
|---|---|
| B402 RelayerV3 | 0xE91b564EB8DFF305Ff8efA332f84c487b9da5171 |
Commitment Structure
Every shielded balance is represented as a commitment stored in the privacy pool contract. A commitment is a Poseidon hash of:| Field | Description |
|---|---|
npk | Nullifier public key (derived from the recipient’s viewing key) |
token | Token address, type, and sub-ID packed together |
value | Amount of tokens being shielded |
random | Random blinding factor for privacy |
Commitment Fields in the Database
When a shield event is indexed, the following fields are stored:Nullifier Generation
Nullifiers prevent double-spending. When a commitment is consumed (via transact or unshield), a nullifier is published on-chain. The nullifier is derived deterministically from the commitment but cannot be linked back to it without the private key.Nullifier States
| State | Meaning |
|---|---|
| Unused | Commitment has not been spent, balance is available |
| Used (TRANSACT) | Commitment was consumed in a private transfer, producing new output commitments |
| Used (UNSHIELD) | Commitment was consumed in a withdrawal to a public address |
Merkle Tree Structure
The privacy pool organizes commitments in a binary Merkle tree using Poseidon hashing. The tree has:- Depth: 16 levels
- Capacity: 2^16 = 65,536 leaves per tree
- Hash function: Poseidon (ZK-friendly)
- Zero value:
keccak256("Railgun") % SNARK_SCALAR_FIELD
treeNumber).
Tree Construction
Merkle Proof
To spend a commitment, the user must prove it exists in the tree by providing a Merkle proof: the sibling hashes along the path from the leaf to the root.Shield Flow in b402
Shielding is the process of depositing tokens from a public address into the b402 privacy pool.Unshield Flow
Unshielding withdraws tokens from the privacy pool to any public address. This is the most complex operation because it requires generating a zero-knowledge proof.Step 1: Generate the ZK Proof
The user’s wallet generates a proof that:- They own a valid commitment in the Merkle tree
- The nullifier has not been spent
- The output amounts are correct (recipient amount + fee)
Step 2: Submit and Broadcast
The facilitator handles unshield submission internally. After the ZK proof is generated, it is submitted to the privacy pool relay contract.Security Considerations
Proof Validation
- Transaction data size is validated (must be under 100KB) to reject junk submissions
- The
transactionTofield must match the configured privacy pool relay contract address - Nullifiers are checked against the on-chain database to prevent double-spend attempts
- Merkle proofs are verified server-side before being returned to clients
Data Integrity
- Merkle proofs are verified before being returned (the backend recomputes the root from the proof path)
- If a proof fails verification, an internal server error is returned indicating database inconsistency
- Nullifier records must always have an associated transact or unshield record; orphaned nullifiers trigger an error
Further Reading
- Smart Wallet Deep Dive - How smart wallets can execute privacy operations
- Poseidon Hash - The ZK-friendly hash function used in Merkle trees
