Privacy Features (SDK)
Obelysk provides four privacy primitives through the SDK. Each uses ElGamal encryption on the STARK curve to hide amounts while maintaining on-chain verifiability through Schnorr proofs and Merkle trees.
Privacy Pools
Privacy pools let you deposit tokens into a shielded set and withdraw later without linking the two transactions. Each deposit generates a note β a cryptographic receipt containing your secret and nullifier.
The note is the only way to withdraw your funds. If you lose it, your tokens are permanently locked. There is no recovery mechanism. Store it encrypted, backed up, and offline.
Deposit
const note = await obelysk.privacyPool.deposit({
token: 'eth',
amount: '1.0',
});
// CRITICAL: Save this note securely
console.log('Note secret:', note.secret);
console.log('Note nullifier:', note.nullifier);
console.log('Leaf index:', note.leafIndex);
Your deposit creates a Pedersen commitment (C = amountΒ·G + blindingΒ·H) and inserts it into a LeanIMT Merkle tree (depth 20). The commitment hides the amount while the Merkle tree enables efficient membership proofs.
Withdraw
const result = await obelysk.privacyPool.withdraw({
token: 'eth',
amount: '1.0',
secret: note.secret,
nullifier: note.nullifier,
leafIndex: note.leafIndex,
recipient: '0xYOUR_ADDRESS',
});
console.log('Withdrawal tx:', result.transactionHash);
The withdrawal generates a Merkle inclusion proof against the pool's commitment tree and a range proof to verify the amount β without revealing which deposit is being spent.
Pool Stats
const stats = await obelysk.privacyPool.getPoolStats('eth');
console.log('Total deposits:', stats.totalDeposits);
console.log('Total withdrawn:', stats.totalWithdrawn);
console.log('Current TVL:', stats.totalDeposited - stats.totalWithdrawn);
// Check if a nullifier has been used (double-spend prevention)
const spent = await obelysk.privacyPool.isNullifierUsed('eth', note.nullifier);
Confidential Transfer
Send tokens peer-to-peer with encrypted amounts. The contract only ever sees ElGamal ciphertexts β never plaintext values. The sender proves sufficient balance using Schnorr proofs, and the recipient decrypts with their ElGamal key pair.
Each confidential transfer requires 6 zero-knowledge proofs verified on-chain: ownership proof, blinding proof, encryption proof (Chaum-Pedersen), range proof (32-bit bounds), and two same-encryption proofs ensuring sender/receiver/auditor ciphertexts encrypt the same value.
Fund β Transfer β Withdraw
// 1. Fund your encrypted balance (public β private)
await obelysk.confidentialTransfer.fund({
token: 'sage',
amount: '5000',
});
// 2. Transfer to another address (private β private)
// Amount is encrypted on-chain. Only sender + recipient can see it.
await obelysk.confidentialTransfer.transfer({
token: 'sage',
amount: '1000',
recipient: '0xRECIPIENT_ADDRESS',
});
// 3. Withdraw back to plaintext balance (private β public)
await obelysk.confidentialTransfer.withdraw({
token: 'sage',
amount: '2000',
});
Read Encrypted Balances
// Get encrypted balance (returns ElGamal ciphertext)
const balance = await obelysk.confidentialTransfer.getEncryptedBalance(
'0xADDRESS',
'sage'
);
console.log('Ciphertext:', balance);
// Get someone's public key
const pubkey = await obelysk.confidentialTransfer.getPublicKey('0xADDRESS');
Stealth Payments
Stealth payments let you send tokens to a recipient without anyone else knowing who received them. The recipient publishes a meta-address once, and senders derive a fresh one-time address for each payment.
Send a Stealth Payment
const result = await obelysk.stealth.send({
to: '0xRECIPIENT_ADDRESS',
token: 'sage',
amount: '1000',
});
console.log('Stealth address:', result.stealthAddress);
console.log('Ephemeral key:', result.ephemeralPubKey);
Scan and Claim (Recipient)
// Scan for payments to your meta-address
const announcements = await obelysk.stealth.scan({
metaAddress: myMetaAddress,
fromBlock: 100000,
});
console.log('Found payments:', announcements.length);
// Claim a specific payment
for (const ann of announcements) {
await obelysk.stealth.claim({
announcement: ann,
recipient: '0xYOUR_WALLET',
});
}
View tags are the first byte of the ECDH shared secret. They let the recipient filter out 255/256 (99.6%) of irrelevant announcements without performing full ECDH, making scanning fast even with millions of announcements.
Dark Pool
The dark pool uses a commit-reveal batch auction with a 3-block lifecycle. Orders are encrypted during the commit phase, revealed when the phase changes, and matched at a uniform clearing price. This prevents front-running, sandwich attacks, and MEV extraction.
Epoch Lifecycle
Full Trading Flow
// 1. Deposit tokens into the dark pool
await obelysk.darkPool.deposit({
token: 'sage',
amount: '10000',
});
// 2. Check the current epoch and phase
const epoch = await obelysk.darkPool.getEpochInfo();
console.log(`Epoch ${epoch.epochNumber}: ${epoch.phase}`);
// 3. Commit an order (during COMMIT phase)
const order = await obelysk.darkPool.commitOrder({
pair: 'SAGE/STRK',
side: 'sell',
price: '0.011',
amount: '5000',
});
// 4. Reveal the order (during REVEAL phase)
await obelysk.darkPool.revealOrder({
commitment: order.commitment,
pair: 'SAGE/STRK',
side: 'sell',
price: '0.011',
amount: '5000',
salt: order.salt,
});
// 5. Claim fills after settlement
await obelysk.darkPool.claimFill({
epochNumber: epoch.epochNumber,
pair: 'SAGE/STRK',
});
5 assets registered (ETH, STRK, USDC, wBTC, SAGE) across 5 trading pairs: ETH/STRK, ETH/USDC, wBTC/ETH, wBTC/STRK, wBTC/USDC. A 30bps fee applies to matched trades.
Privacy Guarantees
| Property | During Commit | During Reveal | After Settlement |
|---|---|---|---|
| Order price | Hidden | Visible | Visible |
| Order amount | Hidden | Visible | Visible |
| Trader identity | Hidden (relayer) | Hidden (relayer) | Hidden (relayer) |
| Front-running possible? | No | No | N/A |
| MEV extractable? | No | No | No |
Which Privacy Primitive Should I Use?
| Need | Use | Why |
|---|---|---|
| Break deposit/withdrawal link | Privacy Pools | Merkle tree anonymity set |
| Send encrypted amounts P2P | Confidential Transfer | ElGamal homomorphic encryption |
| Pay someone without revealing recipient | Stealth Payments | One-time ephemeral addresses |
| Trade without front-running | Dark Pool | Commit-reveal batch auctions |
| Maximum unlinkability | VM31 UTXO Vault | Fixed denominations, entire pool is anonymity set |
| Private AMM swap | Shielded Swap | Ekubo sees router, not your address |
Next Steps
- Trading and Staking β OTC orders, shielded swaps, prover staking
- VM31 UTXO Vault β UTXO-based privacy using Poseidon2
- Privacy Architecture β how the two privacy tracks work together
- Deployed Contracts β all 14 mainnet contract addresses