@veilo/sdk-core 0.3.3 → 0.4.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.
Files changed (195) hide show
  1. package/README.md +42 -3
  2. package/SHIELD_INTEGRATION.md +143 -0
  3. package/config.d.ts +2 -0
  4. package/config.js +4 -0
  5. package/dist/cjs/client.d.ts +5 -5
  6. package/dist/cjs/client.js +79 -42
  7. package/dist/cjs/compactNote.d.ts +107 -0
  8. package/dist/cjs/compactNote.js +167 -0
  9. package/dist/cjs/events.d.ts +1 -1
  10. package/dist/cjs/events.js +2 -2
  11. package/dist/cjs/idl/privacy_pool.d.ts +5 -0
  12. package/dist/cjs/idl/privacy_pool.js +15218 -0
  13. package/dist/cjs/index.d.ts +16 -13
  14. package/dist/cjs/index.js +40 -30
  15. package/dist/cjs/merkle.d.ts +13 -0
  16. package/dist/cjs/merkle.js +31 -8
  17. package/dist/cjs/program.d.ts +11 -0
  18. package/dist/cjs/program.js +26 -3
  19. package/dist/cjs/proof.d.ts +1 -1
  20. package/dist/cjs/proof.js +37 -37
  21. package/dist/cjs/prover.d.ts +1 -1
  22. package/dist/cjs/prover.js +2 -2
  23. package/dist/cjs/random.d.ts +16 -0
  24. package/dist/cjs/random.js +28 -0
  25. package/dist/cjs/relayer.d.ts +27 -4
  26. package/dist/cjs/relayer.js +16 -5
  27. package/dist/cjs/shield/alt.d.ts +87 -0
  28. package/dist/cjs/shield/alt.js +194 -0
  29. package/dist/cjs/shield/computeBudget.d.ts +61 -0
  30. package/dist/cjs/shield/computeBudget.js +64 -0
  31. package/dist/cjs/shield/errors.d.ts +58 -0
  32. package/dist/cjs/shield/errors.js +121 -0
  33. package/dist/cjs/shield/finalize.d.ts +45 -0
  34. package/dist/cjs/shield/finalize.js +119 -0
  35. package/dist/cjs/shield/index.d.ts +35 -0
  36. package/dist/cjs/shield/index.js +68 -0
  37. package/dist/cjs/shield/ix.d.ts +54 -0
  38. package/dist/cjs/shield/ix.js +119 -0
  39. package/dist/cjs/shield/owner.d.ts +36 -0
  40. package/dist/cjs/shield/owner.js +126 -0
  41. package/dist/cjs/shield/ports.d.ts +43 -0
  42. package/dist/cjs/shield/ports.js +153 -0
  43. package/dist/cjs/shield/preflight.d.ts +30 -0
  44. package/dist/cjs/shield/preflight.js +154 -0
  45. package/dist/cjs/shield/shield.d.ts +68 -0
  46. package/dist/cjs/shield/shield.js +499 -0
  47. package/dist/cjs/shield/types.d.ts +202 -0
  48. package/dist/cjs/shield/types.js +2 -0
  49. package/dist/cjs/utxo.d.ts +20 -0
  50. package/dist/cjs/utxo.js +41 -28
  51. package/dist/esm/client.d.ts +407 -0
  52. package/dist/esm/client.js +9 -5
  53. package/dist/esm/compactNote.d.ts +107 -0
  54. package/dist/esm/compactNote.js +156 -0
  55. package/dist/esm/config.d.ts +82 -0
  56. package/dist/esm/events.d.ts +77 -0
  57. package/dist/esm/events.js +1 -1
  58. package/dist/esm/idl/privacy_pool.d.ts +5 -0
  59. package/dist/esm/idl/privacy_pool.js +15216 -0
  60. package/dist/esm/index.d.ts +16 -0
  61. package/dist/esm/index.js +18 -11
  62. package/dist/esm/merkle.d.ts +77 -0
  63. package/dist/esm/merkle.js +23 -1
  64. package/dist/esm/package.json +1 -0
  65. package/dist/esm/poseidon.d.ts +29 -0
  66. package/dist/esm/program.d.ts +37 -0
  67. package/dist/esm/program.js +23 -1
  68. package/dist/esm/proof.d.ts +183 -0
  69. package/dist/esm/proof.js +2 -2
  70. package/dist/esm/prover.d.ts +54 -0
  71. package/dist/esm/prover.js +1 -1
  72. package/dist/esm/random.d.ts +16 -0
  73. package/dist/esm/random.js +21 -0
  74. package/dist/esm/relayer.d.ts +318 -0
  75. package/dist/esm/relayer.js +16 -5
  76. package/dist/esm/retry.d.ts +32 -0
  77. package/dist/esm/shield/alt.d.ts +87 -0
  78. package/dist/esm/shield/alt.js +186 -0
  79. package/dist/esm/shield/computeBudget.d.ts +61 -0
  80. package/dist/esm/shield/computeBudget.js +61 -0
  81. package/dist/esm/shield/errors.d.ts +58 -0
  82. package/dist/esm/shield/errors.js +115 -0
  83. package/dist/esm/shield/finalize.d.ts +45 -0
  84. package/dist/esm/shield/finalize.js +83 -0
  85. package/dist/esm/shield/index.d.ts +35 -0
  86. package/dist/esm/shield/index.js +32 -0
  87. package/dist/esm/shield/ix.d.ts +54 -0
  88. package/dist/esm/shield/ix.js +82 -0
  89. package/dist/esm/shield/owner.d.ts +36 -0
  90. package/dist/esm/shield/owner.js +122 -0
  91. package/dist/esm/shield/ports.d.ts +43 -0
  92. package/dist/esm/shield/ports.js +147 -0
  93. package/dist/esm/shield/preflight.d.ts +30 -0
  94. package/dist/esm/shield/preflight.js +151 -0
  95. package/dist/esm/shield/shield.d.ts +68 -0
  96. package/dist/esm/shield/shield.js +492 -0
  97. package/dist/esm/shield/types.d.ts +202 -0
  98. package/dist/esm/shield/types.js +1 -0
  99. package/dist/esm/utxo.d.ts +235 -0
  100. package/dist/esm/utxo.js +18 -8
  101. package/dist/src/client.d.ts +407 -0
  102. package/dist/src/client.js +951 -0
  103. package/dist/src/compactNote.d.ts +107 -0
  104. package/dist/src/compactNote.js +167 -0
  105. package/dist/src/config.d.ts +82 -0
  106. package/dist/src/config.js +57 -0
  107. package/dist/src/events.d.ts +77 -0
  108. package/dist/src/events.js +167 -0
  109. package/dist/src/idl/privacy_pool.d.ts +5 -0
  110. package/dist/src/idl/privacy_pool.js +15218 -0
  111. package/dist/src/index.d.ts +16 -0
  112. package/dist/src/index.js +67 -0
  113. package/dist/src/merkle.d.ts +77 -0
  114. package/dist/src/merkle.js +156 -0
  115. package/dist/src/poseidon.d.ts +29 -0
  116. package/dist/src/poseidon.js +100 -0
  117. package/dist/src/program.d.ts +37 -0
  118. package/dist/src/program.js +61 -0
  119. package/dist/src/proof.d.ts +183 -0
  120. package/dist/src/proof.js +292 -0
  121. package/dist/src/prover.d.ts +54 -0
  122. package/dist/src/prover.js +112 -0
  123. package/dist/src/random.d.ts +16 -0
  124. package/dist/src/random.js +28 -0
  125. package/dist/src/relayer.d.ts +318 -0
  126. package/dist/src/relayer.js +257 -0
  127. package/dist/src/retry.d.ts +32 -0
  128. package/dist/src/retry.js +75 -0
  129. package/dist/src/shield/alt.d.ts +87 -0
  130. package/dist/src/shield/alt.js +194 -0
  131. package/dist/src/shield/computeBudget.d.ts +61 -0
  132. package/dist/src/shield/computeBudget.js +64 -0
  133. package/dist/src/shield/errors.d.ts +58 -0
  134. package/dist/src/shield/errors.js +121 -0
  135. package/dist/src/shield/finalize.d.ts +45 -0
  136. package/dist/src/shield/finalize.js +119 -0
  137. package/dist/src/shield/index.d.ts +35 -0
  138. package/dist/src/shield/index.js +68 -0
  139. package/dist/src/shield/ix.d.ts +54 -0
  140. package/dist/src/shield/ix.js +119 -0
  141. package/dist/src/shield/owner.d.ts +36 -0
  142. package/dist/src/shield/owner.js +126 -0
  143. package/dist/src/shield/ports.d.ts +43 -0
  144. package/dist/src/shield/ports.js +153 -0
  145. package/dist/src/shield/preflight.d.ts +30 -0
  146. package/dist/src/shield/preflight.js +154 -0
  147. package/dist/src/shield/shield.d.ts +68 -0
  148. package/dist/src/shield/shield.js +499 -0
  149. package/dist/src/shield/types.d.ts +202 -0
  150. package/dist/src/shield/types.js +2 -0
  151. package/dist/src/utxo.d.ts +235 -0
  152. package/dist/src/utxo.js +407 -0
  153. package/dist/tests/compact-note.test.d.ts +1 -0
  154. package/dist/tests/compact-note.test.js +173 -0
  155. package/dist/tests/config.test.d.ts +1 -0
  156. package/dist/tests/config.test.js +102 -0
  157. package/dist/tests/edge-cases.test.d.ts +1 -0
  158. package/dist/tests/edge-cases.test.js +220 -0
  159. package/dist/tests/encryption.test.d.ts +1 -0
  160. package/dist/tests/encryption.test.js +215 -0
  161. package/dist/tests/events.test.d.ts +1 -0
  162. package/dist/tests/events.test.js +78 -0
  163. package/dist/tests/multi-tree.test.d.ts +1 -0
  164. package/dist/tests/multi-tree.test.js +405 -0
  165. package/dist/tests/pda.test.d.ts +1 -0
  166. package/dist/tests/pda.test.js +229 -0
  167. package/dist/tests/poseidon-builder-parity.test.d.ts +1 -0
  168. package/dist/tests/poseidon-builder-parity.test.js +72 -0
  169. package/dist/tests/poseidon.test.d.ts +1 -0
  170. package/dist/tests/poseidon.test.js +142 -0
  171. package/dist/tests/proof.test.d.ts +1 -0
  172. package/dist/tests/proof.test.js +296 -0
  173. package/dist/tests/relayer.test.d.ts +1 -0
  174. package/dist/tests/relayer.test.js +271 -0
  175. package/dist/tests/sdk.integration.test.d.ts +1 -0
  176. package/dist/tests/sdk.integration.test.js +330 -0
  177. package/dist/tests/shield-owner.test.d.ts +1 -0
  178. package/dist/tests/shield-owner.test.js +89 -0
  179. package/dist/tests/shield-preflight.test.d.ts +1 -0
  180. package/dist/tests/shield-preflight.test.js +87 -0
  181. package/dist/tests/shield-realproof.test.d.ts +1 -0
  182. package/dist/tests/shield-realproof.test.js +272 -0
  183. package/dist/tests/shield.test.d.ts +1 -0
  184. package/dist/tests/shield.test.js +403 -0
  185. package/dist/tests/utxo.test.d.ts +1 -0
  186. package/dist/tests/utxo.test.js +140 -0
  187. package/package.json +69 -11
  188. package/poseidon.d.ts +2 -0
  189. package/poseidon.js +4 -0
  190. package/proof.d.ts +2 -0
  191. package/proof.js +4 -0
  192. package/prover.d.ts +2 -0
  193. package/prover.js +4 -0
  194. package/shield.d.ts +2 -0
  195. package/shield.js +4 -0
