@qrlwallet/connect 3.3.0 → 5.0.0

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.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import EventEmitter from 'eventemitter3';
2
2
 
3
3
  /**
4
- * Post-quantum handshake (v2).
4
+ * Post-quantum handshake (v3).
5
5
  *
6
6
  * Flow:
7
7
  * dApp relay wallet
8
8
  * ────────────────────────────────────────────────────────────────────
9
- * initiate() → (pk, sk)
10
- * QR(cid, pk) rendered scan QR
11
- * receiveQR(cid, pk)
9
+ * initiate() → (pk, sk, cap)
10
+ * QR(cid, fp, cap) rendered scan QR
11
+ * receiveQR(cid, pk, cap)
12
12
  * → SYNACK{ct, c0}
13
13
  * ◄── SYNACK ─────── send
14
14
  * onSynAck(cid, SYNACK)
@@ -20,6 +20,7 @@ import EventEmitter from 'eventemitter3';
20
20
  */
21
21
 
22
22
  interface Session {
23
+ protocolVersion: 3;
23
24
  cid: Uint8Array;
24
25
  key: CryptoKey;
25
26
  htx: Uint8Array;
@@ -30,6 +31,7 @@ interface Session {
30
31
  }
31
32
  /** Wire-safe persisted form for localStorage. */
32
33
  interface PersistedSession {
34
+ protocolVersion: 3;
33
35
  cid: string;
34
36
  kAeadRaw: string;
35
37
  htx: string;
@@ -49,6 +51,10 @@ interface AckMessage {
49
51
  c1: string;
50
52
  v: number;
51
53
  }
54
+ interface InitiatedKeyExchange {
55
+ publicKey: Uint8Array;
56
+ capability: Uint8Array;
57
+ }
52
58
  interface KeyExchangeEvents {
53
59
  keys_exchanged: () => void;
54
60
  step_change: (step: KeyExchangeMessageType) => void;
@@ -56,29 +62,40 @@ interface KeyExchangeEvents {
56
62
  declare class KeyExchange extends EventEmitter<KeyExchangeEvents> {
57
63
  private isOriginator;
58
64
  private keypair;
65
+ private capability;
59
66
  private session;
60
67
  private step;
61
68
  private awaitingSynAck;
62
69
  private awaitingAck;
70
+ private awaitingAckDelivery;
63
71
  private keysExchanged;
72
+ private stateGeneration;
64
73
  private lastAck;
65
74
  constructor(isOriginator: boolean, restored?: Session);
66
75
  /**
67
76
  * Originator (dApp) side: begin a fresh handshake by generating an
68
- * ephemeral ML-KEM-768 keypair. Returns pk to embed in the QR URI.
77
+ * ephemeral ML-KEM-768 keypair and a fresh 32-byte pairing capability.
78
+ * Both returned arrays are copies; the originator retains its own secret
79
+ * capability until the handshake succeeds or is retired.
69
80
  */
70
- initiate(): Uint8Array;
81
+ initiate(): InitiatedKeyExchange;
71
82
  /**
72
83
  * Originator: handle a SYNACK received over the relay.
73
84
  * Returns an ACK wire message to send, or null if the SYNACK was a
74
85
  * duplicate (handshake already complete).
75
86
  */
76
87
  onSynAck(cid: Uint8Array, msg: SynAckMessage): Promise<AckMessage | null>;
88
+ /**
89
+ * Originator: publish completion only after the relay acknowledged ACK.
90
+ * This is deliberately separate from onSynAck() so transport ambiguity
91
+ * cannot leave a live or persistable one-sided session.
92
+ */
93
+ confirmOriginatorAckDelivered(): void;
77
94
  /**
78
95
  * Responder (wallet) side: begin handshake from a scanned QR.
79
96
  * Returns the SYNACK wire message to emit on the relay.
80
97
  */
81
- receiveQR(cid: Uint8Array, pk: Uint8Array): Promise<SynAckMessage>;
98
+ receiveQR(cid: Uint8Array, pk: Uint8Array, capability: Uint8Array): Promise<SynAckMessage>;
82
99
  /**
83
100
  * Responder: verify an incoming ACK and finalize the session.
84
101
  * Duplicates are silently ignored (handshake is idempotent).
@@ -108,10 +125,11 @@ interface DAppMetadata {
108
125
  url: string;
109
126
  icon?: string;
110
127
  /**
111
- * Optional "return to dApp" target (a URL or app deep-link scheme). Sent to
112
- * the wallet in ORIGINATOR_INFO; after the wallet resolves a restricted
113
- * request it bounces the user back here (WalletConnect-style peer redirect)
114
- * so a same-device deep-link flow does not strand the user in the wallet.
128
+ * Optional credential-free HTTP(S) "return to dApp" target. HTTPS is
129
+ * required except for explicit loopback development URLs. Sent to the
130
+ * wallet in ORIGINATOR_INFO; after the wallet resolves a restricted request
131
+ * it bounces the user back here so a same-device flow does not strand the
132
+ * user in the wallet.
115
133
  */
116
134
  redirectUrl?: string;
117
135
  }
@@ -122,11 +140,12 @@ interface DAppMetadata {
122
140
  * the ML-KEM keypair is ephemeral and zeroized after the handshake.
123
141
  * Re-pair (generate a new QR) to rotate the session key.
124
142
  *
125
- * v3 checkpoints the AEAD counters on every seal/open. v2 records (sparse
126
- * checkpoints, stale-counter nonce-reuse risk on restore) are dropped.
143
+ * v5 checkpoints every seal/open, requires exclusive browser-tab ownership,
144
+ * and proves the AEAD key came from a PQP3 capability-bound handshake.
145
+ * Older records cannot prove all three invariants and are dropped.
127
146
  */
128
147
  interface DAppSession {
129
- version: 3;
148
+ version: 5;
130
149
  channelId: string;
131
150
  keyExchange: PersistedSession;
132
151
  dappMetadata: DAppMetadata;
@@ -140,6 +159,8 @@ interface PendingRequest {
140
159
  id: string | number;
141
160
  method: string;
142
161
  params?: unknown[] | undefined;
162
+ /** Immutable postcondition captured before caller-owned params can change. */
163
+ expectedChainId?: string | undefined;
143
164
  resolve: (result: unknown) => void;
144
165
  reject: (error: Error) => void;
145
166
  timestamp: number;
@@ -147,9 +168,10 @@ interface PendingRequest {
147
168
  /**
148
169
  * Key-exchange wire-message types.
149
170
  *
150
- * v2 note: only SYNACK (wallet→dApp) and ACK (dApp→wallet) are transmitted
151
- * over the relay. The SYN step is carried by the QR code itself - the dApp's
152
- * ML-KEM-768 encapsulation key is embedded in the URI, not sent on the wire.
171
+ * v3 note: only SYNACK (wallet to dApp) and ACK (dApp to wallet) are
172
+ * transmitted over the relay. The SYN step is represented by the pairing URI,
173
+ * while the relay carries only the dApp's ML-KEM public key. The URI contains
174
+ * the key fingerprint and a capability that is never uploaded to the relay.
153
175
  */
154
176
  declare enum KeyExchangeMessageType {
155
177
  SYN = "key_handshake_SYN",
@@ -247,31 +269,68 @@ interface EIP6963ProviderInfoOverride {
247
269
  rdns?: string;
248
270
  }
249
271
  /**
250
- * Rich response object returned by both `qrl_signMessage` and
251
- * `qrl_signTypedData`. The signature alone is not enough to verify since
252
- * ML-DSA-87 public keys cannot be recovered from a signature, so the
253
- * wallet always returns the public key explicitly. Stateless verifiers
254
- * (e.g. the SDK's `verifyMessage` / `verifyTypedData`) need every field.
272
+ * Rich response object returned by `qrl_signMessage` and retained for locked
273
+ * `qrl_signTypedData` v1 fixtures. The signature alone is not enough to
274
+ * verify since ML-DSA-87 public keys cannot be recovered from a signature,
275
+ * so the wallet returns the public key explicitly. Stateless verifiers need
276
+ * the original payload plus the signature fields below.
255
277
  */
256
278
  interface QrlSignedResult {
257
- /** 0x-hex of the 4595-byte ML-DSA-87 signature. */
279
+ /** 0x-hex of the 4627-byte ML-DSA-87 signature. */
258
280
  signature: string;
259
281
  /** 0x-hex of the 2592-byte ML-DSA-87 public key. */
260
282
  publicKey: string;
261
- /** 41-char checksummed Q-address derived from `publicKey`. */
283
+ /**
284
+ * 0x-hex of the 3-byte wallet descriptor used to derive `signer`.
285
+ * Optional while older wallet builds are upgraded; bound verification
286
+ * requires it.
287
+ */
288
+ descriptor?: string;
289
+ /** QIP-55 129-character checksummed Q-address derived from descriptor + key. */
262
290
  signer: string;
263
291
  /** 0x-hex of the 64-byte SHAKE256 digest that was signed. */
264
292
  digest: string;
265
293
  /** Scheme tag: 'QRL-SIGN-MSG-v1' or 'QRL-SIGN-TYPED-v1'. */
266
- schemeVersion: string;
294
+ schemeVersion: QrlSigningSchemeVersion;
295
+ }
296
+ /** A current wallet response whose descriptor can be signer-bound. */
297
+ interface QrlSignedResultWithDescriptor extends QrlSignedResult {
298
+ descriptor: string;
299
+ }
300
+ type QrlSigningSchemeVersion = 'QRL-SIGN-MSG-v1' | 'QRL-SIGN-TYPED-v1';
301
+ /** Strict wire shape returned by `qrl_signMessage`. */
302
+ interface QrlSignedMessageResult extends QrlSignedResult {
303
+ schemeVersion: 'QRL-SIGN-MSG-v1';
267
304
  }
268
305
  /**
269
- * Response from `qrl_signTypedData`. Echoes `domain` so a stateless
270
- * verifier doesn't need to be told the domain out-of-band.
306
+ * Historical v1 response from `qrl_signTypedData`. It echoes `domain` so a
307
+ * stateless verifier does not need to be told the domain out-of-band.
271
308
  */
272
309
  interface QrlSignedTypedDataResult extends QrlSignedResult {
310
+ schemeVersion: 'QRL-SIGN-TYPED-v1';
273
311
  domain: Record<string, unknown>;
274
312
  }
313
+ /** Every strict signing-result wire shape understood by this SDK. */
314
+ type QrlSigningResult = QrlSignedMessageResult | QrlSignedTypedDataResult;
315
+ /**
316
+ * Validate an unknown value as the exact `qrl_signMessage` result wire shape.
317
+ * This checks structure and fixed widths only. Use `verifyMessageForSigner`
318
+ * with the original message to authenticate the signature and signer binding.
319
+ */
320
+ declare function isQrlSignedMessageResult(value: unknown): value is QrlSignedMessageResult;
321
+ /**
322
+ * Validate an unknown value as the exact `qrl_signTypedData` result wire shape.
323
+ * This checks structure and fixed widths only. Use `verifyTypedDataForSigner`
324
+ * with the original payload to authenticate the signature and signer binding.
325
+ */
326
+ declare function isQrlSignedTypedDataResult(value: unknown): value is QrlSignedTypedDataResult;
327
+ /** Validate either exact signing-result wire shape from an unknown value. */
328
+ declare function isQrlSignedResult(value: unknown): value is QrlSigningResult;
329
+ /**
330
+ * Strictly validate an unknown signing result and narrow it to one carrying
331
+ * the exact 3-byte ML-DSA descriptor required for signer-bound verification.
332
+ */
333
+ declare function hasSigningDescriptor(result: unknown): result is QrlSigningResult & QrlSignedResultWithDescriptor;
275
334
  /**
276
335
  * `qrl_signMessage` params: `[signer, messageHex]`. `signer` must equal the
277
336
  * dApp's currently-connected account; the wallet rejects mismatches before
@@ -279,10 +338,10 @@ interface QrlSignedTypedDataResult extends QrlSignedResult {
279
338
  */
280
339
  type QrlSignMessageParams = [string, string];
281
340
  /**
282
- * `qrl_signTypedData` params: `[signer, payload]`. Payload mirrors EIP-712
283
- * shape: `{ types, primaryType, domain, message }`, but with `QRLDomain`
284
- * in place of `EIP712Domain` and SHAKE256-based hashing. See `signing/`
285
- * for the full encoder.
341
+ * Reserved `qrl_signTypedData` params: `[signer, payload]`. This interface
342
+ * describes the historical v1 payload retained by the verifier exports. The
343
+ * QIP-55 provider rejects requests until a versioned 64-byte encoding is
344
+ * defined.
286
345
  */
287
346
  interface QrlTypedDataPayload {
288
347
  types: Record<string, readonly {
@@ -353,6 +412,12 @@ declare class QRLConnectProvider extends EventEmitter<ProviderEvents> {
353
412
  private orphanedRequests;
354
413
  private readonly requestIdPrefix;
355
414
  private requestCounter;
415
+ private requestGeneration;
416
+ private requestCancellationMessage;
417
+ private lifecycleTransition;
418
+ private disconnectInFlight;
419
+ private restrictedRequestActive;
420
+ private restrictedRequestQueue;
356
421
  readonly isQRLConnect = true;
357
422
  constructor(options: QRLConnectOptions);
358
423
  private startEip6963Announce;
@@ -374,6 +439,10 @@ declare class QRLConnectProvider extends EventEmitter<ProviderEvents> {
374
439
  */
375
440
  stopEip6963Announce(): void;
376
441
  private setupConnectionListeners;
442
+ private handleJsonRpcResponse;
443
+ private cancelAllRequests;
444
+ private assertRequestGeneration;
445
+ private isCurrentChain;
377
446
  /**
378
447
  * Generate a connection URI for QR code display or deep link redirect.
379
448
  */
@@ -393,6 +462,15 @@ declare class QRLConnectProvider extends EventEmitter<ProviderEvents> {
393
462
  method: string;
394
463
  params?: unknown[];
395
464
  }): Promise<unknown>;
465
+ /**
466
+ * Keep at most one approval-bound request active. Wallet approval screens
467
+ * are a serial human interaction, and request-id ordering must remain
468
+ * stable even when a dApp fires several signing calls concurrently.
469
+ */
470
+ private enqueueRestrictedRequest;
471
+ private runRestrictedRequest;
472
+ private finishRestrictedRequest;
473
+ private performRequest;
396
474
  /**
397
475
  * Redirect only for calls the user must approve, on mobile, and only when
398
476
  * the wallet is actually absent from the channel: right after pairing its
@@ -470,7 +548,7 @@ declare class QRLConnectProvider extends EventEmitter<ProviderEvents> {
470
548
  /**
471
549
  * Connection Manager - orchestrates socket lifecycle, the post-quantum
472
550
  * handshake, encrypted message routing, and session persistence for the
473
- * dApp side of QRL Connect v2.
551
+ * dApp side of QRL Connect v3.
474
552
  */
475
553
 
476
554
  interface ConnectionManagerEvents {
@@ -510,6 +588,13 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
510
588
  private pendingRestore;
511
589
  private messageQueue;
512
590
  private outboundQueue;
591
+ private persistenceQueue;
592
+ private readonly storage;
593
+ private readonly persistenceEnabled;
594
+ private readonly sessionOwnership;
595
+ private persistenceBroken;
596
+ private sessionCreatedAt;
597
+ private sessionGeneration;
513
598
  private consecutiveDecryptFailures;
514
599
  private static MAX_RECONNECT_FAILURES;
515
600
  private static MAX_DECRYPT_FAILURES;
@@ -520,9 +605,11 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
520
605
  storageKey?: string | undefined;
521
606
  });
522
607
  private setupSocketListeners;
608
+ private handleAutoReconnected;
523
609
  private setupKeyExchangeListeners;
524
610
  /**
525
- * Generate a new v2 connection URI. Rotates channel id and keypair.
611
+ * Generate a new v3 connection URI. Rotates channel id, keypair, and the
612
+ * QR-only pairing capability.
526
613
  * Returns a `qrlconnect://?q=…` URI safe for QR-rendering or deep-link.
527
614
  */
528
615
  getConnectionURI(retryOnConflict?: boolean): Promise<string>;
@@ -585,6 +672,11 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
585
672
  sendJsonRpc(request: JsonRpcRequest): Promise<void>;
586
673
  getStatus(): ConnectionStatus;
587
674
  getAccounts(): string[];
675
+ /**
676
+ * Commit accounts returned by an approved qrl_requestAccounts response.
677
+ * WALLET_INFO alone cannot grant this authorization.
678
+ */
679
+ authorizeAccounts(value: unknown): Promise<string[]>;
588
680
  getChainId(): string;
589
681
  getChannelId(): string;
590
682
  /** Check (sync) if a persisted session exists and has not expired. */
@@ -597,6 +689,13 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
597
689
  * instant disconnect. Mirrors the wallet side's pattern.
598
690
  */
599
691
  private flushTerminate;
692
+ /**
693
+ * Ensure this socket is a relay participant before closing a restorable or
694
+ * temporarily disconnected channel. close_channel is intentionally
695
+ * participant-only on the relay, so a cold stored session cannot be retired
696
+ * by emitting the close event from an unjoined socket.
697
+ */
698
+ private prepareRelayChannelForRetirement;
600
699
  /**
601
700
  * Reset to a fresh channel + keypair. Sends TERMINATE to any live peer,
602
701
  * drops the persisted session, and prepares a clean state for
@@ -616,6 +715,7 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
616
715
  * clear local state and surface the standard terminated teardown.
617
716
  */
618
717
  private teardownDesyncedSession;
718
+ private teardownPersistenceFailedSession;
619
719
  private handleSynAck;
620
720
  private handleDecryptedMessage;
621
721
  private sendPlaintext;
@@ -634,8 +734,17 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
634
734
  private setStatus;
635
735
  private isDappParticipantConflictError;
636
736
  private areArraysEqual;
737
+ private invalidateSessionWork;
738
+ private captureSessionWorkContext;
739
+ private isSessionWorkCurrent;
740
+ private assertSessionWorkCurrent;
741
+ private acquireSessionOwnership;
742
+ private releaseSessionOwnership;
637
743
  private persistSession;
744
+ private persistSessionNow;
638
745
  private readStoredSession;
746
+ private removeStoredSession;
747
+ private removeStoredSessionOrThrow;
639
748
  private clearSession;
640
749
  }
641
750
 
@@ -644,7 +753,7 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
644
753
  */
645
754
 
646
755
  interface SocketClientEvents {
647
- message: (data: RelayMessage) => void;
756
+ message: (data: unknown) => void;
648
757
  connected: () => void;
649
758
  disconnected: (reason: string) => void;
650
759
  reconnected: (result: JoinResult) => void;
@@ -658,7 +767,7 @@ interface JoinResult {
658
767
  bufferedMessages: unknown[];
659
768
  /**
660
769
  * Base64-encoded KEM public key the relay has bound to this channel.
661
- * Populated for wallet joins (v2 protocol). `null` if the dApp hasn't
770
+ * Populated for wallet joins (v3 protocol). `null` if the dApp hasn't
662
771
  * registered a PK yet - wallet callers must treat this as a retry signal.
663
772
  */
664
773
  channelPublicKey: string | null;
@@ -683,6 +792,9 @@ declare class SocketClient extends EventEmitter<SocketClientEvents> {
683
792
  private publicKeyBase64;
684
793
  private seq;
685
794
  private pendingJoin;
795
+ private pendingSends;
796
+ private pendingJoinAttempts;
797
+ private get expectedPeerType();
686
798
  constructor(relayUrl: string, clientType: 'dapp' | 'wallet');
687
799
  /**
688
800
  * dApp-only: stash the PK so it gets uploaded on every join_channel
@@ -705,6 +817,7 @@ declare class SocketClient extends EventEmitter<SocketClientEvents> {
705
817
  */
706
818
  joinChannel(channelId: string): Promise<JoinResult>;
707
819
  private joinChannelNow;
820
+ private rejectPendingJoinAttempts;
708
821
  /**
709
822
  * Send a message through the relay.
710
823
  */
@@ -712,6 +825,7 @@ declare class SocketClient extends EventEmitter<SocketClientEvents> {
712
825
  success: boolean;
713
826
  buffered: boolean;
714
827
  }>;
828
+ private rejectPendingSends;
715
829
  /**
716
830
  * Emit an event and resolve once the relay acks it, or after a bounded
717
831
  * flush window. Lets a caller await transmission before tearing the
@@ -725,7 +839,7 @@ declare class SocketClient extends EventEmitter<SocketClientEvents> {
725
839
  * peer could not open it). Resolves once the close is flushed or the
726
840
  * bounded window elapses, so the caller can safely disconnect afterwards.
727
841
  */
728
- closeChannel(): Promise<void>;
842
+ closeChannel(): Promise<boolean>;
729
843
  /**
730
844
  * Leave the current channel.
731
845
  */
@@ -773,14 +887,30 @@ declare const DIGEST_LEN = 64;
773
887
  declare function computeMessageDigest(messageBytes: Uint8Array): Uint8Array;
774
888
 
775
889
  /**
776
- * EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
890
+ * Historical EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
777
891
  *
778
892
  * Byte-identical algorithm with the wallet's signing/typedData.ts; this
779
893
  * file is the SDK port (only the import block differs: the SDK routes
780
894
  * SHAKE256 through src/crypto/primitives.ts, its crypto boundary). See
781
895
  * the wallet copy for the spec-level comments; behavior must stay in
782
896
  * lock-step or the cross-repo parity test fails on next CI.
897
+ *
898
+ * The v1 address slot is 32 bytes. QIP-55 address fields fail closed until a
899
+ * 64-byte encoding receives a new scheme tag and lockstep wallet fixtures.
900
+ * Address-free digests retain their existing encoding.
783
901
  */
902
+ declare const TYPED_DATA_LIMITS: Readonly<{
903
+ maxTypes: 32;
904
+ maxFieldsPerType: 32;
905
+ maxTotalFields: 256;
906
+ maxTypeGraphDepth: 12;
907
+ maxArrayNesting: 12;
908
+ maxArrayLength: 256;
909
+ maxEncodedValues: 2048;
910
+ maxDynamicBytes: number;
911
+ maxIdentifierLength: 64;
912
+ maxFieldTypeLength: 128;
913
+ }>;
784
914
  type FieldType = string;
785
915
  interface TypedField {
786
916
  name: string;
@@ -800,28 +930,70 @@ declare function encodeType(primary: string, types: TypeMap): string;
800
930
  declare function typeHash(primary: string, types: TypeMap): Uint8Array;
801
931
  declare function encodeField(type: FieldType, value: unknown, types: TypeMap): Uint8Array;
802
932
  declare function hashStruct(primary: string, data: Message, types: TypeMap): Uint8Array;
803
- declare function computeTypedDataDigest(payload: TypedDataPayload): Uint8Array;
933
+ declare function computeTypedDataDigest(payload: unknown): Uint8Array;
804
934
 
805
935
  /**
806
936
  * Stateless verifiers for the two PQ signing schemes. The SDK exposes these
807
937
  * so a dApp can re-verify the rich response object returned by the wallet
808
- * without round-tripping back through the relay. They never touch a secret
809
- * key and never derive an address; that's the wallet's job.
938
+ * without round-tripping back through the relay. The lower-level
939
+ * verifyMessageSignature / verifyTypedDataSignature prove only that a
940
+ * supplied public key made the signature. Authorization decisions should use
941
+ * the ForSigner variants, which also bind that key to the expected
942
+ * current-format address.
810
943
  */
811
944
 
945
+ declare const ML_DSA_DESCRIPTOR_BYTES = 3;
946
+ declare const ML_DSA_87_PUBLIC_KEY_BYTES = 2592;
947
+ declare const ML_DSA_87_SIGNATURE_BYTES = 4627;
812
948
  interface VerifyMessageParams {
813
949
  signature: Uint8Array | string;
814
950
  publicKey: Uint8Array | string;
815
951
  /** 0x-hex bytes that were originally signed. */
816
952
  messageBytes: Uint8Array | string;
817
953
  }
818
- declare function verifyMessage({ signature, publicKey, messageBytes, }: VerifyMessageParams): boolean;
954
+ /**
955
+ * Verify against an explicitly supplied ML-DSA public key only. This does not
956
+ * prove that the key derives the signer claimed by a wallet response. Use
957
+ * verifyMessageForSigner for authentication or authorization decisions.
958
+ */
959
+ declare function verifyMessageSignature({ signature, publicKey, messageBytes, }: VerifyMessageParams): boolean;
819
960
  interface VerifyTypedDataParams {
820
961
  signature: Uint8Array | string;
821
962
  publicKey: Uint8Array | string;
822
963
  payload: TypedDataPayload;
823
964
  }
824
- declare function verifyTypedData({ signature, publicKey, payload }: VerifyTypedDataParams): boolean;
965
+ /**
966
+ * Verify against an explicitly supplied ML-DSA public key only. This does not
967
+ * bind that key to a Q-address. Use verifyTypedDataForSigner when signer
968
+ * identity matters.
969
+ */
970
+ declare function verifyTypedDataSignature({ signature, publicKey, payload, }: VerifyTypedDataParams): boolean;
971
+ interface VerifyMessageForSignerParams extends VerifyMessageParams {
972
+ /** QIP-55 Q + 128 hex address expected by the dApp. */
973
+ expectedSigner: string;
974
+ /** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
975
+ descriptor?: Uint8Array | string | undefined;
976
+ }
977
+ /** Verify both the message signature and public-key-to-signer binding. */
978
+ declare function verifyMessageForSigner({ expectedSigner, descriptor, signature, publicKey, messageBytes, }: VerifyMessageForSignerParams): boolean;
979
+ interface VerifyTypedDataForSignerParams extends VerifyTypedDataParams {
980
+ /** QIP-55 Q + 128 hex address expected by the dApp. */
981
+ expectedSigner: string;
982
+ /** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
983
+ descriptor?: Uint8Array | string | undefined;
984
+ }
985
+ /** Verify both the typed-data signature and public-key-to-signer binding. */
986
+ declare function verifyTypedDataForSigner({ expectedSigner, descriptor, signature, publicKey, payload, }: VerifyTypedDataForSignerParams): boolean;
987
+ /**
988
+ * @deprecated Use verifyMessageSignature for a key-only check, or
989
+ * verifyMessageForSigner when signer identity matters.
990
+ */
991
+ declare function verifyMessage(params: VerifyMessageParams): boolean;
992
+ /**
993
+ * @deprecated Use verifyTypedDataSignature for a key-only check, or
994
+ * verifyTypedDataForSigner when signer identity matters.
995
+ */
996
+ declare function verifyTypedData(params: VerifyTypedDataParams): boolean;
825
997
 
826
998
  /**
827
999
  * Local hex/bytes utilities for the signing module.
@@ -833,38 +1005,40 @@ declare function bytesToHex(bytes: Uint8Array): string;
833
1005
  declare function concatBytes(...parts: Uint8Array[]): Uint8Array;
834
1006
 
835
1007
  /**
836
- * v2 QR URI codec (PQP2).
1008
+ * v3 QR URI codec (PQP3).
837
1009
  *
838
- * Format: qrlconnect://?q=<base45(PQP2 || cid || fp)>[&r=<relayUrl>]
1010
+ * Format: qrlconnect://?q=<base45(PQP3 || cid || fp || cap)>[&r=<relayUrl>]
839
1011
  *
840
- * PQP2 = "PQP2" magic (4 B)
841
- * cid = 16 bytes (UUIDv4 raw) (16 B)
842
- * fp = SHA-256("pq-fp/v2" || cid || pk) (full 32B) (32 B)
843
- * total (52 B)
1012
+ * PQP3 = "PQP3" magic (4 B)
1013
+ * cid = 16 bytes (UUIDv4 raw) (16 B)
1014
+ * fp = SHA-256("pq-fp/v3" || cid || pk || cap) (full 32B) (32 B)
1015
+ * cap = fresh CSPRNG pairing capability (32 B)
1016
+ * total (84 B)
844
1017
  *
845
- * 52 bytes → ~78 alphanumeric chars after base45 → URI under 150 chars →
846
- * fits a version-5 QR at ECC-M. Scannable from any 2015-vintage camera.
1018
+ * The capability is a bearer secret. It is never uploaded to the relay and
1019
+ * must not be logged or persisted before the handshake consumes it.
847
1020
  *
848
1021
  * Security: the public key is NOT carried in the QR. It's uploaded by the
849
1022
  * dApp to the relay at channel creation, the relay binds it to the cid, and
850
- * the wallet fetches it via the join_channel ack. The 32-byte fp acts as
851
- * the out-of-band commitment: the wallet rejects the PK served by the relay
852
- * unless SHA-256("pq-fp/v2" || cid || pk) matches the fp from the QR.
853
- * Full-width SHA-256 (2^128 collision resistance) rules out brute-force
854
- * substitution of a maliciously-crafted PK with the same fingerprint.
1023
+ * the wallet fetches it via the join_channel ack. The fingerprint commits
1024
+ * the relay-served key to the exact out-of-band capability. The capability
1025
+ * is also bound into the transcript and HKDF by the key-exchange layer, so a
1026
+ * relay cannot fabricate an authenticated wallet hello.
855
1027
  *
856
- * Legacy v2.0 URIs (magic=PQP1, 1208-byte blob with embedded PK) are
857
- * rejected with a clear error by parseConnectionURI - this is a hard
858
- * break before 2.0.0 ships, no backcompat.
1028
+ * Legacy PQP1 and PQP2 URIs are rejected with a clear migration error. This
1029
+ * intentional v4.0 break prevents fallback to a pairing mode where the relay
1030
+ * could impersonate a wallet.
859
1031
  */
860
1032
  declare const CID_LEN = 16;
861
1033
  declare const FP_LEN = 32;
1034
+ declare const CAP_LEN = 32;
862
1035
  declare const BLOB_LEN: number;
1036
+ declare const MAX_CONNECTION_URI_LENGTH = 4096;
863
1037
  /**
864
- * Compute the full 32-byte fingerprint binding (label || cid || pk).
1038
+ * Compute the full 32-byte fingerprint binding (label || cid || pk || cap).
865
1039
  * Exported so the wallet side can re-derive and verify.
866
1040
  */
867
- declare function computeFingerprint(cid: Uint8Array, pk: Uint8Array): Promise<Uint8Array>;
1041
+ declare function computeFingerprint(cid: Uint8Array, pk: Uint8Array, capability: Uint8Array): Promise<Uint8Array>;
868
1042
  /**
869
1043
  * Constant-time comparison of two byte arrays. Both must be the same length.
870
1044
  * The verifier MUST use this rather than `===` or `indexOf` so timing signals
@@ -872,18 +1046,19 @@ declare function computeFingerprint(cid: Uint8Array, pk: Uint8Array): Promise<Ui
872
1046
  */
873
1047
  declare function fingerprintEquals(a: Uint8Array, b: Uint8Array): boolean;
874
1048
  /**
875
- * Encode (cid, pk) as a qrlconnect:// URI. Note the PK is not stored in the
876
- * URI - we compute its fingerprint and embed only that. The caller must
877
- * upload the PK to the relay separately before publishing this URI.
1049
+ * Encode (cid, pk, capability) as a qrlconnect:// URI. The PK is not stored
1050
+ * in the URI; only its capability-bound fingerprint is embedded. The caller
1051
+ * must upload the PK to the relay separately before publishing this URI.
878
1052
  *
879
1053
  * The optional `relayUrl` rides as a sibling query param (not inside the
880
1054
  * fp-bound blob) so a tampered relay can cause the pairing to fail but
881
1055
  * cannot substitute the PK - the fp still pins that.
882
1056
  */
883
- declare function generateConnectionURI(cid: Uint8Array, pk: Uint8Array, relayUrl?: string): Promise<string>;
1057
+ declare function generateConnectionURI(cid: Uint8Array, pk: Uint8Array, capability: Uint8Array, relayUrl?: string): Promise<string>;
884
1058
  interface ParsedURI {
885
1059
  cid: Uint8Array;
886
1060
  fp: Uint8Array;
1061
+ capability: Uint8Array;
887
1062
  relayUrl?: string | undefined;
888
1063
  }
889
1064
  declare function parseConnectionURI(uri: string): Promise<ParsedURI>;
@@ -922,21 +1097,42 @@ declare function getAppStoreUrl(): string;
922
1097
  */
923
1098
  declare function attemptWalletRedirect(uri: string, timeoutMs?: number): Promise<boolean>;
924
1099
 
1100
+ declare const QRL_ADDRESS_BYTES = 64;
1101
+ declare const QRL_ADDRESS_HEX_LENGTH: number;
1102
+ /**
1103
+ * Validate the QRL 2.0 text form. The prefix is the network's uppercase Q.
1104
+ * A case-uniform hex body is accepted for compatibility. Mixed-case input
1105
+ * must carry the canonical SHAKE256 QIP-55 checksum.
1106
+ */
1107
+ declare function isCurrentQrlAddress(value: unknown): value is string;
1108
+ /** Stable visual fingerprint for long QRL addresses. */
1109
+ declare function formatQrlAddressFingerprint(address: string): string;
1110
+ /** Format exactly 64 address bytes as the canonical mixed-case QIP-55 form. */
1111
+ declare function qip55AddressFromBytes(address: Uint8Array): string;
1112
+
925
1113
  declare const DEFAULT_RELAY_URL = "https://qrlwallet.com";
926
- declare const PROTOCOL_VERSION = 2;
1114
+ declare const PROTOCOL_VERSION = 3;
1115
+ declare const PAIRING_CAPABILITY_LEN = 32;
1116
+ declare const MAX_JSON_RPC_ID_LENGTH = 128;
1117
+ declare const MAX_JSON_RPC_METHOD_LENGTH = 128;
1118
+ declare const MAX_RELAY_URL_LENGTH = 2048;
927
1119
  /**
928
- * RPC methods that require user approval in the wallet.
929
- *
930
- * v3.0.0 replaces the Ethereum-flavored signing surface with two
931
- * post-quantum-native methods: `qrl_signMessage` for opaque bytes and
932
- * `qrl_signTypedData` for structured payloads (EIP-712-shaped, but with
933
- * SHAKE256 + native Dilithium ctx). The legacy methods (`personal_sign`,
934
- * `qrl_sign`, `qrl_signTypedData_v3`, `qrl_signTypedData_v4`) are no
935
- * longer accepted; a dApp calling them via this SDK will get a
936
- * "method not supported" error before the relay round-trip.
937
- */
938
- declare const RESTRICTED_METHODS: Set<string>;
939
- /** RPC methods that can be auto-proxied without approval */
940
- declare const UNRESTRICTED_METHODS: Set<string>;
1120
+ * Validate and canonicalize relay endpoints before they reach logging,
1121
+ * Socket.IO, or a QR URI. Production relays require HTTPS; plain HTTP is
1122
+ * limited to explicit loopback hostnames for local development.
1123
+ */
1124
+ declare function normalizeRelayUrl(value: unknown): string;
1125
+ /** Public snapshots for discovery. Provider authorization uses private sets. */
1126
+ declare const RESTRICTED_METHODS: ReadonlySet<string>;
1127
+ declare const UNRESTRICTED_METHODS: ReadonlySet<string>;
1128
+ declare const EXPLICITLY_UNSUPPORTED_METHODS: ReadonlySet<string>;
1129
+ type RpcMethodPolicy = 'restricted' | 'unrestricted' | 'unsupported';
1130
+ declare function isValidJsonRpcId(value: unknown): value is string | number;
1131
+ declare function isValidJsonRpcMethod(value: unknown): value is string;
1132
+ /**
1133
+ * Classify an RPC method with a closed allowlist. Any method absent from the
1134
+ * two positive policy sets is unsupported, including future signing methods.
1135
+ */
1136
+ declare function classifyRpcMethod(method: string): RpcMethodPolicy;
941
1137
 
942
- export { type AckMessage, BLOB_LEN, CID_LEN, ConnectionManager, ConnectionStatus, type DAppMetadata, type DAppSession, DEFAULT_RELAY_URL, DIGEST_LEN, type Domain, type EIP6963ProviderInfoOverride, FP_LEN, type JsonRpcRequest, type JsonRpcResponse, KeyExchange, KeyExchangeMessageType, type Message, MessageType, PROTOCOL_VERSION, type ParsedURI, type PendingRequest, type PersistedSession, type ProviderEvents, QRLConnectProvider as QRLConnect, type QRLConnectOptions, QRLConnectProvider, QRL_CONNECT_PROVIDER_INFO, type QrlSignMessageParams, type QrlSignTypedDataParams, type QrlSignedResult, type QrlSignedTypedDataResult, type QrlTypedDataPayload, RESTRICTED_METHODS, type RelayMessage, SCHEME_TAG_MSG, SCHEME_TAG_TYPED, SCHEME_VERSION_MSG, SCHEME_VERSION_TYPED, type Session, SocketClient, type StructDef, type SynAckMessage, type TypeMap, type TypedDataPayload, type TypedField, UNRESTRICTED_METHODS, type VerifyMessageParams, type VerifyTypedDataParams, attemptWalletRedirect, bytesToHex, cidFromString, cidRandom, cidToString, computeFingerprint, computeMessageDigest, computeTypedDataDigest, concatBytes, encodeField, encodeType, fingerprintEquals, generateConnectionURI, getAppStoreUrl, hashStruct, hexToBytes, isMobileBrowser, parseConnectionURI, typeHash, verifyMessage, verifyTypedData };
1138
+ export { type AckMessage, BLOB_LEN, CAP_LEN, CID_LEN, ConnectionManager, ConnectionStatus, type DAppMetadata, type DAppSession, DEFAULT_RELAY_URL, DIGEST_LEN, type Domain, type EIP6963ProviderInfoOverride, EXPLICITLY_UNSUPPORTED_METHODS, FP_LEN, type InitiatedKeyExchange, type JsonRpcRequest, type JsonRpcResponse, KeyExchange, KeyExchangeMessageType, MAX_CONNECTION_URI_LENGTH, MAX_JSON_RPC_ID_LENGTH, MAX_JSON_RPC_METHOD_LENGTH, MAX_RELAY_URL_LENGTH, ML_DSA_87_PUBLIC_KEY_BYTES, ML_DSA_87_SIGNATURE_BYTES, ML_DSA_DESCRIPTOR_BYTES, type Message, MessageType, PAIRING_CAPABILITY_LEN, PROTOCOL_VERSION, type ParsedURI, type PendingRequest, type PersistedSession, type ProviderEvents, QRLConnectProvider as QRLConnect, type QRLConnectOptions, QRLConnectProvider, QRL_ADDRESS_BYTES, QRL_ADDRESS_HEX_LENGTH, QRL_CONNECT_PROVIDER_INFO, type QrlSignMessageParams, type QrlSignTypedDataParams, type QrlSignedMessageResult, type QrlSignedResult, type QrlSignedResultWithDescriptor, type QrlSignedTypedDataResult, type QrlSigningResult, type QrlSigningSchemeVersion, type QrlTypedDataPayload, RESTRICTED_METHODS, type RelayMessage, type RpcMethodPolicy, SCHEME_TAG_MSG, SCHEME_TAG_TYPED, SCHEME_VERSION_MSG, SCHEME_VERSION_TYPED, type Session, SocketClient, type StructDef, type SynAckMessage, TYPED_DATA_LIMITS, type TypeMap, type TypedDataPayload, type TypedField, UNRESTRICTED_METHODS, type VerifyMessageForSignerParams, type VerifyMessageParams, type VerifyTypedDataForSignerParams, type VerifyTypedDataParams, attemptWalletRedirect, bytesToHex, cidFromString, cidRandom, cidToString, classifyRpcMethod, computeFingerprint, computeMessageDigest, computeTypedDataDigest, concatBytes, encodeField, encodeType, fingerprintEquals, formatQrlAddressFingerprint, generateConnectionURI, getAppStoreUrl, hasSigningDescriptor, hashStruct, hexToBytes, isCurrentQrlAddress, isMobileBrowser, isQrlSignedMessageResult, isQrlSignedResult, isQrlSignedTypedDataResult, isValidJsonRpcId, isValidJsonRpcMethod, normalizeRelayUrl, parseConnectionURI, qip55AddressFromBytes, typeHash, verifyMessage, verifyMessageForSigner, verifyMessageSignature, verifyTypedData, verifyTypedDataForSigner, verifyTypedDataSignature };