UNPKG

@metamask/snaps-rpc-methods

Version:
1 lines 14.9 kB
{"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n HardenedBIP32Node,\n BIP32Node,\n SLIP10PathNode,\n SupportedCurve,\n CryptographicFunctions,\n} from '@metamask/key-tree';\nimport { SLIP10Node } from '@metamask/key-tree';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type { MagicValue } from '@metamask/snaps-utils';\nimport { refine, string } from '@metamask/superstruct';\nimport {\n assertExhaustive,\n add0x,\n assert,\n concatBytes,\n createDataView,\n stringToBytes,\n} from '@metamask/utils';\nimport { keccak_256 as keccak256 } from '@noble/hashes/sha3';\n\nconst HARDENED_VALUE = 0x80000000;\n\nexport const FORBIDDEN_KEYS = ['constructor', '__proto__', 'prototype'];\n\n/**\n * Maps an interface with method hooks to an object, using the keys of the\n * interface, and `true` as value. This ensures that the `methodHooks` object\n * has the same values as the interface.\n */\nexport type MethodHooksObject<HooksType extends Record<string, unknown>> = {\n [Key in keyof HooksType]: true;\n};\n\n/**\n * Returns the subset of the specified `hooks` that are included in the\n * `hookNames` object. This is a Principle of Least Authority (POLA) measure\n * to ensure that each RPC method implementation only has access to the\n * API \"hooks\" it needs to do its job.\n *\n * @param hooks - The hooks to select from.\n * @param hookNames - The names of the hooks to select.\n * @returns The selected hooks.\n * @template Hooks - The hooks to select from.\n * @template HookName - The names of the hooks to select.\n */\nexport function selectHooks<\n Hooks extends Record<string, unknown>,\n HookName extends keyof Hooks,\n>(\n hooks: Hooks,\n hookNames?: Record<HookName, boolean>,\n): Pick<Hooks, HookName> | undefined {\n if (hookNames) {\n return Object.keys(hookNames).reduce<Partial<Pick<Hooks, HookName>>>(\n (hookSubset, _hookName) => {\n const hookName = _hookName as HookName;\n hookSubset[hookName] = hooks[hookName];\n return hookSubset;\n },\n {},\n ) as Pick<Hooks, HookName>;\n }\n return undefined;\n}\n\n/**\n * Get a BIP-32 derivation path array from a hash, which is compatible with\n * `@metamask/key-tree`. The hash is assumed to be 32 bytes long.\n *\n * @param hash - The hash to derive indices from.\n * @returns The derived indices as a {@link HardenedBIP32Node} array.\n */\nfunction getDerivationPathArray(hash: Uint8Array): HardenedBIP32Node[] {\n const array: HardenedBIP32Node[] = [];\n const view = createDataView(hash);\n\n for (let index = 0; index < 8; index++) {\n const uint32 = view.getUint32(index * 4);\n\n // This is essentially `index | 0x80000000`. Because JavaScript numbers are\n // signed, we use the bitwise unsigned right shift operator to ensure that\n // the result is a positive number.\n // eslint-disable-next-line no-bitwise\n const pathIndex = (uint32 | HARDENED_VALUE) >>> 0;\n array.push(`bip32:${pathIndex - HARDENED_VALUE}'` as const);\n }\n\n return array;\n}\n\ntype BaseDeriveEntropyOptions = {\n /**\n * The input value to derive entropy from.\n */\n input: string;\n\n /**\n * An optional salt to use when deriving entropy.\n */\n salt?: string;\n\n /**\n * A hardened BIP-32 index, which is used to derive the root key from the\n * mnemonic phrase.\n */\n magic: MagicValue;\n\n /**\n * The cryptographic functions to use for the derivation.\n */\n cryptographicFunctions: CryptographicFunctions | undefined;\n};\n\ntype SeedDeriveEntropyOptions = BaseDeriveEntropyOptions & {\n /**\n * The mnemonic seed to use for entropy derivation.\n */\n seed: Uint8Array;\n};\n\n/**\n * Get the derivation path to use for entropy derivation.\n *\n * This is based on the reference implementation of\n * [SIP-6](https://metamask.github.io/SIPs/SIPS/sip-6).\n *\n * @param options - The options for entropy derivation.\n * @param options.input - The input value to derive entropy from.\n * @param options.salt - An optional salt to use when deriving entropy.\n * @param options.magic - A hardened BIP-32 index, which is used to derive the\n * root key from the mnemonic phrase.\n * @returns The derivation path to be used for entropy key derivation.\n */\nfunction getEntropyDerivationPath({\n input,\n salt,\n magic,\n}: Required<Omit<BaseDeriveEntropyOptions, 'cryptographicFunctions'>>):\n | BIP32Node[]\n | SLIP10PathNode[] {\n const inputBytes = stringToBytes(input);\n const saltBytes = stringToBytes(salt);\n\n // Get the derivation path from the snap ID.\n const hash = keccak256(concatBytes([inputBytes, keccak256(saltBytes)]));\n const computedDerivationPath = getDerivationPathArray(hash);\n\n return [`bip32:${magic}`, ...computedDerivationPath];\n}\n\n/**\n * Derive entropy from the given mnemonic seed and salt.\n *\n * This is based on the reference implementation of\n * [SIP-6](https://metamask.github.io/SIPs/SIPS/sip-6).\n *\n * @param options - The options for entropy derivation.\n * @param options.input - The input value to derive entropy from.\n * @param options.salt - An optional salt to use when deriving entropy.\n * @param options.seed - The mnemonic seed to use for entropy\n * derivation.\n * @param options.magic - A hardened BIP-32 index, which is used to derive the\n * root key from the mnemonic phrase.\n * @param options.cryptographicFunctions - The cryptographic functions to use\n * for the derivation.\n * @returns The derived entropy.\n */\nexport async function deriveEntropyFromSeed({\n input,\n salt = '',\n seed,\n magic,\n cryptographicFunctions,\n}: SeedDeriveEntropyOptions) {\n const computedDerivationPath = getEntropyDerivationPath({\n input,\n salt,\n magic,\n });\n\n // Derive the private key using BIP-32.\n const { privateKey } = await SLIP10Node.fromSeed(\n {\n derivationPath: [seed, ...computedDerivationPath],\n curve: 'secp256k1',\n },\n cryptographicFunctions,\n );\n\n // This should never happen, but this keeps TypeScript happy.\n assert(privateKey, 'Failed to derive the entropy.');\n\n return add0x(privateKey);\n}\n\n/**\n * Get the path prefix to use for key derivation in `key-tree`. This assumes the\n * following:\n *\n * - The Secp256k1 curve always uses the BIP-32 specification.\n * - The Ed25519 curve always uses the SLIP-10 specification.\n * - The BIP-32-Ed25519 curve always uses the CIP-3 specification.\n *\n * While this does not matter in most situations (no known case at the time of\n * writing), `key-tree` requires a specific specification to be used.\n *\n * @param curve - The curve to get the path prefix for. The curve is NOT\n * validated by this function.\n * @returns The path prefix, i.e., `bip32` or `slip10`.\n */\nexport function getPathPrefix(\n curve: SupportedCurve,\n): 'bip32' | 'slip10' | 'cip3' {\n switch (curve) {\n case 'secp256k1':\n return 'bip32';\n case 'ed25519':\n return 'slip10';\n case 'ed25519Bip32':\n return 'cip3';\n default:\n return assertExhaustive(curve);\n }\n}\n\ntype BaseGetNodeArgs = {\n curve: SupportedCurve;\n path: string[];\n cryptographicFunctions: CryptographicFunctions | undefined;\n};\n\ntype GetNodeArgsMnemonic = BaseGetNodeArgs & {\n secretRecoveryPhrase: Uint8Array;\n};\n\ntype GetNodeArgsSeed = BaseGetNodeArgs & {\n seed: Uint8Array;\n};\n\n/**\n * Get a `key-tree`-compatible node.\n *\n * Note: This function assumes that all the parameters have been validated\n * beforehand.\n *\n * @param options - The derivation options.\n * @param options.curve - The curve to use for derivation.\n * @param options.secretRecoveryPhrase - The secret recovery phrase to use for\n * derivation.\n * @param options.path - The derivation path to use as array, starting with an\n * \"m\" as the first item.\n * @param options.cryptographicFunctions - The cryptographic functions to use\n * for the node.\n * @returns The `key-tree` SLIP-10 node.\n */\nexport async function getNodeFromMnemonic({\n curve,\n secretRecoveryPhrase,\n path,\n cryptographicFunctions,\n}: GetNodeArgsMnemonic) {\n const prefix = getPathPrefix(curve);\n\n return await SLIP10Node.fromDerivationPath(\n {\n curve,\n derivationPath: [\n secretRecoveryPhrase,\n ...(path.slice(1).map((index) => `${prefix}:${index}`) as\n | BIP32Node[]\n | SLIP10PathNode[]),\n ],\n },\n cryptographicFunctions,\n );\n}\n\n/**\n * Get a `key-tree`-compatible node.\n *\n * Note: This function assumes that all the parameters have been validated\n * beforehand.\n *\n * @param options - The derivation options.\n * @param options.curve - The curve to use for derivation.\n * @param options.seed - The BIP-39 to use for\n * derivation.\n * @param options.path - The derivation path to use as array, starting with an\n * \"m\" as the first item.\n * @param options.cryptographicFunctions - The cryptographic functions to use\n * for the node.\n * @returns The `key-tree` SLIP-10 node.\n */\nexport async function getNodeFromSeed({\n curve,\n seed,\n path,\n cryptographicFunctions,\n}: GetNodeArgsSeed) {\n const prefix = getPathPrefix(curve);\n\n return await SLIP10Node.fromSeed(\n {\n curve,\n derivationPath: [\n seed,\n ...(path.slice(1).map((index) => `${prefix}:${index}`) as\n | BIP32Node[]\n | SLIP10PathNode[]),\n ],\n },\n cryptographicFunctions,\n );\n}\n\n/**\n * Validate the key of a state object.\n *\n * @param key - The key to validate.\n * @returns `true` if the key is valid, `false` otherwise.\n */\nexport function isValidStateKey(key: string | undefined) {\n if (key === undefined) {\n return true;\n }\n\n return key.split('.').every((part) => part.length > 0);\n}\n\nexport const StateKeyStruct = refine(string(), 'state key', (value) => {\n if (!isValidStateKey(value)) {\n return 'Invalid state key. Each part of the key must be non-empty.';\n }\n\n return true;\n});\n\n/**\n * Get a value using the entropy source hooks: getMnemonic or getMnemonicSeed.\n * This function calls the passed hook and handles any errors that occur,\n * throwing formatted JSON-RPC errors.\n *\n * @param hook - The hook.\n * @param source - The entropy source to use.\n * @returns The secret recovery phrase.\n */\nexport async function getValueFromEntropySource(\n hook: (source?: string | undefined) => Promise<Uint8Array>,\n source?: string | undefined,\n): Promise<Uint8Array> {\n try {\n return await hook(source);\n } catch (error) {\n if (error instanceof Error) {\n throw rpcErrors.invalidParams({\n message: error.message,\n });\n }\n\n throw rpcErrors.internal({\n message: 'An unknown error occurred.',\n data: {\n error: error.toString(),\n },\n });\n }\n}\n"]}