Add isChildKey to derived key

This commit is contained in:
Jacob Evans
2018-04-11 12:22:41 +10:00
parent 4e4842a62f
commit 9169913a2c
4 changed files with 73 additions and 72 deletions

View File

@@ -22,7 +22,6 @@ import { walletUtils } from '../utils/wallet_utils';
import { BaseWalletSubprovider } from './base_wallet_subprovider'; import { BaseWalletSubprovider } from './base_wallet_subprovider';
const DEFAULT_DERIVATION_PATH = `44'/60'/0'`; const DEFAULT_DERIVATION_PATH = `44'/60'/0'`;
const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10;
const ASK_FOR_ON_DEVICE_CONFIRMATION = false; const ASK_FOR_ON_DEVICE_CONFIRMATION = false;
const SHOULD_GET_CHAIN_CODE = true; const SHOULD_GET_CHAIN_CODE = true;
const IS_CHILD_KEY = true; const IS_CHILD_KEY = true;
@@ -59,9 +58,9 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
: ASK_FOR_ON_DEVICE_CONFIRMATION; : ASK_FOR_ON_DEVICE_CONFIRMATION;
this._addressSearchLimit = this._addressSearchLimit =
!_.isUndefined(config.accountFetchingConfigs) && !_.isUndefined(config.accountFetchingConfigs) &&
!_.isUndefined(config.accountFetchingConfigs.numAddressesToReturn) !_.isUndefined(config.accountFetchingConfigs.addressSearchLimit)
? config.accountFetchingConfigs.numAddressesToReturn ? config.accountFetchingConfigs.addressSearchLimit
: DEFAULT_NUM_ADDRESSES_TO_FETCH; : walletUtils.DEFAULT_ADDRESS_SEARCH_LIMIT;
} }
/** /**
* Retrieve the set derivation path * Retrieve the set derivation path
@@ -86,15 +85,12 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
* @param numberOfAccounts Number of accounts to retrieve (default: 10) * @param numberOfAccounts Number of accounts to retrieve (default: 10)
* @return An array of accounts * @return An array of accounts
*/ */
public async getAccountsAsync(numberOfAccounts: number = DEFAULT_NUM_ADDRESSES_TO_FETCH): Promise<string[]> { public async getAccountsAsync(
const initialHDKey = await this._initialHDKeyAsync(); numberOfAccounts: number = walletUtils.DEFAULT_NUM_ADDRESSES_TO_FETCH,
const derivedKeys = walletUtils._calculateDerivedHDKeys( ): Promise<string[]> {
initialHDKey, const offset = 0;
this._derivationPath, const initialHDerivedKey = await this._initialDerivedKeyAsync();
numberOfAccounts, const derivedKeys = walletUtils.calculateDerivedHDKeys(initialHDerivedKey, numberOfAccounts, offset);
0,
true,
);
const accounts = _.map(derivedKeys, 'address'); const accounts = _.map(derivedKeys, 'address');
return accounts; return accounts;
} }
@@ -108,10 +104,10 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
*/ */
public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {
LedgerSubprovider._validateTxParams(txParams); LedgerSubprovider._validateTxParams(txParams);
const initialHDKey = await this._initialHDKeyAsync(); const initialDerivedKey = await this._initialDerivedKeyAsync();
const derivedKey = _.isUndefined(txParams.from) const derivedKey = _.isUndefined(txParams.from)
? walletUtils._firstDerivedKey(initialHDKey, this._derivationPath, IS_CHILD_KEY) ? walletUtils._firstDerivedKey(initialDerivedKey)
: this._findDerivedKeyByPublicAddress(initialHDKey, txParams.from); : this._findDerivedKeyByPublicAddress(initialDerivedKey, txParams.from);
this._ledgerClientIfExists = await this._createLedgerClientAsync(); this._ledgerClientIfExists = await this._createLedgerClientAsync();
@@ -163,10 +159,10 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage); throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
} }
assert.isHexString('data', data); assert.isHexString('data', data);
const initialHDKey = await this._initialHDKeyAsync(); const initialDerivedKey = await this._initialDerivedKeyAsync();
const derivedKey = _.isUndefined(address) const derivedKey = _.isUndefined(address)
? walletUtils._firstDerivedKey(initialHDKey, this._derivationPath, IS_CHILD_KEY) ? walletUtils._firstDerivedKey(initialDerivedKey)
: this._findDerivedKeyByPublicAddress(initialHDKey, address); : this._findDerivedKeyByPublicAddress(initialDerivedKey, address);
this._ledgerClientIfExists = await this._createLedgerClientAsync(); this._ledgerClientIfExists = await this._createLedgerClientAsync();
try { try {
@@ -208,7 +204,7 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
this._ledgerClientIfExists = undefined; this._ledgerClientIfExists = undefined;
this._connectionLock.release(); this._connectionLock.release();
} }
private async _initialHDKeyAsync(): Promise<HDNode> { private async _initialDerivedKeyAsync(): Promise<DerivedHDKey> {
this._ledgerClientIfExists = await this._createLedgerClientAsync(); this._ledgerClientIfExists = await this._createLedgerClientAsync();
let ledgerResponse; let ledgerResponse;
@@ -224,16 +220,16 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
const hdKey = new HDNode(); const hdKey = new HDNode();
hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex'); hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex');
hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex'); hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex');
return hdKey; return {
hdKey,
address: ledgerResponse.address,
isChildKey: true,
derivationPath: this._derivationPath,
derivationIndex: 0,
};
} }
private _findDerivedKeyByPublicAddress(initalHDKey: HDNode, address: string): DerivedHDKey { private _findDerivedKeyByPublicAddress(initalHDKey: DerivedHDKey, address: string): DerivedHDKey {
const matchedDerivedKey = walletUtils._findDerivedKeyByAddress( const matchedDerivedKey = walletUtils.findDerivedKeyByAddress(address, initalHDKey, this._addressSearchLimit);
address,
initalHDKey,
this._derivationPath,
this._addressSearchLimit,
IS_CHILD_KEY,
);
if (_.isUndefined(matchedDerivedKey)) { if (_.isUndefined(matchedDerivedKey)) {
throw new Error(`${WalletSubproviderErrors.AddressNotFound}: ${address}`); throw new Error(`${WalletSubproviderErrors.AddressNotFound}: ${address}`);
} }

View File

@@ -11,8 +11,6 @@ import { BaseWalletSubprovider } from './base_wallet_subprovider';
import { PrivateKeyWalletSubprovider } from './private_key_wallet_subprovider'; import { PrivateKeyWalletSubprovider } from './private_key_wallet_subprovider';
const DEFAULT_DERIVATION_PATH = `44'/60'/0'/0`; const DEFAULT_DERIVATION_PATH = `44'/60'/0'/0`;
const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10;
const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000;
/** /**
* This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface.
@@ -22,7 +20,7 @@ const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000;
export class MnemonicWalletSubprovider extends BaseWalletSubprovider { export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
private _addressSearchLimit: number; private _addressSearchLimit: number;
private _derivationPath: string; private _derivationPath: string;
private _hdKey: HDNode; private _derivedKey: DerivedHDKey;
/** /**
* Instantiates a MnemonicWalletSubprovider. Defaults to derivationPath set to `44'/60'/0'/0`. * Instantiates a MnemonicWalletSubprovider. Defaults to derivationPath set to `44'/60'/0'/0`.
* This is the default in TestRPC/Ganache, this can be overridden if desired. * This is the default in TestRPC/Ganache, this can be overridden if desired.
@@ -34,15 +32,22 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
constructor( constructor(
mnemonic: string, mnemonic: string,
derivationPath: string = DEFAULT_DERIVATION_PATH, derivationPath: string = DEFAULT_DERIVATION_PATH,
addressSearchLimit: number = DEFAULT_ADDRESS_SEARCH_LIMIT, addressSearchLimit: number = walletUtils.DEFAULT_ADDRESS_SEARCH_LIMIT,
) { ) {
assert.isString('mnemonic', mnemonic); assert.isString('mnemonic', mnemonic);
assert.isString('derivationPath', derivationPath); assert.isString('derivationPath', derivationPath);
assert.isNumber('addressSearchLimit', addressSearchLimit); assert.isNumber('addressSearchLimit', addressSearchLimit);
super(); super();
const seed = bip39.mnemonicToSeed(mnemonic); const seed = bip39.mnemonicToSeed(mnemonic);
this._hdKey = HDNode.fromMasterSeed(seed); const hdKey = HDNode.fromMasterSeed(seed);
this._derivationPath = derivationPath; this._derivationPath = derivationPath;
this._derivedKey = {
address: walletUtils.addressOfHDKey(hdKey),
derivationPath: this._derivationPath,
derivationIndex: 0,
hdKey,
isChildKey: false,
};
this._addressSearchLimit = addressSearchLimit; this._addressSearchLimit = addressSearchLimit;
} }
/** /**
@@ -66,8 +71,10 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
* @param numberOfAccounts Number of accounts to retrieve (default: 10) * @param numberOfAccounts Number of accounts to retrieve (default: 10)
* @return An array of accounts * @return An array of accounts
*/ */
public async getAccountsAsync(numberOfAccounts: number = DEFAULT_NUM_ADDRESSES_TO_FETCH): Promise<string[]> { public async getAccountsAsync(
const derivedKeys = walletUtils._calculateDerivedHDKeys(this._hdKey, this._derivationPath, numberOfAccounts); numberOfAccounts: number = walletUtils.DEFAULT_NUM_ADDRESSES_TO_FETCH,
): Promise<string[]> {
const derivedKeys = walletUtils.calculateDerivedHDKeys(this._derivedKey, numberOfAccounts);
const accounts = _.map(derivedKeys, 'address'); const accounts = _.map(derivedKeys, 'address');
return accounts; return accounts;
} }
@@ -82,7 +89,7 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
*/ */
public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {
const derivedKey = _.isUndefined(txParams.from) const derivedKey = _.isUndefined(txParams.from)
? walletUtils._firstDerivedKey(this._hdKey, this._derivationPath) ? walletUtils._firstDerivedKey(this._derivedKey)
: this._findDerivedKeyByPublicAddress(txParams.from); : this._findDerivedKeyByPublicAddress(txParams.from);
const privateKeyWallet = new PrivateKeyWalletSubprovider(derivedKey.hdKey.privateKey.toString('hex')); const privateKeyWallet = new PrivateKeyWalletSubprovider(derivedKey.hdKey.privateKey.toString('hex'));
const signedTx = privateKeyWallet.signTransactionAsync(txParams); const signedTx = privateKeyWallet.signTransactionAsync(txParams);
@@ -100,17 +107,16 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
*/ */
public async signPersonalMessageAsync(data: string, address?: string): Promise<string> { public async signPersonalMessageAsync(data: string, address?: string): Promise<string> {
const derivedKey = _.isUndefined(address) const derivedKey = _.isUndefined(address)
? walletUtils._firstDerivedKey(this._hdKey, this._derivationPath) ? walletUtils._firstDerivedKey(this._derivedKey)
: this._findDerivedKeyByPublicAddress(address); : this._findDerivedKeyByPublicAddress(address);
const privateKeyWallet = new PrivateKeyWalletSubprovider(derivedKey.hdKey.privateKey.toString('hex')); const privateKeyWallet = new PrivateKeyWalletSubprovider(derivedKey.hdKey.privateKey.toString('hex'));
const sig = await privateKeyWallet.signPersonalMessageAsync(data, derivedKey.address); const sig = await privateKeyWallet.signPersonalMessageAsync(data, derivedKey.address);
return sig; return sig;
} }
private _findDerivedKeyByPublicAddress(address: string): DerivedHDKey { private _findDerivedKeyByPublicAddress(address: string): DerivedHDKey {
const matchedDerivedKey = walletUtils._findDerivedKeyByAddress( const matchedDerivedKey = walletUtils.findDerivedKeyByAddress(
address, address,
this._hdKey, this._derivedKey,
this._derivationPath,
this._addressSearchLimit, this._addressSearchLimit,
); );
if (_.isUndefined(matchedDerivedKey)) { if (_.isUndefined(matchedDerivedKey)) {

View File

@@ -51,6 +51,7 @@ export interface LedgerSubproviderConfigs {
* before fetching their addresses * before fetching their addresses
*/ */
export interface AccountFetchingConfigs { export interface AccountFetchingConfigs {
addressSearchLimit?: number;
numAddressesToReturn?: number; numAddressesToReturn?: number;
shouldAskForOnDeviceConfirmation?: boolean; shouldAskForOnDeviceConfirmation?: boolean;
} }
@@ -116,6 +117,7 @@ export interface DerivedHDKey {
derivationPath: string; derivationPath: string;
derivationIndex: number; derivationIndex: number;
hdKey: HDNode; hdKey: HDNode;
isChildKey: boolean;
} }
export type ErrorCallback = (err: Error | null, data?: any) => void; export type ErrorCallback = (err: Error | null, data?: any) => void;

View File

@@ -8,54 +8,51 @@ const DEFAULT_ADDRESS_SEARCH_OFFSET = 0;
const BATCH_SIZE = 10; const BATCH_SIZE = 10;
export const walletUtils = { export const walletUtils = {
_calculateDerivedHDKeys( DEFAULT_NUM_ADDRESSES_TO_FETCH: 10,
initialHDKey: HDNode, DEFAULT_ADDRESS_SEARCH_LIMIT: 1000,
derivationPath: string, calculateDerivedHDKeys(
initialDerivedKey: DerivedHDKey,
searchLimit: number, searchLimit: number,
offset: number = DEFAULT_ADDRESS_SEARCH_OFFSET, offset: number = DEFAULT_ADDRESS_SEARCH_OFFSET,
isChildKey: boolean = false,
): DerivedHDKey[] { ): DerivedHDKey[] {
const derivedKeys: DerivedHDKey[] = []; const derivedKeys: DerivedHDKey[] = [];
_.times(searchLimit, i => { _.times(searchLimit, i => {
const derivationPath = initialDerivedKey.derivationPath;
const derivationIndex = offset + i; const derivationIndex = offset + i;
// Normally we need to set the full derivation path to walk the tree from the root // If the DerivedHDKey is a child then we walk relative, if not we walk the full derivation path
// as the initial key is at the root. const path = initialDerivedKey.isChildKey
// But with ledger the initial key is a child so we walk the tree relative to that child ? `m/${derivationIndex}`
const path = isChildKey ? `m/${derivationIndex}` : `m/${derivationPath}/${derivationIndex}`; : `m/${derivationPath}/${derivationIndex}`;
const hdKey = initialHDKey.derive(path); const hdKey = initialDerivedKey.hdKey.derive(path);
const derivedPublicKey = hdKey.publicKey; const address = walletUtils.addressOfHDKey(hdKey);
const shouldSanitizePublicKey = true;
const ethereumAddressUnprefixed = ethUtil
.publicToAddress(derivedPublicKey, shouldSanitizePublicKey)
.toString('hex');
const address = ethUtil.addHexPrefix(ethereumAddressUnprefixed);
const derivedKey: DerivedHDKey = { const derivedKey: DerivedHDKey = {
derivationPath,
hdKey,
address, address,
hdKey,
derivationPath,
derivationIndex, derivationIndex,
isChildKey: initialDerivedKey.isChildKey,
}; };
derivedKeys.push(derivedKey); derivedKeys.push(derivedKey);
}); });
return derivedKeys; return derivedKeys;
}, },
addressOfHDKey(hdKey: HDNode): string {
_findDerivedKeyByAddress( const shouldSanitizePublicKey = true;
const derivedPublicKey = hdKey.publicKey;
const ethereumAddressUnprefixed = ethUtil
.publicToAddress(derivedPublicKey, shouldSanitizePublicKey)
.toString('hex');
const address = ethUtil.addHexPrefix(ethereumAddressUnprefixed);
return address;
},
findDerivedKeyByAddress(
address: string, address: string,
initialHDKey: HDNode, initialDerivedKey: DerivedHDKey,
derivationPath: string,
searchLimit: number, searchLimit: number,
isChild: boolean = false,
): DerivedHDKey | undefined { ): DerivedHDKey | undefined {
let matchedKey: DerivedHDKey | undefined; let matchedKey: DerivedHDKey | undefined;
for (let index = 0; index < searchLimit; index = index + BATCH_SIZE) { for (let index = 0; index < searchLimit; index = index + BATCH_SIZE) {
const derivedKeys = walletUtils._calculateDerivedHDKeys( const derivedKeys = walletUtils.calculateDerivedHDKeys(initialDerivedKey, BATCH_SIZE, index);
initialHDKey,
derivationPath,
BATCH_SIZE,
index,
isChild,
);
matchedKey = _.find(derivedKeys, derivedKey => derivedKey.address === address); matchedKey = _.find(derivedKeys, derivedKey => derivedKey.address === address);
if (matchedKey) { if (matchedKey) {
break; break;
@@ -64,8 +61,8 @@ export const walletUtils = {
return matchedKey; return matchedKey;
}, },
_firstDerivedKey(initialHDKey: HDNode, derivationPath: string, isChild: boolean = false): DerivedHDKey { _firstDerivedKey(initialDerivedKey: DerivedHDKey): DerivedHDKey {
const derivedKeys = walletUtils._calculateDerivedHDKeys(initialHDKey, derivationPath, 1, 0, isChild); const derivedKeys = walletUtils.calculateDerivedHDKeys(initialDerivedKey, 1, 0);
const firstDerivedKey = derivedKeys[0]; const firstDerivedKey = derivedKeys[0];
return firstDerivedKey; return firstDerivedKey;
}, },