diff --git a/common/libs/EthEnclave/client-utils.ts b/common/libs/EthEnclave/client-utils.ts new file mode 100644 index 00000000..3c9df81b --- /dev/null +++ b/common/libs/EthEnclave/client-utils.ts @@ -0,0 +1,52 @@ +import { + createRpcRequestedEv, + createRpcProcessedEv, + sendOnChannel, + idGeneratorFactory, + receiveOnChannelFactory +} from './utils'; +import { EnclaveEvents, MatchingIdHandler, RpcEvent, RpcEventSuccess } from './types'; +import { Reject, Resolve } from 'mycrypto-shepherd/dist/lib/types'; + +const genId = idGeneratorFactory(); + +const matchOnId = (id: number | undefined, cb: MatchingIdHandler) => ( + ev: string, + args: RpcEvent +) => { + if (id === args.id) { + cb(ev, args); + } +}; + +type PromiseHandler = (res: Resolve, rej: Reject) => MatchingIdHandler; + +const isRpcSuccess = (args: RpcEvent): args is RpcEventSuccess => args.payload; + +const handleServerResponse: PromiseHandler = (res, rej) => (_, args) => { + if (isRpcSuccess(args)) { + res(args.payload); + } else { + rej(args.errMsg); + } +}; + +const clientRecieve = receiveOnChannelFactory((res, rej, id) => + matchOnId(id, handleServerResponse(res, rej)) +); + +export const createClientRpcHandler = ( + target: any, + channel: EnclaveEvents, + sender: any, + receiver: any +) => (payload: T): Promise => { + const id = genId(); + const sendingChannel = createRpcRequestedEv(channel); + const receivingChannel = createRpcProcessedEv(channel); + // send request event on channel to be processed by server + sendOnChannel(sendingChannel, { payload, id }, target, sender); + + // return a promise that resolves/rejects when a matching id response comes back from server + return clientRecieve(receivingChannel, id, target, receiver); +}; diff --git a/common/libs/EthEnclave/client.ts b/common/libs/EthEnclave/client.ts new file mode 100644 index 00000000..47a1759f --- /dev/null +++ b/common/libs/EthEnclave/client.ts @@ -0,0 +1,23 @@ +import { SignRawTransactionParams, ElectronInjectedGlobals, EnclaveEvents } from './types'; +import { createClientRpcHandler } from './client-utils'; +import EventEmitter from 'events'; + +const myEE = new EventEmitter(); +function isElectronEnv() { + return process.env.BUILD_ELECTRON; +} + +function getElectronWindow() { + return window as ElectronInjectedGlobals; +} + +export function signRawTransaction(params: SignRawTransactionParams) { + // if we're in electron env then use the globally injected signRawTransaction from the preload script + if (isElectronEnv) { + const w = getElectronWindow(); + w.signRawTransaction(params); + } else { + // otherwise create an rpc event handler based on "events" package + createClientRpcHandler(myEE, EnclaveEvents.SIGN_RAW_TRANSACTION, myEE.emit, myEE.on); + } +} diff --git a/common/libs/EthEnclave/index.ts b/common/libs/EthEnclave/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/common/libs/EthEnclave/preload.ts b/common/libs/EthEnclave/preload.ts new file mode 100644 index 00000000..be436d1e --- /dev/null +++ b/common/libs/EthEnclave/preload.ts @@ -0,0 +1,11 @@ +import { ipcRenderer } from 'electron'; +import { EnclaveEvents, SignRawTransactionParams, SignRawTransaction } from './types'; +import { createClientRpcHandler } from './client-utils'; + +const createElectronRpcHandler = (channel: EnclaveEvents) => + createClientRpcHandler(ipcRenderer, channel, ipcRenderer.send, ipcRenderer.once); + +export const signRawTransaction = createElectronRpcHandler< + SignRawTransactionParams, + SignRawTransaction +>(EnclaveEvents.SIGN_RAW_TRANSACTION); diff --git a/common/libs/EthEnclave/server-utils.ts b/common/libs/EthEnclave/server-utils.ts new file mode 100644 index 00000000..7bde8898 --- /dev/null +++ b/common/libs/EthEnclave/server-utils.ts @@ -0,0 +1,18 @@ +import { createRpcRequestedEv, receiveOnChannelFactory } from './utils'; +import { EnclaveEvents, RpcEventServer } from './types'; + +// uses factory component, skips the promise handling and id parameter as those are only used on client side +// cb instead handles the receiving event, and on success/failure emits on EnclaveEvent channel with its response +const serverRecieve = (cb: any) => receiveOnChannelFactory(() => cb); + +// create a receiving channel for incoming events of Enclave events type +// calls cb when a the channel has an event emitted to it +export const createServerRpcHandler = ( + target: any, + channel: EnclaveEvents, + receiver: any, + cb: any +) => { + const receivingChannel = createRpcRequestedEv(channel); + serverRecieve(cb)(receivingChannel, target, receiver); +}; diff --git a/common/libs/EthEnclave/server.ts b/common/libs/EthEnclave/server.ts new file mode 100644 index 00000000..a027c972 --- /dev/null +++ b/common/libs/EthEnclave/server.ts @@ -0,0 +1,39 @@ +import { createRpcProcessedEv, sendOnChannel } from './utils'; +import { + EnclaveEvents, + RpcEventServer, + RpcEventHandler, + SignRawTransactionParams, + RpcEventSuccess, + SignRawTransaction +} from './types'; +import { ipcMain } from 'electron'; +import { createServerRpcHandler } from './server-utils'; + +// mock +// this hard codes electron specific sending atm (ev.sender.send) +export const signRawTransactionRequest: RpcEventHandler< + RpcEventServer, + void +> = (ev: any, args) => { + // "processing steps" + // this is where you would sign the transaction + const res: RpcEventSuccess = { + payload: { r: '1', s: '1', v: '1' }, + id: args.id, + errMsg: null + }; + + // emit result back to client as presonse + // pull this out later on into server-utils + const sendingChannel = createRpcProcessedEv(EnclaveEvents.SIGN_RAW_TRANSACTION); + sendOnChannel(sendingChannel, res, ev, ev.sender.send); +}; + +// create the handler for SIGN_RAW_TRANSACTION +createServerRpcHandler( + ipcMain, + EnclaveEvents.SIGN_RAW_TRANSACTION, + ipcMain.on, + signRawTransactionRequest +); diff --git a/common/libs/EthEnclave/types.ts b/common/libs/EthEnclave/types.ts new file mode 100644 index 00000000..1edc0959 --- /dev/null +++ b/common/libs/EthEnclave/types.ts @@ -0,0 +1,43 @@ +export enum EnclaveEvents { + SIGN_RAW_TRANSACTION = 'sign-raw-transaction' +} + +export interface RpcEventFailure { + payload: null; + errMsg: string; + id: number; +} + +export interface RpcEventSuccess { + payload: T; + errMsg: null; + id: number; +} + +export type RpcEvent = RpcEventFailure | RpcEventSuccess; + +export interface RpcEventServer { + payload: T; + id: number; +} + +export type RpcEventHandler = (event: EnclaveEvents, args: A) => R; + +export type MatchingIdHandler = (event: string, args: A) => void; + +export interface SignRawTransactionParams { + path: string; + rawTxHex: string; +} + +export interface SignRawTransaction { + s: string; + v: string; + r: string; +} + +export interface EnclaveProvider { + signRawTransaction(params: SignRawTransactionParams): Promise; +} + +export type ElectronInjectedGlobals = Window & EnclaveProvider; diff --git a/common/libs/EthEnclave/utils.ts b/common/libs/EthEnclave/utils.ts new file mode 100644 index 00000000..a5e66da1 --- /dev/null +++ b/common/libs/EthEnclave/utils.ts @@ -0,0 +1,34 @@ +import { EnclaveEvents, RpcEventHandler } from './types'; + +export const idGeneratorFactory = () => { + let callId = 0; + return () => { + const currValue = callId; + callId += 1; + return currValue; + }; +}; + +export const createRpcRequestedEv = (e: EnclaveEvents) => `${e}-requested`; +export const createRpcProcessedEv = (e: EnclaveEvents) => `${e}-processed`; + +type Resolve = (value?: T | PromiseLike) => void; +type Reject = (reason?: T) => void; + +export type ReceiveCb = ( + res: Resolve, + rej: Reject, + id?: number +) => RpcEventHandler; + +export const receiveOnChannelFactory = ( + cb: ReceiveCb +) => (channel: string, target: any, on: any, id?: number): Promise => + new Promise((res, rej) => on.call(target, channel, cb(res, rej, id))); + +export const sendOnChannel = ( + channel: string, + payload: any, + target: any, + emit: (args: any) => void +) => emit.call(target, channel, payload); diff --git a/package.json b/package.json index 8b62fcd9..70b889c0 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "ethereumjs-tx": "1.3.4", "ethereumjs-util": "5.1.5", "ethereumjs-wallet": "0.6.0", + "events": "^2.0.0", "font-awesome": "4.7.0", "hdkey": "0.8.0", "idna-uts46": "1.1.0", @@ -62,6 +63,7 @@ "@types/classnames": "2.2.3", "@types/enzyme": "3.1.8", "@types/enzyme-adapter-react-16": "1.0.1", + "@types/events": "^1.2.0", "@types/history": "4.6.2", "@types/jest": "22.2.3", "@types/lodash": "4.14.107", diff --git a/yarn.lock b/yarn.lock index 85591db4..7d1869df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -88,6 +88,10 @@ "@types/cheerio" "*" "@types/react" "*" +"@types/events@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" + "@types/history@*", "@types/history@4.6.2": version "4.6.2" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" @@ -3872,6 +3876,10 @@ events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" +events@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/events/-/events-2.0.0.tgz#cbbb56bf3ab1ac18d71c43bb32c86255062769f2" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"