Merge branch 'master' into library

feature/default_network_editable
kumavis 8 years ago committed by GitHub
commit 2aa8b91a65
  1. 6
      CHANGELOG.md
  2. 2
      app/manifest.json
  3. 8
      app/scripts/lib/id-management.js
  4. 135
      app/scripts/lib/idStore.js
  5. 2
      development/states.js
  6. 6
      development/states/account-detail.json
  7. 8
      development/states/accounts.json
  8. 72
      test/unit/idStore-test.js
  9. 13
      ui/app/accounts/account-list-item.js
  10. 4
      ui/app/accounts/index.js
  11. 1
      ui/app/loading.js

@ -2,6 +2,12 @@
## Current Master ## Current Master
## 2.11.1 2016-09-12
- Fix bug that prevented caches from being cleared in Opera.
## 2.11.0 2016-09-12
- Fix bug where pending transactions from Test net (or other networks) show up In Main net. - Fix bug where pending transactions from Test net (or other networks) show up In Main net.
- Add fiat conversion values to more views. - Add fiat conversion values to more views.
- On fresh install, open a new tab with the MetaMask Introduction video. Does not open on update. - On fresh install, open a new tab with the MetaMask Introduction video. Does not open on update.

@ -1,7 +1,7 @@
{ {
"name": "MetaMask", "name": "MetaMask",
"short_name": "Metamask", "short_name": "Metamask",
"version": "2.10.2", "version": "2.11.1",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "Ethereum Browser Extension", "description": "Ethereum Browser Extension",

@ -1,3 +1,11 @@
/* ID Management
*
* This module exists to hold the decrypted credentials for the current session.
* It therefore exposes sign methods, because it is able to perform these
* with noa dditional authentication, because its very instantiation
* means the vault is unlocked.
*/
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx') const Transaction = require('ethereumjs-tx')

@ -3,7 +3,7 @@ const inherits = require('util').inherits
const async = require('async') const async = require('async')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const EthQuery = require('eth-query') const EthQuery = require('eth-query')
const LightwalletKeyStore = require('eth-lightwallet').keystore const KeyStore = require('eth-lightwallet').keystore
const clone = require('clone') const clone = require('clone')
const extend = require('xtend') const extend = require('xtend')
const createId = require('web3-provider-engine/util/random-id') const createId = require('web3-provider-engine/util/random-id')
@ -50,15 +50,16 @@ IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
if (serializedKeystore) { if (serializedKeystore) {
this.configManager.setData({}) this.configManager.setData({})
} }
this._createIdmgmt(password, null, entropy, (err) => {
this.purgeCache()
this._createVault(password, null, entropy, (err) => {
if (err) return cb(err) if (err) return cb(err)
this._loadIdentities()
this._didUpdate()
this._autoFaucet() this._autoFaucet()
this.configManager.setShowSeedWords(true) this.configManager.setShowSeedWords(true)
var seedWords = this._idmgmt.getSeed() var seedWords = this._idmgmt.getSeed()
cb(null, seedWords) cb(null, seedWords)
}) })
} }
@ -71,11 +72,12 @@ IdentityStore.prototype.recoverSeed = function (cb) {
} }
IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) { IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) {
this._createIdmgmt(password, seed, null, (err) => { this.purgeCache()
this._createVault(password, seed, null, (err) => {
if (err) return cb(err) if (err) return cb(err)
this._loadIdentities() this._loadIdentities()
this._didUpdate()
cb(null, this.getState()) cb(null, this.getState())
}) })
} }
@ -125,7 +127,7 @@ IdentityStore.prototype.getSelectedAddress = function () {
return configManager.getSelectedAccount() return configManager.getSelectedAccount()
} }
IdentityStore.prototype.setSelectedAddress = function (address, cb) { IdentityStore.prototype.setSelectedAddressSync = function (address) {
const configManager = this.configManager const configManager = this.configManager
if (!address) { if (!address) {
var addresses = this._getAddresses() var addresses = this._getAddresses()
@ -133,7 +135,12 @@ IdentityStore.prototype.setSelectedAddress = function (address, cb) {
} }
configManager.setSelectedAccount(address) configManager.setSelectedAccount(address)
if (cb) return cb(null, 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) { IdentityStore.prototype.revealAccount = function (cb) {
@ -143,6 +150,11 @@ IdentityStore.prototype.revealAccount = function (cb) {
keyStore.setDefaultHdDerivationPath(this.hdPathString) keyStore.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 1) keyStore.generateNewAddress(derivedKey, 1)
const addresses = keyStore.getAddresses()
const address = addresses[ addresses.length - 1 ]
this._ethStore.addAccount(ethUtil.addHexPrefix(address))
configManager.setWallet(keyStore.serialize()) configManager.setWallet(keyStore.serialize())
this._loadIdentities() this._loadIdentities()
@ -393,7 +405,7 @@ IdentityStore.prototype._loadIdentities = function () {
var addresses = this._getAddresses() var addresses = this._getAddresses()
addresses.forEach((address, i) => { addresses.forEach((address, i) => {
// // add to ethStore // // add to ethStore
this._ethStore.addAccount(address) this._ethStore.addAccount(ethUtil.addHexPrefix(address))
// add to identities // add to identities
const defaultLabel = 'Wallet ' + (i + 1) const defaultLabel = 'Wallet ' + (i + 1)
const nickname = configManager.nicknameForWallet(address) const nickname = configManager.nicknameForWallet(address)
@ -412,7 +424,6 @@ IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
configManager.setNicknameForWallet(account, label) configManager.setNicknameForWallet(account, label)
this._loadIdentities() this._loadIdentities()
cb(null, label) cb(null, label)
this._didUpdate()
} }
// mayBeFauceting // mayBeFauceting
@ -436,77 +447,87 @@ IdentityStore.prototype._mayBeFauceting = function (i) {
// //
IdentityStore.prototype.tryPassword = function (password, cb) { IdentityStore.prototype.tryPassword = function (password, cb) {
this._createIdmgmt(password, null, null, cb) var serializedKeystore = this.configManager.getWallet()
} var keyStore = KeyStore.deserialize(serializedKeystore)
IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
const configManager = this.configManager
var keyStore = null keyStore.keyFromPassword(password, (err, pwDerivedKey) => {
LightwalletKeyStore.deriveKeyFromPassword(password, (err, derivedKey) => {
if (err) return cb(err) if (err) return cb(err)
var serializedKeystore = configManager.getWallet()
if (seed) { const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey)
try { if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))
keyStore = this._restoreFromSeed(password, seed, derivedKey)
} catch (e) { this._keyStore = keyStore
return cb(e) this._createIdMgmt(pwDerivedKey)
cb()
})
} }
// returning user, recovering from storage IdentityStore.prototype._createVault = function (password, seedPhrase, entropy, cb) {
} else if (serializedKeystore) { const opts = {
keyStore = LightwalletKeyStore.deserialize(serializedKeystore) password,
var isCorrect = keyStore.isDerivedKeyCorrect(derivedKey) hdPathString: this.hdPathString,
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect')) }
// first time here if (seedPhrase) {
} else { opts.seedPhrase = seedPhrase
keyStore = this._createFirstWallet(entropy, derivedKey)
} }
KeyStore.createVault(opts, (err, keyStore) => {
if (err) return cb(err)
this._keyStore = keyStore this._keyStore = keyStore
this._idmgmt = new IdManagement({
keyStore: keyStore, keyStore.keyFromPassword(password, (err, derivedKey) => {
derivedKey: derivedKey, if (err) return cb(err)
hdPathSTring: this.hdPathString,
configManager: this.configManager, this.purgeCache()
})
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
this._createFirstWallet(derivedKey)
this._createIdMgmt(derivedKey)
this.setSelectedAddressSync()
cb() cb()
}) })
})
} }
IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey) { IdentityStore.prototype._createIdMgmt = function (derivedKey) {
const configManager = this.configManager this._idmgmt = new IdManagement({
var keyStore = new LightwalletKeyStore(seed, derivedKey, this.hdPathString) keyStore: this._keyStore,
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'}) derivedKey: derivedKey,
keyStore.setDefaultHdDerivationPath(this.hdPathString) configManager: this.configManager,
})
}
keyStore.generateNewAddress(derivedKey, 1) IdentityStore.prototype.purgeCache = function () {
configManager.setWallet(keyStore.serialize()) this._currentState.identities = {}
if (global.METAMASK_DEBUG) { let accounts
console.log('restored from seed. saved to keystore') try {
accounts = Object.keys(this._ethStore._currentState.accounts)
} catch (e) {
accounts = []
} }
return keyStore accounts.forEach((address) => {
this._ethStore.removeAccount(address)
})
} }
IdentityStore.prototype._createFirstWallet = function (entropy, derivedKey) { IdentityStore.prototype._createFirstWallet = function (derivedKey) {
const configManager = this.configManager const keyStore = this._keyStore
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.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 1) keyStore.generateNewAddress(derivedKey, 1)
configManager.setWallet(keyStore.serialize()) this.configManager.setWallet(keyStore.serialize())
console.log('saved to keystore') var addresses = keyStore.getAddresses()
return keyStore this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0]))
} }
// get addresses and normalize address hexString // get addresses and normalize address hexString
IdentityStore.prototype._getAddresses = function () { IdentityStore.prototype._getAddresses = function () {
return this._keyStore.getAddresses(this.hdPathString).map((address) => { return '0x' + address }) return this._keyStore.getAddresses(this.hdPathString).map((address) => {
return ethUtil.addHexPrefix(address)
})
} }
IdentityStore.prototype._autoFaucet = function () { IdentityStore.prototype._autoFaucet = function () {

File diff suppressed because one or more lines are too long

@ -33,20 +33,20 @@
"accounts": { "accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"code": "0x", "code": "0x",
"balance": "0x0", "balance": "0x100000000000",
"nonce": "0x0", "nonce": "0x0",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
}, },
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": { "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"code": "0x", "code": "0x",
"nonce": "0x0", "nonce": "0x0",
"balance": "0x0", "balance": "0x100000000000",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b" "address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
}, },
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": { "0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"code": "0x", "code": "0x",
"nonce": "0x0", "nonce": "0x0",
"balance": "0x0", "balance": "0x100000000000",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823" "address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
}, },
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": { "0x704107d04affddd9b66ab9de3dd7b095852e9b69": {

