1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Remove keyring controller from project

This commit is contained in:
Dan Finlay 2017-09-22 14:38:40 -07:00
parent aa2abc00eb
commit 443b1a8eb7
5 changed files with 3 additions and 764 deletions

View File

@ -1,594 +0,0 @@
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 sigUtil = require('eth-sig-util')
const normalizeAddress = sigUtil.normalize
// Keyrings:
const SimpleKeyring = require('eth-simple-keyring')
const HdKeyring = require('eth-hd-keyring')
const keyringTypes = [
SimpleKeyring,
HdKeyring,
]
class KeyringController extends EventEmitter {
// PUBLIC METHODS
//
// THE FIRST SECTION OF METHODS ARE PUBLIC-FACING,
// MEANING THEY ARE USED BY CONSUMERS OF THIS CLASS.
//
// THEIR SURFACE AREA SHOULD BE CHANGED WITH GREAT CARE.
constructor (opts) {
super()
const initState = opts.initState || {}
this.keyringTypes = opts.keyringTypes || keyringTypes
this.store = new ObservableStore(initState)
this.memStore = new ObservableStore({
isUnlocked: false,
keyringTypes: this.keyringTypes.map(krt => krt.type),
keyrings: [],
identities: {},
})
this.encryptor = opts.encryptor || encryptor
this.keyrings = []
this.getNetwork = opts.getNetwork
}
// Full Update
// returns Promise( @object state )
//
// Emits the `update` event and
// returns a Promise that resolves to the current state.
//
// Frequently used to end asynchronous chains in this class,
// indicating consumers can often either listen for updates,
// or accept a state-resolving promise to consume their results.
//
// Not all methods end with this, that might be a nice refactor.
fullUpdate () {
this.emit('update')
return Promise.resolve(this.memStore.getState())
}
// Create New Vault And Keychain
// @string password - The password to encrypt the vault with
//
// returns Promise( @object state )
//
// Destroys any old encrypted storage,
// creates a new encrypted store with the given password,
// randomly creates a new HD wallet with 1 account,
// faucets that account on the testnet.
createNewVaultAndKeychain (password) {
return this.persistAllKeyrings(password)
.then(this.createFirstKeyTree.bind(this))
.then(this.fullUpdate.bind(this))
}
// CreateNewVaultAndRestore
// @string password - The password to encrypt the vault with
// @string seed - The BIP44-compliant seed phrase.
//
// returns Promise( @object state )
//
// Destroys any old encrypted storage,
// creates a new encrypted store with the given password,
// creates a new HD wallet from the given seed with 1 account.
createNewVaultAndRestore (password, seed) {
if (typeof password !== 'string') {
return Promise.reject('Password must be text.')
}
if (!bip39.validateMnemonic(seed)) {
return Promise.reject(new Error('Seed phrase is invalid.'))
}
this.clearKeyrings()
return this.persistAllKeyrings(password)
.then(() => {
return this.addNewKeyring('HD Key Tree', {
mnemonic: seed,
numberOfAccounts: 1,
})
})
.then((firstKeyring) => {
return firstKeyring.getAccounts()
})
.then((accounts) => {
const firstAccount = accounts[0]
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))
}
// 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()
}
// Submit Password
// @string password
//
// returns Promise( @object state )
//
// Attempts to decrypt the current vault and load its keyrings
// into memory.
//
// Temporarily also migrates any old-style vaults first, as well.
// (Pre MetaMask 3.0.0)
submitPassword (password) {
return this.unlockKeyrings(password)
.then((keyrings) => {
this.keyrings = keyrings
return this.fullUpdate()
})
}
// Add New Keyring
// @string type
// @object opts
//
// returns Promise( @Keyring keyring )
//
// Adds a new Keyring of the given `type` to the vault
// and the current decrypted Keyrings array.
//
// All Keyring classes implement a unique `type` string,
// and this is used to retrieve them from the keyringTypes array.
addNewKeyring (type, opts) {
const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts)
return keyring.deserialize(opts)
.then(() => {
return keyring.getAccounts()
})
.then((accounts) => {
return this.checkForDuplicate(type, accounts)
})
.then((checkedAccounts) => {
this.keyrings.push(keyring)
return this.setupAccounts(checkedAccounts)
})
.then(() => this.persistAllKeyrings())
.then(() => this._updateMemStoreKeyrings())
.then(() => this.fullUpdate())
.then(() => {
return keyring
})
}
// For now just checks for simple key pairs
// but in the future
// should possibly add HD and other types
//
checkForDuplicate (type, newAccount) {
return this.getAccounts()
.then((accounts) => {
switch (type) {
case 'Simple Key Pair':
const isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0]))
return (isNotIncluded) ? Promise.resolve(newAccount) : Promise.reject(new Error('The account you\'re are trying to import is a duplicate'))
default:
return Promise.resolve(newAccount)
}
})
}
// Add New Account
// @number keyRingNum
//
// returns Promise( @object state )
//
// Calls the `addAccounts` method on the Keyring
// in the kryings array at index `keyringNum`,
// and then saves those changes.
addNewAccount (selectedKeyring) {
return selectedKeyring.addAccounts(1)
.then(this.setupAccounts.bind(this))
.then(this.persistAllKeyrings.bind(this))
.then(this._updateMemStoreKeyrings.bind(this))
.then(this.fullUpdate.bind(this))
}
// Save Account Label
// @string account
// @string label
//
// returns Promise( @string label )
//
// Persists a nickname equal to `label` for the specified account.
saveAccountLabel (account, 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
// @string address
//
// returns Promise( @string privateKey )
//
// Requests the private key from the keyring controlling
// the specified address.
//
// Returns a Promise that may resolve with the private key string.
exportAccount (address) {
try {
return this.getKeyringForAccount(address)
.then((keyring) => {
return keyring.exportAccount(normalizeAddress(address))
})
} catch (e) {
return Promise.reject(e)
}
}
// SIGNING METHODS
//
// This method signs tx and returns a promise for
// TX Manager to update the state after signing
signTransaction (ethTx, _fromAddress) {
const fromAddress = normalizeAddress(_fromAddress)
return this.getKeyringForAccount(fromAddress)
.then((keyring) => {
return keyring.signTransaction(fromAddress, ethTx)
})
}
// Sign Message
// @object msgParams
//
// returns Promise(@buffer rawSig)
//
// Attempts to sign the provided @object msgParams.
signMessage (msgParams) {
const address = normalizeAddress(msgParams.from)
return this.getKeyringForAccount(address)
.then((keyring) => {
return keyring.signMessage(address, msgParams.data)
})
}
// Sign Personal Message
// @object msgParams
//
// returns Promise(@buffer rawSig)
//
// Attempts to sign the provided @object msgParams.
// Prefixes the hash before signing as per the new geth behavior.
signPersonalMessage (msgParams) {
const address = normalizeAddress(msgParams.from)
return this.getKeyringForAccount(address)
.then((keyring) => {
return keyring.signPersonalMessage(address, msgParams.data)
})
}
// PRIVATE METHODS
//
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
// AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
// Create First Key Tree
// returns @Promise
//
// Clears the vault,
// creates a new one,
// creates a random new HD Keyring with 1 account,
// makes that account the selected account,
// faucets that account on testnet,
// puts the current seed words into the state tree.
createFirstKeyTree () {
this.clearKeyrings()
return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 })
.then((keyring) => {
return keyring.getAccounts()
})
.then((accounts) => {
const firstAccount = accounts[0]
if (!firstAccount) throw new Error('KeyringController - No account found on keychain.')
const hexAccount = normalizeAddress(firstAccount)
this.emit('newAccount', hexAccount)
this.emit('newVault', hexAccount)
return this.setupAccounts(accounts)
})
.then(this.persistAllKeyrings.bind(this))
}
// Setup Accounts
// @array accounts
//
// returns @Promise(@object account)
//
// Initializes the provided account array
// Gives them numerically incremented nicknames,
// and adds them to the accountTracker for regular balance checking.
setupAccounts (accounts) {
return this.getAccounts()
.then((loadedAccounts) => {
const arr = accounts || loadedAccounts
return Promise.all(arr.map((account) => {
return this.getBalanceAndNickname(account)
}))
})
}
// Get Balance And Nickname
// @string account
//
// returns Promise( @string label )
//
// Takes an account address and an iterator representing
// the current number of named accounts.
getBalanceAndNickname (account) {
if (!account) {
throw new Error('Problem loading account.')
}
const address = normalizeAddress(account)
this.accountTracker.addAccount(address)
return this.createNickname(address)
}
// Create Nickname
// @string address
//
// returns Promise( @string label )
//
// Takes an address, and assigns it an incremented nickname, persisting it.
createNickname (address) {
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)
}
// Persist All Keyrings
// @password string
//
// returns Promise
//
// Iterates the current `keyrings` array,
// serializes each one into a serialized array,
// encrypts that array with the provided `password`,
// and persists that encrypted string to storage.
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()])
.then((serializedKeyringArray) => {
// Label the output values on each serialized Keyring:
return {
type: serializedKeyringArray[0],
data: serializedKeyringArray[1],
}
})
}))
.then((serializedKeyrings) => {
return this.encryptor.encrypt(this.password, serializedKeyrings)
})
.then((encryptedString) => {
this.store.updateState({ vault: encryptedString })
return true
})
}
// Unlock Keyrings
// @string password
//
// returns Promise( @array keyrings )
//
// Attempts to unlock the persisted encrypted storage,
// initializing the persisted keyrings to RAM.
unlockKeyrings (password) {
const encryptedVault = this.store.getState().vault
if (!encryptedVault) {
throw new Error('Cannot unlock without a previous vault.')
}
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
})
}
// Restore Keyring
// @object serialized
//
// returns Promise( @Keyring deserialized )
//
// Attempts to initialize a new keyring from the provided
// serialized payload.
//
// On success, returns the resulting @Keyring instance.
restoreKeyring (serialized) {
const { type, data } = serialized
const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring()
return keyring.deserialize(data)
.then(() => {
return keyring.getAccounts()
})
.then((accounts) => {
return this.setupAccounts(accounts)
})
.then(() => {
this.keyrings.push(keyring)
this._updateMemStoreKeyrings()
return keyring
})
}
// Get Keyring Class For Type
// @string type
//
// Returns @class Keyring
//
// Searches the current `keyringTypes` array
// for a Keyring class whose unique `type` property
// matches the provided `type`,
// returning it if it exists.
getKeyringClassForType (type) {
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 ] )
//
// Returns the public addresses of all current accounts
// managed by all currently unlocked keyrings.
getAccounts () {
const keyrings = this.keyrings || []
return Promise.all(keyrings.map(kr => kr.getAccounts()))
.then((keyringArrays) => {
return keyringArrays.reduce((res, arr) => {
return res.concat(arr)
}, [])
})
}
// Get Keyring For Account
// @string address
//
// returns Promise(@Keyring keyring)
//
// Returns the currently initialized keyring that manages
// the specified `address` if one exists.
getKeyringForAccount (address) {
const hexed = normalizeAddress(address)
log.debug(`KeyringController - getKeyringForAccount: ${hexed}`)
return Promise.all(this.keyrings.map((keyring) => {
return Promise.all([
keyring,
keyring.getAccounts(),
])
}))
.then(filter((candidate) => {
const accounts = candidate[1].map(normalizeAddress)
return accounts.includes(hexed)
}))
.then((winners) => {
if (winners && winners.length > 0) {
return winners[0][0]
} else {
throw new Error('No keyring found for the requested account.')
}
})
}
// 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
// @string gas (as hexadecimal value)
//
// returns @string bufferedGas (as hexadecimal value)
//
// Adds a healthy buffer of gas to an initial gas estimate.
addGasBuffer (gas) {
const gasBuffer = new BN('100000', 10)
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
const correct = bnGas.add(gasBuffer)
return ethUtil.addHexPrefix(correct.toString(16))
}
// Clear Keyrings
//
// Deallocates all currently managed keyrings and accounts.
// Used before initializing a new vault.
clearKeyrings () {
let accounts
try {
accounts = Object.keys(this.accountTracker.getState())
} catch (e) {
accounts = []
}
accounts.forEach((address) => {
this.accountTracker.removeAccount(address)
})
// clear keyrings from memory
this.keyrings = []
this.memStore.updateState({
keyrings: [],
identities: {},
})
}
_updateMemStoreKeyrings () {
Promise.all(this.keyrings.map(this.displayForKeyring))
.then((keyrings) => {
this.memStore.updateState({ keyrings })
})
}
}
module.exports = KeyringController

