Skip to content

Commit 9f9450b

Browse files
author
tilo-14
committed
feat: add ZK examples (zk-id, zk-nullifier, zk-merkle-proof, shielded-pool)
- Move zk-id from root to zk/ folder - Add zk-nullifier: single and batch (4x) nullifier creation with Groth16 - Add zk-merkle-proof: merkle proof verification with compressed accounts - Add shielded-pool: privacy-preserving deposit/withdrawal - Update CI to test zk/zk-id and zk/zk-nullifier
1 parent fa91f4f commit 9f9450b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+40099
-61
lines changed

.github/workflows/rust-tests.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ jobs:
2929
- counter/native
3030
- counter/pinocchio
3131
- account-comparison
32-
- zk-id
32+
- zk/zk-id
33+
- zk/zk-nullifier
34+
- zk/zk-merkle-proof
35+
- zk/shielded-pool
3336
- airdrop-implementations/simple-claim/program
3437
include:
3538
- example: basic-operations/native
@@ -51,10 +54,10 @@ jobs:
5154
example: ${{ matrix.example }}
5255
solana-cli-version: ${{ env.SOLANA_CLI_VERSION }}
5356
rust-toolchain: ${{ env.RUST_TOOLCHAIN }}
54-
install-circom: ${{ matrix.example == 'zk-id' }}
57+
install-circom: ${{ startsWith(matrix.example, 'zk/') || startsWith(matrix.example, 'zk/') }}
5558

5659
- name: Setup ZK circuits
57-
if: matrix.example == 'zk-id'
60+
if: startsWith(matrix.example, 'zk/') || startsWith(matrix.example, 'zk/')
5861
working-directory: ${{ matrix.example }}
5962
run: ./scripts/setup.sh
6063

README.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@
1212
For simple client side distribution visit [this example](https://github.com/Lightprotocol/example-token-distribution).
1313

1414
### Basic Operations
15-
- **[create-nullifier](./basic-operations/anchor/create-nullifier)** - Basic Anchor example to create nullifiers.
16-
- **[basic-operations/anchor](./basic-operations/anchor/)** - Anchor program with Rust and TypeScript tests
17-
- **[basic-operations/native-rust](./basic-operations/native-rust/)** - Native Solana program with light-sdk and Rust tests.
1815

19-
Basic Operations include:
20-
- **create** - Initialize a new compressed account.
21-
- **update** - Modify data in an existing compressed account.
22-
- **close** - Clear account data and preserve its address.
23-
- **reinit** - Reinitialize a closed account with the same address.
24-
- **burn** - Permanently delete a compressed account.
16+
- **[create-nullifier](./basic-operations/anchor/create-nullifier)** - Basic Anchor example to create nullifiers for payments.
17+
- **create** - Initialize a new compressed account
18+
- [Anchor](./basic-operations/anchor/create) | [Native](./basic-operations/native/programs/create)
19+
- **update** - Modify data in an existing compressed account
20+
- [Anchor](./basic-operations/anchor/update) | [Native](./basic-operations/native/programs/update)
21+
- **close** - Clear account data and preserve its address
22+
- [Anchor](./basic-operations/anchor/close) | [Native](./basic-operations/native/programs/close)
23+
- **reinit** - Reinitialize a closed account with the same address
24+
- [Anchor](./basic-operations/anchor/reinit) | [Native](./basic-operations/native/programs/reinit)
25+
- **burn** - Permanently delete a compressed account
26+
- [Anchor](./basic-operations/anchor/burn) | [Native](./basic-operations/native/programs/burn)
2527

2628
### Counter Program
2729

@@ -45,9 +47,17 @@ Full compressed account lifecycle (create, increment, decrement, reset, close):
4547

4648
- **[account-comparison](./account-comparison/)** - Compare compressed vs regular Solana accounts.
4749

48-
### zk-id Program
50+
### ZK Programs
4951

50-
- **[zk-id](./zk-id)** - A minimal zk id Solana program that uses zero-knowledge proofs for identity verification with compressed accounts.
52+
**Full Examples:**
53+
54+
- **[zk-id](./zk/zk-id)** - Identity verification using Groth16 proofs. Issuers create credentials; users prove ownership without revealing the credential.
55+
- **[shielded-pool](./zk/shielded-pool)** - Privacy-preserving deposit/withdrawal .
56+
57+
**Basic Examples:**
58+
59+
- **[zk-nullifier](./zk/zk-nullifier)** - Creates one or four nullifiers. Uses Groth16 proofs and compressed accounts.
60+
- **[zk-merkle-proof](./zk/zk-merkle-proof)** - Creates compressed accounts and verifies with Groth16 proofs (without nullifier).
5161

5262

5363
## Light Protocol dependencies

zk/shielded-pool/.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules/
2+
build/
3+
target/
4+
pot/
5+
*.sym
6+
*.r1cs
7+
*.wasm
8+
*.zkey
9+
*.wtns
10+
proof.json
11+
public.json

zk/shielded-pool/CLAUDE.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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

Comments
 (0)