UNPKG

@metamask/snaps-rpc-methods

Version:
305 lines 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMnemonicSeed = exports.getMnemonic = exports.HD_KEYRING = exports.UI_PERMISSIONS = exports.getValueFromEntropySource = exports.StateKeyStruct = exports.isValidStateKey = exports.getNodeFromSeed = exports.getNodeFromMnemonic = exports.getPathPrefix = exports.deriveEntropyFromSeed = 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 endowments_1 = require("./endowments/index.cjs"); const HARDENED_VALUE = 0x80000000; exports.FORBIDDEN_KEYS = ['constructor', '__proto__', 'prototype']; /** * 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; /** * The permissions that allow a Snap to show UI. Snaps must have at least one * of these permissions to use the interface management RPC methods. */ exports.UI_PERMISSIONS = [ 'snap_dialog', 'snap_notify', endowments_1.SnapEndowments.HomePage, endowments_1.SnapEndowments.SettingsPage, endowments_1.SnapEndowments.TransactionInsight, endowments_1.SnapEndowments.SignatureInsight, ]; exports.HD_KEYRING = 'hd'; /** * Get the mnemonic for a given entropy source. If no source is * provided, the primary HD keyring's mnemonic will be returned. * * @param messenger - The messenger. * @param source - The ID of the entropy source keyring. * @returns The mnemonic. */ async function getMnemonic(messenger, source) { if (!source) { const mnemonic = (await messenger.call('KeyringController:withKeyringV2Unsafe', { type: exports.HD_KEYRING, index: 0, }, async ({ keyring }) => keyring.mnemonic)); if (!mnemonic) { throw new Error('Primary keyring mnemonic unavailable.'); } return mnemonic; } try { const keyringData = await messenger.call('KeyringController:withKeyringV2Unsafe', { id: source, }, async ({ keyring }) => ({ type: keyring.type, mnemonic: keyring.mnemonic, })); const { type, mnemonic } = keyringData; // The keyring isn't guaranteed to have a mnemonic (e.g., // hardware wallets, which can't be used as entropy sources), // so we throw an error if it doesn't. (0, utils_1.assert)(type === exports.HD_KEYRING && mnemonic); return mnemonic; } catch { throw new Error(`Entropy source with ID "${source}" not found.`); } } exports.getMnemonic = getMnemonic; /** * Get the mnemonic seed for a given entropy source. If no source is * provided, the primary HD keyring's mnemonic seed will be returned. * * @param messenger - The messenger. * @param source - The ID of the entropy source keyring. * @returns The mnemonic seed. */ async function getMnemonicSeed(messenger, source) { if (!source) { const seed = (await messenger.call('KeyringController:withKeyringV2Unsafe', { type: exports.HD_KEYRING, index: 0, }, async ({ keyring }) => keyring.seed)); if (!seed) { throw new Error('Primary keyring mnemonic unavailable.'); } return seed; } try { const keyringData = await messenger.call('KeyringController:withKeyringV2Unsafe', { id: source, }, async ({ keyring }) => ({ type: keyring.type, seed: keyring.seed, })); const { type, seed } = keyringData; // The keyring isn't guaranteed to have a mnemonic (e.g., // hardware wallets, which can't be used as entropy sources), // so we throw an error if it doesn't. (0, utils_1.assert)(type === exports.HD_KEYRING && seed); return seed; } catch { throw new Error(`Entropy source with ID "${source}" not found.`); } } exports.getMnemonicSeed = getMnemonicSeed; //# sourceMappingURL=utils.cjs.map