@orbinum/sdk 1.1.1 → 1.3.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/{chunk-Y6LNYJAJ.mjs → chunk-A2ZRMEYW.mjs} +147 -13
- package/dist/{index-JYVjYJtf.d.mts → index-V5Z9igEN.d.mts} +41 -1
- package/dist/{index-JYVjYJtf.d.ts → index-V5Z9igEN.d.ts} +41 -1
- package/dist/index.d.mts +224 -10
- package/dist/index.d.ts +224 -10
- package/dist/index.js +485 -130
- package/dist/index.mjs +320 -98
- package/dist/wallet/worker/index.d.mts +1 -1
- package/dist/wallet/worker/index.d.ts +1 -1
- package/dist/wallet/worker/index.js +1 -0
- package/dist/wallet/worker/index.mjs +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
LEAVES_PER_TREE,
|
|
16
16
|
MAX_WORKERS,
|
|
17
17
|
NoteBuilder,
|
|
18
|
+
OVK_BLOB_SIZE,
|
|
18
19
|
PAIRWISE_EPH_WINDOW,
|
|
19
20
|
SELF_EPH_WINDOW,
|
|
20
21
|
WORKER_CRASHED,
|
|
@@ -38,6 +39,8 @@ import {
|
|
|
38
39
|
createWorkerPool,
|
|
39
40
|
decodeNoteDisclosureKey,
|
|
40
41
|
decryptHintBatch,
|
|
42
|
+
deriveOutgoingCipherKey,
|
|
43
|
+
deriveOutgoingViewingKey,
|
|
41
44
|
deriveOwnerPk,
|
|
42
45
|
derivePairwiseEphSk,
|
|
43
46
|
derivePairwiseSharedSecret,
|
|
@@ -59,18 +62,22 @@ import {
|
|
|
59
62
|
isSpendable,
|
|
60
63
|
isValidLeafIndex,
|
|
61
64
|
leHexToBigint,
|
|
65
|
+
openOutgoingBlob,
|
|
62
66
|
pairwiseEphWindow,
|
|
67
|
+
randomOutgoingBlob,
|
|
63
68
|
recoverOwnerPkPoint,
|
|
64
69
|
scalarToHex,
|
|
65
70
|
scanAbortError,
|
|
71
|
+
sealOutgoingBlob,
|
|
66
72
|
selectNotes,
|
|
67
73
|
selfEphWindow,
|
|
68
74
|
serializeMemo,
|
|
69
75
|
toHex,
|
|
70
76
|
treeIdOf,
|
|
71
77
|
tryDecryptNote,
|
|
72
|
-
tryDecryptNoteVerbose
|
|
73
|
-
|
|
78
|
+
tryDecryptNoteVerbose,
|
|
79
|
+
tryRecoverOutgoing
|
|
80
|
+
} from "./chunk-A2ZRMEYW.mjs";
|
|
74
81
|
|
|
75
82
|
// src/foundation/encoding/base64.ts
|
|
76
83
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -365,6 +372,101 @@ function addressToFieldElement(address) {
|
|
|
365
372
|
return bytesToBigintLE(bytes) % BN254_R;
|
|
366
373
|
}
|
|
367
374
|
|
|
375
|
+
// src/protocol/memo/PaymentSlip.ts
|
|
376
|
+
import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
377
|
+
import { randomBytes } from "@noble/ciphers/utils.js";
|
|
378
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
379
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
380
|
+
import { packPoint, unpackPoint } from "@zk-kit/baby-jubjub";
|
|
381
|
+
var SLIP_DOMAIN = new TextEncoder().encode("orbinum-payment-slip-v1");
|
|
382
|
+
var NONCE_PREFIX = new TextEncoder().encode("SLP1");
|
|
383
|
+
var EPH_PK_SIZE = 32;
|
|
384
|
+
var NONCE_SUFFIX_SIZE = 8;
|
|
385
|
+
function deriveSlipKey(sharedSecret) {
|
|
386
|
+
return hkdf(sha256, sharedSecret, void 0, SLIP_DOMAIN, 32);
|
|
387
|
+
}
|
|
388
|
+
function buildNonce(suffix) {
|
|
389
|
+
const nonce = new Uint8Array(12);
|
|
390
|
+
nonce.set(NONCE_PREFIX, 0);
|
|
391
|
+
nonce.set(suffix, 4);
|
|
392
|
+
return nonce;
|
|
393
|
+
}
|
|
394
|
+
function sealPaymentSlip(recipientIvkPacked, fields) {
|
|
395
|
+
if (recipientIvkPacked.length !== 32) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`PaymentSlip: recipientIvk must be 32 bytes, got ${recipientIvkPacked.length}`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
const ivkPoint = unpackPoint(bytesToBigintLE(recipientIvkPacked));
|
|
401
|
+
if (!ivkPoint) throw new Error("PaymentSlip: invalid recipient viewing public key");
|
|
402
|
+
const ephSkScalar = bytesToBjjScalar(randomBytes(32));
|
|
403
|
+
const ephPkPacked = bigintTo32Le(packPoint(fastMulBase(ephSkScalar)));
|
|
404
|
+
const sharedPoint = fastMulPoint(ivkPoint, ephSkScalar);
|
|
405
|
+
const sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
406
|
+
const slipKey = deriveSlipKey(sharedSecret);
|
|
407
|
+
const suffix = randomBytes(NONCE_SUFFIX_SIZE);
|
|
408
|
+
const cipher = chacha20poly1305(slipKey, buildNonce(suffix));
|
|
409
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(fields));
|
|
410
|
+
const sealed = cipher.encrypt(plaintext);
|
|
411
|
+
const envelope = new Uint8Array(EPH_PK_SIZE + NONCE_SUFFIX_SIZE + sealed.length);
|
|
412
|
+
envelope.set(ephPkPacked, 0);
|
|
413
|
+
envelope.set(suffix, EPH_PK_SIZE);
|
|
414
|
+
envelope.set(sealed, EPH_PK_SIZE + NONCE_SUFFIX_SIZE);
|
|
415
|
+
return envelope;
|
|
416
|
+
}
|
|
417
|
+
function openPaymentSlip(recipientIvsk, envelope) {
|
|
418
|
+
if (envelope.length < EPH_PK_SIZE + NONCE_SUFFIX_SIZE + 16) return null;
|
|
419
|
+
try {
|
|
420
|
+
const ephPkPacked = envelope.subarray(0, EPH_PK_SIZE);
|
|
421
|
+
const ephPkPoint = unpackPoint(bytesToBigintLE(ephPkPacked));
|
|
422
|
+
if (!ephPkPoint) return null;
|
|
423
|
+
const ivskScalar = bytesToBjjScalar(recipientIvsk);
|
|
424
|
+
const sharedSecret = bigintTo32Le(fastMulPoint(ephPkPoint, ivskScalar)[0]);
|
|
425
|
+
const slipKey = deriveSlipKey(sharedSecret);
|
|
426
|
+
const suffix = envelope.subarray(EPH_PK_SIZE, EPH_PK_SIZE + NONCE_SUFFIX_SIZE);
|
|
427
|
+
const sealed = envelope.subarray(EPH_PK_SIZE + NONCE_SUFFIX_SIZE);
|
|
428
|
+
const cipher = chacha20poly1305(slipKey, buildNonce(suffix));
|
|
429
|
+
const plaintext = cipher.decrypt(sealed);
|
|
430
|
+
const fields = JSON.parse(new TextDecoder().decode(plaintext));
|
|
431
|
+
if (typeof fields.commitmentHex !== "string" || typeof fields.encryptedMemo !== "string") {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
return fields;
|
|
435
|
+
} catch {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
var PAYMENT_SLIP_SCHEME = "orbslip1:";
|
|
440
|
+
function slipChecksum(payload) {
|
|
441
|
+
const digest = sha256(new TextEncoder().encode(PAYMENT_SLIP_SCHEME + payload));
|
|
442
|
+
return toHex(digest.slice(0, 4)).slice(2);
|
|
443
|
+
}
|
|
444
|
+
function encodePaymentSlip(envelope) {
|
|
445
|
+
const payload = base64UrlEncode(toHex(envelope));
|
|
446
|
+
return `${PAYMENT_SLIP_SCHEME}${payload}:${slipChecksum(payload)}`;
|
|
447
|
+
}
|
|
448
|
+
function decodePaymentSlip(text) {
|
|
449
|
+
if (!text.startsWith(PAYMENT_SLIP_SCHEME)) return null;
|
|
450
|
+
const rest = text.slice(PAYMENT_SLIP_SCHEME.length);
|
|
451
|
+
const sep = rest.lastIndexOf(":");
|
|
452
|
+
if (sep < 0) return null;
|
|
453
|
+
const payload = rest.slice(0, sep);
|
|
454
|
+
const checksum = rest.slice(sep + 1);
|
|
455
|
+
if (slipChecksum(payload) !== checksum) return null;
|
|
456
|
+
try {
|
|
457
|
+
const hex = base64UrlDecode(payload);
|
|
458
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
459
|
+
if (clean.length % 2 !== 0) return null;
|
|
460
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
461
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
462
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
463
|
+
}
|
|
464
|
+
return bytes;
|
|
465
|
+
} catch {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
368
470
|
// src/chain/evm/precompiles/addresses.ts
|
|
369
471
|
var PRECOMPILE_ADDR = {
|
|
370
472
|
// ── Ethereum standard (EIP) ─────────────────────────────────────────────
|
|
@@ -463,10 +565,10 @@ ${canonicalAccountId(address)}`;
|
|
|
463
565
|
}
|
|
464
566
|
|
|
465
567
|
// src/protocol/keys/PrivacyKeyManager.ts
|
|
466
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
568
|
+
import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
|
|
467
569
|
function privacyAddressChecksum(ownerPkHex, ivkHex) {
|
|
468
570
|
const body = `orbpriv2:${ownerPkHex}:${ivkHex}`;
|
|
469
|
-
const digest =
|
|
571
|
+
const digest = sha2562(new TextEncoder().encode(body));
|
|
470
572
|
return toHex(digest.slice(0, 4)).slice(2);
|
|
471
573
|
}
|
|
472
574
|
var PrivacyKeyManager = class {
|
|
@@ -475,7 +577,8 @@ var PrivacyKeyManager = class {
|
|
|
475
577
|
masterBytes: null,
|
|
476
578
|
viewingSecretKey: null,
|
|
477
579
|
viewingPublicKeyPacked: null,
|
|
478
|
-
ownerPk: null
|
|
580
|
+
ownerPk: null,
|
|
581
|
+
outgoingViewingKey: null
|
|
479
582
|
};
|
|
480
583
|
/**
|
|
481
584
|
* Load a spending key and its corresponding master bytes into the in-memory session.
|
|
@@ -490,12 +593,14 @@ var PrivacyKeyManager = class {
|
|
|
490
593
|
const viewingSecretKey = deriveViewingSecretKey(spendingKey);
|
|
491
594
|
const viewingPublicKeyPacked = deriveViewingPublicKey(viewingSecretKey);
|
|
492
595
|
const ownerPk = deriveOwnerPk(spendingKey);
|
|
596
|
+
const outgoingViewingKey = deriveOutgoingViewingKey(masterBytes);
|
|
493
597
|
this._state = {
|
|
494
598
|
spendingKey,
|
|
495
599
|
masterBytes,
|
|
496
600
|
viewingSecretKey,
|
|
497
601
|
viewingPublicKeyPacked,
|
|
498
|
-
ownerPk
|
|
602
|
+
ownerPk,
|
|
603
|
+
outgoingViewingKey
|
|
499
604
|
};
|
|
500
605
|
}
|
|
501
606
|
/** Clear all key material from memory. Call on vault lock / sign-out. */
|
|
@@ -505,7 +610,8 @@ var PrivacyKeyManager = class {
|
|
|
505
610
|
masterBytes: null,
|
|
506
611
|
viewingSecretKey: null,
|
|
507
612
|
viewingPublicKeyPacked: null,
|
|
508
|
-
ownerPk: null
|
|
613
|
+
ownerPk: null,
|
|
614
|
+
outgoingViewingKey: null
|
|
509
615
|
};
|
|
510
616
|
}
|
|
511
617
|
/** Returns true if a spending key has been loaded. */
|
|
@@ -550,6 +656,16 @@ var PrivacyKeyManager = class {
|
|
|
550
656
|
}
|
|
551
657
|
return this._state.ownerPk;
|
|
552
658
|
}
|
|
659
|
+
/**
|
|
660
|
+
* Returns the 32-byte outgoing viewing key (ovk). Throws if not loaded.
|
|
661
|
+
* Used to seal/open the outgoing blob that lets the sender recover a transfer.
|
|
662
|
+
*/
|
|
663
|
+
getOutgoingViewingKey() {
|
|
664
|
+
if (this._state.outgoingViewingKey === null) {
|
|
665
|
+
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
666
|
+
}
|
|
667
|
+
return this._state.outgoingViewingKey;
|
|
668
|
+
}
|
|
553
669
|
/** Returns the spending key as a 32-byte little-endian Uint8Array. Throws if not loaded. */
|
|
554
670
|
getSpendingKeyBytes() {
|
|
555
671
|
return bigintTo32Le(this.getSpendingKey());
|
|
@@ -651,8 +767,8 @@ var PrivacyKeyManager = class {
|
|
|
651
767
|
};
|
|
652
768
|
|
|
653
769
|
// src/protocol/keys/spendingKeyDerivation.ts
|
|
654
|
-
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
655
|
-
import { sha256 as
|
|
770
|
+
import { hkdf as hkdf2 } from "@noble/hashes/hkdf.js";
|
|
771
|
+
import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
|
|
656
772
|
var MIN_SIGNATURE_BYTES = 32;
|
|
657
773
|
var MIN_DISTINCT_BYTES = 8;
|
|
658
774
|
function assertUsableSignature(sigBytes) {
|
|
@@ -674,7 +790,7 @@ async function deriveMasterKeyBytes(signatureHex, chainId, address) {
|
|
|
674
790
|
const info = new TextEncoder().encode(
|
|
675
791
|
`orbinum-sk-${KEY_VERSION}:${chainId}:${canonicalAccountId(address)}`
|
|
676
792
|
);
|
|
677
|
-
return
|
|
793
|
+
return hkdf2(sha2563, sigBytes, new Uint8Array(0), info, 32);
|
|
678
794
|
}
|
|
679
795
|
async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
|
|
680
796
|
return deriveSpendingKeyFromMaster(await deriveMasterKeyBytes(signatureHex, chainId, address));
|
|
@@ -686,7 +802,7 @@ import {
|
|
|
686
802
|
generateProof,
|
|
687
803
|
WebArtifactProvider
|
|
688
804
|
} from "@orbinum/proof-generator";
|
|
689
|
-
import { randomBytes } from "@noble/ciphers/utils.js";
|
|
805
|
+
import { randomBytes as randomBytes2 } from "@noble/ciphers/utils.js";
|
|
690
806
|
import { mulPointEscalar, Base8 } from "@zk-kit/baby-jubjub";
|
|
691
807
|
import { poseidon4 } from "poseidon-lite";
|
|
692
808
|
|
|
@@ -720,7 +836,7 @@ async function generateUnshieldProof(inputs, options = {}) {
|
|
|
720
836
|
throw new Error("changeValue must be >= 0.");
|
|
721
837
|
}
|
|
722
838
|
const changeOwnerPubkey = inputs.changeOwnerPubkey ?? mulPointEscalar(Base8, inputs.spendingKey)[0];
|
|
723
|
-
const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE(
|
|
839
|
+
const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE(randomBytes2(32)) : 0n);
|
|
724
840
|
const changeCommitment = changeValue > 0n ? poseidon4([changeValue, inputs.assetId, changeOwnerPubkey, changeBlinding]) : 0n;
|
|
725
841
|
const circuitInputs = {
|
|
726
842
|
merkle_root: leHexToBigint(inputs.merkleRoot).toString(),
|
|
@@ -4582,6 +4698,7 @@ async function processHints(ctx, hints, total) {
|
|
|
4582
4698
|
pageEntries.push({ note, isNew });
|
|
4583
4699
|
}
|
|
4584
4700
|
if (pageEntries.length > 0) await ctx.onPage?.(pageEntries);
|
|
4701
|
+
await ctx.onBatchDone?.(outcome.maxLeafIndex);
|
|
4585
4702
|
ctx.onProgress?.({ scanned: outcome.scanned, total, found: outcome.found });
|
|
4586
4703
|
}
|
|
4587
4704
|
async function processSealedChunks(ctx, source, sinceLeafIndex) {
|
|
@@ -4616,6 +4733,7 @@ async function processSealedChunks(ctx, source, sinceLeafIndex) {
|
|
|
4616
4733
|
}
|
|
4617
4734
|
async function collectScanEntries(params) {
|
|
4618
4735
|
const { source, pool, keys, existingHexes, sinceLeafIndex, onProgress, signal, onPage } = params;
|
|
4736
|
+
const { onBatchDone } = params;
|
|
4619
4737
|
if (signal?.aborted) throw scanAbortError();
|
|
4620
4738
|
const outcome = {
|
|
4621
4739
|
scanEntries: [],
|
|
@@ -4639,7 +4757,8 @@ async function collectScanEntries(params) {
|
|
|
4639
4757
|
vaultHexes: params.vaultHexes ?? existingHexes,
|
|
4640
4758
|
onProgress,
|
|
4641
4759
|
signal,
|
|
4642
|
-
onPage
|
|
4760
|
+
onPage,
|
|
4761
|
+
onBatchDone
|
|
4643
4762
|
};
|
|
4644
4763
|
let tailSince = sinceLeafIndex;
|
|
4645
4764
|
try {
|
|
@@ -4680,52 +4799,12 @@ async function collectScanEntries(params) {
|
|
|
4680
4799
|
return outcome;
|
|
4681
4800
|
}
|
|
4682
4801
|
|
|
4683
|
-
// src/wallet/scanner/selfEphGap.ts
|
|
4684
|
-
function noteEphPkHex(note) {
|
|
4685
|
-
if (!Array.isArray(note.memo) || note.memo.length < 32) return null;
|
|
4686
|
-
return toHex(note.memo.slice(-32)).toLowerCase();
|
|
4687
|
-
}
|
|
4688
|
-
function gapMargin(windowSize) {
|
|
4689
|
-
return Math.max(1, Math.floor(windowSize / 16));
|
|
4690
|
-
}
|
|
4691
|
-
function resolveSelfEphCeiling(params) {
|
|
4692
|
-
const { notes, spendingKey, viewingKey, scanMaxIndex } = params;
|
|
4693
|
-
const windowSize = params.windowSize ?? SELF_EPH_WINDOW;
|
|
4694
|
-
if (scanMaxIndex === null) return null;
|
|
4695
|
-
if (scanMaxIndex < windowSize - gapMargin(windowSize)) return scanMaxIndex;
|
|
4696
|
-
const ownEphPks = /* @__PURE__ */ new Set();
|
|
4697
|
-
for (const note of notes) {
|
|
4698
|
-
const hex = noteEphPkHex(note);
|
|
4699
|
-
if (hex) ownEphPks.add(hex);
|
|
4700
|
-
}
|
|
4701
|
-
const ivkPacked = deriveViewingPublicKey(viewingKey);
|
|
4702
|
-
let max = scanMaxIndex;
|
|
4703
|
-
let from = windowSize;
|
|
4704
|
-
for (; ; ) {
|
|
4705
|
-
const slice = selfEphWindow(spendingKey, ivkPacked, from, windowSize);
|
|
4706
|
-
let matched = false;
|
|
4707
|
-
for (const entry of slice) {
|
|
4708
|
-
if (ownEphPks.has(entry.ephPkHex.toLowerCase())) {
|
|
4709
|
-
matched = true;
|
|
4710
|
-
if (entry.index > max) max = entry.index;
|
|
4711
|
-
}
|
|
4712
|
-
}
|
|
4713
|
-
if (!matched) break;
|
|
4714
|
-
from += windowSize;
|
|
4715
|
-
}
|
|
4716
|
-
return max;
|
|
4717
|
-
}
|
|
4718
|
-
function windowSizeForCounter(counter) {
|
|
4719
|
-
const needed = counter + gapMargin(SELF_EPH_WINDOW);
|
|
4720
|
-
return Math.max(SELF_EPH_WINDOW, Math.ceil(needed / SELF_EPH_WINDOW) * SELF_EPH_WINDOW);
|
|
4721
|
-
}
|
|
4722
|
-
|
|
4723
4802
|
// src/wallet/scanner/phases/spentSet.ts
|
|
4724
4803
|
function normalizeNullifierHex(hex) {
|
|
4725
4804
|
return hex.toLowerCase();
|
|
4726
4805
|
}
|
|
4727
|
-
async function
|
|
4728
|
-
const { source, cache,
|
|
4806
|
+
async function openSpentSet(params) {
|
|
4807
|
+
const { source, cache, signal } = params;
|
|
4729
4808
|
const throwIfAborted = () => {
|
|
4730
4809
|
if (signal?.aborted) throw scanAbortError();
|
|
4731
4810
|
};
|
|
@@ -4734,20 +4813,29 @@ async function resolveSpentSet(params) {
|
|
|
4734
4813
|
await syncNullifierCache(params, manifest, throwIfAborted);
|
|
4735
4814
|
throwIfAborted();
|
|
4736
4815
|
const tail = await getConsistentTail(params, throwIfAborted);
|
|
4737
|
-
const stored = await cache.getSpentNullifiers([...ownNullifiers]);
|
|
4738
4816
|
const tailMap = new Map(
|
|
4739
4817
|
tail.data.map((h, i) => [
|
|
4740
4818
|
normalizeNullifierHex(h),
|
|
4741
4819
|
{ spentAt: tail.timestampsMs?.[i] ?? null, txHash: tail.txHashes?.[i] ?? null }
|
|
4742
4820
|
])
|
|
4743
4821
|
);
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4822
|
+
return {
|
|
4823
|
+
async query(ownNullifiers) {
|
|
4824
|
+
if (ownNullifiers.size === 0) return /* @__PURE__ */ new Map();
|
|
4825
|
+
const stored = await cache.getSpentNullifiers([...ownNullifiers]);
|
|
4826
|
+
const spent = /* @__PURE__ */ new Map();
|
|
4827
|
+
const unknown = { spentAt: null, txHash: null };
|
|
4828
|
+
for (const h of ownNullifiers) {
|
|
4829
|
+
if (stored.has(h)) spent.set(h, stored.get(h) ?? unknown);
|
|
4830
|
+
else if (tailMap.has(h)) spent.set(h, tailMap.get(h) ?? unknown);
|
|
4831
|
+
}
|
|
4832
|
+
return spent;
|
|
4833
|
+
}
|
|
4834
|
+
};
|
|
4835
|
+
}
|
|
4836
|
+
async function resolveSpentSet(params) {
|
|
4837
|
+
const handle = await openSpentSet(params);
|
|
4838
|
+
return handle.query(params.ownNullifiers);
|
|
4751
4839
|
}
|
|
4752
4840
|
async function syncNullifierCache(params, manifest, throwIfAborted) {
|
|
4753
4841
|
const { source, cache } = params;
|
|
@@ -4797,40 +4885,6 @@ async function getConsistentTail(params, throwIfAborted) {
|
|
|
4797
4885
|
throw new Error("nullifier tail inconsistent with local chunks after retry");
|
|
4798
4886
|
}
|
|
4799
4887
|
|
|
4800
|
-
// src/wallet/scanner/phases/spentStatus.ts
|
|
4801
|
-
function collectNullifiersToQuery(scanEntries, onChainHexes, vaultNotes) {
|
|
4802
|
-
const syncedCommitments = new Set(scanEntries.map((e) => e.note.commitmentHex));
|
|
4803
|
-
const nullifiers = /* @__PURE__ */ new Set();
|
|
4804
|
-
for (const { note } of scanEntries) {
|
|
4805
|
-
nullifiers.add(note.nullifierHex.toLowerCase());
|
|
4806
|
-
}
|
|
4807
|
-
for (const vaultNote of vaultNotes) {
|
|
4808
|
-
if (syncedCommitments.has(vaultNote.commitmentHex)) continue;
|
|
4809
|
-
if (!onChainHexes.has(vaultNote.commitmentHex)) continue;
|
|
4810
|
-
if (vaultNote.nullifierHex) nullifiers.add(vaultNote.nullifierHex.toLowerCase());
|
|
4811
|
-
}
|
|
4812
|
-
return nullifiers;
|
|
4813
|
-
}
|
|
4814
|
-
async function resolveSpentStatus(params) {
|
|
4815
|
-
if (params.nullifiers.size === 0) return /* @__PURE__ */ new Map();
|
|
4816
|
-
try {
|
|
4817
|
-
return await resolveSpentSet({
|
|
4818
|
-
source: params.source,
|
|
4819
|
-
cache: params.cache,
|
|
4820
|
-
ownNullifiers: params.nullifiers,
|
|
4821
|
-
signal: params.signal,
|
|
4822
|
-
onWarning: params.onWarning
|
|
4823
|
-
});
|
|
4824
|
-
} catch (err) {
|
|
4825
|
-
if (isAbortError(err)) throw err;
|
|
4826
|
-
params.onWarning?.(
|
|
4827
|
-
"could not resolve spent-nullifier status (feed unavailable or inconsistent); spent status left unverified this pass",
|
|
4828
|
-
err
|
|
4829
|
-
);
|
|
4830
|
-
return /* @__PURE__ */ new Map();
|
|
4831
|
-
}
|
|
4832
|
-
}
|
|
4833
|
-
|
|
4834
4888
|
// src/wallet/scanner/phases/persist.ts
|
|
4835
4889
|
function purgeIsTrustworthy(hintsScanned, purgeableNotes) {
|
|
4836
4890
|
if (purgeableNotes === 0) return true;
|
|
@@ -4889,6 +4943,123 @@ async function persistCursor(storage, maxLeafIndex, isIncremental) {
|
|
|
4889
4943
|
});
|
|
4890
4944
|
}
|
|
4891
4945
|
|
|
4946
|
+
// src/wallet/scanner/phases/checkpoint.ts
|
|
4947
|
+
function createScanCheckpoint(params) {
|
|
4948
|
+
const { vault, storage, nullifiers, isIncremental, signal, onWarning } = params;
|
|
4949
|
+
const throwIfAborted = () => {
|
|
4950
|
+
if (signal?.aborted) throw scanAbortError();
|
|
4951
|
+
};
|
|
4952
|
+
let spentSetPromise = null;
|
|
4953
|
+
const getSpentSet = () => spentSetPromise ??= openSpentSet({
|
|
4954
|
+
source: nullifiers,
|
|
4955
|
+
cache: storage,
|
|
4956
|
+
signal,
|
|
4957
|
+
onWarning
|
|
4958
|
+
}).catch(() => null);
|
|
4959
|
+
return {
|
|
4960
|
+
async savePageNotes(entries) {
|
|
4961
|
+
throwIfAborted();
|
|
4962
|
+
const fresh = entries.filter((e) => e.isNew);
|
|
4963
|
+
if (fresh.length === 0) return;
|
|
4964
|
+
const own = new Set(fresh.map((e) => e.note.nullifierHex.toLowerCase()));
|
|
4965
|
+
const spentSet = await getSpentSet();
|
|
4966
|
+
const spent = spentSet ? await spentSet.query(own).catch(() => /* @__PURE__ */ new Map()) : /* @__PURE__ */ new Map();
|
|
4967
|
+
throwIfAborted();
|
|
4968
|
+
await vault.saveMany(
|
|
4969
|
+
fresh.map(({ note }) => {
|
|
4970
|
+
const spend = spent.get(note.nullifierHex.toLowerCase());
|
|
4971
|
+
return {
|
|
4972
|
+
note: spend ? stampSpentTxHash(note, spend.txHash) : note,
|
|
4973
|
+
noteStatus: {
|
|
4974
|
+
spent: spend !== void 0,
|
|
4975
|
+
spentAt: spend?.spentAt ?? null
|
|
4976
|
+
}
|
|
4977
|
+
};
|
|
4978
|
+
})
|
|
4979
|
+
);
|
|
4980
|
+
},
|
|
4981
|
+
async advanceCursor(maxLeafIndex) {
|
|
4982
|
+
if (maxLeafIndex === void 0) return;
|
|
4983
|
+
throwIfAborted();
|
|
4984
|
+
await persistCursor(storage, maxLeafIndex, isIncremental);
|
|
4985
|
+
}
|
|
4986
|
+
};
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
// src/wallet/scanner/selfEphGap.ts
|
|
4990
|
+
function noteEphPkHex(note) {
|
|
4991
|
+
if (!Array.isArray(note.memo) || note.memo.length < 32) return null;
|
|
4992
|
+
return toHex(note.memo.slice(-32)).toLowerCase();
|
|
4993
|
+
}
|
|
4994
|
+
function gapMargin(windowSize) {
|
|
4995
|
+
return Math.max(1, Math.floor(windowSize / 16));
|
|
4996
|
+
}
|
|
4997
|
+
function resolveSelfEphCeiling(params) {
|
|
4998
|
+
const { notes, spendingKey, viewingKey, scanMaxIndex } = params;
|
|
4999
|
+
const windowSize = params.windowSize ?? SELF_EPH_WINDOW;
|
|
5000
|
+
if (scanMaxIndex === null) return null;
|
|
5001
|
+
if (scanMaxIndex < windowSize - gapMargin(windowSize)) return scanMaxIndex;
|
|
5002
|
+
const ownEphPks = /* @__PURE__ */ new Set();
|
|
5003
|
+
for (const note of notes) {
|
|
5004
|
+
const hex = noteEphPkHex(note);
|
|
5005
|
+
if (hex) ownEphPks.add(hex);
|
|
5006
|
+
}
|
|
5007
|
+
const ivkPacked = deriveViewingPublicKey(viewingKey);
|
|
5008
|
+
let max = scanMaxIndex;
|
|
5009
|
+
let from = windowSize;
|
|
5010
|
+
for (; ; ) {
|
|
5011
|
+
const slice = selfEphWindow(spendingKey, ivkPacked, from, windowSize);
|
|
5012
|
+
let matched = false;
|
|
5013
|
+
for (const entry of slice) {
|
|
5014
|
+
if (ownEphPks.has(entry.ephPkHex.toLowerCase())) {
|
|
5015
|
+
matched = true;
|
|
5016
|
+
if (entry.index > max) max = entry.index;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
if (!matched) break;
|
|
5020
|
+
from += windowSize;
|
|
5021
|
+
}
|
|
5022
|
+
return max;
|
|
5023
|
+
}
|
|
5024
|
+
function windowSizeForCounter(counter) {
|
|
5025
|
+
const needed = counter + gapMargin(SELF_EPH_WINDOW);
|
|
5026
|
+
return Math.max(SELF_EPH_WINDOW, Math.ceil(needed / SELF_EPH_WINDOW) * SELF_EPH_WINDOW);
|
|
5027
|
+
}
|
|
5028
|
+
|
|
5029
|
+
// src/wallet/scanner/phases/spentStatus.ts
|
|
5030
|
+
function collectNullifiersToQuery(scanEntries, onChainHexes, vaultNotes) {
|
|
5031
|
+
const syncedCommitments = new Set(scanEntries.map((e) => e.note.commitmentHex));
|
|
5032
|
+
const nullifiers = /* @__PURE__ */ new Set();
|
|
5033
|
+
for (const { note } of scanEntries) {
|
|
5034
|
+
nullifiers.add(note.nullifierHex.toLowerCase());
|
|
5035
|
+
}
|
|
5036
|
+
for (const vaultNote of vaultNotes) {
|
|
5037
|
+
if (syncedCommitments.has(vaultNote.commitmentHex)) continue;
|
|
5038
|
+
if (!onChainHexes.has(vaultNote.commitmentHex)) continue;
|
|
5039
|
+
if (vaultNote.nullifierHex) nullifiers.add(vaultNote.nullifierHex.toLowerCase());
|
|
5040
|
+
}
|
|
5041
|
+
return nullifiers;
|
|
5042
|
+
}
|
|
5043
|
+
async function resolveSpentStatus(params) {
|
|
5044
|
+
if (params.nullifiers.size === 0) return /* @__PURE__ */ new Map();
|
|
5045
|
+
try {
|
|
5046
|
+
return await resolveSpentSet({
|
|
5047
|
+
source: params.source,
|
|
5048
|
+
cache: params.cache,
|
|
5049
|
+
ownNullifiers: params.nullifiers,
|
|
5050
|
+
signal: params.signal,
|
|
5051
|
+
onWarning: params.onWarning
|
|
5052
|
+
});
|
|
5053
|
+
} catch (err) {
|
|
5054
|
+
if (isAbortError(err)) throw err;
|
|
5055
|
+
params.onWarning?.(
|
|
5056
|
+
"could not resolve spent-nullifier status (feed unavailable or inconsistent); spent status left unverified this pass",
|
|
5057
|
+
err
|
|
5058
|
+
);
|
|
5059
|
+
return /* @__PURE__ */ new Map();
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
|
|
4892
5063
|
// src/wallet/scanner/pipeline.ts
|
|
4893
5064
|
async function runScan(params) {
|
|
4894
5065
|
const { vault, storage, hints, nullifiers, pool, keys, sinceLeafIndex, signal, onWarning } = params;
|
|
@@ -4907,6 +5078,14 @@ async function runScan(params) {
|
|
|
4907
5078
|
}
|
|
4908
5079
|
}
|
|
4909
5080
|
);
|
|
5081
|
+
const checkpoint = createScanCheckpoint({
|
|
5082
|
+
vault,
|
|
5083
|
+
storage,
|
|
5084
|
+
nullifiers,
|
|
5085
|
+
isIncremental,
|
|
5086
|
+
signal,
|
|
5087
|
+
onWarning
|
|
5088
|
+
});
|
|
4910
5089
|
const scan = await collectScanEntries({
|
|
4911
5090
|
source: hints,
|
|
4912
5091
|
pool,
|
|
@@ -4927,7 +5106,9 @@ async function runScan(params) {
|
|
|
4927
5106
|
// consumer probes it with a vault commitment, so keeping the whole pool
|
|
4928
5107
|
// only bought heap.
|
|
4929
5108
|
vaultHexes: preScanHexes,
|
|
4930
|
-
onWarning
|
|
5109
|
+
onWarning,
|
|
5110
|
+
onPage: (entries) => checkpoint.savePageNotes(entries),
|
|
5111
|
+
onBatchDone: (maxLeafIndex) => checkpoint.advanceCursor(maxLeafIndex)
|
|
4931
5112
|
});
|
|
4932
5113
|
const nullifiersToQuery = collectNullifiersToQuery(
|
|
4933
5114
|
scan.scanEntries,
|
|
@@ -5300,6 +5481,22 @@ function isEntry2(value) {
|
|
|
5300
5481
|
return true;
|
|
5301
5482
|
}
|
|
5302
5483
|
|
|
5484
|
+
// src/wallet/ops/notes/paymentSlipImport.ts
|
|
5485
|
+
function importPaymentSlip(slip, keys) {
|
|
5486
|
+
const envelope = typeof slip === "string" ? decodePaymentSlip(slip) : slip;
|
|
5487
|
+
if (!envelope) return null;
|
|
5488
|
+
const fields = openPaymentSlip(keys.viewingSecretKey, envelope);
|
|
5489
|
+
if (!fields) return null;
|
|
5490
|
+
const commitment = {
|
|
5491
|
+
commitmentHex: fields.commitmentHex,
|
|
5492
|
+
leafIndex: fields.leafIndex ?? -1,
|
|
5493
|
+
encryptedMemo: fields.encryptedMemo
|
|
5494
|
+
};
|
|
5495
|
+
const note = tryDecryptNote(commitment, keys.viewingSecretKey, keys.spendingKey, keys.ownerPk);
|
|
5496
|
+
if (!note) return null;
|
|
5497
|
+
return fields.txHash ? stampCreatedTxHash(note, fields.txHash) : note;
|
|
5498
|
+
}
|
|
5499
|
+
|
|
5303
5500
|
// src/wallet/ops/spend/transfer.ts
|
|
5304
5501
|
import { CircuitType as CircuitType5 } from "@orbinum/proof-generator";
|
|
5305
5502
|
|
|
@@ -5509,6 +5706,18 @@ async function transferNotes(deps, params, onProgress) {
|
|
|
5509
5706
|
await deps.vault.save(stampCreatedTxHash(selfNote, txResult.txHash, txKind));
|
|
5510
5707
|
}
|
|
5511
5708
|
}
|
|
5709
|
+
const isSelf = deps.selfOwnerPk !== null && recipientPk === deps.selfOwnerPk;
|
|
5710
|
+
if (recipientViewingPublicKey !== void 0 && !isSelf) {
|
|
5711
|
+
try {
|
|
5712
|
+
const envelope = sealPaymentSlip(recipientViewingPublicKey, {
|
|
5713
|
+
commitmentHex: recipientNote.commitmentHex,
|
|
5714
|
+
encryptedMemo: toHex(Uint8Array.from(recipientNote.memo)),
|
|
5715
|
+
...txResult.txHash ? { txHash: txResult.txHash } : {}
|
|
5716
|
+
});
|
|
5717
|
+
return { ...txResult, paymentSlip: encodePaymentSlip(envelope) };
|
|
5718
|
+
} catch {
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5512
5721
|
}
|
|
5513
5722
|
return txResult;
|
|
5514
5723
|
}
|
|
@@ -6114,11 +6323,13 @@ export {
|
|
|
6114
6323
|
NOTE_BIGINT_FIELDS,
|
|
6115
6324
|
NOTE_TRANSFER_URI_SCHEME,
|
|
6116
6325
|
NoteBuilder,
|
|
6326
|
+
OVK_BLOB_SIZE,
|
|
6117
6327
|
OrbinumClient,
|
|
6118
6328
|
OrbinumClientProvider,
|
|
6119
6329
|
OrbinumWallet,
|
|
6120
6330
|
PAGE_SIZE,
|
|
6121
6331
|
PAIRWISE_EPH_WINDOW,
|
|
6332
|
+
PAYMENT_SLIP_SCHEME,
|
|
6122
6333
|
PRECOMPILE_ADDR,
|
|
6123
6334
|
PrivacyKeyManager,
|
|
6124
6335
|
PrivacyModule,
|
|
@@ -6189,11 +6400,14 @@ export {
|
|
|
6189
6400
|
decodeNoteBackup,
|
|
6190
6401
|
decodeNoteDisclosureKey,
|
|
6191
6402
|
decodeNoteTransferPage,
|
|
6403
|
+
decodePaymentSlip,
|
|
6192
6404
|
decodePrecompileCalldata,
|
|
6193
6405
|
decryptHintBatch,
|
|
6194
6406
|
decryptJson,
|
|
6195
6407
|
decryptNoteRecord,
|
|
6196
6408
|
deriveMasterKeyBytes,
|
|
6409
|
+
deriveOutgoingCipherKey,
|
|
6410
|
+
deriveOutgoingViewingKey,
|
|
6197
6411
|
deriveOwnerPk,
|
|
6198
6412
|
derivePairwiseEphSk,
|
|
6199
6413
|
derivePairwiseSharedSecret,
|
|
@@ -6212,6 +6426,7 @@ export {
|
|
|
6212
6426
|
detectCommitmentMismatch,
|
|
6213
6427
|
encodeNoteBackup,
|
|
6214
6428
|
encodeNoteTransferPages,
|
|
6429
|
+
encodePaymentSlip,
|
|
6215
6430
|
encryptJson,
|
|
6216
6431
|
encryptNote,
|
|
6217
6432
|
ensureCreatedAt,
|
|
@@ -6248,6 +6463,7 @@ export {
|
|
|
6248
6463
|
implicitSubstrateToEvm,
|
|
6249
6464
|
importDeviceKey,
|
|
6250
6465
|
importNotesFromBackup,
|
|
6466
|
+
importPaymentSlip,
|
|
6251
6467
|
isAbortError,
|
|
6252
6468
|
isAlreadySpentError,
|
|
6253
6469
|
isConnectionLossError,
|
|
@@ -6277,6 +6493,8 @@ export {
|
|
|
6277
6493
|
noteSpentTxHash,
|
|
6278
6494
|
noteToTransferEntry,
|
|
6279
6495
|
noteTxKind,
|
|
6496
|
+
openOutgoingBlob,
|
|
6497
|
+
openPaymentSlip,
|
|
6280
6498
|
pairwiseEphWindow,
|
|
6281
6499
|
palletErrorKind,
|
|
6282
6500
|
parseAmount,
|
|
@@ -6286,6 +6504,7 @@ export {
|
|
|
6286
6504
|
planTransfer,
|
|
6287
6505
|
planUnshield,
|
|
6288
6506
|
randomBlinding,
|
|
6507
|
+
randomOutgoingBlob,
|
|
6289
6508
|
reconstructOutgoingTxRecords,
|
|
6290
6509
|
recoverOwnerPkPoint,
|
|
6291
6510
|
recoverSelfStealthNote,
|
|
@@ -6301,6 +6520,8 @@ export {
|
|
|
6301
6520
|
runScan,
|
|
6302
6521
|
scalarToHex,
|
|
6303
6522
|
scanAbortError,
|
|
6523
|
+
sealOutgoingBlob,
|
|
6524
|
+
sealPaymentSlip,
|
|
6304
6525
|
selectGhosts,
|
|
6305
6526
|
selectNotes,
|
|
6306
6527
|
selfEphWindow,
|
|
@@ -6324,6 +6545,7 @@ export {
|
|
|
6324
6545
|
truncateMiddle,
|
|
6325
6546
|
tryDecryptNote,
|
|
6326
6547
|
tryDecryptNoteVerbose,
|
|
6548
|
+
tryRecoverOutgoing,
|
|
6327
6549
|
txLandedAfterError,
|
|
6328
6550
|
u128,
|
|
6329
6551
|
u64,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-
|
|
1
|
+
export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-V5Z9igEN.mjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-
|
|
1
|
+
export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-V5Z9igEN.js';
|
|
@@ -570,6 +570,7 @@ var import_hkdf2 = require("@noble/hashes/hkdf.js");
|
|
|
570
570
|
var import_sha25 = require("@noble/hashes/sha2.js");
|
|
571
571
|
var import_baby_jubjub6 = require("@zk-kit/baby-jubjub");
|
|
572
572
|
var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
|
|
573
|
+
var OVK_DOMAIN = new TextEncoder().encode("orbinum-ovk-v1");
|
|
573
574
|
function deriveViewingPublicKey(ivsk) {
|
|
574
575
|
const ivskScalar = BigInt(toHex(ivsk)) % BABYJUB_SUBORDER || 1n;
|
|
575
576
|
const ivkPoint = fastMulBase(ivskScalar);
|