Skip to content

Commit

Permalink
BREAKING: Bump key-tree to 6.2.0 and use Uint8Array for secret re…
Browse files Browse the repository at this point in the history
…covery phrases (MetaMask#1137)

* Bump key-tree to 6.2.0 and use Uint8Array for secret recovery phrases

* Use mnemonicPhraseToBytes function directly
  • Loading branch information
Mrtenz authored Jan 18, 2023
1 parent 4b477fc commit f20eb9a
Show file tree
Hide file tree
Showing 16 changed files with 66 additions and 47 deletions.
2 changes: 1 addition & 1 deletion packages/rpc-methods/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"dependencies": {
"@metamask/browser-passworder": "^4.0.2",
"@metamask/key-tree": "^6.1.0",
"@metamask/key-tree": "^6.2.0",
"@metamask/permission-controller": "^1.0.1",
"@metamask/snaps-ui": "^0.27.1",
"@metamask/snaps-utils": "^0.27.1",
Expand Down
6 changes: 3 additions & 3 deletions packages/rpc-methods/src/restricted/getBip32Entropy.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SIP_6_MAGIC_VALUE, SnapCaveatType } from '@metamask/snaps-utils';
import { TEST_SECRET_RECOVERY_PHRASE } from '@metamask/snaps-utils/test-utils';
import { TEST_SECRET_RECOVERY_PHRASE_BYTES } from '@metamask/snaps-utils/test-utils';

import {
getBip32EntropyBuilder,
Expand Down Expand Up @@ -266,7 +266,7 @@ describe('getBip32EntropyImplementation', () => {
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
const getMnemonic = jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE);
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES);

expect(
// @ts-expect-error Missing other required properties.
Expand All @@ -291,7 +291,7 @@ describe('getBip32EntropyImplementation', () => {
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
const getMnemonic = jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE);
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES);

expect(
// @ts-expect-error Missing other required properties.
Expand Down
4 changes: 2 additions & 2 deletions packages/rpc-methods/src/restricted/getBip32Entropy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type GetBip32EntropyMethodHooks = {
/**
* @returns The mnemonic of the user's primary keyring.
*/
getMnemonic: () => Promise<string>;
getMnemonic: () => Promise<Uint8Array>;

/**
* Waits for the extension to be unlocked.
Expand Down Expand Up @@ -207,7 +207,7 @@ export function getBip32EntropyImplementation({
const node = await SLIP10Node.fromDerivationPath({
curve: params.curve,
derivationPath: [
`bip39:${await getMnemonic()}`,
await getMnemonic(),
...params.path
.slice(1)
.map<BIP32Node>((index) => `bip32:${index}` as BIP32Node),
Expand Down
9 changes: 4 additions & 5 deletions packages/rpc-methods/src/restricted/getBip32PublicKey.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { TEST_SECRET_RECOVERY_PHRASE_BYTES } from '@metamask/snaps-utils/test-utils';

import {
getBip32PublicKeyBuilder,
getBip32PublicKeyImplementation,
} from './getBip32PublicKey';

const TEST_SECRET_RECOVERY_PHRASE =
'test test test test test test test test test test test ball';

describe('specificationBuilder', () => {
const methodHooks = {
getMnemonic: jest.fn(),
Expand Down Expand Up @@ -49,7 +48,7 @@ describe('getBip32PublicKeyImplementation', () => {
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
const getMnemonic = jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE);
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES);

expect(
await getBip32PublicKeyImplementation({
Expand All @@ -71,7 +70,7 @@ describe('getBip32PublicKeyImplementation', () => {
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
const getMnemonic = jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE);
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES);

expect(
await getBip32PublicKeyImplementation({
Expand Down
4 changes: 2 additions & 2 deletions packages/rpc-methods/src/restricted/getBip32PublicKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type GetBip32PublicKeyMethodHooks = {
/**
* @returns The mnemonic of the user's primary keyring.
*/
getMnemonic: () => Promise<string>;
getMnemonic: () => Promise<Uint8Array>;

/**
* Waits for the extension to be unlocked.
Expand Down Expand Up @@ -150,7 +150,7 @@ export function getBip32PublicKeyImplementation({
const node = await SLIP10Node.fromDerivationPath({
curve: params.curve,
derivationPath: [
`bip39:${await getMnemonic()}`,
await getMnemonic(),
...params.path
.slice(1)
.map<BIP32Node>((index) => `bip32:${index}` as BIP32Node),
Expand Down
6 changes: 2 additions & 4 deletions packages/rpc-methods/src/restricted/getBip44Entropy.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SnapCaveatType } from '@metamask/snaps-utils';
import { TEST_SECRET_RECOVERY_PHRASE_BYTES } from '@metamask/snaps-utils/test-utils';

import {
getBip44EntropyBuilder,
Expand All @@ -9,9 +10,6 @@ import {
validateParams,
} from './getBip44Entropy';

const TEST_SECRET_RECOVERY_PHRASE =
'test test test test test test test test test test test ball';

describe('validateParams', () => {
it.each([true, false, null, undefined, 'foo', [], new (class {})()])(
'throws if the value is not a plain object',
Expand Down Expand Up @@ -222,7 +220,7 @@ describe('getBip44EntropyImplementation', () => {
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
const getMnemonic = jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE);
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES);

expect(
// @ts-expect-error Missing other required properties.
Expand Down
4 changes: 2 additions & 2 deletions packages/rpc-methods/src/restricted/getBip44Entropy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type GetBip44EntropyMethodHooks = {
/**
* @returns The mnemonic of the user's primary keyring.
*/
getMnemonic: () => Promise<string>;
getMnemonic: () => Promise<Uint8Array>;

/**
* Waits for the extension to be unlocked.
Expand Down Expand Up @@ -223,7 +223,7 @@ export function getBip44EntropyImplementation({
const params = args.params!;

const node = await BIP44CoinTypeNode.fromDerivationPath([
`bip39:${await getMnemonic()}`,
await getMnemonic(),
`bip32:44'`,
`bip32:${params.coinType}'`,
]);
Expand Down
10 changes: 5 additions & 5 deletions packages/rpc-methods/src/restricted/getEntropy.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { PermissionType } from '@metamask/permission-controller';
import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils';
import {
MOCK_SNAP_ID,
TEST_SECRET_RECOVERY_PHRASE_BYTES,
} from '@metamask/snaps-utils/test-utils';

import { getEntropyBuilder } from './getEntropy';

const TEST_SECRET_RECOVERY_PHRASE =
'test test test test test test test test test test test ball';

describe('getEntropyBuilder', () => {
it('has the expected shape', () => {
expect(getEntropyBuilder).toStrictEqual({
Expand Down Expand Up @@ -39,7 +39,7 @@ describe('getEntropyImplementation', () => {
it('returns the expected result', async () => {
const getMnemonic = jest
.fn()
.mockImplementation(() => TEST_SECRET_RECOVERY_PHRASE);
.mockImplementation(() => TEST_SECRET_RECOVERY_PHRASE_BYTES);

const getUnlockPromise = jest.fn();

Expand Down
2 changes: 1 addition & 1 deletion packages/rpc-methods/src/restricted/getEntropy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export type GetEntropyHooks = {
/**
* @returns The mnemonic of the user's primary keyring.
*/
getMnemonic: () => Promise<string>;
getMnemonic: () => Promise<Uint8Array>;

/**
* Waits for the extension to be unlocked.
Expand Down
34 changes: 25 additions & 9 deletions packages/rpc-methods/src/restricted/manageState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { encrypt } from '@metamask/browser-passworder';
import {
MOCK_LOCAL_SNAP_ID,
MOCK_SNAP_ID,
TEST_SECRET_RECOVERY_PHRASE,
TEST_SECRET_RECOVERY_PHRASE_BYTES,
} from '@metamask/snaps-utils/test-utils';
import { ethErrors } from 'eth-rpc-errors';

Expand Down Expand Up @@ -76,7 +76,9 @@ describe('snap_manageState', () => {
clearSnapState,
getSnapState,
updateSnapState,
getMnemonic: jest.fn().mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE),
getMnemonic: jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES),
getUnlockPromise: jest.fn(),
});

Expand All @@ -99,7 +101,9 @@ describe('snap_manageState', () => {
clearSnapState,
getSnapState,
updateSnapState,
getMnemonic: jest.fn().mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE),
getMnemonic: jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES),
getUnlockPromise: jest.fn(),
});

Expand All @@ -122,7 +126,9 @@ describe('snap_manageState', () => {
clearSnapState,
getSnapState,
updateSnapState,
getMnemonic: jest.fn().mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE),
getMnemonic: jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES),
getUnlockPromise: jest.fn(),
});

Expand Down Expand Up @@ -152,7 +158,9 @@ describe('snap_manageState', () => {
clearSnapState,
getSnapState,
updateSnapState,
getMnemonic: jest.fn().mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE),
getMnemonic: jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES),
getUnlockPromise: jest.fn(),
});

Expand Down Expand Up @@ -192,7 +200,9 @@ describe('snap_manageState', () => {
clearSnapState,
getSnapState,
updateSnapState,
getMnemonic: jest.fn().mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE),
getMnemonic: jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES),
getUnlockPromise: jest.fn(),
});

Expand Down Expand Up @@ -237,7 +247,9 @@ describe('snap_manageState', () => {
clearSnapState,
getSnapState,
updateSnapState,
getMnemonic: jest.fn().mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE),
getMnemonic: jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES),
getUnlockPromise: jest.fn(),
});

Expand All @@ -263,7 +275,9 @@ describe('snap_manageState', () => {
clearSnapState,
getSnapState,
updateSnapState,
getMnemonic: jest.fn().mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE),
getMnemonic: jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES),
getUnlockPromise: jest.fn(),
});

Expand Down Expand Up @@ -298,7 +312,9 @@ describe('snap_manageState', () => {
clearSnapState,
getSnapState,
updateSnapState,
getMnemonic: jest.fn().mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE),
getMnemonic: jest
.fn()
.mockResolvedValue(TEST_SECRET_RECOVERY_PHRASE_BYTES),
getUnlockPromise: jest.fn(),
});

Expand Down
4 changes: 2 additions & 2 deletions packages/rpc-methods/src/restricted/manageState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type ManageStateMethodHooks = {
/**
* @returns The mnemonic of the user's primary keyring.
*/
getMnemonic: () => Promise<string>;
getMnemonic: () => Promise<Uint8Array>;

/**
* Waits for the extension to be unlocked.
Expand Down Expand Up @@ -121,7 +121,7 @@ export const STORAGE_SIZE_LIMIT = 104857600; // In bytes (100MB)

type GetEncryptionKeyArgs = {
snapId: string;
mnemonicPhrase: string;
mnemonicPhrase: Uint8Array;
};

/**
Expand Down
6 changes: 2 additions & 4 deletions packages/rpc-methods/src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { SIP_6_MAGIC_VALUE } from '@metamask/snaps-utils';
import { TEST_SECRET_RECOVERY_PHRASE_BYTES } from '@metamask/snaps-utils/test-utils';

import { ENTROPY_VECTORS } from './__fixtures__';
import { deriveEntropy } from './utils';

const TEST_SECRET_RECOVERY_PHRASE =
'test test test test test test test test test test test ball';

describe('deriveEntropy', () => {
it.each(ENTROPY_VECTORS)(
'derives entropy from the given parameters',
Expand All @@ -14,7 +12,7 @@ describe('deriveEntropy', () => {
await deriveEntropy({
input: snapId,
salt,
mnemonicPhrase: TEST_SECRET_RECOVERY_PHRASE,
mnemonicPhrase: TEST_SECRET_RECOVERY_PHRASE_BYTES,
magic: SIP_6_MAGIC_VALUE,
}),
).toStrictEqual(entropy);
Expand Down
4 changes: 2 additions & 2 deletions packages/rpc-methods/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ type DeriveEntropyOptions = {
/**
* The mnemonic phrase to use for entropy derivation.
*/
mnemonicPhrase: string;
mnemonicPhrase: Uint8Array;

/**
* A hardened BIP-32 index, which is used to derive the root key from the
Expand Down Expand Up @@ -136,7 +136,7 @@ export async function deriveEntropy({
// Derive the private key using BIP-32.
const { privateKey } = await SLIP10Node.fromDerivationPath({
derivationPath: [
`bip39:${mnemonicPhrase}`,
mnemonicPhrase,
`bip32:${magic}`,
...computedDerivationPath,
],
Expand Down
1 change: 1 addition & 0 deletions packages/snaps-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@metamask/eslint-config-jest": "^11.0.0",
"@metamask/eslint-config-nodejs": "^11.0.1",
"@metamask/eslint-config-typescript": "^11.0.0",
"@metamask/key-tree": "^6.2.0",
"@metamask/post-message-stream": "^6.1.0",
"@types/jest": "^27.5.1",
"@types/semver": "^7.3.10",
Expand Down
6 changes: 6 additions & 0 deletions packages/snaps-utils/src/test-utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// TODO: Import from root.
import { mnemonicPhraseToBytes } from '@metamask/key-tree/dist/utils';
import { SemVerVersion } from '@metamask/utils';

export const TEST_SECRET_RECOVERY_PHRASE =
'test test test test test test test test test test test ball';

export const TEST_SECRET_RECOVERY_PHRASE_BYTES = mnemonicPhraseToBytes(
TEST_SECRET_RECOVERY_PHRASE,
);

/**
* Tens/hundreds legacy tests use creation utils.
*
Expand Down
11 changes: 6 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2563,17 +2563,17 @@ __metadata:
languageName: node
linkType: hard

"@metamask/key-tree@npm:^6.1.0":
version: 6.1.0
resolution: "@metamask/key-tree@npm:6.1.0"
"@metamask/key-tree@npm:^6.2.0":
version: 6.2.0
resolution: "@metamask/key-tree@npm:6.2.0"
dependencies:
"@metamask/scure-bip39": ^2.1.0
"@metamask/utils": ^3.3.0
"@noble/ed25519": ^1.6.0
"@noble/hashes": ^1.0.0
"@noble/secp256k1": ^1.5.5
"@scure/base": ^1.0.0
checksum: 552fb8e59c4972521154ec45c0dc43e561c2cf248e8e4e730fd0a70c3890c69f0e89509387ba3ec64def973a53ad6af1c40b50f950aa1f22674d22d4a79cbf88
checksum: d1f6839ac63c7bee2ece7fcd39c29b7e3f054568d93c07469b7cb28b9dfc3dd1d5c3fbec07702b2029310d96356a7dbbb55bafc66c6fc817b2235a8e0994ec38
languageName: node
linkType: hard

Expand Down Expand Up @@ -2686,7 +2686,7 @@ __metadata:
"@metamask/eslint-config-jest": ^11.0.0
"@metamask/eslint-config-nodejs": ^11.0.1
"@metamask/eslint-config-typescript": ^11.0.0
"@metamask/key-tree": ^6.1.0
"@metamask/key-tree": ^6.2.0
"@metamask/permission-controller": ^1.0.1
"@metamask/snaps-ui": ^0.27.1
"@metamask/snaps-utils": ^0.27.1
Expand Down Expand Up @@ -3086,6 +3086,7 @@ __metadata:
"@metamask/eslint-config-jest": ^11.0.0
"@metamask/eslint-config-nodejs": ^11.0.1
"@metamask/eslint-config-typescript": ^11.0.0
"@metamask/key-tree": ^6.2.0
"@metamask/post-message-stream": ^6.1.0
"@metamask/providers": ^10.2.1
"@metamask/snaps-registry": ^1.0.0
Expand Down

0 comments on commit f20eb9a

Please sign in to comment.