aboutsummaryrefslogblamecommitdiffstats
path: root/app/scripts/lib/idStore.js
blob: 9d0ca7f194dcf164ea2db89c5282e16d91e89d1f (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                                                   
                              
                                          
                     
                                     
                                                    


                                                               
                                             
                                           
                                                   
                                                  
                                               
 

                              
                                     
                                    
                         

                                                  
                                
                                         
                          
                       
                        


                                      
 
                        

                          
   
 
                                                               
                        
                         





         
                                                                           
                       
                                                         
 


                                  
 
                   
                                                       
                           
 
                      
 
                                             
                                          
 

                          

                       

 
                                                     
                                           
                                                                             



                                        
                                                                         

                   
                                                    
                           

                          
                             

    
 
                                                     
                        

 
                                                            
                                          
                                       
                                              
 
 
                                                
                                          
                                          
                                           
                                                             
                                   
                         
                                              
                                                           
                                              
                                            

                                                 
                                                        
                                                          
                                                

                                                      

                                                    


     
                                                         
                                          






                                                       
                                                          
                                          
                                           

 
                                                                     
                                          




                                        
                                           





                                                                     

 
                                                       

                                            
                                          


                                                        

                                                   

                                                          
 

                                               




                        
                                                     

                                          
                     
   
 

                                                  

                                            
     


                                                        
                                        
                     


    
                                                   

                       
      

 
                                                                  
                                          
                                       
                           
                                          

                                                


    
                                                                



                                                         



               
                                                         
                                                                                         
                                          
 
                 


                                                    
                            
                                                         




                          
                                                         
   
 
                                                   
 



                                                                                     
                                                      






























                                                                                 
                                                     
          
      
   
 
                              
                           
                               




                                  

 



                                                          
                                                  

 
                         
                                                                  
                                          
                                                  
 
              
      

                        
                               

                                

 
                         
                                                             
                                          
                                                  



                         
                              

                                

 
                                                     
                                                                   
       
                                          
                                             
                   




                 
  



                                                         
                                                                          








                                                    
                                












                                                                      
                                                               





                                                    
                                  




                                  
                                                          




                                                    
                                 




                                                     
                                                                


                                                                     







                                    





                 


          
                                                  
                                      

 
                                                   
                                                               


               
                                  
                                                       
                                          
                                                          


                                      
                         
                                                            
                        
                                            
                                                             
                    
                                     
                       
                                              
     
                                                     
    
                   

 
                                                                          
                                          


                                                    

 




                                                 
                                                        
                                          








                                          



                                                   
                                                               

                                                         
 
                                                             
                           
 

                                                                            
 
                             
                                    

        

 
                                                                                     




                                    

                                
   








                                                             
                       
 


                                                                                                        
                                    

                                   
          

      

 

                                                               
                             




                                      
                                                  
                                    

              
                                                                 


                 

                                         


    

                                                                    
                                                        
                                            
                                                    
                                         
                                                               

 
                                                
                                                     


                                                                          

 
                                                   



                                      

       
                   
const EventEmitter = require('events').EventEmitter
const inherits = require('util').inherits
const async = require('async')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const EthQuery = require('eth-query')
const KeyStore = require('eth-lightwallet').keystore
const clone = require('clone')
const extend = require('xtend')
const createId = require('web3-provider-engine/util/random-id')
const ethBinToOps = require('eth-bin-to-ops')
const autoFaucet = require('./auto-faucet')
const messageManager = require('./message-manager')
const DEFAULT_RPC = 'https://testrpc.metamask.io/'
const IdManagement = require('./id-management')

module.exports = IdentityStore

inherits(IdentityStore, EventEmitter)
function IdentityStore (opts = {}) {
  EventEmitter.call(this)

  // we just use the ethStore to auto-add accounts
  this._ethStore = opts.ethStore
  this.configManager = opts.configManager
  // lightwallet key store
  this._keyStore = null
  // lightwallet wrapper
  this._idmgmt = null

  this.hdPathString = "m/44'/60'/0'/0"

  this._currentState = {
    selectedAddress: null,
    identities: {},
  }

  // not part of serilized metamask state - only kept in memory
  this._unconfTxCbs = {}
  this._unconfMsgCbs = {}
}

//
// public
//

IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
  delete this._keyStore
  var serializedKeystore = this.configManager.getWallet()

  if (serializedKeystore) {
    this.configManager.setData({})
  }

  this.purgeCache()
  this._createVault(password, null, entropy, (err) => {
    if (err) return cb(err)

    this._autoFaucet()

    this.configManager.setShowSeedWords(true)
    var seedWords = this._idmgmt.getSeed()

    this._loadIdentities()

    cb(null, seedWords)
  })
}

