@orbinum/sdk 2.1.0 → 3.0.1
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/README.md +15 -18
- package/dist/adapters/indexeddb/index.d.mts +10 -10
- package/dist/adapters/indexeddb/index.d.ts +10 -10
- package/dist/{chunk-VYKKBXOE.mjs → chunk-54HFH63J.mjs} +493 -256
- package/dist/{index-CLpM1984.d.mts → index-B-dbSIPN.d.mts} +209 -52
- package/dist/{index-CLpM1984.d.ts → index-B-dbSIPN.d.ts} +209 -52
- package/dist/index.d.mts +904 -485
- package/dist/index.d.ts +904 -485
- package/dist/index.js +958 -501
- package/dist/index.mjs +540 -302
- package/dist/{secretStore-CF6Nse__.d.ts → secretStore-CCYo4hna.d.mts} +15 -11
- package/dist/{secretStore-CF6Nse__.d.mts → secretStore-CCYo4hna.d.ts} +15 -11
- package/dist/wallet/worker/index.d.mts +1 -1
- package/dist/wallet/worker/index.d.ts +1 -1
- package/dist/wallet/worker/index.js +428 -111
- package/dist/wallet/worker/index.mjs +3 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -15,10 +15,11 @@ import {
|
|
|
15
15
|
LEAVES_PER_TREE,
|
|
16
16
|
MAX_WORKERS,
|
|
17
17
|
NoteBuilder,
|
|
18
|
-
|
|
18
|
+
OUTGOING_EPH_WINDOW,
|
|
19
19
|
PAIRWISE_EPH_WINDOW,
|
|
20
20
|
SELF_EPH_WINDOW,
|
|
21
21
|
WORKER_CRASHED,
|
|
22
|
+
assertSecretKeyBytes,
|
|
22
23
|
base64UrlDecode,
|
|
23
24
|
base64UrlEncode,
|
|
24
25
|
bigintTo32Be,
|
|
@@ -40,18 +41,22 @@ import {
|
|
|
40
41
|
createWorkerPool,
|
|
41
42
|
decodeNoteDisclosureKey,
|
|
42
43
|
decryptHintBatch,
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
deriveOutgoingEphPk,
|
|
45
|
+
deriveOutgoingEphSk,
|
|
46
|
+
deriveOutgoingSharedSecret,
|
|
47
|
+
deriveOutgoingViewingKeyV3,
|
|
45
48
|
deriveOwnerPk,
|
|
46
49
|
derivePairwiseEphSk,
|
|
47
50
|
derivePairwiseSharedSecret,
|
|
48
51
|
deriveSelfEphSk,
|
|
49
52
|
deriveSpendingKeyFromMaster,
|
|
53
|
+
deriveSpendingKeyV3,
|
|
50
54
|
deriveStealthOwnerPk,
|
|
51
55
|
deriveStealthSk,
|
|
52
56
|
deriveViewTag,
|
|
53
57
|
deriveViewingPublicKey,
|
|
54
58
|
deriveViewingSecretKey,
|
|
59
|
+
deriveViewingSecretKeyV3,
|
|
55
60
|
ensureHexPrefix,
|
|
56
61
|
fastMulBase,
|
|
57
62
|
fastMulPoint,
|
|
@@ -62,15 +67,20 @@ import {
|
|
|
62
67
|
isAbortError,
|
|
63
68
|
isHexOfLength,
|
|
64
69
|
isSpendable,
|
|
70
|
+
isUsableSecretKey,
|
|
65
71
|
isValidLeafIndex,
|
|
66
72
|
leHexToBigint,
|
|
67
|
-
|
|
73
|
+
openRecipientBookEntry,
|
|
74
|
+
outgoingEphWindow,
|
|
68
75
|
pairwiseEphWindow,
|
|
69
|
-
|
|
76
|
+
randomBlinding,
|
|
77
|
+
reconstructOutgoingIndex,
|
|
70
78
|
recoverOwnerPkPoint,
|
|
79
|
+
recoverSentFromSharedSecret,
|
|
80
|
+
recoverSentNote,
|
|
71
81
|
scalarToHex,
|
|
72
82
|
scanAbortError,
|
|
73
|
-
|
|
83
|
+
sealRecipientBookEntry,
|
|
74
84
|
selectNotes,
|
|
75
85
|
selfEphWindow,
|
|
76
86
|
serializeMemo,
|
|
@@ -78,8 +88,8 @@ import {
|
|
|
78
88
|
treeIdOf,
|
|
79
89
|
tryDecryptNote,
|
|
80
90
|
tryDecryptNoteVerbose,
|
|
81
|
-
|
|
82
|
-
} from "./chunk-
|
|
91
|
+
unpackUsableViewingKey
|
|
92
|
+
} from "./chunk-54HFH63J.mjs";
|
|
83
93
|
|
|
84
94
|
// src/foundation/encoding/base64.ts
|
|
85
95
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -119,14 +129,6 @@ function fromBase64(b64) {
|
|
|
119
129
|
return new Uint8Array(bytes);
|
|
120
130
|
}
|
|
121
131
|
|
|
122
|
-
// src/foundation/crypto/blinding.ts
|
|
123
|
-
function randomBlinding() {
|
|
124
|
-
const buf = new Uint8Array(32);
|
|
125
|
-
crypto.getRandomValues(buf);
|
|
126
|
-
const reduced = bytesToBigintLE(buf) % BN254_R;
|
|
127
|
-
return reduced === 0n ? 1n : reduced;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
132
|
// src/foundation/text/format.ts
|
|
131
133
|
var LOCALE_DECIMAL_SEP = new Intl.NumberFormat(void 0).formatToParts(1.1).find((p) => p.type === "decimal")?.value ?? ".";
|
|
132
134
|
function formatIntegerLocale(intPart) {
|
|
@@ -234,9 +236,6 @@ function parseEvmAddress(addr) {
|
|
|
234
236
|
const withPrefix = addr.startsWith("0x") ? addr : `0x${addr}`;
|
|
235
237
|
return isEvmAddress(withPrefix) ? withPrefix.toLowerCase() : null;
|
|
236
238
|
}
|
|
237
|
-
function isSs58(addr) {
|
|
238
|
-
return !addr.startsWith("0x") && addr.length >= 46 && addr.length <= 50;
|
|
239
|
-
}
|
|
240
239
|
function isEvmAddress(addr) {
|
|
241
240
|
return /^0x[0-9a-fA-F]{40}$/.test(addr);
|
|
242
241
|
}
|
|
@@ -366,11 +365,10 @@ function addressToAccountIdHex(addr) {
|
|
|
366
365
|
function addressToFieldElement(address) {
|
|
367
366
|
const accountIdHex = addressToAccountIdHex(address);
|
|
368
367
|
if (!accountIdHex) throw new Error(`Cannot resolve address to AccountId32: ${address}`);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
for (let i = 0; i < 32; i++) {
|
|
372
|
-
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
368
|
+
if (!isHexOfLength(accountIdHex, 32)) {
|
|
369
|
+
throw new Error(`AccountId32 must be 32 bytes of hex: ${accountIdHex}`);
|
|
373
370
|
}
|
|
371
|
+
const bytes = fromHex(accountIdHex);
|
|
374
372
|
return bytesToBigintLE(bytes) % BN254_R;
|
|
375
373
|
}
|
|
376
374
|
|
|
@@ -379,7 +377,7 @@ import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
|
379
377
|
import { randomBytes } from "@noble/ciphers/utils.js";
|
|
380
378
|
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
381
379
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
382
|
-
import { packPoint
|
|
380
|
+
import { packPoint } from "@zk-kit/baby-jubjub";
|
|
383
381
|
var SLIP_DOMAIN = new TextEncoder().encode("orbinum-payment-slip-v1");
|
|
384
382
|
var NONCE_PREFIX = new TextEncoder().encode("SLP1");
|
|
385
383
|
var EPH_PK_SIZE = 32;
|
|
@@ -400,7 +398,7 @@ function sealPaymentSlip(recipientIvkPacked, fields) {
|
|
|
400
398
|
`PaymentSlip: recipientIvk must be 32 bytes, got ${recipientIvkPacked.length}`
|
|
401
399
|
);
|
|
402
400
|
}
|
|
403
|
-
const ivkPoint =
|
|
401
|
+
const ivkPoint = unpackUsableViewingKey(bytesToBigintLE(recipientIvkPacked));
|
|
404
402
|
if (!ivkPoint) throw new Error("PaymentSlip: invalid recipient viewing public key");
|
|
405
403
|
const ephSkScalar = bytesToBjjScalar(randomBytes(32));
|
|
406
404
|
const ephPkPacked = bigintTo32Le(packPoint(fastMulBase(ephSkScalar)));
|
|
@@ -422,7 +420,7 @@ function openPaymentSlip(recipientIvsk, envelope) {
|
|
|
422
420
|
if (envelope.length > MAX_SLIP_ENVELOPE_SIZE) return null;
|
|
423
421
|
try {
|
|
424
422
|
const ephPkPacked = envelope.subarray(0, EPH_PK_SIZE);
|
|
425
|
-
const ephPkPoint =
|
|
423
|
+
const ephPkPoint = unpackUsableViewingKey(bytesToBigintLE(ephPkPacked));
|
|
426
424
|
if (!ephPkPoint) return null;
|
|
427
425
|
const ivskScalar = bytesToBjjScalar(recipientIvsk);
|
|
428
426
|
const sharedSecret = bigintTo32Le(fastMulPoint(ephPkPoint, ivskScalar)[0]);
|
|
@@ -466,14 +464,7 @@ function decodePaymentSlip(text) {
|
|
|
466
464
|
const checksum = rest.slice(sep + 1);
|
|
467
465
|
if (slipChecksum(payload) !== checksum) return null;
|
|
468
466
|
try {
|
|
469
|
-
|
|
470
|
-
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
471
|
-
if (clean.length % 2 !== 0) return null;
|
|
472
|
-
const bytes = new Uint8Array(clean.length / 2);
|
|
473
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
474
|
-
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
475
|
-
}
|
|
476
|
-
return bytes;
|
|
467
|
+
return fromHex(base64UrlDecode(payload));
|
|
477
468
|
} catch {
|
|
478
469
|
return null;
|
|
479
470
|
}
|
|
@@ -578,8 +569,17 @@ ${canonicalAccountId(address)}`;
|
|
|
578
569
|
|
|
579
570
|
// src/protocol/keys/PrivacyKeyManager.ts
|
|
580
571
|
import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
|
|
581
|
-
|
|
582
|
-
|
|
572
|
+
var SCHEME_BY_VERSION = { v2: "orbpriv2", v3: "orbpriv3" };
|
|
573
|
+
function usableAddressFields(ownerPkHex, ivkHex) {
|
|
574
|
+
const ownerPk = ownerPkHex.toLowerCase();
|
|
575
|
+
const ivk = ivkHex.toLowerCase();
|
|
576
|
+
if (!isHexOfLength(ownerPk, 32) || !isHexOfLength(ivk, 32)) return null;
|
|
577
|
+
if (/^0x0+$/.test(ownerPk) || /^0x0+$/.test(ivk)) return null;
|
|
578
|
+
if (!unpackUsableViewingKey(bytesToBigintLE(fromHex(ivk)))) return null;
|
|
579
|
+
return { ownerPkHex: ownerPk, viewingPublicKeyHex: ivk };
|
|
580
|
+
}
|
|
581
|
+
function privacyAddressChecksum(scheme, ownerPkHex, ivkHex) {
|
|
582
|
+
const body = `${scheme}:${ownerPkHex}:${ivkHex}`;
|
|
583
583
|
const digest = sha2562(new TextEncoder().encode(body));
|
|
584
584
|
return toHex(digest.slice(0, 4)).slice(2);
|
|
585
585
|
}
|
|
@@ -589,30 +589,29 @@ var PrivacyKeyManager = class {
|
|
|
589
589
|
masterBytes: null,
|
|
590
590
|
viewingSecretKey: null,
|
|
591
591
|
viewingPublicKeyPacked: null,
|
|
592
|
-
ownerPk: null
|
|
593
|
-
outgoingViewingKey: null
|
|
592
|
+
ownerPk: null
|
|
594
593
|
};
|
|
595
594
|
/**
|
|
596
595
|
* Load a spending key and its corresponding master bytes into the in-memory session.
|
|
597
596
|
* Derives viewingSecretKey, viewingPublicKeyPacked, and ownerPk immediately.
|
|
598
597
|
* Replaces any previously loaded key.
|
|
599
598
|
*
|
|
600
|
-
* @param
|
|
601
|
-
*
|
|
602
|
-
*
|
|
599
|
+
* @param _legacySpendingKey IGNORED. Kept so v2-era callers still compile;
|
|
600
|
+
* the scalar is re-derived from `masterBytes`.
|
|
601
|
+
* @param masterBytes Raw 32-byte HKDF output. The root every v3 branch and
|
|
602
|
+
* the stable vault key hang off.
|
|
603
603
|
*/
|
|
604
|
-
async load(
|
|
605
|
-
const
|
|
604
|
+
async load(_legacySpendingKey, masterBytes) {
|
|
605
|
+
const spendingKey = deriveSpendingKeyV3(masterBytes);
|
|
606
|
+
const viewingSecretKey = deriveViewingSecretKeyV3(masterBytes);
|
|
606
607
|
const viewingPublicKeyPacked = deriveViewingPublicKey(viewingSecretKey);
|
|
607
608
|
const ownerPk = deriveOwnerPk(spendingKey);
|
|
608
|
-
const outgoingViewingKey = deriveOutgoingViewingKey(masterBytes);
|
|
609
609
|
this._state = {
|
|
610
610
|
spendingKey,
|
|
611
611
|
masterBytes,
|
|
612
612
|
viewingSecretKey,
|
|
613
613
|
viewingPublicKeyPacked,
|
|
614
|
-
ownerPk
|
|
615
|
-
outgoingViewingKey
|
|
614
|
+
ownerPk
|
|
616
615
|
};
|
|
617
616
|
}
|
|
618
617
|
/** Clear all key material from memory. Call on vault lock / sign-out. */
|
|
@@ -622,8 +621,7 @@ var PrivacyKeyManager = class {
|
|
|
622
621
|
masterBytes: null,
|
|
623
622
|
viewingSecretKey: null,
|
|
624
623
|
viewingPublicKeyPacked: null,
|
|
625
|
-
ownerPk: null
|
|
626
|
-
outgoingViewingKey: null
|
|
624
|
+
ownerPk: null
|
|
627
625
|
};
|
|
628
626
|
}
|
|
629
627
|
/** Returns true if a spending key has been loaded. */
|
|
@@ -661,6 +659,24 @@ var PrivacyKeyManager = class {
|
|
|
661
659
|
}
|
|
662
660
|
return this._state.viewingPublicKeyPacked;
|
|
663
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* The OUTGOING viewing key (ovk) — reads what this wallet SENT.
|
|
664
|
+
*
|
|
665
|
+
* Seeds the outgoing ephemeral sequence and opens the recipient book, so a
|
|
666
|
+
* holder can enumerate every payment the wallet made and name who received
|
|
667
|
+
* it. That makes it strictly more sensitive than the incoming viewing key,
|
|
668
|
+
* which yields amounts and no counterparties: hand it out only where the
|
|
669
|
+
* payment graph is meant to travel with it.
|
|
670
|
+
*
|
|
671
|
+
* A scan without it still finds every note the wallet OWNS and simply
|
|
672
|
+
* reconstructs no sent history — the watch-only case.
|
|
673
|
+
*
|
|
674
|
+
* SECURITY: never expose this in an address or a network request.
|
|
675
|
+
* Throws if not loaded.
|
|
676
|
+
*/
|
|
677
|
+
getOutgoingViewingKey() {
|
|
678
|
+
return deriveOutgoingViewingKeyV3(this.getMasterBytes());
|
|
679
|
+
}
|
|
664
680
|
/** Returns the BabyJubJub owner public key (x-coordinate). Throws if not loaded. */
|
|
665
681
|
getOwnerPk() {
|
|
666
682
|
if (this._state.ownerPk === null) {
|
|
@@ -668,16 +684,6 @@ var PrivacyKeyManager = class {
|
|
|
668
684
|
}
|
|
669
685
|
return this._state.ownerPk;
|
|
670
686
|
}
|
|
671
|
-
/**
|
|
672
|
-
* Returns the 32-byte outgoing viewing key (ovk). Throws if not loaded.
|
|
673
|
-
* Used to seal/open the outgoing blob that lets the sender recover a transfer.
|
|
674
|
-
*/
|
|
675
|
-
getOutgoingViewingKey() {
|
|
676
|
-
if (this._state.outgoingViewingKey === null) {
|
|
677
|
-
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
678
|
-
}
|
|
679
|
-
return this._state.outgoingViewingKey;
|
|
680
|
-
}
|
|
681
687
|
/** Returns the spending key as a 32-byte little-endian Uint8Array. Throws if not loaded. */
|
|
682
688
|
getSpendingKeyBytes() {
|
|
683
689
|
return bigintTo32Le(this.getSpendingKey());
|
|
@@ -706,7 +712,7 @@ var PrivacyKeyManager = class {
|
|
|
706
712
|
* Exports a shareable privacy address encoding the owner public key and
|
|
707
713
|
* viewing PUBLIC key of the currently loaded identity.
|
|
708
714
|
*
|
|
709
|
-
* Format: `
|
|
715
|
+
* Format: `orbpriv3:{ownerPk_hex}:{viewingPublicKey_hex}:{checksum}`
|
|
710
716
|
*
|
|
711
717
|
* The recipient uses this address so the sender can:
|
|
712
718
|
* 1. Embed `ownerPk` in the note commitment (Poseidon4 input).
|
|
@@ -721,44 +727,64 @@ var PrivacyKeyManager = class {
|
|
|
721
727
|
*
|
|
722
728
|
* Throws if no key is loaded.
|
|
723
729
|
*/
|
|
724
|
-
encodePrivacyAddress() {
|
|
730
|
+
encodePrivacyAddress(version = "v3") {
|
|
725
731
|
const ownerPk = this.getOwnerPk();
|
|
726
732
|
const ivkPacked = this.getViewingPublicKeyPacked();
|
|
727
733
|
const ownerPkHex = scalarToHex(ownerPk);
|
|
728
734
|
const ivkHex = toHex(ivkPacked);
|
|
729
|
-
const
|
|
730
|
-
|
|
735
|
+
const scheme = SCHEME_BY_VERSION[version];
|
|
736
|
+
const checksum = privacyAddressChecksum(scheme, ownerPkHex, ivkHex);
|
|
737
|
+
return `${scheme}:${ownerPkHex}:${ivkHex}:${checksum}`;
|
|
731
738
|
}
|
|
732
739
|
/**
|
|
733
740
|
* Decode a privacy address into `{ ownerPkHex, viewingPublicKeyHex }`, or
|
|
734
741
|
* `null` if it does not parse.
|
|
735
742
|
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
743
|
+
* Reads every scheme ever emitted; only `orbpriv3` is written.
|
|
744
|
+
*
|
|
745
|
+
* - `orbpriv3` / `orbpriv2` — checksum verified; a mismatch (corrupted
|
|
746
|
+
* paste, or a relabelled prefix) returns null.
|
|
747
|
+
* - `orbpriv1` — legacy, predates the checksum entirely. The FIELDS are
|
|
748
|
+
* validated exactly as above, since this branch has no integrity check
|
|
749
|
+
* of its own.
|
|
750
|
+
*
|
|
751
|
+
* Reading an old address is not the same as supporting an old identity. An
|
|
752
|
+
* address is an owner key and a viewing key; paying one works whatever
|
|
753
|
+
* scheme derived it, and whoever shares it may not have upgraded yet.
|
|
754
|
+
* Refusing them here would break incoming payments for no gain.
|
|
741
755
|
*/
|
|
742
756
|
static decodePrivacyAddress(address) {
|
|
743
757
|
const parts = address.split(":");
|
|
744
|
-
|
|
758
|
+
for (const version of ["v2", "v3"]) {
|
|
759
|
+
if (parts[0] !== SCHEME_BY_VERSION[version]) continue;
|
|
745
760
|
if (parts.length !== 4) return null;
|
|
746
761
|
const [, ownerPkHex, viewingPublicKeyHex, checksum] = parts;
|
|
747
762
|
if (!ownerPkHex || !viewingPublicKeyHex || !checksum) return null;
|
|
748
|
-
if (privacyAddressChecksum(
|
|
749
|
-
|
|
763
|
+
if (privacyAddressChecksum(
|
|
764
|
+
SCHEME_BY_VERSION[version],
|
|
765
|
+
ownerPkHex,
|
|
766
|
+
viewingPublicKeyHex
|
|
767
|
+
) !== checksum) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
const fields = usableAddressFields(ownerPkHex, viewingPublicKeyHex);
|
|
771
|
+
return fields ? { ...fields, scheme: SCHEME_BY_VERSION[version] } : null;
|
|
750
772
|
}
|
|
751
773
|
if (parts[0] === "orbpriv1") {
|
|
752
774
|
if (parts.length !== 3) return null;
|
|
753
775
|
const [, ownerPkHex, viewingPublicKeyHex] = parts;
|
|
754
776
|
if (!ownerPkHex || !viewingPublicKeyHex) return null;
|
|
755
|
-
|
|
777
|
+
const fields = usableAddressFields(ownerPkHex, viewingPublicKeyHex);
|
|
778
|
+
return fields ? { ...fields, scheme: "orbpriv1" } : null;
|
|
756
779
|
}
|
|
757
780
|
return null;
|
|
758
781
|
}
|
|
759
782
|
/**
|
|
760
783
|
* Load keys from a cached "mk:0x{masterBytes_hex}" string produced by exportHex().
|
|
761
|
-
*
|
|
784
|
+
*
|
|
785
|
+
* Throws on a bad prefix, on non-hex content, and on a master that is not
|
|
786
|
+
* 32 usable bytes — an all-zero one included, since every wallet would
|
|
787
|
+
* derive the same identity from it.
|
|
762
788
|
*/
|
|
763
789
|
async importFromHex(hex) {
|
|
764
790
|
if (!hex.startsWith("mk:")) {
|
|
@@ -766,15 +792,9 @@ var PrivacyKeyManager = class {
|
|
|
766
792
|
'PrivacyKeyManager: invalid cache format. Expected "mk:0x{masterBytes_hex}".'
|
|
767
793
|
);
|
|
768
794
|
}
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (masterBytes.length !== 32) {
|
|
773
|
-
throw new Error("PrivacyKeyManager: invalid master bytes \u2014 expected 32 bytes.");
|
|
774
|
-
}
|
|
775
|
-
const masterBigint = BigInt("0x" + h);
|
|
776
|
-
const sk = masterBigint % BABYJUB_SUBORDER || 1n;
|
|
777
|
-
await this.load(sk, masterBytes);
|
|
795
|
+
const masterBytes = fromHex(hex.slice(3));
|
|
796
|
+
assertSecretKeyBytes(masterBytes, "PrivacyKeyManager: master");
|
|
797
|
+
await this.load(0n, masterBytes);
|
|
778
798
|
}
|
|
779
799
|
};
|
|
780
800
|
|
|
@@ -1095,6 +1115,25 @@ function wsUrlToHttp(wsUrl) {
|
|
|
1095
1115
|
}
|
|
1096
1116
|
|
|
1097
1117
|
// src/chain/substrate/SubstrateClient.ts
|
|
1118
|
+
var TIMESTAMP_PALLET = 1;
|
|
1119
|
+
function decodeCompact(bytes, offset) {
|
|
1120
|
+
const first = bytes[offset];
|
|
1121
|
+
if (first === void 0) return null;
|
|
1122
|
+
const mode = first & 3;
|
|
1123
|
+
if (mode === 0) return first >>> 2;
|
|
1124
|
+
if (mode === 1) {
|
|
1125
|
+
const b1 = bytes[offset + 1];
|
|
1126
|
+
return b1 === void 0 ? null : (first >>> 2 | b1 << 6) >>> 0;
|
|
1127
|
+
}
|
|
1128
|
+
const width = mode === 2 ? 4 : (first >>> 2) + 5;
|
|
1129
|
+
if (offset + width > bytes.length) return null;
|
|
1130
|
+
let value = 0n;
|
|
1131
|
+
const start = mode === 2 ? offset : offset + 1;
|
|
1132
|
+
const end = mode === 2 ? offset + 4 : offset + width;
|
|
1133
|
+
for (let i = end - 1; i >= start; i--) value = value << 8n | BigInt(bytes[i]);
|
|
1134
|
+
if (mode === 2) value >>= 2n;
|
|
1135
|
+
return value > BigInt(Number.MAX_SAFE_INTEGER) ? null : Number(value);
|
|
1136
|
+
}
|
|
1098
1137
|
var SubstrateClient = class _SubstrateClient {
|
|
1099
1138
|
constructor(_papi, _httpUrl, _owned) {
|
|
1100
1139
|
this._papi = _papi;
|
|
@@ -1292,25 +1331,17 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
1292
1331
|
}
|
|
1293
1332
|
}
|
|
1294
1333
|
if (!timestampMs) {
|
|
1295
|
-
const
|
|
1334
|
+
const ts = extrinsics.reduce((found, hex) => {
|
|
1335
|
+
if (found !== null) return found;
|
|
1296
1336
|
try {
|
|
1297
1337
|
const b = fromHex(hex);
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
return false;
|
|
1301
|
-
}
|
|
1302
|
-
});
|
|
1303
|
-
if (tsHex) {
|
|
1304
|
-
try {
|
|
1305
|
-
const b = fromHex(tsHex);
|
|
1306
|
-
const view = new DataView(b.buffer, b.byteOffset + 6, 8);
|
|
1307
|
-
const lo = view.getUint32(0, true);
|
|
1308
|
-
const hi = view.getUint32(4, true);
|
|
1309
|
-
const ms = lo + hi * 4294967296;
|
|
1310
|
-
if (ms > 0) timestampMs = ms;
|
|
1338
|
+
if (b.length < 5 || b[2] !== TIMESTAMP_PALLET || b[3] !== 0) return null;
|
|
1339
|
+
return decodeCompact(b, 4);
|
|
1311
1340
|
} catch {
|
|
1341
|
+
return null;
|
|
1312
1342
|
}
|
|
1313
|
-
}
|
|
1343
|
+
}, null);
|
|
1344
|
+
if (ts !== null && ts > 0) timestampMs = ts;
|
|
1314
1345
|
}
|
|
1315
1346
|
const author = _SubstrateClient.extractAuthorFromLogs(header.digest.logs, ss58Prefix);
|
|
1316
1347
|
return { header, extrinsics, timestampMs, author };
|
|
@@ -1580,7 +1611,10 @@ var EvmClient = class {
|
|
|
1580
1611
|
}
|
|
1581
1612
|
/**
|
|
1582
1613
|
* Performs multiple JSON-RPC calls in a single HTTP request (batch).
|
|
1583
|
-
*
|
|
1614
|
+
*
|
|
1615
|
+
* Results come back in the order of `calls`, as a typed tuple. A call the
|
|
1616
|
+
* server answered with an error, or did not answer at all, lands as `null`
|
|
1617
|
+
* in its own slot — never shifting the ones after it.
|
|
1584
1618
|
*/
|
|
1585
1619
|
async batchRequest(calls) {
|
|
1586
1620
|
const body = calls.map((c, i) => ({
|
|
@@ -1592,8 +1626,8 @@ var EvmClient = class {
|
|
|
1592
1626
|
const res = await postJsonWithRetry(this.rpcUrl, JSON.stringify(body));
|
|
1593
1627
|
if (!res.ok) throw new Error(`EVM HTTP ${res.status}: ${res.statusText}`);
|
|
1594
1628
|
const arr = await res.json();
|
|
1595
|
-
arr.
|
|
1596
|
-
return
|
|
1629
|
+
const byId = new Map(arr.map((r) => [r.id, r]));
|
|
1630
|
+
return calls.map((_, i) => byId.get(i + 1)?.result ?? null);
|
|
1597
1631
|
}
|
|
1598
1632
|
// ─── Convenience wrappers ─────────────────────────────────────────────────
|
|
1599
1633
|
/** Returns the native token balance (in wei) for an EVM address. */
|
|
@@ -1639,7 +1673,10 @@ var EvmClient = class {
|
|
|
1639
1673
|
if (from) txObj["from"] = from;
|
|
1640
1674
|
return this.request("eth_call", [txObj, "latest"]);
|
|
1641
1675
|
}
|
|
1642
|
-
/**
|
|
1676
|
+
/**
|
|
1677
|
+
* Estimates a transaction's gas, in GAS UNITS — not wei. Multiply by
|
|
1678
|
+
* `getGasPrice()` for a cost.
|
|
1679
|
+
*/
|
|
1643
1680
|
async estimateGas(params) {
|
|
1644
1681
|
const hex = await this.request("eth_estimateGas", [params]);
|
|
1645
1682
|
return hexToBigint(hex);
|
|
@@ -1890,7 +1927,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
1890
1927
|
// --- Address ---
|
|
1891
1928
|
/**
|
|
1892
1929
|
* Returns aggregated on-chain data for an EVM address: balance, nonce,
|
|
1893
|
-
* bytecode (truncated), and up to 50
|
|
1930
|
+
* bytecode (truncated), and up to 50 logs it EMITTED in the last 1 001 blocks.
|
|
1894
1931
|
*/
|
|
1895
1932
|
async getAddressInfo(address) {
|
|
1896
1933
|
const latest = await this.evm.getBlockNumber().catch(() => 0);
|
|
@@ -1973,7 +2010,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
1973
2010
|
};
|
|
1974
2011
|
}
|
|
1975
2012
|
/**
|
|
1976
|
-
* Returns ERC-20 `Transfer` events for `address` from the last 1
|
|
2013
|
+
* Returns ERC-20 `Transfer` events for `address` from the last 1 001 blocks.
|
|
1977
2014
|
* When `holderAddress` is provided, restricts results to transfers sent or received by that address.
|
|
1978
2015
|
*/
|
|
1979
2016
|
async getTokenTransfers(address, holderAddress) {
|
|
@@ -2346,7 +2383,13 @@ var PrivacyModule = class {
|
|
|
2346
2383
|
async getMerkleRoot() {
|
|
2347
2384
|
return this.substrate.request("privacy_getMerkleRoot", []);
|
|
2348
2385
|
}
|
|
2349
|
-
/**
|
|
2386
|
+
/**
|
|
2387
|
+
* Returns the Merkle proof for a LEAF INDEX.
|
|
2388
|
+
*
|
|
2389
|
+
* The node declares this parameter `u32`, so a commitment hex is rejected
|
|
2390
|
+
* by deserialisation before any lookup runs — `getMerkleProofByCommitment`
|
|
2391
|
+
* is the one that takes a string.
|
|
2392
|
+
*/
|
|
2350
2393
|
async getMerkleProof(leafIndex) {
|
|
2351
2394
|
const raw = await this.substrate.request("privacy_getMerkleProof", [
|
|
2352
2395
|
leafIndex
|
|
@@ -3296,9 +3339,13 @@ var OrbinumClientProvider = class _OrbinumClientProvider {
|
|
|
3296
3339
|
// ─── Reconnection ───────────────────────────────────────────────────────
|
|
3297
3340
|
/**
|
|
3298
3341
|
* Schedules the next connection attempt using exponential backoff
|
|
3299
|
-
* (capped at `reconnectMaxMs`) with
|
|
3300
|
-
* `'reconnecting'`.
|
|
3301
|
-
*
|
|
3342
|
+
* (capped at `reconnectMaxMs`) with EQUAL jitter, then transitions to
|
|
3343
|
+
* `'reconnecting'`.
|
|
3344
|
+
*
|
|
3345
|
+
* The delay lands in `[capped/2, capped]` — half fixed, half random. That
|
|
3346
|
+
* spreads a shared outage's reconnects over a `capped/2` window instead of
|
|
3347
|
+
* having every client retry in lockstep, while keeping a floor so the first
|
|
3348
|
+
* attempts do not all pile up near zero the way full jitter allows.
|
|
3302
3349
|
*/
|
|
3303
3350
|
scheduleReconnect() {
|
|
3304
3351
|
if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
|
|
@@ -3690,6 +3737,16 @@ function mapZkEventData(method, data) {
|
|
|
3690
3737
|
index: get(4, "leaf_index") || get(4, "index")
|
|
3691
3738
|
};
|
|
3692
3739
|
}
|
|
3740
|
+
if (m === "nullifiersspent") {
|
|
3741
|
+
return { nullifiers: get(0, "nullifiers") };
|
|
3742
|
+
}
|
|
3743
|
+
if (m === "commitmentsinserted") {
|
|
3744
|
+
return {
|
|
3745
|
+
commitments: get(0, "commitments"),
|
|
3746
|
+
memos: get(1, "encrypted_memos") || get(1, "memos"),
|
|
3747
|
+
indices: get(2, "leaf_indices") || get(2, "indices")
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3693
3750
|
if (m === "privatetransfer") {
|
|
3694
3751
|
return {
|
|
3695
3752
|
nullifiers: get(0, "nullifiers"),
|
|
@@ -3731,12 +3788,12 @@ function mapZkEventData(method, data) {
|
|
|
3731
3788
|
if (m_norm === "log") {
|
|
3732
3789
|
return { log: get(0, "log") };
|
|
3733
3790
|
}
|
|
3734
|
-
if (m_norm === "created" || m_norm === "createdfailed" || m_norm === "
|
|
3791
|
+
if (m_norm === "created" || m_norm === "createdfailed" || m_norm === "executedfailed") {
|
|
3735
3792
|
return { address: get(0, "address") };
|
|
3736
3793
|
}
|
|
3737
3794
|
if (m_norm === "executed") {
|
|
3738
3795
|
return {
|
|
3739
|
-
from: get(0, "from"),
|
|
3796
|
+
from: get(0, "from") ?? get(0, "address"),
|
|
3740
3797
|
to: get(1, "to"),
|
|
3741
3798
|
transaction_hash: get(2, "transaction_hash"),
|
|
3742
3799
|
exit_reason: get(3, "exit_reason")
|
|
@@ -3883,10 +3940,10 @@ var ERROR_KINDS = {
|
|
|
3883
3940
|
CommitmentAlreadyExists: "shape",
|
|
3884
3941
|
UnknownMerkleRoot: "stale-proof",
|
|
3885
3942
|
// Amounts and fees
|
|
3886
|
-
AmountTooSmall: "amount",
|
|
3887
3943
|
InvalidAmount: "amount",
|
|
3888
3944
|
InsufficientPoolBalance: "amount",
|
|
3889
3945
|
FeeTooLow: "amount",
|
|
3946
|
+
InsufficientPendingFees: "amount",
|
|
3890
3947
|
// Proofs
|
|
3891
3948
|
InvalidProof: "proof",
|
|
3892
3949
|
ProofVerificationFailed: "proof",
|
|
@@ -3895,12 +3952,14 @@ var ERROR_KINDS = {
|
|
|
3895
3952
|
InvalidAssetId: "asset",
|
|
3896
3953
|
AssetNotVerified: "asset",
|
|
3897
3954
|
AssetIdMismatch: "asset",
|
|
3955
|
+
AssetIdAlreadyExists: "asset",
|
|
3898
3956
|
// Request shape
|
|
3899
3957
|
InvalidMemoSize: "shape",
|
|
3900
3958
|
MemoCommitmentMismatch: "shape",
|
|
3901
3959
|
TooManyInputsOrOutputs: "shape",
|
|
3902
3960
|
InvalidRecipient: "shape",
|
|
3903
3961
|
EmptyBatch: "shape",
|
|
3962
|
+
FeeRecipientUnavailable: "shape",
|
|
3904
3963
|
// Capacity
|
|
3905
3964
|
MerkleTreeFull: "capacity",
|
|
3906
3965
|
// Balances pallet, reached through the same path
|
|
@@ -4259,6 +4318,7 @@ function buildConfig(existing, chainFingerprint) {
|
|
|
4259
4318
|
updatedAt: now,
|
|
4260
4319
|
...chainFingerprint ? { chainFingerprint } : {},
|
|
4261
4320
|
...existing?.selfEphCounter !== void 0 ? { selfEphCounter: existing.selfEphCounter } : {},
|
|
4321
|
+
...existing?.outgoingEphCounter !== void 0 ? { outgoingEphCounter: existing.outgoingEphCounter } : {},
|
|
4262
4322
|
...existing?.pairwiseCounterparties !== void 0 ? { pairwiseCounterparties: existing.pairwiseCounterparties } : {}
|
|
4263
4323
|
};
|
|
4264
4324
|
}
|
|
@@ -4270,6 +4330,7 @@ async function writeConfig(storage, fallback, chainFingerprint) {
|
|
|
4270
4330
|
}
|
|
4271
4331
|
function mergeCounters(current, fallback) {
|
|
4272
4332
|
const selfEphCounter = higher(current.selfEphCounter, fallback?.selfEphCounter);
|
|
4333
|
+
const outgoingEphCounter = higher(current.outgoingEphCounter, fallback?.outgoingEphCounter);
|
|
4273
4334
|
const parties = mergePairwise(current.pairwiseCounterparties, fallback?.pairwiseCounterparties);
|
|
4274
4335
|
return {
|
|
4275
4336
|
createdAt: current.createdAt ?? fallback?.createdAt,
|
|
@@ -4277,6 +4338,7 @@ function mergeCounters(current, fallback) {
|
|
|
4277
4338
|
// is exactOptionalPropertyTypes, so an explicit undefined is a type
|
|
4278
4339
|
// error AND would round-trip through structured clone as a present key.
|
|
4279
4340
|
...selfEphCounter !== void 0 ? { selfEphCounter } : {},
|
|
4341
|
+
...outgoingEphCounter !== void 0 ? { outgoingEphCounter } : {},
|
|
4280
4342
|
...parties !== void 0 ? { pairwiseCounterparties: parties } : {}
|
|
4281
4343
|
};
|
|
4282
4344
|
}
|
|
@@ -4314,6 +4376,7 @@ function mergePairwise(current, fallback) {
|
|
|
4314
4376
|
|
|
4315
4377
|
// src/wallet/vault/storage/ephemeralIndex.ts
|
|
4316
4378
|
var MAX_EPH_INDEX = 4294967294;
|
|
4379
|
+
var RESTORE_SAFETY_GAP = 8;
|
|
4317
4380
|
function usableCounter(value) {
|
|
4318
4381
|
if (typeof value !== "number" || !Number.isInteger(value)) return null;
|
|
4319
4382
|
if (value < 0 || value > MAX_EPH_INDEX) return null;
|
|
@@ -4328,27 +4391,51 @@ async function reserveSelfEphIndex(storage) {
|
|
|
4328
4391
|
if (!updated) throw new Error("vault not initialized");
|
|
4329
4392
|
return (usableCounter(updated.selfEphCounter) ?? 1) - 1;
|
|
4330
4393
|
}
|
|
4331
|
-
async function
|
|
4394
|
+
async function reserveOutgoingIndex(storage, fromChain) {
|
|
4395
|
+
const recovered = usableCounter(fromChain);
|
|
4396
|
+
let exhausted = false;
|
|
4397
|
+
const updated = await storage.updateConfig((config) => {
|
|
4398
|
+
const raw = config.outgoingEphCounter;
|
|
4399
|
+
if (typeof raw === "number" && Number.isSafeInteger(raw) && raw > MAX_EPH_INDEX) {
|
|
4400
|
+
exhausted = true;
|
|
4401
|
+
return config;
|
|
4402
|
+
}
|
|
4403
|
+
exhausted = false;
|
|
4404
|
+
const stored = usableCounter(raw) ?? 0;
|
|
4405
|
+
const floor = recovered !== null && recovered > stored ? Math.min(recovered + RESTORE_SAFETY_GAP, MAX_EPH_INDEX) : stored;
|
|
4406
|
+
return {
|
|
4407
|
+
...config,
|
|
4408
|
+
outgoingEphCounter: floor + 1,
|
|
4409
|
+
updatedAt: Date.now()
|
|
4410
|
+
};
|
|
4411
|
+
});
|
|
4412
|
+
if (!updated) throw new Error("vault not initialized");
|
|
4413
|
+
if (exhausted || typeof updated.outgoingEphCounter === "number" && Number.isSafeInteger(updated.outgoingEphCounter) && updated.outgoingEphCounter > MAX_EPH_INDEX) {
|
|
4414
|
+
throw new Error("outgoing ephemeral sequence exhausted");
|
|
4415
|
+
}
|
|
4416
|
+
const committed = usableCounter(updated.outgoingEphCounter);
|
|
4417
|
+
if (committed === null || committed - 1 >= MAX_EPH_INDEX) {
|
|
4418
|
+
throw new Error("outgoing ephemeral sequence exhausted");
|
|
4419
|
+
}
|
|
4420
|
+
return committed - 1;
|
|
4421
|
+
}
|
|
4422
|
+
async function registerPairwiseCounterparty(storage, ivkHex) {
|
|
4332
4423
|
const key = ivkHex.toLowerCase();
|
|
4333
4424
|
const updated = await storage.updateConfig((config) => {
|
|
4334
|
-
const
|
|
4335
|
-
const addedAt = config.pairwiseCounterparties?.[key]?.addedAt;
|
|
4425
|
+
const existing = config.pairwiseCounterparties?.[key];
|
|
4336
4426
|
return {
|
|
4337
4427
|
...config,
|
|
4338
4428
|
pairwiseCounterparties: {
|
|
4339
4429
|
...config.pairwiseCounterparties,
|
|
4340
4430
|
[key]: {
|
|
4341
|
-
nextIndex: (
|
|
4342
|
-
addedAt: typeof addedAt === "number" ? addedAt : Date.now()
|
|
4431
|
+
nextIndex: usableCounter(existing?.nextIndex) ?? 0,
|
|
4432
|
+
addedAt: typeof existing?.addedAt === "number" ? existing.addedAt : Date.now()
|
|
4343
4433
|
}
|
|
4344
4434
|
},
|
|
4345
4435
|
updatedAt: Date.now()
|
|
4346
4436
|
};
|
|
4347
4437
|
});
|
|
4348
4438
|
if (!updated) throw new Error("vault not initialized");
|
|
4349
|
-
const nextIndex = usableCounter(updated.pairwiseCounterparties?.[key]?.nextIndex);
|
|
4350
|
-
if (nextIndex === null || nextIndex <= 1) return null;
|
|
4351
|
-
return nextIndex - 1;
|
|
4352
4439
|
}
|
|
4353
4440
|
|
|
4354
4441
|
// src/wallet/vault/notes/record.ts
|
|
@@ -4783,19 +4870,44 @@ async function processHints(ctx, hints, total) {
|
|
|
4783
4870
|
}
|
|
4784
4871
|
}
|
|
4785
4872
|
outcome.scanned++;
|
|
4786
|
-
if (!hint.encryptedMemo
|
|
4873
|
+
if (!hint.encryptedMemo) {
|
|
4787
4874
|
outcome.noMemo++;
|
|
4788
4875
|
continue;
|
|
4789
4876
|
}
|
|
4790
4877
|
toDecrypt.push(hint);
|
|
4791
4878
|
}
|
|
4792
|
-
const {
|
|
4879
|
+
const {
|
|
4880
|
+
notes,
|
|
4881
|
+
tagFiltered,
|
|
4882
|
+
selfMatched,
|
|
4883
|
+
pairwiseMatched,
|
|
4884
|
+
maxSelfEphIndex,
|
|
4885
|
+
maxOutgoingEphIndex,
|
|
4886
|
+
sentNotes,
|
|
4887
|
+
learnedRecipients,
|
|
4888
|
+
unmatchedSent,
|
|
4889
|
+
sealedBookEntries
|
|
4890
|
+
} = await ctx.pool.decryptBatch(
|
|
4891
|
+
toDecrypt,
|
|
4892
|
+
{ ...ctx.keys, recipientCandidates: [...outcome.learnedRecipients].map(fromHex) },
|
|
4893
|
+
ctx.signal
|
|
4894
|
+
);
|
|
4793
4895
|
outcome.tagFiltered += tagFiltered;
|
|
4794
4896
|
outcome.selfDiscovered += selfMatched;
|
|
4795
4897
|
outcome.pairwiseDiscovered += pairwiseMatched;
|
|
4898
|
+
outcome.sentNotes.push(...sentNotes ?? []);
|
|
4899
|
+
outcome.unmatchedSent.push(...unmatchedSent ?? []);
|
|
4900
|
+
for (const e of sealedBookEntries ?? []) outcome.sealedBookEntries.add(e);
|
|
4901
|
+
for (const r of learnedRecipients ?? []) outcome.learnedRecipients.add(r);
|
|
4796
4902
|
if (maxSelfEphIndex !== null) {
|
|
4797
4903
|
outcome.maxSelfEphIndex = Math.max(outcome.maxSelfEphIndex ?? -1, maxSelfEphIndex);
|
|
4798
4904
|
}
|
|
4905
|
+
if (maxOutgoingEphIndex != null) {
|
|
4906
|
+
outcome.maxOutgoingEphIndex = Math.max(
|
|
4907
|
+
outcome.maxOutgoingEphIndex ?? -1,
|
|
4908
|
+
maxOutgoingEphIndex
|
|
4909
|
+
);
|
|
4910
|
+
}
|
|
4799
4911
|
const pageEntries = [];
|
|
4800
4912
|
for (const [i, decrypted] of notes.entries()) {
|
|
4801
4913
|
const hint = toDecrypt[i];
|
|
@@ -4892,7 +5004,12 @@ async function collectScanEntries(params) {
|
|
|
4892
5004
|
tagFiltered: 0,
|
|
4893
5005
|
selfDiscovered: 0,
|
|
4894
5006
|
pairwiseDiscovered: 0,
|
|
4895
|
-
|
|
5007
|
+
sentNotes: [],
|
|
5008
|
+
unmatchedSent: [],
|
|
5009
|
+
sealedBookEntries: /* @__PURE__ */ new Set(),
|
|
5010
|
+
learnedRecipients: new Set((params.keys.recipientCandidates ?? []).map((c) => toHex(c))),
|
|
5011
|
+
maxSelfEphIndex: null,
|
|
5012
|
+
maxOutgoingEphIndex: null
|
|
4896
5013
|
};
|
|
4897
5014
|
const ctx = {
|
|
4898
5015
|
outcome,
|
|
@@ -5114,7 +5231,7 @@ function gapMargin(windowSize) {
|
|
|
5114
5231
|
return Math.max(1, Math.floor(windowSize / 16));
|
|
5115
5232
|
}
|
|
5116
5233
|
function resolveSelfEphCeiling(params) {
|
|
5117
|
-
const { notes,
|
|
5234
|
+
const { notes, viewingKey, scanMaxIndex } = params;
|
|
5118
5235
|
const windowSize = params.windowSize !== void 0 && params.windowSize > 0 ? params.windowSize : SELF_EPH_WINDOW;
|
|
5119
5236
|
if (scanMaxIndex === null) return null;
|
|
5120
5237
|
if (scanMaxIndex < windowSize - gapMargin(windowSize)) return scanMaxIndex;
|
|
@@ -5127,7 +5244,7 @@ function resolveSelfEphCeiling(params) {
|
|
|
5127
5244
|
let max = scanMaxIndex;
|
|
5128
5245
|
let from = windowSize;
|
|
5129
5246
|
for (; ; ) {
|
|
5130
|
-
const slice = selfEphWindow(
|
|
5247
|
+
const slice = selfEphWindow(viewingKey, ivkPacked, from, windowSize);
|
|
5131
5248
|
let matched = false;
|
|
5132
5249
|
for (const entry of slice) {
|
|
5133
5250
|
if (ownEphPks.has(entry.ephPkHex.toLowerCase())) {
|
|
@@ -5183,6 +5300,8 @@ async function resolveSpentStatus(params) {
|
|
|
5183
5300
|
}
|
|
5184
5301
|
|
|
5185
5302
|
// src/wallet/scanner/pipeline.ts
|
|
5303
|
+
var OUTGOING_RESCAN_GAP = 8;
|
|
5304
|
+
var RETRY_BOOK_LIMIT = 64;
|
|
5186
5305
|
async function runScan(params) {
|
|
5187
5306
|
const { vault, storage, hints, nullifiers, pool, keys, sinceLeafIndex, signal, onWarning } = params;
|
|
5188
5307
|
const isIncremental = sinceLeafIndex !== void 0;
|
|
@@ -5190,6 +5309,7 @@ async function runScan(params) {
|
|
|
5190
5309
|
const preScanHexes = new Set(existingHexes);
|
|
5191
5310
|
const config = await storage.getConfig().catch(() => null);
|
|
5192
5311
|
const selfEphWindowSize = windowSizeForCounter(config?.selfEphCounter ?? 0);
|
|
5312
|
+
const outgoingEphWindowSize = windowSizeForCounter(config?.outgoingEphCounter ?? 0);
|
|
5193
5313
|
const pairwiseCounterparties = Object.keys(config?.pairwiseCounterparties ?? {}).flatMap(
|
|
5194
5314
|
(hex) => {
|
|
5195
5315
|
try {
|
|
@@ -5218,7 +5338,12 @@ async function runScan(params) {
|
|
|
5218
5338
|
// worth it on full scans and restores, waste on small incremental ticks.
|
|
5219
5339
|
selfEph: !isIncremental,
|
|
5220
5340
|
selfEphWindowSize,
|
|
5221
|
-
pairwiseCounterparties
|
|
5341
|
+
pairwiseCounterparties,
|
|
5342
|
+
// Sent-note discovery rides the same full-scan/restore budget as
|
|
5343
|
+
// self discovery: on an incremental tick the history is already
|
|
5344
|
+
// there, and the window would be paid for nothing.
|
|
5345
|
+
outgoingEph: !isIncremental,
|
|
5346
|
+
outgoingEphWindowSize
|
|
5222
5347
|
},
|
|
5223
5348
|
existingHexes,
|
|
5224
5349
|
sinceLeafIndex,
|
|
@@ -5232,6 +5357,49 @@ async function runScan(params) {
|
|
|
5232
5357
|
onPage: (entries) => checkpoint.savePageNotes(entries),
|
|
5233
5358
|
onBatchDone: (maxLeafIndex) => checkpoint.advanceCursor(maxLeafIndex)
|
|
5234
5359
|
});
|
|
5360
|
+
const ovk = keys.outgoingViewingKey;
|
|
5361
|
+
if (scan.unmatchedSent.length > 0 && ovk) {
|
|
5362
|
+
const proven = [...scan.learnedRecipients].map(fromHex);
|
|
5363
|
+
const keptEntries = [...scan.sealedBookEntries].slice(-RETRY_BOOK_LIMIT);
|
|
5364
|
+
const sealed = [];
|
|
5365
|
+
for (const entry of keptEntries) {
|
|
5366
|
+
try {
|
|
5367
|
+
sealed.push(BigInt(entry));
|
|
5368
|
+
} catch {
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
const retries = scan.unmatchedSent.slice(-RETRY_BOOK_LIMIT);
|
|
5372
|
+
if (retries.length < scan.unmatchedSent.length) {
|
|
5373
|
+
onWarning?.(
|
|
5374
|
+
`sent-note retry capped at ${retries.length} of ${scan.unmatchedSent.length}; the rest resolve on the next full scan`
|
|
5375
|
+
);
|
|
5376
|
+
}
|
|
5377
|
+
if (keptEntries.length < scan.sealedBookEntries.size) {
|
|
5378
|
+
onWarning?.(
|
|
5379
|
+
`recipient book capped at ${keptEntries.length} of ${scan.sealedBookEntries.size} entries; older payments resolve on the next full scan`
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
for (const { hint, ephIndex } of retries) {
|
|
5383
|
+
if (signal?.aborted) throw scanAbortError();
|
|
5384
|
+
const candidates = [
|
|
5385
|
+
...sealed.map((e) => openRecipientBookEntry(e, ovk, hint.commitmentHex)),
|
|
5386
|
+
...proven
|
|
5387
|
+
];
|
|
5388
|
+
const sent = recoverSentNote({
|
|
5389
|
+
hint,
|
|
5390
|
+
outgoingViewingKey: ovk,
|
|
5391
|
+
ephIndex,
|
|
5392
|
+
recipientCandidates: candidates
|
|
5393
|
+
});
|
|
5394
|
+
if (!sent) continue;
|
|
5395
|
+
const { recipientIvk, ...facts } = sent;
|
|
5396
|
+
scan.sentNotes.push({
|
|
5397
|
+
...facts,
|
|
5398
|
+
counterpartyIvkHex: toHex(recipientIvk),
|
|
5399
|
+
encryptedMemo: hint.encryptedMemo ?? ""
|
|
5400
|
+
});
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5235
5403
|
const nullifiersToQuery = collectNullifiersToQuery(
|
|
5236
5404
|
scan.scanEntries,
|
|
5237
5405
|
scan.onChainHexes,
|
|
@@ -5261,7 +5429,6 @@ async function runScan(params) {
|
|
|
5261
5429
|
try {
|
|
5262
5430
|
ceiling = resolveSelfEphCeiling({
|
|
5263
5431
|
notes: vault.getAll(),
|
|
5264
|
-
spendingKey: keys.spendingKey,
|
|
5265
5432
|
viewingKey: keys.viewingKey,
|
|
5266
5433
|
scanMaxIndex: scan.maxSelfEphIndex,
|
|
5267
5434
|
windowSize: selfEphWindowSize
|
|
@@ -5271,6 +5438,13 @@ async function runScan(params) {
|
|
|
5271
5438
|
await bumpSelfEphCounterTo(storage, ceiling + 1).catch(() => {
|
|
5272
5439
|
});
|
|
5273
5440
|
}
|
|
5441
|
+
if (scan.maxOutgoingEphIndex !== null) {
|
|
5442
|
+
await bumpOutgoingCounterTo(
|
|
5443
|
+
storage,
|
|
5444
|
+
scan.maxOutgoingEphIndex + 1 + OUTGOING_RESCAN_GAP
|
|
5445
|
+
).catch(() => {
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5274
5448
|
if (scan.decryptFailed > 0 && scan.found === 0 && scan.alreadyPresent === 0) {
|
|
5275
5449
|
onWarning?.(
|
|
5276
5450
|
`no notes recovered from ${scan.decryptFailed} hint(s) with memos; if this wallet should hold notes, check the keys it was unlocked with`
|
|
@@ -5282,7 +5456,13 @@ async function runScan(params) {
|
|
|
5282
5456
|
decryptFailed: scan.decryptFailed,
|
|
5283
5457
|
alreadyPresent: scan.alreadyPresent,
|
|
5284
5458
|
purged,
|
|
5285
|
-
incremental: isIncremental
|
|
5459
|
+
incremental: isIncremental,
|
|
5460
|
+
sentNotes: scan.sentNotes,
|
|
5461
|
+
discovery: {
|
|
5462
|
+
self: scan.selfDiscovered,
|
|
5463
|
+
pairwise: scan.pairwiseDiscovered,
|
|
5464
|
+
tagFiltered: scan.tagFiltered
|
|
5465
|
+
}
|
|
5286
5466
|
};
|
|
5287
5467
|
}
|
|
5288
5468
|
async function bumpSelfEphCounterTo(storage, next) {
|
|
@@ -5290,6 +5470,11 @@ async function bumpSelfEphCounterTo(storage, next) {
|
|
|
5290
5470
|
(config) => (config.selfEphCounter ?? 0) >= next ? config : { ...config, selfEphCounter: next, updatedAt: Date.now() }
|
|
5291
5471
|
);
|
|
5292
5472
|
}
|
|
5473
|
+
async function bumpOutgoingCounterTo(storage, next) {
|
|
5474
|
+
await storage.updateConfig(
|
|
5475
|
+
(config) => (config.outgoingEphCounter ?? 0) >= next ? config : { ...config, outgoingEphCounter: next, updatedAt: Date.now() }
|
|
5476
|
+
);
|
|
5477
|
+
}
|
|
5293
5478
|
|
|
5294
5479
|
// src/wallet/scanner/history/extrinsicFacts.ts
|
|
5295
5480
|
var UNKNOWN = { fee: null, success: true };
|
|
@@ -5428,10 +5613,8 @@ function regeneratePaymentSlip(facts, recipientIvkPacked, txHash) {
|
|
|
5428
5613
|
commitmentHex: facts.commitmentHex,
|
|
5429
5614
|
encryptedMemo: facts.encryptedMemo,
|
|
5430
5615
|
...facts.leafIndex !== void 0 ? { leafIndex: facts.leafIndex } : {},
|
|
5431
|
-
//
|
|
5616
|
+
// The recipient renders this as an explorer link, so an unconstrained
|
|
5432
5617
|
// string is a URL injection wearing the authority of a decrypted slip.
|
|
5433
|
-
// Dropped rather than fatal: the slip still rebuilds the note, which is
|
|
5434
|
-
// the part that matters.
|
|
5435
5618
|
...isHexOfLength(txHash, 32) ? { txHash } : {}
|
|
5436
5619
|
});
|
|
5437
5620
|
return encodePaymentSlip(envelope);
|
|
@@ -5456,15 +5639,37 @@ async function loadExistingRecords(vault) {
|
|
|
5456
5639
|
function recordKey(transfer) {
|
|
5457
5640
|
return transfer.hash ?? `${transfer.blockNumber}-${transfer.extrinsicIndex ?? 0}`;
|
|
5458
5641
|
}
|
|
5642
|
+
function sentByCommitment(sentNotes) {
|
|
5643
|
+
return new Map(sentNotes.map((s) => [s.commitmentHex.toLowerCase(), s]));
|
|
5644
|
+
}
|
|
5645
|
+
function reissuedSlip(sent, hash) {
|
|
5646
|
+
try {
|
|
5647
|
+
const facts = {
|
|
5648
|
+
commitmentHex: sent.commitmentHex,
|
|
5649
|
+
encryptedMemo: sent.encryptedMemo,
|
|
5650
|
+
...sent.leafIndex !== void 0 ? { leafIndex: sent.leafIndex } : {}
|
|
5651
|
+
};
|
|
5652
|
+
return {
|
|
5653
|
+
paymentSlip: regeneratePaymentSlip(
|
|
5654
|
+
facts,
|
|
5655
|
+
fromHex(sent.counterpartyIvkHex),
|
|
5656
|
+
hash ?? void 0
|
|
5657
|
+
)
|
|
5658
|
+
};
|
|
5659
|
+
} catch {
|
|
5660
|
+
return {};
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5459
5663
|
async function reconstructOutgoingTxRecords(deps) {
|
|
5460
5664
|
const { vault, transfers } = deps;
|
|
5461
5665
|
const now = deps.now ?? Date.now;
|
|
5462
5666
|
const allNotes = vault.getAll();
|
|
5463
5667
|
const spentNotes = allNotes.filter((n) => n.spent);
|
|
5464
5668
|
if (spentNotes.length === 0) return;
|
|
5669
|
+
const sentCommitments = (deps.sentNotes ?? []).map((s) => s.commitmentHex);
|
|
5465
5670
|
const [outgoingTransfers, commitmentTransfers] = await Promise.all([
|
|
5466
5671
|
transfers.byNullifiers(spentNotes.map((n) => n.nullifierHex)),
|
|
5467
|
-
transfers.byCommitments(allNotes.map((n) => n.commitmentHex))
|
|
5672
|
+
transfers.byCommitments([...allNotes.map((n) => n.commitmentHex), ...sentCommitments])
|
|
5468
5673
|
]);
|
|
5469
5674
|
if (outgoingTransfers.length === 0) return;
|
|
5470
5675
|
const noteByNullifier = new Map(spentNotes.map((n) => [n.nullifierHex, n]));
|
|
@@ -5473,64 +5678,85 @@ async function reconstructOutgoingTxRecords(deps) {
|
|
|
5473
5678
|
commitmentTransfers.map((ct) => [extrinsicKey(ct), ct.matchedCommitments ?? []])
|
|
5474
5679
|
);
|
|
5475
5680
|
const existingByKey = await loadExistingRecords(vault);
|
|
5681
|
+
const sentIndex = sentByCommitment(deps.sentNotes ?? []);
|
|
5682
|
+
const recoveryEnabled = deps.sentNotes !== void 0;
|
|
5683
|
+
const sentFor = (row) => (commitmentsByExtrinsic.get(extrinsicKey(row)) ?? []).map((hex) => sentIndex.get(hex.toLowerCase())).find((s) => s !== void 0);
|
|
5476
5684
|
for (const transfer of outgoingTransfers) {
|
|
5477
5685
|
const existing = existingByKey.get(recordKey(transfer));
|
|
5478
|
-
|
|
5686
|
+
const known = existing?.recipientPkHex && existing.recipientPkHex !== ZERO_PK;
|
|
5687
|
+
if (known && existing?.paymentSlip) continue;
|
|
5479
5688
|
const inputNotes = (transfer.matchedNullifiers ?? []).map((h) => noteByNullifier.get(h)).filter((n) => n !== void 0);
|
|
5480
5689
|
if (inputNotes.length === 0) continue;
|
|
5481
5690
|
const changeNote = selectDescribingNoteByCommitment(
|
|
5482
5691
|
commitmentsByExtrinsic.get(extrinsicKey(transfer)) ?? [],
|
|
5483
5692
|
noteByCommitment
|
|
5484
5693
|
);
|
|
5485
|
-
const
|
|
5694
|
+
const sentForRow = sentFor(transfer);
|
|
5695
|
+
const recipientPkHex = sentForRow ? toPkHex(sentForRow.recipientStealthPk) : recoveryEnabled ? ZERO_PK : toPkHex(changeNote?.sourcePk);
|
|
5486
5696
|
if (existing) {
|
|
5487
|
-
|
|
5488
|
-
|
|
5697
|
+
const patch = {
|
|
5698
|
+
// Only when it ADDS something: this runs over the whole
|
|
5699
|
+
// outgoing history on every scan, and rewriting what is already
|
|
5700
|
+
// there would make each one a write.
|
|
5701
|
+
...!known && recipientPkHex !== ZERO_PK ? { recipientPkHex } : {},
|
|
5702
|
+
...sentForRow && !existing.paymentSlip ? reissuedSlip(sentForRow, transfer.hash) : {}
|
|
5703
|
+
};
|
|
5704
|
+
if (Object.keys(patch).length > 0) {
|
|
5705
|
+
await vault.saveTxRecord({ ...existing, ...patch });
|
|
5706
|
+
}
|
|
5489
5707
|
continue;
|
|
5490
5708
|
}
|
|
5491
5709
|
const { fee, success } = await fetchExtrinsicFacts(deps.txFacts, transfer.hash);
|
|
5492
5710
|
const totalInputValue = inputNotes.reduce((sum, n) => sum + n.value, 0n);
|
|
5493
|
-
const
|
|
5711
|
+
const derivedAmount = totalInputValue - (changeNote?.value ?? 0n) - (fee ?? 0n);
|
|
5712
|
+
const transferAmount = sentForRow?.value ?? derivedAmount;
|
|
5494
5713
|
if (transferAmount <= 0n) continue;
|
|
5495
5714
|
const record = {
|
|
5496
5715
|
id: recordKey(transfer),
|
|
5497
5716
|
type: "private_transfer",
|
|
5498
5717
|
blockNumber: transfer.blockNumber,
|
|
5499
5718
|
hash: transfer.hash ?? "",
|
|
5500
|
-
assetId: inputNotes[0].assetId.toString(),
|
|
5719
|
+
assetId: (sentForRow?.assetId ?? inputNotes[0].assetId).toString(),
|
|
5501
5720
|
amount: transferAmount.toString(),
|
|
5502
5721
|
recipientPkHex,
|
|
5503
5722
|
status: success ? "success" : "failed",
|
|
5504
|
-
...fee !== null ? { feePlanck: fee.toString() } : {
|
|
5505
|
-
|
|
5723
|
+
...fee !== null ? { feePlanck: fee.toString() } : {},
|
|
5724
|
+
...fee === null && !sentForRow ? { amountApproximate: true } : {},
|
|
5725
|
+
timestampMs: transfer.timestampMs ?? now(),
|
|
5726
|
+
// Best-effort: a slip that cannot be sealed costs the convenience of
|
|
5727
|
+
// handing it over again, never the history row.
|
|
5728
|
+
...sentForRow ? reissuedSlip(sentForRow, transfer.hash) : {}
|
|
5506
5729
|
};
|
|
5507
5730
|
await vault.saveTxRecord(record);
|
|
5508
5731
|
}
|
|
5509
5732
|
}
|
|
5510
5733
|
|
|
5511
5734
|
// src/wallet/ops/notes/buildNote.ts
|
|
5512
|
-
async function
|
|
5735
|
+
async function buildZkNoteWithIndex(params, deps) {
|
|
5513
5736
|
const { keys, storage } = deps;
|
|
5514
5737
|
const ownerPk = params.ownerPk ?? keys.ownerPk;
|
|
5515
5738
|
const spendingKey = params.spendingKey ?? keys.spendingKey;
|
|
5516
5739
|
const viewingPublicKey = params.viewingPublicKey ?? keys.viewingPublicKey;
|
|
5517
5740
|
const isSelf = params.viewingPublicKey === void 0 && params.recipientOwnerPk === void 0;
|
|
5518
5741
|
let ephSkOverride;
|
|
5519
|
-
|
|
5742
|
+
let outgoingIndex;
|
|
5743
|
+
if (isSelf && storage && keys.viewingSecretKey) {
|
|
5520
5744
|
try {
|
|
5521
|
-
ephSkOverride = deriveSelfEphSk(
|
|
5745
|
+
ephSkOverride = deriveSelfEphSk(
|
|
5746
|
+
keys.viewingSecretKey,
|
|
5747
|
+
await reserveSelfEphIndex(storage)
|
|
5748
|
+
);
|
|
5522
5749
|
} catch {
|
|
5523
5750
|
}
|
|
5524
|
-
} else if (params.viewingPublicKey && storage && keys.
|
|
5751
|
+
} else if (params.viewingPublicKey && storage && keys.outgoingViewingKey) {
|
|
5525
5752
|
try {
|
|
5526
|
-
const index = await
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
}
|
|
5753
|
+
const index = await reserveOutgoingIndex(storage, params.outgoingIndexFromChain);
|
|
5754
|
+
ephSkOverride = deriveOutgoingEphSk(keys.outgoingViewingKey, index);
|
|
5755
|
+
outgoingIndex = index;
|
|
5756
|
+
} catch {
|
|
5757
|
+
}
|
|
5758
|
+
try {
|
|
5759
|
+
await registerPairwiseCounterparty(storage, toHex(params.viewingPublicKey));
|
|
5534
5760
|
} catch {
|
|
5535
5761
|
}
|
|
5536
5762
|
}
|
|
@@ -5550,7 +5776,13 @@ async function buildZkNote(params, deps) {
|
|
|
5550
5776
|
},
|
|
5551
5777
|
...ephSkOverride !== void 0 && { ephSkOverride }
|
|
5552
5778
|
});
|
|
5553
|
-
return
|
|
5779
|
+
return {
|
|
5780
|
+
note: stampCreatedAt(note, Date.now()),
|
|
5781
|
+
...outgoingIndex !== void 0 ? { outgoingIndex } : {}
|
|
5782
|
+
};
|
|
5783
|
+
}
|
|
5784
|
+
async function buildZkNote(params, deps) {
|
|
5785
|
+
return (await buildZkNoteWithIndex(params, deps)).note;
|
|
5554
5786
|
}
|
|
5555
5787
|
|
|
5556
5788
|
// src/wallet/ops/notes/selfStealthNote.ts
|
|
@@ -5569,89 +5801,6 @@ function recoverSelfStealthNote(note, keys) {
|
|
|
5569
5801
|
return recovered;
|
|
5570
5802
|
}
|
|
5571
5803
|
|
|
5572
|
-
// src/wallet/ops/notes/noteTransfer.ts
|
|
5573
|
-
var NOTE_TRANSFER_URI_SCHEME = "orbinum://notes/v1/";
|
|
5574
|
-
var QR_PAGE_MAX_CHARS = 1800;
|
|
5575
|
-
function noteToTransferEntry(note) {
|
|
5576
|
-
return {
|
|
5577
|
-
c: note.commitmentHex,
|
|
5578
|
-
n: note.nullifierHex,
|
|
5579
|
-
val: note.value.toString(),
|
|
5580
|
-
aid: note.assetId.toString(),
|
|
5581
|
-
opk: scalarToHex(note.ownerPk),
|
|
5582
|
-
bld: scalarToHex(note.blinding),
|
|
5583
|
-
sk: scalarToHex(note.spendingKey)
|
|
5584
|
-
};
|
|
5585
|
-
}
|
|
5586
|
-
function encodeNoteTransferPages(notes, options = {}) {
|
|
5587
|
-
const now = options.now ?? Date.now;
|
|
5588
|
-
const maxChars = options.maxChars ?? QR_PAGE_MAX_CHARS;
|
|
5589
|
-
const entries = notes.map(noteToTransferEntry);
|
|
5590
|
-
const batches = [];
|
|
5591
|
-
let batch = [];
|
|
5592
|
-
for (const entry of entries) {
|
|
5593
|
-
batch.push(entry);
|
|
5594
|
-
const probe = { v: 1, ts: 0, p: 0, pt: 1, notes: batch };
|
|
5595
|
-
const size = NOTE_TRANSFER_URI_SCHEME.length + base64UrlEncode(JSON.stringify(probe)).length;
|
|
5596
|
-
if (size > maxChars && batch.length > 1) {
|
|
5597
|
-
batch.pop();
|
|
5598
|
-
batches.push(batch);
|
|
5599
|
-
batch = [entry];
|
|
5600
|
-
}
|
|
5601
|
-
}
|
|
5602
|
-
if (batch.length > 0) batches.push(batch);
|
|
5603
|
-
const ts = now();
|
|
5604
|
-
return batches.map((notesInPage, i) => {
|
|
5605
|
-
const payload = {
|
|
5606
|
-
v: 1,
|
|
5607
|
-
ts,
|
|
5608
|
-
p: i,
|
|
5609
|
-
pt: batches.length,
|
|
5610
|
-
notes: notesInPage
|
|
5611
|
-
};
|
|
5612
|
-
return NOTE_TRANSFER_URI_SCHEME + base64UrlEncode(JSON.stringify(payload));
|
|
5613
|
-
});
|
|
5614
|
-
}
|
|
5615
|
-
function decodeNoteTransferPage(uri) {
|
|
5616
|
-
if (!uri.startsWith(NOTE_TRANSFER_URI_SCHEME)) {
|
|
5617
|
-
throw new Error("Not an Orbinum note-transfer code.");
|
|
5618
|
-
}
|
|
5619
|
-
let payload;
|
|
5620
|
-
try {
|
|
5621
|
-
payload = JSON.parse(base64UrlDecode(uri.slice(NOTE_TRANSFER_URI_SCHEME.length)));
|
|
5622
|
-
} catch {
|
|
5623
|
-
throw new Error("Note-transfer code is corrupt.");
|
|
5624
|
-
}
|
|
5625
|
-
const p = payload;
|
|
5626
|
-
if (p.v !== 1) {
|
|
5627
|
-
throw new Error(`Unsupported note-transfer version: ${String(p.v)}.`);
|
|
5628
|
-
}
|
|
5629
|
-
if (typeof p.p !== "number" || typeof p.pt !== "number" || !Array.isArray(p.notes) || p.notes.some((n) => !isEntry(n))) {
|
|
5630
|
-
throw new Error("Note-transfer code is missing required fields.");
|
|
5631
|
-
}
|
|
5632
|
-
return p;
|
|
5633
|
-
}
|
|
5634
|
-
function isEntry(value) {
|
|
5635
|
-
if (typeof value !== "object" || value === null) return false;
|
|
5636
|
-
const e = value;
|
|
5637
|
-
return ["c", "n", "val", "aid", "opk", "bld", "sk"].every(
|
|
5638
|
-
(k) => typeof e[k] === "string" && e[k].length > 0
|
|
5639
|
-
);
|
|
5640
|
-
}
|
|
5641
|
-
function assembleNoteTransfer(pages) {
|
|
5642
|
-
if (pages.length === 0) throw new Error("No pages scanned.");
|
|
5643
|
-
const total = pages[0].pt;
|
|
5644
|
-
if (pages.some((p) => p.pt !== total)) {
|
|
5645
|
-
throw new Error("Pages come from different batches.");
|
|
5646
|
-
}
|
|
5647
|
-
const seen = new Set(pages.map((p) => p.p));
|
|
5648
|
-
if (seen.size !== total) {
|
|
5649
|
-
const missing = [...Array(total).keys()].filter((i) => !seen.has(i));
|
|
5650
|
-
throw new Error(`Missing page(s): ${missing.map((i) => i + 1).join(", ")} of ${total}.`);
|
|
5651
|
-
}
|
|
5652
|
-
return [...pages].sort((a, b) => a.p - b.p).flatMap((p) => p.notes);
|
|
5653
|
-
}
|
|
5654
|
-
|
|
5655
5804
|
// src/wallet/ops/notes/noteBackup.ts
|
|
5656
5805
|
var NOTE_BACKUP_VERSION = 1;
|
|
5657
5806
|
function noteToBackupEntry(note) {
|
|
@@ -5686,12 +5835,13 @@ function decodeNoteBackup(json) {
|
|
|
5686
5835
|
if (p.v !== NOTE_BACKUP_VERSION) {
|
|
5687
5836
|
throw new Error(`Unsupported note-backup version: ${String(p.v)}.`);
|
|
5688
5837
|
}
|
|
5689
|
-
if (!Array.isArray(p.notes) || p.notes.some((n) => !
|
|
5838
|
+
if (!Array.isArray(p.notes) || p.notes.some((n) => !isEntry(n))) {
|
|
5690
5839
|
throw new Error("Note backup is missing required fields.");
|
|
5691
5840
|
}
|
|
5692
5841
|
return p.notes;
|
|
5693
5842
|
}
|
|
5694
5843
|
function importNotesFromBackup(entries, keys) {
|
|
5844
|
+
assertSecretKeyBytes(keys.viewingSecretKey, "importNotesFromBackup: viewingSecretKey");
|
|
5695
5845
|
const out = [];
|
|
5696
5846
|
for (const entry of entries) {
|
|
5697
5847
|
const commitment = {
|
|
@@ -5715,12 +5865,14 @@ function importNotesFromBackup(entries, keys) {
|
|
|
5715
5865
|
}
|
|
5716
5866
|
return out;
|
|
5717
5867
|
}
|
|
5718
|
-
function
|
|
5868
|
+
function isEntry(value) {
|
|
5719
5869
|
if (typeof value !== "object" || value === null) return false;
|
|
5720
5870
|
const e = value;
|
|
5721
|
-
if (
|
|
5722
|
-
if (
|
|
5723
|
-
if (
|
|
5871
|
+
if (!isHexOfLength(e["commitmentHex"], 32)) return false;
|
|
5872
|
+
if (!isHexOfLength(e["encryptedMemo"], ENCRYPTED_MEMO_SIZE)) return false;
|
|
5873
|
+
if (e["leafIndex"] !== void 0 && !isValidLeafIndex(e["leafIndex"])) return false;
|
|
5874
|
+
if (e["spent"] !== void 0 && typeof e["spent"] !== "boolean") return false;
|
|
5875
|
+
if (e["spentAt"] !== void 0 && e["spentAt"] !== null && typeof e["spentAt"] !== "number") {
|
|
5724
5876
|
return false;
|
|
5725
5877
|
}
|
|
5726
5878
|
return true;
|
|
@@ -5728,6 +5880,7 @@ function isEntry2(value) {
|
|
|
5728
5880
|
|
|
5729
5881
|
// src/wallet/ops/notes/paymentSlipImport.ts
|
|
5730
5882
|
function importPaymentSlip(slip, keys) {
|
|
5883
|
+
assertSecretKeyBytes(keys.viewingSecretKey, "importPaymentSlip: viewingSecretKey");
|
|
5731
5884
|
const envelope = typeof slip === "string" ? decodePaymentSlip(slip) : slip;
|
|
5732
5885
|
if (!envelope) return null;
|
|
5733
5886
|
const fields = openPaymentSlip(keys.viewingSecretKey, envelope);
|
|
@@ -5856,10 +6009,7 @@ async function transferNotes(deps, params, onProgress) {
|
|
|
5856
6009
|
const merkleRoot = merkleA.root;
|
|
5857
6010
|
onProgress?.("building-output-notes");
|
|
5858
6011
|
const effectiveSenderPk = senderPk ?? noteA.ownerPk;
|
|
5859
|
-
const
|
|
5860
|
-
deriveViewingSecretKey(noteA.spendingKey)
|
|
5861
|
-
);
|
|
5862
|
-
const recipientNote = await deps.buildNote({
|
|
6012
|
+
const { note: recipientNote, outgoingIndex } = await deps.buildNote({
|
|
5863
6013
|
value: transferAmount,
|
|
5864
6014
|
assetId: noteA.assetId,
|
|
5865
6015
|
ownerPk: recipientPk,
|
|
@@ -5869,13 +6019,17 @@ async function transferNotes(deps, params, onProgress) {
|
|
|
5869
6019
|
// With a viewing key present this activates stealth derivation.
|
|
5870
6020
|
recipientOwnerPk: recipientPk
|
|
5871
6021
|
});
|
|
5872
|
-
const
|
|
6022
|
+
const changeSourcePk = outgoingIndex !== void 0 && recipientViewingPublicKey !== void 0 && deps.outgoingViewingKey !== void 0 ? bytesToBigintLE(
|
|
6023
|
+
sealRecipientBookEntry(
|
|
6024
|
+
recipientViewingPublicKey,
|
|
6025
|
+
deps.outgoingViewingKey,
|
|
6026
|
+
recipientNote.commitmentHex
|
|
6027
|
+
)
|
|
6028
|
+
) : recipientNote.ownerPk;
|
|
6029
|
+
const { note: changeNote } = await deps.buildNote({
|
|
5873
6030
|
value: changeValue,
|
|
5874
6031
|
assetId: noteA.assetId,
|
|
5875
|
-
|
|
5876
|
-
spendingKey: noteA.spendingKey,
|
|
5877
|
-
sourcePk: recipientNote.ownerPk,
|
|
5878
|
-
viewingPublicKey: senderViewingPublicKey
|
|
6032
|
+
sourcePk: changeSourcePk
|
|
5879
6033
|
});
|
|
5880
6034
|
onProgress?.("generating-zk");
|
|
5881
6035
|
const noteToInput = (note, proof) => ({
|
|
@@ -5992,7 +6146,7 @@ async function unshieldNote(deps, params, onProgress) {
|
|
|
5992
6146
|
if (changeValue > 0n) {
|
|
5993
6147
|
const { ownerPk, spendingKey } = deps.stealthKeys;
|
|
5994
6148
|
changeBlinding = randomBlinding();
|
|
5995
|
-
changeNote = await deps.buildNote({
|
|
6149
|
+
changeNote = (await deps.buildNote({
|
|
5996
6150
|
value: changeValue,
|
|
5997
6151
|
assetId: note.assetId,
|
|
5998
6152
|
ownerPk,
|
|
@@ -6000,7 +6154,7 @@ async function unshieldNote(deps, params, onProgress) {
|
|
|
6000
6154
|
spendingKey,
|
|
6001
6155
|
// Activates stealth: the commitment covers a one-time ownerPk.
|
|
6002
6156
|
recipientOwnerPk: ownerPk
|
|
6003
|
-
});
|
|
6157
|
+
})).note;
|
|
6004
6158
|
}
|
|
6005
6159
|
const { provider, version: circuitVersion } = await deps.resolver.resolve(
|
|
6006
6160
|
CircuitType6.Unshield,
|
|
@@ -6286,8 +6440,9 @@ var KEY_PREFIX = "orbinum_sk_";
|
|
|
6286
6440
|
function isEnvelope(value) {
|
|
6287
6441
|
return typeof value === "object" && value !== null && value.v === 1 && typeof value.iv === "string" && typeof value.ct === "string";
|
|
6288
6442
|
}
|
|
6289
|
-
function sessionCacheKey(address, chainId) {
|
|
6290
|
-
|
|
6443
|
+
function sessionCacheKey(address, chainId, version = "v3") {
|
|
6444
|
+
const marker = `${version}_`;
|
|
6445
|
+
return `${KEY_PREFIX}${marker}${chainId}_${canonicalAccountId(address)}`;
|
|
6291
6446
|
}
|
|
6292
6447
|
async function hasCachedSession(store, address, chainId) {
|
|
6293
6448
|
return await store.get(sessionCacheKey(address, chainId)) !== null;
|
|
@@ -6323,9 +6478,55 @@ async function clearSession(store, address, chainId) {
|
|
|
6323
6478
|
|
|
6324
6479
|
// src/wallet/identity/vaultName.ts
|
|
6325
6480
|
var PREFIX = "orbinum-vault";
|
|
6326
|
-
|
|
6481
|
+
var versionMarker = (version) => `${version}-`;
|
|
6482
|
+
var VERSION_MARKERS = ["v3-"];
|
|
6483
|
+
function assertNoVersionMarker(component, label) {
|
|
6484
|
+
for (const marker of VERSION_MARKERS) {
|
|
6485
|
+
if (component.startsWith(marker)) {
|
|
6486
|
+
throw new Error(
|
|
6487
|
+
`vaultStorageName: ${label} must not start with "${marker}" \u2014 it would collide with another identity version`
|
|
6488
|
+
);
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6492
|
+
function vaultStorageName(address, chainFingerprint, version = "v3") {
|
|
6327
6493
|
const account = canonicalAccountId(address);
|
|
6328
|
-
|
|
6494
|
+
assertNoVersionMarker(account, "account");
|
|
6495
|
+
const marker = versionMarker(version);
|
|
6496
|
+
if (chainFingerprint) assertNoVersionMarker(chainFingerprint.toLowerCase(), "chainFingerprint");
|
|
6497
|
+
return chainFingerprint ? `${PREFIX}-${marker}${chainFingerprint.toLowerCase()}-${account}` : `${PREFIX}-${marker}${account}`;
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6500
|
+
// src/wallet/identity/walletIdentity.ts
|
|
6501
|
+
function deriveIdentity(rootSecret, version) {
|
|
6502
|
+
if (rootSecret.length !== 32) {
|
|
6503
|
+
throw new Error(`deriveIdentity: rootSecret must be 32 bytes, got ${rootSecret.length}`);
|
|
6504
|
+
}
|
|
6505
|
+
if (rootSecret.every((b) => b === 0)) {
|
|
6506
|
+
throw new Error("deriveIdentity: rootSecret is all zeros \u2014 every wallet would share it");
|
|
6507
|
+
}
|
|
6508
|
+
if (version !== "v3") {
|
|
6509
|
+
throw new Error(`deriveIdentity: unknown identity version ${String(version)}`);
|
|
6510
|
+
}
|
|
6511
|
+
const spendingKey = deriveSpendingKeyV3(rootSecret);
|
|
6512
|
+
const viewingSecretKey = deriveViewingSecretKeyV3(rootSecret);
|
|
6513
|
+
return {
|
|
6514
|
+
version,
|
|
6515
|
+
spendingKey,
|
|
6516
|
+
ownerPk: deriveOwnerPk(spendingKey),
|
|
6517
|
+
viewingSecretKey,
|
|
6518
|
+
viewingPublicKey: deriveViewingPublicKey(viewingSecretKey),
|
|
6519
|
+
outgoingViewingKey: deriveOutgoingViewingKeyV3(rootSecret)
|
|
6520
|
+
};
|
|
6521
|
+
}
|
|
6522
|
+
function exportViewingCredential(identity, options = {}) {
|
|
6523
|
+
return {
|
|
6524
|
+
version: identity.version,
|
|
6525
|
+
ownerPk: identity.ownerPk,
|
|
6526
|
+
viewingSecretKey: Uint8Array.from(identity.viewingSecretKey),
|
|
6527
|
+
viewingPublicKey: Uint8Array.from(identity.viewingPublicKey),
|
|
6528
|
+
...options.includeOutgoing && identity.outgoingViewingKey ? { outgoingViewingKey: Uint8Array.from(identity.outgoingViewingKey) } : {}
|
|
6529
|
+
};
|
|
6329
6530
|
}
|
|
6330
6531
|
|
|
6331
6532
|
// src/wallet/identity/injectedExtensions.ts
|
|
@@ -6363,25 +6564,36 @@ var OrbinumWallet = class {
|
|
|
6363
6564
|
session;
|
|
6364
6565
|
notes;
|
|
6365
6566
|
vaultStore;
|
|
6366
|
-
/**
|
|
6367
|
-
|
|
6567
|
+
/**
|
|
6568
|
+
* The identity this wallet is unlocked as, or null.
|
|
6569
|
+
*
|
|
6570
|
+
* Held as a bundle rather than a single scalar because v3 keys are SIBLINGS:
|
|
6571
|
+
* the spending key no longer produces the viewing key, so there is nothing
|
|
6572
|
+
* left to derive the others from once unlock returns. Keeping the root out
|
|
6573
|
+
* of this object is deliberate — the branches are what the wallet needs, and
|
|
6574
|
+
* the root is the thing worth not retaining.
|
|
6575
|
+
*/
|
|
6576
|
+
identity = null;
|
|
6368
6577
|
/** The underlying store, for operations the facade does not cover. */
|
|
6369
6578
|
get vault() {
|
|
6370
6579
|
return this.vaultStore;
|
|
6371
6580
|
}
|
|
6372
6581
|
get unlocked() {
|
|
6373
|
-
return this.session.unlocked && this.
|
|
6582
|
+
return this.session.unlocked && this.identity !== null;
|
|
6374
6583
|
}
|
|
6375
6584
|
/**
|
|
6376
6585
|
* Opens the wallet from the master key bytes a `SpendingKeyRequest`
|
|
6377
6586
|
* signature produces — the same value `PrivacyKeyManager.getMasterBytes()`
|
|
6378
6587
|
* returns.
|
|
6379
6588
|
*
|
|
6380
|
-
* Master bytes rather than the spending-key scalar, deliberately: the
|
|
6381
|
-
* is the
|
|
6382
|
-
*
|
|
6383
|
-
*
|
|
6384
|
-
*
|
|
6589
|
+
* Master bytes rather than the spending-key scalar, deliberately: the master
|
|
6590
|
+
* is the root EVERY branch hangs off — spending key, viewing key, outgoing
|
|
6591
|
+
* viewing key and vault key are each their own HKDF of it, none derived
|
|
6592
|
+
* from another. Only `ownerPk` comes from the spending scalar, because the
|
|
6593
|
+
* circuit defines it that way.
|
|
6594
|
+
*
|
|
6595
|
+
* It is also what keeps a stored vault readable across a modulus change:
|
|
6596
|
+
* the vault key is derived pre-reduction.
|
|
6385
6597
|
*
|
|
6386
6598
|
* Reports whether the stored vault was RESET: a different chain, a different
|
|
6387
6599
|
* key, or notes the chain no longer recognises. When it was, the wallet
|
|
@@ -6395,14 +6607,12 @@ var OrbinumWallet = class {
|
|
|
6395
6607
|
* against a vault that was never read.
|
|
6396
6608
|
*/
|
|
6397
6609
|
async unlock(masterBytes, options = {}) {
|
|
6398
|
-
|
|
6399
|
-
throw new Error(`Master key must be 32 bytes, got ${masterBytes.length}.`);
|
|
6400
|
-
}
|
|
6610
|
+
assertSecretKeyBytes(masterBytes, "Master key");
|
|
6401
6611
|
this.session.open(
|
|
6402
6612
|
await deriveVaultKey(masterBytes),
|
|
6403
6613
|
await deriveVaultBlindKey(masterBytes)
|
|
6404
6614
|
);
|
|
6405
|
-
this.
|
|
6615
|
+
this.identity = deriveIdentity(masterBytes, this.config.identityVersion ?? "v3");
|
|
6406
6616
|
try {
|
|
6407
6617
|
return await this.vaultStore.unlock(this.session.cryptoKey, options);
|
|
6408
6618
|
} catch (err) {
|
|
@@ -6413,8 +6623,12 @@ var OrbinumWallet = class {
|
|
|
6413
6623
|
/** Drops the in-memory keys. The stored vault is untouched. */
|
|
6414
6624
|
lock() {
|
|
6415
6625
|
this.session.lock();
|
|
6416
|
-
this.
|
|
6626
|
+
this.identity = null;
|
|
6417
6627
|
this.notes.set([]);
|
|
6628
|
+
try {
|
|
6629
|
+
this.config.pool.terminate();
|
|
6630
|
+
} catch {
|
|
6631
|
+
}
|
|
6418
6632
|
}
|
|
6419
6633
|
/** The notes currently held. Empty while locked. */
|
|
6420
6634
|
getNotes() {
|
|
@@ -6426,11 +6640,8 @@ var OrbinumWallet = class {
|
|
|
6426
6640
|
}
|
|
6427
6641
|
/** This wallet's privacy identity: the keys a payer needs to address it. */
|
|
6428
6642
|
privacyKeys() {
|
|
6429
|
-
const
|
|
6430
|
-
return {
|
|
6431
|
-
ownerPk: deriveOwnerPk(spendingKey),
|
|
6432
|
-
viewingPublicKey: deriveViewingPublicKey(deriveViewingSecretKey(spendingKey))
|
|
6433
|
-
};
|
|
6643
|
+
const { ownerPk, viewingPublicKey } = this.requireIdentity();
|
|
6644
|
+
return { ownerPk, viewingPublicKey };
|
|
6434
6645
|
}
|
|
6435
6646
|
/**
|
|
6436
6647
|
* Walks the feed, recovers this wallet's notes, and persists them with their
|
|
@@ -6440,15 +6651,21 @@ var OrbinumWallet = class {
|
|
|
6440
6651
|
* `sinceLeafIndex` for a full scan, which is what a restore needs.
|
|
6441
6652
|
*/
|
|
6442
6653
|
async scan(options = {}) {
|
|
6443
|
-
const
|
|
6444
|
-
const viewingKey = deriveViewingSecretKey(spendingKey);
|
|
6654
|
+
const identity = this.requireIdentity();
|
|
6445
6655
|
return runScan({
|
|
6446
6656
|
vault: this.vaultStore,
|
|
6447
6657
|
storage: this.config.storage,
|
|
6448
6658
|
hints: this.config.hints,
|
|
6449
6659
|
nullifiers: this.config.nullifiers,
|
|
6450
6660
|
pool: this.config.pool,
|
|
6451
|
-
keys: {
|
|
6661
|
+
keys: {
|
|
6662
|
+
viewingKey: identity.viewingSecretKey,
|
|
6663
|
+
spendingKey: identity.spendingKey,
|
|
6664
|
+
ownerPk: identity.ownerPk,
|
|
6665
|
+
// Absent on a watch-only identity: the scan then finds every
|
|
6666
|
+
// note the wallet owns and reconstructs no payment history.
|
|
6667
|
+
...identity.outgoingViewingKey ? { outgoingViewingKey: identity.outgoingViewingKey } : {}
|
|
6668
|
+
},
|
|
6452
6669
|
sinceLeafIndex: options.sinceLeafIndex,
|
|
6453
6670
|
viewTagActivationLeaf: this.config.viewTagActivationLeaf,
|
|
6454
6671
|
onProgress: options.onProgress,
|
|
@@ -6470,13 +6687,14 @@ var OrbinumWallet = class {
|
|
|
6470
6687
|
* reusing an index would republish an ephPk and link the two notes.
|
|
6471
6688
|
*/
|
|
6472
6689
|
async buildNote(params) {
|
|
6473
|
-
const
|
|
6690
|
+
const identity = this.requireIdentity();
|
|
6474
6691
|
return buildZkNote(params, {
|
|
6475
6692
|
keys: {
|
|
6476
|
-
ownerPk:
|
|
6477
|
-
spendingKey,
|
|
6478
|
-
viewingPublicKey:
|
|
6479
|
-
viewingSecretKey:
|
|
6693
|
+
ownerPk: identity.ownerPk,
|
|
6694
|
+
spendingKey: identity.spendingKey,
|
|
6695
|
+
viewingPublicKey: identity.viewingPublicKey,
|
|
6696
|
+
viewingSecretKey: identity.viewingSecretKey,
|
|
6697
|
+
...identity.outgoingViewingKey ? { outgoingViewingKey: identity.outgoingViewingKey } : {}
|
|
6480
6698
|
},
|
|
6481
6699
|
storage: this.config.storage
|
|
6482
6700
|
});
|
|
@@ -6493,11 +6711,12 @@ var OrbinumWallet = class {
|
|
|
6493
6711
|
* Treat the result as secret: it can spend every note this wallet holds.
|
|
6494
6712
|
*/
|
|
6495
6713
|
spendKeys() {
|
|
6496
|
-
const
|
|
6714
|
+
const identity = this.requireIdentity();
|
|
6497
6715
|
return {
|
|
6498
|
-
spendingKey,
|
|
6499
|
-
viewingSecretKey:
|
|
6500
|
-
ownerPk:
|
|
6716
|
+
spendingKey: identity.spendingKey,
|
|
6717
|
+
viewingSecretKey: identity.viewingSecretKey,
|
|
6718
|
+
ownerPk: identity.ownerPk,
|
|
6719
|
+
...identity.outgoingViewingKey ? { outgoingViewingKey: identity.outgoingViewingKey } : {}
|
|
6501
6720
|
};
|
|
6502
6721
|
}
|
|
6503
6722
|
/**
|
|
@@ -6519,7 +6738,20 @@ var OrbinumWallet = class {
|
|
|
6519
6738
|
* verifying-key rotation, and the failure surfaces much later.
|
|
6520
6739
|
*/
|
|
6521
6740
|
buildOutputNote = async (params) => {
|
|
6522
|
-
|
|
6741
|
+
const identity = this.requireIdentity();
|
|
6742
|
+
return buildZkNoteWithIndex(
|
|
6743
|
+
{ ...params, circuitVersion: await this.circuitVersion() },
|
|
6744
|
+
{
|
|
6745
|
+
keys: {
|
|
6746
|
+
ownerPk: identity.ownerPk,
|
|
6747
|
+
spendingKey: identity.spendingKey,
|
|
6748
|
+
viewingPublicKey: identity.viewingPublicKey,
|
|
6749
|
+
viewingSecretKey: identity.viewingSecretKey,
|
|
6750
|
+
...identity.outgoingViewingKey ? { outgoingViewingKey: identity.outgoingViewingKey } : {}
|
|
6751
|
+
},
|
|
6752
|
+
storage: this.config.storage
|
|
6753
|
+
}
|
|
6754
|
+
);
|
|
6523
6755
|
};
|
|
6524
6756
|
/** The circuit version a new note should carry. */
|
|
6525
6757
|
async circuitVersion() {
|
|
@@ -6531,11 +6763,11 @@ var OrbinumWallet = class {
|
|
|
6531
6763
|
}
|
|
6532
6764
|
return chainActiveCircuitVersion(this.config.zkVerifier);
|
|
6533
6765
|
}
|
|
6534
|
-
|
|
6535
|
-
if (this.
|
|
6766
|
+
requireIdentity() {
|
|
6767
|
+
if (this.identity === null) {
|
|
6536
6768
|
throw new Error("Wallet is locked. Call unlock() before using it.");
|
|
6537
6769
|
}
|
|
6538
|
-
return this.
|
|
6770
|
+
return this.identity;
|
|
6539
6771
|
}
|
|
6540
6772
|
};
|
|
6541
6773
|
export {
|
|
@@ -6566,9 +6798,8 @@ export {
|
|
|
6566
6798
|
NATIVE_ASSET_ID,
|
|
6567
6799
|
NOTE_BACKUP_VERSION,
|
|
6568
6800
|
NOTE_BIGINT_FIELDS,
|
|
6569
|
-
NOTE_TRANSFER_URI_SCHEME,
|
|
6570
6801
|
NoteBuilder,
|
|
6571
|
-
|
|
6802
|
+
OUTGOING_EPH_WINDOW,
|
|
6572
6803
|
OrbinumClient,
|
|
6573
6804
|
OrbinumClientProvider,
|
|
6574
6805
|
OrbinumWallet,
|
|
@@ -6578,7 +6809,6 @@ export {
|
|
|
6578
6809
|
PRECOMPILE_ADDR,
|
|
6579
6810
|
PrivacyKeyManager,
|
|
6580
6811
|
PrivacyModule,
|
|
6581
|
-
QR_PAGE_MAX_CHARS,
|
|
6582
6812
|
RECOVERED_TX_RESULT,
|
|
6583
6813
|
RelayerStatusModule,
|
|
6584
6814
|
RpcV2Module,
|
|
@@ -6603,7 +6833,7 @@ export {
|
|
|
6603
6833
|
addressToFieldElement,
|
|
6604
6834
|
applyBatch,
|
|
6605
6835
|
applyNoteStatus,
|
|
6606
|
-
|
|
6836
|
+
assertSecretKeyBytes,
|
|
6607
6837
|
base58,
|
|
6608
6838
|
base64UrlDecode,
|
|
6609
6839
|
base64UrlEncode,
|
|
@@ -6616,6 +6846,7 @@ export {
|
|
|
6616
6846
|
buildShieldBatchOperations,
|
|
6617
6847
|
buildShieldParams,
|
|
6618
6848
|
buildZkNote,
|
|
6849
|
+
buildZkNoteWithIndex,
|
|
6619
6850
|
bytesToBigintLE,
|
|
6620
6851
|
bytesToBjjScalar,
|
|
6621
6852
|
cacheSession,
|
|
@@ -6645,15 +6876,17 @@ export {
|
|
|
6645
6876
|
createWorkerPool,
|
|
6646
6877
|
decodeNoteBackup,
|
|
6647
6878
|
decodeNoteDisclosureKey,
|
|
6648
|
-
decodeNoteTransferPage,
|
|
6649
6879
|
decodePaymentSlip,
|
|
6650
6880
|
decodePrecompileCalldata,
|
|
6651
6881
|
decryptHintBatch,
|
|
6652
6882
|
decryptJson,
|
|
6653
6883
|
decryptNoteRecord,
|
|
6884
|
+
deriveIdentity,
|
|
6654
6885
|
deriveMasterKeyBytes,
|
|
6655
|
-
|
|
6656
|
-
|
|
6886
|
+
deriveOutgoingEphPk,
|
|
6887
|
+
deriveOutgoingEphSk,
|
|
6888
|
+
deriveOutgoingSharedSecret,
|
|
6889
|
+
deriveOutgoingViewingKeyV3,
|
|
6657
6890
|
deriveOwnerPk,
|
|
6658
6891
|
derivePairwiseEphSk,
|
|
6659
6892
|
derivePairwiseSharedSecret,
|
|
@@ -6662,6 +6895,7 @@ export {
|
|
|
6662
6895
|
deriveSpendingKeyFromSignature,
|
|
6663
6896
|
deriveSpendingKeyMessageV2,
|
|
6664
6897
|
deriveSpendingKeyTypedData,
|
|
6898
|
+
deriveSpendingKeyV3,
|
|
6665
6899
|
deriveStealthOwnerPk,
|
|
6666
6900
|
deriveStealthSk,
|
|
6667
6901
|
deriveVaultBlindKey,
|
|
@@ -6669,9 +6903,9 @@ export {
|
|
|
6669
6903
|
deriveViewTag,
|
|
6670
6904
|
deriveViewingPublicKey,
|
|
6671
6905
|
deriveViewingSecretKey,
|
|
6906
|
+
deriveViewingSecretKeyV3,
|
|
6672
6907
|
detectCommitmentMismatch,
|
|
6673
6908
|
encodeNoteBackup,
|
|
6674
|
-
encodeNoteTransferPages,
|
|
6675
6909
|
encodePaymentSlip,
|
|
6676
6910
|
encryptJson,
|
|
6677
6911
|
encryptNote,
|
|
@@ -6681,6 +6915,7 @@ export {
|
|
|
6681
6915
|
evmToImplicitSubstrate,
|
|
6682
6916
|
evmToMappedAccountHex,
|
|
6683
6917
|
evmToSubstrate,
|
|
6918
|
+
exportViewingCredential,
|
|
6684
6919
|
extractPalletError,
|
|
6685
6920
|
failed,
|
|
6686
6921
|
fastMulBase,
|
|
@@ -6721,9 +6956,9 @@ export {
|
|
|
6721
6956
|
isNativeAsset,
|
|
6722
6957
|
isNoteSelfConsistent,
|
|
6723
6958
|
isSpendable,
|
|
6724
|
-
isSs58,
|
|
6725
6959
|
isSubstrateAddress,
|
|
6726
6960
|
isUnifiedAddress,
|
|
6961
|
+
isUsableSecretKey,
|
|
6727
6962
|
isValidLeafIndex,
|
|
6728
6963
|
leHexToBigint,
|
|
6729
6964
|
mapExtrinsicArgs,
|
|
@@ -6740,10 +6975,10 @@ export {
|
|
|
6740
6975
|
noteMatchesCommitment,
|
|
6741
6976
|
noteOrigin,
|
|
6742
6977
|
noteSpentTxHash,
|
|
6743
|
-
noteToTransferEntry,
|
|
6744
6978
|
noteTxKind,
|
|
6745
|
-
openOutgoingBlob,
|
|
6746
6979
|
openPaymentSlip,
|
|
6980
|
+
openRecipientBookEntry,
|
|
6981
|
+
outgoingEphWindow,
|
|
6747
6982
|
outranks,
|
|
6748
6983
|
pairwiseEphWindow,
|
|
6749
6984
|
palletErrorKind,
|
|
@@ -6754,15 +6989,18 @@ export {
|
|
|
6754
6989
|
planTransfer,
|
|
6755
6990
|
planUnshield,
|
|
6756
6991
|
randomBlinding,
|
|
6757
|
-
|
|
6992
|
+
reconstructOutgoingIndex,
|
|
6758
6993
|
reconstructOutgoingTxRecords,
|
|
6759
6994
|
recoverOwnerPkPoint,
|
|
6760
6995
|
recoverSelfStealthNote,
|
|
6996
|
+
recoverSentFromSharedSecret,
|
|
6997
|
+
recoverSentNote,
|
|
6761
6998
|
refuseIfAlreadySpent,
|
|
6762
6999
|
regeneratePaymentSlip,
|
|
7000
|
+
registerPairwiseCounterparty,
|
|
6763
7001
|
removeByCommitment,
|
|
6764
7002
|
requireSessionKeys,
|
|
6765
|
-
|
|
7003
|
+
reserveOutgoingIndex,
|
|
6766
7004
|
reserveSelfEphIndex,
|
|
6767
7005
|
resolveSelfEphCeiling,
|
|
6768
7006
|
resolveSpentSet,
|
|
@@ -6771,8 +7009,8 @@ export {
|
|
|
6771
7009
|
runScan,
|
|
6772
7010
|
scalarToHex,
|
|
6773
7011
|
scanAbortError,
|
|
6774
|
-
sealOutgoingBlob,
|
|
6775
7012
|
sealPaymentSlip,
|
|
7013
|
+
sealRecipientBookEntry,
|
|
6776
7014
|
selectDescribingNote,
|
|
6777
7015
|
selectDescribingNoteByCommitment,
|
|
6778
7016
|
selectGhosts,
|
|
@@ -6798,10 +7036,10 @@ export {
|
|
|
6798
7036
|
truncateMiddle,
|
|
6799
7037
|
tryDecryptNote,
|
|
6800
7038
|
tryDecryptNoteVerbose,
|
|
6801
|
-
tryRecoverOutgoing,
|
|
6802
7039
|
txLandedAfterError,
|
|
6803
7040
|
u128,
|
|
6804
7041
|
u64,
|
|
7042
|
+
unpackUsableViewingKey,
|
|
6805
7043
|
unshieldNote,
|
|
6806
7044
|
upsertNote,
|
|
6807
7045
|
vaultReplacer,
|