@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.js
CHANGED
|
@@ -79,68 +79,144 @@ var import_eventemitter34 = __toESM(require("eventemitter3"));
|
|
|
79
79
|
|
|
80
80
|
// src/ConnectionManager.ts
|
|
81
81
|
var import_eventemitter33 = __toESM(require("eventemitter3"));
|
|
82
|
-
var import_uuid = require("uuid");
|
|
83
82
|
|
|
84
83
|
// src/KeyExchange.ts
|
|
85
84
|
var import_eventemitter3 = __toESM(require("eventemitter3"));
|
|
86
85
|
|
|
87
|
-
// src/
|
|
86
|
+
// src/crypto/primitives.ts
|
|
88
87
|
var import_ml_kem = require("@noble/post-quantum/ml-kem.js");
|
|
89
|
-
var
|
|
90
|
-
var
|
|
91
|
-
|
|
92
|
-
var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
|
|
93
|
-
var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
|
|
94
|
-
var ML_KEM_768_CT_LEN = 1088;
|
|
95
|
-
var AEAD_KEY_LEN = 32;
|
|
96
|
-
function subtle() {
|
|
88
|
+
var import_sha3 = require("@noble/hashes/sha3.js");
|
|
89
|
+
var mldsa = __toESM(require("@theqrl/mldsa87"));
|
|
90
|
+
function webCrypto() {
|
|
97
91
|
const c = globalThis.crypto;
|
|
98
|
-
if (!c
|
|
99
|
-
throw new Error("
|
|
92
|
+
if (!c) {
|
|
93
|
+
throw new Error("crypto: WebCrypto is not available in this environment");
|
|
94
|
+
}
|
|
95
|
+
return c;
|
|
96
|
+
}
|
|
97
|
+
function subtle() {
|
|
98
|
+
const c = webCrypto();
|
|
99
|
+
if (!c.subtle) {
|
|
100
|
+
throw new Error("crypto: WebCrypto SubtleCrypto is not available in this environment");
|
|
100
101
|
}
|
|
101
102
|
return c.subtle;
|
|
102
103
|
}
|
|
103
104
|
function bs(u) {
|
|
104
|
-
|
|
105
|
+
if (u.buffer instanceof ArrayBuffer) {
|
|
106
|
+
return new Uint8Array(u.buffer, u.byteOffset, u.byteLength);
|
|
107
|
+
}
|
|
108
|
+
const copy = new Uint8Array(u.byteLength);
|
|
109
|
+
copy.set(u);
|
|
110
|
+
return copy;
|
|
105
111
|
}
|
|
106
|
-
function
|
|
112
|
+
function mlkemKeygen() {
|
|
107
113
|
const { publicKey, secretKey } = import_ml_kem.ml_kem768.keygen();
|
|
108
114
|
return { pk: publicKey, sk: secretKey };
|
|
109
115
|
}
|
|
110
|
-
function
|
|
116
|
+
function mlkemEncaps(pk) {
|
|
111
117
|
const { cipherText, sharedSecret } = import_ml_kem.ml_kem768.encapsulate(pk);
|
|
112
118
|
return { ct: cipherText, ss: sharedSecret };
|
|
113
119
|
}
|
|
114
|
-
function
|
|
120
|
+
function mlkemDecaps(sk, ct) {
|
|
115
121
|
return import_ml_kem.ml_kem768.decapsulate(ct, sk);
|
|
116
122
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return new Uint8Array(await subtle().digest("SHA-256", bs(buf)));
|
|
123
|
+
function shake256Digest(data, dkLen) {
|
|
124
|
+
return (0, import_sha3.shake256)(data, { dkLen });
|
|
120
125
|
}
|
|
121
|
-
async function
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
async function sha256(data) {
|
|
127
|
+
return new Uint8Array(await subtle().digest("SHA-256", bs(data)));
|
|
128
|
+
}
|
|
129
|
+
function mldsaVerify(signature, digest, publicKey, ctx) {
|
|
130
|
+
return mldsa.cryptoSignVerify(signature, digest, publicKey, ctx);
|
|
131
|
+
}
|
|
132
|
+
async function hkdfAesGcmKey(ikm, salt, info) {
|
|
133
|
+
const ikmKey = await subtle().importKey("raw", bs(ikm), "HKDF", false, ["deriveKey"]);
|
|
124
134
|
return subtle().deriveKey(
|
|
125
|
-
{ name: "HKDF", hash: "SHA-256", salt:
|
|
126
|
-
|
|
135
|
+
{ name: "HKDF", hash: "SHA-256", salt: bs(salt), info: bs(info) },
|
|
136
|
+
ikmKey,
|
|
127
137
|
{ name: "AES-GCM", length: 256 },
|
|
128
138
|
true,
|
|
129
139
|
["encrypt", "decrypt"]
|
|
130
140
|
);
|
|
131
141
|
}
|
|
132
|
-
async function
|
|
133
|
-
if (raw.length !== AEAD_KEY_LEN) {
|
|
134
|
-
throw new Error(`PQCrypto: raw AEAD key must be ${AEAD_KEY_LEN} bytes`);
|
|
135
|
-
}
|
|
142
|
+
async function importAesGcmKey(raw) {
|
|
136
143
|
return subtle().importKey("raw", bs(raw), { name: "AES-GCM", length: 256 }, true, [
|
|
137
144
|
"encrypt",
|
|
138
145
|
"decrypt"
|
|
139
146
|
]);
|
|
140
147
|
}
|
|
148
|
+
async function exportAesGcmKey(key) {
|
|
149
|
+
return new Uint8Array(await subtle().exportKey("raw", key));
|
|
150
|
+
}
|
|
151
|
+
async function aesGcmEncrypt(key, iv, aad2, plaintext) {
|
|
152
|
+
const ct = await subtle().encrypt(
|
|
153
|
+
{ name: "AES-GCM", iv: bs(iv), additionalData: bs(aad2) },
|
|
154
|
+
key,
|
|
155
|
+
bs(plaintext)
|
|
156
|
+
);
|
|
157
|
+
return new Uint8Array(ct);
|
|
158
|
+
}
|
|
159
|
+
async function aesGcmDecrypt(key, iv, aad2, ciphertext) {
|
|
160
|
+
const pt = await subtle().decrypt(
|
|
161
|
+
{ name: "AES-GCM", iv: bs(iv), additionalData: bs(aad2) },
|
|
162
|
+
key,
|
|
163
|
+
bs(ciphertext)
|
|
164
|
+
);
|
|
165
|
+
return new Uint8Array(pt);
|
|
166
|
+
}
|
|
167
|
+
function randomBytes(n) {
|
|
168
|
+
const c = webCrypto();
|
|
169
|
+
if (typeof c.getRandomValues !== "function") {
|
|
170
|
+
throw new Error("crypto: crypto.getRandomValues is not available");
|
|
171
|
+
}
|
|
172
|
+
return c.getRandomValues(new Uint8Array(n));
|
|
173
|
+
}
|
|
174
|
+
function randomUuid() {
|
|
175
|
+
const b = randomBytes(16);
|
|
176
|
+
b[6] = (b[6] ?? 0) & 15 | 64;
|
|
177
|
+
b[8] = (b[8] ?? 0) & 63 | 128;
|
|
178
|
+
const hex = Array.from(b, (v) => v.toString(16).padStart(2, "0")).join("");
|
|
179
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
180
|
+
}
|
|
181
|
+
function constantTimeEquals(a, b) {
|
|
182
|
+
if (a.length !== b.length) return false;
|
|
183
|
+
let d = 0;
|
|
184
|
+
for (let i = 0; i < a.length; i++) d |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
185
|
+
return d === 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/PQCrypto.ts
|
|
189
|
+
var textEncoder = new TextEncoder();
|
|
190
|
+
var LABEL = textEncoder.encode("pq-pair/v1");
|
|
191
|
+
var LABEL_AEAD_SUFFIX = textEncoder.encode(" aead");
|
|
192
|
+
var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
|
|
193
|
+
var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
|
|
194
|
+
var ML_KEM_768_CT_LEN = 1088;
|
|
195
|
+
var AEAD_KEY_LEN = 32;
|
|
196
|
+
function kemKeygen() {
|
|
197
|
+
return mlkemKeygen();
|
|
198
|
+
}
|
|
199
|
+
function kemEncaps(pk) {
|
|
200
|
+
return mlkemEncaps(pk);
|
|
201
|
+
}
|
|
202
|
+
function kemDecaps(sk, ct) {
|
|
203
|
+
return mlkemDecaps(sk, ct);
|
|
204
|
+
}
|
|
205
|
+
async function transcriptHash(cid, pk, ct) {
|
|
206
|
+
return sha256(concat(LABEL, cid, pk, ct));
|
|
207
|
+
}
|
|
208
|
+
async function deriveAeadKey(ss, htx) {
|
|
209
|
+
const info = concat(LABEL, LABEL_AEAD_SUFFIX, htx);
|
|
210
|
+
return hkdfAesGcmKey(ss, new Uint8Array(32), info);
|
|
211
|
+
}
|
|
212
|
+
async function importRawAeadKey(raw) {
|
|
213
|
+
if (raw.length !== AEAD_KEY_LEN) {
|
|
214
|
+
throw new Error(`PQCrypto: raw AEAD key must be ${AEAD_KEY_LEN} bytes`);
|
|
215
|
+
}
|
|
216
|
+
return importAesGcmKey(raw);
|
|
217
|
+
}
|
|
141
218
|
async function exportRawAeadKey(key) {
|
|
142
|
-
|
|
143
|
-
return new Uint8Array(buf);
|
|
219
|
+
return exportAesGcmKey(key);
|
|
144
220
|
}
|
|
145
221
|
function nonce(dir, seq) {
|
|
146
222
|
if (dir.length !== 4) {
|
|
@@ -162,20 +238,10 @@ function aad(htx, seq) {
|
|
|
162
238
|
return out;
|
|
163
239
|
}
|
|
164
240
|
async function seal(key, dir, seq, htx, pt) {
|
|
165
|
-
|
|
166
|
-
{ name: "AES-GCM", iv: bs(nonce(dir, seq)), additionalData: bs(aad(htx, seq)) },
|
|
167
|
-
key,
|
|
168
|
-
bs(pt)
|
|
169
|
-
);
|
|
170
|
-
return new Uint8Array(ct);
|
|
241
|
+
return aesGcmEncrypt(key, nonce(dir, seq), aad(htx, seq), pt);
|
|
171
242
|
}
|
|
172
243
|
async function open(key, dir, seq, htx, ct) {
|
|
173
|
-
|
|
174
|
-
{ name: "AES-GCM", iv: bs(nonce(dir, seq)), additionalData: bs(aad(htx, seq)) },
|
|
175
|
-
key,
|
|
176
|
-
bs(ct)
|
|
177
|
-
);
|
|
178
|
-
return new Uint8Array(pt);
|
|
244
|
+
return aesGcmDecrypt(key, nonce(dir, seq), aad(htx, seq), ct);
|
|
179
245
|
}
|
|
180
246
|
function zeroize(b) {
|
|
181
247
|
b.fill(0);
|
|
@@ -196,7 +262,7 @@ function toBase64(bytes) {
|
|
|
196
262
|
let bin = "";
|
|
197
263
|
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
198
264
|
const slice = bytes.subarray(i, i + CHUNK);
|
|
199
|
-
bin += String.fromCharCode.apply(null, slice);
|
|
265
|
+
bin += String.fromCharCode.apply(null, Array.from(slice));
|
|
200
266
|
}
|
|
201
267
|
return btoa(bin);
|
|
202
268
|
}
|
|
@@ -206,12 +272,6 @@ function fromBase64(b64) {
|
|
|
206
272
|
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
207
273
|
return out;
|
|
208
274
|
}
|
|
209
|
-
function constantTimeEquals(a, b) {
|
|
210
|
-
if (a.length !== b.length) return false;
|
|
211
|
-
let d = 0;
|
|
212
|
-
for (let i = 0; i < a.length; i++) d |= a[i] ^ b[i];
|
|
213
|
-
return d === 0;
|
|
214
|
-
}
|
|
215
275
|
|
|
216
276
|
// src/config.ts
|
|
217
277
|
var DEFAULT_RELAY_URL = "https://qrlwallet.com";
|
|
@@ -331,6 +391,13 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
331
391
|
this.awaitingSynAck = false;
|
|
332
392
|
this.awaitingAck = false;
|
|
333
393
|
this.keysExchanged = false;
|
|
394
|
+
// The ACK we produced for the current handshake. A wallet that lost its
|
|
395
|
+
// transport right after sending SYNACK re-sends the identical SYNACK on
|
|
396
|
+
// rejoin; we answer with this cached ACK so the handshake converges
|
|
397
|
+
// instead of stalling (onSynAck ignores duplicates and would otherwise
|
|
398
|
+
// never reply). Cleared on reset; not persisted (a reloaded dApp has a
|
|
399
|
+
// completed session and the wallet is no longer awaiting an ACK).
|
|
400
|
+
this.lastAck = null;
|
|
334
401
|
this.isOriginator = isOriginator;
|
|
335
402
|
if (restored) {
|
|
336
403
|
this.session = restored;
|
|
@@ -361,7 +428,7 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
361
428
|
*/
|
|
362
429
|
async onSynAck(cid, msg) {
|
|
363
430
|
if (!this.isOriginator) {
|
|
364
|
-
warn("KeyExchange", "Responder received SYNACK
|
|
431
|
+
warn("KeyExchange", "Responder received SYNACK - ignoring");
|
|
365
432
|
return null;
|
|
366
433
|
}
|
|
367
434
|
if (!this.awaitingSynAck || !this.keypair) {
|
|
@@ -404,11 +471,13 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
404
471
|
this.step = "key_handshake_ACK" /* ACK */;
|
|
405
472
|
this.emit("keys_exchanged");
|
|
406
473
|
this.emit("step_change", this.step);
|
|
407
|
-
|
|
474
|
+
const ack = {
|
|
408
475
|
type: "key_handshake_ACK" /* ACK */,
|
|
409
476
|
c1: toBase64(c1),
|
|
410
477
|
v: PROTOCOL_VERSION
|
|
411
478
|
};
|
|
479
|
+
this.lastAck = ack;
|
|
480
|
+
return ack;
|
|
412
481
|
}
|
|
413
482
|
/**
|
|
414
483
|
* Responder (wallet) side: begin handshake from a scanned QR.
|
|
@@ -449,7 +518,7 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
449
518
|
*/
|
|
450
519
|
async onAck(msg) {
|
|
451
520
|
if (this.isOriginator) {
|
|
452
|
-
warn("KeyExchange", "Originator received ACK
|
|
521
|
+
warn("KeyExchange", "Originator received ACK - ignoring");
|
|
453
522
|
return;
|
|
454
523
|
}
|
|
455
524
|
if (!this.awaitingAck) {
|
|
@@ -476,23 +545,17 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
476
545
|
/** Encrypt a string for the counterparty. Returns base64. */
|
|
477
546
|
async encryptMessage(data) {
|
|
478
547
|
if (!this.session) {
|
|
479
|
-
throw new Error("KeyExchange: cannot encrypt
|
|
548
|
+
throw new Error("KeyExchange: cannot encrypt - session not established");
|
|
480
549
|
}
|
|
550
|
+
const seq = this.session.sendSeq++;
|
|
481
551
|
const pt = textEncoder2.encode(data);
|
|
482
|
-
const ct = await seal(
|
|
483
|
-
this.session.key,
|
|
484
|
-
this.session.sendDir,
|
|
485
|
-
this.session.sendSeq,
|
|
486
|
-
this.session.htx,
|
|
487
|
-
pt
|
|
488
|
-
);
|
|
489
|
-
this.session.sendSeq++;
|
|
552
|
+
const ct = await seal(this.session.key, this.session.sendDir, seq, this.session.htx, pt);
|
|
490
553
|
return toBase64(ct);
|
|
491
554
|
}
|
|
492
555
|
/** Decrypt a base64 ciphertext from the counterparty. */
|
|
493
556
|
async decryptMessage(b64) {
|
|
494
557
|
if (!this.session) {
|
|
495
|
-
throw new Error("KeyExchange: cannot decrypt
|
|
558
|
+
throw new Error("KeyExchange: cannot decrypt - session not established");
|
|
496
559
|
}
|
|
497
560
|
const ct = fromBase64(b64);
|
|
498
561
|
const pt = await open(
|
|
@@ -518,12 +581,17 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
518
581
|
this.keysExchanged = false;
|
|
519
582
|
this.awaitingSynAck = false;
|
|
520
583
|
this.awaitingAck = false;
|
|
584
|
+
this.lastAck = null;
|
|
521
585
|
this.step = "key_handshake_SYN" /* SYN */;
|
|
522
586
|
if (emit) this.emit("step_change", this.step);
|
|
523
587
|
}
|
|
524
588
|
areKeysExchanged() {
|
|
525
589
|
return this.keysExchanged;
|
|
526
590
|
}
|
|
591
|
+
/** The ACK produced for the current handshake, for duplicate-SYNACK replies. */
|
|
592
|
+
getLastAck() {
|
|
593
|
+
return this.lastAck;
|
|
594
|
+
}
|
|
527
595
|
getSession() {
|
|
528
596
|
return this.session;
|
|
529
597
|
}
|
|
@@ -629,8 +697,14 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
629
697
|
this.emit("message", data);
|
|
630
698
|
});
|
|
631
699
|
this.socket.on("participants_changed", (data) => {
|
|
632
|
-
|
|
633
|
-
|
|
700
|
+
if (typeof data !== "object" || data === null) return;
|
|
701
|
+
const rec = { ...data };
|
|
702
|
+
const event = typeof rec.event === "string" ? rec.event : "";
|
|
703
|
+
log("Socket", `Participants changed: ${event}`);
|
|
704
|
+
this.emit("participants_changed", {
|
|
705
|
+
event,
|
|
706
|
+
...typeof rec.clientType === "string" ? { clientType: rec.clientType } : {}
|
|
707
|
+
});
|
|
634
708
|
});
|
|
635
709
|
this.socket.on("connect_error", (err) => {
|
|
636
710
|
warn("Socket", `Connection error: ${err.message}`);
|
|
@@ -643,7 +717,7 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
643
717
|
* it doesn't fire after the fact.
|
|
644
718
|
*/
|
|
645
719
|
settlePendingJoin(channelId, outcome) {
|
|
646
|
-
if (
|
|
720
|
+
if (this.pendingJoin?.channelId !== channelId) return;
|
|
647
721
|
clearTimeout(this.pendingJoin.watchdog);
|
|
648
722
|
const pending = this.pendingJoin;
|
|
649
723
|
this.pendingJoin = null;
|
|
@@ -710,14 +784,14 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
710
784
|
if (response.success) {
|
|
711
785
|
log("Socket", `Joined channel ${channelId}`);
|
|
712
786
|
resolve({
|
|
713
|
-
bufferedMessages: response.bufferedMessages
|
|
787
|
+
bufferedMessages: response.bufferedMessages ?? [],
|
|
714
788
|
channelPublicKey: response.channelPublicKey ?? null,
|
|
715
|
-
participants: response.participants
|
|
789
|
+
participants: response.participants ?? [],
|
|
716
790
|
terminated: response.terminated === true
|
|
717
791
|
});
|
|
718
792
|
} else {
|
|
719
793
|
error("Socket", `Failed to join channel: ${response.error}`);
|
|
720
|
-
reject(new Error(response.error
|
|
794
|
+
reject(new Error(response.error ?? "Failed to join channel"));
|
|
721
795
|
}
|
|
722
796
|
}
|
|
723
797
|
);
|
|
@@ -740,7 +814,7 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
740
814
|
if (response?.success) {
|
|
741
815
|
resolve({ success: true, buffered: response.buffered });
|
|
742
816
|
} else {
|
|
743
|
-
reject(new Error(response?.error
|
|
817
|
+
reject(new Error(response?.error ?? "Failed to send message"));
|
|
744
818
|
}
|
|
745
819
|
}
|
|
746
820
|
);
|
|
@@ -796,26 +870,26 @@ var DECODE = (() => {
|
|
|
796
870
|
})();
|
|
797
871
|
function decodeChar(charCode) {
|
|
798
872
|
if (charCode < 0 || charCode >= 128) return -1;
|
|
799
|
-
return DECODE[charCode];
|
|
873
|
+
return DECODE[charCode] ?? -1;
|
|
800
874
|
}
|
|
801
875
|
function base45Encode(bytes) {
|
|
802
876
|
const n = bytes.length;
|
|
803
877
|
let out = "";
|
|
804
878
|
let i = 0;
|
|
805
879
|
while (i + 2 <= n) {
|
|
806
|
-
const v = bytes[i] << 8 | bytes[i + 1];
|
|
880
|
+
const v = (bytes[i] ?? 0) << 8 | (bytes[i + 1] ?? 0);
|
|
807
881
|
const c2 = Math.floor(v / 2025);
|
|
808
882
|
const r = v - c2 * 2025;
|
|
809
883
|
const c1 = Math.floor(r / 45);
|
|
810
884
|
const c0 = r - c1 * 45;
|
|
811
|
-
out += ALPHABET[c0]
|
|
885
|
+
out += `${ALPHABET[c0] ?? ""}${ALPHABET[c1] ?? ""}${ALPHABET[c2] ?? ""}`;
|
|
812
886
|
i += 2;
|
|
813
887
|
}
|
|
814
888
|
if (i < n) {
|
|
815
|
-
const v = bytes[i];
|
|
889
|
+
const v = bytes[i] ?? 0;
|
|
816
890
|
const c1 = Math.floor(v / 45);
|
|
817
891
|
const c0 = v - c1 * 45;
|
|
818
|
-
out += ALPHABET[c0]
|
|
892
|
+
out += `${ALPHABET[c0] ?? ""}${ALPHABET[c1] ?? ""}`;
|
|
819
893
|
}
|
|
820
894
|
return out;
|
|
821
895
|
}
|
|
@@ -864,29 +938,18 @@ var FP_LABEL = new TextEncoder().encode("pq-fp/v2");
|
|
|
864
938
|
var CID_LEN = 16;
|
|
865
939
|
var FP_LEN = 32;
|
|
866
940
|
var BLOB_LEN = 4 + CID_LEN + FP_LEN;
|
|
867
|
-
function bs2(u) {
|
|
868
|
-
return u;
|
|
869
|
-
}
|
|
870
941
|
async function computeFingerprint(cid, pk) {
|
|
871
942
|
if (cid.length !== CID_LEN) {
|
|
872
943
|
throw new Error(`qrUri: cid must be ${CID_LEN} bytes`);
|
|
873
944
|
}
|
|
874
|
-
const c = globalThis.crypto;
|
|
875
|
-
if (!c || !c.subtle) {
|
|
876
|
-
throw new Error("qrUri: WebCrypto SubtleCrypto is not available");
|
|
877
|
-
}
|
|
878
945
|
const buf = new Uint8Array(FP_LABEL.length + cid.length + pk.length);
|
|
879
946
|
buf.set(FP_LABEL, 0);
|
|
880
947
|
buf.set(cid, FP_LABEL.length);
|
|
881
948
|
buf.set(pk, FP_LABEL.length + cid.length);
|
|
882
|
-
|
|
883
|
-
return new Uint8Array(digest);
|
|
949
|
+
return sha256(buf);
|
|
884
950
|
}
|
|
885
951
|
function fingerprintEquals(a, b) {
|
|
886
|
-
|
|
887
|
-
let diff = 0;
|
|
888
|
-
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
889
|
-
return diff === 0;
|
|
952
|
+
return constantTimeEquals(a, b);
|
|
890
953
|
}
|
|
891
954
|
async function generateConnectionURI(cid, pk, relayUrl) {
|
|
892
955
|
if (cid.length !== CID_LEN) {
|
|
@@ -925,7 +988,7 @@ async function parseConnectionURI(uri) {
|
|
|
925
988
|
}
|
|
926
989
|
if (params.has("channelId") || params.has("pubKey")) {
|
|
927
990
|
throw new Error(
|
|
928
|
-
"qrUri: legacy v1 URI detected
|
|
991
|
+
"qrUri: legacy v1 URI detected - this wallet and this dApp must both run protocol v2"
|
|
929
992
|
);
|
|
930
993
|
}
|
|
931
994
|
const q = params.get("q");
|
|
@@ -941,7 +1004,7 @@ async function parseConnectionURI(uri) {
|
|
|
941
1004
|
}
|
|
942
1005
|
if (blob.length !== BLOB_LEN) {
|
|
943
1006
|
if (blob.length === 1208 && startsWith(blob, PQP1_MAGIC)) {
|
|
944
|
-
throw new Error("qrUri: legacy PQP1 URI detected
|
|
1007
|
+
throw new Error("qrUri: legacy PQP1 URI detected - regenerate the QR with a v2.0+ dApp SDK");
|
|
945
1008
|
}
|
|
946
1009
|
throw new Error(`qrUri: expected ${BLOB_LEN}-byte blob, got ${blob.length}`);
|
|
947
1010
|
}
|
|
@@ -950,8 +1013,8 @@ async function parseConnectionURI(uri) {
|
|
|
950
1013
|
}
|
|
951
1014
|
const cid = blob.slice(4, 4 + CID_LEN);
|
|
952
1015
|
const fp = blob.slice(4 + CID_LEN, 4 + CID_LEN + FP_LEN);
|
|
953
|
-
const
|
|
954
|
-
return { cid, fp, relayUrl };
|
|
1016
|
+
const r = params.get("r");
|
|
1017
|
+
return { cid, fp, relayUrl: r === null || r === "" ? void 0 : r };
|
|
955
1018
|
}
|
|
956
1019
|
function cidToString(cid) {
|
|
957
1020
|
if (cid.length !== CID_LEN) {
|
|
@@ -959,7 +1022,7 @@ function cidToString(cid) {
|
|
|
959
1022
|
}
|
|
960
1023
|
let hex = "";
|
|
961
1024
|
for (let i = 0; i < CID_LEN; i++) {
|
|
962
|
-
hex += cid[i].toString(16).padStart(2, "0");
|
|
1025
|
+
hex += (cid[i] ?? 0).toString(16).padStart(2, "0");
|
|
963
1026
|
}
|
|
964
1027
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
965
1028
|
}
|
|
@@ -975,15 +1038,79 @@ function cidFromString(s) {
|
|
|
975
1038
|
return out;
|
|
976
1039
|
}
|
|
977
1040
|
function cidRandom() {
|
|
978
|
-
|
|
979
|
-
if (!c || typeof c.getRandomValues !== "function") {
|
|
980
|
-
throw new Error("qrUri: crypto.getRandomValues is not available");
|
|
981
|
-
}
|
|
982
|
-
return c.getRandomValues(new Uint8Array(CID_LEN));
|
|
1041
|
+
return randomBytes(CID_LEN);
|
|
983
1042
|
}
|
|
984
1043
|
|
|
985
1044
|
// src/ConnectionManager.ts
|
|
986
1045
|
var DAPP_PARTICIPANT_CONFLICT_ERROR_MSG = "dapp participant is already connected";
|
|
1046
|
+
function isRecord(v) {
|
|
1047
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1048
|
+
}
|
|
1049
|
+
function isStringArray(v) {
|
|
1050
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
1051
|
+
}
|
|
1052
|
+
function isRelayMessage(v) {
|
|
1053
|
+
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);
|
|
1054
|
+
}
|
|
1055
|
+
var MESSAGE_TYPE_BY_VALUE = Object.fromEntries(
|
|
1056
|
+
Object.values(MessageType).map((m) => [m, m])
|
|
1057
|
+
);
|
|
1058
|
+
function parseJsonRpcResponse(msg) {
|
|
1059
|
+
if (typeof msg.id !== "string" && typeof msg.id !== "number") return null;
|
|
1060
|
+
const out = {
|
|
1061
|
+
jsonrpc: typeof msg.jsonrpc === "string" ? msg.jsonrpc : "2.0",
|
|
1062
|
+
id: msg.id
|
|
1063
|
+
};
|
|
1064
|
+
if ("result" in msg) out.result = msg.result;
|
|
1065
|
+
if (msg.error !== void 0) {
|
|
1066
|
+
if (!isRecord(msg.error) || typeof msg.error.message !== "string") return null;
|
|
1067
|
+
out.error = {
|
|
1068
|
+
code: typeof msg.error.code === "number" ? msg.error.code : -32e3,
|
|
1069
|
+
message: msg.error.message,
|
|
1070
|
+
data: msg.error.data
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
return out;
|
|
1074
|
+
}
|
|
1075
|
+
function parsePersistedKex(v) {
|
|
1076
|
+
if (!isRecord(v)) return null;
|
|
1077
|
+
const { cid, kAeadRaw, htx, sendDir, recvDir, sendSeq, recvSeq } = v;
|
|
1078
|
+
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) {
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
return { cid, kAeadRaw, htx, sendDir, recvDir, sendSeq, recvSeq };
|
|
1082
|
+
}
|
|
1083
|
+
function parseDAppMetadata(v) {
|
|
1084
|
+
if (!isRecord(v) || typeof v.name !== "string" || typeof v.url !== "string") return null;
|
|
1085
|
+
const meta = { name: v.name, url: v.url };
|
|
1086
|
+
if (typeof v.icon === "string") meta.icon = v.icon;
|
|
1087
|
+
if (typeof v.redirectUrl === "string") meta.redirectUrl = v.redirectUrl;
|
|
1088
|
+
return meta;
|
|
1089
|
+
}
|
|
1090
|
+
function parseStoredSession(raw) {
|
|
1091
|
+
let v;
|
|
1092
|
+
try {
|
|
1093
|
+
v = JSON.parse(raw);
|
|
1094
|
+
} catch {
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
if (!isRecord(v) || v.version !== 3) return null;
|
|
1098
|
+
if (typeof v.channelId !== "string" || typeof v.chainId !== "string") return null;
|
|
1099
|
+
if (typeof v.createdAt !== "number" || typeof v.lastActivity !== "number") return null;
|
|
1100
|
+
const keyExchange = parsePersistedKex(v.keyExchange);
|
|
1101
|
+
const dappMetadata = parseDAppMetadata(v.dappMetadata);
|
|
1102
|
+
if (!keyExchange || !dappMetadata || !isStringArray(v.connectedAccounts)) return null;
|
|
1103
|
+
return {
|
|
1104
|
+
version: 3,
|
|
1105
|
+
channelId: v.channelId,
|
|
1106
|
+
keyExchange,
|
|
1107
|
+
dappMetadata,
|
|
1108
|
+
connectedAccounts: v.connectedAccounts,
|
|
1109
|
+
chainId: v.chainId,
|
|
1110
|
+
createdAt: v.createdAt,
|
|
1111
|
+
lastActivity: v.lastActivity
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
987
1114
|
var TERMINATE_SEND_TIMEOUT_MS = 800;
|
|
988
1115
|
var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.default {
|
|
989
1116
|
constructor(options) {
|
|
@@ -997,10 +1124,11 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
997
1124
|
this.walletPresent = false;
|
|
998
1125
|
this.pendingRestore = null;
|
|
999
1126
|
this.messageQueue = Promise.resolve();
|
|
1127
|
+
this.outboundQueue = Promise.resolve();
|
|
1000
1128
|
this.dappMetadata = options.dappMetadata;
|
|
1001
|
-
this.relayUrl = options.relayUrl
|
|
1002
|
-
this.chainId = options.chainId
|
|
1003
|
-
this.storageKey = options.storageKey
|
|
1129
|
+
this.relayUrl = options.relayUrl ?? DEFAULT_RELAY_URL;
|
|
1130
|
+
this.chainId = options.chainId ?? "0x0";
|
|
1131
|
+
this.storageKey = options.storageKey ?? `${STORAGE_KEY_PREFIX}:session`;
|
|
1004
1132
|
const stored = this.readStoredSession();
|
|
1005
1133
|
if (stored) {
|
|
1006
1134
|
this.channelId = stored.channelId;
|
|
@@ -1010,7 +1138,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1010
1138
|
this.pendingRestore = stored;
|
|
1011
1139
|
log("ConnectionManager", `Found persisted session for channel ${this.channelId}`);
|
|
1012
1140
|
} else {
|
|
1013
|
-
this.channelId = (
|
|
1141
|
+
this.channelId = randomUuid();
|
|
1014
1142
|
}
|
|
1015
1143
|
this.socketClient = new SocketClient(this.relayUrl, "dapp");
|
|
1016
1144
|
this.setupSocketListeners();
|
|
@@ -1122,7 +1250,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1122
1250
|
this.setupKeyExchangeListeners();
|
|
1123
1251
|
}
|
|
1124
1252
|
const pk = this.keyExchange.initiate();
|
|
1125
|
-
this.channelId = (
|
|
1253
|
+
this.channelId = randomUuid();
|
|
1126
1254
|
this.socketClient.setPublicKey(toBase64(pk));
|
|
1127
1255
|
this.socketClient.connect();
|
|
1128
1256
|
try {
|
|
@@ -1133,7 +1261,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1133
1261
|
"ConnectionManager",
|
|
1134
1262
|
"Channel already has an active dApp participant. Rotating to a fresh channel."
|
|
1135
1263
|
);
|
|
1136
|
-
this.channelId = (
|
|
1264
|
+
this.channelId = randomUuid();
|
|
1137
1265
|
return this.getConnectionURI(false);
|
|
1138
1266
|
}
|
|
1139
1267
|
this.setStatus("disconnected" /* DISCONNECTED */);
|
|
@@ -1303,8 +1431,8 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1303
1431
|
try {
|
|
1304
1432
|
const raw = localStorage.getItem(this.storageKey);
|
|
1305
1433
|
if (!raw) return false;
|
|
1306
|
-
const session =
|
|
1307
|
-
if (session
|
|
1434
|
+
const session = parseStoredSession(raw);
|
|
1435
|
+
if (!session) return false;
|
|
1308
1436
|
return Date.now() - session.createdAt <= SESSION_TTL_MS;
|
|
1309
1437
|
} catch {
|
|
1310
1438
|
return false;
|
|
@@ -1319,8 +1447,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1319
1447
|
*/
|
|
1320
1448
|
async flushTerminate() {
|
|
1321
1449
|
if (!this.keyExchange?.areKeysExchanged()) return;
|
|
1322
|
-
const send = this.sendEncrypted({ type: "terminate" /* TERMINATE */ }).catch(() =>
|
|
1323
|
-
});
|
|
1450
|
+
const send = this.sendEncrypted({ type: "terminate" /* TERMINATE */ }).catch(() => void 0);
|
|
1324
1451
|
const timeout = new Promise((resolve) => setTimeout(resolve, TERMINATE_SEND_TIMEOUT_MS));
|
|
1325
1452
|
await Promise.race([send, timeout]);
|
|
1326
1453
|
}
|
|
@@ -1339,7 +1466,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1339
1466
|
this.clearSession();
|
|
1340
1467
|
this.connectedAccounts = [];
|
|
1341
1468
|
this.pendingRestore = null;
|
|
1342
|
-
this.channelId = (
|
|
1469
|
+
this.channelId = randomUuid();
|
|
1343
1470
|
this.keyExchange = null;
|
|
1344
1471
|
this.socketClient = new SocketClient(this.relayUrl, "dapp");
|
|
1345
1472
|
this.setupSocketListeners();
|
|
@@ -1360,7 +1487,13 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1360
1487
|
}
|
|
1361
1488
|
// ── Internals ──────────────────────────────────────────────
|
|
1362
1489
|
enqueueRelayMessage(data) {
|
|
1363
|
-
|
|
1490
|
+
if (!isRelayMessage(data)) {
|
|
1491
|
+
warn("ConnectionManager", "Dropping malformed relay envelope");
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
this.messageQueue = this.messageQueue.then(() => this.handleRelayMessage(data)).catch((err) => {
|
|
1495
|
+
error("ConnectionManager", "messageQueue handler error:", err);
|
|
1496
|
+
});
|
|
1364
1497
|
}
|
|
1365
1498
|
async handleRelayMessage(data) {
|
|
1366
1499
|
if (data.clientType === "wallet") {
|
|
@@ -1372,22 +1505,35 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1372
1505
|
}
|
|
1373
1506
|
}
|
|
1374
1507
|
const message = data.message;
|
|
1375
|
-
if (
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1508
|
+
if (isRecord(message)) {
|
|
1509
|
+
if (message.type === "key_handshake_SYNACK" /* SYNACK */) {
|
|
1510
|
+
if (typeof message.ct === "string" && typeof message.c0 === "string") {
|
|
1511
|
+
await this.handleSynAck({
|
|
1512
|
+
type: "key_handshake_SYNACK" /* SYNACK */,
|
|
1513
|
+
ct: message.ct,
|
|
1514
|
+
c0: message.c0,
|
|
1515
|
+
v: typeof message.v === "number" ? message.v : 0
|
|
1516
|
+
});
|
|
1517
|
+
} else {
|
|
1518
|
+
warn("ConnectionManager", "Dropping malformed SYNACK");
|
|
1519
|
+
}
|
|
1379
1520
|
return;
|
|
1380
1521
|
}
|
|
1381
|
-
if (
|
|
1382
|
-
warn("ConnectionManager", `Unexpected ${
|
|
1522
|
+
if (message.type === "key_handshake_SYN" /* SYN */ || message.type === "key_handshake_ACK" /* ACK */) {
|
|
1523
|
+
warn("ConnectionManager", `Unexpected ${message.type} on dApp side, ignoring`);
|
|
1383
1524
|
return;
|
|
1384
1525
|
}
|
|
1385
1526
|
}
|
|
1386
1527
|
if (typeof message === "string" && this.keyExchange?.areKeysExchanged()) {
|
|
1387
1528
|
try {
|
|
1388
1529
|
const decrypted = await this.keyExchange.decryptMessage(message);
|
|
1530
|
+
await this.persistSession();
|
|
1389
1531
|
const parsed = JSON.parse(decrypted);
|
|
1390
|
-
|
|
1532
|
+
if (isRecord(parsed)) {
|
|
1533
|
+
this.handleDecryptedMessage(parsed);
|
|
1534
|
+
} else {
|
|
1535
|
+
warn("ConnectionManager", "Dropping non-object decrypted payload");
|
|
1536
|
+
}
|
|
1391
1537
|
} catch (err) {
|
|
1392
1538
|
error("ConnectionManager", "Failed to decrypt message:", err);
|
|
1393
1539
|
}
|
|
@@ -1407,16 +1553,21 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1407
1553
|
}
|
|
1408
1554
|
if (response) {
|
|
1409
1555
|
this.sendPlaintext(response);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
const cachedAck = this.keyExchange.getLastAck();
|
|
1559
|
+
if (this.keyExchange.areKeysExchanged() && cachedAck) {
|
|
1560
|
+
log("ConnectionManager", "Duplicate SYNACK after handshake; re-sending cached ACK");
|
|
1561
|
+
this.sendPlaintext(cachedAck);
|
|
1410
1562
|
}
|
|
1411
1563
|
}
|
|
1412
1564
|
handleDecryptedMessage(msg) {
|
|
1413
1565
|
this.clearUnresponsiveTimer();
|
|
1414
|
-
const type = msg.type;
|
|
1415
|
-
switch (type) {
|
|
1566
|
+
const type = typeof msg.type === "string" ? msg.type : "";
|
|
1567
|
+
switch (MESSAGE_TYPE_BY_VALUE[type]) {
|
|
1416
1568
|
case "wallet_info" /* WALLET_INFO */: {
|
|
1417
|
-
const
|
|
1418
|
-
const
|
|
1419
|
-
const nextChainId = info.chainId || this.chainId;
|
|
1569
|
+
const nextAccounts = isStringArray(msg.accounts) ? msg.accounts : [];
|
|
1570
|
+
const nextChainId = typeof msg.chainId === "string" && msg.chainId ? msg.chainId : this.chainId;
|
|
1420
1571
|
const accountsChanged = !this.areArraysEqual(this.connectedAccounts, nextAccounts);
|
|
1421
1572
|
const chainChanged = this.chainId !== nextChainId;
|
|
1422
1573
|
this.connectedAccounts = nextAccounts;
|
|
@@ -1431,7 +1582,11 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1431
1582
|
break;
|
|
1432
1583
|
}
|
|
1433
1584
|
case "jsonrpc" /* JSONRPC */: {
|
|
1434
|
-
const response = msg;
|
|
1585
|
+
const response = parseJsonRpcResponse(msg);
|
|
1586
|
+
if (!response) {
|
|
1587
|
+
warn("ConnectionManager", "Dropping malformed JSON-RPC response");
|
|
1588
|
+
break;
|
|
1589
|
+
}
|
|
1435
1590
|
this.emit("jsonrpc_response", response);
|
|
1436
1591
|
break;
|
|
1437
1592
|
}
|
|
@@ -1453,11 +1608,28 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1453
1608
|
error("ConnectionManager", "Failed to send plaintext:", err);
|
|
1454
1609
|
});
|
|
1455
1610
|
}
|
|
1456
|
-
|
|
1611
|
+
/**
|
|
1612
|
+
* Serialize every encrypt+persist+send on a single outbound queue.
|
|
1613
|
+
* Callers like sendJsonRpc() are fire-and-forget, so without the queue two
|
|
1614
|
+
* rapid provider.request() calls would interleave inside encryptMessage()
|
|
1615
|
+
* across its await and could complete out of order; the receiver's
|
|
1616
|
+
* contiguous-seq check would then drop the late one. (Nonce reuse itself is
|
|
1617
|
+
* already prevented by the synchronous seq reservation in KeyExchange.)
|
|
1618
|
+
*/
|
|
1619
|
+
sendEncrypted(message) {
|
|
1620
|
+
const task = this.outboundQueue.then(() => this.sendEncryptedNow(message));
|
|
1621
|
+
this.outboundQueue = task.then(
|
|
1622
|
+
() => void 0,
|
|
1623
|
+
() => void 0
|
|
1624
|
+
);
|
|
1625
|
+
return task;
|
|
1626
|
+
}
|
|
1627
|
+
async sendEncryptedNow(message) {
|
|
1457
1628
|
if (!this.keyExchange?.areKeysExchanged()) {
|
|
1458
1629
|
throw new Error("sendEncrypted: not connected");
|
|
1459
1630
|
}
|
|
1460
1631
|
const encrypted = await this.keyExchange.encryptMessage(JSON.stringify(message));
|
|
1632
|
+
await this.persistSession();
|
|
1461
1633
|
await this.socketClient.sendMessage({
|
|
1462
1634
|
id: this.channelId,
|
|
1463
1635
|
clientType: "dapp",
|
|
@@ -1498,7 +1670,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1498
1670
|
const persistedKex = await this.keyExchange.exportPersisted();
|
|
1499
1671
|
if (!persistedKex) return;
|
|
1500
1672
|
const session = {
|
|
1501
|
-
version:
|
|
1673
|
+
version: 3,
|
|
1502
1674
|
channelId: this.channelId,
|
|
1503
1675
|
keyExchange: persistedKex,
|
|
1504
1676
|
dappMetadata: this.dappMetadata,
|
|
@@ -1518,13 +1690,13 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1518
1690
|
try {
|
|
1519
1691
|
const raw = localStorage.getItem(this.storageKey);
|
|
1520
1692
|
if (!raw) return null;
|
|
1521
|
-
const session =
|
|
1522
|
-
if (session
|
|
1523
|
-
log("ConnectionManager", "Dropping legacy
|
|
1693
|
+
const session = parseStoredSession(raw);
|
|
1694
|
+
if (!session) {
|
|
1695
|
+
log("ConnectionManager", "Dropping legacy or malformed session from storage");
|
|
1524
1696
|
localStorage.removeItem(this.storageKey);
|
|
1525
1697
|
return null;
|
|
1526
1698
|
}
|
|
1527
|
-
if (
|
|
1699
|
+
if (Date.now() - session.createdAt > SESSION_TTL_MS) {
|
|
1528
1700
|
localStorage.removeItem(this.storageKey);
|
|
1529
1701
|
return null;
|
|
1530
1702
|
}
|
|
@@ -1554,22 +1726,13 @@ function getAppStoreUrl() {
|
|
|
1554
1726
|
}
|
|
1555
1727
|
|
|
1556
1728
|
// src/QRLConnectProvider.ts
|
|
1557
|
-
var requestCounter = 0;
|
|
1558
1729
|
var QRL_CONNECT_PROVIDER_INFO = {
|
|
1559
|
-
name: "MyQRLWallet
|
|
1730
|
+
name: "MyQRLWallet",
|
|
1560
1731
|
rdns: "com.qrlwallet.connect",
|
|
1561
1732
|
icon: "data:image/png;base64,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"
|
|
1562
1733
|
};
|
|
1563
1734
|
var EIP6963_ANNOUNCE_EVENT = "eip6963:announceProvider";
|
|
1564
1735
|
var EIP6963_REQUEST_EVENT = "eip6963:requestProvider";
|
|
1565
|
-
function generateUuid() {
|
|
1566
|
-
if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
|
|
1567
|
-
return globalThis.crypto.randomUUID();
|
|
1568
|
-
}
|
|
1569
|
-
const r = (n) => Math.floor(Math.random() * n);
|
|
1570
|
-
const hex = (n, len) => n.toString(16).padStart(len, "0");
|
|
1571
|
-
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)}`;
|
|
1572
|
-
}
|
|
1573
1736
|
var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
1574
1737
|
constructor(options) {
|
|
1575
1738
|
super();
|
|
@@ -1578,6 +1741,12 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1578
1741
|
this.eip6963RequestListener = null;
|
|
1579
1742
|
this.resumeListener = null;
|
|
1580
1743
|
this.resumeDebounce = null;
|
|
1744
|
+
// Random per-instance prefix keeps request ids unique across page loads.
|
|
1745
|
+
// A bare counter restarts at 1 on reload, and the relay buffers messages
|
|
1746
|
+
// for 5 minutes, so a stale buffered response could otherwise be matched
|
|
1747
|
+
// to a fresh request that drew the same small id.
|
|
1748
|
+
this.requestIdPrefix = randomUuid().slice(0, 8);
|
|
1749
|
+
this.requestCounter = 0;
|
|
1581
1750
|
this.isQRLConnect = true;
|
|
1582
1751
|
this.options = options;
|
|
1583
1752
|
if (options.debug) {
|
|
@@ -1591,7 +1760,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1591
1760
|
});
|
|
1592
1761
|
this.setupConnectionListeners();
|
|
1593
1762
|
if (options.autoReconnect !== false) {
|
|
1594
|
-
this.connectionManager.reconnect();
|
|
1763
|
+
void this.connectionManager.reconnect();
|
|
1595
1764
|
}
|
|
1596
1765
|
this.setupResumeListeners();
|
|
1597
1766
|
if (options.announceProvider !== false) {
|
|
@@ -1605,7 +1774,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1605
1774
|
const overrides = this.options.providerInfo ?? {};
|
|
1606
1775
|
this.eip6963Detail = Object.freeze({
|
|
1607
1776
|
info: Object.freeze({
|
|
1608
|
-
uuid:
|
|
1777
|
+
uuid: randomUuid(),
|
|
1609
1778
|
name: overrides.name ?? QRL_CONNECT_PROVIDER_INFO.name,
|
|
1610
1779
|
icon: overrides.icon ?? QRL_CONNECT_PROVIDER_INFO.icon,
|
|
1611
1780
|
rdns: overrides.rdns ?? QRL_CONNECT_PROVIDER_INFO.rdns
|
|
@@ -1758,7 +1927,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1758
1927
|
if (this.connectionManager.getStatus() !== "connected" /* CONNECTED */) {
|
|
1759
1928
|
throw new Error("Not connected to QRL Wallet");
|
|
1760
1929
|
}
|
|
1761
|
-
const id = ++requestCounter
|
|
1930
|
+
const id = `${this.requestIdPrefix}-${++this.requestCounter}`;
|
|
1762
1931
|
return new Promise((resolve, reject) => {
|
|
1763
1932
|
const pending = {
|
|
1764
1933
|
id,
|
|
@@ -1839,7 +2008,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1839
2008
|
/**
|
|
1840
2009
|
* Disconnect from wallet and clean up. Returns once the TERMINATE has
|
|
1841
2010
|
* either been flushed to the relay or the 800ms best-effort window has
|
|
1842
|
-
* elapsed
|
|
2011
|
+
* elapsed - the wallet gets an instant disconnect instead of landing in
|
|
1843
2012
|
* its stale-session grace period.
|
|
1844
2013
|
*/
|
|
1845
2014
|
async disconnect() {
|
|
@@ -1859,9 +2028,6 @@ var SCHEME_TAG_MSG = new TextEncoder().encode(SCHEME_VERSION_MSG);
|
|
|
1859
2028
|
var SCHEME_TAG_TYPED = new TextEncoder().encode(SCHEME_VERSION_TYPED);
|
|
1860
2029
|
var DIGEST_LEN = 64;
|
|
1861
2030
|
|
|
1862
|
-
// src/signing/messageDigest.ts
|
|
1863
|
-
var import_sha3 = require("@noble/hashes/sha3.js");
|
|
1864
|
-
|
|
1865
2031
|
// src/signing/bytes.ts
|
|
1866
2032
|
function hexToBytes(hex) {
|
|
1867
2033
|
if (typeof hex !== "string") throw new Error("hex must be a string");
|
|
@@ -1903,12 +2069,14 @@ function computeMessageDigest(messageBytes) {
|
|
|
1903
2069
|
if (!(messageBytes instanceof Uint8Array)) {
|
|
1904
2070
|
throw new Error("messageBytes must be a Uint8Array");
|
|
1905
2071
|
}
|
|
1906
|
-
return (
|
|
2072
|
+
return shake256Digest(concatBytes(SCHEME_TAG_MSG, messageBytes), DIGEST_LEN);
|
|
1907
2073
|
}
|
|
1908
2074
|
|
|
1909
2075
|
// src/signing/typedData.ts
|
|
1910
|
-
var import_sha32 = require("@noble/hashes/sha3.js");
|
|
1911
2076
|
var SLOT = 32;
|
|
2077
|
+
function isMessageObject(v) {
|
|
2078
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2079
|
+
}
|
|
1912
2080
|
var ATOMIC_RE = /^(?:(address|bool|string|bytes)|(u?int)(\d+)|bytes(\d+)|(.+?)\[(\d*)\])$/;
|
|
1913
2081
|
var MAX_TYPE_DEPTH = 12;
|
|
1914
2082
|
function parseFieldType(type, types, depth = 0) {
|
|
@@ -1919,7 +2087,9 @@ function parseFieldType(type, types, depth = 0) {
|
|
|
1919
2087
|
const m = ATOMIC_RE.exec(type);
|
|
1920
2088
|
if (!m) throw new Error(`unknown type: ${type}`);
|
|
1921
2089
|
const [, atomic, intKind, intWidthStr, bytesWidthStr, innerType, sizeStr] = m;
|
|
1922
|
-
if (atomic
|
|
2090
|
+
if (atomic === "address" || atomic === "bool" || atomic === "string" || atomic === "bytes") {
|
|
2091
|
+
return { kind: atomic };
|
|
2092
|
+
}
|
|
1923
2093
|
if (intKind) {
|
|
1924
2094
|
const width = Number(intWidthStr);
|
|
1925
2095
|
if (!Number.isInteger(width) || width < 8 || width > 256 || width % 8 !== 0) {
|
|
@@ -1936,11 +2106,14 @@ function parseFieldType(type, types, depth = 0) {
|
|
|
1936
2106
|
}
|
|
1937
2107
|
if (innerType !== void 0) {
|
|
1938
2108
|
parseFieldType(innerType, types, depth + 1);
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2109
|
+
if (sizeStr) {
|
|
2110
|
+
const size = Number(sizeStr);
|
|
2111
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
2112
|
+
throw new Error(`invalid array size: ${type}`);
|
|
2113
|
+
}
|
|
2114
|
+
return { kind: "array", inner: innerType, size };
|
|
1942
2115
|
}
|
|
1943
|
-
return { kind: "array", inner: innerType
|
|
2116
|
+
return { kind: "array", inner: innerType };
|
|
1944
2117
|
}
|
|
1945
2118
|
throw new Error(`unhandled type: ${type}`);
|
|
1946
2119
|
}
|
|
@@ -1958,7 +2131,9 @@ function collectDependencies(primary, types) {
|
|
|
1958
2131
|
}
|
|
1959
2132
|
if (visited.has(name)) return;
|
|
1960
2133
|
visited.add(name);
|
|
1961
|
-
|
|
2134
|
+
const def = types[name];
|
|
2135
|
+
if (!def) throw new Error(`unknown type: ${name}`);
|
|
2136
|
+
for (const f of def) {
|
|
1962
2137
|
const base = baseTypeName(f.type);
|
|
1963
2138
|
if (Object.prototype.hasOwnProperty.call(types, base)) {
|
|
1964
2139
|
visit(base, [...path, name]);
|
|
@@ -1971,7 +2146,8 @@ function collectDependencies(primary, types) {
|
|
|
1971
2146
|
}
|
|
1972
2147
|
function validateTypeMap(types) {
|
|
1973
2148
|
for (const [name, def] of Object.entries(types)) {
|
|
1974
|
-
|
|
2149
|
+
const defUnknown = def;
|
|
2150
|
+
if (!Array.isArray(defUnknown) || def.length === 0) {
|
|
1975
2151
|
throw new Error(`empty or invalid struct: ${name}`);
|
|
1976
2152
|
}
|
|
1977
2153
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1994,16 +2170,17 @@ function encodeType(primary, types) {
|
|
|
1994
2170
|
const others = [...deps].filter((n) => n !== primary).sort();
|
|
1995
2171
|
return [primary, ...others].map((name) => {
|
|
1996
2172
|
const fields = types[name];
|
|
2173
|
+
if (!fields) throw new Error(`unknown type: ${name}`);
|
|
1997
2174
|
const inner = fields.map((f) => `${f.type} ${f.name}`).join(",");
|
|
1998
2175
|
return `${name}(${inner})`;
|
|
1999
2176
|
}).join("");
|
|
2000
2177
|
}
|
|
2001
2178
|
function typeHash(primary, types) {
|
|
2002
|
-
return (
|
|
2179
|
+
return shake256Digest(new TextEncoder().encode(encodeType(primary, types)), DIGEST_LEN);
|
|
2003
2180
|
}
|
|
2004
2181
|
function parseQAddress(addr) {
|
|
2005
2182
|
if (typeof addr !== "string" || !/^Q[0-9a-fA-F]{40}$/.test(addr)) {
|
|
2006
|
-
throw new Error(`invalid Q-address: ${
|
|
2183
|
+
throw new Error(`invalid Q-address: ${addr}`);
|
|
2007
2184
|
}
|
|
2008
2185
|
return hexToBytes("0x" + addr.slice(1).toLowerCase());
|
|
2009
2186
|
}
|
|
@@ -2065,7 +2242,7 @@ function encodeField(type, value, types) {
|
|
|
2065
2242
|
const parsed = parseFieldType(type, types);
|
|
2066
2243
|
switch (parsed.kind) {
|
|
2067
2244
|
case "ref":
|
|
2068
|
-
if (
|
|
2245
|
+
if (!isMessageObject(value)) {
|
|
2069
2246
|
throw new Error(`struct field expects object: ${type}`);
|
|
2070
2247
|
}
|
|
2071
2248
|
return hashStruct(parsed.name, value, types);
|
|
@@ -2083,12 +2260,12 @@ function encodeField(type, value, types) {
|
|
|
2083
2260
|
if (typeof value !== "string") {
|
|
2084
2261
|
throw new Error(`string field expects string: ${typeof value}`);
|
|
2085
2262
|
}
|
|
2086
|
-
return (
|
|
2263
|
+
return shake256Digest(new TextEncoder().encode(value), DIGEST_LEN);
|
|
2087
2264
|
case "bytes":
|
|
2088
2265
|
if (typeof value !== "string") {
|
|
2089
2266
|
throw new Error(`bytes field expects 0x-hex string: ${typeof value}`);
|
|
2090
2267
|
}
|
|
2091
|
-
return (
|
|
2268
|
+
return shake256Digest(hexToBytes(value), DIGEST_LEN);
|
|
2092
2269
|
case "uintN":
|
|
2093
2270
|
return bigIntToSlot(parseIntValue(value, `uint${parsed.width}`), parsed.width, false);
|
|
2094
2271
|
case "intN":
|
|
@@ -2111,7 +2288,7 @@ function encodeField(type, value, types) {
|
|
|
2111
2288
|
throw new Error(`fixed array ${type} requires length ${parsed.size}, got ${value.length}`);
|
|
2112
2289
|
}
|
|
2113
2290
|
const chunks = value.map((v) => encodeField(parsed.inner, v, types));
|
|
2114
|
-
return (
|
|
2291
|
+
return shake256Digest(concatBytesArr(chunks), DIGEST_LEN);
|
|
2115
2292
|
}
|
|
2116
2293
|
}
|
|
2117
2294
|
}
|
|
@@ -2127,7 +2304,7 @@ function hashStruct(primary, data, types) {
|
|
|
2127
2304
|
if (!(f.name in data)) throw new Error(`missing field ${primary}.${f.name}`);
|
|
2128
2305
|
parts.push(encodeField(f.type, data[f.name], types));
|
|
2129
2306
|
}
|
|
2130
|
-
return (
|
|
2307
|
+
return shake256Digest(concatBytesArr(parts), DIGEST_LEN);
|
|
2131
2308
|
}
|
|
2132
2309
|
var RESERVED_DOMAIN_FIELDS = {
|
|
2133
2310
|
name: "string",
|
|
@@ -2172,11 +2349,10 @@ function computeTypedDataDigest(payload) {
|
|
|
2172
2349
|
validatePayloadReachability(payload.primaryType, payload.types);
|
|
2173
2350
|
const domainHash = hashStruct("QRLDomain", payload.domain, payload.types);
|
|
2174
2351
|
const messageHash = hashStruct(payload.primaryType, payload.message, payload.types);
|
|
2175
|
-
return (
|
|
2352
|
+
return shake256Digest(concatBytes(SCHEME_TAG_TYPED, domainHash, messageHash), DIGEST_LEN);
|
|
2176
2353
|
}
|
|
2177
2354
|
|
|
2178
2355
|
// src/signing/verify.ts
|
|
2179
|
-
var mldsa = __toESM(require("@theqrl/mldsa87"));
|
|
2180
2356
|
function bytesOrHex(v) {
|
|
2181
2357
|
if (v instanceof Uint8Array) return v;
|
|
2182
2358
|
if (typeof v === "string") return hexToBytes(v);
|
|
@@ -2192,7 +2368,7 @@ function verifyMessage({
|
|
|
2192
2368
|
const pk = bytesOrHex(publicKey);
|
|
2193
2369
|
const msg = bytesOrHex(messageBytes);
|
|
2194
2370
|
const digest = computeMessageDigest(msg);
|
|
2195
|
-
return
|
|
2371
|
+
return mldsaVerify(sig, digest, pk, SCHEME_TAG_MSG);
|
|
2196
2372
|
} catch {
|
|
2197
2373
|
return false;
|
|
2198
2374
|
}
|
|
@@ -2202,7 +2378,7 @@ function verifyTypedData({ signature, publicKey, payload }) {
|
|
|
2202
2378
|
const sig = bytesOrHex(signature);
|
|
2203
2379
|
const pk = bytesOrHex(publicKey);
|
|
2204
2380
|
const digest = computeTypedDataDigest(payload);
|
|
2205
|
-
return
|
|
2381
|
+
return mldsaVerify(sig, digest, pk, SCHEME_TAG_TYPED);
|
|
2206
2382
|
} catch {
|
|
2207
2383
|
return false;
|
|
2208
2384
|
}
|