Merge pull request #958 from MetaMask/i897-ReplayProtection

Add replay protection
feature/default_network_editable
Kevin Serrano 8 years ago committed by GitHub
commit b13c54682b
  1. 1
      CHANGELOG.md
  2. 36
      README.md
  3. 25
      app/scripts/keyring-controller.js
  4. 9
      app/scripts/keyrings/simple.js
  5. 2
      app/scripts/lib/inpage-provider.js
  6. 17
      app/scripts/metamask-controller.js
  7. 10
      app/scripts/transaction-manager.js
  8. 126
      development/states/accounts-loose.json
  9. 19
      test/integration/lib/idStore-migrator-test.js
  10. 1
      test/integration/mocks/badVault2.json
  11. 7
      test/unit/idStore-migration-test.js
  12. 20
      ui/app/accounts/account-list-item.js
  13. 14
      ui/app/accounts/index.js
  14. 4
      ui/app/components/eth-balance.js
  15. 17
      ui/app/css/lib.css

@ -3,6 +3,7 @@
## Current Master ## Current Master
- Add a check for when a tx is included in a block. - Add a check for when a tx is included in a block.
- Implement replay attack protections allowed by EIP 155.
## 2.14.1 2016-12-20 ## 2.14.1 2016-12-20

@ -117,3 +117,39 @@ To write tests that will be run in the browser using QUnit, add your test files
3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package. 3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package.
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
### Generate Development Visualization
This will generate a video of the repo commit history.
Install preqs:
```
brew install gource
brew install ffmpeg
```
From the repo dir, pipe `gource` into `ffmpeg`:
```
gource \
--seconds-per-day .1 \
--user-scale 1.5 \
--default-user-image "./images/icon-512.png" \
--viewport 1280x720 \
--auto-skip-seconds .1 \
--multi-sampling \
--stop-at-end \
--highlight-users \
--hide mouse,progress \
--file-idle-time 0 \
--max-files 0 \
--background-colour 000000 \
--font-size 18 \
--date-format "%b %d, %Y" \
--highlight-dirs \
--user-friction 0.1 \
--title "MetaMask Development History" \
--output-ppm-stream - \
--output-framerate 30 \
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4
```

@ -86,12 +86,16 @@ module.exports = class KeyringController extends EventEmitter {
const address = configManager.getSelectedAccount() const address = configManager.getSelectedAccount()
const wallet = configManager.getWallet() // old style vault const wallet = configManager.getWallet() // old style vault
const vault = configManager.getVault() // new style vault const vault = configManager.getVault() // new style vault
const keyrings = this.keyrings
return Promise.all(keyrings.map(this.displayForKeyring))
.then((displayKeyrings) => {
return { return {
seedWords: this.configManager.getSeedWords(), seedWords: this.configManager.getSeedWords(),
isInitialized: (!!wallet || !!vault), isInitialized: (!!wallet || !!vault),
isUnlocked: Boolean(this.password), isUnlocked: Boolean(this.password),
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), // AUDIT this.configManager.getConfirmedDisclaimer(), isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(),
transactions: this.configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(), unconfMsgs: messageManager.unconfirmedMsgs(),
messages: messageManager.getMsgList(), messages: messageManager.getMsgList(),
selectedAccount: address, selectedAccount: address,
@ -101,7 +105,9 @@ module.exports = class KeyringController extends EventEmitter {
conversionDate: this.configManager.getConversionDate(), conversionDate: this.configManager.getConversionDate(),
keyringTypes: this.keyringTypes.map(krt => krt.type), keyringTypes: this.keyringTypes.map(krt => krt.type),
identities: this.identities, identities: this.identities,
keyrings: displayKeyrings,
} }
})
} }
// Create New Vault And Keychain // Create New Vault And Keychain
@ -553,6 +559,7 @@ module.exports = class KeyringController extends EventEmitter {
// On success, returns the resulting @Keyring instance. // On success, returns the resulting @Keyring instance.
restoreKeyring (serialized) { restoreKeyring (serialized) {
const { type, data } = serialized const { type, data } = serialized
const Keyring = this.getKeyringClassForType(type) const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring() const keyring = new Keyring()
return keyring.deserialize(data) return keyring.deserialize(data)
@ -625,6 +632,22 @@ module.exports = class KeyringController extends EventEmitter {
}) })
} }
// Display For Keyring
// @Keyring keyring
//
// returns Promise( @Object { type:String, accounts:Array } )
//
// Is used for adding the current keyrings to the state object.
displayForKeyring (keyring) {
return keyring.getAccounts()
.then((accounts) => {
return {
type: keyring.type,
accounts: accounts,
}
})
}
// Add Gas Buffer // Add Gas Buffer
// @string gas (as hexadecimal value) // @string gas (as hexadecimal value)
// //

