@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.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/PQCrypto.ts
86
+ // src/crypto/primitives.ts
88
87
  var import_ml_kem = require("@noble/post-quantum/ml-kem.js");
89
- var textEncoder = new TextEncoder();
90
- var LABEL = textEncoder.encode("pq-pair/v1");
91
- var LABEL_AEAD_SUFFIX = textEncoder.encode(" aead");
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 || !c.subtle) {
99
- throw new Error("PQCrypto: WebCrypto SubtleCrypto is not available in this environment");
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
- return u;
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 kemKeygen() {
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 kemEncaps(pk) {
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 kemDecaps(sk, ct) {
120
+ function mlkemDecaps(sk, ct) {
115
121
  return import_ml_kem.ml_kem768.decapsulate(ct, sk);
116
122
  }
117
- async function transcriptHash(cid, pk, ct) {
118
- const buf = concat(LABEL, cid, pk, ct);
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 deriveAeadKey(ss, htx) {
122
- const ikm = await subtle().importKey("raw", bs(ss), "HKDF", false, ["deriveKey"]);
123
- const info = concat(LABEL, LABEL_AEAD_SUFFIX, htx);
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: new Uint8Array(32), info: bs(info) },
126
- ikm,
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 importRawAeadKey(raw) {
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
- const buf = await subtle().exportKey("raw", key);
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
- const ct = await subtle().encrypt(
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
- const pt = await subtle().decrypt(
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 \u2014 ignoring");
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
- return {
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 \u2014 ignoring");
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 \u2014 session not established");
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 \u2014 session not established");
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
- log("Socket", `Participants changed: ${data.event}`);
633
- this.emit("participants_changed", data);
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 (!this.pendingJoin || this.pendingJoin.channelId !== channelId) return;
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 || "Failed to join channel"));
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 || "Failed to send message"));
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] + ALPHABET[c1] + ALPHABET[c2];
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] + ALPHABET[c1];
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
- const digest = await c.subtle.digest("SHA-256", bs2(buf));
883
- return new Uint8Array(digest);
949
+ return sha256(buf);
884
950
  }
885
951
  function fingerprintEquals(a, b) {
886
- if (a.length !== b.length) return false;
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 \u2014 this wallet and this dApp must both run protocol v2"
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 \u2014 regenerate the QR with a v2.0+ dApp SDK");
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 relayUrl = params.get("r") || void 0;
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
- const c = globalThis.crypto;
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 || DEFAULT_RELAY_URL;
1002
- this.chainId = options.chainId || "0x0";
1003
- this.storageKey = options.storageKey || `${STORAGE_KEY_PREFIX}:session`;
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 = (0, import_uuid.v4)();
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 = (0, import_uuid.v4)();
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 = (0, import_uuid.v4)();
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 = JSON.parse(raw);
1307
- if (session.version !== 2) return false;
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 = (0, import_uuid.v4)();
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
- this.messageQueue = this.messageQueue.then(() => this.handleRelayMessage(data)).catch((err) => error("ConnectionManager", "messageQueue handler error:", err));
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 (typeof message === "object" && message !== null) {
1376
- const msg = message;
1377
- if (msg.type === "key_handshake_SYNACK" /* SYNACK */) {
1378
- await this.handleSynAck(message);
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 (msg.type === "key_handshake_SYN" /* SYN */ || msg.type === "key_handshake_ACK" /* ACK */) {
1382
- warn("ConnectionManager", `Unexpected ${msg.type} on dApp side \u2014 ignoring`);
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
- this.handleDecryptedMessage(parsed);
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 info = msg;
1418
- const nextAccounts = info.accounts || [];
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
- async sendEncrypted(message) {
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: 2,
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 = JSON.parse(raw);
1522
- if (session.version !== 2) {
1523
- log("ConnectionManager", "Dropping legacy (pre-v2) session from storage");
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 (!session.createdAt || Date.now() - session.createdAt > SESSION_TTL_MS) {
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 (Mobile)",
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: generateUuid(),
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 — the wallet gets an instant disconnect instead of landing in
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 (0, import_sha3.shake256)(concatBytes(SCHEME_TAG_MSG, messageBytes), { dkLen: DIGEST_LEN });
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) return { kind: 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
- const size = sizeStr ? Number(sizeStr) : void 0;
1940
- if (sizeStr && (!Number.isInteger(size) || size <= 0)) {
1941
- throw new Error(`invalid array size: ${type}`);
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, size };
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
- for (const f of types[name]) {
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
- if (!Array.isArray(def) || def.length === 0) {
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 (0, import_sha32.shake256)(new TextEncoder().encode(encodeType(primary, types)), { dkLen: DIGEST_LEN });
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: ${String(addr)}`);
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 (value === null || typeof value !== "object" || Array.isArray(value)) {
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 (0, import_sha32.shake256)(new TextEncoder().encode(value), { dkLen: DIGEST_LEN });
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 (0, import_sha32.shake256)(hexToBytes(value), { dkLen: DIGEST_LEN });
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 (0, import_sha32.shake256)(concatBytesArr(chunks), { dkLen: DIGEST_LEN });
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 (0, import_sha32.shake256)(concatBytesArr(parts), { dkLen: DIGEST_LEN });
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 (0, import_sha32.shake256)(concatBytes(SCHEME_TAG_TYPED, domainHash, messageHash), { dkLen: DIGEST_LEN });
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 mldsa.cryptoSignVerify(sig, digest, pk, SCHEME_TAG_MSG);
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 mldsa.cryptoSignVerify(sig, digest, pk, SCHEME_TAG_TYPED);
2381
+ return mldsaVerify(sig, digest, pk, SCHEME_TAG_TYPED);
2206
2382
  } catch {
2207
2383
  return false;
2208
2384
  }