diff options
Diffstat (limited to 'packages/subproviders/src')
-rw-r--r-- | packages/subproviders/src/globals.d.ts | 55 | ||||
-rw-r--r-- | packages/subproviders/src/index.ts | 16 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/injected_web3.ts | 16 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/ledger.ts | 129 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/redundant_rpc.ts | 31 | ||||
-rw-r--r-- | packages/subproviders/src/subproviders/subprovider.ts | 22 | ||||
-rw-r--r-- | packages/subproviders/src/types.ts | 12 |
7 files changed, 148 insertions, 133 deletions
diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts index 520ca9232..53457fa24 100644 --- a/packages/subproviders/src/globals.d.ts +++ b/packages/subproviders/src/globals.d.ts @@ -1,10 +1,9 @@ -/// <reference types='chai-typescript-typings' /> -/// <reference types='chai-as-promised-typescript-typings' /> declare module 'dirty-chai'; declare module 'es6-promisify'; // tslint:disable:max-classes-per-file // tslint:disable:class-name +// tslint:disable:async-suffix // tslint:disable:completed-docs // Ethereumjs-tx declarations @@ -46,19 +45,20 @@ declare module 'ledgerco' { export class eth { public comm: comm; constructor(comm: comm); - public getAddress_async(path: string, display?: boolean, chaincode?: boolean): - Promise<{publicKey: string; address: string}>; + public getAddress_async( + path: string, + display?: boolean, + chaincode?: boolean, + ): Promise<{ publicKey: string; address: string; chainCode: string }>; public signTransaction_async(path: string, rawTxHex: string): Promise<ECSignatureString>; - public getAppConfiguration_async(): Promise<{ arbitraryDataEnabled: number; version: string }>; + public getAppConfiguration_async(): Promise<{ + arbitraryDataEnabled: number; + version: string; + }>; public signPersonalMessage_async(path: string, messageHex: string): Promise<ECSignature>; } } -// ethereum-address declarations -declare module 'ethereum-address' { - export const isAddress: (address: string) => boolean; -} - // Semaphore-async-await declarations declare module 'semaphore-async-await' { class Semaphore { @@ -77,21 +77,34 @@ declare module 'web3-provider-engine/subproviders/subprovider' { declare module 'web3-provider-engine/subproviders/rpc' { import * as Web3 from 'web3'; class RpcSubprovider { - constructor(options: {rpcUrl: string}); + constructor(options: { rpcUrl: string }); public handleRequest( - payload: Web3.JSONRPCRequestPayload, next: () => void, end: (err: Error|null, data?: any) => void, + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, data?: any) => void, ): void; } export = RpcSubprovider; } declare module 'web3-provider-engine' { - class Web3ProviderEngine { - public on(event: string, handler: () => void): void; - public send(payload: any): void; - public sendAsync(payload: any, callback: (error: any, response: any) => void): void; - public addProvider(provider: any): void; - public start(): void; - public stop(): void; - } - export = Web3ProviderEngine; + class Web3ProviderEngine { + public on(event: string, handler: () => void): void; + public send(payload: any): void; + public sendAsync(payload: any, callback: (error: any, response: any) => void): void; + public addProvider(provider: any): void; + public start(): void; + public stop(): void; + } + export = Web3ProviderEngine; +} + +// hdkey declarations +declare module 'hdkey' { + class HDNode { + public publicKey: Buffer; + public chainCode: Buffer; + public constructor(); + public derive(path: string): HDNode; + } + export = HDNode; } diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts index 9b7cab3fa..720c4362f 100644 --- a/packages/subproviders/src/index.ts +++ b/packages/subproviders/src/index.ts @@ -4,18 +4,12 @@ import { eth as LedgerEthereumClientFn, } from 'ledgerco'; -import {LedgerEthereumClient} from './types'; +import { LedgerEthereumClient } from './types'; -export {InjectedWeb3Subprovider} from './subproviders/injected_web3'; -export {RedundantRPCSubprovider} from './subproviders/redundant_rpc'; -export { - LedgerSubprovider, -} from './subproviders/ledger'; -export { - ECSignature, - LedgerWalletSubprovider, - LedgerCommunicationClient, -} from './types'; +export { InjectedWeb3Subprovider } from './subproviders/injected_web3'; +export { RedundantRPCSubprovider } from './subproviders/redundant_rpc'; +export { LedgerSubprovider } from './subproviders/ledger'; +export { ECSignature, LedgerWalletSubprovider, LedgerCommunicationClient } from './types'; /** * A factory method for creating a LedgerEthereumClient usable in a browser context. diff --git a/packages/subproviders/src/subproviders/injected_web3.ts b/packages/subproviders/src/subproviders/injected_web3.ts index 25d747a62..bd29acb22 100644 --- a/packages/subproviders/src/subproviders/injected_web3.ts +++ b/packages/subproviders/src/subproviders/injected_web3.ts @@ -9,29 +9,31 @@ import Web3ProviderEngine = require('web3-provider-engine'); * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class InjectedWeb3Subprovider { - private injectedWeb3: Web3; + private _injectedWeb3: Web3; constructor(injectedWeb3: Web3) { - this.injectedWeb3 = injectedWeb3; + this._injectedWeb3 = injectedWeb3; } public handleRequest( - payload: Web3.JSONRPCRequestPayload, next: () => void, end: (err: Error|null, result: any) => void, + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, result: any) => void, ) { switch (payload.method) { case 'web3_clientVersion': - this.injectedWeb3.version.getNode(end); + this._injectedWeb3.version.getNode(end); return; case 'eth_accounts': - this.injectedWeb3.eth.getAccounts(end); + this._injectedWeb3.eth.getAccounts(end); return; case 'eth_sendTransaction': const [txParams] = payload.params; - this.injectedWeb3.eth.sendTransaction(txParams, end); + this._injectedWeb3.eth.sendTransaction(txParams, end); return; case 'eth_sign': const [address, message] = payload.params; - this.injectedWeb3.eth.sign(address, message, end); + this._injectedWeb3.eth.sign(address, message, end); return; default: diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index e0a08f792..7267a793e 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -1,9 +1,8 @@ -import {assert} from '@0xproject/assert'; -import promisify = require('es6-promisify'); -import {isAddress} from 'ethereum-address'; +import { assert } from '@0xproject/assert'; +import { addressUtils } from '@0xproject/utils'; import EthereumTx = require('ethereumjs-tx'); import ethUtil = require('ethereumjs-util'); -import * as ledger from 'ledgerco'; +import HDNode = require('hdkey'); import * as _ from 'lodash'; import Semaphore from 'semaphore-async-await'; import Web3 = require('web3'); @@ -17,13 +16,12 @@ import { ResponseWithTxParams, } from '../types'; -import {Subprovider} from './subprovider'; +import { Subprovider } from './subprovider'; const DEFAULT_DERIVATION_PATH = `44'/60'/0'`; const NUM_ADDRESSES_TO_FETCH = 10; const ASK_FOR_ON_DEVICE_CONFIRMATION = false; -const SHOULD_GET_CHAIN_CODE = false; -const HEX_REGEX = /^[0-9A-Fa-f]+$/g; +const SHOULD_GET_CHAIN_CODE = true; export class LedgerSubprovider extends Subprovider { private _nonceLock: Semaphore; @@ -34,20 +32,8 @@ export class LedgerSubprovider extends Subprovider { private _ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; private _ledgerClientIfExists?: LedgerEthereumClient; private _shouldAlwaysAskForConfirmation: boolean; - private static isValidHex(data: string) { - if (!_.isString(data)) { - return false; - } - const isHexPrefixed = data.slice(0, 2) === '0x'; - if (!isHexPrefixed) { - return false; - } - const nonPrefixed = data.slice(2); - const isValid = nonPrefixed.match(HEX_REGEX); - return isValid; - } - private static validateSender(sender: string) { - if (_.isUndefined(sender) || !isAddress(sender)) { + private static _validateSender(sender: string) { + if (_.isUndefined(sender) || !addressUtils.isAddress(sender)) { throw new Error(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); } } @@ -58,12 +44,11 @@ export class LedgerSubprovider extends Subprovider { this._networkId = config.networkId; this._ledgerEthereumClientFactoryAsync = config.ledgerEthereumClientFactoryAsync; this._derivationPath = config.derivationPath || DEFAULT_DERIVATION_PATH; - this._shouldAlwaysAskForConfirmation = !_.isUndefined(config.accountFetchingConfigs) && - !_.isUndefined( - config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation, - ) ? - config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation : - ASK_FOR_ON_DEVICE_CONFIRMATION; + this._shouldAlwaysAskForConfirmation = + !_.isUndefined(config.accountFetchingConfigs) && + !_.isUndefined(config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation) + ? config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation + : ASK_FOR_ON_DEVICE_CONFIRMATION; this._derivationPathIndex = 0; } public getPath(): string { @@ -76,7 +61,9 @@ export class LedgerSubprovider extends Subprovider { this._derivationPathIndex = pathIndex; } public async handleRequest( - payload: Web3.JSONRPCRequestPayload, next: () => void, end: (err: Error|null, result?: any) => void, + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, result?: any) => void, ) { let accounts; let txParams; @@ -102,8 +89,8 @@ export class LedgerSubprovider extends Subprovider { case 'eth_sendTransaction': txParams = payload.params[0]; try { - LedgerSubprovider.validateSender(txParams.from); - const result = await this.sendTransactionAsync(txParams); + LedgerSubprovider._validateSender(txParams.from); + const result = await this._sendTransactionAsync(txParams); end(null, result); } catch (err) { end(err); @@ -113,15 +100,16 @@ export class LedgerSubprovider extends Subprovider { case 'eth_signTransaction': txParams = payload.params[0]; try { - const result = await this.signTransactionWithoutSendingAsync(txParams); + const result = await this._signTransactionWithoutSendingAsync(txParams); end(null, result); } catch (err) { end(err); } return; + case 'eth_sign': case 'personal_sign': - const data = payload.params[0]; + const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; try { if (_.isUndefined(data)) { throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage); @@ -140,27 +128,38 @@ export class LedgerSubprovider extends Subprovider { } } public async getAccountsAsync(): Promise<string[]> { - this._ledgerClientIfExists = await this.createLedgerClientAsync(); + this._ledgerClientIfExists = await this._createLedgerClientAsync(); + + let ledgerResponse; + try { + ledgerResponse = await this._ledgerClientIfExists.getAddress_async( + this._derivationPath, + this._shouldAlwaysAskForConfirmation, + SHOULD_GET_CHAIN_CODE, + ); + } finally { + await this._destroyLedgerClientAsync(); + } + + const hdKey = new HDNode(); + hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex'); + hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex'); - // TODO: replace with generating addresses without hitting Ledger const accounts = []; for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) { - try { - const derivationPath = `${this._derivationPath}/${i + this._derivationPathIndex}`; - const result = await this._ledgerClientIfExists.getAddress_async( - derivationPath, this._shouldAlwaysAskForConfirmation, SHOULD_GET_CHAIN_CODE, - ); - accounts.push(result.address.toLowerCase()); - } catch (err) { - await this.destoryLedgerClientAsync(); - throw err; - } + const derivedHDNode = hdKey.derive(`m/${i + this._derivationPathIndex}`); + const derivedPublicKey = derivedHDNode.publicKey; + const shouldSanitizePublicKey = true; + const ethereumAddressUnprefixed = ethUtil + .publicToAddress(derivedPublicKey, shouldSanitizePublicKey) + .toString('hex'); + const ethereumAddressPrefixed = ethUtil.addHexPrefix(ethereumAddressUnprefixed); + accounts.push(ethereumAddressPrefixed.toLowerCase()); } - await this.destoryLedgerClientAsync(); return accounts; } public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { - this._ledgerClientIfExists = await this.createLedgerClientAsync(); + this._ledgerClientIfExists = await this._createLedgerClientAsync(); const tx = new EthereumTx(txParams); @@ -171,7 +170,7 @@ export class LedgerSubprovider extends Subprovider { const txHex = tx.serialize().toString('hex'); try { - const derivationPath = this.getDerivationPath(); + const derivationPath = this._getDerivationPath(); const result = await this._ledgerClientIfExists.signTransaction_async(derivationPath, txHex); // Store signature in transaction tx.r = Buffer.from(result.r, 'hex'); @@ -181,43 +180,45 @@ export class LedgerSubprovider extends Subprovider { // EIP155: v should be chain_id * 2 + {35, 36} const signedChainId = Math.floor((tx.v[0] - 35) / 2); if (signedChainId !== this._networkId) { - await this.destoryLedgerClientAsync(); + await this._destroyLedgerClientAsync(); const err = new Error(LedgerSubproviderErrors.TooOldLedgerFirmware); throw err; } const signedTxHex = `0x${tx.serialize().toString('hex')}`; - await this.destoryLedgerClientAsync(); + await this._destroyLedgerClientAsync(); return signedTxHex; } catch (err) { - await this.destoryLedgerClientAsync(); + await this._destroyLedgerClientAsync(); throw err; } } public async signPersonalMessageAsync(data: string): Promise<string> { - this._ledgerClientIfExists = await this.createLedgerClientAsync(); + this._ledgerClientIfExists = await this._createLedgerClientAsync(); try { - const derivationPath = this.getDerivationPath(); + const derivationPath = this._getDerivationPath(); const result = await this._ledgerClientIfExists.signPersonalMessage_async( - derivationPath, ethUtil.stripHexPrefix(data)); + derivationPath, + ethUtil.stripHexPrefix(data), + ); const v = result.v - 27; let vHex = v.toString(16); if (vHex.length < 2) { vHex = `0${v}`; } const signature = `0x${result.r}${result.s}${vHex}`; - await this.destoryLedgerClientAsync(); + await this._destroyLedgerClientAsync(); return signature; } catch (err) { - await this.destoryLedgerClientAsync(); + await this._destroyLedgerClientAsync(); throw err; } } - private getDerivationPath() { + private _getDerivationPath() { const derivationPath = `${this.getPath()}/${this._derivationPathIndex}`; return derivationPath; } - private async createLedgerClientAsync(): Promise<LedgerEthereumClient> { + private async _createLedgerClientAsync(): Promise<LedgerEthereumClient> { await this._connectionLock.wait(); if (!_.isUndefined(this._ledgerClientIfExists)) { this._connectionLock.signal(); @@ -227,7 +228,7 @@ export class LedgerSubprovider extends Subprovider { this._connectionLock.signal(); return ledgerEthereumClient; } - private async destoryLedgerClientAsync() { + private async _destroyLedgerClientAsync() { await this._connectionLock.wait(); if (_.isUndefined(this._ledgerClientIfExists)) { this._connectionLock.signal(); @@ -237,11 +238,11 @@ export class LedgerSubprovider extends Subprovider { this._ledgerClientIfExists = undefined; this._connectionLock.signal(); } - private async sendTransactionAsync(txParams: PartialTxParams): Promise<Web3.JSONRPCResponsePayload> { + private async _sendTransactionAsync(txParams: PartialTxParams): Promise<string> { await this._nonceLock.wait(); try { // fill in the extras - const filledParams = await this.populateMissingTxParamsAsync(txParams); + const filledParams = await this._populateMissingTxParamsAsync(txParams); // sign it const signedTx = await this.signTransactionAsync(filledParams); // emit a submit @@ -251,17 +252,17 @@ export class LedgerSubprovider extends Subprovider { }; const result = await this.emitPayloadAsync(payload); this._nonceLock.signal(); - return result; + return result.result; } catch (err) { this._nonceLock.signal(); throw err; } } - private async signTransactionWithoutSendingAsync(txParams: PartialTxParams): Promise<ResponseWithTxParams> { + private async _signTransactionWithoutSendingAsync(txParams: PartialTxParams): Promise<ResponseWithTxParams> { await this._nonceLock.wait(); try { // fill in the extras - const filledParams = await this.populateMissingTxParamsAsync(txParams); + const filledParams = await this._populateMissingTxParamsAsync(txParams); // sign it const signedTx = await this.signTransactionAsync(filledParams); @@ -276,7 +277,7 @@ export class LedgerSubprovider extends Subprovider { throw err; } } - private async populateMissingTxParamsAsync(txParams: PartialTxParams): Promise<PartialTxParams> { + private async _populateMissingTxParamsAsync(txParams: PartialTxParams): Promise<PartialTxParams> { if (_.isUndefined(txParams.gasPrice)) { const gasPriceResult = await this.emitPayloadAsync({ method: 'eth_gasPrice', diff --git a/packages/subproviders/src/subproviders/redundant_rpc.ts b/packages/subproviders/src/subproviders/redundant_rpc.ts index 80462bbfb..a3cb463a8 100644 --- a/packages/subproviders/src/subproviders/redundant_rpc.ts +++ b/packages/subproviders/src/subproviders/redundant_rpc.ts @@ -1,17 +1,19 @@ -import {promisify} from '@0xproject/utils'; +import { promisify } from '@0xproject/utils'; import * as _ from 'lodash'; import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); -import {JSONRPCPayload} from '../types'; +import { JSONRPCPayload } from '../types'; -import {Subprovider} from './subprovider'; +import { Subprovider } from './subprovider'; export class RedundantRPCSubprovider extends Subprovider { - private rpcs: RpcSubprovider[]; - private static async firstSuccessAsync( - rpcs: RpcSubprovider[], payload: JSONRPCPayload, next: () => void, + private _rpcs: RpcSubprovider[]; + private static async _firstSuccessAsync( + rpcs: RpcSubprovider[], + payload: JSONRPCPayload, + next: () => void, ): Promise<any> { - let lastErr: Error|undefined; + let lastErr: Error | undefined; for (const rpc of rpcs) { try { const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next); @@ -27,21 +29,24 @@ export class RedundantRPCSubprovider extends Subprovider { } constructor(endpoints: string[]) { super(); - this.rpcs = _.map(endpoints, endpoint => { + this._rpcs = _.map(endpoints, endpoint => { return new RpcSubprovider({ rpcUrl: endpoint, }); }); } - public async handleRequest(payload: JSONRPCPayload, next: () => void, - end: (err: Error|null, data?: any) => void): Promise<void> { - const rpcsCopy = this.rpcs.slice(); + // tslint:disable-next-line:async-suffix + public async handleRequest( + payload: JSONRPCPayload, + next: () => void, + end: (err: Error | null, data?: any) => void, + ): Promise<void> { + const rpcsCopy = this._rpcs.slice(); try { - const data = await RedundantRPCSubprovider.firstSuccessAsync(rpcsCopy, payload, next); + const data = await RedundantRPCSubprovider._firstSuccessAsync(rpcsCopy, payload, next); end(null, data); } catch (err) { end(err); } - } } diff --git a/packages/subproviders/src/subproviders/subprovider.ts b/packages/subproviders/src/subproviders/subprovider.ts index 64d97b958..6435c9f65 100644 --- a/packages/subproviders/src/subproviders/subprovider.ts +++ b/packages/subproviders/src/subproviders/subprovider.ts @@ -1,19 +1,16 @@ import promisify = require('es6-promisify'); import Web3 = require('web3'); -import { - JSONRPCPayload, -} from '../types'; +import { JSONRPCPayload } from '../types'; /* * A version of the base class Subprovider found in providerEngine * This one has an async/await `emitPayloadAsync` and also defined types. * Altered version of: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class Subprovider { - private engine: any; - private currentBlock: any; + private _engine: any; // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js - private static getRandomId() { + private static _getRandomId() { const extraDigits = 3; // 13 time digits const datePart = new Date().getTime() * Math.pow(10, extraDigits); @@ -22,10 +19,10 @@ export class Subprovider { // 16 digits return datePart + extraPart; } - private static createFinalPayload(payload: JSONRPCPayload): Web3.JSONRPCRequestPayload { + private static _createFinalPayload(payload: JSONRPCPayload): Web3.JSONRPCRequestPayload { const finalPayload = { // defaults - id: Subprovider.getRandomId(), + id: Subprovider._getRandomId(), jsonrpc: '2.0', params: [], ...payload, @@ -33,14 +30,11 @@ export class Subprovider { return finalPayload; } public setEngine(engine: any): void { - this.engine = engine; - engine.on('block', (block: any) => { - this.currentBlock = block; - }); + this._engine = engine; } public async emitPayloadAsync(payload: JSONRPCPayload): Promise<any> { - const finalPayload = Subprovider.createFinalPayload(payload); - const response = await promisify(this.engine.sendAsync, this.engine)(finalPayload); + const finalPayload = Subprovider._createFinalPayload(payload); + const response = await promisify(this._engine.sendAsync, this._engine)(finalPayload); return response; } } diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts index 38dc1e67e..3db8be943 100644 --- a/packages/subproviders/src/types.ts +++ b/packages/subproviders/src/types.ts @@ -1,5 +1,4 @@ import * as _ from 'lodash'; -import * as Web3 from 'web3'; export interface LedgerCommunicationClient { close_async: () => Promise<void>; @@ -11,8 +10,13 @@ export interface LedgerCommunicationClient { * NodeJs and Browser communication are supported. */ export interface LedgerEthereumClient { - getAddress_async: (derivationPath: string, askForDeviceConfirmation: boolean, - shouldGetChainCode: boolean) => Promise<LedgerGetAddressResult>; + // shouldGetChainCode is defined as `true` instead of `boolean` because other types rely on the assumption + // that we get back the chain code and we don't have dependent types to express it properly + getAddress_async: ( + derivationPath: string, + askForDeviceConfirmation: boolean, + shouldGetChainCode: true, + ) => Promise<LedgerGetAddressResult>; signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<ECSignature>; signTransaction_async: (derivationPath: string, txHex: string) => Promise<ECSignatureString>; comm: LedgerCommunicationClient; @@ -64,6 +68,8 @@ export interface SignatureData { export interface LedgerGetAddressResult { address: string; + publicKey: string; + chainCode: string; } export interface LedgerWalletSubprovider { |