@@ -0,0 +1,403 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ require("mocha");
7
+ const assert_1 = require("assert");
8
+ const web3_js_1 = require("@solana/web3.js");
9
+ const spl_token_1 = require("@solana/spl-token");
10
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
11
+ const src_1 = require("../src");
12
+ /**
13
+ * The de-risking test for `shield()`.
14
+ *
15
+ * Everything expensive and non-deterministic is stubbed — the prover, the tree
16
+ * source, the lookup table, the RPC — so what remains under test is the part
17
+ * that actually decides whether a shield works: does the assembled transaction
18
+ * fit in 1232 bytes, is it unsigned, does the note belong to the right person,
19
+ * and does `rebuild()` really avoid re-proving.
20
+ *
21
+ * The size assertions are the point. A shield that is 40 bytes too large fails
22
+ * at the wallet with an opaque error, and no amount of on-chain testing tells
23
+ * you which account caused it.
24
+ */
25
+ const USDC = new web3_js_1.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
26
+ const BLOCKHASH_2 = new web3_js_1.PublicKey(new Uint8Array(32).fill(2)).toBase58();
27
+ const BLOCKHASH_3 = new web3_js_1.PublicKey(new Uint8Array(32).fill(3)).toBase58();
28
+ const BLOCKHASH_4 = new web3_js_1.PublicKey(new Uint8Array(32).fill(4)).toBase58();
29
+ // --- stubs -------------------------------------------------------------------
30
+ let proverCalls = 0;
31
+ /** A structurally valid Groth16 proof. The values are nonsense; the sizes are not. */
32
+ const stubProver = async () => {
33
+ proverCalls++;
34
+ const f = (n) => n.toString();
35
+ return {
36
+ pi_a: [f(1), f(2), f(1)],
37
+ pi_b: [
38
+ [f(3), f(4)],
39
+ [f(5), f(6)],
40
+ [f(1), f(0)],
41
+ ],
42
+ pi_c: [f(7), f(8), f(1)],
43
+ protocol: "groth16",
44
+ curve: "bn128",
45
+ };
46
+ };
47
+ function stubConnection(overrides = {}) {
48
+ return {
49
+ getLatestBlockhash: async () => ({
50
+ blockhash: "11111111111111111111111111111111",
51
+ lastValidBlockHeight: 123456789,
52
+ }),
53
+ getMinimumBalanceForRentExemption: async () => 961680,
54
+ ...overrides,
55
+ };
56
+ }
57
+ /** A lookup table containing exactly the accounts a shield needs compressed. */
58
+ async function stubAlt(mint, outputTreeId, extra = []) {
59
+ const required = (0, src_1.requiredAltKeys)({ mint, outputTreeId }).map((k) => k.address);
60
+ const addresses = [...required, ...extra];
61
+ return new web3_js_1.AddressLookupTableAccount({
62
+ key: web3_js_1.PublicKey.unique(),
63
+ state: {
64
+ deactivationSlot: BigInt("18446744073709551615"),
65
+ lastExtendedSlot: 0,
66
+ lastExtendedSlotStartIndex: 0,
67
+ authority: undefined,
68
+ addresses,
69
+ },
70
+ });
71
+ }
72
+ function makeOwner() {
73
+ const wallet = tweetnacl_1.default.sign.keyPair();
74
+ return {
75
+ owner: (0, src_1.unsafeShieldOwner)({
76
+ veiloPublicKey: (0, src_1.generateKeypair)().publicKey,
77
+ noteViewingKey: new web3_js_1.PublicKey(wallet.publicKey),
78
+ acknowledgeOwnerInvariant: true,
79
+ }),
80
+ secretKey: wallet.secretKey,
81
+ };
82
+ }
83
+ const TARGET = {
84
+ inputTreeId: 0,
85
+ inputRoot: new Uint8Array(32).fill(9),
86
+ outputTreeId: 0,
87
+ outputNextIndex: 4242,
88
+ };
89
+ // --- tests -------------------------------------------------------------------
90
+ describe("shield()", function () {
91
+ this.timeout(60000);
92
+ before(async () => {
93
+ await (0, src_1.initPoseidon)();
94
+ });
95
+ beforeEach(() => {
96
+ proverCalls = 0;
97
+ });
98
+ async function buildShield(opts = {}) {
99
+ const mint = opts.mint ?? src_1.NATIVE_SOL_MINT;
100
+ const outputTreeId = opts.outputTreeId ?? 0;
101
+ const { owner, secretKey } = opts.owner
102
+ ? { owner: opts.owner, secretKey: new Uint8Array(64) }
103
+ : makeOwner();
104
+ const signerPubkey = web3_js_1.Keypair.generate().publicKey;
105
+ const alt = await stubAlt(mint, outputTreeId);
106
+ const result = await (0, src_1.shield)({
107
+ connection: stubConnection(),
108
+ amount: opts.amount ?? 1000000000n,
109
+ mint,
110
+ owner,
111
+ signer: {
112
+ publicKey: signerPubkey,
113
+ tokenAccount: opts.signerTokenAccount,
114
+ },
115
+ prover: stubProver,
116
+ treeSource: (0, src_1.staticTreeSource)({ ...TARGET, outputTreeId }),
117
+ alt: (0, src_1.explicitAlt)(alt),
118
+ cache: (0, src_1.noCache)(),
119
+ preflight: false,
120
+ computeUnitPriceMicroLamports: opts.computeUnitPriceMicroLamports,
121
+ });
122
+ return { result, owner, secretKey, alt, signerPubkey, mint };
123
+ }
124
+ describe("size budget", () => {
125
+ it("fits under 1232 bytes for native SOL", async () => {
126
+ const { result } = await buildShield();
127
+ assert_1.strict.ok(result.quote.serializedSize <= src_1.MAX_TRANSACTION_BYTES, `SOL shield is ${result.quote.serializedSize} bytes, over ${src_1.MAX_TRANSACTION_BYTES}`);
128
+ assert_1.strict.equal(result.quote.serializedSize, result.transaction.serialize().length, "size quote must match the actual wire transaction exactly");
129
+ console.log(` SOL/tree0: ${result.quote.serializedSize} bytes ` +
130
+ `(${src_1.MAX_TRANSACTION_BYTES - result.quote.serializedSize} spare)`);
131
+ });
132
+ it("fits for an SPL mint on tree 0", async () => {
133
+ const signer = web3_js_1.Keypair.generate().publicKey;
134
+ const ata = await (0, spl_token_1.getAssociatedTokenAddress)(USDC, signer);
135
+ const { result } = await buildShield({ mint: USDC, signerTokenAccount: ata });
136
+ assert_1.strict.ok(result.quote.serializedSize <= src_1.MAX_TRANSACTION_BYTES, `USDC shield is ${result.quote.serializedSize} bytes`);
137
+ console.log(` USDC/tree0: ${result.quote.serializedSize} bytes ` +
138
+ `(${src_1.MAX_TRANSACTION_BYTES - result.quote.serializedSize} spare)`);
139
+ });
140
+ it("fits for an SPL mint on a non-zero tree", async () => {
141
+ const signer = web3_js_1.Keypair.generate().publicKey;
142
+ const ata = await (0, spl_token_1.getAssociatedTokenAddress)(USDC, signer);
143
+ const { result } = await buildShield({
144
+ mint: USDC,
145
+ outputTreeId: 3,
146
+ signerTokenAccount: ata,
147
+ });
148
+ assert_1.strict.ok(result.quote.serializedSize <= src_1.MAX_TRANSACTION_BYTES, `USDC/tree3 shield is ${result.quote.serializedSize} bytes — this is the case ` +
149
+ `that breaks first when the shared ALT only covers tree 0`);
150
+ console.log(` USDC/tree3: ${result.quote.serializedSize} bytes ` +
151
+ `(${src_1.MAX_TRANSACTION_BYTES - result.quote.serializedSize} spare)`);
152
+ });
153
+ it("reports which keys could not be compressed when it does not fit", async () => {
154
+ // A table missing the pool PDAs forces them to static keys.
155
+ const emptyAlt = new web3_js_1.AddressLookupTableAccount({
156
+ key: web3_js_1.PublicKey.unique(),
157
+ state: {
158
+ deactivationSlot: BigInt("18446744073709551615"),
159
+ lastExtendedSlot: 0,
160
+ lastExtendedSlotStartIndex: 0,
161
+ authority: undefined,
162
+ addresses: [],
163
+ },
164
+ });
165
+ const { owner } = makeOwner();
166
+ await assert_1.strict.rejects((0, src_1.shield)({
167
+ connection: stubConnection(),
168
+ amount: 1000000n,
169
+ mint: USDC,
170
+ owner,
171
+ signer: { publicKey: web3_js_1.Keypair.generate().publicKey },
172
+ prover: stubProver,
173
+ treeSource: (0, src_1.staticTreeSource)(TARGET),
174
+ alt: (0, src_1.explicitAlt)(emptyAlt),
175
+ cache: (0, src_1.noCache)(),
176
+ preflight: false,
177
+ }), (e) => {
178
+ assert_1.strict.ok(e instanceof src_1.ShieldError);
179
+ assert_1.strict.equal(e.code, "TX_TOO_LARGE");
180
+ const keys = e.context?.uncompressedKeys;
181
+ assert_1.strict.ok(Array.isArray(keys) && keys.length > 0, "expected uncompressed key list");
182
+ return true;
183
+ });
184
+ });
185
+ it("omitting the priority fee saves bytes", async () => {
186
+ const withFee = await buildShield({ computeUnitPriceMicroLamports: 50000 });
187
+ const withoutFee = await buildShield({ computeUnitPriceMicroLamports: 0 });
188
+ assert_1.strict.ok(withoutFee.result.quote.serializedSize < withFee.result.quote.serializedSize, "dropping the priority-fee instruction should shrink the transaction");
189
+ });
190
+ });
191
+ describe("transaction shape", () => {
192
+ it("returns an unsigned v0 transaction with exactly one signature slot", async () => {
193
+ const { result } = await buildShield();
194
+ const tx = result.transaction;
195
+ assert_1.strict.ok(tx instanceof web3_js_1.VersionedTransaction);
196
+ assert_1.strict.equal(tx.signatures.length, 1, "expected exactly one signer");
197
+ assert_1.strict.ok(tx.signatures[0].every((b) => b === 0), "transaction must be returned UNSIGNED — the SDK never holds a key");
198
+ });
199
+ it("uses exactly one address lookup table", async () => {
200
+ const { result, alt } = await buildShield();
201
+ const lookups = result.transaction.message.addressTableLookups;
202
+ assert_1.strict.equal(lookups.length, 1);
203
+ assert_1.strict.equal(lookups[0].accountKey.toBase58(), alt.key.toBase58());
204
+ });
205
+ it("makes the signer the fee payer", async () => {
206
+ const { result, signerPubkey } = await buildShield();
207
+ assert_1.strict.equal(result.transaction.message.staticAccountKeys[0].toBase58(), signerPubkey.toBase58());
208
+ });
209
+ it("targets the Veilo program", async () => {
210
+ const { result } = await buildShield();
211
+ const keys = [
212
+ ...result.transaction.message.staticAccountKeys.map((k) => k.toBase58()),
213
+ ];
214
+ const lookups = result.transaction.message.addressTableLookups;
215
+ assert_1.strict.ok(keys.includes(src_1.PRIVACY_POOL_PROGRAM_ID.toBase58()) || lookups.length > 0, "program id must be reachable");
216
+ });
217
+ });
218
+ describe("note ownership", () => {
219
+ it("creates a note the recipient — and only the recipient — can decrypt", async () => {
220
+ const { result, secretKey } = await buildShield({ amount: 5000000n });
221
+ const recovered = (0, src_1.decryptCompactNoteCipher)(secretKey, result.note.compact.ephemeralPublicKey, result.note.compact.compactBlob);
222
+ assert_1.strict.ok(recovered, "the owner could not decrypt their own note");
223
+ assert_1.strict.equal(recovered.amount, 5000000n);
224
+ assert_1.strict.equal((0, src_1.bytesToBigIntBE)(recovered.blinding), result.note.blinding, "decrypted blinding must match the note");
225
+ const stranger = tweetnacl_1.default.sign.keyPair();
226
+ assert_1.strict.equal((0, src_1.decryptCompactNoteCipher)(stranger.secretKey, result.note.compact.ephemeralPublicKey, result.note.compact.compactBlob), null, "a stranger should not be able to decrypt this note");
227
+ });
228
+ it("binds the commitment to the owner's veilo key, not the signer", async () => {
229
+ const { result, owner, mint } = await buildShield({ amount: 7n });
230
+ const expected = (0, src_1.commitUTXO)({
231
+ amount: 7n,
232
+ pubkey: owner.veiloPublicKey,
233
+ blinding: result.note.blinding,
234
+ mintAddress: (0, src_1.pubkeyToField)(mint),
235
+ });
236
+ assert_1.strict.equal(Buffer.from(result.note.commitment).toString("hex"), Buffer.from(expected).toString("hex"));
237
+ });
238
+ it("refuses a hand-built owner that has not acknowledged the invariant", async () => {
239
+ const alt = await stubAlt(src_1.NATIVE_SOL_MINT, 0);
240
+ await assert_1.strict.rejects((0, src_1.shield)({
241
+ connection: stubConnection(),
242
+ amount: 1n,
243
+ owner: {
244
+ veiloPublicKey: (0, src_1.generateKeypair)().publicKey,
245
+ noteViewingKey: web3_js_1.PublicKey.unique(),
246
+ },
247
+ signer: { publicKey: web3_js_1.PublicKey.unique() },
248
+ prover: stubProver,
249
+ treeSource: (0, src_1.staticTreeSource)(TARGET),
250
+ alt: (0, src_1.explicitAlt)(alt),
251
+ cache: (0, src_1.noCache)(),
252
+ preflight: false,
253
+ }), (e) => {
254
+ assert_1.strict.ok(e instanceof src_1.ShieldError);
255
+ assert_1.strict.equal(e.code, "OWNER_INVARIANT_UNACKNOWLEDGED");
256
+ return true;
257
+ });
258
+ assert_1.strict.equal(proverCalls, 0, "should fail before spending time on a proof");
259
+ });
260
+ it("rejects a non-positive amount", async () => {
261
+ const alt = await stubAlt(src_1.NATIVE_SOL_MINT, 0);
262
+ const { owner } = makeOwner();
263
+ await assert_1.strict.rejects((0, src_1.shield)({
264
+ connection: stubConnection(),
265
+ amount: 0n,
266
+ owner,
267
+ signer: { publicKey: web3_js_1.PublicKey.unique() },
268
+ prover: stubProver,
269
+ treeSource: (0, src_1.staticTreeSource)(TARGET),
270
+ alt: (0, src_1.explicitAlt)(alt),
271
+ cache: (0, src_1.noCache)(),
272
+ preflight: false,
273
+ }), /positive/);
274
+ });
275
+ it("rejects a full output tree before invoking the prover", async () => {
276
+ const alt = await stubAlt(src_1.NATIVE_SOL_MINT, 0);
277
+ const { owner } = makeOwner();
278
+ await assert_1.strict.rejects((0, src_1.shield)({
279
+ connection: stubConnection(),
280
+ amount: 1n,
281
+ owner,
282
+ signer: { publicKey: web3_js_1.Keypair.generate().publicKey },
283
+ prover: stubProver,
284
+ treeSource: (0, src_1.staticTreeSource)({
285
+ ...TARGET,
286
+ outputNextIndex: 2 ** 22,
287
+ }),
288
+ alt: (0, src_1.explicitAlt)(alt),
289
+ cache: (0, src_1.noCache)(),
290
+ preflight: false,
291
+ }), (error) => {
292
+ assert_1.strict.ok(error instanceof src_1.ShieldError);
293
+ assert_1.strict.equal(error.code, "TREE_FULL");
294
+ return true;
295
+ });
296
+ assert_1.strict.equal(proverCalls, 0, "a full tree must fail before proof generation");
297
+ });
298
+ });
299
+ describe("rebuild()", () => {
300
+ it("does not invoke the prover again", async () => {
301
+ const { result, alt } = await buildShield();
302
+ assert_1.strict.equal(proverCalls, 1);
303
+ await (0, src_1.rebuild)(result, {
304
+ alt,
305
+ blockhash: { blockhash: BLOCKHASH_2, lastValidBlockHeight: 2 },
306
+ });
307
+ await (0, src_1.rebuild)(result, {
308
+ alt,
309
+ blockhash: { blockhash: BLOCKHASH_3, lastValidBlockHeight: 3 },
310
+ });
311
+ await (0, src_1.rebuild)(result, {
312
+ alt,
313
+ blockhash: { blockhash: BLOCKHASH_4, lastValidBlockHeight: 4 },
314
+ });
315
+ assert_1.strict.equal(proverCalls, 1, "rebuild() must reuse the cached proof — re-proving costs 10-30s");
316
+ });
317
+ it("changes only the blockhash and the deadline bytes", async () => {
318
+ const { result, alt } = await buildShield();
319
+ const before = result.transaction.message.compiledInstructions;
320
+ const again = await (0, src_1.rebuild)(result, {
321
+ alt,
322
+ blockhash: { blockhash: BLOCKHASH_2, lastValidBlockHeight: 2 },
323
+ deadlineSeconds: 7200,
324
+ });
325
+ const after = again.transaction.message.compiledInstructions;
326
+ assert_1.strict.equal(before.length, after.length);
327
+ // The transact instruction is last; its data must differ ONLY in the
328
+ // 8-byte deadline field.
329
+ const b = Buffer.from(before[before.length - 1].data);
330
+ const a = Buffer.from(after[after.length - 1].data);
331
+ assert_1.strict.equal(a.length, b.length);
332
+ const differing = [];
333
+ for (let i = 0; i < a.length; i++)
334
+ if (a[i] !== b[i])
335
+ differing.push(i);
336
+ assert_1.strict.ok(differing.length > 0 && differing.length <= 8, `expected only the deadline to change, but ${differing.length} bytes differ`);
337
+ // Deadline sits after discriminator(8) + root(32) + treeIds(4) +
338
+ // publicAmount(8) + extDataHash(32) + mint(32) + nullifiers/commitments(128).
339
+ const deadlineOffset = 8 + 32 + 4 + 8 + 32 + 32 + 128;
340
+ for (const i of differing) {
341
+ assert_1.strict.ok(i >= deadlineOffset && i < deadlineOffset + 8, `byte ${i} changed but is outside the deadline field at ${deadlineOffset}..${deadlineOffset + 8}`);
342
+ }
343
+ });
344
+ it("keeps the account keys identical", async () => {
345
+ const { result, alt } = await buildShield();
346
+ const again = await (0, src_1.rebuild)(result, {
347
+ alt,
348
+ blockhash: { blockhash: BLOCKHASH_2, lastValidBlockHeight: 2 },
349
+ });
350
+ assert_1.strict.deepEqual(result.transaction.message.staticAccountKeys.map((k) => k.toBase58()), again.transaction.message.staticAccountKeys.map((k) => k.toBase58()));
351
+ });
352
+ it("carries the new blockhash through to the result", async () => {
353
+ const { result, alt } = await buildShield();
354
+ const again = await (0, src_1.rebuild)(result, {
355
+ alt,
356
+ blockhash: { blockhash: BLOCKHASH_2, lastValidBlockHeight: 99 },
357
+ });
358
+ assert_1.strict.equal(again.blockhash, BLOCKHASH_2);
359
+ assert_1.strict.equal(again.lastValidBlockHeight, 99);
360
+ assert_1.strict.ok(again.deadline >= result.deadline);
361
+ });
362
+ it("preserves the note across rebuilds", async () => {
363
+ const { result, alt, secretKey } = await buildShield();
364
+ const again = await (0, src_1.rebuild)(result, {
365
+ alt,
366
+ blockhash: { blockhash: BLOCKHASH_2, lastValidBlockHeight: 2 },
367
+ });
368
+ assert_1.strict.equal(Buffer.from(again.note.commitment).toString("hex"), Buffer.from(result.note.commitment).toString("hex"));
369
+ assert_1.strict.ok((0, src_1.decryptCompactNoteCipher)(secretKey, again.note.compact.ephemeralPublicKey, again.note.compact.compactBlob));
370
+ });
371
+ });
372
+ describe("proof cache", () => {
373
+ it("round-trips through JSON", async () => {
374
+ const { result } = await buildShield();
375
+ const back = (0, src_1.deserializeProofCache)((0, src_1.serializeProofCache)(result.proofCache));
376
+ assert_1.strict.deepEqual(back, result.proofCache);
377
+ });
378
+ it("carries no secret material", async () => {
379
+ const { result } = await buildShield();
380
+ const json = (0, src_1.serializeProofCache)(result.proofCache);
381
+ // The blinding is the secret that makes a note spendable. It must never
382
+ // appear in the cache, which is explicitly documented as safe to transmit.
383
+ const blindingHex = result.note.blinding.toString(16).padStart(64, "0");
384
+ assert_1.strict.ok(!json.toLowerCase().includes(blindingHex.toLowerCase()), "the proof cache leaked the note blinding");
385
+ assert_1.strict.ok(!json.includes("privateKey"));
386
+ });
387
+ it("rejects an unknown cache version", () => {
388
+ assert_1.strict.throws(() => (0, src_1.deserializeProofCache)(JSON.stringify({ version: 99 })), /version/);
389
+ });
390
+ });
391
+ describe("quote", () => {
392
+ it("prices the burned nullifier-marker rent", async () => {
393
+ const { result } = await buildShield();
394
+ // Two markers at the stubbed rent-exemption minimum.
395
+ assert_1.strict.equal(result.quote.estimatedRentLamports, 961680n * 2n);
396
+ assert_1.strict.ok(result.quote.estimatedFeeLamports > 0n);
397
+ });
398
+ it("reports the predicted leaf index as given by the tree source", async () => {
399
+ const { result } = await buildShield();
400
+ assert_1.strict.equal(result.predictedLeafIndex, TARGET.outputNextIndex);
401
+ });
402
+ });
403
+ });
@@ -0,0 +1 @@
1
+ import "mocha";
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("mocha");
4
+ const assert_1 = require("assert");
5
+ const src_1 = require("../src");
6
+ describe("UTXO helpers", function () {
7
+ this.timeout(10000);
8
+ before(async () => {
9
+ await (0, src_1.initPoseidon)();
10
+ });
11
+ describe("Keypair generation", () => {
12
+ it("generates a random keypair", () => {
13
+ const kp = (0, src_1.generateKeypair)();
14
+ assert_1.strict.ok(kp.privateKey > 0n, "privateKey should be positive");
15
+ assert_1.strict.ok(kp.publicKey > 0n, "publicKey should be positive");
16
+ assert_1.strict.ok(kp.privateKey < src_1.BN254_FR_MODULUS, "privateKey should be less than field modulus");
17
+ assert_1.strict.ok(kp.publicKey < src_1.BN254_FR_MODULUS, "publicKey should be less than field modulus");
18
+ });
19
+ it("derives public key deterministically", () => {
20
+ const privateKey = 12345n;
21
+ const pubkey1 = (0, src_1.derivePublicKey)(privateKey);
22
+ const pubkey2 = (0, src_1.derivePublicKey)(privateKey);
23
+ assert_1.strict.equal(pubkey1, pubkey2, "public key derivation should be deterministic");
24
+ });
25
+ it("creates keypair from private key", () => {
26
+ const privateKey = 67890n;
27
+ const kp = (0, src_1.keypairFromPrivateKey)(privateKey);
28
+ assert_1.strict.equal(kp.privateKey, privateKey);
29
+ assert_1.strict.equal(kp.publicKey, (0, src_1.derivePublicKey)(privateKey));
30
+ });
31
+ });
32
+ describe("UTXO creation", () => {
33
+ it("creates a UTXO with commitment", () => {
34
+ const kp = (0, src_1.generateKeypair)();
35
+ const utxo = (0, src_1.createUTXO)({
36
+ amount: 1000000n,
37
+ pubkey: kp.publicKey,
38
+ mintAddress: src_1.NATIVE_SOL_MINT,
39
+ });
40
+ assert_1.strict.equal(utxo.amount, 1000000n);
41
+ assert_1.strict.equal(utxo.pubkey, kp.publicKey);
42
+ assert_1.strict.ok(utxo.blinding > 0n, "blinding should be positive");
43
+ assert_1.strict.equal(utxo.commitment.length, 32);
44
+ });
45
+ it("creates an owned UTXO with private key", () => {
46
+ const privateKey = 11111n;
47
+ const utxo = (0, src_1.createOwnedUTXO)({
48
+ amount: 500000n,
49
+ privateKey,
50
+ mintAddress: src_1.NATIVE_SOL_MINT,
51
+ });
52
+ assert_1.strict.equal(utxo.amount, 500000n);
53
+ assert_1.strict.equal(utxo.privateKey, privateKey);
54
+ assert_1.strict.equal(utxo.pubkey, (0, src_1.derivePublicKey)(privateKey));
55
+ assert_1.strict.equal(utxo.commitment.length, 32);
56
+ });
57
+ it("creates a zero UTXO", () => {
58
+ const kp = (0, src_1.generateKeypair)();
59
+ const utxo = (0, src_1.createZeroUTXO)(kp.publicKey, src_1.NATIVE_SOL_MINT);
60
+ assert_1.strict.equal(utxo.amount, 0n);
61
+ assert_1.strict.equal(utxo.pubkey, kp.publicKey);
62
+ assert_1.strict.equal(utxo.commitment.length, 32);
63
+ });
64
+ it("creates an owned zero UTXO", () => {
65
+ const privateKey = 22222n;
66
+ const utxo = (0, src_1.createOwnedZeroUTXO)(privateKey, src_1.NATIVE_SOL_MINT);
67
+ assert_1.strict.equal(utxo.amount, 0n);
68
+ assert_1.strict.equal(utxo.privateKey, privateKey);
69
+ assert_1.strict.equal(utxo.pubkey, (0, src_1.derivePublicKey)(privateKey));
70
+ });
71
+ });
72
+ describe("UTXO commitment", () => {
73
+ it("computes commitment deterministically", () => {
74
+ const kp = (0, src_1.generateKeypair)();
75
+ const utxo1 = {
76
+ amount: 1000n,
77
+ pubkey: kp.publicKey,
78
+ blinding: 12345n,
79
+ mintAddress: 0n,
80
+ };
81
+ const utxo2 = { ...utxo1 };
82
+ const commitment1 = (0, src_1.commitUTXO)(utxo1);
83
+ const commitment2 = (0, src_1.commitUTXO)(utxo2);
84
+ assert_1.strict.equal(commitment1.length, 32);
85
+ assert_1.strict.equal(Buffer.compare(Buffer.from(commitment1), Buffer.from(commitment2)), 0, "commitment should be deterministic");
86
+ });
87
+ it("produces different commitments for different amounts", () => {
88
+ const kp = (0, src_1.generateKeypair)();
89
+ const utxo1 = {
90
+ amount: 1000n,
91
+ pubkey: kp.publicKey,
92
+ blinding: 12345n,
93
+ mintAddress: 0n,
94
+ };
95
+ const utxo2 = {
96
+ ...utxo1,
97
+ amount: 2000n,
98
+ };
99
+ const commitment1 = (0, src_1.commitUTXO)(utxo1);
100
+ const commitment2 = (0, src_1.commitUTXO)(utxo2);
101
+ assert_1.strict.notEqual(Buffer.compare(Buffer.from(commitment1), Buffer.from(commitment2)), 0, "different amounts should produce different commitments");
102
+ });
103
+ });
104
+ describe("Nullifier derivation", () => {
105
+ it("derives nullifier from UTXO", () => {
106
+ const privateKey = 33333n;
107
+ const utxo = (0, src_1.createOwnedUTXO)({
108
+ amount: 1000000n,
109
+ privateKey,
110
+ mintAddress: src_1.NATIVE_SOL_MINT,
111
+ });
112
+ const pathIndex = 5;
113
+ const nullifier = (0, src_1.deriveNullifier)(utxo, pathIndex, privateKey);
114
+ assert_1.strict.equal(nullifier.length, 32);
115
+ });
116
+ it("produces different nullifiers for different path indices", () => {
117
+ const privateKey = 44444n;
118
+ const utxo = (0, src_1.createOwnedUTXO)({
119
+ amount: 1000000n,
120
+ privateKey,
121
+ mintAddress: src_1.NATIVE_SOL_MINT,
122
+ });
123
+ const nullifier1 = (0, src_1.deriveNullifier)(utxo, 0, privateKey);
124
+ const nullifier2 = (0, src_1.deriveNullifier)(utxo, 1, privateKey);
125
+ assert_1.strict.notEqual(Buffer.compare(Buffer.from(nullifier1), Buffer.from(nullifier2)), 0, "different path indices should produce different nullifiers");
126
+ });
127
+ it("produces same nullifier for same inputs", () => {
128
+ const privateKey = 55555n;
129
+ const utxo = (0, src_1.createOwnedUTXO)({
130
+ amount: 1000000n,
131
+ privateKey,
132
+ mintAddress: src_1.NATIVE_SOL_MINT,
133
+ });
134
+ const pathIndex = 10;
135
+ const nullifier1 = (0, src_1.deriveNullifier)(utxo, pathIndex, privateKey);
136
+ const nullifier2 = (0, src_1.deriveNullifier)(utxo, pathIndex, privateKey);
137
+ assert_1.strict.equal(Buffer.compare(Buffer.from(nullifier1), Buffer.from(nullifier2)), 0, "same inputs should produce same nullifier");
138
+ });
139
+ });
140
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veilo/sdk-core",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript SDK for the Veilo Privacy Pool - UTXO-based privacy protocol on Solana with ZK-SNARKs",
5
5
  "homepage": "https://github.com/VeiloSolana/veilo-sdk#readme",
6
6
  "bugs": {
@@ -18,19 +18,64 @@
18
18
  "types": "dist/cjs/index.d.ts",
19
19
  "exports": {
20
20
  ".": {
21
- "import": "./dist/esm/index.js",
22
- "require": "./dist/cjs/index.js",
23
- "types": "./dist/cjs/index.d.ts"
21
+ "import": {
22
+ "types": "./dist/esm/index.d.ts",
23
+ "default": "./dist/esm/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/cjs/index.d.ts",
27
+ "default": "./dist/cjs/index.js"
28
+ }
24
29
  },
25
30
  "./config": {
26
- "import": "./dist/esm/config.js",
27
- "require": "./dist/cjs/config.js",
28
- "types": "./dist/cjs/config.d.ts"
31
+ "import": {
32
+ "types": "./dist/esm/config.d.ts",
33
+ "default": "./dist/esm/config.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/cjs/config.d.ts",
37
+ "default": "./dist/cjs/config.js"
38
+ }
29
39
  },
30
40
  "./poseidon": {
31
- "import": "./dist/esm/poseidon.js",
32
- "require": "./dist/cjs/poseidon.js",
33
- "types": "./dist/cjs/poseidon.d.ts"
41
+ "import": {
42
+ "types": "./dist/esm/poseidon.d.ts",
43
+ "default": "./dist/esm/poseidon.js"
44
+ },
45
+ "require": {
46
+ "types": "./dist/cjs/poseidon.d.ts",
47
+ "default": "./dist/cjs/poseidon.js"
48
+ }
49
+ },
50
+ "./proof": {
51
+ "import": {
52
+ "types": "./dist/esm/proof.d.ts",
53
+ "default": "./dist/esm/proof.js"
54
+ },
55
+ "require": {
56
+ "types": "./dist/cjs/proof.d.ts",
57
+ "default": "./dist/cjs/proof.js"
58
+ }
59
+ },
60
+ "./prover": {
61
+ "import": {
62
+ "types": "./dist/esm/prover.d.ts",
63
+ "default": "./dist/esm/prover.js"
64
+ },
65
+ "require": {
66
+ "types": "./dist/cjs/prover.d.ts",
67
+ "default": "./dist/cjs/prover.js"
68
+ }
69
+ },
70
+ "./shield": {
71
+ "import": {
72
+ "types": "./dist/esm/shield/index.d.ts",
73
+ "default": "./dist/esm/shield/index.js"
74
+ },
75
+ "require": {
76
+ "types": "./dist/cjs/shield/index.d.ts",
77
+ "default": "./dist/cjs/shield/index.js"
78
+ }
34
79
  },
35
80
  "./package.json": "./package.json"
36
81
  },
@@ -40,14 +85,27 @@
40
85
  "sideEffects": false,
41
86
  "files": [
42
87
  "dist/",
88
+ "config.js",
89
+ "config.d.ts",
90
+ "poseidon.js",
91
+ "poseidon.d.ts",
92
+ "proof.js",
93
+ "proof.d.ts",
94
+ "prover.js",
95
+ "prover.d.ts",
96
+ "shield.js",
97
+ "shield.d.ts",
43
98
  "README.md",
99
+ "SHIELD_INTEGRATION.md",
44
100
  "LICENSE"
45
101
  ],
46
102
  "directories": {
47
103
  "test": "tests"
48
104
  },
49
105
  "scripts": {
50
- "build": "rm -rf dist/ && tsc -p tsconfig.json && tsc -p tsconfig.esm.json",
106
+ "gen:idl": "node scripts/gen-idl.mjs",
107
+ "verify:cipher-parity": "npm run build && node scripts/verify-compact-cipher-parity.mjs",
108
+ "build": "rm -rf dist/ && tsc -p tsconfig.json && tsc -p tsconfig.esm.json && echo '{\"type\":\"module\"}' > dist/esm/package.json",
51
109
  "test": "rm -rf dist/ && export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com && export ANCHOR_WALLET=$HOME/.config/solana/id.json && tsc -p test-tsconfig.json && mocha -t 1000000 \"dist/tests/**/*.js\""
52
110
  },
53
111
  "dependencies": {
package/poseidon.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Stub for legacy resolvers — see poseidon.js
2
+ export * from "./dist/cjs/poseidon.js";
package/poseidon.js ADDED
@@ -0,0 +1,4 @@
1
+ // Stub for legacy resolvers (TypeScript moduleResolution: "node", older bundlers)
2
+ // that ignore the "exports" map. Modern resolvers use "./poseidon" from package.json
3
+ // exports and never reach this file.
4
+ module.exports = require("./dist/cjs/poseidon.js");
package/proof.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Stub for legacy resolvers — see proof.js
2
+ export * from "./dist/cjs/proof.js";