Core Features
Shield & Send
Deposit B20 tokens into the privacy pool and send them privately using zero-knowledge proofs.
How Shielding Works
Shielding deposits public B20 tokens into the vault. You receive a shielded note — a cryptographic commitment that proves ownership without revealing amount or identity.
- Generate random nullifier and secret locally in browser.
- Compute
Poseidon(nullifier, secret, amount). - Approve vault, call
vault.shield(token, amount, commitment). - Vault transfers B20 and inserts commitment into Merkle tree.
- Store nullifier, secret, amount locally.
Your nullifier and secret never leave your browser. The on-chain commitment reveals nothing.
Shielding Example
javascript
// Alice shields 1,000 B20
const nullifier = randomFieldElement();
const secret = randomFieldElement();
const amount = parseEther("1000");
// Compute commitment
const commitment = poseidon([nullifier, secret, amount]);
// Approve + shield
await b20.approve(vault.address, amount);
await vault.shield(b20.address, amount, commitment);
// Store locally
wallet.saveNote({ nullifier, secret, amount, token: b20.address });How Private Sending Works
A PLONK zero-knowledge proof proves:
- You own a valid note (inclusion proof)
- Nullifier hasn't been spent (no double-spend)
- Input = output amounts (conservation)
- New commitments correctly formed
Transfer Flow
- Input note — Alice's existing note (e.g., 1,000 B20)
- Output notes — Bob (600 B20) + Alice change (400 B20)
- ZK proof — valid without revealing amounts/addresses
- Broadcaster — submits proof, pays gas, collects B20 fee
- Vault — verifies, marks nullifier spent, inserts new commitments
javascript
// Alice sends 600 B20 to Bob
const proof = await generateProof({
inputNote: aliceNote,
outputNotes: [
{ amount: 600, recipient: bobAddress },
{ amount: 400, recipient: aliceAddress }, // change
],
merkleRoot: await getMerkleRoot(),
merkleProof: await getMerkleProof(aliceNote.index),
});
await broadcaster.submitTransfer(proof);Privacy Model
Privacy strength = anonymity set size. Every shielded note is a 32-byte commitment — indistinguishable on-chain.
What's visible on-chain
| Data | Visible? | Details |
|---|---|---|
| Shield deposit amount | Yes | Public ERC-20 transfer |
| Commitment hashes | Yes | 32-byte hashes — reveal nothing |
| Nullifier hashes | Yes | Proves spent, not which note |
| Transfer amounts | No | Hidden by ZK proof |
| Sender identity | No | Broadcaster submits |
| Recipient identity | No | Recipient sees note locally |
Best practices
- Don't deposit and immediately transfer — wait for others to shield.
- Avoid round amounts — links deposit to transfer.
- Use .rail20 usernames — avoid reusing raw addresses.
- Use the broadcaster — direct calls reveal IP and wallet.
Was this page helpful?