@ -19,10 +19,11 @@ class SimpleKeyring extends EventEmitter {
return Promise.resolve(this.wallets.map(w => w.getPrivateKey().toString('hex'))) return Promise.resolve(this.wallets.map(w => w.getPrivateKey().toString('hex')))
} }
deserialize (wallets = []) { deserialize (privateKeys = []) {
this.wallets = wallets.map((w) => { this.wallets = privateKeys.map((privateKey) => {
var b = new Buffer(w, 'hex') const stripped = ethUtil.stripHexPrefix(privateKey)
const wallet = Wallet.fromPrivateKey(b) const buffer = new Buffer(stripped, 'hex')
const wallet = Wallet.fromPrivateKey(buffer)
return wallet return wallet
}) })
return Promise.resolve() return Promise.resolve()

@ -111,6 +111,8 @@ MetamaskInpageProvider.prototype.isConnected = function () {
return true return true
} }
MetamaskInpageProvider.prototype.isMetaMask = true
// util // util
function remoteStoreWithLocalStorageCache (storageKey) { function remoteStoreWithLocalStorageCache (storageKey) {

@ -63,16 +63,19 @@ module.exports = class MetamaskController {
} }
getState () { getState () {
return this.keyringController.getState()
.then((keyringControllerState) => {
return extend( return extend(
this.state, this.state,
this.ethStore.getState(), this.ethStore.getState(),
this.configManager.getConfig(), this.configManager.getConfig(),
this.keyringController.getState(),
this.txManager.getState(), this.txManager.getState(),
keyringControllerState,
this.noticeController.getState(), { this.noticeController.getState(), {
lostAccounts: this.configManager.getLostAccounts(), lostAccounts: this.configManager.getLostAccounts(),
} }
) )
})
} }
getApi () { getApi () {
@ -81,7 +84,7 @@ module.exports = class MetamaskController {
const noticeController = this.noticeController const noticeController = this.noticeController
return { return {
getState: (cb) => { cb(null, this.getState()) }, getState: nodeify(this.getState.bind(this)),
setRpcTarget: this.setRpcTarget.bind(this), setRpcTarget: this.setRpcTarget.bind(this),
setProviderType: this.setProviderType.bind(this), setProviderType: this.setProviderType.bind(this),
useEtherscanProvider: this.useEtherscanProvider.bind(this), useEtherscanProvider: this.useEtherscanProvider.bind(this),
@ -101,7 +104,7 @@ module.exports = class MetamaskController {
setLocked: nodeify(keyringController.setLocked).bind(keyringController), setLocked: nodeify(keyringController.setLocked).bind(keyringController),
submitPassword: (password, cb) => { submitPassword: (password, cb) => {
this.migrateOldVaultIfAny(password) this.migrateOldVaultIfAny(password)
.then(keyringController.submitPassword.bind(keyringController)) .then(keyringController.submitPassword.bind(keyringController, password))
.then((newState) => { cb(null, newState) }) .then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) }) .catch((reason) => { cb(reason) })
}, },
@ -177,8 +180,8 @@ module.exports = class MetamaskController {
// tx signing // tx signing
approveTransaction: this.newUnsignedTransaction.bind(this), approveTransaction: this.newUnsignedTransaction.bind(this),
signTransaction: (...args) => { signTransaction: (...args) => {
this.setupSigningListners(...args) this.setupSigningListeners(...args)
this.txManager.formatTxForSigining(...args) this.txManager.formatTxForSigning(...args)
this.sendUpdate() this.sendUpdate()
}, },
@ -257,7 +260,7 @@ module.exports = class MetamaskController {
}) })
} }
setupSigningListners (txParams) { setupSigningListeners (txParams) {
var txId = txParams.metamaskId var txId = txParams.metamaskId
// apply event listeners for signing and formating events // apply event listeners for signing and formating events
this.txManager.once(`${txId}:formatted`, this.keyringController.signTransaction.bind(this.keyringController)) this.txManager.once(`${txId}:formatted`, this.keyringController.signTransaction.bind(this.keyringController))
@ -471,7 +474,7 @@ module.exports = class MetamaskController {
return this.idStoreMigrator.migratedVaultForPassword(password) return this.idStoreMigrator.migratedVaultForPassword(password)
.then(this.restoreOldVaultAccounts.bind(this)) .then(this.restoreOldVaultAccounts.bind(this))
.then(this.restoreOldLostAccounts.bind(this)) .then(this.restoreOldLostAccounts.bind(this))
.then(keyringController.persistAllKeyrings.bind(keyringController)) .then(keyringController.persistAllKeyrings.bind(keyringController, password))
.then(() => password) .then(() => password)
} }

@ -137,7 +137,12 @@ module.exports = class TransactionManager extends EventEmitter {
} }
// formats txParams so the keyringController can sign it // formats txParams so the keyringController can sign it
formatTxForSigining (txParams, cb) { formatTxForSigning (txParams, cb) {
this.getNetwork((err, networkId) => {
if (err) {
return cb(err)
}
var address = txParams.from var address = txParams.from
var metaTx = this.getTx(txParams.metamaskId) var metaTx = this.getTx(txParams.metamaskId)
var gasMultiplier = metaTx.gasMultiplier var gasMultiplier = metaTx.gasMultiplier
@ -152,10 +157,13 @@ module.exports = class TransactionManager extends EventEmitter {
txParams.data = normalize(txParams.data) txParams.data = normalize(txParams.data)
txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas) txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas)
txParams.nonce = normalize(txParams.nonce) txParams.nonce = normalize(txParams.nonce)
txParams.chainId = parseInt(networkId)
const ethTx = new Transaction(txParams) const ethTx = new Transaction(txParams)
// listener is assigned in metamaskController // listener is assigned in metamaskController
this.emit(`${txParams.metamaskId}:formatted`, ethTx, address, txParams.metamaskId, cb) this.emit(`${txParams.metamaskId}:formatted`, ethTx, address, txParams.metamaskId, cb)
})
} }
// receives a signed tx object and updates the tx hash // receives a signed tx object and updates the tx hash