@ -29,25 +29,25 @@
"unconfTxs": {}, "unconfTxs": {},
"accounts": { "accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"balance": "0x0", "balance": "0x100000000000000",
"nonce": "0x0", "nonce": "0x0",
"code": "0x", "code": "0x",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
}, },
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": { "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"balance": "0x0", "balance": "0x100000000000000",
"nonce": "0x0", "nonce": "0x0",
"code": "0x", "code": "0x",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b" "address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
}, },
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": { "0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"balance": "0x0", "balance": "0x100000000000",
"nonce": "0x0", "nonce": "0x0",
"code": "0x", "code": "0x",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823" "address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
}, },
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": { "0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"balance": "0x0", "balance": "0x100000000000",
"code": "0x", "code": "0x",
"nonce": "0x0", "nonce": "0x0",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69" "address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"

@ -1,6 +1,8 @@
var assert = require('assert') var assert = require('assert')
var IdentityStore = require('../../app/scripts/lib/idStore') var IdentityStore = require('../../app/scripts/lib/idStore')
var configManagerGen = require('../lib/mock-config-manager') var configManagerGen = require('../lib/mock-config-manager')
const ethUtil = require('ethereumjs-util')
const async = require('async')
describe('IdentityStore', function() { describe('IdentityStore', function() {
@ -18,11 +20,12 @@ describe('IdentityStore', function() {
idStore = new IdentityStore({ idStore = new IdentityStore({
configManager: configManagerGen(), configManager: configManagerGen(),
ethStore: { ethStore: {
addAccount(acct) { accounts.push(acct) }, addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
}, },
}) })
idStore.createNewVault(password, entropy, (err, seeds) => { idStore.createNewVault(password, entropy, (err, seeds) => {
assert.ifError(err, 'createNewVault threw error')
seedWords = seeds seedWords = seeds
originalKeystore = idStore._idmgmt.keyStore originalKeystore = idStore._idmgmt.keyStore
done() done()
@ -38,7 +41,7 @@ describe('IdentityStore', function() {
idStore = new IdentityStore({ idStore = new IdentityStore({
configManager: configManagerGen(), configManager: configManagerGen(),
ethStore: { ethStore: {
addAccount(acct) { newAccounts.push(acct) }, addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) },
}, },
}) })
}) })
@ -57,31 +60,80 @@ describe('IdentityStore', function() {
}) })
describe('#recoverFromSeed BIP44 compliance', function() { describe('#recoverFromSeed BIP44 compliance', function() {
let seedWords = 'picnic injury awful upper eagle junk alert toss flower renew silly vague' const salt = 'lightwalletSalt'
let firstAccount = '0x5d8de92c205279c10e5669f797b853ccef4f739a'
let password = 'secret!' let password = 'secret!'
let accounts = [] let accounts = {}
let idStore let idStore
var assertions = [
{
seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague',
account: '0x5d8de92c205279c10e5669f797b853ccef4f739a',
},
{
seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release',
account: '0xe15d894becb0354c501ae69429b05143679f39e0',
},
{
seed: 'phone coyote caught pattern found table wedding list tumble broccoli chief swing',
account: '0xb0e868f24bc7fec2bce2efc2b1c344d7569cd9d2',
},
{
seed: 'recycle tag bird palace blue village anxiety census cook soldier example music',
account: '0xab34a45920afe4af212b96ec51232aaa6a33f663',
},
{
seed: 'half glimpse tape cute harvest sweet bike voyage actual floor poet lazy',
account: '0x28e9044597b625ac4beda7250011670223de43b2',
},
{
seed: 'flavor tiger carpet motor angry hungry document inquiry large critic usage liar',
account: '0xb571be96558940c4e9292e1999461aa7499fb6cd',
},
]
before(function() { before(function() {
window.localStorage = {} // Hacking localStorage support into JSDom window.localStorage = {} // Hacking localStorage support into JSDom
idStore = new IdentityStore({ idStore = new IdentityStore({
configManager: configManagerGen(), configManager: configManagerGen(),
ethStore: { ethStore: {
addAccount(acct) { accounts.push(acct) }, addAccount(acct) { accounts[acct] = acct},
del(acct) { delete accounts[acct] },
}, },
}) })
}) })
it('should return the expected first account', function (done) { it('should enforce seed compliance with TestRPC', function (done) {
this.timeout(10000)
const tests = assertions.map((assertion) => {
return function (cb) {
idStore.recoverFromSeed(password, seedWords, (err) => { idStore.recoverFromSeed(password, assertion.seed, (err) => {
assert.ifError(err) assert.ifError(err)
let newKeystore = idStore._idmgmt.keyStore var expected = assertion.account.toLowerCase()
assert.equal(accounts[0], firstAccount) var received = accounts[expected].toLowerCase()
assert.equal(received, expected)
idStore.tryPassword(password, function (err) {
assert.ok(idStore._isUnlocked(), 'should unlock the id store')
idStore.submitPassword(password, function(err, account) {
assert.ifError(err)
assert.equal(account, expected)
assert.equal(Object.keys(idStore._getAddresses()).length, 1, 'only one account on restore')
cb()
})
})
})
}
})
async.series(tests, function(err, results) {
assert.ifError(err)
done() done()
}) })
}) })

@ -7,14 +7,14 @@ const EthBalance = require('../components/eth-balance')
const CopyButton = require('../components/copyButton') const CopyButton = require('../components/copyButton')
const Identicon = require('../components/identicon') const Identicon = require('../components/identicon')
module.exports = NewComponent module.exports = AccountListItem
inherits(NewComponent, Component) inherits(AccountListItem, Component)
function NewComponent () { function AccountListItem () {
Component.call(this) Component.call(this)
} }
NewComponent.prototype.render = function () { AccountListItem.prototype.render = function () {
const identity = this.props.identity const identity = this.props.identity
var isSelected = this.props.selectedAddress === identity.address var isSelected = this.props.selectedAddress === identity.address
var account = this.props.accounts[identity.address] var account = this.props.accounts[identity.address]
@ -23,9 +23,6 @@ NewComponent.prototype.render = function () {
return ( return (
h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, { h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, {
key: `account-panel-${identity.address}`, key: `account-panel-${identity.address}`,
style: {
flex: '1 0 auto',
},
onClick: (event) => this.props.onShowDetail(identity.address, event), onClick: (event) => this.props.onShowDetail(identity.address, event),
}, [ }, [
@ -73,7 +70,7 @@ NewComponent.prototype.render = function () {
) )
} }
NewComponent.prototype.pendingOrNot = function () { AccountListItem.prototype.pendingOrNot = function () {
const pending = this.props.pending const pending = this.props.pending
if (pending.length === 0) return null if (pending.length === 0) return null
return h('.pending-dot', pending.length) return h('.pending-dot', pending.length)

@ -84,7 +84,7 @@ AccountsScreen.prototype.render = function () {
}) })
}), }),
h('hr.horizontal-line', {key: 'horizontal-line1'}), h('hr.horizontal-line'),
h('div.footer.hover-white.pointer', { h('div.footer.hover-white.pointer', {
key: 'reveal-account-bar', key: 'reveal-account-bar',
onClick: () => { onClick: () => {
@ -92,7 +92,6 @@ AccountsScreen.prototype.render = function () {
}, },
style: { style: {
display: 'flex', display: 'flex',
flex: '1 0 auto',
height: '40px', height: '40px',
paddint: '10px', paddint: '10px',
justifyContent: 'center', justifyContent: 'center',
@ -101,6 +100,7 @@ AccountsScreen.prototype.render = function () {
}, [ }, [
h('i.fa.fa-plus.fa-lg', {key: ''}), h('i.fa.fa-plus.fa-lg', {key: ''}),
]), ]),
h('hr.horizontal-line'),
]), ]),
unconfTxList.length ? ( unconfTxList.length ? (

@ -30,6 +30,7 @@ LoadingIndicator.prototype.render = function () {
isLoading ? h('div', { isLoading ? h('div', {
style: { style: {
zIndex: 10,
position: 'absolute', position: 'absolute',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',

Loading…
Cancel
Save