@metamask/eth-trezor-keyring
Version:
A MetaMask compatible keyring, for trezor hardware wallets
1 lines • 21.5 kB
Source Map (JSON)
{"version":3,"file":"trezor-keyring.cjs","sourceRoot":"","sources":["../../src/v2/trezor-keyring.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Bip44Account } from '@metamask/account-api';\nimport {\n EthAccountType,\n EthMethod,\n EthScope,\n KeyringAccountEntropyTypeOption,\n} from '@metamask/keyring-api';\nimport type { KeyringAccount, EntropySourceId } from '@metamask/keyring-api';\nimport { KeyringType } from '@metamask/keyring-api/v2';\nimport type {\n CreateAccountOptions,\n KeyringCapabilities,\n Keyring,\n} from '@metamask/keyring-api/v2';\nimport { EthKeyringWrapper } from '@metamask/keyring-sdk/v2';\nimport type { AccountId, EthKeyring } from '@metamask/keyring-utils';\nimport type { Hex, Json } from '@metamask/utils';\n\nimport type { TrezorBridge } from '../trezor-bridge';\nimport type {\n AccountPage,\n TrezorKeyring as LegacyTrezorKeyring,\n} from '../trezor-keyring';\n\n/**\n * Methods supported by Trezor keyring EOA accounts.\n * Trezor keyrings support a subset of signing methods (no encryption, app keys, or EIP-7702).\n */\nconst TREZOR_KEYRING_METHODS = [\n EthMethod.SignTransaction,\n EthMethod.PersonalSign,\n EthMethod.SignTypedDataV3,\n EthMethod.SignTypedDataV4,\n];\n\nconst trezorKeyringCapabilities: KeyringCapabilities = {\n scopes: [EthScope.Eoa],\n bip44: {\n deriveIndex: true,\n derivePath: true,\n },\n};\n\n/**\n * BIP-44 standard HD path prefix constant for Ethereum.\n * Used as default for derive-index operations.\n */\nexport const BIP44_HD_PATH_PREFIX = `m/44'/60'/0'/0`;\n\n/**\n * SLIP-0044 testnet HD path prefix constant.\n */\nexport const SLIP0044_TESTNET_PATH_PREFIX = `m/44'/1'/0'/0`;\n\n/**\n * Legacy MEW (MyEtherWallet) HD path prefix constant.\n */\nexport const LEGACY_MEW_PATH_PREFIX = `m/44'/60'/0'`;\n\n/**\n * Allowed HD paths for Trezor keyring.\n * These must match the keys in ALLOWED_HD_PATHS from trezor-keyring.ts.\n */\nconst ALLOWED_HD_PATHS = [\n BIP44_HD_PATH_PREFIX,\n SLIP0044_TESTNET_PATH_PREFIX,\n LEGACY_MEW_PATH_PREFIX,\n] as const;\n\n/**\n * Type representing one of the allowed Trezor HD paths.\n * Used by inner.setHdPath which expects one of these specific paths.\n */\ntype AllowedHdPath = (typeof ALLOWED_HD_PATHS)[number];\n\n/**\n * Regex pattern for validating and parsing Trezor derivation paths.\n * Matches BIP-44 style paths: m/44'/{coin}'/{segments}/{index}\n * where coin is 60' (Ethereum) or 1' (testnet).\n * Captures: [1] = base path prefix, [2] = index\n * The prefix is then validated against ALLOWED_HD_PATHS.\n */\nconst DERIVATION_PATH_PATTERN = /^(m\\/44'\\/(?:60'|1')(?:\\/\\d+'?)*)\\/(\\d+)$/u;\n\n/**\n * Concrete {@link Keyring} adapter for {@link TrezorKeyring}.\n *\n * This wrapper exposes the accounts and signing capabilities of the legacy\n * Trezor keyring via the unified V2 interface.\n *\n * All Trezor keyring accounts are BIP-44 derived from the device.\n */\nexport type TrezorKeyringOptions = {\n legacyKeyring: LegacyTrezorKeyring;\n entropySource: EntropySourceId;\n type?: KeyringType.Trezor | KeyringType.OneKey;\n};\n\n// LegacyTrezorKeyring.signTransaction returns `TypedTransaction | OldEthJsTransaction` for\n// backwards compatibility with old ethereumjs-tx, but EthKeyring expects `TypedTxData`.\n// The runtime behavior is correct - we cast the type to satisfy the constraint.\ntype TrezorKeyringAsEthKeyring = LegacyTrezorKeyring & EthKeyring;\n\nexport class TrezorKeyring\n extends EthKeyringWrapper<\n TrezorKeyringAsEthKeyring,\n Bip44Account<KeyringAccount>\n >\n implements Keyring\n{\n readonly entropySource: EntropySourceId;\n\n constructor(options: TrezorKeyringOptions) {\n super({\n type: options.type ?? KeyringType.Trezor,\n inner: options.legacyKeyring as TrezorKeyringAsEthKeyring,\n capabilities: trezorKeyringCapabilities,\n });\n this.entropySource = options.entropySource;\n }\n\n /**\n * Hydrate the underlying keyring from a previously serialized state.\n *\n * Overrides the base class implementation to avoid calling `getAccounts()`\n * when the Trezor device is locked. The base class calls `getAccounts()` to\n * rebuild the registry, but for Trezor keyrings this requires the HDKey to\n * be initialized (via `unlock()`). Since the device may not be connected\n * during deserialization, we skip the registry rebuild here. The registry\n * will be populated on the first call to `getAccounts()` after the device\n * is unlocked.\n *\n * @param state - The serialized keyring state.\n */\n async deserialize(state: Json): Promise<void> {\n await this.withLock(async () => {\n // Clear the registry when deserializing\n this.registry.clear();\n\n // Deserialize the legacy keyring state only.\n // We intentionally skip calling getAccounts() here because the Trezor\n // device may be locked (HDKey not initialized). The TrezorKeyring's\n // deserialize restores the accounts array, but not the paths map, so\n // getIndexForAddress would need to derive addresses which requires an\n // initialized HDKey. The registry will be populated lazily when\n // getAccounts() is called after the device is unlocked.\n await this.inner.deserialize(state);\n });\n }\n\n /**\n * Parses a derivation path to extract the base HD path and account index.\n *\n * Supports the allowed Trezor paths:\n * - m/44'/60'/0'/0/{index} (BIP44 standard)\n * - m/44'/60'/0'/{index} (legacy MEW)\n * - m/44'/1'/0'/0/{index} (SLIP0044 testnet)\n *\n * @param derivationPath - The full derivation path (e.g., m/44'/60'/0'/0/5).\n * @returns The base HD path and account index.\n * @throws If the path format is invalid or not an allowed Trezor path.\n */\n #parseDerivationPath(derivationPath: string): {\n basePath: AllowedHdPath;\n index: number;\n } {\n const match = derivationPath.match(DERIVATION_PATH_PATTERN);\n if (!match?.[1] || !match[2]) {\n throw new Error(\n `Invalid derivation path: ${derivationPath}. ` +\n `Expected format: {base}/{index} where base is one of: ` +\n `${ALLOWED_HD_PATHS.join(', ')}.`,\n );\n }\n\n const basePath = match[1];\n const index = parseInt(match[2], 10);\n\n // Validate the base path is one of the allowed paths\n if (!ALLOWED_HD_PATHS.includes(basePath as AllowedHdPath)) {\n throw new Error(\n `Invalid derivation path: ${derivationPath}. ` +\n `Expected format: {base}/{index} where base is one of: ` +\n `${ALLOWED_HD_PATHS.join(', ')}.`,\n );\n }\n\n return {\n basePath: basePath as AllowedHdPath,\n index,\n };\n }\n\n /**\n * Creates a Bip44Account object for the given address.\n *\n * @param address - The account address.\n * @param addressIndex - The account index in the derivation path.\n * @returns The created Bip44Account.\n */\n #createKeyringAccount(\n address: Hex,\n addressIndex: number,\n ): Bip44Account<KeyringAccount> {\n const id = this.registry.register(address);\n const derivationPath = `${this.inner.hdPath}/${addressIndex}`;\n\n const account: Bip44Account<KeyringAccount> = {\n id,\n type: EthAccountType.Eoa,\n address,\n scopes: [...this.capabilities.scopes],\n methods: [...TREZOR_KEYRING_METHODS],\n options: {\n entropy: {\n type: KeyringAccountEntropyTypeOption.Mnemonic,\n id: this.entropySource,\n groupIndex: addressIndex,\n derivationPath,\n },\n },\n };\n\n this.registry.set(account);\n return account;\n }\n\n async getAccounts(): Promise<Bip44Account<KeyringAccount>[]> {\n const addresses = await this.inner.getAccounts();\n\n if (addresses.length === 0) {\n return [];\n }\n\n // If the device is locked, we cannot derive addresses to find indices.\n // Return cached accounts if available, otherwise throw a clear error.\n if (!this.inner.isUnlocked()) {\n const cachedAccounts = addresses\n .map((address) => {\n const existingId = this.registry.getAccountId(address);\n return existingId ? this.registry.get(existingId) : undefined;\n })\n .filter(\n (account): account is Bip44Account<KeyringAccount> =>\n account !== undefined,\n );\n\n // If we have all accounts cached, return them\n if (cachedAccounts.length === addresses.length) {\n return cachedAccounts;\n }\n\n // Some accounts are not cached and device is locked\n throw new Error(\n 'Trezor device is locked. Please unlock the device to access accounts.',\n );\n }\n\n return addresses.map((address) => {\n // Check if we already have this account in the registry\n const existingId = this.registry.getAccountId(address);\n if (existingId) {\n const cached = this.registry.get(existingId);\n if (cached) {\n return cached;\n }\n }\n\n const addressIndex = this.inner.getIndexForAddress(address);\n return this.#createKeyringAccount(address, addressIndex);\n });\n }\n\n async createAccounts(\n options: CreateAccountOptions,\n ): Promise<Bip44Account<KeyringAccount>[]> {\n return this.withLock(async () => {\n if (\n options.type === 'bip44:derive-path' ||\n options.type === 'bip44:derive-index'\n ) {\n // Validate that the entropy source matches this keyring's entropy source\n if (options.entropySource !== this.entropySource) {\n throw new Error(\n `Entropy source mismatch: expected '${this.entropySource}', got '${options.entropySource}'`,\n );\n }\n } else {\n throw new Error(\n `Unsupported account creation type for TrezorKeyring: ${String(\n options.type,\n )}`,\n );\n }\n\n // Check if an account at this index already exists with the same derivation path\n const currentAccounts = await this.getAccounts();\n\n let targetIndex: number;\n let basePath: AllowedHdPath;\n let derivationPath: string;\n\n if (options.type === 'bip44:derive-path') {\n // Parse the derivation path to extract base path and index\n const parsed = this.#parseDerivationPath(options.derivationPath);\n targetIndex = parsed.index;\n basePath = parsed.basePath;\n // Use the normalized path to avoid mismatches with leading zeros\n // (e.g., \"m/44'/60'/0'/0/007\" becomes \"m/44'/60'/0'/0/7\")\n derivationPath = `${basePath}/${targetIndex}`;\n } else {\n // derive-index uses BIP-44 standard path by default\n if (options.groupIndex < 0) {\n throw new Error(\n `Invalid groupIndex: ${options.groupIndex}. Must be a non-negative integer.`,\n );\n }\n targetIndex = options.groupIndex;\n basePath = BIP44_HD_PATH_PREFIX;\n derivationPath = `${basePath}/${targetIndex}`;\n }\n\n const existingAccount = currentAccounts.find((account) => {\n return (\n account.options.entropy.groupIndex === targetIndex &&\n account.options.entropy.derivationPath === derivationPath\n );\n });\n\n if (existingAccount) {\n return [existingAccount];\n }\n\n // Derive the account at the specified index.\n // If the HD path is changing, clear the registry to avoid stale accounts.\n // The TrezorKeyring operates on a single path at a time - accounts from\n // different paths cannot coexist in the inner keyring.\n if (basePath !== this.inner.hdPath) {\n this.registry.clear();\n }\n\n this.inner.setHdPath(basePath);\n this.inner.setAccountToUnlock(targetIndex);\n const [newAddress] = await this.inner.addAccounts(1);\n\n if (!newAddress) {\n throw new Error('Failed to create new account');\n }\n\n const newAccount = this.#createKeyringAccount(newAddress, targetIndex);\n\n return [newAccount];\n });\n }\n\n /**\n * Delete an account from the keyring.\n *\n * @param accountId - The account ID to delete.\n */\n async deleteAccount(accountId: AccountId): Promise<void> {\n await this.withLock(async () => {\n const { address } = await this.getAccount(accountId);\n const hexAddress = this.toHexAddress(address);\n\n // Remove from the legacy keyring\n this.inner.removeAccount(hexAddress);\n\n // Remove from the registry\n this.registry.delete(accountId);\n });\n }\n\n /**\n * @returns The device model reported by the bridge, or `undefined` if no\n * device is paired.\n */\n getModel(): string | undefined {\n return this.inner.getModel();\n }\n\n /**\n * @returns The current derivation path used by the inner keyring.\n */\n get hdPath(): string {\n return this.inner.hdPath;\n }\n\n /**\n * @returns The bridge instance used by the inner keyring to communicate\n * with the device.\n */\n get bridge(): TrezorBridge {\n return this.inner.bridge;\n }\n\n /**\n * Set the derivation path on the inner keyring. Must be one of the allowed\n * HD paths supported by the legacy Trezor keyring.\n *\n * @param hdPath - The derivation path to set.\n */\n setHdPath(hdPath: Parameters<LegacyTrezorKeyring['setHdPath']>[0]): void {\n this.inner.setHdPath(hdPath);\n }\n\n /**\n * Fetch the first page of candidate addresses from the device.\n *\n * @returns The first page of accounts.\n */\n async getFirstPage(): Promise<AccountPage> {\n return this.inner.getFirstPage();\n }\n\n /**\n * Fetch the next page of candidate addresses from the device.\n *\n * @returns The next page of accounts.\n */\n async getNextPage(): Promise<AccountPage> {\n return this.inner.getNextPage();\n }\n\n /**\n * Fetch the previous page of candidate addresses from the device.\n *\n * @returns The previous page of accounts.\n */\n async getPreviousPage(): Promise<AccountPage> {\n return this.inner.getPreviousPage();\n }\n\n /**\n * Clear the inner keyring's device-pairing state and accounts, and reset\n * the V2 account registry to keep them in sync.\n */\n async forgetDevice(): Promise<void> {\n await this.withLock(async () => {\n this.inner.forgetDevice();\n this.registry.clear();\n });\n }\n\n /**\n * @returns Whether the inner keyring has an unlocked HD key.\n */\n isUnlocked(): boolean {\n return this.inner.isUnlocked();\n }\n}\n"]}