@ -0,0 +1,126 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"name": "Account 1"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
"name": "Account 2"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241",
"name": "Account 3"
},
"0xe15d894becb0354c501ae69429b05143679f39e0": {
"address": "0xe15d894becb0354c501ae69429b05143679f39e0",
"name": "Account 4"
},
"0x87658c15aefe7448008a28513a11b6b130ef4cd0": {
"address": "0x87658c15aefe7448008a28513a11b6b130ef4cd0",
"name": "Account 5"
},
"0xaa25854c0379e53c957ac9382e720c577fa31fd5": {
"address": "0xaa25854c0379e53c957ac9382e720c577fa31fd5",
"name": "Account 6"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 0,
"conversionDate": "N/A",
"noActiveNotices": true,
"network": "3",
"accounts": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"code": "0x",
"balance": "0x11f646fe14c9c000",
"nonce": "0x3",
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241"
},
"0xe15d894becb0354c501ae69429b05143679f39e0": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xe15d894becb0354c501ae69429b05143679f39e0"
},
"0x87658c15aefe7448008a28513a11b6b130ef4cd0": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x87658c15aefe7448008a28513a11b6b130ef4cd0"
},
"0xaa25854c0379e53c957ac9382e720c577fa31fd5": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xaa25854c0379e53c957ac9382e720c577fa31fd5"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x87658c15aefe7448008a28513a11b6b130ef4cd0",
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"ac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"d7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
"1acfb961c5a8268eac8e09d6241a26cbeff42241"
]
},
{
"type": "Simple Key Pair",
"accounts": [
"e15d894becb0354c501ae69429b05143679f39e0",
"87658c15aefe7448008a28513a11b6b130ef4cd0",
"aa25854c0379e53c957ac9382e720c577fa31fd5"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": false,
"forgottenPassword": false
},
"identities": {}
}

