diff options
-rw-r--r-- | packages/subproviders/CHANGELOG.json | 10 | ||||
-rw-r--r-- | packages/subproviders/package-lock.json | 25 | ||||
-rw-r--r-- | packages/subproviders/package.json | 9 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/ledger.ts | 73 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/mnemonic_wallet.ts | 91 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/private_key_wallet.ts | 21 | ||||
-rw-r--r-- | packages/subproviders/src/types.ts | 18 | ||||
-rw-r--r-- | packages/subproviders/src/utils/wallet_utils.ts | 40 | ||||
-rw-r--r-- | packages/subproviders/test/integration/ledger_subprovider_test.ts | 2 | ||||
-rw-r--r-- | packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts | 12 | ||||
-rw-r--r-- | packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts | 16 | ||||
-rw-r--r-- | packages/subproviders/test/utils/fixture_data.ts | 4 |
12 files changed, 164 insertions, 157 deletions
diff --git a/packages/subproviders/CHANGELOG.json b/packages/subproviders/CHANGELOG.json index 554fbd0cf..5287ee987 100644 --- a/packages/subproviders/CHANGELOG.json +++ b/packages/subproviders/CHANGELOG.json @@ -1,14 +1,18 @@ [ { - "version": "0.8.5", + "version": "0.9.0", "changes": [ { - "note": "Add private key subprovider and refactor shared functionality into a base wallet subprovider", + "note": "Add private key subprovider and refactor shared functionality into a base wallet subprovider.", "pr": 506 }, { + "note": "Add mnemonic wallet subprovider, deprecating our truffle-hdwallet-provider fork.", + "pr": 507 + }, + { "note": - "Add mnemonic wallet subprovider, deprecating our truffle-hdwallet-provider fork. Support multiple addresses in ledger and mnemonic wallets", + "Support multiple addresses in ledger and mnemonic wallets. Renamed derivationPath to baseDerivationPath.", "pr": 507 } ] diff --git a/packages/subproviders/package-lock.json b/packages/subproviders/package-lock.json deleted file mode 100644 index 61675f9ea..000000000 --- a/packages/subproviders/package-lock.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@0xproject/subproviders", - "version": "0.8.4", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/bip39": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/bip39/-/bip39-2.4.0.tgz", - "integrity": "sha512-qxJBGh55SPbSGv+91D6H3WOR8vKdA/p8Oc58oK/DTbORgjO6Ebuo8MRzdy2OWi+dw/lxtX4VWJkkCUTSQvhAnw==", - "dev": true, - "requires": { - "@types/node": "9.6.2" - }, - "dependencies": { - "@types/node": { - "version": "9.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.2.tgz", - "integrity": "sha512-UWkRY9X7RQHp5OhhRIIka58/gVVycL1zHZu0OTsT5LI86ABaMOSbUjAl+b0FeDhQcxclrkyft3kW5QWdMRs8wQ==", - "dev": true - } - } - } - } -} diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index d557eef29..afb6ef5d6 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -21,15 +21,14 @@ "manual:postpublish": "yarn build; node ./scripts/postpublish.js", "docs:stage": "yarn build && node ./scripts/stage_docs.js", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", - "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" + "upload_docs_json": + "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" }, "config": { "postpublish": { "assets": [], "docPublishConfigs": { - "extraFileIncludes": [ - "../types/src/index.ts" - ], + "extraFileIncludes": ["../types/src/index.ts"], "s3BucketPath": "s3://doc-jsons/subproviders/", "s3StagingBucketPath": "s3://staging-doc-jsons/subproviders/" } @@ -46,6 +45,7 @@ "ethereumjs-tx": "^1.3.3", "ethereumjs-util": "^5.1.1", "ganache-core": "0xProject/ganache-core", + "bip39": "^2.5.0", "hdkey": "^0.7.1", "lodash": "^4.17.4", "semaphore-async-await": "^1.5.1", @@ -60,7 +60,6 @@ "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/node": "^8.0.53", - "bip39": "^2.5.0", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "copyfiles": "^1.2.0", diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index 9b2d9d7d0..33aa5c1bf 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -8,7 +8,7 @@ import { Lock } from 'semaphore-async-await'; import { Callback, - DerivedHDKey, + DerivedHDKeyInfo, LedgerEthereumClient, LedgerEthereumClientFactoryAsync, LedgerSubproviderConfigs, @@ -21,9 +21,11 @@ import { walletUtils } from '../utils/wallet_utils'; import { BaseWalletSubprovider } from './base_wallet_subprovider'; -const DEFAULT_DERIVATION_PATH = `44'/60'/0'`; +const DEFAULT_BASE_DERIVATION_PATH = `44'/60'/0'`; const ASK_FOR_ON_DEVICE_CONFIRMATION = false; const SHOULD_GET_CHAIN_CODE = true; +const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10; +const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000; /** * Subprovider for interfacing with a user's [Ledger Nano S](https://www.ledgerwallet.com/products/ledger-nano-s). @@ -34,7 +36,7 @@ export class LedgerSubprovider extends BaseWalletSubprovider { private _nonceLock = new Lock(); private _connectionLock = new Lock(); private _networkId: number; - private _derivationBasePath: string; + private _baseDerivationPath: string; private _ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; private _ledgerClientIfExists?: LedgerEthereumClient; private _shouldAlwaysAskForConfirmation: boolean; @@ -49,7 +51,7 @@ export class LedgerSubprovider extends BaseWalletSubprovider { super(); this._networkId = config.networkId; this._ledgerEthereumClientFactoryAsync = config.ledgerEthereumClientFactoryAsync; - this._derivationBasePath = config.derivationPath || DEFAULT_DERIVATION_PATH; + this._baseDerivationPath = config.baseDerivationPath || DEFAULT_BASE_DERIVATION_PATH; this._shouldAlwaysAskForConfirmation = !_.isUndefined(config.accountFetchingConfigs) && !_.isUndefined(config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation) @@ -59,21 +61,21 @@ export class LedgerSubprovider extends BaseWalletSubprovider { !_.isUndefined(config.accountFetchingConfigs) && !_.isUndefined(config.accountFetchingConfigs.addressSearchLimit) ? config.accountFetchingConfigs.addressSearchLimit - : walletUtils.DEFAULT_ADDRESS_SEARCH_LIMIT; + : DEFAULT_ADDRESS_SEARCH_LIMIT; } /** * Retrieve the set derivation path * @returns derivation path */ public getPath(): string { - return this._derivationBasePath; + return this._baseDerivationPath; } /** * Set a desired derivation path when computing the available user addresses - * @param derivationPath The desired derivation path (e.g `44'/60'/0'`) + * @param basDerivationPath The desired derivation path (e.g `44'/60'/0'`) */ - public setPath(derivationPath: string) { - this._derivationBasePath = derivationPath; + public setPath(basDerivationPath: string) { + this._baseDerivationPath = basDerivationPath; } /** * Retrieve a users Ledger accounts. The accounts are derived from the derivationPath, @@ -84,12 +86,10 @@ export class LedgerSubprovider extends BaseWalletSubprovider { * @param numberOfAccounts Number of accounts to retrieve (default: 10) * @return An array of accounts */ - public async getAccountsAsync( - numberOfAccounts: number = walletUtils.DEFAULT_NUM_ADDRESSES_TO_FETCH, - ): Promise<string[]> { - const initialDerivedKey = await this._initialDerivedKeyAsync(); - const derivedKeys = walletUtils.calculateDerivedHDKeys(initialDerivedKey, numberOfAccounts); - const accounts = _.map(derivedKeys, 'address'); + public async getAccountsAsync(numberOfAccounts: number = DEFAULT_NUM_ADDRESSES_TO_FETCH): Promise<string[]> { + const initialDerivedKeyInfo = await this._initialDerivedKeyInfoAsync(); + const derivedKeyInfos = walletUtils.calculateDerivedHDKeyInfos(initialDerivedKeyInfo, numberOfAccounts); + const accounts = _.map(derivedKeyInfos, k => k.address); return accounts; } /** @@ -102,8 +102,11 @@ export class LedgerSubprovider extends BaseWalletSubprovider { */ public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { LedgerSubprovider._validateTxParams(txParams); - const initialDerivedKey = await this._initialDerivedKeyAsync(); - const derivedKey = this._findDerivedKeyByPublicAddress(initialDerivedKey, txParams.from); + if (_.isUndefined(txParams.from) || !addressUtils.isAddress(txParams.from)) { + throw new Error(WalletSubproviderErrors.FromAddressMissingOrInvalid); + } + const initialDerivedKeyInfo = await this._initialDerivedKeyInfoAsync(); + const derivedKeyInfo = this._findDerivedKeyInfoForAddress(initialDerivedKeyInfo, txParams.from); const ledgerClient = await this._createLedgerClientAsync(); @@ -116,7 +119,7 @@ export class LedgerSubprovider extends BaseWalletSubprovider { const txHex = tx.serialize().toString('hex'); try { - const fullDerivationPath = derivedKey.derivationPath; + const fullDerivationPath = derivedKeyInfo.derivationPath; const result = await ledgerClient.signTransaction(fullDerivationPath, txHex); // Store signature in transaction tx.r = Buffer.from(result.r, 'hex'); @@ -155,12 +158,13 @@ export class LedgerSubprovider extends BaseWalletSubprovider { throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage); } assert.isHexString('data', data); - const initialDerivedKey = await this._initialDerivedKeyAsync(); - const derivedKey = this._findDerivedKeyByPublicAddress(initialDerivedKey, address); + assert.isETHAddressHex('address', address); + const initialDerivedKeyInfo = await this._initialDerivedKeyInfoAsync(); + const derivedKeyInfo = this._findDerivedKeyInfoForAddress(initialDerivedKeyInfo, address); const ledgerClient = await this._createLedgerClientAsync(); try { - const fullDerivationPath = derivedKey.derivationPath; + const fullDerivationPath = derivedKeyInfo.derivationPath; const result = await ledgerClient.signPersonalMessage(fullDerivationPath, ethUtil.stripHexPrefix(data)); const v = result.v - 27; let vHex = v.toString(16); @@ -196,13 +200,13 @@ export class LedgerSubprovider extends BaseWalletSubprovider { this._ledgerClientIfExists = undefined; this._connectionLock.release(); } - private async _initialDerivedKeyAsync(): Promise<DerivedHDKey> { + private async _initialDerivedKeyInfoAsync(): Promise<DerivedHDKeyInfo> { const ledgerClient = await this._createLedgerClientAsync(); let ledgerResponse; try { ledgerResponse = await ledgerClient.getAddress( - this._derivationBasePath, + this._baseDerivationPath, this._shouldAlwaysAskForConfirmation, SHOULD_GET_CHAIN_CODE, ); @@ -212,23 +216,26 @@ export class LedgerSubprovider extends BaseWalletSubprovider { const hdKey = new HDNode(); hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex'); hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex'); + const derivationPath = `${this._baseDerivationPath}/0`; + const derivationIndex = 0; return { hdKey, address: ledgerResponse.address, isChildKey: true, - derivationBasePath: this._derivationBasePath, - derivationPath: `${this._derivationBasePath}/0`, - derivationIndex: 0, + baseDerivationPath: this._baseDerivationPath, + derivationPath, + derivationIndex, }; } - private _findDerivedKeyByPublicAddress(initalHDKey: DerivedHDKey, address: string): DerivedHDKey { - if (_.isUndefined(address) || !addressUtils.isAddress(address)) { - throw new Error(WalletSubproviderErrors.FromAddressMissingOrInvalid); - } - const matchedDerivedKey = walletUtils.findDerivedKeyByAddress(address, initalHDKey, this._addressSearchLimit); - if (_.isUndefined(matchedDerivedKey)) { + private _findDerivedKeyInfoForAddress(initalHDKey: DerivedHDKeyInfo, address: string): DerivedHDKeyInfo { + const matchedDerivedKeyInfo = walletUtils.findDerivedKeyInfoForAddressIfExists( + address, + initalHDKey, + this._addressSearchLimit, + ); + if (_.isUndefined(matchedDerivedKeyInfo)) { throw new Error(`${WalletSubproviderErrors.AddressNotFound}: ${address}`); } - return matchedDerivedKey; + return matchedDerivedKeyInfo; } } diff --git a/packages/subproviders/src/subproviders/mnemonic_wallet.ts b/packages/subproviders/src/subproviders/mnemonic_wallet.ts index de34a9d11..9a627b1ec 100644 --- a/packages/subproviders/src/subproviders/mnemonic_wallet.ts +++ b/packages/subproviders/src/subproviders/mnemonic_wallet.ts @@ -5,13 +5,17 @@ import ethUtil = require('ethereumjs-util'); import HDNode = require('hdkey'); import * as _ from 'lodash'; -import { DerivedHDKey, PartialTxParams, WalletSubproviderErrors } from '../types'; +import { DerivedHDKeyInfo, MnemonicWalletSubproviderConfigs, PartialTxParams, WalletSubproviderErrors } from '../types'; import { walletUtils } from '../utils/wallet_utils'; import { BaseWalletSubprovider } from './base_wallet_subprovider'; import { PrivateKeyWalletSubprovider } from './private_key_wallet'; -const DEFAULT_DERIVATION_PATH = `44'/60'/0'/0`; +const DEFAULT_BASE_DERIVATION_PATH = `44'/60'/0'/0`; +const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10; +const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000; +const ROOT_KEY_DERIVATION_PATH = 'm/'; +const ROOT_KEY_DERIVATION_INDEX = 0; /** * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. @@ -20,35 +24,33 @@ const DEFAULT_DERIVATION_PATH = `44'/60'/0'/0`; */ export class MnemonicWalletSubprovider extends BaseWalletSubprovider { private _addressSearchLimit: number; - private _derivationBasePath: string; - private _derivedKey: DerivedHDKey; + private _baseDerivationPath: string; + private _derivedKeyInfo: DerivedHDKeyInfo; /** - * 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. - * @param mnemonic The mnemonic seed - * @param derivationBasePath The derivation path, defaults to `44'/60'/0'/0` - * @param addressSearchLimit The limit on address search attempts before raising `WalletSubproviderErrors.AddressNotFound` + * Instantiates a MnemonicWalletSubprovider. Defaults to baseDerivationPath set to `44'/60'/0'/0`. + * This is the default in TestRPC/Ganache, it can be overridden if desired. + * @param config Several available configurations * @return MnemonicWalletSubprovider instance */ - constructor( - mnemonic: string, - derivationBasePath: string = DEFAULT_DERIVATION_PATH, - addressSearchLimit: number = walletUtils.DEFAULT_ADDRESS_SEARCH_LIMIT, - ) { + constructor(config: MnemonicWalletSubproviderConfigs) { + const mnemonic = config.mnemonic; assert.isString('mnemonic', mnemonic); - assert.isString('derivationPath', derivationBasePath); + const baseDerivationPath = config.baseDerivationPath || DEFAULT_BASE_DERIVATION_PATH; + assert.isString('baseDerivationPath', baseDerivationPath); + const addressSearchLimit = config.addressSearchLimit || DEFAULT_ADDRESS_SEARCH_LIMIT; assert.isNumber('addressSearchLimit', addressSearchLimit); super(); + const seed = bip39.mnemonicToSeed(mnemonic); const hdKey = HDNode.fromMasterSeed(seed); - this._derivationBasePath = derivationBasePath; - this._derivedKey = { + this._baseDerivationPath = baseDerivationPath; + this._derivedKeyInfo = { address: walletUtils.addressOfHDKey(hdKey), // HACK this isn't the base path for this root key, but is is the base path - // we want all of the derived children to spawn from - derivationBasePath: this._derivationBasePath, - derivationPath: 'm/0', - derivationIndex: 0, + // we want all of the derived children to branched from + baseDerivationPath: this._baseDerivationPath, + derivationPath: ROOT_KEY_DERIVATION_PATH, + derivationIndex: ROOT_KEY_DERIVATION_INDEX, hdKey, isChildKey: false, }; @@ -59,17 +61,17 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider { * @returns derivation path */ public getPath(): string { - return this._derivationBasePath; + return this._baseDerivationPath; } /** * Set a desired derivation path when computing the available user addresses - * @param derivationPath The desired derivation path (e.g `44'/60'/0'`) + * @param baseDerivationPath The desired derivation path (e.g `44'/60'/0'`) */ - public setPath(derivationPath: string) { - this._derivationBasePath = derivationPath; - this._derivedKey = { - ...this._derivedKey, - derivationBasePath: this._derivationBasePath, + public setPath(baseDerivationPath: string) { + this._baseDerivationPath = baseDerivationPath; + this._derivedKeyInfo = { + ...this._derivedKeyInfo, + baseDerivationPath, }; } /** @@ -79,11 +81,9 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider { * @param numberOfAccounts Number of accounts to retrieve (default: 10) * @return An array of accounts */ - public async getAccountsAsync( - numberOfAccounts: number = walletUtils.DEFAULT_NUM_ADDRESSES_TO_FETCH, - ): Promise<string[]> { - const derivedKeys = walletUtils.calculateDerivedHDKeys(this._derivedKey, numberOfAccounts); - const accounts = _.map(derivedKeys, 'address'); + public async getAccountsAsync(numberOfAccounts: number = DEFAULT_NUM_ADDRESSES_TO_FETCH): Promise<string[]> { + const derivedKeys = walletUtils.calculateDerivedHDKeyInfos(this._derivedKeyInfo, numberOfAccounts); + const accounts = _.map(derivedKeys, k => k.address); return accounts; } @@ -96,6 +96,9 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider { * @return Signed transaction hex string */ public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { + if (_.isUndefined(txParams.from) || !addressUtils.isAddress(txParams.from)) { + throw new Error(WalletSubproviderErrors.FromAddressMissingOrInvalid); + } const privateKeyWallet = this._privateKeyWalletForAddress(txParams.from); const signedTx = privateKeyWallet.signTransactionAsync(txParams); return signedTx; @@ -111,28 +114,30 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider { * @return Signature hex string (order: rsv) */ public async signPersonalMessageAsync(data: string, address: string): Promise<string> { + if (_.isUndefined(data)) { + throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage); + } + assert.isHexString('data', data); + assert.isETHAddressHex('address', address); const privateKeyWallet = this._privateKeyWalletForAddress(address); const sig = await privateKeyWallet.signPersonalMessageAsync(data, address); return sig; } private _privateKeyWalletForAddress(address: string): PrivateKeyWalletSubprovider { - const derivedKey = this._findDerivedKeyByPublicAddress(address); - const privateKeyHex = derivedKey.hdKey.privateKey.toString('hex'); + const derivedKeyInfo = this._findDerivedKeyInfoForAddress(address); + const privateKeyHex = derivedKeyInfo.hdKey.privateKey.toString('hex'); const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKeyHex); return privateKeyWallet; } - private _findDerivedKeyByPublicAddress(address: string): DerivedHDKey { - if (_.isUndefined(address) || !addressUtils.isAddress(address)) { - throw new Error(WalletSubproviderErrors.FromAddressMissingOrInvalid); - } - const matchedDerivedKey = walletUtils.findDerivedKeyByAddress( + private _findDerivedKeyInfoForAddress(address: string): DerivedHDKeyInfo { + const matchedDerivedKeyInfo = walletUtils.findDerivedKeyInfoForAddressIfExists( address, - this._derivedKey, + this._derivedKeyInfo, this._addressSearchLimit, ); - if (_.isUndefined(matchedDerivedKey)) { + if (_.isUndefined(matchedDerivedKeyInfo)) { throw new Error(`${WalletSubproviderErrors.AddressNotFound}: ${address}`); } - return matchedDerivedKey; + return matchedDerivedKeyInfo; } } diff --git a/packages/subproviders/src/subproviders/private_key_wallet.ts b/packages/subproviders/src/subproviders/private_key_wallet.ts index f8afab722..f3727039c 100644 --- a/packages/subproviders/src/subproviders/private_key_wallet.ts +++ b/packages/subproviders/src/subproviders/private_key_wallet.ts @@ -17,7 +17,7 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider { private _privateKeyBuffer: Buffer; /** * Instantiates a PrivateKeyWalletSubprovider. - * @param privateKey The private key of the ethereum address + * @param privateKey The corresponding private key to an Ethereum address * @return PrivateKeyWalletSubprovider instance */ constructor(privateKey: string) { @@ -46,7 +46,11 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider { public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { PrivateKeyWalletSubprovider._validateTxParams(txParams); if (!_.isUndefined(txParams.from) && txParams.from !== this._address) { - throw new Error(`${WalletSubproviderErrors.AddressNotFound}: ${txParams.from}`); + throw new Error( + `Requested to sign transaction with address: ${txParams.from}, instantiated with address: ${ + this._address + }`, + ); } const tx = new EthereumTx(txParams); tx.sign(this._privateKeyBuffer); @@ -54,8 +58,8 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider { return rawTx; } /** - * Sign a personal Ethereum signed message. The signing address will be - * calculated from the private key, if an address is provided it must match the address calculated from the private key. + * Sign a personal Ethereum signed message. The signing address will be calculated from the private key. + * if an address is provided it must match the address calculated from the private key. * If you've added this Subprovider to your app's provider, you can simply send an `eth_sign` * or `personal_sign` JSON RPC request, and this method will be called auto-magically. * If you are not using this via a ProviderEngine instance, you can call it directly. @@ -67,10 +71,13 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider { if (_.isUndefined(data)) { throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage); } - if (_.isUndefined(address) || address !== this._address) { - throw new Error(`${WalletSubproviderErrors.FromAddressMissingOrInvalid}: ${address}`); - } assert.isHexString('data', data); + assert.isETHAddressHex('address', address); + if (address !== this._address) { + throw new Error( + `Requested to sign message with address: ${address}, instantiated with address: ${this._address}`, + ); + } const dataBuff = ethUtil.toBuffer(data); const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); const sig = ethUtil.ecsign(msgHashBuff, this._privateKeyBuffer); diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts index 121992278..b9d9d08ee 100644 --- a/packages/subproviders/src/types.ts +++ b/packages/subproviders/src/types.ts @@ -41,11 +41,12 @@ export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClien export interface LedgerSubproviderConfigs { networkId: number; ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; - derivationPath?: string; + baseDerivationPath?: string; accountFetchingConfigs?: AccountFetchingConfigs; } /* + * addressSearchLimit: The maximum number of addresses to search through, defaults to 1000 * numAddressesToReturn: Number of addresses to return from 'eth_accounts' call * shouldAskForOnDeviceConfirmation: Whether you wish to prompt the user on their Ledger * before fetching their addresses @@ -56,6 +57,17 @@ export interface AccountFetchingConfigs { shouldAskForOnDeviceConfirmation?: boolean; } +/* + * mnemonic: The string mnemonic seed + * addressSearchLimit: The maximum number of addresses to search through, defaults to 1000 + * baseDerivationPath: The base derivation path (e.g 44'/60'/0'/0) + */ +export interface MnemonicWalletSubproviderConfigs { + mnemonic: string; + addressSearchLimit?: number; + baseDerivationPath?: string; +} + export interface SignatureData { hash: string; r: string; @@ -106,10 +118,10 @@ export enum NonceSubproviderErrors { EmptyParametersFound = 'EMPTY_PARAMETERS_FOUND', CannotDetermineAddressFromPayload = 'CANNOT_DETERMINE_ADDRESS_FROM_PAYLOAD', } -export interface DerivedHDKey { +export interface DerivedHDKeyInfo { address: string; derivationIndex: number; - derivationBasePath: string; + baseDerivationPath: string; derivationPath: string; hdKey: HDNode; isChildKey: boolean; diff --git a/packages/subproviders/src/utils/wallet_utils.ts b/packages/subproviders/src/utils/wallet_utils.ts index d5ebf5ce6..a37597dff 100644 --- a/packages/subproviders/src/utils/wallet_utils.ts +++ b/packages/subproviders/src/utils/wallet_utils.ts @@ -2,37 +2,35 @@ import ethUtil = require('ethereumjs-util'); import HDNode = require('hdkey'); import * as _ from 'lodash'; -import { DerivedHDKey, WalletSubproviderErrors } from '../types'; +import { DerivedHDKeyInfo, WalletSubproviderErrors } from '../types'; -const DEFAULT_ADDRESS_SEARCH_OFFSET = 0; -const BATCH_SIZE = 10; const DEFAULT_ADDRESS_SEARCH_LIMIT = 1000; -class DerivedHDKeyIterator implements IterableIterator<DerivedHDKey> { - private _initialDerivedKey: DerivedHDKey; +class DerivedHDKeyInfoIterator implements IterableIterator<DerivedHDKeyInfo> { + private _initialDerivedKey: DerivedHDKeyInfo; private _searchLimit: number; private _index: number; - constructor(initialDerivedKey: DerivedHDKey, searchLimit: number = DEFAULT_ADDRESS_SEARCH_OFFSET) { + constructor(initialDerivedKey: DerivedHDKeyInfo, searchLimit: number = DEFAULT_ADDRESS_SEARCH_LIMIT) { this._searchLimit = searchLimit; this._initialDerivedKey = initialDerivedKey; this._index = 0; } - public next(): IteratorResult<DerivedHDKey> { - const derivationBasePath = this._initialDerivedKey.derivationBasePath; + public next(): IteratorResult<DerivedHDKeyInfo> { + const baseDerivationPath = this._initialDerivedKey.baseDerivationPath; const derivationIndex = this._index; const isChildKey = this._initialDerivedKey.isChildKey; // If the DerivedHDKey is a child then we walk relative, if not we walk the full derivation path - const fullDerivationPath = `m/${derivationBasePath}/${derivationIndex}`; + const fullDerivationPath = `m/${baseDerivationPath}/${derivationIndex}`; const relativeDerivationPath = `m/${derivationIndex}`; const path = isChildKey ? relativeDerivationPath : fullDerivationPath; const hdKey = this._initialDerivedKey.hdKey.derive(path); const address = walletUtils.addressOfHDKey(hdKey); - const derivedKey: DerivedHDKey = { + const derivedKey: DerivedHDKeyInfo = { address, hdKey, - derivationBasePath, + baseDerivationPath, derivationIndex, derivationPath: fullDerivationPath, isChildKey, @@ -45,29 +43,27 @@ class DerivedHDKeyIterator implements IterableIterator<DerivedHDKey> { }; } - public [Symbol.iterator](): IterableIterator<DerivedHDKey> { + public [Symbol.iterator](): IterableIterator<DerivedHDKeyInfo> { return this; } } export const walletUtils = { - DEFAULT_ADDRESS_SEARCH_LIMIT, - DEFAULT_NUM_ADDRESSES_TO_FETCH: 10, - calculateDerivedHDKeys(initialDerivedKey: DerivedHDKey, numberOfKeys: number): DerivedHDKey[] { - const derivedKeys: DerivedHDKey[] = []; - const derivedKeyIterator = new DerivedHDKeyIterator(initialDerivedKey, numberOfKeys); + calculateDerivedHDKeyInfos(initialDerivedKey: DerivedHDKeyInfo, numberOfKeys: number): DerivedHDKeyInfo[] { + const derivedKeys: DerivedHDKeyInfo[] = []; + const derivedKeyIterator = new DerivedHDKeyInfoIterator(initialDerivedKey, numberOfKeys); for (const key of derivedKeyIterator) { derivedKeys.push(key); } return derivedKeys; }, - findDerivedKeyByAddress( + findDerivedKeyInfoForAddressIfExists( address: string, - initialDerivedKey: DerivedHDKey, + initialDerivedKey: DerivedHDKeyInfo, searchLimit: number, - ): DerivedHDKey | undefined { - let matchedKey: DerivedHDKey | undefined; - const derivedKeyIterator = new DerivedHDKeyIterator(initialDerivedKey, searchLimit); + ): DerivedHDKeyInfo | undefined { + let matchedKey: DerivedHDKeyInfo | undefined; + const derivedKeyIterator = new DerivedHDKeyInfoIterator(initialDerivedKey, searchLimit); for (const key of derivedKeyIterator) { if (key.address === address) { matchedKey = key; diff --git a/packages/subproviders/test/integration/ledger_subprovider_test.ts b/packages/subproviders/test/integration/ledger_subprovider_test.ts index 2db4befb3..11b5f6410 100644 --- a/packages/subproviders/test/integration/ledger_subprovider_test.ts +++ b/packages/subproviders/test/integration/ledger_subprovider_test.ts @@ -33,7 +33,7 @@ describe('LedgerSubprovider', () => { ledgerSubprovider = new LedgerSubprovider({ networkId, ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, - derivationPath: fixtureData.TESTRPC_DERIVATION_PATH, + baseDerivationPath: fixtureData.TESTRPC_BASE_DERIVATION_PATH, }); }); describe('direct method calls', () => { diff --git a/packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts b/packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts index 9131a8b6a..93300f47d 100644 --- a/packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts +++ b/packages/subproviders/test/unit/mnemonic_wallet_subprovider_test.ts @@ -21,10 +21,10 @@ const expect = chai.expect; describe('MnemonicWalletSubprovider', () => { let subprovider: MnemonicWalletSubprovider; before(async () => { - subprovider = new MnemonicWalletSubprovider( - fixtureData.TEST_RPC_MNEMONIC, - fixtureData.TEST_RPC_MNEMONIC_DERIVATION_PATH, - ); + subprovider = new MnemonicWalletSubprovider({ + mnemonic: fixtureData.TEST_RPC_MNEMONIC, + baseDerivationPath: fixtureData.TEST_RPC_MNEMONIC_BASE_DERIVATION_PATH, + }); }); describe('direct method calls', () => { describe('success cases', () => { @@ -157,11 +157,11 @@ describe('MnemonicWalletSubprovider', () => { provider.sendAsync(payload, callback); }); it('should throw if `address` param not found when calling personal_sign', (done: DoneCallback) => { - const nonHexMessage = 'hello world'; + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING)); const payload = { jsonrpc: '2.0', method: 'personal_sign', - params: [nonHexMessage, fixtureData.NULL_ADDRESS], + params: [messageHex, fixtureData.NULL_ADDRESS], id: 1, }; const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { diff --git a/packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts b/packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts index b84aebb18..5c1b5cd25 100644 --- a/packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts +++ b/packages/subproviders/test/unit/private_key_wallet_subprovider_test.ts @@ -128,18 +128,20 @@ describe('PrivateKeyWalletSubprovider', () => { }); provider.sendAsync(payload, callback); }); - it('should throw if `address` param is not an address from private key when calling personal_sign', (done: DoneCallback) => { - const nonHexMessage = 'hello world'; + it('should throw if `address` param is not the address from private key when calling personal_sign', (done: DoneCallback) => { + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING)); const payload = { jsonrpc: '2.0', method: 'personal_sign', - params: [nonHexMessage, fixtureData.TEST_RPC_ACCOUNT_1], + params: [messageHex, fixtureData.TEST_RPC_ACCOUNT_1], id: 1, }; const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { expect(err).to.not.be.a('null'); expect(err.message).to.be.equal( - `${WalletSubproviderErrors.FromAddressMissingOrInvalid}: ${fixtureData.TEST_RPC_ACCOUNT_1}`, + `Requested to sign message with address: ${ + fixtureData.TEST_RPC_ACCOUNT_1 + }, instantiated with address: ${fixtureData.TEST_RPC_ACCOUNT_0}`, ); done(); }); @@ -183,16 +185,16 @@ describe('PrivateKeyWalletSubprovider', () => { provider.sendAsync(payload, callback); }); it('should throw if `address` param not found when calling personal_sign', (done: DoneCallback) => { - const nonHexMessage = 'hello world'; + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING)); const payload = { jsonrpc: '2.0', method: 'personal_sign', - params: [nonHexMessage, '0x0'], + params: [messageHex, '0x0'], id: 1, }; const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { expect(err).to.not.be.a('null'); - expect(err.message).to.be.equal(`${WalletSubproviderErrors.FromAddressMissingOrInvalid}: 0x0`); + expect(err.message).to.be.equal(`Expected address to be of type ETHAddressHex, encountered: 0x0`); done(); }); provider.sendAsync(payload, callback); diff --git a/packages/subproviders/test/utils/fixture_data.ts b/packages/subproviders/test/utils/fixture_data.ts index a973961ce..3137e08b0 100644 --- a/packages/subproviders/test/utils/fixture_data.ts +++ b/packages/subproviders/test/utils/fixture_data.ts @@ -8,13 +8,13 @@ export const fixtureData = { TEST_RPC_ACCOUNT_0_ACCOUNT_PRIVATE_KEY: 'F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', TEST_RPC_ACCOUNT_1, TEST_RPC_MNEMONIC: 'concert load couple harbor equip island argue ramp clarify fence smart topic', - TEST_RPC_MNEMONIC_DERIVATION_PATH: `44'/60'/0'/0`, + TEST_RPC_MNEMONIC_BASE_DERIVATION_PATH: `44'/60'/0'/0`, PERSONAL_MESSAGE_STRING: 'hello world', PERSONAL_MESSAGE_SIGNED_RESULT: '0x1b0ec5e2908e993d0c8ab6b46da46be2688fdf03c7ea6686075de37392e50a7d7fcc531446699132fbda915bd989882e0064d417018773a315fb8d43ed063c9b00', PERSONAL_MESSAGE_ACCOUNT_1_SIGNED_RESULT: '0xe7ae0c21d02eb38f2c2a20d9d7876a98cc7ef035b7a4559d49375e2ec735e06f0d0ab0ff92ee56c5ffc28d516e6ed0692d0270feae8796408dbef060c6c7100f01', - TESTRPC_DERIVATION_PATH: `m/44'/60'/0'/0`, + TESTRPC_BASE_DERIVATION_PATH: `m/44'/60'/0'/0`, NETWORK_ID: networkId, TX_DATA: { nonce: '0x00', |