diff options
author | Jacob Evans <jacob@dekz.net> | 2018-04-11 16:48:46 +0800 |
---|---|---|
committer | Jacob Evans <jacob@dekz.net> | 2018-04-11 17:08:28 +0800 |
commit | 916b4d3a26e6189c77634b0d2cde4d20bb4cb9a9 (patch) | |
tree | 663308bd8318c36867d4b216ea3ebcb15c6aa7cf | |
parent | f44ef7ce59cd5c811a92662d3fb095f21d80f665 (diff) | |
download | dexon-sol-tools-916b4d3a26e6189c77634b0d2cde4d20bb4cb9a9.tar dexon-sol-tools-916b4d3a26e6189c77634b0d2cde4d20bb4cb9a9.tar.gz dexon-sol-tools-916b4d3a26e6189c77634b0d2cde4d20bb4cb9a9.tar.bz2 dexon-sol-tools-916b4d3a26e6189c77634b0d2cde4d20bb4cb9a9.tar.lz dexon-sol-tools-916b4d3a26e6189c77634b0d2cde4d20bb4cb9a9.tar.xz dexon-sol-tools-916b4d3a26e6189c77634b0d2cde4d20bb4cb9a9.tar.zst dexon-sol-tools-916b4d3a26e6189c77634b0d2cde4d20bb4cb9a9.zip |
Renamed DerivedHDKey to DerivedHDKeyInfo
Added assertions on addresses for public methods
Throw a helpful error message when signer address is not instantiated address in PrivateKeyWalletSubprovider
Update changelog and rename derivationBasePath to baseDerivationPath
When returning undefined use pattern of IfExists
Added configuration object for MnemonicWallet
Put constants back into each individual wallet rather than in walletUtils
Delete accidental package-lock.json
-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', |