The UTXO Model
Privacy Boost uses a UTXO (Unspent Transaction Output) model. Instead of maintaining account balances, the system tracks individual notes, each representing a specific amount of a specific token owned by a specific user. Think of it like cash. You don’t have a “balance of $47,” you have a $20 bill, a $20 bill, a $5 bill, and two $1 bills. To pay someone $15, you hand over the $20 and get $5 back as change. In Privacy Boost:- Spending a note publishes a nullifier (marks it as consumed)
- Receiving creates new notes (adds commitments to the Merkle tree)
- A transfer consumes input notes and creates output notes, with the ZK circuit enforcing that inputs = outputs + fees
Notes and Commitments
What’s Inside a Note
A note contains three fields:
The NPK is derived from the owner’s Master Public Key (MPK) and a per-note random value:
Poseidon2(left, right) without a tag, while authorization-tree nodes use Poseidon2(5, left, right).
The per-note randomness ensures that even if the same user receives the same token and amount twice, the resulting commitments are different.
Commitments
A commitment is the Poseidon2 hash of a note’s contents:Nullifiers
A nullifier is a one-time-use tag that marks a specific note as spent. It prevents double-spending without revealing which note was consumed.How Nullifiers Work
Each nullifier is derived from the user’s secret nullifying key and the note’s position in the Merkle tree:How Ownership Is Proven
To spend a note, you prove three things inside the ZK circuit:- You know the note’s secrets: The circuit verifies your private fields hash to the correct commitment.
- Your account authorized the spend: The circuit proves either a signature from a registered EdDSA key or membership of the operation in an account-owner-approved commitment batch.
- The note exists: The circuit verifies a Merkle proof for your commitment.
Merkle Trees
Fixed-Depth Incremental Merkle Tree
All note commitments are stored in an append-only, fixed-depth binary Merkle tree using Poseidon2 hashing. Each tree has a fixed depth of 24 (~16.7M leaves); empty subtrees are filled with precomputed zero hashes, so an append only recomputes the hashes along one path to the root. Leaves are never modified or deleted.Multi-Tree Architecture
When the active tree fills up, a new tree is created (rollover). Input notes can reference commitments in any historical tree, so old notes remain spendable indefinitely. The active note tree accepts its 128 most recent roots. A finalized tree accepts only its final root, so spending an old note requires a membership path to that final root.Tree Transitions in Circuits
When new notes are created, the ZK circuit proves that the tree state transition is valid, and the contract just verifies the proof and updates the stored root. This means the contract never has to compute Poseidon2 hashes onchain for leaf insertions.Transaction Lifecycle
Deposit (Public → Shielded)
Deposits are a 2-step process:- User locks tokens: The user computes note commitments, encrypts the metadata using the dual-path ECDH scheme, and submits everything to the contract. The contract transfers the ERC-20 tokens and stores the pending deposit with replay protection.
- TEE processes the batch: The TEE detects pending deposits, generates a Groth16 proof verifying that commitments are correctly computed and the tree state transition is valid, then submits it onchain.
Portal Deposit (Public → Shielded, Hidden Recipient)
A portal is a reusable public deposit address, like an exchange deposit address but self-custodial. The owner derives one addressE, delegates it to a shared EIP-7702 implementation, and registers a public onchain binding H that commits to their account without naming it.
- Anyone funds it: Senders make ordinary ERC-20 transfers to
E. They need no Privacy Boost software, and there is no per-deposit ceremony. - Anyone sweeps it: The sweep call is permissionless. It measures the balance actually received, escrows it in the pool, snapshots the fee rate, and records the caller as the fee payee.
- The TEE credits the owner: A portal-deposit epoch proof credits the escrowed net amount as a new note, tying the credit to
Hwithout ever revealing the account.
E.
Transfer (Shielded → Shielded)
Transfers move value between shielded notes. Sender identity, recipient identity, token type, and amounts are all hidden from onchain observers.- User prepares: Select input notes, construct recipient and change notes, encrypt the output metadata, and authorize the operation with a signing key or an account-owner spend approval.
- User submits to TEE: The TEE checks the selected authorization and ciphertext integrity, then batches the transfer into an epoch.
- TEE submits epoch: The TEE generates a Groth16 proof covering the selected authorization, note existence, nullifier correctness, value conservation, and tree transition, then submits it onchain.
- Contract verifies: Checks the proof, marks nullifiers as spent, updates the tree root
Claimable Transfer (Shielded → Unregistered Wallet)
A claimable transfer sends shielded funds to an Ethereum wallet address that has no Privacy Boost account yet. An ordinary transfer needs the recipient’s privacy address, which a wallet address alone does not supply, so the flow splits into fund and claim.- Fund: The sender funds a gift note bound to the recipient’s wallet address and a fresh hiding factor, through an ordinary shielded transfer. Onchain it is indistinguishable from any other transfer.
- Claim: The recipient registers Privacy Boost with that same wallet, proves control of it in zero knowledge, and the gift is re-minted as a normal shielded note they own. A wallet-recipient claim requires a registered signing key.
- Refund: The sender picks a refund deadline at funding time, and refunds with either a signing key or an account-owner approval once it passes. The deadline opens the refund path without expiring the recipient’s claim.
Withdrawal (Shielded → Public)
Withdrawals use the same mechanism as transfers. The difference: instead of creating an output note, the contract sends ERC-20 tokens to a public address. Withdrawals are batched alongside transfers in the same epoch. The withdrawal’s first output is a public payout marker for the destination, token, and amount. It counts in the value balance but is never added to the note tree.Forced Withdrawal (Emergency Exit)
Forced withdrawal is the self-custody escape hatch: exit the shielded pool without any TEE involvement.- Reconstruct your notes: Scan onchain events and decrypt metadata with your viewing key
- Generate a ZK proof locally: Prove you own the notes and derive correct nullifiers (this runs on consumer hardware)
- Submit to the contract: The contract checks the exact live signing-key or dedicated size-1 approval leaf, verifies the proof, reserves the input commitments, and records the payout and fee before starting the delay.
- Execute after the delay: Any caller can execute the recorded payout if its nullifiers remain unspent. Execution uses the request-time authorization and fee, without checking the proof again. The account owner can cancel the request immediately.
Epoch Batching
Multiple user transactions are aggregated into a single epoch, one Groth16 proof covering the entire batch. This is a key design choice for throughput:- A single Groth16 proof verifies the entire epoch, so the fixed cost of proof verification is amortized across every transaction in the batch
- The larger the batch, the lower the proof-verification overhead per transaction
- At Base’s gas target, this enables 300+ sustained TPS; at the gas limit, 1,800+ TPS
status field and surfaces it as phase: 'spendable' so applications can chain the next private action (another transfer or a withdrawal) without waiting for the epoch to land. Preconfirmation is a soft confirmation: a reorg can still force the TEE to re-batch or fail the request, so clients keep polling until completed. Shields and unshields also pass through preconfirmed, but neither produces a spendable output, so that state is progress UI only. See Transaction Lifecycle for the full state machine.
To prevent key-rotation attacks during an epoch, an epoch proof must reference a sufficiently fresh AuthRegistry root. When a root is replaced, the AuthRegistry records the block at which it was superseded, and the pool accepts an auth root only if it is current or still within a configured staleness window.
Fee Model
- Ordinary deposits have no protocol deposit fee.
- Portal deposits credit the gross swept amount minus the fee rate stored when the sweep was requested.
- Transfers and withdrawals settled in an epoch each carry a private fee. The circuit enforces
inputs = outputs + feeper operation and totals fees by token. The fee amount is set off-chain. - Public gift exits apply the current withdrawal fee subject to the authorized minimum net payout. Forced withdrawals snapshot the withdrawal fee at request time and deduct it at execution.
Private DeFi
Private DeFi lets users put shielded funds to work in external DeFi without publicly revealing their wallet identity. All DeFi activity is linked to Privacy Boost rather than to any individual user. The high-level flow:- Supply: move assets from your shielded balance into an approved vault and hold the resulting position privately.
- Redeem: redeem that position back into assets in your shielded balance.
Smart Contracts
PrivacyBoost, AuthRegistry, TokenRegistry, and AuditGateway use upgradeable implementations. PortalDelegate and ExternalCallGateway are separate contracts bound to a pool.
Coming Soon
More privacy-preserving capabilities are in development, each designed to preserve the same privacy and self-custody guarantees:- DeFi swaps: swap between tokens from a shielded balance without publicly exposing the user’s wallet identity.
Next Steps
- Keys & Encryption: How keys are derived and how the dual-path ECDH encryption scheme works
- Trust & Security: TEE guarantees and the full trust model
- Glossary: Technical terminology reference