aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/scripts/background.js132
-rw-r--r--app/scripts/lib/idStore.js263
-rw-r--r--app/scripts/lib/idmgmt.js318
-rw-r--r--package.json1
4 files changed, 332 insertions, 382 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 0276124cc..4dc92cea5 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -1,27 +1,18 @@
const Dnode = require('dnode')
+const eos = require('end-of-stream')
+const extend = require('xtend')
+const EthStore = require('eth-store')
const PortStream = require('./lib/port-stream.js')
const MetaMaskProvider = require('./lib/metamask-provider')
-const IdentityManager = require('./lib/idmgmt')
-const eos = require('end-of-stream')
+// const IdentityManager = require('./lib/idmgmt')
+const IdentityStore = require('./lib/idStore')
console.log('ready to roll')
-var wallet = new IdentityManager()
-
-// setup provider
-var zeroClient = MetaMaskProvider({
- rpcUrl: 'https://rawtestrpc.metamask.io/',
- getAccounts: wallet.getAccounts.bind(wallet),
- signTransaction: wallet.addUnconfirmedTransaction.bind(wallet),
-})
-
-wallet.setProvider(zeroClient)
-zeroClient.on('block', function(block){
- wallet.newBlock(block)
-})
-
+//
+// connect to other contexts
+//
-// setup messaging
chrome.runtime.onConnect.addListener(connectRemote)
function connectRemote(remotePort){
var isMetaMaskInternalProcess = (remotePort.name === 'popup')
@@ -34,40 +25,31 @@ function connectRemote(remotePort){
}
}
-function handleInternalCommunication(remotePort){
- var duplex = new PortStream(remotePort)
- var connection = Dnode({
- // this is annoying, have to decompose wallet
- getState: wallet.getState.bind(wallet),
- submitPassword: wallet.submitPassword.bind(wallet),
- setSelectedAddress: wallet.setSelectedAddress.bind(wallet),
- signTransaction: wallet.signTransaction.bind(wallet),
- setLocked: wallet.setLocked.bind(wallet),
- getAccounts: wallet.getAccounts.bind(wallet),
- newBlock: wallet.newBlock.bind(wallet),
- setProvider: wallet.setProvider.bind(wallet),
- })
- duplex.pipe(connection).pipe(duplex)
- connection.on('remote', function(remote){
-
- // push updates to popup
- wallet.on('update', sendUpdate)
- eos(duplex, function unsubscribe(){
- wallet.removeListener('update', sendUpdate)
- })
- function sendUpdate(state){
- remote.sendUpdate(state)
- }
+function handleExternalCommunication(remotePort){
+ remotePort.onMessage.addListener(onRpcRequest.bind(null, remotePort))
+}
- })
+//
+// state and network
+//
-
-
- // sub to metamask store
-}
+var idStore = new IdentityStore()
+var zeroClient = MetaMaskProvider({
+ rpcUrl: 'https://rawtestrpc.metamask.io/',
+ getAccounts: function(cb){
+ var selectedAddress = idStore.getSelectedAddress()
+ var result = selectedAddress ? [selectedAddress] : []
+ cb(null, result)
+ },
+ signTransaction: idStore.addUnconfirmedTransaction.bind(idStore),
+})
+var ethStore = new EthStore(zeroClient)
+idStore.setStore(ethStore)
-function handleExternalCommunication(remotePort){
- remotePort.onMessage.addListener(onRpcRequest.bind(null, remotePort))
+function getState(){
+ var state = extend(ethStore.getState(), idStore.getState())
+ console.log(state)
+ return state
}
// handle rpc requests
@@ -84,8 +66,44 @@ function onRpcRequest(remotePort, payload){
})
}
-// setup badge text
-wallet.on('update', updateBadge)
+
+//
+// popup integration
+//
+
+function handleInternalCommunication(remotePort){
+ var duplex = new PortStream(remotePort)
+ var connection = Dnode({
+ getState: function(cb){ cb(null, getState()) },
+ // forward directly to idStore
+ submitPassword: idStore.submitPassword.bind(idStore),
+ setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
+ signTransaction: idStore.signTransaction.bind(idStore),
+ setLocked: idStore.setLocked.bind(idStore),
+ })
+ duplex.pipe(connection).pipe(duplex)
+ connection.on('remote', function(remote){
+
+ // push updates to popup
+ ethStore.on('update', sendUpdate)
+ idStore.on('update', sendUpdate)
+ // teardown on disconnect
+ eos(duplex, function unsubscribe(){
+ ethStore.removeListener('update', sendUpdate)
+ })
+ function sendUpdate(){
+ var state = getState()
+ remote.sendUpdate(state)
+ }
+
+ })
+}
+
+//
+// plugin badge text
+//
+
+idStore.on('update', updateBadge)
function updateBadge(state){
var label = ''
@@ -97,17 +115,3 @@ function updateBadge(state){
chrome.browserAction.setBadgeBackgroundColor({color: '#506F8B'})
}
-// function handleMessage(msg){
-// console.log('got message!', msg.type)
-// switch(msg.type){
-
-// case 'addUnsignedTx':
-// addTransaction(msg.payload)
-// return
-
-// case 'removeUnsignedTx':
-// removeTransaction(msg.payload)
-// return
-
-// }
-// }
diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js
new file mode 100644
index 000000000..0dfc4deed
--- /dev/null
+++ b/app/scripts/lib/idStore.js
@@ -0,0 +1,263 @@
+const EventEmitter = require('events').EventEmitter
+const inherits = require('util').inherits
+const Transaction = require('ethereumjs-tx')
+const KeyStore = require('eth-lightwallet').keystore
+const async = require('async')
+const clone = require('clone')
+const extend = require('xtend')
+const createId = require('web3-provider-engine/util/random-id')
+
+
+module.exports = IdentityStore
+
+
+inherits(IdentityStore, EventEmitter)
+function IdentityStore(ethStore) {
+ const self = this
+ EventEmitter.call(self)
+
+ // we just use the ethStore to auto-add accounts
+ self._ethStore = ethStore
+
+ self._currentState = {
+ selectedAddress: null,
+ identities: {},
+ unconfTxs: {},
+ }
+ // not part of serilized metamask state - only kept in memory
+ self._unconfTxCbs = {}
+}
+
+//
+// public
+//
+
+IdentityStore.prototype.setStore = function(store){
+ const self = this
+ self._ethStore = store
+}
+
+IdentityStore.prototype.getState = function(){
+ const self = this
+ return clone(extend(self._currentState, {
+ isUnlocked: self._isUnlocked(),
+ }))
+}
+
+IdentityStore.prototype.getSelectedAddress = function(){
+ const self = this
+ return self._currentState.selectedAddress
+}
+
+IdentityStore.prototype.setSelectedAddress = function(address){
+ const self = this
+ self._currentState.selectedAddress = address
+ self._didUpdate()
+}
+
+IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){
+ var self = this
+
+ var time = (new Date()).getTime()
+ var txId = createId()
+ self._currentState.unconfTxs[txId] = {
+ id: txId,
+ txParams: txParams,
+ time: time,
+ status: 'unconfirmed',
+ }
+ console.log('addUnconfirmedTransaction:', txParams)
+
+ // temp - just sign the tx
+ // otherwise we need to keep the cb around
+ // signTransaction(txId, cb)
+ self._unconfTxCbs[txId] = cb
+
+ // signal update
+ self._didUpdate()
+}
+
+
+
+IdentityStore.prototype.setLocked = function(){
+ const self = this
+ delete self._keyStore
+ delete window.sessionStorage['password']
+}
+
+IdentityStore.prototype.submitPassword = function(password, cb){
+ const self = this
+ console.log('submitPassword:', password)
+ self._tryPassword(password, function(err){
+ if (err) console.log('bad password:', password, err)
+ if (err) return cb(err)
+ console.log('good password:', password)
+ window.sessionStorage['password'] = password
+ // load identities before returning...
+ self._loadIdentities()
+ cb()
+ })
+}
+
+IdentityStore.prototype.signTransaction = function(password, txId, cb){
+ const self = this
+
+ var txData = self._currentState.unconfTxs[txId]
+ var txParams = txData.txParams
+
+ self._signTransaction(txParams, function(err, rawTx, txHash){
+ if (err) {
+ throw err
+ txData.status = 'error'
+ txData.error = err
+ self._didUpdate()
+ return
+ }
+
+ txData.hash = txHash
+ txData.status = 'pending'
+
+ // for now just remove it
+ delete self._currentState.unconfTxs[txData.id]
+
+ // rpc callback
+ var txSigCb = self._unconfTxCbs[txId] || noop
+ txSigCb(null, rawTx)
+
+ // confirm tx callback
+ cb()
+
+ self._didUpdate()
+ })
+}
+
+//
+// private
+//
+
+// internal - actually signs the tx
+IdentityStore.prototype._signTransaction = function(txParams, cb){
+ const self = this
+ try {
+ // console.log('signing tx:', txParams)
+ var tx = new Transaction({
+ nonce: txParams.nonce,
+ to: txParams.to,
+ value: txParams.value,
+ data: txParams.input,
+ gasPrice: txParams.gasPrice,
+ gasLimit: txParams.gas,
+ })
+
+ var password = self._getPassword()
+ var serializedTx = self._keyStore.signTx(tx.serialize(), password, self._currentState.selectedAddress)
+
+ // // deserialize and dump values to confirm configuration
+ // var verifyTx = new Transaction(tx.serialize())
+ // console.log('signed transaction:', {
+ // to: '0x'+verifyTx.to.toString('hex'),
+ // from: '0x'+verifyTx.from.toString('hex'),
+ // nonce: '0x'+verifyTx.nonce.toString('hex'),
+ // value: (ethUtil.bufferToInt(verifyTx.value)/1e18)+' ether',
+ // data: '0x'+verifyTx.data.toString('hex'),
+ // gasPrice: '0x'+verifyTx.gasPrice.toString('hex'),
+ // gasLimit: '0x'+verifyTx.gasLimit.toString('hex'),
+ // })
+ cb(null, serializedTx, tx.hash())
+ } catch (err) {
+ cb(err)
+ }
+}
+
+IdentityStore.prototype._didUpdate = function(){
+ const self = this
+ self.emit('update', self.getState())
+}
+
+IdentityStore.prototype._isUnlocked = function(){
+ const self = this
+ // var password = window.sessionStorage['password']
+ // var result = Boolean(password)
+ var result = Boolean(self._keyStore)
+ return result
+}
+
+// load identities from keyStore
+IdentityStore.prototype._loadIdentities = function(){
+ const self = this
+ if (!self._isUnlocked()) throw new Error('not unlocked')
+ // get addresses and normalize address hexString
+ var addresses = self._keyStore.getAddresses().map(function(address){ return '0x'+address })
+ addresses.forEach(function(address){
+ // add to ethStore
+ self._ethStore.addAccount(address)
+ // add to identities
+ var identity = {
+ name: 'Wally',
+ img: 'QmW6hcwYzXrNkuHrpvo58YeZvbZxUddv69ATSHY3BHpPdd',
+ address: address,
+ }
+ self._currentState.identities[address] = identity
+ })
+ self._didUpdate()
+}
+
+//
+// keyStore managment - unlocking + deserialization
+//
+
+IdentityStore.prototype._tryPassword = function(password, cb){
+ const self = this
+ var keyStore = self._getKeyStore(password)
+ var address = keyStore.getAddresses()[0]
+ if (!address) return cb(new Error('KeyStore - No address to check.'))
+ var hdPathString = keyStore.defaultHdPathString
+ try {
+ var encKey = keyStore.generateEncKey(password)
+ var encPrivKey = keyStore.ksData[hdPathString].encPrivKeys[address]
+ var privKey = KeyStore._decryptKey(encPrivKey, encKey)
+ var addrFromPrivKey = KeyStore._computeAddressFromPrivKey(privKey)
+ } catch (err) {
+ return cb(err)
+ }
+ if (addrFromPrivKey !== address) return cb(new Error('KeyStore - Decrypting private key failed!'))
+ cb()
+}
+
+IdentityStore.prototype._getKeyStore = function(password){
+ const self = this
+ var keyStore = null
+ var serializedKeystore = window.localStorage['lightwallet']
+ // returning user
+ if (serializedKeystore) {
+ keyStore = KeyStore.deserialize(serializedKeystore)
+ // first time here
+ } else {
+ console.log('creating new keystore with password:', password)
+ var secretSeed = KeyStore.generateRandomSeed()
+ keyStore = new KeyStore(secretSeed, password)
+ keyStore.generateNewAddress(password, 3)
+ self._saveKeystore(keyStore)
+ }
+ keyStore.passwordProvider = function getPassword(cb){
+ cb(null, self._getPassword())
+ }
+ self._keyStore = keyStore
+ return keyStore
+}
+
+IdentityStore.prototype._saveKeystore = function(keyStore){
+ const self = this
+ window.localStorage['lightwallet'] = keyStore.serialize()
+}
+
+IdentityStore.prototype._getPassword = function(){
+ const self = this
+ var password = window.sessionStorage['password']
+ console.warn('using password from memory:', password)
+ return password
+}
+
+// util
+
+function noop(){} \ No newline at end of file
diff --git a/app/scripts/lib/idmgmt.js b/app/scripts/lib/idmgmt.js
deleted file mode 100644
index 7bc276041..000000000
--- a/app/scripts/lib/idmgmt.js
+++ /dev/null
@@ -1,318 +0,0 @@
-const inherits = require('util').inherits
-const EventEmitter = require('events').EventEmitter
-const async = require('async')
-const KeyStore = require('eth-lightwallet').keystore
-const createPayload = require('web3-provider-engine/util/create-payload')
-const createId = require('web3-provider-engine/util/random-id')
-const Transaction = require('ethereumjs-tx')
-
-module.exports = IdentityManager
-
-
-var selectedAddress = null
-var identities = {}
-var unconfTxs = {}
-
-// not part of serilized metamask state - only keep in memory
-var unconfTxCbs = {}
-
-var provider = null
-var defaultPassword = 'test'
-
-
-
-inherits(IdentityManager, EventEmitter)
-function IdentityManager(opts){
- const self = this
- self.on('block', function(){
- self.updateIdentities()
- })
-}
-
-// plugin popup
-IdentityManager.prototype.getState = getState
-IdentityManager.prototype.submitPassword = submitPassword
-IdentityManager.prototype.setSelectedAddress = setSelectedAddress
-IdentityManager.prototype.signTransaction = signTransaction
-IdentityManager.prototype.setLocked = setLocked
-// eth rpc
-IdentityManager.prototype.getAccounts = getAccounts
-IdentityManager.prototype.addUnconfirmedTransaction = addUnconfirmedTransaction
-// etc
-IdentityManager.prototype.newBlock = newBlock
-IdentityManager.prototype.setProvider = setProvider
-
-
-
-function setProvider(_provider){
- provider = _provider
-}
-
-function newBlock(block){
- var self = this
- self.emit('block', block)
-}
-
-function getState(cb){
- var result = _getState()
- cb(null, result)
-}
-
-function _getState(cb){
- var unlocked = isUnlocked()
- var result = {
- isUnlocked: unlocked,
- identities: unlocked ? getIdentities() : {},
- unconfTxs: unlocked ? unconfTxs : {},
- selectedAddress: selectedAddress,
- }
- return result
-}
-
-function isUnlocked(){
- var password = window.sessionStorage['password']
- var result = Boolean(password)
- return result
-}
-
-function setLocked(){
- delete window.sessionStorage['password']
-}
-
-function setSelectedAddress(address, cb){
- selectedAddress = address
- cb(null, _getState())
-}
-
-function submitPassword(password, cb){
- const self = this
- console.log('submitPassword:', password)
- tryPassword(password, function(err){
- if (err) console.log('bad password:', password, err)
- if (err) return cb(err)
- console.log('good password:', password)
- window.sessionStorage['password'] = password
- // load identities before returning...
- self.loadIdentities()
- var state = _getState()
- cb(null, state)
- // trigger an update but dont wait for it
- self.updateIdentities()
- })
-}
-
-// get the current selected address
-function getAccounts(cb){
- var result = selectedAddress ? [selectedAddress] : []
- cb(null, result)
-}
-
-function getIdentities(){
- return identities
-}
-
-// load identities from keyStore
-IdentityManager.prototype.loadIdentities = function(){
- const self = this
- if (!isUnlocked()) throw new Error('not unlocked')
- var keyStore = getKeyStore()
- var addresses = keyStore.getAddresses().map(function(address){ return '0x'+address })
- addresses.forEach(function(address){
- var identity = {
- name: 'Wally',
- img: 'QmW6hcwYzXrNkuHrpvo58YeZvbZxUddv69ATSHY3BHpPdd',
- address: address,
- balance: null,
- txCount: null,
- }
- identities[address] = identity
- })
- self._didUpdate()
-}
-
-IdentityManager.prototype._didUpdate = function(){
- const self = this
- self.emit('update', _getState())
-}
-
-// foreach in identities, update balance + nonce
-IdentityManager.prototype.updateIdentities = function(cb){
- var self = this
- cb = cb || function(){}
- if (!isUnlocked()) return cb(new Error('Not unlocked.'))
- var addresses = Object.keys(identities)
- async.map(addresses, self.updateIdentity.bind(self), cb)
-}
-
-// gets latest info from the network for the identity
-IdentityManager.prototype.updateIdentity = function(address, cb){
- var self = this
- async.parallel([
- getAccountBalance.bind(null, address),
- getTxCount.bind(null, address),
- ], function(err, result){
- if (err) return cb(err)
- var identity = identities[address]
- identity.balance = result[0]
- identity.txCount = result[1]
- self._didUpdate()
- cb()
- })
-}
-
-function getTxCount(address, cb){
- provider.sendAsync(createPayload({
- method: 'eth_getTransactionCount',
- params: [address, 'pending'],
- }), function(err, res){
- if (err) return cb(err)
- if (res.error) return cb(res.error)
- cb(null, res.result)
- })
-}
-
-function getAccountBalance(address, cb){
- provider.sendAsync(createPayload({
- method: 'eth_getBalance',
- params: [address, 'latest'],
- }), function(err, res){
- if (err) return cb(err)
- if (res.error) return cb(res.error)
- cb(null, res.result)
- })
-}
-
-function tryPassword(password, cb){
- var keyStore = getKeyStore(password)
- var address = keyStore.getAddresses()[0]
- if (!address) return cb(new Error('KeyStore - No address to check.'))
- var hdPathString = keyStore.defaultHdPathString
- try {
- var encKey = keyStore.generateEncKey(password)
- var encPrivKey = keyStore.ksData[hdPathString].encPrivKeys[address]
- var privKey = KeyStore._decryptKey(encPrivKey, encKey)
- var addrFromPrivKey = KeyStore._computeAddressFromPrivKey(privKey)
- } catch (err) {
- return cb(err)
- }
- if (addrFromPrivKey !== address) return cb(new Error('KeyStore - Decrypting private key failed!'))
- cb()
-}
-
-function addUnconfirmedTransaction(txParams, cb){
- var self = this
-
- var time = (new Date()).getTime()
- var txId = createId()
- unconfTxs[txId] = {
- id: txId,
- txParams: txParams,
- time: time,
- status: 'unconfirmed',
- }
- console.log('addUnconfirmedTransaction:', txParams)
-
- // temp - just sign the tx
- // otherwise we need to keep the cb around
- // signTransaction(txId, cb)
- unconfTxCbs[txId] = cb
-
- // signal update
- self._didUpdate()
-}
-
-// called from
-function signTransaction(password, txId, cb){
- const self = this
-
- var txData = unconfTxs[txId]
- var txParams = txData.txParams
- console.log('signTransaction:', txData)
-
- self._signTransaction(txParams, function(err, rawTx, txHash){
- if (err) {
- txData.status = 'error'
- txData.error = err
- self._didUpdate()
- return
- }
- txData.hash = txHash
- txData.status = 'pending'
- // for now just kill it
- delete unconfTxs[txData.id]
-
- var txSigCb = unconfTxCbs[txId] || function(){}
- txSigCb(null, rawTx)
-
- cb(null, _getState())
- self._didUpdate()
- })
-}
-
-// internal - actually signs the tx
-IdentityManager.prototype._signTransaction = function(txParams, cb){
- try {
- // console.log('signing tx:', txParams)
- var tx = new Transaction({
- nonce: txParams.nonce,
- to: txParams.to,
- value: txParams.value,
- data: txParams.input,
- gasPrice: txParams.gasPrice,
- gasLimit: txParams.gas,
- })
-
- var keyStore = getKeyStore()
- var serializedTx = keyStore.signTx(tx.serialize(), defaultPassword, selectedAddress)
-
- // // deserialize and dump values to confirm configuration
- // var verifyTx = new Transaction(tx.serialize())
- // console.log('signed transaction:', {
- // to: '0x'+verifyTx.to.toString('hex'),
- // from: '0x'+verifyTx.from.toString('hex'),
- // nonce: '0x'+verifyTx.nonce.toString('hex'),
- // value: (ethUtil.bufferToInt(verifyTx.value)/1e18)+' ether',
- // data: '0x'+verifyTx.data.toString('hex'),
- // gasPrice: '0x'+verifyTx.gasPrice.toString('hex'),
- // gasLimit: '0x'+verifyTx.gasLimit.toString('hex'),
- // })
- cb(null, serializedTx, tx.hash())
- } catch (err) {
- cb(err)
- }
-}
-
-var keyStore = null
-function getKeyStore(password){
- if (keyStore) return keyStore
- password = password || getPassword()
- var serializedKeystore = window.localStorage['lightwallet']
- // returning user
- if (serializedKeystore) {
- keyStore = KeyStore.deserialize(serializedKeystore)
- // first time here
- } else {
- console.log('creating new keystore with default password:', defaultPassword)
- var secretSeed = KeyStore.generateRandomSeed()
- keyStore = new KeyStore(secretSeed, defaultPassword)
- keyStore.generateNewAddress(defaultPassword, 3)
- saveKeystore()
- }
- keyStore.passwordProvider = unlockKeystore
- return keyStore
-}
-
-function saveKeystore(){
- window.localStorage['lightwallet'] = keyStore.serialize()
-}
-
-function getPassword(){
- var password = window.sessionStorage['password']
- if (!password) throw new Error('No password found...')
-}
-
-function unlockKeystore(cb){
- var password = getPassword()
- console.warn('unlocking keystore...')
- cb(null, password)
-} \ No newline at end of file
diff --git a/package.json b/package.json
index fd8b5f4a9..4fdd1bc4c 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
},
"dependencies": {
"async": "^1.5.2",
+ "clone": "^1.0.2",
"dnode": "^1.2.2",
"end-of-stream": "^1.1.0",
"eth-lightwallet": "^1.0.1",