diff --git a/packages/examples/packages/bip32/package.json b/packages/examples/packages/bip32/package.json index 511330905b..c5c9075a1c 100644 --- a/packages/examples/packages/bip32/package.json +++ b/packages/examples/packages/bip32/package.json @@ -43,7 +43,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^11.2.0", "@noble/ed25519": "^1.6.0", diff --git a/packages/examples/packages/bip32/snap.manifest.json b/packages/examples/packages/bip32/snap.manifest.json index de3617b099..10a28caebe 100644 --- a/packages/examples/packages/bip32/snap.manifest.json +++ b/packages/examples/packages/bip32/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "nS48OH2I7YacX70E4rPCF+870ZdKRaJb66DwFU6zu8M=", + "shasum": "HwrsD47LbsSGHt9MwKLSl50VI8lnsY1yxq/LPa1BBcc=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/bip44/package.json b/packages/examples/packages/bip44/package.json index eb926331dd..82dbdee64e 100644 --- a/packages/examples/packages/bip44/package.json +++ b/packages/examples/packages/bip44/package.json @@ -43,7 +43,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^11.2.0", "@noble/bls12-381": "^1.2.0" diff --git a/packages/examples/packages/bip44/snap.manifest.json b/packages/examples/packages/bip44/snap.manifest.json index 769b32a799..160d6f0cbb 100644 --- a/packages/examples/packages/bip44/snap.manifest.json +++ b/packages/examples/packages/bip44/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "AeCVkpJQuy7JZHh5vQioQzJIbeR2H9hjYWmuGH8rww0=", + "shasum": "slHRPz+TwwHJfPqaZKAwCmSUAbxi8aAvwkGWY5MJ1Fk=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json index cab72fc2c7..1358b8d646 100644 --- a/packages/examples/packages/browserify-plugin/snap.manifest.json +++ b/packages/examples/packages/browserify-plugin/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "mLWzf5XFmDFHwK9UjU+XBds5oRdj/uXwycZlVhpsyDc=", + "shasum": "M83EcfIuVLDGdfzDz6ydToeMztnTSLhjTHpkiGwi3Mo=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json index 506db0ca51..ae89a5892e 100644 --- a/packages/examples/packages/browserify/snap.manifest.json +++ b/packages/examples/packages/browserify/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "Yf282fJUrz51Ex+hWO9f+VobgiZHhX4MJD2mkEWYAdw=", + "shasum": "AEZW5peO3bzO6yrbWCplB1yo/tyNirb6bAUXwSuspBc=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/package.json b/packages/examples/packages/invoke-snap/packages/consumer-signer/package.json index 711beca387..83a8cd34b2 100644 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/package.json +++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/package.json @@ -43,10 +43,10 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^11.2.0", - "@noble/hashes": "^1.3.1" + "@noble/hashes": "^1.7.1" }, "devDependencies": { "@jest/globals": "^29.5.0", diff --git a/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json b/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json index a4960e0a0a..4ee1470990 100644 --- a/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json +++ b/packages/examples/packages/invoke-snap/packages/consumer-signer/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "xs0Nhu0ONoHzoY0QfyPEutaBMwlMGQsoZgG88/bLxNQ=", + "shasum": "crdduVCAntL3FBIJw+t7ETyP9mbZLuXShtUyP4FXU5o=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/package.json b/packages/examples/packages/invoke-snap/packages/core-signer/package.json index 9a6fe3ba06..5ddc58ff37 100644 --- a/packages/examples/packages/invoke-snap/packages/core-signer/package.json +++ b/packages/examples/packages/invoke-snap/packages/core-signer/package.json @@ -43,7 +43,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/snaps-sdk": "workspace:^", "@metamask/utils": "^11.2.0", "@noble/curves": "^1.1.0", @@ -55,7 +55,7 @@ "@metamask/auto-changelog": "^4.1.0", "@metamask/snaps-cli": "workspace:^", "@metamask/snaps-jest": "workspace:^", - "@noble/hashes": "^1.3.1", + "@noble/hashes": "^1.7.1", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@types/node": "18.14.2", diff --git a/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json b/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json index 1a2e8bf9b0..7c7414fbb6 100644 --- a/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json +++ b/packages/examples/packages/invoke-snap/packages/core-signer/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "uJbZD9CQgbJNvLk1lKmtfeWJNKAUf+vjZSNr+YK0Pak=", + "shasum": "4MKzJCtc1uh4GzIg60F9R7FBWD8LZW8LEMe9Au7UxWQ=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snaps-controllers/package.json b/packages/snaps-controllers/package.json index e1d37bdc50..6f3d3b9fd6 100644 --- a/packages/snaps-controllers/package.json +++ b/packages/snaps-controllers/package.json @@ -83,7 +83,7 @@ "@metamask/base-controller": "^8.0.0", "@metamask/json-rpc-engine": "^10.0.2", "@metamask/json-rpc-middleware-stream": "^8.0.7", - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/object-multiplex": "^2.1.0", "@metamask/permission-controller": "^11.0.6", "@metamask/phishing-controller": "^12.4.0", @@ -115,6 +115,7 @@ "@metamask/auto-changelog": "^4.1.0", "@metamask/browser-passworder": "^6.0.0", "@metamask/template-snap": "^0.7.0", + "@noble/hashes": "^1.7.1", "@swc/core": "1.3.78", "@swc/jest": "^0.2.26", "@ts-bridge/cli": "^0.6.1", diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index 4dd68e957d..d0b5e393ea 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -52,17 +52,18 @@ import { MOCK_SNAP_NAME, DEFAULT_SOURCE_PATH, DEFAULT_ICON_PATH, - TEST_SECRET_RECOVERY_PHRASE_BYTES, + TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES, } from '@metamask/snaps-utils/test-utils'; import type { SemVerRange, SemVerVersion, Json } from '@metamask/utils'; import { - hexToBytes, assert, AssertionError, base64ToBytes, stringToBytes, createDeferredPromise, } from '@metamask/utils'; +import { hmac } from '@noble/hashes/hmac'; +import { sha512 } from '@noble/hashes/sha512'; import { File } from 'buffer'; import { webcrypto } from 'crypto'; import fetchMock from 'jest-fetch-mock'; @@ -2134,9 +2135,9 @@ describe('SnapController', () => { DEFAULT_ENCRYPTION_KEY_DERIVATION_OPTIONS, ); - const getMnemonic = jest + const getMnemonicSeed = jest .fn() - .mockReturnValue(TEST_SECRET_RECOVERY_PHRASE_BYTES); + .mockReturnValue(TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES); const snapController = getSnapController( getSnapControllerOptions({ @@ -2149,14 +2150,14 @@ describe('SnapController', () => { [MOCK_SNAP_ID]: mockEncryptedState, }, }, - getMnemonic, + getMnemonicSeed, }), ); expect( await messenger.call('SnapController:getSnapState', MOCK_SNAP_ID, true), ).toStrictEqual(state); - expect(getMnemonic).toHaveBeenCalledTimes(1); + expect(getMnemonicSeed).toHaveBeenCalledTimes(1); rootMessenger.publish('KeyringController:lock'); @@ -2168,7 +2169,7 @@ describe('SnapController', () => { // decrypt the state again. This is not an ideal way to test this, but it // is the easiest to test this without exposing the internal state of the // `SnapController`. - expect(getMnemonic).toHaveBeenCalledTimes(2); + expect(getMnemonicSeed).toHaveBeenCalledTimes(2); snapController.destroy(); }); @@ -9628,9 +9629,11 @@ describe('SnapController', () => { it('uses custom client cryptography functions', async () => { const messenger = getSnapControllerMessenger(); - const pbkdf2Sha512 = jest + const hmacSha512 = jest .fn() - .mockResolvedValue(hexToBytes(ENCRYPTION_KEY)); + .mockImplementation((key: Uint8Array, data: Uint8Array) => + hmac(sha512, key, data), + ); const snapController = getSnapController( getSnapControllerOptions({ @@ -9639,7 +9642,7 @@ describe('SnapController', () => { snaps: getPersistedSnapsState(), }, clientCryptography: { - pbkdf2Sha512, + hmacSha512, }, }), ); @@ -9656,7 +9659,7 @@ describe('SnapController', () => { await promise; - expect(pbkdf2Sha512).toHaveBeenCalledTimes(1); + expect(hmacSha512).toHaveBeenCalledTimes(10); snapController.destroy(); }); diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 982c2bfbfc..5c8b635e87 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -747,11 +747,11 @@ type SnapControllerArgs = { encryptor: ExportableKeyEncryptor; /** - * A hook to access the mnemonic of the user's primary keyring. + * A hook to access the mnemonic seed of the user's primary keyring. * - * @returns The mnemonic as bytes. + * @returns The mnemonic seed as bytes. */ - getMnemonic: () => Promise; + getMnemonicSeed: () => Promise; /** * A hook to get dynamic feature flags at runtime. @@ -848,7 +848,7 @@ export class SnapController extends BaseController< readonly #encryptor: ExportableKeyEncryptor; - readonly #getMnemonic: () => Promise; + readonly #getMnemonicSeed: () => Promise; readonly #getFeatureFlags: () => DynamicFeatureFlags; @@ -885,7 +885,7 @@ export class SnapController extends BaseController< detectSnapLocation: detectSnapLocationFunction = detectSnapLocation, preinstalledSnaps = null, encryptor, - getMnemonic, + getMnemonicSeed, getFeatureFlags = () => ({}), clientCryptography, }: SnapControllerArgs) { @@ -941,7 +941,7 @@ export class SnapController extends BaseController< this.maxRequestTime = maxRequestTime; this.#detectSnapLocation = detectSnapLocationFunction; this.#encryptor = encryptor; - this.#getMnemonic = getMnemonic; + this.#getMnemonicSeed = getMnemonicSeed; this.#getFeatureFlags = getFeatureFlags; this.#clientCryptography = clientCryptography; this.#preinstalledSnaps = preinstalledSnaps; @@ -1824,11 +1824,11 @@ export class SnapController extends BaseController< } const salt = passedSalt ?? this.#encryptor.generateSalt(); - const mnemonicPhrase = await this.#getMnemonic(); + const seed = await this.#getMnemonicSeed(); const entropy = await getEncryptionEntropy({ snapId, - mnemonicPhrase, + seed, cryptographicFunctions: this.#clientCryptography, }); diff --git a/packages/snaps-controllers/src/test-utils/controller.ts b/packages/snaps-controllers/src/test-utils/controller.ts index 628c710267..386a4dfb24 100644 --- a/packages/snaps-controllers/src/test-utils/controller.ts +++ b/packages/snaps-controllers/src/test-utils/controller.ts @@ -31,7 +31,7 @@ import { MOCK_LOCAL_SNAP_ID, MOCK_ORIGIN, MOCK_SNAP_ID, - TEST_SECRET_RECOVERY_PHRASE_BYTES, + TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES, } from '@metamask/snaps-utils/test-utils'; import type { Json } from '@metamask/utils'; @@ -581,7 +581,8 @@ export const getSnapControllerOptions = ( }, state: undefined, fetchFunction: jest.fn(), - getMnemonic: async () => Promise.resolve(TEST_SECRET_RECOVERY_PHRASE_BYTES), + getMnemonicSeed: async () => + Promise.resolve(TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES), clientCryptography: {}, encryptor: getSnapControllerEncryptor(), ...opts, @@ -613,7 +614,8 @@ export const getSnapControllerWithEESOptions = ({ closeAllConnections: jest.fn(), messenger: snapControllerMessenger, rootMessenger, - getMnemonic: async () => Promise.resolve(TEST_SECRET_RECOVERY_PHRASE_BYTES), + getMnemonicSeed: async () => + Promise.resolve(TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES), encryptor: getSnapControllerEncryptor(), fetchFunction: jest.fn(), ...options, diff --git a/packages/snaps-rpc-methods/jest.config.js b/packages/snaps-rpc-methods/jest.config.js index 67c7595d39..f9f0fb5aa3 100644 --- a/packages/snaps-rpc-methods/jest.config.js +++ b/packages/snaps-rpc-methods/jest.config.js @@ -10,10 +10,10 @@ module.exports = deepmerge(baseConfig, { ], coverageThreshold: { global: { - branches: 95.19, - functions: 98.62, - lines: 98.82, - statements: 98.5, + branches: 95.2, + functions: 98.63, + lines: 98.83, + statements: 98.51, }, }, }); diff --git a/packages/snaps-rpc-methods/package.json b/packages/snaps-rpc-methods/package.json index b1b3d6b76d..820a5984ff 100644 --- a/packages/snaps-rpc-methods/package.json +++ b/packages/snaps-rpc-methods/package.json @@ -55,14 +55,14 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/permission-controller": "^11.0.6", "@metamask/rpc-errors": "^7.0.2", "@metamask/snaps-sdk": "workspace:^", "@metamask/snaps-utils": "workspace:^", "@metamask/superstruct": "^3.1.0", "@metamask/utils": "^11.2.0", - "@noble/hashes": "^1.3.1", + "@noble/hashes": "^1.7.1", "luxon": "^3.5.0" }, "devDependencies": { diff --git a/packages/snaps-rpc-methods/src/restricted/getEntropy.ts b/packages/snaps-rpc-methods/src/restricted/getEntropy.ts index 032fd0625e..f5969ecb8f 100644 --- a/packages/snaps-rpc-methods/src/restricted/getEntropy.ts +++ b/packages/snaps-rpc-methods/src/restricted/getEntropy.ts @@ -14,7 +14,7 @@ import type { NonEmptyArray } from '@metamask/utils'; import { assertStruct } from '@metamask/utils'; import type { MethodHooksObject } from '../utils'; -import { getSecretRecoveryPhrase, deriveEntropy } from '../utils'; +import { getSecretRecoveryPhrase, deriveEntropyFromMnemonic } from '../utils'; const targetName = 'snap_getEntropy'; @@ -141,7 +141,7 @@ function getEntropyImplementation({ params.source, ); - return deriveEntropy({ + return deriveEntropyFromMnemonic({ input: origin, salt: params.salt, mnemonicPhrase, diff --git a/packages/snaps-rpc-methods/src/restricted/manageState.test.ts b/packages/snaps-rpc-methods/src/restricted/manageState.test.ts index 2e9945e3c9..f4444a35d5 100644 --- a/packages/snaps-rpc-methods/src/restricted/manageState.test.ts +++ b/packages/snaps-rpc-methods/src/restricted/manageState.test.ts @@ -2,7 +2,7 @@ import { PermissionType, SubjectType } from '@metamask/permission-controller'; import { ManageStateOperation } from '@metamask/snaps-sdk'; import { MOCK_SNAP_ID, - TEST_SECRET_RECOVERY_PHRASE_BYTES, + TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES, } from '@metamask/snaps-utils/test-utils'; import { webcrypto } from 'crypto'; @@ -20,7 +20,7 @@ const ENCRYPTION_KEY = describe('getEncryptionEntropy', () => { it('returns the encryption entropy for the snap ID', async () => { const result = await getEncryptionEntropy({ - mnemonicPhrase: TEST_SECRET_RECOVERY_PHRASE_BYTES, + seed: TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES, snapId: MOCK_SNAP_ID, }); @@ -29,7 +29,7 @@ describe('getEncryptionEntropy', () => { it('accepts custom cryptographic functions', async () => { const result = await getEncryptionEntropy({ - mnemonicPhrase: TEST_SECRET_RECOVERY_PHRASE_BYTES, + seed: TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES, snapId: MOCK_SNAP_ID, cryptographicFunctions: {}, }); diff --git a/packages/snaps-rpc-methods/src/restricted/manageState.ts b/packages/snaps-rpc-methods/src/restricted/manageState.ts index a99848ce3a..74e3f88cdc 100644 --- a/packages/snaps-rpc-methods/src/restricted/manageState.ts +++ b/packages/snaps-rpc-methods/src/restricted/manageState.ts @@ -13,7 +13,7 @@ import type { Json, NonEmptyArray } from '@metamask/utils'; import { isObject, getJsonSize } from '@metamask/utils'; import type { MethodHooksObject } from '../utils'; -import { deriveEntropy } from '../utils'; +import { deriveEntropyFromSeed } from '../utils'; // The salt used for SIP-6-based entropy derivation. export const STATE_ENCRYPTION_SALT = 'snap_manageState encryption'; @@ -111,7 +111,7 @@ export const STORAGE_SIZE_LIMIT = 64_000_000; // In bytes (64 MB) type GetEncryptionKeyArgs = { snapId: string; - mnemonicPhrase: Uint8Array; + seed: Uint8Array; cryptographicFunctions?: CryptographicFunctions | undefined; }; @@ -124,19 +124,19 @@ type GetEncryptionKeyArgs = { * * @param args - The encryption key args. * @param args.snapId - The ID of the snap to get the encryption key for. - * @param args.mnemonicPhrase - The mnemonic phrase to derive the encryption key + * @param args.seed - The mnemonic seed to derive the encryption key * from. * @param args.cryptographicFunctions - The cryptographic functions to use for * the client. * @returns The state encryption key. */ export async function getEncryptionEntropy({ - mnemonicPhrase, + seed, snapId, cryptographicFunctions, }: GetEncryptionKeyArgs) { - return await deriveEntropy({ - mnemonicPhrase, + return await deriveEntropyFromSeed({ + seed, input: snapId, salt: STATE_ENCRYPTION_SALT, magic: STATE_ENCRYPTION_MAGIC_VALUE, diff --git a/packages/snaps-rpc-methods/src/utils.test.ts b/packages/snaps-rpc-methods/src/utils.test.ts index 4d064eb5a3..dfc4f967a0 100644 --- a/packages/snaps-rpc-methods/src/utils.test.ts +++ b/packages/snaps-rpc-methods/src/utils.test.ts @@ -1,11 +1,15 @@ import { rpcErrors } from '@metamask/rpc-errors'; import { SIP_6_MAGIC_VALUE } from '@metamask/snaps-utils'; -import { TEST_SECRET_RECOVERY_PHRASE_BYTES } from '@metamask/snaps-utils/test-utils'; +import { + TEST_SECRET_RECOVERY_PHRASE_BYTES, + TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES, +} from '@metamask/snaps-utils/test-utils'; import { create, is } from '@metamask/superstruct'; import { ENTROPY_VECTORS } from './__fixtures__'; import { - deriveEntropy, + deriveEntropyFromMnemonic, + deriveEntropyFromSeed, getNode, getPathPrefix, getSecretRecoveryPhrase, @@ -13,12 +17,12 @@ import { StateKeyStruct, } from './utils'; -describe('deriveEntropy', () => { +describe('deriveEntropyFromMnemonic', () => { it.each(ENTROPY_VECTORS)( 'derives entropy from the given parameters', async ({ snapId, salt, entropy }) => { expect( - await deriveEntropy({ + await deriveEntropyFromMnemonic({ input: snapId, salt, mnemonicPhrase: TEST_SECRET_RECOVERY_PHRASE_BYTES, @@ -30,6 +34,23 @@ describe('deriveEntropy', () => { ); }); +describe('deriveEntropyFromSeed', () => { + it.each(ENTROPY_VECTORS)( + 'derives entropy from the given parameters', + async ({ snapId, salt, entropy }) => { + expect( + await deriveEntropyFromSeed({ + input: snapId, + salt, + seed: TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES, + magic: SIP_6_MAGIC_VALUE, + cryptographicFunctions: {}, + }), + ).toStrictEqual(entropy); + }, + ); +}); + describe('getPathPrefix', () => { it('returns "bip32" for "secp256k1"', () => { expect(getPathPrefix('secp256k1')).toBe('bip32'); diff --git a/packages/snaps-rpc-methods/src/utils.ts b/packages/snaps-rpc-methods/src/utils.ts index 3803262790..37f58ff2a7 100644 --- a/packages/snaps-rpc-methods/src/utils.ts +++ b/packages/snaps-rpc-methods/src/utils.ts @@ -90,7 +90,7 @@ function getDerivationPathArray(hash: Uint8Array): HardenedBIP32Node[] { return array; } -type DeriveEntropyOptions = { +type BaseDeriveEntropyOptions = { /** * The input value to derive entropy from. */ @@ -101,11 +101,6 @@ type DeriveEntropyOptions = { */ salt?: string; - /** - * The mnemonic phrase to use for entropy derivation. - */ - mnemonicPhrase: Uint8Array; - /** * A hardened BIP-32 index, which is used to derive the root key from the * mnemonic phrase. @@ -118,6 +113,95 @@ type DeriveEntropyOptions = { cryptographicFunctions: CryptographicFunctions | undefined; }; +type SeedDeriveEntropyOptions = BaseDeriveEntropyOptions & { + /** + * The mnemonic seed to use for entropy derivation. + */ + seed: Uint8Array; +}; + +type MnemonicDeriveEntropyOptions = BaseDeriveEntropyOptions & { + /** + * The mnemonic phrase to use for entropy derivation. + */ + mnemonicPhrase: Uint8Array; +}; + +/** + * 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, +}: Required>): + | BIP32Node[] + | SLIP10PathNode[] { + const inputBytes = stringToBytes(input); + const saltBytes = stringToBytes(salt); + + // Get the derivation path from the snap ID. + const hash = keccak256(concatBytes([inputBytes, keccak256(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. + */ +export async function deriveEntropyFromSeed({ + input, + salt = '', + seed, + magic, + cryptographicFunctions, +}: SeedDeriveEntropyOptions) { + const computedDerivationPath = getEntropyDerivationPath({ + input, + salt, + magic, + }); + + // Derive the private key using BIP-32. + const { privateKey } = await SLIP10Node.fromSeed( + { + derivationPath: [seed, ...computedDerivationPath], + curve: 'secp256k1', + }, + cryptographicFunctions, + ); + + // This should never happen, but this keeps TypeScript happy. + assert(privateKey, 'Failed to derive the entropy.'); + + return add0x(privateKey); +} + /** * Derive entropy from the given mnemonic phrase and salt. * @@ -135,28 +219,23 @@ type DeriveEntropyOptions = { * for the derivation. * @returns The derived entropy. */ -export async function deriveEntropy({ +export async function deriveEntropyFromMnemonic({ input, salt = '', mnemonicPhrase, magic, cryptographicFunctions, -}: DeriveEntropyOptions): Promise { - const inputBytes = stringToBytes(input); - const saltBytes = stringToBytes(salt); - - // Get the derivation path from the snap ID. - const hash = keccak256(concatBytes([inputBytes, keccak256(saltBytes)])); - const computedDerivationPath = getDerivationPathArray(hash); +}: MnemonicDeriveEntropyOptions): Promise { + const computedDerivationPath = getEntropyDerivationPath({ + input, + salt, + magic, + }); // Derive the private key using BIP-32. const { privateKey } = await SLIP10Node.fromDerivationPath( { - derivationPath: [ - mnemonicPhrase, - `bip32:${magic}`, - ...computedDerivationPath, - ], + derivationPath: [mnemonicPhrase, ...computedDerivationPath], curve: 'secp256k1', }, cryptographicFunctions, diff --git a/packages/snaps-sdk/package.json b/packages/snaps-sdk/package.json index a355c8efc7..8789e12834 100644 --- a/packages/snaps-sdk/package.json +++ b/packages/snaps-sdk/package.json @@ -91,7 +91,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/providers": "^20.0.0", "@metamask/rpc-errors": "^7.0.2", "@metamask/superstruct": "^3.1.0", diff --git a/packages/snaps-simulation/package.json b/packages/snaps-simulation/package.json index e9b2022378..d7408b837d 100644 --- a/packages/snaps-simulation/package.json +++ b/packages/snaps-simulation/package.json @@ -59,7 +59,7 @@ "@metamask/eth-json-rpc-middleware": "^15.3.0", "@metamask/json-rpc-engine": "^10.0.2", "@metamask/json-rpc-middleware-stream": "^8.0.7", - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/permission-controller": "^11.0.6", "@metamask/phishing-controller": "^12.4.0", "@metamask/snaps-controllers": "workspace:^", diff --git a/packages/snaps-simulator/package.json b/packages/snaps-simulator/package.json index 0338706984..44b41838e2 100644 --- a/packages/snaps-simulator/package.json +++ b/packages/snaps-simulator/package.json @@ -50,7 +50,7 @@ "@metamask/eth-json-rpc-middleware": "^15.3.0", "@metamask/json-rpc-engine": "^10.0.2", "@metamask/json-rpc-middleware-stream": "^8.0.7", - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/permission-controller": "^11.0.6", "@metamask/snaps-controllers": "workspace:^", "@metamask/snaps-execution-environments": "workspace:^", @@ -60,7 +60,7 @@ "@metamask/superstruct": "^3.1.0", "@metamask/utils": "^11.2.0", "@minoru/react-dnd-treeview": "^3.4.4", - "@noble/hashes": "^1.3.1", + "@noble/hashes": "^1.7.1", "@reduxjs/toolkit": "^1.9.5", "date-fns": "^2.30.0", "fast-deep-equal": "^3.1.3", diff --git a/packages/snaps-utils/package.json b/packages/snaps-utils/package.json index 161d408100..86e8605186 100644 --- a/packages/snaps-utils/package.json +++ b/packages/snaps-utils/package.json @@ -80,7 +80,7 @@ "@babel/core": "^7.23.2", "@babel/types": "^7.23.0", "@metamask/base-controller": "^8.0.0", - "@metamask/key-tree": "^10.0.2", + "@metamask/key-tree": "^10.1.0", "@metamask/permission-controller": "^11.0.6", "@metamask/rpc-errors": "^7.0.2", "@metamask/slip44": "^4.1.0", @@ -88,7 +88,7 @@ "@metamask/snaps-sdk": "workspace:^", "@metamask/superstruct": "^3.1.0", "@metamask/utils": "^11.2.0", - "@noble/hashes": "^1.3.1", + "@noble/hashes": "^1.7.1", "@scure/base": "^1.1.1", "chalk": "^4.1.2", "cron-parser": "^4.5.0", diff --git a/packages/snaps-utils/src/test-utils/common.ts b/packages/snaps-utils/src/test-utils/common.ts index ac92b1493c..25b2d73fd5 100644 --- a/packages/snaps-utils/src/test-utils/common.ts +++ b/packages/snaps-utils/src/test-utils/common.ts @@ -8,6 +8,14 @@ export const TEST_SECRET_RECOVERY_PHRASE_BYTES = mnemonicPhraseToBytes( TEST_SECRET_RECOVERY_PHRASE, ); +// await mnemonicToSeed('test test test test test test test test test test test ball') +export const TEST_SECRET_RECOVERY_PHRASE_SEED_BYTES = new Uint8Array([ + 44, 232, 45, 62, 149, 146, 73, 117, 90, 217, 78, 33, 68, 145, 185, 177, 102, + 61, 41, 58, 21, 196, 248, 21, 155, 72, 140, 191, 191, 66, 144, 46, 47, 188, + 165, 16, 149, 48, 252, 179, 255, 31, 120, 228, 174, 203, 27, 194, 102, 9, 173, + 1, 47, 174, 216, 184, 227, 85, 112, 105, 241, 209, 73, 65, +]); + /** * Tens/hundreds legacy tests use creation utils. * diff --git a/yarn.lock b/yarn.lock index 2041a1d4b6..0f5e2fbbcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4005,7 +4005,7 @@ __metadata: "@jest/globals": "npm:^29.5.0" "@lavamoat/allow-scripts": "npm:^3.0.4" "@metamask/auto-changelog": "npm:^4.1.0" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/snaps-cli": "workspace:^" "@metamask/snaps-jest": "workspace:^" "@metamask/snaps-sdk": "workspace:^" @@ -4033,7 +4033,7 @@ __metadata: "@jest/globals": "npm:^29.5.0" "@lavamoat/allow-scripts": "npm:^3.0.4" "@metamask/auto-changelog": "npm:^4.1.0" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/snaps-cli": "workspace:^" "@metamask/snaps-jest": "workspace:^" "@metamask/snaps-sdk": "workspace:^" @@ -4147,12 +4147,12 @@ __metadata: "@jest/globals": "npm:^29.5.0" "@lavamoat/allow-scripts": "npm:^3.0.4" "@metamask/auto-changelog": "npm:^4.1.0" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/snaps-cli": "workspace:^" "@metamask/snaps-jest": "workspace:^" "@metamask/snaps-sdk": "workspace:^" "@metamask/utils": "npm:^11.2.0" - "@noble/hashes": "npm:^1.3.1" + "@noble/hashes": "npm:^1.7.1" "@swc/core": "npm:1.3.78" "@swc/jest": "npm:^0.2.26" "@types/node": "npm:18.14.2" @@ -4195,13 +4195,13 @@ __metadata: "@jest/globals": "npm:^29.5.0" "@lavamoat/allow-scripts": "npm:^3.0.4" "@metamask/auto-changelog": "npm:^4.1.0" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/snaps-cli": "workspace:^" "@metamask/snaps-jest": "workspace:^" "@metamask/snaps-sdk": "workspace:^" "@metamask/utils": "npm:^11.2.0" "@noble/curves": "npm:^1.1.0" - "@noble/hashes": "npm:^1.3.1" + "@noble/hashes": "npm:^1.7.1" "@swc/core": "npm:1.3.78" "@swc/jest": "npm:^0.2.26" "@types/node": "npm:18.14.2" @@ -4837,16 +4837,16 @@ __metadata: languageName: unknown linkType: soft -"@metamask/key-tree@npm:^10.0.2": - version: 10.0.2 - resolution: "@metamask/key-tree@npm:10.0.2" +"@metamask/key-tree@npm:^10.1.0": + version: 10.1.0 + resolution: "@metamask/key-tree@npm:10.1.0" dependencies: "@metamask/scure-bip39": "npm:^2.1.1" "@metamask/utils": "npm:^11.0.1" - "@noble/curves": "npm:^1.2.0" + "@noble/curves": "npm:^1.8.1" "@noble/hashes": "npm:^1.3.2" "@scure/base": "npm:^1.0.0" - checksum: 10/fd2e445c75dc3cd3976fdc38a5029ee71a6f4afcbbf5c9a17152bba70cf35df8095caa853ae62eef90a51b43f23eeb9546fc6eb7d93a099d82effe8dc7592259 + checksum: 10/b1292de03257d52fe306a0a9131d17cad46bad1a1b36df15b09ca9fe0168fb1d2a0c4f33febe88cd03656ad585273ebfe54cd67ceeb12f5d69d85df6a2b57e99 languageName: node linkType: hard @@ -5398,7 +5398,7 @@ __metadata: "@metamask/browser-passworder": "npm:^6.0.0" "@metamask/json-rpc-engine": "npm:^10.0.2" "@metamask/json-rpc-middleware-stream": "npm:^8.0.7" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/object-multiplex": "npm:^2.1.0" "@metamask/permission-controller": "npm:^11.0.6" "@metamask/phishing-controller": "npm:^12.4.0" @@ -5410,6 +5410,7 @@ __metadata: "@metamask/snaps-utils": "workspace:^" "@metamask/template-snap": "npm:^0.7.0" "@metamask/utils": "npm:^11.2.0" + "@noble/hashes": "npm:^1.7.1" "@swc/core": "npm:1.3.78" "@swc/jest": "npm:^0.2.26" "@ts-bridge/cli": "npm:^0.6.1" @@ -5620,14 +5621,14 @@ __metadata: "@lavamoat/allow-scripts": "npm:^3.0.4" "@metamask/auto-changelog": "npm:^4.1.0" "@metamask/json-rpc-engine": "npm:^10.0.2" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/permission-controller": "npm:^11.0.6" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/snaps-sdk": "workspace:^" "@metamask/snaps-utils": "workspace:^" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^11.2.0" - "@noble/hashes": "npm:^1.3.1" + "@noble/hashes": "npm:^1.7.1" "@swc/core": "npm:1.3.78" "@swc/jest": "npm:^0.2.26" "@ts-bridge/cli": "npm:^0.6.1" @@ -5651,7 +5652,7 @@ __metadata: dependencies: "@lavamoat/allow-scripts": "npm:^3.0.4" "@metamask/auto-changelog": "npm:^4.1.0" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/providers": "npm:^20.0.0" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/superstruct": "npm:^3.1.0" @@ -5682,7 +5683,7 @@ __metadata: "@metamask/eth-json-rpc-middleware": "npm:^15.3.0" "@metamask/json-rpc-engine": "npm:^10.0.2" "@metamask/json-rpc-middleware-stream": "npm:^8.0.7" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/permission-controller": "npm:^11.0.6" "@metamask/phishing-controller": "npm:^12.4.0" "@metamask/snaps-controllers": "workspace:^" @@ -5729,7 +5730,7 @@ __metadata: "@metamask/eth-json-rpc-middleware": "npm:^15.3.0" "@metamask/json-rpc-engine": "npm:^10.0.2" "@metamask/json-rpc-middleware-stream": "npm:^8.0.7" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/permission-controller": "npm:^11.0.6" "@metamask/snaps-controllers": "workspace:^" "@metamask/snaps-execution-environments": "workspace:^" @@ -5739,7 +5740,7 @@ __metadata: "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^11.2.0" "@minoru/react-dnd-treeview": "npm:^3.4.4" - "@noble/hashes": "npm:^1.3.1" + "@noble/hashes": "npm:^1.7.1" "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.10" "@redux-saga/is": "npm:^1.1.3" "@redux-saga/symbols": "npm:^1.1.3" @@ -5820,7 +5821,7 @@ __metadata: "@lavamoat/allow-scripts": "npm:^3.0.4" "@metamask/auto-changelog": "npm:^4.1.0" "@metamask/base-controller": "npm:^8.0.0" - "@metamask/key-tree": "npm:^10.0.2" + "@metamask/key-tree": "npm:^10.1.0" "@metamask/permission-controller": "npm:^11.0.6" "@metamask/post-message-stream": "npm:^9.0.0" "@metamask/rpc-errors": "npm:^7.0.2" @@ -5829,7 +5830,7 @@ __metadata: "@metamask/snaps-sdk": "workspace:^" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^11.2.0" - "@noble/hashes": "npm:^1.3.1" + "@noble/hashes": "npm:^1.7.1" "@scure/base": "npm:^1.1.1" "@swc/core": "npm:1.3.78" "@swc/jest": "npm:^0.2.26" @@ -6184,7 +6185,7 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.4.2, @noble/curves@npm:^1.1.0, @noble/curves@npm:^1.2.0, @noble/curves@npm:~1.4.0": +"@noble/curves@npm:1.4.2, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" dependencies: @@ -6193,6 +6194,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.1.0, @noble/curves@npm:^1.2.0, @noble/curves@npm:^1.8.1": + version: 1.8.1 + resolution: "@noble/curves@npm:1.8.1" + dependencies: + "@noble/hashes": "npm:1.7.1" + checksum: 10/e861db372cc0734b02a4c61c0f5a6688d4a7555edca3d8a9e7c846c9aa103ca52d3c3818e8bc333a1a95b5be7f370ff344668d5d759471b11c2d14c7f24b3984 + languageName: node + linkType: hard + "@noble/ed25519@npm:^1.6.0": version: 1.6.0 resolution: "@noble/ed25519@npm:1.6.0" @@ -6214,10 +6224,10 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.4.0": - version: 1.5.0 - resolution: "@noble/hashes@npm:1.5.0" - checksum: 10/da7fc7af52af7afcf59810a7eea6155075464ff462ffda2572dc6d57d53e2669b1ea2ec774e814f6273f1697e567f28d36823776c9bf7068cba2a2855140f26e +"@noble/hashes@npm:1.7.1, @noble/hashes@npm:^1.1.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.3.2, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.7.1": + version: 1.7.1 + resolution: "@noble/hashes@npm:1.7.1" + checksum: 10/ca3120da0c3e7881d6a481e9667465cc9ebbee1329124fb0de442e56d63fef9870f8cc96f264ebdb18096e0e36cebc0e6e979a872d545deb0a6fed9353f17e05 languageName: node linkType: hard