@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.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/PQCrypto.ts
10
+ // src/crypto/primitives.ts
12
11
  import { ml_kem768 } from "@noble/post-quantum/ml-kem.js";
13
- var textEncoder = new TextEncoder();
14
- var LABEL = textEncoder.encode("pq-pair/v1");
15
- var LABEL_AEAD_SUFFIX = textEncoder.encode(" aead");
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 || !c.subtle) {
23
- throw new Error("PQCrypto: WebCrypto SubtleCrypto is not available in this environment");
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
- return u;
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 kemKeygen() {
36
+ function mlkemKeygen() {
31
37
  const { publicKey, secretKey } = ml_kem768.keygen();
32
38
  return { pk: publicKey, sk: secretKey };
33
39
  }
34
- function kemEncaps(pk) {
40
+ function mlkemEncaps(pk) {
35
41
  const { cipherText, sharedSecret } = ml_kem768.encapsulate(pk);
36
42
  return { ct: cipherText, ss: sharedSecret };
37
43
  }
38
- function kemDecaps(sk, ct) {
44
+ function mlkemDecaps(sk, ct) {
39
45
  return ml_kem768.decapsulate(ct, sk);
40
46
  }
41
- async function transcriptHash(cid, pk, ct) {
42
- const buf = concat(LABEL, cid, pk, ct);
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 deriveAeadKey(ss, htx) {
46
- const ikm = await subtle().importKey("raw", bs(ss), "HKDF", false, ["deriveKey"]);
47
- const info = concat(LABEL, LABEL_AEAD_SUFFIX, htx);
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: new Uint8Array(32), info: bs(info) },
50
- ikm,
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 importRawAeadKey(raw) {
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
- const buf = await subtle().exportKey("raw", key);
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
- const ct = await subtle().encrypt(
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
- const pt = await subtle().decrypt(
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 \u2014 ignoring");
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
- return {
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 \u2014 ignoring");
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 \u2014 session not established");
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 \u2014 session not established");
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
- log("Socket", `Participants changed: ${data.event}`);
557
- this.emit("participants_changed", data);
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 (!this.pendingJoin || this.pendingJoin.channelId !== channelId) return;
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 || "Failed to join channel"));
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 || "Failed to send message"));
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] + ALPHABET[c1] + ALPHABET[c2];
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] + ALPHABET[c1];
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
- const digest = await c.subtle.digest("SHA-256", bs2(buf));
807
- return new Uint8Array(digest);
873
+ return sha256(buf);
808
874
  }
809
875
  function fingerprintEquals(a, b) {
810
- if (a.length !== b.length) return false;
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 \u2014 this wallet and this dApp must both run protocol v2"
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 \u2014 regenerate the QR with a v2.0+ dApp SDK");
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 relayUrl = params.get("r") || void 0;
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
- const c = globalThis.crypto;
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 || DEFAULT_RELAY_URL;
926
- this.chainId = options.chainId || "0x0";
927
- this.storageKey = options.storageKey || `${STORAGE_KEY_PREFIX}:session`;
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 = uuidv4();
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 = uuidv4();
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 = uuidv4();
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 = JSON.parse(raw);
1231
- if (session.version !== 2) return false;
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 = uuidv4();
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
- this.messageQueue = this.messageQueue.then(() => this.handleRelayMessage(data)).catch((err) => error("ConnectionManager", "messageQueue handler error:", err));
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 (typeof message === "object" && message !== null) {
1300
- const msg = message;
1301
- if (msg.type === "key_handshake_SYNACK" /* SYNACK */) {
1302
- await this.handleSynAck(message);
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 (msg.type === "key_handshake_SYN" /* SYN */ || msg.type === "key_handshake_ACK" /* ACK */) {
1306
- warn("ConnectionManager", `Unexpected ${msg.type} on dApp side \u2014 ignoring`);
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
- this.handleDecryptedMessage(parsed);
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 info = msg;
1342
- const nextAccounts = info.accounts || [];
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
- async sendEncrypted(message) {
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: 2,
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 = JSON.parse(raw);
1446
- if (session.version !== 2) {
1447
- log("ConnectionManager", "Dropping legacy (pre-v2) session from storage");
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 (!session.createdAt || Date.now() - session.createdAt > SESSION_TTL_MS) {
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 (Mobile)",
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: generateUuid(),
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 — the wallet gets an instant disconnect instead of landing in
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 shake256(concatBytes(SCHEME_TAG_MSG, messageBytes), { dkLen: DIGEST_LEN });
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) return { kind: 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
- const size = sizeStr ? Number(sizeStr) : void 0;
1864
- if (sizeStr && (!Number.isInteger(size) || size <= 0)) {
1865
- throw new Error(`invalid array size: ${type}`);
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, size };
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
- for (const f of types[name]) {
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
- if (!Array.isArray(def) || def.length === 0) {
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 shake2562(new TextEncoder().encode(encodeType(primary, types)), { dkLen: DIGEST_LEN });
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: ${String(addr)}`);
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 (value === null || typeof value !== "object" || Array.isArray(value)) {
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 shake2562(new TextEncoder().encode(value), { dkLen: DIGEST_LEN });
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 shake2562(hexToBytes(value), { dkLen: DIGEST_LEN });
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 shake2562(concatBytesArr(chunks), { dkLen: DIGEST_LEN });
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 shake2562(concatBytesArr(parts), { dkLen: DIGEST_LEN });
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 shake2562(concatBytes(SCHEME_TAG_TYPED, domainHash, messageHash), { dkLen: DIGEST_LEN });
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 mldsa.cryptoSignVerify(sig, digest, pk, SCHEME_TAG_MSG);
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 mldsa.cryptoSignVerify(sig, digest, pk, SCHEME_TAG_TYPED);
2305
+ return mldsaVerify(sig, digest, pk, SCHEME_TAG_TYPED);
2130
2306
  } catch {
2131
2307
  return false;
2132
2308
  }