mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #948 from MetaMask/RecoverLostAccounts
Auto-Recover accounts lost to BIP44 derivation fix
This commit is contained in:
commit
898e96fd6a
@ -9,7 +9,6 @@ const encryptor = require('browser-passworder')
|
|||||||
|
|
||||||
const normalize = require('./lib/sig-util').normalize
|
const normalize = require('./lib/sig-util').normalize
|
||||||
const messageManager = require('./lib/message-manager')
|
const messageManager = require('./lib/message-manager')
|
||||||
const IdStoreMigrator = require('./lib/idStore-migrator')
|
|
||||||
const BN = ethUtil.BN
|
const BN = ethUtil.BN
|
||||||
|
|
||||||
// Keyrings:
|
// Keyrings:
|
||||||
@ -45,11 +44,6 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
this._unconfMsgCbs = {}
|
this._unconfMsgCbs = {}
|
||||||
|
|
||||||
this.getNetwork = opts.getNetwork
|
this.getNetwork = opts.getNetwork
|
||||||
|
|
||||||
// TEMPORARY UNTIL FULL DEPRECATION:
|
|
||||||
this.idStoreMigrator = new IdStoreMigrator({
|
|
||||||
configManager: this.configManager,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Store
|
// Set Store
|
||||||
@ -114,7 +108,6 @@ 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,
|
||||||
lostAccounts: this.configManager.getLostAccounts(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,10 +215,7 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
// Temporarily also migrates any old-style vaults first, as well.
|
// Temporarily also migrates any old-style vaults first, as well.
|
||||||
// (Pre MetaMask 3.0.0)
|
// (Pre MetaMask 3.0.0)
|
||||||
submitPassword (password) {
|
submitPassword (password) {
|
||||||
return this.migrateOldVaultIfAny(password)
|
return this.unlockKeyrings(password)
|
||||||
.then(() => {
|
|
||||||
return this.unlockKeyrings(password)
|
|
||||||
})
|
|
||||||
.then((keyrings) => {
|
.then((keyrings) => {
|
||||||
this.keyrings = keyrings
|
this.keyrings = keyrings
|
||||||
return this.fullUpdate()
|
return this.fullUpdate()
|
||||||
@ -611,41 +601,6 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
|
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
|
||||||
// AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
|
// AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
|
||||||
|
|
||||||
// Migrate Old Vault If Any
|
|
||||||
// @string password
|
|
||||||
//
|
|
||||||
// returns Promise()
|
|
||||||
//
|
|
||||||
// Temporary step used when logging in.
|
|
||||||
// Checks if old style (pre-3.0.0) Metamask Vault exists.
|
|
||||||
// If so, persists that vault in the new vault format
|
|
||||||
// with the provided password, so the other unlock steps
|
|
||||||
// may be completed without interruption.
|
|
||||||
migrateOldVaultIfAny (password) {
|
|
||||||
const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault()
|
|
||||||
if (!shouldMigrate) {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.idStoreMigrator.migratedVaultForPassword(password)
|
|
||||||
.then((result) => {
|
|
||||||
this.password = password
|
|
||||||
|
|
||||||
if (result && shouldMigrate) {
|
|
||||||
const { serialized, lostAccounts } = result
|
|
||||||
this.configManager.setLostAccounts(lostAccounts)
|
|
||||||
return this.restoreKeyring(serialized)
|
|
||||||
.then(keyring => keyring.getAccounts())
|
|
||||||
.then((accounts) => {
|
|
||||||
this.configManager.setSelectedAccount(accounts[0])
|
|
||||||
return this.persistAllKeyrings()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create First Key Tree
|
// Create First Key Tree
|
||||||
// returns @Promise
|
// returns @Promise
|
||||||
//
|
//
|
||||||
@ -766,6 +721,10 @@ module.exports = class KeyringController extends EventEmitter {
|
|||||||
// initializing the persisted keyrings to RAM.
|
// initializing the persisted keyrings to RAM.
|
||||||
unlockKeyrings (password) {
|
unlockKeyrings (password) {
|
||||||
const encryptedVault = this.configManager.getVault()
|
const encryptedVault = this.configManager.getVault()
|
||||||
|
if (!encryptedVault) {
|
||||||
|
throw new Error('Cannot unlock without a previous vault.')
|
||||||
|
}
|
||||||
|
|
||||||
return this.encryptor.decrypt(password, encryptedVault)
|
return this.encryptor.decrypt(password, encryptedVault)
|
||||||
.then((vault) => {
|
.then((vault) => {
|
||||||
this.password = password
|
this.password = password
|
||||||
|
@ -121,7 +121,7 @@ ConfigManager.prototype.setVault = function (encryptedString) {
|
|||||||
|
|
||||||
ConfigManager.prototype.getVault = function () {
|
ConfigManager.prototype.getVault = function () {
|
||||||
var data = this.getData()
|
var data = this.getData()
|
||||||
return ('vault' in data) && data.vault
|
return data.vault
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigManager.prototype.getKeychains = function () {
|
ConfigManager.prototype.getKeychains = function () {
|
||||||
|
@ -63,7 +63,12 @@ module.exports = class IdentityStoreMigrator {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
serialized,
|
serialized,
|
||||||
lostAccounts,
|
lostAccounts: lostAccounts.map((address) => {
|
||||||
|
return {
|
||||||
|
address,
|
||||||
|
privateKey: this.idStore.exportAccount(address),
|
||||||
|
}
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,8 @@ IdentityStore.prototype.submitPassword = function (password, cb) {
|
|||||||
|
|
||||||
IdentityStore.prototype.exportAccount = function (address, cb) {
|
IdentityStore.prototype.exportAccount = function (address, cb) {
|
||||||
var privateKey = this._idmgmt.exportPrivateKey(address)
|
var privateKey = this._idmgmt.exportPrivateKey(address)
|
||||||
cb(null, privateKey)
|
if (cb) cb(null, privateKey)
|
||||||
|
return privateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -10,6 +10,7 @@ const ConfigManager = require('./lib/config-manager')
|
|||||||
const extension = require('./lib/extension')
|
const extension = require('./lib/extension')
|
||||||
const autoFaucet = require('./lib/auto-faucet')
|
const autoFaucet = require('./lib/auto-faucet')
|
||||||
const nodeify = require('./lib/nodeify')
|
const nodeify = require('./lib/nodeify')
|
||||||
|
const IdStoreMigrator = require('./lib/idStore-migrator')
|
||||||
|
|
||||||
|
|
||||||
module.exports = class MetamaskController {
|
module.exports = class MetamaskController {
|
||||||
@ -44,6 +45,11 @@ module.exports = class MetamaskController {
|
|||||||
this.checkTOSChange()
|
this.checkTOSChange()
|
||||||
|
|
||||||
this.scheduleConversionInterval()
|
this.scheduleConversionInterval()
|
||||||
|
|
||||||
|
// TEMPORARY UNTIL FULL DEPRECATION:
|
||||||
|
this.idStoreMigrator = new IdStoreMigrator({
|
||||||
|
configManager: this.configManager,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getState () {
|
getState () {
|
||||||
@ -52,7 +58,9 @@ module.exports = class MetamaskController {
|
|||||||
this.ethStore.getState(),
|
this.ethStore.getState(),
|
||||||
this.configManager.getConfig(),
|
this.configManager.getConfig(),
|
||||||
this.keyringController.getState(),
|
this.keyringController.getState(),
|
||||||
this.noticeController.getState()
|
this.noticeController.getState(), {
|
||||||
|
lostAccounts: this.configManager.getLostAccounts(),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +79,7 @@ module.exports = class MetamaskController {
|
|||||||
setTOSHash: this.setTOSHash.bind(this),
|
setTOSHash: this.setTOSHash.bind(this),
|
||||||
checkTOSChange: this.checkTOSChange.bind(this),
|
checkTOSChange: this.checkTOSChange.bind(this),
|
||||||
setGasMultiplier: this.setGasMultiplier.bind(this),
|
setGasMultiplier: this.setGasMultiplier.bind(this),
|
||||||
|
markAccountsFound: this.markAccountsFound.bind(this),
|
||||||
|
|
||||||
// forward directly to keyringController
|
// forward directly to keyringController
|
||||||
createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController),
|
createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController),
|
||||||
@ -78,7 +87,12 @@ module.exports = class MetamaskController {
|
|||||||
placeSeedWords: nodeify(keyringController.placeSeedWords).bind(keyringController),
|
placeSeedWords: nodeify(keyringController.placeSeedWords).bind(keyringController),
|
||||||
clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController),
|
clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController),
|
||||||
setLocked: nodeify(keyringController.setLocked).bind(keyringController),
|
setLocked: nodeify(keyringController.setLocked).bind(keyringController),
|
||||||
submitPassword: nodeify(keyringController.submitPassword).bind(keyringController),
|
submitPassword: (password, cb) => {
|
||||||
|
this.migrateOldVaultIfAny(password)
|
||||||
|
.then(keyringController.submitPassword.bind(keyringController))
|
||||||
|
.then((newState) => { cb(null, newState) })
|
||||||
|
.catch((reason) => { cb(reason) })
|
||||||
|
},
|
||||||
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
|
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
|
||||||
addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
|
addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
|
||||||
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
|
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
|
||||||
@ -410,4 +424,66 @@ module.exports = class MetamaskController {
|
|||||||
getStateNetwork () {
|
getStateNetwork () {
|
||||||
return this.state.network
|
return this.state.network
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAccountsFound(cb) {
|
||||||
|
this.configManager.setLostAccounts([])
|
||||||
|
this.sendUpdate()
|
||||||
|
cb(null, this.getState())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate Old Vault If Any
|
||||||
|
// @string password
|
||||||
|
//
|
||||||
|
// returns Promise()
|
||||||
|
//
|
||||||
|
// Temporary step used when logging in.
|
||||||
|
// Checks if old style (pre-3.0.0) Metamask Vault exists.
|
||||||
|
// If so, persists that vault in the new vault format
|
||||||
|
// with the provided password, so the other unlock steps
|
||||||
|
// may be completed without interruption.
|
||||||
|
migrateOldVaultIfAny (password) {
|
||||||
|
|
||||||
|
if (!this.checkIfShouldMigrate()) {
|
||||||
|
return Promise.resolve(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyringController = this.keyringController
|
||||||
|
|
||||||
|
return this.idStoreMigrator.migratedVaultForPassword(password)
|
||||||
|
.then(this.restoreOldVaultAccounts.bind(this))
|
||||||
|
.then(this.restoreOldLostAccounts.bind(this))
|
||||||
|
.then(keyringController.persistAllKeyrings.bind(keyringController))
|
||||||
|
.then(() => password)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIfShouldMigrate() {
|
||||||
|
return !!this.configManager.getWallet() && !this.configManager.getVault()
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreOldVaultAccounts(migratorOutput) {
|
||||||
|
const { serialized } = migratorOutput
|
||||||
|
return this.keyringController.restoreKeyring(serialized)
|
||||||
|
.then(() => migratorOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreOldLostAccounts(migratorOutput) {
|
||||||
|
const { lostAccounts } = migratorOutput
|
||||||
|
if (lostAccounts) {
|
||||||
|
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
|
||||||
|
return this.importLostAccounts(migratorOutput)
|
||||||
|
}
|
||||||
|
return Promise.resolve(migratorOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMPORT LOST ACCOUNTS
|
||||||
|
// @Object with key lostAccounts: @Array accounts <{ address, privateKey }>
|
||||||
|
// Uses the array's private keys to create a new Simple Key Pair keychain
|
||||||
|
// and add it to the keyring controller.
|
||||||
|
importLostAccounts ({ lostAccounts }) {
|
||||||
|
const privKeys = lostAccounts.map(acct => acct.privateKey)
|
||||||
|
return this.keyringController.restoreKeyring({
|
||||||
|
type: 'Simple Key Pair',
|
||||||
|
data: privKeys,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
91
development/states/lost-accounts.json
Normal file
91
development/states/lost-accounts.json
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"metamask": {
|
||||||
|
"currentFiat": "USD",
|
||||||
|
"lostAccounts": [
|
||||||
|
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||||
|
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
|
||||||
|
],
|
||||||
|
"conversionRate": 11.06608791,
|
||||||
|
"conversionDate": 1470421024,
|
||||||
|
"isInitialized": true,
|
||||||
|
"isUnlocked": true,
|
||||||
|
"currentDomain": "example.com",
|
||||||
|
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||||
|
"identities": {
|
||||||
|
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||||
|
"name": "Wallet 1",
|
||||||
|
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||||
|
"mayBeFauceting": false
|
||||||
|
},
|
||||||
|
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
|
||||||
|
"name": "Wallet 2",
|
||||||
|
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
|
||||||
|
"mayBeFauceting": false
|
||||||
|
},
|
||||||
|
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
|
||||||
|
"name": "Wallet 3",
|
||||||
|
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
|
||||||
|
"mayBeFauceting": false
|
||||||
|
},
|
||||||
|
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
|
||||||
|
"name": "Wallet 4",
|
||||||
|
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
|
||||||
|
"mayBeFauceting": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unconfTxs": {},
|
||||||
|
"accounts": {
|
||||||
|
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||||
|
"code": "0x",
|
||||||
|
"balance": "0x100000000000",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||||
|
},
|
||||||
|
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"balance": "0x100000000000",
|
||||||
|
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
|
||||||
|
},
|
||||||
|
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
|
||||||
|
"code": "0x",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"balance": "0x100000000000",
|
||||||
|
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
|
||||||
|
},
|
||||||
|
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
|
||||||
|
"code": "0x",
|
||||||
|
"balance": "0x0",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"transactions": [],
|
||||||
|
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||||
|
"network": "2",
|
||||||
|
"seedWords": null,
|
||||||
|
"isDisclaimerConfirmed": true,
|
||||||
|
"unconfMsgs": {},
|
||||||
|
"messages": [],
|
||||||
|
"provider": {
|
||||||
|
"type": "testnet"
|
||||||
|
},
|
||||||
|
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||||
|
},
|
||||||
|
"appState": {
|
||||||
|
"menuOpen": false,
|
||||||
|
"currentView": {
|
||||||
|
"name": "accountDetail",
|
||||||
|
"detailView": null,
|
||||||
|
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||||
|
},
|
||||||
|
"accountDetail": {
|
||||||
|
"subview": "transactions"
|
||||||
|
},
|
||||||
|
"currentDomain": "127.0.0.1:9966",
|
||||||
|
"transForward": true,
|
||||||
|
"isLoading": false,
|
||||||
|
"warning": null
|
||||||
|
},
|
||||||
|
"identities": {}
|
||||||
|
}
|
@ -79,7 +79,7 @@ QUnit.test('agree to terms', function (assert) {
|
|||||||
var createButton = app.find('button.primary')[0]
|
var createButton = app.find('button.primary')[0]
|
||||||
createButton.click()
|
createButton.click()
|
||||||
|
|
||||||
return wait(1500)
|
return wait(1000)
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
|
|
||||||
var detail = app.find('.account-detail-section')[0]
|
var detail = app.find('.account-detail-section')[0]
|
||||||
|
74
test/integration/lib/idStore-migrator-test.js
Normal file
74
test/integration/lib/idStore-migrator-test.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
var KeyringController = require('../../../app/scripts/keyring-controller')
|
||||||
|
var ConfigManager = require('../../../app/scripts/lib/config-manager')
|
||||||
|
var IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator')
|
||||||
|
|
||||||
|
var oldStyleVault = require('../mocks/oldVault.json')
|
||||||
|
var badStyleVault = require('../mocks/badVault.json')
|
||||||
|
|
||||||
|
var STORAGE_KEY = 'metamask-config'
|
||||||
|
var PASSWORD = '12345678'
|
||||||
|
var FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
|
||||||
|
var SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner'
|
||||||
|
|
||||||
|
var BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
|
||||||
|
|
||||||
|
QUnit.module('Old Style Vaults', {
|
||||||
|
beforeEach: function () {
|
||||||
|
window.localStorage[STORAGE_KEY] = JSON.stringify(oldStyleVault)
|
||||||
|
|
||||||
|
this.configManager = new ConfigManager({
|
||||||
|
loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
|
||||||
|
setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
|
||||||
|
})
|
||||||
|
|
||||||
|
this.migrator = new IdStoreMigrator({
|
||||||
|
configManager: this.configManager,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
QUnit.test('migrator:isInitialized', function (assert) {
|
||||||
|
assert.ok(this.migrator)
|
||||||
|
})
|
||||||
|
|
||||||
|
QUnit.test('migrator:migratedVaultForPassword', function (assert) {
|
||||||
|
var done = assert.async()
|
||||||
|
|
||||||
|
this.migrator.migratedVaultForPassword(PASSWORD)
|
||||||
|
.then((result) => {
|
||||||
|
const { serialized, lostAccounts } = result
|
||||||
|
assert.equal(serialized.data.mnemonic, SEED, 'seed phrase recovered')
|
||||||
|
assert.equal(lostAccounts.length, 0, 'no lost accounts')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
QUnit.module('Old Style Vaults with bad HD seed', {
|
||||||
|
beforeEach: function () {
|
||||||
|
window.localStorage[STORAGE_KEY] = JSON.stringify(badStyleVault)
|
||||||
|
|
||||||
|
this.configManager = new ConfigManager({
|
||||||
|
loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
|
||||||
|
setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
|
||||||
|
})
|
||||||
|
|
||||||
|
this.migrator = new IdStoreMigrator({
|
||||||
|
configManager: this.configManager,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
QUnit.test('migrator:migratedVaultForPassword', function (assert) {
|
||||||
|
var done = assert.async()
|
||||||
|
|
||||||
|
this.migrator.migratedVaultForPassword(PASSWORD)
|
||||||
|
.then((result) => {
|
||||||
|
const { serialized, lostAccounts } = result
|
||||||
|
|
||||||
|
assert.equal(lostAccounts.length, 1, 'one lost account')
|
||||||
|
assert.equal(lostAccounts[0].address, '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase())
|
||||||
|
assert.ok(lostAccounts[0].privateKey, 'private key exported')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -1,122 +0,0 @@
|
|||||||
var KeyringController = require('../../../app/scripts/keyring-controller')
|
|
||||||
var ConfigManager = require('../../../app/scripts/lib/config-manager')
|
|
||||||
|
|
||||||
var oldStyleVault = require('../mocks/oldVault.json')
|
|
||||||
var badStyleVault = require('../mocks/badVault.json')
|
|
||||||
|
|
||||||
var STORAGE_KEY = 'metamask-config'
|
|
||||||
var PASSWORD = '12345678'
|
|
||||||
var FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
|
|
||||||
|
|
||||||
var BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
|
|
||||||
|
|
||||||
|
|
||||||
QUnit.module('Old Style Vaults', {
|
|
||||||
beforeEach: function () {
|
|
||||||
window.localStorage[STORAGE_KEY] = JSON.stringify(oldStyleVault)
|
|
||||||
|
|
||||||
this.configManager = new ConfigManager({
|
|
||||||
loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
|
|
||||||
setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
|
|
||||||
})
|
|
||||||
|
|
||||||
this.keyringController = new KeyringController({
|
|
||||||
configManager: this.configManager,
|
|
||||||
getNetwork: () => { return '2' },
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ethStore = {
|
|
||||||
addAccount: () => {},
|
|
||||||
removeAccount: () => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
this.keyringController.setStore(this.ethStore)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
QUnit.test('keyringController:isInitialized', function (assert) {
|
|
||||||
assert.ok(this.keyringController.getState().isInitialized)
|
|
||||||
})
|
|
||||||
|
|
||||||
QUnit.test('keyringController:submitPassword', function (assert) {
|
|
||||||
var done = assert.async()
|
|
||||||
|
|
||||||
this.keyringController.submitPassword(PASSWORD)
|
|
||||||
.then((state) => {
|
|
||||||
assert.ok(state.identities[FIRST_ADDRESS])
|
|
||||||
assert.equal(state.lostAccounts.length, 0, 'no lost accounts')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
QUnit.test('keyringController:setLocked', function (assert) {
|
|
||||||
var done = assert.async()
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
this.keyringController.setLocked()
|
|
||||||
.then(function() {
|
|
||||||
assert.notOk(self.keyringController.password, 'password should be deallocated')
|
|
||||||
assert.deepEqual(self.keyringController.keyrings, [], 'keyrings should be deallocated')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
.catch((reason) => {
|
|
||||||
assert.ifError(reason)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
QUnit.module('Old Style Vaults with bad HD seed', {
|
|
||||||
beforeEach: function () {
|
|
||||||
window.localStorage[STORAGE_KEY] = JSON.stringify(badStyleVault)
|
|
||||||
|
|
||||||
this.configManager = new ConfigManager({
|
|
||||||
loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
|
|
||||||
setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
|
|
||||||
})
|
|
||||||
|
|
||||||
this.keyringController = new KeyringController({
|
|
||||||
configManager: this.configManager,
|
|
||||||
getNetwork: () => { return '2' },
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ethStore = {
|
|
||||||
addAccount: () => {},
|
|
||||||
removeAccount: () => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
this.keyringController.setStore(this.ethStore)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
QUnit.test('keyringController:isInitialized', function (assert) {
|
|
||||||
assert.ok(this.keyringController.getState().isInitialized, 'vault is initialized')
|
|
||||||
})
|
|
||||||
|
|
||||||
QUnit.test('keyringController:submitPassword', function (assert) {
|
|
||||||
var done = assert.async()
|
|
||||||
|
|
||||||
this.keyringController.submitPassword(PASSWORD)
|
|
||||||
.then((state) => {
|
|
||||||
assert.ok(state.identities[BAD_STYLE_FIRST_ADDRESS])
|
|
||||||
assert.equal(state.lostAccounts.length, 1, 'one lost account')
|
|
||||||
assert.equal(state.lostAccounts[0], '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase())
|
|
||||||
assert.deepEqual(this.configManager.getLostAccounts(), state.lostAccounts, 'persisted')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
QUnit.test('keyringController:setLocked', function (assert) {
|
|
||||||
var done = assert.async()
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
this.keyringController.setLocked()
|
|
||||||
.then(function() {
|
|
||||||
assert.notOk(self.keyringController.password, 'password should be deallocated')
|
|
||||||
assert.deepEqual(self.keyringController.keyrings, [], 'keyrings should be deallocated')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
.catch((reason) => {
|
|
||||||
assert.ifError(reason)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
@ -83,7 +83,7 @@ describe('IdentityStore to KeyringController migration', function() {
|
|||||||
keyringController.configManager.setWallet('something')
|
keyringController.configManager.setWallet('something')
|
||||||
const state = keyringController.getState()
|
const state = keyringController.getState()
|
||||||
assert(state.isInitialized, 'old vault counted as initialized.')
|
assert(state.isInitialized, 'old vault counted as initialized.')
|
||||||
assert.equal(state.lostAccounts.length, 0, 'no lost accounts')
|
assert(!state.lostAccounts, 'no lost accounts')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -20,6 +20,7 @@ var actions = {
|
|||||||
showNotice: showNotice,
|
showNotice: showNotice,
|
||||||
CLEAR_NOTICES: 'CLEAR_NOTICES',
|
CLEAR_NOTICES: 'CLEAR_NOTICES',
|
||||||
clearNotices: clearNotices,
|
clearNotices: clearNotices,
|
||||||
|
markAccountsFound,
|
||||||
// intialize screen
|
// intialize screen
|
||||||
AGREE_TO_DISCLAIMER: 'AGREE_TO_DISCLAIMER',
|
AGREE_TO_DISCLAIMER: 'AGREE_TO_DISCLAIMER',
|
||||||
agreeToDisclaimer: agreeToDisclaimer,
|
agreeToDisclaimer: agreeToDisclaimer,
|
||||||
@ -591,6 +592,17 @@ function clearNotices () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function markAccountsFound() {
|
||||||
|
return (dispatch) => {
|
||||||
|
dispatch(this.showLoadingIndication())
|
||||||
|
background.markAccountsFound((err, newState) => {
|
||||||
|
dispatch(this.hideLoadingIndication())
|
||||||
|
if (err) return dispatch(this.showWarning(err.message))
|
||||||
|
dispatch(actions.updateMetamaskState(newState))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// config
|
// config
|
||||||
//
|
//
|
||||||
|
@ -16,7 +16,8 @@ const AccountDetailScreen = require('./account-detail')
|
|||||||
const SendTransactionScreen = require('./send')
|
const SendTransactionScreen = require('./send')
|
||||||
const ConfirmTxScreen = require('./conf-tx')
|
const ConfirmTxScreen = require('./conf-tx')
|
||||||
// notice
|
// notice
|
||||||
const NoticeScreen = require('./notice')
|
const NoticeScreen = require('./components/notice')
|
||||||
|
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
|
||||||
// other views
|
// other views
|
||||||
const ConfigScreen = require('./config')
|
const ConfigScreen = require('./config')
|
||||||
const InfoScreen = require('./info')
|
const InfoScreen = require('./info')
|
||||||
@ -55,6 +56,8 @@ function mapStateToProps (state) {
|
|||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
provider: state.metamask.provider,
|
provider: state.metamask.provider,
|
||||||
forgottenPassword: state.appState.forgottenPassword,
|
forgottenPassword: state.appState.forgottenPassword,
|
||||||
|
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
||||||
|
lostAccounts: state.metamask.lostAccounts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,8 +369,19 @@ App.prototype.renderPrimary = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// notices
|
||||||
if (!props.noActiveNotices) {
|
if (!props.noActiveNotices) {
|
||||||
return h(NoticeScreen, {key: 'NoticeScreen'})
|
return h(NoticeScreen, {
|
||||||
|
notice: props.lastUnreadNotice,
|
||||||
|
key: 'NoticeScreen',
|
||||||
|
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
||||||
|
})
|
||||||
|
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
||||||
|
return h(NoticeScreen, {
|
||||||
|
notice: generateLostAccountsNotice(props.lostAccounts),
|
||||||
|
key: 'LostAccountsNotice',
|
||||||
|
onConfirm: () => props.dispatch(actions.markAccountsFound()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// show current view
|
// show current view
|
||||||
|
@ -2,18 +2,10 @@ const inherits = require('util').inherits
|
|||||||
const Component = require('react').Component
|
const Component = require('react').Component
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const ReactMarkdown = require('react-markdown')
|
const ReactMarkdown = require('react-markdown')
|
||||||
const connect = require('react-redux').connect
|
|
||||||
const actions = require('./actions')
|
|
||||||
const linker = require('extension-link-enabler')
|
const linker = require('extension-link-enabler')
|
||||||
const findDOMNode = require('react-dom').findDOMNode
|
const findDOMNode = require('react-dom').findDOMNode
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(Notice)
|
module.exports = Notice
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
|
||||||
return {
|
|
||||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inherits(Notice, Component)
|
inherits(Notice, Component)
|
||||||
function Notice () {
|
function Notice () {
|
||||||
@ -21,9 +13,8 @@ function Notice () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Notice.prototype.render = function () {
|
Notice.prototype.render = function () {
|
||||||
const props = this.props
|
const { notice, onConfirm } = this.props
|
||||||
const title = props.lastUnreadNotice.title
|
const { title, date, body } = notice
|
||||||
const date = props.lastUnreadNotice.date
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
h('.flex-column.flex-center.flex-grow', [
|
h('.flex-column.flex-center.flex-grow', [
|
||||||
@ -59,6 +50,7 @@ Notice.prototype.render = function () {
|
|||||||
.markdown {
|
.markdown {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown h1, .markdown h2, .markdown h3 {
|
.markdown h1, .markdown h2, .markdown h3 {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -92,13 +84,13 @@ Notice.prototype.render = function () {
|
|||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
h(ReactMarkdown, {
|
h(ReactMarkdown, {
|
||||||
source: props.lastUnreadNotice.body,
|
source: body,
|
||||||
skipHtml: true,
|
skipHtml: true,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
h('button', {
|
h('button', {
|
||||||
onClick: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
onClick: onConfirm,
|
||||||
style: {
|
style: {
|
||||||
marginTop: '18px',
|
marginTop: '18px',
|
||||||
},
|
},
|
23
ui/lib/lost-accounts-notice.js
Normal file
23
ui/lib/lost-accounts-notice.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const summary = require('../app/util').addressSummary
|
||||||
|
|
||||||
|
module.exports = function (lostAccounts) {
|
||||||
|
return {
|
||||||
|
date: new Date().toDateString(),
|
||||||
|
title: 'Account Problem Caught',
|
||||||
|
body: `MetaMask has fixed a bug where some accounts were previously mis-generated. This was a rare issue, but you were affected!
|
||||||
|
|
||||||
|
We have successfully imported the accounts that were mis-generated, but they will no longer be recovered with your normal seed phrase.
|
||||||
|
|
||||||
|
We have marked the affected accounts as "Loose", and recommend you transfer ether and tokens away from those accounts, or export & back them up elsewhere.
|
||||||
|
|
||||||
|
Your affected accounts are:
|
||||||
|
${lostAccounts.map(acct => ` - ${summary(acct)}`).join('\n')}
|
||||||
|
|
||||||
|
These accounts have been marked as "Loose" so they will be easy to recognize in the account list.
|
||||||
|
|
||||||
|
For more information, please read [our blog post.][1]
|
||||||
|
|
||||||
|
[1]: https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.7d8ktj4h3
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user