diff options
Merge branch 'master' into Identicon
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | app/scripts/background.js | 25 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 82 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 111 | ||||
-rw-r--r-- | app/scripts/lib/notifications.js (renamed from app/scripts/lib/tx-notification.js) | 30 | ||||
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | ui/app/info.js | 2 |
7 files changed, 236 insertions, 19 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f53bc4fc0..ba591dfa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Clicking accounts in the account list now both selects that account and displays that account's detail view. - Selected account is now persisted between sessions, so the current account stays selected. - Account icons are now "identicons" (deterministically generated from the address). +- Fixed link to Slack channel. # 1.6.0 2016-04-22 diff --git a/app/scripts/background.js b/app/scripts/background.js index 523df1261..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) @@ -131,7 +137,10 @@ function onRpcRequest(remoteStream, payload){ // console.log('MetaMaskPlugin - incoming payload:', payload) provider.sendAsync(payload, function onPayloadHandled(err, response){ // provider engine errors are included in response objects - if (!payload.isMetamaskInternal) console.log('MetaMaskPlugin - RPC complete:', payload, '->', response) + if (!payload.isMetamaskInternal) { + console.log('MetaMaskPlugin - RPC complete:', payload, '->', response) + if (response.error) console.error('Error in RPC response:\n'+response.error.message) + } try { remoteStream.write(response) } catch (err) { @@ -206,7 +215,7 @@ function updateBadge(state){ } // -// Add unconfirmed Tx +// Add unconfirmed Tx + Msg // function addUnconfirmedTx(txParams, cb){ @@ -219,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 e9aaed82e..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 @@ -207,6 +211,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 // @@ -351,14 +422,30 @@ function IdManagement(opts) { txParams.nonce = ethUtil.addHexPrefix(txParams.nonce) var tx = new Transaction(txParams) + // sign tx + var privKeyHex = this.exportPrivateKey(txParams.from) + var privKey = ethUtil.toBuffer(privKeyHex) + tx.sign(privKey) + // Add the tx hash to the persisted meta-tx object - var hash = '0x' + tx.hash().toString('hex') + var txHash = ethUtil.bufferToHex(tx.hash()) var metaTx = configManager.getTx(txParams.metamaskId) - metaTx.hash = hash + metaTx.hash = txHash configManager.updateTx(metaTx) - var rawTx = '0x'+tx.serialize().toString('hex') - return '0x'+LightwalletSigner.signTx(this.keyStore, this.derivedKey, rawTx, txParams.from, this.hdPathString) + // return raw serialized tx + var rawTx = ethUtil.bufferToHex(tx.serialize()) + 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(){ @@ -366,7 +453,8 @@ function IdManagement(opts) { } 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 } } @@ -374,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 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 e1713e66d..8d99f1cae 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", "identicon.js": "^1.2.1", "inject-css": "^0.1.1", @@ -54,7 +54,7 @@ "three.js": "^0.73.2", "through2": "^2.0.1", "web3": "^0.15.1", - "web3-provider-engine": "^7.5.0", + "web3-provider-engine": "^7.6.2", "xtend": "^4.0.1" }, "devDependencies": { diff --git a/ui/app/info.js b/ui/app/info.js index f6311b4cb..7e6516222 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -42,7 +42,7 @@ InfoScreen.prototype.render = function() { h('div', [ h('a', { - href: 'https://consensys.slack.com/archives/team-metamask', + href: 'http://slack.metamask.io', target: '_blank', onClick(event) { this.navigateTo(event.target.href) }, }, 'Join the conversation on Slack'), |