View File

@ -14,7 +14,7 @@ const createOriginMiddleware = require('./lib/createOriginMiddleware')
const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
const createProviderMiddleware = require('./lib/createProviderMiddleware')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const KeyringController = require('./keyring-controller')
const KeyringController = require('eth-keyring-controller')
const NetworkController = require('./controllers/network')
const PreferencesController = require('./controllers/preferences')
const CurrencyController = require('./controllers/currency')

View File

@ -10,7 +10,7 @@ which we dont have access to at the time of this writing.
const ObservableStore = require('obs-store')
const ConfigManager = require('../../app/scripts/lib/config-manager')
const IdentityStoreMigrator = require('../../app/scripts/lib/idStore-migrator')
const KeyringController = require('../../app/scripts/lib/keyring-controller')
const KeyringController = require('eth-keyring-controller')
const password = 'obviously not correct'

View File

@ -53,10 +53,8 @@
"async": "^2.5.0",
"await-semaphore": "^0.1.1",
"babel-runtime": "^6.23.0",
"bip39": "^2.2.0",
"bluebird": "^3.5.0",
"bn.js": "^4.11.7",
"browser-passworder": "^2.0.3",
"browserify-derequire": "^0.9.4",
"client-sw-ready-event": "^3.3.0",
"clone": "^2.1.1",
@ -70,12 +68,11 @@
"ensnare": "^1.0.0",
"eth-bin-to-ops": "^1.0.1",
"eth-contract-metadata": "^1.1.4",
"eth-hd-keyring": "^1.1.1",
"eth-json-rpc-filters": "^1.1.0",
"eth-keyring-controller": "^1.0.1",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^1.2.2",
"eth-simple-keyring": "^1.1.1",
"eth-token-tracker": "^1.1.3",
"ethereumjs-tx": "^1.3.0",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",

