@veilo/sdk-core 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,439 @@
1
+ # @zkprivacysol/sdk-core
2
+
3
+ Tiny TypeScript SDK for the `privacy-pool` Anchor program.
4
+
5
+ This package wraps the on-chain program with a small set of ergonomic helpers for:
6
+
7
+ - Deriving PDAs (`config`, `vault`, `note_tree`, `nullifiers`)
8
+ - Initializing the pool with fixed SOL denominations
9
+ - Depositing using a note commitment + **off-chain Merkle root**
10
+ - Withdrawing via an authorized relayer (with fee + TVL accounting)
11
+ - Building simple Merkle roots off-chain (for demo / testing)
12
+ - Handling note commitments (`createRandomNote`, `commitNote`, etc.)
13
+
14
+ > **Status:** internal/dev SDK. No production guarantees.
15
+ > ZK verification is expected to happen **off-chain in a relayer service**.
16
+ > On-chain, `proof: Vec<u8>` is treated as opaque bytes (hook for a future verifier).
17
+
18
+ ---
19
+
20
+ ## 1. Install
21
+
22
+ From the monorepo root (or inside the package folder):
23
+
24
+ ```bash
25
+ cd packages/sdk-core
26
+ npm install
27
+ ```
28
+
29
+ If you publish it somewhere later:
30
+
31
+ ```bash
32
+ npm install @zkprivacysol/sdk-core
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 2. Prerequisites
38
+
39
+ You need:
40
+
41
+ - A running Solana validator (localnet recommended):
42
+
43
+ ```bash
44
+ solana-test-validator
45
+ ```
46
+
47
+ - The `privacy-pool` program built and deployed to that validator.
48
+ - The `privacy-pool` Anchor IDL available (the SDK tests load it from):
49
+
50
+ ```text
51
+ ../../privacy-pool/target/idl/privacy_pool.json
52
+ ```
53
+
54
+ - A funded keypair on that validator:
55
+
56
+ ```bash
57
+ solana config set --url http://127.0.0.1:8899
58
+ solana-keygen new --outfile ~/.config/solana/id.json
59
+ solana airdrop 10
60
+ ```
61
+
62
+ Environment variables for tests:
63
+
64
+ ```bash
65
+ export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
66
+ export ANCHOR_WALLET=$HOME/.config/solana/id.json
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 3. Build & Test
72
+
73
+ From `packages/sdk-core`:
74
+
75
+ ```bash
76
+ # Typecheck & build to dist/
77
+ npm run build
78
+
79
+ # Run unit + integration tests
80
+ npm test
81
+ ```
82
+
83
+ What the tests do:
84
+
85
+ - **Unit tests (`note.test.ts`)**
86
+ - `createRandomNote` / `encodeNoteToBytes` / `commitNote` / `createNoteWithCommitment`
87
+ - Ensures 32-byte commitments, deterministic encoding, etc.
88
+
89
+ - **Integration test (`sdk.integration.test.ts`)**
90
+ - Loads the `privacy-pool` IDL from `privacy-pool/target/idl/privacy_pool.json`
91
+ - Constructs an Anchor `Program` with a provider from `ANCHOR_PROVIDER_URL` / `ANCHOR_WALLET`
92
+ - Runs end-to-end flow:
93
+ 1. `initializePool` (configures denoms + fee)
94
+ 2. `createNoteAndDeposit` (creates note, commits it, calls on-chain `depositFixed` and updates the Merkle root)
95
+ 3. `addRelayer`
96
+ 4. `withdrawViaRelayer` (using a Merkle root that actually contains the note + a demo nullifier, empty proof)
97
+ 5. Asserts vault TVL decreased, recipient gained funds, relayer received fee.
98
+
99
+ The integration test uses an **empty proof** for now; in a real deployment your **relayer** would generate and verify a Groth16/Plonk proof off-chain, then pass the proof bytes into `withdraw`.
100
+
101
+ ---
102
+
103
+ ## 4. SDK Surface
104
+
105
+ ### 4.1 PDA helpers
106
+
107
+ ```ts
108
+ import { getPoolPdas } from "@zkprivacysol/sdk-core";
109
+ import { PublicKey } from "@solana/web3.js";
110
+
111
+ const programId = new PublicKey("YourProgram1111111111111111111111111111111111");
112
+ const { config, vault, noteTree, nullifiers } = getPoolPdas(programId);
113
+ ```
114
+
115
+ These must match the on-chain seeds (v3 layout):
116
+
117
+ - `["privacy_config_v3"]`
118
+ - `["privacy_vault_v3"]`
119
+ - `["privacy_note_tree_v3"]`
120
+ - `["privacy_nullifiers_v3"]`
121
+
122
+ ---
123
+
124
+ ### 4.2 Pool init / configuration
125
+
126
+ ```ts
127
+ import * as anchor from "@coral-xyz/anchor";
128
+ import { initializePool } from "@zkprivacysol/sdk-core";
129
+ import { sol } from "@zkprivacysol/sdk-core/config";
130
+ import type { Program, Idl } from "@coral-xyz/anchor";
131
+
132
+ async function initPool(program: Program<Idl>, adminWallet: anchor.Wallet) {
133
+ await initializePool({
134
+ program,
135
+ admin: adminWallet,
136
+ denomsLamports: [sol(1), sol(5)], // 1 SOL & 5 SOL
137
+ feeBps: 50, // 0.5% fee
138
+ });
139
+ }
140
+ ```
141
+
142
+ This calls the on-chain `initialize` instruction and sets:
143
+
144
+ - fixed denominations (in lamports)
145
+ - vault + note tree + nullifier set PDAs
146
+ - fee in basis points
147
+ - initial TVL = 0
148
+
149
+ ---
150
+
151
+ ### 4.3 Notes & commitments
152
+
153
+ `src/note.ts` is a small “note” helper module. It **does not** implement real zk-friendly Poseidon hashing yet; it’s just using SHA-256 to get 32-byte commitments that the on-chain program treats as opaque.
154
+
155
+ ```ts
156
+ import {
157
+ createRandomNote,
158
+ encodeNoteToBytes,
159
+ commitNote,
160
+ createNoteWithCommitment,
161
+ } from "@zkprivacysol/sdk-core/note";
162
+ import { Keypair } from "@solana/web3.js";
163
+
164
+ const owner = Keypair.generate().publicKey;
165
+
166
+ // 1. Create a note
167
+ const note = createRandomNote({
168
+ value: 1_000_000n, // lamports
169
+ owner,
170
+ });
171
+
172
+ // 2. Encode deterministically
173
+ const bytes = encodeNoteToBytes(note);
174
+
175
+ // 3. Hash to a 32-byte commitment (placeholder)
176
+ const commitment = commitNote(note);
177
+
178
+ // 4. Convenience combo
179
+ const full = createNoteWithCommitment({
180
+ value: 1_000_000n,
181
+ owner,
182
+ });
183
+ // full.commitment is 32 bytes
184
+ ```
185
+
186
+ Encoding layout:
187
+
188
+ ```text
189
+ value (u64 LE, 8 bytes)
190
+ || owner pubkey (32 bytes)
191
+ || rho (32 bytes random)
192
+ || r (32 bytes random)
193
+ ```
194
+
195
+ Hash:
196
+
197
+ ```ts
198
+ commitment = sha256(encodedBytes);
199
+ ```
200
+
201
+ Later, a real implementation should swap this out for the exact hash function used inside the zk circuit (Poseidon/Rescue/etc.). The on-chain program just sees `[u8; 32]`.
202
+
203
+ ---
204
+
205
+ ### 4.4 Merkle helpers (demo-only)
206
+
207
+ `src/merkle.ts` provides **very basic** Merkle helpers so callers can build roots off-chain. This is meant for demos/tests, not production.
208
+
209
+ Key functions:
210
+
211
+ ```ts
212
+ import {
213
+ merkleLeafFromCommitment,
214
+ merkleHashPair,
215
+ merkleRootFromLeaves,
216
+ MerkleTree,
217
+ } from "@zkprivacysol/sdk-core/merkle";
218
+
219
+ // Stateless helpers
220
+ const leaf = merkleLeafFromCommitment(commitment);
221
+ const parent = merkleHashPair(left, right);
222
+ const root = merkleRootFromLeaves([leaf1, leaf2, leaf3]);
223
+
224
+ // Simple incremental tree (toy)
225
+ const tree = new MerkleTree();
226
+ const { index, root: newRoot } = tree.insert(commitment);
227
+ const path = tree.getPath(index); // Merkle path for proofs
228
+ ```
229
+
230
+ Notes:
231
+
232
+ - Uses SHA-256 under the hood, returning `Uint8Array` of length 32.
233
+ - Pads with a “zero node” derived from hashing the all-zero leaf repeatedly up the tree.
234
+ - This is intentionally “toy” to keep the SDK usable while the real circuit/Merkle design is still in flux.
235
+ - On-chain, the program only stores the **latest Merkle root** in the `NoteTree` account (v3 layout exposes a `currentRoot` field).
236
+
237
+ ---
238
+
239
+ ### 4.5 Deposits
240
+
241
+ Low-level helper (you supply both commitment + Merkle root):
242
+
243
+ ```ts
244
+ import * as anchor from "@coral-xyz/anchor";
245
+ import { depositFixedSol } from "@zkprivacysol/sdk-core";
246
+
247
+ const provider = anchor.getProvider() as anchor.AnchorProvider;
248
+ const wallet = provider.wallet as anchor.Wallet;
249
+
250
+ await depositFixedSol({
251
+ program,
252
+ depositor: wallet,
253
+ denomIndex: 0, // index into cfg.denoms
254
+ commitment, // 32-byte note commitment
255
+ newRoot, // 32-byte Merkle root (caller computed off-chain)
256
+ });
257
+ ```
258
+
259
+ High-level helper (with note creation, but you still feed a root):
260
+
261
+ ```ts
262
+ import * as anchor from "@coral-xyz/anchor";
263
+ import { createNoteAndDeposit } from "@zkprivacysol/sdk-core";
264
+ import { sol } from "@zkprivacysol/sdk-core/config";
265
+
266
+ const provider = anchor.getProvider() as anchor.AnchorProvider;
267
+ const wallet = provider.wallet as anchor.Wallet;
268
+
269
+ const dummyRoot = new Uint8Array(32).fill(7); // replace with real Merkle root
270
+
271
+ const note = await createNoteAndDeposit({
272
+ program,
273
+ depositor: wallet,
274
+ denomIndex: 0,
275
+ valueLamports: sol(1),
276
+ newRoot: dummyRoot,
277
+ });
278
+
279
+ // note.commitment can later be used in your off-chain tree
280
+ ```
281
+
282
+ There’s also a higher-level helper that integrates directly with an in-memory `MerkleTree`:
283
+
284
+ ```ts
285
+ import { createNoteDepositWithMerkle } from "@zkprivacysol/sdk-core";
286
+ import { MerkleTree } from "@zkprivacysol/sdk-core/merkle";
287
+
288
+ const tree = new MerkleTree();
289
+
290
+ const { note, leafIndex, root, merklePath } =
291
+ await createNoteDepositWithMerkle({
292
+ program,
293
+ depositor: wallet,
294
+ denomIndex: 0,
295
+ valueLamports: sol(1),
296
+ tree,
297
+ });
298
+
299
+ // `root` is what got written to the on-chain NoteTree
300
+ // `merklePath` can be used as witness for the zk circuit
301
+ ```
302
+
303
+ On-chain, the `depositFixed` instruction:
304
+
305
+ - moves SOL from `depositor` to the vault PDA,
306
+ - updates TVL,
307
+ - writes `new_root` into the on-chain note tree’s current root field.
308
+
309
+ ---
310
+
311
+ ### 4.6 Relayers & Withdrawals
312
+
313
+ Add a relayer (admin-only):
314
+
315
+ ```ts
316
+ import * as anchor from "@coral-xyz/anchor";
317
+ import { addRelayer } from "@zkprivacysol/sdk-core";
318
+ import { Keypair } from "@solana/web3.js";
319
+
320
+ const provider = anchor.getProvider() as anchor.AnchorProvider;
321
+ const wallet = provider.wallet as anchor.Wallet;
322
+
323
+ const relayer = Keypair.generate();
324
+
325
+ await addRelayer({
326
+ program,
327
+ admin: wallet,
328
+ newRelayer: relayer.publicKey,
329
+ });
330
+ ```
331
+
332
+ Withdraw via relayer (SDK-level helper):
333
+
334
+ ```ts
335
+ import { withdrawViaRelayer } from "@zkprivacysol/sdk-core";
336
+ import { Keypair } from "@solana/web3.js";
337
+
338
+ const relayer = Keypair.generate();
339
+ const recipient = Keypair.generate();
340
+
341
+ const root = /* 32-byte Merkle root containing the note */;
342
+ const nullifier = new Uint8Array(32).fill(3); // demo only
343
+
344
+ // In the real world, `proof` will be zk-proof bytes coming from your prover.
345
+ const proofBytes = new Uint8Array([]); // currently ignored by on-chain program
346
+
347
+ await withdrawViaRelayer({
348
+ program,
349
+ relayer,
350
+ recipient: recipient.publicKey,
351
+ denomIndex: 0,
352
+ root,
353
+ nullifier,
354
+ proof: proofBytes,
355
+ });
356
+ ```
357
+
358
+ There is also a higher-level helper (`withdrawViaRelayerWithProof`) that takes:
359
+
360
+ - `noteData` (serialized note),
361
+ - `merklePath`,
362
+ - `feeBps`,
363
+ - a `builder: ProofBuilder` callback
364
+
365
+ and lets you plug in your own proof generator. In practice, your **relayer service** will own that logic.
366
+
367
+ **Production pattern:**
368
+
369
+ - A **backend relayer service** (see `packages/relayer` or similar) owns:
370
+ - the proving key / circuits,
371
+ - a mirror view of the Merkle tree and nullifier set,
372
+ - a funded relayer keypair.
373
+ - The front-end sends a withdraw request to that service:
374
+ - `root`, `nullifier`, `denomIndex`, `recipient`, plus any private witness data.
375
+ - The relayer:
376
+ 1. Builds & verifies the zk proof off-chain.
377
+ 2. Packs it into bytes (e.g. via a `packProofToBytes` helper).
378
+ 3. Calls the on-chain `withdraw` via Anchor, using the same program/PDAs as the SDK.
379
+
380
+ From the SDK’s perspective, `proof: Uint8Array` is **already-built**; this package doesn’t know how you generated it.
381
+
382
+ ---
383
+
384
+ ## 5. Environment & Localnet
385
+
386
+ To run the integration tests successfully, you should:
387
+
388
+ 1. Start local validator:
389
+
390
+ ```bash
391
+ solana-test-validator
392
+ ```
393
+
394
+ 2. Build & deploy the `privacy-pool` Anchor program in `packages/privacy-pool`:
395
+
396
+ ```bash
397
+ cd packages/privacy-pool
398
+ anchor build
399
+ anchor deploy
400
+ ```
401
+
402
+ 3. Ensure your CLI and wallet match the validator:
403
+
404
+ ```bash
405
+ solana config set --url http://127.0.0.1:8899
406
+ solana-keygen new --outfile ~/.config/solana/id.json
407
+ solana airdrop 10
408
+ ```
409
+
410
+ 4. Export env vars (or inject via `npm test` script):
411
+
412
+ ```bash
413
+ export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
414
+ export ANCHOR_WALLET=$HOME/.config/solana/id.json
415
+ ```
416
+
417
+ 5. Then from `packages/sdk-core`:
418
+
419
+ ```bash
420
+ npm run build
421
+ npm test
422
+ ```
423
+
424
+ ---
425
+
426
+ ## 6. Limitations & TODOs
427
+
428
+ - **Merkle tree is minimal.**
429
+ - Toy implementation, primarily for demos/tests.
430
+ - No persisted tree; you’re expected to maintain state in your own service.
431
+ - **On-chain NoteTree only stores the latest root.**
432
+ - Historical roots/nullifiers must be mirrored off-chain.
433
+ - **Proofs are relayer-only.**
434
+ - SDK does **not** generate Groth16/Plonk proofs.
435
+ - On-chain program currently only sees `Vec<u8>` and does not verify it yet.
436
+ - **API is still evolving.**
437
+ - Types, exports, and function signatures may change as the circuit + relayer design solidifies.
438
+
439
+ This SDK is meant as a thin, hackable layer around the Anchor program while the core privacy design (circuit, proof system, Merkle layout, relayer flow) is being explored.
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@veilo/sdk-core",
3
+ "version": "0.1.17",
4
+ "description": "Tiny TypeScript SDK for the `privacy-pool` Anchor program.",
5
+ "homepage": "https://github.com/ZKSOLDev/core-sdk#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/ZKSOLDev/core-sdk/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ZKSOLDev/core-sdk.git"
12
+ },
13
+ "license": "ISC",
14
+ "author": "",
15
+ "type": "commonjs",
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "directories": {
19
+ "test": "tests"
20
+ },
21
+ "scripts": {
22
+ "build": "rm -rf dist/ && tsc -p tsconfig.json",
23
+ "test": "ANCHOR_PROVIDER_URL=http://127.0.0.1:8899 ANCHOR_WALLET=$HOME/.config/solana/id.json rm -rf dist/ && tsc -p test-tsconfig.json && mocha -t 1000000 \"dist/tests/**/*.js\""
24
+ },
25
+ "dependencies": {
26
+ "@coral-xyz/anchor": "^0.30.0",
27
+ "@noble/hashes": "^1.8.0",
28
+ "@solana/spl-token": "^0.4.6",
29
+ "@solana/web3.js": "^1.95.0",
30
+ "circomlibjs": "^0.1.7",
31
+ "mocha": "^11.7.5",
32
+ "tweetnacl": "^1.0.3"
33
+ },
34
+ "devDependencies": {
35
+ "@types/circomlibjs": "^0.1.6",
36
+ "@types/mocha": "^10.0.10",
37
+ "@types/node": "^24.10.1",
38
+ "chai": "^6.2.1",
39
+ "ts-node": "^10.9.2",
40
+ "typescript": "^5.9.3"
41
+ }
42
+ }