@ -1,6 +1,7 @@
var KeyringController = require('../../../app/scripts/keyring-controller')
var ConfigManager = require('../../../app/scripts/lib/config-manager') var ConfigManager = require('../../../app/scripts/lib/config-manager')
var IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator') var IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator')
var SimpleKeyring = require('../../../app/scripts/keyrings/simple')
var normalize = require('../../../app/scripts/lib/sig-util').normalize
var oldStyleVault = require('../mocks/oldVault.json') var oldStyleVault = require('../mocks/oldVault.json')
var badStyleVault = require('../mocks/badVault.json') var badStyleVault = require('../mocks/badVault.json')
@ -68,7 +69,23 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
assert.equal(lostAccounts.length, 1, 'one lost account') assert.equal(lostAccounts.length, 1, 'one lost account')
assert.equal(lostAccounts[0].address, '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase()) assert.equal(lostAccounts[0].address, '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase())
assert.ok(lostAccounts[0].privateKey, 'private key exported') assert.ok(lostAccounts[0].privateKey, 'private key exported')
var lostAccount = lostAccounts[0]
var privateKey = lostAccount.privateKey
var simple = new SimpleKeyring()
simple.deserialize([privateKey])
.then(() => {
return simple.getAccounts()
})
.then((accounts) => {
assert.equal(normalize(accounts[0]), lostAccount.address, 'recovered address.')
done() done()
}) })
.catch((reason) => {
assert.ifError(reason)
done(reason)
})
})
}) })

