nifty-wallet/app/scripts/lib/idStore.js

302 lines
8.5 KiB
JavaScript
Raw Normal View History

2016-02-10 17:44:46 -08:00
const EventEmitter = require('events').EventEmitter
const inherits = require('util').inherits
const Transaction = require('ethereumjs-tx')
2016-03-02 22:58:23 -08:00
const LightwalletKeyStore = require('eth-lightwallet').keystore
const LightwalletSigner = require('eth-lightwallet').signing
2016-02-10 17:44:46 -08:00
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) {
EventEmitter.call(this)
2016-02-10 17:44:46 -08:00
// we just use the ethStore to auto-add accounts
this._ethStore = ethStore
2016-03-02 22:58:23 -08:00
// lightwallet key store
this._keyStore = null
2016-03-02 22:58:23 -08:00
// lightwallet wrapper
this._idmgmt = null
this.hdPathString = "m/44'/60'/0'/0"
2016-02-10 17:44:46 -08:00
this._currentState = {
2016-02-10 17:44:46 -08:00
selectedAddress: null,
identities: {},
unconfTxs: {},
}
// not part of serilized metamask state - only kept in memory
this._unconfTxCbs = {}
2016-02-10 17:44:46 -08:00
}
//
// public
//
IdentityStore.prototype.createNewVault = function(password, entropy, cb){
delete this._keyStore
2016-02-17 00:55:57 -08:00
delete window.localStorage['lightwallet']
this._createIdmgmt(password, null, entropy, (err) => {
2016-03-02 22:58:23 -08:00
if (err) return cb(err)
var seedWords = this._idmgmt.getSeed()
this._cacheSeedWordsUntilConfirmed(seedWords)
this._loadIdentities()
this._didUpdate()
2016-03-02 22:58:23 -08:00
cb(null, seedWords)
})
2016-02-17 00:55:57 -08:00
}
2016-03-15 13:39:12 -07:00
IdentityStore.prototype.recoverFromSeed = function(password, seed, cb){
this._createIdmgmt(password, seed, null, (err) => {
2016-03-15 13:39:12 -07:00
if (err) return cb(err)
this._loadIdentities()
this._didUpdate()
2016-03-15 13:39:12 -07:00
cb()
})
}
2016-02-17 00:55:57 -08:00
2016-02-10 17:44:46 -08:00
IdentityStore.prototype.setStore = function(store){
this._ethStore = store
2016-02-10 17:44:46 -08:00
}
IdentityStore.prototype.clearSeedWordCache = function(cb) {
delete window.localStorage['seedWords']
cb()
}
2016-02-17 00:55:57 -08:00
2016-02-10 17:44:46 -08:00
IdentityStore.prototype.getState = function(){
const cachedSeeds = window.localStorage['seedWords']
return clone(extend(this._currentState, {
isInitialized: !!window.localStorage['lightwallet'] && !cachedSeeds,
isUnlocked: this._isUnlocked(),
seedWords: cachedSeeds,
2016-02-10 17:44:46 -08:00
}))
}
IdentityStore.prototype.getSelectedAddress = function(){
return this._currentState.selectedAddress
2016-02-10 17:44:46 -08:00
}
IdentityStore.prototype.setSelectedAddress = function(address){
this._currentState.selectedAddress = address
this._didUpdate()
2016-02-10 17:44:46 -08:00
}
IdentityStore.prototype.setLocked = function(cb){
delete this._keyStore
delete this._idmgmt
cb()
2016-02-10 17:44:46 -08:00
}
IdentityStore.prototype.submitPassword = function(password, cb){
this._tryPassword(password, (err) => {
2016-02-10 17:44:46 -08:00
if (err) return cb(err)
// load identities before returning...
this._loadIdentities()
2016-02-10 17:44:46 -08:00
cb()
})
}
2016-03-02 22:58:23 -08:00
// comes from dapp via zero-client hooked-wallet provider
IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){
2016-02-10 17:44:46 -08:00
2016-03-02 22:58:23 -08:00
// 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',
}
this._currentState.unconfTxs[txId] = txData
console.log('addUnconfirmedTransaction:', txData)
2016-02-10 17:44:46 -08:00
2016-03-02 22:58:23 -08:00
// keep the cb around for after approval (requires user interaction)
this._unconfTxCbs[txId] = cb
2016-02-10 17:44:46 -08:00
2016-03-02 22:58:23 -08:00
// signal update
this._didUpdate()
return txId
2016-02-10 17:44:46 -08:00
}
2016-03-02 22:58:23 -08:00
// comes from metamask ui
IdentityStore.prototype.approveTransaction = function(txId, cb){
var txData = this._currentState.unconfTxs[txId]
2016-03-02 22:58:23 -08:00
var txParams = txData.txParams
var approvalCb = this._unconfTxCbs[txId] || noop
2016-03-02 22:58:23 -08:00
// accept tx
2016-02-12 17:57:10 -08:00
cb()
2016-03-02 22:58:23 -08:00
approvalCb(null, true)
// clean up
delete this._currentState.unconfTxs[txId]
delete this._unconfTxCbs[txId]
this._didUpdate()
2016-02-12 17:57:10 -08:00
}
2016-03-02 22:58:23 -08:00
// comes from metamask ui
2016-02-12 12:55:20 -08:00
IdentityStore.prototype.cancelTransaction = function(txId){
var txData = this._currentState.unconfTxs[txId]
var approvalCb = this._unconfTxCbs[txId] || noop
2016-03-02 22:58:23 -08:00
// reject tx
approvalCb(null, false)
// clean up
delete this._currentState.unconfTxs[txId]
delete this._unconfTxCbs[txId]
this._didUpdate()
2016-02-12 12:55:20 -08:00
}
2016-03-02 22:58:23 -08:00
// performs the actual signing, no autofill of params
IdentityStore.prototype.signTransaction = function(txParams, cb){
2016-02-10 17:44:46 -08:00
try {
2016-03-02 22:58:23 -08:00
console.log('signing tx...', txParams)
var rawTx = this._idmgmt.signTx(txParams)
2016-03-02 22:58:23 -08:00
cb(null, rawTx)
2016-02-10 17:44:46 -08:00
} catch (err) {
cb(err)
}
}
2016-03-02 22:58:23 -08:00
//
// private
//
2016-02-10 17:44:46 -08:00
IdentityStore.prototype._didUpdate = function(){
this.emit('update', this.getState())
2016-02-10 17:44:46 -08:00
}
IdentityStore.prototype._isUnlocked = function(){
var result = Boolean(this._keyStore) && Boolean(this._idmgmt)
2016-02-10 17:44:46 -08:00
return result
}
IdentityStore.prototype._cacheSeedWordsUntilConfirmed = function(seedWords) {
window.localStorage['seedWords'] = seedWords
}
2016-02-17 00:55:57 -08:00
// load identities from keyStoreet
2016-02-10 17:44:46 -08:00
IdentityStore.prototype._loadIdentities = function(){
if (!this._isUnlocked()) throw new Error('not unlocked')
2016-02-10 17:44:46 -08:00
// get addresses and normalize address hexString
var addresses = this._keyStore.getAddresses(this.hdPathString).map((address) => { return '0x'+address })
addresses.forEach((address) => {
2016-02-17 00:55:57 -08:00
// // add to ethStore
this._ethStore.addAccount(address)
2016-02-10 17:44:46 -08:00
// add to identities
var identity = {
name: 'Wally',
img: 'QmW6hcwYzXrNkuHrpvo58YeZvbZxUddv69ATSHY3BHpPdd',
address: address,
}
this._currentState.identities[address] = identity
2016-02-10 17:44:46 -08:00
})
this._didUpdate()
2016-02-10 17:44:46 -08:00
}
//
// keyStore managment - unlocking + deserialization
//
IdentityStore.prototype._tryPassword = function(password, cb){
this._createIdmgmt(password, null, null, cb)
2016-02-10 17:44:46 -08:00
}
IdentityStore.prototype._createIdmgmt = function(password, seed, entropy, cb){
2016-02-10 17:44:46 -08:00
var keyStore = null
LightwalletKeyStore.deriveKeyFromPassword(password, (err, derivedKey) => {
2016-03-02 22:58:23 -08:00
if (err) return cb(err)
var serializedKeystore = window.localStorage['lightwallet']
2016-03-15 13:39:12 -07:00
if (seed) {
keyStore = this._restoreFromSeed(password, seed, derivedKey)
2016-03-15 13:39:12 -07:00
// returning user, recovering from localStorage
} else if (serializedKeystore) {
keyStore = this._loadFromLocalStorage(serializedKeystore, derivedKey, cb)
var isCorrect = keyStore.isDerivedKeyCorrect(derivedKey)
2016-03-02 22:58:23 -08:00
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))
// first time here
2016-03-02 22:58:23 -08:00
} else {
keyStore = this._createFirstWallet(entropy, derivedKey)
2016-03-02 22:58:23 -08:00
}
this._keyStore = keyStore
this._idmgmt = new IdManagement({
keyStore: keyStore,
derivedKey: derivedKey,
hdPathSTring: this.hdPathString,
})
2016-03-02 22:58:23 -08:00
cb()
})
2016-02-10 17:44:46 -08:00
}
IdentityStore.prototype._restoreFromSeed = function(password, seed, derivedKey) {
var keyStore = new LightwalletKeyStore(seed, derivedKey, this.hdPathString)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'});
keyStore.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 3)
window.localStorage['lightwallet'] = keyStore.serialize()
console.log('restored from seed. saved to keystore localStorage')
return keyStore
}
IdentityStore.prototype._loadFromLocalStorage = function(serializedKeystore, derivedKey) {
return LightwalletKeyStore.deserialize(serializedKeystore)
}
IdentityStore.prototype._createFirstWallet = function(entropy, derivedKey) {
var secretSeed = LightwalletKeyStore.generateRandomSeed(entropy)
var keyStore = new LightwalletKeyStore(secretSeed, derivedKey, this.hdPathString)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'});
keyStore.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 3)
window.localStorage['lightwallet'] = keyStore.serialize()
console.log('saved to keystore localStorage')
return keyStore
}
function IdManagement( opts = { keyStore: null, derivedKey: null, hdPathString: null } ) {
this.keyStore = opts.keyStore
this.derivedKey = opts.derivedKey
this.hdPathString = opts.hdPathString
this.getAddresses = function(){
return keyStore.getAddresses(this.hdPathString).map(function(address){ return '0x'+address })
}
this.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(this.keyStore, this.derivedKey, rawTx, txParams.from)
}
this.getSeed = function(){
return this.keyStore.getSeed(this.derivedKey)
}
}
2016-02-10 17:44:46 -08:00
// util
function noop(){}