@cloak.dev/sdk 0.1.7 → 0.2.0

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.
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  NATIVE_SOL_MINT,
3
- bigintToBytes32,
3
+ bigintToBytes322 as bigintToBytes32,
4
4
  bigintToHex,
5
- computeCommitment,
6
- computeNullifier,
5
+ computeCommitment2 as computeCommitment,
6
+ computeNullifier2 as computeNullifier,
7
7
  computeSignature,
8
8
  createUtxo,
9
9
  createZeroUtxo,
@@ -11,14 +11,15 @@ import {
11
11
  deriveUtxoKeypairFromSpendKey,
12
12
  deserializeUtxo,
13
13
  generateUtxoKeypair,
14
- hexToBigint,
14
+ hexToBigint2 as hexToBigint,
15
15
  pubkeyToFieldElement,
16
+ pubkeyToFieldLimbs,
16
17
  randomFieldElement,
17
18
  selectUtxos,
18
19
  serializeUtxo,
19
20
  sumUtxoAmounts,
20
21
  utxoEquals
21
- } from "./chunk-2SOX3JNO.js";
22
+ } from "./chunk-YX5SCAMR.js";
22
23
  export {
23
24
  NATIVE_SOL_MINT,
24
25
  bigintToBytes32,
@@ -34,6 +35,7 @@ export {
34
35
  generateUtxoKeypair,
35
36
  hexToBigint,
36
37
  pubkeyToFieldElement,
38
+ pubkeyToFieldLimbs,
37
39
  randomFieldElement,
38
40
  selectUtxos,
39
41
  serializeUtxo,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloak.dev/sdk",
3
- "description": "Shield, send, and swap on Solana privately TypeScript SDK with UTXO-based zero-knowledge transactions",
4
- "version": "0.1.7",
3
+ "description": "Shield, send, and swap on Solana privately \u2014 TypeScript SDK with UTXO-based zero-knowledge transactions",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
@@ -22,7 +22,8 @@
22
22
  "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
23
23
  "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
24
24
  "prepare": "npm run build",
25
- "prepublishOnly": "npm run build",
25
+ "prepublishOnly": "npm run build && node scripts/verify-dist-matches-src.mjs",
26
+ "verify:dist": "node scripts/verify-dist-matches-src.mjs",
26
27
  "example:fast-send": "tsx examples/fast-send.ts",
27
28
  "example:fast-usdc-send": "tsx examples/fast-usdc-send.ts",
28
29
  "example:usdc-pool-transfer": "tsx examples/usdc-pool-transfer.ts",
@@ -103,7 +104,6 @@
103
104
  "files": [
104
105
  "dist",
105
106
  "README.md",
106
- "CHANGELOG.md",
107
107
  "LICENSE"
108
108
  ],
109
109
  "publishConfig": {
package/CHANGELOG.md DELETED
@@ -1,173 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to `@cloak.dev/sdk` are documented here. The format
4
- follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
- project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
-
7
- ## [0.1.6] — 2026-04-27
8
-
9
- ### Removed (BREAKING)
10
-
11
- - **`CloakSDK.deposit / privateTransfer / withdraw / send / swap`** —
12
- these methods built a legacy `[discriminator: 1, amount: u64,
13
- commitment: 32]` deposit instruction. The deployed shield-pool program
14
- has repurposed discriminator `1` to `TransactSwap` (which requires
15
- `>= 8` accounts), so every external invocation hit `0x1063
16
- MissingAccounts` at ~112 CU. Reported by the cloak-squads hackathon
17
- team on 2026-04-26.
18
- - **`createDepositInstruction` / `validateDepositParams` /
19
- `DepositInstructionParams`** — the disc-1 instruction builder and its
20
- validators.
21
- - **`CloakNote` model and helpers** — `generateNote`, `parseNote`,
22
- `exportNote`, `findNoteByCommitment`, `filterNotesByNetwork`,
23
- `filterWithdrawableNotes`, `isWithdrawable`, `updateNoteWithDeposit`,
24
- `getRecipientAmount`, `generateNoteFromWallet`. The OLD
25
- `withdraw_regular.circom` model used a 3-input
26
- `Poseidon(amount, r0, r1, pk_spend)` commitment that is incompatible
27
- with the deployed `transaction.circom` UTXO circuit (4-input
28
- `Poseidon(amount, pubkey, blinding, mintAddress)`).
29
- - **`CloakSDK.loadNotes / saveNote / findNote / getTransactionMetadata`**
30
- and the `StorageAdapter.notes*` methods. The adapter is now keys-only.
31
- - **Encrypted-note helpers** — `prepareEncryptedOutput`,
32
- `prepareEncryptedOutputForRecipient`, `encodeNoteSimple` (legacy
33
- `CloakNote`-based variants).
34
- - **Pending-operations helpers** — `savePendingDeposit`/`Withdrawal`
35
- and friends, plus `PendingDeposit` / `PendingWithdrawal` types.
36
- - **Legacy proof generation** — `src/utils/proof-generation.ts`
37
- (`generateWithdrawRegularProof`, `generateWithdrawSwapProof`,
38
- `verifyCircuitIntegrity`, `verifyAllCircuits`,
39
- `areCircuitsAvailable`, `getDefaultCircuitsPath`,
40
- `EXPECTED_CIRCUIT_HASHES`, `DEFAULT_CIRCUITS_URL`).
41
- - **Legacy bigint hash helpers** in `src/utils/crypto.ts` —
42
- `computeCommitment` (3-arg), `generateCommitmentAsync`,
43
- `computeNullifier{,Async,Sync}`, `computeOutputsHash{,Async,Sync,FromLimbs}`,
44
- `computeSwapOutputsHash{,Async,Sync}`. The UTXO-model
45
- `computeCommitment(utxo)` / `computeNullifier(utxo)` live in
46
- `core/utxo` and are re-exported as `computeUtxoCommitment` /
47
- `computeUtxoNullifier`.
48
- - **OLD types** in `src/core/types.ts` — `CloakNote`, `Transfer`,
49
- `MaxLengthArray`, `TransferResult`, `DepositResult`, `DepositOptions`,
50
- `TransferOptions`, `WithdrawOptions`, `DepositStatus`, `SwapOptions`,
51
- `SwapParams`, `SwapResult`. Plus `proofTimeout` (was unused on the
52
- snapshot).
53
- - **`withdraw_regular_final.zkey`** — the ~10 MB legacy circuit asset.
54
-
55
- ### Changed (BREAKING)
56
-
57
- - **`CloakConfig` is now a snapshot-only type.** It is the structurally
58
- narrow value returned by `CloakSDK.getConfig()`: exactly seven
59
- fields — `network`, `cloakKeys`, `programId`, `poolAddress`,
60
- `merkleTreeAddress`, `treasuryAddress`, `debug`. The constructor
61
- input is the new exported `CloakSDKOptions` interface (which still
62
- accepts `keypairBytes`, `wallet`, and `storage` — they are consumed
63
- into private SDK state and are *never* persisted on the snapshot).
64
- `sdk.getConfig().keypairBytes` is now a `TS2339` compile-time error.
65
- - `CloakConfig.cloakKeys` is now typed as `CloakKeyPair | undefined`
66
- (was `any`).
67
- - `LocalStorageAdapter` constructor signature is `(keysKey?: string)`;
68
- the second `notesKey` parameter is gone.
69
- - `CloakSDK` constructor now throws `CloakError(_, "validation", false)`
70
- instead of plain `Error` when no signer is provided; `getPublicKey()`
71
- throws `CloakError(_, "wallet", false)` when neither keypair nor
72
- wallet adapter is configured. Consumers' `parseError()` /
73
- `classifyRelayError()` flows now handle these uniformly.
74
-
75
- ### Added
76
-
77
- - **`LocalStorageAdapter` auto-purges legacy plaintext secrets.** On
78
- the first construction in any browser origin, the adapter removes the
79
- pre-0.1.6 `cloak_notes` localStorage entry (which contained `r`
80
- randomness and `sk_spend` hex in plaintext) and writes a sentinel at
81
- `cloak_purged_v0_1_5` so subsequent constructions skip the work.
82
- Closes the upgrade-time security gap without requiring consumers to
83
- call any helper. Consumers using a non-default legacy key can still
84
- call `LocalStorageAdapter.purgeLegacyNoteStorage(customKey)`
85
- explicitly.
86
- - `LocalStorageAdapter.LEGACY_PURGE_SENTINEL_KEY` and
87
- `LocalStorageAdapter.LEGACY_NOTES_KEY` static constants for callers
88
- that want to inspect / reset the purge state.
89
- - `LocalStorageAdapter.runLegacyPurgeOnce()` static helper for callers
90
- that want to force the auto-purge before the first adapter
91
- instantiation (e.g. during an upgrade boot sequence).
92
-
93
- ### Security
94
-
95
- - `getConfig()` no longer leaks `keypairBytes`. The constructor
96
- consumed the secret bytes into a `Keypair` and used to spread the
97
- whole input onto `this.config`; `getConfig()`'s shallow-copy return
98
- exposed the original `Uint8Array` by reference. `this.config` is now
99
- built explicitly without `keypairBytes`/`wallet`/`storage`, and the
100
- type system enforces the asymmetry (see "Changed" above).
101
- - `LocalStorageAdapter` upgrade-time auto-purge (see "Added") removes
102
- the now-unredeemable plaintext secrets that pre-0.1.6 versions left
103
- in browser localStorage.
104
-
105
- ### Migration guide
106
-
107
- The supported flow for transaction emission is the functional UTXO API.
108
-
109
- ```ts
110
- import {
111
- transact,
112
- fullWithdraw,
113
- partialWithdraw,
114
- transfer,
115
- swapUtxo,
116
- createUtxo,
117
- createZeroUtxo,
118
- generateUtxoKeypair,
119
- CLOAK_PROGRAM_ID,
120
- NATIVE_SOL_MINT,
121
- } from "@cloak.dev/sdk";
122
- import { Connection, Keypair } from "@solana/web3.js";
123
-
124
- const connection = new Connection("https://api.mainnet-beta.solana.com");
125
- const signer = Keypair.fromSecretKey(/* ... */);
126
-
127
- // Deposit (positive externalAmount).
128
- const owner = await generateUtxoKeypair();
129
- const output = await createUtxo(10_000_000n, owner, NATIVE_SOL_MINT);
130
- const deposited = await transact(
131
- {
132
- inputUtxos: [
133
- await createZeroUtxo(NATIVE_SOL_MINT),
134
- await createZeroUtxo(NATIVE_SOL_MINT),
135
- ],
136
- outputUtxos: [output, await createZeroUtxo(NATIVE_SOL_MINT)],
137
- externalAmount: 10_000_000n,
138
- depositor: signer.publicKey,
139
- },
140
- {
141
- connection,
142
- programId: CLOAK_PROGRAM_ID,
143
- relayUrl: "https://api.cloak.ag",
144
- depositorKeypair: signer,
145
- },
146
- );
147
-
148
- // Reclaim to a public address.
149
- await fullWithdraw(deposited.outputUtxos, signer.publicKey, {
150
- connection,
151
- programId: CLOAK_PROGRAM_ID,
152
- relayUrl: "https://api.cloak.ag",
153
- depositorKeypair: signer,
154
- cachedMerkleTree: deposited.merkleTree,
155
- });
156
- ```
157
-
158
- The `CloakSDK` class is retained as a thin config + read-only helper
159
- (`getPublicKey`, `getCurrentRoot`, `getMerkleProof`,
160
- `getTransactionStatus`, `importWalletKeys`, `exportWalletKeys`,
161
- `getConfig`). Construction is unchanged for new fields.
162
-
163
- ### Verification
164
-
165
- - Mainnet end-to-end smoke (deposit + withdraw cycle) on the patched
166
- SDK: deposit signature
167
- `5iGmrMYoSucYJ5PzEo5BCWioqTof4KNfjowq14ZYrbQACicaP2tJdJfRUy2CctsQLyNu6xpYXn8gYBg3EefsmXrv`
168
- (slot 416038846, leaf 318); withdraw signature
169
- `37A4T1KNiZLkmRvLAtui5ZN2VCmzjsZMzzGNwMosFA2UM1NGn7kP28p5APTvKRb51rNaLmWF42TJqK9GSZM5xCxn`
170
- (slot 416055377). Net delta on the test wallet matches the expected
171
- fee schedule (0.005 fixed + 0.3% var).
172
-
173
- [0.1.6]: https://github.com/cloak-ag/sdk/pull/11
@@ -1,255 +0,0 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
- // src/core/utxo.ts
9
- import { PublicKey } from "@solana/web3.js";
10
- import { buildPoseidon } from "circomlibjs";
11
- import { blake3 } from "@noble/hashes/blake3";
12
- var FIELD_MODULUS = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
13
- var UTXO_KEY_DOMAIN = new TextEncoder().encode("cloak_utxo_priv_v1");
14
- var NATIVE_SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
15
- var poseidonInstance = null;
16
- async function getPoseidon() {
17
- if (!poseidonInstance) {
18
- poseidonInstance = await buildPoseidon();
19
- }
20
- return poseidonInstance;
21
- }
22
- function randomFieldElement() {
23
- const bytes = new Uint8Array(32);
24
- if (typeof crypto !== "undefined" && crypto.getRandomValues) {
25
- crypto.getRandomValues(bytes);
26
- } else {
27
- const { randomBytes } = __require("crypto");
28
- const nodeBytes = randomBytes(32);
29
- for (let i = 0; i < 32; i++) {
30
- bytes[i] = nodeBytes[i];
31
- }
32
- }
33
- let value = BigInt(0);
34
- for (let i = 0; i < 32; i++) {
35
- value = value << BigInt(8) | BigInt(bytes[i]);
36
- }
37
- return value % (FIELD_MODULUS >> BigInt(4));
38
- }
39
- async function generateUtxoKeypair() {
40
- const poseidon = await getPoseidon();
41
- const privateKey = randomFieldElement();
42
- const publicKeyHash = poseidon([privateKey, BigInt(0)]);
43
- const publicKey = poseidon.F.toObject(publicKeyHash);
44
- return { privateKey, publicKey };
45
- }
46
- async function deriveUtxoKeypairFromSpendKey(skSpend) {
47
- if (!skSpend || !(skSpend instanceof Uint8Array) || skSpend.length !== 32) {
48
- throw new Error("skSpend must be 32 bytes");
49
- }
50
- const preimage = new Uint8Array(UTXO_KEY_DOMAIN.length + skSpend.length);
51
- preimage.set(UTXO_KEY_DOMAIN, 0);
52
- preimage.set(skSpend, UTXO_KEY_DOMAIN.length);
53
- const hash = blake3(preimage);
54
- let value = BigInt(0);
55
- for (let i = 0; i < 32; i++) {
56
- value = value << BigInt(8) | BigInt(hash[i]);
57
- }
58
- const privateKey = value % (FIELD_MODULUS >> BigInt(4));
59
- const publicKey = await derivePublicKey(privateKey);
60
- return { privateKey, publicKey };
61
- }
62
- async function derivePublicKey(privateKey) {
63
- const poseidon = await getPoseidon();
64
- const hash = poseidon([privateKey, BigInt(0)]);
65
- return poseidon.F.toObject(hash);
66
- }
67
- async function createUtxo(amount, keypair, mintAddress = NATIVE_SOL_MINT) {
68
- const blinding = randomFieldElement();
69
- const utxo = {
70
- amount,
71
- keypair,
72
- blinding,
73
- mintAddress
74
- };
75
- utxo.commitment = await computeCommitment(utxo);
76
- return utxo;
77
- }
78
- async function createZeroUtxo(mintAddress = NATIVE_SOL_MINT, salt) {
79
- const poseidon = await getPoseidon();
80
- const effectiveSalt = salt ?? randomFieldElement();
81
- const publicKeyHash = poseidon([effectiveSalt, BigInt(0)]);
82
- const publicKey = poseidon.F.toObject(publicKeyHash);
83
- const keypair = { privateKey: effectiveSalt, publicKey };
84
- const utxo = {
85
- amount: BigInt(0),
86
- keypair,
87
- blinding: BigInt(0),
88
- mintAddress
89
- };
90
- utxo.commitment = await computeCommitment(utxo);
91
- utxo.index = 0;
92
- return utxo;
93
- }
94
- function pubkeyToFieldElement(pubkey) {
95
- const bytes = pubkey.toBytes();
96
- let value = BigInt(0);
97
- for (let i = 0; i < 32; i++) {
98
- value = value << BigInt(8) | BigInt(bytes[i]);
99
- }
100
- return value % FIELD_MODULUS;
101
- }
102
- async function computeCommitment(utxo) {
103
- const poseidon = await getPoseidon();
104
- const mintField = pubkeyToFieldElement(utxo.mintAddress);
105
- const hash = poseidon([
106
- utxo.amount,
107
- utxo.keypair.publicKey,
108
- utxo.blinding,
109
- mintField
110
- ]);
111
- return poseidon.F.toObject(hash);
112
- }
113
- async function computeSignature(privateKey, commitment, pathIndex) {
114
- const poseidon = await getPoseidon();
115
- const hash = poseidon([privateKey, commitment, pathIndex]);
116
- return poseidon.F.toObject(hash);
117
- }
118
- async function computeNullifier(utxo) {
119
- if (utxo.index === void 0) {
120
- throw new Error("UTXO must have an index to compute nullifier");
121
- }
122
- const poseidon = await getPoseidon();
123
- const commitment = utxo.commitment ?? await computeCommitment(utxo);
124
- const pathIndex = BigInt(utxo.index);
125
- const signature = await computeSignature(
126
- utxo.keypair.privateKey,
127
- commitment,
128
- pathIndex
129
- );
130
- const hash = poseidon([commitment, pathIndex, signature]);
131
- return poseidon.F.toObject(hash);
132
- }
133
- function serializeUtxo(utxo) {
134
- const buffer = new ArrayBuffer(128);
135
- const view = new DataView(buffer);
136
- const amountBytes = bigintToBytes(utxo.amount, 8);
137
- for (let i = 0; i < 8; i++) {
138
- view.setUint8(i, amountBytes[i]);
139
- }
140
- const privkeyBytes = bigintToBytes(utxo.keypair.privateKey, 32);
141
- for (let i = 0; i < 32; i++) {
142
- view.setUint8(8 + i, privkeyBytes[i]);
143
- }
144
- const blindingBytes = bigintToBytes(utxo.blinding, 32);
145
- for (let i = 0; i < 32; i++) {
146
- view.setUint8(40 + i, blindingBytes[i]);
147
- }
148
- const mintBytes = utxo.mintAddress.toBytes();
149
- for (let i = 0; i < 32; i++) {
150
- view.setUint8(72 + i, mintBytes[i]);
151
- }
152
- view.setUint32(104, utxo.index ?? 0, true);
153
- return new Uint8Array(buffer);
154
- }
155
- async function deserializeUtxo(bytes) {
156
- const view = new DataView(bytes.buffer, bytes.byteOffset);
157
- const amountBytes = bytes.slice(0, 8);
158
- const amount = bytesToBigint(amountBytes);
159
- const privkeyBytes = bytes.slice(8, 40);
160
- const privateKey = bytesToBigint(privkeyBytes);
161
- const publicKey = await derivePublicKey(privateKey);
162
- const blindingBytes = bytes.slice(40, 72);
163
- const blinding = bytesToBigint(blindingBytes);
164
- const mintBytes = bytes.slice(72, 104);
165
- const mintAddress = new PublicKey(mintBytes);
166
- const index = view.getUint32(104, true);
167
- const utxo = {
168
- amount,
169
- keypair: { privateKey, publicKey },
170
- blinding,
171
- mintAddress,
172
- index: index > 0 ? index : void 0
173
- };
174
- utxo.commitment = await computeCommitment(utxo);
175
- return utxo;
176
- }
177
- function bigintToBytes(value, length) {
178
- const result = new Uint8Array(length);
179
- let remaining = value;
180
- for (let i = 0; i < length; i++) {
181
- result[i] = Number(remaining & BigInt(255));
182
- remaining >>= BigInt(8);
183
- }
184
- return result;
185
- }
186
- function bytesToBigint(bytes) {
187
- let result = BigInt(0);
188
- for (let i = bytes.length - 1; i >= 0; i--) {
189
- result = result << BigInt(8) | BigInt(bytes[i]);
190
- }
191
- return result;
192
- }
193
- function bigintToHex(value) {
194
- return value.toString(16).padStart(64, "0");
195
- }
196
- function hexToBigint(hex) {
197
- const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
198
- return BigInt("0x" + cleanHex);
199
- }
200
- function bigintToBytes32(value) {
201
- const result = new Uint8Array(32);
202
- let remaining = value;
203
- for (let i = 31; i >= 0; i--) {
204
- result[i] = Number(remaining & BigInt(255));
205
- remaining >>= BigInt(8);
206
- }
207
- return result;
208
- }
209
- async function utxoEquals(a, b) {
210
- const commitmentA = a.commitment ?? await computeCommitment(a);
211
- const commitmentB = b.commitment ?? await computeCommitment(b);
212
- return commitmentA === commitmentB;
213
- }
214
- function sumUtxoAmounts(utxos) {
215
- return utxos.reduce((sum, utxo) => sum + utxo.amount, BigInt(0));
216
- }
217
- function selectUtxos(available, targetAmount) {
218
- const sorted = [...available].sort(
219
- (a, b) => Number(b.amount - a.amount)
220
- );
221
- const selected = [];
222
- let total = BigInt(0);
223
- for (const utxo of sorted) {
224
- if (total >= targetAmount) break;
225
- selected.push(utxo);
226
- total += utxo.amount;
227
- }
228
- if (total < targetAmount) {
229
- return null;
230
- }
231
- return selected;
232
- }
233
-
234
- export {
235
- __require,
236
- NATIVE_SOL_MINT,
237
- randomFieldElement,
238
- generateUtxoKeypair,
239
- deriveUtxoKeypairFromSpendKey,
240
- derivePublicKey,
241
- createUtxo,
242
- createZeroUtxo,
243
- pubkeyToFieldElement,
244
- computeCommitment,
245
- computeSignature,
246
- computeNullifier,
247
- serializeUtxo,
248
- deserializeUtxo,
249
- bigintToHex,
250
- hexToBigint,
251
- bigintToBytes32,
252
- utxoEquals,
253
- sumUtxoAmounts,
254
- selectUtxos
255
- };