@qrlwallet/connect 3.0.0 → 3.1.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/dist/index.d.mts +39 -20
- package/dist/index.d.ts +39 -20
- package/dist/index.js +341 -165
- package/dist/index.mjs +341 -165
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -3,68 +3,144 @@ import EventEmitter4 from "eventemitter3";
|
|
|
3
3
|
|
|
4
4
|
// src/ConnectionManager.ts
|
|
5
5
|
import EventEmitter3 from "eventemitter3";
|
|
6
|
-
import { v4 as uuidv4 } from "uuid";
|
|
7
6
|
|
|
8
7
|
// src/KeyExchange.ts
|
|
9
8
|
import EventEmitter from "eventemitter3";
|
|
10
9
|
|
|
11
|
-
// src/
|
|
10
|
+
// src/crypto/primitives.ts
|
|
12
11
|
import { ml_kem768 } from "@noble/post-quantum/ml-kem.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
|
|
17
|
-
var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
|
|
18
|
-
var ML_KEM_768_CT_LEN = 1088;
|
|
19
|
-
var AEAD_KEY_LEN = 32;
|
|
20
|
-
function subtle() {
|
|
12
|
+
import { shake256 } from "@noble/hashes/sha3.js";
|
|
13
|
+
import * as mldsa from "@theqrl/mldsa87";
|
|
14
|
+
function webCrypto() {
|
|
21
15
|
const c = globalThis.crypto;
|
|
22
|
-
if (!c
|
|
23
|
-
throw new Error("
|
|
16
|
+
if (!c) {
|
|
17
|
+
throw new Error("crypto: WebCrypto is not available in this environment");
|
|
18
|
+
}
|
|
19
|
+
return c;
|
|
20
|
+
}
|
|
21
|
+
function subtle() {
|
|
22
|
+
const c = webCrypto();
|
|
23
|
+
if (!c.subtle) {
|
|
24
|
+
throw new Error("crypto: WebCrypto SubtleCrypto is not available in this environment");
|
|
24
25
|
}
|
|
25
26
|
return c.subtle;
|
|
26
27
|
}
|
|
27
28
|
function bs(u) {
|
|
28
|
-
|
|
29
|
+
if (u.buffer instanceof ArrayBuffer) {
|
|
30
|
+
return new Uint8Array(u.buffer, u.byteOffset, u.byteLength);
|
|
31
|
+
}
|
|
32
|
+
const copy = new Uint8Array(u.byteLength);
|
|
33
|
+
copy.set(u);
|
|
34
|
+
return copy;
|
|
29
35
|
}
|
|
30
|
-
function
|
|
36
|
+
function mlkemKeygen() {
|
|
31
37
|
const { publicKey, secretKey } = ml_kem768.keygen();
|
|
32
38
|
return { pk: publicKey, sk: secretKey };
|
|
33
39
|
}
|
|
34
|
-
function
|
|
40
|
+
function mlkemEncaps(pk) {
|
|
35
41
|
const { cipherText, sharedSecret } = ml_kem768.encapsulate(pk);
|
|
36
42
|
return { ct: cipherText, ss: sharedSecret };
|
|
37
43
|
}
|
|
38
|
-
function
|
|
44
|
+
function mlkemDecaps(sk, ct) {
|
|
39
45
|
return ml_kem768.decapsulate(ct, sk);
|
|
40
46
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return new Uint8Array(await subtle().digest("SHA-256", bs(buf)));
|
|
47
|
+
function shake256Digest(data, dkLen) {
|
|
48
|
+
return shake256(data, { dkLen });
|
|
44
49
|
}
|
|
45
|
-
async function
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
async function sha256(data) {
|
|
51
|
+
return new Uint8Array(await subtle().digest("SHA-256", bs(data)));
|
|
52
|
+
}
|
|
53
|
+
function mldsaVerify(signature, digest, publicKey, ctx) {
|
|
54
|
+
return mldsa.cryptoSignVerify(signature, digest, publicKey, ctx);
|
|
55
|
+
}
|
|
56
|
+
async function hkdfAesGcmKey(ikm, salt, info) {
|
|
57
|
+
const ikmKey = await subtle().importKey("raw", bs(ikm), "HKDF", false, ["deriveKey"]);
|
|
48
58
|
return subtle().deriveKey(
|
|
49
|
-
{ name: "HKDF", hash: "SHA-256", salt:
|
|
50
|
-
|
|
59
|
+
{ name: "HKDF", hash: "SHA-256", salt: bs(salt), info: bs(info) },
|
|
60
|
+
ikmKey,
|
|
51
61
|
{ name: "AES-GCM", length: 256 },
|
|
52
62
|
true,
|
|
53
63
|
["encrypt", "decrypt"]
|
|
54
64
|
);
|
|
55
65
|
}
|
|
56
|
-
async function
|
|
57
|
-
if (raw.length !== AEAD_KEY_LEN) {
|
|
58
|
-
throw new Error(`PQCrypto: raw AEAD key must be ${AEAD_KEY_LEN} bytes`);
|
|
59
|
-
}
|
|
66
|
+
async function importAesGcmKey(raw) {
|
|
60
67
|
return subtle().importKey("raw", bs(raw), { name: "AES-GCM", length: 256 }, true, [
|
|
61
68
|
"encrypt",
|
|
62
69
|
"decrypt"
|
|
63
70
|
]);
|
|
64
71
|
}
|
|
72
|
+
async function exportAesGcmKey(key) {
|
|
73
|
+
return new Uint8Array(await subtle().exportKey("raw", key));
|
|
74
|
+
}
|
|
75
|
+
async function aesGcmEncrypt(key, iv, aad2, plaintext) {
|
|
76
|
+
const ct = await subtle().encrypt(
|
|
77
|
+
{ name: "AES-GCM", iv: bs(iv), additionalData: bs(aad2) },
|
|
78
|
+
key,
|
|
79
|
+
bs(plaintext)
|
|
80
|
+
);
|
|
81
|
+
return new Uint8Array(ct);
|
|
82
|
+
}
|
|
83
|
+
async function aesGcmDecrypt(key, iv, aad2, ciphertext) {
|
|
84
|
+
const pt = await subtle().decrypt(
|
|
85
|
+
{ name: "AES-GCM", iv: bs(iv), additionalData: bs(aad2) },
|
|
86
|
+
key,
|
|
87
|
+
bs(ciphertext)
|
|
88
|
+
);
|
|
89
|
+
return new Uint8Array(pt);
|
|
90
|
+
}
|
|
91
|
+
function randomBytes(n) {
|
|
92
|
+
const c = webCrypto();
|
|
93
|
+
if (typeof c.getRandomValues !== "function") {
|
|
94
|
+
throw new Error("crypto: crypto.getRandomValues is not available");
|
|
95
|
+
}
|
|
96
|
+
return c.getRandomValues(new Uint8Array(n));
|
|
97
|
+
}
|
|
98
|
+
function randomUuid() {
|
|
99
|
+
const b = randomBytes(16);
|
|
100
|
+
b[6] = (b[6] ?? 0) & 15 | 64;
|
|
101
|
+
b[8] = (b[8] ?? 0) & 63 | 128;
|
|
102
|
+
const hex = Array.from(b, (v) => v.toString(16).padStart(2, "0")).join("");
|
|
103
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
104
|
+
}
|
|
105
|
+
function constantTimeEquals(a, b) {
|
|
106
|
+
if (a.length !== b.length) return false;
|
|
107
|
+
let d = 0;
|
|
108
|
+
for (let i = 0; i < a.length; i++) d |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
109
|
+
return d === 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/PQCrypto.ts
|
|
113
|
+
var textEncoder = new TextEncoder();
|
|
114
|
+
var LABEL = textEncoder.encode("pq-pair/v1");
|
|
115
|
+
var LABEL_AEAD_SUFFIX = textEncoder.encode(" aead");
|
|
116
|
+
var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
|
|
117
|
+
var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
|
|
118
|
+
var ML_KEM_768_CT_LEN = 1088;
|
|
119
|
+
var AEAD_KEY_LEN = 32;
|
|
120
|
+
function kemKeygen() {
|
|
121
|
+
return mlkemKeygen();
|
|
122
|
+
}
|
|
123
|
+
function kemEncaps(pk) {
|
|
124
|
+
return mlkemEncaps(pk);
|
|
125
|
+
}
|
|
126
|
+
function kemDecaps(sk, ct) {
|
|
127
|
+
return mlkemDecaps(sk, ct);
|
|
128
|
+
}
|
|
129
|
+
async function transcriptHash(cid, pk, ct) {
|
|
130
|
+
return sha256(concat(LABEL, cid, pk, ct));
|
|
131
|
+
}
|
|
132
|
+
async function deriveAeadKey(ss, htx) {
|
|
133
|
+
const info = concat(LABEL, LABEL_AEAD_SUFFIX, htx);
|
|
134
|
+
return hkdfAesGcmKey(ss, new Uint8Array(32), info);
|
|
135
|
+
}
|
|
136
|
+
async function importRawAeadKey(raw) {
|
|
137
|
+
if (raw.length !== AEAD_KEY_LEN) {
|
|
138
|
+
throw new Error(`PQCrypto: raw AEAD key must be ${AEAD_KEY_LEN} bytes`);
|
|
139
|
+
}
|
|
140
|
+
return importAesGcmKey(raw);
|
|
141
|
+
}
|
|
65
142
|
async function exportRawAeadKey(key) {
|
|
66
|
-
|
|
67
|
-
return new Uint8Array(buf);
|
|
143
|
+
return exportAesGcmKey(key);
|
|
68
144
|
}
|
|
69
145
|
function nonce(dir, seq) {
|
|
70
146
|
if (dir.length !== 4) {
|
|
@@ -86,20 +162,10 @@ function aad(htx, seq) {
|
|
|
86
162
|
return out;
|
|
87
163
|
}
|
|
88
164
|
async function seal(key, dir, seq, htx, pt) {
|
|
89
|
-
|
|
90
|
-
{ name: "AES-GCM", iv: bs(nonce(dir, seq)), additionalData: bs(aad(htx, seq)) },
|
|
91
|
-
key,
|
|
92
|
-
bs(pt)
|
|
93
|
-
);
|
|
94
|
-
return new Uint8Array(ct);
|
|
165
|
+
return aesGcmEncrypt(key, nonce(dir, seq), aad(htx, seq), pt);
|
|
95
166
|
}
|
|
96
167
|
async function open(key, dir, seq, htx, ct) {
|
|
97
|
-
|
|
98
|
-
{ name: "AES-GCM", iv: bs(nonce(dir, seq)), additionalData: bs(aad(htx, seq)) },
|
|
99
|
-
key,
|
|
100
|
-
bs(ct)
|
|
101
|
-
);
|
|
102
|
-
return new Uint8Array(pt);
|
|
168
|
+
return aesGcmDecrypt(key, nonce(dir, seq), aad(htx, seq), ct);
|
|
103
169
|
}
|
|
104
170
|
function zeroize(b) {
|
|
105
171
|
b.fill(0);
|
|
@@ -120,7 +186,7 @@ function toBase64(bytes) {
|
|
|
120
186
|
let bin = "";
|
|
121
187
|
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
122
188
|
const slice = bytes.subarray(i, i + CHUNK);
|
|
123
|
-
bin += String.fromCharCode.apply(null, slice);
|
|
189
|
+
bin += String.fromCharCode.apply(null, Array.from(slice));
|
|
124
190
|
}
|
|
125
191
|
return btoa(bin);
|
|
126
192
|
}
|
|
@@ -130,12 +196,6 @@ function fromBase64(b64) {
|
|
|
130
196
|
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
131
197
|
return out;
|
|
132
198
|
}
|
|
133
|
-
function constantTimeEquals(a, b) {
|
|
134
|
-
if (a.length !== b.length) return false;
|
|
135
|
-
let d = 0;
|
|
136
|
-
for (let i = 0; i < a.length; i++) d |= a[i] ^ b[i];
|
|
137
|
-
return d === 0;
|
|
138
|
-
}
|
|
139
199
|
|
|
140
200
|
// src/config.ts
|
|
141
201
|
var DEFAULT_RELAY_URL = "https://qrlwallet.com";
|
|
@@ -255,6 +315,13 @@ var KeyExchange = class extends EventEmitter {
|
|
|
255
315
|
this.awaitingSynAck = false;
|
|
256
316
|
this.awaitingAck = false;
|
|
257
317
|
this.keysExchanged = false;
|
|
318
|
+
// The ACK we produced for the current handshake. A wallet that lost its
|
|
319
|
+
// transport right after sending SYNACK re-sends the identical SYNACK on
|
|
320
|
+
// rejoin; we answer with this cached ACK so the handshake converges
|
|
321
|
+
// instead of stalling (onSynAck ignores duplicates and would otherwise
|
|
322
|
+
// never reply). Cleared on reset; not persisted (a reloaded dApp has a
|
|
323
|
+
// completed session and the wallet is no longer awaiting an ACK).
|
|
324
|
+
this.lastAck = null;
|
|
258
325
|
this.isOriginator = isOriginator;
|
|
259
326
|
if (restored) {
|
|
260
327
|
this.session = restored;
|
|
@@ -285,7 +352,7 @@ var KeyExchange = class extends EventEmitter {
|
|
|
285
352
|
*/
|
|
286
353
|
async onSynAck(cid, msg) {
|
|
287
354
|
if (!this.isOriginator) {
|
|
288
|
-
warn("KeyExchange", "Responder received SYNACK
|
|
355
|
+
warn("KeyExchange", "Responder received SYNACK - ignoring");
|
|
289
356
|
return null;
|
|
290
357
|
}
|
|
291
358
|
if (!this.awaitingSynAck || !this.keypair) {
|
|
@@ -328,11 +395,13 @@ var KeyExchange = class extends EventEmitter {
|
|
|
328
395
|
this.step = "key_handshake_ACK" /* ACK */;
|
|
329
396
|
this.emit("keys_exchanged");
|
|
330
397
|
this.emit("step_change", this.step);
|
|
331
|
-
|
|
398
|
+
const ack = {
|
|
332
399
|
type: "key_handshake_ACK" /* ACK */,
|
|
333
400
|
c1: toBase64(c1),
|
|
334
401
|
v: PROTOCOL_VERSION
|
|
335
402
|
};
|
|
403
|
+
this.lastAck = ack;
|
|
404
|
+
return ack;
|
|
336
405
|
}
|
|
337
406
|
/**
|
|
338
407
|
* Responder (wallet) side: begin handshake from a scanned QR.
|
|
@@ -373,7 +442,7 @@ var KeyExchange = class extends EventEmitter {
|
|
|
373
442
|
*/
|
|
374
443
|
async onAck(msg) {
|
|
375
444
|
if (this.isOriginator) {
|
|
376
|
-
warn("KeyExchange", "Originator received ACK
|
|
445
|
+
warn("KeyExchange", "Originator received ACK - ignoring");
|
|
377
446
|
return;
|
|
378
447
|
}
|
|
379
448
|
if (!this.awaitingAck) {
|
|
@@ -400,23 +469,17 @@ var KeyExchange = class extends EventEmitter {
|
|
|
400
469
|
/** Encrypt a string for the counterparty. Returns base64. */
|
|
401
470
|
async encryptMessage(data) {
|
|
402
471
|
if (!this.session) {
|
|
403
|
-
throw new Error("KeyExchange: cannot encrypt
|
|
472
|
+
throw new Error("KeyExchange: cannot encrypt - session not established");
|
|
404
473
|
}
|
|
474
|
+
const seq = this.session.sendSeq++;
|
|
405
475
|
const pt = textEncoder2.encode(data);
|
|
406
|
-
const ct = await seal(
|
|
407
|
-
this.session.key,
|
|
408
|
-
this.session.sendDir,
|
|
409
|
-
this.session.sendSeq,
|
|
410
|
-
this.session.htx,
|
|
411
|
-
pt
|
|
412
|
-
);
|
|
413
|
-
this.session.sendSeq++;
|
|
476
|
+
const ct = await seal(this.session.key, this.session.sendDir, seq, this.session.htx, pt);
|
|
414
477
|
return toBase64(ct);
|
|
415
478
|
}
|
|
416
479
|
/** Decrypt a base64 ciphertext from the counterparty. */
|
|
417
480
|
async decryptMessage(b64) {
|
|
418
481
|
if (!this.session) {
|
|
419
|
-
throw new Error("KeyExchange: cannot decrypt
|
|
482
|
+
throw new Error("KeyExchange: cannot decrypt - session not established");
|
|
420
483
|
}
|
|
421
484
|
const ct = fromBase64(b64);
|
|
422
485
|
const pt = await open(
|
|
@@ -442,12 +505,17 @@ var KeyExchange = class extends EventEmitter {
|
|
|
442
505
|
this.keysExchanged = false;
|
|
443
506
|
this.awaitingSynAck = false;
|
|
444
507
|
this.awaitingAck = false;
|
|
508
|
+
this.lastAck = null;
|
|
445
509
|
this.step = "key_handshake_SYN" /* SYN */;
|
|
446
510
|
if (emit) this.emit("step_change", this.step);
|
|
447
511
|
}
|
|
448
512
|
areKeysExchanged() {
|
|
449
513
|
return this.keysExchanged;
|
|
450
514
|
}
|
|
515
|
+
/** The ACK produced for the current handshake, for duplicate-SYNACK replies. */
|
|
516
|
+
getLastAck() {
|
|
517
|
+
return this.lastAck;
|
|
518
|
+
}
|
|
451
519
|
getSession() {
|
|
452
520
|
return this.session;
|
|
453
521
|
}
|
|
@@ -553,8 +621,14 @@ var SocketClient = class extends EventEmitter2 {
|
|
|
553
621
|
this.emit("message", data);
|
|
554
622
|
});
|
|
555
623
|
this.socket.on("participants_changed", (data) => {
|
|
556
|
-
|
|
557
|
-
|
|
624
|
+
if (typeof data !== "object" || data === null) return;
|
|
625
|
+
const rec = { ...data };
|
|
626
|
+
const event = typeof rec.event === "string" ? rec.event : "";
|
|
627
|
+
log("Socket", `Participants changed: ${event}`);
|
|
628
|
+
this.emit("participants_changed", {
|
|
629
|
+
event,
|
|
630
|
+
...typeof rec.clientType === "string" ? { clientType: rec.clientType } : {}
|
|
631
|
+
});
|
|
558
632
|
});
|
|
559
633
|
this.socket.on("connect_error", (err) => {
|
|
560
634
|
warn("Socket", `Connection error: ${err.message}`);
|
|
@@ -567,7 +641,7 @@ var SocketClient = class extends EventEmitter2 {
|
|
|
567
641
|
* it doesn't fire after the fact.
|
|
568
642
|
*/
|
|
569
643
|
settlePendingJoin(channelId, outcome) {
|
|
570
|
-
if (
|
|
644
|
+
if (this.pendingJoin?.channelId !== channelId) return;
|
|
571
645
|
clearTimeout(this.pendingJoin.watchdog);
|
|
572
646
|
const pending = this.pendingJoin;
|
|
573
647
|
this.pendingJoin = null;
|
|
@@ -634,14 +708,14 @@ var SocketClient = class extends EventEmitter2 {
|
|
|
634
708
|
if (response.success) {
|
|
635
709
|
log("Socket", `Joined channel ${channelId}`);
|
|
636
710
|
resolve({
|
|
637
|
-
bufferedMessages: response.bufferedMessages
|
|
711
|
+
bufferedMessages: response.bufferedMessages ?? [],
|
|
638
712
|
channelPublicKey: response.channelPublicKey ?? null,
|
|
639
|
-
participants: response.participants
|
|
713
|
+
participants: response.participants ?? [],
|
|
640
714
|
terminated: response.terminated === true
|
|
641
715
|
});
|
|
642
716
|
} else {
|
|
643
717
|
error("Socket", `Failed to join channel: ${response.error}`);
|
|
644
|
-
reject(new Error(response.error
|
|
718
|
+
reject(new Error(response.error ?? "Failed to join channel"));
|
|
645
719
|
}
|
|
646
720
|
}
|
|
647
721
|
);
|
|
@@ -664,7 +738,7 @@ var SocketClient = class extends EventEmitter2 {
|
|
|
664
738
|
if (response?.success) {
|
|
665
739
|
resolve({ success: true, buffered: response.buffered });
|
|
666
740
|
} else {
|
|
667
|
-
reject(new Error(response?.error
|
|
741
|
+
reject(new Error(response?.error ?? "Failed to send message"));
|
|
668
742
|
}
|
|
669
743
|
}
|
|
670
744
|
);
|
|
@@ -720,26 +794,26 @@ var DECODE = (() => {
|
|
|
720
794
|
})();
|
|
721
795
|
function decodeChar(charCode) {
|
|
722
796
|
if (charCode < 0 || charCode >= 128) return -1;
|
|
723
|
-
return DECODE[charCode];
|
|
797
|
+
return DECODE[charCode] ?? -1;
|
|
724
798
|
}
|
|
725
799
|
function base45Encode(bytes) {
|
|
726
800
|
const n = bytes.length;
|
|
727
801
|
let out = "";
|
|
728
802
|
let i = 0;
|
|
729
803
|
while (i + 2 <= n) {
|
|
730
|
-
const v = bytes[i] << 8 | bytes[i + 1];
|
|
804
|
+
const v = (bytes[i] ?? 0) << 8 | (bytes[i + 1] ?? 0);
|
|
731
805
|
const c2 = Math.floor(v / 2025);
|
|
732
806
|
const r = v - c2 * 2025;
|
|
733
807
|
const c1 = Math.floor(r / 45);
|
|
734
808
|
const c0 = r - c1 * 45;
|
|
735
|
-
out += ALPHABET[c0]
|
|
809
|
+
out += `${ALPHABET[c0] ?? ""}${ALPHABET[c1] ?? ""}${ALPHABET[c2] ?? ""}`;
|
|
736
810
|
i += 2;
|
|
737
811
|
}
|
|
738
812
|
if (i < n) {
|
|
739
|
-
const v = bytes[i];
|
|
813
|
+
const v = bytes[i] ?? 0;
|
|
740
814
|
const c1 = Math.floor(v / 45);
|
|
741
815
|
const c0 = v - c1 * 45;
|
|
742
|
-
out += ALPHABET[c0]
|
|
816
|
+
out += `${ALPHABET[c0] ?? ""}${ALPHABET[c1] ?? ""}`;
|
|
743
817
|
}
|
|
744
818
|
return out;
|
|
745
819
|
}
|
|
@@ -788,29 +862,18 @@ var FP_LABEL = new TextEncoder().encode("pq-fp/v2");
|
|
|
788
862
|
var CID_LEN = 16;
|
|
789
863
|
var FP_LEN = 32;
|
|
790
864
|
var BLOB_LEN = 4 + CID_LEN + FP_LEN;
|
|
791
|
-
function bs2(u) {
|
|
792
|
-
return u;
|
|
793
|
-
}
|
|
794
865
|
async function computeFingerprint(cid, pk) {
|
|
795
866
|
if (cid.length !== CID_LEN) {
|
|
796
867
|
throw new Error(`qrUri: cid must be ${CID_LEN} bytes`);
|
|
797
868
|
}
|
|
798
|
-
const c = globalThis.crypto;
|
|
799
|
-
if (!c || !c.subtle) {
|
|
800
|
-
throw new Error("qrUri: WebCrypto SubtleCrypto is not available");
|
|
801
|
-
}
|
|
802
869
|
const buf = new Uint8Array(FP_LABEL.length + cid.length + pk.length);
|
|
803
870
|
buf.set(FP_LABEL, 0);
|
|
804
871
|
buf.set(cid, FP_LABEL.length);
|
|
805
872
|
buf.set(pk, FP_LABEL.length + cid.length);
|
|
806
|
-
|
|
807
|
-
return new Uint8Array(digest);
|
|
873
|
+
return sha256(buf);
|
|
808
874
|
}
|
|
809
875
|
function fingerprintEquals(a, b) {
|
|
810
|
-
|
|
811
|
-
let diff = 0;
|
|
812
|
-
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
813
|
-
return diff === 0;
|
|
876
|
+
return constantTimeEquals(a, b);
|
|
814
877
|
}
|
|
815
878
|
async function generateConnectionURI(cid, pk, relayUrl) {
|
|
816
879
|
if (cid.length !== CID_LEN) {
|
|
@@ -849,7 +912,7 @@ async function parseConnectionURI(uri) {
|
|
|
849
912
|
}
|
|
850
913
|
if (params.has("channelId") || params.has("pubKey")) {
|
|
851
914
|
throw new Error(
|
|
852
|
-
"qrUri: legacy v1 URI detected
|
|
915
|
+
"qrUri: legacy v1 URI detected - this wallet and this dApp must both run protocol v2"
|
|
853
916
|
);
|
|
854
917
|
}
|
|
855
918
|
const q = params.get("q");
|
|
@@ -865,7 +928,7 @@ async function parseConnectionURI(uri) {
|
|
|
865
928
|
}
|
|
866
929
|
if (blob.length !== BLOB_LEN) {
|
|
867
930
|
if (blob.length === 1208 && startsWith(blob, PQP1_MAGIC)) {
|
|
868
|
-
throw new Error("qrUri: legacy PQP1 URI detected
|
|
931
|
+
throw new Error("qrUri: legacy PQP1 URI detected - regenerate the QR with a v2.0+ dApp SDK");
|
|
869
932
|
}
|
|
870
933
|
throw new Error(`qrUri: expected ${BLOB_LEN}-byte blob, got ${blob.length}`);
|
|
871
934
|
}
|
|
@@ -874,8 +937,8 @@ async function parseConnectionURI(uri) {
|
|
|
874
937
|
}
|
|
875
938
|
const cid = blob.slice(4, 4 + CID_LEN);
|
|
876
939
|
const fp = blob.slice(4 + CID_LEN, 4 + CID_LEN + FP_LEN);
|
|
877
|
-
const
|
|
878
|
-
return { cid, fp, relayUrl };
|
|
940
|
+
const r = params.get("r");
|
|
941
|
+
return { cid, fp, relayUrl: r === null || r === "" ? void 0 : r };
|
|
879
942
|
}
|
|
880
943
|
function cidToString(cid) {
|
|
881
944
|
if (cid.length !== CID_LEN) {
|
|
@@ -883,7 +946,7 @@ function cidToString(cid) {
|
|
|
883
946
|
}
|
|
884
947
|
let hex = "";
|
|
885
948
|
for (let i = 0; i < CID_LEN; i++) {
|
|
886
|
-
hex += cid[i].toString(16).padStart(2, "0");
|
|
949
|
+
hex += (cid[i] ?? 0).toString(16).padStart(2, "0");
|
|
887
950
|
}
|
|
888
951
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
889
952
|
}
|
|
@@ -899,15 +962,79 @@ function cidFromString(s) {
|
|
|
899
962
|
return out;
|
|
900
963
|
}
|
|
901
964
|
function cidRandom() {
|
|
902
|
-
|
|
903
|
-
if (!c || typeof c.getRandomValues !== "function") {
|
|
904
|
-
throw new Error("qrUri: crypto.getRandomValues is not available");
|
|
905
|
-
}
|
|
906
|
-
return c.getRandomValues(new Uint8Array(CID_LEN));
|
|
965
|
+
return randomBytes(CID_LEN);
|
|
907
966
|
}
|
|
908
967
|
|
|
909
968
|
// src/ConnectionManager.ts
|
|
910
969
|
var DAPP_PARTICIPANT_CONFLICT_ERROR_MSG = "dapp participant is already connected";
|
|
970
|
+
function isRecord(v) {
|
|
971
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
972
|
+
}
|
|
973
|
+
function isStringArray(v) {
|
|
974
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
975
|
+
}
|
|
976
|
+
function isRelayMessage(v) {
|
|
977
|
+
return isRecord(v) && typeof v.id === "string" && (v.clientType === "dapp" || v.clientType === "wallet") && "message" in v && (typeof v.message === "string" || typeof v.message === "object" && v.message !== null);
|
|
978
|
+
}
|
|
979
|
+
var MESSAGE_TYPE_BY_VALUE = Object.fromEntries(
|
|
980
|
+
Object.values(MessageType).map((m) => [m, m])
|
|
981
|
+
);
|
|
982
|
+
function parseJsonRpcResponse(msg) {
|
|
983
|
+
if (typeof msg.id !== "string" && typeof msg.id !== "number") return null;
|
|
984
|
+
const out = {
|
|
985
|
+
jsonrpc: typeof msg.jsonrpc === "string" ? msg.jsonrpc : "2.0",
|
|
986
|
+
id: msg.id
|
|
987
|
+
};
|
|
988
|
+
if ("result" in msg) out.result = msg.result;
|
|
989
|
+
if (msg.error !== void 0) {
|
|
990
|
+
if (!isRecord(msg.error) || typeof msg.error.message !== "string") return null;
|
|
991
|
+
out.error = {
|
|
992
|
+
code: typeof msg.error.code === "number" ? msg.error.code : -32e3,
|
|
993
|
+
message: msg.error.message,
|
|
994
|
+
data: msg.error.data
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
return out;
|
|
998
|
+
}
|
|
999
|
+
function parsePersistedKex(v) {
|
|
1000
|
+
if (!isRecord(v)) return null;
|
|
1001
|
+
const { cid, kAeadRaw, htx, sendDir, recvDir, sendSeq, recvSeq } = v;
|
|
1002
|
+
if (typeof cid !== "string" || typeof kAeadRaw !== "string" || typeof htx !== "string" || typeof sendDir !== "string" || typeof recvDir !== "string" || typeof sendSeq !== "number" || !Number.isInteger(sendSeq) || sendSeq < 0 || typeof recvSeq !== "number" || !Number.isInteger(recvSeq) || recvSeq < 0) {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
return { cid, kAeadRaw, htx, sendDir, recvDir, sendSeq, recvSeq };
|
|
1006
|
+
}
|
|
1007
|
+
function parseDAppMetadata(v) {
|
|
1008
|
+
if (!isRecord(v) || typeof v.name !== "string" || typeof v.url !== "string") return null;
|
|
1009
|
+
const meta = { name: v.name, url: v.url };
|
|
1010
|
+
if (typeof v.icon === "string") meta.icon = v.icon;
|
|
1011
|
+
if (typeof v.redirectUrl === "string") meta.redirectUrl = v.redirectUrl;
|
|
1012
|
+
return meta;
|
|
1013
|
+
}
|
|
1014
|
+
function parseStoredSession(raw) {
|
|
1015
|
+
let v;
|
|
1016
|
+
try {
|
|
1017
|
+
v = JSON.parse(raw);
|
|
1018
|
+
} catch {
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
if (!isRecord(v) || v.version !== 3) return null;
|
|
1022
|
+
if (typeof v.channelId !== "string" || typeof v.chainId !== "string") return null;
|
|
1023
|
+
if (typeof v.createdAt !== "number" || typeof v.lastActivity !== "number") return null;
|
|
1024
|
+
const keyExchange = parsePersistedKex(v.keyExchange);
|
|
1025
|
+
const dappMetadata = parseDAppMetadata(v.dappMetadata);
|
|
1026
|
+
if (!keyExchange || !dappMetadata || !isStringArray(v.connectedAccounts)) return null;
|
|
1027
|
+
return {
|
|
1028
|
+
version: 3,
|
|
1029
|
+
channelId: v.channelId,
|
|
1030
|
+
keyExchange,
|
|
1031
|
+
dappMetadata,
|
|
1032
|
+
connectedAccounts: v.connectedAccounts,
|
|
1033
|
+
chainId: v.chainId,
|
|
1034
|
+
createdAt: v.createdAt,
|
|
1035
|
+
lastActivity: v.lastActivity
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
911
1038
|
var TERMINATE_SEND_TIMEOUT_MS = 800;
|
|
912
1039
|
var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
913
1040
|
constructor(options) {
|
|
@@ -921,10 +1048,11 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
921
1048
|
this.walletPresent = false;
|
|
922
1049
|
this.pendingRestore = null;
|
|
923
1050
|
this.messageQueue = Promise.resolve();
|
|
1051
|
+
this.outboundQueue = Promise.resolve();
|
|
924
1052
|
this.dappMetadata = options.dappMetadata;
|
|
925
|
-
this.relayUrl = options.relayUrl
|
|
926
|
-
this.chainId = options.chainId
|
|
927
|
-
this.storageKey = options.storageKey
|
|
1053
|
+
this.relayUrl = options.relayUrl ?? DEFAULT_RELAY_URL;
|
|
1054
|
+
this.chainId = options.chainId ?? "0x0";
|
|
1055
|
+
this.storageKey = options.storageKey ?? `${STORAGE_KEY_PREFIX}:session`;
|
|
928
1056
|
const stored = this.readStoredSession();
|
|
929
1057
|
if (stored) {
|
|
930
1058
|
this.channelId = stored.channelId;
|
|
@@ -934,7 +1062,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
934
1062
|
this.pendingRestore = stored;
|
|
935
1063
|
log("ConnectionManager", `Found persisted session for channel ${this.channelId}`);
|
|
936
1064
|
} else {
|
|
937
|
-
this.channelId =
|
|
1065
|
+
this.channelId = randomUuid();
|
|
938
1066
|
}
|
|
939
1067
|
this.socketClient = new SocketClient(this.relayUrl, "dapp");
|
|
940
1068
|
this.setupSocketListeners();
|
|
@@ -1046,7 +1174,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1046
1174
|
this.setupKeyExchangeListeners();
|
|
1047
1175
|
}
|
|
1048
1176
|
const pk = this.keyExchange.initiate();
|
|
1049
|
-
this.channelId =
|
|
1177
|
+
this.channelId = randomUuid();
|
|
1050
1178
|
this.socketClient.setPublicKey(toBase64(pk));
|
|
1051
1179
|
this.socketClient.connect();
|
|
1052
1180
|
try {
|
|
@@ -1057,7 +1185,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1057
1185
|
"ConnectionManager",
|
|
1058
1186
|
"Channel already has an active dApp participant. Rotating to a fresh channel."
|
|
1059
1187
|
);
|
|
1060
|
-
this.channelId =
|
|
1188
|
+
this.channelId = randomUuid();
|
|
1061
1189
|
return this.getConnectionURI(false);
|
|
1062
1190
|
}
|
|
1063
1191
|
this.setStatus("disconnected" /* DISCONNECTED */);
|
|
@@ -1227,8 +1355,8 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1227
1355
|
try {
|
|
1228
1356
|
const raw = localStorage.getItem(this.storageKey);
|
|
1229
1357
|
if (!raw) return false;
|
|
1230
|
-
const session =
|
|
1231
|
-
if (session
|
|
1358
|
+
const session = parseStoredSession(raw);
|
|
1359
|
+
if (!session) return false;
|
|
1232
1360
|
return Date.now() - session.createdAt <= SESSION_TTL_MS;
|
|
1233
1361
|
} catch {
|
|
1234
1362
|
return false;
|
|
@@ -1243,8 +1371,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1243
1371
|
*/
|
|
1244
1372
|
async flushTerminate() {
|
|
1245
1373
|
if (!this.keyExchange?.areKeysExchanged()) return;
|
|
1246
|
-
const send = this.sendEncrypted({ type: "terminate" /* TERMINATE */ }).catch(() =>
|
|
1247
|
-
});
|
|
1374
|
+
const send = this.sendEncrypted({ type: "terminate" /* TERMINATE */ }).catch(() => void 0);
|
|
1248
1375
|
const timeout = new Promise((resolve) => setTimeout(resolve, TERMINATE_SEND_TIMEOUT_MS));
|
|
1249
1376
|
await Promise.race([send, timeout]);
|
|
1250
1377
|
}
|
|
@@ -1263,7 +1390,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1263
1390
|
this.clearSession();
|
|
1264
1391
|
this.connectedAccounts = [];
|
|
1265
1392
|
this.pendingRestore = null;
|
|
1266
|
-
this.channelId =
|
|
1393
|
+
this.channelId = randomUuid();
|
|
1267
1394
|
this.keyExchange = null;
|
|
1268
1395
|
this.socketClient = new SocketClient(this.relayUrl, "dapp");
|
|
1269
1396
|
this.setupSocketListeners();
|
|
@@ -1284,7 +1411,13 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1284
1411
|
}
|
|
1285
1412
|
// ── Internals ──────────────────────────────────────────────
|
|
1286
1413
|
enqueueRelayMessage(data) {
|
|
1287
|
-
|
|
1414
|
+
if (!isRelayMessage(data)) {
|
|
1415
|
+
warn("ConnectionManager", "Dropping malformed relay envelope");
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
this.messageQueue = this.messageQueue.then(() => this.handleRelayMessage(data)).catch((err) => {
|
|
1419
|
+
error("ConnectionManager", "messageQueue handler error:", err);
|
|
1420
|
+
});
|
|
1288
1421
|
}
|
|
1289
1422
|
async handleRelayMessage(data) {
|
|
1290
1423
|
if (data.clientType === "wallet") {
|
|
@@ -1296,22 +1429,35 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1296
1429
|
}
|
|
1297
1430
|
}
|
|
1298
1431
|
const message = data.message;
|
|
1299
|
-
if (
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1432
|
+
if (isRecord(message)) {
|
|
1433
|
+
if (message.type === "key_handshake_SYNACK" /* SYNACK */) {
|
|
1434
|
+
if (typeof message.ct === "string" && typeof message.c0 === "string") {
|
|
1435
|
+
await this.handleSynAck({
|
|
1436
|
+
type: "key_handshake_SYNACK" /* SYNACK */,
|
|
1437
|
+
ct: message.ct,
|
|
1438
|
+
c0: message.c0,
|
|
1439
|
+
v: typeof message.v === "number" ? message.v : 0
|
|
1440
|
+
});
|
|
1441
|
+
} else {
|
|
1442
|
+
warn("ConnectionManager", "Dropping malformed SYNACK");
|
|
1443
|
+
}
|
|
1303
1444
|
return;
|
|
1304
1445
|
}
|
|
1305
|
-
if (
|
|
1306
|
-
warn("ConnectionManager", `Unexpected ${
|
|
1446
|
+
if (message.type === "key_handshake_SYN" /* SYN */ || message.type === "key_handshake_ACK" /* ACK */) {
|
|
1447
|
+
warn("ConnectionManager", `Unexpected ${message.type} on dApp side, ignoring`);
|
|
1307
1448
|
return;
|
|
1308
1449
|
}
|
|
1309
1450
|
}
|
|
1310
1451
|
if (typeof message === "string" && this.keyExchange?.areKeysExchanged()) {
|
|
1311
1452
|
try {
|
|
1312
1453
|
const decrypted = await this.keyExchange.decryptMessage(message);
|
|
1454
|
+
await this.persistSession();
|
|
1313
1455
|
const parsed = JSON.parse(decrypted);
|
|
1314
|
-
|
|
1456
|
+
if (isRecord(parsed)) {
|
|
1457
|
+
this.handleDecryptedMessage(parsed);
|
|
1458
|
+
} else {
|
|
1459
|
+
warn("ConnectionManager", "Dropping non-object decrypted payload");
|
|
1460
|
+
}
|
|
1315
1461
|
} catch (err) {
|
|
1316
1462
|
error("ConnectionManager", "Failed to decrypt message:", err);
|
|
1317
1463
|
}
|
|
@@ -1331,16 +1477,21 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1331
1477
|
}
|
|
1332
1478
|
if (response) {
|
|
1333
1479
|
this.sendPlaintext(response);
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const cachedAck = this.keyExchange.getLastAck();
|
|
1483
|
+
if (this.keyExchange.areKeysExchanged() && cachedAck) {
|
|
1484
|
+
log("ConnectionManager", "Duplicate SYNACK after handshake; re-sending cached ACK");
|
|
1485
|
+
this.sendPlaintext(cachedAck);
|
|
1334
1486
|
}
|
|
1335
1487
|
}
|
|
1336
1488
|
handleDecryptedMessage(msg) {
|
|
1337
1489
|
this.clearUnresponsiveTimer();
|
|
1338
|
-
const type = msg.type;
|
|
1339
|
-
switch (type) {
|
|
1490
|
+
const type = typeof msg.type === "string" ? msg.type : "";
|
|
1491
|
+
switch (MESSAGE_TYPE_BY_VALUE[type]) {
|
|
1340
1492
|
case "wallet_info" /* WALLET_INFO */: {
|
|
1341
|
-
const
|
|
1342
|
-
const
|
|
1343
|
-
const nextChainId = info.chainId || this.chainId;
|
|
1493
|
+
const nextAccounts = isStringArray(msg.accounts) ? msg.accounts : [];
|
|
1494
|
+
const nextChainId = typeof msg.chainId === "string" && msg.chainId ? msg.chainId : this.chainId;
|
|
1344
1495
|
const accountsChanged = !this.areArraysEqual(this.connectedAccounts, nextAccounts);
|
|
1345
1496
|
const chainChanged = this.chainId !== nextChainId;
|
|
1346
1497
|
this.connectedAccounts = nextAccounts;
|
|
@@ -1355,7 +1506,11 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1355
1506
|
break;
|
|
1356
1507
|
}
|
|
1357
1508
|
case "jsonrpc" /* JSONRPC */: {
|
|
1358
|
-
const response = msg;
|
|
1509
|
+
const response = parseJsonRpcResponse(msg);
|
|
1510
|
+
if (!response) {
|
|
1511
|
+
warn("ConnectionManager", "Dropping malformed JSON-RPC response");
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1359
1514
|
this.emit("jsonrpc_response", response);
|
|
1360
1515
|
break;
|
|
1361
1516
|
}
|
|
@@ -1377,11 +1532,28 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1377
1532
|
error("ConnectionManager", "Failed to send plaintext:", err);
|
|
1378
1533
|
});
|
|
1379
1534
|
}
|
|
1380
|
-
|
|
1535
|
+
/**
|
|
1536
|
+
* Serialize every encrypt+persist+send on a single outbound queue.
|
|
1537
|
+
* Callers like sendJsonRpc() are fire-and-forget, so without the queue two
|
|
1538
|
+
* rapid provider.request() calls would interleave inside encryptMessage()
|
|
1539
|
+
* across its await and could complete out of order; the receiver's
|
|
1540
|
+
* contiguous-seq check would then drop the late one. (Nonce reuse itself is
|
|
1541
|
+
* already prevented by the synchronous seq reservation in KeyExchange.)
|
|
1542
|
+
*/
|
|
1543
|
+
sendEncrypted(message) {
|
|
1544
|
+
const task = this.outboundQueue.then(() => this.sendEncryptedNow(message));
|
|
1545
|
+
this.outboundQueue = task.then(
|
|
1546
|
+
() => void 0,
|
|
1547
|
+
() => void 0
|
|
1548
|
+
);
|
|
1549
|
+
return task;
|
|
1550
|
+
}
|
|
1551
|
+
async sendEncryptedNow(message) {
|
|
1381
1552
|
if (!this.keyExchange?.areKeysExchanged()) {
|
|
1382
1553
|
throw new Error("sendEncrypted: not connected");
|
|
1383
1554
|
}
|
|
1384
1555
|
const encrypted = await this.keyExchange.encryptMessage(JSON.stringify(message));
|
|
1556
|
+
await this.persistSession();
|
|
1385
1557
|
await this.socketClient.sendMessage({
|
|
1386
1558
|
id: this.channelId,
|
|
1387
1559
|
clientType: "dapp",
|
|
@@ -1422,7 +1594,7 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1422
1594
|
const persistedKex = await this.keyExchange.exportPersisted();
|
|
1423
1595
|
if (!persistedKex) return;
|
|
1424
1596
|
const session = {
|
|
1425
|
-
version:
|
|
1597
|
+
version: 3,
|
|
1426
1598
|
channelId: this.channelId,
|
|
1427
1599
|
keyExchange: persistedKex,
|
|
1428
1600
|
dappMetadata: this.dappMetadata,
|
|
@@ -1442,13 +1614,13 @@ var _ConnectionManager = class _ConnectionManager extends EventEmitter3 {
|
|
|
1442
1614
|
try {
|
|
1443
1615
|
const raw = localStorage.getItem(this.storageKey);
|
|
1444
1616
|
if (!raw) return null;
|
|
1445
|
-
const session =
|
|
1446
|
-
if (session
|
|
1447
|
-
log("ConnectionManager", "Dropping legacy
|
|
1617
|
+
const session = parseStoredSession(raw);
|
|
1618
|
+
if (!session) {
|
|
1619
|
+
log("ConnectionManager", "Dropping legacy or malformed session from storage");
|
|
1448
1620
|
localStorage.removeItem(this.storageKey);
|
|
1449
1621
|
return null;
|
|
1450
1622
|
}
|
|
1451
|
-
if (
|
|
1623
|
+
if (Date.now() - session.createdAt > SESSION_TTL_MS) {
|
|
1452
1624
|
localStorage.removeItem(this.storageKey);
|
|
1453
1625
|
return null;
|
|
1454
1626
|
}
|
|
@@ -1478,22 +1650,13 @@ function getAppStoreUrl() {
|
|
|
1478
1650
|
}
|
|
1479
1651
|
|
|
1480
1652
|
// src/QRLConnectProvider.ts
|
|
1481
|
-
var requestCounter = 0;
|
|
1482
1653
|
var QRL_CONNECT_PROVIDER_INFO = {
|
|
1483
|
-
name: "MyQRLWallet
|
|
1654
|
+
name: "MyQRLWallet",
|
|
1484
1655
|
rdns: "com.qrlwallet.connect",
|
|
1485
1656
|
icon: "data:image/png;base64,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"
|
|
1486
1657
|
};
|
|
1487
1658
|
var EIP6963_ANNOUNCE_EVENT = "eip6963:announceProvider";
|
|
1488
1659
|
var EIP6963_REQUEST_EVENT = "eip6963:requestProvider";
|
|
1489
|
-
function generateUuid() {
|
|
1490
|
-
if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
|
|
1491
|
-
return globalThis.crypto.randomUUID();
|
|
1492
|
-
}
|
|
1493
|
-
const r = (n) => Math.floor(Math.random() * n);
|
|
1494
|
-
const hex = (n, len) => n.toString(16).padStart(len, "0");
|
|
1495
|
-
return `${hex(r(4294967296), 8)}-${hex(r(65536), 4)}-${hex(16384 | r(4096), 4)}-${hex(32768 | r(16384), 4)}-${hex(r(4294967296), 8)}${hex(r(65536), 4)}`;
|
|
1496
|
-
}
|
|
1497
1660
|
var QRLConnectProvider = class extends EventEmitter4 {
|
|
1498
1661
|
constructor(options) {
|
|
1499
1662
|
super();
|
|
@@ -1502,6 +1665,12 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
1502
1665
|
this.eip6963RequestListener = null;
|
|
1503
1666
|
this.resumeListener = null;
|
|
1504
1667
|
this.resumeDebounce = null;
|
|
1668
|
+
// Random per-instance prefix keeps request ids unique across page loads.
|
|
1669
|
+
// A bare counter restarts at 1 on reload, and the relay buffers messages
|
|
1670
|
+
// for 5 minutes, so a stale buffered response could otherwise be matched
|
|
1671
|
+
// to a fresh request that drew the same small id.
|
|
1672
|
+
this.requestIdPrefix = randomUuid().slice(0, 8);
|
|
1673
|
+
this.requestCounter = 0;
|
|
1505
1674
|
this.isQRLConnect = true;
|
|
1506
1675
|
this.options = options;
|
|
1507
1676
|
if (options.debug) {
|
|
@@ -1515,7 +1684,7 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
1515
1684
|
});
|
|
1516
1685
|
this.setupConnectionListeners();
|
|
1517
1686
|
if (options.autoReconnect !== false) {
|
|
1518
|
-
this.connectionManager.reconnect();
|
|
1687
|
+
void this.connectionManager.reconnect();
|
|
1519
1688
|
}
|
|
1520
1689
|
this.setupResumeListeners();
|
|
1521
1690
|
if (options.announceProvider !== false) {
|
|
@@ -1529,7 +1698,7 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
1529
1698
|
const overrides = this.options.providerInfo ?? {};
|
|
1530
1699
|
this.eip6963Detail = Object.freeze({
|
|
1531
1700
|
info: Object.freeze({
|
|
1532
|
-
uuid:
|
|
1701
|
+
uuid: randomUuid(),
|
|
1533
1702
|
name: overrides.name ?? QRL_CONNECT_PROVIDER_INFO.name,
|
|
1534
1703
|
icon: overrides.icon ?? QRL_CONNECT_PROVIDER_INFO.icon,
|
|
1535
1704
|
rdns: overrides.rdns ?? QRL_CONNECT_PROVIDER_INFO.rdns
|
|
@@ -1682,7 +1851,7 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
1682
1851
|
if (this.connectionManager.getStatus() !== "connected" /* CONNECTED */) {
|
|
1683
1852
|
throw new Error("Not connected to QRL Wallet");
|
|
1684
1853
|
}
|
|
1685
|
-
const id = ++requestCounter
|
|
1854
|
+
const id = `${this.requestIdPrefix}-${++this.requestCounter}`;
|
|
1686
1855
|
return new Promise((resolve, reject) => {
|
|
1687
1856
|
const pending = {
|
|
1688
1857
|
id,
|
|
@@ -1763,7 +1932,7 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
1763
1932
|
/**
|
|
1764
1933
|
* Disconnect from wallet and clean up. Returns once the TERMINATE has
|
|
1765
1934
|
* either been flushed to the relay or the 800ms best-effort window has
|
|
1766
|
-
* elapsed
|
|
1935
|
+
* elapsed - the wallet gets an instant disconnect instead of landing in
|
|
1767
1936
|
* its stale-session grace period.
|
|
1768
1937
|
*/
|
|
1769
1938
|
async disconnect() {
|
|
@@ -1783,9 +1952,6 @@ var SCHEME_TAG_MSG = new TextEncoder().encode(SCHEME_VERSION_MSG);
|
|
|
1783
1952
|
var SCHEME_TAG_TYPED = new TextEncoder().encode(SCHEME_VERSION_TYPED);
|
|
1784
1953
|
var DIGEST_LEN = 64;
|
|
1785
1954
|
|
|
1786
|
-
// src/signing/messageDigest.ts
|
|
1787
|
-
import { shake256 } from "@noble/hashes/sha3.js";
|
|
1788
|
-
|
|
1789
1955
|
// src/signing/bytes.ts
|
|
1790
1956
|
function hexToBytes(hex) {
|
|
1791
1957
|
if (typeof hex !== "string") throw new Error("hex must be a string");
|
|
@@ -1827,12 +1993,14 @@ function computeMessageDigest(messageBytes) {
|
|
|
1827
1993
|
if (!(messageBytes instanceof Uint8Array)) {
|
|
1828
1994
|
throw new Error("messageBytes must be a Uint8Array");
|
|
1829
1995
|
}
|
|
1830
|
-
return
|
|
1996
|
+
return shake256Digest(concatBytes(SCHEME_TAG_MSG, messageBytes), DIGEST_LEN);
|
|
1831
1997
|
}
|
|
1832
1998
|
|
|
1833
1999
|
// src/signing/typedData.ts
|
|
1834
|
-
import { shake256 as shake2562 } from "@noble/hashes/sha3.js";
|
|
1835
2000
|
var SLOT = 32;
|
|
2001
|
+
function isMessageObject(v) {
|
|
2002
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2003
|
+
}
|
|
1836
2004
|
var ATOMIC_RE = /^(?:(address|bool|string|bytes)|(u?int)(\d+)|bytes(\d+)|(.+?)\[(\d*)\])$/;
|
|
1837
2005
|
var MAX_TYPE_DEPTH = 12;
|
|
1838
2006
|
function parseFieldType(type, types, depth = 0) {
|
|
@@ -1843,7 +2011,9 @@ function parseFieldType(type, types, depth = 0) {
|
|
|
1843
2011
|
const m = ATOMIC_RE.exec(type);
|
|
1844
2012
|
if (!m) throw new Error(`unknown type: ${type}`);
|
|
1845
2013
|
const [, atomic, intKind, intWidthStr, bytesWidthStr, innerType, sizeStr] = m;
|
|
1846
|
-
if (atomic
|
|
2014
|
+
if (atomic === "address" || atomic === "bool" || atomic === "string" || atomic === "bytes") {
|
|
2015
|
+
return { kind: atomic };
|
|
2016
|
+
}
|
|
1847
2017
|
if (intKind) {
|
|
1848
2018
|
const width = Number(intWidthStr);
|
|
1849
2019
|
if (!Number.isInteger(width) || width < 8 || width > 256 || width % 8 !== 0) {
|
|
@@ -1860,11 +2030,14 @@ function parseFieldType(type, types, depth = 0) {
|
|
|
1860
2030
|
}
|
|
1861
2031
|
if (innerType !== void 0) {
|
|
1862
2032
|
parseFieldType(innerType, types, depth + 1);
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2033
|
+
if (sizeStr) {
|
|
2034
|
+
const size = Number(sizeStr);
|
|
2035
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
2036
|
+
throw new Error(`invalid array size: ${type}`);
|
|
2037
|
+
}
|
|
2038
|
+
return { kind: "array", inner: innerType, size };
|
|
1866
2039
|
}
|
|
1867
|
-
return { kind: "array", inner: innerType
|
|
2040
|
+
return { kind: "array", inner: innerType };
|
|
1868
2041
|
}
|
|
1869
2042
|
throw new Error(`unhandled type: ${type}`);
|
|
1870
2043
|
}
|
|
@@ -1882,7 +2055,9 @@ function collectDependencies(primary, types) {
|
|
|
1882
2055
|
}
|
|
1883
2056
|
if (visited.has(name)) return;
|
|
1884
2057
|
visited.add(name);
|
|
1885
|
-
|
|
2058
|
+
const def = types[name];
|
|
2059
|
+
if (!def) throw new Error(`unknown type: ${name}`);
|
|
2060
|
+
for (const f of def) {
|
|
1886
2061
|
const base = baseTypeName(f.type);
|
|
1887
2062
|
if (Object.prototype.hasOwnProperty.call(types, base)) {
|
|
1888
2063
|
visit(base, [...path, name]);
|
|
@@ -1895,7 +2070,8 @@ function collectDependencies(primary, types) {
|
|
|
1895
2070
|
}
|
|
1896
2071
|
function validateTypeMap(types) {
|
|
1897
2072
|
for (const [name, def] of Object.entries(types)) {
|
|
1898
|
-
|
|
2073
|
+
const defUnknown = def;
|
|
2074
|
+
if (!Array.isArray(defUnknown) || def.length === 0) {
|
|
1899
2075
|
throw new Error(`empty or invalid struct: ${name}`);
|
|
1900
2076
|
}
|
|
1901
2077
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1918,16 +2094,17 @@ function encodeType(primary, types) {
|
|
|
1918
2094
|
const others = [...deps].filter((n) => n !== primary).sort();
|
|
1919
2095
|
return [primary, ...others].map((name) => {
|
|
1920
2096
|
const fields = types[name];
|
|
2097
|
+
if (!fields) throw new Error(`unknown type: ${name}`);
|
|
1921
2098
|
const inner = fields.map((f) => `${f.type} ${f.name}`).join(",");
|
|
1922
2099
|
return `${name}(${inner})`;
|
|
1923
2100
|
}).join("");
|
|
1924
2101
|
}
|
|
1925
2102
|
function typeHash(primary, types) {
|
|
1926
|
-
return
|
|
2103
|
+
return shake256Digest(new TextEncoder().encode(encodeType(primary, types)), DIGEST_LEN);
|
|
1927
2104
|
}
|
|
1928
2105
|
function parseQAddress(addr) {
|
|
1929
2106
|
if (typeof addr !== "string" || !/^Q[0-9a-fA-F]{40}$/.test(addr)) {
|
|
1930
|
-
throw new Error(`invalid Q-address: ${
|
|
2107
|
+
throw new Error(`invalid Q-address: ${addr}`);
|
|
1931
2108
|
}
|
|
1932
2109
|
return hexToBytes("0x" + addr.slice(1).toLowerCase());
|
|
1933
2110
|
}
|
|
@@ -1989,7 +2166,7 @@ function encodeField(type, value, types) {
|
|
|
1989
2166
|
const parsed = parseFieldType(type, types);
|
|
1990
2167
|
switch (parsed.kind) {
|
|
1991
2168
|
case "ref":
|
|
1992
|
-
if (
|
|
2169
|
+
if (!isMessageObject(value)) {
|
|
1993
2170
|
throw new Error(`struct field expects object: ${type}`);
|
|
1994
2171
|
}
|
|
1995
2172
|
return hashStruct(parsed.name, value, types);
|
|
@@ -2007,12 +2184,12 @@ function encodeField(type, value, types) {
|
|
|
2007
2184
|
if (typeof value !== "string") {
|
|
2008
2185
|
throw new Error(`string field expects string: ${typeof value}`);
|
|
2009
2186
|
}
|
|
2010
|
-
return
|
|
2187
|
+
return shake256Digest(new TextEncoder().encode(value), DIGEST_LEN);
|
|
2011
2188
|
case "bytes":
|
|
2012
2189
|
if (typeof value !== "string") {
|
|
2013
2190
|
throw new Error(`bytes field expects 0x-hex string: ${typeof value}`);
|
|
2014
2191
|
}
|
|
2015
|
-
return
|
|
2192
|
+
return shake256Digest(hexToBytes(value), DIGEST_LEN);
|
|
2016
2193
|
case "uintN":
|
|
2017
2194
|
return bigIntToSlot(parseIntValue(value, `uint${parsed.width}`), parsed.width, false);
|
|
2018
2195
|
case "intN":
|
|
@@ -2035,7 +2212,7 @@ function encodeField(type, value, types) {
|
|
|
2035
2212
|
throw new Error(`fixed array ${type} requires length ${parsed.size}, got ${value.length}`);
|
|
2036
2213
|
}
|
|
2037
2214
|
const chunks = value.map((v) => encodeField(parsed.inner, v, types));
|
|
2038
|
-
return
|
|
2215
|
+
return shake256Digest(concatBytesArr(chunks), DIGEST_LEN);
|
|
2039
2216
|
}
|
|
2040
2217
|
}
|
|
2041
2218
|
}
|
|
@@ -2051,7 +2228,7 @@ function hashStruct(primary, data, types) {
|
|
|
2051
2228
|
if (!(f.name in data)) throw new Error(`missing field ${primary}.${f.name}`);
|
|
2052
2229
|
parts.push(encodeField(f.type, data[f.name], types));
|
|
2053
2230
|
}
|
|
2054
|
-
return
|
|
2231
|
+
return shake256Digest(concatBytesArr(parts), DIGEST_LEN);
|
|
2055
2232
|
}
|
|
2056
2233
|
var RESERVED_DOMAIN_FIELDS = {
|
|
2057
2234
|
name: "string",
|
|
@@ -2096,11 +2273,10 @@ function computeTypedDataDigest(payload) {
|
|
|
2096
2273
|
validatePayloadReachability(payload.primaryType, payload.types);
|
|
2097
2274
|
const domainHash = hashStruct("QRLDomain", payload.domain, payload.types);
|
|
2098
2275
|
const messageHash = hashStruct(payload.primaryType, payload.message, payload.types);
|
|
2099
|
-
return
|
|
2276
|
+
return shake256Digest(concatBytes(SCHEME_TAG_TYPED, domainHash, messageHash), DIGEST_LEN);
|
|
2100
2277
|
}
|
|
2101
2278
|
|
|
2102
2279
|
// src/signing/verify.ts
|
|
2103
|
-
import * as mldsa from "@theqrl/mldsa87";
|
|
2104
2280
|
function bytesOrHex(v) {
|
|
2105
2281
|
if (v instanceof Uint8Array) return v;
|
|
2106
2282
|
if (typeof v === "string") return hexToBytes(v);
|
|
@@ -2116,7 +2292,7 @@ function verifyMessage({
|
|
|
2116
2292
|
const pk = bytesOrHex(publicKey);
|
|
2117
2293
|
const msg = bytesOrHex(messageBytes);
|
|
2118
2294
|
const digest = computeMessageDigest(msg);
|
|
2119
|
-
return
|
|
2295
|
+
return mldsaVerify(sig, digest, pk, SCHEME_TAG_MSG);
|
|
2120
2296
|
} catch {
|
|
2121
2297
|
return false;
|
|
2122
2298
|
}
|
|
@@ -2126,7 +2302,7 @@ function verifyTypedData({ signature, publicKey, payload }) {
|
|
|
2126
2302
|
const sig = bytesOrHex(signature);
|
|
2127
2303
|
const pk = bytesOrHex(publicKey);
|
|
2128
2304
|
const digest = computeTypedDataDigest(payload);
|
|
2129
|
-
return
|
|
2305
|
+
return mldsaVerify(sig, digest, pk, SCHEME_TAG_TYPED);
|
|
2130
2306
|
} catch {
|
|
2131
2307
|
return false;
|
|
2132
2308
|
}
|