@auditable/privacy-pool-zk-sdk 0.7.0 → 0.9.1-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ciphertext-blob.d.ts +17 -0
- package/dist/circuit-artifacts.d.ts +9 -0
- package/dist/cli.js +1366 -1017
- package/dist/cli.js.map +1 -1
- package/dist/derived-escrow-key.d.ts +10 -0
- package/dist/domain-separators.d.ts +16 -0
- package/dist/index.d.ts +15 -8
- package/dist/index.mjs +1601 -1145
- package/dist/index.mjs.map +1 -1
- package/dist/kyt-flow.d.ts +2 -3
- package/dist/kyt-passage.d.ts +23 -12
- package/dist/output-note-encryption.d.ts +32 -2
- package/dist/prover.d.ts +3 -3
- package/dist/sdk.d.ts +44 -34
- package/dist/stealth-sign-message.d.ts +15 -2
- package/dist/stealth-signature.d.ts +13 -5
- package/dist/transaction-audit.d.ts +13 -12
- package/dist/types.d.ts +14 -4
- package/dist/withdrawal-transaction-input.d.ts +60 -45
- package/dist/witness.d.ts +2 -1
- package/dist/zk-artifact-url.d.ts +4 -0
- package/dist/zk-layout.d.ts +84 -0
- package/package.json +4 -9
- package/pkg/client_sdk_wasm.d.ts +29 -15
- package/pkg/client_sdk_wasm.js +129 -48
- package/pkg/client_sdk_wasm_bg.wasm +0 -0
- package/pkg/client_sdk_wasm_bg.wasm.d.ts +7 -5
- package/assets/main.wasm +0 -0
- package/assets/main_final.zkey +0 -0
- package/assets/witness_calculator.js +0 -383
- package/dist/kyt-onboarding.d.ts +0 -36
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
|
|
6
|
+
var circomlibjs = require('circomlibjs');
|
|
7
7
|
|
|
8
8
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
9
9
|
function _interopNamespaceDefault(e) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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$
|
|
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$
|
|
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);
|
|
@@ -227,7 +222,7 @@ function bytesToHex$1(bytes) {
|
|
|
227
222
|
}
|
|
228
223
|
return s;
|
|
229
224
|
}
|
|
230
|
-
function concatBytes(a, b) {
|
|
225
|
+
function concatBytes$1(a, b) {
|
|
231
226
|
const out = new Uint8Array(a.length + b.length);
|
|
232
227
|
out.set(a, 0);
|
|
233
228
|
out.set(b, a.length);
|
|
@@ -243,7 +238,7 @@ function encodeStealthAddress(decoded) {
|
|
|
243
238
|
if (xb.length !== yb.length) {
|
|
244
239
|
throw new Error('stealth-address: x and y must encode the same number of bytes for a reversible address');
|
|
245
240
|
}
|
|
246
|
-
const payload = concatBytes(xb, yb);
|
|
241
|
+
const payload = concatBytes$1(xb, yb);
|
|
247
242
|
const words = bech32.toWords(payload);
|
|
248
243
|
return bech32.encode(STEALTH_ADDRESS_HRP, words, BECH32_STEALTH_LIMIT);
|
|
249
244
|
}
|
|
@@ -268,375 +263,57 @@ function decodeStealthAddress(address) {
|
|
|
268
263
|
|
|
269
264
|
/** Default nonce when deriving the wallet sign-in message. */
|
|
270
265
|
const DEFAULT_STEALTH_SIGN_NONCE = 'main address';
|
|
271
|
-
/**
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
function
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/** Bech32 HRP for {@link DecodedEphemeralKey} only (`x ‖ y`, 64 bytes). */
|
|
280
|
-
const DECODED_EPHEMERAL_HRP = 'epk1';
|
|
281
|
-
/** Bech32 HRP for {@link DecodedDepositorSharedSecretPreimage} (96 bytes). */
|
|
282
|
-
const DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP = 'epk_dep_pre1';
|
|
283
|
-
const FIELD_BYTES = 32;
|
|
284
|
-
const EPK_POINT_PAYLOAD = FIELD_BYTES * 2;
|
|
285
|
-
const EPK_DEP_PRE_PAYLOAD = FIELD_BYTES * 3;
|
|
286
|
-
const BECH32_LONG_LIMIT = 1023;
|
|
287
|
-
function normalizeHex$1(hex) {
|
|
288
|
-
const s = hex.trim().replace(/^0x/i, '');
|
|
289
|
-
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
290
|
-
throw new Error('ephemeral-key: hex must contain only 0-9, a-f');
|
|
291
|
-
}
|
|
292
|
-
return s.length % 2 === 0 ? s : `0${s}`;
|
|
293
|
-
}
|
|
294
|
-
function hexToBytes$1(hex) {
|
|
295
|
-
const norm = normalizeHex$1(hex);
|
|
296
|
-
const out = new Uint8Array(norm.length / 2);
|
|
297
|
-
for (let i = 0; i < out.length; i++) {
|
|
298
|
-
out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
|
|
299
|
-
}
|
|
300
|
-
return out;
|
|
301
|
-
}
|
|
302
|
-
function bytesToHex(bytes) {
|
|
303
|
-
let s = '';
|
|
304
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
305
|
-
s += bytes[i].toString(16).padStart(2, '0');
|
|
306
|
-
}
|
|
307
|
-
return s;
|
|
308
|
-
}
|
|
309
|
-
function concat2(a, b) {
|
|
310
|
-
const out = new Uint8Array(a.length + b.length);
|
|
311
|
-
out.set(a, 0);
|
|
312
|
-
out.set(b, a.length);
|
|
313
|
-
return out;
|
|
314
|
-
}
|
|
315
|
-
function concat3(a, b, c) {
|
|
316
|
-
const out = new Uint8Array(a.length + b.length + c.length);
|
|
317
|
-
out.set(a, 0);
|
|
318
|
-
out.set(b, a.length);
|
|
319
|
-
out.set(c, a.length + b.length);
|
|
320
|
-
return out;
|
|
321
|
-
}
|
|
322
|
-
function require32(label, bytes) {
|
|
323
|
-
if (bytes.length !== FIELD_BYTES) {
|
|
324
|
-
throw new Error(`ephemeral-key: ${label} must encode exactly ${FIELD_BYTES} bytes, got ${bytes.length}`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* 32-byte big-endian integer as hex (64 chars), **< 2^253**, so BabyJub ECDH matches
|
|
329
|
-
* `circuits/encryption.circom` `Num2Bits(253)` and `libs/cryptography` `scalar_mul_253`.
|
|
330
|
-
*/
|
|
331
|
-
function generateRandomScalarHex32() {
|
|
332
|
-
const g = globalThis.crypto;
|
|
333
|
-
if (!g?.getRandomValues) {
|
|
334
|
-
throw new Error('ephemeral-key: crypto.getRandomValues is required');
|
|
335
|
-
}
|
|
336
|
-
const max = 1n << 253n;
|
|
337
|
-
for (let attempt = 0; attempt < 65536; attempt++) {
|
|
338
|
-
const b = new Uint8Array(32);
|
|
339
|
-
g.getRandomValues(b);
|
|
340
|
-
const hex = bytesToHex(b);
|
|
341
|
-
if (BigInt(`0x${hex}`) < max) {
|
|
342
|
-
return hex;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
throw new Error('ephemeral-key: failed to sample scalar < 2^253');
|
|
346
|
-
}
|
|
347
|
-
/** Bech32 `epk1`: encodes `x ‖ y` (64 bytes). */
|
|
348
|
-
function encodeDecodedEphemeralKey(decoded) {
|
|
349
|
-
const xb = hexToBytes$1(decoded.x);
|
|
350
|
-
const yb = hexToBytes$1(decoded.y);
|
|
351
|
-
require32('x', xb);
|
|
352
|
-
require32('y', yb);
|
|
353
|
-
const payload = concat2(xb, yb);
|
|
354
|
-
const words = bech32.toWords(payload);
|
|
355
|
-
return bech32.encode(DECODED_EPHEMERAL_HRP, words, BECH32_LONG_LIMIT);
|
|
356
|
-
}
|
|
357
|
-
function decodeDecodedEphemeralKey(encoded) {
|
|
358
|
-
const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
|
|
359
|
-
if (prefix !== DECODED_EPHEMERAL_HRP) {
|
|
360
|
-
throw new Error(`ephemeral-key: expected HRP ${DECODED_EPHEMERAL_HRP}, got ${JSON.stringify(prefix)}`);
|
|
361
|
-
}
|
|
362
|
-
const bytes = new Uint8Array(bech32.fromWords(words));
|
|
363
|
-
if (bytes.length !== EPK_POINT_PAYLOAD) {
|
|
364
|
-
throw new Error(`ephemeral-key: epk1 payload must be ${EPK_POINT_PAYLOAD} bytes, got ${bytes.length}`);
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
x: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
|
|
368
|
-
y: bytesToHex(bytes.subarray(FIELD_BYTES)),
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
/** Bech32 `epk_dep_pre1`: `randomNonceScalar ‖ recipient.x ‖ recipient.y`. */
|
|
372
|
-
function encodeDepositorSharedSecretPreimage(decoded) {
|
|
373
|
-
const sb = hexToBytes$1(decoded.randomNonceScalar);
|
|
374
|
-
const xb = hexToBytes$1(decoded.recipientStealthAddress.x);
|
|
375
|
-
const yb = hexToBytes$1(decoded.recipientStealthAddress.y);
|
|
376
|
-
require32('randomNonceScalar', sb);
|
|
377
|
-
require32('recipientStealthAddress.x', xb);
|
|
378
|
-
require32('recipientStealthAddress.y', yb);
|
|
379
|
-
const payload = concat3(sb, xb, yb);
|
|
380
|
-
const words = bech32.toWords(payload);
|
|
381
|
-
return bech32.encode(DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, words, BECH32_LONG_LIMIT);
|
|
266
|
+
/** Domain tag mixed into the spend-scalar digest (H6). */
|
|
267
|
+
const SPEND_SCALAR_DOMAIN_TAG = 'privacy-pool-spend-scalar-v1';
|
|
268
|
+
/** Schema version bound into the spend-scalar message and digest. */
|
|
269
|
+
const OWNER_BOUND_NOTE_SCHEMA_VERSION = 1;
|
|
270
|
+
function resolveSpendScalarSchemaVersion(domain) {
|
|
271
|
+
return domain.schemaVersion ?? OWNER_BOUND_NOTE_SCHEMA_VERSION;
|
|
382
272
|
}
|
|
383
|
-
function decodeDepositorSharedSecretPreimage(encoded) {
|
|
384
|
-
const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
|
|
385
|
-
if (prefix !== DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP) {
|
|
386
|
-
throw new Error(`ephemeral-key: expected HRP ${DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP}, got ${JSON.stringify(prefix)}`);
|
|
387
|
-
}
|
|
388
|
-
const bytes = new Uint8Array(bech32.fromWords(words));
|
|
389
|
-
if (bytes.length !== EPK_DEP_PRE_PAYLOAD) {
|
|
390
|
-
throw new Error(`ephemeral-key: epk_dep_pre1 payload must be ${EPK_DEP_PRE_PAYLOAD} bytes, got ${bytes.length}`);
|
|
391
|
-
}
|
|
392
|
-
return {
|
|
393
|
-
randomNonceScalar: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
|
|
394
|
-
recipientStealthAddress: {
|
|
395
|
-
x: bytesToHex(bytes.subarray(FIELD_BYTES, FIELD_BYTES * 2)),
|
|
396
|
-
y: bytesToHex(bytes.subarray(FIELD_BYTES * 2)),
|
|
397
|
-
},
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const DEFAULT_APPLICATION_ID = '101';
|
|
402
|
-
/** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
|
|
403
|
-
const DEMO_AUDIT_PUBLIC_KEY = [
|
|
404
|
-
'21605515851820432880964235241069234202284600780825340516808373216881770219365',
|
|
405
|
-
'18856460861531942120859708048677603751294231190189224157283439874962410808705',
|
|
406
|
-
];
|
|
407
|
-
function isActiveWithdraw(slot) {
|
|
408
|
-
return slot.value !== '0';
|
|
409
|
-
}
|
|
410
|
-
function isActiveDeposit(slot) {
|
|
411
|
-
return slot.value !== '0';
|
|
412
|
-
}
|
|
413
|
-
function resolveWithdrawSlot(slot) {
|
|
414
|
-
if (slot === 'dummy') {
|
|
415
|
-
return null;
|
|
416
|
-
}
|
|
417
|
-
return slot;
|
|
418
|
-
}
|
|
419
|
-
function resolveDepositSlot(slot) {
|
|
420
|
-
if (slot === 'dummy') {
|
|
421
|
-
return null;
|
|
422
|
-
}
|
|
423
|
-
return slot;
|
|
424
|
-
}
|
|
425
|
-
function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey) {
|
|
426
|
-
return {
|
|
427
|
-
applicationId,
|
|
428
|
-
noteAuditPublicKeys: [
|
|
429
|
-
auditPublicKey,
|
|
430
|
-
auditPublicKey,
|
|
431
|
-
auditPublicKey,
|
|
432
|
-
auditPublicKey,
|
|
433
|
-
],
|
|
434
|
-
auditEphemeralScalars: [
|
|
435
|
-
randomFrDecimal253(),
|
|
436
|
-
randomFrDecimal253(),
|
|
437
|
-
randomFrDecimal253(),
|
|
438
|
-
randomFrDecimal253(),
|
|
439
|
-
],
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
function resolveSlotApplicationIds(audit, withdrawSlots, depositSlots) {
|
|
443
|
-
const w0 = resolveWithdrawSlot(withdrawSlots[0]);
|
|
444
|
-
const w1 = resolveWithdrawSlot(withdrawSlots[1]);
|
|
445
|
-
const d0 = resolveDepositSlot(depositSlots[0]);
|
|
446
|
-
const d1 = resolveDepositSlot(depositSlots[1]);
|
|
447
|
-
return {
|
|
448
|
-
inputApplicationIds: [
|
|
449
|
-
w0 && isActiveWithdraw(w0) ? audit.applicationId : '0',
|
|
450
|
-
w1 && isActiveWithdraw(w1) ? audit.applicationId : '0',
|
|
451
|
-
],
|
|
452
|
-
outputApplicationIds: [
|
|
453
|
-
d0 && isActiveDeposit(d0) ? audit.applicationId : '0',
|
|
454
|
-
d1 && isActiveDeposit(d1) ? audit.applicationId : '0',
|
|
455
|
-
],
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
function resolveTransactionAuditParams(applicationId, auditPublicKey) {
|
|
459
|
-
return buildUniformAuditParams(applicationId, auditPublicKey ?? DEMO_AUDIT_PUBLIC_KEY);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 9)` in `circuits/main.circom`. */
|
|
463
|
-
const TRANSACTION_TREE_DEPTH = 20;
|
|
464
|
-
/** BN254 scalar field modulus (ark `Fr`, circom signals). */
|
|
465
|
-
const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
466
273
|
/**
|
|
467
|
-
*
|
|
468
|
-
* (
|
|
274
|
+
* Plaintext for Stellar wallet message signing (spend-key derivation).
|
|
275
|
+
* Sign the exact bytes of this string (UTF-8) with the stellar account key.
|
|
276
|
+
*
|
|
277
|
+
* The derived scalar is the owner-bound spend key for notes in this environment.
|
|
469
278
|
*/
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
279
|
+
function buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
|
|
280
|
+
const schemaVersion = resolveSpendScalarSchemaVersion(domain);
|
|
281
|
+
return [
|
|
282
|
+
'Arcane privacy layer: derive your private-note spend key.',
|
|
283
|
+
'',
|
|
284
|
+
`Wallet: ${address}`,
|
|
285
|
+
`Network: ${domain.networkPassphrase}`,
|
|
286
|
+
`Pool: ${domain.poolContract}`,
|
|
287
|
+
`Registry: ${domain.registryContract}`,
|
|
288
|
+
`Schema: owner-bound-note-v${schemaVersion}`,
|
|
289
|
+
`Nonce: ${nonce}`,
|
|
290
|
+
'',
|
|
291
|
+
'This signature derives the key that authorizes spending your private notes in this environment.',
|
|
292
|
+
].join('\n');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const DOM_ENC = 1346719299n;
|
|
296
|
+
const DOM_AUDIT_TAG = 1346458695n;
|
|
297
|
+
const DOM_OUTPUT_NOTE_TAG = 1347310663n;
|
|
298
|
+
const BABYJUB_SUBGROUP_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
487
299
|
/**
|
|
488
|
-
*
|
|
489
|
-
*
|
|
300
|
+
* Map any integer into `(0, l)` so it satisfies circom `LessThan(252)` vs
|
|
301
|
+
* `BABYJUB_SUBGROUP_ORDER` and `BabyPbk`. Values already in range are unchanged.
|
|
490
302
|
*/
|
|
491
|
-
function
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
throw new Error('withdrawal-transaction-input: scalar hex too long');
|
|
303
|
+
function canonicalBabyJubScalarFromInteger(value) {
|
|
304
|
+
if (value > 0n && value < BABYJUB_SUBGROUP_ORDER) {
|
|
305
|
+
return value;
|
|
495
306
|
}
|
|
496
|
-
|
|
497
|
-
if (v >= BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE) {
|
|
498
|
-
throw new Error('depositor ephemeral scalar must be < 2^253 (BabyJub Num2Bits); resample with random-scalar');
|
|
499
|
-
}
|
|
500
|
-
return v.toString(10);
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Stellar G-address Ed25519 payload (32 bytes as 64 hex, optional 0x) → two circom public decimals
|
|
504
|
-
* (`withdrawAddressHi` / `withdrawAddressLo`). No mod-r; each half fits in 128 bits.
|
|
505
|
-
*/
|
|
506
|
-
function ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex) {
|
|
507
|
-
const h = normalizeHex(hex).padStart(64, '0').slice(-64);
|
|
508
|
-
const hi = BigInt(`0x${h.slice(0, 32)}`);
|
|
509
|
-
const lo = BigInt(`0x${h.slice(32, 64)}`);
|
|
510
|
-
return { hi: hi.toString(10), lo: lo.toString(10) };
|
|
511
|
-
}
|
|
512
|
-
/**
|
|
513
|
-
* Stellar contract id (`C…`) → two circom field decimals for `asset[0]`, `asset[1]` (same 32-byte split as accounts).
|
|
514
|
-
*/
|
|
515
|
-
function stellarContractAddressToAssetFrDecimals(address) {
|
|
516
|
-
const raw = stellarSdk.StrKey.decodeContract(address);
|
|
517
|
-
const hex = Buffer.from(raw).toString('hex');
|
|
518
|
-
const { hi, lo } = ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex);
|
|
519
|
-
return [hi, lo];
|
|
520
|
-
}
|
|
521
|
-
/** Uniform random `Fr` as decimal (32 random bytes, mod r). For Poseidon-only inputs (e.g. nullifiers). */
|
|
522
|
-
function randomFrDecimal() {
|
|
523
|
-
const hex = generateRandomScalarHex32();
|
|
524
|
-
const v = BigInt(`0x${normalizeHex(hex)}`);
|
|
525
|
-
return (v % BN254_SCALAR_MOD).toString(10);
|
|
526
|
-
}
|
|
527
|
-
/** Random scalar < 2^253 for BabyJub ECDH / `Num2Bits(253)` (uses {@link generateRandomScalarHex32}). */
|
|
528
|
-
function randomFrDecimal253() {
|
|
529
|
-
const hex = generateRandomScalarHex32();
|
|
530
|
-
return BigInt(`0x${normalizeHex(hex)}`).toString(10);
|
|
531
|
-
}
|
|
532
|
-
function zerosTreeSiblings() {
|
|
533
|
-
return Array(TRANSACTION_TREE_DEPTH).fill('0');
|
|
534
|
-
}
|
|
535
|
-
function dummyWithdraw(wasm) {
|
|
536
|
-
const nullifier = randomFrDecimal();
|
|
537
|
-
const secretHex = generateRandomScalarHex32();
|
|
538
|
-
const secret = (BigInt(`0x${normalizeHex(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
|
|
539
|
-
const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(secretHex);
|
|
540
|
-
return {
|
|
541
|
-
value: '0',
|
|
542
|
-
nullifier,
|
|
543
|
-
secret,
|
|
544
|
-
asset: ['0', '0'],
|
|
545
|
-
applicationId: '0',
|
|
546
|
-
ephemeralKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
|
|
547
|
-
stateSiblings: zerosTreeSiblings(),
|
|
548
|
-
stateIndex: '0',
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
function dummyDeposit(wasm) {
|
|
552
|
-
const nullifier = randomFrDecimal();
|
|
553
|
-
const ephemeralKeyScalar = randomFrDecimal253();
|
|
554
|
-
const skHex = generateRandomScalarHex32();
|
|
555
|
-
const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(skHex);
|
|
556
|
-
return {
|
|
557
|
-
value: '0',
|
|
558
|
-
nullifier,
|
|
559
|
-
ephemeralKeyScalar,
|
|
560
|
-
asset: ['0', '0'],
|
|
561
|
-
applicationId: '0',
|
|
562
|
-
recipientPublicKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
function resolveWithdraw(slot, wasm) {
|
|
566
|
-
return slot === 'dummy' ? dummyWithdraw(wasm) : slot;
|
|
567
|
-
}
|
|
568
|
-
function resolveDeposit(slot, wasm) {
|
|
569
|
-
return slot === 'dummy' ? dummyDeposit(wasm) : slot;
|
|
570
|
-
}
|
|
571
|
-
function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, depositSlots, audit, wasm) {
|
|
572
|
-
const w0 = resolveWithdraw(withdrawSlots[0], wasm);
|
|
573
|
-
const w1 = resolveWithdraw(withdrawSlots[1], wasm);
|
|
574
|
-
const d0 = resolveDeposit(depositSlots[0], wasm);
|
|
575
|
-
const d1 = resolveDeposit(depositSlots[1], wasm);
|
|
576
|
-
const appIds = resolveSlotApplicationIds(audit, withdrawSlots, depositSlots);
|
|
577
|
-
return {
|
|
578
|
-
stateRoot: publicParams.stateRoot,
|
|
579
|
-
withdrawAddressHi: publicParams.withdrawAddressHi,
|
|
580
|
-
withdrawAddressLo: publicParams.withdrawAddressLo,
|
|
581
|
-
privKeyScalar: publicParams.privKeyScalar,
|
|
582
|
-
withdrawnValues: [w0.value, w1.value],
|
|
583
|
-
withdrawnNullifiers: [w0.nullifier, w1.nullifier],
|
|
584
|
-
withdrawnSecrets: [w0.secret, w1.secret],
|
|
585
|
-
withdrawnAssets: [w0.asset, w1.asset],
|
|
586
|
-
ephemeralKeys: [w0.ephemeralKeys, w1.ephemeralKeys],
|
|
587
|
-
stateSiblings: [w0.stateSiblings, w1.stateSiblings],
|
|
588
|
-
stateIndex: [w0.stateIndex, w1.stateIndex],
|
|
589
|
-
depositedValues: [d0.value, d1.value],
|
|
590
|
-
depositedNullifiers: [d0.nullifier, d1.nullifier],
|
|
591
|
-
depositedAssets: [d0.asset, d1.asset],
|
|
592
|
-
depositedEphemeralKeyScalars: [d0.ephemeralKeyScalar, d1.ephemeralKeyScalar],
|
|
593
|
-
depositedRecipientPublicKeys: [d0.recipientPublicKeys, d1.recipientPublicKeys],
|
|
594
|
-
inputApplicationIds: appIds.inputApplicationIds,
|
|
595
|
-
outputApplicationIds: appIds.outputApplicationIds,
|
|
596
|
-
auditEphemeralScalars: audit.auditEphemeralScalars,
|
|
597
|
-
noteAuditPublicKeys: audit.noteAuditPublicKeys,
|
|
598
|
-
publicWithdrawnAssets: publicLegs.publicWithdrawnAssets,
|
|
599
|
-
publicDepositedAssets: publicLegs.publicDepositedAssets,
|
|
600
|
-
publicDeposits: publicLegs.publicDeposits,
|
|
601
|
-
publicWithdrawals: publicLegs.publicWithdrawals,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
/** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
|
|
605
|
-
function recipientPublicKeysDecimalFromStealthAddress(stealthAddress) {
|
|
606
|
-
const { x, y } = decodeStealthAddress(stealthAddress);
|
|
607
|
-
return [coordHexToDecimal(x), coordHexToDecimal(y)];
|
|
608
|
-
}
|
|
609
|
-
/** First withdraw leg: Merkle witness + depositor ECDH point coordinates (hex). */
|
|
610
|
-
function withdrawObjectFromMerkleWitness(witness, depositorEphemeralHex, applicationId) {
|
|
611
|
-
return {
|
|
612
|
-
value: witness.value,
|
|
613
|
-
nullifier: witness.nullifier,
|
|
614
|
-
secret: witness.secret,
|
|
615
|
-
asset: witness.withdrawnAsset,
|
|
616
|
-
applicationId,
|
|
617
|
-
ephemeralKeys: [
|
|
618
|
-
coordHexToDecimal(depositorEphemeralHex.x),
|
|
619
|
-
coordHexToDecimal(depositorEphemeralHex.y),
|
|
620
|
-
],
|
|
621
|
-
stateSiblings: witness.stateSiblings,
|
|
622
|
-
stateIndex: witness.stateIndex,
|
|
623
|
-
};
|
|
307
|
+
return (value % (BABYJUB_SUBGROUP_ORDER - 1n)) + 1n;
|
|
624
308
|
}
|
|
625
309
|
|
|
626
|
-
function hexToBytes(hex) {
|
|
310
|
+
function hexToBytes$1(hex) {
|
|
627
311
|
const out = new Uint8Array(hex.length / 2);
|
|
628
312
|
for (let i = 0; i < out.length; i++) {
|
|
629
313
|
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
630
314
|
}
|
|
631
315
|
return out;
|
|
632
316
|
}
|
|
633
|
-
function bytesToHexLower(bytes) {
|
|
634
|
-
let s = '';
|
|
635
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
636
|
-
s += bytes[i].toString(16).padStart(2, '0');
|
|
637
|
-
}
|
|
638
|
-
return s;
|
|
639
|
-
}
|
|
640
317
|
/** Base64 → bytes via `atob` (browsers; Node 16+). No Node `Buffer`. */
|
|
641
318
|
function decodeBase64ToBytes(s) {
|
|
642
319
|
const t = s.replace(/\s/g, '');
|
|
@@ -662,7 +339,7 @@ function parseStellarEd25519SignatureRaw(input) {
|
|
|
662
339
|
const trimmed = input.trim();
|
|
663
340
|
const no0x = trimmed.replace(/^0x/i, '');
|
|
664
341
|
if (/^[0-9a-fA-F]+$/.test(no0x) && no0x.length === 128) {
|
|
665
|
-
return hexToBytes(no0x.toLowerCase());
|
|
342
|
+
return hexToBytes$1(no0x.toLowerCase());
|
|
666
343
|
}
|
|
667
344
|
const fromB64 = decodeBase64ToBytes(trimmed);
|
|
668
345
|
if (fromB64.length !== 64) {
|
|
@@ -680,29 +357,66 @@ async function sha256(data) {
|
|
|
680
357
|
copy.set(data);
|
|
681
358
|
return new Uint8Array(await subtle.digest('SHA-256', copy));
|
|
682
359
|
}
|
|
360
|
+
function appendU32BE(parts, value) {
|
|
361
|
+
const buf = new Uint8Array(4);
|
|
362
|
+
new DataView(buf.buffer).setUint32(0, value >>> 0, false);
|
|
363
|
+
parts.push(buf);
|
|
364
|
+
}
|
|
365
|
+
function appendUtf8Prefixed(parts, text) {
|
|
366
|
+
const bytes = new TextEncoder().encode(text);
|
|
367
|
+
appendU32BE(parts, bytes.length);
|
|
368
|
+
parts.push(bytes);
|
|
369
|
+
}
|
|
370
|
+
function concatBytes(parts) {
|
|
371
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
372
|
+
const out = new Uint8Array(total);
|
|
373
|
+
let offset = 0;
|
|
374
|
+
for (const part of parts) {
|
|
375
|
+
out.set(part, offset);
|
|
376
|
+
offset += part.length;
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
683
380
|
/**
|
|
684
|
-
*
|
|
381
|
+
* Domain-separated spend-scalar digest: SHA-256(
|
|
382
|
+
* SPEND_SCALAR_DOMAIN_TAG || network || pool || registry || schemaVersion || signature
|
|
383
|
+
* ).
|
|
685
384
|
*/
|
|
686
|
-
async function
|
|
385
|
+
async function spendScalarDigestFromStellarSignature(signature, domain) {
|
|
687
386
|
const raw = parseStellarEd25519SignatureRaw(signature);
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
387
|
+
const parts = [new TextEncoder().encode(SPEND_SCALAR_DOMAIN_TAG)];
|
|
388
|
+
appendUtf8Prefixed(parts, domain.networkPassphrase);
|
|
389
|
+
appendUtf8Prefixed(parts, domain.poolContract);
|
|
390
|
+
appendUtf8Prefixed(parts, domain.registryContract);
|
|
391
|
+
appendU32BE(parts, resolveSpendScalarSchemaVersion(domain));
|
|
392
|
+
parts.push(raw);
|
|
393
|
+
return sha256(concatBytes(parts));
|
|
394
|
+
}
|
|
395
|
+
function digestToCanonicalScalar(digest) {
|
|
396
|
+
let v = 0n;
|
|
397
|
+
for (let i = 0; i < digest.length; i++) {
|
|
398
|
+
v = (v << 8n) + BigInt(digest[i]);
|
|
399
|
+
}
|
|
400
|
+
return canonicalBabyJubScalarFromInteger(v);
|
|
401
|
+
}
|
|
402
|
+
function canonicalScalarHex(scalar) {
|
|
403
|
+
return scalar.toString(16).padStart(64, '0');
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Stellar Ed25519 signature (hex or base64) → domain-separated SHA-256 → scalar → WASM ECDH.
|
|
407
|
+
*/
|
|
408
|
+
async function stealthAddressFromStellarSignature(ecdhFromScalarHex, encodeStealth, signature, domain) {
|
|
409
|
+
const digest = await spendScalarDigestFromStellarSignature(signature, domain);
|
|
410
|
+
const decoded = ecdhFromScalarHex(canonicalScalarHex(digestToCanonicalScalar(digest)));
|
|
691
411
|
return encodeStealth(decoded);
|
|
692
412
|
}
|
|
693
413
|
/**
|
|
694
|
-
* SHA-256(signature
|
|
695
|
-
* `
|
|
414
|
+
* Domain-separated SHA-256(signature, network, pool, registry, schema) reduced into
|
|
415
|
+
* `(0, BabyJub subgroup order)` so `privKeyScalar` matches `BabyPbk` and circom `LessThan(l)`.
|
|
696
416
|
*/
|
|
697
|
-
async function privKeyScalarDecimalFromStellarSignature(signature) {
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
let v = 0n;
|
|
701
|
-
for (let i = 0; i < h.length; i++) {
|
|
702
|
-
v = (v << 8n) + BigInt(h[i]);
|
|
703
|
-
}
|
|
704
|
-
const mask = BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE - 1n;
|
|
705
|
-
return (v & mask).toString(10);
|
|
417
|
+
async function privKeyScalarDecimalFromStellarSignature(signature, domain) {
|
|
418
|
+
const digest = await spendScalarDigestFromStellarSignature(signature, domain);
|
|
419
|
+
return digestToCanonicalScalar(digest).toString(10);
|
|
706
420
|
}
|
|
707
421
|
|
|
708
422
|
/* @ts-self-types="./client_sdk_wasm.d.ts" */
|
|
@@ -743,35 +457,69 @@ function buildWithdrawMerkleWitness(coin_json, state_json) {
|
|
|
743
457
|
}
|
|
744
458
|
|
|
745
459
|
/**
|
|
746
|
-
* Calculate nullifier hash from nullifier decimal
|
|
460
|
+
* Calculate owner-bound nullifier hash from nullifier and spend-scalar decimal strings.
|
|
747
461
|
* Returns hex string (0x...)
|
|
748
462
|
* @param {string} nullifier_decimal
|
|
463
|
+
* @param {string} priv_key_scalar_decimal
|
|
749
464
|
* @returns {string}
|
|
750
465
|
*/
|
|
751
|
-
function calculateNullifierHash(nullifier_decimal) {
|
|
752
|
-
let
|
|
753
|
-
let
|
|
466
|
+
function calculateNullifierHash(nullifier_decimal, priv_key_scalar_decimal) {
|
|
467
|
+
let deferred4_0;
|
|
468
|
+
let deferred4_1;
|
|
754
469
|
try {
|
|
755
470
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
756
471
|
const ptr0 = passStringToWasm0(nullifier_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
757
472
|
const len0 = WASM_VECTOR_LEN;
|
|
758
|
-
|
|
473
|
+
const ptr1 = passStringToWasm0(priv_key_scalar_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
474
|
+
const len1 = WASM_VECTOR_LEN;
|
|
475
|
+
wasm.calculateNullifierHash(retptr, ptr0, len0, ptr1, len1);
|
|
759
476
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
760
477
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
761
478
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
762
479
|
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
763
|
-
var
|
|
764
|
-
var
|
|
480
|
+
var ptr3 = r0;
|
|
481
|
+
var len3 = r1;
|
|
765
482
|
if (r3) {
|
|
766
|
-
|
|
483
|
+
ptr3 = 0; len3 = 0;
|
|
767
484
|
throw takeObject(r2);
|
|
768
485
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
return getStringFromWasm0(
|
|
486
|
+
deferred4_0 = ptr3;
|
|
487
|
+
deferred4_1 = len3;
|
|
488
|
+
return getStringFromWasm0(ptr3, len3);
|
|
489
|
+
} finally {
|
|
490
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
491
|
+
wasm.__wbindgen_export4(deferred4_0, deferred4_1, 1);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* `R = Poseidon255(2)(hi, lo)`; `scalar` is the raw Poseidon hash when it is
|
|
497
|
+
* already in `(0, BabyJub subgroup order)`. Out-of-range hashes fail so the
|
|
498
|
+
* caller can rejection-sample a new nonce.
|
|
499
|
+
* @param {string} nonce_decimal
|
|
500
|
+
* @param {string} recipient_hi_decimal
|
|
501
|
+
* @param {string} recipient_lo_decimal
|
|
502
|
+
* @returns {any}
|
|
503
|
+
*/
|
|
504
|
+
function derivedEscrowKey(nonce_decimal, recipient_hi_decimal, recipient_lo_decimal) {
|
|
505
|
+
try {
|
|
506
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
507
|
+
const ptr0 = passStringToWasm0(nonce_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
508
|
+
const len0 = WASM_VECTOR_LEN;
|
|
509
|
+
const ptr1 = passStringToWasm0(recipient_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
510
|
+
const len1 = WASM_VECTOR_LEN;
|
|
511
|
+
const ptr2 = passStringToWasm0(recipient_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
512
|
+
const len2 = WASM_VECTOR_LEN;
|
|
513
|
+
wasm.derivedEscrowKey(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
|
|
514
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
515
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
516
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
517
|
+
if (r2) {
|
|
518
|
+
throw takeObject(r1);
|
|
519
|
+
}
|
|
520
|
+
return takeObject(r0);
|
|
772
521
|
} finally {
|
|
773
522
|
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
774
|
-
wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1);
|
|
775
523
|
}
|
|
776
524
|
}
|
|
777
525
|
|
|
@@ -852,26 +600,28 @@ function ecdhSharedKey(priv_hex, pub_x_hex, pub_y_hex) {
|
|
|
852
600
|
}
|
|
853
601
|
|
|
854
602
|
/**
|
|
855
|
-
* Generate a new coin with random nullifier, secret, and
|
|
856
|
-
* `
|
|
603
|
+
* Generate a new coin with random nullifier, secret, and owner public-key field elements.
|
|
604
|
+
* `amount_decimal` is an arbitrary-precision decimal field element (never JS `number`).
|
|
857
605
|
* `asset_hi_decimal` / `asset_lo_decimal` are decimal Fr strings for the Stellar asset contract id (two limbs).
|
|
858
606
|
* Returns JSON: { coin: { value, nullifier, secret, commitment, asset_hi, asset_lo }, commitment_hex, precommitement_hex }
|
|
859
|
-
* @param {
|
|
607
|
+
* @param {string} amount_decimal
|
|
860
608
|
* @param {string} asset_hi_decimal
|
|
861
609
|
* @param {string} asset_lo_decimal
|
|
862
610
|
* @param {string} application_id_decimal
|
|
863
611
|
* @returns {any}
|
|
864
612
|
*/
|
|
865
|
-
function generateCoin(
|
|
613
|
+
function generateCoin(amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
866
614
|
try {
|
|
867
615
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
868
|
-
const ptr0 = passStringToWasm0(
|
|
616
|
+
const ptr0 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
869
617
|
const len0 = WASM_VECTOR_LEN;
|
|
870
|
-
const ptr1 = passStringToWasm0(
|
|
618
|
+
const ptr1 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
871
619
|
const len1 = WASM_VECTOR_LEN;
|
|
872
|
-
const ptr2 = passStringToWasm0(
|
|
620
|
+
const ptr2 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
873
621
|
const len2 = WASM_VECTOR_LEN;
|
|
874
|
-
|
|
622
|
+
const ptr3 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
623
|
+
const len3 = WASM_VECTOR_LEN;
|
|
624
|
+
wasm.generateCoin(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
|
|
875
625
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
876
626
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
877
627
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
@@ -885,32 +635,34 @@ function generateCoin(amount, asset_hi_decimal, asset_lo_decimal, application_id
|
|
|
885
635
|
}
|
|
886
636
|
|
|
887
637
|
/**
|
|
888
|
-
* `Poseidon₁(scalar)` secret +
|
|
638
|
+
* `Poseidon₁(scalar)` secret + recipient owner public key (hex), matching an aligned deposit witness.
|
|
889
639
|
* @param {string} scalar_hex
|
|
890
|
-
* @param {string}
|
|
891
|
-
* @param {string}
|
|
892
|
-
* @param {
|
|
640
|
+
* @param {string} owner_x_hex
|
|
641
|
+
* @param {string} owner_y_hex
|
|
642
|
+
* @param {string} amount_decimal
|
|
893
643
|
* @param {string} asset_hi_decimal
|
|
894
644
|
* @param {string} asset_lo_decimal
|
|
895
645
|
* @param {string} application_id_decimal
|
|
896
646
|
* @returns {any}
|
|
897
647
|
*/
|
|
898
|
-
function
|
|
648
|
+
function generateCoinForDepositWithOwnerPubHex(scalar_hex, owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
899
649
|
try {
|
|
900
650
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
901
651
|
const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
902
652
|
const len0 = WASM_VECTOR_LEN;
|
|
903
|
-
const ptr1 = passStringToWasm0(
|
|
653
|
+
const ptr1 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
904
654
|
const len1 = WASM_VECTOR_LEN;
|
|
905
|
-
const ptr2 = passStringToWasm0(
|
|
655
|
+
const ptr2 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
906
656
|
const len2 = WASM_VECTOR_LEN;
|
|
907
|
-
const ptr3 = passStringToWasm0(
|
|
657
|
+
const ptr3 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
908
658
|
const len3 = WASM_VECTOR_LEN;
|
|
909
|
-
const ptr4 = passStringToWasm0(
|
|
659
|
+
const ptr4 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
910
660
|
const len4 = WASM_VECTOR_LEN;
|
|
911
|
-
const ptr5 = passStringToWasm0(
|
|
661
|
+
const ptr5 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
912
662
|
const len5 = WASM_VECTOR_LEN;
|
|
913
|
-
|
|
663
|
+
const ptr6 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
664
|
+
const len6 = WASM_VECTOR_LEN;
|
|
665
|
+
wasm.generateCoinForDepositWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6);
|
|
914
666
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
915
667
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
916
668
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
@@ -926,24 +678,26 @@ function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_
|
|
|
926
678
|
/**
|
|
927
679
|
* `secret` in coin = `Poseidon255(1)(scalar)` per `deposit.circom`; scalar is 32-byte hex (64 chars, optional `0x`).
|
|
928
680
|
* @param {string} scalar_hex
|
|
929
|
-
* @param {
|
|
681
|
+
* @param {string} amount_decimal
|
|
930
682
|
* @param {string} asset_hi_decimal
|
|
931
683
|
* @param {string} asset_lo_decimal
|
|
932
684
|
* @param {string} application_id_decimal
|
|
933
685
|
* @returns {any}
|
|
934
686
|
*/
|
|
935
|
-
function generateCoinFromDepositEphemeralScalarHex(scalar_hex,
|
|
687
|
+
function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
936
688
|
try {
|
|
937
689
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
938
690
|
const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
939
691
|
const len0 = WASM_VECTOR_LEN;
|
|
940
|
-
const ptr1 = passStringToWasm0(
|
|
692
|
+
const ptr1 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
941
693
|
const len1 = WASM_VECTOR_LEN;
|
|
942
|
-
const ptr2 = passStringToWasm0(
|
|
694
|
+
const ptr2 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
943
695
|
const len2 = WASM_VECTOR_LEN;
|
|
944
|
-
const ptr3 = passStringToWasm0(
|
|
696
|
+
const ptr3 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
945
697
|
const len3 = WASM_VECTOR_LEN;
|
|
946
|
-
|
|
698
|
+
const ptr4 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
699
|
+
const len4 = WASM_VECTOR_LEN;
|
|
700
|
+
wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4);
|
|
947
701
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
948
702
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
949
703
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
@@ -957,29 +711,31 @@ function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount, asset_hi_
|
|
|
957
711
|
}
|
|
958
712
|
|
|
959
713
|
/**
|
|
960
|
-
* Same as `generateCoin`, but commitment uses the given
|
|
961
|
-
* @param {string}
|
|
962
|
-
* @param {string}
|
|
963
|
-
* @param {
|
|
714
|
+
* Same as `generateCoin`, but commitment uses the given owner public key (64-char hex coords).
|
|
715
|
+
* @param {string} owner_x_hex
|
|
716
|
+
* @param {string} owner_y_hex
|
|
717
|
+
* @param {string} amount_decimal
|
|
964
718
|
* @param {string} asset_hi_decimal
|
|
965
719
|
* @param {string} asset_lo_decimal
|
|
966
720
|
* @param {string} application_id_decimal
|
|
967
721
|
* @returns {any}
|
|
968
722
|
*/
|
|
969
|
-
function
|
|
723
|
+
function generateCoinWithOwnerPubHex(owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
970
724
|
try {
|
|
971
725
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
972
|
-
const ptr0 = passStringToWasm0(
|
|
726
|
+
const ptr0 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
973
727
|
const len0 = WASM_VECTOR_LEN;
|
|
974
|
-
const ptr1 = passStringToWasm0(
|
|
728
|
+
const ptr1 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
975
729
|
const len1 = WASM_VECTOR_LEN;
|
|
976
|
-
const ptr2 = passStringToWasm0(
|
|
730
|
+
const ptr2 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
977
731
|
const len2 = WASM_VECTOR_LEN;
|
|
978
|
-
const ptr3 = passStringToWasm0(
|
|
732
|
+
const ptr3 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
979
733
|
const len3 = WASM_VECTOR_LEN;
|
|
980
|
-
const ptr4 = passStringToWasm0(
|
|
734
|
+
const ptr4 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
981
735
|
const len4 = WASM_VECTOR_LEN;
|
|
982
|
-
|
|
736
|
+
const ptr5 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
737
|
+
const len5 = WASM_VECTOR_LEN;
|
|
738
|
+
wasm.generateCoinWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5);
|
|
983
739
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
984
740
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
985
741
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
@@ -1016,6 +772,38 @@ function proofToHex(proof_json) {
|
|
|
1016
772
|
}
|
|
1017
773
|
}
|
|
1018
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
|
+
|
|
1019
807
|
/**
|
|
1020
808
|
* Convert snarkjs public signals JSON to hex bytes for Soroban contract.
|
|
1021
809
|
* @param {string} public_json
|
|
@@ -1226,6 +1014,13 @@ function isLikeNone(x) {
|
|
|
1226
1014
|
return x === undefined || x === null;
|
|
1227
1015
|
}
|
|
1228
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
|
+
|
|
1229
1024
|
function passStringToWasm0(arg, malloc, realloc) {
|
|
1230
1025
|
if (realloc === undefined) {
|
|
1231
1026
|
const buf = cachedTextEncoder.encode(arg);
|
|
@@ -1388,28 +1183,30 @@ async function __wbg_init(module_or_path) {
|
|
|
1388
1183
|
}
|
|
1389
1184
|
|
|
1390
1185
|
var wasmBindings = /*#__PURE__*/Object.freeze({
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
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
|
|
1405
1202
|
});
|
|
1406
1203
|
|
|
1407
|
-
const isNode$
|
|
1204
|
+
const isNode$1 = typeof process !== 'undefined' && !!process.versions?.node;
|
|
1408
1205
|
let wasmModule = null;
|
|
1409
1206
|
async function loadWasm(wasmBinary) {
|
|
1410
1207
|
if (wasmModule)
|
|
1411
1208
|
return wasmModule;
|
|
1412
|
-
if (isNode$
|
|
1209
|
+
if (isNode$1) {
|
|
1413
1210
|
if (wasmBinary) {
|
|
1414
1211
|
initSync({ module: wasmBinary });
|
|
1415
1212
|
}
|
|
@@ -1436,511 +1233,1020 @@ async function loadWasm(wasmBinary) {
|
|
|
1436
1233
|
return wasmModule;
|
|
1437
1234
|
}
|
|
1438
1235
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
} else if (code == 5) {
|
|
1470
|
-
err = "Not enough memory.\n";
|
|
1471
|
-
} else if (code == 6) {
|
|
1472
|
-
err = "Input signal array access exceeds the size.\n";
|
|
1473
|
-
} else if (code == 7) {
|
|
1474
|
-
err = "Out of bounds array access.\n";
|
|
1475
|
-
} else {
|
|
1476
|
-
err = "Unknown error.\n";
|
|
1477
|
-
}
|
|
1478
|
-
throw new Error(err + errStr);
|
|
1479
|
-
},
|
|
1480
|
-
printErrorMessage : function() {
|
|
1481
|
-
errStr += getMessage() + "\n";
|
|
1482
|
-
// console.error(getMessage());
|
|
1483
|
-
},
|
|
1484
|
-
writeBufferMessage : function() {
|
|
1485
|
-
const msg = getMessage();
|
|
1486
|
-
// Any calls to `log()` will always end with a `\n`, so that's when we print and reset
|
|
1487
|
-
if (msg === "\n") {
|
|
1488
|
-
console.log(msgStr);
|
|
1489
|
-
msgStr = "";
|
|
1490
|
-
} else {
|
|
1491
|
-
// If we've buffered other content, put a space in between the items
|
|
1492
|
-
if (msgStr !== "") {
|
|
1493
|
-
msgStr += " ";
|
|
1494
|
-
}
|
|
1495
|
-
// Then append the message to the message we are creating
|
|
1496
|
-
msgStr += msg;
|
|
1497
|
-
}
|
|
1498
|
-
},
|
|
1499
|
-
showSharedRWMemory : function() {
|
|
1500
|
-
printSharedRWMemory ();
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
}
|
|
1504
|
-
});
|
|
1505
|
-
|
|
1506
|
-
const sanityCheck =
|
|
1507
|
-
options;
|
|
1508
|
-
// options &&
|
|
1509
|
-
// (
|
|
1510
|
-
// options.sanityCheck ||
|
|
1511
|
-
// options.logGetSignal ||
|
|
1512
|
-
// options.logSetSignal ||
|
|
1513
|
-
// options.logStartComponent ||
|
|
1514
|
-
// options.logFinishComponent
|
|
1515
|
-
// );
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
wc = new WitnessCalculator(instance, sanityCheck);
|
|
1519
|
-
return wc;
|
|
1520
|
-
|
|
1521
|
-
function getMessage() {
|
|
1522
|
-
var message = "";
|
|
1523
|
-
var c = instance.exports.getMessageChar();
|
|
1524
|
-
while ( c != 0 ) {
|
|
1525
|
-
message += String.fromCharCode(c);
|
|
1526
|
-
c = instance.exports.getMessageChar();
|
|
1527
|
-
}
|
|
1528
|
-
return message;
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
function printSharedRWMemory () {
|
|
1532
|
-
const shared_rw_memory_size = instance.exports.getFieldNumLen32();
|
|
1533
|
-
const arr = new Uint32Array(shared_rw_memory_size);
|
|
1534
|
-
for (let j=0; j<shared_rw_memory_size; j++) {
|
|
1535
|
-
arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
// If we've buffered other content, put a space in between the items
|
|
1539
|
-
if (msgStr !== "") {
|
|
1540
|
-
msgStr += " ";
|
|
1541
|
-
}
|
|
1542
|
-
// Then append the value to the message we are creating
|
|
1543
|
-
msgStr += (fromArray32(arr).toString());
|
|
1544
|
-
}
|
|
1545
|
-
|
|
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
|
+
},
|
|
1546
1266
|
};
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
const arr = new Uint32Array(this.n32);
|
|
1557
|
-
for (let i=0; i<this.n32; i++) {
|
|
1558
|
-
arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
|
|
1559
|
-
}
|
|
1560
|
-
this.prime = fromArray32(arr);
|
|
1561
|
-
|
|
1562
|
-
this.witnessSize = this.instance.exports.getWitnessSize();
|
|
1563
|
-
|
|
1564
|
-
this.sanityCheck = sanityCheck;
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
circom_version() {
|
|
1568
|
-
return this.instance.exports.getVersion();
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
async _doCalculateWitness(input_orig, sanityCheck) {
|
|
1572
|
-
//input is assumed to be a map from signals to arrays of bigints
|
|
1573
|
-
this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
|
|
1574
|
-
let prefix = "";
|
|
1575
|
-
var input = new Object();
|
|
1576
|
-
//console.log("Input: ", input_orig);
|
|
1577
|
-
qualify_input(prefix,input_orig,input);
|
|
1578
|
-
//console.log("Input after: ",input);
|
|
1579
|
-
const keys = Object.keys(input);
|
|
1580
|
-
var input_counter = 0;
|
|
1581
|
-
keys.forEach( (k) => {
|
|
1582
|
-
const h = fnvHash(k);
|
|
1583
|
-
const hMSB = parseInt(h.slice(0,8), 16);
|
|
1584
|
-
const hLSB = parseInt(h.slice(8,16), 16);
|
|
1585
|
-
const fArr = flatArray(input[k]);
|
|
1586
|
-
let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
|
|
1587
|
-
if (signalSize < 0){
|
|
1588
|
-
throw new Error(`Signal ${k} not found\n`);
|
|
1589
|
-
}
|
|
1590
|
-
if (fArr.length < signalSize) {
|
|
1591
|
-
throw new Error(`Not enough values for input signal ${k}\n`);
|
|
1592
|
-
}
|
|
1593
|
-
if (fArr.length > signalSize) {
|
|
1594
|
-
throw new Error(`Too many values for input signal ${k}\n`);
|
|
1595
|
-
}
|
|
1596
|
-
for (let i=0; i<fArr.length; i++) {
|
|
1597
|
-
const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32);
|
|
1598
|
-
for (let j=0; j<this.n32; j++) {
|
|
1599
|
-
this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
|
|
1600
|
-
}
|
|
1601
|
-
try {
|
|
1602
|
-
this.instance.exports.setInputSignal(hMSB, hLSB,i);
|
|
1603
|
-
input_counter++;
|
|
1604
|
-
} catch (err) {
|
|
1605
|
-
// console.log(`After adding signal ${i} of ${k}`)
|
|
1606
|
-
throw new Error(err);
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
});
|
|
1611
|
-
if (input_counter < this.instance.exports.getInputSize()) {
|
|
1612
|
-
throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
|
|
1613
|
-
}
|
|
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()}`);
|
|
1614
1276
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
}
|
|
1627
|
-
w.push(fromArray32(arr));
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
return w;
|
|
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
|
+
};
|
|
1631
1288
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
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;
|
|
1315
|
+
}
|
|
1647
1316
|
|
|
1648
|
-
|
|
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);
|
|
1649
1327
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
async calculateWTNSBin(input, sanityCheck) {
|
|
1653
|
-
|
|
1654
|
-
const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
|
|
1655
|
-
const buff = new Uint8Array( buff32.buffer);
|
|
1656
|
-
await this._doCalculateWitness(input, sanityCheck);
|
|
1657
|
-
|
|
1658
|
-
//"wtns"
|
|
1659
|
-
buff[0] = "w".charCodeAt(0);
|
|
1660
|
-
buff[1] = "t".charCodeAt(0);
|
|
1661
|
-
buff[2] = "n".charCodeAt(0);
|
|
1662
|
-
buff[3] = "s".charCodeAt(0);
|
|
1663
|
-
|
|
1664
|
-
//version 2
|
|
1665
|
-
buff32[1] = 2;
|
|
1666
|
-
|
|
1667
|
-
//number of sections: 2
|
|
1668
|
-
buff32[2] = 2;
|
|
1669
|
-
|
|
1670
|
-
//id section 1
|
|
1671
|
-
buff32[3] = 1;
|
|
1672
|
-
|
|
1673
|
-
const n8 = this.n32*4;
|
|
1674
|
-
//id section 1 length in 64bytes
|
|
1675
|
-
const idSection1length = 8 + n8;
|
|
1676
|
-
const idSection1lengthHex = idSection1length.toString(16);
|
|
1677
|
-
buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
|
|
1678
|
-
buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
|
|
1679
|
-
|
|
1680
|
-
//this.n32
|
|
1681
|
-
buff32[6] = n8;
|
|
1328
|
+
return trimmed;
|
|
1329
|
+
}
|
|
1682
1330
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1331
|
+
const isNode = typeof process !== 'undefined' && !!process.versions?.node;
|
|
1332
|
+
function toUint8Array(src) {
|
|
1333
|
+
if (src instanceof Uint8Array) {
|
|
1334
|
+
return src;
|
|
1335
|
+
}
|
|
1336
|
+
if (src instanceof ArrayBuffer) {
|
|
1337
|
+
return new Uint8Array(src);
|
|
1338
|
+
}
|
|
1339
|
+
return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
|
|
1340
|
+
}
|
|
1341
|
+
function nodeEnvArtifactBaseUrl() {
|
|
1342
|
+
if (!isNode) {
|
|
1343
|
+
return undefined;
|
|
1344
|
+
}
|
|
1345
|
+
const fromEnv = process.env.ZK_ARTIFACT_BASE_URL?.trim();
|
|
1346
|
+
return fromEnv ? trimArtifactBaseUrl(fromEnv) : undefined;
|
|
1347
|
+
}
|
|
1348
|
+
function resolveZkArtifactBaseUrl(explicit) {
|
|
1349
|
+
const trimmed = explicit?.trim();
|
|
1350
|
+
if (trimmed) {
|
|
1351
|
+
return trimArtifactBaseUrl(trimmed);
|
|
1352
|
+
}
|
|
1353
|
+
return nodeEnvArtifactBaseUrl() ?? defaultZkArtifactBaseUrl();
|
|
1354
|
+
}
|
|
1355
|
+
function isHttpUrl(value) {
|
|
1356
|
+
return /^https?:\/\//i.test(value) || value.startsWith('file:');
|
|
1357
|
+
}
|
|
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.`);
|
|
1362
|
+
}
|
|
1363
|
+
return fs.readFileSync(filePath);
|
|
1364
|
+
}
|
|
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)}`);
|
|
1369
|
+
}
|
|
1370
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1371
|
+
}
|
|
1372
|
+
function remoteArtifactHref(base, fileName) {
|
|
1373
|
+
const withSlash = `${trimArtifactBaseUrl(base)}/`;
|
|
1374
|
+
if (isHttpUrl(withSlash)) {
|
|
1375
|
+
return new URL(fileName, withSlash).href;
|
|
1376
|
+
}
|
|
1377
|
+
if (typeof window !== 'undefined') {
|
|
1378
|
+
return new URL(fileName, new URL(withSlash, window.location.href)).href;
|
|
1379
|
+
}
|
|
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
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
return loadBundledCircuitArtifacts(config.circuit, artifactBaseUrl);
|
|
1408
|
+
}
|
|
1685
1409
|
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1410
|
+
/** Bech32 HRP for {@link DecodedEphemeralKey} only (`x ‖ y`, 64 bytes). */
|
|
1411
|
+
const DECODED_EPHEMERAL_HRP = 'epk1';
|
|
1412
|
+
/** Bech32 HRP for {@link DecodedDepositorSharedSecretPreimage} (96 bytes). */
|
|
1413
|
+
const DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP = 'epk_dep_pre1';
|
|
1414
|
+
const FIELD_BYTES = 32;
|
|
1415
|
+
const EPK_POINT_PAYLOAD = FIELD_BYTES * 2;
|
|
1416
|
+
const EPK_DEP_PRE_PAYLOAD = FIELD_BYTES * 3;
|
|
1417
|
+
const BECH32_LONG_LIMIT = 1023;
|
|
1418
|
+
function normalizeHex$2(hex) {
|
|
1419
|
+
const s = hex.trim().replace(/^0x/i, '');
|
|
1420
|
+
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
1421
|
+
throw new Error('ephemeral-key: hex must contain only 0-9, a-f');
|
|
1422
|
+
}
|
|
1423
|
+
return s.length % 2 === 0 ? s : `0${s}`;
|
|
1424
|
+
}
|
|
1425
|
+
function hexToBytes(hex) {
|
|
1426
|
+
const norm = normalizeHex$2(hex);
|
|
1427
|
+
const out = new Uint8Array(norm.length / 2);
|
|
1428
|
+
for (let i = 0; i < out.length; i++) {
|
|
1429
|
+
out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
|
|
1430
|
+
}
|
|
1431
|
+
return out;
|
|
1432
|
+
}
|
|
1433
|
+
function bytesToHex(bytes) {
|
|
1434
|
+
let s = '';
|
|
1435
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1436
|
+
s += bytes[i].toString(16).padStart(2, '0');
|
|
1437
|
+
}
|
|
1438
|
+
return s;
|
|
1439
|
+
}
|
|
1440
|
+
function concat2(a, b) {
|
|
1441
|
+
const out = new Uint8Array(a.length + b.length);
|
|
1442
|
+
out.set(a, 0);
|
|
1443
|
+
out.set(b, a.length);
|
|
1444
|
+
return out;
|
|
1445
|
+
}
|
|
1446
|
+
function concat3(a, b, c) {
|
|
1447
|
+
const out = new Uint8Array(a.length + b.length + c.length);
|
|
1448
|
+
out.set(a, 0);
|
|
1449
|
+
out.set(b, a.length);
|
|
1450
|
+
out.set(c, a.length + b.length);
|
|
1451
|
+
return out;
|
|
1452
|
+
}
|
|
1453
|
+
function require32(label, bytes) {
|
|
1454
|
+
if (bytes.length !== FIELD_BYTES) {
|
|
1455
|
+
throw new Error(`ephemeral-key: ${label} must encode exactly ${FIELD_BYTES} bytes, got ${bytes.length}`);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* 32-byte big-endian integer as hex (64 chars), **< 2^253**, so BabyJub ECDH matches
|
|
1460
|
+
* `circuits/encryption.circom` `Num2Bits(253)` and `libs/cryptography` `scalar_mul_253`.
|
|
1461
|
+
*/
|
|
1462
|
+
function generateRandomScalarHex32() {
|
|
1463
|
+
const g = globalThis.crypto;
|
|
1464
|
+
if (!g?.getRandomValues) {
|
|
1465
|
+
throw new Error('ephemeral-key: crypto.getRandomValues is required');
|
|
1466
|
+
}
|
|
1467
|
+
const max = 1n << 253n;
|
|
1468
|
+
for (let attempt = 0; attempt < 65536; attempt++) {
|
|
1469
|
+
const b = new Uint8Array(32);
|
|
1470
|
+
g.getRandomValues(b);
|
|
1471
|
+
const hex = bytesToHex(b);
|
|
1472
|
+
if (BigInt(`0x${hex}`) < max) {
|
|
1473
|
+
return hex;
|
|
1713
1474
|
}
|
|
1714
|
-
|
|
1715
|
-
return buff;
|
|
1716
1475
|
}
|
|
1717
|
-
|
|
1476
|
+
throw new Error('ephemeral-key: failed to sample scalar < 2^253');
|
|
1718
1477
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
for (let i = 1; i<a.length; i++) {
|
|
1738
|
-
if (typeof a[i] != t){
|
|
1739
|
-
throw new Error(`Types are not the same in the key ${prefix}`);
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
if (t == "object") {
|
|
1743
|
-
qualify_input_list(prefix,input,input1);
|
|
1744
|
-
} else {
|
|
1745
|
-
input1[prefix] = input;
|
|
1746
|
-
}
|
|
1747
|
-
} else {
|
|
1748
|
-
input1[prefix] = input;
|
|
1749
|
-
}
|
|
1750
|
-
} else if (typeof input == "object") {
|
|
1751
|
-
const keys = Object.keys(input);
|
|
1752
|
-
keys.forEach( (k) => {
|
|
1753
|
-
let new_prefix = prefix == ""? k : prefix + "." + k;
|
|
1754
|
-
qualify_input(new_prefix,input[k],input1);
|
|
1755
|
-
});
|
|
1756
|
-
} else {
|
|
1757
|
-
input1[prefix] = input;
|
|
1478
|
+
/** Bech32 `epk1`: encodes `x ‖ y` (64 bytes). */
|
|
1479
|
+
function encodeDecodedEphemeralKey(decoded) {
|
|
1480
|
+
const xb = hexToBytes(decoded.x);
|
|
1481
|
+
const yb = hexToBytes(decoded.y);
|
|
1482
|
+
require32('x', xb);
|
|
1483
|
+
require32('y', yb);
|
|
1484
|
+
const payload = concat2(xb, yb);
|
|
1485
|
+
const words = bech32.toWords(payload);
|
|
1486
|
+
return bech32.encode(DECODED_EPHEMERAL_HRP, words, BECH32_LONG_LIMIT);
|
|
1487
|
+
}
|
|
1488
|
+
function decodeDecodedEphemeralKey(encoded) {
|
|
1489
|
+
const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
|
|
1490
|
+
if (prefix !== DECODED_EPHEMERAL_HRP) {
|
|
1491
|
+
throw new Error(`ephemeral-key: expected HRP ${DECODED_EPHEMERAL_HRP}, got ${JSON.stringify(prefix)}`);
|
|
1492
|
+
}
|
|
1493
|
+
const bytes = new Uint8Array(bech32.fromWords(words));
|
|
1494
|
+
if (bytes.length !== EPK_POINT_PAYLOAD) {
|
|
1495
|
+
throw new Error(`ephemeral-key: epk1 payload must be ${EPK_POINT_PAYLOAD} bytes, got ${bytes.length}`);
|
|
1758
1496
|
}
|
|
1497
|
+
return {
|
|
1498
|
+
x: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
|
|
1499
|
+
y: bytesToHex(bytes.subarray(FIELD_BYTES)),
|
|
1500
|
+
};
|
|
1759
1501
|
}
|
|
1760
|
-
|
|
1761
|
-
function
|
|
1762
|
-
const
|
|
1763
|
-
const
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1502
|
+
/** Bech32 `epk_dep_pre1`: `randomNonceScalar ‖ recipient.x ‖ recipient.y`. */
|
|
1503
|
+
function encodeDepositorSharedSecretPreimage(decoded) {
|
|
1504
|
+
const sb = hexToBytes(decoded.randomNonceScalar);
|
|
1505
|
+
const xb = hexToBytes(decoded.recipientStealthAddress.x);
|
|
1506
|
+
const yb = hexToBytes(decoded.recipientStealthAddress.y);
|
|
1507
|
+
require32('randomNonceScalar', sb);
|
|
1508
|
+
require32('recipientStealthAddress.x', xb);
|
|
1509
|
+
require32('recipientStealthAddress.y', yb);
|
|
1510
|
+
const payload = concat3(sb, xb, yb);
|
|
1511
|
+
const words = bech32.toWords(payload);
|
|
1512
|
+
return bech32.encode(DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, words, BECH32_LONG_LIMIT);
|
|
1513
|
+
}
|
|
1514
|
+
function decodeDepositorSharedSecretPreimage(encoded) {
|
|
1515
|
+
const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
|
|
1516
|
+
if (prefix !== DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP) {
|
|
1517
|
+
throw new Error(`ephemeral-key: expected HRP ${DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP}, got ${JSON.stringify(prefix)}`);
|
|
1518
|
+
}
|
|
1519
|
+
const bytes = new Uint8Array(bech32.fromWords(words));
|
|
1520
|
+
if (bytes.length !== EPK_DEP_PRE_PAYLOAD) {
|
|
1521
|
+
throw new Error(`ephemeral-key: epk_dep_pre1 payload must be ${EPK_DEP_PRE_PAYLOAD} bytes, got ${bytes.length}`);
|
|
1767
1522
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1523
|
+
return {
|
|
1524
|
+
randomNonceScalar: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
|
|
1525
|
+
recipientStealthAddress: {
|
|
1526
|
+
x: bytesToHex(bytes.subarray(FIELD_BYTES, FIELD_BYTES * 2)),
|
|
1527
|
+
y: bytesToHex(bytes.subarray(FIELD_BYTES * 2)),
|
|
1528
|
+
},
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Depositor: `randomNonceScalar * recipientStealthPoint` (same as circom `ECDH` with depositor scalar).
|
|
1534
|
+
*/
|
|
1535
|
+
function sharedSecretFromDepositorPreimage(ecdhShared, preimage) {
|
|
1536
|
+
const out = ecdhShared(preimage.randomNonceScalar, preimage.recipientStealthAddress.x, preimage.recipientStealthAddress.y);
|
|
1537
|
+
return { x: out.x, y: out.y };
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Recipient: `recipientScalar * ephemeralKey` (same shared point as depositor path when keys match).
|
|
1541
|
+
*/
|
|
1542
|
+
function sharedSecretFromRecipientPreimage(ecdhShared, preimage) {
|
|
1543
|
+
const out = ecdhShared(preimage.recipientScalar, preimage.ephemeralKey.x, preimage.ephemeralKey.y);
|
|
1544
|
+
return { x: out.x, y: out.y };
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const TRANSACTION_N_AUDIT_SLOTS = 4;
|
|
1548
|
+
const DEFAULT_APPLICATION_ID = '101';
|
|
1549
|
+
/** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
|
|
1550
|
+
const DEMO_AUDIT_PUBLIC_KEY = [
|
|
1551
|
+
'21605515851820432880964235241069234202284600780825340516808373216881770219365',
|
|
1552
|
+
'18856460861531942120859708048677603751294231190189224157283439874962410808705',
|
|
1553
|
+
];
|
|
1554
|
+
function isActiveWithdraw(slot) {
|
|
1555
|
+
return slot.value !== '0';
|
|
1556
|
+
}
|
|
1557
|
+
function isActiveDeposit(slot) {
|
|
1558
|
+
return slot.value !== '0';
|
|
1559
|
+
}
|
|
1560
|
+
function resolveWithdrawSlot(slot) {
|
|
1561
|
+
if (slot === 'dummy') {
|
|
1562
|
+
return null;
|
|
1774
1563
|
}
|
|
1775
|
-
return
|
|
1564
|
+
return slot;
|
|
1776
1565
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
const radix = BigInt(0x100000000);
|
|
1781
|
-
for (let i = 0; i<arr.length; i++) {
|
|
1782
|
-
res = res*radix + BigInt(arr[i]);
|
|
1566
|
+
function resolveDepositSlot(slot) {
|
|
1567
|
+
if (slot === 'dummy') {
|
|
1568
|
+
return null;
|
|
1783
1569
|
}
|
|
1784
|
-
return
|
|
1570
|
+
return slot;
|
|
1785
1571
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
function fillArray(res, a) {
|
|
1793
|
-
if (Array.isArray(a)) {
|
|
1794
|
-
for (let i=0; i<a.length; i++) {
|
|
1795
|
-
fillArray(res, a[i]);
|
|
1796
|
-
}
|
|
1797
|
-
} else {
|
|
1798
|
-
res.push(a);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1572
|
+
function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey, _nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
|
|
1573
|
+
return {
|
|
1574
|
+
applicationId,
|
|
1575
|
+
noteAuditPublicKey: auditPublicKey,
|
|
1576
|
+
auditEphemeralScalar: randomFrDecimal253(),
|
|
1577
|
+
};
|
|
1801
1578
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1579
|
+
function resolveSlotApplicationIds(audit, withdrawSlots, depositSlots) {
|
|
1580
|
+
return {
|
|
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
|
+
}),
|
|
1589
|
+
};
|
|
1807
1590
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
hash %= uint64_max;
|
|
1591
|
+
/**
|
|
1592
|
+
* Prefer an explicit key, then `NOTE_AUDIT_PUBLIC_KEY_X`/`_Y` env (decimal Fr),
|
|
1593
|
+
* then the built-in demo key. Used by CLI/`demo.sh` for per-app audit pubkeys.
|
|
1594
|
+
*/
|
|
1595
|
+
function resolveAuditPublicKeyFromEnv(auditPublicKey) {
|
|
1596
|
+
if (auditPublicKey) {
|
|
1597
|
+
return auditPublicKey;
|
|
1816
1598
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1599
|
+
const x = process.env.NOTE_AUDIT_PUBLIC_KEY_X?.trim();
|
|
1600
|
+
const y = process.env.NOTE_AUDIT_PUBLIC_KEY_Y?.trim();
|
|
1601
|
+
if (x && y) {
|
|
1602
|
+
return [x, y];
|
|
1603
|
+
}
|
|
1604
|
+
return DEMO_AUDIT_PUBLIC_KEY;
|
|
1605
|
+
}
|
|
1606
|
+
function resolveTransactionAuditParams(applicationId, auditPublicKey, nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
|
|
1607
|
+
return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey), nAuditSlots);
|
|
1821
1608
|
}
|
|
1822
1609
|
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
const
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1610
|
+
/** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 12, 6)` in `circuits/main.circom`. */
|
|
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;
|
|
1617
|
+
/** BN254 scalar field modulus (ark `Fr`, circom signals). */
|
|
1618
|
+
const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
1619
|
+
/**
|
|
1620
|
+
* BabyJub ECDH in `circuits/encryption.circom` uses `Num2Bits(253)`; scalars must be < 2^253
|
|
1621
|
+
* (matches `libs/cryptography` `scalar_mul_253`).
|
|
1622
|
+
*/
|
|
1623
|
+
const BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE = 1n << 253n;
|
|
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}`);
|
|
1838
1628
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
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}`);
|
|
1845
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) {
|
|
1648
|
+
const s = hex.trim().replace(/^0x/i, '');
|
|
1649
|
+
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
1650
|
+
throw new Error('withdrawal-transaction-input: invalid hex');
|
|
1846
1651
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1652
|
+
return s.length % 2 === 0 ? s : `0${s}`;
|
|
1653
|
+
}
|
|
1654
|
+
/** 32-byte field coordinate (hex, no 0x) → decimal string mod BN254 scalar field. */
|
|
1655
|
+
function coordHexToDecimal(hex) {
|
|
1656
|
+
const h = normalizeHex$1(hex);
|
|
1657
|
+
if (h.length > 64) {
|
|
1658
|
+
throw new Error('withdrawal-transaction-input: coordinate hex too long');
|
|
1854
1659
|
}
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
return wtns;
|
|
1660
|
+
const v = BigInt(`0x${h}`);
|
|
1661
|
+
return (v % BN254_SCALAR_MOD).toString(10);
|
|
1858
1662
|
}
|
|
1859
|
-
|
|
1860
|
-
// @ts-ignore - snarkjs types
|
|
1861
|
-
const isNode = typeof process !== 'undefined' && !!process.versions?.node;
|
|
1862
1663
|
/**
|
|
1863
|
-
*
|
|
1864
|
-
*
|
|
1664
|
+
* 32-byte big-endian scalar hex → decimal for circom `ephemeralKeyScalar` / ECDH `priv`.
|
|
1665
|
+
* Integer must be < 2^253 (not reduced mod r — values ≥ 2^253 are rejected).
|
|
1865
1666
|
*/
|
|
1866
|
-
function
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
return new Uint8Array(src);
|
|
1871
|
-
return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
|
|
1872
|
-
}
|
|
1873
|
-
async function generateProof(wtns, zkey) {
|
|
1874
|
-
let zkeyData;
|
|
1875
|
-
if (zkey) {
|
|
1876
|
-
zkeyData = toUint8Array(zkey);
|
|
1877
|
-
}
|
|
1878
|
-
else if (isNode) {
|
|
1879
|
-
const fs = await import('fs');
|
|
1880
|
-
const nodePath = await import('path');
|
|
1881
|
-
const { fileURLToPath } = await import('url');
|
|
1882
|
-
const dir = nodePath.dirname(fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href))));
|
|
1883
|
-
const zkeyPath = nodePath.resolve(dir, '..', 'assets', 'main_final.zkey');
|
|
1884
|
-
if (!fs.existsSync(zkeyPath)) {
|
|
1885
|
-
throw new Error(`Proving key not found at ${zkeyPath}. Run build.sh first.`);
|
|
1886
|
-
}
|
|
1887
|
-
const zkeyBuffer = fs.readFileSync(zkeyPath);
|
|
1888
|
-
zkeyData = toUint8Array(zkeyBuffer);
|
|
1667
|
+
function scalarHexToFrDecimal(hex) {
|
|
1668
|
+
const h = normalizeHex$1(hex);
|
|
1669
|
+
if (h.length > 64) {
|
|
1670
|
+
throw new Error('withdrawal-transaction-input: scalar hex too long');
|
|
1889
1671
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1672
|
+
const v = BigInt(`0x${h.padStart(64, '0').slice(-64)}`);
|
|
1673
|
+
if (v >= BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE) {
|
|
1674
|
+
throw new Error('depositor ephemeral scalar must be < 2^253 (BabyJub Num2Bits); resample with random-scalar');
|
|
1893
1675
|
}
|
|
1894
|
-
|
|
1895
|
-
const { proof, publicSignals } = await snarkjs__namespace.groth16.prove({ type: 'mem', data: zkeyData }, { type: 'mem', data: wtnsData });
|
|
1896
|
-
return { proof, publicSignals };
|
|
1676
|
+
return v.toString(10);
|
|
1897
1677
|
}
|
|
1898
|
-
|
|
1899
1678
|
/**
|
|
1900
|
-
*
|
|
1679
|
+
* Stellar G-address Ed25519 payload (32 bytes as 64 hex, optional 0x) → two circom public decimals
|
|
1680
|
+
* (`withdrawAddressHi` / `withdrawAddressLo`). No mod-r; each half fits in 128 bits.
|
|
1901
1681
|
*/
|
|
1902
|
-
function
|
|
1903
|
-
const
|
|
1904
|
-
|
|
1682
|
+
function ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex) {
|
|
1683
|
+
const h = normalizeHex$1(hex).padStart(64, '0').slice(-64);
|
|
1684
|
+
const hi = BigInt(`0x${h.slice(0, 32)}`);
|
|
1685
|
+
const lo = BigInt(`0x${h.slice(32, 64)}`);
|
|
1686
|
+
return { hi: hi.toString(10), lo: lo.toString(10) };
|
|
1905
1687
|
}
|
|
1906
1688
|
/**
|
|
1907
|
-
*
|
|
1689
|
+
* Stellar contract id (`C…`) → two circom field decimals for `asset[0]`, `asset[1]` (same 32-byte split as accounts).
|
|
1908
1690
|
*/
|
|
1909
|
-
function
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1691
|
+
function stellarContractAddressToAssetFrDecimals(address) {
|
|
1692
|
+
const raw = stellarSdk.StrKey.decodeContract(address);
|
|
1693
|
+
const hex = Buffer.from(raw).toString('hex');
|
|
1694
|
+
const { hi, lo } = ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex);
|
|
1695
|
+
return [hi, lo];
|
|
1696
|
+
}
|
|
1697
|
+
/** Uniform random `Fr` as decimal (32 random bytes, mod r). For Poseidon-only inputs (e.g. nullifiers). */
|
|
1698
|
+
function randomFrDecimal() {
|
|
1699
|
+
const hex = generateRandomScalarHex32();
|
|
1700
|
+
const v = BigInt(`0x${normalizeHex$1(hex)}`);
|
|
1701
|
+
return (v % BN254_SCALAR_MOD).toString(10);
|
|
1702
|
+
}
|
|
1703
|
+
/** Random scalar < 2^253 for BabyJub ECDH / `Num2Bits(253)` (uses {@link generateRandomScalarHex32}). */
|
|
1704
|
+
function randomFrDecimal253() {
|
|
1705
|
+
const hex = generateRandomScalarHex32();
|
|
1706
|
+
return BigInt(`0x${normalizeHex$1(hex)}`).toString(10);
|
|
1707
|
+
}
|
|
1708
|
+
/** Spend-scalar hex in `(0, BabyJub subgroup order)` so dummy withdraws satisfy `LessThan(l)`. */
|
|
1709
|
+
function randomCanonicalBabyJubScalarHex() {
|
|
1710
|
+
const hex = generateRandomScalarHex32();
|
|
1711
|
+
const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex$1(hex)}`));
|
|
1712
|
+
return reduced.toString(16).padStart(64, '0');
|
|
1713
|
+
}
|
|
1714
|
+
function zerosTreeSiblings() {
|
|
1715
|
+
return Array(TRANSACTION_TREE_DEPTH).fill('0');
|
|
1716
|
+
}
|
|
1717
|
+
function dummyWithdraw(wasm) {
|
|
1718
|
+
const nullifier = randomFrDecimal();
|
|
1719
|
+
const secretHex = generateRandomScalarHex32();
|
|
1720
|
+
const secret = (BigInt(`0x${normalizeHex$1(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
|
|
1721
|
+
const scalarHex = randomCanonicalBabyJubScalarHex();
|
|
1722
|
+
const privKeyScalar = scalarHexToFrDecimal(scalarHex);
|
|
1723
|
+
const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(scalarHex);
|
|
1724
|
+
return {
|
|
1725
|
+
value: '0',
|
|
1726
|
+
nullifier,
|
|
1727
|
+
secret,
|
|
1728
|
+
asset: ['0', '0'],
|
|
1729
|
+
applicationId: '0',
|
|
1730
|
+
ownerPub: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
|
|
1731
|
+
privKeyScalar,
|
|
1732
|
+
paddingRandom: randomFrDecimal(),
|
|
1733
|
+
escrowNonce: '0',
|
|
1734
|
+
recipientStellar: ['0', '0'],
|
|
1735
|
+
stateSiblings: zerosTreeSiblings(),
|
|
1736
|
+
stateIndex: '0',
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function dummyDeposit(wasm) {
|
|
1740
|
+
const nullifier = randomFrDecimal();
|
|
1741
|
+
const ephemeralKeyScalar = randomFrDecimal253();
|
|
1742
|
+
const skHex = generateRandomScalarHex32();
|
|
1743
|
+
const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(skHex);
|
|
1744
|
+
return {
|
|
1745
|
+
value: '0',
|
|
1746
|
+
nullifier,
|
|
1747
|
+
ephemeralKeyScalar,
|
|
1748
|
+
asset: ['0', '0'],
|
|
1749
|
+
applicationId: '0',
|
|
1750
|
+
recipientPublicKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
|
|
1751
|
+
escrowNonce: '0',
|
|
1752
|
+
recipientStellar: ['0', '0'],
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
function resolveWithdraw(slot, wasm) {
|
|
1756
|
+
return slot === 'dummy' ? dummyWithdraw(wasm) : slot;
|
|
1757
|
+
}
|
|
1758
|
+
function resolveDeposit(slot, wasm) {
|
|
1759
|
+
return slot === 'dummy' ? dummyDeposit(wasm) : slot;
|
|
1760
|
+
}
|
|
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);
|
|
1784
|
+
return {
|
|
1785
|
+
stateRoot: publicParams.stateRoot,
|
|
1786
|
+
withdrawAddressHi: publicParams.withdrawAddressHi,
|
|
1787
|
+
withdrawAddressLo: publicParams.withdrawAddressLo,
|
|
1788
|
+
escrowRecipientHi: publicParams.escrowRecipientHi ?? '0',
|
|
1789
|
+
escrowRecipientLo: publicParams.escrowRecipientLo ?? '0',
|
|
1790
|
+
sweepOutputOwnerPubX: publicParams.sweepOutputOwnerPubX ?? '0',
|
|
1791
|
+
sweepOutputOwnerPubY: publicParams.sweepOutputOwnerPubY ?? '0',
|
|
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']),
|
|
1810
|
+
inputApplicationIds: appIds.inputApplicationIds,
|
|
1811
|
+
outputApplicationIds: appIds.outputApplicationIds,
|
|
1812
|
+
auditEphemeralScalar: audit.auditEphemeralScalar,
|
|
1813
|
+
noteAuditPublicKey: audit.noteAuditPublicKey,
|
|
1814
|
+
publicWithdrawnAssets: paddedLegs.publicWithdrawnAssets,
|
|
1815
|
+
publicDepositedAssets: paddedLegs.publicDepositedAssets,
|
|
1816
|
+
publicDeposits: paddedLegs.publicDeposits,
|
|
1817
|
+
publicWithdrawals: paddedLegs.publicWithdrawals,
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
/** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
|
|
1821
|
+
function recipientPublicKeysDecimalFromStealthAddress(stealthAddress) {
|
|
1822
|
+
const { x, y } = decodeStealthAddress(stealthAddress);
|
|
1823
|
+
return [coordHexToDecimal(x), coordHexToDecimal(y)];
|
|
1824
|
+
}
|
|
1825
|
+
/** First withdraw leg: Merkle witness + owner public key (hex) and matching scalar. */
|
|
1826
|
+
function withdrawObjectFromMerkleWitness(witness, ownerPubHex, applicationId, privKeyScalar) {
|
|
1827
|
+
return {
|
|
1828
|
+
value: witness.value,
|
|
1829
|
+
nullifier: witness.nullifier,
|
|
1830
|
+
secret: witness.secret,
|
|
1831
|
+
asset: witness.withdrawnAsset,
|
|
1832
|
+
applicationId,
|
|
1833
|
+
ownerPub: [coordHexToDecimal(ownerPubHex.x), coordHexToDecimal(ownerPubHex.y)],
|
|
1834
|
+
privKeyScalar,
|
|
1835
|
+
paddingRandom: randomFrDecimal(),
|
|
1836
|
+
escrowNonce: '0',
|
|
1837
|
+
recipientStellar: ['0', '0'],
|
|
1838
|
+
stateSiblings: witness.stateSiblings,
|
|
1839
|
+
stateIndex: witness.stateIndex,
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
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
|
+
};
|
|
1912
2181
|
}
|
|
1913
2182
|
|
|
1914
|
-
/** Stroops amount as
|
|
1915
|
-
function
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
2183
|
+
/** Stroops amount as a decimal field string. Reject JS `number` so 18-decimal values cannot silently narrow. */
|
|
2184
|
+
function coinValueDecimal(amount) {
|
|
2185
|
+
if (typeof amount === "number") {
|
|
2186
|
+
throw new TypeError("coin value must be bigint or decimal string, not number");
|
|
2187
|
+
}
|
|
2188
|
+
if (typeof amount === "bigint") {
|
|
2189
|
+
if (amount < 0n) {
|
|
2190
|
+
throw new RangeError("amount must be a non-negative integer");
|
|
2191
|
+
}
|
|
2192
|
+
return amount.toString(10);
|
|
2193
|
+
}
|
|
2194
|
+
if (typeof amount !== "string") {
|
|
2195
|
+
throw new TypeError("coin value must be bigint or decimal string, not number");
|
|
1919
2196
|
}
|
|
1920
|
-
|
|
2197
|
+
const trimmed = amount.trim();
|
|
2198
|
+
if (!/^[0-9]+$/.test(trimmed)) {
|
|
2199
|
+
throw new TypeError("coin value decimal string must be a non-negative integer");
|
|
2200
|
+
}
|
|
2201
|
+
return trimmed;
|
|
2202
|
+
}
|
|
2203
|
+
function generatedCoinFromWasm(value) {
|
|
2204
|
+
const raw = value;
|
|
2205
|
+
const precommitementHex = raw.precommitementHex ?? raw.precommitement_hex;
|
|
2206
|
+
if (!precommitementHex) {
|
|
2207
|
+
throw new Error("generated coin missing precommitement hex");
|
|
2208
|
+
}
|
|
2209
|
+
return {
|
|
2210
|
+
coin: raw.coin,
|
|
2211
|
+
commitment_hex: raw.commitment_hex,
|
|
2212
|
+
precommitement_hex: precommitementHex,
|
|
2213
|
+
precommitementHex,
|
|
2214
|
+
};
|
|
1921
2215
|
}
|
|
1922
2216
|
class PrivacyPoolSDK {
|
|
1923
|
-
constructor(wasm, options) {
|
|
2217
|
+
constructor(wasm, options, resolved) {
|
|
1924
2218
|
this.wasm = wasm;
|
|
1925
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;
|
|
1926
2229
|
}
|
|
1927
2230
|
/**
|
|
1928
2231
|
* Initialize the SDK by loading WASM modules.
|
|
1929
2232
|
*
|
|
1930
|
-
* In Node.js,
|
|
1931
|
-
*
|
|
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:
|
|
1932
2236
|
* ```ts
|
|
1933
2237
|
* const sdk = await PrivacyPoolSDK.init({
|
|
1934
2238
|
* wasmBinary: await fetch('/pkg/client_sdk_wasm_bg.wasm').then(r => r.arrayBuffer()),
|
|
1935
|
-
*
|
|
1936
|
-
* 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',
|
|
1937
2240
|
* });
|
|
1938
2241
|
* ```
|
|
1939
2242
|
*/
|
|
1940
2243
|
static async init(options) {
|
|
1941
2244
|
const opts = options ?? {};
|
|
1942
2245
|
const wasm = await loadWasm(opts.wasmBinary);
|
|
1943
|
-
|
|
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;
|
|
1944
2250
|
}
|
|
1945
2251
|
/**
|
|
1946
2252
|
* Uniform 32-byte scalar as lowercase hex (Web Crypto). Same in browser and Node 19+.
|
|
@@ -1949,39 +2255,40 @@ class PrivacyPoolSDK {
|
|
|
1949
2255
|
return generateRandomScalarHex32();
|
|
1950
2256
|
}
|
|
1951
2257
|
/**
|
|
1952
|
-
* Text to sign with a Stellar wallet for
|
|
2258
|
+
* Text to sign with a Stellar wallet for spend-key derivation (UTF-8). No WASM required.
|
|
2259
|
+
* The message binds network, pool, registry, and schema version (H6).
|
|
1953
2260
|
*/
|
|
1954
|
-
static buildStealthAddressSignMessage(address, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
|
|
1955
|
-
return buildStealthAddressSignMessage(address, nonce);
|
|
2261
|
+
static buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
|
|
2262
|
+
return buildStealthAddressSignMessage(address, domain, nonce);
|
|
1956
2263
|
}
|
|
1957
2264
|
/**
|
|
1958
|
-
* Generate a new coin with random nullifier, secret, and random
|
|
1959
|
-
* @param amount
|
|
2265
|
+
* Generate a new coin with random nullifier, secret, and random owner-pub field elements (dev / self-contained tests).
|
|
2266
|
+
* @param amount Decimal field element as `bigint` or decimal `string` (never JS `number`).
|
|
1960
2267
|
* @param assetHiDecimal / assetLoDecimal Decimal Fr strings for Stellar asset contract id (two limbs).
|
|
1961
2268
|
*/
|
|
1962
|
-
generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal =
|
|
1963
|
-
return this.wasm.generateCoin(
|
|
2269
|
+
generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2270
|
+
return generatedCoinFromWasm(this.wasm.generateCoin(coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
1964
2271
|
}
|
|
1965
2272
|
/**
|
|
1966
|
-
* Generate a coin with the same commitment shape as on-chain deposit: pass
|
|
1967
|
-
* @param amount
|
|
2273
|
+
* Generate a coin with the same commitment shape as on-chain deposit: pass owner public key (hex x, y).
|
|
2274
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
1968
2275
|
*/
|
|
1969
|
-
|
|
1970
|
-
return this.wasm.
|
|
2276
|
+
generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2277
|
+
return generatedCoinFromWasm(this.wasm.generateCoinWithOwnerPubHex(ownerPub.x, ownerPub.y, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
1971
2278
|
}
|
|
1972
2279
|
/**
|
|
1973
2280
|
* Coin for a depositor `ephemeralKeyScalar` (32-byte hex): `coin.secret = Poseidon255(1)(scalar)` as in `deposit.circom`.
|
|
1974
|
-
* @param amount
|
|
2281
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
1975
2282
|
*/
|
|
1976
|
-
generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal =
|
|
1977
|
-
return this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex,
|
|
2283
|
+
generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2284
|
+
return generatedCoinFromWasm(this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
1978
2285
|
}
|
|
1979
2286
|
/**
|
|
1980
|
-
* Aligned deposit coin: `secret = Poseidon₁(scalar)` and
|
|
1981
|
-
* @param amount
|
|
2287
|
+
* Aligned deposit coin: `secret = Poseidon₁(scalar)` and owner pub from recipient hex coords.
|
|
2288
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
1982
2289
|
*/
|
|
1983
|
-
|
|
1984
|
-
return this.wasm.
|
|
2290
|
+
generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2291
|
+
return generatedCoinFromWasm(this.wasm.generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
1985
2292
|
}
|
|
1986
2293
|
/**
|
|
1987
2294
|
* Merkle root, path, and coin fields for the first withdraw leg (Rust LeanIMT + Poseidon).
|
|
@@ -2000,20 +2307,18 @@ class PrivacyPoolSDK {
|
|
|
2000
2307
|
*/
|
|
2001
2308
|
async proveWithdrawal(coin, state, params) {
|
|
2002
2309
|
const witness = this.buildWithdrawMerkleWitness(coin, state);
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2005
|
-
y: params.ephemeralYHex,
|
|
2006
|
-
}, params.applicationId ?? coin.application_id ?? '0');
|
|
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);
|
|
2007
2312
|
const fullV = BigInt(coin.value);
|
|
2008
2313
|
let publicWithdrawals;
|
|
2009
2314
|
let deposits;
|
|
2010
2315
|
if (params.publicWithdrawStroops !== undefined) {
|
|
2011
2316
|
const W = params.publicWithdrawStroops;
|
|
2012
2317
|
if (params.changeRecipientStealthAddress === undefined) {
|
|
2013
|
-
throw new Error(
|
|
2318
|
+
throw new Error("proveWithdrawal: changeRecipientStealthAddress is required when publicWithdrawStroops is set");
|
|
2014
2319
|
}
|
|
2015
2320
|
if (W <= 0n || W >= fullV) {
|
|
2016
|
-
throw new Error(
|
|
2321
|
+
throw new Error("proveWithdrawal: publicWithdrawStroops must be strictly between 0 and coin.value");
|
|
2017
2322
|
}
|
|
2018
2323
|
const change = fullV - W;
|
|
2019
2324
|
publicWithdrawals = [W.toString()];
|
|
@@ -2022,29 +2327,29 @@ class PrivacyPoolSDK {
|
|
|
2022
2327
|
nullifier: randomFrDecimal(),
|
|
2023
2328
|
ephemeralKeyScalar: randomFrDecimal253(),
|
|
2024
2329
|
asset: [coin.asset_hi, coin.asset_lo],
|
|
2025
|
-
applicationId: params.applicationId ??
|
|
2330
|
+
applicationId: params.applicationId ?? "0",
|
|
2026
2331
|
recipientPublicKeys: recipientPublicKeysDecimalFromStealthAddress(params.changeRecipientStealthAddress),
|
|
2027
2332
|
};
|
|
2028
|
-
deposits = [changeDeposit
|
|
2333
|
+
deposits = [changeDeposit];
|
|
2029
2334
|
}
|
|
2030
2335
|
else {
|
|
2031
2336
|
publicWithdrawals = [coin.value];
|
|
2032
|
-
deposits = [
|
|
2337
|
+
deposits = [];
|
|
2033
2338
|
}
|
|
2034
2339
|
// On-chain `transact` sends `publicWithdrawals` / `publicWithdrawnAssets` to the withdraw account.
|
|
2035
2340
|
const publicLegs = {
|
|
2036
2341
|
publicWithdrawnAssets: [[coin.asset_hi, coin.asset_lo]],
|
|
2037
|
-
publicDepositedAssets: [[
|
|
2038
|
-
publicDeposits: [
|
|
2342
|
+
publicDepositedAssets: [["0", "0"]],
|
|
2343
|
+
publicDeposits: ["0"],
|
|
2039
2344
|
publicWithdrawals,
|
|
2040
2345
|
};
|
|
2041
|
-
return this.
|
|
2346
|
+
return this.proveTransactionSlots({
|
|
2042
2347
|
stateRoot: witness.stateRoot,
|
|
2043
2348
|
withdrawAddressHi: params.withdrawAddressHi,
|
|
2044
2349
|
withdrawAddressLo: params.withdrawAddressLo,
|
|
2045
2350
|
privKeyScalar: params.privKeyScalar,
|
|
2046
|
-
}, publicLegs, [w0
|
|
2047
|
-
|
|
2351
|
+
}, publicLegs, [w0], deposits, params.audit ??
|
|
2352
|
+
resolveTransactionAuditParams(params.applicationId ?? coin.application_id ?? DEFAULT_APPLICATION_ID, DEMO_AUDIT_PUBLIC_KEY, this.layout.nAuditSlots));
|
|
2048
2353
|
}
|
|
2049
2354
|
/**
|
|
2050
2355
|
* Convert a snarkjs proof JSON to hex bytes for Soroban.
|
|
@@ -2069,39 +2374,50 @@ class PrivacyPoolSDK {
|
|
|
2069
2374
|
return this.wasm.ecdhSharedKey(scalarHex, recipientPubXHex, recipientPubYHex);
|
|
2070
2375
|
}
|
|
2071
2376
|
/**
|
|
2072
|
-
* `Transaction(20,
|
|
2073
|
-
*
|
|
2074
|
-
* @param publicParams Public inputs: `stateRoot`, `withdrawAddressHi` / `withdrawAddressLo`, `privKeyScalar` (decimal field strings).
|
|
2075
|
-
* @param publicLegs Public token legs (`publicWithdrawnAssets`, `publicDepositedAssets`, `publicDeposits`, `publicWithdrawals`).
|
|
2076
|
-
* @param withdraws Exactly two withdraw slots (`WithdrawObject` or `"dummy"`).
|
|
2077
|
-
* @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).
|
|
2078
2379
|
*/
|
|
2079
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) {
|
|
2080
2384
|
const wasmEcdh = {
|
|
2081
2385
|
ecdhEphemeralPublicKeyFromScalarHex: (h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h),
|
|
2082
2386
|
};
|
|
2083
|
-
const
|
|
2084
|
-
const
|
|
2085
|
-
const
|
|
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
|
+
});
|
|
2086
2399
|
return {
|
|
2087
|
-
proof_hex:
|
|
2088
|
-
public_hex:
|
|
2400
|
+
proof_hex: proved.proof_hex,
|
|
2401
|
+
public_hex: proved.public_hex,
|
|
2402
|
+
ciphertext_hex: encrypted.ciphertextHex,
|
|
2403
|
+
output_note_ephemeral_scalars: witnessInput.depositedEphemeralKeyScalars,
|
|
2089
2404
|
};
|
|
2090
2405
|
}
|
|
2091
2406
|
/**
|
|
2092
|
-
* Calculate nullifier hash: Poseidon(nullifier)
|
|
2407
|
+
* Calculate owner-bound nullifier hash: Poseidon(DOM_NULLIFIER, nullifier, privKeyScalar)
|
|
2093
2408
|
* @param nullifier Nullifier decimal string from coin data
|
|
2409
|
+
* @param privKeyScalar Spend scalar decimal string
|
|
2094
2410
|
* @returns Hex string (0x...) of the hash bytes
|
|
2095
2411
|
*/
|
|
2096
|
-
calculateNullifierHash(nullifier) {
|
|
2097
|
-
return this.wasm.calculateNullifierHash(nullifier);
|
|
2412
|
+
calculateNullifierHash(nullifier, privKeyScalar) {
|
|
2413
|
+
return this.wasm.calculateNullifierHash(nullifier, privKeyScalar);
|
|
2098
2414
|
}
|
|
2099
2415
|
/**
|
|
2100
2416
|
* Ed25519 signature from signing {@link buildStealthAddressSignMessage}: **128 hex chars** (optional `0x`)
|
|
2101
|
-
* or **base64** (64 raw bytes after decode).
|
|
2417
|
+
* or **base64** (64 raw bytes after decode). Domain-separated SHA-256 → scalar → ECDH → `stpl1` Bech32.
|
|
2102
2418
|
*/
|
|
2103
|
-
async generateStealthAddressFromStellarSignature(signature) {
|
|
2104
|
-
return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature);
|
|
2419
|
+
async generateStealthAddressFromStellarSignature(signature, domain) {
|
|
2420
|
+
return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature, domain);
|
|
2105
2421
|
}
|
|
2106
2422
|
encodeDecodedEphemeralKey(decoded) {
|
|
2107
2423
|
return encodeDecodedEphemeralKey(decoded);
|
|
@@ -2123,38 +2439,6 @@ class PrivacyPoolSDK {
|
|
|
2123
2439
|
}
|
|
2124
2440
|
}
|
|
2125
2441
|
|
|
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
2442
|
function signatureBase64ToBytes(signature) {
|
|
2159
2443
|
const value = signature.trim();
|
|
2160
2444
|
if (/^(0x)?[0-9a-fA-F]{128}$/.test(value)) {
|
|
@@ -2163,6 +2447,9 @@ function signatureBase64ToBytes(signature) {
|
|
|
2163
2447
|
return Buffer.from(value, 'base64');
|
|
2164
2448
|
}
|
|
2165
2449
|
|
|
2450
|
+
function addressToScVal(address) {
|
|
2451
|
+
return stellarSdk.nativeToScVal(address, { type: 'address' });
|
|
2452
|
+
}
|
|
2166
2453
|
function passageIdHexToScVal(passageIdHex) {
|
|
2167
2454
|
const bytes = Buffer.from(passageIdHex.replace(/^0x/, ''), 'hex');
|
|
2168
2455
|
return stellarSdk.xdr.ScVal.scvBytes(bytes);
|
|
@@ -2179,7 +2466,6 @@ function helperSubmitWithPassageOperation(helperContractId, params, passageId, e
|
|
|
2179
2466
|
stellarSdk.nativeToScVal(params.nonce, { type: 'u64' }),
|
|
2180
2467
|
stellarSdk.xdr.ScVal.scvBytes(params.proofBytes),
|
|
2181
2468
|
stellarSdk.xdr.ScVal.scvBytes(params.publicSignalsBytes),
|
|
2182
|
-
onboardingOptionToScVal(params.onboarding),
|
|
2183
2469
|
passageIdHexToScVal(passageId),
|
|
2184
2470
|
stellarSdk.xdr.ScVal.scvU32(expiresAtLedger),
|
|
2185
2471
|
signatureToScVal(signatureBase64ToBytes(signature)),
|
|
@@ -2309,6 +2595,20 @@ const COIN_VALUE_STROOPS = 1000000000;
|
|
|
2309
2595
|
function resolveApplicationIdFromCli(parsed) {
|
|
2310
2596
|
return parsed['application-id'] ?? process.env.APPLICATION_ID ?? DEFAULT_APPLICATION_ID;
|
|
2311
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
|
+
}
|
|
2312
2612
|
async function main() {
|
|
2313
2613
|
const args = process.argv.slice(2);
|
|
2314
2614
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
@@ -2337,6 +2637,9 @@ async function main() {
|
|
|
2337
2637
|
else if (command === 'priv-scalar-from-signature') {
|
|
2338
2638
|
await handlePrivScalarFromSignature(args.slice(1));
|
|
2339
2639
|
}
|
|
2640
|
+
else if (command === 'stealth-pubkey-hex') {
|
|
2641
|
+
handleStealthPubkeyHex(args.slice(1));
|
|
2642
|
+
}
|
|
2340
2643
|
else if (command === 'kyt-submit-approved') {
|
|
2341
2644
|
await handleKytSubmitApproved(args.slice(1));
|
|
2342
2645
|
}
|
|
@@ -2364,13 +2667,25 @@ Commands:
|
|
|
2364
2667
|
|
|
2365
2668
|
stealth-sign-message Print message to sign (Stellar wallet / stellar CLI)
|
|
2366
2669
|
--address <G...> Stellar account address (required)
|
|
2670
|
+
--network-passphrase <string> Network passphrase bound into the spend scalar
|
|
2671
|
+
--pool <C...> Pool contract id bound into the spend scalar
|
|
2672
|
+
--registry <C...> Registry contract id bound into the spend scalar
|
|
2673
|
+
--schema-version <u32> Note schema version (default: 1)
|
|
2367
2674
|
--nonce <string> Nonce label (default: "main address")
|
|
2368
2675
|
|
|
2369
2676
|
stealth-from-signature Derive stpl1 stealth address from Ed25519 signature
|
|
2370
2677
|
--signature <value> 128 hex chars (optional 0x) or standard base64 (64 bytes)
|
|
2371
2678
|
--signature-file <path> Read signature from file (whitespace trimmed)
|
|
2679
|
+
--network-passphrase <string> Spend-scalar domain (same as stealth-sign-message)
|
|
2680
|
+
--pool <C...>
|
|
2681
|
+
--registry <C...>
|
|
2682
|
+
--schema-version <u32>
|
|
2372
2683
|
|
|
2373
|
-
priv-scalar-from-signature Print privKeyScalar (decimal Fr) from
|
|
2684
|
+
priv-scalar-from-signature Print privKeyScalar (decimal Fr) from domain-separated spend digest
|
|
2685
|
+
|
|
2686
|
+
stealth-pubkey-hex Decode a stpl1 stealth address into its BabyJubJub public key
|
|
2687
|
+
--stealth <stpl1...> Stealth address to decode
|
|
2688
|
+
Prints x hex on line 1, y hex on line 2 (registry public_key_x/public_key_y)
|
|
2374
2689
|
|
|
2375
2690
|
kyt-submit-approved Submit helper.register_passage + pool.transact with signed non-root auth
|
|
2376
2691
|
--source-secret <S...> Secret seed for the depositing/withdrawing Stellar account (or SOURCE_SECRET env)
|
|
@@ -2397,10 +2712,12 @@ Commands:
|
|
|
2397
2712
|
--public-withdraw-stroops <u64> Optional: amount to withdraw publicly (must be < coin.value). Rest stays in pool as change.
|
|
2398
2713
|
--change-stealth <stpl1...> Required with --public-withdraw-stroops: stealth for the change note (same as deposit)
|
|
2399
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)
|
|
2400
2716
|
--output-proof <file> Write proof hex to file
|
|
2401
2717
|
--output-public <file> Write public signals hex to file
|
|
2718
|
+
--output-ciphertext <file> Write userspace MiMC ciphertext blob hex to file
|
|
2402
2719
|
|
|
2403
|
-
deposit-proof Groth16 proof for deposit-only transact (
|
|
2720
|
+
deposit-proof Groth16 proof for deposit-only transact (dummy withdraws, 1 deposit + dummy outs)
|
|
2404
2721
|
--state-root <dec> Current Merkle root (decimal Fr), must match pool on submit
|
|
2405
2722
|
--stealth <stpl1...> Recipient stealth address (decodes to recipient public key)
|
|
2406
2723
|
--token <C...> Asset contract id (or set TOKEN_ADDRESS)
|
|
@@ -2408,8 +2725,10 @@ Commands:
|
|
|
2408
2725
|
--ephemeral-scalar-hex <hex> Same 32-byte hex as deposit / generate --scalar
|
|
2409
2726
|
--value <dec> Deposit amount (stroops as decimal Fr); optional if --coin (must match coin)
|
|
2410
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)
|
|
2411
2729
|
--output-proof <file> Write proof hex to file
|
|
2412
2730
|
--output-public <file> Write public signals hex to file
|
|
2731
|
+
--output-ciphertext <file> Write userspace MiMC ciphertext blob hex to file
|
|
2413
2732
|
|
|
2414
2733
|
Output (withdraw, deposit-proof):
|
|
2415
2734
|
Prints proof_hex on first line and public_hex on second line to stdout.
|
|
@@ -2478,6 +2797,24 @@ function parseArgs(args) {
|
|
|
2478
2797
|
}
|
|
2479
2798
|
return parsed;
|
|
2480
2799
|
}
|
|
2800
|
+
function requireSpendScalarDomain(parsed) {
|
|
2801
|
+
const networkPassphrase = parsed['network-passphrase'] ?? networkPassphraseFromArgs(parsed);
|
|
2802
|
+
const poolContract = requireParsedArg(parsed, 'pool');
|
|
2803
|
+
const registryContract = requireParsedArg(parsed, 'registry');
|
|
2804
|
+
const schemaRaw = parsed['schema-version'];
|
|
2805
|
+
const domain = {
|
|
2806
|
+
networkPassphrase,
|
|
2807
|
+
poolContract,
|
|
2808
|
+
registryContract,
|
|
2809
|
+
};
|
|
2810
|
+
if (schemaRaw !== undefined) {
|
|
2811
|
+
domain.schemaVersion = Number(schemaRaw);
|
|
2812
|
+
}
|
|
2813
|
+
else {
|
|
2814
|
+
domain.schemaVersion = OWNER_BOUND_NOTE_SCHEMA_VERSION;
|
|
2815
|
+
}
|
|
2816
|
+
return domain;
|
|
2817
|
+
}
|
|
2481
2818
|
function handleStealthSignMessage(args) {
|
|
2482
2819
|
const parsed = parseArgs(args);
|
|
2483
2820
|
const address = parsed['address'];
|
|
@@ -2486,7 +2823,7 @@ function handleStealthSignMessage(args) {
|
|
|
2486
2823
|
process.exit(1);
|
|
2487
2824
|
}
|
|
2488
2825
|
const nonce = parsed['nonce'] ?? DEFAULT_STEALTH_SIGN_NONCE;
|
|
2489
|
-
console.log(buildStealthAddressSignMessage(address, nonce));
|
|
2826
|
+
console.log(buildStealthAddressSignMessage(address, requireSpendScalarDomain(parsed), nonce));
|
|
2490
2827
|
}
|
|
2491
2828
|
async function handleStealthFromSignature(args) {
|
|
2492
2829
|
const parsed = parseArgs(args);
|
|
@@ -2499,9 +2836,16 @@ async function handleStealthFromSignature(args) {
|
|
|
2499
2836
|
process.exit(1);
|
|
2500
2837
|
}
|
|
2501
2838
|
const sdk = await PrivacyPoolSDK.init();
|
|
2502
|
-
const stealth = await sdk.generateStealthAddressFromStellarSignature(sig);
|
|
2839
|
+
const stealth = await sdk.generateStealthAddressFromStellarSignature(sig, requireSpendScalarDomain(parsed));
|
|
2503
2840
|
console.log(stealth);
|
|
2504
2841
|
}
|
|
2842
|
+
function handleStealthPubkeyHex(args) {
|
|
2843
|
+
const parsed = parseArgs(args);
|
|
2844
|
+
const stealth = requireParsedArg(parsed, 'stealth');
|
|
2845
|
+
const { x, y } = decodeStealthAddress(stealth);
|
|
2846
|
+
console.log(x);
|
|
2847
|
+
console.log(y);
|
|
2848
|
+
}
|
|
2505
2849
|
async function handlePrivScalarFromSignature(args) {
|
|
2506
2850
|
const parsed = parseArgs(args);
|
|
2507
2851
|
let sig = parsed['signature'];
|
|
@@ -2512,7 +2856,7 @@ async function handlePrivScalarFromSignature(args) {
|
|
|
2512
2856
|
console.error('Error: --signature <hex> or --signature-file <path> is required');
|
|
2513
2857
|
process.exit(1);
|
|
2514
2858
|
}
|
|
2515
|
-
const dec = await privKeyScalarDecimalFromStellarSignature(sig.trim());
|
|
2859
|
+
const dec = await privKeyScalarDecimalFromStellarSignature(sig.trim(), requireSpendScalarDomain(parsed));
|
|
2516
2860
|
console.log(dec);
|
|
2517
2861
|
}
|
|
2518
2862
|
function handleRandomScalar() {
|
|
@@ -2541,33 +2885,29 @@ async function handleKytSubmitApproved(args) {
|
|
|
2541
2885
|
});
|
|
2542
2886
|
console.log(txHash);
|
|
2543
2887
|
}
|
|
2544
|
-
function parseStroopsU64(label, raw,
|
|
2545
|
-
if (raw === undefined) {
|
|
2546
|
-
return defaultStroops;
|
|
2547
|
-
}
|
|
2888
|
+
function parseStroopsU64(label, raw, _min) {
|
|
2548
2889
|
if (!/^\d+$/.test(raw)) {
|
|
2549
|
-
console.error(`Error: ${label} must be a non-negative decimal integer
|
|
2890
|
+
console.error(`Error: ${label} must be a non-negative decimal integer`);
|
|
2550
2891
|
process.exit(1);
|
|
2551
2892
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
}
|
|
2558
|
-
return b;
|
|
2893
|
+
return BigInt(raw);
|
|
2894
|
+
}
|
|
2895
|
+
function parseCoinValueDecimal(label, raw, defaultStroops) {
|
|
2896
|
+
if (raw === undefined) {
|
|
2897
|
+
return defaultStroops.toString(10);
|
|
2559
2898
|
}
|
|
2560
|
-
|
|
2561
|
-
console.error(`Error:
|
|
2899
|
+
if (!/^\d+$/.test(raw)) {
|
|
2900
|
+
console.error(`Error: ${label} must be a non-negative decimal integer`);
|
|
2562
2901
|
process.exit(1);
|
|
2563
2902
|
}
|
|
2903
|
+
return raw;
|
|
2564
2904
|
}
|
|
2565
2905
|
async function handleGenerate(args) {
|
|
2566
2906
|
const parsed = parseArgs(args);
|
|
2567
2907
|
const sdk = await PrivacyPoolSDK.init();
|
|
2568
2908
|
const scalar = parsed['scalar'];
|
|
2569
2909
|
const stealth = parsed['stealth'];
|
|
2570
|
-
const amount =
|
|
2910
|
+
const amount = parseCoinValueDecimal('--amount', parsed['amount'], BigInt(COIN_VALUE_STROOPS));
|
|
2571
2911
|
const applicationId = resolveApplicationIdFromCli(parsed);
|
|
2572
2912
|
const token = parsed['token'] ?? process.env.TOKEN_ADDRESS;
|
|
2573
2913
|
if (!token) {
|
|
@@ -2582,8 +2922,7 @@ async function handleGenerate(args) {
|
|
|
2582
2922
|
let coin;
|
|
2583
2923
|
if (scalar && stealth) {
|
|
2584
2924
|
const { x, y } = decodeStealthAddress(stealth);
|
|
2585
|
-
|
|
2586
|
-
coin = sdk.generateCoinForDepositWithSharedHex(scalar, shared.x, shared.y, amount, assetHi, assetLo, applicationId);
|
|
2925
|
+
coin = sdk.generateCoinForDepositWithOwnerPubHex(scalar, x, y, amount, assetHi, assetLo, applicationId);
|
|
2587
2926
|
}
|
|
2588
2927
|
else if (scalar) {
|
|
2589
2928
|
coin = sdk.generateCoinFromDepositEphemeralScalarHex(scalar, amount, assetHi, assetLo, applicationId);
|
|
@@ -2644,9 +2983,10 @@ async function handleWithdraw(args) {
|
|
|
2644
2983
|
console.error('Error: --change-stealth is only valid with --public-withdraw-stroops');
|
|
2645
2984
|
process.exit(1);
|
|
2646
2985
|
}
|
|
2647
|
-
const
|
|
2986
|
+
const { zkConfigNonce } = resolveZkInit(parsed);
|
|
2987
|
+
const sdk = await PrivacyPoolSDK.init({ zkConfigNonce });
|
|
2648
2988
|
const applicationId = resolveApplicationIdFromCli(parsed);
|
|
2649
|
-
const audit = resolveTransactionAuditParams(applicationId);
|
|
2989
|
+
const audit = resolveTransactionAuditParams(applicationId, undefined, sdk.getLayout().nAuditSlots);
|
|
2650
2990
|
const result = await sdk.proveWithdrawal(coin, state, {
|
|
2651
2991
|
withdrawAddressHi: withdrawHi,
|
|
2652
2992
|
withdrawAddressLo: withdrawLo,
|
|
@@ -2657,7 +2997,7 @@ async function handleWithdraw(args) {
|
|
|
2657
2997
|
audit,
|
|
2658
2998
|
...(pubW !== undefined
|
|
2659
2999
|
? {
|
|
2660
|
-
publicWithdrawStroops: parseStroopsU64('--public-withdraw-stroops', pubW
|
|
3000
|
+
publicWithdrawStroops: parseStroopsU64('--public-withdraw-stroops', pubW),
|
|
2661
3001
|
changeRecipientStealthAddress: changeStealth,
|
|
2662
3002
|
}
|
|
2663
3003
|
: {}),
|
|
@@ -2674,6 +3014,10 @@ async function handleWithdraw(args) {
|
|
|
2674
3014
|
fs__namespace.writeFileSync(parsed['output-public'], result.public_hex);
|
|
2675
3015
|
console.error(`Public signals written to: ${parsed['output-public']}`);
|
|
2676
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
|
+
}
|
|
2677
3021
|
}
|
|
2678
3022
|
async function handleDepositProof(args) {
|
|
2679
3023
|
const parsed = parseArgs(args);
|
|
@@ -2691,9 +3035,10 @@ async function handleDepositProof(args) {
|
|
|
2691
3035
|
process.exit(1);
|
|
2692
3036
|
}
|
|
2693
3037
|
const [assetHi, assetLo] = stellarContractAddressToAssetFrDecimals(token);
|
|
2694
|
-
const
|
|
3038
|
+
const { zkConfigNonce } = resolveZkInit(parsed);
|
|
3039
|
+
const sdk = await PrivacyPoolSDK.init({ zkConfigNonce });
|
|
2695
3040
|
const applicationId = resolveApplicationIdFromCli(parsed);
|
|
2696
|
-
const audit = resolveTransactionAuditParams(applicationId);
|
|
3041
|
+
const audit = resolveTransactionAuditParams(applicationId, undefined, sdk.getLayout().nAuditSlots);
|
|
2697
3042
|
const recipientPublicKeys = recipientPublicKeysDecimalFromStealthAddress(parsed['stealth']);
|
|
2698
3043
|
const coinPath = parsed['coin'];
|
|
2699
3044
|
const ephemeralScalarHex = parsed['ephemeral-scalar-hex'];
|
|
@@ -2757,6 +3102,10 @@ async function handleDepositProof(args) {
|
|
|
2757
3102
|
fs__namespace.writeFileSync(parsed['output-public'], result.public_hex);
|
|
2758
3103
|
console.error(`Public signals written to: ${parsed['output-public']}`);
|
|
2759
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
|
+
}
|
|
2760
3109
|
}
|
|
2761
3110
|
main().catch((err) => {
|
|
2762
3111
|
console.error('Error:', err.message || err);
|