IdentityStore.prototype.recoverSeed = function (cb) {
  this.configManager.setShowSeedWords(true)
  if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.'))
  var seedWords = this._idmgmt.getSeed()
  cb(null, seedWords)
}

IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) {
  this.purgeCache()

  this._createVault(password, seed, null, (err) => {
    if (err) return cb(err)

    this._loadIdentities()
    cb(null, this.getState())
  })
}

IdentityStore.prototype.setStore = function (store) {
  this._ethStore = store
}

IdentityStore.prototype.clearSeedWordCache = function (cb) {
  const configManager = this.configManager
  configManager.setShowSeedWords(false)
  cb(null, configManager.getSelectedAccount())
}

IdentityStore.prototype.getState = function () {
  const configManager = this.configManager
  var seedWords = this.getSeedIfUnlocked()
  return clone(extend(this._currentState, {
    isInitialized: !!configManager.getWallet() && !seedWords,
    isUnlocked: this._isUnlocked(),
    seedWords: seedWords,
    isConfirmed: configManager.getConfirmed(),
    isEthConfirmed: configManager.getShouldntShowWarning(),
    unconfTxs: configManager.unconfirmedTxs(),
    transactions: configManager.getTxList(),
    unconfMsgs: messageManager.unconfirmedMsgs(),
    messages: messageManager.getMsgList(),
    selectedAddress: configManager.getSelectedAccount(),
    shapeShiftTxList: configManager.getShapeShiftTxList(),
    currentFiat: configManager.getCurrentFiat(),
    conversionRate: configManager.getConversionRate(),
    conversionDate: configManager.getConversionDate(),
    gasMultiplier: configManager.getGasMultiplier(),

  }))
}

IdentityStore.prototype.getSeedIfUnlocked = function () {
  const configManager = this.configManager
  var showSeed = configManager.getShouldShowSeedWords()
  var idmgmt = this._idmgmt
  var shouldShow = showSeed && !!idmgmt
  var seedWords = shouldShow ? idmgmt.getSeed() : null
  return seedWords
}

IdentityStore.prototype.getSelectedAddress = function () {
  const configManager = this.configManager
  return configManager.getSelectedAccount()
}

IdentityStore.prototype.setSelectedAddressSync = function (address) {
  const configManager = this.configManager
  if (!address) {
    var addresses = this._getAddresses()
    address = addresses[0]
  }

  configManager.setSelectedAccount(address)
  return address
}

IdentityStore.prototype.setSelectedAddress = function (address, cb) {
  const resultAddress = this.setSelectedAddressSync(address)
  if (cb) return cb(null, resultAddress)
}

IdentityStore.prototype.revealAccount = function (cb) {
  const derivedKey = this._idmgmt.derivedKey
  const keyStore = this._keyStore
  const configManager = this.configManager

  keyStore.setDefaultHdDerivationPath(this.hdPathString)
  keyStore.generateNewAddress(derivedKey, 1)
  const addresses = keyStore.getAddresses()
  const address = addresses[ addresses.length - 1 ]

  this._ethStore.addAccount(ethUtil.addHexPrefix(address))

  configManager.setWallet(keyStore.serialize())

  this._loadIdentities()
  this._didUpdate()
  cb(null)
}

IdentityStore.prototype.getNetwork = function (err) {
  if (err) {
    this._currentState.network = 'loading'
    this._didUpdate()
  }

  this.web3.version.getNetwork((err, network) => {
    if (err) {
      this._currentState.network = 'loading'
      return this._didUpdate()
    }
    if (global.METAMASK_DEBUG) {
      console.log('web3.getNetwork returned ' + network)
    }
    this._currentState.network = network
    this._didUpdate()
  })
}

IdentityStore.prototype.setLocked = function (cb) {
  delete this._keyStore
  delete this._idmgmt
  cb()
}

