From 9d18f751c8822910391e55c29fc2b1912a1ee108 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 5 Apr 2018 15:01:56 +1000 Subject: Create a private key subprovider --- packages/subproviders/src/index.ts | 1 + .../src/subproviders/base_wallet_subprovider.ts | 134 +++++++++++++++++ packages/subproviders/src/subproviders/ledger.ts | 158 ++------------------- .../src/subproviders/pk_wallet_subprovider.ts | 68 +++++++++ packages/subproviders/src/types.ts | 6 +- 5 files changed, 215 insertions(+), 152 deletions(-) create mode 100644 packages/subproviders/src/subproviders/base_wallet_subprovider.ts create mode 100644 packages/subproviders/src/subproviders/pk_wallet_subprovider.ts (limited to 'packages/subproviders/src') diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts index 9786347e6..3541ac6f5 100644 --- a/packages/subproviders/src/index.ts +++ b/packages/subproviders/src/index.ts @@ -12,6 +12,7 @@ export { LedgerSubprovider } from './subproviders/ledger'; export { GanacheSubprovider } from './subproviders/ganache'; export { Subprovider } from './subproviders/subprovider'; export { NonceTrackerSubprovider } from './subproviders/nonce_tracker'; +export { PKWalletSubprovider } from './subproviders/pk_wallet_subprovider'; export { Callback, ErrorCallback, diff --git a/packages/subproviders/src/subproviders/base_wallet_subprovider.ts b/packages/subproviders/src/subproviders/base_wallet_subprovider.ts new file mode 100644 index 000000000..83b0da52f --- /dev/null +++ b/packages/subproviders/src/subproviders/base_wallet_subprovider.ts @@ -0,0 +1,134 @@ +import { JSONRPCRequestPayload, JSONRPCResponsePayload } from '@0xproject/types'; +import { addressUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; + +import { Callback, PartialTxParams, ResponseWithTxParams, WalletSubproviderErrors } from '../types'; + +import { Subprovider } from './subprovider'; + +export abstract class BaseWalletSubprovider extends Subprovider { + protected static _validateSender(sender: string) { + if (_.isUndefined(sender) || !addressUtils.isAddress(sender)) { + throw new Error(WalletSubproviderErrors.SenderInvalidOrNotSupplied); + } + } + + public abstract async getAccountsAsync(): Promise; + public abstract async signTransactionAsync(txParams: PartialTxParams): Promise; + public abstract async signPersonalMessageAsync(dataIfExists: string): Promise; + + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:async-suffix + public async handleRequest( + payload: JSONRPCRequestPayload, + next: Callback, + end: (err: Error | null, result?: any) => void, + ) { + let accounts; + let txParams; + switch (payload.method) { + case 'eth_coinbase': + try { + accounts = await this.getAccountsAsync(); + end(null, accounts[0]); + } catch (err) { + end(err); + } + return; + + case 'eth_accounts': + try { + accounts = await this.getAccountsAsync(); + end(null, accounts); + } catch (err) { + end(err); + } + return; + + case 'eth_sendTransaction': + txParams = payload.params[0]; + try { + BaseWalletSubprovider._validateSender(txParams.from); + const filledParams = await this._populateMissingTxParamsAsync(txParams); + const signedTx = await this.signTransactionAsync(filledParams); + const response = await this._emitSendTransactionAsync(signedTx); + end(null, response.result); + } catch (err) { + end(err); + } + return; + + case 'eth_signTransaction': + txParams = payload.params[0]; + try { + const filledParams = await this._populateMissingTxParamsAsync(txParams); + const signedTx = await this.signTransactionAsync(filledParams); + const result = { + raw: signedTx, + tx: txParams, + }; + end(null, result); + } catch (err) { + end(err); + } + return; + + case 'eth_sign': + case 'personal_sign': + const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; + try { + const ecSignatureHex = await this.signPersonalMessageAsync(data); + end(null, ecSignatureHex); + } catch (err) { + end(err); + } + return; + + default: + next(); + return; + } + } + private async _emitSendTransactionAsync(signedTx: string): Promise { + const payload = { + method: 'eth_sendRawTransaction', + params: [signedTx], + }; + const result = await this.emitPayloadAsync(payload); + return result; + } + private async _populateMissingTxParamsAsync(txParams: PartialTxParams): Promise { + if (_.isUndefined(txParams.gasPrice)) { + const gasPriceResult = await this.emitPayloadAsync({ + method: 'eth_gasPrice', + params: [], + }); + const gasPrice = gasPriceResult.result.toString(); + txParams.gasPrice = gasPrice; + } + if (_.isUndefined(txParams.nonce)) { + const nonceResult = await this.emitPayloadAsync({ + method: 'eth_getTransactionCount', + params: [txParams.from, 'pending'], + }); + const nonce = nonceResult.result; + txParams.nonce = nonce; + } + if (_.isUndefined(txParams.gas)) { + const gasResult = await this.emitPayloadAsync({ + method: 'eth_estimateGas', + params: [txParams], + }); + const gas = gasResult.result.toString(); + txParams.gas = gas; + } + return txParams; + } +} diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index 95784a391..71864f19c 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -15,9 +15,10 @@ import { LedgerSubproviderErrors, PartialTxParams, ResponseWithTxParams, + WalletSubproviderErrors, } from '../types'; -import { Subprovider } from './subprovider'; +import { BaseWalletSubprovider } from './base_wallet_subprovider'; const DEFAULT_DERIVATION_PATH = `44'/60'/0'`; const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10; @@ -29,7 +30,7 @@ const SHOULD_GET_CHAIN_CODE = true; * This subprovider intercepts all account related RPC requests (e.g message/transaction signing, etc...) and * re-routes them to a Ledger device plugged into the users computer. */ -export class LedgerSubprovider extends Subprovider { +export class LedgerSubprovider extends BaseWalletSubprovider { private _nonceLock = new Lock(); private _connectionLock = new Lock(); private _networkId: number; @@ -38,11 +39,6 @@ export class LedgerSubprovider extends Subprovider { private _ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; private _ledgerClientIfExists?: LedgerEthereumClient; private _shouldAlwaysAskForConfirmation: boolean; - private static _validateSender(sender: string) { - if (_.isUndefined(sender) || !addressUtils.isAddress(sender)) { - throw new Error(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); - } - } /** * Instantiates a LedgerSubprovider. Defaults to derivationPath set to `44'/60'/0'`. * TestRPC/Ganache defaults to `m/44'/60'/0'/0`, so set this in the configs if desired. @@ -168,7 +164,7 @@ export class LedgerSubprovider extends Subprovider { } } /** - * Sign a personal Ethereum signed message. The signing address will be to one + * Sign a personal Ethereum signed message. The signing address will be the one * retrieved given a derivationPath and pathIndex set on the subprovider. * The Ledger adds the Ethereum signed message prefix on-device. If you've added * the LedgerSubprovider to your app's provider, you can simply send an `eth_sign` @@ -178,6 +174,10 @@ export class LedgerSubprovider extends Subprovider { * @return Signature hex string (order: rsv) */ public async signPersonalMessageAsync(data: string): Promise { + if (_.isUndefined(data)) { + throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage); + } + assert.isHexString('data', data); this._ledgerClientIfExists = await this._createLedgerClientAsync(); try { const derivationPath = this._getDerivationPath(); @@ -198,82 +198,6 @@ export class LedgerSubprovider extends Subprovider { throw err; } } - /** - * This method conforms to the web3-provider-engine interface. - * It is called internally by the ProviderEngine when it is this subproviders - * turn to handle a JSON RPC request. - * @param payload JSON RPC payload - * @param next Callback to call if this subprovider decides not to handle the request - * @param end Callback to call if subprovider handled the request and wants to pass back the request. - */ - // tslint:disable-next-line:async-suffix - public async handleRequest( - payload: JSONRPCRequestPayload, - next: Callback, - end: (err: Error | null, result?: any) => void, - ) { - let accounts; - let txParams; - switch (payload.method) { - case 'eth_coinbase': - try { - accounts = await this.getAccountsAsync(); - end(null, accounts[0]); - } catch (err) { - end(err); - } - return; - - case 'eth_accounts': - try { - accounts = await this.getAccountsAsync(); - end(null, accounts); - } catch (err) { - end(err); - } - return; - - case 'eth_sendTransaction': - txParams = payload.params[0]; - try { - LedgerSubprovider._validateSender(txParams.from); - const result = await this._sendTransactionAsync(txParams); - end(null, result); - } catch (err) { - end(err); - } - return; - - case 'eth_signTransaction': - txParams = payload.params[0]; - try { - const result = await this._signTransactionWithoutSendingAsync(txParams); - end(null, result); - } catch (err) { - end(err); - } - return; - - case 'eth_sign': - case 'personal_sign': - const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; - try { - if (_.isUndefined(data)) { - throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage); - } - assert.isHexString('data', data); - const ecSignatureHex = await this.signPersonalMessageAsync(data); - end(null, ecSignatureHex); - } catch (err) { - end(err); - } - return; - - default: - next(); - return; - } - } private _getDerivationPath() { const derivationPath = `${this.getPath()}/${this._derivationPathIndex}`; return derivationPath; @@ -298,70 +222,4 @@ export class LedgerSubprovider extends Subprovider { this._ledgerClientIfExists = undefined; this._connectionLock.release(); } - private async _sendTransactionAsync(txParams: PartialTxParams): Promise { - await this._nonceLock.acquire(); - try { - // fill in the extras - const filledParams = await this._populateMissingTxParamsAsync(txParams); - // sign it - const signedTx = await this.signTransactionAsync(filledParams); - // emit a submit - const payload = { - method: 'eth_sendRawTransaction', - params: [signedTx], - }; - const result = await this.emitPayloadAsync(payload); - this._nonceLock.release(); - return result.result; - } catch (err) { - this._nonceLock.release(); - throw err; - } - } - private async _signTransactionWithoutSendingAsync(txParams: PartialTxParams): Promise { - await this._nonceLock.acquire(); - try { - // fill in the extras - const filledParams = await this._populateMissingTxParamsAsync(txParams); - // sign it - const signedTx = await this.signTransactionAsync(filledParams); - - this._nonceLock.release(); - const result = { - raw: signedTx, - tx: txParams, - }; - return result; - } catch (err) { - this._nonceLock.release(); - throw err; - } - } - private async _populateMissingTxParamsAsync(txParams: PartialTxParams): Promise { - if (_.isUndefined(txParams.gasPrice)) { - const gasPriceResult = await this.emitPayloadAsync({ - method: 'eth_gasPrice', - params: [], - }); - const gasPrice = gasPriceResult.result.toString(); - txParams.gasPrice = gasPrice; - } - if (_.isUndefined(txParams.nonce)) { - const nonceResult = await this.emitPayloadAsync({ - method: 'eth_getTransactionCount', - params: [txParams.from, 'pending'], - }); - const nonce = nonceResult.result; - txParams.nonce = nonce; - } - if (_.isUndefined(txParams.gas)) { - const gasResult = await this.emitPayloadAsync({ - method: 'eth_estimateGas', - params: [txParams], - }); - const gas = gasResult.result.toString(); - txParams.gas = gas; - } - return txParams; - } } diff --git a/packages/subproviders/src/subproviders/pk_wallet_subprovider.ts b/packages/subproviders/src/subproviders/pk_wallet_subprovider.ts new file mode 100644 index 000000000..06dc39237 --- /dev/null +++ b/packages/subproviders/src/subproviders/pk_wallet_subprovider.ts @@ -0,0 +1,68 @@ +import { assert } from '@0xproject/assert'; +import { JSONRPCRequestPayload } from '@0xproject/types'; +import EthereumTx = require('ethereumjs-tx'); +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { Callback, ErrorCallback, PartialTxParams, ResponseWithTxParams, WalletSubproviderErrors } from '../types'; + +import { BaseWalletSubprovider } from './base_wallet_subprovider'; +import { Subprovider } from './subprovider'; + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * This subprovider intercepts all account related RPC requests (e.g message/transaction signing, etc...) + */ +export class PKWalletSubprovider extends BaseWalletSubprovider { + private _address: string; + private _privateKeyBuffer: Buffer; + constructor(privateKey: string) { + super(); + this._privateKeyBuffer = new Buffer(privateKey, 'hex'); + this._address = `0x${ethUtil.privateToAddress(this._privateKeyBuffer).toString('hex')}`; + } + /** + * Retrieve the account calcuated from the private key. + * This method is automatically called when issuing a `eth_accounts` JSON RPC request + * via your providerEngine instance. + * @return An array of accounts + */ + public async getAccountsAsync(): Promise { + return [this._address]; + } + /** + * Sign a transaction with the private key. If you've added this Subprovider to your + * app's provider, you can simply send an `eth_sendTransaction` 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. + * @param txParams Parameters of the transaction to sign + * @return Signed transaction hex string + */ + public async signTransactionAsync(txParams: PartialTxParams): Promise { + const tx = new EthereumTx(txParams); + tx.sign(this._privateKeyBuffer); + const rawTx = `0x${tx.serialize().toString('hex')}`; + return rawTx; + } + /** + * Sign a personal Ethereum signed message. The signing address will be + * calculated from the private key. + * If you've added the PKWalletSubprovider 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. + * @param data Message to sign + * @return Signature hex string (order: rsv) + */ + public async signPersonalMessageAsync(dataIfExists: string): Promise { + if (_.isUndefined(dataIfExists)) { + throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage); + } + assert.isHexString('data', dataIfExists); + const dataBuff = ethUtil.toBuffer(dataIfExists); + const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); + const sig = ethUtil.ecsign(msgHashBuff, this._privateKeyBuffer); + const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s); + + return rpcSig; + } +} diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts index a1fec1882..bacb7091b 100644 --- a/packages/subproviders/src/types.ts +++ b/packages/subproviders/src/types.ts @@ -95,11 +95,13 @@ export interface ResponseWithTxParams { tx: PartialTxParams; } +export enum WalletSubproviderErrors { + DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE', + SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', +} export enum LedgerSubproviderErrors { TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE', FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID', - DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE', - SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED', } -- cgit v1.2.3 From 774ab8a8efa1ff5914896d9435c0362a4586c2ef Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 6 Apr 2018 14:55:03 +1000 Subject: Feedback remove id management from testnet faucet spread over txParams rather than modify in place --- packages/subproviders/src/index.ts | 2 +- .../src/subproviders/base_wallet_subprovider.ts | 37 ++++++------ packages/subproviders/src/subproviders/ledger.ts | 1 + .../src/subproviders/pk_wallet_subprovider.ts | 68 --------------------- .../subproviders/private_key_wallet_subprovider.ts | 70 ++++++++++++++++++++++ 5 files changed, 92 insertions(+), 86 deletions(-) delete mode 100644 packages/subproviders/src/subproviders/pk_wallet_subprovider.ts create mode 100644 packages/subproviders/src/subproviders/private_key_wallet_subprovider.ts (limited to 'packages/subproviders/src') diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts index 3541ac6f5..dd553fde4 100644 --- a/packages/subproviders/src/index.ts +++ b/packages/subproviders/src/index.ts @@ -12,7 +12,7 @@ export { LedgerSubprovider } from './subproviders/ledger'; export { GanacheSubprovider } from './subproviders/ganache'; export { Subprovider } from './subproviders/subprovider'; export { NonceTrackerSubprovider } from './subproviders/nonce_tracker'; -export { PKWalletSubprovider } from './subproviders/pk_wallet_subprovider'; +export { PrivateKeyWalletSubprovider } from './subproviders/private_key_wallet_subprovider'; export { Callback, ErrorCallback, diff --git a/packages/subproviders/src/subproviders/base_wallet_subprovider.ts b/packages/subproviders/src/subproviders/base_wallet_subprovider.ts index 83b0da52f..034f83e7f 100644 --- a/packages/subproviders/src/subproviders/base_wallet_subprovider.ts +++ b/packages/subproviders/src/subproviders/base_wallet_subprovider.ts @@ -1,13 +1,19 @@ +import { assert } from '@0xproject/assert'; import { JSONRPCRequestPayload, JSONRPCResponsePayload } from '@0xproject/types'; import { addressUtils } from '@0xproject/utils'; import * as _ from 'lodash'; -import { Callback, PartialTxParams, ResponseWithTxParams, WalletSubproviderErrors } from '../types'; +import { Callback, ErrorCallback, PartialTxParams, ResponseWithTxParams, WalletSubproviderErrors } from '../types'; import { Subprovider } from './subprovider'; export abstract class BaseWalletSubprovider extends Subprovider { - protected static _validateSender(sender: string) { + protected static _validateTxParams(txParams: PartialTxParams) { + assert.isETHAddressHex('to', txParams.to); + assert.isHexString('nonce', txParams.nonce); + assert.isHexString('gas', txParams.gas); + } + private static _validateSender(sender: string) { if (_.isUndefined(sender) || !addressUtils.isAddress(sender)) { throw new Error(WalletSubproviderErrors.SenderInvalidOrNotSupplied); } @@ -15,7 +21,7 @@ export abstract class BaseWalletSubprovider extends Subprovider { public abstract async getAccountsAsync(): Promise; public abstract async signTransactionAsync(txParams: PartialTxParams): Promise; - public abstract async signPersonalMessageAsync(dataIfExists: string): Promise; + public abstract async signPersonalMessageAsync(data: string): Promise; /** * This method conforms to the web3-provider-engine interface. @@ -26,11 +32,7 @@ export abstract class BaseWalletSubprovider extends Subprovider { * @param end Callback to call if subprovider handled the request and wants to pass back the request. */ // tslint:disable-next-line:async-suffix - public async handleRequest( - payload: JSONRPCRequestPayload, - next: Callback, - end: (err: Error | null, result?: any) => void, - ) { + public async handleRequest(payload: JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { let accounts; let txParams; switch (payload.method) { @@ -104,30 +106,31 @@ export abstract class BaseWalletSubprovider extends Subprovider { const result = await this.emitPayloadAsync(payload); return result; } - private async _populateMissingTxParamsAsync(txParams: PartialTxParams): Promise { - if (_.isUndefined(txParams.gasPrice)) { + private async _populateMissingTxParamsAsync(partialTxParams: PartialTxParams): Promise { + let txParams = partialTxParams; + if (_.isUndefined(partialTxParams.gasPrice)) { const gasPriceResult = await this.emitPayloadAsync({ method: 'eth_gasPrice', params: [], }); const gasPrice = gasPriceResult.result.toString(); - txParams.gasPrice = gasPrice; + txParams = { ...txParams, gasPrice }; } - if (_.isUndefined(txParams.nonce)) { + if (_.isUndefined(partialTxParams.nonce)) { const nonceResult = await this.emitPayloadAsync({ method: 'eth_getTransactionCount', - params: [txParams.from, 'pending'], + params: [partialTxParams.from, 'pending'], }); const nonce = nonceResult.result; - txParams.nonce = nonce; + txParams = { ...txParams, nonce }; } - if (_.isUndefined(txParams.gas)) { + if (_.isUndefined(partialTxParams.gas)) { const gasResult = await this.emitPayloadAsync({ method: 'eth_estimateGas', - params: [txParams], + params: [partialTxParams], }); const gas = gasResult.result.toString(); - txParams.gas = gas; + txParams = { ...txParams, gas }; } return txParams; } diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index 71864f19c..aa86bf6c0 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -129,6 +129,7 @@ export class LedgerSubprovider extends BaseWalletSubprovider { * @return Signed transaction hex string */ public async signTransactionAsync(txParams: PartialTxParams): Promise { + LedgerSubprovider._validateTxParams(txParams); this._ledgerClientIfExists = await this._createLedgerClientAsync(); const tx = new EthereumTx(txParams); diff --git a/packages/subproviders/src/subproviders/pk_wallet_subprovider.ts b/packages/subproviders/src/subproviders/pk_wallet_subprovider.ts deleted file mode 100644 index 06dc39237..000000000 --- a/packages/subproviders/src/subproviders/pk_wallet_subprovider.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { assert } from '@0xproject/assert'; -import { JSONRPCRequestPayload } from '@0xproject/types'; -import EthereumTx = require('ethereumjs-tx'); -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { Callback, ErrorCallback, PartialTxParams, ResponseWithTxParams, WalletSubproviderErrors } from '../types'; - -import { BaseWalletSubprovider } from './base_wallet_subprovider'; -import { Subprovider } from './subprovider'; - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * This subprovider intercepts all account related RPC requests (e.g message/transaction signing, etc...) - */ -export class PKWalletSubprovider extends BaseWalletSubprovider { - private _address: string; - private _privateKeyBuffer: Buffer; - constructor(privateKey: string) { - super(); - this._privateKeyBuffer = new Buffer(privateKey, 'hex'); - this._address = `0x${ethUtil.privateToAddress(this._privateKeyBuffer).toString('hex')}`; - } - /** - * Retrieve the account calcuated from the private key. - * This method is automatically called when issuing a `eth_accounts` JSON RPC request - * via your providerEngine instance. - * @return An array of accounts - */ - public async getAccountsAsync(): Promise { - return [this._address]; - } - /** - * Sign a transaction with the private key. If you've added this Subprovider to your - * app's provider, you can simply send an `eth_sendTransaction` 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. - * @param txParams Parameters of the transaction to sign - * @return Signed transaction hex string - */ - public async signTransactionAsync(txParams: PartialTxParams): Promise { - const tx = new EthereumTx(txParams); - tx.sign(this._privateKeyBuffer); - const rawTx = `0x${tx.serialize().toString('hex')}`; - return rawTx; - } - /** - * Sign a personal Ethereum signed message. The signing address will be - * calculated from the private key. - * If you've added the PKWalletSubprovider 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. - * @param data Message to sign - * @return Signature hex string (order: rsv) - */ - public async signPersonalMessageAsync(dataIfExists: string): Promise { - if (_.isUndefined(dataIfExists)) { - throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage); - } - assert.isHexString('data', dataIfExists); - const dataBuff = ethUtil.toBuffer(dataIfExists); - const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); - const sig = ethUtil.ecsign(msgHashBuff, this._privateKeyBuffer); - const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s); - - return rpcSig; - } -} diff --git a/packages/subproviders/src/subproviders/private_key_wallet_subprovider.ts b/packages/subproviders/src/subproviders/private_key_wallet_subprovider.ts new file mode 100644 index 000000000..c3a53773a --- /dev/null +++ b/packages/subproviders/src/subproviders/private_key_wallet_subprovider.ts @@ -0,0 +1,70 @@ +import { assert } from '@0xproject/assert'; +import { JSONRPCRequestPayload } from '@0xproject/types'; +import EthereumTx = require('ethereumjs-tx'); +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { Callback, ErrorCallback, PartialTxParams, ResponseWithTxParams, WalletSubproviderErrors } from '../types'; + +import { BaseWalletSubprovider } from './base_wallet_subprovider'; +import { Subprovider } from './subprovider'; + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * This subprovider intercepts all account related RPC requests (e.g message/transaction signing, etc...) and handles + * all requests with the supplied Ethereum private key. + */ +export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider { + private _address: string; + private _privateKeyBuffer: Buffer; + constructor(privateKey: string) { + assert.isString('privateKey', privateKey); + super(); + this._privateKeyBuffer = new Buffer(privateKey, 'hex'); + this._address = `0x${ethUtil.privateToAddress(this._privateKeyBuffer).toString('hex')}`; + } + /** + * Retrieve the account associated with the supplied private key. + * This method is implicitly called when issuing a `eth_accounts` JSON RPC request + * via your providerEngine instance. + * @return An array of accounts + */ + public async getAccountsAsync(): Promise { + return [this._address]; + } + /** + * Sign a transaction with the private key. If you've added this Subprovider to your + * app's provider, you can simply send an `eth_sendTransaction` 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. + * @param txParams Parameters of the transaction to sign + * @return Signed transaction hex string + */ + public async signTransactionAsync(txParams: PartialTxParams): Promise { + PrivateKeyWalletSubprovider._validateTxParams(txParams); + const tx = new EthereumTx(txParams); + tx.sign(this._privateKeyBuffer); + const rawTx = `0x${tx.serialize().toString('hex')}`; + return rawTx; + } + /** + * Sign a personal Ethereum signed message. The signing address will be + * calculated from the private key. + * If you've added the PKWalletSubprovider 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. + * @param data Message to sign + * @return Signature hex string (order: rsv) + */ + public async signPersonalMessageAsync(dataIfExists: string): Promise { + if (_.isUndefined(dataIfExists)) { + throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage); + } + assert.isHexString('data', dataIfExists); + const dataBuff = ethUtil.toBuffer(dataIfExists); + const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); + const sig = ethUtil.ecsign(msgHashBuff, this._privateKeyBuffer); + const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s); + return rpcSig; + } +} -- cgit v1.2.3