|
| 1 | +# shielded-pool |
| 2 | + |
| 3 | +Privacy-preserving deposit/withdrawal using Groth16 proofs and rent-free nullifiers. |
| 4 | + |
| 5 | +## Summary |
| 6 | + |
| 7 | +- Deposits create note commitments as compressed accounts with Poseidon hashing |
| 8 | +- Note commitment: `Poseidon(amount, secret, nullifier_secret)` |
| 9 | +- Withdrawals verify ZK proof and create nullifier to prevent double-spending |
| 10 | +- Nullifier: `Poseidon(pool_id, nullifier_secret)` |
| 11 | +- Address derivation prevents duplicate notes/nullifiers |
| 12 | + |
| 13 | +## Instructions |
| 14 | + |
| 15 | +### `deposit` |
| 16 | + |
| 17 | +Creates a note commitment as a compressed account. |
| 18 | + |
| 19 | +- **discriminator**: `[242, 35, 198, 137, 82, 225, 242, 182]` |
| 20 | +- **path**: `src/lib.rs:41` |
| 21 | + |
| 22 | +**Instruction data:** |
| 23 | + |
| 24 | +| Field | Type | Description | |
| 25 | +|-------|------|-------------| |
| 26 | +| `proof` | `ValidityProof` | Light Protocol validity proof for new address | |
| 27 | +| `address_tree_info` | `PackedAddressTreeInfo` | Address tree reference | |
| 28 | +| `output_state_tree_index` | `u8` | State tree for output account | |
| 29 | +| `note_commitment` | `[u8; 32]` | Poseidon(amount, secret, nullifier_secret) | |
| 30 | +| `_amount` | `u64` | Amount (encoded in commitment, kept for client reference) | |
| 31 | + |
| 32 | +**Accounts:** |
| 33 | + |
| 34 | +| Name | Signer | Writable | Description | |
| 35 | +|------|--------|----------|-------------| |
| 36 | +| `signer` | ✓ | ✓ | Transaction fee payer | |
| 37 | + |
| 38 | +**Logic:** |
| 39 | +1. Validate address tree is `ADDRESS_TREE_V2` |
| 40 | +2. Derive address from `[b"note", note_commitment]` |
| 41 | +3. Create `NoteAccount` with Poseidon hashing via Light CPI |
| 42 | + |
| 43 | +### `withdraw` |
| 44 | + |
| 45 | +Verifies ZK proof and creates nullifier to prevent double-spending. |
| 46 | + |
| 47 | +- **discriminator**: `[183, 18, 70, 156, 148, 109, 161, 34]` |
| 48 | +- **path**: `src/lib.rs:89` |
| 49 | + |
| 50 | +**Instruction data:** |
| 51 | + |
| 52 | +| Field | Type | Description | |
| 53 | +|-------|------|-------------| |
| 54 | +| `proof` | `ValidityProof` | Light Protocol validity proof | |
| 55 | +| `address_tree_info` | `PackedAddressTreeInfo` | Address tree reference | |
| 56 | +| `output_state_tree_index` | `u8` | State tree for nullifier account | |
| 57 | +| `zk_proof` | `CompressedProof` | Groth16 proof (a, b, c compressed) | |
| 58 | +| `commitment` | `[u8; 32]` | Note commitment being spent | |
| 59 | +| `nullifier` | `[u8; 32]` | Nullifier hash | |
| 60 | +| `amount` | `u64` | Withdrawal amount | |
| 61 | + |
| 62 | +**Accounts:** |
| 63 | + |
| 64 | +| Name | Signer | Writable | Description | |
| 65 | +|------|--------|----------|-------------| |
| 66 | +| `signer` | ✓ | ✓ | Transaction fee payer | |
| 67 | + |
| 68 | +**Logic:** |
| 69 | +1. Validate address tree is `ADDRESS_TREE_V2` |
| 70 | +2. Compute public inputs: `[pool_id_hashed, commitment, nullifier, amount]` |
| 71 | +3. Decompress G1/G2 proof points |
| 72 | +4. Verify Groth16 proof against public inputs |
| 73 | +5. Derive nullifier address from `[b"nullifier", nullifier, POOL_ID]` |
| 74 | +6. Create `NullifierAccount` via Light CPI (fails if exists = double-spend) |
| 75 | + |
| 76 | +## Accounts |
| 77 | + |
| 78 | +### `NoteAccount` |
| 79 | + |
| 80 | +Compressed account storing a deposit commitment. |
| 81 | + |
| 82 | +- **path**: `src/lib.rs:201` |
| 83 | +- **derivation**: `[b"note", note_commitment]` |
| 84 | +- **hashing**: Poseidon (via `LightAccountPoseidon`) |
| 85 | +- **data**: `commitment: [u8; 32]` |
| 86 | + |
| 87 | +### `NullifierAccount` |
| 88 | + |
| 89 | +Empty marker account. Existence at derived address proves nullifier was spent. |
| 90 | + |
| 91 | +- **path**: `src/lib.rs:217` |
| 92 | +- **derivation**: `[b"nullifier", nullifier, POOL_ID]` |
| 93 | +- **data**: None (empty struct) |
| 94 | + |
| 95 | +## Circuit |
| 96 | + |
| 97 | +**`shielded_pool.circom`** |
| 98 | + |
| 99 | +| Public Inputs | Private Inputs | |
| 100 | +|---------------|----------------| |
| 101 | +| `pool_id`, `commitment`, `nullifier`, `amount` | `secret`, `nullifier_secret` | |
| 102 | + |
| 103 | +**Constraints:** |
| 104 | +1. `commitment === Poseidon(amount, secret, nullifier_secret)` |
| 105 | +2. `nullifier === Poseidon(pool_id, nullifier_secret)` |
| 106 | + |
| 107 | +## Diagrams |
| 108 | + |
| 109 | +- `flow.mermaid` - Sequence diagram of deposit/withdraw flows |
| 110 | +- `states.mermaid` - State machine showing note lifecycle and double-spend prevention |
| 111 | + |
| 112 | +## State Flow |
| 113 | + |
| 114 | +```text |
| 115 | + deposit() |
| 116 | + | |
| 117 | + v |
| 118 | ++----------+ +-----------+ |
| 119 | +| [None] | ---> | NoteAccount| |
| 120 | ++----------+ +-----------+ |
| 121 | + | |
| 122 | + withdraw() |
| 123 | + | |
| 124 | + v |
| 125 | + +----------------+ |
| 126 | + | NullifierAccount| |
| 127 | + +----------------+ |
| 128 | + | |
| 129 | + (exists = spent) |
| 130 | +``` |
| 131 | + |
| 132 | +## Errors |
| 133 | + |
| 134 | +| Code | Name | Condition | |
| 135 | +|------|------|-----------| |
| 136 | +| 6000 | `AccountNotEnoughKeys` | Missing accounts in remaining_accounts | |
| 137 | +| 6001 | `ProofVerificationFailed` | Groth16 proof invalid | |
| 138 | +| 6002 | `InvalidNullifier` | Nullifier validation failed | |
| 139 | + |
| 140 | +## Build & Test |
| 141 | + |
| 142 | +```bash |
| 143 | +./scripts/setup.sh # Compile circuit, generate zkey |
| 144 | +cargo build-sbf && cargo test-sbf # Rust tests |
| 145 | +``` |
| 146 | + |
0 commit comments