@qrlwallet/connect 0.1.0 → 3.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/README.md +13 -32
- package/dist/index.d.mts +483 -113
- package/dist/index.d.ts +483 -113
- package/dist/index.js +1409 -444
- package/dist/index.mjs +1382 -442
- package/package.json +8 -6
package/dist/index.d.mts
CHANGED
|
@@ -1,16 +1,128 @@
|
|
|
1
1
|
import EventEmitter from 'eventemitter3';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Post-quantum handshake (v2).
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* dApp relay wallet
|
|
8
|
+
* ────────────────────────────────────────────────────────────────────
|
|
9
|
+
* initiate() → (pk, sk)
|
|
10
|
+
* QR(cid, pk) rendered scan QR
|
|
11
|
+
* receiveQR(cid, pk)
|
|
12
|
+
* → SYNACK{ct, c0}
|
|
13
|
+
* ◄── SYNACK ─────── send
|
|
14
|
+
* onSynAck(cid, SYNACK)
|
|
15
|
+
* → ACK{c1}
|
|
16
|
+
* send ACK ──► ──► ack onAck(ACK)
|
|
17
|
+
*
|
|
18
|
+
* Data phase: encryptMessage / decryptMessage use direction-tagged counter
|
|
19
|
+
* nonces and AAD = H_tx || seq to bind every ciphertext to the transcript.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface Session {
|
|
23
|
+
cid: Uint8Array;
|
|
24
|
+
key: CryptoKey;
|
|
25
|
+
htx: Uint8Array;
|
|
26
|
+
sendDir: Uint8Array;
|
|
27
|
+
recvDir: Uint8Array;
|
|
28
|
+
sendSeq: number;
|
|
29
|
+
recvSeq: number;
|
|
30
|
+
}
|
|
31
|
+
/** Wire-safe persisted form for localStorage. */
|
|
32
|
+
interface PersistedSession {
|
|
33
|
+
cid: string;
|
|
34
|
+
kAeadRaw: string;
|
|
35
|
+
htx: string;
|
|
36
|
+
sendDir: string;
|
|
37
|
+
recvDir: string;
|
|
38
|
+
sendSeq: number;
|
|
39
|
+
recvSeq: number;
|
|
40
|
+
}
|
|
41
|
+
interface SynAckMessage {
|
|
42
|
+
type: KeyExchangeMessageType.SYNACK;
|
|
43
|
+
ct: string;
|
|
44
|
+
c0: string;
|
|
45
|
+
v: number;
|
|
46
|
+
}
|
|
47
|
+
interface AckMessage {
|
|
48
|
+
type: KeyExchangeMessageType.ACK;
|
|
49
|
+
c1: string;
|
|
50
|
+
v: number;
|
|
51
|
+
}
|
|
52
|
+
interface KeyExchangeEvents {
|
|
53
|
+
keys_exchanged: () => void;
|
|
54
|
+
step_change: (step: KeyExchangeMessageType) => void;
|
|
55
|
+
}
|
|
56
|
+
declare class KeyExchange extends EventEmitter<KeyExchangeEvents> {
|
|
57
|
+
private isOriginator;
|
|
58
|
+
private keypair;
|
|
59
|
+
private session;
|
|
60
|
+
private step;
|
|
61
|
+
private awaitingSynAck;
|
|
62
|
+
private awaitingAck;
|
|
63
|
+
private keysExchanged;
|
|
64
|
+
constructor(isOriginator: boolean, restored?: Session);
|
|
65
|
+
/**
|
|
66
|
+
* Originator (dApp) side: begin a fresh handshake by generating an
|
|
67
|
+
* ephemeral ML-KEM-768 keypair. Returns pk to embed in the QR URI.
|
|
68
|
+
*/
|
|
69
|
+
initiate(): Uint8Array;
|
|
70
|
+
/**
|
|
71
|
+
* Originator: handle a SYNACK received over the relay.
|
|
72
|
+
* Returns an ACK wire message to send, or null if the SYNACK was a
|
|
73
|
+
* duplicate (handshake already complete).
|
|
74
|
+
*/
|
|
75
|
+
onSynAck(cid: Uint8Array, msg: SynAckMessage): Promise<AckMessage | null>;
|
|
76
|
+
/**
|
|
77
|
+
* Responder (wallet) side: begin handshake from a scanned QR.
|
|
78
|
+
* Returns the SYNACK wire message to emit on the relay.
|
|
79
|
+
*/
|
|
80
|
+
receiveQR(cid: Uint8Array, pk: Uint8Array): Promise<SynAckMessage>;
|
|
81
|
+
/**
|
|
82
|
+
* Responder: verify an incoming ACK and finalize the session.
|
|
83
|
+
* Duplicates are silently ignored (handshake is idempotent).
|
|
84
|
+
*/
|
|
85
|
+
onAck(msg: AckMessage): Promise<void>;
|
|
86
|
+
/** Encrypt a string for the counterparty. Returns base64. */
|
|
87
|
+
encryptMessage(data: string): Promise<string>;
|
|
88
|
+
/** Decrypt a base64 ciphertext from the counterparty. */
|
|
89
|
+
decryptMessage(b64: string): Promise<string>;
|
|
90
|
+
/** Reset all state for a fresh handshake. Zeroes any live secret-key buffer. */
|
|
91
|
+
reset(): void;
|
|
92
|
+
private resetInternal;
|
|
93
|
+
areKeysExchanged(): boolean;
|
|
94
|
+
getSession(): Session | null;
|
|
95
|
+
getCurrentStep(): KeyExchangeMessageType;
|
|
96
|
+
/** Export the current session as a JSON-safe persisted record. */
|
|
97
|
+
exportPersisted(): Promise<PersistedSession | null>;
|
|
98
|
+
/** Rehydrate a Session from its persisted form. */
|
|
99
|
+
static sessionFromPersisted(p: PersistedSession): Promise<Session>;
|
|
100
|
+
}
|
|
101
|
+
|
|
3
102
|
/** dApp metadata shown to user in approval UI */
|
|
4
103
|
interface DAppMetadata {
|
|
5
104
|
name: string;
|
|
6
105
|
url: string;
|
|
7
106
|
icon?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Optional "return to dApp" target (a URL or app deep-link scheme). Sent to
|
|
109
|
+
* the wallet in ORIGINATOR_INFO; after the wallet resolves a restricted
|
|
110
|
+
* request it bounces the user back here (WalletConnect-style peer redirect)
|
|
111
|
+
* so a same-device deep-link flow does not strand the user in the wallet.
|
|
112
|
+
*/
|
|
113
|
+
redirectUrl?: string;
|
|
8
114
|
}
|
|
9
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Stored session for reconnection.
|
|
117
|
+
*
|
|
118
|
+
* v2 persists the derived AES-256 session key (not the ML-KEM secret key) —
|
|
119
|
+
* the ML-KEM keypair is ephemeral and zeroized after the handshake.
|
|
120
|
+
* Re-pair (generate a new QR) to rotate the session key.
|
|
121
|
+
*/
|
|
10
122
|
interface DAppSession {
|
|
123
|
+
version: 2;
|
|
11
124
|
channelId: string;
|
|
12
|
-
|
|
13
|
-
otherPublicKey: string | null;
|
|
125
|
+
keyExchange: PersistedSession;
|
|
14
126
|
dappMetadata: DAppMetadata;
|
|
15
127
|
connectedAccounts: string[];
|
|
16
128
|
chainId: string;
|
|
@@ -26,7 +138,13 @@ interface PendingRequest {
|
|
|
26
138
|
reject: (error: Error) => void;
|
|
27
139
|
timestamp: number;
|
|
28
140
|
}
|
|
29
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Key-exchange wire-message types.
|
|
143
|
+
*
|
|
144
|
+
* v2 note: only SYNACK (wallet→dApp) and ACK (dApp→wallet) are transmitted
|
|
145
|
+
* over the relay. The SYN step is carried by the QR code itself — the dApp's
|
|
146
|
+
* ML-KEM-768 encapsulation key is embedded in the URI, not sent on the wire.
|
|
147
|
+
*/
|
|
30
148
|
declare enum KeyExchangeMessageType {
|
|
31
149
|
SYN = "key_handshake_SYN",
|
|
32
150
|
SYNACK = "key_handshake_SYNACK",
|
|
@@ -93,6 +211,66 @@ interface JsonRpcResponse {
|
|
|
93
211
|
data?: unknown;
|
|
94
212
|
};
|
|
95
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* EIP-6963 wallet identity advertised to dApps.
|
|
216
|
+
*
|
|
217
|
+
* `rdns` MUST be unique to this wallet and SHOULD be a reverse-DNS string
|
|
218
|
+
* (per EIP-6963). Defaults work for most consumers — override only when
|
|
219
|
+
* embedding the SDK inside a different branded wallet.
|
|
220
|
+
*/
|
|
221
|
+
interface EIP6963ProviderInfoOverride {
|
|
222
|
+
name?: string;
|
|
223
|
+
icon?: string;
|
|
224
|
+
rdns?: string;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Rich response object returned by both `qrl_signMessage` and
|
|
228
|
+
* `qrl_signTypedData`. The signature alone is not enough to verify since
|
|
229
|
+
* ML-DSA-87 public keys cannot be recovered from a signature, so the
|
|
230
|
+
* wallet always returns the public key explicitly. Stateless verifiers
|
|
231
|
+
* (e.g. the SDK's `verifyMessage` / `verifyTypedData`) need every field.
|
|
232
|
+
*/
|
|
233
|
+
interface QrlSignedResult {
|
|
234
|
+
/** 0x-hex of the 4595-byte ML-DSA-87 signature. */
|
|
235
|
+
signature: string;
|
|
236
|
+
/** 0x-hex of the 2592-byte ML-DSA-87 public key. */
|
|
237
|
+
publicKey: string;
|
|
238
|
+
/** 41-char checksummed Q-address derived from `publicKey`. */
|
|
239
|
+
signer: string;
|
|
240
|
+
/** 0x-hex of the 64-byte SHAKE256 digest that was signed. */
|
|
241
|
+
digest: string;
|
|
242
|
+
/** Scheme tag: 'QRL-SIGN-MSG-v1' or 'QRL-SIGN-TYPED-v1'. */
|
|
243
|
+
schemeVersion: string;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Response from `qrl_signTypedData`. Echoes `domain` so a stateless
|
|
247
|
+
* verifier doesn't need to be told the domain out-of-band.
|
|
248
|
+
*/
|
|
249
|
+
interface QrlSignedTypedDataResult extends QrlSignedResult {
|
|
250
|
+
domain: Record<string, unknown>;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* `qrl_signMessage` params: `[signer, messageHex]`. `signer` must equal the
|
|
254
|
+
* dApp's currently-connected account; the wallet rejects mismatches before
|
|
255
|
+
* unlocking. `messageHex` is strict `0x`-prefixed bytes (no UTF-8 strings).
|
|
256
|
+
*/
|
|
257
|
+
type QrlSignMessageParams = [string, string];
|
|
258
|
+
/**
|
|
259
|
+
* `qrl_signTypedData` params: `[signer, payload]`. Payload mirrors EIP-712
|
|
260
|
+
* shape: `{ types, primaryType, domain, message }`, but with `QRLDomain`
|
|
261
|
+
* in place of `EIP712Domain` and SHAKE256-based hashing. See `signing/`
|
|
262
|
+
* for the full encoder.
|
|
263
|
+
*/
|
|
264
|
+
interface QrlTypedDataPayload {
|
|
265
|
+
types: Record<string, ReadonlyArray<{
|
|
266
|
+
name: string;
|
|
267
|
+
type: string;
|
|
268
|
+
}>>;
|
|
269
|
+
primaryType: string;
|
|
270
|
+
domain: Record<string, unknown>;
|
|
271
|
+
message: Record<string, unknown>;
|
|
272
|
+
}
|
|
273
|
+
type QrlSignTypedDataParams = [string, QrlTypedDataPayload];
|
|
96
274
|
/** QRL Connect configuration */
|
|
97
275
|
interface QRLConnectOptions {
|
|
98
276
|
dappMetadata: DAppMetadata;
|
|
@@ -104,6 +282,15 @@ interface QRLConnectOptions {
|
|
|
104
282
|
storageKey?: string;
|
|
105
283
|
/** Enable debug logging */
|
|
106
284
|
debug?: boolean;
|
|
285
|
+
/**
|
|
286
|
+
* Announce the provider via EIP-6963 so dApp wallet pickers can see it
|
|
287
|
+
* alongside the QRL browser extension. Defaults to true in browser
|
|
288
|
+
* environments. Set false to suppress (e.g. when the dApp wires the
|
|
289
|
+
* provider in manually).
|
|
290
|
+
*/
|
|
291
|
+
announceProvider?: boolean;
|
|
292
|
+
/** Override the EIP-6963 announce metadata (name / icon / rdns). */
|
|
293
|
+
providerInfo?: EIP6963ProviderInfoOverride;
|
|
107
294
|
}
|
|
108
295
|
|
|
109
296
|
/**
|
|
@@ -111,12 +298,44 @@ interface QRLConnectOptions {
|
|
|
111
298
|
* Bridges JSON-RPC requests from dApp to QRL Wallet via the relay.
|
|
112
299
|
*/
|
|
113
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Default EIP-6963 identity for the QRL Connect provider. The `rdns` is
|
|
303
|
+
* deliberately distinct from the QRL browser extension (`theqrl.org`) so
|
|
304
|
+
* both wallets can coexist in the same dApp picker.
|
|
305
|
+
*/
|
|
306
|
+
declare const QRL_CONNECT_PROVIDER_INFO: {
|
|
307
|
+
readonly name: "MyQRLWallet (Mobile)";
|
|
308
|
+
readonly rdns: "com.qrlwallet.connect";
|
|
309
|
+
readonly icon: "data:image/png;base64,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";
|
|
310
|
+
};
|
|
114
311
|
declare class QRLConnectProvider extends EventEmitter<ProviderEvents> {
|
|
115
312
|
private connectionManager;
|
|
116
313
|
private pendingRequests;
|
|
117
314
|
private options;
|
|
315
|
+
private eip6963Detail;
|
|
316
|
+
private eip6963RequestListener;
|
|
317
|
+
private resumeListener;
|
|
318
|
+
private resumeDebounce;
|
|
118
319
|
readonly isQRLConnect = true;
|
|
119
320
|
constructor(options: QRLConnectOptions);
|
|
321
|
+
private startEip6963Announce;
|
|
322
|
+
/**
|
|
323
|
+
* Re-open the relay socket when the dApp tab regains focus or the network
|
|
324
|
+
* comes back. Debounced because visibilitychange + online can fire together.
|
|
325
|
+
* No-op when already connected (ConnectionManager.resume guards that).
|
|
326
|
+
*/
|
|
327
|
+
private setupResumeListeners;
|
|
328
|
+
/**
|
|
329
|
+
* Remove the foreground/online resume listeners. Called on disconnect so a
|
|
330
|
+
* torn-down session can't be silently revived by a later tab focus; the
|
|
331
|
+
* listeners are re-armed by getConnectionURI()/newConnection() if the same
|
|
332
|
+
* provider is re-paired. Safe in any env.
|
|
333
|
+
*/
|
|
334
|
+
private teardownResumeListeners;
|
|
335
|
+
/**
|
|
336
|
+
* Stop announcing this provider over EIP-6963. Safe to call from any env.
|
|
337
|
+
*/
|
|
338
|
+
stopEip6963Announce(): void;
|
|
120
339
|
private setupConnectionListeners;
|
|
121
340
|
/**
|
|
122
341
|
* Generate a connection URI for QR code display or deep link redirect.
|
|
@@ -164,14 +383,18 @@ declare class QRLConnectProvider extends EventEmitter<ProviderEvents> {
|
|
|
164
383
|
*/
|
|
165
384
|
newConnection(): Promise<string>;
|
|
166
385
|
/**
|
|
167
|
-
* Disconnect from wallet and clean up.
|
|
386
|
+
* Disconnect from wallet and clean up. Returns once the TERMINATE has
|
|
387
|
+
* either been flushed to the relay or the 800ms best-effort window has
|
|
388
|
+
* elapsed — the wallet gets an instant disconnect instead of landing in
|
|
389
|
+
* its stale-session grace period.
|
|
168
390
|
*/
|
|
169
|
-
disconnect(): void
|
|
391
|
+
disconnect(): Promise<void>;
|
|
170
392
|
}
|
|
171
393
|
|
|
172
394
|
/**
|
|
173
|
-
* Connection Manager
|
|
174
|
-
*
|
|
395
|
+
* Connection Manager — orchestrates socket lifecycle, the post-quantum
|
|
396
|
+
* handshake, encrypted message routing, and session persistence for the
|
|
397
|
+
* dApp side of QRL Connect v2.
|
|
175
398
|
*/
|
|
176
399
|
|
|
177
400
|
interface ConnectionManagerEvents {
|
|
@@ -188,7 +411,6 @@ interface ConnectionManagerEvents {
|
|
|
188
411
|
}
|
|
189
412
|
declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
|
|
190
413
|
private socketClient;
|
|
191
|
-
private ecies;
|
|
192
414
|
private keyExchange;
|
|
193
415
|
private status;
|
|
194
416
|
private channelId;
|
|
@@ -198,14 +420,12 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
|
|
|
198
420
|
private connectedAccounts;
|
|
199
421
|
private storageKey;
|
|
200
422
|
private unresponsiveTimer;
|
|
201
|
-
private
|
|
423
|
+
private reconnectProbeTimer;
|
|
202
424
|
private failedReconnects;
|
|
203
|
-
private synSent;
|
|
204
|
-
private synAttempts;
|
|
205
425
|
private walletPresent;
|
|
426
|
+
private pendingRestore;
|
|
427
|
+
private messageQueue;
|
|
206
428
|
private static MAX_RECONNECT_FAILURES;
|
|
207
|
-
private static SYN_RETRY_MS;
|
|
208
|
-
private static MAX_SYN_ATTEMPTS;
|
|
209
429
|
constructor(options: {
|
|
210
430
|
dappMetadata: DAppMetadata;
|
|
211
431
|
relayUrl?: string;
|
|
@@ -215,110 +435,80 @@ declare class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
|
|
|
215
435
|
private setupSocketListeners;
|
|
216
436
|
private setupKeyExchangeListeners;
|
|
217
437
|
/**
|
|
218
|
-
* Generate
|
|
438
|
+
* Generate a new v2 connection URI. Rotates channel id and keypair.
|
|
439
|
+
* Returns a `qrlconnect://?q=…` URI safe for QR-rendering or deep-link.
|
|
219
440
|
*/
|
|
220
441
|
getConnectionURI(retryOnConflict?: boolean): Promise<string>;
|
|
221
442
|
/**
|
|
222
443
|
* Reconnect to an existing session.
|
|
444
|
+
* Returns false if there is nothing to restore.
|
|
223
445
|
*/
|
|
224
446
|
reconnect(): Promise<boolean>;
|
|
225
447
|
/**
|
|
226
|
-
*
|
|
448
|
+
* (Re)open the socket, join the persisted channel, drain buffered relay
|
|
449
|
+
* messages, and settle status. Shared by the cold-restore reconnect() and
|
|
450
|
+
* the warm resume() path whose socket was torn down by the reconnect probe.
|
|
451
|
+
* Assumes channelId and a hydrated keyExchange are already in place, and
|
|
452
|
+
* that the caller has reset walletPresent + set RECONNECTING status.
|
|
453
|
+
*/
|
|
454
|
+
private joinAndSettle;
|
|
455
|
+
/**
|
|
456
|
+
* Nudge the connection back to life after the dApp tab / wallet app was
|
|
457
|
+
* backgrounded. Idempotent and safe to call from visibilitychange / online
|
|
458
|
+
* / pageshow handlers. If a restored session has not yet been hydrated,
|
|
459
|
+
* runs the full reconnect(); otherwise just re-opens the socket, whose
|
|
460
|
+
* own connect handler re-joins the channel and drains the relay buffer.
|
|
461
|
+
*/
|
|
462
|
+
resume(): void;
|
|
463
|
+
/**
|
|
464
|
+
* A session was terminated for good (wallet sent a relay 'close', or the
|
|
465
|
+
* join ack reported a tombstone). Clear local state and surface
|
|
466
|
+
* DISCONNECTED so the consumer drops to a fresh-pairing UI.
|
|
467
|
+
*/
|
|
468
|
+
private handleSessionTerminated;
|
|
469
|
+
private armReconnectProbe;
|
|
470
|
+
private clearReconnectProbe;
|
|
471
|
+
/**
|
|
472
|
+
* Send a JSON-RPC request to the wallet (async, fire-and-forget).
|
|
227
473
|
*/
|
|
228
474
|
sendJsonRpc(request: JsonRpcRequest): void;
|
|
475
|
+
getStatus(): ConnectionStatus;
|
|
476
|
+
getAccounts(): string[];
|
|
477
|
+
getChainId(): string;
|
|
478
|
+
getChannelId(): string;
|
|
479
|
+
/** Check (sync) if a persisted session exists and has not expired. */
|
|
480
|
+
hasStoredSession(): boolean;
|
|
229
481
|
/**
|
|
230
|
-
*
|
|
482
|
+
* Best-effort TERMINATE delivery before we tear the socket down. Without
|
|
483
|
+
* awaiting, the socket.disconnect() below would win the race against the
|
|
484
|
+
* outbound emit, the wallet would only see `participants_changed:
|
|
485
|
+
* disconnect` and enter its stale-session grace period instead of an
|
|
486
|
+
* instant disconnect. Mirrors the wallet side's pattern.
|
|
231
487
|
*/
|
|
488
|
+
private flushTerminate;
|
|
489
|
+
/**
|
|
490
|
+
* Reset to a fresh channel + keypair. Sends TERMINATE to any live peer,
|
|
491
|
+
* drops the persisted session, and prepares a clean state for
|
|
492
|
+
* getConnectionURI() to be called again.
|
|
493
|
+
*/
|
|
494
|
+
resetForNewChannel(): Promise<void>;
|
|
495
|
+
disconnect(): Promise<void>;
|
|
496
|
+
private enqueueRelayMessage;
|
|
232
497
|
private handleRelayMessage;
|
|
233
|
-
private
|
|
498
|
+
private handleSynAck;
|
|
234
499
|
private handleDecryptedMessage;
|
|
235
|
-
/** Send a plaintext (unencrypted) message through the relay. */
|
|
236
500
|
private sendPlaintext;
|
|
237
|
-
/** Send an encrypted message through the relay. */
|
|
238
501
|
private sendEncrypted;
|
|
239
502
|
private startUnresponsiveTimer;
|
|
240
503
|
private clearUnresponsiveTimer;
|
|
241
|
-
private sendSYN;
|
|
242
|
-
private scheduleSynRetry;
|
|
243
|
-
private clearSynRetryTimer;
|
|
244
504
|
private setStatus;
|
|
245
505
|
private isDappParticipantConflictError;
|
|
246
506
|
private areArraysEqual;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
getChainId(): string;
|
|
250
|
-
getChannelId(): string;
|
|
251
|
-
/** Check if a stored session exists (without loading/restoring it). */
|
|
252
|
-
hasStoredSession(): boolean;
|
|
253
|
-
/**
|
|
254
|
-
* Reset the connection and create a new channel for a fresh pairing.
|
|
255
|
-
* Disconnects any existing session, generates a new channel ID and keys.
|
|
256
|
-
*/
|
|
257
|
-
resetForNewChannel(): void;
|
|
258
|
-
/** Disconnect and clean up. */
|
|
259
|
-
disconnect(): void;
|
|
260
|
-
private saveSession;
|
|
261
|
-
private loadSession;
|
|
507
|
+
private persistSession;
|
|
508
|
+
private readStoredSession;
|
|
262
509
|
private clearSession;
|
|
263
510
|
}
|
|
264
511
|
|
|
265
|
-
/**
|
|
266
|
-
* ECIES encryption/decryption client.
|
|
267
|
-
* Adapted from MetaMask SDK's ECIES.ts pattern.
|
|
268
|
-
*/
|
|
269
|
-
declare class ECIESClient {
|
|
270
|
-
private privateKey;
|
|
271
|
-
constructor(existingPrivateKey?: string);
|
|
272
|
-
/** Get public key as hex string */
|
|
273
|
-
getPublicKey(): string;
|
|
274
|
-
/** Get private key as hex string (for session persistence) */
|
|
275
|
-
getPrivateKeyHex(): string;
|
|
276
|
-
/** Encrypt a string message for the given public key. Returns base64. */
|
|
277
|
-
encrypt(data: string, otherPublicKey: string): string;
|
|
278
|
-
/** Decrypt a base64-encoded message using our private key. */
|
|
279
|
-
decrypt(encryptedBase64: string): string;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Key Exchange - 3-step SYN/SYNACK/ACK handshake protocol.
|
|
284
|
-
* Adapted from MetaMask SDK's KeyExchange.ts.
|
|
285
|
-
*/
|
|
286
|
-
|
|
287
|
-
interface KeyExchangeEvents {
|
|
288
|
-
keys_exchanged: () => void;
|
|
289
|
-
step_change: (step: KeyExchangeMessageType) => void;
|
|
290
|
-
}
|
|
291
|
-
declare class KeyExchange extends EventEmitter<KeyExchangeEvents> {
|
|
292
|
-
private ecies;
|
|
293
|
-
private otherPublicKey;
|
|
294
|
-
private keysExchanged;
|
|
295
|
-
private awaitingSynAck;
|
|
296
|
-
private awaitingAck;
|
|
297
|
-
private step;
|
|
298
|
-
private isOriginator;
|
|
299
|
-
constructor(ecies: ECIESClient, isOriginator: boolean, otherPublicKey?: string);
|
|
300
|
-
/** Get the SYN message to start the handshake (originator/dApp side). */
|
|
301
|
-
createSYN(): object;
|
|
302
|
-
/**
|
|
303
|
-
* Process an incoming key exchange message.
|
|
304
|
-
* Returns a response message to send back, or null if handshake is complete.
|
|
305
|
-
*/
|
|
306
|
-
onMessage(message: {
|
|
307
|
-
type: KeyExchangeMessageType;
|
|
308
|
-
pubkey?: string;
|
|
309
|
-
v?: number;
|
|
310
|
-
}): object | null;
|
|
311
|
-
/** Encrypt a message for the counterparty. */
|
|
312
|
-
encryptMessage(data: string): string;
|
|
313
|
-
/** Decrypt a message from the counterparty. */
|
|
314
|
-
decryptMessage(encryptedBase64: string): string;
|
|
315
|
-
/** Reset key exchange state for a fresh handshake (e.g. new QR code). */
|
|
316
|
-
reset(): void;
|
|
317
|
-
areKeysExchanged(): boolean;
|
|
318
|
-
getOtherPublicKey(): string | null;
|
|
319
|
-
getCurrentStep(): KeyExchangeMessageType;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
512
|
/**
|
|
323
513
|
* Socket.IO client wrapper for relay communication.
|
|
324
514
|
*/
|
|
@@ -327,31 +517,63 @@ interface SocketClientEvents {
|
|
|
327
517
|
message: (data: RelayMessage) => void;
|
|
328
518
|
connected: () => void;
|
|
329
519
|
disconnected: (reason: string) => void;
|
|
330
|
-
reconnected: () => void;
|
|
520
|
+
reconnected: (result: JoinResult) => void;
|
|
331
521
|
participants_changed: (data: {
|
|
332
522
|
event: string;
|
|
333
523
|
clientType?: string;
|
|
334
524
|
}) => void;
|
|
335
525
|
error: (err: Error) => void;
|
|
336
526
|
}
|
|
527
|
+
interface JoinResult {
|
|
528
|
+
bufferedMessages: unknown[];
|
|
529
|
+
/**
|
|
530
|
+
* Base64-encoded KEM public key the relay has bound to this channel.
|
|
531
|
+
* Populated for wallet joins (v2 protocol). `null` if the dApp hasn't
|
|
532
|
+
* registered a PK yet — wallet callers must treat this as a retry signal.
|
|
533
|
+
*/
|
|
534
|
+
channelPublicKey: string | null;
|
|
535
|
+
/**
|
|
536
|
+
* Client types of the OTHER participants already in the channel at join
|
|
537
|
+
* time (e.g. `['wallet']` or `[]`). Lets a (re)joining peer detect an
|
|
538
|
+
* absent counterparty immediately instead of waiting on a future
|
|
539
|
+
* participants_changed event. Empty array on older relays.
|
|
540
|
+
*/
|
|
541
|
+
participants: string[];
|
|
542
|
+
/**
|
|
543
|
+
* True if the channel was explicitly closed (a terminated tombstone). The
|
|
544
|
+
* peer should drop its stored session rather than wait or re-pair.
|
|
545
|
+
*/
|
|
546
|
+
terminated: boolean;
|
|
547
|
+
}
|
|
337
548
|
declare class SocketClient extends EventEmitter<SocketClientEvents> {
|
|
338
549
|
private socket;
|
|
339
550
|
private relayUrl;
|
|
340
551
|
private channelId;
|
|
341
552
|
private clientType;
|
|
553
|
+
private publicKeyBase64;
|
|
342
554
|
private seq;
|
|
343
555
|
private pendingJoin;
|
|
344
556
|
constructor(relayUrl: string, clientType: 'dapp' | 'wallet');
|
|
557
|
+
/**
|
|
558
|
+
* dApp-only: stash the PK so it gets uploaded on every join_channel
|
|
559
|
+
* attempt (including auto-rejoins after reconnect).
|
|
560
|
+
*/
|
|
561
|
+
setPublicKey(publicKeyBase64: string): void;
|
|
345
562
|
/**
|
|
346
563
|
* Connect to the relay server.
|
|
347
564
|
*/
|
|
348
565
|
connect(): void;
|
|
349
566
|
/**
|
|
350
|
-
*
|
|
567
|
+
* Resolve or reject the currently-pending `joinChannel` promise, if it
|
|
568
|
+
* matches the given channelId. Clears the watchdog timer on settle so
|
|
569
|
+
* it doesn't fire after the fact.
|
|
351
570
|
*/
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
571
|
+
private settlePendingJoin;
|
|
572
|
+
/**
|
|
573
|
+
* Join a relay channel. Resolves with any buffered messages plus the
|
|
574
|
+
* dApp's public key bound to the channel (for wallet joins).
|
|
575
|
+
*/
|
|
576
|
+
joinChannel(channelId: string): Promise<JoinResult>;
|
|
355
577
|
private joinChannelNow;
|
|
356
578
|
/**
|
|
357
579
|
* Send a message through the relay.
|
|
@@ -372,23 +594,160 @@ declare class SocketClient extends EventEmitter<SocketClientEvents> {
|
|
|
372
594
|
getChannelId(): string | null;
|
|
373
595
|
}
|
|
374
596
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
597
|
+
/**
|
|
598
|
+
* Per-scheme domain-separation tags + version strings.
|
|
599
|
+
*
|
|
600
|
+
* The tag bytes are mixed into both:
|
|
601
|
+
* 1. The SHAKE256 preimage that produces the signed digest (so the digest
|
|
602
|
+
* itself commits to which scheme is in use), and
|
|
603
|
+
* 2. The ML-DSA-87 `ctx` parameter (so the signature verifier rejects
|
|
604
|
+
* cross-scheme replay even if a digest collision were ever found).
|
|
605
|
+
*
|
|
606
|
+
* Both stay well under FIPS 204's 255-byte ctx cap.
|
|
607
|
+
*
|
|
608
|
+
* NOTE: This file is byte-identical with the wallet's
|
|
609
|
+
* `myqrlwallet-frontend/src/utils/signing/ctx.ts`. Any divergence is a
|
|
610
|
+
* cross-repo bug that will surface as fixture mismatches in both test
|
|
611
|
+
* suites; keep them in lock-step.
|
|
612
|
+
*/
|
|
613
|
+
declare const SCHEME_VERSION_MSG = "QRL-SIGN-MSG-v1";
|
|
614
|
+
declare const SCHEME_VERSION_TYPED = "QRL-SIGN-TYPED-v1";
|
|
615
|
+
declare const SCHEME_TAG_MSG: Uint8Array;
|
|
616
|
+
declare const SCHEME_TAG_TYPED: Uint8Array;
|
|
617
|
+
/** SHAKE256 output length used throughout the signing module (NIST L5-matched). */
|
|
618
|
+
declare const DIGEST_LEN = 64;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Pure SHAKE256 digest for `qrl_signMessage` v1.
|
|
622
|
+
*
|
|
623
|
+
* digest = SHAKE256("QRL-SIGN-MSG-v1" || messageBytes, 64)
|
|
624
|
+
*
|
|
625
|
+
* The scheme tag is committed into the preimage so a signature produced for
|
|
626
|
+
* this scheme cannot be re-interpreted as belonging to typed-data signing
|
|
627
|
+
* (or vice versa) even if the `ctx` parameter were ever stripped.
|
|
628
|
+
*/
|
|
629
|
+
declare function computeMessageDigest(messageBytes: Uint8Array): Uint8Array;
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
|
|
633
|
+
*
|
|
634
|
+
* Byte-identical algorithm with the wallet's signing/typedData.ts; this
|
|
635
|
+
* file is the SDK port (only the relative-import extensions differ). See
|
|
636
|
+
* the wallet copy for the spec-level comments; behavior must stay in
|
|
637
|
+
* lock-step or the cross-repo parity test fails on next CI.
|
|
638
|
+
*/
|
|
639
|
+
type FieldType = string;
|
|
640
|
+
interface TypedField {
|
|
378
641
|
name: string;
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
642
|
+
type: FieldType;
|
|
643
|
+
}
|
|
644
|
+
type StructDef = readonly TypedField[];
|
|
645
|
+
type TypeMap = Record<string, StructDef>;
|
|
646
|
+
type Domain = Record<string, unknown>;
|
|
647
|
+
type Message = Record<string, unknown>;
|
|
648
|
+
interface TypedDataPayload {
|
|
649
|
+
types: TypeMap;
|
|
650
|
+
primaryType: string;
|
|
651
|
+
domain: Domain;
|
|
652
|
+
message: Message;
|
|
383
653
|
}
|
|
654
|
+
declare function encodeType(primary: string, types: TypeMap): string;
|
|
655
|
+
declare function typeHash(primary: string, types: TypeMap): Uint8Array;
|
|
656
|
+
declare function encodeField(type: FieldType, value: unknown, types: TypeMap): Uint8Array;
|
|
657
|
+
declare function hashStruct(primary: string, data: Message, types: TypeMap): Uint8Array;
|
|
658
|
+
declare function computeTypedDataDigest(payload: TypedDataPayload): Uint8Array;
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Stateless verifiers for the two PQ signing schemes. The SDK exposes these
|
|
662
|
+
* so a dApp can re-verify the rich response object returned by the wallet
|
|
663
|
+
* without round-tripping back through the relay. They never touch a secret
|
|
664
|
+
* key and never derive an address; that's the wallet's job.
|
|
665
|
+
*/
|
|
666
|
+
|
|
667
|
+
interface VerifyMessageParams {
|
|
668
|
+
signature: Uint8Array | string;
|
|
669
|
+
publicKey: Uint8Array | string;
|
|
670
|
+
/** 0x-hex bytes that were originally signed. */
|
|
671
|
+
messageBytes: Uint8Array | string;
|
|
672
|
+
}
|
|
673
|
+
declare function verifyMessage({ signature, publicKey, messageBytes, }: VerifyMessageParams): boolean;
|
|
674
|
+
interface VerifyTypedDataParams {
|
|
675
|
+
signature: Uint8Array | string;
|
|
676
|
+
publicKey: Uint8Array | string;
|
|
677
|
+
payload: TypedDataPayload;
|
|
678
|
+
}
|
|
679
|
+
declare function verifyTypedData({ signature, publicKey, payload }: VerifyTypedDataParams): boolean;
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Local hex/bytes utilities for the signing module.
|
|
683
|
+
*
|
|
684
|
+
* Byte-identical with the wallet's signing/bytes.ts.
|
|
685
|
+
*/
|
|
686
|
+
declare function hexToBytes(hex: string): Uint8Array;
|
|
687
|
+
declare function bytesToHex(bytes: Uint8Array): string;
|
|
688
|
+
declare function concatBytes(...parts: Uint8Array[]): Uint8Array;
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* v2 QR URI codec (PQP2).
|
|
692
|
+
*
|
|
693
|
+
* Format: qrlconnect://?q=<base45(PQP2 || cid || fp)>[&r=<relayUrl>]
|
|
694
|
+
*
|
|
695
|
+
* PQP2 = "PQP2" magic (4 B)
|
|
696
|
+
* cid = 16 bytes (UUIDv4 raw) (16 B)
|
|
697
|
+
* fp = SHA-256("pq-fp/v2" || cid || pk) (full 32B) (32 B)
|
|
698
|
+
* total (52 B)
|
|
699
|
+
*
|
|
700
|
+
* 52 bytes → ~78 alphanumeric chars after base45 → URI under 150 chars →
|
|
701
|
+
* fits a version-5 QR at ECC-M. Scannable from any 2015-vintage camera.
|
|
702
|
+
*
|
|
703
|
+
* Security: the public key is NOT carried in the QR. It's uploaded by the
|
|
704
|
+
* dApp to the relay at channel creation, the relay binds it to the cid, and
|
|
705
|
+
* the wallet fetches it via the join_channel ack. The 32-byte fp acts as
|
|
706
|
+
* the out-of-band commitment: the wallet rejects the PK served by the relay
|
|
707
|
+
* unless SHA-256("pq-fp/v2" || cid || pk) matches the fp from the QR.
|
|
708
|
+
* Full-width SHA-256 (2^128 collision resistance) rules out brute-force
|
|
709
|
+
* substitution of a maliciously-crafted PK with the same fingerprint.
|
|
710
|
+
*
|
|
711
|
+
* Legacy v2.0 URIs (magic=PQP1, 1208-byte blob with embedded PK) are
|
|
712
|
+
* rejected with a clear error by parseConnectionURI — this is a hard
|
|
713
|
+
* break before 2.0.0 ships, no backcompat.
|
|
714
|
+
*/
|
|
715
|
+
declare const CID_LEN = 16;
|
|
716
|
+
declare const FP_LEN = 32;
|
|
717
|
+
declare const BLOB_LEN: number;
|
|
718
|
+
/**
|
|
719
|
+
* Compute the full 32-byte fingerprint binding (label || cid || pk).
|
|
720
|
+
* Exported so the wallet side can re-derive and verify.
|
|
721
|
+
*/
|
|
722
|
+
declare function computeFingerprint(cid: Uint8Array, pk: Uint8Array): Promise<Uint8Array>;
|
|
384
723
|
/**
|
|
385
|
-
*
|
|
724
|
+
* Constant-time comparison of two byte arrays. Both must be the same length.
|
|
725
|
+
* The verifier MUST use this rather than `===` or `indexOf` so timing signals
|
|
726
|
+
* don't leak which byte of `fp` diverged first.
|
|
386
727
|
*/
|
|
387
|
-
declare function
|
|
728
|
+
declare function fingerprintEquals(a: Uint8Array, b: Uint8Array): boolean;
|
|
388
729
|
/**
|
|
389
|
-
*
|
|
730
|
+
* Encode (cid, pk) as a qrlconnect:// URI. Note the PK is not stored in the
|
|
731
|
+
* URI — we compute its fingerprint and embed only that. The caller must
|
|
732
|
+
* upload the PK to the relay separately before publishing this URI.
|
|
733
|
+
*
|
|
734
|
+
* The optional `relayUrl` rides as a sibling query param (not inside the
|
|
735
|
+
* fp-bound blob) so a tampered relay can cause the pairing to fail but
|
|
736
|
+
* cannot substitute the PK — the fp still pins that.
|
|
390
737
|
*/
|
|
391
|
-
declare function
|
|
738
|
+
declare function generateConnectionURI(cid: Uint8Array, pk: Uint8Array, relayUrl?: string): Promise<string>;
|
|
739
|
+
interface ParsedURI {
|
|
740
|
+
cid: Uint8Array;
|
|
741
|
+
fp: Uint8Array;
|
|
742
|
+
relayUrl?: string;
|
|
743
|
+
}
|
|
744
|
+
declare function parseConnectionURI(uri: string): Promise<ParsedURI>;
|
|
745
|
+
/** Convert 16 raw cid bytes to RFC 4122 UUID hex string. */
|
|
746
|
+
declare function cidToString(cid: Uint8Array): string;
|
|
747
|
+
/** Convert RFC 4122 UUID string to 16 raw cid bytes. */
|
|
748
|
+
declare function cidFromString(s: string): Uint8Array;
|
|
749
|
+
/** Generate 16 random bytes for a fresh channel ID. */
|
|
750
|
+
declare function cidRandom(): Uint8Array;
|
|
392
751
|
|
|
393
752
|
/**
|
|
394
753
|
* Detect if the current browser is on a mobile device.
|
|
@@ -401,9 +760,20 @@ declare function isMobileBrowser(): boolean;
|
|
|
401
760
|
declare function getAppStoreUrl(): string;
|
|
402
761
|
|
|
403
762
|
declare const DEFAULT_RELAY_URL = "https://qrlwallet.com";
|
|
404
|
-
|
|
763
|
+
declare const PROTOCOL_VERSION = 2;
|
|
764
|
+
/**
|
|
765
|
+
* RPC methods that require user approval in the wallet.
|
|
766
|
+
*
|
|
767
|
+
* v3.0.0 replaces the Ethereum-flavored signing surface with two
|
|
768
|
+
* post-quantum-native methods: `qrl_signMessage` for opaque bytes and
|
|
769
|
+
* `qrl_signTypedData` for structured payloads (EIP-712-shaped, but with
|
|
770
|
+
* SHAKE256 + native Dilithium ctx). The legacy methods (`personal_sign`,
|
|
771
|
+
* `qrl_sign`, `qrl_signTypedData_v3`, `qrl_signTypedData_v4`) are no
|
|
772
|
+
* longer accepted; a dApp calling them via this SDK will get a
|
|
773
|
+
* "method not supported" error before the relay round-trip.
|
|
774
|
+
*/
|
|
405
775
|
declare const RESTRICTED_METHODS: Set<string>;
|
|
406
776
|
/** RPC methods that can be auto-proxied without approval */
|
|
407
777
|
declare const UNRESTRICTED_METHODS: Set<string>;
|
|
408
778
|
|
|
409
|
-
export { ConnectionManager, ConnectionStatus, type DAppMetadata, type DAppSession, DEFAULT_RELAY_URL,
|
|
779
|
+
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, bytesToHex, cidFromString, cidRandom, cidToString, computeFingerprint, computeMessageDigest, computeTypedDataDigest, concatBytes, encodeField, encodeType, fingerprintEquals, generateConnectionURI, getAppStoreUrl, hashStruct, hexToBytes, isMobileBrowser, parseConnectionURI, typeHash, verifyMessage, verifyTypedData };
|