From b9bd734cd03f1c4e733af44e427bf99372926fa4 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Fri, 4 May 2018 00:00:34 -0400 Subject: [PATCH] Transaction signing --- common/libs/wallet/deterministic/ledger.ts | 47 +++++++++++-------- shared/enclave/client/index.ts | 3 +- .../server/handlers/signTransaction.ts | 9 ++-- shared/enclave/server/index.ts | 17 ------- shared/enclave/server/wallets/ledger.ts | 40 ++++++++++++++-- shared/enclave/server/wallets/trezor.ts | 4 ++ shared/enclave/types.ts | 18 +++++-- 7 files changed, 87 insertions(+), 51 deletions(-) diff --git a/common/libs/wallet/deterministic/ledger.ts b/common/libs/wallet/deterministic/ledger.ts index d2fb5718..0277cee2 100644 --- a/common/libs/wallet/deterministic/ledger.ts +++ b/common/libs/wallet/deterministic/ledger.ts @@ -38,30 +38,39 @@ export class LedgerWallet extends HardwareWallet implements IFullWallet { // modeled after // https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/uiFuncs.js#L58 - public signRawTransaction(t: EthTx): Promise { + public async signRawTransaction(t: EthTx): Promise { + const txFields = getTransactionFields(t); + + if (process.env.BUILD_ELECTRON) { + const res = await EnclaveAPI.signTransaction({ + walletType: WalletTypes.LEDGER, + transaction: txFields, + path: this.getPath() + }); + return new EthTx(res.signedTransaction).serialize(); + } + t.v = Buffer.from([t._chainId]); t.r = toBuffer(0); t.s = toBuffer(0); - return new Promise((resolve, reject) => { - this.ethApp - .signTransaction_async(this.getPath(), t.serialize().toString('hex')) - .then(result => { - const strTx = getTransactionFields(t); - const txToSerialize: TxObj = { - ...strTx, - v: addHexPrefix(result.v), - r: addHexPrefix(result.r), - s: addHexPrefix(result.s) - }; + try { + const result = await this.ethApp.signTransaction_async( + this.getPath(), + t.serialize().toString('hex') + ); - const serializedTx = new EthTx(txToSerialize).serialize(); - resolve(serializedTx); - }) - .catch(err => { - return reject(Error(err + '. Check to make sure contract data is on')); - }); - }); + const txToSerialize: TxObj = { + ...txFields, + v: addHexPrefix(result.v), + r: addHexPrefix(result.r), + s: addHexPrefix(result.s) + }; + + return new EthTx(txToSerialize).serialize(); + } catch (err) { + throw Error(err + '. Check to make sure contract data is on'); + } } // modeled after diff --git a/shared/enclave/client/index.ts b/shared/enclave/client/index.ts index b6aa23cb..5f603b2c 100644 --- a/shared/enclave/client/index.ts +++ b/shared/enclave/client/index.ts @@ -1,3 +1,4 @@ +import EthTx from 'ethereumjs-tx'; import { makeRequest } from './requests'; import { EnclaveMethods, @@ -18,7 +19,7 @@ const api = { return makeRequest(EnclaveMethods.GET_CHAIN_CODE, params); }, - signTransaction(params: SignTransactionParams) { + async signTransaction(params: SignTransactionParams) { return makeRequest(EnclaveMethods.SIGN_TRANSACTION, params); } }; diff --git a/shared/enclave/server/handlers/signTransaction.ts b/shared/enclave/server/handlers/signTransaction.ts index 21583a6b..3554103a 100644 --- a/shared/enclave/server/handlers/signTransaction.ts +++ b/shared/enclave/server/handlers/signTransaction.ts @@ -1,10 +1,7 @@ +import { getWalletLib } from 'shared/enclave/server/wallets'; import { SignTransactionParams, SignTransactionResponse } from 'shared/enclave/types'; export default function(params: SignTransactionParams): SignTransactionResponse { - console.log('Sign transaction called with', params); - return { - s: 'test', - v: 'test', - r: 'test' - }; + const wallet = getWalletLib(params.walletType); + return wallet.signTransaction(params.transaction, params.path); } diff --git a/shared/enclave/server/index.ts b/shared/enclave/server/index.ts index 7796f72d..7760e337 100644 --- a/shared/enclave/server/index.ts +++ b/shared/enclave/server/index.ts @@ -31,23 +31,6 @@ export function registerServer(app: App) { cb(JSON.stringify(res)); }); }); - - // Fix trezord requests for every new browser window - app.on('web-contents-created', (_, webContents) => { - const { session } = webContents; - if (!session.webRequest) { - return; - } - session.webRequest.onBeforeSendHeaders((details: any, callback: any) => { - const url = details.url; - if (url.startsWith('https://localback.net:21324')) { - if (details.requestHeaders.Origin === 'null') { - delete details.requestHeaders.Origin; - } - } - callback({ cancel: false, requestHeaders: details.requestHeaders }); - }); - }); } function getMethod(req: Electron.RegisterStringProtocolRequest): EnclaveMethods { diff --git a/shared/enclave/server/wallets/ledger.ts b/shared/enclave/server/wallets/ledger.ts index bbb24486..165db0d9 100644 --- a/shared/enclave/server/wallets/ledger.ts +++ b/shared/enclave/server/wallets/ledger.ts @@ -1,10 +1,22 @@ -import { WalletLib } from 'shared/enclave/types'; +import EthTx from 'ethereumjs-tx'; +import { addHexPrefix, toBuffer } from 'ethereumjs-util'; +import { WalletLib, RawTransaction } from 'shared/enclave/types'; import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; import LedgerEth from '@ledgerhq/hw-app-eth'; async function getEthApp() { - const transport = await TransportNodeHid.create(); - return new LedgerEth(transport); + try { + const transport = await TransportNodeHid.create(); + return new LedgerEth(transport); + } catch (err) { + console.log(err.message); + if (err && err.message && err.message.includes('cannot open device with path')) { + throw new Error( + 'Failed to connect with your Ledger. It may be in use with another application. Try plugging the device back in.' + ); + } + throw err; + } } const Ledger: WalletLib = { @@ -17,9 +29,29 @@ const Ledger: WalletLib = { chainCode: res.chainCode }; } catch (err) { - console.log('wtf', err); throw new Error('Failed to connect to Ledger'); } + }, + + async signTransaction(tx: RawTransaction, path: string) { + const app = await getEthApp(); + const ethTx = new EthTx({ + ...tx, + v: Buffer.from([tx.chainId]), + r: toBuffer(0), + s: toBuffer(0) + }); + + const rsv = await app.signTransaction(path, ethTx.serialize().toString('hex')); + const signedTx = new EthTx({ + ...tx, + r: addHexPrefix(rsv.r), + s: addHexPrefix(rsv.s), + v: addHexPrefix(rsv.v) + }); + return { + signedTransaction: signedTx.serialize().toString('hex') + }; } }; diff --git a/shared/enclave/server/wallets/trezor.ts b/shared/enclave/server/wallets/trezor.ts index ad9479e6..1bf16295 100644 --- a/shared/enclave/server/wallets/trezor.ts +++ b/shared/enclave/server/wallets/trezor.ts @@ -14,6 +14,10 @@ const Trezor: WalletLib = { // }); // // return { chainCode: 'test', publicKey: 'test' }; + }, + + async signTransaction() { + throw new Error('Not yet implemented'); } }; diff --git a/shared/enclave/types.ts b/shared/enclave/types.ts index 87f19850..51dd1cbc 100644 --- a/shared/enclave/types.ts +++ b/shared/enclave/types.ts @@ -11,6 +11,16 @@ export enum WalletTypes { KEEPKEY = 'keepkey' } +export interface RawTransaction { + chainId: number; + gasLimit: string; + gasPrice: string; + to: string; + nonce: string; + data: string; + value: string; +} + // Get Addresses Request export interface GetAddressesParams { walletType: WalletTypes; @@ -33,14 +43,13 @@ export interface GetChainCodeResponse { // Sign Transaction Request export interface SignTransactionParams { + walletType: WalletTypes; + transaction: RawTransaction; path: string; - rawTxHex: string; } export interface SignTransactionResponse { - s: string; - v: string; - r: string; + signedTransaction: string; } // All Requests & Responses @@ -72,4 +81,5 @@ export type EnclaveResponse = // Wallet lib export interface WalletLib { getChainCode(dpath: string): Promise; + signTransaction(transaction: RawTransaction, path: string): Promise; }