IdentityStore.prototype.submitPassword = function (password, cb) {
  const configManager = this.configManager
  this.tryPassword(password, (err) => {
    if (err) return cb(err)
    // load identities before returning...
    this._loadIdentities()
    cb(null, configManager.getSelectedAccount())
  })
}

IdentityStore.prototype.exportAccount = function (address, cb) {
  var privateKey = this._idmgmt.exportPrivateKey(address)
  cb(null, privateKey)
}

//
// Transactions
//

// comes from dapp via zero-client hooked-wallet provider
IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDoneCb, cb) {
  const configManager = this.configManager

  var self = this
  // create txData obj with parameters and meta data
  var time = (new Date()).getTime()
  var txId = createId()
  txParams.metamaskId = txId
  txParams.metamaskNetworkId = self._currentState.network
  var txData = {
    id: txId,
    txParams: txParams,
    time: time,
    status: 'unconfirmed',
    gasMultiplier: configManager.getGasMultiplier() || 1,
  }

  console.log('addUnconfirmedTransaction:', txData)

  // keep the onTxDoneCb around for after approval/denial (requires user interaction)
  // This onTxDoneCb fires completion to the Dapp's write operation.
  self._unconfTxCbs[txId] = onTxDoneCb

  var provider = self._ethStore._query.currentProvider
  var query = new EthQuery(provider)

  // calculate metadata for tx
  async.parallel([
    analyzeForDelegateCall,
    estimateGas,
  ], didComplete)

  // perform static analyis on the target contract code
  function analyzeForDelegateCall(cb){
    if (txParams.to) {
      query.getCode(txParams.to, function (err, result) {
        if (err) return cb(err)
        var code = ethUtil.toBuffer(result)
        if (code !== '0x') {
          var ops = ethBinToOps(code)
          var containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL')
          txData.containsDelegateCall = containsDelegateCall
          cb()
        } else {
          cb()
        }
      })
    } else {
      cb()
    }
  }

  function estimateGas(cb){
    query.estimateGas(txParams, function(err, result){
      if (err) return cb(err)
      txData.estimatedGas = self.addGasBuffer(result)
      cb()
    })
  }

  function didComplete (err) {
    if (err) return cb(err)
    configManager.addTx(txData)
    // signal update
    self._didUpdate()
    // signal completion of add tx
    cb(null, txData)
  }
}

IdentityStore.prototype.addGasBuffer = function (gasHex) {
  var gas = new BN(gasHex, 16)
  var buffer = new BN('100000', 10)
  var result = gas.add(buffer)
  return ethUtil.addHexPrefix(result.toString(16))
}

// comes from metamask ui
IdentityStore.prototype.approveTransaction = function (txId, cb) {
  const configManager = this.configManager
  var approvalCb = this._unconfTxCbs[txId] || noop

  // accept tx
  cb()
  approvalCb(null, true)
  // clean up
  configManager.confirmTx(txId)
  delete this._unconfTxCbs[txId]
  this._didUpdate()
}

// comes from metamask ui
IdentityStore.prototype.cancelTransaction = function (txId) {
  const configManager = this.configManager
  var approvalCb = this._unconfTxCbs[txId] || noop

  // reject tx
  approvalCb(null, false)
  // clean up
  configManager.rejectTx(txId)
  delete this._unconfTxCbs[txId]
  this._didUpdate()
}

// performs the actual signing, no autofill of params
IdentityStore.prototype.signTransaction = function (txParams, cb) {
  try {
    console.log('signing tx...', txParams)
    var rawTx = this._idmgmt.signTx(txParams)
    cb(null, rawTx)
  } catch (err) {
    cb(err)
  }
}

//
// 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',
  }
  messageManager.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 approvalCb = this._unconfMsgCbs[msgId] || noop

  // accept msg
  cb()
  approvalCb(null, true)
  // clean up
  messageManager.confirmMsg(msgId)
  delete this._unconfMsgCbs[msgId]
  this._didUpdate()
}

// comes from metamask ui
IdentityStore.prototype.cancelMessage = function (msgId) {
  var approvalCb = this._unconfMsgCbs[msgId] || noop

  // reject tx
  approvalCb(null, false)
  // clean up
  messageManager.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)
    if ('metamaskId' in msgParams) {
      var id = msgParams.metamaskId
      delete msgParams.metamaskId

      this.approveMessage(id, cb)
    } else {
      cb(null, rawMsg)
    }
  } catch (err) {
    cb(err)
  }
}

