From 82983e5effb236474c7eba2ac2ba62ea3fe58f5f Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 28 Apr 2016 14:16:24 -0700 Subject: [PATCH] idmgmt - eth_sign support + notifications --- app/scripts/background.js | 20 +++- app/scripts/lib/config-manager.js | 82 ++++++++++++++- app/scripts/lib/idStore.js | 99 ++++++++++++++++++- .../{tx-notification.js => notifications.js} | 30 +++++- package.json | 4 +- 5 files changed, 221 insertions(+), 14 deletions(-) rename app/scripts/lib/{tx-notification.js => notifications.js} (65%) diff --git a/app/scripts/background.js b/app/scripts/background.js index 83d0f575a..3c46f4693 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -7,7 +7,8 @@ const EthStore = require('eth-store') const PortStream = require('./lib/port-stream.js') const MetaMaskProvider = require('web3-provider-engine/zero.js') const IdentityStore = require('./lib/idStore') -const createTxNotification = require('./lib/tx-notification.js') +const createTxNotification = require('./lib/notifications.js').createTxNotification +const createMsgNotification = require('./lib/notifications.js').createMsgNotification const configManager = require('./lib/config-manager-singleton') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const HostStore = require('./lib/remote-store.js').HostStore @@ -55,13 +56,18 @@ var idStore = new IdentityStore() var providerOpts = { rpcUrl: configManager.getCurrentRpcAddress(), + // account mgmt getAccounts: function(cb){ var selectedAddress = idStore.getSelectedAddress() var result = selectedAddress ? [selectedAddress] : [] cb(null, result) }, + // tx signing approveTransaction: addUnconfirmedTx, signTransaction: idStore.signTransaction.bind(idStore), + // msg signing + approveMessage: addUnconfirmedMsg, + signMessage: idStore.signMessage.bind(idStore), } var provider = MetaMaskProvider(providerOpts) var web3 = new Web3(provider) @@ -209,7 +215,7 @@ function updateBadge(state){ } // -// Add unconfirmed Tx +// Add unconfirmed Tx + Msg // function addUnconfirmedTx(txParams, cb){ @@ -222,6 +228,16 @@ function addUnconfirmedTx(txParams, cb){ }) } +function addUnconfirmedMsg(msgParams, cb){ + var msgId = idStore.addUnconfirmedMessage(msgParams, cb) + createMsgNotification({ + title: 'New Unsigned Message', + msgParams: msgParams, + confirm: idStore.approveMessage.bind(idStore, msgId, noop), + cancel: idStore.cancelMessage.bind(idStore, msgId), + }) +} + // // config // diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 102327c2d..0e7454dfd 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -145,15 +145,25 @@ ConfigManager.prototype.setData = function(data) { this.migrator.saveData(data) } +// +// Tx +// + ConfigManager.prototype.getTxList = function() { var data = this.migrator.getData() - if ('transactions' in data) { + if (data.transactions !== undefined) { return data.transactions } else { return [] } } +ConfigManager.prototype.unconfirmedTxs = function() { + var transactions = this.getTxList() + return transactions.filter(tx => tx.status === 'unconfirmed') + .reduce((result, tx) => { result[tx.id] = tx; return result }, {}) +} + ConfigManager.prototype._saveTxList = function(txList) { var data = this.migrator.getData() data.transactions = txList @@ -201,12 +211,74 @@ ConfigManager.prototype.updateTx = function(tx) { this._saveTxList(transactions) } -ConfigManager.prototype.unconfirmedTxs = function() { - var transactions = this.getTxList() - return transactions.filter(tx => tx.status === 'unconfirmed') - .reduce((result, tx) => { result[tx.id] = tx; return result }, {}) +// +// Msg +// + +ConfigManager.prototype.getMsgList = function() { + var data = this.migrator.getData() + if (data.messages !== undefined) { + return data.messages + } else { + return [] + } } +ConfigManager.prototype.unconfirmedMsgs = function() { + var messages = this.getMsgList() + return messages.filter(msg => msg.status === 'unconfirmed') + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) +} + +ConfigManager.prototype._saveMsgList = function(msgList) { + var data = this.migrator.getData() + data.messages = msgList + this.setData(data) +} + +ConfigManager.prototype.addMsg = function(msg) { + var messages = this.getMsgList() + messages.push(msg) + this._saveMsgList(messages) +} + +ConfigManager.prototype.getMsg = function(msgId) { + var messages = this.getMsgList() + var matching = messages.filter(msg => msg.id === msgId) + return matching.length > 0 ? matching[0] : null +} + +ConfigManager.prototype.confirmMsg = function(msgId) { + this._setMsgStatus(msgId, 'confirmed') +} + +ConfigManager.prototype.rejectMsg = function(msgId) { + this._setMsgStatus(msgId, 'rejected') +} + +ConfigManager.prototype._setMsgStatus = function(msgId, status) { + var msg = this.getMsg(msgId) + msg.status = status + this.updateMsg(msg) +} + +ConfigManager.prototype.updateMsg = function(msg) { + var messages = this.getMsgList() + var found, index + messages.forEach((otherMsg, i) => { + if (otherMsg.id === msg.id) { + found = true + index = i + } + }) + if (found) { + messages[index] = msg + } + this._saveMsgList(messages) +} + + + // observable ConfigManager.prototype.subscribe = function(fn){ diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index b462d4ad5..c25d83c9d 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -34,6 +34,7 @@ function IdentityStore(opts = {}) { } // not part of serilized metamask state - only kept in memory this._unconfTxCbs = {} + this._unconfMsgCbs = {} } // @@ -140,6 +141,10 @@ IdentityStore.prototype.exportAccount = function(address, cb) { cb(null, privateKey) } +// +// Transactions +// + // comes from dapp via zero-client hooked-wallet provider IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){ @@ -170,7 +175,6 @@ IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){ // comes from metamask ui IdentityStore.prototype.approveTransaction = function(txId, cb){ var txData = configManager.getTx(txId) - var txParams = txData.txParams var approvalCb = this._unconfTxCbs[txId] || noop // accept tx @@ -206,6 +210,73 @@ IdentityStore.prototype.signTransaction = function(txParams, cb){ } } +// +// Messages +// + +// comes from dapp via zero-client hooked-wallet provider +IdentityStore.prototype.addUnconfirmedMessage = function(msgParams, cb){ + + // create txData obj with parameters and meta data + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { + id: msgId, + msgParams: msgParams, + time: time, + status: 'unconfirmed', + } + configManager.addMsg(msgData) + console.log('addUnconfirmedMessage:', msgData) + + // keep the cb around for after approval (requires user interaction) + // This cb fires completion to the Dapp's write operation. + this._unconfMsgCbs[msgId] = cb + + // signal update + this._didUpdate() + + return msgId +} + +// comes from metamask ui +IdentityStore.prototype.approveMessage = function(msgId, cb){ + var msgData = configManager.getMsg(msgId) + var approvalCb = this._unconfMsgCbs[msgId] || noop + + // accept msg + cb() + approvalCb(null, true) + // clean up + configManager.confirmMsg(msgId) + delete this._unconfMsgCbs[msgId] + this._didUpdate() +} + +// comes from metamask ui +IdentityStore.prototype.cancelMessage = function(msgId){ + var txData = configManager.getMsg(msgId) + var approvalCb = this._unconfMsgCbs[msgId] || noop + + // reject tx + approvalCb(null, false) + // clean up + configManager.rejectMsg(msgId) + delete this._unconfTxCbs[msgId] + this._didUpdate() +} + +// performs the actual signing, no autofill of params +IdentityStore.prototype.signMessage = function(msgParams, cb){ + try { + console.log('signing msg...', msgParams.data) + var rawMsg = this._idmgmt.signMsg(msgParams.from, msgParams.data) + cb(null, rawMsg) + } catch (err) { + cb(err) + } +} + // // private // @@ -352,7 +423,7 @@ function IdManagement(opts) { var tx = new Transaction(txParams) // sign tx - var privKeyHex = ethUtil.addHexPrefix(this.keyStore.exportPrivateKey(txParams.from, this.derivedKey, this.hdPathString)) + var privKeyHex = this.exportPrivateKey(txParams.from) var privKey = ethUtil.toBuffer(privKeyHex) tx.sign(privKey) @@ -367,12 +438,23 @@ function IdManagement(opts) { return rawTx } + this.signMsg = function(address, message){ + // sign message + var privKeyHex = this.exportPrivateKey(address) + var privKey = ethUtil.toBuffer(privKeyHex) + var msgHash = ethUtil.sha3(message) + var msgSig = ethUtil.ecsign(msgHash, privKey) + var rawMsgSig = ethUtil.bufferToHex(concatSig(msgSig.v, msgSig.r, msgSig.s)) + return rawMsgSig + } + this.getSeed = function(){ return this.keyStore.getSeed(this.derivedKey) } this.exportPrivateKey = function(address) { - return this.keyStore.exportPrivateKey(address, this.derivedKey, this.hdPathString) + var privKeyHex = ethUtil.addHexPrefix(this.keyStore.exportPrivateKey(address, this.derivedKey, this.hdPathString)) + return privKeyHex } } @@ -380,3 +462,14 @@ function IdManagement(opts) { // util function noop(){} + + +function concatSig(v, r, s) { + r = ethUtil.fromSigned(r) + s = ethUtil.fromSigned(s) + v = ethUtil.bufferToInt(v) + r = ethUtil.toUnsigned(r).toString('hex') + s = ethUtil.toUnsigned(s).toString('hex') + v = ethUtil.stripHexPrefix(ethUtil.intToHex(v)) + return ethUtil.addHexPrefix(r.concat(s, v).toString("hex")) +} \ No newline at end of file diff --git a/app/scripts/lib/tx-notification.js b/app/scripts/lib/notifications.js similarity index 65% rename from app/scripts/lib/tx-notification.js rename to app/scripts/lib/notifications.js index 75985dee1..2b7cbfe66 100644 --- a/app/scripts/lib/tx-notification.js +++ b/app/scripts/lib/notifications.js @@ -2,8 +2,10 @@ const createId = require('hat') const uiUtils = require('../../../ui/app/util') var notificationHandlers = {} -module.exports = createTxNotification - +module.exports = { + createTxNotification: createTxNotification, + createMsgNotification: createMsgNotification, +} // notification button press chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex){ @@ -47,3 +49,27 @@ function createTxNotification(opts){ cancel: opts.cancel, } } + +function createMsgNotification(opts){ + var message = [ + 'to be signed by: '+uiUtils.addressSummary(opts.msgParams.from), + 'message:\n'+opts.msgParams.data, + ].join('\n') + + var id = createId() + chrome.notifications.create(id, { + type: 'basic', + iconUrl: '/images/icon-128.png', + title: opts.title, + message: message, + buttons: [{ + title: 'confirm', + },{ + title: 'cancel', + }] + }) + notificationHandlers[id] = { + confirm: opts.confirm, + cancel: opts.cancel, + } +} \ No newline at end of file diff --git a/package.json b/package.json index c3f323d77..f9db63478 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "eth-lightwallet": "^2.2.2", "eth-store": "^1.1.0", "ethereumjs-tx": "^1.0.0", - "ethereumjs-util": "^4.3.0", + "ethereumjs-util": "^4.4.0", "hat": "0.0.3", "inject-css": "^0.1.1", "metamask-logo": "^1.1.5", @@ -53,7 +53,7 @@ "three.js": "^0.73.2", "through2": "^2.0.1", "web3": "^0.15.1", - "web3-provider-engine": "^7.6.1", + "web3-provider-engine": "^7.6.2", "xtend": "^4.0.1" }, "devDependencies": {