@ -0,0 +1 @@
{"meta":{"version":4},"data":{"fiatCurrency":"USD","isConfirmed":true,"TOSHash":"a4f4e23f823a7ac51783e7ffba7914a911b09acdb97263296b7e14b527f80c5b","noticesList":[{"read":true,"date":"Fri Dec 16 2016","title":"Ending Morden Support","body":"Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.\n\nUsers will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).\n\nPlease use the new Ropsten Network as your new default test network.\n\nYou can fund your Ropsten account using the buy button on your account page.\n\nBest wishes!\nThe MetaMask Team\n\n","id":0}],"conversionRate":7.07341909,"conversionDate":1482539284,"wallet":"{\"encSeed\":{\"encStr\":\"LZsdN8lJzYkUe1UpmAalnERdgkBFt25gWDdK8kfQUwMAk/27XR+dc+8n5swgoF5qgwhc9LBgliEGNDs1Q/lnuld3aQLabkOeAW4BHS1vS7FxqKrzDS3iyzSuQO6wDQmGno/buuknVgDsKiyjW22hpt7vtVVWA+ZL1P3x6M0+AxGJjeGVrG+E8Q==\",\"nonce\":\"T6O9BmwmTj214XUK3KF0s3iCKo3OlrUD\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"GNNfZevCMlgMVh9y21y1UwrC9qcmH6XYq7v+9UoqbHnzPQJFlxidN5+x/Sldo72a6+5zJpQkkdZ+Q0lePrzvXfuSd3D/RO7WKFIKo9nAQI5+JWwz4INuCmVcmqCv2J4BTLGjrG8fp5pDJ62Bn0XHqkJo3gx3fpvs3cS66+ZKwg==\",\"nonce\":\"HRTlGj44khQs2veYHEF/GqTI1t0yYvyd\"},\"hdIndex\":3,\"encPrivKeys\":{\"e15d894becb0354c501ae69429b05143679f39e0\":{\"key\":\"ZAeZL9VcRUtiiO4VXOQKBFg787PR5R3iymjUeU5vpDRIqOXbjWN6N4ZNR8YpSXl+\",\"nonce\":\"xLsADagS8uqDYae6cImyhxF7o1kBDbPe\"},\"87658c15aefe7448008a28513a11b6b130ef4cd0\":{\"key\":\"ku0mm5s1agRJNAMYIJO0qeoDe+FqcbqdQI6azXF3GL1OLo6uMlt6I4qS+eeravFi\",\"nonce\":\"xdGfSUPKtkW8ge0SWIbbpahs/NyEMzn5\"},\"aa25854c0379e53c957ac9382e720c577fa31fd5\":{\"key\":\"NjpYC9FbiC95CTx/1kwgOHk5LSN9vl4RULEBbvwfVOjqSH8WixNoP3R6I/QyNIs2\",\"nonce\":\"M/HWpXXA9QvuZxEykkGQPJKKdz33ovQr\"}},\"addresses\":[\"e15d894becb0354c501ae69429b05143679f39e0\",\"87658c15aefe7448008a28513a11b6b130ef4cd0\",\"aa25854c0379e53c957ac9382e720c577fa31fd5\"]}},\"encHdRootPriv\":{\"encStr\":\"f+3prUOzl+95aNAV+ad6lZdsYZz120ZsL67ucjj3tiMXf/CC4X8XB9N2QguhoMy6fW+fATUsTdJe8+CbAAyb79V9HY0Pitzq9Yw/g1g0/Ii2JzsdGBriuMsPdwZSVqz+rvQFw/6Qms1xjW6cqa8S7kM2WA5l8RB1Ck6r5zaqbA==\",\"nonce\":\"oGahxNFekVxH9sg6PUCCHIByvo4WFSqm\"},\"salt\":\"N7xYoEA53yhSweOsEphku1UKkIEuZtX2MwLBhVM6RR8=\",\"version\":2}","config":{"provider":{"type":"testnet"},"selectedAccount":"0xe15d894becb0354c501ae69429b05143679f39e0"},"isDisclaimerConfirmed":true,"walletNicknames":{"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9":"Account 1","0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4":"Account 2","0x1acfb961c5a8268eac8e09d6241a26cbeff42241":"Account 3"},"lostAccounts":["0xe15d894becb0354c501ae69429b05143679f39e0","0x87658c15aefe7448008a28513a11b6b130ef4cd0","0xaa25854c0379e53c957ac9382e720c577fa31fd5"]}}

@ -83,11 +83,14 @@ describe('IdentityStore to KeyringController migration', function() {
}) })
describe('entering a password', function() { describe('entering a password', function() {
it('should identify an old wallet as an initialized keyring', function() { it('should identify an old wallet as an initialized keyring', function(done) {
keyringController.configManager.setWallet('something') keyringController.configManager.setWallet('something')
const state = keyringController.getState() keyringController.getState()
.then((state) => {
assert(state.isInitialized, 'old vault counted as initialized.') assert(state.isInitialized, 'old vault counted as initialized.')
assert(!state.lostAccounts, 'no lost accounts') assert(!state.lostAccounts, 'no lost accounts')
done()
})
}) })
}) })
}) })

