Privacy Pools
Privacy Pools are the core shielding mechanism in Obelysk. You deposit tokens into a pool, which adds a cryptographic commitment to a Merkle tree. Later, you withdraw by proving you know the secret behind one of the commitments — without revealing which one.
How It Works
The note contains the secret and nullifier needed to withdraw. If you lose it, your funds are permanently locked. There is no recovery mechanism — not even the protocol owner can retrieve them.
Supported Pools
| Pool | Token | Contract Address | Decimals |
|---|---|---|---|
| ETH Pool | ETH | 0x06d0b41c...1e82b5 | 18 |
| STRK Pool | STRK | 0x02c348e...3c9cf1 | 18 |
| USDC Pool | USDC | 0x05d36d7...4d4d59b | 6 |
| wBTC Pool | wBTC | 0x030fcfd4...d5e5e2 | 8 |
| SAGE Pool | SAGE | 0x022497...ac724f | 18 |
Cryptographic Primitives
Pedersen Commitments
Each deposit creates a Pedersen commitment that hides the amount while being binding (you can't change the amount later):
C = amount · G + blinding · H
where:
G = STARK curve generator (known)
H = Pedersen generator (nothing-up-my-sleeve point, unknown discrete log)
amount = deposit value
blinding = random scalar (part of your note)
Using two generators G and H with unknown relative discrete log makes the commitment perfectly hiding — there are infinitely many (amount, blinding) pairs that produce the same commitment. But it's computationally binding — you can't find two different openings unless you can solve the discrete log problem.
LeanIMT Merkle Tree
The pool uses a Lean Indexed Merkle Tree (LeanIMT) with Poseidon hashing:
- Depth: 20 levels (supports 2^20 = 1,048,576 deposits)
- Hash: Poseidon-252 (STARK native)
- Leaf: Pedersen commitment hash
- Insert: O(20) hashes per deposit (update path to root)
- Proof: 20 sibling hashes from leaf to root
Nullifiers
Nullifiers prevent double-spending without revealing which deposit is being withdrawn:
nullifier = Poseidon(nullifier_secret || commitment_hash)
The contract maintains a set of used nullifiers. When you withdraw, the contract checks that your nullifier hasn't been used before and adds it to the set. Since the nullifier is derived from your secret (which only you know), nobody can link it to a specific deposit.
Range Proofs
Range proofs verify that the withdrawal amount is valid (0 ≤ amount < 2^64) without revealing the actual value. This prevents overflow attacks where someone could withdraw more than they deposited.
ASP Compliance Layer
Privacy Pools include an optional Association Set Provider (ASP) compliance layer:
ASPs are registered on-chain with their own Merkle trees. They can add/remove deposits from their sets, and users can prove membership or non-membership during withdrawal.
SDK Usage
// Deposit
const note = await obelysk.privacyPool.deposit({
token: 'eth',
amount: '1.0',
});
// SAVE THIS NOTE SECURELY!
// Check pool stats
const stats = await obelysk.privacyPool.getPoolStats('eth');
console.log('Total deposits:', stats.totalDeposits);
console.log('Merkle root:', await obelysk.privacyPool.getMerkleRoot('eth'));
// Withdraw
const result = await obelysk.privacyPool.withdraw({
token: 'eth',
amount: '1.0',
secret: note.secret,
nullifier: note.nullifier,
leafIndex: note.leafIndex,
recipient: '0xYOUR_ADDRESS',
});
// Verify nullifier was consumed
const spent = await obelysk.privacyPool.isNullifierUsed('eth', note.nullifier);
console.log('Nullifier spent:', spent); // true
Fee Structure
A 30bps (0.30%) fee is deducted from withdrawals before the token transfer. Fees accumulate per-pool and are collected by the protocol owner via collect_fees().
Next Steps
- ElGamal Encryption — the encryption scheme behind amount hiding
- Dark Pool Trading — trade privately with commit-reveal auctions
- Shielded Swaps — swap tokens without revealing your identity
- STARK Curve Primitives — Pedersen and Schnorr details