@metamask-previews/eth-qr-keyring 1.0.0-ae6ca94 → 1.0.0-e4f6caa
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 +9 -0
- package/dist/airgapped-signer.cjs +23 -0
- package/dist/airgapped-signer.cjs.map +1 -1
- package/dist/airgapped-signer.d.cts +9 -2
- package/dist/airgapped-signer.d.cts.map +1 -1
- package/dist/airgapped-signer.d.mts +9 -2
- package/dist/airgapped-signer.d.mts.map +1 -1
- package/dist/airgapped-signer.mjs +23 -0
- package/dist/airgapped-signer.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{qr-keyring-bridge.cjs → qr-keyring-scanner-bridge.cjs} +6 -6
- package/dist/qr-keyring-scanner-bridge.cjs.map +1 -0
- package/dist/{qr-keyring-bridge.d.mts → qr-keyring-scanner-bridge.d.cts} +7 -7
- package/dist/qr-keyring-scanner-bridge.d.cts.map +1 -0
- package/dist/{qr-keyring-bridge.d.cts → qr-keyring-scanner-bridge.d.mts} +7 -7
- package/dist/qr-keyring-scanner-bridge.d.mts.map +1 -0
- package/dist/{qr-keyring-bridge.mjs → qr-keyring-scanner-bridge.mjs} +6 -6
- package/dist/qr-keyring-scanner-bridge.mjs.map +1 -0
- package/dist/qr-keyring.cjs +111 -2
- package/dist/qr-keyring.cjs.map +1 -1
- package/dist/qr-keyring.d.cts +36 -5
- package/dist/qr-keyring.d.cts.map +1 -1
- package/dist/qr-keyring.d.mts +36 -5
- package/dist/qr-keyring.d.mts.map +1 -1
- package/dist/qr-keyring.mjs +111 -2
- package/dist/qr-keyring.mjs.map +1 -1
- package/package.json +6 -2
- package/dist/qr-keyring-bridge.cjs.map +0 -1
- package/dist/qr-keyring-bridge.d.cts.map +0 -1
- package/dist/qr-keyring-bridge.d.mts.map +0 -1
- package/dist/qr-keyring-bridge.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
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/
|
|
@@ -133,6 +133,29 @@ class AirgappedSigner {
|
|
|
133
133
|
__classPrivateFieldGet(this, _AirgappedSigner_source, "f").indexes[normalizedAddress] = index;
|
|
134
134
|
return normalizedAddress;
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Retrieve the path of an address derived from the source.
|
|
138
|
+
*
|
|
139
|
+
* @param address - The address to retrieve the path for
|
|
140
|
+
* @returns The path of the address
|
|
141
|
+
*/
|
|
142
|
+
pathFromAddress(address) {
|
|
143
|
+
if (!__classPrivateFieldGet(this, _AirgappedSigner_source, "f")) {
|
|
144
|
+
throw new Error('UR not initialized');
|
|
145
|
+
}
|
|
146
|
+
const normalizedAddress = (0, utils_1.getChecksumAddress)((0, utils_1.add0x)(address));
|
|
147
|
+
if (__classPrivateFieldGet(this, _AirgappedSigner_source, "f").keyringMode === KeyringMode.ACCOUNT) {
|
|
148
|
+
const path = __classPrivateFieldGet(this, _AirgappedSigner_source, "f").paths[normalizedAddress];
|
|
149
|
+
if (path === undefined) {
|
|
150
|
+
throw new Error(`Unknown address ${normalizedAddress}`);
|
|
151
|
+
}
|
|
152
|
+
return path;
|
|
153
|
+
}
|
|
154
|
+
const index = this.indexFromAddress(normalizedAddress);
|
|
155
|
+
return `${__classPrivateFieldGet(this, _AirgappedSigner_source, "f").hdPath}/${__classPrivateFieldGet(this, _AirgappedSigner_source, "f").childrenPath
|
|
156
|
+
.replace('*', index.toString())
|
|
157
|
+
.replace(/\*/gu, '0')}`;
|
|
158
|
+
}
|
|
136
159
|
/**
|
|
137
160
|
* Retrieve the index of an address from the source
|
|
138
161
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airgapped-signer.cjs","sourceRoot":"","sources":["../src/airgapped-signer.ts"],"names":[],"mappings":"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","sourcesContent":["import { publicToAddress } from '@ethereumjs/util';\nimport {\n CryptoAccount,\n CryptoHDKey,\n URRegistryDecoder,\n} from '@keystonehq/bc-ur-registry-eth';\nimport { add0x, getChecksumAddress, type Hex } from '@metamask/utils';\n// eslint-disable-next-line @typescript-eslint/naming-convention\nimport HdKey from 'hdkey';\n\nimport type { QrScanResponse } 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 KeyringMode {\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 signer source, which can be either a CryptoAccount or CryptoHDKey.\n */\nexport type CommonSignerDetails = {\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 AirgappedSigner. This mode derives\n * accounts from a root public key (xpub) and a derivation path.\n */\nexport type HDModeSignerDetails = {\n /**\n * The keyring mode is HD, indicating that it derives accounts from a\n * root public key (xpub) and a derivation path.\n */\n keyringMode: KeyringMode.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 AirgappedSigner. This mode derives\n * accounts from a set of addresses and their corresponding paths.\n */\nexport type AccountModeSignerDetails = {\n /**\n * The keyring mode is ACCOUNT, indicating that it derives accounts from\n * a set of addresses and their corresponding paths.\n */\n keyringMode: KeyringMode.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 AirgappedSigner uses to derive accounts.\n */\nexport type AirgappedSignerDetails = CommonSignerDetails &\n (HDModeSignerDetails | AccountModeSignerDetails);\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 AirgappedSigner {\n #source?: AirgappedSignerDetails | undefined;\n\n /**\n * Initialize the AirgappedSigner with a source.\n *\n * @param source - The signer source, in the form of details object, or a UR string\n */\n init(source: AirgappedSignerDetails | string | QrScanResponse): void {\n if (typeof source === 'string' || 'cbor' in source) {\n this.#initFromUR(source);\n } else {\n this.#source = source;\n }\n }\n\n /**\n * Check if the AirgappedSigner is initialized\n *\n * @returns True if the AirgappedSigner is initialized, false otherwise\n */\n isInitialized(): boolean {\n return this.#source !== undefined;\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.#source) {\n throw new Error('UR not initialized');\n }\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const address = Object.keys(this.#source.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.#source.childrenPath.replace(\n '*',\n index.toString(),\n )}`;\n\n const hdKey = HdKey.fromExtendedKey(this.#source.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.#source.indexes[normalizedAddress] = index;\n\n return normalizedAddress;\n }\n\n /**\n * Retrieve the index of an address from the source\n *\n * @param address - The normalized address to retrieve the index for\n * @returns The index of the address\n */\n indexFromAddress(address: Hex): number {\n if (!this.#source) {\n throw new Error('UR not initialized');\n }\n\n const cachedIndex = this.#source.indexes[address];\n if (cachedIndex !== undefined) {\n return Number(cachedIndex);\n }\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const path = this.#source.paths[address];\n if (path === undefined) {\n throw new Error(`Unknown address`);\n }\n\n const index = path.split('/').pop();\n if (index === undefined) {\n throw new Error(`Invalid path for address ${address}`);\n }\n\n return Number(index);\n }\n\n for (let i = 0; i < MAX_INDEX; i++) {\n const derivedAddress = this.addressFromIndex(i);\n if (derivedAddress === address) {\n return i;\n }\n }\n\n throw new Error(`Address ${address} not found`);\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 * Gets the source details of the AirgappedSigner.\n *\n * @returns The source details, or undefined if not initialized\n */\n getSourceDetails(): AirgappedSignerDetails | undefined {\n return this.#source;\n }\n\n /**\n * Clear the source details.\n */\n clear(): void {\n this.#source = undefined;\n }\n\n /**\n * Set the root account from a UR string\n *\n * @param ur - The UR string to set the root account from\n */\n #initFromUR(ur: string | QrScanResponse): void {\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 this.#source = {\n keyringMode: KeyringMode.ACCOUNT,\n keyringAccount,\n name,\n xfp,\n paths,\n indexes: {},\n };\n } else {\n const { getBip32Key, getOrigin, getChildren, getName, getNote } = source;\n this.#source = {\n keyringMode: KeyringMode.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 /**\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 | QrScanResponse): 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"]}
|
|
1
|
+
{"version":3,"file":"airgapped-signer.cjs","sourceRoot":"","sources":["../src/airgapped-signer.ts"],"names":[],"mappings":"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","sourcesContent":["import { publicToAddress } from '@ethereumjs/util';\nimport {\n CryptoAccount,\n CryptoHDKey,\n URRegistryDecoder,\n} from '@keystonehq/bc-ur-registry-eth';\nimport { add0x, getChecksumAddress, type Hex } from '@metamask/utils';\n// eslint-disable-next-line @typescript-eslint/naming-convention\nimport HdKey from 'hdkey';\n\nimport type { SerializedUR } 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 KeyringMode {\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 signer source, which can be either a CryptoAccount or CryptoHDKey.\n */\nexport type CommonSignerDetails = {\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 AirgappedSigner. This mode derives\n * accounts from a root public key (xpub) and a derivation path.\n */\nexport type HDModeSignerDetails = {\n /**\n * The keyring mode is HD, indicating that it derives accounts from a\n * root public key (xpub) and a derivation path.\n */\n keyringMode: KeyringMode.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 AirgappedSigner. This mode derives\n * accounts from a set of addresses and their corresponding paths.\n */\nexport type AccountModeSignerDetails = {\n /**\n * The keyring mode is ACCOUNT, indicating that it derives accounts from\n * a set of addresses and their corresponding paths.\n */\n keyringMode: KeyringMode.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 AirgappedSigner uses to derive accounts.\n */\nexport type AirgappedSignerDetails = CommonSignerDetails &\n (HDModeSignerDetails | AccountModeSignerDetails);\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 AirgappedSigner {\n #source?: AirgappedSignerDetails | undefined;\n\n /**\n * Initialize the AirgappedSigner with a source.\n *\n * @param source - The signer source, in the form of details object, or a UR string\n */\n init(source: AirgappedSignerDetails | string | SerializedUR): void {\n if (typeof source === 'string' || 'cbor' in source) {\n this.#initFromUR(source);\n } else {\n this.#source = source;\n }\n }\n\n /**\n * Check if the AirgappedSigner is initialized\n *\n * @returns True if the AirgappedSigner is initialized, false otherwise\n */\n isInitialized(): boolean {\n return this.#source !== undefined;\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.#source) {\n throw new Error('UR not initialized');\n }\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const address = Object.keys(this.#source.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.#source.childrenPath.replace(\n '*',\n index.toString(),\n )}`;\n\n const hdKey = HdKey.fromExtendedKey(this.#source.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.#source.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 if (!this.#source) {\n throw new Error('UR not initialized');\n }\n\n const normalizedAddress = getChecksumAddress(add0x(address));\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const path = this.#source.paths[normalizedAddress];\n if (path === undefined) {\n throw new Error(`Unknown address ${normalizedAddress}`);\n }\n return path;\n }\n\n const index = this.indexFromAddress(normalizedAddress);\n return `${this.#source.hdPath}/${this.#source.childrenPath\n .replace('*', index.toString())\n .replace(/\\*/gu, '0')}`;\n }\n\n /**\n * Retrieve the index of an address from the source\n *\n * @param address - The normalized address to retrieve the index for\n * @returns The index of the address\n */\n indexFromAddress(address: Hex): number {\n if (!this.#source) {\n throw new Error('UR not initialized');\n }\n\n const cachedIndex = this.#source.indexes[address];\n if (cachedIndex !== undefined) {\n return Number(cachedIndex);\n }\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const path = this.#source.paths[address];\n if (path === undefined) {\n throw new Error(`Unknown address`);\n }\n\n const index = path.split('/').pop();\n if (index === undefined) {\n throw new Error(`Invalid path for address ${address}`);\n }\n\n return Number(index);\n }\n\n for (let i = 0; i < MAX_INDEX; i++) {\n const derivedAddress = this.addressFromIndex(i);\n if (derivedAddress === address) {\n return i;\n }\n }\n\n throw new Error(`Address ${address} not found`);\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 * Gets the source details of the AirgappedSigner.\n *\n * @returns The source details, or undefined if not initialized\n */\n getSourceDetails(): AirgappedSignerDetails | undefined {\n return this.#source;\n }\n\n /**\n * Clear the source details.\n */\n clear(): void {\n this.#source = undefined;\n }\n\n /**\n * Set the root account from a UR string\n *\n * @param ur - The UR string to set the root account from\n */\n #initFromUR(ur: string | SerializedUR): void {\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 this.#source = {\n keyringMode: KeyringMode.ACCOUNT,\n keyringAccount,\n name,\n xfp,\n paths,\n indexes: {},\n };\n } else {\n const { getBip32Key, getOrigin, getChildren, getName, getNote } = source;\n this.#source = {\n keyringMode: KeyringMode.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 /**\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"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Hex } from "@metamask/utils";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SerializedUR } from "./qr-keyring.cjs";
|
|
3
3
|
export declare const SUPPORTED_UR_TYPE: {
|
|
4
4
|
CRYPTO_HDKEY: string;
|
|
5
5
|
CRYPTO_ACCOUNT: string;
|
|
@@ -88,7 +88,7 @@ export declare class AirgappedSigner {
|
|
|
88
88
|
*
|
|
89
89
|
* @param source - The signer source, in the form of details object, or a UR string
|
|
90
90
|
*/
|
|
91
|
-
init(source: AirgappedSignerDetails | string |
|
|
91
|
+
init(source: AirgappedSignerDetails | string | SerializedUR): void;
|
|
92
92
|
/**
|
|
93
93
|
* Check if the AirgappedSigner is initialized
|
|
94
94
|
*
|
|
@@ -103,6 +103,13 @@ export declare class AirgappedSigner {
|
|
|
103
103
|
* @throws Will throw an error if the source is not initialized
|
|
104
104
|
*/
|
|
105
105
|
addressFromIndex(index: number): Hex;
|
|
106
|
+
/**
|
|
107
|
+
* Retrieve the path of an address derived from the source.
|
|
108
|
+
*
|
|
109
|
+
* @param address - The address to retrieve the path for
|
|
110
|
+
* @returns The path of the address
|
|
111
|
+
*/
|
|
112
|
+
pathFromAddress(address: Hex): string;
|
|
106
113
|
/**
|
|
107
114
|
* Retrieve the index of an address from the source
|
|
108
115
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airgapped-signer.d.cts","sourceRoot":"","sources":["../src/airgapped-signer.ts"],"names":[],"mappings":"AAMA,OAAO,EAA6B,KAAK,GAAG,EAAE,wBAAwB;AAItE,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"airgapped-signer.d.cts","sourceRoot":"","sources":["../src/airgapped-signer.ts"],"names":[],"mappings":"AAMA,OAAO,EAA6B,KAAK,GAAG,EAAE,wBAAwB;AAItE,OAAO,KAAK,EAAE,YAAY,EAAE,yBAAqB;AAEjD,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAC;AAEF,oBAAY,WAAW;IACrB,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,WAAW,CAAC,EAAE,CAAC;IAC5B;;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,WAAW,CAAC,OAAO,CAAC;IACjC;;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,sBAAsB,GAAG,mBAAmB,GACtD,CAAC,mBAAmB,GAAG,wBAAwB,CAAC,CAAC;AAmEnD,qBAAa,eAAe;;IAG1B;;;;OAIG;IACH,IAAI,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,GAAG,YAAY,GAAG,IAAI;IAQlE;;;;OAIG;IACH,aAAa,IAAI,OAAO;IAIxB;;;;;;OAMG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG;IA+BpC;;;;;OAKG;IACH,eAAe,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM;IAqBrC;;;;;OAKG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM;IAkCtC;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,cAAc,EAAE;IAa9D;;;;OAIG;IACH,gBAAgB,IAAI,sBAAsB,GAAG,SAAS;IAItD;;OAEG;IACH,KAAK,IAAI,IAAI;CAkEd"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Hex } from "@metamask/utils";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SerializedUR } from "./qr-keyring.mjs";
|
|
3
3
|
export declare const SUPPORTED_UR_TYPE: {
|
|
4
4
|
CRYPTO_HDKEY: string;
|
|
5
5
|
CRYPTO_ACCOUNT: string;
|
|
@@ -88,7 +88,7 @@ export declare class AirgappedSigner {
|
|
|
88
88
|
*
|
|
89
89
|
* @param source - The signer source, in the form of details object, or a UR string
|
|
90
90
|
*/
|
|
91
|
-
init(source: AirgappedSignerDetails | string |
|
|
91
|
+
init(source: AirgappedSignerDetails | string | SerializedUR): void;
|
|
92
92
|
/**
|
|
93
93
|
* Check if the AirgappedSigner is initialized
|
|
94
94
|
*
|
|
@@ -103,6 +103,13 @@ export declare class AirgappedSigner {
|
|
|
103
103
|
* @throws Will throw an error if the source is not initialized
|
|
104
104
|
*/
|
|
105
105
|
addressFromIndex(index: number): Hex;
|
|
106
|
+
/**
|
|
107
|
+
* Retrieve the path of an address derived from the source.
|
|
108
|
+
*
|
|
109
|
+
* @param address - The address to retrieve the path for
|
|
110
|
+
* @returns The path of the address
|
|
111
|
+
*/
|
|
112
|
+
pathFromAddress(address: Hex): string;
|
|
106
113
|
/**
|
|
107
114
|
* Retrieve the index of an address from the source
|
|
108
115
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airgapped-signer.d.mts","sourceRoot":"","sources":["../src/airgapped-signer.ts"],"names":[],"mappings":"AAMA,OAAO,EAA6B,KAAK,GAAG,EAAE,wBAAwB;AAItE,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"airgapped-signer.d.mts","sourceRoot":"","sources":["../src/airgapped-signer.ts"],"names":[],"mappings":"AAMA,OAAO,EAA6B,KAAK,GAAG,EAAE,wBAAwB;AAItE,OAAO,KAAK,EAAE,YAAY,EAAE,yBAAqB;AAEjD,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAC;AAEF,oBAAY,WAAW;IACrB,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,WAAW,CAAC,EAAE,CAAC;IAC5B;;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,WAAW,CAAC,OAAO,CAAC;IACjC;;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,sBAAsB,GAAG,mBAAmB,GACtD,CAAC,mBAAmB,GAAG,wBAAwB,CAAC,CAAC;AAmEnD,qBAAa,eAAe;;IAG1B;;;;OAIG;IACH,IAAI,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,GAAG,YAAY,GAAG,IAAI;IAQlE;;;;OAIG;IACH,aAAa,IAAI,OAAO;IAIxB;;;;;;OAMG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG;IA+BpC;;;;;OAKG;IACH,eAAe,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM;IAqBrC;;;;;OAKG;IACH,gBAAgB,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM;IAkCtC;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,cAAc,EAAE;IAa9D;;;;OAIG;IACH,gBAAgB,IAAI,sBAAsB,GAAG,SAAS;IAItD;;OAEG;IACH,KAAK,IAAI,IAAI;CAkEd"}
|
|
@@ -134,6 +134,29 @@ export class AirgappedSigner {
|
|
|
134
134
|
__classPrivateFieldGet(this, _AirgappedSigner_source, "f").indexes[normalizedAddress] = index;
|
|
135
135
|
return normalizedAddress;
|
|
136
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Retrieve the path of an address derived from the source.
|
|
139
|
+
*
|
|
140
|
+
* @param address - The address to retrieve the path for
|
|
141
|
+
* @returns The path of the address
|
|
142
|
+
*/
|
|
143
|
+
pathFromAddress(address) {
|
|
144
|
+
if (!__classPrivateFieldGet(this, _AirgappedSigner_source, "f")) {
|
|
145
|
+
throw new Error('UR not initialized');
|
|
146
|
+
}
|
|
147
|
+
const normalizedAddress = getChecksumAddress(add0x(address));
|
|
148
|
+
if (__classPrivateFieldGet(this, _AirgappedSigner_source, "f").keyringMode === KeyringMode.ACCOUNT) {
|
|
149
|
+
const path = __classPrivateFieldGet(this, _AirgappedSigner_source, "f").paths[normalizedAddress];
|
|
150
|
+
if (path === undefined) {
|
|
151
|
+
throw new Error(`Unknown address ${normalizedAddress}`);
|
|
152
|
+
}
|
|
153
|
+
return path;
|
|
154
|
+
}
|
|
155
|
+
const index = this.indexFromAddress(normalizedAddress);
|
|
156
|
+
return `${__classPrivateFieldGet(this, _AirgappedSigner_source, "f").hdPath}/${__classPrivateFieldGet(this, _AirgappedSigner_source, "f").childrenPath
|
|
157
|
+
.replace('*', index.toString())
|
|
158
|
+
.replace(/\*/gu, '0')}`;
|
|
159
|
+
}
|
|
137
160
|
/**
|
|
138
161
|
* Retrieve the index of an address from the source
|
|
139
162
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airgapped-signer.mjs","sourceRoot":"","sources":["../src/airgapped-signer.ts"],"names":[],"mappings":"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","sourcesContent":["import { publicToAddress } from '@ethereumjs/util';\nimport {\n CryptoAccount,\n CryptoHDKey,\n URRegistryDecoder,\n} from '@keystonehq/bc-ur-registry-eth';\nimport { add0x, getChecksumAddress, type Hex } from '@metamask/utils';\n// eslint-disable-next-line @typescript-eslint/naming-convention\nimport HdKey from 'hdkey';\n\nimport type { QrScanResponse } 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 KeyringMode {\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 signer source, which can be either a CryptoAccount or CryptoHDKey.\n */\nexport type CommonSignerDetails = {\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 AirgappedSigner. This mode derives\n * accounts from a root public key (xpub) and a derivation path.\n */\nexport type HDModeSignerDetails = {\n /**\n * The keyring mode is HD, indicating that it derives accounts from a\n * root public key (xpub) and a derivation path.\n */\n keyringMode: KeyringMode.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 AirgappedSigner. This mode derives\n * accounts from a set of addresses and their corresponding paths.\n */\nexport type AccountModeSignerDetails = {\n /**\n * The keyring mode is ACCOUNT, indicating that it derives accounts from\n * a set of addresses and their corresponding paths.\n */\n keyringMode: KeyringMode.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 AirgappedSigner uses to derive accounts.\n */\nexport type AirgappedSignerDetails = CommonSignerDetails &\n (HDModeSignerDetails | AccountModeSignerDetails);\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 AirgappedSigner {\n #source?: AirgappedSignerDetails | undefined;\n\n /**\n * Initialize the AirgappedSigner with a source.\n *\n * @param source - The signer source, in the form of details object, or a UR string\n */\n init(source: AirgappedSignerDetails | string | QrScanResponse): void {\n if (typeof source === 'string' || 'cbor' in source) {\n this.#initFromUR(source);\n } else {\n this.#source = source;\n }\n }\n\n /**\n * Check if the AirgappedSigner is initialized\n *\n * @returns True if the AirgappedSigner is initialized, false otherwise\n */\n isInitialized(): boolean {\n return this.#source !== undefined;\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.#source) {\n throw new Error('UR not initialized');\n }\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const address = Object.keys(this.#source.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.#source.childrenPath.replace(\n '*',\n index.toString(),\n )}`;\n\n const hdKey = HdKey.fromExtendedKey(this.#source.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.#source.indexes[normalizedAddress] = index;\n\n return normalizedAddress;\n }\n\n /**\n * Retrieve the index of an address from the source\n *\n * @param address - The normalized address to retrieve the index for\n * @returns The index of the address\n */\n indexFromAddress(address: Hex): number {\n if (!this.#source) {\n throw new Error('UR not initialized');\n }\n\n const cachedIndex = this.#source.indexes[address];\n if (cachedIndex !== undefined) {\n return Number(cachedIndex);\n }\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const path = this.#source.paths[address];\n if (path === undefined) {\n throw new Error(`Unknown address`);\n }\n\n const index = path.split('/').pop();\n if (index === undefined) {\n throw new Error(`Invalid path for address ${address}`);\n }\n\n return Number(index);\n }\n\n for (let i = 0; i < MAX_INDEX; i++) {\n const derivedAddress = this.addressFromIndex(i);\n if (derivedAddress === address) {\n return i;\n }\n }\n\n throw new Error(`Address ${address} not found`);\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 * Gets the source details of the AirgappedSigner.\n *\n * @returns The source details, or undefined if not initialized\n */\n getSourceDetails(): AirgappedSignerDetails | undefined {\n return this.#source;\n }\n\n /**\n * Clear the source details.\n */\n clear(): void {\n this.#source = undefined;\n }\n\n /**\n * Set the root account from a UR string\n *\n * @param ur - The UR string to set the root account from\n */\n #initFromUR(ur: string | QrScanResponse): void {\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 this.#source = {\n keyringMode: KeyringMode.ACCOUNT,\n keyringAccount,\n name,\n xfp,\n paths,\n indexes: {},\n };\n } else {\n const { getBip32Key, getOrigin, getChildren, getName, getNote } = source;\n this.#source = {\n keyringMode: KeyringMode.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 /**\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 | QrScanResponse): 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"]}
|
|
1
|
+
{"version":3,"file":"airgapped-signer.mjs","sourceRoot":"","sources":["../src/airgapped-signer.ts"],"names":[],"mappings":"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","sourcesContent":["import { publicToAddress } from '@ethereumjs/util';\nimport {\n CryptoAccount,\n CryptoHDKey,\n URRegistryDecoder,\n} from '@keystonehq/bc-ur-registry-eth';\nimport { add0x, getChecksumAddress, type Hex } from '@metamask/utils';\n// eslint-disable-next-line @typescript-eslint/naming-convention\nimport HdKey from 'hdkey';\n\nimport type { SerializedUR } 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 KeyringMode {\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 signer source, which can be either a CryptoAccount or CryptoHDKey.\n */\nexport type CommonSignerDetails = {\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 AirgappedSigner. This mode derives\n * accounts from a root public key (xpub) and a derivation path.\n */\nexport type HDModeSignerDetails = {\n /**\n * The keyring mode is HD, indicating that it derives accounts from a\n * root public key (xpub) and a derivation path.\n */\n keyringMode: KeyringMode.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 AirgappedSigner. This mode derives\n * accounts from a set of addresses and their corresponding paths.\n */\nexport type AccountModeSignerDetails = {\n /**\n * The keyring mode is ACCOUNT, indicating that it derives accounts from\n * a set of addresses and their corresponding paths.\n */\n keyringMode: KeyringMode.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 AirgappedSigner uses to derive accounts.\n */\nexport type AirgappedSignerDetails = CommonSignerDetails &\n (HDModeSignerDetails | AccountModeSignerDetails);\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 AirgappedSigner {\n #source?: AirgappedSignerDetails | undefined;\n\n /**\n * Initialize the AirgappedSigner with a source.\n *\n * @param source - The signer source, in the form of details object, or a UR string\n */\n init(source: AirgappedSignerDetails | string | SerializedUR): void {\n if (typeof source === 'string' || 'cbor' in source) {\n this.#initFromUR(source);\n } else {\n this.#source = source;\n }\n }\n\n /**\n * Check if the AirgappedSigner is initialized\n *\n * @returns True if the AirgappedSigner is initialized, false otherwise\n */\n isInitialized(): boolean {\n return this.#source !== undefined;\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.#source) {\n throw new Error('UR not initialized');\n }\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const address = Object.keys(this.#source.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.#source.childrenPath.replace(\n '*',\n index.toString(),\n )}`;\n\n const hdKey = HdKey.fromExtendedKey(this.#source.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.#source.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 if (!this.#source) {\n throw new Error('UR not initialized');\n }\n\n const normalizedAddress = getChecksumAddress(add0x(address));\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const path = this.#source.paths[normalizedAddress];\n if (path === undefined) {\n throw new Error(`Unknown address ${normalizedAddress}`);\n }\n return path;\n }\n\n const index = this.indexFromAddress(normalizedAddress);\n return `${this.#source.hdPath}/${this.#source.childrenPath\n .replace('*', index.toString())\n .replace(/\\*/gu, '0')}`;\n }\n\n /**\n * Retrieve the index of an address from the source\n *\n * @param address - The normalized address to retrieve the index for\n * @returns The index of the address\n */\n indexFromAddress(address: Hex): number {\n if (!this.#source) {\n throw new Error('UR not initialized');\n }\n\n const cachedIndex = this.#source.indexes[address];\n if (cachedIndex !== undefined) {\n return Number(cachedIndex);\n }\n\n if (this.#source.keyringMode === KeyringMode.ACCOUNT) {\n const path = this.#source.paths[address];\n if (path === undefined) {\n throw new Error(`Unknown address`);\n }\n\n const index = path.split('/').pop();\n if (index === undefined) {\n throw new Error(`Invalid path for address ${address}`);\n }\n\n return Number(index);\n }\n\n for (let i = 0; i < MAX_INDEX; i++) {\n const derivedAddress = this.addressFromIndex(i);\n if (derivedAddress === address) {\n return i;\n }\n }\n\n throw new Error(`Address ${address} not found`);\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 * Gets the source details of the AirgappedSigner.\n *\n * @returns The source details, or undefined if not initialized\n */\n getSourceDetails(): AirgappedSignerDetails | undefined {\n return this.#source;\n }\n\n /**\n * Clear the source details.\n */\n clear(): void {\n this.#source = undefined;\n }\n\n /**\n * Set the root account from a UR string\n *\n * @param ur - The UR string to set the root account from\n */\n #initFromUR(ur: string | SerializedUR): void {\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 this.#source = {\n keyringMode: KeyringMode.ACCOUNT,\n keyringAccount,\n name,\n xfp,\n paths,\n indexes: {},\n };\n } else {\n const { getBip32Key, getOrigin, getChildren, getName, getNote } = source;\n this.#source = {\n keyringMode: KeyringMode.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 /**\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"]}
|
package/dist/index.cjs
CHANGED
|
@@ -14,6 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./qr-keyring-bridge.cjs"), exports);
|
|
17
|
+
__exportStar(require("./qr-keyring-scanner-bridge.cjs"), exports);
|
|
18
18
|
__exportStar(require("./qr-keyring.cjs"), exports);
|
|
19
19
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kEAA4C;AAC5C,mDAA6B","sourcesContent":["export * from './qr-keyring-scanner-bridge';\nexport * from './qr-keyring';\n"]}
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAA4C;AAC5C,iCAA6B"}
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAA4C;AAC5C,iCAA6B"}
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAA4C;AAC5C,iCAA6B","sourcesContent":["export * from './qr-keyring-scanner-bridge';\nexport * from './qr-keyring';\n"]}
|
|
@@ -23,15 +23,15 @@ class QrKeyringScannerBridge {
|
|
|
23
23
|
__classPrivateFieldSet(this, _QrKeyringScannerBridge_requestScan, requestScan, "f");
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Request a QR code scan, obtaining a CBOR and a type as response.
|
|
27
27
|
*
|
|
28
|
-
* @param
|
|
29
|
-
* @returns The scanned data as a
|
|
28
|
+
* @param request - The type of QR scan request.
|
|
29
|
+
* @returns The scanned data as a serialized UR.
|
|
30
30
|
*/
|
|
31
|
-
async requestScan(
|
|
32
|
-
return __classPrivateFieldGet(this, _QrKeyringScannerBridge_requestScan, "f").call(this,
|
|
31
|
+
async requestScan(request) {
|
|
32
|
+
return __classPrivateFieldGet(this, _QrKeyringScannerBridge_requestScan, "f").call(this, request);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
exports.QrKeyringScannerBridge = QrKeyringScannerBridge;
|
|
36
36
|
_QrKeyringScannerBridge_requestScan = new WeakMap();
|
|
37
|
-
//# sourceMappingURL=qr-keyring-bridge.cjs.map
|
|
37
|
+
//# sourceMappingURL=qr-keyring-scanner-bridge.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-keyring-scanner-bridge.cjs","sourceRoot":"","sources":["../src/qr-keyring-scanner-bridge.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAgBA;;;GAGG;AACH,MAAa,sBAAsB;IAGjC,YAAY,EAAE,WAAW,EAAiC;QAFjD,sDAA2D;QAGlE,uBAAA,IAAI,uCAAgB,WAAW,MAAA,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,OAAsB;QACtC,OAAO,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,OAAO,CAAC,CAAC;IACpC,CAAC;CACF;AAhBD,wDAgBC","sourcesContent":["import type {\n QrKeyringBridge,\n QrScanRequest,\n SerializedUR,\n} from './qr-keyring';\n\n/**\n * Options for the QrKeyringScannerBridge.\n */\nexport type QrKeyringScannerBridgeOptions = {\n /**\n * An injected function that the bridge uses to request a QR code scan.\n */\n requestScan: (request: QrScanRequest) => Promise<SerializedUR>;\n};\n\n/**\n * A bridge that allows the QrKeyring to request a QR code scan\n * while keeping the implementation defined by the QrKeyring consumer.\n */\nexport class QrKeyringScannerBridge implements QrKeyringBridge {\n readonly #requestScan: QrKeyringScannerBridgeOptions['requestScan'];\n\n constructor({ requestScan }: QrKeyringScannerBridgeOptions) {\n this.#requestScan = requestScan;\n }\n\n /**\n * Request a QR code scan, obtaining a CBOR and a type as response.\n *\n * @param request - The type of QR scan request.\n * @returns The scanned data as a serialized UR.\n */\n async requestScan(request: QrScanRequest): Promise<SerializedUR> {\n return this.#requestScan(request);\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QrKeyringBridge,
|
|
1
|
+
import type { QrKeyringBridge, QrScanRequest, SerializedUR } from "./qr-keyring.cjs";
|
|
2
2
|
/**
|
|
3
3
|
* Options for the QrKeyringScannerBridge.
|
|
4
4
|
*/
|
|
@@ -6,7 +6,7 @@ export type QrKeyringScannerBridgeOptions = {
|
|
|
6
6
|
/**
|
|
7
7
|
* An injected function that the bridge uses to request a QR code scan.
|
|
8
8
|
*/
|
|
9
|
-
requestScan: (
|
|
9
|
+
requestScan: (request: QrScanRequest) => Promise<SerializedUR>;
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* A bridge that allows the QrKeyring to request a QR code scan
|
|
@@ -16,11 +16,11 @@ export declare class QrKeyringScannerBridge implements QrKeyringBridge {
|
|
|
16
16
|
#private;
|
|
17
17
|
constructor({ requestScan }: QrKeyringScannerBridgeOptions);
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Request a QR code scan, obtaining a CBOR and a type as response.
|
|
20
20
|
*
|
|
21
|
-
* @param
|
|
22
|
-
* @returns The scanned data as a
|
|
21
|
+
* @param request - The type of QR scan request.
|
|
22
|
+
* @returns The scanned data as a serialized UR.
|
|
23
23
|
*/
|
|
24
|
-
requestScan(
|
|
24
|
+
requestScan(request: QrScanRequest): Promise<SerializedUR>;
|
|
25
25
|
}
|
|
26
|
-
//# sourceMappingURL=qr-keyring-bridge.d.
|
|
26
|
+
//# sourceMappingURL=qr-keyring-scanner-bridge.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-keyring-scanner-bridge.d.cts","sourceRoot":"","sources":["../src/qr-keyring-scanner-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACb,yBAAqB;AAEtB;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;OAEG;IACH,WAAW,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAChE,CAAC;AAEF;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,eAAe;;gBAGhD,EAAE,WAAW,EAAE,EAAE,6BAA6B;IAI1D;;;;;OAKG;IACG,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;CAGjE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QrKeyringBridge,
|
|
1
|
+
import type { QrKeyringBridge, QrScanRequest, SerializedUR } from "./qr-keyring.mjs";
|
|
2
2
|
/**
|
|
3
3
|
* Options for the QrKeyringScannerBridge.
|
|
4
4
|
*/
|
|
@@ -6,7 +6,7 @@ export type QrKeyringScannerBridgeOptions = {
|
|
|
6
6
|
/**
|
|
7
7
|
* An injected function that the bridge uses to request a QR code scan.
|
|
8
8
|
*/
|
|
9
|
-
requestScan: (
|
|
9
|
+
requestScan: (request: QrScanRequest) => Promise<SerializedUR>;
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* A bridge that allows the QrKeyring to request a QR code scan
|
|
@@ -16,11 +16,11 @@ export declare class QrKeyringScannerBridge implements QrKeyringBridge {
|
|
|
16
16
|
#private;
|
|
17
17
|
constructor({ requestScan }: QrKeyringScannerBridgeOptions);
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Request a QR code scan, obtaining a CBOR and a type as response.
|
|
20
20
|
*
|
|
21
|
-
* @param
|
|
22
|
-
* @returns The scanned data as a
|
|
21
|
+
* @param request - The type of QR scan request.
|
|
22
|
+
* @returns The scanned data as a serialized UR.
|
|
23
23
|
*/
|
|
24
|
-
requestScan(
|
|
24
|
+
requestScan(request: QrScanRequest): Promise<SerializedUR>;
|
|
25
25
|
}
|
|
26
|
-
//# sourceMappingURL=qr-keyring-bridge.d.
|
|
26
|
+
//# sourceMappingURL=qr-keyring-scanner-bridge.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-keyring-scanner-bridge.d.mts","sourceRoot":"","sources":["../src/qr-keyring-scanner-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACb,yBAAqB;AAEtB;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;OAEG;IACH,WAAW,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAChE,CAAC;AAEF;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,eAAe;;gBAGhD,EAAE,WAAW,EAAE,EAAE,6BAA6B;IAI1D;;;;;OAKG;IACG,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;CAGjE"}
|
|
@@ -20,14 +20,14 @@ export class QrKeyringScannerBridge {
|
|
|
20
20
|
__classPrivateFieldSet(this, _QrKeyringScannerBridge_requestScan, requestScan, "f");
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Request a QR code scan, obtaining a CBOR and a type as response.
|
|
24
24
|
*
|
|
25
|
-
* @param
|
|
26
|
-
* @returns The scanned data as a
|
|
25
|
+
* @param request - The type of QR scan request.
|
|
26
|
+
* @returns The scanned data as a serialized UR.
|
|
27
27
|
*/
|
|
28
|
-
async requestScan(
|
|
29
|
-
return __classPrivateFieldGet(this, _QrKeyringScannerBridge_requestScan, "f").call(this,
|
|
28
|
+
async requestScan(request) {
|
|
29
|
+
return __classPrivateFieldGet(this, _QrKeyringScannerBridge_requestScan, "f").call(this, request);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
_QrKeyringScannerBridge_requestScan = new WeakMap();
|
|
33
|
-
//# sourceMappingURL=qr-keyring-bridge.mjs.map
|
|
33
|
+
//# sourceMappingURL=qr-keyring-scanner-bridge.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr-keyring-scanner-bridge.mjs","sourceRoot":"","sources":["../src/qr-keyring-scanner-bridge.ts"],"names":[],"mappings":";;;;;;;;;;;;AAgBA;;;GAGG;AACH,MAAM,OAAO,sBAAsB;IAGjC,YAAY,EAAE,WAAW,EAAiC;QAFjD,sDAA2D;QAGlE,uBAAA,IAAI,uCAAgB,WAAW,MAAA,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,OAAsB;QACtC,OAAO,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,OAAO,CAAC,CAAC;IACpC,CAAC;CACF","sourcesContent":["import type {\n QrKeyringBridge,\n QrScanRequest,\n SerializedUR,\n} from './qr-keyring';\n\n/**\n * Options for the QrKeyringScannerBridge.\n */\nexport type QrKeyringScannerBridgeOptions = {\n /**\n * An injected function that the bridge uses to request a QR code scan.\n */\n requestScan: (request: QrScanRequest) => Promise<SerializedUR>;\n};\n\n/**\n * A bridge that allows the QrKeyring to request a QR code scan\n * while keeping the implementation defined by the QrKeyring consumer.\n */\nexport class QrKeyringScannerBridge implements QrKeyringBridge {\n readonly #requestScan: QrKeyringScannerBridgeOptions['requestScan'];\n\n constructor({ requestScan }: QrKeyringScannerBridgeOptions) {\n this.#requestScan = requestScan;\n }\n\n /**\n * Request a QR code scan, obtaining a CBOR and a type as response.\n *\n * @param request - The type of QR scan request.\n * @returns The scanned data as a serialized UR.\n */\n async requestScan(request: QrScanRequest): Promise<SerializedUR> {\n return this.#requestScan(request);\n }\n}\n"]}
|
package/dist/qr-keyring.cjs
CHANGED
|
@@ -10,12 +10,18 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
10
10
|
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");
|
|
11
11
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
12
12
|
};
|
|
13
|
-
var _QrKeyring_instances, _QrKeyring_signer, _QrKeyring_accounts, _QrKeyring_accountToUnlock, _QrKeyring_currentPage, _QrKeyring_scanAndInitialize;
|
|
13
|
+
var _QrKeyring_instances, _QrKeyring_signer, _QrKeyring_accounts, _QrKeyring_accountToUnlock, _QrKeyring_currentPage, _QrKeyring_scanAndInitialize, _QrKeyring_requestSignature;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.QrKeyring = exports.getDefaultSerializedQrKeyringState = exports.QrScanRequestType = exports.QR_KEYRING_TYPE = void 0;
|
|
16
|
+
const rlp_1 = require("@ethereumjs/rlp");
|
|
17
|
+
const tx_1 = require("@ethereumjs/tx");
|
|
18
|
+
const bc_ur_registry_eth_1 = require("@keystonehq/bc-ur-registry-eth");
|
|
16
19
|
const utils_1 = require("@metamask/utils");
|
|
20
|
+
const uuid_1 = require("uuid");
|
|
17
21
|
const airgapped_signer_1 = require("./airgapped-signer.cjs");
|
|
18
22
|
exports.QR_KEYRING_TYPE = 'QR Hardware Wallet Device';
|
|
23
|
+
const DEFAULT_SCAN_REQUEST_TITLE = 'Scan with your hardware wallet';
|
|
24
|
+
const DEFAULT_SCAN_REQUEST_DESCRIPTION = 'After your device has scanned this QR code, click on "Scan" to receive the information.';
|
|
19
25
|
var QrScanRequestType;
|
|
20
26
|
(function (QrScanRequestType) {
|
|
21
27
|
/**
|
|
@@ -237,6 +243,83 @@ class QrKeyring {
|
|
|
237
243
|
__classPrivateFieldSet(this, _QrKeyring_accountToUnlock, undefined, "f");
|
|
238
244
|
__classPrivateFieldSet(this, _QrKeyring_currentPage, 0, "f");
|
|
239
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Sign a transaction. This is equivalent to the `eth_signTransaction`
|
|
248
|
+
* Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
|
|
249
|
+
* more details.
|
|
250
|
+
*
|
|
251
|
+
* @param address - The address of the account to use for signing.
|
|
252
|
+
* @param transaction - The transaction to sign.
|
|
253
|
+
* @returns The signed transaction.
|
|
254
|
+
*/
|
|
255
|
+
async signTransaction(address, transaction) {
|
|
256
|
+
const signer = __classPrivateFieldGet(this, _QrKeyring_signer, "f").getSourceDetails();
|
|
257
|
+
if (!signer?.xfp) {
|
|
258
|
+
throw new Error('Keyring is not initialized. Please scan a QR code.');
|
|
259
|
+
}
|
|
260
|
+
const dataType = transaction.type === tx_1.TransactionType.Legacy
|
|
261
|
+
? bc_ur_registry_eth_1.DataType.transaction
|
|
262
|
+
: bc_ur_registry_eth_1.DataType.typedTransaction;
|
|
263
|
+
let messageToSign;
|
|
264
|
+
if (transaction.type === tx_1.TransactionType.Legacy) {
|
|
265
|
+
messageToSign = Buffer.from(rlp_1.RLP.encode(transaction.getMessageToSign()));
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
messageToSign = Buffer.from(transaction.getMessageToSign());
|
|
269
|
+
}
|
|
270
|
+
const hdPath = __classPrivateFieldGet(this, _QrKeyring_signer, "f").pathFromAddress(address);
|
|
271
|
+
const chainId = Number(transaction.common.chainId());
|
|
272
|
+
const requestId = (0, uuid_1.v4)();
|
|
273
|
+
const ethSignRequestUR = bc_ur_registry_eth_1.EthSignRequest.constructETHRequest(messageToSign, dataType, hdPath, signer.xfp, requestId, chainId).toUR();
|
|
274
|
+
const { r, s, v } = await __classPrivateFieldGet(this, _QrKeyring_instances, "m", _QrKeyring_requestSignature).call(this, {
|
|
275
|
+
requestId,
|
|
276
|
+
payload: {
|
|
277
|
+
type: ethSignRequestUR.type,
|
|
278
|
+
cbor: ethSignRequestUR.cbor.toString('hex'),
|
|
279
|
+
},
|
|
280
|
+
requestTitle: 'Scan with your hardware wallet',
|
|
281
|
+
requestDescription: 'After your device has signed this message, click on "Scan" to receive the signature',
|
|
282
|
+
});
|
|
283
|
+
return tx_1.TransactionFactory.fromTxData({
|
|
284
|
+
...transaction,
|
|
285
|
+
r,
|
|
286
|
+
s,
|
|
287
|
+
v,
|
|
288
|
+
}, {
|
|
289
|
+
common: transaction.common,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
|
|
294
|
+
* JSON-RPC method.
|
|
295
|
+
*
|
|
296
|
+
* @param address - The address of the account to use for signing.
|
|
297
|
+
* @param data - The data to sign.
|
|
298
|
+
* @returns The signed message.
|
|
299
|
+
*/
|
|
300
|
+
async signTypedData(address, data) {
|
|
301
|
+
const signer = __classPrivateFieldGet(this, _QrKeyring_signer, "f").getSourceDetails();
|
|
302
|
+
if (!signer?.xfp) {
|
|
303
|
+
throw new Error('Keyring is not initialized. Please scan a QR code.');
|
|
304
|
+
}
|
|
305
|
+
const hdPath = __classPrivateFieldGet(this, _QrKeyring_signer, "f").pathFromAddress(address);
|
|
306
|
+
const requestId = (0, uuid_1.v4)();
|
|
307
|
+
const ethSignRequestUR = bc_ur_registry_eth_1.EthSignRequest.constructETHRequest(Buffer.from(JSON.stringify(data), 'utf8'), bc_ur_registry_eth_1.DataType.typedData, hdPath, signer.xfp, requestId, undefined, address).toUR();
|
|
308
|
+
const { r, s, v } = await __classPrivateFieldGet(this, _QrKeyring_instances, "m", _QrKeyring_requestSignature).call(this, {
|
|
309
|
+
requestId,
|
|
310
|
+
payload: {
|
|
311
|
+
type: ethSignRequestUR.type,
|
|
312
|
+
cbor: ethSignRequestUR.cbor.toString('hex'),
|
|
313
|
+
},
|
|
314
|
+
requestTitle: DEFAULT_SCAN_REQUEST_TITLE,
|
|
315
|
+
requestDescription: DEFAULT_SCAN_REQUEST_DESCRIPTION,
|
|
316
|
+
});
|
|
317
|
+
return (0, utils_1.add0x)(Buffer.concat([
|
|
318
|
+
Uint8Array.from(r),
|
|
319
|
+
Uint8Array.from(s),
|
|
320
|
+
Uint8Array.from(v),
|
|
321
|
+
]).toString('hex'));
|
|
322
|
+
}
|
|
240
323
|
}
|
|
241
324
|
exports.QrKeyring = QrKeyring;
|
|
242
325
|
_QrKeyring_signer = new WeakMap(), _QrKeyring_accounts = new WeakMap(), _QrKeyring_accountToUnlock = new WeakMap(), _QrKeyring_currentPage = new WeakMap(), _QrKeyring_instances = new WeakSet(), _QrKeyring_scanAndInitialize =
|
|
@@ -245,7 +328,33 @@ _QrKeyring_signer = new WeakMap(), _QrKeyring_accounts = new WeakMap(), _QrKeyri
|
|
|
245
328
|
* scanned UR.
|
|
246
329
|
*/
|
|
247
330
|
async function _QrKeyring_scanAndInitialize() {
|
|
248
|
-
this.submitUR(await this.bridge.requestScan(QrScanRequestType.PAIR));
|
|
331
|
+
this.submitUR(await this.bridge.requestScan({ type: QrScanRequestType.PAIR }));
|
|
332
|
+
}, _QrKeyring_requestSignature =
|
|
333
|
+
/**
|
|
334
|
+
* Request a signature for a transaction or message.
|
|
335
|
+
*
|
|
336
|
+
* @param request - The signature request containing the data to sign.
|
|
337
|
+
* @returns The signature as an object containing r, s, and v values.
|
|
338
|
+
*/
|
|
339
|
+
async function _QrKeyring_requestSignature(request) {
|
|
340
|
+
const response = await this.bridge.requestScan({
|
|
341
|
+
type: QrScanRequestType.SIGN,
|
|
342
|
+
request,
|
|
343
|
+
});
|
|
344
|
+
const signatureEnvelope = bc_ur_registry_eth_1.ETHSignature.fromCBOR(Buffer.from(response.cbor, 'hex'));
|
|
345
|
+
const signature = signatureEnvelope.getSignature();
|
|
346
|
+
const requestId = signatureEnvelope.getRequestId();
|
|
347
|
+
if (!requestId) {
|
|
348
|
+
throw new Error('Signature request ID is missing.');
|
|
349
|
+
}
|
|
350
|
+
if (request.requestId !== (0, uuid_1.stringify)(requestId)) {
|
|
351
|
+
throw new Error(`Signature request ID mismatch. Expected: ${request.requestId}, received: ${requestId.toString('hex')}`);
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
r: signature.subarray(0, 32),
|
|
355
|
+
s: signature.subarray(32, 64),
|
|
356
|
+
v: signature.subarray(64),
|
|
357
|
+
};
|
|
249
358
|
};
|
|
250
359
|
QrKeyring.type = exports.QR_KEYRING_TYPE;
|
|
251
360
|
//# sourceMappingURL=qr-keyring.cjs.map
|
package/dist/qr-keyring.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qr-keyring.cjs","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAEA,2CAA4D;AAE5D,6DAK4B;AAEf,QAAA,eAAe,GAAG,2BAA2B,CAAC;AAE3D,IAAY,iBAWX;AAXD,WAAY,iBAAiB;IAC3B;;;OAGG;IACH,kCAAa,CAAA;IACb;;;OAGG;IACH,kCAAa,CAAA;AACf,CAAC,EAXW,iBAAiB,iCAAjB,iBAAiB,QAW5B;AAkCD;;;;GAIG;AACI,MAAM,kCAAkC,GAC7C,GAA6B,EAAE,CAAC,CAAC;IAC/B,WAAW,EAAE,KAAK;IAClB,QAAQ,EAAE,EAAE;CACb,CAAC,CAAC;AAJQ,QAAA,kCAAkC,sCAI1C;AAEL;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,IAAA,0BAAkB,EAAC,IAAA,aAAK,EAAC,OAAO,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,MAAa,SAAS;IAepB,YAAY,OAAyB;;QAZ5B,SAAI,GAAG,uBAAe,CAAC;QAIvB,4BAA2B,IAAI,kCAAe,EAAE,EAAC;QAE1D,8BAAmB,EAAE,EAAC;QAEtB,6CAAsC;QAEtC,iCAAuB,CAAC,EAAC;QAGvB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAE7B,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;YAChB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAS;QACb,MAAM,MAAM,GAAG,uBAAA,IAAI,yBAAQ,CAAC,gBAAgB,EAAE,CAAC;QAE/C,IACE,CAAC,MAAM;YACP,CAAC,CAAC,8BAAW,CAAC,EAAE,EAAE,8BAAW,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,EACnE,CAAC;YACD,2DAA2D;YAC3D,OAAO,IAAA,0CAAkC,GAAE,CAAC;QAC9C,CAAC;QAED,MAAM,QAAQ,GAAG,uBAAA,IAAI,2BAAU,CAAC,KAAK,EAAE,CAAC;QAExC,IAAI,MAAM,CAAC,WAAW,KAAK,8BAAW,CAAC,EAAE,EAAE,CAAC;YAC1C,iDAAiD;YACjD,OAAO;gBACL,WAAW,EAAE,IAAI;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,WAAW,EAAE,8BAAW,CAAC,EAAE;gBAC3B,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,YAAY,EAAE,MAAM,CAAC,YAAY;gBACjC,QAAQ;gBACR,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;QACJ,CAAC;QACD,sDAAsD;QACtD,OAAO;YACL,WAAW,EAAE,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,WAAW,EAAE,8BAAW,CAAC,OAAO;YAChC,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,QAAQ;YACR,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,KAA+B;QAC/C,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;YACvB,uBAAA,IAAI,uBAAa,EAAE,MAAA,CAAC;YACpB,uBAAA,IAAI,yBAAQ,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,uBAAA,IAAI,yBAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,uBAAA,IAAI,uBAAa,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAA,CAAC;IAChE,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,aAAqB;QACrC,MAAM,WAAW,GAAG,uBAAA,IAAI,2BAAU,CAAC,uBAAA,IAAI,2BAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC9D,MAAM,UAAU,GACd,uBAAA,IAAI,kCAAiB;YACrB,CAAC,WAAW,CAAC,CAAC,CAAC,uBAAA,IAAI,yBAAQ,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,MAAM,WAAW,GAAU,EAAE,CAAC;QAE9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,UAAU,GAAG,CAAC,CAAC;YAC7B,MAAM,OAAO,GAAG,uBAAA,IAAI,yBAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAErD,IAAI,uBAAA,IAAI,2BAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrC,SAAS;YACX,CAAC;YAED,uBAAA,IAAI,2BAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7B,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAED,uBAAA,IAAI,8BAAoB,UAAU,GAAG,aAAa,MAAA,CAAC;QACnD,OAAO,WAAW,CAAC;IACrB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW;QACf,OAAO,KAAK,CAAC,IAAI,CAAC,uBAAA,IAAI,2BAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED;;;;OAIG;IACH,aAAa,CAAC,OAAY;QACxB,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACpD,uBAAA,IAAI,uBAAa,uBAAA,IAAI,2BAAU,CAAC,MAAM,CACpC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,iBAAiB,CAC3C,MAAA,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,EAA2B;QAClC,uBAAA,IAAI,yBAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,KAAa;QAC9B,uBAAA,IAAI,8BAAoB,KAAK,MAAA,CAAC;IAChC,CAAC;IAED;;;;;OAKG;IACH,OAAO;QACL,MAAM,MAAM,GAAG,uBAAA,IAAI,yBAAQ,CAAC,gBAAgB,EAAE,CAAC;QAC/C,OAAO,MAAM,EAAE,IAAI,IAAI,uBAAe,CAAC;IACzC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,YAAY;QAChB,uBAAA,IAAI,0BAAgB,CAAC,MAAA,CAAC;QACtB,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW;QACf,iHAAqB,CAAC,MAAA,CAAC;QACvB,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,eAAe;QACnB,IAAI,uBAAA,IAAI,8BAAa,GAAG,CAAC,EAAE,CAAC;YAC1B,iHAAqB,CAAC,MAAA,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,uBAAA,IAAI,0BAAgB,CAAC,MAAA,CAAC;QACxB,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC,uBAAA,IAAI,yBAAQ,CAAC,aAAa,EAAE,EAAE,CAAC;YAClC,MAAM,uBAAA,IAAI,0DAAmB,MAAvB,IAAI,CAAqB,CAAC;QAClC,CAAC;QAED,OAAO,uBAAA,IAAI,yBAAQ,CAAC,gBAAgB,CAAC,uBAAA,IAAI,8BAAa,CAAC,CAAC;IAC1D,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,uBAAA,IAAI,yBAAQ,CAAC,KAAK,EAAE,CAAC;QACrB,uBAAA,IAAI,uBAAa,EAAE,MAAA,CAAC;QACpB,uBAAA,IAAI,8BAAoB,SAAS,MAAA,CAAC;QAClC,uBAAA,IAAI,0BAAgB,CAAC,MAAA,CAAC;IACxB,CAAC;;AA/NH,8BAwOC;;AAPC;;;GAGG;AACH,KAAK;IACH,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;AACvE,CAAC;AAtOM,cAAI,GAAG,uBAAe,AAAlB,CAAmB","sourcesContent":["import type { Keyring } from '@metamask/keyring-utils';\nimport type { Hex } from '@metamask/utils';\nimport { add0x, getChecksumAddress } from '@metamask/utils';\n\nimport {\n type AirgappedSignerDetails,\n type IndexedAddress,\n AirgappedSigner,\n KeyringMode,\n} from './airgapped-signer';\n\nexport const QR_KEYRING_TYPE = 'QR Hardware Wallet Device';\n\nexport enum QrScanRequestType {\n /**\n * Request a scan for a QR code containing a UR\n * with information related to a hardware wallet.\n */\n PAIR = 'pair',\n /**\n * Request a scan for a QR code containing a\n * UR-encoded transaction signature.\n */\n SIGN = 'sign',\n}\n\nexport type QrScanResponse = {\n type: string;\n cbor: Hex;\n};\n\nexport type QrKeyringBridge = {\n requestScan: (type: QrScanRequestType) => Promise<QrScanResponse>;\n};\n\nexport type QrKeyringOptions = {\n ur?: string;\n bridge: QrKeyringBridge;\n};\n\n/**\n * The state of the QrKeyring\n *\n * @property accounts - The accounts in the QrKeyring\n */\nexport type SerializedQrKeyringState = {\n version?: number;\n accounts?: string[];\n currentAccount?: number;\n} & (\n | {\n initialized?: false;\n }\n | ({\n initialized: true;\n } & AirgappedSignerDetails)\n);\n\n/**\n * Returns the default serialized state of the QrKeyring.\n *\n * @returns The default serialized state.\n */\nexport const getDefaultSerializedQrKeyringState =\n (): SerializedQrKeyringState => ({\n initialized: false,\n accounts: [],\n });\n\n/**\n * Normalizes an address to a 0x-prefixed checksum address.\n *\n * @param address - The address to normalize.\n * @returns The normalized address as a Hex string.\n */\nfunction normalizeAddress(address: string): Hex {\n return getChecksumAddress(add0x(address));\n}\n\nexport class QrKeyring implements Keyring {\n static type = QR_KEYRING_TYPE;\n\n readonly type = QR_KEYRING_TYPE;\n\n readonly bridge: QrKeyringBridge;\n\n readonly #signer: AirgappedSigner = new AirgappedSigner();\n\n #accounts: Hex[] = [];\n\n #accountToUnlock?: number | undefined;\n\n #currentPage: number = 0;\n\n constructor(options: QrKeyringOptions) {\n this.bridge = options.bridge;\n\n if (options?.ur) {\n this.submitUR(options.ur);\n }\n }\n\n /**\n * Serializes the QrKeyring state\n *\n * @returns The serialized state\n */\n async serialize(): Promise<SerializedQrKeyringState> {\n const source = this.#signer.getSourceDetails();\n\n if (\n !source ||\n ![KeyringMode.HD, KeyringMode.ACCOUNT].includes(source.keyringMode)\n ) {\n // the keyring has not initialized with a device source yet\n return getDefaultSerializedQrKeyringState();\n }\n\n const accounts = this.#accounts.slice();\n\n if (source.keyringMode === KeyringMode.HD) {\n // These properties are only relevant for HD Keys\n return {\n initialized: true,\n name: source.name,\n keyringMode: KeyringMode.HD,\n keyringAccount: source.keyringAccount,\n xfp: source.xfp,\n xpub: source.xpub,\n hdPath: source.hdPath,\n childrenPath: source.childrenPath,\n accounts,\n indexes: source.indexes,\n };\n }\n // These properties are only relevant for Account Keys\n return {\n initialized: true,\n name: source.name,\n keyringMode: KeyringMode.ACCOUNT,\n keyringAccount: source.keyringAccount,\n xfp: source.xfp,\n paths: source.paths,\n accounts,\n indexes: source.indexes,\n };\n }\n\n /**\n * Deserializes the QrKeyring state\n *\n * @param state - The serialized state to deserialize\n */\n async deserialize(state: SerializedQrKeyringState): Promise<void> {\n if (!state.initialized) {\n this.#accounts = [];\n this.#signer.clear();\n return;\n }\n\n this.#signer.init(state);\n this.#accounts = (state.accounts ?? []).map(normalizeAddress);\n }\n\n /**\n * Adds accounts to the QrKeyring\n *\n * @param accountsToAdd - The number of accounts to add\n * @returns The accounts added\n */\n async addAccounts(accountsToAdd: number): Promise<Hex[]> {\n const lastAccount = this.#accounts[this.#accounts.length - 1];\n const startIndex =\n this.#accountToUnlock ??\n (lastAccount ? this.#signer.indexFromAddress(lastAccount) : 0);\n const newAccounts: Hex[] = [];\n\n for (let i = 0; i < accountsToAdd; i++) {\n const index = startIndex + i;\n const address = this.#signer.addressFromIndex(index);\n\n if (this.#accounts.includes(address)) {\n continue;\n }\n\n this.#accounts.push(address);\n newAccounts.push(address);\n }\n\n this.#accountToUnlock = startIndex + accountsToAdd;\n return newAccounts;\n }\n\n /**\n * Gets the accounts in the QrKeyring\n *\n * @returns The accounts in the QrKeyring\n */\n async getAccounts(): Promise<Hex[]> {\n return Array.from(this.#accounts.values());\n }\n\n /**\n * Remove an account from the keyring\n *\n * @param address - The address of the account to remove\n */\n removeAccount(address: Hex): void {\n const normalizedAddress = normalizeAddress(address);\n this.#accounts = this.#accounts.filter(\n (account) => account !== normalizedAddress,\n );\n }\n\n /**\n * Submits a CBOR encoded UR to the QrKeyring\n *\n * @param ur - The CBOR encoded UR\n */\n submitUR(ur: string | QrScanResponse): void {\n this.#signer.init(ur);\n }\n\n /**\n * Sets the next account index to unlock\n *\n * @param index - The index of the account to unlock\n */\n setAccountToUnlock(index: number): void {\n this.#accountToUnlock = index;\n }\n\n /**\n * Get the name of the paired device or the keyring type\n * if unavailable.\n *\n * @returns The name of the paired device or the keyring type.\n */\n getName(): string {\n const source = this.#signer.getSourceDetails();\n return source?.name ?? QR_KEYRING_TYPE;\n }\n\n /**\n * Fetch the first page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The first page of accounts as an array of Hex strings.\n */\n async getFirstPage(): Promise<IndexedAddress[]> {\n this.#currentPage = 0;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the next page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The next page of accounts as an array of IndexedAddress objects.\n */\n async getNextPage(): Promise<IndexedAddress[]> {\n this.#currentPage += 1;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the previous page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The previous page of accounts as an array of IndexedAddress objects.\n */\n async getPreviousPage(): Promise<IndexedAddress[]> {\n if (this.#currentPage > 0) {\n this.#currentPage -= 1;\n } else {\n this.#currentPage = 0;\n }\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the current page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The current page of accounts as an array of IndexedAddress objects.\n */\n async getCurrentPage(): Promise<IndexedAddress[]> {\n if (!this.#signer.isInitialized()) {\n await this.#scanAndInitialize();\n }\n\n return this.#signer.getAddressesPage(this.#currentPage);\n }\n\n /**\n * Clear the keyring state and forget any paired device or accounts.\n */\n async forgetDevice(): Promise<void> {\n this.#signer.clear();\n this.#accounts = [];\n this.#accountToUnlock = undefined;\n this.#currentPage = 0;\n }\n\n /**\n * Scan for a QR code and initialize the keyring with the\n * scanned UR.\n */\n async #scanAndInitialize(): Promise<void> {\n this.submitUR(await this.bridge.requestScan(QrScanRequestType.PAIR));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"qr-keyring.cjs","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"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","sourcesContent":["import { RLP } from '@ethereumjs/rlp';\nimport {\n type FeeMarketEIP1559Transaction,\n TransactionFactory,\n TransactionType,\n type TypedTransaction,\n type TypedTxData,\n} from '@ethereumjs/tx';\nimport {\n DataType,\n ETHSignature,\n EthSignRequest,\n} from '@keystonehq/bc-ur-registry-eth';\nimport type { TypedMessage, MessageTypes } from '@metamask/eth-sig-util';\nimport type { Keyring } from '@metamask/keyring-utils';\nimport { type Hex, add0x, getChecksumAddress } from '@metamask/utils';\nimport { stringify, v4 as uuidv4 } from 'uuid';\n\nimport {\n type AirgappedSignerDetails,\n type IndexedAddress,\n AirgappedSigner,\n KeyringMode,\n} from './airgapped-signer';\n\nexport const QR_KEYRING_TYPE = 'QR Hardware Wallet Device';\n\nconst DEFAULT_SCAN_REQUEST_TITLE = 'Scan with your hardware wallet';\n\nconst DEFAULT_SCAN_REQUEST_DESCRIPTION =\n 'After your device has scanned this QR code, click on \"Scan\" to receive the information.';\n\nexport enum QrScanRequestType {\n /**\n * Request a scan for a QR code containing a UR\n * with information related to a hardware wallet.\n */\n PAIR = 'pair',\n /**\n * Request a scan for a QR code containing a\n * UR-encoded transaction signature.\n */\n SIGN = 'sign',\n}\n\nexport type QrSignatureRequest = {\n requestId: string;\n payload: SerializedUR;\n requestTitle?: string;\n requestDescription?: string;\n};\n\nexport type QrScanRequest = {\n type: QrScanRequestType;\n request?: QrSignatureRequest;\n};\n\nexport type SerializedUR = {\n type: string;\n cbor: string;\n};\n\nexport type QrKeyringBridge = {\n requestScan: (request: QrScanRequest) => Promise<SerializedUR>;\n};\n\nexport type QrKeyringOptions = {\n ur?: string;\n bridge: QrKeyringBridge;\n};\n\n/**\n * The state of the QrKeyring\n *\n * @property accounts - The accounts in the QrKeyring\n */\nexport type SerializedQrKeyringState = {\n version?: number;\n accounts?: string[];\n currentAccount?: number;\n} & (\n | {\n initialized?: false;\n }\n | ({\n initialized: true;\n } & AirgappedSignerDetails)\n);\n\n/**\n * Returns the default serialized state of the QrKeyring.\n *\n * @returns The default serialized state.\n */\nexport const getDefaultSerializedQrKeyringState =\n (): SerializedQrKeyringState => ({\n initialized: false,\n accounts: [],\n });\n\n/**\n * Normalizes an address to a 0x-prefixed checksum address.\n *\n * @param address - The address to normalize.\n * @returns The normalized address as a Hex string.\n */\nfunction normalizeAddress(address: string): Hex {\n return getChecksumAddress(add0x(address));\n}\n\nexport class QrKeyring implements Keyring {\n static type = QR_KEYRING_TYPE;\n\n readonly type = QR_KEYRING_TYPE;\n\n readonly bridge: QrKeyringBridge;\n\n readonly #signer: AirgappedSigner = new AirgappedSigner();\n\n #accounts: Hex[] = [];\n\n #accountToUnlock?: number | undefined;\n\n #currentPage: number = 0;\n\n constructor(options: QrKeyringOptions) {\n this.bridge = options.bridge;\n\n if (options?.ur) {\n this.submitUR(options.ur);\n }\n }\n\n /**\n * Serializes the QrKeyring state\n *\n * @returns The serialized state\n */\n async serialize(): Promise<SerializedQrKeyringState> {\n const source = this.#signer.getSourceDetails();\n\n if (\n !source ||\n ![KeyringMode.HD, KeyringMode.ACCOUNT].includes(source.keyringMode)\n ) {\n // the keyring has not initialized with a device source yet\n return getDefaultSerializedQrKeyringState();\n }\n\n const accounts = this.#accounts.slice();\n\n if (source.keyringMode === KeyringMode.HD) {\n // These properties are only relevant for HD Keys\n return {\n initialized: true,\n name: source.name,\n keyringMode: KeyringMode.HD,\n keyringAccount: source.keyringAccount,\n xfp: source.xfp,\n xpub: source.xpub,\n hdPath: source.hdPath,\n childrenPath: source.childrenPath,\n accounts,\n indexes: source.indexes,\n };\n }\n // These properties are only relevant for Account Keys\n return {\n initialized: true,\n name: source.name,\n keyringMode: KeyringMode.ACCOUNT,\n keyringAccount: source.keyringAccount,\n xfp: source.xfp,\n paths: source.paths,\n accounts,\n indexes: source.indexes,\n };\n }\n\n /**\n * Deserializes the QrKeyring state\n *\n * @param state - The serialized state to deserialize\n */\n async deserialize(state: SerializedQrKeyringState): Promise<void> {\n if (!state.initialized) {\n this.#accounts = [];\n this.#signer.clear();\n return;\n }\n\n this.#signer.init(state);\n this.#accounts = (state.accounts ?? []).map(normalizeAddress);\n }\n\n /**\n * Adds accounts to the QrKeyring\n *\n * @param accountsToAdd - The number of accounts to add\n * @returns The accounts added\n */\n async addAccounts(accountsToAdd: number): Promise<Hex[]> {\n const lastAccount = this.#accounts[this.#accounts.length - 1];\n const startIndex =\n this.#accountToUnlock ??\n (lastAccount ? this.#signer.indexFromAddress(lastAccount) : 0);\n const newAccounts: Hex[] = [];\n\n for (let i = 0; i < accountsToAdd; i++) {\n const index = startIndex + i;\n const address = this.#signer.addressFromIndex(index);\n\n if (this.#accounts.includes(address)) {\n continue;\n }\n\n this.#accounts.push(address);\n newAccounts.push(address);\n }\n\n this.#accountToUnlock = startIndex + accountsToAdd;\n return newAccounts;\n }\n\n /**\n * Gets the accounts in the QrKeyring\n *\n * @returns The accounts in the QrKeyring\n */\n async getAccounts(): Promise<Hex[]> {\n return Array.from(this.#accounts.values());\n }\n\n /**\n * Remove an account from the keyring\n *\n * @param address - The address of the account to remove\n */\n removeAccount(address: Hex): void {\n const normalizedAddress = normalizeAddress(address);\n this.#accounts = this.#accounts.filter(\n (account) => account !== normalizedAddress,\n );\n }\n\n /**\n * Submits a CBOR encoded UR to the QrKeyring\n *\n * @param ur - The CBOR encoded UR\n */\n submitUR(ur: string | SerializedUR): void {\n this.#signer.init(ur);\n }\n\n /**\n * Sets the next account index to unlock\n *\n * @param index - The index of the account to unlock\n */\n setAccountToUnlock(index: number): void {\n this.#accountToUnlock = index;\n }\n\n /**\n * Get the name of the paired device or the keyring type\n * if unavailable.\n *\n * @returns The name of the paired device or the keyring type.\n */\n getName(): string {\n const source = this.#signer.getSourceDetails();\n return source?.name ?? QR_KEYRING_TYPE;\n }\n\n /**\n * Fetch the first page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The first page of accounts as an array of Hex strings.\n */\n async getFirstPage(): Promise<IndexedAddress[]> {\n this.#currentPage = 0;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the next page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The next page of accounts as an array of IndexedAddress objects.\n */\n async getNextPage(): Promise<IndexedAddress[]> {\n this.#currentPage += 1;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the previous page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The previous page of accounts as an array of IndexedAddress objects.\n */\n async getPreviousPage(): Promise<IndexedAddress[]> {\n if (this.#currentPage > 0) {\n this.#currentPage -= 1;\n } else {\n this.#currentPage = 0;\n }\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the current page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The current page of accounts as an array of IndexedAddress objects.\n */\n async getCurrentPage(): Promise<IndexedAddress[]> {\n if (!this.#signer.isInitialized()) {\n await this.#scanAndInitialize();\n }\n\n return this.#signer.getAddressesPage(this.#currentPage);\n }\n\n /**\n * Clear the keyring state and forget any paired device or accounts.\n */\n async forgetDevice(): Promise<void> {\n this.#signer.clear();\n this.#accounts = [];\n this.#accountToUnlock = undefined;\n this.#currentPage = 0;\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 signer = this.#signer.getSourceDetails();\n if (!signer?.xfp) {\n throw new Error('Keyring is not initialized. Please scan a QR code.');\n }\n const dataType =\n transaction.type === TransactionType.Legacy\n ? DataType.transaction\n : DataType.typedTransaction;\n let messageToSign: Buffer;\n if (transaction.type === TransactionType.Legacy) {\n messageToSign = Buffer.from(RLP.encode(transaction.getMessageToSign()));\n } else {\n messageToSign = Buffer.from(\n (transaction as FeeMarketEIP1559Transaction).getMessageToSign(),\n );\n }\n\n const hdPath = this.#signer.pathFromAddress(address);\n const chainId = Number(transaction.common.chainId());\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n messageToSign,\n dataType,\n hdPath,\n signer.xfp,\n requestId,\n chainId,\n ).toUR();\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,\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 signer = this.#signer.getSourceDetails();\n if (!signer?.xfp) {\n throw new Error('Keyring is not initialized. Please scan a QR code.');\n }\n\n const hdPath = this.#signer.pathFromAddress(address);\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n Buffer.from(JSON.stringify(data), 'utf8'),\n DataType.typedData,\n hdPath,\n signer.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 requestTitle: DEFAULT_SCAN_REQUEST_TITLE,\n requestDescription: DEFAULT_SCAN_REQUEST_DESCRIPTION,\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 * Scan for a QR code and initialize the keyring with the\n * scanned UR.\n */\n async #scanAndInitialize(): Promise<void> {\n this.submitUR(\n await this.bridge.requestScan({ type: QrScanRequestType.PAIR }),\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.bridge.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"]}
|
package/dist/qr-keyring.d.cts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type TypedTransaction, type TypedTxData } from "@ethereumjs/tx";
|
|
2
|
+
import type { TypedMessage, MessageTypes } from "@metamask/eth-sig-util";
|
|
1
3
|
import type { Keyring } from "@metamask/keyring-utils";
|
|
2
|
-
import type
|
|
4
|
+
import { type Hex } from "@metamask/utils";
|
|
3
5
|
import { type AirgappedSignerDetails, type IndexedAddress } from "./airgapped-signer.cjs";
|
|
4
6
|
export declare const QR_KEYRING_TYPE = "QR Hardware Wallet Device";
|
|
5
7
|
export declare enum QrScanRequestType {
|
|
@@ -14,12 +16,22 @@ export declare enum QrScanRequestType {
|
|
|
14
16
|
*/
|
|
15
17
|
SIGN = "sign"
|
|
16
18
|
}
|
|
17
|
-
export type
|
|
19
|
+
export type QrSignatureRequest = {
|
|
20
|
+
requestId: string;
|
|
21
|
+
payload: SerializedUR;
|
|
22
|
+
requestTitle?: string;
|
|
23
|
+
requestDescription?: string;
|
|
24
|
+
};
|
|
25
|
+
export type QrScanRequest = {
|
|
26
|
+
type: QrScanRequestType;
|
|
27
|
+
request?: QrSignatureRequest;
|
|
28
|
+
};
|
|
29
|
+
export type SerializedUR = {
|
|
18
30
|
type: string;
|
|
19
|
-
cbor:
|
|
31
|
+
cbor: string;
|
|
20
32
|
};
|
|
21
33
|
export type QrKeyringBridge = {
|
|
22
|
-
requestScan: (
|
|
34
|
+
requestScan: (request: QrScanRequest) => Promise<SerializedUR>;
|
|
23
35
|
};
|
|
24
36
|
export type QrKeyringOptions = {
|
|
25
37
|
ur?: string;
|
|
@@ -87,7 +99,7 @@ export declare class QrKeyring implements Keyring {
|
|
|
87
99
|
*
|
|
88
100
|
* @param ur - The CBOR encoded UR
|
|
89
101
|
*/
|
|
90
|
-
submitUR(ur: string |
|
|
102
|
+
submitUR(ur: string | SerializedUR): void;
|
|
91
103
|
/**
|
|
92
104
|
* Sets the next account index to unlock
|
|
93
105
|
*
|
|
@@ -133,5 +145,24 @@ export declare class QrKeyring implements Keyring {
|
|
|
133
145
|
* Clear the keyring state and forget any paired device or accounts.
|
|
134
146
|
*/
|
|
135
147
|
forgetDevice(): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Sign a transaction. This is equivalent to the `eth_signTransaction`
|
|
150
|
+
* Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
|
|
151
|
+
* more details.
|
|
152
|
+
*
|
|
153
|
+
* @param address - The address of the account to use for signing.
|
|
154
|
+
* @param transaction - The transaction to sign.
|
|
155
|
+
* @returns The signed transaction.
|
|
156
|
+
*/
|
|
157
|
+
signTransaction(address: Hex, transaction: TypedTransaction): Promise<TypedTxData>;
|
|
158
|
+
/**
|
|
159
|
+
* Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
|
|
160
|
+
* JSON-RPC method.
|
|
161
|
+
*
|
|
162
|
+
* @param address - The address of the account to use for signing.
|
|
163
|
+
* @param data - The data to sign.
|
|
164
|
+
* @returns The signed message.
|
|
165
|
+
*/
|
|
166
|
+
signTypedData<Types extends MessageTypes>(address: Hex, data: TypedMessage<Types>): Promise<string>;
|
|
136
167
|
}
|
|
137
168
|
//# sourceMappingURL=qr-keyring.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qr-keyring.d.cts","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"qr-keyring.d.cts","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"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"}
|
package/dist/qr-keyring.d.mts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type TypedTransaction, type TypedTxData } from "@ethereumjs/tx";
|
|
2
|
+
import type { TypedMessage, MessageTypes } from "@metamask/eth-sig-util";
|
|
1
3
|
import type { Keyring } from "@metamask/keyring-utils";
|
|
2
|
-
import type
|
|
4
|
+
import { type Hex } from "@metamask/utils";
|
|
3
5
|
import { type AirgappedSignerDetails, type IndexedAddress } from "./airgapped-signer.mjs";
|
|
4
6
|
export declare const QR_KEYRING_TYPE = "QR Hardware Wallet Device";
|
|
5
7
|
export declare enum QrScanRequestType {
|
|
@@ -14,12 +16,22 @@ export declare enum QrScanRequestType {
|
|
|
14
16
|
*/
|
|
15
17
|
SIGN = "sign"
|
|
16
18
|
}
|
|
17
|
-
export type
|
|
19
|
+
export type QrSignatureRequest = {
|
|
20
|
+
requestId: string;
|
|
21
|
+
payload: SerializedUR;
|
|
22
|
+
requestTitle?: string;
|
|
23
|
+
requestDescription?: string;
|
|
24
|
+
};
|
|
25
|
+
export type QrScanRequest = {
|
|
26
|
+
type: QrScanRequestType;
|
|
27
|
+
request?: QrSignatureRequest;
|
|
28
|
+
};
|
|
29
|
+
export type SerializedUR = {
|
|
18
30
|
type: string;
|
|
19
|
-
cbor:
|
|
31
|
+
cbor: string;
|
|
20
32
|
};
|
|
21
33
|
export type QrKeyringBridge = {
|
|
22
|
-
requestScan: (
|
|
34
|
+
requestScan: (request: QrScanRequest) => Promise<SerializedUR>;
|
|
23
35
|
};
|
|
24
36
|
export type QrKeyringOptions = {
|
|
25
37
|
ur?: string;
|
|
@@ -87,7 +99,7 @@ export declare class QrKeyring implements Keyring {
|
|
|
87
99
|
*
|
|
88
100
|
* @param ur - The CBOR encoded UR
|
|
89
101
|
*/
|
|
90
|
-
submitUR(ur: string |
|
|
102
|
+
submitUR(ur: string | SerializedUR): void;
|
|
91
103
|
/**
|
|
92
104
|
* Sets the next account index to unlock
|
|
93
105
|
*
|
|
@@ -133,5 +145,24 @@ export declare class QrKeyring implements Keyring {
|
|
|
133
145
|
* Clear the keyring state and forget any paired device or accounts.
|
|
134
146
|
*/
|
|
135
147
|
forgetDevice(): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Sign a transaction. This is equivalent to the `eth_signTransaction`
|
|
150
|
+
* Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
|
|
151
|
+
* more details.
|
|
152
|
+
*
|
|
153
|
+
* @param address - The address of the account to use for signing.
|
|
154
|
+
* @param transaction - The transaction to sign.
|
|
155
|
+
* @returns The signed transaction.
|
|
156
|
+
*/
|
|
157
|
+
signTransaction(address: Hex, transaction: TypedTransaction): Promise<TypedTxData>;
|
|
158
|
+
/**
|
|
159
|
+
* Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
|
|
160
|
+
* JSON-RPC method.
|
|
161
|
+
*
|
|
162
|
+
* @param address - The address of the account to use for signing.
|
|
163
|
+
* @param data - The data to sign.
|
|
164
|
+
* @returns The signed message.
|
|
165
|
+
*/
|
|
166
|
+
signTypedData<Types extends MessageTypes>(address: Hex, data: TypedMessage<Types>): Promise<string>;
|
|
136
167
|
}
|
|
137
168
|
//# sourceMappingURL=qr-keyring.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qr-keyring.d.mts","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"qr-keyring.d.mts","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"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"}
|
package/dist/qr-keyring.mjs
CHANGED
|
@@ -9,10 +9,16 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
9
9
|
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");
|
|
10
10
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
11
|
};
|
|
12
|
-
var _QrKeyring_instances, _QrKeyring_signer, _QrKeyring_accounts, _QrKeyring_accountToUnlock, _QrKeyring_currentPage, _QrKeyring_scanAndInitialize;
|
|
12
|
+
var _QrKeyring_instances, _QrKeyring_signer, _QrKeyring_accounts, _QrKeyring_accountToUnlock, _QrKeyring_currentPage, _QrKeyring_scanAndInitialize, _QrKeyring_requestSignature;
|
|
13
|
+
import { RLP } from "@ethereumjs/rlp";
|
|
14
|
+
import { TransactionFactory, TransactionType } from "@ethereumjs/tx";
|
|
15
|
+
import { DataType, ETHSignature, EthSignRequest } from "@keystonehq/bc-ur-registry-eth";
|
|
13
16
|
import { add0x, getChecksumAddress } from "@metamask/utils";
|
|
17
|
+
import { stringify, v4 as uuidv4 } from "uuid";
|
|
14
18
|
import { AirgappedSigner, KeyringMode } from "./airgapped-signer.mjs";
|
|
15
19
|
export const QR_KEYRING_TYPE = 'QR Hardware Wallet Device';
|
|
20
|
+
const DEFAULT_SCAN_REQUEST_TITLE = 'Scan with your hardware wallet';
|
|
21
|
+
const DEFAULT_SCAN_REQUEST_DESCRIPTION = 'After your device has scanned this QR code, click on "Scan" to receive the information.';
|
|
16
22
|
export var QrScanRequestType;
|
|
17
23
|
(function (QrScanRequestType) {
|
|
18
24
|
/**
|
|
@@ -233,6 +239,83 @@ export class QrKeyring {
|
|
|
233
239
|
__classPrivateFieldSet(this, _QrKeyring_accountToUnlock, undefined, "f");
|
|
234
240
|
__classPrivateFieldSet(this, _QrKeyring_currentPage, 0, "f");
|
|
235
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Sign a transaction. This is equivalent to the `eth_signTransaction`
|
|
244
|
+
* Ethereum JSON-RPC method. See the Ethereum JSON-RPC API documentation for
|
|
245
|
+
* more details.
|
|
246
|
+
*
|
|
247
|
+
* @param address - The address of the account to use for signing.
|
|
248
|
+
* @param transaction - The transaction to sign.
|
|
249
|
+
* @returns The signed transaction.
|
|
250
|
+
*/
|
|
251
|
+
async signTransaction(address, transaction) {
|
|
252
|
+
const signer = __classPrivateFieldGet(this, _QrKeyring_signer, "f").getSourceDetails();
|
|
253
|
+
if (!signer?.xfp) {
|
|
254
|
+
throw new Error('Keyring is not initialized. Please scan a QR code.');
|
|
255
|
+
}
|
|
256
|
+
const dataType = transaction.type === TransactionType.Legacy
|
|
257
|
+
? DataType.transaction
|
|
258
|
+
: DataType.typedTransaction;
|
|
259
|
+
let messageToSign;
|
|
260
|
+
if (transaction.type === TransactionType.Legacy) {
|
|
261
|
+
messageToSign = Buffer.from(RLP.encode(transaction.getMessageToSign()));
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
messageToSign = Buffer.from(transaction.getMessageToSign());
|
|
265
|
+
}
|
|
266
|
+
const hdPath = __classPrivateFieldGet(this, _QrKeyring_signer, "f").pathFromAddress(address);
|
|
267
|
+
const chainId = Number(transaction.common.chainId());
|
|
268
|
+
const requestId = uuidv4();
|
|
269
|
+
const ethSignRequestUR = EthSignRequest.constructETHRequest(messageToSign, dataType, hdPath, signer.xfp, requestId, chainId).toUR();
|
|
270
|
+
const { r, s, v } = await __classPrivateFieldGet(this, _QrKeyring_instances, "m", _QrKeyring_requestSignature).call(this, {
|
|
271
|
+
requestId,
|
|
272
|
+
payload: {
|
|
273
|
+
type: ethSignRequestUR.type,
|
|
274
|
+
cbor: ethSignRequestUR.cbor.toString('hex'),
|
|
275
|
+
},
|
|
276
|
+
requestTitle: 'Scan with your hardware wallet',
|
|
277
|
+
requestDescription: 'After your device has signed this message, click on "Scan" to receive the signature',
|
|
278
|
+
});
|
|
279
|
+
return TransactionFactory.fromTxData({
|
|
280
|
+
...transaction,
|
|
281
|
+
r,
|
|
282
|
+
s,
|
|
283
|
+
v,
|
|
284
|
+
}, {
|
|
285
|
+
common: transaction.common,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Sign a message. This is equivalent to the `eth_signTypedData` v4 Ethereum
|
|
290
|
+
* JSON-RPC method.
|
|
291
|
+
*
|
|
292
|
+
* @param address - The address of the account to use for signing.
|
|
293
|
+
* @param data - The data to sign.
|
|
294
|
+
* @returns The signed message.
|
|
295
|
+
*/
|
|
296
|
+
async signTypedData(address, data) {
|
|
297
|
+
const signer = __classPrivateFieldGet(this, _QrKeyring_signer, "f").getSourceDetails();
|
|
298
|
+
if (!signer?.xfp) {
|
|
299
|
+
throw new Error('Keyring is not initialized. Please scan a QR code.');
|
|
300
|
+
}
|
|
301
|
+
const hdPath = __classPrivateFieldGet(this, _QrKeyring_signer, "f").pathFromAddress(address);
|
|
302
|
+
const requestId = uuidv4();
|
|
303
|
+
const ethSignRequestUR = EthSignRequest.constructETHRequest(Buffer.from(JSON.stringify(data), 'utf8'), DataType.typedData, hdPath, signer.xfp, requestId, undefined, address).toUR();
|
|
304
|
+
const { r, s, v } = await __classPrivateFieldGet(this, _QrKeyring_instances, "m", _QrKeyring_requestSignature).call(this, {
|
|
305
|
+
requestId,
|
|
306
|
+
payload: {
|
|
307
|
+
type: ethSignRequestUR.type,
|
|
308
|
+
cbor: ethSignRequestUR.cbor.toString('hex'),
|
|
309
|
+
},
|
|
310
|
+
requestTitle: DEFAULT_SCAN_REQUEST_TITLE,
|
|
311
|
+
requestDescription: DEFAULT_SCAN_REQUEST_DESCRIPTION,
|
|
312
|
+
});
|
|
313
|
+
return add0x(Buffer.concat([
|
|
314
|
+
Uint8Array.from(r),
|
|
315
|
+
Uint8Array.from(s),
|
|
316
|
+
Uint8Array.from(v),
|
|
317
|
+
]).toString('hex'));
|
|
318
|
+
}
|
|
236
319
|
}
|
|
237
320
|
_QrKeyring_signer = new WeakMap(), _QrKeyring_accounts = new WeakMap(), _QrKeyring_accountToUnlock = new WeakMap(), _QrKeyring_currentPage = new WeakMap(), _QrKeyring_instances = new WeakSet(), _QrKeyring_scanAndInitialize =
|
|
238
321
|
/**
|
|
@@ -240,7 +323,33 @@ _QrKeyring_signer = new WeakMap(), _QrKeyring_accounts = new WeakMap(), _QrKeyri
|
|
|
240
323
|
* scanned UR.
|
|
241
324
|
*/
|
|
242
325
|
async function _QrKeyring_scanAndInitialize() {
|
|
243
|
-
this.submitUR(await this.bridge.requestScan(QrScanRequestType.PAIR));
|
|
326
|
+
this.submitUR(await this.bridge.requestScan({ type: QrScanRequestType.PAIR }));
|
|
327
|
+
}, _QrKeyring_requestSignature =
|
|
328
|
+
/**
|
|
329
|
+
* Request a signature for a transaction or message.
|
|
330
|
+
*
|
|
331
|
+
* @param request - The signature request containing the data to sign.
|
|
332
|
+
* @returns The signature as an object containing r, s, and v values.
|
|
333
|
+
*/
|
|
334
|
+
async function _QrKeyring_requestSignature(request) {
|
|
335
|
+
const response = await this.bridge.requestScan({
|
|
336
|
+
type: QrScanRequestType.SIGN,
|
|
337
|
+
request,
|
|
338
|
+
});
|
|
339
|
+
const signatureEnvelope = ETHSignature.fromCBOR(Buffer.from(response.cbor, 'hex'));
|
|
340
|
+
const signature = signatureEnvelope.getSignature();
|
|
341
|
+
const requestId = signatureEnvelope.getRequestId();
|
|
342
|
+
if (!requestId) {
|
|
343
|
+
throw new Error('Signature request ID is missing.');
|
|
344
|
+
}
|
|
345
|
+
if (request.requestId !== stringify(requestId)) {
|
|
346
|
+
throw new Error(`Signature request ID mismatch. Expected: ${request.requestId}, received: ${requestId.toString('hex')}`);
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
r: signature.subarray(0, 32),
|
|
350
|
+
s: signature.subarray(32, 64),
|
|
351
|
+
v: signature.subarray(64),
|
|
352
|
+
};
|
|
244
353
|
};
|
|
245
354
|
QrKeyring.type = QR_KEYRING_TYPE;
|
|
246
355
|
//# sourceMappingURL=qr-keyring.mjs.map
|
package/dist/qr-keyring.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qr-keyring.mjs","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Keyring } from '@metamask/keyring-utils';\nimport type { Hex } from '@metamask/utils';\nimport { add0x, getChecksumAddress } from '@metamask/utils';\n\nimport {\n type AirgappedSignerDetails,\n type IndexedAddress,\n AirgappedSigner,\n KeyringMode,\n} from './airgapped-signer';\n\nexport const QR_KEYRING_TYPE = 'QR Hardware Wallet Device';\n\nexport enum QrScanRequestType {\n /**\n * Request a scan for a QR code containing a UR\n * with information related to a hardware wallet.\n */\n PAIR = 'pair',\n /**\n * Request a scan for a QR code containing a\n * UR-encoded transaction signature.\n */\n SIGN = 'sign',\n}\n\nexport type QrScanResponse = {\n type: string;\n cbor: Hex;\n};\n\nexport type QrKeyringBridge = {\n requestScan: (type: QrScanRequestType) => Promise<QrScanResponse>;\n};\n\nexport type QrKeyringOptions = {\n ur?: string;\n bridge: QrKeyringBridge;\n};\n\n/**\n * The state of the QrKeyring\n *\n * @property accounts - The accounts in the QrKeyring\n */\nexport type SerializedQrKeyringState = {\n version?: number;\n accounts?: string[];\n currentAccount?: number;\n} & (\n | {\n initialized?: false;\n }\n | ({\n initialized: true;\n } & AirgappedSignerDetails)\n);\n\n/**\n * Returns the default serialized state of the QrKeyring.\n *\n * @returns The default serialized state.\n */\nexport const getDefaultSerializedQrKeyringState =\n (): SerializedQrKeyringState => ({\n initialized: false,\n accounts: [],\n });\n\n/**\n * Normalizes an address to a 0x-prefixed checksum address.\n *\n * @param address - The address to normalize.\n * @returns The normalized address as a Hex string.\n */\nfunction normalizeAddress(address: string): Hex {\n return getChecksumAddress(add0x(address));\n}\n\nexport class QrKeyring implements Keyring {\n static type = QR_KEYRING_TYPE;\n\n readonly type = QR_KEYRING_TYPE;\n\n readonly bridge: QrKeyringBridge;\n\n readonly #signer: AirgappedSigner = new AirgappedSigner();\n\n #accounts: Hex[] = [];\n\n #accountToUnlock?: number | undefined;\n\n #currentPage: number = 0;\n\n constructor(options: QrKeyringOptions) {\n this.bridge = options.bridge;\n\n if (options?.ur) {\n this.submitUR(options.ur);\n }\n }\n\n /**\n * Serializes the QrKeyring state\n *\n * @returns The serialized state\n */\n async serialize(): Promise<SerializedQrKeyringState> {\n const source = this.#signer.getSourceDetails();\n\n if (\n !source ||\n ![KeyringMode.HD, KeyringMode.ACCOUNT].includes(source.keyringMode)\n ) {\n // the keyring has not initialized with a device source yet\n return getDefaultSerializedQrKeyringState();\n }\n\n const accounts = this.#accounts.slice();\n\n if (source.keyringMode === KeyringMode.HD) {\n // These properties are only relevant for HD Keys\n return {\n initialized: true,\n name: source.name,\n keyringMode: KeyringMode.HD,\n keyringAccount: source.keyringAccount,\n xfp: source.xfp,\n xpub: source.xpub,\n hdPath: source.hdPath,\n childrenPath: source.childrenPath,\n accounts,\n indexes: source.indexes,\n };\n }\n // These properties are only relevant for Account Keys\n return {\n initialized: true,\n name: source.name,\n keyringMode: KeyringMode.ACCOUNT,\n keyringAccount: source.keyringAccount,\n xfp: source.xfp,\n paths: source.paths,\n accounts,\n indexes: source.indexes,\n };\n }\n\n /**\n * Deserializes the QrKeyring state\n *\n * @param state - The serialized state to deserialize\n */\n async deserialize(state: SerializedQrKeyringState): Promise<void> {\n if (!state.initialized) {\n this.#accounts = [];\n this.#signer.clear();\n return;\n }\n\n this.#signer.init(state);\n this.#accounts = (state.accounts ?? []).map(normalizeAddress);\n }\n\n /**\n * Adds accounts to the QrKeyring\n *\n * @param accountsToAdd - The number of accounts to add\n * @returns The accounts added\n */\n async addAccounts(accountsToAdd: number): Promise<Hex[]> {\n const lastAccount = this.#accounts[this.#accounts.length - 1];\n const startIndex =\n this.#accountToUnlock ??\n (lastAccount ? this.#signer.indexFromAddress(lastAccount) : 0);\n const newAccounts: Hex[] = [];\n\n for (let i = 0; i < accountsToAdd; i++) {\n const index = startIndex + i;\n const address = this.#signer.addressFromIndex(index);\n\n if (this.#accounts.includes(address)) {\n continue;\n }\n\n this.#accounts.push(address);\n newAccounts.push(address);\n }\n\n this.#accountToUnlock = startIndex + accountsToAdd;\n return newAccounts;\n }\n\n /**\n * Gets the accounts in the QrKeyring\n *\n * @returns The accounts in the QrKeyring\n */\n async getAccounts(): Promise<Hex[]> {\n return Array.from(this.#accounts.values());\n }\n\n /**\n * Remove an account from the keyring\n *\n * @param address - The address of the account to remove\n */\n removeAccount(address: Hex): void {\n const normalizedAddress = normalizeAddress(address);\n this.#accounts = this.#accounts.filter(\n (account) => account !== normalizedAddress,\n );\n }\n\n /**\n * Submits a CBOR encoded UR to the QrKeyring\n *\n * @param ur - The CBOR encoded UR\n */\n submitUR(ur: string | QrScanResponse): void {\n this.#signer.init(ur);\n }\n\n /**\n * Sets the next account index to unlock\n *\n * @param index - The index of the account to unlock\n */\n setAccountToUnlock(index: number): void {\n this.#accountToUnlock = index;\n }\n\n /**\n * Get the name of the paired device or the keyring type\n * if unavailable.\n *\n * @returns The name of the paired device or the keyring type.\n */\n getName(): string {\n const source = this.#signer.getSourceDetails();\n return source?.name ?? QR_KEYRING_TYPE;\n }\n\n /**\n * Fetch the first page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The first page of accounts as an array of Hex strings.\n */\n async getFirstPage(): Promise<IndexedAddress[]> {\n this.#currentPage = 0;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the next page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The next page of accounts as an array of IndexedAddress objects.\n */\n async getNextPage(): Promise<IndexedAddress[]> {\n this.#currentPage += 1;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the previous page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The previous page of accounts as an array of IndexedAddress objects.\n */\n async getPreviousPage(): Promise<IndexedAddress[]> {\n if (this.#currentPage > 0) {\n this.#currentPage -= 1;\n } else {\n this.#currentPage = 0;\n }\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the current page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The current page of accounts as an array of IndexedAddress objects.\n */\n async getCurrentPage(): Promise<IndexedAddress[]> {\n if (!this.#signer.isInitialized()) {\n await this.#scanAndInitialize();\n }\n\n return this.#signer.getAddressesPage(this.#currentPage);\n }\n\n /**\n * Clear the keyring state and forget any paired device or accounts.\n */\n async forgetDevice(): Promise<void> {\n this.#signer.clear();\n this.#accounts = [];\n this.#accountToUnlock = undefined;\n this.#currentPage = 0;\n }\n\n /**\n * Scan for a QR code and initialize the keyring with the\n * scanned UR.\n */\n async #scanAndInitialize(): Promise<void> {\n this.submitUR(await this.bridge.requestScan(QrScanRequestType.PAIR));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"qr-keyring.mjs","sourceRoot":"","sources":["../src/qr-keyring.ts"],"names":[],"mappings":"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","sourcesContent":["import { RLP } from '@ethereumjs/rlp';\nimport {\n type FeeMarketEIP1559Transaction,\n TransactionFactory,\n TransactionType,\n type TypedTransaction,\n type TypedTxData,\n} from '@ethereumjs/tx';\nimport {\n DataType,\n ETHSignature,\n EthSignRequest,\n} from '@keystonehq/bc-ur-registry-eth';\nimport type { TypedMessage, MessageTypes } from '@metamask/eth-sig-util';\nimport type { Keyring } from '@metamask/keyring-utils';\nimport { type Hex, add0x, getChecksumAddress } from '@metamask/utils';\nimport { stringify, v4 as uuidv4 } from 'uuid';\n\nimport {\n type AirgappedSignerDetails,\n type IndexedAddress,\n AirgappedSigner,\n KeyringMode,\n} from './airgapped-signer';\n\nexport const QR_KEYRING_TYPE = 'QR Hardware Wallet Device';\n\nconst DEFAULT_SCAN_REQUEST_TITLE = 'Scan with your hardware wallet';\n\nconst DEFAULT_SCAN_REQUEST_DESCRIPTION =\n 'After your device has scanned this QR code, click on \"Scan\" to receive the information.';\n\nexport enum QrScanRequestType {\n /**\n * Request a scan for a QR code containing a UR\n * with information related to a hardware wallet.\n */\n PAIR = 'pair',\n /**\n * Request a scan for a QR code containing a\n * UR-encoded transaction signature.\n */\n SIGN = 'sign',\n}\n\nexport type QrSignatureRequest = {\n requestId: string;\n payload: SerializedUR;\n requestTitle?: string;\n requestDescription?: string;\n};\n\nexport type QrScanRequest = {\n type: QrScanRequestType;\n request?: QrSignatureRequest;\n};\n\nexport type SerializedUR = {\n type: string;\n cbor: string;\n};\n\nexport type QrKeyringBridge = {\n requestScan: (request: QrScanRequest) => Promise<SerializedUR>;\n};\n\nexport type QrKeyringOptions = {\n ur?: string;\n bridge: QrKeyringBridge;\n};\n\n/**\n * The state of the QrKeyring\n *\n * @property accounts - The accounts in the QrKeyring\n */\nexport type SerializedQrKeyringState = {\n version?: number;\n accounts?: string[];\n currentAccount?: number;\n} & (\n | {\n initialized?: false;\n }\n | ({\n initialized: true;\n } & AirgappedSignerDetails)\n);\n\n/**\n * Returns the default serialized state of the QrKeyring.\n *\n * @returns The default serialized state.\n */\nexport const getDefaultSerializedQrKeyringState =\n (): SerializedQrKeyringState => ({\n initialized: false,\n accounts: [],\n });\n\n/**\n * Normalizes an address to a 0x-prefixed checksum address.\n *\n * @param address - The address to normalize.\n * @returns The normalized address as a Hex string.\n */\nfunction normalizeAddress(address: string): Hex {\n return getChecksumAddress(add0x(address));\n}\n\nexport class QrKeyring implements Keyring {\n static type = QR_KEYRING_TYPE;\n\n readonly type = QR_KEYRING_TYPE;\n\n readonly bridge: QrKeyringBridge;\n\n readonly #signer: AirgappedSigner = new AirgappedSigner();\n\n #accounts: Hex[] = [];\n\n #accountToUnlock?: number | undefined;\n\n #currentPage: number = 0;\n\n constructor(options: QrKeyringOptions) {\n this.bridge = options.bridge;\n\n if (options?.ur) {\n this.submitUR(options.ur);\n }\n }\n\n /**\n * Serializes the QrKeyring state\n *\n * @returns The serialized state\n */\n async serialize(): Promise<SerializedQrKeyringState> {\n const source = this.#signer.getSourceDetails();\n\n if (\n !source ||\n ![KeyringMode.HD, KeyringMode.ACCOUNT].includes(source.keyringMode)\n ) {\n // the keyring has not initialized with a device source yet\n return getDefaultSerializedQrKeyringState();\n }\n\n const accounts = this.#accounts.slice();\n\n if (source.keyringMode === KeyringMode.HD) {\n // These properties are only relevant for HD Keys\n return {\n initialized: true,\n name: source.name,\n keyringMode: KeyringMode.HD,\n keyringAccount: source.keyringAccount,\n xfp: source.xfp,\n xpub: source.xpub,\n hdPath: source.hdPath,\n childrenPath: source.childrenPath,\n accounts,\n indexes: source.indexes,\n };\n }\n // These properties are only relevant for Account Keys\n return {\n initialized: true,\n name: source.name,\n keyringMode: KeyringMode.ACCOUNT,\n keyringAccount: source.keyringAccount,\n xfp: source.xfp,\n paths: source.paths,\n accounts,\n indexes: source.indexes,\n };\n }\n\n /**\n * Deserializes the QrKeyring state\n *\n * @param state - The serialized state to deserialize\n */\n async deserialize(state: SerializedQrKeyringState): Promise<void> {\n if (!state.initialized) {\n this.#accounts = [];\n this.#signer.clear();\n return;\n }\n\n this.#signer.init(state);\n this.#accounts = (state.accounts ?? []).map(normalizeAddress);\n }\n\n /**\n * Adds accounts to the QrKeyring\n *\n * @param accountsToAdd - The number of accounts to add\n * @returns The accounts added\n */\n async addAccounts(accountsToAdd: number): Promise<Hex[]> {\n const lastAccount = this.#accounts[this.#accounts.length - 1];\n const startIndex =\n this.#accountToUnlock ??\n (lastAccount ? this.#signer.indexFromAddress(lastAccount) : 0);\n const newAccounts: Hex[] = [];\n\n for (let i = 0; i < accountsToAdd; i++) {\n const index = startIndex + i;\n const address = this.#signer.addressFromIndex(index);\n\n if (this.#accounts.includes(address)) {\n continue;\n }\n\n this.#accounts.push(address);\n newAccounts.push(address);\n }\n\n this.#accountToUnlock = startIndex + accountsToAdd;\n return newAccounts;\n }\n\n /**\n * Gets the accounts in the QrKeyring\n *\n * @returns The accounts in the QrKeyring\n */\n async getAccounts(): Promise<Hex[]> {\n return Array.from(this.#accounts.values());\n }\n\n /**\n * Remove an account from the keyring\n *\n * @param address - The address of the account to remove\n */\n removeAccount(address: Hex): void {\n const normalizedAddress = normalizeAddress(address);\n this.#accounts = this.#accounts.filter(\n (account) => account !== normalizedAddress,\n );\n }\n\n /**\n * Submits a CBOR encoded UR to the QrKeyring\n *\n * @param ur - The CBOR encoded UR\n */\n submitUR(ur: string | SerializedUR): void {\n this.#signer.init(ur);\n }\n\n /**\n * Sets the next account index to unlock\n *\n * @param index - The index of the account to unlock\n */\n setAccountToUnlock(index: number): void {\n this.#accountToUnlock = index;\n }\n\n /**\n * Get the name of the paired device or the keyring type\n * if unavailable.\n *\n * @returns The name of the paired device or the keyring type.\n */\n getName(): string {\n const source = this.#signer.getSourceDetails();\n return source?.name ?? QR_KEYRING_TYPE;\n }\n\n /**\n * Fetch the first page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The first page of accounts as an array of Hex strings.\n */\n async getFirstPage(): Promise<IndexedAddress[]> {\n this.#currentPage = 0;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the next page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The next page of accounts as an array of IndexedAddress objects.\n */\n async getNextPage(): Promise<IndexedAddress[]> {\n this.#currentPage += 1;\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the previous page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The previous page of accounts as an array of IndexedAddress objects.\n */\n async getPreviousPage(): Promise<IndexedAddress[]> {\n if (this.#currentPage > 0) {\n this.#currentPage -= 1;\n } else {\n this.#currentPage = 0;\n }\n return this.getCurrentPage();\n }\n\n /**\n * Fetch the current page of accounts. If the keyring is not currently initialized,\n * it will trigger a scan request to initialize it.\n *\n * @returns The current page of accounts as an array of IndexedAddress objects.\n */\n async getCurrentPage(): Promise<IndexedAddress[]> {\n if (!this.#signer.isInitialized()) {\n await this.#scanAndInitialize();\n }\n\n return this.#signer.getAddressesPage(this.#currentPage);\n }\n\n /**\n * Clear the keyring state and forget any paired device or accounts.\n */\n async forgetDevice(): Promise<void> {\n this.#signer.clear();\n this.#accounts = [];\n this.#accountToUnlock = undefined;\n this.#currentPage = 0;\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 signer = this.#signer.getSourceDetails();\n if (!signer?.xfp) {\n throw new Error('Keyring is not initialized. Please scan a QR code.');\n }\n const dataType =\n transaction.type === TransactionType.Legacy\n ? DataType.transaction\n : DataType.typedTransaction;\n let messageToSign: Buffer;\n if (transaction.type === TransactionType.Legacy) {\n messageToSign = Buffer.from(RLP.encode(transaction.getMessageToSign()));\n } else {\n messageToSign = Buffer.from(\n (transaction as FeeMarketEIP1559Transaction).getMessageToSign(),\n );\n }\n\n const hdPath = this.#signer.pathFromAddress(address);\n const chainId = Number(transaction.common.chainId());\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n messageToSign,\n dataType,\n hdPath,\n signer.xfp,\n requestId,\n chainId,\n ).toUR();\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,\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 signer = this.#signer.getSourceDetails();\n if (!signer?.xfp) {\n throw new Error('Keyring is not initialized. Please scan a QR code.');\n }\n\n const hdPath = this.#signer.pathFromAddress(address);\n const requestId = uuidv4();\n const ethSignRequestUR = EthSignRequest.constructETHRequest(\n Buffer.from(JSON.stringify(data), 'utf8'),\n DataType.typedData,\n hdPath,\n signer.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 requestTitle: DEFAULT_SCAN_REQUEST_TITLE,\n requestDescription: DEFAULT_SCAN_REQUEST_DESCRIPTION,\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 * Scan for a QR code and initialize the keyring with the\n * scanned UR.\n */\n async #scanAndInitialize(): Promise<void> {\n this.submitUR(\n await this.bridge.requestScan({ type: QrScanRequestType.PAIR }),\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.bridge.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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/eth-qr-keyring",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-e4f6caa",
|
|
4
4
|
"description": "A simple standard interface for a series of Ethereum private keys.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ethereum",
|
|
@@ -48,11 +48,15 @@
|
|
|
48
48
|
"test:watch": "jest --watch"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
+
"@ethereumjs/rlp": "^5.0.2",
|
|
52
|
+
"@ethereumjs/tx": "^5.4.0",
|
|
51
53
|
"@ethereumjs/util": "^9.1.0",
|
|
52
54
|
"@keystonehq/bc-ur-registry-eth": "^0.19.1",
|
|
55
|
+
"@metamask/eth-sig-util": "^8.2.0",
|
|
53
56
|
"@metamask/keyring-utils": "3.0.0",
|
|
54
57
|
"@metamask/utils": "^11.1.0",
|
|
55
|
-
"hdkey": "^2.1.0"
|
|
58
|
+
"hdkey": "^2.1.0",
|
|
59
|
+
"uuid": "^9.0.1"
|
|
56
60
|
},
|
|
57
61
|
"devDependencies": {
|
|
58
62
|
"@keystonehq/base-eth-keyring": "^0.15.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"qr-keyring-bridge.cjs","sourceRoot":"","sources":["../src/qr-keyring-bridge.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAgBA;;;GAGG;AACH,MAAa,sBAAsB;IAGjC,YAAY,EAAE,WAAW,EAAiC;QAFjD,sDAAmE;QAG1E,uBAAA,IAAI,uCAAgB,WAAW,MAAA,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,IAAuB;QACvC,OAAO,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,IAAI,CAAC,CAAC;IACjC,CAAC;CACF;AAhBD,wDAgBC","sourcesContent":["import type {\n QrKeyringBridge,\n QrScanRequestType,\n QrScanResponse,\n} from './qr-keyring';\n\n/**\n * Options for the QrKeyringScannerBridge.\n */\nexport type QrKeyringScannerBridgeOptions = {\n /**\n * An injected function that the bridge uses to request a QR code scan.\n */\n requestScan: (type: QrScanRequestType) => Promise<QrScanResponse>;\n};\n\n/**\n * A bridge that allows the QrKeyring to request a QR code scan\n * while keeping the implementation defined by the QrKeyring consumer.\n */\nexport class QrKeyringScannerBridge implements QrKeyringBridge {\n readonly #requestScan: (type: QrScanRequestType) => Promise<QrScanResponse>;\n\n constructor({ requestScan }: QrKeyringScannerBridgeOptions) {\n this.#requestScan = requestScan;\n }\n\n /**\n * Requests a QR code scan and returns the scanned data.\n *\n * @param type - The type of QR scan request.\n * @returns The scanned data as a string.\n */\n async requestScan(type: QrScanRequestType): Promise<QrScanResponse> {\n return this.#requestScan(type);\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"qr-keyring-bridge.d.cts","sourceRoot":"","sources":["../src/qr-keyring-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,cAAc,EACf,yBAAqB;AAEtB;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;OAEG;IACH,WAAW,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACnE,CAAC;AAEF;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,eAAe;;gBAGhD,EAAE,WAAW,EAAE,EAAE,6BAA6B;IAI1D;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC;CAGpE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"qr-keyring-bridge.d.mts","sourceRoot":"","sources":["../src/qr-keyring-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,cAAc,EACf,yBAAqB;AAEtB;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;OAEG;IACH,WAAW,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACnE,CAAC;AAEF;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,eAAe;;gBAGhD,EAAE,WAAW,EAAE,EAAE,6BAA6B;IAI1D;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC;CAGpE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"qr-keyring-bridge.mjs","sourceRoot":"","sources":["../src/qr-keyring-bridge.ts"],"names":[],"mappings":";;;;;;;;;;;;AAgBA;;;GAGG;AACH,MAAM,OAAO,sBAAsB;IAGjC,YAAY,EAAE,WAAW,EAAiC;QAFjD,sDAAmE;QAG1E,uBAAA,IAAI,uCAAgB,WAAW,MAAA,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,IAAuB;QACvC,OAAO,uBAAA,IAAI,2CAAa,MAAjB,IAAI,EAAc,IAAI,CAAC,CAAC;IACjC,CAAC;CACF","sourcesContent":["import type {\n QrKeyringBridge,\n QrScanRequestType,\n QrScanResponse,\n} from './qr-keyring';\n\n/**\n * Options for the QrKeyringScannerBridge.\n */\nexport type QrKeyringScannerBridgeOptions = {\n /**\n * An injected function that the bridge uses to request a QR code scan.\n */\n requestScan: (type: QrScanRequestType) => Promise<QrScanResponse>;\n};\n\n/**\n * A bridge that allows the QrKeyring to request a QR code scan\n * while keeping the implementation defined by the QrKeyring consumer.\n */\nexport class QrKeyringScannerBridge implements QrKeyringBridge {\n readonly #requestScan: (type: QrScanRequestType) => Promise<QrScanResponse>;\n\n constructor({ requestScan }: QrKeyringScannerBridgeOptions) {\n this.#requestScan = requestScan;\n }\n\n /**\n * Requests a QR code scan and returns the scanned data.\n *\n * @param type - The type of QR scan request.\n * @returns The scanned data as a string.\n */\n async requestScan(type: QrScanRequestType): Promise<QrScanResponse> {\n return this.#requestScan(type);\n }\n}\n"]}
|