Skip to content

Commit

Permalink
Fix deriveKeyFromPath interface, refactor derivers (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
rekmarks authored Feb 27, 2021
1 parent acb5b4b commit 96343d0
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 19 deletions.
3 changes: 2 additions & 1 deletion src/derivers/bip32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ export function bip32PathToMultipath(bip32Path: string): string {
return multipath;
}

export function deriveChildKey(parentKey: Buffer, pathPart: string): Buffer {
export function deriveChildKey(pathPart: string, parentKey: Buffer): Buffer {
const isHardened = pathPart.includes(`'`);
const indexPart = pathPart.split(`'`)[0];
const childIndex = parseInt(indexPart, 10);
assert(childIndex < HARDENED_OFFSET, 'Invalid index');
assert(Boolean(parentKey), 'Must provide parentKey');
assert(parentKey.length === 64, 'Parent key invalid length');

const parentPrivateKey = parentKey.slice(0, 32);
Expand Down
2 changes: 1 addition & 1 deletion src/derivers/bip39.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function bip39MnemonicToMultipath(mnemonic: string): string {
}

// this creates a child key using bip39, ignoring the parent key
export function deriveChildKey(_parentKey: unknown, pathPart: string): Buffer {
export function deriveChildKey(pathPart: string, _parentKey?: never): Buffer {
const mnemonic = pathPart;
const seedBuffer = bip39.mnemonicToSeed(mnemonic);
const entropy = crypto.createHmac('sha512', ROOT_BASE_SECRET).update(seedBuffer).digest();
Expand Down
6 changes: 5 additions & 1 deletion src/derivers/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as bip32 from './bip32';
import * as bip39 from './bip39';

export = {
export interface Deriver {
deriveChildKey: (pathPart: string, parentKey?: Buffer) => Buffer;
}

export const derivers = {
bip32,
bip39,
};
38 changes: 29 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import bip39 from 'bip39';
import derivers from './derivers';
import { derivers, Deriver } from './derivers';

/**
* Converts the given BIP-39 mnemonic to a cryptographic seed.
* @param mnemonic - The BIP-39 mnemonic.
* @returns The cryptographic seed corresponding to the given mnemonic.
*/
export function mnemonicToSeed(mnemonic: string): Buffer {
return bip39.mnemonicToSeed(mnemonic);
}
Expand All @@ -18,11 +23,26 @@ export function mnemonicToSeed(mnemonic: string): Buffer {
*/

/**
* @param {string} pathSegment - A full or leaf HD path segment. If full,
* optionally preceded by "bip39:<SPACE_DELIMITED_SEED_PHRASE>/".
* @param {Buffer} [parentKey] - The parent key of the given path segment.
* Takes a full or partial HD path string and returns the key corresponding to
* the given path, with the following constraints:
*
* - If the path starts with a BIP-32 segment, a parent key must be provided.
* - If the path starts with a BIP-39 segment, a parent key may NOT be provided.
* - The path cannot exceed 5 BIP-32 segments in length, optionally preceded by
* a single BIP-39 segment.
*
* WARNING: It is the consumer's responsibility to ensure that the path is valid
* relative to its parent key.
*
* @param pathSegment - A full or partial HD path, e.g.:
* bip39:SEED_PHRASE/bip32:44'/bip32:60'/bip32:0'/bip32:0/bip32:0
*
* BIP-39 seed phrases must be lowercase, space-delimited, and 12-24 words long.
*
* @param parentKey - The parent key of the given path segment, if any.
* @returns The derived key.
*/
export function deriveKeyFromPath(pathSegment: string, parentKey: Buffer) {
export function deriveKeyFromPath(pathSegment: string, parentKey?: Buffer): Buffer {
validateDeriveKeyParams(pathSegment, parentKey);

let key = parentKey;
Expand All @@ -33,13 +53,13 @@ export function deriveKeyFromPath(pathSegment: string, parentKey: Buffer) {
if (!(hasDeriver(pathType))) {
throw new Error(`Unknown derivation type "${pathType}"`);
}
const deriver = derivers[pathType];
const childKey = deriver.deriveChildKey(key, pathValue);
const deriver = derivers[pathType] as Deriver;
const childKey = deriver.deriveChildKey(pathValue, key);
// continue deriving from child key
key = childKey;
});

return key;
return key as Buffer;
}

function hasDeriver(pathType: string): pathType is keyof typeof derivers {
Expand Down Expand Up @@ -67,7 +87,7 @@ const BIP_39_PATH_REGEX = /^bip39:([a-z]+){1}( [a-z]+){11,23}$/u;
*/
const MULTI_PATH_REGEX = /^(bip39:([a-z]+){1}( [a-z]+){11,23}\/)?(bip32:\d+'?\/){0,4}(bip32:\d+'?)$/u;

function validateDeriveKeyParams(pathSegment: string, parentKey: Buffer) {
function validateDeriveKeyParams(pathSegment: string, parentKey?: Buffer) {
// The path segment must be one of the following:
// - A lone BIP-32 path segment
// - A lone BIP-39 path segment
Expand Down
14 changes: 7 additions & 7 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const {
privateKeyToEthAddress,
},
bip39: { deriveChildKey: bip39Derive, bip39MnemonicToMultipath },
} = require('../dist/derivers');
} = require('../dist/derivers').derivers;

const defaultEthereumPath = `m/44'/60'/0'/0`;

Expand Down Expand Up @@ -121,15 +121,15 @@ test('bip32Derive', (t) => {
// generate parent key
let parentKey = null;
parentKey = bip39Derive(
parentKey,
`romance hurry grit huge rifle ordinary loud toss sound congress upset twist`,
parentKey,
);
parentKey = bip32Derive(parentKey, `44'`);
parentKey = bip32Derive(parentKey, `60'`);
parentKey = bip32Derive(parentKey, `0'`);
parentKey = bip32Derive(parentKey, `0`);
parentKey = bip32Derive(`44'`, parentKey);
parentKey = bip32Derive(`60'`, parentKey);
parentKey = bip32Derive(`0'`, parentKey);
parentKey = bip32Derive(`0`, parentKey);
const keys = expectedAddresses.map((_, index) => {
return bip32Derive(parentKey, `${index}`);
return bip32Derive(`${index}`, parentKey);
});
// validate addresses
keys.forEach((key, index) => {
Expand Down

0 comments on commit 96343d0

Please sign in to comment.