@auditable/privacy-pool-zk-sdk 0.7.0 → 0.8.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/cli.js CHANGED
@@ -227,7 +227,7 @@ function bytesToHex$1(bytes) {
227
227
  }
228
228
  return s;
229
229
  }
230
- function concatBytes(a, b) {
230
+ function concatBytes$1(a, b) {
231
231
  const out = new Uint8Array(a.length + b.length);
232
232
  out.set(a, 0);
233
233
  out.set(b, a.length);
@@ -243,7 +243,7 @@ function encodeStealthAddress(decoded) {
243
243
  if (xb.length !== yb.length) {
244
244
  throw new Error('stealth-address: x and y must encode the same number of bytes for a reversible address');
245
245
  }
246
- const payload = concatBytes(xb, yb);
246
+ const payload = concatBytes$1(xb, yb);
247
247
  const words = bech32.toWords(payload);
248
248
  return bech32.encode(STEALTH_ADDRESS_HRP, words, BECH32_STEALTH_LIMIT);
249
249
  }
@@ -268,375 +268,54 @@ function decodeStealthAddress(address) {
268
268
 
269
269
  /** Default nonce when deriving the wallet sign-in message. */
270
270
  const DEFAULT_STEALTH_SIGN_NONCE = 'main address';
271
- /**
272
- * Plaintext for Stellar wallet message signing (stealth address derivation).
273
- * Sign the exact bytes of this string (UTF-8) with the stellar account key.
274
- */
275
- function buildStealthAddressSignMessage(address, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
276
- 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.`;
277
- }
278
-
279
- /** Bech32 HRP for {@link DecodedEphemeralKey} only (`x ‖ y`, 64 bytes). */
280
- const DECODED_EPHEMERAL_HRP = 'epk1';
281
- /** Bech32 HRP for {@link DecodedDepositorSharedSecretPreimage} (96 bytes). */
282
- const DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP = 'epk_dep_pre1';
283
- const FIELD_BYTES = 32;
284
- const EPK_POINT_PAYLOAD = FIELD_BYTES * 2;
285
- const EPK_DEP_PRE_PAYLOAD = FIELD_BYTES * 3;
286
- const BECH32_LONG_LIMIT = 1023;
287
- function normalizeHex$1(hex) {
288
- const s = hex.trim().replace(/^0x/i, '');
289
- if (!/^[0-9a-fA-F]*$/.test(s)) {
290
- throw new Error('ephemeral-key: hex must contain only 0-9, a-f');
291
- }
292
- return s.length % 2 === 0 ? s : `0${s}`;
293
- }
294
- function hexToBytes$1(hex) {
295
- const norm = normalizeHex$1(hex);
296
- const out = new Uint8Array(norm.length / 2);
297
- for (let i = 0; i < out.length; i++) {
298
- out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
299
- }
300
- return out;
301
- }
302
- function bytesToHex(bytes) {
303
- let s = '';
304
- for (let i = 0; i < bytes.length; i++) {
305
- s += bytes[i].toString(16).padStart(2, '0');
306
- }
307
- return s;
308
- }
309
- function concat2(a, b) {
310
- const out = new Uint8Array(a.length + b.length);
311
- out.set(a, 0);
312
- out.set(b, a.length);
313
- return out;
314
- }
315
- function concat3(a, b, c) {
316
- const out = new Uint8Array(a.length + b.length + c.length);
317
- out.set(a, 0);
318
- out.set(b, a.length);
319
- out.set(c, a.length + b.length);
320
- return out;
321
- }
322
- function require32(label, bytes) {
323
- if (bytes.length !== FIELD_BYTES) {
324
- throw new Error(`ephemeral-key: ${label} must encode exactly ${FIELD_BYTES} bytes, got ${bytes.length}`);
325
- }
326
- }
327
- /**
328
- * 32-byte big-endian integer as hex (64 chars), **< 2^253**, so BabyJub ECDH matches
329
- * `circuits/encryption.circom` `Num2Bits(253)` and `libs/cryptography` `scalar_mul_253`.
330
- */
331
- function generateRandomScalarHex32() {
332
- const g = globalThis.crypto;
333
- if (!g?.getRandomValues) {
334
- throw new Error('ephemeral-key: crypto.getRandomValues is required');
335
- }
336
- const max = 1n << 253n;
337
- for (let attempt = 0; attempt < 65536; attempt++) {
338
- const b = new Uint8Array(32);
339
- g.getRandomValues(b);
340
- const hex = bytesToHex(b);
341
- if (BigInt(`0x${hex}`) < max) {
342
- return hex;
343
- }
344
- }
345
- throw new Error('ephemeral-key: failed to sample scalar < 2^253');
346
- }
347
- /** Bech32 `epk1`: encodes `x ‖ y` (64 bytes). */
348
- function encodeDecodedEphemeralKey(decoded) {
349
- const xb = hexToBytes$1(decoded.x);
350
- const yb = hexToBytes$1(decoded.y);
351
- require32('x', xb);
352
- require32('y', yb);
353
- const payload = concat2(xb, yb);
354
- const words = bech32.toWords(payload);
355
- return bech32.encode(DECODED_EPHEMERAL_HRP, words, BECH32_LONG_LIMIT);
356
- }
357
- function decodeDecodedEphemeralKey(encoded) {
358
- const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
359
- if (prefix !== DECODED_EPHEMERAL_HRP) {
360
- throw new Error(`ephemeral-key: expected HRP ${DECODED_EPHEMERAL_HRP}, got ${JSON.stringify(prefix)}`);
361
- }
362
- const bytes = new Uint8Array(bech32.fromWords(words));
363
- if (bytes.length !== EPK_POINT_PAYLOAD) {
364
- throw new Error(`ephemeral-key: epk1 payload must be ${EPK_POINT_PAYLOAD} bytes, got ${bytes.length}`);
365
- }
366
- return {
367
- x: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
368
- y: bytesToHex(bytes.subarray(FIELD_BYTES)),
369
- };
370
- }
371
- /** Bech32 `epk_dep_pre1`: `randomNonceScalar ‖ recipient.x ‖ recipient.y`. */
372
- function encodeDepositorSharedSecretPreimage(decoded) {
373
- const sb = hexToBytes$1(decoded.randomNonceScalar);
374
- const xb = hexToBytes$1(decoded.recipientStealthAddress.x);
375
- const yb = hexToBytes$1(decoded.recipientStealthAddress.y);
376
- require32('randomNonceScalar', sb);
377
- require32('recipientStealthAddress.x', xb);
378
- require32('recipientStealthAddress.y', yb);
379
- const payload = concat3(sb, xb, yb);
380
- const words = bech32.toWords(payload);
381
- return bech32.encode(DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, words, BECH32_LONG_LIMIT);
382
- }
383
- function decodeDepositorSharedSecretPreimage(encoded) {
384
- const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
385
- if (prefix !== DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP) {
386
- throw new Error(`ephemeral-key: expected HRP ${DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP}, got ${JSON.stringify(prefix)}`);
387
- }
388
- const bytes = new Uint8Array(bech32.fromWords(words));
389
- if (bytes.length !== EPK_DEP_PRE_PAYLOAD) {
390
- throw new Error(`ephemeral-key: epk_dep_pre1 payload must be ${EPK_DEP_PRE_PAYLOAD} bytes, got ${bytes.length}`);
391
- }
392
- return {
393
- randomNonceScalar: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
394
- recipientStealthAddress: {
395
- x: bytesToHex(bytes.subarray(FIELD_BYTES, FIELD_BYTES * 2)),
396
- y: bytesToHex(bytes.subarray(FIELD_BYTES * 2)),
397
- },
398
- };
399
- }
400
-
401
- const DEFAULT_APPLICATION_ID = '101';
402
- /** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
403
- const DEMO_AUDIT_PUBLIC_KEY = [
404
- '21605515851820432880964235241069234202284600780825340516808373216881770219365',
405
- '18856460861531942120859708048677603751294231190189224157283439874962410808705',
406
- ];
407
- function isActiveWithdraw(slot) {
408
- return slot.value !== '0';
409
- }
410
- function isActiveDeposit(slot) {
411
- return slot.value !== '0';
412
- }
413
- function resolveWithdrawSlot(slot) {
414
- if (slot === 'dummy') {
415
- return null;
416
- }
417
- return slot;
418
- }
419
- function resolveDepositSlot(slot) {
420
- if (slot === 'dummy') {
421
- return null;
422
- }
423
- return slot;
424
- }
425
- function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey) {
426
- return {
427
- applicationId,
428
- noteAuditPublicKeys: [
429
- auditPublicKey,
430
- auditPublicKey,
431
- auditPublicKey,
432
- auditPublicKey,
433
- ],
434
- auditEphemeralScalars: [
435
- randomFrDecimal253(),
436
- randomFrDecimal253(),
437
- randomFrDecimal253(),
438
- randomFrDecimal253(),
439
- ],
440
- };
441
- }
442
- function resolveSlotApplicationIds(audit, withdrawSlots, depositSlots) {
443
- const w0 = resolveWithdrawSlot(withdrawSlots[0]);
444
- const w1 = resolveWithdrawSlot(withdrawSlots[1]);
445
- const d0 = resolveDepositSlot(depositSlots[0]);
446
- const d1 = resolveDepositSlot(depositSlots[1]);
447
- return {
448
- inputApplicationIds: [
449
- w0 && isActiveWithdraw(w0) ? audit.applicationId : '0',
450
- w1 && isActiveWithdraw(w1) ? audit.applicationId : '0',
451
- ],
452
- outputApplicationIds: [
453
- d0 && isActiveDeposit(d0) ? audit.applicationId : '0',
454
- d1 && isActiveDeposit(d1) ? audit.applicationId : '0',
455
- ],
456
- };
457
- }
458
- function resolveTransactionAuditParams(applicationId, auditPublicKey) {
459
- return buildUniformAuditParams(applicationId, auditPublicKey ?? DEMO_AUDIT_PUBLIC_KEY);
271
+ /** Domain tag mixed into the spend-scalar digest (H6). */
272
+ const SPEND_SCALAR_DOMAIN_TAG = 'privacy-pool-spend-scalar-v1';
273
+ /** Schema version bound into the spend-scalar message and digest. */
274
+ const OWNER_BOUND_NOTE_SCHEMA_VERSION = 1;
275
+ function resolveSpendScalarSchemaVersion(domain) {
276
+ return domain.schemaVersion ?? OWNER_BOUND_NOTE_SCHEMA_VERSION;
460
277
  }
461
-
462
- /** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 9)` in `circuits/main.circom`. */
463
- const TRANSACTION_TREE_DEPTH = 20;
464
- /** BN254 scalar field modulus (ark `Fr`, circom signals). */
465
- const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
466
278
  /**
467
- * BabyJub ECDH in `circuits/encryption.circom` uses `Num2Bits(253)`; scalars must be < 2^253
468
- * (matches `libs/cryptography` `scalar_mul_253`).
279
+ * Plaintext for Stellar wallet message signing (spend-key derivation).
280
+ * Sign the exact bytes of this string (UTF-8) with the stellar account key.
281
+ *
282
+ * The derived scalar is the owner-bound spend key for notes in this environment.
469
283
  */
470
- const BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE = 1n << 253n;
471
- function normalizeHex(hex) {
472
- const s = hex.trim().replace(/^0x/i, '');
473
- if (!/^[0-9a-fA-F]*$/.test(s)) {
474
- throw new Error('withdrawal-transaction-input: invalid hex');
475
- }
476
- return s.length % 2 === 0 ? s : `0${s}`;
477
- }
478
- /** 32-byte field coordinate (hex, no 0x) → decimal string mod BN254 scalar field. */
479
- function coordHexToDecimal(hex) {
480
- const h = normalizeHex(hex);
481
- if (h.length > 64) {
482
- throw new Error('withdrawal-transaction-input: coordinate hex too long');
483
- }
484
- const v = BigInt(`0x${h}`);
485
- return (v % BN254_SCALAR_MOD).toString(10);
486
- }
284
+ function buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
285
+ const schemaVersion = resolveSpendScalarSchemaVersion(domain);
286
+ return [
287
+ 'Arcane privacy layer: derive your private-note spend key.',
288
+ '',
289
+ `Wallet: ${address}`,
290
+ `Network: ${domain.networkPassphrase}`,
291
+ `Pool: ${domain.poolContract}`,
292
+ `Registry: ${domain.registryContract}`,
293
+ `Schema: owner-bound-note-v${schemaVersion}`,
294
+ `Nonce: ${nonce}`,
295
+ '',
296
+ 'This signature derives the key that authorizes spending your private notes in this environment.',
297
+ ].join('\n');
298
+ }
299
+
300
+ const BABYJUB_SUBGROUP_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
487
301
  /**
488
- * 32-byte big-endian scalar hex decimal for circom `ephemeralKeyScalar` / ECDH `priv`.
489
- * Integer must be < 2^253 (not reduced mod r — values ≥ 2^253 are rejected).
302
+ * Map any integer into `(0, l)` so it satisfies circom `LessThan(252)` vs
303
+ * `BABYJUB_SUBGROUP_ORDER` and `BabyPbk`. Values already in range are unchanged.
490
304
  */
491
- function scalarHexToFrDecimal(hex) {
492
- const h = normalizeHex(hex);
493
- if (h.length > 64) {
494
- throw new Error('withdrawal-transaction-input: scalar hex too long');
305
+ function canonicalBabyJubScalarFromInteger(value) {
306
+ if (value > 0n && value < BABYJUB_SUBGROUP_ORDER) {
307
+ return value;
495
308
  }
496
- const v = BigInt(`0x${h.padStart(64, '0').slice(-64)}`);
497
- if (v >= BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE) {
498
- throw new Error('depositor ephemeral scalar must be < 2^253 (BabyJub Num2Bits); resample with random-scalar');
499
- }
500
- return v.toString(10);
501
- }
502
- /**
503
- * Stellar G-address Ed25519 payload (32 bytes as 64 hex, optional 0x) → two circom public decimals
504
- * (`withdrawAddressHi` / `withdrawAddressLo`). No mod-r; each half fits in 128 bits.
505
- */
506
- function ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex) {
507
- const h = normalizeHex(hex).padStart(64, '0').slice(-64);
508
- const hi = BigInt(`0x${h.slice(0, 32)}`);
509
- const lo = BigInt(`0x${h.slice(32, 64)}`);
510
- return { hi: hi.toString(10), lo: lo.toString(10) };
511
- }
512
- /**
513
- * Stellar contract id (`C…`) → two circom field decimals for `asset[0]`, `asset[1]` (same 32-byte split as accounts).
514
- */
515
- function stellarContractAddressToAssetFrDecimals(address) {
516
- const raw = stellarSdk.StrKey.decodeContract(address);
517
- const hex = Buffer.from(raw).toString('hex');
518
- const { hi, lo } = ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex);
519
- return [hi, lo];
520
- }
521
- /** Uniform random `Fr` as decimal (32 random bytes, mod r). For Poseidon-only inputs (e.g. nullifiers). */
522
- function randomFrDecimal() {
523
- const hex = generateRandomScalarHex32();
524
- const v = BigInt(`0x${normalizeHex(hex)}`);
525
- return (v % BN254_SCALAR_MOD).toString(10);
526
- }
527
- /** Random scalar < 2^253 for BabyJub ECDH / `Num2Bits(253)` (uses {@link generateRandomScalarHex32}). */
528
- function randomFrDecimal253() {
529
- const hex = generateRandomScalarHex32();
530
- return BigInt(`0x${normalizeHex(hex)}`).toString(10);
531
- }
532
- function zerosTreeSiblings() {
533
- return Array(TRANSACTION_TREE_DEPTH).fill('0');
534
- }
535
- function dummyWithdraw(wasm) {
536
- const nullifier = randomFrDecimal();
537
- const secretHex = generateRandomScalarHex32();
538
- const secret = (BigInt(`0x${normalizeHex(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
539
- const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(secretHex);
540
- return {
541
- value: '0',
542
- nullifier,
543
- secret,
544
- asset: ['0', '0'],
545
- applicationId: '0',
546
- ephemeralKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
547
- stateSiblings: zerosTreeSiblings(),
548
- stateIndex: '0',
549
- };
550
- }
551
- function dummyDeposit(wasm) {
552
- const nullifier = randomFrDecimal();
553
- const ephemeralKeyScalar = randomFrDecimal253();
554
- const skHex = generateRandomScalarHex32();
555
- const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(skHex);
556
- return {
557
- value: '0',
558
- nullifier,
559
- ephemeralKeyScalar,
560
- asset: ['0', '0'],
561
- applicationId: '0',
562
- recipientPublicKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
563
- };
564
- }
565
- function resolveWithdraw(slot, wasm) {
566
- return slot === 'dummy' ? dummyWithdraw(wasm) : slot;
567
- }
568
- function resolveDeposit(slot, wasm) {
569
- return slot === 'dummy' ? dummyDeposit(wasm) : slot;
570
- }
571
- function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, depositSlots, audit, wasm) {
572
- const w0 = resolveWithdraw(withdrawSlots[0], wasm);
573
- const w1 = resolveWithdraw(withdrawSlots[1], wasm);
574
- const d0 = resolveDeposit(depositSlots[0], wasm);
575
- const d1 = resolveDeposit(depositSlots[1], wasm);
576
- const appIds = resolveSlotApplicationIds(audit, withdrawSlots, depositSlots);
577
- return {
578
- stateRoot: publicParams.stateRoot,
579
- withdrawAddressHi: publicParams.withdrawAddressHi,
580
- withdrawAddressLo: publicParams.withdrawAddressLo,
581
- privKeyScalar: publicParams.privKeyScalar,
582
- withdrawnValues: [w0.value, w1.value],
583
- withdrawnNullifiers: [w0.nullifier, w1.nullifier],
584
- withdrawnSecrets: [w0.secret, w1.secret],
585
- withdrawnAssets: [w0.asset, w1.asset],
586
- ephemeralKeys: [w0.ephemeralKeys, w1.ephemeralKeys],
587
- stateSiblings: [w0.stateSiblings, w1.stateSiblings],
588
- stateIndex: [w0.stateIndex, w1.stateIndex],
589
- depositedValues: [d0.value, d1.value],
590
- depositedNullifiers: [d0.nullifier, d1.nullifier],
591
- depositedAssets: [d0.asset, d1.asset],
592
- depositedEphemeralKeyScalars: [d0.ephemeralKeyScalar, d1.ephemeralKeyScalar],
593
- depositedRecipientPublicKeys: [d0.recipientPublicKeys, d1.recipientPublicKeys],
594
- inputApplicationIds: appIds.inputApplicationIds,
595
- outputApplicationIds: appIds.outputApplicationIds,
596
- auditEphemeralScalars: audit.auditEphemeralScalars,
597
- noteAuditPublicKeys: audit.noteAuditPublicKeys,
598
- publicWithdrawnAssets: publicLegs.publicWithdrawnAssets,
599
- publicDepositedAssets: publicLegs.publicDepositedAssets,
600
- publicDeposits: publicLegs.publicDeposits,
601
- publicWithdrawals: publicLegs.publicWithdrawals,
602
- };
603
- }
604
- /** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
605
- function recipientPublicKeysDecimalFromStealthAddress(stealthAddress) {
606
- const { x, y } = decodeStealthAddress(stealthAddress);
607
- return [coordHexToDecimal(x), coordHexToDecimal(y)];
608
- }
609
- /** First withdraw leg: Merkle witness + depositor ECDH point coordinates (hex). */
610
- function withdrawObjectFromMerkleWitness(witness, depositorEphemeralHex, applicationId) {
611
- return {
612
- value: witness.value,
613
- nullifier: witness.nullifier,
614
- secret: witness.secret,
615
- asset: witness.withdrawnAsset,
616
- applicationId,
617
- ephemeralKeys: [
618
- coordHexToDecimal(depositorEphemeralHex.x),
619
- coordHexToDecimal(depositorEphemeralHex.y),
620
- ],
621
- stateSiblings: witness.stateSiblings,
622
- stateIndex: witness.stateIndex,
623
- };
309
+ return (value % (BABYJUB_SUBGROUP_ORDER - 1n)) + 1n;
624
310
  }
625
311
 
626
- function hexToBytes(hex) {
312
+ function hexToBytes$1(hex) {
627
313
  const out = new Uint8Array(hex.length / 2);
628
314
  for (let i = 0; i < out.length; i++) {
629
315
  out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
630
316
  }
631
317
  return out;
632
318
  }
633
- function bytesToHexLower(bytes) {
634
- let s = '';
635
- for (let i = 0; i < bytes.length; i++) {
636
- s += bytes[i].toString(16).padStart(2, '0');
637
- }
638
- return s;
639
- }
640
319
  /** Base64 → bytes via `atob` (browsers; Node 16+). No Node `Buffer`. */
641
320
  function decodeBase64ToBytes(s) {
642
321
  const t = s.replace(/\s/g, '');
@@ -662,7 +341,7 @@ function parseStellarEd25519SignatureRaw(input) {
662
341
  const trimmed = input.trim();
663
342
  const no0x = trimmed.replace(/^0x/i, '');
664
343
  if (/^[0-9a-fA-F]+$/.test(no0x) && no0x.length === 128) {
665
- return hexToBytes(no0x.toLowerCase());
344
+ return hexToBytes$1(no0x.toLowerCase());
666
345
  }
667
346
  const fromB64 = decodeBase64ToBytes(trimmed);
668
347
  if (fromB64.length !== 64) {
@@ -680,29 +359,66 @@ async function sha256(data) {
680
359
  copy.set(data);
681
360
  return new Uint8Array(await subtle.digest('SHA-256', copy));
682
361
  }
362
+ function appendU32BE(parts, value) {
363
+ const buf = new Uint8Array(4);
364
+ new DataView(buf.buffer).setUint32(0, value >>> 0, false);
365
+ parts.push(buf);
366
+ }
367
+ function appendUtf8Prefixed(parts, text) {
368
+ const bytes = new TextEncoder().encode(text);
369
+ appendU32BE(parts, bytes.length);
370
+ parts.push(bytes);
371
+ }
372
+ function concatBytes(parts) {
373
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
374
+ const out = new Uint8Array(total);
375
+ let offset = 0;
376
+ for (const part of parts) {
377
+ out.set(part, offset);
378
+ offset += part.length;
379
+ }
380
+ return out;
381
+ }
683
382
  /**
684
- * Stellar Ed25519 signature (hex or base64) → SHA-256(signature bytes) → scalar → WASM ECDH (no UTF-8 seed hash).
383
+ * Domain-separated spend-scalar digest: SHA-256(
384
+ * SPEND_SCALAR_DOMAIN_TAG || network || pool || registry || schemaVersion || signature
385
+ * ).
685
386
  */
686
- async function stealthAddressFromStellarSignature(ecdhFromScalarHex, encodeStealth, signature) {
387
+ async function spendScalarDigestFromStellarSignature(signature, domain) {
687
388
  const raw = parseStellarEd25519SignatureRaw(signature);
688
- const scalar = await sha256(raw);
689
- const scalarHex = bytesToHexLower(scalar);
690
- const decoded = ecdhFromScalarHex(scalarHex);
389
+ const parts = [new TextEncoder().encode(SPEND_SCALAR_DOMAIN_TAG)];
390
+ appendUtf8Prefixed(parts, domain.networkPassphrase);
391
+ appendUtf8Prefixed(parts, domain.poolContract);
392
+ appendUtf8Prefixed(parts, domain.registryContract);
393
+ appendU32BE(parts, resolveSpendScalarSchemaVersion(domain));
394
+ parts.push(raw);
395
+ return sha256(concatBytes(parts));
396
+ }
397
+ function digestToCanonicalScalar(digest) {
398
+ let v = 0n;
399
+ for (let i = 0; i < digest.length; i++) {
400
+ v = (v << 8n) + BigInt(digest[i]);
401
+ }
402
+ return canonicalBabyJubScalarFromInteger(v);
403
+ }
404
+ function canonicalScalarHex(scalar) {
405
+ return scalar.toString(16).padStart(64, '0');
406
+ }
407
+ /**
408
+ * Stellar Ed25519 signature (hex or base64) → domain-separated SHA-256 → scalar → WASM ECDH.
409
+ */
410
+ async function stealthAddressFromStellarSignature(ecdhFromScalarHex, encodeStealth, signature, domain) {
411
+ const digest = await spendScalarDigestFromStellarSignature(signature, domain);
412
+ const decoded = ecdhFromScalarHex(canonicalScalarHex(digestToCanonicalScalar(digest)));
691
413
  return encodeStealth(decoded);
692
414
  }
693
415
  /**
694
- * SHA-256(signature) as 32-byte BE integer, truncated to **253 bits** (same effective scalar as
695
- * `libs/cryptography` `scalar_mul_253` / circom `Num2Bits(253)`), decimal for `privKeyScalar`.
416
+ * Domain-separated SHA-256(signature, network, pool, registry, schema) reduced into
417
+ * `(0, BabyJub subgroup order)` so `privKeyScalar` matches `BabyPbk` and circom `LessThan(l)`.
696
418
  */
697
- async function privKeyScalarDecimalFromStellarSignature(signature) {
698
- const raw = parseStellarEd25519SignatureRaw(signature);
699
- const h = await sha256(raw);
700
- let v = 0n;
701
- for (let i = 0; i < h.length; i++) {
702
- v = (v << 8n) + BigInt(h[i]);
703
- }
704
- const mask = BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE - 1n;
705
- return (v & mask).toString(10);
419
+ async function privKeyScalarDecimalFromStellarSignature(signature, domain) {
420
+ const digest = await spendScalarDigestFromStellarSignature(signature, domain);
421
+ return digestToCanonicalScalar(digest).toString(10);
706
422
  }
707
423
 
708
424
  /* @ts-self-types="./client_sdk_wasm.d.ts" */
@@ -722,7 +438,43 @@ function buildWithdrawMerkleWitness(coin_json, state_json) {
722
438
  const len0 = WASM_VECTOR_LEN;
723
439
  const ptr1 = passStringToWasm0(state_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
724
440
  const len1 = WASM_VECTOR_LEN;
725
- wasm.buildWithdrawMerkleWitness(retptr, ptr0, len0, ptr1, len1);
441
+ wasm.buildWithdrawMerkleWitness(retptr, ptr0, len0, ptr1, len1);
442
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
443
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
444
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
445
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
446
+ var ptr3 = r0;
447
+ var len3 = r1;
448
+ if (r3) {
449
+ ptr3 = 0; len3 = 0;
450
+ throw takeObject(r2);
451
+ }
452
+ deferred4_0 = ptr3;
453
+ deferred4_1 = len3;
454
+ return getStringFromWasm0(ptr3, len3);
455
+ } finally {
456
+ wasm.__wbindgen_add_to_stack_pointer(16);
457
+ wasm.__wbindgen_export4(deferred4_0, deferred4_1, 1);
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Calculate owner-bound nullifier hash from nullifier and spend-scalar decimal strings.
463
+ * Returns hex string (0x...)
464
+ * @param {string} nullifier_decimal
465
+ * @param {string} priv_key_scalar_decimal
466
+ * @returns {string}
467
+ */
468
+ function calculateNullifierHash(nullifier_decimal, priv_key_scalar_decimal) {
469
+ let deferred4_0;
470
+ let deferred4_1;
471
+ try {
472
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
473
+ const ptr0 = passStringToWasm0(nullifier_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
474
+ const len0 = WASM_VECTOR_LEN;
475
+ const ptr1 = passStringToWasm0(priv_key_scalar_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
476
+ const len1 = WASM_VECTOR_LEN;
477
+ wasm.calculateNullifierHash(retptr, ptr0, len0, ptr1, len1);
726
478
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
727
479
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
728
480
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -743,35 +495,33 @@ function buildWithdrawMerkleWitness(coin_json, state_json) {
743
495
  }
744
496
 
745
497
  /**
746
- * Calculate nullifier hash from nullifier decimal string.
747
- * Returns hex string (0x...)
748
- * @param {string} nullifier_decimal
749
- * @returns {string}
498
+ * `R = Poseidon255(2)(hi, lo)`; `scalar` is the raw Poseidon hash when it is
499
+ * already in `(0, BabyJub subgroup order)`. Out-of-range hashes fail so the
500
+ * caller can rejection-sample a new nonce.
501
+ * @param {string} nonce_decimal
502
+ * @param {string} recipient_hi_decimal
503
+ * @param {string} recipient_lo_decimal
504
+ * @returns {any}
750
505
  */
751
- function calculateNullifierHash(nullifier_decimal) {
752
- let deferred3_0;
753
- let deferred3_1;
506
+ function derivedEscrowKey(nonce_decimal, recipient_hi_decimal, recipient_lo_decimal) {
754
507
  try {
755
508
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
756
- const ptr0 = passStringToWasm0(nullifier_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
509
+ const ptr0 = passStringToWasm0(nonce_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
757
510
  const len0 = WASM_VECTOR_LEN;
758
- wasm.calculateNullifierHash(retptr, ptr0, len0);
511
+ const ptr1 = passStringToWasm0(recipient_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
512
+ const len1 = WASM_VECTOR_LEN;
513
+ const ptr2 = passStringToWasm0(recipient_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
514
+ const len2 = WASM_VECTOR_LEN;
515
+ wasm.derivedEscrowKey(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
759
516
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
760
517
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
761
518
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
762
- var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
763
- var ptr2 = r0;
764
- var len2 = r1;
765
- if (r3) {
766
- ptr2 = 0; len2 = 0;
767
- throw takeObject(r2);
519
+ if (r2) {
520
+ throw takeObject(r1);
768
521
  }
769
- deferred3_0 = ptr2;
770
- deferred3_1 = len2;
771
- return getStringFromWasm0(ptr2, len2);
522
+ return takeObject(r0);
772
523
  } finally {
773
524
  wasm.__wbindgen_add_to_stack_pointer(16);
774
- wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1);
775
525
  }
776
526
  }
777
527
 
@@ -852,26 +602,28 @@ function ecdhSharedKey(priv_hex, pub_x_hex, pub_y_hex) {
852
602
  }
853
603
 
854
604
  /**
855
- * Generate a new coin with random nullifier, secret, and shared-secret field elements.
856
- * `amount` is stroops (u64); JS passes `bigint`.
605
+ * Generate a new coin with random nullifier, secret, and owner public-key field elements.
606
+ * `amount_decimal` is an arbitrary-precision decimal field element (never JS `number`).
857
607
  * `asset_hi_decimal` / `asset_lo_decimal` are decimal Fr strings for the Stellar asset contract id (two limbs).
858
608
  * Returns JSON: { coin: { value, nullifier, secret, commitment, asset_hi, asset_lo }, commitment_hex, precommitement_hex }
859
- * @param {bigint} amount
609
+ * @param {string} amount_decimal
860
610
  * @param {string} asset_hi_decimal
861
611
  * @param {string} asset_lo_decimal
862
612
  * @param {string} application_id_decimal
863
613
  * @returns {any}
864
614
  */
865
- function generateCoin(amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
615
+ function generateCoin(amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
866
616
  try {
867
617
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
868
- const ptr0 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
618
+ const ptr0 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
869
619
  const len0 = WASM_VECTOR_LEN;
870
- const ptr1 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
620
+ const ptr1 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
871
621
  const len1 = WASM_VECTOR_LEN;
872
- const ptr2 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
622
+ const ptr2 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
873
623
  const len2 = WASM_VECTOR_LEN;
874
- wasm.generateCoin(retptr, amount, ptr0, len0, ptr1, len1, ptr2, len2);
624
+ const ptr3 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
625
+ const len3 = WASM_VECTOR_LEN;
626
+ wasm.generateCoin(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
875
627
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
876
628
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
877
629
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -885,32 +637,34 @@ function generateCoin(amount, asset_hi_decimal, asset_lo_decimal, application_id
885
637
  }
886
638
 
887
639
  /**
888
- * `Poseidon₁(scalar)` secret + fixed ECDH shared coords (hex), matching an aligned deposit witness.
640
+ * `Poseidon₁(scalar)` secret + recipient owner public key (hex), matching an aligned deposit witness.
889
641
  * @param {string} scalar_hex
890
- * @param {string} shared_x_hex
891
- * @param {string} shared_y_hex
892
- * @param {bigint} amount
642
+ * @param {string} owner_x_hex
643
+ * @param {string} owner_y_hex
644
+ * @param {string} amount_decimal
893
645
  * @param {string} asset_hi_decimal
894
646
  * @param {string} asset_lo_decimal
895
647
  * @param {string} application_id_decimal
896
648
  * @returns {any}
897
649
  */
898
- function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
650
+ function generateCoinForDepositWithOwnerPubHex(scalar_hex, owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
899
651
  try {
900
652
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
901
653
  const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
902
654
  const len0 = WASM_VECTOR_LEN;
903
- const ptr1 = passStringToWasm0(shared_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
655
+ const ptr1 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
904
656
  const len1 = WASM_VECTOR_LEN;
905
- const ptr2 = passStringToWasm0(shared_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
657
+ const ptr2 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
906
658
  const len2 = WASM_VECTOR_LEN;
907
- const ptr3 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
659
+ const ptr3 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
908
660
  const len3 = WASM_VECTOR_LEN;
909
- const ptr4 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
661
+ const ptr4 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
910
662
  const len4 = WASM_VECTOR_LEN;
911
- const ptr5 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
663
+ const ptr5 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
912
664
  const len5 = WASM_VECTOR_LEN;
913
- wasm.generateCoinForDepositWithSharedHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, amount, ptr3, len3, ptr4, len4, ptr5, len5);
665
+ const ptr6 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
666
+ const len6 = WASM_VECTOR_LEN;
667
+ wasm.generateCoinForDepositWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6);
914
668
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
915
669
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
916
670
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -926,24 +680,26 @@ function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_
926
680
  /**
927
681
  * `secret` in coin = `Poseidon255(1)(scalar)` per `deposit.circom`; scalar is 32-byte hex (64 chars, optional `0x`).
928
682
  * @param {string} scalar_hex
929
- * @param {bigint} amount
683
+ * @param {string} amount_decimal
930
684
  * @param {string} asset_hi_decimal
931
685
  * @param {string} asset_lo_decimal
932
686
  * @param {string} application_id_decimal
933
687
  * @returns {any}
934
688
  */
935
- function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
689
+ function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
936
690
  try {
937
691
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
938
692
  const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
939
693
  const len0 = WASM_VECTOR_LEN;
940
- const ptr1 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
694
+ const ptr1 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
941
695
  const len1 = WASM_VECTOR_LEN;
942
- const ptr2 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
696
+ const ptr2 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
943
697
  const len2 = WASM_VECTOR_LEN;
944
- const ptr3 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
698
+ const ptr3 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
945
699
  const len3 = WASM_VECTOR_LEN;
946
- wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, amount, ptr1, len1, ptr2, len2, ptr3, len3);
700
+ const ptr4 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
701
+ const len4 = WASM_VECTOR_LEN;
702
+ wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4);
947
703
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
948
704
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
949
705
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -957,29 +713,31 @@ function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount, asset_hi_
957
713
  }
958
714
 
959
715
  /**
960
- * 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.
961
- * @param {string} shared_x_hex
962
- * @param {string} shared_y_hex
963
- * @param {bigint} amount
716
+ * Same as `generateCoin`, but commitment uses the given owner public key (64-char hex coords).
717
+ * @param {string} owner_x_hex
718
+ * @param {string} owner_y_hex
719
+ * @param {string} amount_decimal
964
720
  * @param {string} asset_hi_decimal
965
721
  * @param {string} asset_lo_decimal
966
722
  * @param {string} application_id_decimal
967
723
  * @returns {any}
968
724
  */
969
- function generateCoinWithSharedSecretHex(shared_x_hex, shared_y_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
725
+ function generateCoinWithOwnerPubHex(owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
970
726
  try {
971
727
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
972
- const ptr0 = passStringToWasm0(shared_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
728
+ const ptr0 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
973
729
  const len0 = WASM_VECTOR_LEN;
974
- const ptr1 = passStringToWasm0(shared_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
730
+ const ptr1 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
975
731
  const len1 = WASM_VECTOR_LEN;
976
- const ptr2 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
732
+ const ptr2 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
977
733
  const len2 = WASM_VECTOR_LEN;
978
- const ptr3 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
734
+ const ptr3 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
979
735
  const len3 = WASM_VECTOR_LEN;
980
- const ptr4 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
736
+ const ptr4 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
981
737
  const len4 = WASM_VECTOR_LEN;
982
- wasm.generateCoinWithSharedSecretHex(retptr, ptr0, len0, ptr1, len1, amount, ptr2, len2, ptr3, len3, ptr4, len4);
738
+ const ptr5 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
739
+ const len5 = WASM_VECTOR_LEN;
740
+ wasm.generateCoinWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5);
983
741
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
984
742
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
985
743
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -1392,13 +1150,14 @@ var wasmBindings = /*#__PURE__*/Object.freeze({
1392
1150
  buildWithdrawMerkleWitness: buildWithdrawMerkleWitness,
1393
1151
  calculateNullifierHash: calculateNullifierHash,
1394
1152
  default: __wbg_init,
1153
+ derivedEscrowKey: derivedEscrowKey,
1395
1154
  ecdhEphemeralPublicKey: ecdhEphemeralPublicKey,
1396
1155
  ecdhEphemeralPublicKeyFromScalarHex: ecdhEphemeralPublicKeyFromScalarHex,
1397
1156
  ecdhSharedKey: ecdhSharedKey,
1398
1157
  generateCoin: generateCoin,
1399
- generateCoinForDepositWithSharedHex: generateCoinForDepositWithSharedHex,
1158
+ generateCoinForDepositWithOwnerPubHex: generateCoinForDepositWithOwnerPubHex,
1400
1159
  generateCoinFromDepositEphemeralScalarHex: generateCoinFromDepositEphemeralScalarHex,
1401
- generateCoinWithSharedSecretHex: generateCoinWithSharedSecretHex,
1160
+ generateCoinWithOwnerPubHex: generateCoinWithOwnerPubHex,
1402
1161
  initSync: initSync,
1403
1162
  proofToHex: proofToHex,
1404
1163
  publicToHex: publicToHex
@@ -1844,80 +1603,494 @@ async function generateWitness(input, circuitWasm) {
1844
1603
  wasmBuffer = fs.readFileSync(wasmPath);
1845
1604
  }
1846
1605
  }
1847
- else {
1848
- if (!circuitWasm) {
1849
- throw new Error('In browser, you must pass circuitWasm to PrivacyPoolSDK.init(). ' +
1850
- 'Load the circuit WASM file via fetch() and pass the ArrayBuffer.');
1851
- }
1852
- wasmBuffer = circuitWasm;
1853
- wc = { default: witnessCalculatorModule };
1606
+ else {
1607
+ if (!circuitWasm) {
1608
+ throw new Error('In browser, you must pass circuitWasm to PrivacyPoolSDK.init(). ' +
1609
+ 'Load the circuit WASM file via fetch() and pass the ArrayBuffer.');
1610
+ }
1611
+ wasmBuffer = circuitWasm;
1612
+ wc = { default: witnessCalculatorModule };
1613
+ }
1614
+ const calculator = await wc.default(wasmBuffer);
1615
+ const wtns = await calculator.calculateWTNSBin(input, 0);
1616
+ return wtns;
1617
+ }
1618
+
1619
+ // @ts-ignore - snarkjs types
1620
+ const isNode = typeof process !== 'undefined' && !!process.versions?.node;
1621
+ /**
1622
+ * Normalize BufferSource to Uint8Array. Required for @iden3/binfileutils/fastfile:
1623
+ * they read via data.buffer and data.byteOffset, which ArrayBuffer does not have.
1624
+ */
1625
+ function toUint8Array(src) {
1626
+ if (src instanceof Uint8Array)
1627
+ return src;
1628
+ if (src instanceof ArrayBuffer)
1629
+ return new Uint8Array(src);
1630
+ return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
1631
+ }
1632
+ async function generateProof(wtns, zkey) {
1633
+ let zkeyData;
1634
+ if (zkey) {
1635
+ zkeyData = toUint8Array(zkey);
1636
+ }
1637
+ else if (isNode) {
1638
+ const fs = await import('fs');
1639
+ const nodePath = await import('path');
1640
+ const { fileURLToPath } = await import('url');
1641
+ const dir = nodePath.dirname(fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href))));
1642
+ const zkeyPath = nodePath.resolve(dir, '..', 'assets', 'main_final.zkey');
1643
+ if (!fs.existsSync(zkeyPath)) {
1644
+ throw new Error(`Proving key not found at ${zkeyPath}. Run build.sh first.`);
1645
+ }
1646
+ const zkeyBuffer = fs.readFileSync(zkeyPath);
1647
+ zkeyData = toUint8Array(zkeyBuffer);
1648
+ }
1649
+ else {
1650
+ throw new Error('In browser, you must pass zkey to PrivacyPoolSDK.init(). ' +
1651
+ 'Load the zkey file via fetch() and pass the ArrayBuffer.');
1652
+ }
1653
+ const wtnsData = toUint8Array(wtns);
1654
+ const { proof, publicSignals } = await snarkjs__namespace.groth16.prove({ type: 'mem', data: zkeyData }, { type: 'mem', data: wtnsData });
1655
+ return { proof, publicSignals };
1656
+ }
1657
+
1658
+ /** Bech32 HRP for {@link DecodedEphemeralKey} only (`x ‖ y`, 64 bytes). */
1659
+ const DECODED_EPHEMERAL_HRP = 'epk1';
1660
+ /** Bech32 HRP for {@link DecodedDepositorSharedSecretPreimage} (96 bytes). */
1661
+ const DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP = 'epk_dep_pre1';
1662
+ const FIELD_BYTES = 32;
1663
+ const EPK_POINT_PAYLOAD = FIELD_BYTES * 2;
1664
+ const EPK_DEP_PRE_PAYLOAD = FIELD_BYTES * 3;
1665
+ const BECH32_LONG_LIMIT = 1023;
1666
+ function normalizeHex$1(hex) {
1667
+ const s = hex.trim().replace(/^0x/i, '');
1668
+ if (!/^[0-9a-fA-F]*$/.test(s)) {
1669
+ throw new Error('ephemeral-key: hex must contain only 0-9, a-f');
1670
+ }
1671
+ return s.length % 2 === 0 ? s : `0${s}`;
1672
+ }
1673
+ function hexToBytes(hex) {
1674
+ const norm = normalizeHex$1(hex);
1675
+ const out = new Uint8Array(norm.length / 2);
1676
+ for (let i = 0; i < out.length; i++) {
1677
+ out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
1678
+ }
1679
+ return out;
1680
+ }
1681
+ function bytesToHex(bytes) {
1682
+ let s = '';
1683
+ for (let i = 0; i < bytes.length; i++) {
1684
+ s += bytes[i].toString(16).padStart(2, '0');
1685
+ }
1686
+ return s;
1687
+ }
1688
+ function concat2(a, b) {
1689
+ const out = new Uint8Array(a.length + b.length);
1690
+ out.set(a, 0);
1691
+ out.set(b, a.length);
1692
+ return out;
1693
+ }
1694
+ function concat3(a, b, c) {
1695
+ const out = new Uint8Array(a.length + b.length + c.length);
1696
+ out.set(a, 0);
1697
+ out.set(b, a.length);
1698
+ out.set(c, a.length + b.length);
1699
+ return out;
1700
+ }
1701
+ function require32(label, bytes) {
1702
+ if (bytes.length !== FIELD_BYTES) {
1703
+ throw new Error(`ephemeral-key: ${label} must encode exactly ${FIELD_BYTES} bytes, got ${bytes.length}`);
1704
+ }
1705
+ }
1706
+ /**
1707
+ * 32-byte big-endian integer as hex (64 chars), **< 2^253**, so BabyJub ECDH matches
1708
+ * `circuits/encryption.circom` `Num2Bits(253)` and `libs/cryptography` `scalar_mul_253`.
1709
+ */
1710
+ function generateRandomScalarHex32() {
1711
+ const g = globalThis.crypto;
1712
+ if (!g?.getRandomValues) {
1713
+ throw new Error('ephemeral-key: crypto.getRandomValues is required');
1714
+ }
1715
+ const max = 1n << 253n;
1716
+ for (let attempt = 0; attempt < 65536; attempt++) {
1717
+ const b = new Uint8Array(32);
1718
+ g.getRandomValues(b);
1719
+ const hex = bytesToHex(b);
1720
+ if (BigInt(`0x${hex}`) < max) {
1721
+ return hex;
1722
+ }
1723
+ }
1724
+ throw new Error('ephemeral-key: failed to sample scalar < 2^253');
1725
+ }
1726
+ /** Bech32 `epk1`: encodes `x ‖ y` (64 bytes). */
1727
+ function encodeDecodedEphemeralKey(decoded) {
1728
+ const xb = hexToBytes(decoded.x);
1729
+ const yb = hexToBytes(decoded.y);
1730
+ require32('x', xb);
1731
+ require32('y', yb);
1732
+ const payload = concat2(xb, yb);
1733
+ const words = bech32.toWords(payload);
1734
+ return bech32.encode(DECODED_EPHEMERAL_HRP, words, BECH32_LONG_LIMIT);
1735
+ }
1736
+ function decodeDecodedEphemeralKey(encoded) {
1737
+ const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
1738
+ if (prefix !== DECODED_EPHEMERAL_HRP) {
1739
+ throw new Error(`ephemeral-key: expected HRP ${DECODED_EPHEMERAL_HRP}, got ${JSON.stringify(prefix)}`);
1740
+ }
1741
+ const bytes = new Uint8Array(bech32.fromWords(words));
1742
+ if (bytes.length !== EPK_POINT_PAYLOAD) {
1743
+ throw new Error(`ephemeral-key: epk1 payload must be ${EPK_POINT_PAYLOAD} bytes, got ${bytes.length}`);
1744
+ }
1745
+ return {
1746
+ x: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
1747
+ y: bytesToHex(bytes.subarray(FIELD_BYTES)),
1748
+ };
1749
+ }
1750
+ /** Bech32 `epk_dep_pre1`: `randomNonceScalar ‖ recipient.x ‖ recipient.y`. */
1751
+ function encodeDepositorSharedSecretPreimage(decoded) {
1752
+ const sb = hexToBytes(decoded.randomNonceScalar);
1753
+ const xb = hexToBytes(decoded.recipientStealthAddress.x);
1754
+ const yb = hexToBytes(decoded.recipientStealthAddress.y);
1755
+ require32('randomNonceScalar', sb);
1756
+ require32('recipientStealthAddress.x', xb);
1757
+ require32('recipientStealthAddress.y', yb);
1758
+ const payload = concat3(sb, xb, yb);
1759
+ const words = bech32.toWords(payload);
1760
+ return bech32.encode(DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, words, BECH32_LONG_LIMIT);
1761
+ }
1762
+ function decodeDepositorSharedSecretPreimage(encoded) {
1763
+ const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
1764
+ if (prefix !== DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP) {
1765
+ throw new Error(`ephemeral-key: expected HRP ${DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP}, got ${JSON.stringify(prefix)}`);
1766
+ }
1767
+ const bytes = new Uint8Array(bech32.fromWords(words));
1768
+ if (bytes.length !== EPK_DEP_PRE_PAYLOAD) {
1769
+ throw new Error(`ephemeral-key: epk_dep_pre1 payload must be ${EPK_DEP_PRE_PAYLOAD} bytes, got ${bytes.length}`);
1770
+ }
1771
+ return {
1772
+ randomNonceScalar: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
1773
+ recipientStealthAddress: {
1774
+ x: bytesToHex(bytes.subarray(FIELD_BYTES, FIELD_BYTES * 2)),
1775
+ y: bytesToHex(bytes.subarray(FIELD_BYTES * 2)),
1776
+ },
1777
+ };
1778
+ }
1779
+
1780
+ /**
1781
+ * Depositor: `randomNonceScalar * recipientStealthPoint` (same as circom `ECDH` with depositor scalar).
1782
+ */
1783
+ function sharedSecretFromDepositorPreimage(ecdhShared, preimage) {
1784
+ const out = ecdhShared(preimage.randomNonceScalar, preimage.recipientStealthAddress.x, preimage.recipientStealthAddress.y);
1785
+ return { x: out.x, y: out.y };
1786
+ }
1787
+ /**
1788
+ * Recipient: `recipientScalar * ephemeralKey` (same shared point as depositor path when keys match).
1789
+ */
1790
+ function sharedSecretFromRecipientPreimage(ecdhShared, preimage) {
1791
+ const out = ecdhShared(preimage.recipientScalar, preimage.ephemeralKey.x, preimage.ephemeralKey.y);
1792
+ return { x: out.x, y: out.y };
1793
+ }
1794
+
1795
+ const DEFAULT_APPLICATION_ID = '101';
1796
+ /** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
1797
+ const DEMO_AUDIT_PUBLIC_KEY = [
1798
+ '21605515851820432880964235241069234202284600780825340516808373216881770219365',
1799
+ '18856460861531942120859708048677603751294231190189224157283439874962410808705',
1800
+ ];
1801
+ function isActiveWithdraw(slot) {
1802
+ return slot.value !== '0';
1803
+ }
1804
+ function isActiveDeposit(slot) {
1805
+ return slot.value !== '0';
1806
+ }
1807
+ function resolveWithdrawSlot(slot) {
1808
+ if (slot === 'dummy') {
1809
+ return null;
1810
+ }
1811
+ return slot;
1812
+ }
1813
+ function resolveDepositSlot(slot) {
1814
+ if (slot === 'dummy') {
1815
+ return null;
1816
+ }
1817
+ return slot;
1818
+ }
1819
+ function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey) {
1820
+ return {
1821
+ applicationId,
1822
+ noteAuditPublicKeys: [
1823
+ auditPublicKey,
1824
+ auditPublicKey,
1825
+ auditPublicKey,
1826
+ auditPublicKey,
1827
+ ],
1828
+ auditEphemeralScalars: [
1829
+ randomFrDecimal253(),
1830
+ randomFrDecimal253(),
1831
+ randomFrDecimal253(),
1832
+ randomFrDecimal253(),
1833
+ ],
1834
+ };
1835
+ }
1836
+ function resolveSlotApplicationIds(audit, withdrawSlots, depositSlots) {
1837
+ const w0 = resolveWithdrawSlot(withdrawSlots[0]);
1838
+ const w1 = resolveWithdrawSlot(withdrawSlots[1]);
1839
+ const d0 = resolveDepositSlot(depositSlots[0]);
1840
+ const d1 = resolveDepositSlot(depositSlots[1]);
1841
+ return {
1842
+ inputApplicationIds: [
1843
+ w0 && isActiveWithdraw(w0) ? audit.applicationId : '0',
1844
+ w1 && isActiveWithdraw(w1) ? audit.applicationId : '0',
1845
+ ],
1846
+ outputApplicationIds: [
1847
+ d0 && isActiveDeposit(d0) ? audit.applicationId : '0',
1848
+ d1 && isActiveDeposit(d1) ? audit.applicationId : '0',
1849
+ ],
1850
+ };
1851
+ }
1852
+ /**
1853
+ * Prefer an explicit key, then `NOTE_AUDIT_PUBLIC_KEY_X`/`_Y` env (decimal Fr),
1854
+ * then the built-in demo key. Used by CLI/`demo.sh` for per-app audit pubkeys.
1855
+ */
1856
+ function resolveAuditPublicKeyFromEnv(auditPublicKey) {
1857
+ if (auditPublicKey) {
1858
+ return auditPublicKey;
1859
+ }
1860
+ const x = process.env.NOTE_AUDIT_PUBLIC_KEY_X?.trim();
1861
+ const y = process.env.NOTE_AUDIT_PUBLIC_KEY_Y?.trim();
1862
+ if (x && y) {
1863
+ return [x, y];
1854
1864
  }
1855
- const calculator = await wc.default(wasmBuffer);
1856
- const wtns = await calculator.calculateWTNSBin(input, 0);
1857
- return wtns;
1865
+ return DEMO_AUDIT_PUBLIC_KEY;
1866
+ }
1867
+ function resolveTransactionAuditParams(applicationId, auditPublicKey) {
1868
+ return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey));
1858
1869
  }
1859
1870
 
1860
- // @ts-ignore - snarkjs types
1861
- const isNode = typeof process !== 'undefined' && !!process.versions?.node;
1871
+ /** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 12, 6)` in `circuits/main.circom`. */
1872
+ const TRANSACTION_TREE_DEPTH = 20;
1873
+ /** BN254 scalar field modulus (ark `Fr`, circom signals). */
1874
+ const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1862
1875
  /**
1863
- * Normalize BufferSource to Uint8Array. Required for @iden3/binfileutils/fastfile:
1864
- * they read via data.buffer and data.byteOffset, which ArrayBuffer does not have.
1876
+ * BabyJub ECDH in `circuits/encryption.circom` uses `Num2Bits(253)`; scalars must be < 2^253
1877
+ * (matches `libs/cryptography` `scalar_mul_253`).
1865
1878
  */
1866
- function toUint8Array(src) {
1867
- if (src instanceof Uint8Array)
1868
- return src;
1869
- if (src instanceof ArrayBuffer)
1870
- return new Uint8Array(src);
1871
- return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
1879
+ const BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE = 1n << 253n;
1880
+ function normalizeHex(hex) {
1881
+ const s = hex.trim().replace(/^0x/i, '');
1882
+ if (!/^[0-9a-fA-F]*$/.test(s)) {
1883
+ throw new Error('withdrawal-transaction-input: invalid hex');
1884
+ }
1885
+ return s.length % 2 === 0 ? s : `0${s}`;
1872
1886
  }
1873
- async function generateProof(wtns, zkey) {
1874
- let zkeyData;
1875
- if (zkey) {
1876
- zkeyData = toUint8Array(zkey);
1887
+ /** 32-byte field coordinate (hex, no 0x) → decimal string mod BN254 scalar field. */
1888
+ function coordHexToDecimal(hex) {
1889
+ const h = normalizeHex(hex);
1890
+ if (h.length > 64) {
1891
+ throw new Error('withdrawal-transaction-input: coordinate hex too long');
1877
1892
  }
1878
- else if (isNode) {
1879
- const fs = await import('fs');
1880
- const nodePath = await import('path');
1881
- const { fileURLToPath } = await import('url');
1882
- const dir = nodePath.dirname(fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href))));
1883
- const zkeyPath = nodePath.resolve(dir, '..', 'assets', 'main_final.zkey');
1884
- if (!fs.existsSync(zkeyPath)) {
1885
- throw new Error(`Proving key not found at ${zkeyPath}. Run build.sh first.`);
1886
- }
1887
- const zkeyBuffer = fs.readFileSync(zkeyPath);
1888
- zkeyData = toUint8Array(zkeyBuffer);
1893
+ const v = BigInt(`0x${h}`);
1894
+ return (v % BN254_SCALAR_MOD).toString(10);
1895
+ }
1896
+ /**
1897
+ * 32-byte big-endian scalar hex decimal for circom `ephemeralKeyScalar` / ECDH `priv`.
1898
+ * Integer must be < 2^253 (not reduced mod r — values ≥ 2^253 are rejected).
1899
+ */
1900
+ function scalarHexToFrDecimal(hex) {
1901
+ const h = normalizeHex(hex);
1902
+ if (h.length > 64) {
1903
+ throw new Error('withdrawal-transaction-input: scalar hex too long');
1889
1904
  }
1890
- else {
1891
- throw new Error('In browser, you must pass zkey to PrivacyPoolSDK.init(). ' +
1892
- 'Load the zkey file via fetch() and pass the ArrayBuffer.');
1905
+ const v = BigInt(`0x${h.padStart(64, '0').slice(-64)}`);
1906
+ if (v >= BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE) {
1907
+ throw new Error('depositor ephemeral scalar must be < 2^253 (BabyJub Num2Bits); resample with random-scalar');
1893
1908
  }
1894
- const wtnsData = toUint8Array(wtns);
1895
- const { proof, publicSignals } = await snarkjs__namespace.groth16.prove({ type: 'mem', data: zkeyData }, { type: 'mem', data: wtnsData });
1896
- return { proof, publicSignals };
1909
+ return v.toString(10);
1897
1910
  }
1898
-
1899
1911
  /**
1900
- * Depositor: `randomNonceScalar * recipientStealthPoint` (same as circom `ECDH` with depositor scalar).
1912
+ * Stellar G-address Ed25519 payload (32 bytes as 64 hex, optional 0x) → two circom public decimals
1913
+ * (`withdrawAddressHi` / `withdrawAddressLo`). No mod-r; each half fits in 128 bits.
1901
1914
  */
1902
- function sharedSecretFromDepositorPreimage(ecdhShared, preimage) {
1903
- const out = ecdhShared(preimage.randomNonceScalar, preimage.recipientStealthAddress.x, preimage.recipientStealthAddress.y);
1904
- return { x: out.x, y: out.y };
1915
+ function ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex) {
1916
+ const h = normalizeHex(hex).padStart(64, '0').slice(-64);
1917
+ const hi = BigInt(`0x${h.slice(0, 32)}`);
1918
+ const lo = BigInt(`0x${h.slice(32, 64)}`);
1919
+ return { hi: hi.toString(10), lo: lo.toString(10) };
1905
1920
  }
1906
1921
  /**
1907
- * Recipient: `recipientScalar * ephemeralKey` (same shared point as depositor path when keys match).
1922
+ * Stellar contract id (`C…`) two circom field decimals for `asset[0]`, `asset[1]` (same 32-byte split as accounts).
1908
1923
  */
1909
- function sharedSecretFromRecipientPreimage(ecdhShared, preimage) {
1910
- const out = ecdhShared(preimage.recipientScalar, preimage.ephemeralKey.x, preimage.ephemeralKey.y);
1911
- return { x: out.x, y: out.y };
1924
+ function stellarContractAddressToAssetFrDecimals(address) {
1925
+ const raw = stellarSdk.StrKey.decodeContract(address);
1926
+ const hex = Buffer.from(raw).toString('hex');
1927
+ const { hi, lo } = ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex);
1928
+ return [hi, lo];
1929
+ }
1930
+ /** Uniform random `Fr` as decimal (32 random bytes, mod r). For Poseidon-only inputs (e.g. nullifiers). */
1931
+ function randomFrDecimal() {
1932
+ const hex = generateRandomScalarHex32();
1933
+ const v = BigInt(`0x${normalizeHex(hex)}`);
1934
+ return (v % BN254_SCALAR_MOD).toString(10);
1935
+ }
1936
+ /** Random scalar < 2^253 for BabyJub ECDH / `Num2Bits(253)` (uses {@link generateRandomScalarHex32}). */
1937
+ function randomFrDecimal253() {
1938
+ const hex = generateRandomScalarHex32();
1939
+ return BigInt(`0x${normalizeHex(hex)}`).toString(10);
1940
+ }
1941
+ /** Spend-scalar hex in `(0, BabyJub subgroup order)` so dummy withdraws satisfy `LessThan(l)`. */
1942
+ function randomCanonicalBabyJubScalarHex() {
1943
+ const hex = generateRandomScalarHex32();
1944
+ const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex(hex)}`));
1945
+ return reduced.toString(16).padStart(64, '0');
1946
+ }
1947
+ function zerosTreeSiblings() {
1948
+ return Array(TRANSACTION_TREE_DEPTH).fill('0');
1949
+ }
1950
+ function dummyWithdraw(wasm) {
1951
+ const nullifier = randomFrDecimal();
1952
+ const secretHex = generateRandomScalarHex32();
1953
+ const secret = (BigInt(`0x${normalizeHex(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
1954
+ const scalarHex = randomCanonicalBabyJubScalarHex();
1955
+ const privKeyScalar = scalarHexToFrDecimal(scalarHex);
1956
+ const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(scalarHex);
1957
+ return {
1958
+ value: '0',
1959
+ nullifier,
1960
+ secret,
1961
+ asset: ['0', '0'],
1962
+ applicationId: '0',
1963
+ ownerPub: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
1964
+ privKeyScalar,
1965
+ paddingRandom: randomFrDecimal(),
1966
+ escrowNonce: '0',
1967
+ recipientStellar: ['0', '0'],
1968
+ stateSiblings: zerosTreeSiblings(),
1969
+ stateIndex: '0',
1970
+ };
1971
+ }
1972
+ function dummyDeposit(wasm) {
1973
+ const nullifier = randomFrDecimal();
1974
+ const ephemeralKeyScalar = randomFrDecimal253();
1975
+ const skHex = generateRandomScalarHex32();
1976
+ const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(skHex);
1977
+ return {
1978
+ value: '0',
1979
+ nullifier,
1980
+ ephemeralKeyScalar,
1981
+ asset: ['0', '0'],
1982
+ applicationId: '0',
1983
+ recipientPublicKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
1984
+ escrowNonce: '0',
1985
+ recipientStellar: ['0', '0'],
1986
+ };
1987
+ }
1988
+ function resolveWithdraw(slot, wasm) {
1989
+ return slot === 'dummy' ? dummyWithdraw(wasm) : slot;
1990
+ }
1991
+ function resolveDeposit(slot, wasm) {
1992
+ return slot === 'dummy' ? dummyDeposit(wasm) : slot;
1993
+ }
1994
+ function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, depositSlots, audit, wasm) {
1995
+ const w0 = resolveWithdraw(withdrawSlots[0], wasm);
1996
+ const w1 = resolveWithdraw(withdrawSlots[1], wasm);
1997
+ const d0 = resolveDeposit(depositSlots[0], wasm);
1998
+ const d1 = resolveDeposit(depositSlots[1], wasm);
1999
+ const appIds = resolveSlotApplicationIds(audit, withdrawSlots, depositSlots);
2000
+ return {
2001
+ stateRoot: publicParams.stateRoot,
2002
+ withdrawAddressHi: publicParams.withdrawAddressHi,
2003
+ withdrawAddressLo: publicParams.withdrawAddressLo,
2004
+ escrowRecipientHi: publicParams.escrowRecipientHi ?? '0',
2005
+ escrowRecipientLo: publicParams.escrowRecipientLo ?? '0',
2006
+ sweepOutputOwnerPubX: publicParams.sweepOutputOwnerPubX ?? '0',
2007
+ sweepOutputOwnerPubY: publicParams.sweepOutputOwnerPubY ?? '0',
2008
+ privKeyScalars: [w0.privKeyScalar, w1.privKeyScalar],
2009
+ ownerPubs: [w0.ownerPub, w1.ownerPub],
2010
+ paddingRandoms: [w0.paddingRandom, w1.paddingRandom],
2011
+ withdrawnValues: [w0.value, w1.value],
2012
+ withdrawnNullifiers: [w0.nullifier, w1.nullifier],
2013
+ withdrawnSecrets: [w0.secret, w1.secret],
2014
+ withdrawnAssets: [w0.asset, w1.asset],
2015
+ withdrawnEscrowNonces: [w0.escrowNonce, w1.escrowNonce],
2016
+ inputRecipientStellar: [w0.recipientStellar, w1.recipientStellar],
2017
+ stateSiblings: [w0.stateSiblings, w1.stateSiblings],
2018
+ stateIndex: [w0.stateIndex, w1.stateIndex],
2019
+ depositedValues: [d0.value, d1.value],
2020
+ depositedNullifiers: [d0.nullifier, d1.nullifier],
2021
+ depositedAssets: [d0.asset, d1.asset],
2022
+ depositedEphemeralKeyScalars: [d0.ephemeralKeyScalar, d1.ephemeralKeyScalar],
2023
+ depositedRecipientPublicKeys: [d0.recipientPublicKeys, d1.recipientPublicKeys],
2024
+ depositedEscrowNonces: [d0.escrowNonce ?? '0', d1.escrowNonce ?? '0'],
2025
+ outputRecipientStellar: [
2026
+ d0.recipientStellar ?? ['0', '0'],
2027
+ d1.recipientStellar ?? ['0', '0'],
2028
+ ],
2029
+ inputApplicationIds: appIds.inputApplicationIds,
2030
+ outputApplicationIds: appIds.outputApplicationIds,
2031
+ auditEphemeralScalars: audit.auditEphemeralScalars,
2032
+ noteAuditPublicKeys: audit.noteAuditPublicKeys,
2033
+ publicWithdrawnAssets: publicLegs.publicWithdrawnAssets,
2034
+ publicDepositedAssets: publicLegs.publicDepositedAssets,
2035
+ publicDeposits: publicLegs.publicDeposits,
2036
+ publicWithdrawals: publicLegs.publicWithdrawals,
2037
+ };
2038
+ }
2039
+ /** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
2040
+ function recipientPublicKeysDecimalFromStealthAddress(stealthAddress) {
2041
+ const { x, y } = decodeStealthAddress(stealthAddress);
2042
+ return [coordHexToDecimal(x), coordHexToDecimal(y)];
2043
+ }
2044
+ /** First withdraw leg: Merkle witness + owner public key (hex) and matching scalar. */
2045
+ function withdrawObjectFromMerkleWitness(witness, ownerPubHex, applicationId, privKeyScalar) {
2046
+ return {
2047
+ value: witness.value,
2048
+ nullifier: witness.nullifier,
2049
+ secret: witness.secret,
2050
+ asset: witness.withdrawnAsset,
2051
+ applicationId,
2052
+ ownerPub: [coordHexToDecimal(ownerPubHex.x), coordHexToDecimal(ownerPubHex.y)],
2053
+ privKeyScalar,
2054
+ paddingRandom: randomFrDecimal(),
2055
+ escrowNonce: '0',
2056
+ recipientStellar: ['0', '0'],
2057
+ stateSiblings: witness.stateSiblings,
2058
+ stateIndex: witness.stateIndex,
2059
+ };
1912
2060
  }
1913
2061
 
1914
- /** Stroops amount as WASM `u64` (`bigint`). */
1915
- function wasmU64Stroops(amount) {
1916
- const b = typeof amount === 'bigint' ? amount : BigInt(amount);
1917
- if (b < 0n || b > 0xffffffffffffffffn) {
1918
- throw new RangeError('amount must be a non-negative u64 (stroops)');
2062
+ /** Stroops amount as a decimal field string. Reject JS `number` so 18-decimal values cannot silently narrow. */
2063
+ function coinValueDecimal(amount) {
2064
+ if (typeof amount === 'number') {
2065
+ throw new TypeError('coin value must be bigint or decimal string, not number');
2066
+ }
2067
+ if (typeof amount === 'bigint') {
2068
+ if (amount < 0n) {
2069
+ throw new RangeError('amount must be a non-negative integer');
2070
+ }
2071
+ return amount.toString(10);
2072
+ }
2073
+ if (typeof amount !== 'string') {
2074
+ throw new TypeError('coin value must be bigint or decimal string, not number');
1919
2075
  }
1920
- return b;
2076
+ const trimmed = amount.trim();
2077
+ if (!/^[0-9]+$/.test(trimmed)) {
2078
+ throw new TypeError('coin value decimal string must be a non-negative integer');
2079
+ }
2080
+ return trimmed;
2081
+ }
2082
+ function generatedCoinFromWasm(value) {
2083
+ const raw = value;
2084
+ const precommitementHex = raw.precommitementHex ?? raw.precommitement_hex;
2085
+ if (!precommitementHex) {
2086
+ throw new Error('generated coin missing precommitement hex');
2087
+ }
2088
+ return {
2089
+ coin: raw.coin,
2090
+ commitment_hex: raw.commitment_hex,
2091
+ precommitement_hex: precommitementHex,
2092
+ precommitementHex,
2093
+ };
1921
2094
  }
1922
2095
  class PrivacyPoolSDK {
1923
2096
  constructor(wasm, options) {
@@ -1949,39 +2122,40 @@ class PrivacyPoolSDK {
1949
2122
  return generateRandomScalarHex32();
1950
2123
  }
1951
2124
  /**
1952
- * Text to sign with a Stellar wallet for stealth derivation (UTF-8). No WASM required.
2125
+ * Text to sign with a Stellar wallet for spend-key derivation (UTF-8). No WASM required.
2126
+ * The message binds network, pool, registry, and schema version (H6).
1953
2127
  */
1954
- static buildStealthAddressSignMessage(address, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
1955
- return buildStealthAddressSignMessage(address, nonce);
2128
+ static buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
2129
+ return buildStealthAddressSignMessage(address, domain, nonce);
1956
2130
  }
1957
2131
  /**
1958
- * Generate a new coin with random nullifier, secret, and random shared-secret field elements (dev / self-contained tests).
1959
- * @param amount Stroops encoded as `bigint` or integer `number` (WASM `u64`).
2132
+ * Generate a new coin with random nullifier, secret, and random owner-pub field elements (dev / self-contained tests).
2133
+ * @param amount Decimal field element as `bigint` or decimal `string` (never JS `number`).
1960
2134
  * @param assetHiDecimal / assetLoDecimal Decimal Fr strings for Stellar asset contract id (two limbs).
1961
2135
  */
1962
2136
  generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1963
- return this.wasm.generateCoin(wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
2137
+ return generatedCoinFromWasm(this.wasm.generateCoin(coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
1964
2138
  }
1965
2139
  /**
1966
- * Generate a coin with the same commitment shape as on-chain deposit: pass `ecdhSharedKey` output (hex x, y).
1967
- * @param amount Stroops (`bigint` | `number`).
2140
+ * Generate a coin with the same commitment shape as on-chain deposit: pass owner public key (hex x, y).
2141
+ * @param amount Decimal field element (`bigint` | `string`).
1968
2142
  */
1969
- generateCoinWithSharedSecret(shared, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1970
- return this.wasm.generateCoinWithSharedSecretHex(shared.x, shared.y, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
2143
+ generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
2144
+ return generatedCoinFromWasm(this.wasm.generateCoinWithOwnerPubHex(ownerPub.x, ownerPub.y, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
1971
2145
  }
1972
2146
  /**
1973
2147
  * Coin for a depositor `ephemeralKeyScalar` (32-byte hex): `coin.secret = Poseidon255(1)(scalar)` as in `deposit.circom`.
1974
- * @param amount Stroops (`bigint` | `number`).
2148
+ * @param amount Decimal field element (`bigint` | `string`).
1975
2149
  */
1976
2150
  generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1977
- return this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
2151
+ return generatedCoinFromWasm(this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
1978
2152
  }
1979
2153
  /**
1980
- * Aligned deposit coin: `secret = Poseidon₁(scalar)` and ECDH shared key from hex coords (e.g. `ecdhSharedKey(scalar, recipient_x, recipient_y)`).
1981
- * @param amount Stroops (`bigint` | `number`).
2154
+ * Aligned deposit coin: `secret = Poseidon₁(scalar)` and owner pub from recipient hex coords.
2155
+ * @param amount Decimal field element (`bigint` | `string`).
1982
2156
  */
1983
- generateCoinForDepositWithSharedHex(scalarHex, sharedXHex, sharedYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1984
- return this.wasm.generateCoinForDepositWithSharedHex(scalarHex, sharedXHex, sharedYHex, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
2157
+ generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
2158
+ return generatedCoinFromWasm(this.wasm.generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
1985
2159
  }
1986
2160
  /**
1987
2161
  * Merkle root, path, and coin fields for the first withdraw leg (Rust LeanIMT + Poseidon).
@@ -2000,10 +2174,8 @@ class PrivacyPoolSDK {
2000
2174
  */
2001
2175
  async proveWithdrawal(coin, state, params) {
2002
2176
  const witness = this.buildWithdrawMerkleWitness(coin, state);
2003
- const w0 = withdrawObjectFromMerkleWitness(witness, {
2004
- x: params.ephemeralXHex,
2005
- y: params.ephemeralYHex,
2006
- }, params.applicationId ?? coin.application_id ?? '0');
2177
+ const ownerPubHex = this.ecdhEphemeralPublicKeyFromScalarHex(BigInt(params.privKeyScalar).toString(16).padStart(64, '0'));
2178
+ const w0 = withdrawObjectFromMerkleWitness(witness, ownerPubHex, params.applicationId ?? coin.application_id ?? '0', params.privKeyScalar);
2007
2179
  const fullV = BigInt(coin.value);
2008
2180
  let publicWithdrawals;
2009
2181
  let deposits;
@@ -2089,19 +2261,20 @@ class PrivacyPoolSDK {
2089
2261
  };
2090
2262
  }
2091
2263
  /**
2092
- * Calculate nullifier hash: Poseidon(nullifier)
2264
+ * Calculate owner-bound nullifier hash: Poseidon(DOM_NULLIFIER, nullifier, privKeyScalar)
2093
2265
  * @param nullifier Nullifier decimal string from coin data
2266
+ * @param privKeyScalar Spend scalar decimal string
2094
2267
  * @returns Hex string (0x...) of the hash bytes
2095
2268
  */
2096
- calculateNullifierHash(nullifier) {
2097
- return this.wasm.calculateNullifierHash(nullifier);
2269
+ calculateNullifierHash(nullifier, privKeyScalar) {
2270
+ return this.wasm.calculateNullifierHash(nullifier, privKeyScalar);
2098
2271
  }
2099
2272
  /**
2100
2273
  * Ed25519 signature from signing {@link buildStealthAddressSignMessage}: **128 hex chars** (optional `0x`)
2101
- * or **base64** (64 raw bytes after decode). `SHA-256(signature bytes)` → scalar → ECDH → `stpl1` Bech32.
2274
+ * or **base64** (64 raw bytes after decode). Domain-separated SHA-256 → scalar → ECDH → `stpl1` Bech32.
2102
2275
  */
2103
- async generateStealthAddressFromStellarSignature(signature) {
2104
- return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature);
2276
+ async generateStealthAddressFromStellarSignature(signature, domain) {
2277
+ return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature, domain);
2105
2278
  }
2106
2279
  encodeDecodedEphemeralKey(decoded) {
2107
2280
  return encodeDecodedEphemeralKey(decoded);
@@ -2123,38 +2296,6 @@ class PrivacyPoolSDK {
2123
2296
  }
2124
2297
  }
2125
2298
 
2126
- function onboardingContractValue(onboarding) {
2127
- return {
2128
- owner: onboarding.owner,
2129
- temp_public_key_x: onboarding.temp_public_key_x,
2130
- temp_public_key_y: onboarding.temp_public_key_y,
2131
- encrypted_private_key: onboarding.encrypted_private_key,
2132
- notes: {
2133
- notes: onboarding.notes.map((note) => ({
2134
- value: note.value,
2135
- asset_hi: note.asset_hi,
2136
- asset_lo: note.asset_lo,
2137
- nullifier: note.nullifier,
2138
- secret: note.secret,
2139
- deposited_ephemeral_scalar: note.deposited_ephemeral_scalar,
2140
- })),
2141
- },
2142
- private_address_registration: onboarding.private_address_registration,
2143
- };
2144
- }
2145
- function onboardingToScVal(onboarding) {
2146
- return stellarSdk.nativeToScVal(onboardingContractValue(onboarding));
2147
- }
2148
- function onboardingOptionToScVal(onboarding) {
2149
- if (onboarding === null || onboarding === undefined) {
2150
- return stellarSdk.xdr.ScVal.scvVoid();
2151
- }
2152
- return onboardingToScVal(onboarding);
2153
- }
2154
- function addressToScVal(address) {
2155
- return stellarSdk.nativeToScVal(address, { type: 'address' });
2156
- }
2157
-
2158
2299
  function signatureBase64ToBytes(signature) {
2159
2300
  const value = signature.trim();
2160
2301
  if (/^(0x)?[0-9a-fA-F]{128}$/.test(value)) {
@@ -2163,6 +2304,9 @@ function signatureBase64ToBytes(signature) {
2163
2304
  return Buffer.from(value, 'base64');
2164
2305
  }
2165
2306
 
2307
+ function addressToScVal(address) {
2308
+ return stellarSdk.nativeToScVal(address, { type: 'address' });
2309
+ }
2166
2310
  function passageIdHexToScVal(passageIdHex) {
2167
2311
  const bytes = Buffer.from(passageIdHex.replace(/^0x/, ''), 'hex');
2168
2312
  return stellarSdk.xdr.ScVal.scvBytes(bytes);
@@ -2179,7 +2323,6 @@ function helperSubmitWithPassageOperation(helperContractId, params, passageId, e
2179
2323
  stellarSdk.nativeToScVal(params.nonce, { type: 'u64' }),
2180
2324
  stellarSdk.xdr.ScVal.scvBytes(params.proofBytes),
2181
2325
  stellarSdk.xdr.ScVal.scvBytes(params.publicSignalsBytes),
2182
- onboardingOptionToScVal(params.onboarding),
2183
2326
  passageIdHexToScVal(passageId),
2184
2327
  stellarSdk.xdr.ScVal.scvU32(expiresAtLedger),
2185
2328
  signatureToScVal(signatureBase64ToBytes(signature)),
@@ -2337,6 +2480,9 @@ async function main() {
2337
2480
  else if (command === 'priv-scalar-from-signature') {
2338
2481
  await handlePrivScalarFromSignature(args.slice(1));
2339
2482
  }
2483
+ else if (command === 'stealth-pubkey-hex') {
2484
+ handleStealthPubkeyHex(args.slice(1));
2485
+ }
2340
2486
  else if (command === 'kyt-submit-approved') {
2341
2487
  await handleKytSubmitApproved(args.slice(1));
2342
2488
  }
@@ -2364,13 +2510,25 @@ Commands:
2364
2510
 
2365
2511
  stealth-sign-message Print message to sign (Stellar wallet / stellar CLI)
2366
2512
  --address <G...> Stellar account address (required)
2513
+ --network-passphrase <string> Network passphrase bound into the spend scalar
2514
+ --pool <C...> Pool contract id bound into the spend scalar
2515
+ --registry <C...> Registry contract id bound into the spend scalar
2516
+ --schema-version <u32> Note schema version (default: 1)
2367
2517
  --nonce <string> Nonce label (default: "main address")
2368
2518
 
2369
2519
  stealth-from-signature Derive stpl1 stealth address from Ed25519 signature
2370
2520
  --signature <value> 128 hex chars (optional 0x) or standard base64 (64 bytes)
2371
2521
  --signature-file <path> Read signature from file (whitespace trimmed)
2522
+ --network-passphrase <string> Spend-scalar domain (same as stealth-sign-message)
2523
+ --pool <C...>
2524
+ --registry <C...>
2525
+ --schema-version <u32>
2372
2526
 
2373
- priv-scalar-from-signature Print privKeyScalar (decimal Fr) from Stellar Ed25519 signature (SHA-256(sig) mod r)
2527
+ priv-scalar-from-signature Print privKeyScalar (decimal Fr) from domain-separated spend digest
2528
+
2529
+ stealth-pubkey-hex Decode a stpl1 stealth address into its BabyJubJub public key
2530
+ --stealth <stpl1...> Stealth address to decode
2531
+ Prints x hex on line 1, y hex on line 2 (registry public_key_x/public_key_y)
2374
2532
 
2375
2533
  kyt-submit-approved Submit helper.register_passage + pool.transact with signed non-root auth
2376
2534
  --source-secret <S...> Secret seed for the depositing/withdrawing Stellar account (or SOURCE_SECRET env)
@@ -2478,6 +2636,24 @@ function parseArgs(args) {
2478
2636
  }
2479
2637
  return parsed;
2480
2638
  }
2639
+ function requireSpendScalarDomain(parsed) {
2640
+ const networkPassphrase = parsed['network-passphrase'] ?? networkPassphraseFromArgs(parsed);
2641
+ const poolContract = requireParsedArg(parsed, 'pool');
2642
+ const registryContract = requireParsedArg(parsed, 'registry');
2643
+ const schemaRaw = parsed['schema-version'];
2644
+ const domain = {
2645
+ networkPassphrase,
2646
+ poolContract,
2647
+ registryContract,
2648
+ };
2649
+ if (schemaRaw !== undefined) {
2650
+ domain.schemaVersion = Number(schemaRaw);
2651
+ }
2652
+ else {
2653
+ domain.schemaVersion = OWNER_BOUND_NOTE_SCHEMA_VERSION;
2654
+ }
2655
+ return domain;
2656
+ }
2481
2657
  function handleStealthSignMessage(args) {
2482
2658
  const parsed = parseArgs(args);
2483
2659
  const address = parsed['address'];
@@ -2486,7 +2662,7 @@ function handleStealthSignMessage(args) {
2486
2662
  process.exit(1);
2487
2663
  }
2488
2664
  const nonce = parsed['nonce'] ?? DEFAULT_STEALTH_SIGN_NONCE;
2489
- console.log(buildStealthAddressSignMessage(address, nonce));
2665
+ console.log(buildStealthAddressSignMessage(address, requireSpendScalarDomain(parsed), nonce));
2490
2666
  }
2491
2667
  async function handleStealthFromSignature(args) {
2492
2668
  const parsed = parseArgs(args);
@@ -2499,9 +2675,16 @@ async function handleStealthFromSignature(args) {
2499
2675
  process.exit(1);
2500
2676
  }
2501
2677
  const sdk = await PrivacyPoolSDK.init();
2502
- const stealth = await sdk.generateStealthAddressFromStellarSignature(sig);
2678
+ const stealth = await sdk.generateStealthAddressFromStellarSignature(sig, requireSpendScalarDomain(parsed));
2503
2679
  console.log(stealth);
2504
2680
  }
2681
+ function handleStealthPubkeyHex(args) {
2682
+ const parsed = parseArgs(args);
2683
+ const stealth = requireParsedArg(parsed, 'stealth');
2684
+ const { x, y } = decodeStealthAddress(stealth);
2685
+ console.log(x);
2686
+ console.log(y);
2687
+ }
2505
2688
  async function handlePrivScalarFromSignature(args) {
2506
2689
  const parsed = parseArgs(args);
2507
2690
  let sig = parsed['signature'];
@@ -2512,7 +2695,7 @@ async function handlePrivScalarFromSignature(args) {
2512
2695
  console.error('Error: --signature <hex> or --signature-file <path> is required');
2513
2696
  process.exit(1);
2514
2697
  }
2515
- const dec = await privKeyScalarDecimalFromStellarSignature(sig.trim());
2698
+ const dec = await privKeyScalarDecimalFromStellarSignature(sig.trim(), requireSpendScalarDomain(parsed));
2516
2699
  console.log(dec);
2517
2700
  }
2518
2701
  function handleRandomScalar() {
@@ -2541,33 +2724,22 @@ async function handleKytSubmitApproved(args) {
2541
2724
  });
2542
2725
  console.log(txHash);
2543
2726
  }
2544
- function parseStroopsU64(label, raw, defaultStroops) {
2727
+ function parseCoinValueDecimal(label, raw, defaultStroops) {
2545
2728
  if (raw === undefined) {
2546
- return defaultStroops;
2729
+ return defaultStroops.toString(10);
2547
2730
  }
2548
2731
  if (!/^\d+$/.test(raw)) {
2549
- console.error(`Error: ${label} must be a non-negative decimal integer (stroops u64)`);
2550
- process.exit(1);
2551
- }
2552
- try {
2553
- const b = BigInt(raw);
2554
- if (b < 0n || b > 0xffffffffffffffffn) {
2555
- console.error(`Error: ${label} out of u64 range`);
2556
- process.exit(1);
2557
- }
2558
- return b;
2559
- }
2560
- catch {
2561
- console.error(`Error: invalid ${label}`);
2732
+ console.error(`Error: ${label} must be a non-negative decimal integer`);
2562
2733
  process.exit(1);
2563
2734
  }
2735
+ return raw;
2564
2736
  }
2565
2737
  async function handleGenerate(args) {
2566
2738
  const parsed = parseArgs(args);
2567
2739
  const sdk = await PrivacyPoolSDK.init();
2568
2740
  const scalar = parsed['scalar'];
2569
2741
  const stealth = parsed['stealth'];
2570
- const amount = parseStroopsU64('--amount', parsed['amount'], BigInt(COIN_VALUE_STROOPS));
2742
+ const amount = parseCoinValueDecimal('--amount', parsed['amount'], BigInt(COIN_VALUE_STROOPS));
2571
2743
  const applicationId = resolveApplicationIdFromCli(parsed);
2572
2744
  const token = parsed['token'] ?? process.env.TOKEN_ADDRESS;
2573
2745
  if (!token) {
@@ -2582,8 +2754,7 @@ async function handleGenerate(args) {
2582
2754
  let coin;
2583
2755
  if (scalar && stealth) {
2584
2756
  const { x, y } = decodeStealthAddress(stealth);
2585
- const shared = sdk.ecdhSharedKey(scalar, x, y);
2586
- coin = sdk.generateCoinForDepositWithSharedHex(scalar, shared.x, shared.y, amount, assetHi, assetLo, applicationId);
2757
+ coin = sdk.generateCoinForDepositWithOwnerPubHex(scalar, x, y, amount, assetHi, assetLo, applicationId);
2587
2758
  }
2588
2759
  else if (scalar) {
2589
2760
  coin = sdk.generateCoinFromDepositEphemeralScalarHex(scalar, amount, assetHi, assetLo, applicationId);