//
// private
//

IdentityStore.prototype._didUpdate = function () {
  this.emit('update', this.getState())
}

IdentityStore.prototype._isUnlocked = function () {
  var result = Boolean(this._keyStore) && Boolean(this._idmgmt)
  return result
}

// load identities from keyStoreet
IdentityStore.prototype._loadIdentities = function () {
  const configManager = this.configManager
  if (!this._isUnlocked()) throw new Error('not unlocked')

  var addresses = this._getAddresses()
  addresses.forEach((address, i) => {
    // // add to ethStore
    this._ethStore.addAccount(ethUtil.addHexPrefix(address))
    // add to identities
    const defaultLabel = 'Wallet ' + (i + 1)
    const nickname = configManager.nicknameForWallet(address)
    var identity = {
      name: nickname || defaultLabel,
      address: address,
      mayBeFauceting: this._mayBeFauceting(i),
    }
    this._currentState.identities[address] = identity
  })
  this._didUpdate()
}

IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
  const configManager = this.configManager
  configManager.setNicknameForWallet(account, label)
  this._loadIdentities()
  cb(null, label)
}

// mayBeFauceting
// If on testnet, index 0 may be fauceting.
// The UI will have to check the balance to know.
// If there is no balance and it mayBeFauceting,
// then it is in fact fauceting.
IdentityStore.prototype._mayBeFauceting = function (i) {
  const configManager = this.configManager
  var config = configManager.getProvider()
  if (i === 0 &&
      config.type === 'rpc' &&
      config.rpcTarget === DEFAULT_RPC) {
    return true
  }
  return false
}

//
// keyStore managment - unlocking + deserialization
//

IdentityStore.prototype.tryPassword = function (password, cb) {
  var serializedKeystore = this.configManager.getWallet()
  var keyStore = KeyStore.deserialize(serializedKeystore)

  keyStore.keyFromPassword(password, (err, pwDerivedKey) => {
    if (err) return cb(err)

    const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey)
    if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))

    this._keyStore = keyStore
    this._createIdMgmt(pwDerivedKey)
    cb()
  })
}

IdentityStore.prototype._createVault = function (password, seedPhrase, entropy, cb) {
  const opts = {
    password,
    hdPathString: this.hdPathString,
  }

  if (seedPhrase) {
    opts.seedPhrase = seedPhrase
  }

  KeyStore.createVault(opts, (err, keyStore) => {
    if (err) return cb(err)

    this._keyStore = keyStore

    keyStore.keyFromPassword(password, (err, derivedKey) => {
      if (err) return cb(err)

      this.purgeCache()

      keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})

      this._createFirstWallet(derivedKey)
      this._createIdMgmt(derivedKey)
      this.setSelectedAddressSync()

      cb()
    })
  })
}

IdentityStore.prototype._createIdMgmt = function (derivedKey) {
  this._idmgmt = new IdManagement({
    keyStore: this._keyStore,
    derivedKey: derivedKey,
    configManager: this.configManager,
  })
}

IdentityStore.prototype.purgeCache = function () {
  this._currentState.identities = {}
  let accounts
  try {
    accounts = Object.keys(this._ethStore._currentState.accounts)
  } catch (e) {
    accounts = []
  }
  accounts.forEach((address) => {
    this._ethStore.removeAccount(address)
  })
}

IdentityStore.prototype._createFirstWallet = function (derivedKey) {
  const keyStore = this._keyStore
  keyStore.setDefaultHdDerivationPath(this.hdPathString)
  keyStore.generateNewAddress(derivedKey, 1)
  this.configManager.setWallet(keyStore.serialize())
  var addresses = keyStore.getAddresses()
  this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0]))
}

// get addresses and normalize address hexString
IdentityStore.prototype._getAddresses = function () {
  return this._keyStore.getAddresses(this.hdPathString).map((address) => {
    return ethUtil.addHexPrefix(address)
  })
}

IdentityStore.prototype._autoFaucet = function () {
  var addresses = this._getAddresses()
  autoFaucet(addresses[0])
}

// util

function noop () {}