@auditable/privacy-pool-zk-sdk 0.1.0 → 0.6.1-rc.5

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
@@ -2,6 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var fs = require('fs');
5
+ var stellarSdk = require('@stellar/stellar-sdk');
5
6
  var snarkjs = require('snarkjs');
6
7
 
7
8
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -397,7 +398,68 @@ function decodeDepositorSharedSecretPreimage(encoded) {
397
398
  };
398
399
  }
399
400
 
400
- /** Matches `Transaction(20, 2, 2)` in `circuits/main.circom`. */
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);
460
+ }
461
+
462
+ /** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 9)` in `circuits/main.circom`. */
401
463
  const TRANSACTION_TREE_DEPTH = 20;
402
464
  /** BN254 scalar field modulus (ark `Fr`, circom signals). */
403
465
  const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
@@ -447,6 +509,15 @@ function ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex) {
447
509
  const lo = BigInt(`0x${h.slice(32, 64)}`);
448
510
  return { hi: hi.toString(10), lo: lo.toString(10) };
449
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
+ }
450
521
  /** Uniform random `Fr` as decimal (32 random bytes, mod r). For Poseidon-only inputs (e.g. nullifiers). */
451
522
  function randomFrDecimal() {
452
523
  const hex = generateRandomScalarHex32();
@@ -470,6 +541,8 @@ function dummyWithdraw(wasm) {
470
541
  value: '0',
471
542
  nullifier,
472
543
  secret,
544
+ asset: ['0', '0'],
545
+ applicationId: '0',
473
546
  ephemeralKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
474
547
  stateSiblings: zerosTreeSiblings(),
475
548
  stateIndex: '0',
@@ -484,6 +557,8 @@ function dummyDeposit(wasm) {
484
557
  value: '0',
485
558
  nullifier,
486
559
  ephemeralKeyScalar,
560
+ asset: ['0', '0'],
561
+ applicationId: '0',
487
562
  recipientPublicKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
488
563
  };
489
564
  }
@@ -493,11 +568,12 @@ function resolveWithdraw(slot, wasm) {
493
568
  function resolveDeposit(slot, wasm) {
494
569
  return slot === 'dummy' ? dummyDeposit(wasm) : slot;
495
570
  }
496
- function buildTransactionWitnessInput(publicParams, withdrawSlots, depositSlots, wasm) {
571
+ function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, depositSlots, audit, wasm) {
497
572
  const w0 = resolveWithdraw(withdrawSlots[0], wasm);
498
573
  const w1 = resolveWithdraw(withdrawSlots[1], wasm);
499
574
  const d0 = resolveDeposit(depositSlots[0], wasm);
500
575
  const d1 = resolveDeposit(depositSlots[1], wasm);
576
+ const appIds = resolveSlotApplicationIds(audit, withdrawSlots, depositSlots);
501
577
  return {
502
578
  stateRoot: publicParams.stateRoot,
503
579
  withdrawAddressHi: publicParams.withdrawAddressHi,
@@ -506,13 +582,23 @@ function buildTransactionWitnessInput(publicParams, withdrawSlots, depositSlots,
506
582
  withdrawnValues: [w0.value, w1.value],
507
583
  withdrawnNullifiers: [w0.nullifier, w1.nullifier],
508
584
  withdrawnSecrets: [w0.secret, w1.secret],
585
+ withdrawnAssets: [w0.asset, w1.asset],
509
586
  ephemeralKeys: [w0.ephemeralKeys, w1.ephemeralKeys],
510
587
  stateSiblings: [w0.stateSiblings, w1.stateSiblings],
511
588
  stateIndex: [w0.stateIndex, w1.stateIndex],
512
589
  depositedValues: [d0.value, d1.value],
513
590
  depositedNullifiers: [d0.nullifier, d1.nullifier],
591
+ depositedAssets: [d0.asset, d1.asset],
514
592
  depositedEphemeralKeyScalars: [d0.ephemeralKeyScalar, d1.ephemeralKeyScalar],
515
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,
516
602
  };
517
603
  }
518
604
  /** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
@@ -521,11 +607,13 @@ function recipientPublicKeysDecimalFromStealthAddress(stealthAddress) {
521
607
  return [coordHexToDecimal(x), coordHexToDecimal(y)];
522
608
  }
523
609
  /** First withdraw leg: Merkle witness + depositor ECDH point coordinates (hex). */
524
- function withdrawObjectFromMerkleWitness(witness, depositorEphemeralHex) {
610
+ function withdrawObjectFromMerkleWitness(witness, depositorEphemeralHex, applicationId) {
525
611
  return {
526
612
  value: witness.value,
527
613
  nullifier: witness.nullifier,
528
614
  secret: witness.secret,
615
+ asset: witness.withdrawnAsset,
616
+ applicationId,
529
617
  ephemeralKeys: [
530
618
  coordHexToDecimal(depositorEphemeralHex.x),
531
619
  coordHexToDecimal(depositorEphemeralHex.y),
@@ -766,13 +854,34 @@ function ecdhSharedKey(priv_hex, pub_x_hex, pub_y_hex) {
766
854
  /**
767
855
  * Generate a new coin with random nullifier, secret, and shared-secret field elements.
768
856
  * `amount` is stroops (u64); JS passes `bigint`.
769
- * Returns JSON: { coin: { value, nullifier, secret, commitment }, commitment_hex }
857
+ * `asset_hi_decimal` / `asset_lo_decimal` are decimal Fr strings for the Stellar asset contract id (two limbs).
858
+ * Returns JSON: { coin: { value, nullifier, secret, commitment, asset_hi, asset_lo }, commitment_hex, precommitement_hex }
770
859
  * @param {bigint} amount
860
+ * @param {string} asset_hi_decimal
861
+ * @param {string} asset_lo_decimal
862
+ * @param {string} application_id_decimal
771
863
  * @returns {any}
772
864
  */
773
- function generateCoin(amount) {
774
- const ret = wasm.generateCoin(amount);
775
- return takeObject(ret);
865
+ function generateCoin(amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
866
+ try {
867
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
868
+ const ptr0 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
869
+ const len0 = WASM_VECTOR_LEN;
870
+ const ptr1 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
871
+ const len1 = WASM_VECTOR_LEN;
872
+ const ptr2 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
873
+ const len2 = WASM_VECTOR_LEN;
874
+ wasm.generateCoin(retptr, amount, ptr0, len0, ptr1, len1, ptr2, len2);
875
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
876
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
877
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
878
+ if (r2) {
879
+ throw takeObject(r1);
880
+ }
881
+ return takeObject(r0);
882
+ } finally {
883
+ wasm.__wbindgen_add_to_stack_pointer(16);
884
+ }
776
885
  }
777
886
 
778
887
  /**
@@ -781,9 +890,12 @@ function generateCoin(amount) {
781
890
  * @param {string} shared_x_hex
782
891
  * @param {string} shared_y_hex
783
892
  * @param {bigint} amount
893
+ * @param {string} asset_hi_decimal
894
+ * @param {string} asset_lo_decimal
895
+ * @param {string} application_id_decimal
784
896
  * @returns {any}
785
897
  */
786
- function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_hex, amount) {
898
+ function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
787
899
  try {
788
900
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
789
901
  const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
@@ -792,7 +904,13 @@ function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_
792
904
  const len1 = WASM_VECTOR_LEN;
793
905
  const ptr2 = passStringToWasm0(shared_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
794
906
  const len2 = WASM_VECTOR_LEN;
795
- wasm.generateCoinForDepositWithSharedHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, amount);
907
+ const ptr3 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
908
+ const len3 = WASM_VECTOR_LEN;
909
+ const ptr4 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
910
+ const len4 = WASM_VECTOR_LEN;
911
+ const ptr5 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
912
+ const len5 = WASM_VECTOR_LEN;
913
+ wasm.generateCoinForDepositWithSharedHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, amount, ptr3, len3, ptr4, len4, ptr5, len5);
796
914
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
797
915
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
798
916
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -809,14 +927,23 @@ function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_
809
927
  * `secret` in coin = `Poseidon255(1)(scalar)` per `deposit.circom`; scalar is 32-byte hex (64 chars, optional `0x`).
810
928
  * @param {string} scalar_hex
811
929
  * @param {bigint} amount
930
+ * @param {string} asset_hi_decimal
931
+ * @param {string} asset_lo_decimal
932
+ * @param {string} application_id_decimal
812
933
  * @returns {any}
813
934
  */
814
- function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount) {
935
+ function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
815
936
  try {
816
937
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
817
938
  const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
818
939
  const len0 = WASM_VECTOR_LEN;
819
- wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, amount);
940
+ const ptr1 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
941
+ const len1 = WASM_VECTOR_LEN;
942
+ const ptr2 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
943
+ const len2 = WASM_VECTOR_LEN;
944
+ const ptr3 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
945
+ const len3 = WASM_VECTOR_LEN;
946
+ wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, amount, ptr1, len1, ptr2, len2, ptr3, len3);
820
947
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
821
948
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
822
949
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -834,16 +961,25 @@ function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount) {
834
961
  * @param {string} shared_x_hex
835
962
  * @param {string} shared_y_hex
836
963
  * @param {bigint} amount
964
+ * @param {string} asset_hi_decimal
965
+ * @param {string} asset_lo_decimal
966
+ * @param {string} application_id_decimal
837
967
  * @returns {any}
838
968
  */
839
- function generateCoinWithSharedSecretHex(shared_x_hex, shared_y_hex, amount) {
969
+ function generateCoinWithSharedSecretHex(shared_x_hex, shared_y_hex, amount, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
840
970
  try {
841
971
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
842
972
  const ptr0 = passStringToWasm0(shared_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
843
973
  const len0 = WASM_VECTOR_LEN;
844
974
  const ptr1 = passStringToWasm0(shared_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
845
975
  const len1 = WASM_VECTOR_LEN;
846
- wasm.generateCoinWithSharedSecretHex(retptr, ptr0, len0, ptr1, len1, amount);
976
+ const ptr2 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
977
+ const len2 = WASM_VECTOR_LEN;
978
+ const ptr3 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
979
+ const len3 = WASM_VECTOR_LEN;
980
+ const ptr4 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
981
+ const len4 = WASM_VECTOR_LEN;
982
+ wasm.generateCoinWithSharedSecretHex(retptr, ptr0, len0, ptr1, len1, amount, ptr2, len2, ptr3, len3, ptr4, len4);
847
983
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
848
984
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
849
985
  var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
@@ -914,13 +1050,6 @@ function __wbg_get_imports() {
914
1050
  getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
915
1051
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
916
1052
  },
917
- __wbg___wbindgen_debug_string_5398f5bb970e0daa: function(arg0, arg1) {
918
- const ret = debugString(getObject(arg1));
919
- const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2);
920
- const len1 = WASM_VECTOR_LEN;
921
- getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
922
- getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
923
- },
924
1053
  __wbg___wbindgen_is_function_3c846841762788c1: function(arg0) {
925
1054
  const ret = typeof(getObject(arg0)) === 'function';
926
1055
  return ret;
@@ -1046,71 +1175,6 @@ function addHeapObject(obj) {
1046
1175
  return idx;
1047
1176
  }
1048
1177
 
1049
- function debugString(val) {
1050
- // primitive types
1051
- const type = typeof val;
1052
- if (type == 'number' || type == 'boolean' || val == null) {
1053
- return `${val}`;
1054
- }
1055
- if (type == 'string') {
1056
- return `"${val}"`;
1057
- }
1058
- if (type == 'symbol') {
1059
- const description = val.description;
1060
- if (description == null) {
1061
- return 'Symbol';
1062
- } else {
1063
- return `Symbol(${description})`;
1064
- }
1065
- }
1066
- if (type == 'function') {
1067
- const name = val.name;
1068
- if (typeof name == 'string' && name.length > 0) {
1069
- return `Function(${name})`;
1070
- } else {
1071
- return 'Function';
1072
- }
1073
- }
1074
- // objects
1075
- if (Array.isArray(val)) {
1076
- const length = val.length;
1077
- let debug = '[';
1078
- if (length > 0) {
1079
- debug += debugString(val[0]);
1080
- }
1081
- for(let i = 1; i < length; i++) {
1082
- debug += ', ' + debugString(val[i]);
1083
- }
1084
- debug += ']';
1085
- return debug;
1086
- }
1087
- // Test for built-in
1088
- const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
1089
- let className;
1090
- if (builtInMatches && builtInMatches.length > 1) {
1091
- className = builtInMatches[1];
1092
- } else {
1093
- // Failed to match the standard '[object ClassName]'
1094
- return toString.call(val);
1095
- }
1096
- if (className == 'Object') {
1097
- // we're a user defined class or Object
1098
- // JSON.stringify avoids problems with cycles, and is generally much
1099
- // easier than looping through ownProperties of `val`.
1100
- try {
1101
- return 'Object(' + JSON.stringify(val) + ')';
1102
- } catch (_) {
1103
- return 'Object';
1104
- }
1105
- }
1106
- // errors
1107
- if (val instanceof Error) {
1108
- return `${val.name}: ${val.message}\n${val.stack}`;
1109
- }
1110
- // TODO we could test for more things here, like `Set`s and `Map`s.
1111
- return className;
1112
- }
1113
-
1114
1178
  function dropObject(idx) {
1115
1179
  if (idx < 1028) return;
1116
1180
  heap[idx] = heap_next;
@@ -1893,30 +1957,31 @@ class PrivacyPoolSDK {
1893
1957
  /**
1894
1958
  * Generate a new coin with random nullifier, secret, and random shared-secret field elements (dev / self-contained tests).
1895
1959
  * @param amount Stroops encoded as `bigint` or integer `number` (WASM `u64`).
1960
+ * @param assetHiDecimal / assetLoDecimal Decimal Fr strings for Stellar asset contract id (two limbs).
1896
1961
  */
1897
- generateCoin(amount) {
1898
- return this.wasm.generateCoin(wasmU64Stroops(amount));
1962
+ generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1963
+ return this.wasm.generateCoin(wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
1899
1964
  }
1900
1965
  /**
1901
1966
  * Generate a coin with the same commitment shape as on-chain deposit: pass `ecdhSharedKey` output (hex x, y).
1902
1967
  * @param amount Stroops (`bigint` | `number`).
1903
1968
  */
1904
- generateCoinWithSharedSecret(shared, amount) {
1905
- return this.wasm.generateCoinWithSharedSecretHex(shared.x, shared.y, wasmU64Stroops(amount));
1969
+ generateCoinWithSharedSecret(shared, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1970
+ return this.wasm.generateCoinWithSharedSecretHex(shared.x, shared.y, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
1906
1971
  }
1907
1972
  /**
1908
1973
  * Coin for a depositor `ephemeralKeyScalar` (32-byte hex): `coin.secret = Poseidon255(1)(scalar)` as in `deposit.circom`.
1909
1974
  * @param amount Stroops (`bigint` | `number`).
1910
1975
  */
1911
- generateCoinFromDepositEphemeralScalarHex(scalarHex, amount) {
1912
- return this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, wasmU64Stroops(amount));
1976
+ generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1977
+ return this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
1913
1978
  }
1914
1979
  /**
1915
1980
  * Aligned deposit coin: `secret = Poseidon₁(scalar)` and ECDH shared key from hex coords (e.g. `ecdhSharedKey(scalar, recipient_x, recipient_y)`).
1916
1981
  * @param amount Stroops (`bigint` | `number`).
1917
1982
  */
1918
- generateCoinForDepositWithSharedHex(scalarHex, sharedXHex, sharedYHex, amount) {
1919
- return this.wasm.generateCoinForDepositWithSharedHex(scalarHex, sharedXHex, sharedYHex, wasmU64Stroops(amount));
1983
+ generateCoinForDepositWithSharedHex(scalarHex, sharedXHex, sharedYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
1984
+ return this.wasm.generateCoinForDepositWithSharedHex(scalarHex, sharedXHex, sharedYHex, wasmU64Stroops(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal);
1920
1985
  }
1921
1986
  /**
1922
1987
  * Merkle root, path, and coin fields for the first withdraw leg (Rust LeanIMT + Poseidon).
@@ -1929,19 +1994,57 @@ class PrivacyPoolSDK {
1929
1994
  }
1930
1995
  /**
1931
1996
  * Full `Transaction(20,2,2)` withdrawal proof: one real withdraw + dummies, using coin/state and depositor ECDH point (hex).
1997
+ *
1998
+ * Optional **partial public withdraw**: spend the full coin commitment `coin.value` (V), send `publicWithdrawStroops` (W) to the
1999
+ * Stellar receiver, and re-deposit the remainder (V−W) as a new private note to `changeRecipientStealthAddress` (same circuit balance).
1932
2000
  */
1933
2001
  async proveWithdrawal(coin, state, params) {
1934
2002
  const witness = this.buildWithdrawMerkleWitness(coin, state);
1935
2003
  const w0 = withdrawObjectFromMerkleWitness(witness, {
1936
2004
  x: params.ephemeralXHex,
1937
2005
  y: params.ephemeralYHex,
1938
- });
2006
+ }, params.applicationId ?? coin.application_id ?? '0');
2007
+ const fullV = BigInt(coin.value);
2008
+ let publicWithdrawals;
2009
+ let deposits;
2010
+ if (params.publicWithdrawStroops !== undefined) {
2011
+ const W = params.publicWithdrawStroops;
2012
+ if (params.changeRecipientStealthAddress === undefined) {
2013
+ throw new Error('proveWithdrawal: changeRecipientStealthAddress is required when publicWithdrawStroops is set');
2014
+ }
2015
+ if (W <= 0n || W >= fullV) {
2016
+ throw new Error('proveWithdrawal: publicWithdrawStroops must be strictly between 0 and coin.value');
2017
+ }
2018
+ const change = fullV - W;
2019
+ publicWithdrawals = [W.toString()];
2020
+ const changeDeposit = {
2021
+ value: change.toString(),
2022
+ nullifier: randomFrDecimal(),
2023
+ ephemeralKeyScalar: randomFrDecimal253(),
2024
+ asset: [coin.asset_hi, coin.asset_lo],
2025
+ applicationId: params.applicationId ?? '0',
2026
+ recipientPublicKeys: recipientPublicKeysDecimalFromStealthAddress(params.changeRecipientStealthAddress),
2027
+ };
2028
+ deposits = [changeDeposit, 'dummy'];
2029
+ }
2030
+ else {
2031
+ publicWithdrawals = [coin.value];
2032
+ deposits = ['dummy', 'dummy'];
2033
+ }
2034
+ // On-chain `transact` sends `publicWithdrawals` / `publicWithdrawnAssets` to the withdraw account.
2035
+ const publicLegs = {
2036
+ publicWithdrawnAssets: [[coin.asset_hi, coin.asset_lo]],
2037
+ publicDepositedAssets: [['0', '0']],
2038
+ publicDeposits: ['0'],
2039
+ publicWithdrawals,
2040
+ };
1939
2041
  return this.proveTransaction({
1940
2042
  stateRoot: witness.stateRoot,
1941
2043
  withdrawAddressHi: params.withdrawAddressHi,
1942
2044
  withdrawAddressLo: params.withdrawAddressLo,
1943
2045
  privKeyScalar: params.privKeyScalar,
1944
- }, [w0, 'dummy'], ['dummy', 'dummy']);
2046
+ }, publicLegs, [w0, 'dummy'], deposits, params.audit
2047
+ ?? resolveTransactionAuditParams(params.applicationId ?? coin.application_id ?? DEFAULT_APPLICATION_ID, DEMO_AUDIT_PUBLIC_KEY));
1945
2048
  }
1946
2049
  /**
1947
2050
  * Convert a snarkjs proof JSON to hex bytes for Soroban.
@@ -1969,14 +2072,15 @@ class PrivacyPoolSDK {
1969
2072
  * `Transaction(20,2,2)` proof from high-level legs: maps to witness input (incl. `"dummy"` ECDH via WASM), then Groth16 → Soroban hex.
1970
2073
  *
1971
2074
  * @param publicParams Public inputs: `stateRoot`, `withdrawAddressHi` / `withdrawAddressLo`, `privKeyScalar` (decimal field strings).
2075
+ * @param publicLegs Public token legs (`publicWithdrawnAssets`, `publicDepositedAssets`, `publicDeposits`, `publicWithdrawals`).
1972
2076
  * @param withdraws Exactly two withdraw slots (`WithdrawObject` or `"dummy"`).
1973
2077
  * @param deposits Exactly two deposit slots (`DepositObject` or `"dummy"`).
1974
2078
  */
1975
- async proveTransaction(publicParams, withdraws, deposits) {
2079
+ async proveTransaction(publicParams, publicLegs, withdraws, deposits, audit) {
1976
2080
  const wasmEcdh = {
1977
2081
  ecdhEphemeralPublicKeyFromScalarHex: (h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h),
1978
2082
  };
1979
- const witnessInput = buildTransactionWitnessInput(publicParams, withdraws, deposits, wasmEcdh);
2083
+ const witnessInput = buildTransactionWitnessInput(publicParams, publicLegs, withdraws, deposits, audit, wasmEcdh);
1980
2084
  const wtns = await generateWitness(witnessInput, this.options.circuitWasm);
1981
2085
  const { proof, publicSignals } = await generateProof(wtns, this.options.zkey);
1982
2086
  return {
@@ -2019,10 +2123,191 @@ class PrivacyPoolSDK {
2019
2123
  }
2020
2124
  }
2021
2125
 
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
+ function signatureBase64ToBytes(signature) {
2159
+ const value = signature.trim();
2160
+ if (/^(0x)?[0-9a-fA-F]{128}$/.test(value)) {
2161
+ return Buffer.from(value.replace(/^0x/i, ''), 'hex');
2162
+ }
2163
+ return Buffer.from(value, 'base64');
2164
+ }
2165
+
2166
+ function passageIdHexToScVal(passageIdHex) {
2167
+ const bytes = Buffer.from(passageIdHex.replace(/^0x/, ''), 'hex');
2168
+ return stellarSdk.xdr.ScVal.scvBytes(bytes);
2169
+ }
2170
+ function signatureToScVal(signature) {
2171
+ return stellarSdk.xdr.ScVal.scvBytes(signature);
2172
+ }
2173
+ function helperSubmitWithPassageOperation(helperContractId, params, passageId, expiresAtLedger, signature) {
2174
+ return stellarSdk.Operation.invokeContractFunction({
2175
+ contract: helperContractId,
2176
+ function: 'submit_with_passage',
2177
+ args: [
2178
+ addressToScVal(params.owner),
2179
+ stellarSdk.xdr.ScVal.scvBytes(params.proofBytes),
2180
+ stellarSdk.xdr.ScVal.scvBytes(params.publicSignalsBytes),
2181
+ onboardingOptionToScVal(params.onboarding),
2182
+ passageIdHexToScVal(passageId),
2183
+ stellarSdk.xdr.ScVal.scvU32(expiresAtLedger),
2184
+ signatureToScVal(signatureBase64ToBytes(signature)),
2185
+ ],
2186
+ });
2187
+ }
2188
+ async function submitTransaction(params) {
2189
+ const server = new stellarSdk.rpc.Server(params.rpcUrl);
2190
+ const sourceAccount = await server.getAccount(params.source.publicKey());
2191
+ let builder = new stellarSdk.TransactionBuilder(sourceAccount, {
2192
+ fee: params.fee,
2193
+ networkPassphrase: params.networkPassphrase,
2194
+ });
2195
+ for (const operation of params.operations) {
2196
+ builder = builder.addOperation(operation);
2197
+ }
2198
+ const tx = builder.setTimeout(300).build();
2199
+ const prepared = await prepareTransactionAllowingNonRootAuth(server, params.rpcUrl, tx, params.source, params.networkPassphrase)
2200
+ ;
2201
+ prepared.sign(params.source);
2202
+ const result = await server.sendTransaction(prepared);
2203
+ const status = String(result.status);
2204
+ if (status !== 'PENDING' && status !== 'SUCCESS') {
2205
+ throw new Error(`transaction submit failed: ${status}`);
2206
+ }
2207
+ await waitForTransaction(server, result.hash);
2208
+ return result.hash;
2209
+ }
2210
+ async function prepareTransactionAllowingNonRootAuth(server, rpcUrl, tx, source, networkPassphrase) {
2211
+ const recording = await simulateTransactionRecordingNonRoot(rpcUrl, tx.toXDR());
2212
+ const validUntilLedgerSeq = recording.latestLedger + 1000;
2213
+ const signedAuth = await Promise.all(recording.auth.map((entry) => {
2214
+ if (entry.credentials().switch()
2215
+ === stellarSdk.xdr.SorobanCredentialsType.sorobanCredentialsSourceAccount()) {
2216
+ return entry;
2217
+ }
2218
+ return stellarSdk.authorizeEntry(entry, source, validUntilLedgerSeq, networkPassphrase);
2219
+ }));
2220
+ const invokeOp = tx.operations[0];
2221
+ if (tx.operations.length !== 1 || invokeOp.type !== 'invokeHostFunction') {
2222
+ throw new Error('Soroban transactions must contain exactly one invokeHostFunction operation');
2223
+ }
2224
+ const sourceAccount = await server.getAccount(source.publicKey());
2225
+ const signedTx = new stellarSdk.TransactionBuilder(sourceAccount, {
2226
+ fee: tx.fee,
2227
+ networkPassphrase,
2228
+ })
2229
+ .addOperation(stellarSdk.Operation.invokeHostFunction({
2230
+ func: invokeOp.func,
2231
+ auth: signedAuth,
2232
+ source: invokeOp.source,
2233
+ }))
2234
+ .setTimeout(300)
2235
+ .build();
2236
+ const enforcedSimulation = await server.simulateTransaction(signedTx);
2237
+ if (stellarSdk.rpc.Api.isSimulationError(enforcedSimulation)) {
2238
+ throw new Error(`signed authorization simulation failed: ${enforcedSimulation.error}`);
2239
+ }
2240
+ return stellarSdk.rpc.assembleTransaction(signedTx, enforcedSimulation).build();
2241
+ }
2242
+ async function simulateTransactionRecordingNonRoot(rpcUrl, transactionXdr) {
2243
+ const response = await fetch(rpcUrl, {
2244
+ method: 'POST',
2245
+ headers: { 'content-type': 'application/json' },
2246
+ body: JSON.stringify({
2247
+ jsonrpc: '2.0',
2248
+ id: 1,
2249
+ method: 'simulateTransaction',
2250
+ params: {
2251
+ transaction: transactionXdr,
2252
+ authMode: 'record_allow_nonroot',
2253
+ },
2254
+ }),
2255
+ });
2256
+ const body = await response.json();
2257
+ if (!response.ok || body.error) {
2258
+ throw new Error(`transaction simulation failed: ${body.error?.message ?? response.statusText}`);
2259
+ }
2260
+ if (!body.result) {
2261
+ throw new Error('transaction simulation failed: missing RPC result');
2262
+ }
2263
+ if (body.result.error) {
2264
+ throw new Error(`transaction simulation failed: ${body.result.error}`);
2265
+ }
2266
+ const latestLedger = Number(body.result.latestLedger);
2267
+ if (!Number.isFinite(latestLedger)) {
2268
+ throw new Error('transaction simulation failed: missing latest ledger');
2269
+ }
2270
+ return {
2271
+ latestLedger,
2272
+ auth: (body.result.results?.[0]?.auth ?? []).map((entry) => stellarSdk.xdr.SorobanAuthorizationEntry.fromXDR(entry, 'base64')),
2273
+ };
2274
+ }
2275
+ async function waitForTransaction(server, hash) {
2276
+ for (let attempt = 0; attempt < 60; attempt++) {
2277
+ const result = await server.getTransaction(hash);
2278
+ const status = String(result.status);
2279
+ if (status === 'SUCCESS') {
2280
+ return;
2281
+ }
2282
+ if (status === 'FAILED') {
2283
+ throw new Error(`transaction failed: ${hash}`);
2284
+ }
2285
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2286
+ }
2287
+ throw new Error(`transaction did not confirm in time: ${hash}`);
2288
+ }
2289
+ async function submitApprovedKytPassage(params) {
2290
+ if (params.source.publicKey() !== params.owner) {
2291
+ throw new Error('KYT submit source keypair must match pool transact owner');
2292
+ }
2293
+ return submitTransaction({
2294
+ source: params.source,
2295
+ networkPassphrase: params.networkPassphrase,
2296
+ rpcUrl: params.rpcUrl,
2297
+ fee: '12000000',
2298
+ operations: [
2299
+ helperSubmitWithPassageOperation(params.kytSubmitHelperId, params, params.passageId, params.expiresAtLedger, params.signature),
2300
+ ],
2301
+ });
2302
+ }
2303
+
2022
2304
  /** Pool Merkle tree depth (matches `coin::TREE_DEPTH` / `Transaction` circuit). */
2023
2305
  /** Default coin value in stroops (1 XLM); matches Rust `coin::COIN_VALUE`. */
2024
2306
  const COIN_VALUE_STROOPS = 1000000000;
2025
2307
 
2308
+ function resolveApplicationIdFromCli(parsed) {
2309
+ return parsed['application-id'] ?? process.env.APPLICATION_ID ?? DEFAULT_APPLICATION_ID;
2310
+ }
2026
2311
  async function main() {
2027
2312
  const args = process.argv.slice(2);
2028
2313
  if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
@@ -2051,6 +2336,9 @@ async function main() {
2051
2336
  else if (command === 'priv-scalar-from-signature') {
2052
2337
  await handlePrivScalarFromSignature(args.slice(1));
2053
2338
  }
2339
+ else if (command === 'kyt-submit-approved') {
2340
+ await handleKytSubmitApproved(args.slice(1));
2341
+ }
2054
2342
  else {
2055
2343
  console.error(`Unknown command: ${command}`);
2056
2344
  printUsage();
@@ -2066,6 +2354,8 @@ Commands:
2066
2354
  generate Generate a new coin
2067
2355
  --output, -o <file> Output coin to file (default: stdout)
2068
2356
  --amount <stroops> Coin value in stroops (u64); default: 1000000000 (1 XLM)
2357
+ --token <C...> Stellar asset contract id (or set TOKEN_ADDRESS)
2358
+ --application-id <dec> Application id (decimal Fr); default: APPLICATION_ID env or 101
2069
2359
  --scalar <hex> 32-byte depositor ephemeral scalar (64 hex, optional 0x); coin.secret = Poseidon₁(scalar) (deposit.circom)
2070
2360
  --stealth <stpl1...> With --scalar: ECDH(scalar, recipient) shared secret + aligned commitment (requires WASM ecdhSharedKey)
2071
2361
 
@@ -2081,6 +2371,19 @@ Commands:
2081
2371
 
2082
2372
  priv-scalar-from-signature Print privKeyScalar (decimal Fr) from Stellar Ed25519 signature (SHA-256(sig) mod r)
2083
2373
 
2374
+ kyt-submit-approved Submit helper.register_passage + pool.transact with signed non-root auth
2375
+ --source-secret <S...> Secret seed for the depositing/withdrawing Stellar account (or SOURCE_SECRET env)
2376
+ --owner <G...> Same account address passed to pool.transact as from
2377
+ --pool <C...> Privacy pool contract id
2378
+ --helper <C...> KYT submit helper contract id
2379
+ --proof <hex> Groth16 proof bytes
2380
+ --public-signals <hex> Public signals bytes
2381
+ --passage-id <hex> KYT passage id
2382
+ --expires-at-ledger <u32> Passage expiration ledger
2383
+ --signature <hex|base64> KYT registry signature
2384
+ --rpc-url <url> Soroban RPC URL
2385
+ --network <testnet|public|local> or --network-passphrase <string>
2386
+
2084
2387
  withdraw Generate a withdrawal proof
2085
2388
  --coin <file> Path to coin JSON file
2086
2389
  --state <file> Path to state JSON file
@@ -2090,15 +2393,20 @@ Commands:
2090
2393
  --priv-key-scalar <dec> privKeyScalar (decimal Fr)
2091
2394
  --ephemeral-x <hex> Depositor ECDH point x (64 hex chars, optional 0x)
2092
2395
  --ephemeral-y <hex> Depositor ECDH point y (64 hex chars, optional 0x)
2396
+ --public-withdraw-stroops <u64> Optional: amount to withdraw publicly (must be < coin.value). Rest stays in pool as change.
2397
+ --change-stealth <stpl1...> Required with --public-withdraw-stroops: stealth for the change note (same as deposit)
2398
+ --application-id <dec> Application id (decimal Fr); default: APPLICATION_ID env or 101
2093
2399
  --output-proof <file> Write proof hex to file
2094
2400
  --output-public <file> Write public signals hex to file
2095
2401
 
2096
2402
  deposit-proof Groth16 proof for deposit-only transact (2 dummy withdraws, 1 deposit + dummy)
2097
2403
  --state-root <dec> Current Merkle root (decimal Fr), must match pool on submit
2098
2404
  --stealth <stpl1...> Recipient stealth address (decodes to recipient public key)
2405
+ --token <C...> Asset contract id (or set TOKEN_ADDRESS)
2099
2406
  --coin <file> With --ephemeral-scalar-hex: use coin nullifier/value (aligned with generate --scalar --stealth)
2100
2407
  --ephemeral-scalar-hex <hex> Same 32-byte hex as deposit / generate --scalar
2101
2408
  --value <dec> Deposit amount (stroops as decimal Fr); optional if --coin (must match coin)
2409
+ --application-id <dec> Application id (decimal Fr); default: APPLICATION_ID env or 101
2102
2410
  --output-proof <file> Write proof hex to file
2103
2411
  --output-public <file> Write public signals hex to file
2104
2412
 
@@ -2106,6 +2414,39 @@ Output (withdraw, deposit-proof):
2106
2414
  Prints proof_hex on first line and public_hex on second line to stdout.
2107
2415
  `);
2108
2416
  }
2417
+ function requireParsedArg(parsed, name) {
2418
+ const value = parsed[name]?.trim();
2419
+ if (!value) {
2420
+ console.error(`Error: --${name} is required`);
2421
+ process.exit(1);
2422
+ }
2423
+ return value;
2424
+ }
2425
+ function networkPassphraseFromArgs(parsed) {
2426
+ if (parsed['network-passphrase']) {
2427
+ return parsed['network-passphrase'];
2428
+ }
2429
+ const network = parsed['network'] ?? 'testnet';
2430
+ if (network === 'testnet') {
2431
+ return stellarSdk.Networks.TESTNET;
2432
+ }
2433
+ if (network === 'public') {
2434
+ return stellarSdk.Networks.PUBLIC;
2435
+ }
2436
+ if (network === 'local') {
2437
+ return 'Standalone Network ; February 2017';
2438
+ }
2439
+ console.error('Error: --network must be testnet, public, or local');
2440
+ process.exit(1);
2441
+ }
2442
+ function hexToBuffer(label, value) {
2443
+ const hex = value.trim().replace(/^0x/i, '');
2444
+ if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
2445
+ console.error(`Error: --${label} must be even-length hex`);
2446
+ process.exit(1);
2447
+ }
2448
+ return Buffer.from(hex, 'hex');
2449
+ }
2109
2450
  function parseArgs(args) {
2110
2451
  const parsed = {};
2111
2452
  for (let i = 0; i < args.length; i++) {
@@ -2176,6 +2517,28 @@ async function handlePrivScalarFromSignature(args) {
2176
2517
  function handleRandomScalar() {
2177
2518
  console.log(PrivacyPoolSDK.generateRandomScalarHex32());
2178
2519
  }
2520
+ async function handleKytSubmitApproved(args) {
2521
+ const parsed = parseArgs(args);
2522
+ const sourceSecret = parsed['source-secret']?.trim() ?? process.env.SOURCE_SECRET?.trim();
2523
+ if (!sourceSecret) {
2524
+ console.error('Error: --source-secret <S...> or SOURCE_SECRET env is required');
2525
+ process.exit(1);
2526
+ }
2527
+ const txHash = await submitApprovedKytPassage({
2528
+ source: stellarSdk.Keypair.fromSecret(sourceSecret),
2529
+ networkPassphrase: networkPassphraseFromArgs(parsed),
2530
+ rpcUrl: parsed['rpc-url'] ?? 'https://soroban-testnet.stellar.org',
2531
+ poolContract: requireParsedArg(parsed, 'pool'),
2532
+ owner: requireParsedArg(parsed, 'owner'),
2533
+ proofBytes: hexToBuffer('proof', requireParsedArg(parsed, 'proof')),
2534
+ publicSignalsBytes: hexToBuffer('public-signals', requireParsedArg(parsed, 'public-signals')),
2535
+ kytSubmitHelperId: requireParsedArg(parsed, 'helper'),
2536
+ passageId: requireParsedArg(parsed, 'passage-id'),
2537
+ expiresAtLedger: Number(requireParsedArg(parsed, 'expires-at-ledger')),
2538
+ signature: requireParsedArg(parsed, 'signature'),
2539
+ });
2540
+ console.log(txHash);
2541
+ }
2179
2542
  function parseStroopsU64(label, raw, defaultStroops) {
2180
2543
  if (raw === undefined) {
2181
2544
  return defaultStroops;
@@ -2203,6 +2566,13 @@ async function handleGenerate(args) {
2203
2566
  const scalar = parsed['scalar'];
2204
2567
  const stealth = parsed['stealth'];
2205
2568
  const amount = parseStroopsU64('--amount', parsed['amount'], BigInt(COIN_VALUE_STROOPS));
2569
+ const applicationId = resolveApplicationIdFromCli(parsed);
2570
+ const token = parsed['token'] ?? process.env.TOKEN_ADDRESS;
2571
+ if (!token) {
2572
+ console.error('Error: --token <C...> or TOKEN_ADDRESS is required');
2573
+ process.exit(1);
2574
+ }
2575
+ const [assetHi, assetLo] = stellarContractAddressToAssetFrDecimals(token);
2206
2576
  if (stealth && !scalar) {
2207
2577
  console.error('Error: --stealth requires --scalar');
2208
2578
  process.exit(1);
@@ -2211,13 +2581,13 @@ async function handleGenerate(args) {
2211
2581
  if (scalar && stealth) {
2212
2582
  const { x, y } = decodeStealthAddress(stealth);
2213
2583
  const shared = sdk.ecdhSharedKey(scalar, x, y);
2214
- coin = sdk.generateCoinForDepositWithSharedHex(scalar, shared.x, shared.y, amount);
2584
+ coin = sdk.generateCoinForDepositWithSharedHex(scalar, shared.x, shared.y, amount, assetHi, assetLo, applicationId);
2215
2585
  }
2216
2586
  else if (scalar) {
2217
- coin = sdk.generateCoinFromDepositEphemeralScalarHex(scalar, amount);
2587
+ coin = sdk.generateCoinFromDepositEphemeralScalarHex(scalar, amount, assetHi, assetLo, applicationId);
2218
2588
  }
2219
2589
  else {
2220
- coin = sdk.generateCoin(amount);
2590
+ coin = sdk.generateCoin(amount, assetHi, assetLo, applicationId);
2221
2591
  }
2222
2592
  const json = JSON.stringify(coin, null, 2);
2223
2593
  if (parsed['output']) {
@@ -2262,13 +2632,33 @@ async function handleWithdraw(args) {
2262
2632
  const coinFile = JSON.parse(fs__namespace.readFileSync(parsed['coin'], 'utf-8'));
2263
2633
  const coin = coinFile.coin || coinFile;
2264
2634
  const state = JSON.parse(fs__namespace.readFileSync(parsed['state'], 'utf-8'));
2635
+ const pubW = parsed['public-withdraw-stroops'];
2636
+ const changeStealth = parsed['change-stealth'];
2637
+ if (pubW !== undefined && changeStealth === undefined) {
2638
+ console.error('Error: --change-stealth <stpl1...> is required when using --public-withdraw-stroops');
2639
+ process.exit(1);
2640
+ }
2641
+ if (pubW === undefined && changeStealth !== undefined) {
2642
+ console.error('Error: --change-stealth is only valid with --public-withdraw-stroops');
2643
+ process.exit(1);
2644
+ }
2265
2645
  const sdk = await PrivacyPoolSDK.init();
2646
+ const applicationId = resolveApplicationIdFromCli(parsed);
2647
+ const audit = resolveTransactionAuditParams(applicationId);
2266
2648
  const result = await sdk.proveWithdrawal(coin, state, {
2267
2649
  withdrawAddressHi: withdrawHi,
2268
2650
  withdrawAddressLo: withdrawLo,
2269
2651
  privKeyScalar: parsed['priv-key-scalar'],
2270
2652
  ephemeralXHex: parsed['ephemeral-x'],
2271
2653
  ephemeralYHex: parsed['ephemeral-y'],
2654
+ applicationId,
2655
+ audit,
2656
+ ...(pubW !== undefined
2657
+ ? {
2658
+ publicWithdrawStroops: parseStroopsU64('--public-withdraw-stroops', pubW, 0n),
2659
+ changeRecipientStealthAddress: changeStealth,
2660
+ }
2661
+ : {}),
2272
2662
  });
2273
2663
  // Output proof and public hex to stdout (newline-separated)
2274
2664
  console.log(result.proof_hex);
@@ -2293,7 +2683,15 @@ async function handleDepositProof(args) {
2293
2683
  console.error('Error: --stealth <stpl1...> is required');
2294
2684
  process.exit(1);
2295
2685
  }
2686
+ const token = parsed['token'] ?? process.env.TOKEN_ADDRESS;
2687
+ if (!token) {
2688
+ console.error('Error: --token <C...> or TOKEN_ADDRESS is required');
2689
+ process.exit(1);
2690
+ }
2691
+ const [assetHi, assetLo] = stellarContractAddressToAssetFrDecimals(token);
2296
2692
  const sdk = await PrivacyPoolSDK.init();
2693
+ const applicationId = resolveApplicationIdFromCli(parsed);
2694
+ const audit = resolveTransactionAuditParams(applicationId);
2297
2695
  const recipientPublicKeys = recipientPublicKeysDecimalFromStealthAddress(parsed['stealth']);
2298
2696
  const coinPath = parsed['coin'];
2299
2697
  const ephemeralScalarHex = parsed['ephemeral-scalar-hex'];
@@ -2310,11 +2708,15 @@ async function handleDepositProof(args) {
2310
2708
  console.error('Error: --value must match coin.value when using --coin');
2311
2709
  process.exit(1);
2312
2710
  }
2711
+ const ah = c.asset_hi ?? assetHi;
2712
+ const al = c.asset_lo ?? assetLo;
2313
2713
  deposit = {
2314
2714
  value,
2315
2715
  nullifier: c.nullifier,
2316
2716
  ephemeralKeyScalar: scalarHexToFrDecimal(ephemeralScalarHex),
2317
2717
  recipientPublicKeys,
2718
+ asset: [ah, al],
2719
+ applicationId,
2318
2720
  };
2319
2721
  }
2320
2722
  else {
@@ -2327,14 +2729,22 @@ async function handleDepositProof(args) {
2327
2729
  nullifier: randomFrDecimal(),
2328
2730
  ephemeralKeyScalar: randomFrDecimal253(),
2329
2731
  recipientPublicKeys,
2732
+ asset: [assetHi, assetLo],
2733
+ applicationId,
2330
2734
  };
2331
2735
  }
2736
+ const publicLegs = {
2737
+ publicWithdrawnAssets: [['0', '0']],
2738
+ publicDepositedAssets: [[deposit.asset[0], deposit.asset[1]]],
2739
+ publicDeposits: [deposit.value],
2740
+ publicWithdrawals: ['0'],
2741
+ };
2332
2742
  const result = await sdk.proveTransaction({
2333
2743
  stateRoot: parsed['state-root'],
2334
2744
  withdrawAddressHi: '0',
2335
2745
  withdrawAddressLo: '0',
2336
2746
  privKeyScalar: randomFrDecimal253(),
2337
- }, ['dummy', 'dummy'], [deposit, 'dummy']);
2747
+ }, publicLegs, ['dummy', 'dummy'], [deposit, 'dummy'], audit);
2338
2748
  console.log(result.proof_hex);
2339
2749
  console.log(result.public_hex);
2340
2750
  if (parsed['output-proof']) {