@qrlwallet/connect 4.0.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/README.md +29 -4
- package/dist/index.d.mts +32 -15
- package/dist/index.d.ts +32 -15
- package/dist/index.js +91 -29
- package/dist/index.mjs +86 -29
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Connect your dApp to QRL Wallet. Users scan a QR code (desktop) or tap a button (mobile) to pair their wallet, then approve transactions right from the app.
|
|
4
4
|
|
|
5
|
+
## Version 5 migration
|
|
6
|
+
|
|
7
|
+
Version 5 requires QIP-55 addresses: `Q` followed by 128 hexadecimal characters.
|
|
8
|
+
Account validation and message-signature verification use the complete 64-byte
|
|
9
|
+
address. Applications using legacy 20-byte addresses must remain on version 4
|
|
10
|
+
until their network and wallet support QIP-55.
|
|
11
|
+
|
|
12
|
+
Wallet typed-data requests are disabled pending a versioned 64-byte address
|
|
13
|
+
encoding. The local verifier remains available for address-free typed data.
|
|
14
|
+
The private v3 network is a test network; its availability does not establish
|
|
15
|
+
compatibility with legacy deployments or contracts.
|
|
16
|
+
|
|
5
17
|
## How it works
|
|
6
18
|
|
|
7
19
|
1. Your dApp generates a connection URI and shows it as a QR code
|
|
@@ -78,7 +90,7 @@ const txHash = await qrl.request({
|
|
|
78
90
|
params: [
|
|
79
91
|
{
|
|
80
92
|
from: accounts[0],
|
|
81
|
-
to: '
|
|
93
|
+
to: 'Q22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222',
|
|
82
94
|
value: '0x2386F26FC10000', // 0.01 QRL
|
|
83
95
|
},
|
|
84
96
|
],
|
|
@@ -161,15 +173,16 @@ The main class. Creates a connection manager and EIP-1193 provider.
|
|
|
161
173
|
**Require user approval:**
|
|
162
174
|
`qrl_requestAccounts`, `qrl_sendTransaction`, `qrl_signTransaction`, `qrl_signMessage`, `qrl_signTypedData`, `wallet_switchQrlChain`
|
|
163
175
|
|
|
164
|
-
`qrl_signMessage`
|
|
176
|
+
`qrl_signMessage` uses SHAKE256 plus native ML-DSA-87 ctx and returns a rich `{ signature, publicKey, descriptor, signer, digest, schemeVersion }` object. During the QIP-55 port, `qrl_signTypedData` stays in the approval policy and fails locally before relay use. Its v1 encoding fixes addresses to a 32-byte slot, so a new 64-byte encoding needs a new scheme version and regenerated wallet parity vectors.
|
|
165
177
|
|
|
166
178
|
`request()` returns `unknown`. First validate the exact response shape with
|
|
167
179
|
`isQrlSignedMessageResult` or `isQrlSignedTypedDataResult`, then require
|
|
168
180
|
`hasSigningDescriptor`. PQP3 authenticates the paired transport session. It
|
|
169
181
|
does not prove that the returned ML-DSA signature is valid or that its public
|
|
170
182
|
key belongs to the claimed signer. Authentication and authorization flows must
|
|
171
|
-
call `verifyMessageForSigner` or `verifyTypedDataForSigner`
|
|
172
|
-
challenge or payload and the expected
|
|
183
|
+
call `verifyMessageForSigner` or the historical v1 `verifyTypedDataForSigner`
|
|
184
|
+
with the original challenge or payload and the expected QIP-55 Q + 128 hex
|
|
185
|
+
address.
|
|
173
186
|
|
|
174
187
|
The lower-level `verifyMessageSignature` / `verifyTypedDataSignature` helpers
|
|
175
188
|
verify only the supplied key and signature; they do not prove that the key
|
|
@@ -195,6 +208,18 @@ field must exactly match that approved address.
|
|
|
195
208
|
An account string is not proof of control. For authentication, request a fresh
|
|
196
209
|
challenge with `qrl_signMessage` and verify it with `verifyMessageForSigner`.
|
|
197
210
|
|
|
211
|
+
## Address display
|
|
212
|
+
|
|
213
|
+
Use `formatQrlAddressFingerprint(address)` for compact QRL account labels. It
|
|
214
|
+
preserves checksum case and shows the first, exact middle, and final 8 hex
|
|
215
|
+
characters. Keep the complete raw address in links, clipboard values, QR codes,
|
|
216
|
+
RPC requests, and signing payloads.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const label = formatQrlAddressFingerprint(account);
|
|
220
|
+
// Q11111111...33333333...55555555
|
|
221
|
+
```
|
|
222
|
+
|
|
198
223
|
## Sessions
|
|
199
224
|
|
|
200
225
|
Established v5 sessions persist in `localStorage` for 7 days. When a user
|
package/dist/index.d.mts
CHANGED
|
@@ -269,10 +269,10 @@ interface EIP6963ProviderInfoOverride {
|
|
|
269
269
|
rdns?: string;
|
|
270
270
|
}
|
|
271
271
|
/**
|
|
272
|
-
* Rich response object returned by
|
|
273
|
-
* `qrl_signTypedData
|
|
274
|
-
* ML-DSA-87 public keys cannot be recovered from a signature,
|
|
275
|
-
* wallet
|
|
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
276
|
* the original payload plus the signature fields below.
|
|
277
277
|
*/
|
|
278
278
|
interface QrlSignedResult {
|
|
@@ -286,7 +286,7 @@ interface QrlSignedResult {
|
|
|
286
286
|
* requires it.
|
|
287
287
|
*/
|
|
288
288
|
descriptor?: string;
|
|
289
|
-
/**
|
|
289
|
+
/** QIP-55 129-character checksummed Q-address derived from descriptor + key. */
|
|
290
290
|
signer: string;
|
|
291
291
|
/** 0x-hex of the 64-byte SHAKE256 digest that was signed. */
|
|
292
292
|
digest: string;
|
|
@@ -303,8 +303,8 @@ interface QrlSignedMessageResult extends QrlSignedResult {
|
|
|
303
303
|
schemeVersion: 'QRL-SIGN-MSG-v1';
|
|
304
304
|
}
|
|
305
305
|
/**
|
|
306
|
-
*
|
|
307
|
-
* verifier
|
|
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.
|
|
308
308
|
*/
|
|
309
309
|
interface QrlSignedTypedDataResult extends QrlSignedResult {
|
|
310
310
|
schemeVersion: 'QRL-SIGN-TYPED-v1';
|
|
@@ -338,10 +338,10 @@ declare function hasSigningDescriptor(result: unknown): result is QrlSigningResu
|
|
|
338
338
|
*/
|
|
339
339
|
type QrlSignMessageParams = [string, string];
|
|
340
340
|
/**
|
|
341
|
-
* `qrl_signTypedData` params: `[signer, payload]`.
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
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.
|
|
345
345
|
*/
|
|
346
346
|
interface QrlTypedDataPayload {
|
|
347
347
|
types: Record<string, readonly {
|
|
@@ -887,13 +887,17 @@ declare const DIGEST_LEN = 64;
|
|
|
887
887
|
declare function computeMessageDigest(messageBytes: Uint8Array): Uint8Array;
|
|
888
888
|
|
|
889
889
|
/**
|
|
890
|
-
* EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
|
|
890
|
+
* Historical EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
|
|
891
891
|
*
|
|
892
892
|
* Byte-identical algorithm with the wallet's signing/typedData.ts; this
|
|
893
893
|
* file is the SDK port (only the import block differs: the SDK routes
|
|
894
894
|
* SHAKE256 through src/crypto/primitives.ts, its crypto boundary). See
|
|
895
895
|
* the wallet copy for the spec-level comments; behavior must stay in
|
|
896
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.
|
|
897
901
|
*/
|
|
898
902
|
declare const TYPED_DATA_LIMITS: Readonly<{
|
|
899
903
|
maxTypes: 32;
|
|
@@ -965,7 +969,7 @@ interface VerifyTypedDataParams {
|
|
|
965
969
|
*/
|
|
966
970
|
declare function verifyTypedDataSignature({ signature, publicKey, payload, }: VerifyTypedDataParams): boolean;
|
|
967
971
|
interface VerifyMessageForSignerParams extends VerifyMessageParams {
|
|
968
|
-
/**
|
|
972
|
+
/** QIP-55 Q + 128 hex address expected by the dApp. */
|
|
969
973
|
expectedSigner: string;
|
|
970
974
|
/** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
|
|
971
975
|
descriptor?: Uint8Array | string | undefined;
|
|
@@ -973,7 +977,7 @@ interface VerifyMessageForSignerParams extends VerifyMessageParams {
|
|
|
973
977
|
/** Verify both the message signature and public-key-to-signer binding. */
|
|
974
978
|
declare function verifyMessageForSigner({ expectedSigner, descriptor, signature, publicKey, messageBytes, }: VerifyMessageForSignerParams): boolean;
|
|
975
979
|
interface VerifyTypedDataForSignerParams extends VerifyTypedDataParams {
|
|
976
|
-
/**
|
|
980
|
+
/** QIP-55 Q + 128 hex address expected by the dApp. */
|
|
977
981
|
expectedSigner: string;
|
|
978
982
|
/** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
|
|
979
983
|
descriptor?: Uint8Array | string | undefined;
|
|
@@ -1093,6 +1097,19 @@ declare function getAppStoreUrl(): string;
|
|
|
1093
1097
|
*/
|
|
1094
1098
|
declare function attemptWalletRedirect(uri: string, timeoutMs?: number): Promise<boolean>;
|
|
1095
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
|
+
|
|
1096
1113
|
declare const DEFAULT_RELAY_URL = "https://qrlwallet.com";
|
|
1097
1114
|
declare const PROTOCOL_VERSION = 3;
|
|
1098
1115
|
declare const PAIRING_CAPABILITY_LEN = 32;
|
|
@@ -1118,4 +1135,4 @@ declare function isValidJsonRpcMethod(value: unknown): value is string;
|
|
|
1118
1135
|
*/
|
|
1119
1136
|
declare function classifyRpcMethod(method: string): RpcMethodPolicy;
|
|
1120
1137
|
|
|
1121
|
-
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_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, generateConnectionURI, getAppStoreUrl, hasSigningDescriptor, hashStruct, hexToBytes, isMobileBrowser, isQrlSignedMessageResult, isQrlSignedResult, isQrlSignedTypedDataResult, isValidJsonRpcId, isValidJsonRpcMethod, normalizeRelayUrl, parseConnectionURI, typeHash, verifyMessage, verifyMessageForSigner, verifyMessageSignature, verifyTypedData, verifyTypedDataForSigner, verifyTypedDataSignature };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -269,10 +269,10 @@ interface EIP6963ProviderInfoOverride {
|
|
|
269
269
|
rdns?: string;
|
|
270
270
|
}
|
|
271
271
|
/**
|
|
272
|
-
* Rich response object returned by
|
|
273
|
-
* `qrl_signTypedData
|
|
274
|
-
* ML-DSA-87 public keys cannot be recovered from a signature,
|
|
275
|
-
* wallet
|
|
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
276
|
* the original payload plus the signature fields below.
|
|
277
277
|
*/
|
|
278
278
|
interface QrlSignedResult {
|
|
@@ -286,7 +286,7 @@ interface QrlSignedResult {
|
|
|
286
286
|
* requires it.
|
|
287
287
|
*/
|
|
288
288
|
descriptor?: string;
|
|
289
|
-
/**
|
|
289
|
+
/** QIP-55 129-character checksummed Q-address derived from descriptor + key. */
|
|
290
290
|
signer: string;
|
|
291
291
|
/** 0x-hex of the 64-byte SHAKE256 digest that was signed. */
|
|
292
292
|
digest: string;
|
|
@@ -303,8 +303,8 @@ interface QrlSignedMessageResult extends QrlSignedResult {
|
|
|
303
303
|
schemeVersion: 'QRL-SIGN-MSG-v1';
|
|
304
304
|
}
|
|
305
305
|
/**
|
|
306
|
-
*
|
|
307
|
-
* verifier
|
|
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.
|
|
308
308
|
*/
|
|
309
309
|
interface QrlSignedTypedDataResult extends QrlSignedResult {
|
|
310
310
|
schemeVersion: 'QRL-SIGN-TYPED-v1';
|
|
@@ -338,10 +338,10 @@ declare function hasSigningDescriptor(result: unknown): result is QrlSigningResu
|
|
|
338
338
|
*/
|
|
339
339
|
type QrlSignMessageParams = [string, string];
|
|
340
340
|
/**
|
|
341
|
-
* `qrl_signTypedData` params: `[signer, payload]`.
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
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.
|
|
345
345
|
*/
|
|
346
346
|
interface QrlTypedDataPayload {
|
|
347
347
|
types: Record<string, readonly {
|
|
@@ -887,13 +887,17 @@ declare const DIGEST_LEN = 64;
|
|
|
887
887
|
declare function computeMessageDigest(messageBytes: Uint8Array): Uint8Array;
|
|
888
888
|
|
|
889
889
|
/**
|
|
890
|
-
* EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
|
|
890
|
+
* Historical EIP-712-shaped typed-data encoding for `qrl_signTypedData` v1.
|
|
891
891
|
*
|
|
892
892
|
* Byte-identical algorithm with the wallet's signing/typedData.ts; this
|
|
893
893
|
* file is the SDK port (only the import block differs: the SDK routes
|
|
894
894
|
* SHAKE256 through src/crypto/primitives.ts, its crypto boundary). See
|
|
895
895
|
* the wallet copy for the spec-level comments; behavior must stay in
|
|
896
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.
|
|
897
901
|
*/
|
|
898
902
|
declare const TYPED_DATA_LIMITS: Readonly<{
|
|
899
903
|
maxTypes: 32;
|
|
@@ -965,7 +969,7 @@ interface VerifyTypedDataParams {
|
|
|
965
969
|
*/
|
|
966
970
|
declare function verifyTypedDataSignature({ signature, publicKey, payload, }: VerifyTypedDataParams): boolean;
|
|
967
971
|
interface VerifyMessageForSignerParams extends VerifyMessageParams {
|
|
968
|
-
/**
|
|
972
|
+
/** QIP-55 Q + 128 hex address expected by the dApp. */
|
|
969
973
|
expectedSigner: string;
|
|
970
974
|
/** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
|
|
971
975
|
descriptor?: Uint8Array | string | undefined;
|
|
@@ -973,7 +977,7 @@ interface VerifyMessageForSignerParams extends VerifyMessageParams {
|
|
|
973
977
|
/** Verify both the message signature and public-key-to-signer binding. */
|
|
974
978
|
declare function verifyMessageForSigner({ expectedSigner, descriptor, signature, publicKey, messageBytes, }: VerifyMessageForSignerParams): boolean;
|
|
975
979
|
interface VerifyTypedDataForSignerParams extends VerifyTypedDataParams {
|
|
976
|
-
/**
|
|
980
|
+
/** QIP-55 Q + 128 hex address expected by the dApp. */
|
|
977
981
|
expectedSigner: string;
|
|
978
982
|
/** Exact 3-byte wallet descriptor. Missing legacy values fail closed. */
|
|
979
983
|
descriptor?: Uint8Array | string | undefined;
|
|
@@ -1093,6 +1097,19 @@ declare function getAppStoreUrl(): string;
|
|
|
1093
1097
|
*/
|
|
1094
1098
|
declare function attemptWalletRedirect(uri: string, timeoutMs?: number): Promise<boolean>;
|
|
1095
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
|
+
|
|
1096
1113
|
declare const DEFAULT_RELAY_URL = "https://qrlwallet.com";
|
|
1097
1114
|
declare const PROTOCOL_VERSION = 3;
|
|
1098
1115
|
declare const PAIRING_CAPABILITY_LEN = 32;
|
|
@@ -1118,4 +1135,4 @@ declare function isValidJsonRpcMethod(value: unknown): value is string;
|
|
|
1118
1135
|
*/
|
|
1119
1136
|
declare function classifyRpcMethod(method: string): RpcMethodPolicy;
|
|
1120
1137
|
|
|
1121
|
-
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_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, generateConnectionURI, getAppStoreUrl, hasSigningDescriptor, hashStruct, hexToBytes, isMobileBrowser, isQrlSignedMessageResult, isQrlSignedResult, isQrlSignedTypedDataResult, isValidJsonRpcId, isValidJsonRpcMethod, normalizeRelayUrl, parseConnectionURI, typeHash, verifyMessage, verifyMessageForSigner, verifyMessageSignature, verifyTypedData, verifyTypedDataForSigner, verifyTypedDataSignature };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,8 @@ __export(index_exports, {
|
|
|
53
53
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
54
54
|
QRLConnect: () => QRLConnectProvider,
|
|
55
55
|
QRLConnectProvider: () => QRLConnectProvider,
|
|
56
|
+
QRL_ADDRESS_BYTES: () => QRL_ADDRESS_BYTES,
|
|
57
|
+
QRL_ADDRESS_HEX_LENGTH: () => QRL_ADDRESS_HEX_LENGTH,
|
|
56
58
|
QRL_CONNECT_PROVIDER_INFO: () => QRL_CONNECT_PROVIDER_INFO,
|
|
57
59
|
RESTRICTED_METHODS: () => RESTRICTED_METHODS,
|
|
58
60
|
SCHEME_TAG_MSG: () => SCHEME_TAG_MSG,
|
|
@@ -75,11 +77,13 @@ __export(index_exports, {
|
|
|
75
77
|
encodeField: () => encodeField,
|
|
76
78
|
encodeType: () => encodeType,
|
|
77
79
|
fingerprintEquals: () => fingerprintEquals,
|
|
80
|
+
formatQrlAddressFingerprint: () => formatQrlAddressFingerprint,
|
|
78
81
|
generateConnectionURI: () => generateConnectionURI,
|
|
79
82
|
getAppStoreUrl: () => getAppStoreUrl,
|
|
80
83
|
hasSigningDescriptor: () => hasSigningDescriptor,
|
|
81
84
|
hashStruct: () => hashStruct,
|
|
82
85
|
hexToBytes: () => hexToBytes,
|
|
86
|
+
isCurrentQrlAddress: () => isCurrentQrlAddress,
|
|
83
87
|
isMobileBrowser: () => isMobileBrowser,
|
|
84
88
|
isQrlSignedMessageResult: () => isQrlSignedMessageResult,
|
|
85
89
|
isQrlSignedResult: () => isQrlSignedResult,
|
|
@@ -88,6 +92,7 @@ __export(index_exports, {
|
|
|
88
92
|
isValidJsonRpcMethod: () => isValidJsonRpcMethod,
|
|
89
93
|
normalizeRelayUrl: () => normalizeRelayUrl,
|
|
90
94
|
parseConnectionURI: () => parseConnectionURI,
|
|
95
|
+
qip55AddressFromBytes: () => qip55AddressFromBytes,
|
|
91
96
|
typeHash: () => typeHash,
|
|
92
97
|
verifyMessage: () => verifyMessage,
|
|
93
98
|
verifyMessageForSigner: () => verifyMessageForSigner,
|
|
@@ -209,6 +214,54 @@ function constantTimeEquals(a, b) {
|
|
|
209
214
|
return d === 0;
|
|
210
215
|
}
|
|
211
216
|
|
|
217
|
+
// src/utils/qrlAddress.ts
|
|
218
|
+
var QRL_ADDRESS_BYTES = 64;
|
|
219
|
+
var QRL_ADDRESS_HEX_LENGTH = QRL_ADDRESS_BYTES * 2;
|
|
220
|
+
var QRL_ADDRESS_RE = new RegExp(`^Q[0-9a-fA-F]{${QRL_ADDRESS_HEX_LENGTH}}$`);
|
|
221
|
+
var textEncoder = new TextEncoder();
|
|
222
|
+
function checksummedHex(lowerHex) {
|
|
223
|
+
const hash = shake256Digest(textEncoder.encode(lowerHex), QRL_ADDRESS_BYTES);
|
|
224
|
+
let result = "";
|
|
225
|
+
for (let index = 0; index < lowerHex.length; index++) {
|
|
226
|
+
const char = lowerHex[index] ?? "";
|
|
227
|
+
if (char >= "a" && char <= "f") {
|
|
228
|
+
const byte = hash[index >> 1] ?? 0;
|
|
229
|
+
const nibble = (index & 1) === 0 ? byte >> 4 : byte & 15;
|
|
230
|
+
result += nibble >= 8 ? char.toUpperCase() : char;
|
|
231
|
+
} else {
|
|
232
|
+
result += char;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
function isCurrentQrlAddress(value) {
|
|
238
|
+
if (typeof value !== "string" || !QRL_ADDRESS_RE.test(value)) return false;
|
|
239
|
+
const body = value.slice(1);
|
|
240
|
+
const lower = body.toLowerCase();
|
|
241
|
+
if (body === lower || body === body.toUpperCase()) return true;
|
|
242
|
+
return body === checksummedHex(lower);
|
|
243
|
+
}
|
|
244
|
+
function formatQrlAddressFingerprint(address) {
|
|
245
|
+
if (!/^Q(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{128})$/.test(address)) return address;
|
|
246
|
+
const body = address.slice(1);
|
|
247
|
+
const segmentLength = 8;
|
|
248
|
+
if (body.length < segmentLength * 3) return address;
|
|
249
|
+
const middleStart = Math.floor((body.length - segmentLength) / 2);
|
|
250
|
+
return [
|
|
251
|
+
`Q${body.slice(0, segmentLength)}`,
|
|
252
|
+
body.slice(middleStart, middleStart + segmentLength),
|
|
253
|
+
body.slice(-segmentLength)
|
|
254
|
+
].join("...");
|
|
255
|
+
}
|
|
256
|
+
function qip55AddressFromBytes(address) {
|
|
257
|
+
if (!(address instanceof Uint8Array) || address.length !== QRL_ADDRESS_BYTES) {
|
|
258
|
+
throw new Error(`address must be exactly ${QRL_ADDRESS_BYTES} bytes`);
|
|
259
|
+
}
|
|
260
|
+
let lower = "";
|
|
261
|
+
for (const byte of address) lower += byte.toString(16).padStart(2, "0");
|
|
262
|
+
return `Q${checksummedHex(lower)}`;
|
|
263
|
+
}
|
|
264
|
+
|
|
212
265
|
// src/config.ts
|
|
213
266
|
var DEFAULT_RELAY_URL = "https://qrlwallet.com";
|
|
214
267
|
var RELAY_PATH = "/relay";
|
|
@@ -222,10 +275,6 @@ var MAX_JSON_RPC_ID_LENGTH = 128;
|
|
|
222
275
|
var MAX_JSON_RPC_METHOD_LENGTH = 128;
|
|
223
276
|
var MAX_RELAY_URL_LENGTH = 2048;
|
|
224
277
|
var RECONNECT_WALLET_PROBE_MS = 12 * 1e3;
|
|
225
|
-
var CURRENT_QRL_ADDRESS_RE = /^Q[0-9a-fA-F]{40}$/;
|
|
226
|
-
function isCurrentQrlAddress(value) {
|
|
227
|
-
return typeof value === "string" && CURRENT_QRL_ADDRESS_RE.test(value);
|
|
228
|
-
}
|
|
229
278
|
function isExplicitLoopbackHostname(value) {
|
|
230
279
|
const hostname = value.toLowerCase();
|
|
231
280
|
return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
@@ -301,9 +350,9 @@ function classifyRpcMethod(method) {
|
|
|
301
350
|
}
|
|
302
351
|
|
|
303
352
|
// src/PQCrypto.ts
|
|
304
|
-
var
|
|
305
|
-
var LABEL =
|
|
306
|
-
var LABEL_AEAD_SUFFIX =
|
|
353
|
+
var textEncoder2 = new TextEncoder();
|
|
354
|
+
var LABEL = textEncoder2.encode("pq-pair/v3");
|
|
355
|
+
var LABEL_AEAD_SUFFIX = textEncoder2.encode(" aead");
|
|
307
356
|
var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
|
|
308
357
|
var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
|
|
309
358
|
var ML_KEM_768_PK_LEN = 1184;
|
|
@@ -502,7 +551,7 @@ function isSigningDescriptor(value) {
|
|
|
502
551
|
return typeof value === "string" && /^0x01[0-9a-fA-F]{4}$/.test(value);
|
|
503
552
|
}
|
|
504
553
|
function hasValidSigningFields(record) {
|
|
505
|
-
return isFixedHex(record.signature, SIGNATURE_HEX_BYTES) && isFixedHex(record.publicKey, PUBLIC_KEY_HEX_BYTES) && (!hasOwn(record, "descriptor") || isSigningDescriptor(record.descriptor)) && typeof record.signer === "string" &&
|
|
554
|
+
return isFixedHex(record.signature, SIGNATURE_HEX_BYTES) && isFixedHex(record.publicKey, PUBLIC_KEY_HEX_BYTES) && (!hasOwn(record, "descriptor") || isSigningDescriptor(record.descriptor)) && typeof record.signer === "string" && isCurrentQrlAddress(record.signer) && isFixedHex(record.digest, DIGEST_HEX_BYTES);
|
|
506
555
|
}
|
|
507
556
|
function isQrlSignedMessageResult(value) {
|
|
508
557
|
try {
|
|
@@ -551,10 +600,10 @@ function error(tag, ...args) {
|
|
|
551
600
|
}
|
|
552
601
|
|
|
553
602
|
// src/KeyExchange.ts
|
|
554
|
-
var
|
|
603
|
+
var textEncoder3 = new TextEncoder();
|
|
555
604
|
var textDecoder = new TextDecoder();
|
|
556
|
-
var HELLO_WALLET =
|
|
557
|
-
var HELLO_DAPP =
|
|
605
|
+
var HELLO_WALLET = textEncoder3.encode("hello/wallet/v1");
|
|
606
|
+
var HELLO_DAPP = textEncoder3.encode("hello/dapp/v1");
|
|
558
607
|
var AES_GCM_TAG_LEN = 16;
|
|
559
608
|
var SYNACK_C0_LEN = HELLO_WALLET.length + AES_GCM_TAG_LEN;
|
|
560
609
|
var ACK_C1_LEN = HELLO_DAPP.length + AES_GCM_TAG_LEN;
|
|
@@ -828,7 +877,7 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
828
877
|
throw new Error("KeyExchange: send counter exhausted");
|
|
829
878
|
}
|
|
830
879
|
const seq = session.sendSeq++;
|
|
831
|
-
const pt =
|
|
880
|
+
const pt = textEncoder3.encode(data);
|
|
832
881
|
try {
|
|
833
882
|
const ct = await seal(session.key, session.sendDir, seq, session.htx, pt);
|
|
834
883
|
if (this.stateGeneration !== generation || this.session !== session) {
|
|
@@ -3272,10 +3321,14 @@ function typeHash(primary, types) {
|
|
|
3272
3321
|
return shake256Digest(new TextEncoder().encode(encodeType(primary, types)), DIGEST_LEN);
|
|
3273
3322
|
}
|
|
3274
3323
|
function parseQAddress(addr) {
|
|
3275
|
-
if (
|
|
3276
|
-
throw new Error(`invalid Q-address: ${addr}`);
|
|
3324
|
+
if (!isCurrentQrlAddress(addr)) {
|
|
3325
|
+
throw new Error(`invalid Q-address: ${String(addr)}`);
|
|
3277
3326
|
}
|
|
3278
|
-
|
|
3327
|
+
const bytes = hexToBytes("0x" + addr.slice(1).toLowerCase());
|
|
3328
|
+
if (bytes.length > SLOT) {
|
|
3329
|
+
throw new Error("qrl_signTypedData v1 does not support QIP-55 address fields");
|
|
3330
|
+
}
|
|
3331
|
+
return bytes;
|
|
3279
3332
|
}
|
|
3280
3333
|
function padLeft32(bytes) {
|
|
3281
3334
|
if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
|
|
@@ -3687,8 +3740,9 @@ function validateRestrictedRequest(method, params, authorizedAccounts) {
|
|
|
3687
3740
|
if (params[0] !== authorizedAccount) {
|
|
3688
3741
|
throw new Error("qrl_signTypedData signer is not the authorized account");
|
|
3689
3742
|
}
|
|
3690
|
-
|
|
3691
|
-
|
|
3743
|
+
throw new Error(
|
|
3744
|
+
"qrl_signTypedData is unavailable for QIP-55 until the 64-byte word encoding and signing scheme version are finalized"
|
|
3745
|
+
);
|
|
3692
3746
|
}
|
|
3693
3747
|
function requiresAuthorizedAccount(method) {
|
|
3694
3748
|
return method === "qrl_sendTransaction" || method === "qrl_signTransaction" || method === "qrl_signMessage" || method === "qrl_signTypedData";
|
|
@@ -3896,7 +3950,12 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
3896
3950
|
}
|
|
3897
3951
|
this.pendingRequests.delete(response.id);
|
|
3898
3952
|
if (response.error) {
|
|
3899
|
-
pending.reject(
|
|
3953
|
+
pending.reject(
|
|
3954
|
+
Object.assign(new Error(response.error.message || "Request failed"), {
|
|
3955
|
+
code: response.error.code,
|
|
3956
|
+
...response.error.data === void 0 ? {} : { data: response.error.data }
|
|
3957
|
+
})
|
|
3958
|
+
);
|
|
3900
3959
|
return;
|
|
3901
3960
|
}
|
|
3902
3961
|
try {
|
|
@@ -4143,9 +4202,10 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
4143
4202
|
}
|
|
4144
4203
|
// ── In-flight persistence (survive the return-redirect reload) ──────
|
|
4145
4204
|
readInflight() {
|
|
4146
|
-
|
|
4205
|
+
const storage = getBrowserStorage();
|
|
4206
|
+
if (!storage) return [];
|
|
4147
4207
|
try {
|
|
4148
|
-
const raw =
|
|
4208
|
+
const raw = storage.getItem(this.inflightKey);
|
|
4149
4209
|
if (!raw) return [];
|
|
4150
4210
|
const parsed = JSON.parse(raw);
|
|
4151
4211
|
if (!Array.isArray(parsed)) return [];
|
|
@@ -4161,12 +4221,13 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
4161
4221
|
}
|
|
4162
4222
|
}
|
|
4163
4223
|
writeInflight(records) {
|
|
4164
|
-
|
|
4224
|
+
const storage = getBrowserStorage();
|
|
4225
|
+
if (!storage) return;
|
|
4165
4226
|
try {
|
|
4166
4227
|
if (records.length === 0) {
|
|
4167
|
-
|
|
4228
|
+
storage.removeItem(this.inflightKey);
|
|
4168
4229
|
} else {
|
|
4169
|
-
|
|
4230
|
+
storage.setItem(this.inflightKey, JSON.stringify(records));
|
|
4170
4231
|
}
|
|
4171
4232
|
} catch {
|
|
4172
4233
|
}
|
|
@@ -4300,7 +4361,6 @@ var ML_DSA_87_DESCRIPTOR_TYPE = 1;
|
|
|
4300
4361
|
var ML_DSA_DESCRIPTOR_BYTES = 3;
|
|
4301
4362
|
var ML_DSA_87_PUBLIC_KEY_BYTES = 2592;
|
|
4302
4363
|
var ML_DSA_87_SIGNATURE_BYTES = 4627;
|
|
4303
|
-
var CURRENT_QRL_ADDRESS_BYTES = 20;
|
|
4304
4364
|
function bytesOrHex(v) {
|
|
4305
4365
|
if (v instanceof Uint8Array) return new Uint8Array(v);
|
|
4306
4366
|
if (typeof v === "string") return hexToBytes(v);
|
|
@@ -4357,11 +4417,8 @@ function publicKeyMatchesSigner(expectedSigner, descriptor, publicKey) {
|
|
|
4357
4417
|
if (descriptor[0] !== ML_DSA_87_DESCRIPTOR_TYPE) {
|
|
4358
4418
|
return false;
|
|
4359
4419
|
}
|
|
4360
|
-
const addressBytes = shake256Digest(
|
|
4361
|
-
|
|
4362
|
-
CURRENT_QRL_ADDRESS_BYTES
|
|
4363
|
-
);
|
|
4364
|
-
const derived = `Q${bytesToHex(addressBytes).slice(2)}`;
|
|
4420
|
+
const addressBytes = shake256Digest(concatBytes(descriptor, publicKey), QRL_ADDRESS_BYTES);
|
|
4421
|
+
const derived = qip55AddressFromBytes(addressBytes);
|
|
4365
4422
|
return derived.toLowerCase() === expectedSigner.toLowerCase();
|
|
4366
4423
|
}
|
|
4367
4424
|
function verifyMessageForSigner({
|
|
@@ -4432,6 +4489,8 @@ function verifyTypedData(params) {
|
|
|
4432
4489
|
PROTOCOL_VERSION,
|
|
4433
4490
|
QRLConnect,
|
|
4434
4491
|
QRLConnectProvider,
|
|
4492
|
+
QRL_ADDRESS_BYTES,
|
|
4493
|
+
QRL_ADDRESS_HEX_LENGTH,
|
|
4435
4494
|
QRL_CONNECT_PROVIDER_INFO,
|
|
4436
4495
|
RESTRICTED_METHODS,
|
|
4437
4496
|
SCHEME_TAG_MSG,
|
|
@@ -4454,11 +4513,13 @@ function verifyTypedData(params) {
|
|
|
4454
4513
|
encodeField,
|
|
4455
4514
|
encodeType,
|
|
4456
4515
|
fingerprintEquals,
|
|
4516
|
+
formatQrlAddressFingerprint,
|
|
4457
4517
|
generateConnectionURI,
|
|
4458
4518
|
getAppStoreUrl,
|
|
4459
4519
|
hasSigningDescriptor,
|
|
4460
4520
|
hashStruct,
|
|
4461
4521
|
hexToBytes,
|
|
4522
|
+
isCurrentQrlAddress,
|
|
4462
4523
|
isMobileBrowser,
|
|
4463
4524
|
isQrlSignedMessageResult,
|
|
4464
4525
|
isQrlSignedResult,
|
|
@@ -4467,6 +4528,7 @@ function verifyTypedData(params) {
|
|
|
4467
4528
|
isValidJsonRpcMethod,
|
|
4468
4529
|
normalizeRelayUrl,
|
|
4469
4530
|
parseConnectionURI,
|
|
4531
|
+
qip55AddressFromBytes,
|
|
4470
4532
|
typeHash,
|
|
4471
4533
|
verifyMessage,
|
|
4472
4534
|
verifyMessageForSigner,
|
package/dist/index.mjs
CHANGED
|
@@ -109,6 +109,54 @@ function constantTimeEquals(a, b) {
|
|
|
109
109
|
return d === 0;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// src/utils/qrlAddress.ts
|
|
113
|
+
var QRL_ADDRESS_BYTES = 64;
|
|
114
|
+
var QRL_ADDRESS_HEX_LENGTH = QRL_ADDRESS_BYTES * 2;
|
|
115
|
+
var QRL_ADDRESS_RE = new RegExp(`^Q[0-9a-fA-F]{${QRL_ADDRESS_HEX_LENGTH}}$`);
|
|
116
|
+
var textEncoder = new TextEncoder();
|
|
117
|
+
function checksummedHex(lowerHex) {
|
|
118
|
+
const hash = shake256Digest(textEncoder.encode(lowerHex), QRL_ADDRESS_BYTES);
|
|
119
|
+
let result = "";
|
|
120
|
+
for (let index = 0; index < lowerHex.length; index++) {
|
|
121
|
+
const char = lowerHex[index] ?? "";
|
|
122
|
+
if (char >= "a" && char <= "f") {
|
|
123
|
+
const byte = hash[index >> 1] ?? 0;
|
|
124
|
+
const nibble = (index & 1) === 0 ? byte >> 4 : byte & 15;
|
|
125
|
+
result += nibble >= 8 ? char.toUpperCase() : char;
|
|
126
|
+
} else {
|
|
127
|
+
result += char;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
function isCurrentQrlAddress(value) {
|
|
133
|
+
if (typeof value !== "string" || !QRL_ADDRESS_RE.test(value)) return false;
|
|
134
|
+
const body = value.slice(1);
|
|
135
|
+
const lower = body.toLowerCase();
|
|
136
|
+
if (body === lower || body === body.toUpperCase()) return true;
|
|
137
|
+
return body === checksummedHex(lower);
|
|
138
|
+
}
|
|
139
|
+
function formatQrlAddressFingerprint(address) {
|
|
140
|
+
if (!/^Q(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{128})$/.test(address)) return address;
|
|
141
|
+
const body = address.slice(1);
|
|
142
|
+
const segmentLength = 8;
|
|
143
|
+
if (body.length < segmentLength * 3) return address;
|
|
144
|
+
const middleStart = Math.floor((body.length - segmentLength) / 2);
|
|
145
|
+
return [
|
|
146
|
+
`Q${body.slice(0, segmentLength)}`,
|
|
147
|
+
body.slice(middleStart, middleStart + segmentLength),
|
|
148
|
+
body.slice(-segmentLength)
|
|
149
|
+
].join("...");
|
|
150
|
+
}
|
|
151
|
+
function qip55AddressFromBytes(address) {
|
|
152
|
+
if (!(address instanceof Uint8Array) || address.length !== QRL_ADDRESS_BYTES) {
|
|
153
|
+
throw new Error(`address must be exactly ${QRL_ADDRESS_BYTES} bytes`);
|
|
154
|
+
}
|
|
155
|
+
let lower = "";
|
|
156
|
+
for (const byte of address) lower += byte.toString(16).padStart(2, "0");
|
|
157
|
+
return `Q${checksummedHex(lower)}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
112
160
|
// src/config.ts
|
|
113
161
|
var DEFAULT_RELAY_URL = "https://qrlwallet.com";
|
|
114
162
|
var RELAY_PATH = "/relay";
|
|
@@ -122,10 +170,6 @@ var MAX_JSON_RPC_ID_LENGTH = 128;
|
|
|
122
170
|
var MAX_JSON_RPC_METHOD_LENGTH = 128;
|
|
123
171
|
var MAX_RELAY_URL_LENGTH = 2048;
|
|
124
172
|
var RECONNECT_WALLET_PROBE_MS = 12 * 1e3;
|
|
125
|
-
var CURRENT_QRL_ADDRESS_RE = /^Q[0-9a-fA-F]{40}$/;
|
|
126
|
-
function isCurrentQrlAddress(value) {
|
|
127
|
-
return typeof value === "string" && CURRENT_QRL_ADDRESS_RE.test(value);
|
|
128
|
-
}
|
|
129
173
|
function isExplicitLoopbackHostname(value) {
|
|
130
174
|
const hostname = value.toLowerCase();
|
|
131
175
|
return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
@@ -201,9 +245,9 @@ function classifyRpcMethod(method) {
|
|
|
201
245
|
}
|
|
202
246
|
|
|
203
247
|
// src/PQCrypto.ts
|
|
204
|
-
var
|
|
205
|
-
var LABEL =
|
|
206
|
-
var LABEL_AEAD_SUFFIX =
|
|
248
|
+
var textEncoder2 = new TextEncoder();
|
|
249
|
+
var LABEL = textEncoder2.encode("pq-pair/v3");
|
|
250
|
+
var LABEL_AEAD_SUFFIX = textEncoder2.encode(" aead");
|
|
207
251
|
var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
|
|
208
252
|
var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
|
|
209
253
|
var ML_KEM_768_PK_LEN = 1184;
|
|
@@ -402,7 +446,7 @@ function isSigningDescriptor(value) {
|
|
|
402
446
|
return typeof value === "string" && /^0x01[0-9a-fA-F]{4}$/.test(value);
|
|
403
447
|
}
|
|
404
448
|
function hasValidSigningFields(record) {
|
|
405
|
-
return isFixedHex(record.signature, SIGNATURE_HEX_BYTES) && isFixedHex(record.publicKey, PUBLIC_KEY_HEX_BYTES) && (!hasOwn(record, "descriptor") || isSigningDescriptor(record.descriptor)) && typeof record.signer === "string" &&
|
|
449
|
+
return isFixedHex(record.signature, SIGNATURE_HEX_BYTES) && isFixedHex(record.publicKey, PUBLIC_KEY_HEX_BYTES) && (!hasOwn(record, "descriptor") || isSigningDescriptor(record.descriptor)) && typeof record.signer === "string" && isCurrentQrlAddress(record.signer) && isFixedHex(record.digest, DIGEST_HEX_BYTES);
|
|
406
450
|
}
|
|
407
451
|
function isQrlSignedMessageResult(value) {
|
|
408
452
|
try {
|
|
@@ -451,10 +495,10 @@ function error(tag, ...args) {
|
|
|
451
495
|
}
|
|
452
496
|
|
|
453
497
|
// src/KeyExchange.ts
|
|
454
|
-
var
|
|
498
|
+
var textEncoder3 = new TextEncoder();
|
|
455
499
|
var textDecoder = new TextDecoder();
|
|
456
|
-
var HELLO_WALLET =
|
|
457
|
-
var HELLO_DAPP =
|
|
500
|
+
var HELLO_WALLET = textEncoder3.encode("hello/wallet/v1");
|
|
501
|
+
var HELLO_DAPP = textEncoder3.encode("hello/dapp/v1");
|
|
458
502
|
var AES_GCM_TAG_LEN = 16;
|
|
459
503
|
var SYNACK_C0_LEN = HELLO_WALLET.length + AES_GCM_TAG_LEN;
|
|
460
504
|
var ACK_C1_LEN = HELLO_DAPP.length + AES_GCM_TAG_LEN;
|
|
@@ -728,7 +772,7 @@ var KeyExchange = class extends EventEmitter {
|
|
|
728
772
|
throw new Error("KeyExchange: send counter exhausted");
|
|
729
773
|
}
|
|
730
774
|
const seq = session.sendSeq++;
|
|
731
|
-
const pt =
|
|
775
|
+
const pt = textEncoder3.encode(data);
|
|
732
776
|
try {
|
|
733
777
|
const ct = await seal(session.key, session.sendDir, seq, session.htx, pt);
|
|
734
778
|
if (this.stateGeneration !== generation || this.session !== session) {
|
|
@@ -3172,10 +3216,14 @@ function typeHash(primary, types) {
|
|
|
3172
3216
|
return shake256Digest(new TextEncoder().encode(encodeType(primary, types)), DIGEST_LEN);
|
|
3173
3217
|
}
|
|
3174
3218
|
function parseQAddress(addr) {
|
|
3175
|
-
if (
|
|
3176
|
-
throw new Error(`invalid Q-address: ${addr}`);
|
|
3219
|
+
if (!isCurrentQrlAddress(addr)) {
|
|
3220
|
+
throw new Error(`invalid Q-address: ${String(addr)}`);
|
|
3177
3221
|
}
|
|
3178
|
-
|
|
3222
|
+
const bytes = hexToBytes("0x" + addr.slice(1).toLowerCase());
|
|
3223
|
+
if (bytes.length > SLOT) {
|
|
3224
|
+
throw new Error("qrl_signTypedData v1 does not support QIP-55 address fields");
|
|
3225
|
+
}
|
|
3226
|
+
return bytes;
|
|
3179
3227
|
}
|
|
3180
3228
|
function padLeft32(bytes) {
|
|
3181
3229
|
if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
|
|
@@ -3587,8 +3635,9 @@ function validateRestrictedRequest(method, params, authorizedAccounts) {
|
|
|
3587
3635
|
if (params[0] !== authorizedAccount) {
|
|
3588
3636
|
throw new Error("qrl_signTypedData signer is not the authorized account");
|
|
3589
3637
|
}
|
|
3590
|
-
|
|
3591
|
-
|
|
3638
|
+
throw new Error(
|
|
3639
|
+
"qrl_signTypedData is unavailable for QIP-55 until the 64-byte word encoding and signing scheme version are finalized"
|
|
3640
|
+
);
|
|
3592
3641
|
}
|
|
3593
3642
|
function requiresAuthorizedAccount(method) {
|
|
3594
3643
|
return method === "qrl_sendTransaction" || method === "qrl_signTransaction" || method === "qrl_signMessage" || method === "qrl_signTypedData";
|
|
@@ -3796,7 +3845,12 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
3796
3845
|
}
|
|
3797
3846
|
this.pendingRequests.delete(response.id);
|
|
3798
3847
|
if (response.error) {
|
|
3799
|
-
pending.reject(
|
|
3848
|
+
pending.reject(
|
|
3849
|
+
Object.assign(new Error(response.error.message || "Request failed"), {
|
|
3850
|
+
code: response.error.code,
|
|
3851
|
+
...response.error.data === void 0 ? {} : { data: response.error.data }
|
|
3852
|
+
})
|
|
3853
|
+
);
|
|
3800
3854
|
return;
|
|
3801
3855
|
}
|
|
3802
3856
|
try {
|
|
@@ -4043,9 +4097,10 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
4043
4097
|
}
|
|
4044
4098
|
// ── In-flight persistence (survive the return-redirect reload) ──────
|
|
4045
4099
|
readInflight() {
|
|
4046
|
-
|
|
4100
|
+
const storage = getBrowserStorage();
|
|
4101
|
+
if (!storage) return [];
|
|
4047
4102
|
try {
|
|
4048
|
-
const raw =
|
|
4103
|
+
const raw = storage.getItem(this.inflightKey);
|
|
4049
4104
|
if (!raw) return [];
|
|
4050
4105
|
const parsed = JSON.parse(raw);
|
|
4051
4106
|
if (!Array.isArray(parsed)) return [];
|
|
@@ -4061,12 +4116,13 @@ var QRLConnectProvider = class extends EventEmitter4 {
|
|
|
4061
4116
|
}
|
|
4062
4117
|
}
|
|
4063
4118
|
writeInflight(records) {
|
|
4064
|
-
|
|
4119
|
+
const storage = getBrowserStorage();
|
|
4120
|
+
if (!storage) return;
|
|
4065
4121
|
try {
|
|
4066
4122
|
if (records.length === 0) {
|
|
4067
|
-
|
|
4123
|
+
storage.removeItem(this.inflightKey);
|
|
4068
4124
|
} else {
|
|
4069
|
-
|
|
4125
|
+
storage.setItem(this.inflightKey, JSON.stringify(records));
|
|
4070
4126
|
}
|
|
4071
4127
|
} catch {
|
|
4072
4128
|
}
|
|
@@ -4200,7 +4256,6 @@ var ML_DSA_87_DESCRIPTOR_TYPE = 1;
|
|
|
4200
4256
|
var ML_DSA_DESCRIPTOR_BYTES = 3;
|
|
4201
4257
|
var ML_DSA_87_PUBLIC_KEY_BYTES = 2592;
|
|
4202
4258
|
var ML_DSA_87_SIGNATURE_BYTES = 4627;
|
|
4203
|
-
var CURRENT_QRL_ADDRESS_BYTES = 20;
|
|
4204
4259
|
function bytesOrHex(v) {
|
|
4205
4260
|
if (v instanceof Uint8Array) return new Uint8Array(v);
|
|
4206
4261
|
if (typeof v === "string") return hexToBytes(v);
|
|
@@ -4257,11 +4312,8 @@ function publicKeyMatchesSigner(expectedSigner, descriptor, publicKey) {
|
|
|
4257
4312
|
if (descriptor[0] !== ML_DSA_87_DESCRIPTOR_TYPE) {
|
|
4258
4313
|
return false;
|
|
4259
4314
|
}
|
|
4260
|
-
const addressBytes = shake256Digest(
|
|
4261
|
-
|
|
4262
|
-
CURRENT_QRL_ADDRESS_BYTES
|
|
4263
|
-
);
|
|
4264
|
-
const derived = `Q${bytesToHex(addressBytes).slice(2)}`;
|
|
4315
|
+
const addressBytes = shake256Digest(concatBytes(descriptor, publicKey), QRL_ADDRESS_BYTES);
|
|
4316
|
+
const derived = qip55AddressFromBytes(addressBytes);
|
|
4265
4317
|
return derived.toLowerCase() === expectedSigner.toLowerCase();
|
|
4266
4318
|
}
|
|
4267
4319
|
function verifyMessageForSigner({
|
|
@@ -4331,6 +4383,8 @@ export {
|
|
|
4331
4383
|
PROTOCOL_VERSION,
|
|
4332
4384
|
QRLConnectProvider as QRLConnect,
|
|
4333
4385
|
QRLConnectProvider,
|
|
4386
|
+
QRL_ADDRESS_BYTES,
|
|
4387
|
+
QRL_ADDRESS_HEX_LENGTH,
|
|
4334
4388
|
QRL_CONNECT_PROVIDER_INFO,
|
|
4335
4389
|
RESTRICTED_METHODS,
|
|
4336
4390
|
SCHEME_TAG_MSG,
|
|
@@ -4353,11 +4407,13 @@ export {
|
|
|
4353
4407
|
encodeField,
|
|
4354
4408
|
encodeType,
|
|
4355
4409
|
fingerprintEquals,
|
|
4410
|
+
formatQrlAddressFingerprint,
|
|
4356
4411
|
generateConnectionURI,
|
|
4357
4412
|
getAppStoreUrl,
|
|
4358
4413
|
hasSigningDescriptor,
|
|
4359
4414
|
hashStruct,
|
|
4360
4415
|
hexToBytes,
|
|
4416
|
+
isCurrentQrlAddress,
|
|
4361
4417
|
isMobileBrowser,
|
|
4362
4418
|
isQrlSignedMessageResult,
|
|
4363
4419
|
isQrlSignedResult,
|
|
@@ -4366,6 +4422,7 @@ export {
|
|
|
4366
4422
|
isValidJsonRpcMethod,
|
|
4367
4423
|
normalizeRelayUrl,
|
|
4368
4424
|
parseConnectionURI,
|
|
4425
|
+
qip55AddressFromBytes,
|
|
4369
4426
|
typeHash,
|
|
4370
4427
|
verifyMessage,
|
|
4371
4428
|
verifyMessageForSigner,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qrlwallet/connect",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"packageManager": "npm@10.9.8",
|
|
5
5
|
"description": "Connect your dApp to QRL Wallet via QR code or deep link",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,13 +44,13 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@noble/hashes": "^2.2.0",
|
|
46
46
|
"@noble/post-quantum": "^0.5.4",
|
|
47
|
-
"@theqrl/mldsa87": "^2.1.
|
|
47
|
+
"@theqrl/mldsa87": "^2.1.4",
|
|
48
48
|
"eventemitter3": "^5.0.1",
|
|
49
49
|
"socket.io-client": "^4.8.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@size-limit/file": "^11.0.0",
|
|
53
|
-
"@theqrl/wallet.js": "^
|
|
53
|
+
"@theqrl/wallet.js": "^6.2.4",
|
|
54
54
|
"@types/node": "^25.3.3",
|
|
55
55
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
56
56
|
"@typescript-eslint/parser": "^8.0.0",
|
|
@@ -60,10 +60,13 @@
|
|
|
60
60
|
"size-limit": "^11.0.0",
|
|
61
61
|
"tsup": "^8.4.0",
|
|
62
62
|
"typescript": "^5.7.0",
|
|
63
|
-
"vitest": "
|
|
63
|
+
"vitest": "4.1.11"
|
|
64
64
|
},
|
|
65
65
|
"overrides": {
|
|
66
|
-
"esbuild": "0.28.1"
|
|
66
|
+
"esbuild": "0.28.1",
|
|
67
|
+
"vitest": {
|
|
68
|
+
"vite": "7.3.5"
|
|
69
|
+
}
|
|
67
70
|
},
|
|
68
71
|
"size-limit": [
|
|
69
72
|
{
|