UNPKG

@metamask/snaps-rpc-methods

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