aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--app/scripts/background.js25
-rw-r--r--app/scripts/lib/config-manager.js82
-rw-r--r--app/scripts/lib/idStore.js111
-rw-r--r--app/scripts/lib/notifications.js (renamed from app/scripts/lib/tx-notification.js)30
-rw-r--r--package.json4
-rw-r--r--ui/app/info.js2
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'),