@auditable/privacy-pool-zk-sdk 0.7.0 → 0.9.1-rc.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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,4 @@
1
- import * as snarkjs from 'snarkjs';
2
- import { StrKey, xdr, nativeToScVal, Address, Keypair, rpc, TransactionBuilder, Operation, authorizeEntry } from '@stellar/stellar-sdk';
1
+ import { StrKey, Address, Keypair, rpc, TransactionBuilder, Operation, xdr, nativeToScVal, authorizeEntry } from '@stellar/stellar-sdk';
3
2
  import { buildBabyjub, buildMimc7, buildPoseidon } from 'circomlibjs';
4
3
 
5
4
  /* @ts-self-types="./client_sdk_wasm.d.ts" */
@@ -40,35 +39,69 @@ function buildWithdrawMerkleWitness(coin_json, state_json) {
40
39
  }
41
40
 
42
41
  /**
43
- * Calculate nullifier hash from nullifier decimal string.
42
+ * Calculate owner-bound nullifier hash from nullifier and spend-scalar decimal strings.
44
43
  * Returns hex string (0x...)
45
44
  * @param {string} nullifier_decimal
45
+ * @param {string} priv_key_scalar_decimal
46
46
  * @returns {string}
47
47
  */
48
- function calculateNullifierHash(nullifier_decimal) {
49
- let deferred3_0;
50
- let deferred3_1;
48
+ function calculateNullifierHash(nullifier_decimal, priv_key_scalar_decimal) {
49
+ let deferred4_0;
50
+ let deferred4_1;
51
51
  try {
52
52
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
53
53
  const ptr0 = passStringToWasm0(nullifier_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
54
54
  const len0 = WASM_VECTOR_LEN;
55
- wasm.calculateNullifierHash(retptr, ptr0, len0);
55
+ const ptr1 = passStringToWasm0(priv_key_scalar_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
56
+ const len1 = WASM_VECTOR_LEN;
57
+ wasm.calculateNullifierHash(retptr, ptr0, len0, ptr1, len1);
56
58
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
57
59
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
58
60
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
59
61
  var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
60
- var ptr2 = r0;
61
- var len2 = r1;
62
+ var ptr3 = r0;
63
+ var len3 = r1;
62
64
  if (r3) {
63
- ptr2 = 0; len2 = 0;
65
+ ptr3 = 0; len3 = 0;
64
66
  throw takeObject(r2);
65
67
  }
66
- deferred3_0 = ptr2;
67
- deferred3_1 = len2;
68
- return getStringFromWasm0(ptr2, len2);
68
+ deferred4_0 = ptr3;
69
+ deferred4_1 = len3;
70
+ return getStringFromWasm0(ptr3, len3);
71
+ } finally {
72
+ wasm.__wbindgen_add_to_stack_pointer(16);
73
+ wasm.__wbindgen_export4(deferred4_0, deferred4_1, 1);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * `R = Poseidon255(2)(hi, lo)`; `scalar` is the raw Poseidon hash when it is
79
+ * already in `(0, BabyJub subgroup order)`. Out-of-range hashes fail so the
80
+ * caller can rejection-sample a new nonce.
81
+ * @param {string} nonce_decimal
82
+ * @param {string} recipient_hi_decimal
83
+ * @param {string} recipient_lo_decimal
84
+ * @returns {any}
85
+ */
86
+ function derivedEscrowKey$1(nonce_decimal, recipient_hi_decimal, recipient_lo_decimal) {
87
+ try {
88
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
89
+ const ptr0 = passStringToWasm0(nonce_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
90
+ const len0 = WASM_VECTOR_LEN;
91
+ const ptr1 = passStringToWasm0(recipient_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
92
+ const len1 = WASM_VECTOR_LEN;
93
+ const ptr2 = passStringToWasm0(recipient_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
94
+ const len2 = WASM_VECTOR_LEN;
95
+ wasm.derivedEscrowKey(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
96
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
97
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
98
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
99
+ if (r2) {
100
+ throw takeObject(r1);
101
+ }
102
+ return takeObject(r0);
69
103
  } finally {
70
104
  wasm.__wbindgen_add_to_stack_pointer(16);
71
- wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1);
72
105
  }
73
106
  }
74
107
 
@@ -149,26 +182,28 @@ function ecdhSharedKey(priv_hex, pub_x_hex, pub_y_hex) {
149
182
  }
150
183
 
151
184
  /**
152
- * Generate a new coin with random nullifier, secret, and shared-secret field elements.
153
- * `amount` is stroops (u64); JS passes `bigint`.
185
+ * Generate a new coin with random nullifier, secret, and owner public-key field elements.
186
+ * `amount_decimal` is an arbitrary-precision decimal field element (never JS `number`).
154
187
  * `asset_hi_decimal` / `asset_lo_decimal` are decimal Fr strings for the Stellar asset contract id (two limbs).
155
188
  * Returns JSON: { coin: { value, nullifier, secret, commitment, asset_hi, asset_lo }, commitment_hex, precommitement_hex }
156
- * @param {bigint} amount
189
+ * @param {string} amount_decimal
157
190
  * @param {string} asset_hi_decimal
158
191
  * @param {string} asset_lo_decimal
159
192
  * @param {string} application_id_decimal
160
193
  * @returns {any}
161
194
  */
162
- function generateCoin(amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
195
+ function generateCoin(amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
163
196
  try {
164
197
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
165
- const ptr0 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
198
+ const ptr0 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
166
199
  const len0 = WASM_VECTOR_LEN;
167
- const ptr1 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
200
+ const ptr1 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
168
201
  const len1 = WASM_VECTOR_LEN;
169
- const ptr2 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
202
+ const ptr2 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
170
203
  const len2 = WASM_VECTOR_LEN;
171
- wasm.generateCoin(retptr, amount, ptr0, len0, ptr1, len1, ptr2, len2);
204
+ const ptr3 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
205
+ const len3 = WASM_VECTOR_LEN;
206
+ wasm.generateCoin(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
172
207
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
173
208
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
174
209
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -182,32 +217,34 @@ function generateCoin(amount, asset_hi_decimal, asset_lo_decimal, application_id
182
217
  }
183
218
 
184
219
  /**
185
- * `Poseidon₁(scalar)` secret + fixed ECDH shared coords (hex), matching an aligned deposit witness.
220
+ * `Poseidon₁(scalar)` secret + recipient owner public key (hex), matching an aligned deposit witness.
186
221
  * @param {string} scalar_hex
187
- * @param {string} shared_x_hex
188
- * @param {string} shared_y_hex
189
- * @param {bigint} amount
222
+ * @param {string} owner_x_hex
223
+ * @param {string} owner_y_hex
224
+ * @param {string} amount_decimal
190
225
  * @param {string} asset_hi_decimal
191
226
  * @param {string} asset_lo_decimal
192
227
  * @param {string} application_id_decimal
193
228
  * @returns {any}
194
229
  */
195
- function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
230
+ function generateCoinForDepositWithOwnerPubHex(scalar_hex, owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
196
231
  try {
197
232
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
198
233
  const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
199
234
  const len0 = WASM_VECTOR_LEN;
200
- const ptr1 = passStringToWasm0(shared_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
235
+ const ptr1 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
201
236
  const len1 = WASM_VECTOR_LEN;
202
- const ptr2 = passStringToWasm0(shared_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
237
+ const ptr2 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
203
238
  const len2 = WASM_VECTOR_LEN;
204
- const ptr3 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
239
+ const ptr3 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
205
240
  const len3 = WASM_VECTOR_LEN;
206
- const ptr4 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
241
+ const ptr4 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
207
242
  const len4 = WASM_VECTOR_LEN;
208
- const ptr5 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
243
+ const ptr5 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
209
244
  const len5 = WASM_VECTOR_LEN;
210
- wasm.generateCoinForDepositWithSharedHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, amount, ptr3, len3, ptr4, len4, ptr5, len5);
245
+ const ptr6 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
246
+ const len6 = WASM_VECTOR_LEN;
247
+ wasm.generateCoinForDepositWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6);
211
248
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
212
249
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
213
250
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -223,24 +260,26 @@ function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_
223
260
  /**
224
261
  * `secret` in coin = `Poseidon255(1)(scalar)` per `deposit.circom`; scalar is 32-byte hex (64 chars, optional `0x`).
225
262
  * @param {string} scalar_hex
226
- * @param {bigint} amount
263
+ * @param {string} amount_decimal
227
264
  * @param {string} asset_hi_decimal
228
265
  * @param {string} asset_lo_decimal
229
266
  * @param {string} application_id_decimal
230
267
  * @returns {any}
231
268
  */
232
- function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
269
+ function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
233
270
  try {
234
271
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
235
272
  const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
236
273
  const len0 = WASM_VECTOR_LEN;
237
- const ptr1 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
274
+ const ptr1 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
238
275
  const len1 = WASM_VECTOR_LEN;
239
- const ptr2 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
276
+ const ptr2 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
240
277
  const len2 = WASM_VECTOR_LEN;
241
- const ptr3 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
278
+ const ptr3 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
242
279
  const len3 = WASM_VECTOR_LEN;
243
- wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, amount, ptr1, len1, ptr2, len2, ptr3, len3);
280
+ const ptr4 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
281
+ const len4 = WASM_VECTOR_LEN;
282
+ wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4);
244
283
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
245
284
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
246
285
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -254,29 +293,31 @@ function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount, asset_hi_
254
293
  }
255
294
 
256
295
  /**
257
- * Same as `generateCoin`, but commitment uses the given ECDH shared key (64-char hex coords from `ecdhSharedKey`); shared coords are not stored in `coin` JSON.
258
- * @param {string} shared_x_hex
259
- * @param {string} shared_y_hex
260
- * @param {bigint} amount
296
+ * Same as `generateCoin`, but commitment uses the given owner public key (64-char hex coords).
297
+ * @param {string} owner_x_hex
298
+ * @param {string} owner_y_hex
299
+ * @param {string} amount_decimal
261
300
  * @param {string} asset_hi_decimal
262
301
  * @param {string} asset_lo_decimal
263
302
  * @param {string} application_id_decimal
264
303
  * @returns {any}
265
304
  */
266
- function generateCoinWithSharedSecretHex(shared_x_hex, shared_y_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
305
+ function generateCoinWithOwnerPubHex(owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
267
306
  try {
268
307
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
269
- const ptr0 = passStringToWasm0(shared_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
308
+ const ptr0 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
270
309
  const len0 = WASM_VECTOR_LEN;
271
- const ptr1 = passStringToWasm0(shared_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
310
+ const ptr1 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
272
311
  const len1 = WASM_VECTOR_LEN;
273
- const ptr2 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
312
+ const ptr2 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
274
313
  const len2 = WASM_VECTOR_LEN;
275
- const ptr3 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
314
+ const ptr3 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
276
315
  const len3 = WASM_VECTOR_LEN;
277
- const ptr4 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
316
+ const ptr4 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
278
317
  const len4 = WASM_VECTOR_LEN;
279
- wasm.generateCoinWithSharedSecretHex(retptr, ptr0, len0, ptr1, len1, amount, ptr2, len2, ptr3, len3, ptr4, len4);
318
+ const ptr5 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
319
+ const len5 = WASM_VECTOR_LEN;
320
+ wasm.generateCoinWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5);
280
321
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
281
322
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
282
323
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -313,6 +354,38 @@ function proofToHex(proof_json) {
313
354
  }
314
355
  }
315
356
 
357
+ /**
358
+ * Graph witness + arkworks Groth16 prove. Returns `{ proof_hex, public_hex }`.
359
+ * @param {Uint8Array} graph
360
+ * @param {Uint8Array} proving_key
361
+ * @param {Uint8Array} r1cs
362
+ * @param {string} inputs_json
363
+ * @returns {any}
364
+ */
365
+ function proveGroth16(graph, proving_key, r1cs, inputs_json) {
366
+ try {
367
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
368
+ const ptr0 = passArray8ToWasm0(graph, wasm.__wbindgen_export);
369
+ const len0 = WASM_VECTOR_LEN;
370
+ const ptr1 = passArray8ToWasm0(proving_key, wasm.__wbindgen_export);
371
+ const len1 = WASM_VECTOR_LEN;
372
+ const ptr2 = passArray8ToWasm0(r1cs, wasm.__wbindgen_export);
373
+ const len2 = WASM_VECTOR_LEN;
374
+ const ptr3 = passStringToWasm0(inputs_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
375
+ const len3 = WASM_VECTOR_LEN;
376
+ wasm.proveGroth16(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
377
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
378
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
379
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
380
+ if (r2) {
381
+ throw takeObject(r1);
382
+ }
383
+ return takeObject(r0);
384
+ } finally {
385
+ wasm.__wbindgen_add_to_stack_pointer(16);
386
+ }
387
+ }
388
+
316
389
  /**
317
390
  * Convert snarkjs public signals JSON to hex bytes for Soroban contract.
318
391
  * @param {string} public_json
@@ -523,6 +596,13 @@ function isLikeNone(x) {
523
596
  return x === undefined || x === null;
524
597
  }
525
598
 
599
+ function passArray8ToWasm0(arg, malloc) {
600
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
601
+ getUint8ArrayMemory0().set(arg, ptr / 1);
602
+ WASM_VECTOR_LEN = arg.length;
603
+ return ptr;
604
+ }
605
+
526
606
  function passStringToWasm0(arg, malloc, realloc) {
527
607
  if (realloc === undefined) {
528
608
  const buf = cachedTextEncoder.encode(arg);
@@ -689,24 +769,26 @@ var wasmBindings = /*#__PURE__*/Object.freeze({
689
769
  buildWithdrawMerkleWitness: buildWithdrawMerkleWitness,
690
770
  calculateNullifierHash: calculateNullifierHash,
691
771
  default: __wbg_init,
772
+ derivedEscrowKey: derivedEscrowKey$1,
692
773
  ecdhEphemeralPublicKey: ecdhEphemeralPublicKey,
693
774
  ecdhEphemeralPublicKeyFromScalarHex: ecdhEphemeralPublicKeyFromScalarHex,
694
775
  ecdhSharedKey: ecdhSharedKey,
695
776
  generateCoin: generateCoin,
696
- generateCoinForDepositWithSharedHex: generateCoinForDepositWithSharedHex,
777
+ generateCoinForDepositWithOwnerPubHex: generateCoinForDepositWithOwnerPubHex,
697
778
  generateCoinFromDepositEphemeralScalarHex: generateCoinFromDepositEphemeralScalarHex,
698
- generateCoinWithSharedSecretHex: generateCoinWithSharedSecretHex,
779
+ generateCoinWithOwnerPubHex: generateCoinWithOwnerPubHex,
699
780
  initSync: initSync,
700
781
  proofToHex: proofToHex,
782
+ proveGroth16: proveGroth16,
701
783
  publicToHex: publicToHex
702
784
  });
703
785
 
704
- const isNode$2 = typeof process !== 'undefined' && !!process.versions?.node;
786
+ const isNode$1 = typeof process !== 'undefined' && !!process.versions?.node;
705
787
  let wasmModule = null;
706
788
  async function loadWasm(wasmBinary) {
707
789
  if (wasmModule)
708
790
  return wasmModule;
709
- if (isNode$2) {
791
+ if (isNode$1) {
710
792
  if (wasmBinary) {
711
793
  initSync({ module: wasmBinary });
712
794
  }
@@ -733,468 +815,301 @@ async function loadWasm(wasmBinary) {
733
815
  return wasmModule;
734
816
  }
735
817
 
736
- function getDefaultExportFromCjs (x) {
737
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
738
- }
739
-
740
- var witness_calculator = async function builder(code, options) {
741
-
742
- options = options || {};
743
-
744
- let wasmModule;
745
- try {
746
- wasmModule = await WebAssembly.compile(code);
747
- } catch (err) {
748
- console.log(err);
749
- console.log("\nTry to run circom --c in order to generate c++ code instead\n");
750
- throw new Error(err);
751
- }
752
-
753
- let wc;
754
-
755
- let errStr = "";
756
- let msgStr = "";
757
-
758
- const instance = await WebAssembly.instantiate(wasmModule, {
759
- runtime: {
760
- exceptionHandler : function(code) {
761
- let err;
762
- if (code == 1) {
763
- err = "Signal not found.\n";
764
- } else if (code == 2) {
765
- err = "Too many signals set.\n";
766
- } else if (code == 3) {
767
- err = "Signal already set.\n";
768
- } else if (code == 4) {
769
- err = "Assert Failed.\n";
770
- } else if (code == 5) {
771
- err = "Not enough memory.\n";
772
- } else if (code == 6) {
773
- err = "Input signal array access exceeds the size.\n";
774
- } else if (code == 7) {
775
- err = "Out of bounds array access.\n";
776
- } else {
777
- err = "Unknown error.\n";
778
- }
779
- throw new Error(err + errStr);
780
- },
781
- printErrorMessage : function() {
782
- errStr += getMessage() + "\n";
783
- // console.error(getMessage());
784
- },
785
- writeBufferMessage : function() {
786
- const msg = getMessage();
787
- // Any calls to `log()` will always end with a `\n`, so that's when we print and reset
788
- if (msg === "\n") {
789
- console.log(msgStr);
790
- msgStr = "";
791
- } else {
792
- // If we've buffered other content, put a space in between the items
793
- if (msgStr !== "") {
794
- msgStr += " ";
795
- }
796
- // Then append the message to the message we are creating
797
- msgStr += msg;
798
- }
799
- },
800
- showSharedRWMemory : function() {
801
- printSharedRWMemory ();
802
- }
803
-
804
- }
805
- });
806
-
807
- const sanityCheck =
808
- options;
809
- // options &&
810
- // (
811
- // options.sanityCheck ||
812
- // options.logGetSignal ||
813
- // options.logSetSignal ||
814
- // options.logStartComponent ||
815
- // options.logFinishComponent
816
- // );
817
-
818
-
819
- wc = new WitnessCalculator(instance, sanityCheck);
820
- return wc;
821
-
822
- function getMessage() {
823
- var message = "";
824
- var c = instance.exports.getMessageChar();
825
- while ( c != 0 ) {
826
- message += String.fromCharCode(c);
827
- c = instance.exports.getMessageChar();
828
- }
829
- return message;
830
- }
831
-
832
- function printSharedRWMemory () {
833
- const shared_rw_memory_size = instance.exports.getFieldNumLen32();
834
- const arr = new Uint32Array(shared_rw_memory_size);
835
- for (let j=0; j<shared_rw_memory_size; j++) {
836
- arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
837
- }
838
-
839
- // If we've buffered other content, put a space in between the items
840
- if (msgStr !== "") {
841
- msgStr += " ";
842
- }
843
- // Then append the value to the message we are creating
844
- msgStr += (fromArray32(arr).toString());
845
- }
846
-
818
+ /** Off-chain ZK layout table. Must stay byte-identical to `libs/zk::ZkLayoutParams`. */
819
+ const LEGACY_ZK_NONCE = 0n;
820
+ const COMMITMENT_V2_ZK_NONCE = 2n;
821
+ const BINDING_ZK_NONCE = 3n;
822
+ const SIX_BY_SIX_ZK_NONCE = 6n;
823
+ const SIX_BY_SIX_BINDING_ZK_NONCE = 7n;
824
+ const LEGACY_PUBLIC_INPUT_PREFIX_LEN = 5;
825
+ const V2_PUBLIC_INPUT_PREFIX_LEN = 7;
826
+ const NOTE_AUDIT_LEN$3 = 12;
827
+ const NOTE_OUTPUT_LEN$2 = 6;
828
+ var BundledZkCircuit;
829
+ (function (BundledZkCircuit) {
830
+ BundledZkCircuit["TwoByTwo"] = "2x2";
831
+ BundledZkCircuit["SixBySix"] = "6x6";
832
+ })(BundledZkCircuit || (BundledZkCircuit = {}));
833
+ function isDevZkCircuitConfig(config) {
834
+ return !('circuit' in config);
835
+ }
836
+ const DEFAULT_ZK_CONFIG_NONCE = BINDING_ZK_NONCE;
837
+ const DEFAULT_ZK_CIRCUITS = {
838
+ [BINDING_ZK_NONCE.toString()]: {
839
+ nIns: 2,
840
+ nOuts: 2,
841
+ publicNInputs: 1,
842
+ publicNOutputs: 1,
843
+ circuit: BundledZkCircuit.TwoByTwo,
844
+ },
845
+ [SIX_BY_SIX_BINDING_ZK_NONCE.toString()]: {
846
+ nIns: 6,
847
+ nOuts: 6,
848
+ publicNInputs: 1,
849
+ publicNOutputs: 1,
850
+ circuit: BundledZkCircuit.SixBySix,
851
+ },
847
852
  };
848
-
849
- class WitnessCalculator {
850
- constructor(instance, sanityCheck) {
851
- this.instance = instance;
852
-
853
- this.version = this.instance.exports.getVersion();
854
- this.n32 = this.instance.exports.getFieldNumLen32();
855
-
856
- this.instance.exports.getRawPrime();
857
- const arr = new Uint32Array(this.n32);
858
- for (let i=0; i<this.n32; i++) {
859
- arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
860
- }
861
- this.prime = fromArray32(arr);
862
-
863
- this.witnessSize = this.instance.exports.getWitnessSize();
864
-
865
- this.sanityCheck = sanityCheck;
866
- }
867
-
868
- circom_version() {
869
- return this.instance.exports.getVersion();
870
- }
871
-
872
- async _doCalculateWitness(input_orig, sanityCheck) {
873
- //input is assumed to be a map from signals to arrays of bigints
874
- this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
875
- let prefix = "";
876
- var input = new Object();
877
- //console.log("Input: ", input_orig);
878
- qualify_input(prefix,input_orig,input);
879
- //console.log("Input after: ",input);
880
- const keys = Object.keys(input);
881
- var input_counter = 0;
882
- keys.forEach( (k) => {
883
- const h = fnvHash(k);
884
- const hMSB = parseInt(h.slice(0,8), 16);
885
- const hLSB = parseInt(h.slice(8,16), 16);
886
- const fArr = flatArray(input[k]);
887
- let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
888
- if (signalSize < 0){
889
- throw new Error(`Signal ${k} not found\n`);
890
- }
891
- if (fArr.length < signalSize) {
892
- throw new Error(`Not enough values for input signal ${k}\n`);
893
- }
894
- if (fArr.length > signalSize) {
895
- throw new Error(`Too many values for input signal ${k}\n`);
896
- }
897
- for (let i=0; i<fArr.length; i++) {
898
- const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32);
899
- for (let j=0; j<this.n32; j++) {
900
- this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
901
- }
902
- try {
903
- this.instance.exports.setInputSignal(hMSB, hLSB,i);
904
- input_counter++;
905
- } catch (err) {
906
- // console.log(`After adding signal ${i} of ${k}`)
907
- throw new Error(err);
908
- }
909
- }
910
-
911
- });
912
- if (input_counter < this.instance.exports.getInputSize()) {
913
- throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
914
- }
853
+ function layoutFromCircuitConfig(config) {
854
+ return zkLayoutFromShapeBinding(config.nIns, config.nOuts, config.publicNInputs, config.publicNOutputs, config.nIns + config.nOuts, NOTE_AUDIT_LEN$3, NOTE_OUTPUT_LEN$2);
855
+ }
856
+ function resolveZkCircuitConfig(zkCircuits, zkConfigNonce) {
857
+ const nonce = zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
858
+ const circuits = zkCircuits ?? DEFAULT_ZK_CIRCUITS;
859
+ const config = circuits[nonce.toString()];
860
+ if (!config) {
861
+ throw new Error(`no zk circuit configured for nonce ${nonce.toString()}`);
915
862
  }
916
-
917
- async calculateWitness(input, sanityCheck) {
918
-
919
- const w = [];
920
- await this._doCalculateWitness(input, sanityCheck);
921
-
922
- for (let i=0; i<this.witnessSize; i++) {
923
- this.instance.exports.getWitness(i);
924
- const arr = new Uint32Array(this.n32);
925
- for (let j=0; j<this.n32; j++) {
926
- arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
927
- }
928
- w.push(fromArray32(arr));
929
- }
930
-
931
- return w;
863
+ return { nonce, config };
864
+ }
865
+ function bundledCircuitFileNames(circuit) {
866
+ if (circuit === BundledZkCircuit.SixBySix) {
867
+ return {
868
+ graphFileName: 'main_6x6.graph.bin',
869
+ r1csFileName: 'main_6x6.r1cs.gz',
870
+ provingKeyFileName: 'main_6x6_proving_key.bin',
871
+ verificationKeyFileName: 'main_6x6_verification_key.json',
872
+ zkeyFileName: 'main_6x6_final.zkey',
873
+ };
932
874
  }
933
-
934
-
935
- async calculateBinWitness(input, sanityCheck) {
936
-
937
- const buff32 = new Uint32Array(this.witnessSize*this.n32);
938
- const buff = new Uint8Array( buff32.buffer);
939
- await this._doCalculateWitness(input, sanityCheck);
940
-
941
- for (let i=0; i<this.witnessSize; i++) {
942
- this.instance.exports.getWitness(i);
943
- const pos = i*this.n32;
944
- for (let j=0; j<this.n32; j++) {
945
- buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
946
- }
947
- }
948
-
949
- return buff;
875
+ return {
876
+ graphFileName: 'main.graph.bin',
877
+ r1csFileName: 'main.r1cs.gz',
878
+ provingKeyFileName: 'main_proving_key.bin',
879
+ verificationKeyFileName: 'main_verification_key.json',
880
+ zkeyFileName: 'main_final.zkey',
881
+ };
882
+ }
883
+ function zkLayoutFromShape(nIns, nOuts, publicNInputs, publicNOutputs, nAuditSlots, noteAuditLen, noteOutputLen, publicInputPrefixLen = LEGACY_PUBLIC_INPUT_PREFIX_LEN) {
884
+ const auditOffset = nIns + nOuts + nOuts * 2;
885
+ const outputNoteOffset = auditOffset + nAuditSlots * 2 + nAuditSlots * noteAuditLen + nAuditSlots;
886
+ return {
887
+ nIns,
888
+ nOuts,
889
+ publicNInputs,
890
+ publicNOutputs,
891
+ nAuditSlots,
892
+ noteAuditLen,
893
+ noteOutputLen,
894
+ publicInputPrefixLen,
895
+ auditOffset,
896
+ outputNoteOffset,
897
+ };
898
+ }
899
+ function zkLayoutFromShapeBinding(nIns, nOuts, publicNInputs, publicNOutputs, nAuditSlots, noteAuditLen, noteOutputLen) {
900
+ const auditOffset = nIns + nOuts + nOuts * 2;
901
+ const outputNoteOffset = auditOffset + 2 + nAuditSlots;
902
+ return {
903
+ nIns,
904
+ nOuts,
905
+ publicNInputs,
906
+ publicNOutputs,
907
+ nAuditSlots,
908
+ noteAuditLen,
909
+ noteOutputLen,
910
+ publicInputPrefixLen: V2_PUBLIC_INPUT_PREFIX_LEN,
911
+ auditOffset,
912
+ outputNoteOffset,
913
+ };
914
+ }
915
+ function ciphertextsInPublicSignals(layout) {
916
+ const withCiphertext = layout.auditOffset +
917
+ layout.nAuditSlots * 2 +
918
+ layout.nAuditSlots * layout.noteAuditLen +
919
+ layout.nAuditSlots;
920
+ return layout.outputNoteOffset === withCiphertext;
921
+ }
922
+ function ciphertextFieldCount(layout) {
923
+ return layout.nAuditSlots * layout.noteAuditLen + layout.nOuts * layout.noteOutputLen;
924
+ }
925
+ function auditEphemeralPublicKeyCount(layout) {
926
+ return ciphertextsInPublicSignals(layout) ? layout.nAuditSlots : 1;
927
+ }
928
+ function indexAuditTags(layout) {
929
+ if (ciphertextsInPublicSignals(layout)) {
930
+ return (layout.auditOffset +
931
+ layout.nAuditSlots * 2 +
932
+ layout.nAuditSlots * layout.noteAuditLen);
950
933
  }
951
-
952
-
953
- async calculateWTNSBin(input, sanityCheck) {
954
-
955
- const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
956
- const buff = new Uint8Array( buff32.buffer);
957
- await this._doCalculateWitness(input, sanityCheck);
958
-
959
- //"wtns"
960
- buff[0] = "w".charCodeAt(0);
961
- buff[1] = "t".charCodeAt(0);
962
- buff[2] = "n".charCodeAt(0);
963
- buff[3] = "s".charCodeAt(0);
964
-
965
- //version 2
966
- buff32[1] = 2;
967
-
968
- //number of sections: 2
969
- buff32[2] = 2;
970
-
971
- //id section 1
972
- buff32[3] = 1;
973
-
974
- const n8 = this.n32*4;
975
- //id section 1 length in 64bytes
976
- const idSection1length = 8 + n8;
977
- const idSection1lengthHex = idSection1length.toString(16);
978
- buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
979
- buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
980
-
981
- //this.n32
982
- buff32[6] = n8;
983
-
984
- //prime number
985
- this.instance.exports.getRawPrime();
986
-
987
- var pos = 7;
988
- for (let j=0; j<this.n32; j++) {
989
- buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
990
- }
991
- pos += this.n32;
992
-
993
- // witness size
994
- buff32[pos] = this.witnessSize;
995
- pos++;
996
-
997
- //id section 2
998
- buff32[pos] = 2;
999
- pos++;
1000
-
1001
- // section 2 length
1002
- const idSection2length = n8*this.witnessSize;
1003
- const idSection2lengthHex = idSection2length.toString(16);
1004
- buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
1005
- buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
1006
-
1007
- pos += 2;
1008
- for (let i=0; i<this.witnessSize; i++) {
1009
- this.instance.exports.getWitness(i);
1010
- for (let j=0; j<this.n32; j++) {
1011
- buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
1012
- }
1013
- pos += this.n32;
1014
- }
1015
-
1016
- return buff;
934
+ return layout.auditOffset + auditEphemeralPublicKeyCount(layout) * 2;
935
+ }
936
+ function indexOutputNoteTags(layout) {
937
+ if (ciphertextsInPublicSignals(layout)) {
938
+ return layout.outputNoteOffset + layout.nOuts * layout.noteOutputLen;
1017
939
  }
1018
-
940
+ return layout.outputNoteOffset;
1019
941
  }
1020
-
1021
-
1022
- function qualify_input_list(prefix,input,input1){
1023
- if (Array.isArray(input)) {
1024
- for (let i = 0; i<input.length; i++) {
1025
- let new_prefix = prefix + "[" + i + "]";
1026
- qualify_input_list(new_prefix,input[i],input1);
1027
- }
1028
- } else {
1029
- qualify_input(prefix,input,input1);
1030
- }
1031
- }
1032
-
1033
- function qualify_input(prefix,input,input1) {
1034
- if (Array.isArray(input)) {
1035
- let a = flatArray(input);
1036
- if (a.length > 0) {
1037
- let t = typeof a[0];
1038
- for (let i = 1; i<a.length; i++) {
1039
- if (typeof a[i] != t){
1040
- throw new Error(`Types are not the same in the key ${prefix}`);
1041
- }
1042
- }
1043
- if (t == "object") {
1044
- qualify_input_list(prefix,input,input1);
1045
- } else {
1046
- input1[prefix] = input;
1047
- }
1048
- } else {
1049
- input1[prefix] = input;
1050
- }
1051
- } else if (typeof input == "object") {
1052
- const keys = Object.keys(input);
1053
- keys.forEach( (k) => {
1054
- let new_prefix = prefix == ""? k : prefix + "." + k;
1055
- qualify_input(new_prefix,input[k],input1);
1056
- });
1057
- } else {
1058
- input1[prefix] = input;
942
+ function publicOutputsLen(layout) {
943
+ if (ciphertextsInPublicSignals(layout)) {
944
+ return layout.outputNoteOffset + layout.nOuts * layout.noteOutputLen + layout.nOuts;
1059
945
  }
946
+ return layout.outputNoteOffset + layout.nOuts;
1060
947
  }
1061
-
1062
- function toArray32(rem,size) {
1063
- const res = []; //new Uint32Array(size); //has no unshift
1064
- const radix = BigInt(0x100000000);
1065
- while (rem) {
1066
- res.unshift( Number(rem % radix));
1067
- rem = rem / radix;
948
+ function totalPublicSignals(layout) {
949
+ return (publicOutputsLen(layout) +
950
+ layout.publicInputPrefixLen +
951
+ 2 * layout.publicNInputs +
952
+ 2 * layout.publicNOutputs +
953
+ layout.publicNInputs +
954
+ layout.publicNOutputs);
955
+ }
956
+ function stateRootIndex(layout) {
957
+ return publicOutputsLen(layout);
958
+ }
959
+ function legacyOwnerBoundLayout() {
960
+ return zkLayoutFromShape(2, 2, 1, 1, 4, NOTE_AUDIT_LEN$3, NOTE_OUTPUT_LEN$2);
961
+ }
962
+ function standardLayout() {
963
+ return zkLayoutFromShape(2, 2, 1, 1, 4, NOTE_AUDIT_LEN$3, NOTE_OUTPUT_LEN$2, V2_PUBLIC_INPUT_PREFIX_LEN);
964
+ }
965
+ function sixBySixLayout() {
966
+ return zkLayoutFromShape(6, 6, 1, 1, 12, NOTE_AUDIT_LEN$3, NOTE_OUTPUT_LEN$2, V2_PUBLIC_INPUT_PREFIX_LEN);
967
+ }
968
+ function standardBindingLayout() {
969
+ return zkLayoutFromShapeBinding(2, 2, 1, 1, 4, NOTE_AUDIT_LEN$3, NOTE_OUTPUT_LEN$2);
970
+ }
971
+ function sixBySixBindingLayout() {
972
+ return zkLayoutFromShapeBinding(6, 6, 1, 1, 12, NOTE_AUDIT_LEN$3, NOTE_OUTPUT_LEN$2);
973
+ }
974
+ function layoutForKnownNonce(nonce) {
975
+ if (nonce === LEGACY_ZK_NONCE) {
976
+ return legacyOwnerBoundLayout();
1068
977
  }
1069
- if (size) {
1070
- var i = size - res.length;
1071
- while (i>0) {
1072
- res.unshift(0);
1073
- i--;
1074
- }
978
+ if (nonce === COMMITMENT_V2_ZK_NONCE) {
979
+ return standardLayout();
1075
980
  }
1076
- return res;
1077
- }
1078
-
1079
- function fromArray32(arr) { //returns a BigInt
1080
- var res = BigInt(0);
1081
- const radix = BigInt(0x100000000);
1082
- for (let i = 0; i<arr.length; i++) {
1083
- res = res*radix + BigInt(arr[i]);
981
+ if (nonce === BINDING_ZK_NONCE) {
982
+ return standardBindingLayout();
1084
983
  }
1085
- return res;
1086
- }
1087
-
1088
- function flatArray(a) {
1089
- var res = [];
1090
- fillArray(res, a);
1091
- return res;
1092
-
1093
- function fillArray(res, a) {
1094
- if (Array.isArray(a)) {
1095
- for (let i=0; i<a.length; i++) {
1096
- fillArray(res, a[i]);
1097
- }
1098
- } else {
1099
- res.push(a);
1100
- }
984
+ if (nonce === SIX_BY_SIX_ZK_NONCE) {
985
+ return sixBySixLayout();
986
+ }
987
+ if (nonce === SIX_BY_SIX_BINDING_ZK_NONCE) {
988
+ return sixBySixBindingLayout();
1101
989
  }
990
+ throw new Error(`unknown zk config nonce ${nonce.toString()}`);
1102
991
  }
1103
-
1104
- function normalize(n, prime) {
1105
- let res = BigInt(n) % prime;
1106
- if (res < 0) res += prime;
1107
- return res
992
+ function circuitProfileFromName(name) {
993
+ const normalized = name.trim().toLowerCase();
994
+ if (normalized === '6x6' || normalized === 'six' || normalized === '6') {
995
+ return {
996
+ name: '6x6',
997
+ nonce: SIX_BY_SIX_BINDING_ZK_NONCE,
998
+ layout: sixBySixBindingLayout(),
999
+ wasmFileName: 'main_6x6.wasm',
1000
+ zkeyFileName: 'main_6x6_final.zkey',
1001
+ };
1002
+ }
1003
+ if (normalized === '2x2' || normalized === 'standard' || normalized === '2') {
1004
+ return {
1005
+ name: '2x2',
1006
+ nonce: BINDING_ZK_NONCE,
1007
+ layout: standardBindingLayout(),
1008
+ wasmFileName: 'main.wasm',
1009
+ zkeyFileName: 'main_final.zkey',
1010
+ };
1011
+ }
1012
+ throw new Error(`unknown circuit profile ${name}`);
1108
1013
  }
1109
-
1110
- function fnvHash(str) {
1111
- const uint64_max = BigInt(2) ** BigInt(64);
1112
- let hash = BigInt("0xCBF29CE484222325");
1113
- for (var i = 0; i < str.length; i++) {
1114
- hash ^= BigInt(str[i].charCodeAt());
1115
- hash *= BigInt(0x100000001B3);
1116
- hash %= uint64_max;
1014
+ function circuitProfileFromNonce(nonce) {
1015
+ if (nonce === SIX_BY_SIX_ZK_NONCE ||
1016
+ nonce === SIX_BY_SIX_BINDING_ZK_NONCE) {
1017
+ return circuitProfileFromName('6x6');
1117
1018
  }
1118
- let shash = hash.toString(16);
1119
- let n = 16 - shash.length;
1120
- shash = '0'.repeat(n).concat(shash);
1121
- return shash;
1019
+ return circuitProfileFromName('2x2');
1122
1020
  }
1123
1021
 
1124
- var witnessCalculatorModule = /*@__PURE__*/getDefaultExportFromCjs(witness_calculator);
1125
-
1126
- const isNode$1 = typeof process !== 'undefined' && !!process.versions?.node;
1127
- async function generateWitness(input, circuitWasm) {
1128
- let wasmBuffer;
1129
- let wc;
1130
- if (isNode$1) {
1131
- const fs = await import('fs');
1132
- const nodePath = await import('path');
1133
- const { fileURLToPath } = await import('url');
1134
- const dir = nodePath.dirname(fileURLToPath(import.meta.url));
1135
- const assetsDir = nodePath.resolve(dir, '..', 'assets');
1136
- wc = { default: witnessCalculatorModule };
1137
- if (circuitWasm) {
1138
- wasmBuffer = circuitWasm;
1139
- }
1140
- else {
1141
- const wasmPath = nodePath.resolve(assetsDir, 'main.wasm');
1142
- if (!fs.existsSync(wasmPath)) {
1143
- throw new Error(`Circuit WASM not found at ${wasmPath}. Run build.sh first.`);
1144
- }
1145
- wasmBuffer = fs.readFileSync(wasmPath);
1146
- }
1147
- }
1148
- else {
1149
- if (!circuitWasm) {
1150
- throw new Error('In browser, you must pass circuitWasm to PrivacyPoolSDK.init(). ' +
1151
- 'Load the circuit WASM file via fetch() and pass the ArrayBuffer.');
1152
- }
1153
- wasmBuffer = circuitWasm;
1154
- wc = { default: witnessCalculatorModule };
1022
+ const ZK_ARTIFACT_GITHUB_REPO = "Polynom-Labs/stellar-privacy-layer-contracts";
1023
+ const ZK_SDK_PACKAGE_VERSION = "0.9.1-rc.0";
1024
+ function defaultZkArtifactBaseUrl(version = ZK_SDK_PACKAGE_VERSION) {
1025
+ const tag = version.startsWith("v") ? version.slice(1) : version;
1026
+ return `https://github.com/${ZK_ARTIFACT_GITHUB_REPO}/releases/download/v${tag}`;
1027
+ }
1028
+ function trimArtifactBaseUrl(value) {
1029
+ let trimmed = value.trim();
1030
+ while (trimmed.endsWith("/")) {
1031
+ trimmed = trimmed.slice(0, -1);
1155
1032
  }
1156
- const calculator = await wc.default(wasmBuffer);
1157
- const wtns = await calculator.calculateWTNSBin(input, 0);
1158
- return wtns;
1033
+ return trimmed;
1159
1034
  }
1160
1035
 
1161
- // @ts-ignore - snarkjs types
1162
1036
  const isNode = typeof process !== 'undefined' && !!process.versions?.node;
1163
- /**
1164
- * Normalize BufferSource to Uint8Array. Required for @iden3/binfileutils/fastfile:
1165
- * they read via data.buffer and data.byteOffset, which ArrayBuffer does not have.
1166
- */
1167
1037
  function toUint8Array(src) {
1168
- if (src instanceof Uint8Array)
1038
+ if (src instanceof Uint8Array) {
1169
1039
  return src;
1170
- if (src instanceof ArrayBuffer)
1040
+ }
1041
+ if (src instanceof ArrayBuffer) {
1171
1042
  return new Uint8Array(src);
1043
+ }
1172
1044
  return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
1173
1045
  }
1174
- async function generateProof(wtns, zkey) {
1175
- let zkeyData;
1176
- if (zkey) {
1177
- zkeyData = toUint8Array(zkey);
1046
+ function nodeEnvArtifactBaseUrl() {
1047
+ if (!isNode) {
1048
+ return undefined;
1049
+ }
1050
+ const fromEnv = process.env.ZK_ARTIFACT_BASE_URL?.trim();
1051
+ return fromEnv ? trimArtifactBaseUrl(fromEnv) : undefined;
1052
+ }
1053
+ function resolveZkArtifactBaseUrl(explicit) {
1054
+ const trimmed = explicit?.trim();
1055
+ if (trimmed) {
1056
+ return trimArtifactBaseUrl(trimmed);
1057
+ }
1058
+ return nodeEnvArtifactBaseUrl() ?? defaultZkArtifactBaseUrl();
1059
+ }
1060
+ function isHttpUrl(value) {
1061
+ return /^https?:\/\//i.test(value) || value.startsWith('file:');
1062
+ }
1063
+ async function readNodeAssetFile(filePath) {
1064
+ const fs = await import('fs');
1065
+ if (!fs.existsSync(filePath)) {
1066
+ throw new Error(`Circuit artifact not found at ${filePath}. Set zkArtifactBaseUrl or ZK_ARTIFACT_BASE_URL.`);
1067
+ }
1068
+ return fs.readFileSync(filePath);
1069
+ }
1070
+ async function fetchArtifact(url) {
1071
+ const response = await fetch(url);
1072
+ if (!response.ok) {
1073
+ throw new Error(`Failed to fetch ${url}: ${String(response.status)}`);
1074
+ }
1075
+ return new Uint8Array(await response.arrayBuffer());
1076
+ }
1077
+ function remoteArtifactHref(base, fileName) {
1078
+ const withSlash = `${trimArtifactBaseUrl(base)}/`;
1079
+ if (isHttpUrl(withSlash)) {
1080
+ return new URL(fileName, withSlash).href;
1178
1081
  }
1179
- else if (isNode) {
1180
- const fs = await import('fs');
1082
+ if (typeof window !== 'undefined') {
1083
+ return new URL(fileName, new URL(withSlash, window.location.href)).href;
1084
+ }
1085
+ return `${withSlash}${fileName}`;
1086
+ }
1087
+ async function loadNamedArtifact(base, fileName) {
1088
+ if (isNode && !isHttpUrl(base)) {
1181
1089
  const nodePath = await import('path');
1182
- const { fileURLToPath } = await import('url');
1183
- const dir = nodePath.dirname(fileURLToPath(import.meta.url));
1184
- const zkeyPath = nodePath.resolve(dir, '..', 'assets', 'main_final.zkey');
1185
- if (!fs.existsSync(zkeyPath)) {
1186
- throw new Error(`Proving key not found at ${zkeyPath}. Run build.sh first.`);
1187
- }
1188
- const zkeyBuffer = fs.readFileSync(zkeyPath);
1189
- zkeyData = toUint8Array(zkeyBuffer);
1090
+ return readNodeAssetFile(nodePath.resolve(base, fileName));
1190
1091
  }
1191
- else {
1192
- throw new Error('In browser, you must pass zkey to PrivacyPoolSDK.init(). ' +
1193
- 'Load the zkey file via fetch() and pass the ArrayBuffer.');
1092
+ return fetchArtifact(remoteArtifactHref(base, fileName));
1093
+ }
1094
+ async function loadBundledCircuitArtifacts(circuit, artifactBaseUrl) {
1095
+ const base = resolveZkArtifactBaseUrl(artifactBaseUrl);
1096
+ const names = bundledCircuitFileNames(circuit);
1097
+ const [graph, r1cs, provingKey] = await Promise.all([
1098
+ loadNamedArtifact(base, names.graphFileName),
1099
+ loadNamedArtifact(base, names.r1csFileName),
1100
+ loadNamedArtifact(base, names.provingKeyFileName),
1101
+ ]);
1102
+ return { graph, r1cs, provingKey };
1103
+ }
1104
+ async function resolveCircuitArtifacts(config, artifactBaseUrl) {
1105
+ if (isDevZkCircuitConfig(config)) {
1106
+ return {
1107
+ graph: toUint8Array(config.circuitGraph),
1108
+ r1cs: toUint8Array(config.r1cs),
1109
+ provingKey: toUint8Array(config.provingKey),
1110
+ };
1194
1111
  }
1195
- const wtnsData = toUint8Array(wtns);
1196
- const { proof, publicSignals } = await snarkjs.groth16.prove({ type: 'mem', data: zkeyData }, { type: 'mem', data: wtnsData });
1197
- return { proof, publicSignals };
1112
+ return loadBundledCircuitArtifacts(config.circuit, artifactBaseUrl);
1198
1113
  }
1199
1114
 
1200
1115
  var dist = {};
@@ -1531,7 +1446,7 @@ function bytesToHex$1(bytes) {
1531
1446
  }
1532
1447
  return s;
1533
1448
  }
1534
- function concatBytes(a, b) {
1449
+ function concatBytes$1(a, b) {
1535
1450
  const out = new Uint8Array(a.length + b.length);
1536
1451
  out.set(a, 0);
1537
1452
  out.set(b, a.length);
@@ -1547,7 +1462,7 @@ function encodeStealthAddress(decoded) {
1547
1462
  if (xb.length !== yb.length) {
1548
1463
  throw new Error('stealth-address: x and y must encode the same number of bytes for a reversible address');
1549
1464
  }
1550
- const payload = concatBytes(xb, yb);
1465
+ const payload = concatBytes$1(xb, yb);
1551
1466
  const words = bech32.toWords(payload);
1552
1467
  return bech32.encode(STEALTH_ADDRESS_HRP, words, BECH32_STEALTH_LIMIT);
1553
1468
  }
@@ -1572,70 +1487,211 @@ function decodeStealthAddress(address) {
1572
1487
 
1573
1488
  /** Default nonce when deriving the wallet sign-in message. */
1574
1489
  const DEFAULT_STEALTH_SIGN_NONCE = 'main address';
1490
+ /** Domain tag mixed into the spend-scalar digest (H6). */
1491
+ const SPEND_SCALAR_DOMAIN_TAG = 'privacy-pool-spend-scalar-v1';
1492
+ /** Schema version bound into the spend-scalar message and digest. */
1493
+ const OWNER_BOUND_NOTE_SCHEMA_VERSION = 1;
1494
+ function resolveSpendScalarSchemaVersion(domain) {
1495
+ return domain.schemaVersion ?? OWNER_BOUND_NOTE_SCHEMA_VERSION;
1496
+ }
1575
1497
  /**
1576
- * Plaintext for Stellar wallet message signing (stealth address derivation).
1498
+ * Plaintext for Stellar wallet message signing (spend-key derivation).
1577
1499
  * Sign the exact bytes of this string (UTF-8) with the stellar account key.
1500
+ *
1501
+ * The derived scalar is the owner-bound spend key for notes in this environment.
1502
+ */
1503
+ function buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
1504
+ const schemaVersion = resolveSpendScalarSchemaVersion(domain);
1505
+ return [
1506
+ 'Arcane privacy layer: derive your private-note spend key.',
1507
+ '',
1508
+ `Wallet: ${address}`,
1509
+ `Network: ${domain.networkPassphrase}`,
1510
+ `Pool: ${domain.poolContract}`,
1511
+ `Registry: ${domain.registryContract}`,
1512
+ `Schema: owner-bound-note-v${schemaVersion}`,
1513
+ `Nonce: ${nonce}`,
1514
+ '',
1515
+ 'This signature derives the key that authorizes spending your private notes in this environment.',
1516
+ ].join('\n');
1517
+ }
1518
+
1519
+ const DOM_PADDING = 1347436868n;
1520
+ const DOM_NULLIFIER = 1346917708n;
1521
+ const DOM_ESCROW = 1346720579n;
1522
+ const DOM_ENC = 1346719299n;
1523
+ const DOM_AUDIT_TAG = 1346458695n;
1524
+ const DOM_OUTPUT_NOTE_TAG = 1347310663n;
1525
+ const BABYJUB_SUBGROUP_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
1526
+ /**
1527
+ * Map any integer into `(0, l)` so it satisfies circom `LessThan(252)` vs
1528
+ * `BABYJUB_SUBGROUP_ORDER` and `BabyPbk`. Values already in range are unchanged.
1578
1529
  */
1579
- function buildStealthAddressSignMessage(address, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
1580
- return `Privacy layer app needs you to sign the message for stealth address derivation with address:\n${address}\n and nonce:\n${nonce}.\n\n This will not authorize any transaction.`;
1530
+ function canonicalBabyJubScalarFromInteger(value) {
1531
+ if (value > 0n && value < BABYJUB_SUBGROUP_ORDER) {
1532
+ return value;
1533
+ }
1534
+ return (value % (BABYJUB_SUBGROUP_ORDER - 1n)) + 1n;
1581
1535
  }
1582
1536
 
1583
- const TRANSACTION_N_AUDIT_SLOTS = 4;
1584
- const DEFAULT_APPLICATION_ID = '101';
1585
- /** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
1586
- const DEMO_AUDIT_PUBLIC_KEY = [
1587
- '21605515851820432880964235241069234202284600780825340516808373216881770219365',
1588
- '18856460861531942120859708048677603751294231190189224157283439874962410808705',
1589
- ];
1590
- function isActiveWithdraw(slot) {
1591
- return slot.value !== '0';
1592
- }
1593
- function isActiveDeposit(slot) {
1594
- return slot.value !== '0';
1595
- }
1596
- function resolveWithdrawSlot(slot) {
1597
- if (slot === 'dummy') {
1598
- return null;
1537
+ function hexToBytes(hex) {
1538
+ const out = new Uint8Array(hex.length / 2);
1539
+ for (let i = 0; i < out.length; i++) {
1540
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1599
1541
  }
1600
- return slot;
1542
+ return out;
1601
1543
  }
1602
- function resolveDepositSlot(slot) {
1603
- if (slot === 'dummy') {
1604
- return null;
1544
+ /** Base64 → bytes via `atob` (browsers; Node 16+). No Node `Buffer`. */
1545
+ function decodeBase64ToBytes(s) {
1546
+ const t = s.replace(/\s/g, '');
1547
+ if (typeof atob !== 'function') {
1548
+ throw new Error('Base64 decoding requires atob (browser or Node 16+)');
1549
+ }
1550
+ try {
1551
+ const binary = atob(t);
1552
+ const out = new Uint8Array(binary.length);
1553
+ for (let i = 0; i < binary.length; i++) {
1554
+ out[i] = binary.charCodeAt(i);
1555
+ }
1556
+ return out;
1557
+ }
1558
+ catch {
1559
+ throw new Error('Invalid base64 signature');
1605
1560
  }
1606
- return slot;
1607
1561
  }
1608
- function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey) {
1609
- return {
1610
- applicationId,
1611
- noteAuditPublicKeys: [
1612
- auditPublicKey,
1613
- auditPublicKey,
1614
- auditPublicKey,
1615
- auditPublicKey,
1616
- ],
1617
- auditEphemeralScalars: [
1618
- randomFrDecimal253(),
1619
- randomFrDecimal253(),
1620
- randomFrDecimal253(),
1621
- randomFrDecimal253(),
1622
- ],
1623
- };
1562
+ /**
1563
+ * Ed25519 signature as **128 hex chars** (optional `0x`) or **base64** (typically 88 chars for 64 bytes, whitespace ignored).
1564
+ */
1565
+ function parseStellarEd25519SignatureRaw(input) {
1566
+ const trimmed = input.trim();
1567
+ const no0x = trimmed.replace(/^0x/i, '');
1568
+ if (/^[0-9a-fA-F]+$/.test(no0x) && no0x.length === 128) {
1569
+ return hexToBytes(no0x.toLowerCase());
1570
+ }
1571
+ const fromB64 = decodeBase64ToBytes(trimmed);
1572
+ if (fromB64.length !== 64) {
1573
+ throw new Error(`Decoded signature must be 64 bytes (Ed25519); got ${fromB64.length} from base64`);
1574
+ }
1575
+ return fromB64;
1576
+ }
1577
+ /** SHA-256 via Web Crypto only (`crypto.subtle`) — works in browsers and Node 19+ (global `crypto`). */
1578
+ async function sha256$1(data) {
1579
+ const subtle = globalThis.crypto?.subtle;
1580
+ if (!subtle) {
1581
+ throw new Error('SHA-256 requires crypto.subtle (HTTPS or localhost in browsers, or Node.js 19+).');
1582
+ }
1583
+ const copy = new Uint8Array(data.length);
1584
+ copy.set(data);
1585
+ return new Uint8Array(await subtle.digest('SHA-256', copy));
1586
+ }
1587
+ function appendU32BE$1(parts, value) {
1588
+ const buf = new Uint8Array(4);
1589
+ new DataView(buf.buffer).setUint32(0, value >>> 0, false);
1590
+ parts.push(buf);
1591
+ }
1592
+ function appendUtf8Prefixed(parts, text) {
1593
+ const bytes = new TextEncoder().encode(text);
1594
+ appendU32BE$1(parts, bytes.length);
1595
+ parts.push(bytes);
1596
+ }
1597
+ function concatBytes(parts) {
1598
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
1599
+ const out = new Uint8Array(total);
1600
+ let offset = 0;
1601
+ for (const part of parts) {
1602
+ out.set(part, offset);
1603
+ offset += part.length;
1604
+ }
1605
+ return out;
1606
+ }
1607
+ /**
1608
+ * Domain-separated spend-scalar digest: SHA-256(
1609
+ * SPEND_SCALAR_DOMAIN_TAG || network || pool || registry || schemaVersion || signature
1610
+ * ).
1611
+ */
1612
+ async function spendScalarDigestFromStellarSignature(signature, domain) {
1613
+ const raw = parseStellarEd25519SignatureRaw(signature);
1614
+ const parts = [new TextEncoder().encode(SPEND_SCALAR_DOMAIN_TAG)];
1615
+ appendUtf8Prefixed(parts, domain.networkPassphrase);
1616
+ appendUtf8Prefixed(parts, domain.poolContract);
1617
+ appendUtf8Prefixed(parts, domain.registryContract);
1618
+ appendU32BE$1(parts, resolveSpendScalarSchemaVersion(domain));
1619
+ parts.push(raw);
1620
+ return sha256$1(concatBytes(parts));
1621
+ }
1622
+ function digestToCanonicalScalar(digest) {
1623
+ let v = 0n;
1624
+ for (let i = 0; i < digest.length; i++) {
1625
+ v = (v << 8n) + BigInt(digest[i]);
1626
+ }
1627
+ return canonicalBabyJubScalarFromInteger(v);
1628
+ }
1629
+ function canonicalScalarHex(scalar) {
1630
+ return scalar.toString(16).padStart(64, '0');
1631
+ }
1632
+ async function spendScalarHexFromStellarSignature(signature, domain) {
1633
+ const digest = await spendScalarDigestFromStellarSignature(signature, domain);
1634
+ return canonicalScalarHex(digestToCanonicalScalar(digest));
1635
+ }
1636
+ /**
1637
+ * Stellar Ed25519 signature (hex or base64) → domain-separated SHA-256 → scalar → WASM ECDH.
1638
+ */
1639
+ async function stealthAddressFromStellarSignature(ecdhFromScalarHex, encodeStealth, signature, domain) {
1640
+ const digest = await spendScalarDigestFromStellarSignature(signature, domain);
1641
+ const decoded = ecdhFromScalarHex(canonicalScalarHex(digestToCanonicalScalar(digest)));
1642
+ return encodeStealth(decoded);
1643
+ }
1644
+ /**
1645
+ * Domain-separated SHA-256(signature, network, pool, registry, schema) reduced into
1646
+ * `(0, BabyJub subgroup order)` so `privKeyScalar` matches `BabyPbk` and circom `LessThan(l)`.
1647
+ */
1648
+ async function privKeyScalarDecimalFromStellarSignature(signature, domain) {
1649
+ const digest = await spendScalarDigestFromStellarSignature(signature, domain);
1650
+ return digestToCanonicalScalar(digest).toString(10);
1651
+ }
1652
+
1653
+ const TRANSACTION_N_AUDIT_SLOTS = 4;
1654
+ const DEFAULT_APPLICATION_ID = '101';
1655
+ /** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
1656
+ const DEMO_AUDIT_PUBLIC_KEY = [
1657
+ '21605515851820432880964235241069234202284600780825340516808373216881770219365',
1658
+ '18856460861531942120859708048677603751294231190189224157283439874962410808705',
1659
+ ];
1660
+ function isActiveWithdraw(slot) {
1661
+ return slot.value !== '0';
1662
+ }
1663
+ function isActiveDeposit(slot) {
1664
+ return slot.value !== '0';
1665
+ }
1666
+ function resolveWithdrawSlot(slot) {
1667
+ if (slot === 'dummy') {
1668
+ return null;
1669
+ }
1670
+ return slot;
1671
+ }
1672
+ function resolveDepositSlot(slot) {
1673
+ if (slot === 'dummy') {
1674
+ return null;
1675
+ }
1676
+ return slot;
1677
+ }
1678
+ function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey, _nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
1679
+ return {
1680
+ applicationId,
1681
+ noteAuditPublicKey: auditPublicKey,
1682
+ auditEphemeralScalar: randomFrDecimal253(),
1683
+ };
1624
1684
  }
1625
1685
  function resolveSlotApplicationIds(audit, withdrawSlots, depositSlots) {
1626
- const w0 = resolveWithdrawSlot(withdrawSlots[0]);
1627
- const w1 = resolveWithdrawSlot(withdrawSlots[1]);
1628
- const d0 = resolveDepositSlot(depositSlots[0]);
1629
- const d1 = resolveDepositSlot(depositSlots[1]);
1630
1686
  return {
1631
- inputApplicationIds: [
1632
- w0 && isActiveWithdraw(w0) ? audit.applicationId : '0',
1633
- w1 && isActiveWithdraw(w1) ? audit.applicationId : '0',
1634
- ],
1635
- outputApplicationIds: [
1636
- d0 && isActiveDeposit(d0) ? audit.applicationId : '0',
1637
- d1 && isActiveDeposit(d1) ? audit.applicationId : '0',
1638
- ],
1687
+ inputApplicationIds: withdrawSlots.map((slot) => {
1688
+ const resolved = resolveWithdrawSlot(slot);
1689
+ return resolved && isActiveWithdraw(resolved) ? audit.applicationId : '0';
1690
+ }),
1691
+ outputApplicationIds: depositSlots.map((slot) => {
1692
+ const resolved = resolveDepositSlot(slot);
1693
+ return resolved && isActiveDeposit(resolved) ? audit.applicationId : '0';
1694
+ }),
1639
1695
  };
1640
1696
  }
1641
1697
  function withApplicationIdOnDeposit(deposit, applicationId) {
@@ -1644,16 +1700,34 @@ function withApplicationIdOnDeposit(deposit, applicationId) {
1644
1700
  function withApplicationIdOnWithdraw(withdraw, applicationId) {
1645
1701
  return { ...withdraw, applicationId };
1646
1702
  }
1647
- function resolveTransactionAuditParams(applicationId, auditPublicKey) {
1648
- return buildUniformAuditParams(applicationId, auditPublicKey ?? DEMO_AUDIT_PUBLIC_KEY);
1703
+ /**
1704
+ * Prefer an explicit key, then `NOTE_AUDIT_PUBLIC_KEY_X`/`_Y` env (decimal Fr),
1705
+ * then the built-in demo key. Used by CLI/`demo.sh` for per-app audit pubkeys.
1706
+ */
1707
+ function resolveAuditPublicKeyFromEnv(auditPublicKey) {
1708
+ if (auditPublicKey) {
1709
+ return auditPublicKey;
1710
+ }
1711
+ const x = process.env.NOTE_AUDIT_PUBLIC_KEY_X?.trim();
1712
+ const y = process.env.NOTE_AUDIT_PUBLIC_KEY_Y?.trim();
1713
+ if (x && y) {
1714
+ return [x, y];
1715
+ }
1716
+ return DEMO_AUDIT_PUBLIC_KEY;
1717
+ }
1718
+ function resolveTransactionAuditParams(applicationId, auditPublicKey, nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
1719
+ return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey), nAuditSlots);
1649
1720
  }
1650
1721
 
1651
- /** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 9)` in `circuits/main.circom`. */
1722
+ /** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 12, 6)` in `circuits/main.circom`. */
1652
1723
  const TRANSACTION_TREE_DEPTH = 20;
1653
1724
  const TRANSACTION_N_INS = 2;
1654
1725
  const TRANSACTION_N_OUTS = 2;
1655
- const NOTE_AUDIT_LEN$1 = 9;
1726
+ const NOTE_AUDIT_LEN$2 = 12;
1656
1727
  const NOTE_OUTPUT_LEN$1 = 6;
1728
+ /** Must match `Transaction(..., publicNInputs, publicNOutputs)` and contract `get_public_slot_config`. */
1729
+ const TRANSACTION_PUBLIC_N_INPUTS = 1;
1730
+ const TRANSACTION_PUBLIC_N_OUTPUTS = 1;
1657
1731
  /** BN254 scalar field modulus (ark `Fr`, circom signals). */
1658
1732
  const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1659
1733
  /**
@@ -1661,6 +1735,29 @@ const BN254_SCALAR_MOD = 2188824287183927522224640574525727508854836440041603434
1661
1735
  * (matches `libs/cryptography` `scalar_mul_253`).
1662
1736
  */
1663
1737
  const BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE = 1n << 253n;
1738
+ function padPublicLegs(legs, publicNInputs, publicNOutputs) {
1739
+ const padPairs = (values, expected, label) => {
1740
+ if (values.length > expected) {
1741
+ throw new Error(`withdrawal-transaction-input: expected at most ${expected} ${label}`);
1742
+ }
1743
+ return [
1744
+ ...values,
1745
+ ...Array.from({ length: expected - values.length }, () => ['0', '0']),
1746
+ ];
1747
+ };
1748
+ const padAmounts = (values, expected, label) => {
1749
+ if (values.length > expected) {
1750
+ throw new Error(`withdrawal-transaction-input: expected at most ${expected} ${label}`);
1751
+ }
1752
+ return [...values, ...Array.from({ length: expected - values.length }, () => '0')];
1753
+ };
1754
+ return {
1755
+ publicWithdrawnAssets: padPairs(legs.publicWithdrawnAssets, publicNOutputs, 'public withdrawn assets'),
1756
+ publicDepositedAssets: padPairs(legs.publicDepositedAssets, publicNInputs, 'public deposited assets'),
1757
+ publicDeposits: padAmounts(legs.publicDeposits, publicNInputs, 'public deposits'),
1758
+ publicWithdrawals: padAmounts(legs.publicWithdrawals, publicNOutputs, 'public withdrawals'),
1759
+ };
1760
+ }
1664
1761
  function normalizeHex$1(hex) {
1665
1762
  const s = hex.trim().replace(/^0x/i, '');
1666
1763
  if (!/^[0-9a-fA-F]*$/.test(s)) {
@@ -1722,6 +1819,12 @@ function randomFrDecimal253() {
1722
1819
  const hex = generateRandomScalarHex32();
1723
1820
  return BigInt(`0x${normalizeHex$1(hex)}`).toString(10);
1724
1821
  }
1822
+ /** Spend-scalar hex in `(0, BabyJub subgroup order)` so dummy withdraws satisfy `LessThan(l)`. */
1823
+ function randomCanonicalBabyJubScalarHex() {
1824
+ const hex = generateRandomScalarHex32();
1825
+ const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex$1(hex)}`));
1826
+ return reduced.toString(16).padStart(64, '0');
1827
+ }
1725
1828
  function zerosTreeSiblings() {
1726
1829
  return Array(TRANSACTION_TREE_DEPTH).fill('0');
1727
1830
  }
@@ -1729,14 +1832,20 @@ function dummyWithdraw(wasm) {
1729
1832
  const nullifier = randomFrDecimal();
1730
1833
  const secretHex = generateRandomScalarHex32();
1731
1834
  const secret = (BigInt(`0x${normalizeHex$1(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
1732
- const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(secretHex);
1835
+ const scalarHex = randomCanonicalBabyJubScalarHex();
1836
+ const privKeyScalar = scalarHexToFrDecimal(scalarHex);
1837
+ const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(scalarHex);
1733
1838
  return {
1734
1839
  value: '0',
1735
1840
  nullifier,
1736
1841
  secret,
1737
1842
  asset: ['0', '0'],
1738
1843
  applicationId: '0',
1739
- ephemeralKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
1844
+ ownerPub: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
1845
+ privKeyScalar,
1846
+ paddingRandom: randomFrDecimal(),
1847
+ escrowNonce: '0',
1848
+ recipientStellar: ['0', '0'],
1740
1849
  stateSiblings: zerosTreeSiblings(),
1741
1850
  stateIndex: '0',
1742
1851
  };
@@ -1753,6 +1862,8 @@ function dummyDeposit(wasm) {
1753
1862
  asset: ['0', '0'],
1754
1863
  applicationId: '0',
1755
1864
  recipientPublicKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
1865
+ escrowNonce: '0',
1866
+ recipientStellar: ['0', '0'],
1756
1867
  };
1757
1868
  }
1758
1869
  function resolveWithdraw(slot, wasm) {
@@ -1767,37 +1878,63 @@ function resolveDepositsForWitness(depositSlots, wasm) {
1767
1878
  resolveDeposit(depositSlots[1], wasm),
1768
1879
  ];
1769
1880
  }
1770
- function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, depositSlots, audit, wasm) {
1771
- const w0 = resolveWithdraw(withdrawSlots[0], wasm);
1772
- const w1 = resolveWithdraw(withdrawSlots[1], wasm);
1773
- const d0 = resolveDeposit(depositSlots[0], wasm);
1774
- const d1 = resolveDeposit(depositSlots[1], wasm);
1775
- const appIds = resolveSlotApplicationIds(audit, withdrawSlots, depositSlots);
1881
+ function padWithdrawSlots(slots, nIns) {
1882
+ if (slots.length > nIns) {
1883
+ throw new Error(`withdrawal-transaction-input: expected at most ${nIns} withdraw slots`);
1884
+ }
1885
+ return [...slots, ...Array.from({ length: nIns - slots.length }, () => 'dummy')];
1886
+ }
1887
+ function padDepositSlots(slots, nOuts) {
1888
+ if (slots.length > nOuts) {
1889
+ throw new Error(`withdrawal-transaction-input: expected at most ${nOuts} deposit slots`);
1890
+ }
1891
+ return [...slots, ...Array.from({ length: nOuts - slots.length }, () => 'dummy')];
1892
+ }
1893
+ function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, depositSlots, audit, wasm, dims = {}) {
1894
+ const nIns = dims.nIns ?? TRANSACTION_N_INS;
1895
+ const nOuts = dims.nOuts ?? TRANSACTION_N_OUTS;
1896
+ const publicNInputs = dims.publicNInputs ?? TRANSACTION_PUBLIC_N_INPUTS;
1897
+ const publicNOutputs = dims.publicNOutputs ?? TRANSACTION_PUBLIC_N_OUTPUTS;
1898
+ const paddedWithdraws = padWithdrawSlots(withdrawSlots, nIns);
1899
+ const paddedDeposits = padDepositSlots(depositSlots, nOuts);
1900
+ const paddedLegs = padPublicLegs(publicLegs, publicNInputs, publicNOutputs);
1901
+ const withdraws = paddedWithdraws.map((slot) => resolveWithdraw(slot, wasm));
1902
+ const deposits = paddedDeposits.map((slot) => resolveDeposit(slot, wasm));
1903
+ const appIds = resolveSlotApplicationIds(audit, paddedWithdraws, paddedDeposits);
1776
1904
  return {
1777
1905
  stateRoot: publicParams.stateRoot,
1778
1906
  withdrawAddressHi: publicParams.withdrawAddressHi,
1779
1907
  withdrawAddressLo: publicParams.withdrawAddressLo,
1780
- privKeyScalar: publicParams.privKeyScalar,
1781
- withdrawnValues: [w0.value, w1.value],
1782
- withdrawnNullifiers: [w0.nullifier, w1.nullifier],
1783
- withdrawnSecrets: [w0.secret, w1.secret],
1784
- withdrawnAssets: [w0.asset, w1.asset],
1785
- ephemeralKeys: [w0.ephemeralKeys, w1.ephemeralKeys],
1786
- stateSiblings: [w0.stateSiblings, w1.stateSiblings],
1787
- stateIndex: [w0.stateIndex, w1.stateIndex],
1788
- depositedValues: [d0.value, d1.value],
1789
- depositedNullifiers: [d0.nullifier, d1.nullifier],
1790
- depositedAssets: [d0.asset, d1.asset],
1791
- depositedEphemeralKeyScalars: [d0.ephemeralKeyScalar, d1.ephemeralKeyScalar],
1792
- depositedRecipientPublicKeys: [d0.recipientPublicKeys, d1.recipientPublicKeys],
1908
+ escrowRecipientHi: publicParams.escrowRecipientHi ?? '0',
1909
+ escrowRecipientLo: publicParams.escrowRecipientLo ?? '0',
1910
+ sweepOutputOwnerPubX: publicParams.sweepOutputOwnerPubX ?? '0',
1911
+ sweepOutputOwnerPubY: publicParams.sweepOutputOwnerPubY ?? '0',
1912
+ privKeyScalars: withdraws.map((w) => w.privKeyScalar),
1913
+ ownerPubs: withdraws.map((w) => w.ownerPub),
1914
+ paddingRandoms: withdraws.map((w) => w.paddingRandom),
1915
+ withdrawnValues: withdraws.map((w) => w.value),
1916
+ withdrawnNullifiers: withdraws.map((w) => w.nullifier),
1917
+ withdrawnSecrets: withdraws.map((w) => w.secret),
1918
+ withdrawnAssets: withdraws.map((w) => w.asset),
1919
+ withdrawnEscrowNonces: withdraws.map((w) => w.escrowNonce),
1920
+ inputRecipientStellar: withdraws.map((w) => w.recipientStellar),
1921
+ stateSiblings: withdraws.map((w) => w.stateSiblings),
1922
+ stateIndex: withdraws.map((w) => w.stateIndex),
1923
+ depositedValues: deposits.map((d) => d.value),
1924
+ depositedNullifiers: deposits.map((d) => d.nullifier),
1925
+ depositedAssets: deposits.map((d) => d.asset),
1926
+ depositedEphemeralKeyScalars: deposits.map((d) => d.ephemeralKeyScalar),
1927
+ depositedRecipientPublicKeys: deposits.map((d) => d.recipientPublicKeys),
1928
+ depositedEscrowNonces: deposits.map((d) => d.escrowNonce ?? '0'),
1929
+ outputRecipientStellar: deposits.map((d) => d.recipientStellar ?? ['0', '0']),
1793
1930
  inputApplicationIds: appIds.inputApplicationIds,
1794
1931
  outputApplicationIds: appIds.outputApplicationIds,
1795
- auditEphemeralScalars: audit.auditEphemeralScalars,
1796
- noteAuditPublicKeys: audit.noteAuditPublicKeys,
1797
- publicWithdrawnAssets: publicLegs.publicWithdrawnAssets,
1798
- publicDepositedAssets: publicLegs.publicDepositedAssets,
1799
- publicDeposits: publicLegs.publicDeposits,
1800
- publicWithdrawals: publicLegs.publicWithdrawals,
1932
+ auditEphemeralScalar: audit.auditEphemeralScalar,
1933
+ noteAuditPublicKey: audit.noteAuditPublicKey,
1934
+ publicWithdrawnAssets: paddedLegs.publicWithdrawnAssets,
1935
+ publicDepositedAssets: paddedLegs.publicDepositedAssets,
1936
+ publicDeposits: paddedLegs.publicDeposits,
1937
+ publicWithdrawals: paddedLegs.publicWithdrawals,
1801
1938
  };
1802
1939
  }
1803
1940
  /** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
@@ -1805,135 +1942,574 @@ function recipientPublicKeysDecimalFromStealthAddress(stealthAddress) {
1805
1942
  const { x, y } = decodeStealthAddress(stealthAddress);
1806
1943
  return [coordHexToDecimal(x), coordHexToDecimal(y)];
1807
1944
  }
1808
- /** First withdraw leg: Merkle witness + depositor ECDH point coordinates (hex). */
1809
- function withdrawObjectFromMerkleWitness(witness, depositorEphemeralHex, applicationId) {
1945
+ /** First withdraw leg: Merkle witness + owner public key (hex) and matching scalar. */
1946
+ function withdrawObjectFromMerkleWitness(witness, ownerPubHex, applicationId, privKeyScalar) {
1810
1947
  return {
1811
1948
  value: witness.value,
1812
1949
  nullifier: witness.nullifier,
1813
1950
  secret: witness.secret,
1814
1951
  asset: witness.withdrawnAsset,
1815
1952
  applicationId,
1816
- ephemeralKeys: [
1817
- coordHexToDecimal(depositorEphemeralHex.x),
1818
- coordHexToDecimal(depositorEphemeralHex.y),
1819
- ],
1953
+ ownerPub: [coordHexToDecimal(ownerPubHex.x), coordHexToDecimal(ownerPubHex.y)],
1954
+ privKeyScalar,
1955
+ paddingRandom: randomFrDecimal(),
1956
+ escrowNonce: '0',
1957
+ recipientStellar: ['0', '0'],
1820
1958
  stateSiblings: witness.stateSiblings,
1821
1959
  stateIndex: witness.stateIndex,
1822
1960
  };
1823
1961
  }
1824
1962
 
1825
- function hexToBytes(hex) {
1826
- const out = new Uint8Array(hex.length / 2);
1827
- for (let i = 0; i < out.length; i++) {
1828
- out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1963
+ const FR_SIZE$1 = 32;
1964
+ const CIPHERTEXT_BLOB_HEADER_BYTES = 4;
1965
+ const FR_MODULUS$1 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1966
+ function moduleFr$1(value) {
1967
+ const reduced = value % FR_MODULUS$1;
1968
+ return reduced >= 0n ? reduced : reduced + FR_MODULUS$1;
1969
+ }
1970
+ function fieldToBytes(value) {
1971
+ let remaining = moduleFr$1(value);
1972
+ const buf = Buffer.alloc(FR_SIZE$1);
1973
+ for (let index = FR_SIZE$1 - 1; index >= 0; index -= 1) {
1974
+ buf[index] = Number(remaining & 0xffn);
1975
+ remaining >>= 8n;
1829
1976
  }
1830
- return out;
1977
+ return buf;
1831
1978
  }
1832
- function bytesToHexLower(bytes) {
1833
- let s = '';
1834
- for (let i = 0; i < bytes.length; i++) {
1835
- s += bytes[i].toString(16).padStart(2, '0');
1979
+ function decimalOrHexToBigint(value) {
1980
+ const trimmed = value.trim();
1981
+ if (/^0x/i.test(trimmed) || /^[0-9a-f]{64}$/i.test(trimmed)) {
1982
+ return BigInt(`0x${trimmed.replace(/^0x/i, '')}`);
1836
1983
  }
1837
- return s;
1984
+ return BigInt(trimmed);
1838
1985
  }
1839
- /** Base64 → bytes via `atob` (browsers; Node 16+). No Node `Buffer`. */
1840
- function decodeBase64ToBytes(s) {
1841
- const t = s.replace(/\s/g, '');
1842
- if (typeof atob !== 'function') {
1843
- throw new Error('Base64 decoding requires atob (browser or Node 16+)');
1986
+ function encodeCiphertextBlob(fields) {
1987
+ const prefix = Buffer.alloc(CIPHERTEXT_BLOB_HEADER_BYTES);
1988
+ prefix.writeUInt32BE(fields.length);
1989
+ return Buffer.concat([
1990
+ prefix,
1991
+ ...fields.map((field) => fieldToBytes(decimalOrHexToBigint(field))),
1992
+ ]);
1993
+ }
1994
+ function decodeCiphertextBlob(blob) {
1995
+ if (blob.length < CIPHERTEXT_BLOB_HEADER_BYTES) {
1996
+ throw new Error('ciphertext_bytes too short');
1844
1997
  }
1845
- try {
1846
- const binary = atob(t);
1847
- const out = new Uint8Array(binary.length);
1848
- for (let i = 0; i < binary.length; i++) {
1849
- out[i] = binary.charCodeAt(i);
1850
- }
1851
- return out;
1998
+ const count = blob.readUInt32BE(0);
1999
+ const need = CIPHERTEXT_BLOB_HEADER_BYTES + count * FR_SIZE$1;
2000
+ if (blob.length < need) {
2001
+ throw new Error('ciphertext_bytes truncated');
1852
2002
  }
1853
- catch {
1854
- throw new Error('Invalid base64 signature');
2003
+ const fields = [];
2004
+ let offset = CIPHERTEXT_BLOB_HEADER_BYTES;
2005
+ for (let index = 0; index < count; index += 1) {
2006
+ fields.push(blob.subarray(offset, offset + FR_SIZE$1).toString('hex'));
2007
+ offset += FR_SIZE$1;
1855
2008
  }
2009
+ return fields;
1856
2010
  }
1857
- /**
1858
- * Ed25519 signature as **128 hex chars** (optional `0x`) or **base64** (typically 88 chars for 64 bytes, whitespace ignored).
1859
- */
1860
- function parseStellarEd25519SignatureRaw(input) {
1861
- const trimmed = input.trim();
1862
- const no0x = trimmed.replace(/^0x/i, '');
1863
- if (/^[0-9a-fA-F]+$/.test(no0x) && no0x.length === 128) {
1864
- return hexToBytes(no0x.toLowerCase());
2011
+ function encodeTransactionCiphertextBlob(parameters) {
2012
+ const expected = ciphertextFieldCount(parameters.layout);
2013
+ const fields = [];
2014
+ for (const slot of parameters.auditCiphertexts) {
2015
+ fields.push(...slot);
1865
2016
  }
1866
- const fromB64 = decodeBase64ToBytes(trimmed);
1867
- if (fromB64.length !== 64) {
1868
- throw new Error(`Decoded signature must be 64 bytes (Ed25519); got ${fromB64.length} from base64`);
2017
+ for (const note of parameters.outputNoteCiphertexts) {
2018
+ fields.push(...note);
1869
2019
  }
1870
- return fromB64;
2020
+ if (fields.length !== expected) {
2021
+ throw new Error(`ciphertext blob expected ${String(expected)} fields, got ${String(fields.length)}`);
2022
+ }
2023
+ return encodeCiphertextBlob(fields);
1871
2024
  }
1872
- /** SHA-256 via Web Crypto only (`crypto.subtle`) — works in browsers and Node 19+ (global `crypto`). */
1873
- async function sha256$1(data) {
1874
- const subtle = globalThis.crypto?.subtle;
1875
- if (!subtle) {
1876
- throw new Error('SHA-256 requires crypto.subtle (HTTPS or localhost in browsers, or Node.js 19+).');
2025
+ function splitCiphertextBlob(parameters) {
2026
+ const fields = decodeCiphertextBlob(parameters.blob);
2027
+ const expected = ciphertextFieldCount(parameters.layout);
2028
+ if (fields.length !== expected) {
2029
+ throw new Error(`ciphertext blob expected ${String(expected)} fields, got ${String(fields.length)}`);
1877
2030
  }
1878
- const copy = new Uint8Array(data.length);
1879
- copy.set(data);
1880
- return new Uint8Array(await subtle.digest('SHA-256', copy));
2031
+ const { nAuditSlots, noteAuditLen, nOuts, noteOutputLen } = parameters.layout;
2032
+ const auditCiphertexts = [];
2033
+ let offset = 0;
2034
+ for (let slot = 0; slot < nAuditSlots; slot += 1) {
2035
+ auditCiphertexts.push(fields.slice(offset, offset + noteAuditLen));
2036
+ offset += noteAuditLen;
2037
+ }
2038
+ const outputNoteCiphertexts = [];
2039
+ for (let note = 0; note < nOuts; note += 1) {
2040
+ outputNoteCiphertexts.push(fields.slice(offset, offset + noteOutputLen));
2041
+ offset += noteOutputLen;
2042
+ }
2043
+ return { auditCiphertexts, outputNoteCiphertexts };
1881
2044
  }
1882
- /**
1883
- * Stellar Ed25519 signature (hex or base64) → SHA-256(signature bytes) → scalar → WASM ECDH (no UTF-8 seed hash).
1884
- */
1885
- async function stealthAddressFromStellarSignature(ecdhFromScalarHex, encodeStealth, signature) {
1886
- const raw = parseStellarEd25519SignatureRaw(signature);
1887
- const scalar = await sha256$1(raw);
1888
- const scalarHex = bytesToHexLower(scalar);
1889
- const decoded = ecdhFromScalarHex(scalarHex);
1890
- return encodeStealth(decoded);
2045
+
2046
+ const NOTE_OUTPUT_LEN = 6;
2047
+ const NOTE_AUDIT_LEN$1 = 12;
2048
+ const STREAM_DOMAIN = 1n;
2049
+ const FR_SIZE = 32;
2050
+ const FR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
2051
+ const ONE = 1n;
2052
+ const ZERO = 0n;
2053
+ const BABYJUB_SCALAR_BITS = 253n;
2054
+ const SCALAR_253_MASK = (ONE << BABYJUB_SCALAR_BITS) - ONE;
2055
+ let primitivesPromise;
2056
+ function primitives() {
2057
+ primitivesPromise ?? (primitivesPromise = Promise.all([
2058
+ buildBabyjub(),
2059
+ buildMimc7(),
2060
+ buildPoseidon(),
2061
+ ]).then(([babyJub, mimc7, poseidon]) => ({ babyJub, mimc7, poseidon })));
2062
+ return primitivesPromise;
1891
2063
  }
1892
- /**
1893
- * SHA-256(signature) as 32-byte BE integer, truncated to **253 bits** (same effective scalar as
1894
- * `libs/cryptography` `scalar_mul_253` / circom `Num2Bits(253)`), decimal for `privKeyScalar`.
1895
- */
1896
- async function privKeyScalarDecimalFromStellarSignature(signature) {
1897
- const raw = parseStellarEd25519SignatureRaw(signature);
1898
- const h = await sha256$1(raw);
1899
- let v = 0n;
1900
- for (let i = 0; i < h.length; i++) {
1901
- v = (v << 8n) + BigInt(h[i]);
2064
+ function normalizeHex(value) {
2065
+ return value.trim().replace(/^0x/i, '').padStart(FR_SIZE * 2, '0');
2066
+ }
2067
+ function bigintFromHex(value) {
2068
+ return BigInt(`0x${normalizeHex(value)}`);
2069
+ }
2070
+ function bigintFromDecimal(value) {
2071
+ const parsed = BigInt(value);
2072
+ return parsed % FR_MODULUS;
2073
+ }
2074
+ function bigintToFrHex(value) {
2075
+ let v = moduleFr(value);
2076
+ const buf = Buffer.alloc(FR_SIZE);
2077
+ for (let index = FR_SIZE - 1; index >= 0; index -= 1) {
2078
+ buf[index] = Number(v & 0xffn);
2079
+ v >>= 8n;
1902
2080
  }
1903
- const mask = BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE - 1n;
1904
- return (v & mask).toString(10);
2081
+ return buf.toString('hex');
2082
+ }
2083
+ function frDecimalToHex(value) {
2084
+ return bigintToFrHex(bigintFromDecimal(value));
2085
+ }
2086
+ function frDecimalFromBigint(value) {
2087
+ const reduced = value >= ZERO ? value % FR_MODULUS : (value % FR_MODULUS) + FR_MODULUS;
2088
+ return reduced.toString(10);
2089
+ }
2090
+ function fieldBigint(field, value) {
2091
+ return BigInt(field.toString(value));
2092
+ }
2093
+ function moduleFr(value) {
2094
+ const reduced = value % FR_MODULUS;
2095
+ return reduced >= ZERO ? reduced : reduced + FR_MODULUS;
2096
+ }
2097
+ function mimcHash(parameters) {
2098
+ return fieldBigint(parameters.mimc7.F, parameters.mimc7.hash(parameters.mimc7.F.e(parameters.message), parameters.mimc7.F.e(parameters.key)));
2099
+ }
2100
+ function poseidonHash(poseidon, inputs) {
2101
+ return fieldBigint(poseidon.F, poseidon(inputs.map((input) => poseidon.F.e(input))));
2102
+ }
2103
+ function deriveEncCipherKey(poseidon, sharedX) {
2104
+ return poseidonHash(poseidon, [DOM_ENC, sharedX]);
2105
+ }
2106
+ function sharedKeyFromDepositor(parameters) {
2107
+ const scalarHex = frDecimalToHex(parameters.ephemeralKeyScalarDecimal);
2108
+ const shared = parameters.ecdhShared(scalarHex, frDecimalToHex(parameters.recipientPublicKey[0]), frDecimalToHex(parameters.recipientPublicKey[1]));
2109
+ return [bigintFromHex(shared.x), bigintFromHex(shared.y)];
2110
+ }
2111
+ function sharedKeyFromRecipient(parameters) {
2112
+ const { babyJub, recipientScalarHex, ephemeralPublicKey } = parameters;
2113
+ const point = [
2114
+ babyJub.F.e(bigintFromHex(ephemeralPublicKey[0])),
2115
+ babyJub.F.e(bigintFromHex(ephemeralPublicKey[1])),
2116
+ ];
2117
+ if (!babyJub.inCurve(point)) {
2118
+ throw new Error('Created ephemeral public key is not on BabyJubJub curve');
2119
+ }
2120
+ const product = babyJub.mulPointEscalar(point, bigintFromHex(recipientScalarHex) & SCALAR_253_MASK);
2121
+ return [
2122
+ fieldBigint(babyJub.F, product[0]),
2123
+ fieldBigint(babyJub.F, product[1]),
2124
+ ];
2125
+ }
2126
+ function poseidonMacTag(parameters) {
2127
+ return poseidonHash(parameters.poseidon, [
2128
+ parameters.domain,
2129
+ parameters.key,
2130
+ parameters.nonce,
2131
+ ...parameters.message,
2132
+ ]);
2133
+ }
2134
+ function outputNoteTag(parameters) {
2135
+ return poseidonMacTag({
2136
+ poseidon: parameters.poseidon,
2137
+ domain: DOM_OUTPUT_NOTE_TAG,
2138
+ key: parameters.key,
2139
+ nonce: parameters.nonce,
2140
+ message: parameters.plaintext,
2141
+ });
2142
+ }
2143
+ function encryptFields(parameters) {
2144
+ return parameters.plaintext.map((field, index) => {
2145
+ const seed = poseidonHash(parameters.poseidon, [
2146
+ parameters.nonce,
2147
+ STREAM_DOMAIN,
2148
+ BigInt(index),
2149
+ ]);
2150
+ const stream = mimcHash({
2151
+ mimc7: parameters.mimc7,
2152
+ message: seed,
2153
+ key: parameters.key,
2154
+ });
2155
+ return moduleFr(field + stream);
2156
+ });
2157
+ }
2158
+ function decryptFields(parameters) {
2159
+ return parameters.ciphertext.map((field, index) => {
2160
+ const seed = poseidonHash(parameters.poseidon, [
2161
+ parameters.nonce,
2162
+ STREAM_DOMAIN,
2163
+ BigInt(index),
2164
+ ]);
2165
+ const stream = mimcHash({
2166
+ mimc7: parameters.mimc7,
2167
+ message: seed,
2168
+ key: parameters.key,
2169
+ });
2170
+ return moduleFr(bigintFromHex(field) - stream);
2171
+ });
2172
+ }
2173
+ function plaintextBigints(plaintext) {
2174
+ return [
2175
+ bigintFromDecimal(plaintext.value),
2176
+ bigintFromDecimal(plaintext.assetHi),
2177
+ bigintFromDecimal(plaintext.assetLo),
2178
+ bigintFromDecimal(plaintext.nullifier),
2179
+ bigintFromDecimal(plaintext.secret),
2180
+ bigintFromDecimal(plaintext.applicationId),
2181
+ ];
2182
+ }
2183
+ function plaintextFromBigints(fields) {
2184
+ return {
2185
+ value: frDecimalFromBigint(fields[0] ?? ZERO),
2186
+ assetHi: frDecimalFromBigint(fields[1] ?? ZERO),
2187
+ assetLo: frDecimalFromBigint(fields[2] ?? ZERO),
2188
+ nullifier: frDecimalFromBigint(fields[3] ?? ZERO),
2189
+ secret: frDecimalFromBigint(fields[4] ?? ZERO),
2190
+ applicationId: frDecimalFromBigint(fields[5] ?? ZERO),
2191
+ };
2192
+ }
2193
+ async function secretFromDepositEphemeralScalarDecimal(ephemeralKeyScalarDecimal) {
2194
+ const { poseidon } = await primitives();
2195
+ const secret = poseidonHash(poseidon, [bigintFromDecimal(ephemeralKeyScalarDecimal)]);
2196
+ return frDecimalFromBigint(secret);
2197
+ }
2198
+ async function encryptOutputNoteForDeposit(parameters) {
2199
+ const { babyJub, mimc7, poseidon } = await primitives();
2200
+ const secret = await secretFromDepositEphemeralScalarDecimal(parameters.deposit.ephemeralKeyScalar);
2201
+ const plaintext = {
2202
+ value: parameters.deposit.value,
2203
+ assetHi: parameters.deposit.asset[0],
2204
+ assetLo: parameters.deposit.asset[1],
2205
+ nullifier: parameters.deposit.nullifier,
2206
+ secret,
2207
+ applicationId: parameters.deposit.applicationId,
2208
+ };
2209
+ const [sharedX, sharedY] = sharedKeyFromDepositor({
2210
+ babyJub,
2211
+ ecdhShared: parameters.ecdhShared,
2212
+ ephemeralKeyScalarDecimal: parameters.deposit.ephemeralKeyScalar,
2213
+ recipientPublicKey: parameters.deposit.recipientPublicKeys,
2214
+ });
2215
+ const key = deriveEncCipherKey(poseidon, sharedX);
2216
+ const nonce = sharedY;
2217
+ const message = plaintextBigints(plaintext);
2218
+ const ciphertext = encryptFields({ mimc7, poseidon, key, nonce, plaintext: message });
2219
+ const tag = outputNoteTag({ poseidon, key, nonce, plaintext: message });
2220
+ return {
2221
+ ciphertext: ciphertext.map((field) => frDecimalFromBigint(field)),
2222
+ tag: frDecimalFromBigint(tag),
2223
+ };
2224
+ }
2225
+ function ownerPubFromRecipientScalar(babyJub, recipientScalarHex) {
2226
+ const product = babyJub.mulPointEscalar(babyJub.Base8, bigintFromHex(recipientScalarHex) & SCALAR_253_MASK);
2227
+ return [
2228
+ fieldBigint(babyJub.F, product[0]),
2229
+ fieldBigint(babyJub.F, product[1]),
2230
+ ];
2231
+ }
2232
+ function commitmentFromPlaintextAndOwnerPub(parameters) {
2233
+ const commitment = poseidonHash(parameters.poseidon, [
2234
+ bigintFromDecimal(parameters.plaintext.value),
2235
+ bigintFromDecimal(parameters.plaintext.secret),
2236
+ bigintFromDecimal(parameters.plaintext.nullifier),
2237
+ parameters.ownerPub[0],
2238
+ parameters.ownerPub[1],
2239
+ parameters.ownerMode,
2240
+ bigintFromDecimal(parameters.plaintext.assetHi),
2241
+ bigintFromDecimal(parameters.plaintext.assetLo),
2242
+ bigintFromDecimal(parameters.plaintext.applicationId),
2243
+ ]);
2244
+ return normalizeHex(commitment.toString(16));
2245
+ }
2246
+ async function decryptOutputNoteEvent(input) {
2247
+ const { babyJub, mimc7, poseidon } = await primitives();
2248
+ const [sharedX, sharedY] = sharedKeyFromRecipient({
2249
+ babyJub,
2250
+ recipientScalarHex: input.recipientScalarHex,
2251
+ ephemeralPublicKey: input.createdEphemeralKey,
2252
+ });
2253
+ const key = deriveEncCipherKey(poseidon, sharedX);
2254
+ const nonce = sharedY;
2255
+ const decrypted = decryptFields({
2256
+ mimc7,
2257
+ poseidon,
2258
+ key,
2259
+ nonce,
2260
+ ciphertext: input.ciphertext,
2261
+ });
2262
+ const expectedTag = outputNoteTag({
2263
+ poseidon,
2264
+ key,
2265
+ nonce,
2266
+ plaintext: decrypted,
2267
+ });
2268
+ if (expectedTag !== bigintFromHex(input.tag)) {
2269
+ throw new Error('Output note tag verification failed');
2270
+ }
2271
+ const plaintext = plaintextFromBigints(decrypted);
2272
+ const commitmentHex = commitmentFromPlaintextAndOwnerPub({
2273
+ poseidon,
2274
+ plaintext,
2275
+ ownerPub: ownerPubFromRecipientScalar(babyJub, input.recipientScalarHex),
2276
+ ownerMode: input.ownerMode ?? 0n,
2277
+ });
2278
+ const commitmentMatches = normalizeHex(input.commitmentHashHex) === commitmentHex;
2279
+ if (!commitmentMatches) {
2280
+ throw new Error('Output note commitment checksum mismatch');
2281
+ }
2282
+ return {
2283
+ ...plaintext,
2284
+ commitmentHex,
2285
+ commitmentMatches,
2286
+ };
2287
+ }
2288
+ function ownerModeFromEscrowNonce(escrowNonce) {
2289
+ return bigintFromDecimal(escrowNonce) === ZERO ? ZERO : ONE;
2290
+ }
2291
+ function emptyFlagFromValue(value) {
2292
+ return bigintFromDecimal(value) === ZERO ? ONE : ZERO;
2293
+ }
2294
+ function precommitmentFromOpening(parameters) {
2295
+ const ownerKeyHash = poseidonHash(parameters.poseidon, [
2296
+ parameters.ownerPub[0],
2297
+ parameters.ownerPub[1],
2298
+ ]);
2299
+ return poseidonHash(parameters.poseidon, [
2300
+ parameters.secret,
2301
+ parameters.ownerMode,
2302
+ ownerKeyHash,
2303
+ ]);
2304
+ }
2305
+ async function encryptNoteAuditSlot(parameters) {
2306
+ if (parameters.plaintext.length !== NOTE_AUDIT_LEN$1) {
2307
+ throw new Error(`encryptNoteAuditSlot: expected ${String(NOTE_AUDIT_LEN$1)} limbs`);
2308
+ }
2309
+ const { babyJub, mimc7, poseidon } = await primitives();
2310
+ const [sharedX, sharedY] = sharedKeyFromDepositor({
2311
+ babyJub,
2312
+ ecdhShared: parameters.ecdhShared,
2313
+ ephemeralKeyScalarDecimal: parameters.auditEphemeralScalar,
2314
+ recipientPublicKey: parameters.auditPublicKey,
2315
+ });
2316
+ const key = deriveEncCipherKey(poseidon, sharedX);
2317
+ const nonce = sharedY;
2318
+ const message = parameters.plaintext.map((field) => bigintFromDecimal(field));
2319
+ const ciphertext = encryptFields({ mimc7, poseidon, key, nonce, plaintext: message });
2320
+ const tag = poseidonMacTag({
2321
+ poseidon,
2322
+ domain: DOM_AUDIT_TAG,
2323
+ key,
2324
+ nonce,
2325
+ message,
2326
+ });
2327
+ return {
2328
+ ciphertext: ciphertext.map((field) => frDecimalFromBigint(field)),
2329
+ tag: frDecimalFromBigint(tag),
2330
+ };
2331
+ }
2332
+ function auditPlaintextForWithdraw(parameters) {
2333
+ const value = parameters.witness.withdrawnValues[parameters.index] ?? '0';
2334
+ const ownerPub = parameters.witness.ownerPubs[parameters.index] ?? ['0', '0'];
2335
+ const escrowNonce = parameters.witness.withdrawnEscrowNonces[parameters.index] ?? '0';
2336
+ const secret = bigintFromDecimal(parameters.witness.withdrawnSecrets[parameters.index] ?? '0');
2337
+ const ownerMode = ownerModeFromEscrowNonce(escrowNonce);
2338
+ const precommitment = precommitmentFromOpening({
2339
+ poseidon: parameters.poseidon,
2340
+ secret,
2341
+ ownerMode,
2342
+ ownerPub: [bigintFromDecimal(ownerPub[0]), bigintFromDecimal(ownerPub[1])],
2343
+ });
2344
+ const asset = parameters.witness.withdrawnAssets[parameters.index] ?? ['0', '0'];
2345
+ const stellar = parameters.witness.inputRecipientStellar[parameters.index] ?? ['0', '0'];
2346
+ return [
2347
+ frDecimalFromBigint(emptyFlagFromValue(value)),
2348
+ value,
2349
+ asset[0],
2350
+ asset[1],
2351
+ frDecimalFromBigint(precommitment),
2352
+ parameters.witness.withdrawnNullifiers[parameters.index] ?? '0',
2353
+ ownerPub[0],
2354
+ ownerPub[1],
2355
+ parameters.witness.inputApplicationIds[parameters.index] ?? '0',
2356
+ stellar[0],
2357
+ stellar[1],
2358
+ escrowNonce,
2359
+ ];
2360
+ }
2361
+ async function auditPlaintextForDeposit(parameters) {
2362
+ const value = parameters.witness.depositedValues[parameters.index] ?? '0';
2363
+ const ownerPub = parameters.witness.depositedRecipientPublicKeys[parameters.index] ?? ['0', '0'];
2364
+ const escrowNonce = parameters.witness.depositedEscrowNonces[parameters.index] ?? '0';
2365
+ const secret = poseidonHash(parameters.poseidon, [
2366
+ bigintFromDecimal(parameters.witness.depositedEphemeralKeyScalars[parameters.index] ?? '0'),
2367
+ ]);
2368
+ const ownerMode = ownerModeFromEscrowNonce(escrowNonce);
2369
+ const precommitment = precommitmentFromOpening({
2370
+ poseidon: parameters.poseidon,
2371
+ secret,
2372
+ ownerMode,
2373
+ ownerPub: [bigintFromDecimal(ownerPub[0]), bigintFromDecimal(ownerPub[1])],
2374
+ });
2375
+ const asset = parameters.witness.depositedAssets[parameters.index] ?? ['0', '0'];
2376
+ const stellar = parameters.witness.outputRecipientStellar[parameters.index] ?? ['0', '0'];
2377
+ return [
2378
+ frDecimalFromBigint(emptyFlagFromValue(value)),
2379
+ value,
2380
+ asset[0],
2381
+ asset[1],
2382
+ frDecimalFromBigint(precommitment),
2383
+ parameters.witness.depositedNullifiers[parameters.index] ?? '0',
2384
+ ownerPub[0],
2385
+ ownerPub[1],
2386
+ parameters.witness.outputApplicationIds[parameters.index] ?? '0',
2387
+ stellar[0],
2388
+ stellar[1],
2389
+ escrowNonce,
2390
+ ];
2391
+ }
2392
+ async function encryptTransactionCiphertextBlob(parameters) {
2393
+ const { poseidon } = await primitives();
2394
+ const nIns = parameters.witness.withdrawnValues.length;
2395
+ const nOuts = parameters.witness.depositedValues.length;
2396
+ const nAuditSlots = parameters.layout.nAuditSlots;
2397
+ const auditCiphertexts = [];
2398
+ for (let slot = 0; slot < nAuditSlots; slot += 1) {
2399
+ const plaintext = slot < nIns
2400
+ ? auditPlaintextForWithdraw({
2401
+ poseidon,
2402
+ witness: parameters.witness,
2403
+ index: slot,
2404
+ })
2405
+ : await auditPlaintextForDeposit({
2406
+ poseidon,
2407
+ witness: parameters.witness,
2408
+ index: slot - nIns,
2409
+ });
2410
+ const encrypted = await encryptNoteAuditSlot({
2411
+ auditPublicKey: parameters.witness.noteAuditPublicKey,
2412
+ auditEphemeralScalar: parameters.witness.auditEphemeralScalar,
2413
+ plaintext,
2414
+ ecdhShared: parameters.ecdhShared,
2415
+ });
2416
+ auditCiphertexts.push(encrypted.ciphertext);
2417
+ }
2418
+ const outputNoteCiphertexts = [];
2419
+ for (let index = 0; index < nOuts; index += 1) {
2420
+ const deposit = {
2421
+ value: parameters.witness.depositedValues[index] ?? '0',
2422
+ nullifier: parameters.witness.depositedNullifiers[index] ?? '0',
2423
+ ephemeralKeyScalar: parameters.witness.depositedEphemeralKeyScalars[index] ?? '0',
2424
+ asset: parameters.witness.depositedAssets[index] ?? ['0', '0'],
2425
+ applicationId: parameters.witness.outputApplicationIds[index] ?? '0',
2426
+ recipientPublicKeys: parameters.witness.depositedRecipientPublicKeys[index] ?? ['0', '0'],
2427
+ };
2428
+ const encrypted = await encryptOutputNoteForDeposit({
2429
+ deposit,
2430
+ ecdhShared: parameters.ecdhShared,
2431
+ });
2432
+ outputNoteCiphertexts.push([...encrypted.ciphertext]);
2433
+ }
2434
+ const blob = encodeTransactionCiphertextBlob({
2435
+ layout: parameters.layout,
2436
+ auditCiphertexts,
2437
+ outputNoteCiphertexts,
2438
+ });
2439
+ return {
2440
+ ciphertextHex: blob.toString('hex'),
2441
+ auditCiphertexts,
2442
+ outputNoteCiphertexts,
2443
+ };
1905
2444
  }
1906
2445
 
1907
- /** Stroops amount as WASM `u64` (`bigint`). */
1908
- function wasmU64Stroops(amount) {
1909
- const b = typeof amount === 'bigint' ? amount : BigInt(amount);
1910
- if (b < 0n || b > 0xffffffffffffffffn) {
1911
- throw new RangeError('amount must be a non-negative u64 (stroops)');
2446
+ /** Stroops amount as a decimal field string. Reject JS `number` so 18-decimal values cannot silently narrow. */
2447
+ function coinValueDecimal(amount) {
2448
+ if (typeof amount === "number") {
2449
+ throw new TypeError("coin value must be bigint or decimal string, not number");
2450
+ }
2451
+ if (typeof amount === "bigint") {
2452
+ if (amount < 0n) {
2453
+ throw new RangeError("amount must be a non-negative integer");
2454
+ }
2455
+ return amount.toString(10);
2456
+ }
2457
+ if (typeof amount !== "string") {
2458
+ throw new TypeError("coin value must be bigint or decimal string, not number");
2459
+ }
2460
+ const trimmed = amount.trim();
2461
+ if (!/^[0-9]+$/.test(trimmed)) {
2462
+ throw new TypeError("coin value decimal string must be a non-negative integer");
1912
2463
  }
1913
- return b;
2464
+ return trimmed;
2465
+ }
2466
+ function generatedCoinFromWasm(value) {
2467
+ const raw = value;
2468
+ const precommitementHex = raw.precommitementHex ?? raw.precommitement_hex;
2469
+ if (!precommitementHex) {
2470
+ throw new Error("generated coin missing precommitement hex");
2471
+ }
2472
+ return {
2473
+ coin: raw.coin,
2474
+ commitment_hex: raw.commitment_hex,
2475
+ precommitement_hex: precommitementHex,
2476
+ precommitementHex,
2477
+ };
1914
2478
  }
1915
2479
  class PrivacyPoolSDK {
1916
- constructor(wasm, options) {
2480
+ constructor(wasm, options, resolved) {
1917
2481
  this.wasm = wasm;
1918
2482
  this.options = options;
2483
+ this.zkConfigNonce = resolved.nonce;
2484
+ this.circuitConfig = resolved.config;
2485
+ this.layout = layoutFromCircuitConfig(resolved.config);
2486
+ }
2487
+ getZkConfigNonce() {
2488
+ return this.zkConfigNonce;
2489
+ }
2490
+ getLayout() {
2491
+ return this.layout;
1919
2492
  }
1920
2493
  /**
1921
2494
  * Initialize the SDK by loading WASM modules.
1922
2495
  *
1923
- * In Node.js, assets are loaded from the filesystem automatically.
1924
- * In browser, pass pre-loaded buffers via options:
2496
+ * In Node.js, proving artifacts load from `zkArtifactBaseUrl` or
2497
+ * `ZK_ARTIFACT_BASE_URL`, defaulting to this package's GitHub release.
2498
+ * In browser, pass pre-loaded WASM via options:
1925
2499
  * ```ts
1926
2500
  * const sdk = await PrivacyPoolSDK.init({
1927
2501
  * wasmBinary: await fetch('/pkg/client_sdk_wasm_bg.wasm').then(r => r.arrayBuffer()),
1928
- * circuitWasm: await fetch('/assets/main.wasm').then(r => r.arrayBuffer()),
1929
- * zkey: await fetch('/assets/main_final.zkey').then(r => r.arrayBuffer()),
2502
+ * zkArtifactBaseUrl: 'https://github.com/Polynom-Labs/stellar-privacy-layer-contracts/releases/download/v0.8.0',
1930
2503
  * });
1931
2504
  * ```
1932
2505
  */
1933
2506
  static async init(options) {
1934
2507
  const opts = options ?? {};
1935
2508
  const wasm = await loadWasm(opts.wasmBinary);
1936
- return new PrivacyPoolSDK(wasm, opts);
2509
+ const resolved = resolveZkCircuitConfig(opts.zkCircuits, opts.zkConfigNonce);
2510
+ const sdk = new PrivacyPoolSDK(wasm, opts, resolved);
2511
+ sdk.circuitArtifacts = await resolveCircuitArtifacts(resolved.config, opts.zkArtifactBaseUrl);
2512
+ return sdk;
1937
2513
  }
1938
2514
  /**
1939
2515
  * Uniform 32-byte scalar as lowercase hex (Web Crypto). Same in browser and Node 19+.
@@ -1942,39 +2518,40 @@ class PrivacyPoolSDK {
1942
2518
  return generateRandomScalarHex32();
1943
2519
  }
1944
2520
  /**
1945
- * Text to sign with a Stellar wallet for stealth derivation (UTF-8). No WASM required.
2521
+ * Text to sign with a Stellar wallet for spend-key derivation (UTF-8). No WASM required.
2522
+ * The message binds network, pool, registry, and schema version (H6).
1946
2523
  */
1947
- static buildStealthAddressSignMessage(address, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
1948
- return buildStealthAddressSignMessage(address, nonce);
2524
+ static buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
2525
+ return buildStealthAddressSignMessage(address, domain, nonce);
1949
2526
  }
1950
2527
  /**
1951
- * Generate a new coin with random nullifier, secret, and random shared-secret field elements (dev / self-contained tests).
1952
- * @param amount Stroops encoded as `bigint` or integer `number` (WASM `u64`).
2528
+ * Generate a new coin with random nullifier, secret, and random owner-pub field elements (dev / self-contained tests).
2529
+ * @param amount Decimal field element as `bigint` or decimal `string` (never JS `number`).
1953
2530
  * @param assetHiDecimal / assetLoDecimal Decimal Fr strings for Stellar asset contract id (two limbs).
1954
2531
  */
1955
- generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1956
- return this.wasm.generateCoin(wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
2532
+ generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
2533
+ return generatedCoinFromWasm(this.wasm.generateCoin(coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
1957
2534
  }
1958
2535
  /**
1959
- * Generate a coin with the same commitment shape as on-chain deposit: pass `ecdhSharedKey` output (hex x, y).
1960
- * @param amount Stroops (`bigint` | `number`).
2536
+ * Generate a coin with the same commitment shape as on-chain deposit: pass owner public key (hex x, y).
2537
+ * @param amount Decimal field element (`bigint` | `string`).
1961
2538
  */
1962
- generateCoinWithSharedSecret(shared, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1963
- return this.wasm.generateCoinWithSharedSecretHex(shared.x, shared.y, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
2539
+ generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
2540
+ return generatedCoinFromWasm(this.wasm.generateCoinWithOwnerPubHex(ownerPub.x, ownerPub.y, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
1964
2541
  }
1965
2542
  /**
1966
2543
  * Coin for a depositor `ephemeralKeyScalar` (32-byte hex): `coin.secret = Poseidon255(1)(scalar)` as in `deposit.circom`.
1967
- * @param amount Stroops (`bigint` | `number`).
2544
+ * @param amount Decimal field element (`bigint` | `string`).
1968
2545
  */
1969
- generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1970
- return this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
2546
+ generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
2547
+ return generatedCoinFromWasm(this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
1971
2548
  }
1972
2549
  /**
1973
- * Aligned deposit coin: `secret = Poseidon₁(scalar)` and ECDH shared key from hex coords (e.g. `ecdhSharedKey(scalar, recipient_x, recipient_y)`).
1974
- * @param amount Stroops (`bigint` | `number`).
2550
+ * Aligned deposit coin: `secret = Poseidon₁(scalar)` and owner pub from recipient hex coords.
2551
+ * @param amount Decimal field element (`bigint` | `string`).
1975
2552
  */
1976
- generateCoinForDepositWithSharedHex(scalarHex, sharedXHex, sharedYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1977
- return this.wasm.generateCoinForDepositWithSharedHex(scalarHex, sharedXHex, sharedYHex, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
2553
+ generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
2554
+ return generatedCoinFromWasm(this.wasm.generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
1978
2555
  }
1979
2556
  /**
1980
2557
  * Merkle root, path, and coin fields for the first withdraw leg (Rust LeanIMT + Poseidon).
@@ -1993,20 +2570,18 @@ class PrivacyPoolSDK {
1993
2570
  */
1994
2571
  async proveWithdrawal(coin, state, params) {
1995
2572
  const witness = this.buildWithdrawMerkleWitness(coin, state);
1996
- const w0 = withdrawObjectFromMerkleWitness(witness, {
1997
- x: params.ephemeralXHex,
1998
- y: params.ephemeralYHex,
1999
- }, params.applicationId ?? coin.application_id ?? '0');
2573
+ const ownerPubHex = this.ecdhEphemeralPublicKeyFromScalarHex(BigInt(params.privKeyScalar).toString(16).padStart(64, "0"));
2574
+ const w0 = withdrawObjectFromMerkleWitness(witness, ownerPubHex, params.applicationId ?? coin.application_id ?? "0", params.privKeyScalar);
2000
2575
  const fullV = BigInt(coin.value);
2001
2576
  let publicWithdrawals;
2002
2577
  let deposits;
2003
2578
  if (params.publicWithdrawStroops !== undefined) {
2004
2579
  const W = params.publicWithdrawStroops;
2005
2580
  if (params.changeRecipientStealthAddress === undefined) {
2006
- throw new Error('proveWithdrawal: changeRecipientStealthAddress is required when publicWithdrawStroops is set');
2581
+ throw new Error("proveWithdrawal: changeRecipientStealthAddress is required when publicWithdrawStroops is set");
2007
2582
  }
2008
2583
  if (W <= 0n || W >= fullV) {
2009
- throw new Error('proveWithdrawal: publicWithdrawStroops must be strictly between 0 and coin.value');
2584
+ throw new Error("proveWithdrawal: publicWithdrawStroops must be strictly between 0 and coin.value");
2010
2585
  }
2011
2586
  const change = fullV - W;
2012
2587
  publicWithdrawals = [W.toString()];
@@ -2015,29 +2590,29 @@ class PrivacyPoolSDK {
2015
2590
  nullifier: randomFrDecimal(),
2016
2591
  ephemeralKeyScalar: randomFrDecimal253(),
2017
2592
  asset: [coin.asset_hi, coin.asset_lo],
2018
- applicationId: params.applicationId ?? '0',
2593
+ applicationId: params.applicationId ?? "0",
2019
2594
  recipientPublicKeys: recipientPublicKeysDecimalFromStealthAddress(params.changeRecipientStealthAddress),
2020
2595
  };
2021
- deposits = [changeDeposit, 'dummy'];
2596
+ deposits = [changeDeposit];
2022
2597
  }
2023
2598
  else {
2024
2599
  publicWithdrawals = [coin.value];
2025
- deposits = ['dummy', 'dummy'];
2600
+ deposits = [];
2026
2601
  }
2027
2602
  // On-chain `transact` sends `publicWithdrawals` / `publicWithdrawnAssets` to the withdraw account.
2028
2603
  const publicLegs = {
2029
2604
  publicWithdrawnAssets: [[coin.asset_hi, coin.asset_lo]],
2030
- publicDepositedAssets: [['0', '0']],
2031
- publicDeposits: ['0'],
2605
+ publicDepositedAssets: [["0", "0"]],
2606
+ publicDeposits: ["0"],
2032
2607
  publicWithdrawals,
2033
2608
  };
2034
- return this.proveTransaction({
2609
+ return this.proveTransactionSlots({
2035
2610
  stateRoot: witness.stateRoot,
2036
2611
  withdrawAddressHi: params.withdrawAddressHi,
2037
2612
  withdrawAddressLo: params.withdrawAddressLo,
2038
2613
  privKeyScalar: params.privKeyScalar,
2039
- }, publicLegs, [w0, 'dummy'], deposits, params.audit
2040
- ?? resolveTransactionAuditParams(params.applicationId ?? coin.application_id ?? DEFAULT_APPLICATION_ID, DEMO_AUDIT_PUBLIC_KEY));
2614
+ }, publicLegs, [w0], deposits, params.audit ??
2615
+ resolveTransactionAuditParams(params.applicationId ?? coin.application_id ?? DEFAULT_APPLICATION_ID, DEMO_AUDIT_PUBLIC_KEY, this.layout.nAuditSlots));
2041
2616
  }
2042
2617
  /**
2043
2618
  * Convert a snarkjs proof JSON to hex bytes for Soroban.
@@ -2062,39 +2637,50 @@ class PrivacyPoolSDK {
2062
2637
  return this.wasm.ecdhSharedKey(scalarHex, recipientPubXHex, recipientPubYHex);
2063
2638
  }
2064
2639
  /**
2065
- * `Transaction(20,2,2)` proof from high-level legs: maps to witness input (incl. `"dummy"` ECDH via WASM), then Groth16 → Soroban hex.
2066
- *
2067
- * @param publicParams Public inputs: `stateRoot`, `withdrawAddressHi` / `withdrawAddressLo`, `privKeyScalar` (decimal field strings).
2068
- * @param publicLegs Public token legs (`publicWithdrawnAssets`, `publicDepositedAssets`, `publicDeposits`, `publicWithdrawals`).
2069
- * @param withdraws Exactly two withdraw slots (`WithdrawObject` or `"dummy"`).
2070
- * @param deposits Exactly two deposit slots (`DepositObject` or `"dummy"`).
2640
+ * `Transaction(20,nIns,nOuts)` proof from high-level legs: maps to witness input (incl. `"dummy"` ECDH via WASM), then Groth16 → Soroban hex.
2641
+ * Slots are padded to the selected ZK config layout (bundled 2×2 or 6×6).
2071
2642
  */
2072
2643
  async proveTransaction(publicParams, publicLegs, withdraws, deposits, audit) {
2644
+ return this.proveTransactionSlots(publicParams, publicLegs, withdraws, deposits, audit);
2645
+ }
2646
+ async proveTransactionSlots(publicParams, publicLegs, withdraws, deposits, audit) {
2073
2647
  const wasmEcdh = {
2074
2648
  ecdhEphemeralPublicKeyFromScalarHex: (h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h),
2075
2649
  };
2076
- const witnessInput = buildTransactionWitnessInput(publicParams, publicLegs, withdraws, deposits, audit, wasmEcdh);
2077
- const wtns = await generateWitness(witnessInput, this.options.circuitWasm);
2078
- const { proof, publicSignals } = await generateProof(wtns, this.options.zkey);
2650
+ const { nIns, nOuts, publicNInputs, publicNOutputs } = this.layout;
2651
+ const witnessInput = buildTransactionWitnessInput(publicParams, publicLegs, withdraws, deposits, audit, wasmEcdh, { nIns, nOuts, publicNInputs, publicNOutputs });
2652
+ const artifacts = this.circuitArtifacts;
2653
+ if (!artifacts) {
2654
+ throw new Error("PrivacyPoolSDK circuit artifacts are not loaded");
2655
+ }
2656
+ const proved = this.wasm.proveGroth16(artifacts.graph, artifacts.provingKey, artifacts.r1cs, JSON.stringify(witnessInput));
2657
+ const encrypted = await encryptTransactionCiphertextBlob({
2658
+ witness: witnessInput,
2659
+ layout: this.layout,
2660
+ ecdhShared: (priv, pubX, pubY) => this.wasm.ecdhSharedKey(priv, pubX, pubY),
2661
+ });
2079
2662
  return {
2080
- proof_hex: this.proofToHex(proof),
2081
- public_hex: this.publicToHex(publicSignals),
2663
+ proof_hex: proved.proof_hex,
2664
+ public_hex: proved.public_hex,
2665
+ ciphertext_hex: encrypted.ciphertextHex,
2666
+ output_note_ephemeral_scalars: witnessInput.depositedEphemeralKeyScalars,
2082
2667
  };
2083
2668
  }
2084
2669
  /**
2085
- * Calculate nullifier hash: Poseidon(nullifier)
2670
+ * Calculate owner-bound nullifier hash: Poseidon(DOM_NULLIFIER, nullifier, privKeyScalar)
2086
2671
  * @param nullifier Nullifier decimal string from coin data
2672
+ * @param privKeyScalar Spend scalar decimal string
2087
2673
  * @returns Hex string (0x...) of the hash bytes
2088
2674
  */
2089
- calculateNullifierHash(nullifier) {
2090
- return this.wasm.calculateNullifierHash(nullifier);
2675
+ calculateNullifierHash(nullifier, privKeyScalar) {
2676
+ return this.wasm.calculateNullifierHash(nullifier, privKeyScalar);
2091
2677
  }
2092
2678
  /**
2093
2679
  * Ed25519 signature from signing {@link buildStealthAddressSignMessage}: **128 hex chars** (optional `0x`)
2094
- * or **base64** (64 raw bytes after decode). `SHA-256(signature bytes)` → scalar → ECDH → `stpl1` Bech32.
2680
+ * or **base64** (64 raw bytes after decode). Domain-separated SHA-256 → scalar → ECDH → `stpl1` Bech32.
2095
2681
  */
2096
- async generateStealthAddressFromStellarSignature(signature) {
2097
- return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature);
2682
+ async generateStealthAddressFromStellarSignature(signature, domain) {
2683
+ return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature, domain);
2098
2684
  }
2099
2685
  encodeDecodedEphemeralKey(decoded) {
2100
2686
  return encodeDecodedEphemeralKey(decoded);
@@ -2116,11 +2702,30 @@ class PrivacyPoolSDK {
2116
2702
  }
2117
2703
  }
2118
2704
 
2705
+ /** @deprecated Witness generation is in the main SDK WASM (`proveGroth16`). */
2706
+ async function generateWitness(_input, _circuitWasm, _wasmFileName) {
2707
+ throw new Error('generateWitness is removed. Call PrivacyPoolSDK.proveTransaction (graph witness + Groth16 in sdk.wasm).');
2708
+ }
2709
+
2119
2710
  /** Pool Merkle tree depth (matches `coin::TREE_DEPTH` / `Transaction` circuit). */
2120
2711
  const POOL_MERKLE_TREE_DEPTH = 20;
2121
2712
  /** Default coin value in stroops (1 XLM); matches Rust `coin::COIN_VALUE`. */
2122
2713
  const COIN_VALUE_STROOPS = 1000000000;
2123
2714
 
2715
+ // Split a JS number into u32 halves without a BigInt allocation. Exact only for integers
2716
+ // `0 <= n < 2**53`; callers use it on byte / bit counters, which JS length math caps far below
2717
+ // that (an ArrayBuffer cannot exceed 2**53 - 1 bytes).
2718
+ const fromNumH = (n) => (n / 2 ** 32) | 0;
2719
+ const fromNumL = (n) => n >>> 0;
2720
+ // Drop-in replacement for `view.setBigUint64(byteOffset, BigInt(n), isLE)` without the per-call
2721
+ // BigInt allocation. Same `n < 2**53` precondition as `fromNumH`/`fromNumL`.
2722
+ function setU64FromNum(view, byteOffset, n, isLE) {
2723
+ const h = fromNumH(n);
2724
+ const l = fromNumL(n);
2725
+ view.setUint32(byteOffset, isLE ? l : h, isLE);
2726
+ view.setUint32(byteOffset + 4, isLE ? h : l, isLE);
2727
+ }
2728
+
2124
2729
  /**
2125
2730
  * Checks if something is Uint8Array. Be careful: nodejs Buffer will return true.
2126
2731
  * @param a - value to test
@@ -2142,6 +2747,9 @@ function isBytes(a) {
2142
2747
  'BYTES_PER_ELEMENT' in a &&
2143
2748
  a.BYTES_PER_ELEMENT === 1));
2144
2749
  }
2750
+ // Shared error-message prefix builder. Only called on throw paths, so assert
2751
+ // success paths never pay for the string concatenation.
2752
+ const atitle = (title) => (title ? `"${title}" ` : '');
2145
2753
  /**
2146
2754
  * Asserts something is Uint8Array.
2147
2755
  * @param value - value to validate
@@ -2157,20 +2765,32 @@ function isBytes(a) {
2157
2765
  * ```
2158
2766
  */
2159
2767
  function abytes(value, length, title = '') {
2768
+ // Success path first: this runs at the start of every update() / digestInto(), and the
2769
+ // common `abytes(data)` form must not pay for length handling it does not use.
2770
+ if (isBytes(value) && (length === undefined))
2771
+ return value;
2160
2772
  const bytes = isBytes(value);
2161
- const len = value?.length;
2162
- const needsLen = length !== undefined;
2163
- if (!bytes || (needsLen)) {
2164
- const prefix = title && `"${title}" `;
2165
- const ofLen = '';
2166
- const got = bytes ? `length=${len}` : `type=${typeof value}`;
2167
- const message = prefix + 'expected Uint8Array' + ofLen + ', got ' + got;
2168
- if (!bytes)
2169
- throw new TypeError(message);
2170
- throw new RangeError(message);
2171
- }
2172
- return value;
2173
- }
2773
+ const ofLen = '';
2774
+ const got = bytes ? `length=${value.length}` : `type=${typeof value}`;
2775
+ const message = atitle(title) + 'expected Uint8Array' + ofLen + ', got ' + got;
2776
+ if (!bytes)
2777
+ throw new TypeError(message);
2778
+ throw new RangeError(message);
2779
+ }
2780
+ const aobject = (value, label) => {
2781
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
2782
+ throw new TypeError((label === 'object' ? '' : `"${label}" `) + 'expected object, got type=' + typeof value);
2783
+ };
2784
+ const aopts = (value, label) => {
2785
+ aobject(value, label);
2786
+ const proto = Object.getPrototypeOf(value);
2787
+ if (proto !== Object.prototype && proto !== null)
2788
+ throw new TypeError(`"${label}" expected plain object`);
2789
+ // Object.assign() treats an own "__proto__" source key as a write to the target's legacy
2790
+ // prototype setter. Reject it before merging so inherited option values cannot be injected.
2791
+ if (Object.hasOwn(value, '__proto__'))
2792
+ throw new TypeError(`"${label}.__proto__" is not allowed`);
2793
+ };
2174
2794
  /**
2175
2795
  * Asserts a hash instance has not been destroyed or finished.
2176
2796
  * @param instance - hash instance to validate
@@ -2186,10 +2806,12 @@ function abytes(value, length, title = '') {
2186
2806
  * ```
2187
2807
  */
2188
2808
  function aexists(instance, checkFinished = true) {
2809
+ // Runs on every update()/digestInto(); the flags are library-owned booleans, so only their
2810
+ // truthiness is checked - re-validating their type per call was pure hot-path overhead.
2189
2811
  if (instance.destroyed)
2190
- throw new Error('Hash instance has been destroyed');
2812
+ throw new Error('hash was destroyed');
2191
2813
  if (checkFinished && instance.finished)
2192
- throw new Error('Hash#digest() has already been called');
2814
+ throw new Error('digest() was already called');
2193
2815
  }
2194
2816
  /**
2195
2817
  * Asserts output is a sufficiently-sized byte array.
@@ -2208,10 +2830,12 @@ function aexists(instance, checkFinished = true) {
2208
2830
  * ```
2209
2831
  */
2210
2832
  function aoutput(out, instance) {
2211
- abytes(out, undefined, 'digestInto() output');
2833
+ abytes(out, undefined, 'output');
2834
+ // `outputLen` is a library-owned readonly number; the negated comparison keeps failing fast
2835
+ // when it is missing/NaN (comparisons with undefined/NaN are false) without an anumber() call.
2212
2836
  const min = instance.outputLen;
2213
- if (out.length < min) {
2214
- throw new RangeError('"digestInto() output" expected to be of length >=' + min);
2837
+ if (!(out.length >= min)) {
2838
+ throw new RangeError('"output" expected length >= ' + min);
2215
2839
  }
2216
2840
  }
2217
2841
  /**
@@ -2286,6 +2910,28 @@ function bytesToHex(bytes) {
2286
2910
  }
2287
2911
  return hex;
2288
2912
  }
2913
+ /**
2914
+ * Merges default options and passed options.
2915
+ * @param defaults - base option object
2916
+ * @param opts - user overrides
2917
+ * @param title - label included in thrown override errors
2918
+ * @returns Fresh merged option object with a null prototype.
2919
+ * @throws On wrong argument types. {@link TypeError}
2920
+ * @example
2921
+ * Merge user overrides onto default options.
2922
+ * ```ts
2923
+ * checkOpts({ dkLen: 32 }, { asyncTick: 10 });
2924
+ * ```
2925
+ */
2926
+ function checkOpts(defaults, opts, title = 'opts') {
2927
+ aopts(defaults, 'defaults');
2928
+ if (opts !== undefined)
2929
+ aopts(opts, title);
2930
+ // Callers read optional fields directly, so omitted values must not fall through to ambient
2931
+ // Object.prototype pollution (for example a forged `dkLen` changing SHAKE's default output).
2932
+ const merged = Object.assign(Object.create(null), defaults, opts);
2933
+ return merged;
2934
+ }
2289
2935
  /**
2290
2936
  * Creates a callable hash function from a stateful class constructor.
2291
2937
  * @param hashCons - hash constructor or factory
@@ -2294,6 +2940,7 @@ function bytesToHex(bytes) {
2294
2940
  * Wrapper construction eagerly calls `hashCons(undefined)` once to read
2295
2941
  * `outputLen` / `blockLen`, so constructor side effects happen at module
2296
2942
  * init time.
2943
+ * @throws On wrong argument types. {@link TypeError}
2297
2944
  * @example
2298
2945
  * Wrap a stateful hash constructor into a callable helper.
2299
2946
  * ```ts
@@ -2304,6 +2951,9 @@ function bytesToHex(bytes) {
2304
2951
  * ```
2305
2952
  */
2306
2953
  function createHasher(hashCons, info = {}) {
2954
+ if (typeof hashCons !== 'function')
2955
+ throw new TypeError('"hashCons" expected function, got type=' + typeof hashCons);
2956
+ info = checkOpts({}, info, 'info');
2307
2957
  const hashC = (msg, opts) => hashCons(opts)
2308
2958
  .update(msg)
2309
2959
  .digest();
@@ -2415,6 +3065,7 @@ class HashMD {
2415
3065
  abytes(data);
2416
3066
  const { view, buffer, blockLen } = this;
2417
3067
  const len = data.length;
3068
+ let processed = false;
2418
3069
  for (let pos = 0; pos < len;) {
2419
3070
  const take = Math.min(blockLen - this.pos, len - pos);
2420
3071
  // Fast path only when there is no buffered partial block: `take === blockLen` implies
@@ -2423,18 +3074,25 @@ class HashMD {
2423
3074
  const dataView = createView(data);
2424
3075
  for (; blockLen <= len - pos; pos += blockLen)
2425
3076
  this.process(dataView, pos);
3077
+ processed = true;
2426
3078
  continue;
2427
3079
  }
2428
- buffer.set(data.subarray(pos, pos + take), this.pos);
3080
+ // When the whole input is buffered in one go (common for short messages), passing `data`
3081
+ // directly avoids allocating a subarray view.
3082
+ buffer.set(pos === 0 && take === len ? data : data.subarray(pos, pos + take), this.pos);
2429
3083
  this.pos += take;
2430
3084
  pos += take;
2431
3085
  if (this.pos === blockLen) {
2432
3086
  this.process(view, 0);
2433
3087
  this.pos = 0;
3088
+ processed = true;
2434
3089
  }
2435
3090
  }
2436
3091
  this.length += data.length;
2437
- this.roundClean();
3092
+ // Shared schedule buffers only pick up input-derived words inside process(); if everything
3093
+ // was buffered without processing, there is nothing to zero.
3094
+ if (processed)
3095
+ this.roundClean();
2438
3096
  return this;
2439
3097
  }
2440
3098
  digestInto(out) {
@@ -2446,32 +3104,33 @@ class HashMD {
2446
3104
  // was previously not allocated here. But it won't change performance.
2447
3105
  const { buffer, view, blockLen, isLE } = this;
2448
3106
  let { pos } = this;
2449
- // append the bit '1' to the message
3107
+ // append the bit '1' to the message, then zero-pad the rest of the block
2450
3108
  buffer[pos++] = 0b10000000;
2451
- clean(this.buffer.subarray(pos));
3109
+ buffer.fill(0, pos);
2452
3110
  // we have less than padOffset left in buffer, so we cannot put length in
2453
3111
  // current block, need process it and pad again
2454
3112
  if (this.padOffset > blockLen - pos) {
2455
3113
  this.process(view, 0);
2456
- pos = 0;
3114
+ buffer.fill(0);
2457
3115
  }
2458
- // Pad until full block byte with zeros
2459
- for (let i = pos; i < blockLen; i++)
2460
- buffer[i] = 0;
2461
3116
  // `padOffset` reserves the whole length field. For SHA-384/512 the high 64 bits stay zero from
2462
3117
  // the padding fill above, and JS will overflow before user input can make that half non-zero.
2463
- // So we only need to write the low 64 bits here.
2464
- view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
3118
+ // So we only need to write the low 64 bits here (`length * 8` only scales the exponent of an
3119
+ // integer below 2**53, so the split inside the helper stays exact).
3120
+ setU64FromNum(view, blockLen - 8, this.length * 8, isLE);
2465
3121
  this.process(view, 0);
2466
- const oview = createView(out);
3122
+ // The final block above is processed outside update(), so the shared message-schedule
3123
+ // buffers (e.g. SHA256_W) would otherwise retain input-derived words after digest().
3124
+ this.roundClean();
3125
+ // digest() passes our own `buffer` as `out`; reuse its cached view instead of allocating one.
3126
+ const oview = out === buffer ? view : createView(out);
2467
3127
  const len = this.outputLen;
2468
3128
  // NOTE: we do division by 4 later, which must be fused in single op with modulo by JIT
2469
- if (len % 4)
2470
- throw new Error('_sha2: outputLen must be aligned to 32bit');
2471
3129
  const outLen = len / 4;
2472
3130
  const state = this.get();
2473
- if (outLen > state.length)
2474
- throw new Error('_sha2: outputLen bigger than state');
3131
+ // Subclass-misconfiguration invariant: outputLen must be 32-bit aligned and fit the state.
3132
+ if (len % 4 || outLen > state.length)
3133
+ throw new Error('invalid outputLen');
2475
3134
  for (let i = 0; i < outLen; i++)
2476
3135
  oview.setUint32(4 * i, state[i], isLE);
2477
3136
  }
@@ -2484,18 +3143,16 @@ class HashMD {
2484
3143
  this.destroy();
2485
3144
  return res;
2486
3145
  }
2487
- _cloneInto(to) {
2488
- to ||= new this.constructor();
2489
- to.set(...this.get());
2490
- const { blockLen, buffer, length, finished, destroyed, pos } = this;
3146
+ _cloneIntoMeta(to) {
3147
+ const { buffer, length, finished, destroyed, pos } = this;
2491
3148
  to.destroyed = destroyed;
2492
3149
  to.finished = finished;
2493
3150
  to.length = length;
2494
3151
  to.pos = pos;
2495
3152
  // Only partial-block bytes need copying: when `length % blockLen === 0`, `pos === 0` and
2496
3153
  // later `update()` / `digestInto()` overwrite `to.buffer` from the start before reading it.
2497
- if (length % blockLen)
2498
- to.buffer.set(buffer);
3154
+ if (pos)
3155
+ to.buffer.set(buffer); // Avoid a hot modulo guard.
2499
3156
  return to;
2500
3157
  }
2501
3158
  clone() {
@@ -2539,8 +3196,28 @@ const SHA256_K = /* @__PURE__ */ Uint32Array.from([
2539
3196
  const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
2540
3197
  /** Internal SHA-224 / SHA-256 compression engine from RFC 6234 §6.2. */
2541
3198
  class SHA2_32B extends HashMD {
2542
- constructor(outputLen) {
3199
+ // We cannot use array here since array allows indexing by variable
3200
+ // which means optimizer/compiler cannot use registers.
3201
+ // Numeric initializers matter: starting the fields as `undefined` changes
3202
+ // V8's field representation and makes sha256 3x slower (measured).
3203
+ A = 0;
3204
+ B = 0;
3205
+ C = 0;
3206
+ D = 0;
3207
+ E = 0;
3208
+ F = 0;
3209
+ G = 0;
3210
+ H = 0;
3211
+ constructor(outputLen, IV) {
2543
3212
  super(64, outputLen, 8, false);
3213
+ this.A = IV[0] | 0;
3214
+ this.B = IV[1] | 0;
3215
+ this.C = IV[2] | 0;
3216
+ this.D = IV[3] | 0;
3217
+ this.E = IV[4] | 0;
3218
+ this.F = IV[5] | 0;
3219
+ this.G = IV[6] | 0;
3220
+ this.H = IV[7] | 0;
2544
3221
  }
2545
3222
  get() {
2546
3223
  const { A, B, C, D, E, F, G, H } = this;
@@ -2557,6 +3234,10 @@ class SHA2_32B extends HashMD {
2557
3234
  this.G = G | 0;
2558
3235
  this.H = H | 0;
2559
3236
  }
3237
+ _cloneInto(to) {
3238
+ (to ||= new this.constructor()).set(...this.get());
3239
+ return this._cloneIntoMeta(to);
3240
+ }
2560
3241
  process(view, offset) {
2561
3242
  // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
2562
3243
  for (let i = 0; i < 16; i++, offset += 4)
@@ -2608,153 +3289,39 @@ class SHA2_32B extends HashMD {
2608
3289
  }
2609
3290
  /** Internal SHA-256 hash class grounded in RFC 6234 §6.2. */
2610
3291
  class _SHA256 extends SHA2_32B {
2611
- // We cannot use array here since array allows indexing by variable
2612
- // which means optimizer/compiler cannot use registers.
2613
- A = SHA256_IV[0] | 0;
2614
- B = SHA256_IV[1] | 0;
2615
- C = SHA256_IV[2] | 0;
2616
- D = SHA256_IV[3] | 0;
2617
- E = SHA256_IV[4] | 0;
2618
- F = SHA256_IV[5] | 0;
2619
- G = SHA256_IV[6] | 0;
2620
- H = SHA256_IV[7] | 0;
2621
- constructor() {
2622
- super(32);
2623
- }
2624
- }
2625
- /**
2626
- * SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info:
2627
- *
2628
- * - Trying 2^128 hashes would get 50% chance of collision, using birthday attack.
2629
- * - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
2630
- * - Each sha256 hash is executing 2^18 bit operations.
2631
- * - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.
2632
- * @param msg - message bytes to hash
2633
- * @returns Digest bytes.
2634
- * @example
2635
- * Hash a message with SHA2-256.
2636
- * ```ts
2637
- * sha256(new Uint8Array([97, 98, 99]));
2638
- * ```
2639
- */
2640
- const sha256 = /* @__PURE__ */ createHasher(() => new _SHA256(),
2641
- /* @__PURE__ */ oidNist(0x01));
2642
-
2643
- const ONBOARDING_ZERO_DOMAIN = 'privacy-pool-onboarding-zero-v1';
2644
- function sha256Hex$1(data) {
2645
- return bytesToHex(sha256(data));
2646
- }
2647
- function appendU8$1(parts, value) {
2648
- parts.push(Buffer.from([value & 0xff]));
2649
- }
2650
- function appendU32BE$1(parts, value) {
2651
- const buf = Buffer.alloc(4);
2652
- buf.writeUInt32BE(value >>> 0);
2653
- parts.push(buf);
2654
- }
2655
- function appendU128BE$1(parts, value) {
2656
- const buf = Buffer.alloc(16);
2657
- buf.writeBigUInt64BE(value >> 64n, 0);
2658
- buf.writeBigUInt64BE(value & ((1n << 64n) - 1n), 8);
2659
- parts.push(buf);
2660
- }
2661
- function appendAddressStrkey$1(parts, address) {
2662
- const bytes = Buffer.from(address.trim(), 'utf8');
2663
- appendU32BE$1(parts, bytes.length);
2664
- parts.push(bytes);
2665
- }
2666
- function appendBytes32$1(parts, value) {
2667
- if (value.length !== 32) {
2668
- throw new Error(`expected 32-byte field, got ${value.length}`);
2669
- }
2670
- parts.push(value);
2671
- }
2672
- function appendBytesBlob(parts, data) {
2673
- appendU32BE$1(parts, data.length);
2674
- parts.push(data);
2675
- }
2676
- function encodePlaintextNote(parts, note) {
2677
- appendU128BE$1(parts, note.value);
2678
- appendBytes32$1(parts, note.asset_hi);
2679
- appendBytes32$1(parts, note.asset_lo);
2680
- appendBytes32$1(parts, note.nullifier);
2681
- appendBytes32$1(parts, note.secret);
2682
- if (note.deposited_ephemeral_scalar !== undefined) {
2683
- appendU8$1(parts, 1);
2684
- appendBytes32$1(parts, note.deposited_ephemeral_scalar);
2685
- }
2686
- else {
2687
- appendU8$1(parts, 0);
2688
- }
2689
- }
2690
- function encodeOptionalRegistration(parts, registration) {
2691
- if (registration.tag === 'None') {
2692
- appendU8$1(parts, 0);
2693
- return;
2694
- }
2695
- appendU8$1(parts, 1);
2696
- const reg = registration.values[0];
2697
- appendAddressStrkey$1(parts, reg.owner);
2698
- appendBytes32$1(parts, reg.public_key_x);
2699
- appendBytes32$1(parts, reg.public_key_y);
2700
- }
2701
- /** Canonical `onboardingHash` matching `libs/kyt-passage/src/lib.rs::hash_onboarding`. */
2702
- function hashOnboardingCanonical(onboarding) {
2703
- if (onboarding === null || onboarding === undefined) {
2704
- return sha256Hex$1(Buffer.from(ONBOARDING_ZERO_DOMAIN, 'utf8'));
2705
- }
2706
- const parts = [];
2707
- appendAddressStrkey$1(parts, onboarding.owner);
2708
- appendBytes32$1(parts, onboarding.temp_public_key_x);
2709
- appendBytes32$1(parts, onboarding.temp_public_key_y);
2710
- appendBytesBlob(parts, onboarding.encrypted_private_key);
2711
- appendU32BE$1(parts, onboarding.notes.length);
2712
- for (const note of onboarding.notes) {
2713
- encodePlaintextNote(parts, note);
2714
- }
2715
- encodeOptionalRegistration(parts, onboarding.private_address_registration);
2716
- return sha256Hex$1(Buffer.concat(parts));
2717
- }
2718
- function onboardingContractValue(onboarding) {
2719
- return {
2720
- owner: onboarding.owner,
2721
- temp_public_key_x: onboarding.temp_public_key_x,
2722
- temp_public_key_y: onboarding.temp_public_key_y,
2723
- encrypted_private_key: onboarding.encrypted_private_key,
2724
- notes: {
2725
- notes: onboarding.notes.map((note) => ({
2726
- value: note.value,
2727
- asset_hi: note.asset_hi,
2728
- asset_lo: note.asset_lo,
2729
- nullifier: note.nullifier,
2730
- secret: note.secret,
2731
- deposited_ephemeral_scalar: note.deposited_ephemeral_scalar,
2732
- })),
2733
- },
2734
- private_address_registration: onboarding.private_address_registration,
2735
- };
2736
- }
2737
- function onboardingToScVal(onboarding) {
2738
- return nativeToScVal(onboardingContractValue(onboarding));
2739
- }
2740
- function onboardingOptionToScVal(onboarding) {
2741
- if (onboarding === null || onboarding === undefined) {
2742
- return xdr.ScVal.scvVoid();
2743
- }
2744
- return onboardingToScVal(onboarding);
2745
- }
2746
- function addressToScVal(address) {
2747
- return nativeToScVal(address, { type: 'address' });
3292
+ constructor() {
3293
+ super(32, SHA256_IV);
3294
+ }
2748
3295
  }
3296
+ /**
3297
+ * SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info:
3298
+ *
3299
+ * - Trying 2^128 hashes would get 50% chance of collision, using birthday attack.
3300
+ * - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
3301
+ * - Each sha256 hash is executing 2^18 bit operations.
3302
+ * - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.
3303
+ * @param msg - message bytes to hash
3304
+ * @param opts - Reserved hash options.
3305
+ * @returns Digest bytes.
3306
+ * @example
3307
+ * Hash a message with SHA2-256.
3308
+ * ```ts
3309
+ * sha256(new Uint8Array([97, 98, 99]));
3310
+ * ```
3311
+ */
3312
+ const sha256 = /* @__PURE__ */ createHasher(() => new _SHA256(),
3313
+ /* @__PURE__ */ oidNist(0x01));
2749
3314
 
2750
3315
  const KYT_PASSAGE_AUTH_DOMAIN = 'privacy-pool-kyt-passage-v1';
2751
3316
  const PUBLIC_LEG_CONTEXT_DOMAIN = 'privacy-pool-public-leg-context-v1';
2752
- const PASSAGE_ID_DOMAIN = 'privacy-pool-passage-id-v1';
2753
- const NOTE_AUDIT_LEN = 9;
2754
- const TOTAL_PUBLIC_SIGNALS = 79;
3317
+ const PASSAGE_ID_DOMAIN = 'privacy-pool-passage-id-v3';
3318
+ const PASSAGE_ID_DOMAIN_V2 = 'privacy-pool-passage-id-v2';
3319
+ const PASSAGE_ID_DOMAIN_V1 = 'privacy-pool-passage-id-v1';
3320
+ const NOTE_AUDIT_LEN = 12;
3321
+ const TOTAL_PUBLIC_SIGNALS = 93;
3322
+ const NOTE_AUDIT_PLAINTEXT_LEN = 12;
2755
3323
  const FIELD_BYTES = 32;
2756
3324
  const LENGTH_PREFIX_BYTES = 4;
2757
- const STATE_ROOT_SIGNAL_INDEX = 70;
2758
3325
  function sha256Hex(data) {
2759
3326
  return bytesToHex(sha256(data));
2760
3327
  }
@@ -2763,7 +3330,14 @@ function publicSignalsBuffer(publicSignalsBytes) {
2763
3330
  ? Buffer.from(publicSignalsBytes.replace(/^0x/, ''), 'hex')
2764
3331
  : publicSignalsBytes;
2765
3332
  }
2766
- function parsePublicSignalsBytes(publicSignalsBytes) {
3333
+ function expectedSignalCountForNonce(zkConfigNonce = 0n) {
3334
+ return totalPublicSignals(layoutForKnownNonce(zkConfigNonce));
3335
+ }
3336
+ function stateRootIndexForNonce(zkConfigNonce = 0n) {
3337
+ return stateRootIndex(layoutForKnownNonce(zkConfigNonce));
3338
+ }
3339
+ function parsePublicSignalsBytes(publicSignalsBytes, zkConfigNonce = 0n) {
3340
+ const expectedCount = expectedSignalCountForNonce(zkConfigNonce);
2767
3341
  const buf = publicSignalsBuffer(publicSignalsBytes);
2768
3342
  let offset = 0;
2769
3343
  let hasLengthPrefix = false;
@@ -2775,11 +3349,11 @@ function parsePublicSignalsBytes(publicSignalsBytes) {
2775
3349
  }
2776
3350
  }
2777
3351
  const signalBytes = buf.subarray(offset);
2778
- if (signalBytes.length !== TOTAL_PUBLIC_SIGNALS * FIELD_BYTES) {
2779
- throw new Error(`expected ${TOTAL_PUBLIC_SIGNALS} public signals`);
3352
+ if (signalBytes.length !== expectedCount * FIELD_BYTES) {
3353
+ throw new Error(`expected ${expectedCount} public signals`);
2780
3354
  }
2781
3355
  return {
2782
- fields: Array.from({ length: TOTAL_PUBLIC_SIGNALS }, (_, index) => Buffer.from(signalBytes.subarray(index * FIELD_BYTES, (index + 1) * FIELD_BYTES))),
3356
+ fields: Array.from({ length: expectedCount }, (_, index) => Buffer.from(signalBytes.subarray(index * FIELD_BYTES, (index + 1) * FIELD_BYTES))),
2783
3357
  hasLengthPrefix,
2784
3358
  };
2785
3359
  }
@@ -2874,9 +3448,9 @@ function encodeLegSlot(parts, kind, slot, isPresent, assetHi, assetLo, amount, a
2874
3448
  parts.push(zeroAddressBuffer());
2875
3449
  }
2876
3450
  }
2877
- function hashPublicSignalBytes(publicSignalsBytes) {
2878
- const parsed = parsePublicSignalsBytes(publicSignalsBytes);
2879
- parsed.fields[STATE_ROOT_SIGNAL_INDEX] = Buffer.alloc(FIELD_BYTES);
3451
+ function hashPublicSignalBytes(publicSignalsBytes, zkConfigNonce = 0n) {
3452
+ const parsed = parsePublicSignalsBytes(publicSignalsBytes, zkConfigNonce);
3453
+ parsed.fields[stateRootIndexForNonce(zkConfigNonce)] = Buffer.alloc(FIELD_BYTES);
2880
3454
  return sha256Hex(encodePublicSignalsBytes(parsed.fields, parsed.hasLengthPrefix));
2881
3455
  }
2882
3456
  function hashPublicLegContext(layout, input) {
@@ -2898,16 +3472,32 @@ function hashPublicLegContext(layout, input) {
2898
3472
  }
2899
3473
  return sha256Hex(Buffer.concat(parts));
2900
3474
  }
2901
- function hashOnboarding(onboarding) {
2902
- return hashOnboardingCanonical(onboarding);
2903
- }
2904
- function derivePassageId(owner, nonce, publicSignalHash, publicLegContextHash, onboardingHash) {
3475
+ function derivePassageId(owner, nonce, publicSignalHash, publicLegContextHash, ciphertextHash) {
2905
3476
  const parts = [];
2906
3477
  appendBytes(parts, Buffer.from(PASSAGE_ID_DOMAIN, 'utf8'));
2907
3478
  appendAddressStrkey(parts, owner);
2908
3479
  appendU64BE(parts, nonce);
2909
3480
  appendBytes32(parts, publicSignalHash);
2910
3481
  appendBytes32(parts, publicLegContextHash);
3482
+ appendBytes32(parts, ciphertextHash);
3483
+ return sha256Hex(Buffer.concat(parts));
3484
+ }
3485
+ function derivePassageIdV2(owner, nonce, publicSignalHash, publicLegContextHash) {
3486
+ const parts = [];
3487
+ appendBytes(parts, Buffer.from(PASSAGE_ID_DOMAIN_V2, 'utf8'));
3488
+ appendAddressStrkey(parts, owner);
3489
+ appendU64BE(parts, nonce);
3490
+ appendBytes32(parts, publicSignalHash);
3491
+ appendBytes32(parts, publicLegContextHash);
3492
+ return sha256Hex(Buffer.concat(parts));
3493
+ }
3494
+ function derivePassageIdV1(owner, nonce, publicSignalHash, publicLegContextHash, onboardingHash) {
3495
+ const parts = [];
3496
+ appendBytes(parts, Buffer.from(PASSAGE_ID_DOMAIN_V1, 'utf8'));
3497
+ appendAddressStrkey(parts, owner);
3498
+ appendU64BE(parts, nonce);
3499
+ appendBytes32(parts, publicSignalHash);
3500
+ appendBytes32(parts, publicLegContextHash);
2911
3501
  appendBytes32(parts, onboardingHash);
2912
3502
  return sha256Hex(Buffer.concat(parts));
2913
3503
  }
@@ -2929,32 +3519,93 @@ function buildKytPassageAuthorization(input) {
2929
3519
  }
2930
3520
  function derivePassageFromTransactContext(input) {
2931
3521
  const layout = input.layout ?? { publicNInputs: 1, publicNOutputs: 1 };
2932
- const nonce = input.nonce ?? 0n;
2933
- const publicSignalHash = hashPublicSignalBytes(input.publicSignalsBytes);
3522
+ const nonce = BigInt(input.nonce ?? 0);
3523
+ const publicSignalHash = hashPublicSignalBytes(input.publicSignalsBytes, nonce);
2934
3524
  const publicLegContextHash = hashPublicLegContext(layout, input.publicLegContext);
2935
- const onboardingHash = hashOnboarding(input.onboarding ?? null);
2936
- const passageId = derivePassageId(input.owner, nonce, publicSignalHash, publicLegContextHash, onboardingHash);
3525
+ const zkLayout = layoutForKnownNonce(nonce);
3526
+ const passageId = ciphertextsInPublicSignals(zkLayout)
3527
+ ? derivePassageIdV2(input.owner, nonce, publicSignalHash, publicLegContextHash)
3528
+ : derivePassageId(input.owner, nonce, publicSignalHash, publicLegContextHash, hashCiphertextBytes(input.ciphertextBytes ?? Buffer.alloc(0)));
2937
3529
  return {
2938
3530
  publicSignalHash,
2939
3531
  publicLegContextHash,
2940
- onboardingHash,
2941
3532
  passageId,
2942
3533
  };
2943
3534
  }
3535
+ function hashCiphertextBytes(ciphertextBytes) {
3536
+ const buffer = typeof ciphertextBytes === 'string'
3537
+ ? Buffer.from(ciphertextBytes.replace(/^0x/i, ''), 'hex')
3538
+ : ciphertextBytes;
3539
+ return sha256Hex(buffer);
3540
+ }
3541
+ function zeroBindingTagsInPublicSignals(publicSignalsBytes, zkConfigNonce = 0n) {
3542
+ const layout = layoutForKnownNonce(zkConfigNonce);
3543
+ if (ciphertextsInPublicSignals(layout)) {
3544
+ return publicSignalsBuffer(publicSignalsBytes);
3545
+ }
3546
+ const parsed = parsePublicSignalsBytes(publicSignalsBytes, zkConfigNonce);
3547
+ const zero = Buffer.alloc(FIELD_BYTES);
3548
+ const auditStart = indexAuditTags(layout);
3549
+ for (let slot = 0; slot < layout.nAuditSlots; slot += 1) {
3550
+ parsed.fields[auditStart + slot] = Buffer.from(zero);
3551
+ }
3552
+ const outputStart = indexOutputNoteTags(layout);
3553
+ for (let note = 0; note < layout.nOuts; note += 1) {
3554
+ parsed.fields[outputStart + note] = Buffer.from(zero);
3555
+ }
3556
+ return encodePublicSignalsBytes(parsed.fields, parsed.hasLengthPrefix);
3557
+ }
3558
+ function spliceBindingTagsIntoPublicSignals(parameters) {
3559
+ const layout = layoutForKnownNonce(parameters.zkConfigNonce);
3560
+ const parsed = parsePublicSignalsBytes(parameters.publicSignalsBytes, parameters.zkConfigNonce);
3561
+ const auditStart = indexAuditTags(layout);
3562
+ for (let slot = 0; slot < layout.nAuditSlots; slot += 1) {
3563
+ const tag = parameters.auditTags[slot];
3564
+ if (tag !== undefined) {
3565
+ parsed.fields[auditStart + slot] = fieldElementBuffer(tag);
3566
+ }
3567
+ }
3568
+ const outputStart = indexOutputNoteTags(layout);
3569
+ for (let note = 0; note < layout.nOuts; note += 1) {
3570
+ const tag = parameters.outputNoteTags[note];
3571
+ if (tag !== undefined) {
3572
+ parsed.fields[outputStart + note] = fieldElementBuffer(tag);
3573
+ }
3574
+ }
3575
+ return encodePublicSignalsBytes(parsed.fields, parsed.hasLengthPrefix);
3576
+ }
3577
+ function fieldElementBuffer(value) {
3578
+ const trimmed = value.trim().replace(/^0x/i, '');
3579
+ const asBigint = /^[0-9]+$/.test(trimmed)
3580
+ ? BigInt(trimmed)
3581
+ : BigInt(`0x${trimmed}`);
3582
+ const buf = Buffer.alloc(FIELD_BYTES);
3583
+ let remaining = asBigint;
3584
+ for (let index = FIELD_BYTES - 1; index >= 0; index -= 1) {
3585
+ buf[index] = Number(remaining & 0xffn);
3586
+ remaining >>= 8n;
3587
+ }
3588
+ return buf;
3589
+ }
2944
3590
  async function inspectKytPassage(backendUrl, request) {
3591
+ const nonce = BigInt(request.zkConfigNonce ?? 0);
3592
+ const body = {
3593
+ ...request,
3594
+ publicSignalsBytes: zeroBindingTagsInPublicSignals(request.publicSignalsBytes, nonce).toString('hex'),
3595
+ };
2945
3596
  const response = await fetch(`${backendUrl.replace(/\/$/, '')}/api/kyt/passages/inspect`, {
2946
3597
  method: 'POST',
2947
3598
  headers: { 'content-type': 'application/json' },
2948
- body: JSON.stringify(request),
3599
+ body: JSON.stringify(body),
2949
3600
  });
2950
3601
  if (!response.ok) {
2951
3602
  throw new Error(`KYT inspect failed: ${response.status} ${await response.text()}`);
2952
3603
  }
2953
- const body = await response.json();
2954
- if (body.status !== 'approved') {
2955
- throw new Error(`KYT inspect rejected: ${JSON.stringify(body)}`);
3604
+ const payload = await response.json();
3605
+ if (payload.status !== 'approved') {
3606
+ throw new Error(`KYT inspect rejected: ${JSON.stringify(payload)}`);
2956
3607
  }
2957
- return body;
3608
+ return payload;
2958
3609
  }
2959
3610
  function signatureBase64ToBytes(signature) {
2960
3611
  const value = signature.trim();
@@ -2968,21 +3619,31 @@ var kytPassage = /*#__PURE__*/Object.freeze({
2968
3619
  __proto__: null,
2969
3620
  KYT_PASSAGE_AUTH_DOMAIN: KYT_PASSAGE_AUTH_DOMAIN,
2970
3621
  NOTE_AUDIT_LEN: NOTE_AUDIT_LEN,
3622
+ NOTE_AUDIT_PLAINTEXT_LEN: NOTE_AUDIT_PLAINTEXT_LEN,
2971
3623
  PASSAGE_ID_DOMAIN: PASSAGE_ID_DOMAIN,
3624
+ PASSAGE_ID_DOMAIN_V1: PASSAGE_ID_DOMAIN_V1,
3625
+ PASSAGE_ID_DOMAIN_V2: PASSAGE_ID_DOMAIN_V2,
2972
3626
  PUBLIC_LEG_CONTEXT_DOMAIN: PUBLIC_LEG_CONTEXT_DOMAIN,
2973
3627
  TOTAL_PUBLIC_SIGNALS: TOTAL_PUBLIC_SIGNALS,
2974
3628
  addressToCanonicalXdr: addressToCanonicalXdr,
2975
3629
  buildKytPassageAuthorization: buildKytPassageAuthorization,
2976
3630
  derivePassageFromTransactContext: derivePassageFromTransactContext,
2977
3631
  derivePassageId: derivePassageId,
2978
- hashOnboarding: hashOnboarding,
3632
+ derivePassageIdV1: derivePassageIdV1,
3633
+ derivePassageIdV2: derivePassageIdV2,
3634
+ hashCiphertextBytes: hashCiphertextBytes,
2979
3635
  hashPublicLegContext: hashPublicLegContext,
2980
3636
  hashPublicSignalBytes: hashPublicSignalBytes,
2981
3637
  inspectKytPassage: inspectKytPassage,
2982
3638
  signatureBase64ToBytes: signatureBase64ToBytes,
2983
- zeroAddressSentinelBuffer: zeroAddressSentinelBuffer
3639
+ spliceBindingTagsIntoPublicSignals: spliceBindingTagsIntoPublicSignals,
3640
+ zeroAddressSentinelBuffer: zeroAddressSentinelBuffer,
3641
+ zeroBindingTagsInPublicSignals: zeroBindingTagsInPublicSignals
2984
3642
  });
2985
3643
 
3644
+ function addressToScVal(address) {
3645
+ return nativeToScVal(address, { type: 'address' });
3646
+ }
2986
3647
  function passageIdHexToScVal(passageIdHex) {
2987
3648
  const bytes = Buffer.from(passageIdHex.replace(/^0x/, ''), 'hex');
2988
3649
  return xdr.ScVal.scvBytes(bytes);
@@ -3019,7 +3680,7 @@ function poolTransactOperation(params) {
3019
3680
  nativeToScVal(params.nonce, { type: 'u64' }),
3020
3681
  xdr.ScVal.scvBytes(params.proofBytes),
3021
3682
  xdr.ScVal.scvBytes(params.publicSignalsBytes),
3022
- onboardingOptionToScVal(params.onboarding),
3683
+ xdr.ScVal.scvBytes(params.ciphertextBytes ?? Buffer.alloc(0)),
3023
3684
  kytAuthorizationOptionToScVal(params.kytAuthorization),
3024
3685
  ],
3025
3686
  });
@@ -3033,7 +3694,6 @@ function helperSubmitWithPassageOperation(helperContractId, params, passageId, e
3033
3694
  nativeToScVal(params.nonce, { type: 'u64' }),
3034
3695
  xdr.ScVal.scvBytes(params.proofBytes),
3035
3696
  xdr.ScVal.scvBytes(params.publicSignalsBytes),
3036
- onboardingOptionToScVal(params.onboarding),
3037
3697
  passageIdHexToScVal(passageId),
3038
3698
  xdr.ScVal.scvU32(expiresAtLedger),
3039
3699
  signatureToScVal(signatureBase64ToBytes(signature)),
@@ -3165,8 +3825,8 @@ async function registerKytPassage(params) {
3165
3825
  owner: params.owner,
3166
3826
  nonce: params.nonce,
3167
3827
  publicSignalsBytes: params.publicSignalsBytes,
3828
+ ciphertextBytes: params.ciphertextBytes,
3168
3829
  publicLegContext: params.publicLegContext,
3169
- onboarding: params.onboarding,
3170
3830
  });
3171
3831
  await submitTransaction({
3172
3832
  source: params.source,
@@ -3215,8 +3875,8 @@ async function submitWithKytPassage(params) {
3215
3875
  owner: params.owner,
3216
3876
  nonce: params.nonce,
3217
3877
  publicSignalsBytes: params.publicSignalsBytes,
3878
+ ciphertextBytes: params.ciphertextBytes,
3218
3879
  publicLegContext: params.publicLegContext,
3219
- onboarding: params.onboarding,
3220
3880
  });
3221
3881
  if (approved.passageId.replace(/^0x/, '') !== derivation.passageId) {
3222
3882
  throw new Error('KYT approval passageId does not match local transaction context');
@@ -3231,7 +3891,6 @@ async function submitWithKytPassage(params) {
3231
3891
  nonce: params.nonce,
3232
3892
  proofBytes: params.proofBytes,
3233
3893
  publicSignalsBytes: params.publicSignalsBytes,
3234
- onboarding: params.onboarding,
3235
3894
  kytSubmitHelperId: params.kytSubmitHelperId,
3236
3895
  passageId: derivation.passageId,
3237
3896
  expiresAtLedger: approved.expiresAtLedger,
@@ -3255,246 +3914,43 @@ function localSignKytPassageAuthorization(secretSeed, authorization) {
3255
3914
  return sig.toString('base64');
3256
3915
  }
3257
3916
 
3258
- const NOTE_OUTPUT_LEN = 6;
3259
- const OUTPUT_NOTE_TAG_DOMAIN = 3n;
3260
- const STREAM_DOMAIN = 1n;
3261
- const FR_SIZE = 32;
3262
- const FR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
3263
- const ONE = 1n;
3264
- const ZERO = 0n;
3265
- const BABYJUB_SCALAR_BITS = 253n;
3266
- const SCALAR_253_MASK = (ONE << BABYJUB_SCALAR_BITS) - ONE;
3267
- let primitivesPromise;
3268
- function primitives() {
3269
- primitivesPromise ?? (primitivesPromise = Promise.all([
3270
- buildBabyjub(),
3271
- buildMimc7(),
3272
- buildPoseidon(),
3273
- ]).then(([babyJub, mimc7, poseidon]) => ({ babyJub, mimc7, poseidon })));
3274
- return primitivesPromise;
3275
- }
3276
- function normalizeHex(value) {
3277
- return value.trim().replace(/^0x/i, '').padStart(FR_SIZE * 2, '0');
3278
- }
3279
- function bigintFromHex(value) {
3280
- return BigInt(`0x${normalizeHex(value)}`);
3281
- }
3282
- function bigintFromDecimal(value) {
3283
- const parsed = BigInt(value);
3284
- return parsed % FR_MODULUS;
3285
- }
3286
- function bigintToFrHex(value) {
3287
- let v = moduleFr(value);
3288
- const buf = Buffer.alloc(FR_SIZE);
3289
- for (let index = FR_SIZE - 1; index >= 0; index -= 1) {
3290
- buf[index] = Number(v & 0xffn);
3291
- v >>= 8n;
3292
- }
3293
- return buf.toString('hex');
3294
- }
3295
- function frDecimalToHex(value) {
3296
- return bigintToFrHex(bigintFromDecimal(value));
3297
- }
3298
- function frDecimalFromBigint(value) {
3299
- const reduced = value >= ZERO ? value % FR_MODULUS : (value % FR_MODULUS) + FR_MODULUS;
3300
- return reduced.toString(10);
3301
- }
3302
- function fieldBigint(field, value) {
3303
- return BigInt(field.toString(value));
3304
- }
3305
- function moduleFr(value) {
3306
- const reduced = value % FR_MODULUS;
3307
- return reduced >= ZERO ? reduced : reduced + FR_MODULUS;
3308
- }
3309
- function mimcHash(parameters) {
3310
- return fieldBigint(parameters.mimc7.F, parameters.mimc7.hash(parameters.mimc7.F.e(parameters.message), parameters.mimc7.F.e(parameters.key)));
3311
- }
3312
- function poseidonHash(poseidon, inputs) {
3313
- return fieldBigint(poseidon.F, poseidon(inputs.map((input) => poseidon.F.e(input))));
3314
- }
3315
- function sharedKeyFromDepositor(parameters) {
3316
- const scalarHex = frDecimalToHex(parameters.ephemeralKeyScalarDecimal);
3317
- const shared = parameters.ecdhShared(scalarHex, frDecimalToHex(parameters.recipientPublicKey[0]), frDecimalToHex(parameters.recipientPublicKey[1]));
3318
- return [bigintFromHex(shared.x), bigintFromHex(shared.y)];
3319
- }
3320
- function sharedKeyFromRecipient(parameters) {
3321
- const { babyJub, recipientScalarHex, ephemeralPublicKey } = parameters;
3322
- const point = [
3323
- babyJub.F.e(bigintFromHex(ephemeralPublicKey[0])),
3324
- babyJub.F.e(bigintFromHex(ephemeralPublicKey[1])),
3325
- ];
3326
- if (!babyJub.inCurve(point)) {
3327
- throw new Error('Created ephemeral public key is not on BabyJubJub curve');
3328
- }
3329
- const product = babyJub.mulPointEscalar(point, bigintFromHex(recipientScalarHex) & SCALAR_253_MASK);
3330
- return [
3331
- fieldBigint(babyJub.F, product[0]),
3332
- fieldBigint(babyJub.F, product[1]),
3333
- ];
3917
+ const SCALAR_HEX_LENGTH = 64;
3918
+ const OUT_OF_RANGE_MESSAGE = 'derived escrow hash is outside BabyJub subgroup; retry nonce';
3919
+ function hexFromCanonicalScalar(value) {
3920
+ return value.toString(16).padStart(SCALAR_HEX_LENGTH, '0');
3334
3921
  }
3335
- function outputNoteTag(parameters) {
3336
- let accumulator = poseidonHash(parameters.poseidon, [
3337
- parameters.nonce,
3338
- OUTPUT_NOTE_TAG_DOMAIN,
3339
- BigInt(parameters.plaintext.length),
3340
- ]);
3341
- for (const field of parameters.plaintext) {
3342
- accumulator = poseidonHash(parameters.poseidon, [accumulator, field]);
3922
+ function assertCanonicalBabyJubScalar(scalar) {
3923
+ if (!(scalar > 0n && scalar < BABYJUB_SUBGROUP_ORDER)) {
3924
+ throw new Error(OUT_OF_RANGE_MESSAGE);
3343
3925
  }
3344
- return mimcHash({
3345
- mimc7: parameters.mimc7,
3346
- message: accumulator,
3347
- key: parameters.key,
3348
- });
3349
- }
3350
- function encryptFields(parameters) {
3351
- return parameters.plaintext.map((field, index) => {
3352
- const seed = poseidonHash(parameters.poseidon, [
3353
- parameters.nonce,
3354
- STREAM_DOMAIN,
3355
- BigInt(index),
3356
- ]);
3357
- const stream = mimcHash({
3358
- mimc7: parameters.mimc7,
3359
- message: seed,
3360
- key: parameters.key,
3361
- });
3362
- return moduleFr(field + stream);
3363
- });
3364
- }
3365
- function decryptFields(parameters) {
3366
- return parameters.ciphertext.map((field, index) => {
3367
- const seed = poseidonHash(parameters.poseidon, [
3368
- parameters.nonce,
3369
- STREAM_DOMAIN,
3370
- BigInt(index),
3371
- ]);
3372
- const stream = mimcHash({
3373
- mimc7: parameters.mimc7,
3374
- message: seed,
3375
- key: parameters.key,
3376
- });
3377
- return moduleFr(bigintFromHex(field) - stream);
3378
- });
3379
- }
3380
- function plaintextBigints(plaintext) {
3381
- return [
3382
- bigintFromDecimal(plaintext.value),
3383
- bigintFromDecimal(plaintext.assetHi),
3384
- bigintFromDecimal(plaintext.assetLo),
3385
- bigintFromDecimal(plaintext.nullifier),
3386
- bigintFromDecimal(plaintext.secret),
3387
- bigintFromDecimal(plaintext.applicationId),
3388
- ];
3389
- }
3390
- function plaintextFromBigints(fields) {
3391
- return {
3392
- value: frDecimalFromBigint(fields[0] ?? ZERO),
3393
- assetHi: frDecimalFromBigint(fields[1] ?? ZERO),
3394
- assetLo: frDecimalFromBigint(fields[2] ?? ZERO),
3395
- nullifier: frDecimalFromBigint(fields[3] ?? ZERO),
3396
- secret: frDecimalFromBigint(fields[4] ?? ZERO),
3397
- applicationId: frDecimalFromBigint(fields[5] ?? ZERO),
3398
- };
3399
- }
3400
- async function secretFromDepositEphemeralScalarDecimal(ephemeralKeyScalarDecimal) {
3401
- const { poseidon } = await primitives();
3402
- const secret = poseidonHash(poseidon, [bigintFromDecimal(ephemeralKeyScalarDecimal)]);
3403
- return frDecimalFromBigint(secret);
3404
3926
  }
3405
- async function encryptOutputNoteForDeposit(parameters) {
3406
- const { babyJub, mimc7, poseidon } = await primitives();
3407
- const secret = await secretFromDepositEphemeralScalarDecimal(parameters.deposit.ephemeralKeyScalar);
3408
- const plaintext = {
3409
- value: parameters.deposit.value,
3410
- assetHi: parameters.deposit.asset[0],
3411
- assetLo: parameters.deposit.asset[1],
3412
- nullifier: parameters.deposit.nullifier,
3413
- secret,
3414
- applicationId: parameters.deposit.applicationId,
3415
- };
3416
- const [key, nonce] = sharedKeyFromDepositor({
3417
- babyJub,
3418
- ecdhShared: parameters.ecdhShared,
3419
- ephemeralKeyScalarDecimal: parameters.deposit.ephemeralKeyScalar,
3420
- recipientPublicKey: parameters.deposit.recipientPublicKeys,
3421
- });
3422
- const message = plaintextBigints(plaintext);
3423
- const ciphertext = encryptFields({ mimc7, poseidon, key, nonce, plaintext: message });
3424
- const tag = outputNoteTag({ mimc7, poseidon, key, nonce, plaintext: message });
3927
+ async function derivedEscrowKey(nonce, recipientHi, recipientLo) {
3928
+ const wasm = await loadWasm();
3929
+ const out = wasm.derivedEscrowKey(nonce.toString(), recipientHi.toString(), recipientLo.toString());
3930
+ const scalar = BigInt(`0x${out.scalarHex}`);
3931
+ assertCanonicalBabyJubScalar(scalar);
3425
3932
  return {
3426
- ciphertext: ciphertext.map((field) => frDecimalFromBigint(field)),
3427
- tag: frDecimalFromBigint(tag),
3933
+ scalarHex: hexFromCanonicalScalar(scalar),
3934
+ pointXHex: out.pointXHex,
3935
+ pointYHex: out.pointYHex,
3428
3936
  };
3429
3937
  }
3430
- function commitmentFromPlaintextAndSharedSecret(parameters) {
3431
- const sharedX = bigintFromHex(parameters.sharedSecret.x);
3432
- const sharedY = bigintFromHex(parameters.sharedSecret.y);
3433
- const precommitment = poseidonHash(parameters.poseidon, [
3434
- bigintFromDecimal(parameters.plaintext.secret),
3435
- sharedX,
3436
- sharedY,
3437
- ]);
3438
- const balance = poseidonHash(parameters.poseidon, [
3439
- bigintFromDecimal(parameters.plaintext.value),
3440
- bigintFromDecimal(parameters.plaintext.assetHi),
3441
- bigintFromDecimal(parameters.plaintext.assetLo),
3442
- ]);
3443
- const appCommitment = poseidonHash(parameters.poseidon, [
3444
- bigintFromDecimal(parameters.plaintext.applicationId),
3445
- balance,
3446
- precommitment,
3447
- ]);
3448
- const commitment = poseidonHash(parameters.poseidon, [
3449
- bigintFromDecimal(parameters.plaintext.nullifier),
3450
- appCommitment,
3451
- ]);
3452
- return normalizeHex(commitment.toString(16));
3453
- }
3454
- async function decryptOutputNoteEvent(input) {
3455
- const { babyJub, mimc7, poseidon } = await primitives();
3456
- const [key, nonce] = sharedKeyFromRecipient({
3457
- babyJub,
3458
- recipientScalarHex: input.recipientScalarHex,
3459
- ephemeralPublicKey: input.createdEphemeralKey,
3460
- });
3461
- const decrypted = decryptFields({
3462
- mimc7,
3463
- poseidon,
3464
- key,
3465
- nonce,
3466
- ciphertext: input.ciphertext,
3467
- });
3468
- const expectedTag = outputNoteTag({
3469
- mimc7,
3470
- poseidon,
3471
- key,
3472
- nonce,
3473
- plaintext: decrypted,
3474
- });
3475
- if (expectedTag !== bigintFromHex(input.tag)) {
3476
- throw new Error('Output note tag verification failed');
3477
- }
3478
- const plaintext = plaintextFromBigints(decrypted);
3479
- const sharedSecret = {
3480
- x: bigintToFrHex(key),
3481
- y: bigintToFrHex(nonce),
3482
- };
3483
- const commitmentHex = commitmentFromPlaintextAndSharedSecret({
3484
- poseidon,
3485
- plaintext,
3486
- sharedSecret,
3487
- });
3488
- const commitmentMatches = normalizeHex(input.commitmentHashHex) === commitmentHex;
3489
- if (!commitmentMatches) {
3490
- throw new Error('Output note commitment checksum mismatch');
3938
+ async function sampleDerivedEscrowKey(recipientHi, recipientLo, startNonce = 1n, window = 1024n) {
3939
+ for (let offset = 0n; offset < window; offset += 1n) {
3940
+ const nonce = startNonce + offset;
3941
+ try {
3942
+ const key = await derivedEscrowKey(nonce, recipientHi, recipientLo);
3943
+ return { nonce, key };
3944
+ }
3945
+ catch (error) {
3946
+ const message = error instanceof Error ? error.message : String(error);
3947
+ if (!message.includes('outside BabyJub subgroup')) {
3948
+ throw error;
3949
+ }
3950
+ }
3491
3951
  }
3492
- return {
3493
- ...plaintext,
3494
- commitmentHex,
3495
- commitmentMatches,
3496
- };
3952
+ throw new Error(OUT_OF_RANGE_MESSAGE);
3497
3953
  }
3498
3954
 
3499
- export { BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE, BN254_SCALAR_MOD, COIN_VALUE_STROOPS, DECODED_EPHEMERAL_HRP, DEFAULT_APPLICATION_ID, DEFAULT_STEALTH_SIGN_NONCE, DEMO_AUDIT_PUBLIC_KEY, DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, NOTE_AUDIT_LEN as KYT_NOTE_AUDIT_LEN, NOTE_AUDIT_LEN$1 as NOTE_AUDIT_LEN, NOTE_OUTPUT_LEN$1 as NOTE_OUTPUT_LEN, NOTE_OUTPUT_LEN as OUTPUT_NOTE_LEN, POOL_MERKLE_TREE_DEPTH, PrivacyPoolSDK, STEALTH_ADDRESS_HRP, TOTAL_PUBLIC_SIGNALS, TRANSACTION_N_AUDIT_SLOTS, TRANSACTION_N_INS, TRANSACTION_N_OUTS, TRANSACTION_TREE_DEPTH, addressToCanonicalXdr, buildKytPassageAuthorization, buildStealthAddressSignMessage, buildTransactionWitnessInput, buildUniformAuditParams, coordHexToDecimal, decodeDecodedEphemeralKey, decodeDepositorSharedSecretPreimage, decodeStealthAddress, decryptOutputNoteEvent, derivePassageFromTransactContext, derivePassageId, ed25519PubkeyPayloadHexToWithdrawFrDecimals, encodeDecodedEphemeralKey, encodeDepositorSharedSecretPreimage, encodeStealthAddress, encryptOutputNoteForDeposit, generateRandomScalarHex32, hashOnboarding, hashOnboardingCanonical, hashPublicLegContext, hashPublicSignalBytes, inspectKytPassage, localSignKytPassageAuthorization, onboardingOptionToScVal, onboardingToScVal, planKytSubmit, privKeyScalarDecimalFromStellarSignature, randomFrDecimal, randomFrDecimal253, recipientPublicKeysDecimalFromStealthAddress, registerKytPassage, resolveDepositsForWitness, resolveSlotApplicationIds, resolveTransactionAuditParams, scalarHexToFrDecimal, sharedSecretFromDepositorPreimage, sharedSecretFromRecipientPreimage, stellarContractAddressToAssetFrDecimals, submitApprovedKytPassage, submitPoolTransact, submitWithKytPassage, withApplicationIdOnDeposit, withApplicationIdOnWithdraw, withdrawObjectFromMerkleWitness, zeroAddressSentinelBuffer };
3955
+ export { BABYJUB_SUBGROUP_ORDER, BINDING_ZK_NONCE, BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE, BN254_SCALAR_MOD, BundledZkCircuit, COIN_VALUE_STROOPS, COMMITMENT_V2_ZK_NONCE, DECODED_EPHEMERAL_HRP, DEFAULT_APPLICATION_ID, DEFAULT_STEALTH_SIGN_NONCE, DEFAULT_ZK_CIRCUITS, DEFAULT_ZK_CONFIG_NONCE, DEMO_AUDIT_PUBLIC_KEY, DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, DOM_ENC, DOM_ESCROW, DOM_NULLIFIER, DOM_PADDING, NOTE_AUDIT_LEN as KYT_NOTE_AUDIT_LEN, LEGACY_ZK_NONCE, NOTE_AUDIT_LEN$2 as NOTE_AUDIT_LEN, NOTE_AUDIT_PLAINTEXT_LEN, NOTE_OUTPUT_LEN$1 as NOTE_OUTPUT_LEN, NOTE_OUTPUT_LEN as OUTPUT_NOTE_LEN, OWNER_BOUND_NOTE_SCHEMA_VERSION, POOL_MERKLE_TREE_DEPTH, PrivacyPoolSDK, SIX_BY_SIX_BINDING_ZK_NONCE, SIX_BY_SIX_ZK_NONCE, SPEND_SCALAR_DOMAIN_TAG, STEALTH_ADDRESS_HRP, TOTAL_PUBLIC_SIGNALS, TRANSACTION_N_AUDIT_SLOTS, TRANSACTION_N_INS, TRANSACTION_N_OUTS, TRANSACTION_TREE_DEPTH, ZK_ARTIFACT_GITHUB_REPO, ZK_SDK_PACKAGE_VERSION, addressToCanonicalXdr, auditEphemeralPublicKeyCount, buildKytPassageAuthorization, buildStealthAddressSignMessage, buildTransactionWitnessInput, buildUniformAuditParams, bundledCircuitFileNames, canonicalBabyJubScalarFromInteger, ciphertextFieldCount, ciphertextsInPublicSignals, circuitProfileFromName, circuitProfileFromNonce, coordHexToDecimal, decodeCiphertextBlob, decodeDecodedEphemeralKey, decodeDepositorSharedSecretPreimage, decodeStealthAddress, decryptOutputNoteEvent, defaultZkArtifactBaseUrl, derivePassageFromTransactContext, derivePassageId, derivePassageIdV1, derivePassageIdV2, derivedEscrowKey, ed25519PubkeyPayloadHexToWithdrawFrDecimals, encodeCiphertextBlob, encodeDecodedEphemeralKey, encodeDepositorSharedSecretPreimage, encodeStealthAddress, encodeTransactionCiphertextBlob, encryptNoteAuditSlot, encryptOutputNoteForDeposit, encryptTransactionCiphertextBlob, generateRandomScalarHex32, generateWitness, hashCiphertextBytes, hashPublicLegContext, hashPublicSignalBytes, indexAuditTags, indexOutputNoteTags, inspectKytPassage, layoutForKnownNonce, layoutFromCircuitConfig, localSignKytPassageAuthorization, padDepositSlots, padPublicLegs, padWithdrawSlots, planKytSubmit, poseidonMacTag, privKeyScalarDecimalFromStellarSignature, randomFrDecimal, randomFrDecimal253, recipientPublicKeysDecimalFromStealthAddress, registerKytPassage, resolveAuditPublicKeyFromEnv, resolveDepositsForWitness, resolveSlotApplicationIds, resolveSpendScalarSchemaVersion, resolveTransactionAuditParams, resolveZkCircuitConfig, sampleDerivedEscrowKey, scalarHexToFrDecimal, secretFromDepositEphemeralScalarDecimal, sharedSecretFromDepositorPreimage, sharedSecretFromRecipientPreimage, sixBySixBindingLayout, sixBySixLayout, spendScalarHexFromStellarSignature, spliceBindingTagsIntoPublicSignals, splitCiphertextBlob, standardBindingLayout, standardLayout, stateRootIndex, stellarContractAddressToAssetFrDecimals, submitApprovedKytPassage, submitPoolTransact, submitWithKytPassage, totalPublicSignals, withApplicationIdOnDeposit, withApplicationIdOnWithdraw, withdrawObjectFromMerkleWitness, zeroAddressSentinelBuffer, zeroBindingTagsInPublicSignals };
3500
3956
  //# sourceMappingURL=index.mjs.map