@metamask-previews/eth-qr-keyring 0.0.0-a9b0f29

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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ [Unreleased]: https://github.com/MetaMask/accounts/
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2020 MetaMask
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # QR Keyring
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ var _Device_instances, _Device_requestScan, _Device_pairedDevice, _Device_deviceDetailsFromUR, _Device_requestSignature, _Device_decodeUR;
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.Device = exports.DeviceMode = exports.SUPPORTED_UR_TYPE = void 0;
19
+ const rlp_1 = require("@ethereumjs/rlp");
20
+ const tx_1 = require("@ethereumjs/tx");
21
+ const util_1 = require("@ethereumjs/util");
22
+ const bc_ur_registry_eth_1 = require("@keystonehq/bc-ur-registry-eth");
23
+ const utils_1 = require("@metamask/utils");
24
+ // eslint-disable-next-line @typescript-eslint/naming-convention
25
+ const hdkey_1 = __importDefault(require("hdkey"));
26
+ const uuid_1 = require("uuid");
27
+ const qr_keyring_1 = require("./qr-keyring.cjs");
28
+ exports.SUPPORTED_UR_TYPE = {
29
+ CRYPTO_HDKEY: 'crypto-hdkey',
30
+ CRYPTO_ACCOUNT: 'crypto-account',
31
+ ETH_SIGNATURE: 'eth-signature',
32
+ };
33
+ var DeviceMode;
34
+ (function (DeviceMode) {
35
+ DeviceMode["HD"] = "hd";
36
+ DeviceMode["ACCOUNT"] = "account";
37
+ })(DeviceMode || (exports.DeviceMode = DeviceMode = {}));
38
+ const DEFAULT_CHILDREN_PATH = '0/*';
39
+ const MAX_INDEX = 1000;
40
+ /**
41
+ * Get the fingerprint of the source CryptoAccount or CryptoHDKey
42
+ *
43
+ * @param source - The source CryptoAccount or CryptoHDKey
44
+ * @returns The fingerprint of the source
45
+ */
46
+ function getFingerprintFromSource(source) {
47
+ return source instanceof bc_ur_registry_eth_1.CryptoAccount
48
+ ? source.getMasterFingerprint()?.toString('hex')
49
+ : source.getParentFingerprint()?.toString('hex');
50
+ }
51
+ /**
52
+ * Get fingerprint, account paths and names from the a CryptoAccount
53
+ *
54
+ * Note: This function emulates the behavior of the `@keystonehq/base-eth-keyring`
55
+ * library when dealing with CryptoAccount objects (for backwards compatibility
56
+ * reasons). Though, the way it retrieves `name` and `keyringAccount` is questionable,
57
+ * as `name` and `keyringAccount` are updated after each descriptor discovery, effectively
58
+ * returning the last descriptor's `name` and `keyringAccount`.
59
+ *
60
+ * @param source - The source CryptoAccount
61
+ * @returns The paths
62
+ */
63
+ function readCryptoAccountOutputDescriptors(source) {
64
+ const descriptors = source.getOutputDescriptors();
65
+ if (!descriptors || descriptors.length === 0) {
66
+ throw new Error('No output descriptors found in CryptoAccount');
67
+ }
68
+ let name = '';
69
+ let keyringAccount = '';
70
+ const paths = descriptors.reduce((descriptorsPaths, current) => {
71
+ const hdKey = current.getHDKey();
72
+ if (hdKey) {
73
+ const path = `M/${hdKey.getOrigin().getPath()}`;
74
+ const address = (0, utils_1.getChecksumAddress)((0, utils_1.add0x)(Buffer.from((0, util_1.publicToAddress)(hdKey.getKey(), true)).toString('hex')));
75
+ descriptorsPaths[address] = path;
76
+ name = hdKey.getName();
77
+ keyringAccount = hdKey.getNote();
78
+ }
79
+ return descriptorsPaths;
80
+ }, {});
81
+ return {
82
+ paths,
83
+ name,
84
+ keyringAccount,
85
+ xfp: getFingerprintFromSource(source),
86
+ };
87
+ }
88
+ class Device {
89
+ /**
90
+ * Create a new Device instance.
91
+ *
92
+ * @param options - The options for the Device, including the requestScan function
93
+ * and the source of the device details.
94
+ * @param options.requestScan - The function to request a scan of the QR code.
95
+ * @param options.source - The source of the device details, which can be a
96
+ * UR string, a `SerializedUR`, or a `DeviceDetails` object.
97
+ */
98
+ constructor({ requestScan, source }) {
99
+ _Device_instances.add(this);
100
+ _Device_requestScan.set(this, void 0);
101
+ _Device_pairedDevice.set(this, void 0);
102
+ __classPrivateFieldSet(this, _Device_requestScan, requestScan, "f");
103
+ __classPrivateFieldSet(this, _Device_pairedDevice, typeof source === 'string' || 'cbor' in source
104
+ ? __classPrivateFieldGet(this, _Device_instances, "m", _Device_deviceDetailsFromUR).call(this, source)
105
+ : source, "f");
106
+ }
107
+ /**
108
+ * Derive an address from the source at a given index
109
+ *
110
+ * @param index - The index to derive the address from
111
+ * @returns The derived address in hex format
112
+ * @throws Will throw an error if the source is not initialized
113
+ */
114
+ addressFromIndex(index) {
115
+ if (__classPrivateFieldGet(this, _Device_pairedDevice, "f").keyringMode === DeviceMode.ACCOUNT) {
116
+ const address = Object.keys(__classPrivateFieldGet(this, _Device_pairedDevice, "f").paths)[index];
117
+ if (!address) {
118
+ throw new Error(`Address not found for index ${index}`);
119
+ }
120
+ return (0, utils_1.add0x)(address);
121
+ }
122
+ const childPath = `m/${__classPrivateFieldGet(this, _Device_pairedDevice, "f").childrenPath.replace('*', index.toString())}`;
123
+ const hdKey = hdkey_1.default.fromExtendedKey(__classPrivateFieldGet(this, _Device_pairedDevice, "f").xpub);
124
+ const childKey = hdKey.derive(childPath);
125
+ const address = Buffer.from((0, util_1.publicToAddress)(childKey.publicKey, true)).toString('hex');
126
+ const normalizedAddress = (0, utils_1.getChecksumAddress)((0, utils_1.add0x)(address));
127
+ __classPrivateFieldGet(this, _Device_pairedDevice, "f").indexes[normalizedAddress] = index;
128
+ return normalizedAddress;
129
+ }
130
+ /**
131
+ * Retrieve the path of an address derived from the source.
132
+ *
133
+ * @param address - The address to retrieve the path for
134
+ * @returns The path of the address
135
+ */
136
+ pathFromAddress(address) {
137
+ const normalizedAddress = (0, utils_1.getChecksumAddress)((0, utils_1.add0x)(address));
138
+ if (__classPrivateFieldGet(this, _Device_pairedDevice, "f").keyringMode === DeviceMode.ACCOUNT) {
139
+ const path = __classPrivateFieldGet(this, _Device_pairedDevice, "f").paths[normalizedAddress];
140
+ if (path === undefined) {
141
+ throw new Error(`Unknown address ${normalizedAddress}`);
142
+ }
143
+ return path;
144
+ }
145
+ let index = __classPrivateFieldGet(this, _Device_pairedDevice, "f").indexes[normalizedAddress];
146
+ if (index === undefined) {
147
+ for (let i = 0; i < MAX_INDEX; i++) {
148
+ const derivedAddress = this.addressFromIndex(i);
149
+ if (derivedAddress === normalizedAddress) {
150
+ index = i;
151
+ break;
152
+ }
153
+ }
154
+ if (index === undefined) {
155
+ throw new Error(`Unknown address ${normalizedAddress}`);
156
+ }
157
+ }
158
+ return `${__classPrivateFieldGet(this, _Device_pairedDevice, "f").hdPath}/${__classPrivateFieldGet(this, _Device_pairedDevice, "f").childrenPath
159
+ .replace('*', index.toString())
160
+ .replace(/\*/gu, '0')}`;
161
+ }
162
+ /**
163
+ * Get a page of addresses derived from the source.
164
+ *
165
+ * @param page - The page number to retrieve
166
+ * @param pageSize - The number of addresses per page
167
+ * @returns An array of IndexedAddress objects, each containing the address and its index
168
+ * @throws Will throw an error if the source is not initialized
169
+ */
170
+ getAddressesPage(page, pageSize = 5) {
171
+ const startIndex = page * pageSize;
172
+ const endIndex = startIndex + pageSize;
173
+ const addresses = [];
174
+ for (let i = startIndex; i < endIndex; i++) {
175
+ const address = this.addressFromIndex(i);
176
+ addresses.push({ address, index: i });
177
+ }
178
+ return addresses;
179
+ }
180
+ /**
181
+ * Retrieve the details of the paired device.
182
+ *
183
+ * @returns Thea paired device details
184
+ */
185
+ getDeviceDetails() {
186
+ return __classPrivateFieldGet(this, _Device_pairedDevice, "f");
187
+ }
188
+ /**
189
+ * Sign a transaction. This is equivalent to the `eth_signTransaction`
190
+ * Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
191
+ * more details.
192
+ *
193
+ * @param address - The address of the account to use for signing.
194
+ * @param transaction - The transaction to sign.
195
+ * @returns The signed transaction.
196
+ */
197
+ async signTransaction(address, transaction) {
198
+ const dataType = transaction.type === tx_1.TransactionType.Legacy
199
+ ? bc_ur_registry_eth_1.DataType.transaction
200
+ : bc_ur_registry_eth_1.DataType.typedTransaction;
201
+ const messageToSign = Buffer.from(transaction.type === tx_1.TransactionType.Legacy
202
+ ? rlp_1.RLP.encode(transaction.getMessageToSign())
203
+ : transaction.getMessageToSign());
204
+ const requestId = (0, uuid_1.v4)();
205
+ const ethSignRequestUR = bc_ur_registry_eth_1.EthSignRequest.constructETHRequest(messageToSign, dataType, this.pathFromAddress(address), __classPrivateFieldGet(this, _Device_pairedDevice, "f").xfp, requestId, Number(transaction.common.chainId())).toUR();
206
+ const { r, s, v } = await __classPrivateFieldGet(this, _Device_instances, "m", _Device_requestSignature).call(this, {
207
+ requestId,
208
+ payload: {
209
+ type: ethSignRequestUR.type,
210
+ cbor: ethSignRequestUR.cbor.toString('hex'),
211
+ },
212
+ requestTitle: 'Scan with your hardware wallet',
213
+ requestDescription: 'After your device has signed this message, click on "Scan" to receive the signature',
214
+ });
215
+ return tx_1.TransactionFactory.fromTxData({
216
+ ...transaction.toJSON(),
217
+ r,
218
+ s,
219
+ v,
220
+ }, {
221
+ common: transaction.common,
222
+ });
223
+ }
224
+ /**
225
+ * Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
226
+ * JSON-RPC method.
227
+ *
228
+ * @param address - The address of the account to use for signing.
229
+ * @param data - The data to sign.
230
+ * @returns The signed message.
231
+ */
232
+ async signTypedData(address, data) {
233
+ const requestId = (0, uuid_1.v4)();
234
+ const ethSignRequestUR = bc_ur_registry_eth_1.EthSignRequest.constructETHRequest(Buffer.from(JSON.stringify(data), 'utf8'), bc_ur_registry_eth_1.DataType.typedData, this.pathFromAddress(address), __classPrivateFieldGet(this, _Device_pairedDevice, "f").xfp, requestId, undefined, address).toUR();
235
+ const { r, s, v } = await __classPrivateFieldGet(this, _Device_instances, "m", _Device_requestSignature).call(this, {
236
+ requestId,
237
+ payload: {
238
+ type: ethSignRequestUR.type,
239
+ cbor: ethSignRequestUR.cbor.toString('hex'),
240
+ },
241
+ });
242
+ return (0, utils_1.add0x)(Buffer.concat([
243
+ Uint8Array.from(r),
244
+ Uint8Array.from(s),
245
+ Uint8Array.from(v),
246
+ ]).toString('hex'));
247
+ }
248
+ /**
249
+ * Sign a message. This is equivalent to the `eth_sign` Ethereum JSON-RPC
250
+ * method, which is exposed by MetaMask as the method `personal_sign`. See
251
+ * the Ethereum JSON-RPC API documentation for more details.
252
+ *
253
+ * For more information about this method and why we call it `personal_sign`,
254
+ * see the {@link https://docs.metamask.io/guide/signing-data.html|MetaMask Docs}.
255
+ *
256
+ * @param address - The address of the account to use for signing.
257
+ * @param message - The message to sign.
258
+ * @returns The signed message.
259
+ */
260
+ async signPersonalMessage(address, message) {
261
+ const requestId = (0, uuid_1.v4)();
262
+ const ethSignRequestUR = bc_ur_registry_eth_1.EthSignRequest.constructETHRequest(Buffer.from((0, utils_1.remove0x)(message), 'hex'), bc_ur_registry_eth_1.DataType.personalMessage, this.pathFromAddress(address), __classPrivateFieldGet(this, _Device_pairedDevice, "f").xfp, requestId, undefined, address).toUR();
263
+ const { r, s, v } = await __classPrivateFieldGet(this, _Device_instances, "m", _Device_requestSignature).call(this, {
264
+ requestId,
265
+ payload: {
266
+ type: ethSignRequestUR.type,
267
+ cbor: ethSignRequestUR.cbor.toString('hex'),
268
+ },
269
+ });
270
+ return (0, utils_1.add0x)(Buffer.concat([
271
+ Uint8Array.from(r),
272
+ Uint8Array.from(s),
273
+ Uint8Array.from(v),
274
+ ]).toString('hex'));
275
+ }
276
+ }
277
+ exports.Device = Device;
278
+ _Device_requestScan = new WeakMap(), _Device_pairedDevice = new WeakMap(), _Device_instances = new WeakSet(), _Device_deviceDetailsFromUR = function _Device_deviceDetailsFromUR(ur) {
279
+ const source = __classPrivateFieldGet(this, _Device_instances, "m", _Device_decodeUR).call(this, ur);
280
+ const fingerprint = getFingerprintFromSource(source);
281
+ if (source instanceof bc_ur_registry_eth_1.CryptoAccount) {
282
+ const { name, xfp, paths, keyringAccount } = readCryptoAccountOutputDescriptors(source);
283
+ return {
284
+ keyringMode: DeviceMode.ACCOUNT,
285
+ keyringAccount,
286
+ name,
287
+ xfp,
288
+ paths,
289
+ indexes: {},
290
+ };
291
+ }
292
+ const { getBip32Key, getOrigin, getChildren, getName, getNote } = source;
293
+ return {
294
+ keyringMode: DeviceMode.HD,
295
+ keyringAccount: getNote(),
296
+ name: getName(),
297
+ xfp: fingerprint,
298
+ hdPath: `m/${getOrigin().getPath()}`,
299
+ childrenPath: getChildren()?.getPath() || DEFAULT_CHILDREN_PATH,
300
+ xpub: getBip32Key(),
301
+ indexes: {},
302
+ };
303
+ }, _Device_requestSignature =
304
+ /**
305
+ * Request a signature for a transaction or message.
306
+ *
307
+ * @param request - The signature request containing the data to sign.
308
+ * @returns The signature as an object containing r, s, and v values.
309
+ */
310
+ async function _Device_requestSignature(request) {
311
+ const response = await __classPrivateFieldGet(this, _Device_requestScan, "f").call(this, {
312
+ type: qr_keyring_1.QrScanRequestType.SIGN,
313
+ request,
314
+ });
315
+ const signatureEnvelope = bc_ur_registry_eth_1.ETHSignature.fromCBOR(Buffer.from(response.cbor, 'hex'));
316
+ const signature = signatureEnvelope.getSignature();
317
+ const requestId = signatureEnvelope.getRequestId();
318
+ if (!requestId) {
319
+ throw new Error('Signature request ID is missing.');
320
+ }
321
+ if (request.requestId !== (0, uuid_1.stringify)(requestId)) {
322
+ throw new Error(`Signature request ID mismatch. Expected: ${request.requestId}, received: ${requestId.toString('hex')}`);
323
+ }
324
+ return {
325
+ r: signature.subarray(0, 32),
326
+ s: signature.subarray(32, 64),
327
+ v: signature.subarray(64),
328
+ };
329
+ }, _Device_decodeUR = function _Device_decodeUR(ur) {
330
+ const decodedUR = typeof ur === 'string'
331
+ ? bc_ur_registry_eth_1.URRegistryDecoder.decode(ur)
332
+ : {
333
+ type: ur.type,
334
+ cbor: Buffer.from(ur.cbor, 'hex'),
335
+ };
336
+ const { type, cbor } = decodedUR;
337
+ switch (type) {
338
+ case exports.SUPPORTED_UR_TYPE.CRYPTO_HDKEY:
339
+ return bc_ur_registry_eth_1.CryptoHDKey.fromCBOR(cbor);
340
+ case exports.SUPPORTED_UR_TYPE.CRYPTO_ACCOUNT:
341
+ return bc_ur_registry_eth_1.CryptoAccount.fromCBOR(cbor);
342
+ default:
343
+ throw new Error('Unsupported UR type');
344
+ }
345
+ };
346
+ //# sourceMappingURL=device.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.cjs","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"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","sourcesContent":["import { RLP } from '@ethereumjs/rlp';\nimport {\n type TypedTransaction,\n TransactionType,\n type TypedTxData,\n type FeeMarketEIP1559Transaction,\n TransactionFactory,\n} from '@ethereumjs/tx';\nimport { publicToAddress } from '@ethereumjs/util';\nimport {\n CryptoAccount,\n CryptoHDKey,\n DataType,\n ETHSignature,\n EthSignRequest,\n URRegistryDecoder,\n} from '@keystonehq/bc-ur-registry-eth';\nimport type { MessageTypes, TypedMessage } from '@metamask/eth-sig-util';\nimport { add0x, getChecksumAddress, remove0x, type Hex } from '@metamask/utils';\n// eslint-disable-next-line @typescript-eslint/naming-convention\nimport HdKey from 'hdkey';\nimport { stringify, v4 as uuidv4 } from 'uuid';\n\nimport {\n QrScanRequestType,\n type QrKeyringBridge,\n type QrSignatureRequest,\n type SerializedUR,\n} from './qr-keyring';\n\nexport const SUPPORTED_UR_TYPE = {\n CRYPTO_HDKEY: 'crypto-hdkey',\n CRYPTO_ACCOUNT: 'crypto-account',\n ETH_SIGNATURE: 'eth-signature',\n};\n\nexport enum DeviceMode {\n HD = 'hd',\n ACCOUNT = 'account',\n}\n\nconst DEFAULT_CHILDREN_PATH = '0/*';\n\nconst MAX_INDEX = 1_000;\n\n/**\n * Common details for the device source, which can be either a CryptoAccount or CryptoHDKey.\n */\nexport type CommonDeviceDetails = {\n /**\n * Value take out from the device note field, if available.\n */\n keyringAccount: string;\n /**\n * The name of the device\n */\n name: string;\n /**\n * The device fingerprint, hex-encoded\n */\n xfp: string;\n /**\n * Indexes of the accounts derived from the device\n * in the form of a map from address to index\n */\n indexes: Record<Hex, number>;\n};\n\n/**\n * Details for the HD mode of the Device. This mode derives\n * accounts from a root public key (xpub) and a derivation path.\n */\nexport type HDModeDeviceDetails = {\n /**\n * The device mode is HD, indicating that it derives accounts from a\n * root public key (xpub) and a derivation path.\n */\n keyringMode: DeviceMode.HD;\n /**\n * The xpub of the HD key\n */\n xpub: string;\n /**\n * The derivation path of the HD key\n */\n hdPath: string;\n /**\n * The path used to derive child accounts\n */\n childrenPath: string;\n};\n\n/**\n * Details for the Account mode of the Device. This mode derives\n * accounts from a set of addresses and their corresponding paths.\n */\nexport type AccountModeDeviceDetails = {\n /**\n * The device mode is ACCOUNT, indicating that it derives accounts from\n * a set of addresses and their corresponding paths.\n */\n keyringMode: DeviceMode.ACCOUNT;\n /**\n * The derivation paths for each hex-encoded address in the device\n */\n paths: Record<Hex, string>;\n};\n\n/**\n * An address with its corresponding index.\n */\nexport type IndexedAddress = {\n address: Hex;\n index: number;\n};\n\n/**\n * The details of the source CryptoAccount or CryptoHDKey\n * that the Device uses to derive accounts.\n */\nexport type DeviceDetails = CommonDeviceDetails &\n (HDModeDeviceDetails | AccountModeDeviceDetails);\n\nexport type DeviceOptions = {\n /**\n * The requestScan function to scan the QR code\n */\n requestScan: QrKeyringBridge['requestScan'];\n /**\n * The source of the device, which can be of type `DeviceDetails`,\n * `string`, or `SerializedUR`.\n *\n * When a `string` or `SerializedUR` is provided, the Device will\n * initialize itself from the UR.\n */\n source: DeviceDetails | string | SerializedUR;\n};\n\n/**\n * Get the fingerprint of the source CryptoAccount or CryptoHDKey\n *\n * @param source - The source CryptoAccount or CryptoHDKey\n * @returns The fingerprint of the source\n */\nfunction getFingerprintFromSource(source: CryptoAccount | CryptoHDKey): string {\n return source instanceof CryptoAccount\n ? source.getMasterFingerprint()?.toString('hex')\n : source.getParentFingerprint()?.toString('hex');\n}\n\n/**\n * Get fingerprint, account paths and names from the a CryptoAccount\n *\n * Note: This function emulates the behavior of the `@keystonehq/base-eth-keyring`\n * library when dealing with CryptoAccount objects (for backwards compatibility\n * reasons). Though, the way it retrieves `name` and `keyringAccount` is questionable,\n * as `name` and `keyringAccount` are updated after each descriptor discovery, effectively\n * returning the last descriptor's `name` and `keyringAccount`.\n *\n * @param source - The source CryptoAccount\n * @returns The paths\n */\nfunction readCryptoAccountOutputDescriptors(source: CryptoAccount): {\n xfp: string;\n paths: Record<Hex, string>;\n name: string;\n keyringAccount: string;\n} {\n const descriptors = source.getOutputDescriptors();\n\n if (!descriptors || descriptors.length === 0) {\n throw new Error('No output descriptors found in CryptoAccount');\n }\n\n let name = '';\n let keyringAccount = '';\n const paths = descriptors.reduce(\n (descriptorsPaths: Record<Hex, string>, current) => {\n const hdKey = current.getHDKey();\n if (hdKey) {\n const path = `M/${hdKey.getOrigin().getPath()}`;\n const address = getChecksumAddress(\n add0x(\n Buffer.from(publicToAddress(hdKey.getKey(), true)).toString('hex'),\n ),\n );\n descriptorsPaths[address] = path;\n name = hdKey.getName();\n keyringAccount = hdKey.getNote();\n }\n return descriptorsPaths;\n },\n {},\n );\n\n return {\n paths,\n name,\n keyringAccount,\n xfp: getFingerprintFromSource(source),\n };\n}\n\nexport class Device {\n readonly #requestScan: QrKeyringBridge['requestScan'];\n\n readonly #pairedDevice: DeviceDetails;\n\n /**\n * Create a new Device instance.\n *\n * @param options - The options for the Device, including the requestScan function\n * and the source of the device details.\n * @param options.requestScan - The function to request a scan of the QR code.\n * @param options.source - The source of the device details, which can be a\n * UR string, a `SerializedUR`, or a `DeviceDetails` object.\n */\n constructor({ requestScan, source }: DeviceOptions) {\n this.#requestScan = requestScan;\n this.#pairedDevice =\n typeof source === 'string' || 'cbor' in source\n ? this.#deviceDetailsFromUR(source)\n : source;\n }\n\n /**\n * Derive an address from the source at a given index\n *\n * @param index - The index to derive the address from\n * @returns The derived address in hex format\n * @throws Will throw an error if the source is not initialized\n */\n addressFromIndex(index: number): Hex {\n if (this.#pairedDevice.keyringMode === DeviceMode.ACCOUNT) {\n const address = Object.keys(this.#pairedDevice.paths)[index];\n if (!address) {\n throw new Error(`Address not found for index ${index}`);\n }\n return add0x(address);\n }\n const childPath = `m/${this.#pairedDevice.childrenPath.replace(\n '*',\n index.toString(),\n )}`;\n\n const hdKey = HdKey.fromExtendedKey(this.#pairedDevice.xpub);\n const childKey = hdKey.derive(childPath);\n\n const address = Buffer.from(\n publicToAddress(childKey.publicKey, true),\n ).toString('hex');\n\n const normalizedAddress = getChecksumAddress(add0x(address));\n\n this.#pairedDevice.indexes[normalizedAddress] = index;\n\n return normalizedAddress;\n }\n\n /**\n * Retrieve the path of an address derived from the source.\n *\n * @param address - The address to retrieve the path for\n * @returns The path of the address\n */\n pathFromAddress(address: Hex): string {\n const normalizedAddress = getChecksumAddress(add0x(address));\n\n if (this.#pairedDevice.keyringMode === DeviceMode.ACCOUNT) {\n const path = this.#pairedDevice.paths[normalizedAddress];\n if (path === undefined) {\n throw new Error(`Unknown address ${normalizedAddress}`);\n }\n return path;\n }\n\n let index = this.#pairedDevice.indexes[normalizedAddress];\n if (index === undefined) {\n for (let i = 0; i < MAX_INDEX; i++) {\n const derivedAddress = this.addressFromIndex(i);\n if (derivedAddress === normalizedAddress) {\n index = i;\n break;\n }\n }\n if (index === undefined) {\n throw new Error(`Unknown address ${normalizedAddress}`);\n }\n }\n\n return `${this.#pairedDevice.hdPath}/${this.#pairedDevice.childrenPath\n .replace('*', index.toString())\n .replace(/\\*/gu, '0')}`;\n }\n\n /**\n * Get a page of addresses derived from the source.\n *\n * @param page - The page number to retrieve\n * @param pageSize - The number of addresses per page\n * @returns An array of IndexedAddress objects, each containing the address and its index\n * @throws Will throw an error if the source is not initialized\n */\n getAddressesPage(page: number, pageSize = 5): IndexedAddress[] {\n const startIndex = page * pageSize;\n const endIndex = startIndex + pageSize;\n const addresses: IndexedAddress[] = [];\n\n for (let i = startIndex; i < endIndex; i++) {\n const address = this.addressFromIndex(i);\n addresses.push({ address, index: i });\n }\n\n return addresses;\n }\n\n /**\n * Retrieve the details of the paired device.\n *\n * @returns Thea paired device details\n */\n getDeviceDetails(): DeviceDetails {\n return this.#pairedDevice;\n }\n\n /**\n * Sign a transaction. This is equivalent to the `eth_signTransaction`\n * Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for\n * more details.\n *\n * @param address - The address of the account to use for signing.\n * @param transaction - The transaction to sign.\n * @returns The signed transaction.\n */\n async signTransaction(\n address: Hex,\n transaction: TypedTransaction,\n ): Promise<TypedTxData> {\n const dataType =\n transaction.type === TransactionType.Legacy\n ? DataType.transaction\n : DataType.typedTransaction;\n\n const messageToSign = Buffer.from(\n transaction.type === TransactionType.Legacy\n ? RLP.encode(transaction.getMessageToSign())\n : (transaction as FeeMarketEIP1559Transaction).getMessageToSign(),\n );\n\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n messageToSign,\n dataType,\n this.pathFromAddress(address),\n this.#pairedDevice.xfp,\n requestId,\n Number(transaction.common.chainId()),\n ).toUR();\n\n const { r, s, v } = await this.#requestSignature({\n requestId,\n payload: {\n type: ethSignRequestUR.type,\n cbor: ethSignRequestUR.cbor.toString('hex'),\n },\n requestTitle: 'Scan with your hardware wallet',\n requestDescription:\n 'After your device has signed this message, click on \"Scan\" to receive the signature',\n });\n\n return TransactionFactory.fromTxData(\n {\n ...transaction.toJSON(),\n r,\n s,\n v,\n },\n {\n common: transaction.common,\n },\n );\n }\n\n /**\n * Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum\n * JSON-RPC method.\n *\n * @param address - The address of the account to use for signing.\n * @param data - The data to sign.\n * @returns The signed message.\n */\n async signTypedData<Types extends MessageTypes>(\n address: Hex,\n data: TypedMessage<Types>,\n ): Promise<string> {\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n Buffer.from(JSON.stringify(data), 'utf8'),\n DataType.typedData,\n this.pathFromAddress(address),\n this.#pairedDevice.xfp,\n requestId,\n undefined,\n address,\n ).toUR();\n\n const { r, s, v } = await this.#requestSignature({\n requestId,\n payload: {\n type: ethSignRequestUR.type,\n cbor: ethSignRequestUR.cbor.toString('hex'),\n },\n });\n\n return add0x(\n Buffer.concat([\n Uint8Array.from(r),\n Uint8Array.from(s),\n Uint8Array.from(v),\n ]).toString('hex'),\n );\n }\n\n /**\n * Sign a message. This is equivalent to the `eth_sign` Ethereum JSON-RPC\n * method, which is exposed by MetaMask as the method `personal_sign`. See\n * the Ethereum JSON-RPC API documentation for more details.\n *\n * For more information about this method and why we call it `personal_sign`,\n * see the {@link https://docs.metamask.io/guide/signing-data.html|MetaMask Docs}.\n *\n * @param address - The address of the account to use for signing.\n * @param message - The message to sign.\n * @returns The signed message.\n */\n async signPersonalMessage(address: Hex, message: Hex): Promise<string> {\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n Buffer.from(remove0x(message), 'hex'),\n DataType.personalMessage,\n this.pathFromAddress(address),\n this.#pairedDevice.xfp,\n requestId,\n undefined,\n address,\n ).toUR();\n\n const { r, s, v } = await this.#requestSignature({\n requestId,\n payload: {\n type: ethSignRequestUR.type,\n cbor: ethSignRequestUR.cbor.toString('hex'),\n },\n });\n\n return add0x(\n Buffer.concat([\n Uint8Array.from(r),\n Uint8Array.from(s),\n Uint8Array.from(v),\n ]).toString('hex'),\n );\n }\n\n /**\n * Derive the device details from a UR string\n *\n * @param ur - The UR string to set the root account from\n * @returns The device details derived from the UR\n */\n #deviceDetailsFromUR(ur: string | SerializedUR): DeviceDetails {\n const source = this.#decodeUR(ur);\n const fingerprint = getFingerprintFromSource(source);\n\n if (source instanceof CryptoAccount) {\n const { name, xfp, paths, keyringAccount } =\n readCryptoAccountOutputDescriptors(source);\n return {\n keyringMode: DeviceMode.ACCOUNT,\n keyringAccount,\n name,\n xfp,\n paths,\n indexes: {},\n };\n }\n\n const { getBip32Key, getOrigin, getChildren, getName, getNote } = source;\n return {\n keyringMode: DeviceMode.HD,\n keyringAccount: getNote(),\n name: getName(),\n xfp: fingerprint,\n hdPath: `m/${getOrigin().getPath()}`,\n childrenPath: getChildren()?.getPath() || DEFAULT_CHILDREN_PATH,\n xpub: getBip32Key(),\n indexes: {},\n };\n }\n\n /**\n * Request a signature for a transaction or message.\n *\n * @param request - The signature request containing the data to sign.\n * @returns The signature as an object containing r, s, and v values.\n */\n async #requestSignature(\n request: QrSignatureRequest,\n ): Promise<{ r: Buffer; s: Buffer; v: Buffer }> {\n const response = await this.#requestScan({\n type: QrScanRequestType.SIGN,\n request,\n });\n const signatureEnvelope = ETHSignature.fromCBOR(\n Buffer.from(response.cbor, 'hex'),\n );\n const signature = signatureEnvelope.getSignature();\n const requestId = signatureEnvelope.getRequestId();\n\n if (!requestId) {\n throw new Error('Signature request ID is missing.');\n }\n\n if (request.requestId !== stringify(requestId)) {\n throw new Error(\n `Signature request ID mismatch. Expected: ${\n request.requestId\n }, received: ${requestId.toString('hex')}`,\n );\n }\n\n return {\n r: signature.subarray(0, 32),\n s: signature.subarray(32, 64),\n v: signature.subarray(64),\n };\n }\n\n /**\n * Decodes a UR\n *\n * @param ur - The UR to decode\n * @returns The decoded CryptoAccount or CryptoHDKey\n * @throws Will throw an error if the UR type is not supported\n */\n #decodeUR(ur: string | SerializedUR): CryptoAccount | CryptoHDKey {\n const decodedUR =\n typeof ur === 'string'\n ? URRegistryDecoder.decode(ur)\n : {\n type: ur.type,\n cbor: Buffer.from(ur.cbor, 'hex'),\n };\n\n const { type, cbor } = decodedUR;\n\n switch (type) {\n case SUPPORTED_UR_TYPE.CRYPTO_HDKEY:\n return CryptoHDKey.fromCBOR(cbor);\n case SUPPORTED_UR_TYPE.CRYPTO_ACCOUNT:\n return CryptoAccount.fromCBOR(cbor);\n default:\n throw new Error('Unsupported UR type');\n }\n }\n}\n"]}
@@ -0,0 +1,175 @@
1
+ import { type TypedTransaction, type TypedTxData } from "@ethereumjs/tx";
2
+ import type { MessageTypes, TypedMessage } from "@metamask/eth-sig-util";
3
+ import { type Hex } from "@metamask/utils";
4
+ import { type QrKeyringBridge, type SerializedUR } from "./qr-keyring.cjs";
5
+ export declare const SUPPORTED_UR_TYPE: {
6
+ CRYPTO_HDKEY: string;
7
+ CRYPTO_ACCOUNT: string;
8
+ ETH_SIGNATURE: string;
9
+ };
10
+ export declare enum DeviceMode {
11
+ HD = "hd",
12
+ ACCOUNT = "account"
13
+ }
14
+ /**
15
+ * Common details for the device source, which can be either a CryptoAccount or CryptoHDKey.
16
+ */
17
+ export type CommonDeviceDetails = {
18
+ /**
19
+ * Value take out from the device note field, if available.
20
+ */
21
+ keyringAccount: string;
22
+ /**
23
+ * The name of the device
24
+ */
25
+ name: string;
26
+ /**
27
+ * The device fingerprint, hex-encoded
28
+ */
29
+ xfp: string;
30
+ /**
31
+ * Indexes of the accounts derived from the device
32
+ * in the form of a map from address to index
33
+ */
34
+ indexes: Record<Hex, number>;
35
+ };
36
+ /**
37
+ * Details for the HD mode of the Device. This mode derives
38
+ * accounts from a root public key (xpub) and a derivation path.
39
+ */
40
+ export type HDModeDeviceDetails = {
41
+ /**
42
+ * The device mode is HD, indicating that it derives accounts from a
43
+ * root public key (xpub) and a derivation path.
44
+ */
45
+ keyringMode: DeviceMode.HD;
46
+ /**
47
+ * The xpub of the HD key
48
+ */
49
+ xpub: string;
50
+ /**
51
+ * The derivation path of the HD key
52
+ */
53
+ hdPath: string;
54
+ /**
55
+ * The path used to derive child accounts
56
+ */
57
+ childrenPath: string;
58
+ };
59
+ /**
60
+ * Details for the Account mode of the Device. This mode derives
61
+ * accounts from a set of addresses and their corresponding paths.
62
+ */
63
+ export type AccountModeDeviceDetails = {
64
+ /**
65
+ * The device mode is ACCOUNT, indicating that it derives accounts from
66
+ * a set of addresses and their corresponding paths.
67
+ */
68
+ keyringMode: DeviceMode.ACCOUNT;
69
+ /**
70
+ * The derivation paths for each hex-encoded address in the device
71
+ */
72
+ paths: Record<Hex, string>;
73
+ };
74
+ /**
75
+ * An address with its corresponding index.
76
+ */
77
+ export type IndexedAddress = {
78
+ address: Hex;
79
+ index: number;
80
+ };
81
+ /**
82
+ * The details of the source CryptoAccount or CryptoHDKey
83
+ * that the Device uses to derive accounts.
84
+ */
85
+ export type DeviceDetails = CommonDeviceDetails & (HDModeDeviceDetails | AccountModeDeviceDetails);
86
+ export type DeviceOptions = {
87
+ /**
88
+ * The requestScan function to scan the QR code
89
+ */
90
+ requestScan: QrKeyringBridge['requestScan'];
91
+ /**
92
+ * The source of the device, which can be of type `DeviceDetails`,
93
+ * `string`, or `SerializedUR`.
94
+ *
95
+ * When a `string` or `SerializedUR` is provided, the Device will
96
+ * initialize itself from the UR.
97
+ */
98
+ source: DeviceDetails | string | SerializedUR;
99
+ };
100
+ export declare class Device {
101
+ #private;
102
+ /**
103
+ * Create a new Device instance.
104
+ *
105
+ * @param options - The options for the Device, including the requestScan function
106
+ * and the source of the device details.
107
+ * @param options.requestScan - The function to request a scan of the QR code.
108
+ * @param options.source - The source of the device details, which can be a
109
+ * UR string, a `SerializedUR`, or a `DeviceDetails` object.
110
+ */
111
+ constructor({ requestScan, source }: DeviceOptions);
112
+ /**
113
+ * Derive an address from the source at a given index
114
+ *
115
+ * @param index - The index to derive the address from
116
+ * @returns The derived address in hex format
117
+ * @throws Will throw an error if the source is not initialized
118
+ */
119
+ addressFromIndex(index: number): Hex;
120
+ /**
121
+ * Retrieve the path of an address derived from the source.
122
+ *
123
+ * @param address - The address to retrieve the path for
124
+ * @returns The path of the address
125
+ */
126
+ pathFromAddress(address: Hex): string;
127
+ /**
128
+ * Get a page of addresses derived from the source.
129
+ *
130
+ * @param page - The page number to retrieve
131
+ * @param pageSize - The number of addresses per page
132
+ * @returns An array of IndexedAddress objects, each containing the address and its index
133
+ * @throws Will throw an error if the source is not initialized
134
+ */
135
+ getAddressesPage(page: number, pageSize?: number): IndexedAddress[];
136
+ /**
137
+ * Retrieve the details of the paired device.
138
+ *
139
+ * @returns Thea paired device details
140
+ */
141
+ getDeviceDetails(): DeviceDetails;
142
+ /**
143
+ * Sign a transaction. This is equivalent to the `eth_signTransaction`
144
+ * Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
145
+ * more details.
146
+ *
147
+ * @param address - The address of the account to use for signing.
148
+ * @param transaction - The transaction to sign.
149
+ * @returns The signed transaction.
150
+ */
151
+ signTransaction(address: Hex, transaction: TypedTransaction): Promise<TypedTxData>;
152
+ /**
153
+ * Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
154
+ * JSON-RPC method.
155
+ *
156
+ * @param address - The address of the account to use for signing.
157
+ * @param data - The data to sign.
158
+ * @returns The signed message.
159
+ */
160
+ signTypedData<Types extends MessageTypes>(address: Hex, data: TypedMessage<Types>): Promise<string>;
161
+ /**
162
+ * Sign a message. This is equivalent to the `eth_sign` Ethereum JSON-RPC
163
+ * method, which is exposed by MetaMask as the method `personal_sign`. See
164
+ * the Ethereum JSON-RPC API documentation for more details.
165
+ *
166
+ * For more information about this method and why we call it `personal_sign`,
167
+ * see the {@link https://docs.metamask.io/guide/signing-data.html|MetaMask Docs}.
168
+ *
169
+ * @param address - The address of the account to use for signing.
170
+ * @param message - The message to sign.
171
+ * @returns The signed message.
172
+ */
173
+ signPersonalMessage(address: Hex, message: Hex): Promise<string>;
174
+ }
175
+ //# sourceMappingURL=device.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.d.cts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,WAAW,EAGjB,uBAAuB;AAUxB,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,+BAA+B;AACzE,OAAO,EAAuC,KAAK,GAAG,EAAE,wBAAwB;AAKhF,OAAO,EAEL,KAAK,eAAe,EAEpB,KAAK,YAAY,EAClB,yBAAqB;AAEtB,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAC;AAEF,oBAAY,UAAU;IACpB,EAAE,OAAO;IACT,OAAO,YAAY;CACpB;AAMD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;CAC9B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,WAAW,EAAE,UAAU,CAAC,EAAE,CAAC;IAC3B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;OAGG;IACH,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;CAC5B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,GAAG,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAC7C,CAAC,mBAAmB,GAAG,wBAAwB,CAAC,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG;IAC1B;;OAEG;IACH,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,YAAY,CAAC;CAC/C,CAAC;AAmEF,qBAAa,MAAM;;IAKjB;;;;;;;;OAQG;gBACS,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,aAAa;IAQlD;;;;;;OAMG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG;IA2BpC;;;;;OAKG;IACH,eAAe,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM;IA8BrC;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,cAAc,EAAE;IAa9D;;;;OAIG;IACH,gBAAgB,IAAI,aAAa;IAIjC;;;;;;;;OAQG;IACG,eAAe,CACnB,OAAO,EAAE,GAAG,EACZ,WAAW,EAAE,gBAAgB,GAC5B,OAAO,CAAC,WAAW,CAAC;IA8CvB;;;;;;;OAOG;IACG,aAAa,CAAC,KAAK,SAAS,YAAY,EAC5C,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,YAAY,CAAC,KAAK,CAAC,GACxB,OAAO,CAAC,MAAM,CAAC;IA6BlB;;;;;;;;;;;OAWG;IACG,mBAAmB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;CAkIvE"}