diff options
Diffstat (limited to 'app/scripts/keyring-controller.js')
-rw-r--r-- | app/scripts/keyring-controller.js | 311 |
1 files changed, 88 insertions, 223 deletions
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 76422bf6b..348f81fc9 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -1,13 +1,11 @@ const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN const bip39 = require('bip39') const EventEmitter = require('events').EventEmitter +const ObservableStore = require('obs-store') const filter = require('promise-filter') const encryptor = require('browser-passworder') - -const normalize = require('./lib/sig-util').normalize -const messageManager = require('./lib/message-manager') -const BN = ethUtil.BN - +const normalizeAddress = require('./lib/sig-util').normalize // Keyrings: const SimpleKeyring = require('./keyrings/simple') const HdKeyring = require('./keyrings/hd') @@ -16,9 +14,7 @@ const keyringTypes = [ HdKeyring, ] -const createId = require('./lib/random-id') - -module.exports = class KeyringController extends EventEmitter { +class KeyringController extends EventEmitter { // PUBLIC METHODS // @@ -29,29 +25,21 @@ module.exports = class KeyringController extends EventEmitter { constructor (opts) { super() - this.configManager = opts.configManager + const initState = opts.initState || {} + this.keyringTypes = keyringTypes + this.store = new ObservableStore(initState) + this.memStore = new ObservableStore({ + isUnlocked: false, + keyringTypes: this.keyringTypes.map(krt => krt.type), + keyrings: [], + identities: {}, + }) this.ethStore = opts.ethStore this.encryptor = encryptor - this.keyringTypes = keyringTypes this.keyrings = [] - this.identities = {} // Essentially a name hash - - this._unconfMsgCbs = {} - this.getNetwork = opts.getNetwork } - // Set Store - // - // Allows setting the ethStore after the constructor. - // This is currently required because of the initialization order - // of the ethStore and this class. - // - // Eventually would be nice to be able to add this in the constructor. - setStore (ethStore) { - this.ethStore = ethStore - } - // Full Update // returns Promise( @object state ) // @@ -65,48 +53,7 @@ module.exports = class KeyringController extends EventEmitter { // Not all methods end with this, that might be a nice refactor. fullUpdate () { this.emit('update') - return Promise.resolve(this.getState()) - } - - // Get State - // returns @object state - // - // This method returns a hash representing the current state - // that the keyringController manages. - // - // It is extended in the MetamaskController along with the EthStore - // state, and its own state, to create the metamask state branch - // that is passed to the UI. - // - // This is currently a rare example of a synchronously resolving method - // in this class, but will need to be Promisified when we move our - // persistence to an async model. - getState () { - const configManager = this.configManager - const address = configManager.getSelectedAccount() - const wallet = configManager.getWallet() // old style vault - const vault = configManager.getVault() // new style vault - const keyrings = this.keyrings - - return Promise.all(keyrings.map(this.displayForKeyring)) - .then((displayKeyrings) => { - return { - seedWords: this.configManager.getSeedWords(), - isInitialized: (!!wallet || !!vault), - isUnlocked: Boolean(this.password), - isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), - unconfMsgs: messageManager.unconfirmedMsgs(), - messages: messageManager.getMsgList(), - selectedAccount: address, - shapeShiftTxList: this.configManager.getShapeShiftTxList(), - currentFiat: this.configManager.getCurrentFiat(), - conversionRate: this.configManager.getConversionRate(), - conversionDate: this.configManager.getConversionDate(), - keyringTypes: this.keyringTypes.map(krt => krt.type), - identities: this.identities, - keyrings: displayKeyrings, - } - }) + return Promise.resolve(this.memStore.getState()) } // Create New Vault And Keychain @@ -150,57 +97,32 @@ module.exports = class KeyringController extends EventEmitter { mnemonic: seed, numberOfAccounts: 1, }) - }).then(() => { - const firstKeyring = this.keyrings[0] + }) + .then((firstKeyring) => { return firstKeyring.getAccounts() }) .then((accounts) => { const firstAccount = accounts[0] - const hexAccount = normalize(firstAccount) - this.configManager.setSelectedAccount(hexAccount) + if (!firstAccount) throw new Error('KeyringController - First Account not found.') + const hexAccount = normalizeAddress(firstAccount) + this.emit('newAccount', hexAccount) return this.setupAccounts(accounts) }) .then(this.persistAllKeyrings.bind(this, password)) .then(this.fullUpdate.bind(this)) } - // PlaceSeedWords - // returns Promise( @object state ) - // - // Adds the current vault's seed words to the UI's state tree. - // - // Used when creating a first vault, to allow confirmation. - // Also used when revealing the seed words in the confirmation view. - placeSeedWords () { - const hdKeyrings = this.keyrings.filter((keyring) => keyring.type === 'HD Key Tree') - const firstKeyring = hdKeyrings[0] - if (!firstKeyring) throw new Error('KeyringController - No HD Key Tree found') - return firstKeyring.serialize() - .then((serialized) => { - const seedWords = serialized.mnemonic - this.configManager.setSeedWords(seedWords) - return this.fullUpdate() - }) - } - - // ClearSeedWordCache - // - // returns Promise( @string currentSelectedAccount ) - // - // Removes the current vault's seed words from the UI's state tree, - // ensuring they are only ever available in the background process. - clearSeedWordCache () { - this.configManager.setSeedWords(null) - return Promise.resolve(this.configManager.getSelectedAccount()) - } - // Set Locked // returns Promise( @object state ) // // This method deallocates all secrets, and effectively locks metamask. setLocked () { + // set locked this.password = null + this.memStore.updateState({ isUnlocked: false }) + // remove keyrings this.keyrings = [] + this._updateMemStoreKeyrings() return this.fullUpdate() } @@ -244,8 +166,8 @@ module.exports = class KeyringController extends EventEmitter { this.keyrings.push(keyring) return this.setupAccounts(accounts) }) - .then(() => { return this.password }) - .then(this.persistAllKeyrings.bind(this)) + .then(() => this.persistAllKeyrings()) + .then(() => this.fullUpdate()) .then(() => { return keyring }) @@ -259,29 +181,13 @@ module.exports = class KeyringController extends EventEmitter { // Calls the `addAccounts` method on the Keyring // in the kryings array at index `keyringNum`, // and then saves those changes. - addNewAccount () { - const hdKeyrings = this.keyrings.filter((keyring) => keyring.type === 'HD Key Tree') - const firstKeyring = hdKeyrings[0] - if (!firstKeyring) throw new Error('KeyringController - No HD Key Tree found') - return firstKeyring.addAccounts(1) + addNewAccount (selectedKeyring) { + return selectedKeyring.addAccounts(1) .then(this.setupAccounts.bind(this)) .then(this.persistAllKeyrings.bind(this)) .then(this.fullUpdate.bind(this)) } - // Set Selected Account - // @string address - // - // returns Promise( @string address ) - // - // Sets the state's `selectedAccount` value - // to the specified address. - setSelectedAccount (address) { - var addr = normalize(address) - this.configManager.setSelectedAccount(addr) - return this.fullUpdate() - } - // Save Account Label // @string account // @string label @@ -290,11 +196,21 @@ module.exports = class KeyringController extends EventEmitter { // // Persists a nickname equal to `label` for the specified account. saveAccountLabel (account, label) { - const address = normalize(account) - const configManager = this.configManager - configManager.setNicknameForWallet(address, label) - this.identities[address].name = label - return Promise.resolve(label) + try { + const hexAddress = normalizeAddress(account) + // update state on diskStore + const state = this.store.getState() + const walletNicknames = state.walletNicknames || {} + walletNicknames[hexAddress] = label + this.store.updateState({ walletNicknames }) + // update state on memStore + const identities = this.memStore.getState().identities + identities[hexAddress].name = label + this.memStore.updateState({ identities }) + return Promise.resolve(label) + } catch (err) { + return Promise.reject(err) + } } // Export Account @@ -310,7 +226,7 @@ module.exports = class KeyringController extends EventEmitter { try { return this.getKeyringForAccount(address) .then((keyring) => { - return keyring.exportAccount(normalize(address)) + return keyring.exportAccount(normalizeAddress(address)) }) } catch (e) { return Promise.reject(e) @@ -324,92 +240,25 @@ module.exports = class KeyringController extends EventEmitter { // TX Manager to update the state after signing signTransaction (ethTx, _fromAddress) { - const fromAddress = normalize(_fromAddress) + const fromAddress = normalizeAddress(_fromAddress) return this.getKeyringForAccount(fromAddress) .then((keyring) => { return keyring.signTransaction(fromAddress, ethTx) }) } - // Add Unconfirmed Message - // @object msgParams - // @function cb - // - // Does not call back, only emits an `update` event. - // - // Adds the given `msgParams` and `cb` to a local cache, - // for displaying to a user for approval before signing or canceling. - addUnconfirmedMessage (msgParams, cb) { - // create txData obj with parameters and meta data - var time = (new Date()).getTime() - var msgId = createId() - var msgData = { - id: msgId, - msgParams: msgParams, - time: time, - status: 'unconfirmed', - } - messageManager.addMsg(msgData) - console.log('addUnconfirmedMessage:', msgData) - - // keep the cb around for after approval (requires user interaction) - // This cb fires completion to the Dapp's write operation. - this._unconfMsgCbs[msgId] = cb - - // signal update - this.emit('update') - return msgId - } - - // Cancel Message - // @string msgId - // @function cb (optional) - // - // Calls back to cached `unconfMsgCb`. - // Calls back to `cb` if provided. - // - // Forgets any messages matching `msgId`. - cancelMessage (msgId, cb) { - var approvalCb = this._unconfMsgCbs[msgId] || noop - - // reject tx - approvalCb(null, false) - // clean up - messageManager.rejectMsg(msgId) - delete this._unconfTxCbs[msgId] - - if (cb && typeof cb === 'function') { - cb() - } - } // Sign Message // @object msgParams - // @function cb // // returns Promise(@buffer rawSig) - // calls back @function cb with @buffer rawSig - // calls back cached Dapp's @function unconfMsgCb. // // Attempts to sign the provided @object msgParams. - signMessage (msgParams, cb) { - try { - const msgId = msgParams.metamaskId - delete msgParams.metamaskId - const approvalCb = this._unconfMsgCbs[msgId] || noop - - const address = normalize(msgParams.from) - return this.getKeyringForAccount(address) - .then((keyring) => { - return keyring.signMessage(address, msgParams.data) - }).then((rawSig) => { - cb(null, rawSig) - approvalCb(null, true) - messageManager.confirmMsg(msgId) - return rawSig - }) - } catch (e) { - cb(e) - } + signMessage (msgParams) { + const address = normalizeAddress(msgParams.from) + return this.getKeyringForAccount(address) + .then((keyring) => { + return keyring.signMessage(address, msgParams.data) + }) } // PRIVATE METHODS @@ -428,18 +277,16 @@ module.exports = class KeyringController extends EventEmitter { // puts the current seed words into the state tree. createFirstKeyTree () { this.clearKeyrings() - return this.addNewKeyring('HD Key Tree', {numberOfAccounts: 1}) - .then(() => { - return this.keyrings[0].getAccounts() + return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 }) + .then((keyring) => { + return keyring.getAccounts() }) .then((accounts) => { const firstAccount = accounts[0] - const hexAccount = normalize(firstAccount) - this.configManager.setSelectedAccount(hexAccount) + if (!firstAccount) throw new Error('KeyringController - No account found on keychain.') + const hexAccount = normalizeAddress(firstAccount) this.emit('newAccount', hexAccount) return this.setupAccounts(accounts) - }).then(() => { - return this.placeSeedWords() }) .then(this.persistAllKeyrings.bind(this)) } @@ -473,7 +320,7 @@ module.exports = class KeyringController extends EventEmitter { if (!account) { throw new Error('Problem loading account.') } - const address = normalize(account) + const address = normalizeAddress(account) this.ethStore.addAccount(address) return this.createNickname(address) } @@ -485,14 +332,17 @@ module.exports = class KeyringController extends EventEmitter { // // Takes an address, and assigns it an incremented nickname, persisting it. createNickname (address) { - const hexAddress = normalize(address) - var i = Object.keys(this.identities).length - const oldNickname = this.configManager.nicknameForWallet(address) - const name = oldNickname || `Account ${++i}` - this.identities[hexAddress] = { + const hexAddress = normalizeAddress(address) + const identities = this.memStore.getState().identities + const currentIdentityCount = Object.keys(identities).length + 1 + const nicknames = this.store.getState().walletNicknames || {} + const existingNickname = nicknames[hexAddress] + const name = existingNickname || `Account ${currentIdentityCount}` + identities[hexAddress] = { address: hexAddress, name, } + this.memStore.updateState({ identities }) return this.saveAccountLabel(hexAddress, name) } @@ -508,6 +358,7 @@ module.exports = class KeyringController extends EventEmitter { persistAllKeyrings (password = this.password) { if (typeof password === 'string') { this.password = password + this.memStore.updateState({ isUnlocked: true }) } return Promise.all(this.keyrings.map((keyring) => { return Promise.all([keyring.type, keyring.serialize()]) @@ -523,7 +374,7 @@ module.exports = class KeyringController extends EventEmitter { return this.encryptor.encrypt(this.password, serializedKeyrings) }) .then((encryptedString) => { - this.configManager.setVault(encryptedString) + this.store.updateState({ vault: encryptedString }) return true }) } @@ -536,7 +387,7 @@ module.exports = class KeyringController extends EventEmitter { // Attempts to unlock the persisted encrypted storage, // initializing the persisted keyrings to RAM. unlockKeyrings (password) { - const encryptedVault = this.configManager.getVault() + const encryptedVault = this.store.getState().vault if (!encryptedVault) { throw new Error('Cannot unlock without a previous vault.') } @@ -544,6 +395,7 @@ module.exports = class KeyringController extends EventEmitter { return this.encryptor.decrypt(password, encryptedVault) .then((vault) => { this.password = password + this.memStore.updateState({ isUnlocked: true }) vault.forEach(this.restoreKeyring.bind(this)) return this.keyrings }) @@ -589,6 +441,10 @@ module.exports = class KeyringController extends EventEmitter { return this.keyringTypes.find(kr => kr.type === type) } + getKeyringsByType (type) { + return this.keyrings.filter((keyring) => keyring.type === type) + } + // Get Accounts // returns Promise( @Array[ @string accounts ] ) // @@ -612,7 +468,7 @@ module.exports = class KeyringController extends EventEmitter { // Returns the currently initialized keyring that manages // the specified `address` if one exists. getKeyringForAccount (address) { - const hexed = normalize(address) + const hexed = normalizeAddress(address) return Promise.all(this.keyrings.map((keyring) => { return Promise.all([ @@ -621,7 +477,7 @@ module.exports = class KeyringController extends EventEmitter { ]) })) .then(filter((candidate) => { - const accounts = candidate[1].map(normalize) + const accounts = candidate[1].map(normalizeAddress) return accounts.includes(hexed) })) .then((winners) => { @@ -669,7 +525,7 @@ module.exports = class KeyringController extends EventEmitter { clearKeyrings () { let accounts try { - accounts = Object.keys(this.ethStore._currentState.accounts) + accounts = Object.keys(this.ethStore.getState()) } catch (e) { accounts = [] } @@ -677,12 +533,21 @@ module.exports = class KeyringController extends EventEmitter { this.ethStore.removeAccount(address) }) + // clear keyrings from memory this.keyrings = [] - this.identities = {} - this.configManager.setSelectedAccount() + this.memStore.updateState({ + keyrings: [], + identities: {}, + }) } -} + _updateMemStoreKeyrings() { + Promise.all(this.keyrings.map(this.displayForKeyring)) + .then((keyrings) => { + this.memStore.updateState({ keyrings }) + }) + } +} -function noop () {} +module.exports = KeyringController |