Skip to content

Commit

Permalink
Move stuff back to rpc-methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Mrtenz committed Dec 14, 2022
1 parent b3b937e commit e33752a
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 118 deletions.
6 changes: 3 additions & 3 deletions packages/rpc-methods/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ module.exports = deepmerge(baseConfig, {
coverageThreshold: {
global: {
branches: 75.18,
functions: 87.14,
lines: 87.96,
statements: 87.57,
functions: 87.5,
lines: 88.67,
statements: 88.32,
},
},
testTimeout: 2500,
Expand Down
4 changes: 3 additions & 1 deletion packages/rpc-methods/src/restricted/getEntropy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import {
RestrictedMethodOptions,
ValidPermissionSpecification,
} from '@metamask/permission-controller';
import { deriveEntropy, SIP_6_MAGIC_VALUE } from '@metamask/snaps-utils';
import { SIP_6_MAGIC_VALUE } from '@metamask/snaps-utils';
import { assertStruct, Hex, NonEmptyArray } from '@metamask/utils';
import { ethErrors } from 'eth-rpc-errors';
import { Infer, literal, object, optional, string } from 'superstruct';

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

const targetKey = 'snap_getEntropy';

type GetEntropySpecificationBuilderOptions = {
Expand Down
6 changes: 2 additions & 4 deletions packages/rpc-methods/src/restricted/manageState.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { encrypt } from '@metamask/browser-passworder';
import {
deriveEntropy,
STATE_ENCRYPTION_MAGIC_VALUE,
} from '@metamask/snaps-utils';
import { STATE_ENCRYPTION_MAGIC_VALUE } from '@metamask/snaps-utils';
import {
MOCK_LOCAL_SNAP_ID,
MOCK_SNAP_ID,
TEST_SECRET_RECOVERY_PHRASE,
} from '@metamask/snaps-utils/test-utils';
import { ethErrors } from 'eth-rpc-errors';

import { deriveEntropy } from '../utils';
import {
getManageStateImplementation,
getValidatedParams,
Expand Down
7 changes: 3 additions & 4 deletions packages/rpc-methods/src/restricted/manageState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import {
RestrictedMethodOptions,
ValidPermissionSpecification,
} from '@metamask/permission-controller';
import {
deriveEntropy,
STATE_ENCRYPTION_MAGIC_VALUE,
} from '@metamask/snaps-utils';
import { STATE_ENCRYPTION_MAGIC_VALUE } from '@metamask/snaps-utils';
import {
Json,
NonEmptyArray,
Expand All @@ -19,6 +16,8 @@ import {
} from '@metamask/utils';
import { ethErrors } from 'eth-rpc-errors';

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

// The salt used for SIP-6-based entropy derivation.
export const STATE_ENCRYPTION_SALT = 'snap_manageState encryption';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { SIP_6_MAGIC_VALUE } from '@metamask/snaps-utils';

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

const TEST_SECRET_RECOVERY_PHRASE =
'test test test test test test test test test test test ball';
Expand Down
106 changes: 106 additions & 0 deletions packages/rpc-methods/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import { HardenedBIP32Node, SLIP10Node } from '@metamask/key-tree';
import { MagicValue } from '@metamask/snaps-utils';
import {
add0x,
assert,
concatBytes,
createDataView,
Hex,
stringToBytes,
} from '@metamask/utils';
import { keccak_256 as keccak256 } from '@noble/hashes/sha3';

const HARDENED_VALUE = 0x80000000;

/**
* Returns the subset of the specified `hooks` that are included in the
* `hookNames` object. This is a Principle of Least Authority (POLA) measure
Expand Down Expand Up @@ -42,3 +56,95 @@ export function selectHooks<
export function isEqual(a: unknown[], b: unknown[]): boolean {
return a.length === b.length && a.every((value, index) => value === b[index]);
}

/**
* 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: Uint8Array): HardenedBIP32Node[] {
const array: HardenedBIP32Node[] = [];
const view = 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}'` as const);
}

return array;
}

type DeriveEntropyOptions = {
/**
* The input value to derive entropy from.
*/
input: string;

/**
* An optional salt to use when deriving entropy.
*/
salt?: string;

/**
* The mnemonic phrase to use for entropy derivation.
*/
mnemonicPhrase: string;

/**
* A hardened BIP-32 index, which is used to derive the root key from the
* mnemonic phrase.
*/
magic: MagicValue;
};

/**
* Derive entropy from the given mnemonic phrase 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.mnemonicPhrase - The mnemonic phrase 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.
* @returns The derived entropy.
*/
export async function deriveEntropy({
input,
salt = '',
mnemonicPhrase,
magic,
}: DeriveEntropyOptions): Promise<Hex> {
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);

// Derive the private key using BIP-32.
const { privateKey } = await SLIP10Node.fromDerivationPath({
derivationPath: [
`bip39:${mnemonicPhrase}`,
`bip32:${magic}`,
...computedDerivationPath,
],
curve: 'secp256k1',
});

// This should never happen, but this keeps TypeScript happy.
assert(privateKey, 'Failed to derive the entropy.');

return add0x(privateKey);
}
105 changes: 0 additions & 105 deletions packages/snaps-utils/src/entropy.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
import { HardenedBIP32Node, SLIP10Node } from '@metamask/key-tree';
import {
add0x,
assert,
concatBytes,
createDataView,
Hex,
stringToBytes,
} from '@metamask/utils';
import { keccak_256 as keccak256 } from '@noble/hashes/sha3';

const HARDENED_VALUE = 0x80000000;

// 0xd36e6170 - 0x80000000
export const SIP_6_MAGIC_VALUE = `1399742832'` as `${number}'`;

Expand All @@ -20,95 +7,3 @@ export const STATE_ENCRYPTION_MAGIC_VALUE = `572232532'` as `${number}'`;
export type MagicValue =
| typeof SIP_6_MAGIC_VALUE
| typeof STATE_ENCRYPTION_MAGIC_VALUE;

/**
* 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: Uint8Array): HardenedBIP32Node[] {
const array: HardenedBIP32Node[] = [];
const view = 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}'` as const);
}

return array;
}

type DeriveEntropyOptions = {
/**
* The input value to derive entropy from.
*/
input: string;

/**
* An optional salt to use when deriving entropy.
*/
salt?: string;

/**
* The mnemonic phrase to use for entropy derivation.
*/
mnemonicPhrase: string;

/**
* A hardened BIP-32 index, which is used to derive the root key from the
* mnemonic phrase.
*/
magic: MagicValue;
};

/**
* Derive entropy from the given mnemonic phrase 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.mnemonicPhrase - The mnemonic phrase 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.
* @returns The derived entropy.
*/
export async function deriveEntropy({
input,
salt = '',
mnemonicPhrase,
magic,
}: DeriveEntropyOptions): Promise<Hex> {
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);

// Derive the private key using BIP-32.
const { privateKey } = await SLIP10Node.fromDerivationPath({
derivationPath: [
`bip39:${mnemonicPhrase}`,
`bip32:${magic}`,
...computedDerivationPath,
],
curve: 'secp256k1',
});

// This should never happen, but this keeps TypeScript happy.
assert(privateKey, 'Failed to derive the entropy.');

return add0x(privateKey);
}

0 comments on commit e33752a

Please sign in to comment.