DEVELOPER GUIDE
Technical implementation details and cryptographic primitives
CRYPTOGRAPHIC PRIMITIVES
KEY DERIVATION HIERARCHY
KEY PROPERTIES
- • spending_key: Root secret — must be kept private and backed up. It is the only way to recover your account; losing it means permanent loss of all shielded funds.
- • nullifying_key: Derived from spending_key — must be kept private. Exposure allows an attacker to generate your nullifiers and front-run your spends.
- • MPK: Master Public Key — safe to share. Senders need your MPK to create a note commitment addressed to you.
- • viewing_private_key: Derived via X25519(SHA256(spending_key)) — used locally to decrypt incoming notes. Never leave the client.
- • viewing_public_key: Safe to share. Senders need your VPK to encrypt the note payload so only you can read it. Together with MPK, it forms your transfer address.
- • NSK: Note Secret Key — unique per note, derived from MPK and a random value.
- • commitment: Public commitment to a note, stored in the Merkle tree.
- • nullifier: Unique identifier for a spent note, prevents double-spending.
SENDING A TRANSFER
To send tokens to another user, you need their MPK and viewing_public_key (VPK). MPK is used to derive the output note commitment; VPK is used to encrypt the note payload via ECDH so only the recipient can decrypt it. Both values together act as the recipient's shielded address.
▲ SHIELD OPERATION
Deposit tokens into the privacy pool by creating an encrypted note commitment
PROCESS FLOW
Generate Note Parameters
Create random value for NSK derivation, compute NSK = Poseidon(MPK, random)
Compute Commitment
commitment = Poseidon(NSK, token_id, amount)
Encrypt Note Data
Encrypt (NSK, token, value, random) using ChaCha20-Poly1305 with recipient's viewing key
Submit to Pool
Call pool::shield(pool, coin, commitment, encrypted_note) - NO ZK PROOF REQUIRED
RESULT
- • Commitment added to Merkle tree
- • Encrypted note stored on-chain via event
- • User's public balance decreases
- • Shielded balance increases (only visible to user)
▼ UNSHIELD OPERATION
Withdraw tokens from the privacy pool using a zero-knowledge proof of ownership
PROCESS FLOW
Select Input Note
Choose an unspent note with sufficient balance for withdrawal
Compute Nullifier
nullifier = Poseidon(nullifying_key, leaf_index) - prevents double-spending
Create Change Note (if needed)
If input_value > withdraw_amount, create change commitment for remaining balance
Generate ZK Proof
Prove: (1) ownership of note, (2) note exists in Merkle tree, (3) correct nullifier, (4) balance conservation, (5) correct change commitment
Submit to Pool
Call pool::unshield(pool, proof, public_inputs, recipient, encrypted_change_note)
RESULT
- • ZK proof verified on-chain
- • Nullifier marked as spent (prevents double-spending)
- • Tokens transferred to recipient address
- • Change note added to Merkle tree (if any)
- • No link between deposit and withdrawal revealed
⇄ TRANSFER OPERATION
Privately transfer tokens using 2 inputs to generate a recipient note and optional change, completely hiding sender, receiver, and value from observers.
PROCESS FLOW
Select Input Notes
Choose up to 2 unspent notes with sufficient total balance. If only 1 note needed, use a dummy note.
Create Output Notes
Output 1: Recipient's note with transfer amount
Output 2: Sender's change note with remaining balance
Compute Nullifiers
Generate nullifier for each input note to mark them as spent
Generate ZK Proof
Prove: (1) ownership of input notes, (2) inputs exist in Merkle tree, (3) correct nullifiers, (4) balance conservation (in = out), (5) valid output commitments
Submit to Pool
Call pool::transfer(pool, proof, public_inputs, encrypted_notes)
RESULT
- • ZK proof verified on-chain
- • Input nullifiers marked as spent
- • Output commitments added to Merkle tree
- • Sender, recipient, and amount remain hidden
- • Only recipient can decrypt their note using viewing key
⇌ SWAP OPERATION
Exchange tokens privately via DeepBook V3, spending shielded input notes and receiving shielded output notes
PROCESS FLOW
Select Input Notes
Choose up to 2 unspent notes of the input token with sufficient total balance
Compute Swap Data Hash
Hash swap parameters (token_in, token_out, amount_in, min_amount_out) to bind them to the proof
Create Output Notes
Output note: recipient commitment for the swapped token_out amount
Change note: sender's change commitment if input exceeds amount_in
Generate ZK Proof
Prove: (1) ownership of input notes, (2) inputs exist in Merkle tree, (3) correct nullifiers, (4) valid swap_data_hash, (5) valid output commitments
Submit to Pool
Call pool::swap(pool_in, pool_out, deepbook_pool, proof, public_inputs, amount_in, min_amount_out, encrypted_output_note, encrypted_change_note)
RESULT
- • ZK proof verified on-chain
- • Input nullifiers marked as spent
- • Swap executed via DeepBook V3
- • Output and change commitments added to Merkle tree
- • Token amounts and identities remain hidden