@auditable/privacy-pool-zk-sdk 0.8.0 → 0.9.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,32 +3,27 @@
3
3
 
4
4
  var fs = require('fs');
5
5
  var stellarSdk = require('@stellar/stellar-sdk');
6
- var snarkjs = require('snarkjs');
6
+ var circomlibjs = require('circomlibjs');
7
7
 
8
8
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
9
9
  function _interopNamespaceDefault(e) {
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
20
- });
21
- }
22
- n.default = e;
23
- return Object.freeze(n);
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
24
  }
25
25
 
26
26
  var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
27
- var snarkjs__namespace = /*#__PURE__*/_interopNamespaceDefault(snarkjs);
28
-
29
- function getDefaultExportFromCjs (x) {
30
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
31
- }
32
27
 
33
28
  var dist = {};
34
29
 
@@ -205,7 +200,7 @@ dist.bech32m = getLibraryFromEncoding('bech32m');
205
200
  const STEALTH_ADDRESS_HRP = 'stpl1';
206
201
  /** BIP-173 default 90 is too small for 64-byte payload (32+32 field coords) after 8→5 bit conversion. */
207
202
  const BECH32_STEALTH_LIMIT = 1023;
208
- function normalizeHex$2(hex) {
203
+ function normalizeHex$3(hex) {
209
204
  const s = hex.trim().replace(/^0x/i, '');
210
205
  if (!/^[0-9a-fA-F]*$/.test(s)) {
211
206
  throw new Error('stealth-address: hex must contain only 0-9, a-f');
@@ -213,7 +208,7 @@ function normalizeHex$2(hex) {
213
208
  return s.length % 2 === 0 ? s : `0${s}`;
214
209
  }
215
210
  function hexToBytes$2(hex) {
216
- const norm = normalizeHex$2(hex);
211
+ const norm = normalizeHex$3(hex);
217
212
  const out = new Uint8Array(norm.length / 2);
218
213
  for (let i = 0; i < out.length; i++) {
219
214
  out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
@@ -297,6 +292,9 @@ function buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH
297
292
  ].join('\n');
298
293
  }
299
294
 
295
+ const DOM_ENC = 1346719299n;
296
+ const DOM_AUDIT_TAG = 1346458695n;
297
+ const DOM_OUTPUT_NOTE_TAG = 1347310663n;
300
298
  const BABYJUB_SUBGROUP_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
301
299
  /**
302
300
  * Map any integer into `(0, l)` so it satisfies circom `LessThan(252)` vs
@@ -774,6 +772,38 @@ function proofToHex(proof_json) {
774
772
  }
775
773
  }
776
774
 
775
+ /**
776
+ * Graph witness + arkworks Groth16 prove. Returns `{ proof_hex, public_hex }`.
777
+ * @param {Uint8Array} graph
778
+ * @param {Uint8Array} proving_key
779
+ * @param {Uint8Array} r1cs
780
+ * @param {string} inputs_json
781
+ * @returns {any}
782
+ */
783
+ function proveGroth16(graph, proving_key, r1cs, inputs_json) {
784
+ try {
785
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
786
+ const ptr0 = passArray8ToWasm0(graph, wasm.__wbindgen_export);
787
+ const len0 = WASM_VECTOR_LEN;
788
+ const ptr1 = passArray8ToWasm0(proving_key, wasm.__wbindgen_export);
789
+ const len1 = WASM_VECTOR_LEN;
790
+ const ptr2 = passArray8ToWasm0(r1cs, wasm.__wbindgen_export);
791
+ const len2 = WASM_VECTOR_LEN;
792
+ const ptr3 = passStringToWasm0(inputs_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
793
+ const len3 = WASM_VECTOR_LEN;
794
+ wasm.proveGroth16(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
795
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
796
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
797
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
798
+ if (r2) {
799
+ throw takeObject(r1);
800
+ }
801
+ return takeObject(r0);
802
+ } finally {
803
+ wasm.__wbindgen_add_to_stack_pointer(16);
804
+ }
805
+ }
806
+
777
807
  /**
778
808
  * Convert snarkjs public signals JSON to hex bytes for Soroban contract.
779
809
  * @param {string} public_json
@@ -984,6 +1014,13 @@ function isLikeNone(x) {
984
1014
  return x === undefined || x === null;
985
1015
  }
986
1016
 
1017
+ function passArray8ToWasm0(arg, malloc) {
1018
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
1019
+ getUint8ArrayMemory0().set(arg, ptr / 1);
1020
+ WASM_VECTOR_LEN = arg.length;
1021
+ return ptr;
1022
+ }
1023
+
987
1024
  function passStringToWasm0(arg, malloc, realloc) {
988
1025
  if (realloc === undefined) {
989
1026
  const buf = cachedTextEncoder.encode(arg);
@@ -1146,29 +1183,30 @@ async function __wbg_init(module_or_path) {
1146
1183
  }
1147
1184
 
1148
1185
  var wasmBindings = /*#__PURE__*/Object.freeze({
1149
- __proto__: null,
1150
- buildWithdrawMerkleWitness: buildWithdrawMerkleWitness,
1151
- calculateNullifierHash: calculateNullifierHash,
1152
- default: __wbg_init,
1153
- derivedEscrowKey: derivedEscrowKey,
1154
- ecdhEphemeralPublicKey: ecdhEphemeralPublicKey,
1155
- ecdhEphemeralPublicKeyFromScalarHex: ecdhEphemeralPublicKeyFromScalarHex,
1156
- ecdhSharedKey: ecdhSharedKey,
1157
- generateCoin: generateCoin,
1158
- generateCoinForDepositWithOwnerPubHex: generateCoinForDepositWithOwnerPubHex,
1159
- generateCoinFromDepositEphemeralScalarHex: generateCoinFromDepositEphemeralScalarHex,
1160
- generateCoinWithOwnerPubHex: generateCoinWithOwnerPubHex,
1161
- initSync: initSync,
1162
- proofToHex: proofToHex,
1163
- publicToHex: publicToHex
1186
+ __proto__: null,
1187
+ buildWithdrawMerkleWitness: buildWithdrawMerkleWitness,
1188
+ calculateNullifierHash: calculateNullifierHash,
1189
+ default: __wbg_init,
1190
+ derivedEscrowKey: derivedEscrowKey,
1191
+ ecdhEphemeralPublicKey: ecdhEphemeralPublicKey,
1192
+ ecdhEphemeralPublicKeyFromScalarHex: ecdhEphemeralPublicKeyFromScalarHex,
1193
+ ecdhSharedKey: ecdhSharedKey,
1194
+ generateCoin: generateCoin,
1195
+ generateCoinForDepositWithOwnerPubHex: generateCoinForDepositWithOwnerPubHex,
1196
+ generateCoinFromDepositEphemeralScalarHex: generateCoinFromDepositEphemeralScalarHex,
1197
+ generateCoinWithOwnerPubHex: generateCoinWithOwnerPubHex,
1198
+ initSync: initSync,
1199
+ proofToHex: proofToHex,
1200
+ proveGroth16: proveGroth16,
1201
+ publicToHex: publicToHex
1164
1202
  });
1165
1203
 
1166
- const isNode$2 = typeof process !== 'undefined' && !!process.versions?.node;
1204
+ const isNode$1 = typeof process !== 'undefined' && !!process.versions?.node;
1167
1205
  let wasmModule = null;
1168
1206
  async function loadWasm(wasmBinary) {
1169
1207
  if (wasmModule)
1170
1208
  return wasmModule;
1171
- if (isNode$2) {
1209
+ if (isNode$1) {
1172
1210
  if (wasmBinary) {
1173
1211
  initSync({ module: wasmBinary });
1174
1212
  }
@@ -1195,464 +1233,178 @@ async function loadWasm(wasmBinary) {
1195
1233
  return wasmModule;
1196
1234
  }
1197
1235
 
1198
- var witness_calculator = async function builder(code, options) {
1199
-
1200
- options = options || {};
1201
-
1202
- let wasmModule;
1203
- try {
1204
- wasmModule = await WebAssembly.compile(code);
1205
- } catch (err) {
1206
- console.log(err);
1207
- console.log("\nTry to run circom --c in order to generate c++ code instead\n");
1208
- throw new Error(err);
1209
- }
1210
-
1211
- let wc;
1212
-
1213
- let errStr = "";
1214
- let msgStr = "";
1215
-
1216
- const instance = await WebAssembly.instantiate(wasmModule, {
1217
- runtime: {
1218
- exceptionHandler : function(code) {
1219
- let err;
1220
- if (code == 1) {
1221
- err = "Signal not found.\n";
1222
- } else if (code == 2) {
1223
- err = "Too many signals set.\n";
1224
- } else if (code == 3) {
1225
- err = "Signal already set.\n";
1226
- } else if (code == 4) {
1227
- err = "Assert Failed.\n";
1228
- } else if (code == 5) {
1229
- err = "Not enough memory.\n";
1230
- } else if (code == 6) {
1231
- err = "Input signal array access exceeds the size.\n";
1232
- } else if (code == 7) {
1233
- err = "Out of bounds array access.\n";
1234
- } else {
1235
- err = "Unknown error.\n";
1236
- }
1237
- throw new Error(err + errStr);
1238
- },
1239
- printErrorMessage : function() {
1240
- errStr += getMessage() + "\n";
1241
- // console.error(getMessage());
1242
- },
1243
- writeBufferMessage : function() {
1244
- const msg = getMessage();
1245
- // Any calls to `log()` will always end with a `\n`, so that's when we print and reset
1246
- if (msg === "\n") {
1247
- console.log(msgStr);
1248
- msgStr = "";
1249
- } else {
1250
- // If we've buffered other content, put a space in between the items
1251
- if (msgStr !== "") {
1252
- msgStr += " ";
1253
- }
1254
- // Then append the message to the message we are creating
1255
- msgStr += msg;
1256
- }
1257
- },
1258
- showSharedRWMemory : function() {
1259
- printSharedRWMemory ();
1260
- }
1261
-
1262
- }
1263
- });
1264
-
1265
- const sanityCheck =
1266
- options;
1267
- // options &&
1268
- // (
1269
- // options.sanityCheck ||
1270
- // options.logGetSignal ||
1271
- // options.logSetSignal ||
1272
- // options.logStartComponent ||
1273
- // options.logFinishComponent
1274
- // );
1275
-
1276
-
1277
- wc = new WitnessCalculator(instance, sanityCheck);
1278
- return wc;
1279
-
1280
- function getMessage() {
1281
- var message = "";
1282
- var c = instance.exports.getMessageChar();
1283
- while ( c != 0 ) {
1284
- message += String.fromCharCode(c);
1285
- c = instance.exports.getMessageChar();
1286
- }
1287
- return message;
1288
- }
1289
-
1290
- function printSharedRWMemory () {
1291
- const shared_rw_memory_size = instance.exports.getFieldNumLen32();
1292
- const arr = new Uint32Array(shared_rw_memory_size);
1293
- for (let j=0; j<shared_rw_memory_size; j++) {
1294
- arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
1295
- }
1296
-
1297
- // If we've buffered other content, put a space in between the items
1298
- if (msgStr !== "") {
1299
- msgStr += " ";
1300
- }
1301
- // Then append the value to the message we are creating
1302
- msgStr += (fromArray32(arr).toString());
1303
- }
1304
-
1236
+ /** Off-chain ZK layout table. Must stay byte-identical to `libs/zk::ZkLayoutParams`. */
1237
+ const BINDING_ZK_NONCE = 3n;
1238
+ const SIX_BY_SIX_BINDING_ZK_NONCE = 7n;
1239
+ const V2_PUBLIC_INPUT_PREFIX_LEN = 7;
1240
+ const NOTE_AUDIT_LEN$1 = 12;
1241
+ const NOTE_OUTPUT_LEN = 6;
1242
+ var BundledZkCircuit;
1243
+ (function (BundledZkCircuit) {
1244
+ BundledZkCircuit["TwoByTwo"] = "2x2";
1245
+ BundledZkCircuit["SixBySix"] = "6x6";
1246
+ })(BundledZkCircuit || (BundledZkCircuit = {}));
1247
+ function isDevZkCircuitConfig(config) {
1248
+ return !('circuit' in config);
1249
+ }
1250
+ const DEFAULT_ZK_CONFIG_NONCE = BINDING_ZK_NONCE;
1251
+ const DEFAULT_ZK_CIRCUITS = {
1252
+ [BINDING_ZK_NONCE.toString()]: {
1253
+ nIns: 2,
1254
+ nOuts: 2,
1255
+ publicNInputs: 1,
1256
+ publicNOutputs: 1,
1257
+ circuit: BundledZkCircuit.TwoByTwo,
1258
+ },
1259
+ [SIX_BY_SIX_BINDING_ZK_NONCE.toString()]: {
1260
+ nIns: 6,
1261
+ nOuts: 6,
1262
+ publicNInputs: 1,
1263
+ publicNOutputs: 1,
1264
+ circuit: BundledZkCircuit.SixBySix,
1265
+ },
1305
1266
  };
1306
-
1307
- class WitnessCalculator {
1308
- constructor(instance, sanityCheck) {
1309
- this.instance = instance;
1310
-
1311
- this.version = this.instance.exports.getVersion();
1312
- this.n32 = this.instance.exports.getFieldNumLen32();
1313
-
1314
- this.instance.exports.getRawPrime();
1315
- const arr = new Uint32Array(this.n32);
1316
- for (let i=0; i<this.n32; i++) {
1317
- arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
1318
- }
1319
- this.prime = fromArray32(arr);
1320
-
1321
- this.witnessSize = this.instance.exports.getWitnessSize();
1322
-
1323
- this.sanityCheck = sanityCheck;
1324
- }
1325
-
1326
- circom_version() {
1327
- return this.instance.exports.getVersion();
1328
- }
1329
-
1330
- async _doCalculateWitness(input_orig, sanityCheck) {
1331
- //input is assumed to be a map from signals to arrays of bigints
1332
- this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
1333
- let prefix = "";
1334
- var input = new Object();
1335
- //console.log("Input: ", input_orig);
1336
- qualify_input(prefix,input_orig,input);
1337
- //console.log("Input after: ",input);
1338
- const keys = Object.keys(input);
1339
- var input_counter = 0;
1340
- keys.forEach( (k) => {
1341
- const h = fnvHash(k);
1342
- const hMSB = parseInt(h.slice(0,8), 16);
1343
- const hLSB = parseInt(h.slice(8,16), 16);
1344
- const fArr = flatArray(input[k]);
1345
- let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
1346
- if (signalSize < 0){
1347
- throw new Error(`Signal ${k} not found\n`);
1348
- }
1349
- if (fArr.length < signalSize) {
1350
- throw new Error(`Not enough values for input signal ${k}\n`);
1351
- }
1352
- if (fArr.length > signalSize) {
1353
- throw new Error(`Too many values for input signal ${k}\n`);
1354
- }
1355
- for (let i=0; i<fArr.length; i++) {
1356
- const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32);
1357
- for (let j=0; j<this.n32; j++) {
1358
- this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
1359
- }
1360
- try {
1361
- this.instance.exports.setInputSignal(hMSB, hLSB,i);
1362
- input_counter++;
1363
- } catch (err) {
1364
- // console.log(`After adding signal ${i} of ${k}`)
1365
- throw new Error(err);
1366
- }
1367
- }
1368
-
1369
- });
1370
- if (input_counter < this.instance.exports.getInputSize()) {
1371
- throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
1372
- }
1373
- }
1374
-
1375
- async calculateWitness(input, sanityCheck) {
1376
-
1377
- const w = [];
1378
- await this._doCalculateWitness(input, sanityCheck);
1379
-
1380
- for (let i=0; i<this.witnessSize; i++) {
1381
- this.instance.exports.getWitness(i);
1382
- const arr = new Uint32Array(this.n32);
1383
- for (let j=0; j<this.n32; j++) {
1384
- arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
1385
- }
1386
- w.push(fromArray32(arr));
1387
- }
1388
-
1389
- return w;
1390
- }
1391
-
1392
-
1393
- async calculateBinWitness(input, sanityCheck) {
1394
-
1395
- const buff32 = new Uint32Array(this.witnessSize*this.n32);
1396
- const buff = new Uint8Array( buff32.buffer);
1397
- await this._doCalculateWitness(input, sanityCheck);
1398
-
1399
- for (let i=0; i<this.witnessSize; i++) {
1400
- this.instance.exports.getWitness(i);
1401
- const pos = i*this.n32;
1402
- for (let j=0; j<this.n32; j++) {
1403
- buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
1404
- }
1405
- }
1406
-
1407
- return buff;
1267
+ function layoutFromCircuitConfig(config) {
1268
+ return zkLayoutFromShapeBinding(config.nIns, config.nOuts, config.publicNInputs, config.publicNOutputs, config.nIns + config.nOuts, NOTE_AUDIT_LEN$1, NOTE_OUTPUT_LEN);
1269
+ }
1270
+ function resolveZkCircuitConfig(zkCircuits, zkConfigNonce) {
1271
+ const nonce = zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1272
+ const circuits = zkCircuits ?? DEFAULT_ZK_CIRCUITS;
1273
+ const config = circuits[nonce.toString()];
1274
+ if (!config) {
1275
+ throw new Error(`no zk circuit configured for nonce ${nonce.toString()}`);
1408
1276
  }
1409
-
1410
-
1411
- async calculateWTNSBin(input, sanityCheck) {
1412
-
1413
- const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
1414
- const buff = new Uint8Array( buff32.buffer);
1415
- await this._doCalculateWitness(input, sanityCheck);
1416
-
1417
- //"wtns"
1418
- buff[0] = "w".charCodeAt(0);
1419
- buff[1] = "t".charCodeAt(0);
1420
- buff[2] = "n".charCodeAt(0);
1421
- buff[3] = "s".charCodeAt(0);
1422
-
1423
- //version 2
1424
- buff32[1] = 2;
1425
-
1426
- //number of sections: 2
1427
- buff32[2] = 2;
1428
-
1429
- //id section 1
1430
- buff32[3] = 1;
1431
-
1432
- const n8 = this.n32*4;
1433
- //id section 1 length in 64bytes
1434
- const idSection1length = 8 + n8;
1435
- const idSection1lengthHex = idSection1length.toString(16);
1436
- buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
1437
- buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
1438
-
1439
- //this.n32
1440
- buff32[6] = n8;
1441
-
1442
- //prime number
1443
- this.instance.exports.getRawPrime();
1444
-
1445
- var pos = 7;
1446
- for (let j=0; j<this.n32; j++) {
1447
- buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
1448
- }
1449
- pos += this.n32;
1450
-
1451
- // witness size
1452
- buff32[pos] = this.witnessSize;
1453
- pos++;
1454
-
1455
- //id section 2
1456
- buff32[pos] = 2;
1457
- pos++;
1458
-
1459
- // section 2 length
1460
- const idSection2length = n8*this.witnessSize;
1461
- const idSection2lengthHex = idSection2length.toString(16);
1462
- buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
1463
- buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
1464
-
1465
- pos += 2;
1466
- for (let i=0; i<this.witnessSize; i++) {
1467
- this.instance.exports.getWitness(i);
1468
- for (let j=0; j<this.n32; j++) {
1469
- buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
1470
- }
1471
- pos += this.n32;
1472
- }
1473
-
1474
- return buff;
1277
+ return { nonce, config };
1278
+ }
1279
+ function bundledCircuitFileNames(circuit) {
1280
+ if (circuit === BundledZkCircuit.SixBySix) {
1281
+ return {
1282
+ graphFileName: 'main_6x6.graph.bin',
1283
+ r1csFileName: 'main_6x6.r1cs.gz',
1284
+ provingKeyFileName: 'main_6x6_proving_key.bin',
1285
+ verificationKeyFileName: 'main_6x6_verification_key.json',
1286
+ zkeyFileName: 'main_6x6_final.zkey',
1287
+ };
1475
1288
  }
1476
-
1289
+ return {
1290
+ graphFileName: 'main.graph.bin',
1291
+ r1csFileName: 'main.r1cs.gz',
1292
+ provingKeyFileName: 'main_proving_key.bin',
1293
+ verificationKeyFileName: 'main_verification_key.json',
1294
+ zkeyFileName: 'main_final.zkey',
1295
+ };
1296
+ }
1297
+ function zkLayoutFromShapeBinding(nIns, nOuts, publicNInputs, publicNOutputs, nAuditSlots, noteAuditLen, noteOutputLen) {
1298
+ const auditOffset = nIns + nOuts + nOuts * 2;
1299
+ const outputNoteOffset = auditOffset + 2 + nAuditSlots;
1300
+ return {
1301
+ nIns,
1302
+ nOuts,
1303
+ publicNInputs,
1304
+ publicNOutputs,
1305
+ nAuditSlots,
1306
+ noteAuditLen,
1307
+ noteOutputLen,
1308
+ publicInputPrefixLen: V2_PUBLIC_INPUT_PREFIX_LEN,
1309
+ auditOffset,
1310
+ outputNoteOffset,
1311
+ };
1312
+ }
1313
+ function ciphertextFieldCount(layout) {
1314
+ return layout.nAuditSlots * layout.noteAuditLen + layout.nOuts * layout.noteOutputLen;
1477
1315
  }
1478
1316
 
1479
-
1480
- function qualify_input_list(prefix,input,input1){
1481
- if (Array.isArray(input)) {
1482
- for (let i = 0; i<input.length; i++) {
1483
- let new_prefix = prefix + "[" + i + "]";
1484
- qualify_input_list(new_prefix,input[i],input1);
1485
- }
1486
- } else {
1487
- qualify_input(prefix,input,input1);
1488
- }
1489
- }
1490
-
1491
- function qualify_input(prefix,input,input1) {
1492
- if (Array.isArray(input)) {
1493
- let a = flatArray(input);
1494
- if (a.length > 0) {
1495
- let t = typeof a[0];
1496
- for (let i = 1; i<a.length; i++) {
1497
- if (typeof a[i] != t){
1498
- throw new Error(`Types are not the same in the key ${prefix}`);
1499
- }
1500
- }
1501
- if (t == "object") {
1502
- qualify_input_list(prefix,input,input1);
1503
- } else {
1504
- input1[prefix] = input;
1505
- }
1506
- } else {
1507
- input1[prefix] = input;
1508
- }
1509
- } else if (typeof input == "object") {
1510
- const keys = Object.keys(input);
1511
- keys.forEach( (k) => {
1512
- let new_prefix = prefix == ""? k : prefix + "." + k;
1513
- qualify_input(new_prefix,input[k],input1);
1514
- });
1515
- } else {
1516
- input1[prefix] = input;
1317
+ const ZK_ARTIFACT_GITHUB_REPO = "Polynom-Labs/stellar-privacy-layer-contracts";
1318
+ const ZK_SDK_PACKAGE_VERSION = "0.9.1-rc.0";
1319
+ function defaultZkArtifactBaseUrl(version = ZK_SDK_PACKAGE_VERSION) {
1320
+ const tag = version.startsWith("v") ? version.slice(1) : version;
1321
+ return `https://github.com/${ZK_ARTIFACT_GITHUB_REPO}/releases/download/v${tag}`;
1322
+ }
1323
+ function trimArtifactBaseUrl(value) {
1324
+ let trimmed = value.trim();
1325
+ while (trimmed.endsWith("/")) {
1326
+ trimmed = trimmed.slice(0, -1);
1517
1327
  }
1328
+ return trimmed;
1518
1329
  }
1519
1330
 
1520
- function toArray32(rem,size) {
1521
- const res = []; //new Uint32Array(size); //has no unshift
1522
- const radix = BigInt(0x100000000);
1523
- while (rem) {
1524
- res.unshift( Number(rem % radix));
1525
- rem = rem / radix;
1331
+ const isNode = typeof process !== 'undefined' && !!process.versions?.node;
1332
+ function toUint8Array(src) {
1333
+ if (src instanceof Uint8Array) {
1334
+ return src;
1526
1335
  }
1527
- if (size) {
1528
- var i = size - res.length;
1529
- while (i>0) {
1530
- res.unshift(0);
1531
- i--;
1532
- }
1336
+ if (src instanceof ArrayBuffer) {
1337
+ return new Uint8Array(src);
1533
1338
  }
1534
- return res;
1339
+ return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
1535
1340
  }
1536
-
1537
- function fromArray32(arr) { //returns a BigInt
1538
- var res = BigInt(0);
1539
- const radix = BigInt(0x100000000);
1540
- for (let i = 0; i<arr.length; i++) {
1541
- res = res*radix + BigInt(arr[i]);
1341
+ function nodeEnvArtifactBaseUrl() {
1342
+ if (!isNode) {
1343
+ return undefined;
1542
1344
  }
1543
- return res;
1345
+ const fromEnv = process.env.ZK_ARTIFACT_BASE_URL?.trim();
1346
+ return fromEnv ? trimArtifactBaseUrl(fromEnv) : undefined;
1544
1347
  }
1545
-
1546
- function flatArray(a) {
1547
- var res = [];
1548
- fillArray(res, a);
1549
- return res;
1550
-
1551
- function fillArray(res, a) {
1552
- if (Array.isArray(a)) {
1553
- for (let i=0; i<a.length; i++) {
1554
- fillArray(res, a[i]);
1555
- }
1556
- } else {
1557
- res.push(a);
1558
- }
1348
+ function resolveZkArtifactBaseUrl(explicit) {
1349
+ const trimmed = explicit?.trim();
1350
+ if (trimmed) {
1351
+ return trimArtifactBaseUrl(trimmed);
1559
1352
  }
1353
+ return nodeEnvArtifactBaseUrl() ?? defaultZkArtifactBaseUrl();
1560
1354
  }
1561
-
1562
- function normalize(n, prime) {
1563
- let res = BigInt(n) % prime;
1564
- if (res < 0) res += prime;
1565
- return res
1355
+ function isHttpUrl(value) {
1356
+ return /^https?:\/\//i.test(value) || value.startsWith('file:');
1566
1357
  }
1567
-
1568
- function fnvHash(str) {
1569
- const uint64_max = BigInt(2) ** BigInt(64);
1570
- let hash = BigInt("0xCBF29CE484222325");
1571
- for (var i = 0; i < str.length; i++) {
1572
- hash ^= BigInt(str[i].charCodeAt());
1573
- hash *= BigInt(0x100000001B3);
1574
- hash %= uint64_max;
1358
+ async function readNodeAssetFile(filePath) {
1359
+ const fs = await import('fs');
1360
+ if (!fs.existsSync(filePath)) {
1361
+ throw new Error(`Circuit artifact not found at ${filePath}. Set zkArtifactBaseUrl or ZK_ARTIFACT_BASE_URL.`);
1575
1362
  }
1576
- let shash = hash.toString(16);
1577
- let n = 16 - shash.length;
1578
- shash = '0'.repeat(n).concat(shash);
1579
- return shash;
1363
+ return fs.readFileSync(filePath);
1580
1364
  }
1581
-
1582
- var witnessCalculatorModule = /*@__PURE__*/getDefaultExportFromCjs(witness_calculator);
1583
-
1584
- const isNode$1 = typeof process !== 'undefined' && !!process.versions?.node;
1585
- async function generateWitness(input, circuitWasm) {
1586
- let wasmBuffer;
1587
- let wc;
1588
- if (isNode$1) {
1589
- const fs = await import('fs');
1590
- const nodePath = await import('path');
1591
- const { fileURLToPath } = await import('url');
1592
- 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))));
1593
- const assetsDir = nodePath.resolve(dir, '..', 'assets');
1594
- wc = { default: witnessCalculatorModule };
1595
- if (circuitWasm) {
1596
- wasmBuffer = circuitWasm;
1597
- }
1598
- else {
1599
- const wasmPath = nodePath.resolve(assetsDir, 'main.wasm');
1600
- if (!fs.existsSync(wasmPath)) {
1601
- throw new Error(`Circuit WASM not found at ${wasmPath}. Run build.sh first.`);
1602
- }
1603
- wasmBuffer = fs.readFileSync(wasmPath);
1604
- }
1365
+ async function fetchArtifact(url) {
1366
+ const response = await fetch(url);
1367
+ if (!response.ok) {
1368
+ throw new Error(`Failed to fetch ${url}: ${String(response.status)}`);
1605
1369
  }
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;
1370
+ return new Uint8Array(await response.arrayBuffer());
1617
1371
  }
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);
1372
+ function remoteArtifactHref(base, fileName) {
1373
+ const withSlash = `${trimArtifactBaseUrl(base)}/`;
1374
+ if (isHttpUrl(withSlash)) {
1375
+ return new URL(fileName, withSlash).href;
1636
1376
  }
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);
1377
+ if (typeof window !== 'undefined') {
1378
+ return new URL(fileName, new URL(withSlash, window.location.href)).href;
1648
1379
  }
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.');
1380
+ return `${withSlash}${fileName}`;
1381
+ }
1382
+ async function loadNamedArtifact(base, fileName) {
1383
+ if (isNode && !isHttpUrl(base)) {
1384
+ const nodePath = await import('path');
1385
+ return readNodeAssetFile(nodePath.resolve(base, fileName));
1386
+ }
1387
+ return fetchArtifact(remoteArtifactHref(base, fileName));
1388
+ }
1389
+ async function loadBundledCircuitArtifacts(circuit, artifactBaseUrl) {
1390
+ const base = resolveZkArtifactBaseUrl(artifactBaseUrl);
1391
+ const names = bundledCircuitFileNames(circuit);
1392
+ const [graph, r1cs, provingKey] = await Promise.all([
1393
+ loadNamedArtifact(base, names.graphFileName),
1394
+ loadNamedArtifact(base, names.r1csFileName),
1395
+ loadNamedArtifact(base, names.provingKeyFileName),
1396
+ ]);
1397
+ return { graph, r1cs, provingKey };
1398
+ }
1399
+ async function resolveCircuitArtifacts(config, artifactBaseUrl) {
1400
+ if (isDevZkCircuitConfig(config)) {
1401
+ return {
1402
+ graph: toUint8Array(config.circuitGraph),
1403
+ r1cs: toUint8Array(config.r1cs),
1404
+ provingKey: toUint8Array(config.provingKey),
1405
+ };
1652
1406
  }
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 };
1407
+ return loadBundledCircuitArtifacts(config.circuit, artifactBaseUrl);
1656
1408
  }
1657
1409
 
1658
1410
  /** Bech32 HRP for {@link DecodedEphemeralKey} only (`x ‖ y`, 64 bytes). */
@@ -1663,7 +1415,7 @@ const FIELD_BYTES = 32;
1663
1415
  const EPK_POINT_PAYLOAD = FIELD_BYTES * 2;
1664
1416
  const EPK_DEP_PRE_PAYLOAD = FIELD_BYTES * 3;
1665
1417
  const BECH32_LONG_LIMIT = 1023;
1666
- function normalizeHex$1(hex) {
1418
+ function normalizeHex$2(hex) {
1667
1419
  const s = hex.trim().replace(/^0x/i, '');
1668
1420
  if (!/^[0-9a-fA-F]*$/.test(s)) {
1669
1421
  throw new Error('ephemeral-key: hex must contain only 0-9, a-f');
@@ -1671,7 +1423,7 @@ function normalizeHex$1(hex) {
1671
1423
  return s.length % 2 === 0 ? s : `0${s}`;
1672
1424
  }
1673
1425
  function hexToBytes(hex) {
1674
- const norm = normalizeHex$1(hex);
1426
+ const norm = normalizeHex$2(hex);
1675
1427
  const out = new Uint8Array(norm.length / 2);
1676
1428
  for (let i = 0; i < out.length; i++) {
1677
1429
  out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
@@ -1792,6 +1544,7 @@ function sharedSecretFromRecipientPreimage(ecdhShared, preimage) {
1792
1544
  return { x: out.x, y: out.y };
1793
1545
  }
1794
1546
 
1547
+ const TRANSACTION_N_AUDIT_SLOTS = 4;
1795
1548
  const DEFAULT_APPLICATION_ID = '101';
1796
1549
  /** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
1797
1550
  const DEMO_AUDIT_PUBLIC_KEY = [
@@ -1816,37 +1569,23 @@ function resolveDepositSlot(slot) {
1816
1569
  }
1817
1570
  return slot;
1818
1571
  }
1819
- function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey) {
1572
+ function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey, _nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
1820
1573
  return {
1821
1574
  applicationId,
1822
- noteAuditPublicKeys: [
1823
- auditPublicKey,
1824
- auditPublicKey,
1825
- auditPublicKey,
1826
- auditPublicKey,
1827
- ],
1828
- auditEphemeralScalars: [
1829
- randomFrDecimal253(),
1830
- randomFrDecimal253(),
1831
- randomFrDecimal253(),
1832
- randomFrDecimal253(),
1833
- ],
1575
+ noteAuditPublicKey: auditPublicKey,
1576
+ auditEphemeralScalar: randomFrDecimal253(),
1834
1577
  };
1835
1578
  }
1836
1579
  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
1580
  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
- ],
1581
+ inputApplicationIds: withdrawSlots.map((slot) => {
1582
+ const resolved = resolveWithdrawSlot(slot);
1583
+ return resolved && isActiveWithdraw(resolved) ? audit.applicationId : '0';
1584
+ }),
1585
+ outputApplicationIds: depositSlots.map((slot) => {
1586
+ const resolved = resolveDepositSlot(slot);
1587
+ return resolved && isActiveDeposit(resolved) ? audit.applicationId : '0';
1588
+ }),
1850
1589
  };
1851
1590
  }
1852
1591
  /**
@@ -1864,12 +1603,17 @@ function resolveAuditPublicKeyFromEnv(auditPublicKey) {
1864
1603
  }
1865
1604
  return DEMO_AUDIT_PUBLIC_KEY;
1866
1605
  }
1867
- function resolveTransactionAuditParams(applicationId, auditPublicKey) {
1868
- return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey));
1606
+ function resolveTransactionAuditParams(applicationId, auditPublicKey, nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
1607
+ return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey), nAuditSlots);
1869
1608
  }
1870
1609
 
1871
1610
  /** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 12, 6)` in `circuits/main.circom`. */
1872
1611
  const TRANSACTION_TREE_DEPTH = 20;
1612
+ const TRANSACTION_N_INS = 2;
1613
+ const TRANSACTION_N_OUTS = 2;
1614
+ /** Must match `Transaction(..., publicNInputs, publicNOutputs)` and contract `get_public_slot_config`. */
1615
+ const TRANSACTION_PUBLIC_N_INPUTS = 1;
1616
+ const TRANSACTION_PUBLIC_N_OUTPUTS = 1;
1873
1617
  /** BN254 scalar field modulus (ark `Fr`, circom signals). */
1874
1618
  const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1875
1619
  /**
@@ -1877,7 +1621,30 @@ const BN254_SCALAR_MOD = 2188824287183927522224640574525727508854836440041603434
1877
1621
  * (matches `libs/cryptography` `scalar_mul_253`).
1878
1622
  */
1879
1623
  const BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE = 1n << 253n;
1880
- function normalizeHex(hex) {
1624
+ function padPublicLegs(legs, publicNInputs, publicNOutputs) {
1625
+ const padPairs = (values, expected, label) => {
1626
+ if (values.length > expected) {
1627
+ throw new Error(`withdrawal-transaction-input: expected at most ${expected} ${label}`);
1628
+ }
1629
+ return [
1630
+ ...values,
1631
+ ...Array.from({ length: expected - values.length }, () => ['0', '0']),
1632
+ ];
1633
+ };
1634
+ const padAmounts = (values, expected, label) => {
1635
+ if (values.length > expected) {
1636
+ throw new Error(`withdrawal-transaction-input: expected at most ${expected} ${label}`);
1637
+ }
1638
+ return [...values, ...Array.from({ length: expected - values.length }, () => '0')];
1639
+ };
1640
+ return {
1641
+ publicWithdrawnAssets: padPairs(legs.publicWithdrawnAssets, publicNOutputs, 'public withdrawn assets'),
1642
+ publicDepositedAssets: padPairs(legs.publicDepositedAssets, publicNInputs, 'public deposited assets'),
1643
+ publicDeposits: padAmounts(legs.publicDeposits, publicNInputs, 'public deposits'),
1644
+ publicWithdrawals: padAmounts(legs.publicWithdrawals, publicNOutputs, 'public withdrawals'),
1645
+ };
1646
+ }
1647
+ function normalizeHex$1(hex) {
1881
1648
  const s = hex.trim().replace(/^0x/i, '');
1882
1649
  if (!/^[0-9a-fA-F]*$/.test(s)) {
1883
1650
  throw new Error('withdrawal-transaction-input: invalid hex');
@@ -1886,7 +1653,7 @@ function normalizeHex(hex) {
1886
1653
  }
1887
1654
  /** 32-byte field coordinate (hex, no 0x) → decimal string mod BN254 scalar field. */
1888
1655
  function coordHexToDecimal(hex) {
1889
- const h = normalizeHex(hex);
1656
+ const h = normalizeHex$1(hex);
1890
1657
  if (h.length > 64) {
1891
1658
  throw new Error('withdrawal-transaction-input: coordinate hex too long');
1892
1659
  }
@@ -1898,7 +1665,7 @@ function coordHexToDecimal(hex) {
1898
1665
  * Integer must be < 2^253 (not reduced mod r — values ≥ 2^253 are rejected).
1899
1666
  */
1900
1667
  function scalarHexToFrDecimal(hex) {
1901
- const h = normalizeHex(hex);
1668
+ const h = normalizeHex$1(hex);
1902
1669
  if (h.length > 64) {
1903
1670
  throw new Error('withdrawal-transaction-input: scalar hex too long');
1904
1671
  }
@@ -1913,7 +1680,7 @@ function scalarHexToFrDecimal(hex) {
1913
1680
  * (`withdrawAddressHi` / `withdrawAddressLo`). No mod-r; each half fits in 128 bits.
1914
1681
  */
1915
1682
  function ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex) {
1916
- const h = normalizeHex(hex).padStart(64, '0').slice(-64);
1683
+ const h = normalizeHex$1(hex).padStart(64, '0').slice(-64);
1917
1684
  const hi = BigInt(`0x${h.slice(0, 32)}`);
1918
1685
  const lo = BigInt(`0x${h.slice(32, 64)}`);
1919
1686
  return { hi: hi.toString(10), lo: lo.toString(10) };
@@ -1930,18 +1697,18 @@ function stellarContractAddressToAssetFrDecimals(address) {
1930
1697
  /** Uniform random `Fr` as decimal (32 random bytes, mod r). For Poseidon-only inputs (e.g. nullifiers). */
1931
1698
  function randomFrDecimal() {
1932
1699
  const hex = generateRandomScalarHex32();
1933
- const v = BigInt(`0x${normalizeHex(hex)}`);
1700
+ const v = BigInt(`0x${normalizeHex$1(hex)}`);
1934
1701
  return (v % BN254_SCALAR_MOD).toString(10);
1935
1702
  }
1936
1703
  /** Random scalar < 2^253 for BabyJub ECDH / `Num2Bits(253)` (uses {@link generateRandomScalarHex32}). */
1937
1704
  function randomFrDecimal253() {
1938
1705
  const hex = generateRandomScalarHex32();
1939
- return BigInt(`0x${normalizeHex(hex)}`).toString(10);
1706
+ return BigInt(`0x${normalizeHex$1(hex)}`).toString(10);
1940
1707
  }
1941
1708
  /** Spend-scalar hex in `(0, BabyJub subgroup order)` so dummy withdraws satisfy `LessThan(l)`. */
1942
1709
  function randomCanonicalBabyJubScalarHex() {
1943
1710
  const hex = generateRandomScalarHex32();
1944
- const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex(hex)}`));
1711
+ const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex$1(hex)}`));
1945
1712
  return reduced.toString(16).padStart(64, '0');
1946
1713
  }
1947
1714
  function zerosTreeSiblings() {
@@ -1950,7 +1717,7 @@ function zerosTreeSiblings() {
1950
1717
  function dummyWithdraw(wasm) {
1951
1718
  const nullifier = randomFrDecimal();
1952
1719
  const secretHex = generateRandomScalarHex32();
1953
- const secret = (BigInt(`0x${normalizeHex(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
1720
+ const secret = (BigInt(`0x${normalizeHex$1(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
1954
1721
  const scalarHex = randomCanonicalBabyJubScalarHex();
1955
1722
  const privKeyScalar = scalarHexToFrDecimal(scalarHex);
1956
1723
  const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(scalarHex);
@@ -1991,12 +1758,29 @@ function resolveWithdraw(slot, wasm) {
1991
1758
  function resolveDeposit(slot, wasm) {
1992
1759
  return slot === 'dummy' ? dummyDeposit(wasm) : slot;
1993
1760
  }
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);
1761
+ function padWithdrawSlots(slots, nIns) {
1762
+ if (slots.length > nIns) {
1763
+ throw new Error(`withdrawal-transaction-input: expected at most ${nIns} withdraw slots`);
1764
+ }
1765
+ return [...slots, ...Array.from({ length: nIns - slots.length }, () => 'dummy')];
1766
+ }
1767
+ function padDepositSlots(slots, nOuts) {
1768
+ if (slots.length > nOuts) {
1769
+ throw new Error(`withdrawal-transaction-input: expected at most ${nOuts} deposit slots`);
1770
+ }
1771
+ return [...slots, ...Array.from({ length: nOuts - slots.length }, () => 'dummy')];
1772
+ }
1773
+ function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, depositSlots, audit, wasm, dims = {}) {
1774
+ const nIns = dims.nIns ?? TRANSACTION_N_INS;
1775
+ const nOuts = dims.nOuts ?? TRANSACTION_N_OUTS;
1776
+ const publicNInputs = dims.publicNInputs ?? TRANSACTION_PUBLIC_N_INPUTS;
1777
+ const publicNOutputs = dims.publicNOutputs ?? TRANSACTION_PUBLIC_N_OUTPUTS;
1778
+ const paddedWithdraws = padWithdrawSlots(withdrawSlots, nIns);
1779
+ const paddedDeposits = padDepositSlots(depositSlots, nOuts);
1780
+ const paddedLegs = padPublicLegs(publicLegs, publicNInputs, publicNOutputs);
1781
+ const withdraws = paddedWithdraws.map((slot) => resolveWithdraw(slot, wasm));
1782
+ const deposits = paddedDeposits.map((slot) => resolveDeposit(slot, wasm));
1783
+ const appIds = resolveSlotApplicationIds(audit, paddedWithdraws, paddedDeposits);
2000
1784
  return {
2001
1785
  stateRoot: publicParams.stateRoot,
2002
1786
  withdrawAddressHi: publicParams.withdrawAddressHi,
@@ -2005,35 +1789,32 @@ function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, d
2005
1789
  escrowRecipientLo: publicParams.escrowRecipientLo ?? '0',
2006
1790
  sweepOutputOwnerPubX: publicParams.sweepOutputOwnerPubX ?? '0',
2007
1791
  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
- ],
1792
+ privKeyScalars: withdraws.map((w) => w.privKeyScalar),
1793
+ ownerPubs: withdraws.map((w) => w.ownerPub),
1794
+ paddingRandoms: withdraws.map((w) => w.paddingRandom),
1795
+ withdrawnValues: withdraws.map((w) => w.value),
1796
+ withdrawnNullifiers: withdraws.map((w) => w.nullifier),
1797
+ withdrawnSecrets: withdraws.map((w) => w.secret),
1798
+ withdrawnAssets: withdraws.map((w) => w.asset),
1799
+ withdrawnEscrowNonces: withdraws.map((w) => w.escrowNonce),
1800
+ inputRecipientStellar: withdraws.map((w) => w.recipientStellar),
1801
+ stateSiblings: withdraws.map((w) => w.stateSiblings),
1802
+ stateIndex: withdraws.map((w) => w.stateIndex),
1803
+ depositedValues: deposits.map((d) => d.value),
1804
+ depositedNullifiers: deposits.map((d) => d.nullifier),
1805
+ depositedAssets: deposits.map((d) => d.asset),
1806
+ depositedEphemeralKeyScalars: deposits.map((d) => d.ephemeralKeyScalar),
1807
+ depositedRecipientPublicKeys: deposits.map((d) => d.recipientPublicKeys),
1808
+ depositedEscrowNonces: deposits.map((d) => d.escrowNonce ?? '0'),
1809
+ outputRecipientStellar: deposits.map((d) => d.recipientStellar ?? ['0', '0']),
2029
1810
  inputApplicationIds: appIds.inputApplicationIds,
2030
1811
  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,
1812
+ auditEphemeralScalar: audit.auditEphemeralScalar,
1813
+ noteAuditPublicKey: audit.noteAuditPublicKey,
1814
+ publicWithdrawnAssets: paddedLegs.publicWithdrawnAssets,
1815
+ publicDepositedAssets: paddedLegs.publicDepositedAssets,
1816
+ publicDeposits: paddedLegs.publicDeposits,
1817
+ publicWithdrawals: paddedLegs.publicWithdrawals,
2037
1818
  };
2038
1819
  }
2039
1820
  /** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
@@ -2059,23 +1840,363 @@ function withdrawObjectFromMerkleWitness(witness, ownerPubHex, applicationId, pr
2059
1840
  };
2060
1841
  }
2061
1842
 
1843
+ const FR_SIZE$1 = 32;
1844
+ const CIPHERTEXT_BLOB_HEADER_BYTES = 4;
1845
+ const FR_MODULUS$1 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1846
+ function moduleFr$1(value) {
1847
+ const reduced = value % FR_MODULUS$1;
1848
+ return reduced >= 0n ? reduced : reduced + FR_MODULUS$1;
1849
+ }
1850
+ function fieldToBytes(value) {
1851
+ let remaining = moduleFr$1(value);
1852
+ const buf = Buffer.alloc(FR_SIZE$1);
1853
+ for (let index = FR_SIZE$1 - 1; index >= 0; index -= 1) {
1854
+ buf[index] = Number(remaining & 0xffn);
1855
+ remaining >>= 8n;
1856
+ }
1857
+ return buf;
1858
+ }
1859
+ function decimalOrHexToBigint(value) {
1860
+ const trimmed = value.trim();
1861
+ if (/^0x/i.test(trimmed) || /^[0-9a-f]{64}$/i.test(trimmed)) {
1862
+ return BigInt(`0x${trimmed.replace(/^0x/i, '')}`);
1863
+ }
1864
+ return BigInt(trimmed);
1865
+ }
1866
+ function encodeCiphertextBlob(fields) {
1867
+ const prefix = Buffer.alloc(CIPHERTEXT_BLOB_HEADER_BYTES);
1868
+ prefix.writeUInt32BE(fields.length);
1869
+ return Buffer.concat([
1870
+ prefix,
1871
+ ...fields.map((field) => fieldToBytes(decimalOrHexToBigint(field))),
1872
+ ]);
1873
+ }
1874
+ function encodeTransactionCiphertextBlob(parameters) {
1875
+ const expected = ciphertextFieldCount(parameters.layout);
1876
+ const fields = [];
1877
+ for (const slot of parameters.auditCiphertexts) {
1878
+ fields.push(...slot);
1879
+ }
1880
+ for (const note of parameters.outputNoteCiphertexts) {
1881
+ fields.push(...note);
1882
+ }
1883
+ if (fields.length !== expected) {
1884
+ throw new Error(`ciphertext blob expected ${String(expected)} fields, got ${String(fields.length)}`);
1885
+ }
1886
+ return encodeCiphertextBlob(fields);
1887
+ }
1888
+
1889
+ const NOTE_AUDIT_LEN = 12;
1890
+ const STREAM_DOMAIN = 1n;
1891
+ const FR_SIZE = 32;
1892
+ const FR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1893
+ const ONE = 1n;
1894
+ const ZERO = 0n;
1895
+ let primitivesPromise;
1896
+ function primitives() {
1897
+ primitivesPromise ?? (primitivesPromise = Promise.all([
1898
+ circomlibjs.buildBabyjub(),
1899
+ circomlibjs.buildMimc7(),
1900
+ circomlibjs.buildPoseidon(),
1901
+ ]).then(([babyJub, mimc7, poseidon]) => ({ babyJub, mimc7, poseidon })));
1902
+ return primitivesPromise;
1903
+ }
1904
+ function normalizeHex(value) {
1905
+ return value.trim().replace(/^0x/i, '').padStart(FR_SIZE * 2, '0');
1906
+ }
1907
+ function bigintFromHex(value) {
1908
+ return BigInt(`0x${normalizeHex(value)}`);
1909
+ }
1910
+ function bigintFromDecimal(value) {
1911
+ const parsed = BigInt(value);
1912
+ return parsed % FR_MODULUS;
1913
+ }
1914
+ function bigintToFrHex(value) {
1915
+ let v = moduleFr(value);
1916
+ const buf = Buffer.alloc(FR_SIZE);
1917
+ for (let index = FR_SIZE - 1; index >= 0; index -= 1) {
1918
+ buf[index] = Number(v & 0xffn);
1919
+ v >>= 8n;
1920
+ }
1921
+ return buf.toString('hex');
1922
+ }
1923
+ function frDecimalToHex(value) {
1924
+ return bigintToFrHex(bigintFromDecimal(value));
1925
+ }
1926
+ function frDecimalFromBigint(value) {
1927
+ const reduced = value >= ZERO ? value % FR_MODULUS : (value % FR_MODULUS) + FR_MODULUS;
1928
+ return reduced.toString(10);
1929
+ }
1930
+ function fieldBigint(field, value) {
1931
+ return BigInt(field.toString(value));
1932
+ }
1933
+ function moduleFr(value) {
1934
+ const reduced = value % FR_MODULUS;
1935
+ return reduced >= ZERO ? reduced : reduced + FR_MODULUS;
1936
+ }
1937
+ function mimcHash(parameters) {
1938
+ return fieldBigint(parameters.mimc7.F, parameters.mimc7.hash(parameters.mimc7.F.e(parameters.message), parameters.mimc7.F.e(parameters.key)));
1939
+ }
1940
+ function poseidonHash(poseidon, inputs) {
1941
+ return fieldBigint(poseidon.F, poseidon(inputs.map((input) => poseidon.F.e(input))));
1942
+ }
1943
+ function deriveEncCipherKey(poseidon, sharedX) {
1944
+ return poseidonHash(poseidon, [DOM_ENC, sharedX]);
1945
+ }
1946
+ function sharedKeyFromDepositor(parameters) {
1947
+ const scalarHex = frDecimalToHex(parameters.ephemeralKeyScalarDecimal);
1948
+ const shared = parameters.ecdhShared(scalarHex, frDecimalToHex(parameters.recipientPublicKey[0]), frDecimalToHex(parameters.recipientPublicKey[1]));
1949
+ return [bigintFromHex(shared.x), bigintFromHex(shared.y)];
1950
+ }
1951
+ function poseidonMacTag(parameters) {
1952
+ return poseidonHash(parameters.poseidon, [
1953
+ parameters.domain,
1954
+ parameters.key,
1955
+ parameters.nonce,
1956
+ ...parameters.message,
1957
+ ]);
1958
+ }
1959
+ function outputNoteTag(parameters) {
1960
+ return poseidonMacTag({
1961
+ poseidon: parameters.poseidon,
1962
+ domain: DOM_OUTPUT_NOTE_TAG,
1963
+ key: parameters.key,
1964
+ nonce: parameters.nonce,
1965
+ message: parameters.plaintext,
1966
+ });
1967
+ }
1968
+ function encryptFields(parameters) {
1969
+ return parameters.plaintext.map((field, index) => {
1970
+ const seed = poseidonHash(parameters.poseidon, [
1971
+ parameters.nonce,
1972
+ STREAM_DOMAIN,
1973
+ BigInt(index),
1974
+ ]);
1975
+ const stream = mimcHash({
1976
+ mimc7: parameters.mimc7,
1977
+ message: seed,
1978
+ key: parameters.key,
1979
+ });
1980
+ return moduleFr(field + stream);
1981
+ });
1982
+ }
1983
+ function plaintextBigints(plaintext) {
1984
+ return [
1985
+ bigintFromDecimal(plaintext.value),
1986
+ bigintFromDecimal(plaintext.assetHi),
1987
+ bigintFromDecimal(plaintext.assetLo),
1988
+ bigintFromDecimal(plaintext.nullifier),
1989
+ bigintFromDecimal(plaintext.secret),
1990
+ bigintFromDecimal(plaintext.applicationId),
1991
+ ];
1992
+ }
1993
+ async function secretFromDepositEphemeralScalarDecimal(ephemeralKeyScalarDecimal) {
1994
+ const { poseidon } = await primitives();
1995
+ const secret = poseidonHash(poseidon, [bigintFromDecimal(ephemeralKeyScalarDecimal)]);
1996
+ return frDecimalFromBigint(secret);
1997
+ }
1998
+ async function encryptOutputNoteForDeposit(parameters) {
1999
+ const { babyJub, mimc7, poseidon } = await primitives();
2000
+ const secret = await secretFromDepositEphemeralScalarDecimal(parameters.deposit.ephemeralKeyScalar);
2001
+ const plaintext = {
2002
+ value: parameters.deposit.value,
2003
+ assetHi: parameters.deposit.asset[0],
2004
+ assetLo: parameters.deposit.asset[1],
2005
+ nullifier: parameters.deposit.nullifier,
2006
+ secret,
2007
+ applicationId: parameters.deposit.applicationId,
2008
+ };
2009
+ const [sharedX, sharedY] = sharedKeyFromDepositor({
2010
+ babyJub,
2011
+ ecdhShared: parameters.ecdhShared,
2012
+ ephemeralKeyScalarDecimal: parameters.deposit.ephemeralKeyScalar,
2013
+ recipientPublicKey: parameters.deposit.recipientPublicKeys,
2014
+ });
2015
+ const key = deriveEncCipherKey(poseidon, sharedX);
2016
+ const nonce = sharedY;
2017
+ const message = plaintextBigints(plaintext);
2018
+ const ciphertext = encryptFields({ mimc7, poseidon, key, nonce, plaintext: message });
2019
+ const tag = outputNoteTag({ poseidon, key, nonce, plaintext: message });
2020
+ return {
2021
+ ciphertext: ciphertext.map((field) => frDecimalFromBigint(field)),
2022
+ tag: frDecimalFromBigint(tag),
2023
+ };
2024
+ }
2025
+ function ownerModeFromEscrowNonce(escrowNonce) {
2026
+ return bigintFromDecimal(escrowNonce) === ZERO ? ZERO : ONE;
2027
+ }
2028
+ function emptyFlagFromValue(value) {
2029
+ return bigintFromDecimal(value) === ZERO ? ONE : ZERO;
2030
+ }
2031
+ function precommitmentFromOpening(parameters) {
2032
+ const ownerKeyHash = poseidonHash(parameters.poseidon, [
2033
+ parameters.ownerPub[0],
2034
+ parameters.ownerPub[1],
2035
+ ]);
2036
+ return poseidonHash(parameters.poseidon, [
2037
+ parameters.secret,
2038
+ parameters.ownerMode,
2039
+ ownerKeyHash,
2040
+ ]);
2041
+ }
2042
+ async function encryptNoteAuditSlot(parameters) {
2043
+ if (parameters.plaintext.length !== NOTE_AUDIT_LEN) {
2044
+ throw new Error(`encryptNoteAuditSlot: expected ${String(NOTE_AUDIT_LEN)} limbs`);
2045
+ }
2046
+ const { babyJub, mimc7, poseidon } = await primitives();
2047
+ const [sharedX, sharedY] = sharedKeyFromDepositor({
2048
+ babyJub,
2049
+ ecdhShared: parameters.ecdhShared,
2050
+ ephemeralKeyScalarDecimal: parameters.auditEphemeralScalar,
2051
+ recipientPublicKey: parameters.auditPublicKey,
2052
+ });
2053
+ const key = deriveEncCipherKey(poseidon, sharedX);
2054
+ const nonce = sharedY;
2055
+ const message = parameters.plaintext.map((field) => bigintFromDecimal(field));
2056
+ const ciphertext = encryptFields({ mimc7, poseidon, key, nonce, plaintext: message });
2057
+ const tag = poseidonMacTag({
2058
+ poseidon,
2059
+ domain: DOM_AUDIT_TAG,
2060
+ key,
2061
+ nonce,
2062
+ message,
2063
+ });
2064
+ return {
2065
+ ciphertext: ciphertext.map((field) => frDecimalFromBigint(field)),
2066
+ tag: frDecimalFromBigint(tag),
2067
+ };
2068
+ }
2069
+ function auditPlaintextForWithdraw(parameters) {
2070
+ const value = parameters.witness.withdrawnValues[parameters.index] ?? '0';
2071
+ const ownerPub = parameters.witness.ownerPubs[parameters.index] ?? ['0', '0'];
2072
+ const escrowNonce = parameters.witness.withdrawnEscrowNonces[parameters.index] ?? '0';
2073
+ const secret = bigintFromDecimal(parameters.witness.withdrawnSecrets[parameters.index] ?? '0');
2074
+ const ownerMode = ownerModeFromEscrowNonce(escrowNonce);
2075
+ const precommitment = precommitmentFromOpening({
2076
+ poseidon: parameters.poseidon,
2077
+ secret,
2078
+ ownerMode,
2079
+ ownerPub: [bigintFromDecimal(ownerPub[0]), bigintFromDecimal(ownerPub[1])],
2080
+ });
2081
+ const asset = parameters.witness.withdrawnAssets[parameters.index] ?? ['0', '0'];
2082
+ const stellar = parameters.witness.inputRecipientStellar[parameters.index] ?? ['0', '0'];
2083
+ return [
2084
+ frDecimalFromBigint(emptyFlagFromValue(value)),
2085
+ value,
2086
+ asset[0],
2087
+ asset[1],
2088
+ frDecimalFromBigint(precommitment),
2089
+ parameters.witness.withdrawnNullifiers[parameters.index] ?? '0',
2090
+ ownerPub[0],
2091
+ ownerPub[1],
2092
+ parameters.witness.inputApplicationIds[parameters.index] ?? '0',
2093
+ stellar[0],
2094
+ stellar[1],
2095
+ escrowNonce,
2096
+ ];
2097
+ }
2098
+ async function auditPlaintextForDeposit(parameters) {
2099
+ const value = parameters.witness.depositedValues[parameters.index] ?? '0';
2100
+ const ownerPub = parameters.witness.depositedRecipientPublicKeys[parameters.index] ?? ['0', '0'];
2101
+ const escrowNonce = parameters.witness.depositedEscrowNonces[parameters.index] ?? '0';
2102
+ const secret = poseidonHash(parameters.poseidon, [
2103
+ bigintFromDecimal(parameters.witness.depositedEphemeralKeyScalars[parameters.index] ?? '0'),
2104
+ ]);
2105
+ const ownerMode = ownerModeFromEscrowNonce(escrowNonce);
2106
+ const precommitment = precommitmentFromOpening({
2107
+ poseidon: parameters.poseidon,
2108
+ secret,
2109
+ ownerMode,
2110
+ ownerPub: [bigintFromDecimal(ownerPub[0]), bigintFromDecimal(ownerPub[1])],
2111
+ });
2112
+ const asset = parameters.witness.depositedAssets[parameters.index] ?? ['0', '0'];
2113
+ const stellar = parameters.witness.outputRecipientStellar[parameters.index] ?? ['0', '0'];
2114
+ return [
2115
+ frDecimalFromBigint(emptyFlagFromValue(value)),
2116
+ value,
2117
+ asset[0],
2118
+ asset[1],
2119
+ frDecimalFromBigint(precommitment),
2120
+ parameters.witness.depositedNullifiers[parameters.index] ?? '0',
2121
+ ownerPub[0],
2122
+ ownerPub[1],
2123
+ parameters.witness.outputApplicationIds[parameters.index] ?? '0',
2124
+ stellar[0],
2125
+ stellar[1],
2126
+ escrowNonce,
2127
+ ];
2128
+ }
2129
+ async function encryptTransactionCiphertextBlob(parameters) {
2130
+ const { poseidon } = await primitives();
2131
+ const nIns = parameters.witness.withdrawnValues.length;
2132
+ const nOuts = parameters.witness.depositedValues.length;
2133
+ const nAuditSlots = parameters.layout.nAuditSlots;
2134
+ const auditCiphertexts = [];
2135
+ for (let slot = 0; slot < nAuditSlots; slot += 1) {
2136
+ const plaintext = slot < nIns
2137
+ ? auditPlaintextForWithdraw({
2138
+ poseidon,
2139
+ witness: parameters.witness,
2140
+ index: slot,
2141
+ })
2142
+ : await auditPlaintextForDeposit({
2143
+ poseidon,
2144
+ witness: parameters.witness,
2145
+ index: slot - nIns,
2146
+ });
2147
+ const encrypted = await encryptNoteAuditSlot({
2148
+ auditPublicKey: parameters.witness.noteAuditPublicKey,
2149
+ auditEphemeralScalar: parameters.witness.auditEphemeralScalar,
2150
+ plaintext,
2151
+ ecdhShared: parameters.ecdhShared,
2152
+ });
2153
+ auditCiphertexts.push(encrypted.ciphertext);
2154
+ }
2155
+ const outputNoteCiphertexts = [];
2156
+ for (let index = 0; index < nOuts; index += 1) {
2157
+ const deposit = {
2158
+ value: parameters.witness.depositedValues[index] ?? '0',
2159
+ nullifier: parameters.witness.depositedNullifiers[index] ?? '0',
2160
+ ephemeralKeyScalar: parameters.witness.depositedEphemeralKeyScalars[index] ?? '0',
2161
+ asset: parameters.witness.depositedAssets[index] ?? ['0', '0'],
2162
+ applicationId: parameters.witness.outputApplicationIds[index] ?? '0',
2163
+ recipientPublicKeys: parameters.witness.depositedRecipientPublicKeys[index] ?? ['0', '0'],
2164
+ };
2165
+ const encrypted = await encryptOutputNoteForDeposit({
2166
+ deposit,
2167
+ ecdhShared: parameters.ecdhShared,
2168
+ });
2169
+ outputNoteCiphertexts.push([...encrypted.ciphertext]);
2170
+ }
2171
+ const blob = encodeTransactionCiphertextBlob({
2172
+ layout: parameters.layout,
2173
+ auditCiphertexts,
2174
+ outputNoteCiphertexts,
2175
+ });
2176
+ return {
2177
+ ciphertextHex: blob.toString('hex'),
2178
+ auditCiphertexts,
2179
+ outputNoteCiphertexts,
2180
+ };
2181
+ }
2182
+
2062
2183
  /** Stroops amount as a decimal field string. Reject JS `number` so 18-decimal values cannot silently narrow. */
2063
2184
  function coinValueDecimal(amount) {
2064
- if (typeof amount === 'number') {
2065
- throw new TypeError('coin value must be bigint or decimal string, not number');
2185
+ if (typeof amount === "number") {
2186
+ throw new TypeError("coin value must be bigint or decimal string, not number");
2066
2187
  }
2067
- if (typeof amount === 'bigint') {
2188
+ if (typeof amount === "bigint") {
2068
2189
  if (amount < 0n) {
2069
- throw new RangeError('amount must be a non-negative integer');
2190
+ throw new RangeError("amount must be a non-negative integer");
2070
2191
  }
2071
2192
  return amount.toString(10);
2072
2193
  }
2073
- if (typeof amount !== 'string') {
2074
- throw new TypeError('coin value must be bigint or decimal string, not number');
2194
+ if (typeof amount !== "string") {
2195
+ throw new TypeError("coin value must be bigint or decimal string, not number");
2075
2196
  }
2076
2197
  const trimmed = amount.trim();
2077
2198
  if (!/^[0-9]+$/.test(trimmed)) {
2078
- throw new TypeError('coin value decimal string must be a non-negative integer');
2199
+ throw new TypeError("coin value decimal string must be a non-negative integer");
2079
2200
  }
2080
2201
  return trimmed;
2081
2202
  }
@@ -2083,7 +2204,7 @@ function generatedCoinFromWasm(value) {
2083
2204
  const raw = value;
2084
2205
  const precommitementHex = raw.precommitementHex ?? raw.precommitement_hex;
2085
2206
  if (!precommitementHex) {
2086
- throw new Error('generated coin missing precommitement hex');
2207
+ throw new Error("generated coin missing precommitement hex");
2087
2208
  }
2088
2209
  return {
2089
2210
  coin: raw.coin,
@@ -2093,27 +2214,39 @@ function generatedCoinFromWasm(value) {
2093
2214
  };
2094
2215
  }
2095
2216
  class PrivacyPoolSDK {
2096
- constructor(wasm, options) {
2217
+ constructor(wasm, options, resolved) {
2097
2218
  this.wasm = wasm;
2098
2219
  this.options = options;
2220
+ this.zkConfigNonce = resolved.nonce;
2221
+ this.circuitConfig = resolved.config;
2222
+ this.layout = layoutFromCircuitConfig(resolved.config);
2223
+ }
2224
+ getZkConfigNonce() {
2225
+ return this.zkConfigNonce;
2226
+ }
2227
+ getLayout() {
2228
+ return this.layout;
2099
2229
  }
2100
2230
  /**
2101
2231
  * Initialize the SDK by loading WASM modules.
2102
2232
  *
2103
- * In Node.js, assets are loaded from the filesystem automatically.
2104
- * In browser, pass pre-loaded buffers via options:
2233
+ * In Node.js, proving artifacts load from `zkArtifactBaseUrl` or
2234
+ * `ZK_ARTIFACT_BASE_URL`, defaulting to this package's GitHub release.
2235
+ * In browser, pass pre-loaded WASM via options:
2105
2236
  * ```ts
2106
2237
  * const sdk = await PrivacyPoolSDK.init({
2107
2238
  * wasmBinary: await fetch('/pkg/client_sdk_wasm_bg.wasm').then(r => r.arrayBuffer()),
2108
- * circuitWasm: await fetch('/assets/main.wasm').then(r => r.arrayBuffer()),
2109
- * zkey: await fetch('/assets/main_final.zkey').then(r => r.arrayBuffer()),
2239
+ * zkArtifactBaseUrl: 'https://github.com/Polynom-Labs/stellar-privacy-layer-contracts/releases/download/v0.8.0',
2110
2240
  * });
2111
2241
  * ```
2112
2242
  */
2113
2243
  static async init(options) {
2114
2244
  const opts = options ?? {};
2115
2245
  const wasm = await loadWasm(opts.wasmBinary);
2116
- return new PrivacyPoolSDK(wasm, opts);
2246
+ const resolved = resolveZkCircuitConfig(opts.zkCircuits, opts.zkConfigNonce);
2247
+ const sdk = new PrivacyPoolSDK(wasm, opts, resolved);
2248
+ sdk.circuitArtifacts = await resolveCircuitArtifacts(resolved.config, opts.zkArtifactBaseUrl);
2249
+ return sdk;
2117
2250
  }
2118
2251
  /**
2119
2252
  * Uniform 32-byte scalar as lowercase hex (Web Crypto). Same in browser and Node 19+.
@@ -2133,28 +2266,28 @@ class PrivacyPoolSDK {
2133
2266
  * @param amount Decimal field element as `bigint` or decimal `string` (never JS `number`).
2134
2267
  * @param assetHiDecimal / assetLoDecimal Decimal Fr strings for Stellar asset contract id (two limbs).
2135
2268
  */
2136
- generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
2269
+ generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
2137
2270
  return generatedCoinFromWasm(this.wasm.generateCoin(coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
2138
2271
  }
2139
2272
  /**
2140
2273
  * Generate a coin with the same commitment shape as on-chain deposit: pass owner public key (hex x, y).
2141
2274
  * @param amount Decimal field element (`bigint` | `string`).
2142
2275
  */
2143
- generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
2276
+ generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
2144
2277
  return generatedCoinFromWasm(this.wasm.generateCoinWithOwnerPubHex(ownerPub.x, ownerPub.y, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
2145
2278
  }
2146
2279
  /**
2147
2280
  * Coin for a depositor `ephemeralKeyScalar` (32-byte hex): `coin.secret = Poseidon255(1)(scalar)` as in `deposit.circom`.
2148
2281
  * @param amount Decimal field element (`bigint` | `string`).
2149
2282
  */
2150
- generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
2283
+ generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
2151
2284
  return generatedCoinFromWasm(this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
2152
2285
  }
2153
2286
  /**
2154
2287
  * Aligned deposit coin: `secret = Poseidon₁(scalar)` and owner pub from recipient hex coords.
2155
2288
  * @param amount Decimal field element (`bigint` | `string`).
2156
2289
  */
2157
- generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
2290
+ generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
2158
2291
  return generatedCoinFromWasm(this.wasm.generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
2159
2292
  }
2160
2293
  /**
@@ -2174,18 +2307,18 @@ class PrivacyPoolSDK {
2174
2307
  */
2175
2308
  async proveWithdrawal(coin, state, params) {
2176
2309
  const witness = this.buildWithdrawMerkleWitness(coin, state);
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);
2310
+ const ownerPubHex = this.ecdhEphemeralPublicKeyFromScalarHex(BigInt(params.privKeyScalar).toString(16).padStart(64, "0"));
2311
+ const w0 = withdrawObjectFromMerkleWitness(witness, ownerPubHex, params.applicationId ?? coin.application_id ?? "0", params.privKeyScalar);
2179
2312
  const fullV = BigInt(coin.value);
2180
2313
  let publicWithdrawals;
2181
2314
  let deposits;
2182
2315
  if (params.publicWithdrawStroops !== undefined) {
2183
2316
  const W = params.publicWithdrawStroops;
2184
2317
  if (params.changeRecipientStealthAddress === undefined) {
2185
- throw new Error('proveWithdrawal: changeRecipientStealthAddress is required when publicWithdrawStroops is set');
2318
+ throw new Error("proveWithdrawal: changeRecipientStealthAddress is required when publicWithdrawStroops is set");
2186
2319
  }
2187
2320
  if (W <= 0n || W >= fullV) {
2188
- throw new Error('proveWithdrawal: publicWithdrawStroops must be strictly between 0 and coin.value');
2321
+ throw new Error("proveWithdrawal: publicWithdrawStroops must be strictly between 0 and coin.value");
2189
2322
  }
2190
2323
  const change = fullV - W;
2191
2324
  publicWithdrawals = [W.toString()];
@@ -2194,29 +2327,29 @@ class PrivacyPoolSDK {
2194
2327
  nullifier: randomFrDecimal(),
2195
2328
  ephemeralKeyScalar: randomFrDecimal253(),
2196
2329
  asset: [coin.asset_hi, coin.asset_lo],
2197
- applicationId: params.applicationId ?? '0',
2330
+ applicationId: params.applicationId ?? "0",
2198
2331
  recipientPublicKeys: recipientPublicKeysDecimalFromStealthAddress(params.changeRecipientStealthAddress),
2199
2332
  };
2200
- deposits = [changeDeposit, 'dummy'];
2333
+ deposits = [changeDeposit];
2201
2334
  }
2202
2335
  else {
2203
2336
  publicWithdrawals = [coin.value];
2204
- deposits = ['dummy', 'dummy'];
2337
+ deposits = [];
2205
2338
  }
2206
2339
  // On-chain `transact` sends `publicWithdrawals` / `publicWithdrawnAssets` to the withdraw account.
2207
2340
  const publicLegs = {
2208
2341
  publicWithdrawnAssets: [[coin.asset_hi, coin.asset_lo]],
2209
- publicDepositedAssets: [['0', '0']],
2210
- publicDeposits: ['0'],
2342
+ publicDepositedAssets: [["0", "0"]],
2343
+ publicDeposits: ["0"],
2211
2344
  publicWithdrawals,
2212
2345
  };
2213
- return this.proveTransaction({
2346
+ return this.proveTransactionSlots({
2214
2347
  stateRoot: witness.stateRoot,
2215
2348
  withdrawAddressHi: params.withdrawAddressHi,
2216
2349
  withdrawAddressLo: params.withdrawAddressLo,
2217
2350
  privKeyScalar: params.privKeyScalar,
2218
- }, publicLegs, [w0, 'dummy'], deposits, params.audit
2219
- ?? resolveTransactionAuditParams(params.applicationId ?? coin.application_id ?? DEFAULT_APPLICATION_ID, DEMO_AUDIT_PUBLIC_KEY));
2351
+ }, publicLegs, [w0], deposits, params.audit ??
2352
+ resolveTransactionAuditParams(params.applicationId ?? coin.application_id ?? DEFAULT_APPLICATION_ID, DEMO_AUDIT_PUBLIC_KEY, this.layout.nAuditSlots));
2220
2353
  }
2221
2354
  /**
2222
2355
  * Convert a snarkjs proof JSON to hex bytes for Soroban.
@@ -2241,23 +2374,33 @@ class PrivacyPoolSDK {
2241
2374
  return this.wasm.ecdhSharedKey(scalarHex, recipientPubXHex, recipientPubYHex);
2242
2375
  }
2243
2376
  /**
2244
- * `Transaction(20,2,2)` proof from high-level legs: maps to witness input (incl. `"dummy"` ECDH via WASM), then Groth16 → Soroban hex.
2245
- *
2246
- * @param publicParams Public inputs: `stateRoot`, `withdrawAddressHi` / `withdrawAddressLo`, `privKeyScalar` (decimal field strings).
2247
- * @param publicLegs Public token legs (`publicWithdrawnAssets`, `publicDepositedAssets`, `publicDeposits`, `publicWithdrawals`).
2248
- * @param withdraws Exactly two withdraw slots (`WithdrawObject` or `"dummy"`).
2249
- * @param deposits Exactly two deposit slots (`DepositObject` or `"dummy"`).
2377
+ * `Transaction(20,nIns,nOuts)` proof from high-level legs: maps to witness input (incl. `"dummy"` ECDH via WASM), then Groth16 → Soroban hex.
2378
+ * Slots are padded to the selected ZK config layout (bundled 2×2 or 6×6).
2250
2379
  */
2251
2380
  async proveTransaction(publicParams, publicLegs, withdraws, deposits, audit) {
2381
+ return this.proveTransactionSlots(publicParams, publicLegs, withdraws, deposits, audit);
2382
+ }
2383
+ async proveTransactionSlots(publicParams, publicLegs, withdraws, deposits, audit) {
2252
2384
  const wasmEcdh = {
2253
2385
  ecdhEphemeralPublicKeyFromScalarHex: (h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h),
2254
2386
  };
2255
- const witnessInput = buildTransactionWitnessInput(publicParams, publicLegs, withdraws, deposits, audit, wasmEcdh);
2256
- const wtns = await generateWitness(witnessInput, this.options.circuitWasm);
2257
- const { proof, publicSignals } = await generateProof(wtns, this.options.zkey);
2387
+ const { nIns, nOuts, publicNInputs, publicNOutputs } = this.layout;
2388
+ const witnessInput = buildTransactionWitnessInput(publicParams, publicLegs, withdraws, deposits, audit, wasmEcdh, { nIns, nOuts, publicNInputs, publicNOutputs });
2389
+ const artifacts = this.circuitArtifacts;
2390
+ if (!artifacts) {
2391
+ throw new Error("PrivacyPoolSDK circuit artifacts are not loaded");
2392
+ }
2393
+ const proved = this.wasm.proveGroth16(artifacts.graph, artifacts.provingKey, artifacts.r1cs, JSON.stringify(witnessInput));
2394
+ const encrypted = await encryptTransactionCiphertextBlob({
2395
+ witness: witnessInput,
2396
+ layout: this.layout,
2397
+ ecdhShared: (priv, pubX, pubY) => this.wasm.ecdhSharedKey(priv, pubX, pubY),
2398
+ });
2258
2399
  return {
2259
- proof_hex: this.proofToHex(proof),
2260
- public_hex: this.publicToHex(publicSignals),
2400
+ proof_hex: proved.proof_hex,
2401
+ public_hex: proved.public_hex,
2402
+ ciphertext_hex: encrypted.ciphertextHex,
2403
+ output_note_ephemeral_scalars: witnessInput.depositedEphemeralKeyScalars,
2261
2404
  };
2262
2405
  }
2263
2406
  /**
@@ -2452,6 +2595,20 @@ const COIN_VALUE_STROOPS = 1000000000;
2452
2595
  function resolveApplicationIdFromCli(parsed) {
2453
2596
  return parsed['application-id'] ?? process.env.APPLICATION_ID ?? DEFAULT_APPLICATION_ID;
2454
2597
  }
2598
+ function resolveZkInit(parsed) {
2599
+ const explicit = parsed['profile'] ?? process.env.ZK_PROFILE;
2600
+ if (explicit && (explicit === '6x6' || explicit === 'six' || explicit === '6')) {
2601
+ return { zkConfigNonce: SIX_BY_SIX_BINDING_ZK_NONCE };
2602
+ }
2603
+ const envNonce = (process.env.ZK_CONFIG_NONCE ?? '').trim();
2604
+ if (envNonce === '6' || envNonce === '7') {
2605
+ return { zkConfigNonce: SIX_BY_SIX_BINDING_ZK_NONCE };
2606
+ }
2607
+ if (envNonce === '3' || envNonce === '2' || envNonce === '') {
2608
+ return { zkConfigNonce: BINDING_ZK_NONCE };
2609
+ }
2610
+ return { zkConfigNonce: BINDING_ZK_NONCE };
2611
+ }
2455
2612
  async function main() {
2456
2613
  const args = process.argv.slice(2);
2457
2614
  if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
@@ -2555,10 +2712,12 @@ Commands:
2555
2712
  --public-withdraw-stroops <u64> Optional: amount to withdraw publicly (must be < coin.value). Rest stays in pool as change.
2556
2713
  --change-stealth <stpl1...> Required with --public-withdraw-stroops: stealth for the change note (same as deposit)
2557
2714
  --application-id <dec> Application id (decimal Fr); default: APPLICATION_ID env or 101
2715
+ --profile <2x2|6x6> Circuit artifact profile (default: ZK_PROFILE / ZK_CONFIG_NONCE=7 → 6x6)
2558
2716
  --output-proof <file> Write proof hex to file
2559
2717
  --output-public <file> Write public signals hex to file
2718
+ --output-ciphertext <file> Write userspace MiMC ciphertext blob hex to file
2560
2719
 
2561
- deposit-proof Groth16 proof for deposit-only transact (2 dummy withdraws, 1 deposit + dummy)
2720
+ deposit-proof Groth16 proof for deposit-only transact (dummy withdraws, 1 deposit + dummy outs)
2562
2721
  --state-root <dec> Current Merkle root (decimal Fr), must match pool on submit
2563
2722
  --stealth <stpl1...> Recipient stealth address (decodes to recipient public key)
2564
2723
  --token <C...> Asset contract id (or set TOKEN_ADDRESS)
@@ -2566,8 +2725,10 @@ Commands:
2566
2725
  --ephemeral-scalar-hex <hex> Same 32-byte hex as deposit / generate --scalar
2567
2726
  --value <dec> Deposit amount (stroops as decimal Fr); optional if --coin (must match coin)
2568
2727
  --application-id <dec> Application id (decimal Fr); default: APPLICATION_ID env or 101
2728
+ --profile <2x2|6x6> Circuit artifact profile (default: ZK_PROFILE / ZK_CONFIG_NONCE=7 → 6x6)
2569
2729
  --output-proof <file> Write proof hex to file
2570
2730
  --output-public <file> Write public signals hex to file
2731
+ --output-ciphertext <file> Write userspace MiMC ciphertext blob hex to file
2571
2732
 
2572
2733
  Output (withdraw, deposit-proof):
2573
2734
  Prints proof_hex on first line and public_hex on second line to stdout.
@@ -2724,6 +2885,13 @@ async function handleKytSubmitApproved(args) {
2724
2885
  });
2725
2886
  console.log(txHash);
2726
2887
  }
2888
+ function parseStroopsU64(label, raw, _min) {
2889
+ if (!/^\d+$/.test(raw)) {
2890
+ console.error(`Error: ${label} must be a non-negative decimal integer`);
2891
+ process.exit(1);
2892
+ }
2893
+ return BigInt(raw);
2894
+ }
2727
2895
  function parseCoinValueDecimal(label, raw, defaultStroops) {
2728
2896
  if (raw === undefined) {
2729
2897
  return defaultStroops.toString(10);
@@ -2815,9 +2983,10 @@ async function handleWithdraw(args) {
2815
2983
  console.error('Error: --change-stealth is only valid with --public-withdraw-stroops');
2816
2984
  process.exit(1);
2817
2985
  }
2818
- const sdk = await PrivacyPoolSDK.init();
2986
+ const { zkConfigNonce } = resolveZkInit(parsed);
2987
+ const sdk = await PrivacyPoolSDK.init({ zkConfigNonce });
2819
2988
  const applicationId = resolveApplicationIdFromCli(parsed);
2820
- const audit = resolveTransactionAuditParams(applicationId);
2989
+ const audit = resolveTransactionAuditParams(applicationId, undefined, sdk.getLayout().nAuditSlots);
2821
2990
  const result = await sdk.proveWithdrawal(coin, state, {
2822
2991
  withdrawAddressHi: withdrawHi,
2823
2992
  withdrawAddressLo: withdrawLo,
@@ -2828,7 +2997,7 @@ async function handleWithdraw(args) {
2828
2997
  audit,
2829
2998
  ...(pubW !== undefined
2830
2999
  ? {
2831
- publicWithdrawStroops: parseStroopsU64('--public-withdraw-stroops', pubW, 0n),
3000
+ publicWithdrawStroops: parseStroopsU64('--public-withdraw-stroops', pubW),
2832
3001
  changeRecipientStealthAddress: changeStealth,
2833
3002
  }
2834
3003
  : {}),
@@ -2845,6 +3014,10 @@ async function handleWithdraw(args) {
2845
3014
  fs__namespace.writeFileSync(parsed['output-public'], result.public_hex);
2846
3015
  console.error(`Public signals written to: ${parsed['output-public']}`);
2847
3016
  }
3017
+ if (parsed['output-ciphertext'] && result.ciphertext_hex) {
3018
+ fs__namespace.writeFileSync(parsed['output-ciphertext'], result.ciphertext_hex);
3019
+ console.error(`Ciphertext written to: ${parsed['output-ciphertext']}`);
3020
+ }
2848
3021
  }
2849
3022
  async function handleDepositProof(args) {
2850
3023
  const parsed = parseArgs(args);
@@ -2862,9 +3035,10 @@ async function handleDepositProof(args) {
2862
3035
  process.exit(1);
2863
3036
  }
2864
3037
  const [assetHi, assetLo] = stellarContractAddressToAssetFrDecimals(token);
2865
- const sdk = await PrivacyPoolSDK.init();
3038
+ const { zkConfigNonce } = resolveZkInit(parsed);
3039
+ const sdk = await PrivacyPoolSDK.init({ zkConfigNonce });
2866
3040
  const applicationId = resolveApplicationIdFromCli(parsed);
2867
- const audit = resolveTransactionAuditParams(applicationId);
3041
+ const audit = resolveTransactionAuditParams(applicationId, undefined, sdk.getLayout().nAuditSlots);
2868
3042
  const recipientPublicKeys = recipientPublicKeysDecimalFromStealthAddress(parsed['stealth']);
2869
3043
  const coinPath = parsed['coin'];
2870
3044
  const ephemeralScalarHex = parsed['ephemeral-scalar-hex'];
@@ -2928,6 +3102,10 @@ async function handleDepositProof(args) {
2928
3102
  fs__namespace.writeFileSync(parsed['output-public'], result.public_hex);
2929
3103
  console.error(`Public signals written to: ${parsed['output-public']}`);
2930
3104
  }
3105
+ if (parsed['output-ciphertext'] && result.ciphertext_hex) {
3106
+ fs__namespace.writeFileSync(parsed['output-ciphertext'], result.ciphertext_hex);
3107
+ console.error(`Ciphertext written to: ${parsed['output-ciphertext']}`);
3108
+ }
2931
3109
  }
2932
3110
  main().catch((err) => {
2933
3111
  console.error('Error:', err.message || err);