const EventEmitter = require('events').EventEmitter const inherits = require('util').inherits const Transaction = require('ethereumjs-tx') const LightwalletKeyStore = require('eth-lightwallet').keystore const LightwalletSigner = require('eth-lightwallet').signing 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 // lightwallet key store self._keyStore = null // lightwallet wrapper self._idmgmt = null self._currentState = { selectedAddress: null, identities: {}, unconfTxs: {}, } // not part of serilized metamask state - only kept in memory self._unconfTxCbs = {} } // // public // IdentityStore.prototype.createNewVault = function(password, cb){ const self = this delete self._keyStore delete window.localStorage['lightwallet'] var keyStore = self._createIdmgmt(password, function(err){ if (err) return cb(err) var seedWords = self._idmgmt.getSeed() self._loadIdentities() self._didUpdate() cb(null, seedWords) }) } IdentityStore.prototype.setStore = function(store){ const self = this self._ethStore = store } IdentityStore.prototype.getState = function(){ const self = this return clone(extend(self._currentState, { isInitialized: !!window.localStorage['lightwallet'], 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.setLocked = function(){ const self = this delete self._keyStore delete self._idmgmt } IdentityStore.prototype.submitPassword = function(password, cb){ const self = this self._tryPassword(password, function(err){ if (err) return cb(err) // load identities before returning... self._loadIdentities() cb() }) } // comes from dapp via zero-client hooked-wallet provider IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){ var self = this // create txData obj with parameters and meta data var time = (new Date()).getTime() var txId = createId() var txData = { id: txId, txParams: txParams, time: time, status: 'unconfirmed', } self._currentState.unconfTxs[txId] = txData console.log('addUnconfirmedTransaction:', txData) // keep the cb around for after approval (requires user interaction) self._unconfTxCbs[txId] = cb // signal update self._didUpdate() } // comes from metamask ui IdentityStore.prototype.approveTransaction = function(txId, cb){ const self = this var txData = self._currentState.unconfTxs[txId] var txParams = txData.txParams var approvalCb = self._unconfTxCbs[txId] || noop // accept tx cb() approvalCb(null, true) // clean up delete self._currentState.unconfTxs[txId] delete self._unconfTxCbs[txId] self._didUpdate() } // comes from metamask ui IdentityStore.prototype.cancelTransaction = function(txId){ const self = this var txData = self._currentState.unconfTxs[txId] var approvalCb = self._unconfTxCbs[txId] || noop // reject tx approvalCb(null, false) // clean up delete self._currentState.unconfTxs[txId] delete self._unconfTxCbs[txId] self._didUpdate() } // performs the actual signing, no autofill of params IdentityStore.prototype.signTransaction = function(txParams, cb){ const self = this try { console.log('signing tx...', txParams) var rawTx = self._idmgmt.signTx(txParams) cb(null, rawTx) } catch (err) { cb(err) } } // // private // IdentityStore.prototype._didUpdate = function(){ const self = this self.emit('update', self.getState()) } IdentityStore.prototype._isUnlocked = function(){ const self = this var result = Boolean(self._keyStore) && Boolean(self._idmgmt) return result } // load identities from keyStoreet 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 self._createIdmgmt(password, cb) } IdentityStore.prototype._createIdmgmt = function(password, cb){ const self = this var keyStore = null LightwalletKeyStore.deriveKeyFromPassword(password, function(err, derrivedKey){ if (err) return cb(err) var serializedKeystore = window.localStorage['lightwallet'] // returning user if (serializedKeystore) { keyStore = LightwalletKeyStore.deserialize(serializedKeystore) var isCorrect = keyStore.isDerivedKeyCorrect(derrivedKey) if (!isCorrect) return cb(new Error('Lightwallet - password incorrect')) // first time here } else { var secretSeed = LightwalletKeyStore.generateRandomSeed() keyStore = new LightwalletKeyStore(secretSeed, derrivedKey) keyStore.generateNewAddress(derrivedKey, 3) window.localStorage['lightwallet'] = keyStore.serialize() console.log('saved to keystore localStorage') } self._keyStore = keyStore self._idmgmt = { getAddresses: function(){ return keyStore.getAddresses().map(function(address){ return '0x'+address }) }, signTx: function(txParams){ // normalize values txParams.to = ethUtil.addHexPrefix(txParams.to) txParams.from = ethUtil.addHexPrefix(txParams.from) txParams.value = ethUtil.addHexPrefix(txParams.value) txParams.data = ethUtil.addHexPrefix(txParams.data) txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas) txParams.nonce = ethUtil.addHexPrefix(txParams.nonce) var tx = new Transaction(txParams) var rawTx = '0x'+tx.serialize().toString('hex') return '0x'+LightwalletSigner.signTx(keyStore, derrivedKey, rawTx, txParams.from) }, getSeed: function(){ return keyStore.getSeed(derrivedKey) }, } cb() }) } // util function noop(){}