|
|
|
@ -1,7 +1,8 @@ |
|
|
|
|
const EventEmitter = require('events').EventEmitter |
|
|
|
|
const inherits = require('util').inherits |
|
|
|
|
const Transaction = require('ethereumjs-tx') |
|
|
|
|
const KeyStore = require('eth-lightwallet').keystore |
|
|
|
|
const LightwalletKeyStore = require('eth-lightwallet').keystore |
|
|
|
|
const LightwalletSigner = require('eth-lightwallet').signing |
|
|
|
|
const async = require('async') |
|
|
|
|
const clone = require('clone') |
|
|
|
|
const extend = require('xtend') |
|
|
|
@ -18,6 +19,10 @@ function IdentityStore(ethStore) { |
|
|
|
|
|
|
|
|
|
// 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, |
|
|
|
@ -36,11 +41,13 @@ IdentityStore.prototype.createNewVault = function(password, cb){ |
|
|
|
|
const self = this |
|
|
|
|
delete self._keyStore |
|
|
|
|
delete window.localStorage['lightwallet'] |
|
|
|
|
var keyStore = self._getKeyStore(password) |
|
|
|
|
var seedWords = keyStore.getSeed(password) |
|
|
|
|
self._loadIdentities() |
|
|
|
|
self._didUpdate() |
|
|
|
|
cb(null, seedWords) |
|
|
|
|
var keyStore = self._createIdmgmt(password, function(err){ |
|
|
|
|
if (err) return cb(err) |
|
|
|
|
var seedWords = self._idmgmt.getSeed() |
|
|
|
|
self._loadIdentities() |
|
|
|
|
self._didUpdate() |
|
|
|
|
cb(null, seedWords) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -69,141 +76,93 @@ IdentityStore.prototype.setSelectedAddress = function(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 self._idmgmt |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
// 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 |
|
|
|
|
// comes from dapp via zero-client hooked-wallet provider
|
|
|
|
|
IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){ |
|
|
|
|
var self = this |
|
|
|
|
|
|
|
|
|
self._signTransaction(password, txParams, function(err, rawTx, txHash){ |
|
|
|
|
if (err) { |
|
|
|
|
cb(err) |
|
|
|
|
txData.status = 'error' |
|
|
|
|
txData.error = err |
|
|
|
|
self._didUpdate() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
// 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) |
|
|
|
|
|
|
|
|
|
txData.rawTx = rawTx |
|
|
|
|
txData.hash = txHash |
|
|
|
|
txData.status = 'signed' |
|
|
|
|
// keep the cb around for after approval (requires user interaction)
|
|
|
|
|
self._unconfTxCbs[txId] = cb |
|
|
|
|
|
|
|
|
|
// confirm tx signed
|
|
|
|
|
cb() |
|
|
|
|
self._didUpdate() |
|
|
|
|
}) |
|
|
|
|
// signal update
|
|
|
|
|
self._didUpdate() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
IdentityStore.prototype.sendTransaction = function(txId, cb){ |
|
|
|
|
// 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 |
|
|
|
|
|
|
|
|
|
if (!txData || txData.status !== 'signed') { |
|
|
|
|
return cb(new Error('IdentityStore - Transaction not signed:', txId)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var rawTx = txData.rawTx |
|
|
|
|
|
|
|
|
|
// for now just remove it
|
|
|
|
|
delete self._currentState.unconfTxs[txData.id] |
|
|
|
|
|
|
|
|
|
// rpc callback
|
|
|
|
|
var txSigCb = self._unconfTxCbs[txId] || noop |
|
|
|
|
txSigCb(null, rawTx) |
|
|
|
|
|
|
|
|
|
// confirm tx sent
|
|
|
|
|
// 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() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// private
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
// internal - actually signs the tx
|
|
|
|
|
IdentityStore.prototype._signTransaction = function(password, txParams, cb){ |
|
|
|
|
// performs the actual signing, no autofill of params
|
|
|
|
|
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.data, |
|
|
|
|
gasPrice: txParams.gasPrice, |
|
|
|
|
gasLimit: txParams.gas, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
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()) |
|
|
|
|
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()) |
|
|
|
@ -211,8 +170,7 @@ IdentityStore.prototype._didUpdate = function(){ |
|
|
|
|
|
|
|
|
|
IdentityStore.prototype._isUnlocked = function(){ |
|
|
|
|
const self = this |
|
|
|
|
// var result = Boolean(password)
|
|
|
|
|
var result = Boolean(self._keyStore) |
|
|
|
|
var result = Boolean(self._keyStore) && Boolean(self._idmgmt) |
|
|
|
|
return result |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -242,48 +200,45 @@ IdentityStore.prototype._loadIdentities = function(){ |
|
|
|
|
|
|
|
|
|
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() |
|
|
|
|
self._createIdmgmt(password, cb) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
IdentityStore.prototype._getKeyStore = function(password){ |
|
|
|
|
IdentityStore.prototype._createIdmgmt = function(password, cb){ |
|
|
|
|
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, password) |
|
|
|
|
} |
|
|
|
|
self._keyStore = keyStore |
|
|
|
|
return keyStore |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
IdentityStore.prototype._saveKeystore = function(keyStore){ |
|
|
|
|
const self = this |
|
|
|
|
window.localStorage['lightwallet'] = keyStore.serialize() |
|
|
|
|
console.log('saved to localStorage') |
|
|
|
|
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){ |
|
|
|
|
txParams.gasLimit = txParams.gas |
|
|
|
|
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
|
|
|
|
|