@ -15,19 +15,21 @@ function AccountListItem () {
} }
AccountListItem.prototype.render = function () { AccountListItem.prototype.render = function () {
const identity = this.props.identity const { identity, selectedAccount, accounts, onShowDetail } = this.props
var isSelected = this.props.selectedAccount === identity.address
var account = this.props.accounts[identity.address] const isSelected = selectedAccount === identity.address
const account = accounts[identity.address]
const selectedClass = isSelected ? '.selected' : '' const selectedClass = isSelected ? '.selected' : ''
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}`,
onClick: (event) => this.props.onShowDetail(identity.address, event), onClick: (event) => onShowDetail(identity.address, event),
}, [ }, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [ h('.identicon-wrapper.flex-column.flex-center.select-none', [
this.pendingOrNot(), this.pendingOrNot(),
this.indicateIfLoose(),
h(Identicon, { h(Identicon, {
address: identity.address, address: identity.address,
imageify: true, imageify: true,
@ -48,7 +50,7 @@ AccountListItem.prototype.render = function () {
}, },
}, ethUtil.toChecksumAddress(identity.address)), }, ethUtil.toChecksumAddress(identity.address)),
h(EthBalance, { h(EthBalance, {
value: account.balance, value: account && account.balance,
style: { style: {
lineHeight: '7px', lineHeight: '7px',
marginTop: '10px', marginTop: '10px',
@ -70,6 +72,14 @@ AccountListItem.prototype.render = function () {
) )
} }
AccountListItem.prototype.indicateIfLoose = function () {
try { // Sometimes keyrings aren't loaded yet:
const type = this.props.keyring.type
const isLoose = type !== 'HD Key Tree'
return isLoose ? h('.keyring-label', 'LOOSE') : null
} catch (e) { return }
}
AccountListItem.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

@ -22,6 +22,7 @@ function mapStateToProps (state) {
selectedAccount: state.metamask.selectedAccount, selectedAccount: state.metamask.selectedAccount,
scrollToBottom: state.appState.scrollToBottom, scrollToBottom: state.appState.scrollToBottom,
pending, pending,
keyrings: state.metamask.keyrings,
} }
} }
@ -31,9 +32,10 @@ function AccountsScreen () {
} }
AccountsScreen.prototype.render = function () { AccountsScreen.prototype.render = function () {
var state = this.props const props = this.props
var identityList = valuesFor(state.identities) const { keyrings } = props
var unconfTxList = valuesFor(state.unconfTxs) const identityList = valuesFor(props.identities)
const unconfTxList = valuesFor(props.unconfTxs)
return ( return (
@ -69,6 +71,11 @@ AccountsScreen.prototype.render = function () {
} }
}) })
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress)
})
return h(AccountListItem, { return h(AccountListItem, {
key: `acct-panel-${identity.address}`, key: `acct-panel-${identity.address}`,
identity, identity,
@ -76,6 +83,7 @@ AccountsScreen.prototype.render = function () {
accounts: this.props.accounts, accounts: this.props.accounts,
onShowDetail: this.onShowDetail.bind(this), onShowDetail: this.onShowDetail.bind(this),
pending, pending,
keyring,
}) })
}), }),

@ -15,9 +15,10 @@ function EthBalanceComponent () {
EthBalanceComponent.prototype.render = function () { EthBalanceComponent.prototype.render = function () {
var props = this.props var props = this.props
let { value } = props
var style = props.style var style = props.style
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
const value = formatBalance(props.value, 6, needsParse) value = value ? formatBalance(value, 6, needsParse) : '...'
var width = props.width var width = props.width
return ( return (
@ -38,6 +39,7 @@ EthBalanceComponent.prototype.render = function () {
EthBalanceComponent.prototype.renderBalance = function (value) { EthBalanceComponent.prototype.renderBalance = function (value) {
var props = this.props var props = this.props
if (value === 'None') return value if (value === 'None') return value
if (value === '...') return value
var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3) var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3)
var balance var balance
var splitBalance = value.split(' ') var splitBalance = value.split(' ')

@ -196,6 +196,23 @@ hr.horizontal-line {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 4px; padding: 4px;
z-index: 1;
}
.keyring-label {
z-index: 1;
font-size: 11px;
background: rgba(255,0,0,0.8);
bottom: -47px;
color: white;
border-radius: 10px;
height: 20px;
min-width: 20px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
} }
.ether-balance { .ether-balance {

Loading…
Cancel
Save