View File

@ -1,164 +0,0 @@
const assert = require('assert')
const KeyringController = require('../../app/scripts/keyring-controller')
const configManagerGen = require('../lib/mock-config-manager')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const mockEncryptor = require('../lib/mock-encryptor')
const sinon = require('sinon')
describe('KeyringController', function () {
let keyringController
const password = 'password123'
const seedWords = 'puzzle seed penalty soldier say clay field arctic metal hen cage runway'
const addresses = ['eF35cA8EbB9669A35c31b5F6f249A9941a812AC1'.toLowerCase()]
const accounts = []
// let originalKeystore
beforeEach(function (done) {
this.sinon = sinon.sandbox.create()
window.localStorage = {} // Hacking localStorage support into JSDom
keyringController = new KeyringController({
configManager: configManagerGen(),
txManager: {
getTxList: () => [],
getUnapprovedTxList: () => [],
},
accountTracker: {
addAccount (acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
},
encryptor: mockEncryptor,
})
keyringController.createNewVaultAndKeychain(password)
.then(function (newState) {
newState
done()
})
.catch((err) => {
done(err)
})
})
afterEach(function () {
// Cleanup mocks
this.sinon.restore()
})
describe('#createNewVaultAndKeychain', function () {
this.timeout(10000)
it('should set a vault on the configManager', function (done) {
keyringController.store.updateState({ vault: null })
assert(!keyringController.store.getState().vault, 'no previous vault')
keyringController.createNewVaultAndKeychain(password)
.then(() => {
const vault = keyringController.store.getState().vault
assert(vault, 'vault created')
done()
})
.catch((reason) => {
done(reason)
})
})
})
describe('#restoreKeyring', function () {
it(`should pass a keyring's serialized data back to the correct type.`, function (done) {
const mockSerialized = {
type: 'HD Key Tree',
data: {
mnemonic: seedWords,
numberOfAccounts: 1,
},
}
const mock = this.sinon.mock(keyringController)
mock.expects('getBalanceAndNickname')
.exactly(1)
keyringController.restoreKeyring(mockSerialized)
.then((keyring) => {
assert.equal(keyring.wallets.length, 1, 'one wallet restored')
return keyring.getAccounts()
})
.then((accounts) => {
assert.equal(accounts[0], addresses[0])
mock.verify()
done()
})
.catch((reason) => {
done(reason)
})
})
})
describe('#createNickname', function () {
it('should add the address to the identities hash', function () {
const fakeAddress = '0x12345678'
keyringController.createNickname(fakeAddress)
const identities = keyringController.memStore.getState().identities
const identity = identities[fakeAddress]
assert.equal(identity.address, fakeAddress)
})
})
describe('#saveAccountLabel', function () {
it('sets the nickname', function (done) {
const account = addresses[0]
var nick = 'Test nickname'
const identities = keyringController.memStore.getState().identities
identities[ethUtil.addHexPrefix(account)] = {}
keyringController.memStore.updateState({ identities })
keyringController.saveAccountLabel(account, nick)
.then((label) => {
try {
assert.equal(label, nick)
const persisted = keyringController.store.getState().walletNicknames[account]
assert.equal(persisted, nick)
done()
} catch (err) {
done()
}
})
.catch((reason) => {
done(reason)
})
})
})
describe('#getAccounts', function () {
it('returns the result of getAccounts for each keyring', function (done) {
keyringController.keyrings = [
{ getAccounts () { return Promise.resolve([1, 2, 3]) } },
{ getAccounts () { return Promise.resolve([4, 5, 6]) } },
]
keyringController.getAccounts()
.then((result) => {
assert.deepEqual(result, [1, 2, 3, 4, 5, 6])
done()
})
})
})
describe('#addGasBuffer', function () {
it('adds 100k gas buffer to estimates', function () {
const gas = '0x04ee59' // Actual estimated gas example
const tooBigOutput = '0x80674f9' // Actual bad output
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
const correctBuffer = new BN('100000', 10)
const correct = bnGas.add(correctBuffer)
// const tooBig = new BN(tooBigOutput, 16)
const result = keyringController.addGasBuffer(gas)
const bnResult = new BN(ethUtil.stripHexPrefix(result), 16)
assert.equal(result.indexOf('0x'), 0, 'included hex prefix')
assert(bnResult.gt(bnGas), 'Estimate increased in value.')
assert.equal(bnResult.sub(bnGas).toString(10), '100000', 'added 100k gas')
assert.equal(result, '0x' + correct.toString(16), 'Added the right amount')
assert.notEqual(result, tooBigOutput, 'not that bad estimate')
})
})
})