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