aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
authorDan Finlay <dan@danfinlay.com>2016-11-22 03:03:32 +0800
committerDan Finlay <dan@danfinlay.com>2016-11-22 03:03:32 +0800
commit7ab9d408201ae65ad30512f1361df61a487a9330 (patch)
treecc7b208d36e9cf6eafcab5bd36edba0f46595a64 /app/scripts
parente8a83026248e6e27e6d801cf1713551eb740137a (diff)
parent7021c7708c927b8723b6ba5842b451b6f46dbb3e (diff)
downloadtangerine-wallet-browser-7ab9d408201ae65ad30512f1361df61a487a9330.tar
tangerine-wallet-browser-7ab9d408201ae65ad30512f1361df61a487a9330.tar.gz
tangerine-wallet-browser-7ab9d408201ae65ad30512f1361df61a487a9330.tar.bz2
tangerine-wallet-browser-7ab9d408201ae65ad30512f1361df61a487a9330.tar.lz
tangerine-wallet-browser-7ab9d408201ae65ad30512f1361df61a487a9330.tar.xz
tangerine-wallet-browser-7ab9d408201ae65ad30512f1361df61a487a9330.tar.zst
tangerine-wallet-browser-7ab9d408201ae65ad30512f1361df61a487a9330.zip
Merge branch 'dev' into i831-AddRopsten-Dev
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/background.js11
-rw-r--r--app/scripts/contentscript.js14
-rw-r--r--app/scripts/inpage.js2
-rw-r--r--app/scripts/keyring-controller.js567
-rw-r--r--app/scripts/keyrings/hd.js101
-rw-r--r--app/scripts/keyrings/simple.js67
-rw-r--r--app/scripts/lib/auto-faucet.js5
-rw-r--r--app/scripts/lib/auto-reload.js5
-rw-r--r--app/scripts/lib/config-manager.js77
-rw-r--r--app/scripts/lib/encryptor.js149
-rw-r--r--app/scripts/lib/idStore-migrator.js52
-rw-r--r--app/scripts/lib/idStore.js16
-rw-r--r--app/scripts/lib/inpage-provider.js6
-rw-r--r--app/scripts/lib/is-popup-or-notification.js2
-rw-r--r--app/scripts/lib/notifications.js12
-rw-r--r--app/scripts/lib/random-id.js6
-rw-r--r--app/scripts/lib/sig-util.js28
-rw-r--r--app/scripts/metamask-controller.js154
-rw-r--r--app/scripts/popup-core.js4
-rw-r--r--app/scripts/popup.js2
20 files changed, 1135 insertions, 145 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 652acc113..7cb25d8bf 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -10,6 +10,7 @@ const MetamaskController = require('./metamask-controller')
const extension = require('./lib/extension')
const STORAGE_KEY = 'metamask-config'
+const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
var popupIsOpen = false
const controller = new MetamaskController({
@@ -21,7 +22,7 @@ const controller = new MetamaskController({
setData,
loadData,
})
-const idStore = controller.idStore
+const keyringController = controller.keyringController
function triggerUi () {
if (!popupIsOpen) notification.show()
@@ -29,7 +30,7 @@ function triggerUi () {
// On first install, open a window to MetaMask website to how-it-works.
extension.runtime.onInstalled.addListener(function (details) {
- if (details.reason === 'install') {
+ if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
}
})
@@ -82,7 +83,7 @@ function setupControllerConnection (stream) {
// push updates to popup
controller.ethStore.on('update', controller.sendUpdate.bind(controller))
controller.listeners.push(remote)
- idStore.on('update', controller.sendUpdate.bind(controller))
+ keyringController.on('update', controller.sendUpdate.bind(controller))
// teardown on disconnect
eos(stream, () => {
@@ -96,9 +97,9 @@ function setupControllerConnection (stream) {
// plugin badge text
//
-idStore.on('update', updateBadge)
+keyringController.on('update', updateBadge)
-function updateBadge (state) {
+function updateBadge () {
var label = ''
var unconfTxs = controller.configManager.unconfirmedTxs()
var unconfTxLen = Object.keys(unconfTxs).length
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index e2a968ac9..ab64dc9fa 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -6,7 +6,7 @@ const extension = require('./lib/extension')
const fs = require('fs')
const path = require('path')
-const inpageText = fs.readFileSync(path.join(__dirname + '/inpage.js')).toString()
+const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString()
// Eventually this streaming injection could be replaced with:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
@@ -20,9 +20,8 @@ if (shouldInjectWeb3()) {
setupStreams()
}
-function setupInjection(){
+function setupInjection () {
try {
-
// inject in-page script
var scriptTag = document.createElement('script')
scriptTag.src = extension.extension.getURL('scripts/inpage.js')
@@ -31,14 +30,12 @@ function setupInjection(){
var container = document.head || document.documentElement
// append as first child
container.insertBefore(scriptTag, container.children[0])
-
} catch (e) {
console.error('Metamask injection failed.', e)
}
}
-function setupStreams(){
-
+function setupStreams () {
// setup communication to page and plugin
var pageStream = new LocalMessageDuplexStream({
name: 'contentscript',
@@ -65,14 +62,13 @@ function setupStreams(){
mx.ignoreStream('provider')
mx.ignoreStream('publicConfig')
mx.ignoreStream('reload')
-
}
-function shouldInjectWeb3(){
+function shouldInjectWeb3 () {
return isAllowedSuffix(window.location.href)
}
-function isAllowedSuffix(testCase) {
+function isAllowedSuffix (testCase) {
var prohibitedTypes = ['xml', 'pdf']
var currentUrl = window.location.href
var currentRegex
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index 85dd70b4d..9d6fc96de 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -43,7 +43,7 @@ reloadStream.once('data', triggerReload)
var pingChannel = inpageProvider.multiStream.createStream('pingpong')
var pingStream = new PingStream({ objectMode: true })
// wait for first successful reponse
-metamaskStream.once('_data', function(){
+metamaskStream.once('_data', function () {
pingStream.pipe(pingChannel).pipe(pingStream)
})
endOfStream(pingStream, triggerReload)
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
new file mode 100644
index 000000000..cf761c88c
--- /dev/null
+++ b/app/scripts/keyring-controller.js
@@ -0,0 +1,567 @@
+const async = require('async')
+const ethUtil = require('ethereumjs-util')
+const ethBinToOps = require('eth-bin-to-ops')
+const EthQuery = require('eth-query')
+const bip39 = require('bip39')
+const Transaction = require('ethereumjs-tx')
+const EventEmitter = require('events').EventEmitter
+
+const normalize = require('./lib/sig-util').normalize
+const encryptor = require('./lib/encryptor')
+const messageManager = require('./lib/message-manager')
+const autoFaucet = require('./lib/auto-faucet')
+const IdStoreMigrator = require('./lib/idStore-migrator')
+const BN = ethUtil.BN
+
+// Keyrings:
+const SimpleKeyring = require('./keyrings/simple')
+const HdKeyring = require('./keyrings/hd')
+const keyringTypes = [
+ SimpleKeyring,
+ HdKeyring,
+]
+
+const createId = require('./lib/random-id')
+
+module.exports = class KeyringController extends EventEmitter {
+
+ constructor (opts) {
+ super()
+ this.configManager = opts.configManager
+ this.ethStore = opts.ethStore
+ this.encryptor = encryptor
+ this.keyringTypes = keyringTypes
+
+ this.keyrings = []
+ this.identities = {} // Essentially a name hash
+
+ this._unconfTxCbs = {}
+ this._unconfMsgCbs = {}
+
+ this.getNetwork = opts.getNetwork
+
+ // TEMPORARY UNTIL FULL DEPRECATION:
+ this.idStoreMigrator = new IdStoreMigrator({
+ configManager: this.configManager,
+ })
+ }
+
+ getState () {
+ const configManager = this.configManager
+ const address = configManager.getSelectedAccount()
+ const wallet = configManager.getWallet() // old style vault
+ const vault = configManager.getVault() // new style vault
+
+ return {
+ seedWords: this.configManager.getSeedWords(),
+ isInitialized: (!!wallet || !!vault),
+ isUnlocked: !!this.key,
+ isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), // AUDIT this.configManager.getConfirmedDisclaimer(),
+ unconfTxs: this.configManager.unconfirmedTxs(),
+ transactions: this.configManager.getTxList(),
+ unconfMsgs: messageManager.unconfirmedMsgs(),
+ messages: messageManager.getMsgList(),
+ selectedAddress: address,
+ 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,
+ }
+ }
+
+ setStore (ethStore) {
+ this.ethStore = ethStore
+ }
+
+ createNewVaultAndKeychain (password, entropy, cb) {
+ this.createNewVault(password, entropy, (err) => {
+ if (err) return cb(err)
+ this.createFirstKeyTree(password, cb)
+ })
+ }
+
+ createNewVaultAndRestore (password, seed, cb) {
+ if (typeof password !== 'string') {
+ return cb('Password must be text.')
+ }
+
+ if (!bip39.validateMnemonic(seed)) {
+ return cb('Seed phrase is invalid.')
+ }
+
+ this.clearKeyrings()
+
+ this.createNewVault(password, '', (err) => {
+ if (err) return cb(err)
+ this.addNewKeyring('HD Key Tree', {
+ mnemonic: seed,
+ numberOfAccounts: 1,
+ }, (err) => {
+ if (err) return cb(err)
+ const firstKeyring = this.keyrings[0]
+ const accounts = firstKeyring.getAccounts()
+ const firstAccount = accounts[0]
+ const hexAccount = normalize(firstAccount)
+ this.configManager.setSelectedAccount(hexAccount)
+ this.setupAccounts(accounts)
+
+ this.emit('update')
+ cb()
+ })
+ })
+ }
+
+ migrateAndGetKey (password) {
+ let key
+ const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault()
+ return this.loadKey(password)
+ .then((derivedKey) => {
+ key = derivedKey
+ this.key = key
+ return this.idStoreMigrator.oldSeedForPassword(password)
+ })
+ .then((serialized) => {
+ if (serialized && shouldMigrate) {
+ const keyring = this.restoreKeyring(serialized)
+ this.keyrings.push(keyring)
+ this.configManager.setSelectedAccount(keyring.getAccounts()[0])
+ return this.persistAllKeyrings()
+ .then(() => { return key })
+ }
+ return key
+ })
+ }
+
+ createNewVault (password, entropy, cb) {
+ const configManager = this.configManager
+ const salt = this.encryptor.generateSalt()
+ configManager.setSalt(salt)
+
+ return this.migrateAndGetKey(password)
+ .then(() => {
+ return this.persistAllKeyrings()
+ })
+ .then(() => {
+ cb()
+ })
+ .catch((err) => {
+ cb(err)
+ })
+ }
+
+ createFirstKeyTree (password, cb) {
+ this.clearKeyrings()
+ this.addNewKeyring('HD Key Tree', {numberOfAccounts: 1}, (err) => {
+ const accounts = this.keyrings[0].getAccounts()
+ const firstAccount = accounts[0]
+ const hexAccount = normalize(firstAccount)
+ this.configManager.setSelectedAccount(firstAccount)
+
+ this.placeSeedWords()
+ autoFaucet(hexAccount)
+ this.setupAccounts(accounts)
+ this.persistAllKeyrings()
+ .then(() => {
+ cb(err)
+ })
+ .catch((reason) => {
+ cb(reason)
+ })
+ })
+ }
+
+ placeSeedWords () {
+ const firstKeyring = this.keyrings[0]
+ const seedWords = firstKeyring.serialize().mnemonic
+ this.configManager.setSeedWords(seedWords)
+ }
+
+ submitPassword (password, cb) {
+ this.migrateAndGetKey(password)
+ .then((key) => {
+ return this.unlockKeyrings(key)
+ })
+ .then((keyrings) => {
+ this.keyrings = keyrings
+ this.setupAccounts()
+ this.emit('update')
+ cb(null, this.getState())
+ })
+ .catch((err) => {
+ console.error(err)
+ cb(err)
+ })
+ }
+
+ loadKey (password) {
+ const salt = this.configManager.getSalt() || this.encryptor.generateSalt()
+ return this.encryptor.keyFromPassword(password + salt)
+ .then((key) => {
+ this.key = key
+ this.configManager.setSalt(salt)
+ return key
+ })
+ }
+
+ addNewKeyring (type, opts, cb) {
+ const Keyring = this.getKeyringClassForType(type)
+ const keyring = new Keyring(opts)
+ const accounts = keyring.getAccounts()
+
+ this.keyrings.push(keyring)
+ this.setupAccounts(accounts)
+ this.persistAllKeyrings()
+ .then(() => {
+ cb()
+ })
+ .catch((reason) => {
+ cb(reason)
+ })
+ }
+
+ addNewAccount (keyRingNum = 0, cb) {
+ const ring = this.keyrings[keyRingNum]
+ const accounts = ring.addAccounts(1)
+ this.setupAccounts(accounts)
+ this.persistAllKeyrings()
+ .then(() => {
+ cb()
+ })
+ .catch((reason) => {
+ cb(reason)
+ })
+ }
+
+ setupAccounts (accounts) {
+ var arr = accounts || this.getAccounts()
+ arr.forEach((account) => {
+ this.getBalanceAndNickname(account)
+ })
+ }
+
+ // Takes an account address and an iterator representing
+ // the current number of named accounts.
+ getBalanceAndNickname (account) {
+ const address = normalize(account)
+ this.ethStore.addAccount(address)
+ this.createNickname(address)
+ }
+
+ 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] = {
+ address: hexAddress,
+ name,
+ }
+ return this.saveAccountLabel(hexAddress, name)
+ }
+
+ saveAccountLabel (account, label, cb) {
+ const address = normalize(account)
+ const configManager = this.configManager
+ configManager.setNicknameForWallet(address, label)
+ this.identities[address].name = label
+ if (cb) {
+ cb(null, label)
+ } else {
+ return label
+ }
+ }
+
+ persistAllKeyrings () {
+ const serialized = this.keyrings.map((keyring) => {
+ return {
+ type: keyring.type,
+ data: keyring.serialize(),
+ }
+ })
+ return this.encryptor.encryptWithKey(this.key, serialized)
+ .then((encryptedString) => {
+ this.configManager.setVault(encryptedString)
+ return true
+ })
+ }
+
+ unlockKeyrings (key) {
+ const encryptedVault = this.configManager.getVault()
+ return this.encryptor.decryptWithKey(key, encryptedVault)
+ .then((vault) => {
+ vault.forEach(this.restoreKeyring.bind(this))
+ return this.keyrings
+ })
+ }
+
+ restoreKeyring (serialized) {
+ const { type, data } = serialized
+ const Keyring = this.getKeyringClassForType(type)
+ const keyring = new Keyring()
+ keyring.deserialize(data)
+
+ const accounts = keyring.getAccounts()
+ this.setupAccounts(accounts)
+
+ this.keyrings.push(keyring)
+ return keyring
+ }
+
+ getKeyringClassForType (type) {
+ const Keyring = this.keyringTypes.reduce((res, kr) => {
+ if (kr.type() === type) {
+ return kr
+ } else {
+ return res
+ }
+ })
+ return Keyring
+ }
+
+ getAccounts () {
+ const keyrings = this.keyrings || []
+ return keyrings.map(kr => kr.getAccounts())
+ .reduce((res, arr) => {
+ return res.concat(arr)
+ }, [])
+ }
+
+ setSelectedAddress (address, cb) {
+ var addr = normalize(address)
+ this.configManager.setSelectedAccount(addr)
+ cb(null, addr)
+ }
+
+ addUnconfirmedTransaction (txParams, onTxDoneCb, cb) {
+ var self = this
+ const configManager = this.configManager
+
+ // create txData obj with parameters and meta data
+ var time = (new Date()).getTime()
+ var txId = createId()
+ txParams.metamaskId = txId
+ txParams.metamaskNetworkId = this.getNetwork()
+ var txData = {
+ id: txId,
+ txParams: txParams,
+ time: time,
+ status: 'unconfirmed',
+ gasMultiplier: configManager.getGasMultiplier() || 1,
+ metamaskNetworkId: this.getNetwork(),
+ }
+
+
+ // keep the onTxDoneCb around for after approval/denial (requires user interaction)
+ // This onTxDoneCb fires completion to the Dapp's write operation.
+ this._unconfTxCbs[txId] = onTxDoneCb
+
+ var provider = this.ethStore._query.currentProvider
+ var query = new EthQuery(provider)
+
+ // calculate metadata for tx
+ async.parallel([
+ analyzeForDelegateCall,
+ estimateGas,
+ ], didComplete)
+
+ // perform static analyis on the target contract code
+ function analyzeForDelegateCall (cb) {
+ if (txParams.to) {
+ query.getCode(txParams.to, function (err, result) {
+ if (err) return cb(err)
+ var code = ethUtil.toBuffer(result)
+ if (code !== '0x') {
+ var ops = ethBinToOps(code)
+ var containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL')
+ txData.containsDelegateCall = containsDelegateCall
+ cb()
+ } else {
+ cb()
+ }
+ })
+ } else {
+ cb()
+ }
+ }
+
+ function estimateGas (cb) {
+ query.estimateGas(txParams, function (err, result) {
+ if (err) return cb(err)
+ txData.estimatedGas = self.addGasBuffer(result)
+ cb()
+ })
+ }
+
+ function didComplete (err) {
+ if (err) return cb(err)
+ configManager.addTx(txData)
+ // signal update
+ self.emit('update')
+ // signal completion of add tx
+ cb(null, txData)
+ }
+ }
+
+ 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
+ }
+
+ approveTransaction (txId, cb) {
+ const configManager = this.configManager
+ var approvalCb = this._unconfTxCbs[txId] || noop
+
+ // accept tx
+ cb()
+ approvalCb(null, true)
+ // clean up
+ configManager.confirmTx(txId)
+ delete this._unconfTxCbs[txId]
+ this.emit('update')
+ }
+
+ cancelTransaction (txId, cb) {
+ const configManager = this.configManager
+ var approvalCb = this._unconfTxCbs[txId] || noop
+
+ // reject tx
+ approvalCb(null, false)
+ // clean up
+ configManager.rejectTx(txId)
+ delete this._unconfTxCbs[txId]
+
+ if (cb && typeof cb === 'function') {
+ cb()
+ }
+ }
+
+ signTransaction (txParams, cb) {
+ try {
+ const address = normalize(txParams.from)
+ const keyring = this.getKeyringForAccount(address)
+
+ // Handle gas pricing
+ var gasMultiplier = this.configManager.getGasMultiplier() || 1
+ var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16)
+ gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10))
+ txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber())
+
+ // normalize values
+ txParams.to = normalize(txParams.to)
+ txParams.from = normalize(txParams.from)
+ txParams.value = normalize(txParams.value)
+ txParams.data = normalize(txParams.data)
+ txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas)
+ txParams.nonce = normalize(txParams.nonce)
+
+ let tx = new Transaction(txParams)
+ tx = keyring.signTransaction(address, tx)
+
+ // Add the tx hash to the persisted meta-tx object
+ var txHash = ethUtil.bufferToHex(tx.hash())
+ var metaTx = this.configManager.getTx(txParams.metamaskId)
+ metaTx.hash = txHash
+ this.configManager.updateTx(metaTx)
+
+ // return raw serialized tx
+ var rawTx = ethUtil.bufferToHex(tx.serialize())
+ cb(null, rawTx)
+ } catch (e) {
+ cb(e)
+ }
+ }
+
+ signMessage (msgParams, cb) {
+ try {
+ const keyring = this.getKeyringForAccount(msgParams.from)
+ const address = normalize(msgParams.from)
+ const rawSig = keyring.signMessage(address, msgParams.data)
+ cb(null, rawSig)
+ } catch (e) {
+ cb(e)
+ }
+ }
+
+ getKeyringForAccount (address) {
+ const hexed = normalize(address)
+ return this.keyrings.find((ring) => {
+ return ring.getAccounts()
+ .map(normalize)
+ .includes(hexed)
+ })
+ }
+
+ cancelMessage (msgId, cb) {
+ if (cb && typeof cb === 'function') {
+ cb()
+ }
+ }
+
+ setLocked (cb) {
+ this.key = null
+ this.keyrings = []
+ this.emit('update')
+ cb()
+ }
+
+ exportAccount (address, cb) {
+ try {
+ const keyring = this.getKeyringForAccount(address)
+ const privateKey = keyring.exportAccount(normalize(address))
+ cb(null, privateKey)
+ } catch (e) {
+ cb(e)
+ }
+ }
+
+ 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))
+ }
+
+ clearSeedWordCache (cb) {
+ this.configManager.setSeedWords(null)
+ cb(null, this.configManager.getSelectedAccount())
+ }
+
+ clearKeyrings () {
+ let accounts
+ try {
+ accounts = Object.keys(this.ethStore._currentState.accounts)
+ } catch (e) {
+ accounts = []
+ }
+ accounts.forEach((address) => {
+ this.ethStore.removeAccount(address)
+ })
+
+ this.keyrings = []
+ this.identities = {}
+ this.configManager.setSelectedAccount()
+ }
+
+}
+
+function noop () {}
diff --git a/app/scripts/keyrings/hd.js b/app/scripts/keyrings/hd.js
new file mode 100644
index 000000000..b98f29187
--- /dev/null
+++ b/app/scripts/keyrings/hd.js
@@ -0,0 +1,101 @@
+const EventEmitter = require('events').EventEmitter
+const hdkey = require('ethereumjs-wallet/hdkey')
+const bip39 = require('bip39')
+const ethUtil = require('ethereumjs-util')
+const sigUtil = require('../lib/sig-util')
+
+const type = 'HD Key Tree'
+
+const hdPathString = `m/44'/60'/0'/0`
+
+module.exports = class HdKeyring extends EventEmitter {
+
+ static type () {
+ return type
+ }
+
+ constructor (opts = {}) {
+ super()
+ this.type = type
+ this.deserialize(opts)
+ }
+
+ deserialize (opts = {}) {
+ this.opts = opts || {}
+ this.wallets = []
+ this.mnemonic = null
+ this.root = null
+
+ if ('mnemonic' in opts) {
+ this.initFromMnemonic(opts.mnemonic)
+ }
+
+ if ('numberOfAccounts' in opts) {
+ this.addAccounts(opts.numberOfAccounts)
+ }
+ }
+
+ initFromMnemonic (mnemonic) {
+ this.mnemonic = mnemonic
+ const seed = bip39.mnemonicToSeed(mnemonic)
+ this.hdWallet = hdkey.fromMasterSeed(seed)
+ this.root = this.hdWallet.derivePath(hdPathString)
+ }
+
+ serialize () {
+ return {
+ mnemonic: this.mnemonic,
+ numberOfAccounts: this.wallets.length,
+ }
+ }
+
+ exportAccount (address) {
+ const wallet = this.getWalletForAccount(address)
+ return wallet.getPrivateKey().toString('hex')
+ }
+
+ addAccounts (numberOfAccounts = 1) {
+ if (!this.root) {
+ this.initFromMnemonic(bip39.generateMnemonic())
+ }
+
+ const oldLen = this.wallets.length
+ const newWallets = []
+ for (let i = oldLen; i < numberOfAccounts + oldLen; i++) {
+ const child = this.root.deriveChild(i)
+ const wallet = child.getWallet()
+ newWallets.push(wallet)
+ this.wallets.push(wallet)
+ }
+ return newWallets.map(w => w.getAddress().toString('hex'))
+ }
+
+ getAccounts () {
+ return this.wallets.map(w => w.getAddress().toString('hex'))
+ }
+
+ // tx is an instance of the ethereumjs-transaction class.
+ signTransaction (address, tx) {
+ const wallet = this.getWalletForAccount(address)
+ var privKey = wallet.getPrivateKey()
+ tx.sign(privKey)
+ return tx
+ }
+
+ // For eth_sign, we need to sign transactions:
+ signMessage (withAccount, data) {
+ const wallet = this.getWalletForAccount(withAccount)
+ const message = ethUtil.removeHexPrefix(data)
+ var privKey = wallet.getPrivateKey()
+ var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
+ var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
+ return rawMsgSig
+ }
+
+ getWalletForAccount (account) {
+ return this.wallets.find((w) => {
+ const address = w.getAddress().toString('hex')
+ return ((address === account) || (sigUtil.normalize(address) === account))
+ })
+ }
+}
diff --git a/app/scripts/keyrings/simple.js b/app/scripts/keyrings/simple.js
new file mode 100644
index 000000000..ee743bc03
--- /dev/null
+++ b/app/scripts/keyrings/simple.js
@@ -0,0 +1,67 @@
+const EventEmitter = require('events').EventEmitter
+const Wallet = require('ethereumjs-wallet')
+const ethUtil = require('ethereumjs-util')
+const type = 'Simple Key Pair'
+const sigUtil = require('../lib/sig-util')
+
+module.exports = class SimpleKeyring extends EventEmitter {
+
+ static type () {
+ return type
+ }
+
+ constructor (opts) {
+ super()
+ this.type = type
+ this.opts = opts || {}
+ this.wallets = []
+ }
+
+ serialize () {
+ return this.wallets.map(w => w.getPrivateKey().toString('hex'))
+ }
+
+ deserialize (wallets = []) {
+ this.wallets = wallets.map((w) => {
+ var b = new Buffer(w, 'hex')
+ const wallet = Wallet.fromPrivateKey(b)
+ return wallet
+ })
+ }
+
+ addAccounts (n = 1) {
+ var newWallets = []
+ for (var i = 0; i < n; i++) {
+ newWallets.push(Wallet.generate())
+ }
+ this.wallets = this.wallets.concat(newWallets)
+ return newWallets.map(w => w.getAddress().toString('hex'))
+ }
+
+ getAccounts () {
+ return this.wallets.map(w => w.getAddress().toString('hex'))
+ }
+
+ // tx is an instance of the ethereumjs-transaction class.
+ signTransaction (address, tx) {
+ const wallet = this.getWalletForAccount(address)
+ var privKey = wallet.getPrivateKey()
+ tx.sign(privKey)
+ return tx
+ }
+
+ // For eth_sign, we need to sign transactions:
+ signMessage (withAccount, data) {
+ const wallet = this.getWalletForAccount(withAccount)
+ const message = ethUtil.removeHexPrefix(data)
+ var privKey = wallet.getPrivateKey()
+ var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
+ var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
+ return rawMsgSig
+ }
+
+ getWalletForAccount (account) {
+ return this.wallets.find(w => w.getAddress().toString('hex') === account)
+ }
+
+}
diff --git a/app/scripts/lib/auto-faucet.js b/app/scripts/lib/auto-faucet.js
index 59cf0ec20..1e86f735e 100644
--- a/app/scripts/lib/auto-faucet.js
+++ b/app/scripts/lib/auto-faucet.js
@@ -1,6 +1,9 @@
-var uri = 'https://faucet.metamask.io/'
+const uri = 'https://faucet.metamask.io/'
+const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
+const env = process.env.METAMASK_ENV
module.exports = function (address) {
+ if (METAMASK_DEBUG || env === 'test') return // Don't faucet in development or test
var http = new XMLHttpRequest()
var data = address
http.open('POST', uri, true)
diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js
index 3c90905db..1302df35f 100644
--- a/app/scripts/lib/auto-reload.js
+++ b/app/scripts/lib/auto-reload.js
@@ -18,17 +18,16 @@ function setupDappAutoReload (web3) {
return handleResetRequest
- function handleResetRequest() {
+ function handleResetRequest () {
resetWasRequested = true
// ignore if web3 was not used
if (!pageIsUsingWeb3) return
// reload after short timeout
setTimeout(triggerReset, 500)
}
-
}
// reload the page
function triggerReset () {
global.location.reload()
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index b8ffb6991..8e63265d2 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -2,6 +2,7 @@ const Migrator = require('pojo-migrator')
const MetamaskConfig = require('../config.js')
const migrations = require('./migrations')
const rp = require('request-promise')
+const ethUtil = require('ethereumjs-util')
const TESTNET_RPC = MetamaskConfig.network.testnet
const MAINNET_RPC = MetamaskConfig.network.mainnet
@@ -111,6 +112,27 @@ ConfigManager.prototype.setWallet = function (wallet) {
this.setData(data)
}
+ConfigManager.prototype.setVault = function (encryptedString) {
+ var data = this.getData()
+ data.vault = encryptedString
+ this.setData(data)
+}
+
+ConfigManager.prototype.getVault = function () {
+ var data = this.getData()
+ return ('vault' in data) && data.vault
+}
+
+ConfigManager.prototype.getKeychains = function () {
+ return this.migrator.getData().keychains || []
+}
+
+ConfigManager.prototype.setKeychains = function (keychains) {
+ var data = this.migrator.getData()
+ data.keychains = keychains
+ this.setData(data)
+}
+
ConfigManager.prototype.getSelectedAccount = function () {
var config = this.getConfig()
return config.selectedAccount
@@ -118,7 +140,7 @@ ConfigManager.prototype.getSelectedAccount = function () {
ConfigManager.prototype.setSelectedAccount = function (address) {
var config = this.getConfig()
- config.selectedAccount = address
+ config.selectedAccount = ethUtil.addHexPrefix(address)
this.setConfig(config)
}
@@ -133,11 +155,23 @@ ConfigManager.prototype.setShowSeedWords = function (should) {
this.setData(data)
}
+
ConfigManager.prototype.getShouldShowSeedWords = function () {
var data = this.migrator.getData()
return data.showSeedWords
}
+ConfigManager.prototype.setSeedWords = function (words) {
+ var data = this.getData()
+ data.seedWords = words
+ this.setData(data)
+}
+
+ConfigManager.prototype.getSeedWords = function () {
+ var data = this.getData()
+ return ('seedWords' in data) && data.seedWords
+}
+
ConfigManager.prototype.getCurrentRpcAddress = function () {
var provider = this.getProvider()
if (!provider) return null
@@ -239,13 +273,15 @@ ConfigManager.prototype.getWalletNicknames = function () {
}
ConfigManager.prototype.nicknameForWallet = function (account) {
+ const address = ethUtil.addHexPrefix(account.toLowerCase())
const nicknames = this.getWalletNicknames()
- return nicknames[account]
+ return nicknames[address]
}
ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
+ const address = ethUtil.addHexPrefix(account.toLowerCase())
const nicknames = this.getWalletNicknames()
- nicknames[account] = nickname
+ nicknames[address] = nickname
var data = this.getData()
data.walletNicknames = nicknames
this.setData(data)
@@ -253,6 +289,17 @@ ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
// observable
+ConfigManager.prototype.getSalt = function () {
+ var data = this.getData()
+ return ('salt' in data) && data.salt
+}
+
+ConfigManager.prototype.setSalt = function (salt) {
+ var data = this.getData()
+ data.salt = salt
+ this.setData(data)
+}
+
ConfigManager.prototype.subscribe = function (fn) {
this._subs.push(fn)
var unsubscribe = this.unsubscribe.bind(this, fn)
@@ -270,15 +317,15 @@ ConfigManager.prototype._emitUpdates = function (state) {
})
}
-ConfigManager.prototype.setConfirmed = function (confirmed) {
+ConfigManager.prototype.setConfirmedDisclaimer = function (confirmed) {
var data = this.getData()
- data.isConfirmed = confirmed
+ data.isDisclaimerConfirmed = confirmed
this.setData(data)
}
-ConfigManager.prototype.getConfirmed = function () {
+ConfigManager.prototype.getConfirmedDisclaimer = function () {
var data = this.getData()
- return ('isConfirmed' in data) && data.isConfirmed
+ return ('isDisclaimerConfirmed' in data) && data.isDisclaimerConfirmed
}
ConfigManager.prototype.setTOSHash = function (hash) {
@@ -315,7 +362,6 @@ ConfigManager.prototype.updateConversionRate = function () {
this.setConversionPrice(0)
this.setConversionDate('N/A')
})
-
}
ConfigManager.prototype.setConversionPrice = function (price) {
@@ -340,21 +386,6 @@ ConfigManager.prototype.getConversionDate = function () {
return (('conversionDate' in data) && data.conversionDate) || 'N/A'
}
-ConfigManager.prototype.setShouldntShowWarning = function () {
- var data = this.getData()
- if (data.isEthConfirmed) {
- data.isEthConfirmed = !data.isEthConfirmed
- } else {
- data.isEthConfirmed = true
- }
- this.setData(data)
-}
-
-ConfigManager.prototype.getShouldntShowWarning = function () {
- var data = this.getData()
- return ('isEthConfirmed' in data) && data.isEthConfirmed
-}
-
ConfigManager.prototype.getShapeShiftTxList = function () {
var data = this.getData()
var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : []
diff --git a/app/scripts/lib/encryptor.js b/app/scripts/lib/encryptor.js
new file mode 100644
index 000000000..2af2a1d2b
--- /dev/null
+++ b/app/scripts/lib/encryptor.js
@@ -0,0 +1,149 @@
+var ethUtil = require('ethereumjs-util')
+
+module.exports = {
+
+ // Simple encryption methods:
+ encrypt,
+ decrypt,
+
+ // More advanced encryption methods:
+ keyFromPassword,
+ encryptWithKey,
+ decryptWithKey,
+
+ // Buffer <-> String methods
+ convertArrayBufferViewtoString,
+ convertStringToArrayBufferView,
+
+ // Buffer <-> Hex string methods
+ serializeBufferForStorage,
+ serializeBufferFromStorage,
+
+ // Buffer <-> base64 string methods
+ encodeBufferToBase64,
+ decodeBase64ToBuffer,
+
+ generateSalt,
+}
+
+// Takes a Pojo, returns cypher text.
+function encrypt (password, dataObj) {
+ return keyFromPassword(password)
+ .then(function (passwordDerivedKey) {
+ return encryptWithKey(passwordDerivedKey, dataObj)
+ })
+}
+
+function encryptWithKey (key, dataObj) {
+ var data = JSON.stringify(dataObj)
+ var dataBuffer = convertStringToArrayBufferView(data)
+ var vector = global.crypto.getRandomValues(new Uint8Array(16))
+
+ return global.crypto.subtle.encrypt({
+ name: 'AES-GCM',
+ iv: vector,
+ }, key, dataBuffer).then(function (buf) {
+ var buffer = new Uint8Array(buf)
+ var vectorStr = encodeBufferToBase64(vector)
+ var vaultStr = encodeBufferToBase64(buffer)
+ return `${vaultStr}\\${vectorStr}`
+ })
+}
+
+// Takes encrypted text, returns the restored Pojo.
+function decrypt (password, text) {
+ return keyFromPassword(password)
+ .then(function (key) {
+ return decryptWithKey(key, text)
+ })
+}
+
+function decryptWithKey (key, text) {
+ const parts = text.split('\\')
+ const encryptedData = decodeBase64ToBuffer(parts[0])
+ const vector = decodeBase64ToBuffer(parts[1])
+ return crypto.subtle.decrypt({name: 'AES-GCM', iv: vector}, key, encryptedData)
+ .then(function (result) {
+ const decryptedData = new Uint8Array(result)
+ const decryptedStr = convertArrayBufferViewtoString(decryptedData)
+ const decryptedObj = JSON.parse(decryptedStr)
+ return decryptedObj
+ })
+ .catch(function (reason) {
+ throw new Error('Incorrect password')
+ })
+}
+
+function convertStringToArrayBufferView (str) {
+ var bytes = new Uint8Array(str.length)
+ for (var i = 0; i < str.length; i++) {
+ bytes[i] = str.charCodeAt(i)
+ }
+
+ return bytes
+}
+
+function convertArrayBufferViewtoString (buffer) {
+ var str = ''
+ for (var i = 0; i < buffer.byteLength; i++) {
+ str += String.fromCharCode(buffer[i])
+ }
+
+ return str
+}
+
+function keyFromPassword (password) {
+ var passBuffer = convertStringToArrayBufferView(password)
+ return global.crypto.subtle.digest('SHA-256', passBuffer)
+ .then(function (passHash) {
+ return global.crypto.subtle.importKey('raw', passHash, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt'])
+ })
+}
+
+function serializeBufferFromStorage (str) {
+ str = ethUtil.stripHexPrefix(str)
+ var buf = new Uint8Array(str.length / 2)
+ for (var i = 0; i < str.length; i += 2) {
+ var seg = str.substr(i, 2)
+ buf[i / 2] = parseInt(seg, 16)
+ }
+ return buf
+}
+
+// Should return a string, ready for storage, in hex format.
+function serializeBufferForStorage (buffer) {
+ var result = '0x'
+ var len = buffer.length || buffer.byteLength
+ for (var i = 0; i < len; i++) {
+ result += unprefixedHex(buffer[i])
+ }
+ return result
+}
+
+function unprefixedHex (num) {
+ var hex = num.toString(16)
+ while (hex.length < 2) {
+ hex = '0' + hex
+ }
+ return hex
+}
+
+function encodeBufferToBase64 (buf) {
+ var b64encoded = btoa(String.fromCharCode.apply(null, buf))
+ return b64encoded
+}
+
+function decodeBase64ToBuffer (base64) {
+ var buf = new Uint8Array(atob(base64).split('')
+ .map(function (c) {
+ return c.charCodeAt(0)
+ }))
+ return buf
+}
+
+function generateSalt (byteCount = 32) {
+ var view = new Uint8Array(byteCount)
+ global.crypto.getRandomValues(view)
+ var b64encoded = btoa(String.fromCharCode.apply(null, view))
+ return b64encoded
+}
diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js
new file mode 100644
index 000000000..18134b677
--- /dev/null
+++ b/app/scripts/lib/idStore-migrator.js
@@ -0,0 +1,52 @@
+const IdentityStore = require('./idStore')
+
+
+module.exports = class IdentityStoreMigrator {
+
+ constructor ({ configManager }) {
+ this.configManager = configManager
+ const hasOldVault = this.hasOldVault()
+ if (!hasOldVault) {
+ this.idStore = new IdentityStore({ configManager })
+ }
+ }
+
+ oldSeedForPassword (password) {
+ const hasOldVault = this.hasOldVault()
+ const configManager = this.configManager
+
+ if (!this.idStore) {
+ this.idStore = new IdentityStore({ configManager })
+ }
+
+ if (!hasOldVault) {
+ return Promise.resolve(null)
+ }
+
+ return new Promise((resolve, reject) => {
+ this.idStore.submitPassword(password, (err) => {
+ if (err) return reject(err)
+ try {
+ resolve(this.serializeVault())
+ } catch (e) {
+ reject(e)
+ }
+ })
+ })
+ }
+
+ serializeVault () {
+ const mnemonic = this.idStore._idmgmt.getSeed()
+ const numberOfAccounts = this.idStore._getAddresses().length
+
+ return {
+ type: 'HD Key Tree',
+ data: { mnemonic, numberOfAccounts },
+ }
+ }
+
+ hasOldVault () {
+ const wallet = this.configManager.getWallet()
+ return wallet
+ }
+}
diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js
index ccd5efe69..b73652af5 100644
--- a/app/scripts/lib/idStore.js
+++ b/app/scripts/lib/idStore.js
@@ -102,8 +102,7 @@ IdentityStore.prototype.getState = function () {
isInitialized: !!configManager.getWallet() && !seedWords,
isUnlocked: this._isUnlocked(),
seedWords: seedWords,
- isConfirmed: configManager.getConfirmed(),
- isEthConfirmed: configManager.getShouldntShowWarning(),
+ isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(),
unconfTxs: configManager.unconfirmedTxs(),
transactions: configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(),
@@ -114,7 +113,6 @@ IdentityStore.prototype.getState = function () {
conversionRate: configManager.getConversionRate(),
conversionDate: configManager.getConversionDate(),
gasMultiplier: configManager.getGasMultiplier(),
-
}))
}
@@ -245,7 +243,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone
], didComplete)
// perform static analyis on the target contract code
- function analyzeForDelegateCall(cb){
+ function analyzeForDelegateCall (cb) {
if (txParams.to) {
query.getCode(txParams.to, (err, result) => {
if (err) return cb(err.message || err)
@@ -258,16 +256,16 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone
}
}
- function estimateGas(cb){
+ function estimateGas (cb) {
var estimationParams = extend(txParams)
// 1 billion gas for estimation
var gasLimit = '0x3b9aca00'
estimationParams.gas = gasLimit
- query.estimateGas(estimationParams, function(err, result){
+ query.estimateGas(estimationParams, function (err, result) {
if (err) return cb(err.message || err)
if (result === estimationParams.gas) {
txData.simulationFails = true
- query.getBlockByNumber('latest', true, function(err, block){
+ query.getBlockByNumber('latest', true, function (err, block) {
if (err) return cb(err)
txData.estimatedGas = block.gasLimit
txData.txParams.gas = block.gasLimit
@@ -440,7 +438,9 @@ IdentityStore.prototype._loadIdentities = function () {
var addresses = this._getAddresses()
addresses.forEach((address, i) => {
// // add to ethStore
- this._ethStore.addAccount(ethUtil.addHexPrefix(address))
+ if (this._ethStore) {
+ this._ethStore.addAccount(ethUtil.addHexPrefix(address))
+ }
// add to identities
const defaultLabel = 'Account ' + (i + 1)
const nickname = configManager.nicknameForWallet(address)
diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js
index f1ba2e0ed..034812b6a 100644
--- a/app/scripts/lib/inpage-provider.js
+++ b/app/scripts/lib/inpage-provider.js
@@ -40,7 +40,7 @@ function MetamaskInpageProvider (connectionStream) {
self.idMap = {}
// handle sendAsync requests via asyncProvider
- self.sendAsync = function(payload, cb){
+ self.sendAsync = function (payload, cb) {
// rewrite request ids
var request = eachJsonMessage(payload, (message) => {
var newId = createRandomId()
@@ -49,7 +49,7 @@ function MetamaskInpageProvider (connectionStream) {
return message
})
// forward to asyncProvider
- asyncProvider.sendAsync(request, function(err, res){
+ asyncProvider.sendAsync(request, function (err, res) {
if (err) return cb(err)
// transform messages to original ids
eachJsonMessage(res, (message) => {
@@ -120,7 +120,7 @@ function remoteStoreWithLocalStorageCache (storageKey) {
return store
}
-function eachJsonMessage(payload, transformFn){
+function eachJsonMessage (payload, transformFn) {
if (Array.isArray(payload)) {
return payload.map(transformFn)
} else {
diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js
index 5c38ac823..693fa8751 100644
--- a/app/scripts/lib/is-popup-or-notification.js
+++ b/app/scripts/lib/is-popup-or-notification.js
@@ -1,4 +1,4 @@
-module.exports = function isPopupOrNotification() {
+module.exports = function isPopupOrNotification () {
const url = window.location.href
if (url.match(/popup.html$/)) {
return 'popup'
diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js
index cd7535232..3db1ac6b5 100644
--- a/app/scripts/lib/notifications.js
+++ b/app/scripts/lib/notifications.js
@@ -15,12 +15,9 @@ function show () {
if (err) throw err
if (popup) {
-
// bring focus to existing popup
extension.windows.update(popup.id, { focused: true })
-
} else {
-
// create new popup
extension.windows.create({
url: 'notification.html',
@@ -29,12 +26,11 @@ function show () {
width,
height,
})
-
}
})
}
-function getWindows(cb) {
+function getWindows (cb) {
// Ignore in test environment
if (!extension.windows) {
return cb()
@@ -45,14 +41,14 @@ function getWindows(cb) {
})
}
-function getPopup(cb) {
+function getPopup (cb) {
getWindows((err, windows) => {
if (err) throw err
cb(null, getPopupIn(windows))
})
}
-function getPopupIn(windows) {
+function getPopupIn (windows) {
return windows ? windows.find((win) => {
return (win && win.type === 'popup' &&
win.height === height &&
@@ -60,7 +56,7 @@ function getPopupIn(windows) {
}) : null
}
-function closePopup() {
+function closePopup () {
getPopup((err, popup) => {
if (err) throw err
if (!popup) return
diff --git a/app/scripts/lib/random-id.js b/app/scripts/lib/random-id.js
index 3c5ae5600..788f3370f 100644
--- a/app/scripts/lib/random-id.js
+++ b/app/scripts/lib/random-id.js
@@ -1,7 +1,7 @@
-const MAX = 1000000000
+const MAX = Number.MAX_SAFE_INTEGER
-let idCounter = Math.round( Math.random() * MAX )
-function createRandomId() {
+let idCounter = Math.round(Math.random() * MAX)
+function createRandomId () {
idCounter = idCounter % MAX
return idCounter++
}
diff --git a/app/scripts/lib/sig-util.js b/app/scripts/lib/sig-util.js
new file mode 100644
index 000000000..193dda381
--- /dev/null
+++ b/app/scripts/lib/sig-util.js
@@ -0,0 +1,28 @@
+const ethUtil = require('ethereumjs-util')
+
+module.exports = {
+
+ concatSig: function (v, r, s) {
+ const rSig = ethUtil.fromSigned(r)
+ const sSig = ethUtil.fromSigned(s)
+ const vSig = ethUtil.bufferToInt(v)
+ const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64)
+ const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64)
+ const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig))
+ return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex')
+ },
+
+ normalize: function (address) {
+ if (!address) return
+ return ethUtil.addHexPrefix(address.toLowerCase())
+ },
+
+}
+
+function padWithZeroes (number, length) {
+ var myString = '' + number
+ while (myString.length < length) {
+ myString = '0' + myString
+ }
+ return myString
+}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 5e76c5ebd..951918460 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -1,7 +1,7 @@
const extend = require('xtend')
const EthStore = require('eth-store')
const MetaMaskProvider = require('web3-provider-engine/zero.js')
-const IdentityStore = require('./lib/idStore')
+const KeyringController = require('./keyring-controller')
const messageManager = require('./lib/message-manager')
const HostStore = require('./lib/remote-store.js').HostStore
const Web3 = require('web3')
@@ -11,15 +11,18 @@ const extension = require('./lib/extension')
module.exports = class MetamaskController {
constructor (opts) {
+ this.state = { network: 'loading' }
this.opts = opts
this.listeners = []
this.configManager = new ConfigManager(opts)
- this.idStore = new IdentityStore({
+ this.keyringController = new KeyringController({
configManager: this.configManager,
+ getNetwork: this.getStateNetwork.bind(this),
})
this.provider = this.initializeProvider(opts)
this.ethStore = new EthStore(this.provider)
- this.idStore.setStore(this.ethStore)
+ this.keyringController.setStore(this.ethStore)
+ this.getNetwork()
this.messageManager = messageManager
this.publicConfigStore = this.initPublicConfigStore()
@@ -30,19 +33,19 @@ module.exports = class MetamaskController {
this.checkTOSChange()
this.scheduleConversionInterval()
-
}
getState () {
return extend(
+ this.state,
this.ethStore.getState(),
- this.idStore.getState(),
- this.configManager.getConfig()
+ this.configManager.getConfig(),
+ this.keyringController.getState()
)
}
getApi () {
- const idStore = this.idStore
+ const keyringController = this.keyringController
return {
getState: (cb) => { cb(null, this.getState()) },
@@ -52,27 +55,26 @@ module.exports = class MetamaskController {
agreeToDisclaimer: this.agreeToDisclaimer.bind(this),
resetDisclaimer: this.resetDisclaimer.bind(this),
setCurrentFiat: this.setCurrentFiat.bind(this),
- agreeToEthWarning: this.agreeToEthWarning.bind(this),
setTOSHash: this.setTOSHash.bind(this),
checkTOSChange: this.checkTOSChange.bind(this),
setGasMultiplier: this.setGasMultiplier.bind(this),
- // forward directly to idStore
- createNewVault: idStore.createNewVault.bind(idStore),
- recoverFromSeed: idStore.recoverFromSeed.bind(idStore),
- submitPassword: idStore.submitPassword.bind(idStore),
- setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
- approveTransaction: idStore.approveTransaction.bind(idStore),
- cancelTransaction: idStore.cancelTransaction.bind(idStore),
- signMessage: idStore.signMessage.bind(idStore),
- cancelMessage: idStore.cancelMessage.bind(idStore),
- setLocked: idStore.setLocked.bind(idStore),
- clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
- exportAccount: idStore.exportAccount.bind(idStore),
- revealAccount: idStore.revealAccount.bind(idStore),
- saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
- tryPassword: idStore.tryPassword.bind(idStore),
- recoverSeed: idStore.recoverSeed.bind(idStore),
+ // forward directly to keyringController
+ placeSeedWords: keyringController.placeSeedWords.bind(keyringController),
+ createNewVaultAndKeychain: keyringController.createNewVaultAndKeychain.bind(keyringController),
+ createNewVaultAndRestore: keyringController.createNewVaultAndRestore.bind(keyringController),
+ clearSeedWordCache: keyringController.clearSeedWordCache.bind(keyringController),
+ addNewKeyring: keyringController.addNewKeyring.bind(keyringController),
+ addNewAccount: keyringController.addNewAccount.bind(keyringController),
+ submitPassword: keyringController.submitPassword.bind(keyringController),
+ setSelectedAddress: keyringController.setSelectedAddress.bind(keyringController),
+ approveTransaction: keyringController.approveTransaction.bind(keyringController),
+ cancelTransaction: keyringController.cancelTransaction.bind(keyringController),
+ signMessage: keyringController.signMessage.bind(keyringController),
+ cancelMessage: keyringController.cancelMessage.bind(keyringController),
+ setLocked: keyringController.setLocked.bind(keyringController),
+ exportAccount: keyringController.exportAccount.bind(keyringController),
+ saveAccountLabel: keyringController.saveAccountLabel.bind(keyringController),
// coinbase
buyEth: this.buyEth.bind(this),
// shapeshift
@@ -85,23 +87,6 @@ module.exports = class MetamaskController {
}
onRpcRequest (stream, originDomain, request) {
-
- /* Commented out for Parity compliance
- * Parity does not permit additional keys, like `origin`,
- * and Infura is not currently filtering this key out.
- var payloads = Array.isArray(request) ? request : [request]
- payloads.forEach(function (payload) {
- // Append origin to rpc payload
- payload.origin = originDomain
- // Append origin to signature request
- if (payload.method === 'eth_sendTransaction') {
- payload.params[0].origin = originDomain
- } else if (payload.method === 'eth_sign') {
- payload.params.push({ origin: originDomain })
- }
- })
- */
-
// handle rpc request
this.provider.sendAsync(request, function onPayloadHandled (err, response) {
logger(err, request, response)
@@ -134,38 +119,38 @@ module.exports = class MetamaskController {
}
initializeProvider (opts) {
- const idStore = this.idStore
+ const keyringController = this.keyringController
var providerOpts = {
rpcUrl: this.configManager.getCurrentRpcAddress(),
// account mgmt
getAccounts: (cb) => {
- var selectedAddress = idStore.getSelectedAddress()
+ var selectedAddress = this.configManager.getSelectedAccount()
var result = selectedAddress ? [selectedAddress] : []
cb(null, result)
},
// tx signing
approveTransaction: this.newUnsignedTransaction.bind(this),
signTransaction: (...args) => {
- idStore.signTransaction(...args)
+ keyringController.signTransaction(...args)
this.sendUpdate()
},
// msg signing
approveMessage: this.newUnsignedMessage.bind(this),
signMessage: (...args) => {
- idStore.signMessage(...args)
+ keyringController.signMessage(...args)
this.sendUpdate()
},
}
var provider = MetaMaskProvider(providerOpts)
var web3 = new Web3(provider)
- idStore.web3 = web3
- idStore.getNetwork()
+ this.web3 = web3
+ keyringController.web3 = web3
provider.on('block', this.processBlock.bind(this))
- provider.on('error', idStore.getNetwork.bind(idStore))
+ provider.on('error', this.getNetwork.bind(this))
return provider
}
@@ -173,7 +158,7 @@ module.exports = class MetamaskController {
initPublicConfigStore () {
// get init state
var initPublicState = extend(
- idStoreToPublic(this.idStore.getState()),
+ keyringControllerToPublic(this.keyringController.getState()),
configToPublic(this.configManager.getConfig())
)
@@ -183,12 +168,14 @@ module.exports = class MetamaskController {
this.configManager.subscribe(function (state) {
storeSetFromObj(publicConfigStore, configToPublic(state))
})
- this.idStore.on('update', function (state) {
- storeSetFromObj(publicConfigStore, idStoreToPublic(state))
+ this.keyringController.on('update', () => {
+ const state = this.keyringController.getState()
+ storeSetFromObj(publicConfigStore, keyringControllerToPublic(state))
+ this.sendUpdate()
})
- // idStore substate
- function idStoreToPublic (state) {
+ // keyringController substate
+ function keyringControllerToPublic (state) {
return {
selectedAddress: state.selectedAddress,
}
@@ -211,10 +198,10 @@ module.exports = class MetamaskController {
}
newUnsignedTransaction (txParams, onTxDoneCb) {
- const idStore = this.idStore
- let err = this.enforceTxValidations(txParams)
+ const keyringController = this.keyringController
+ const err = this.enforceTxValidations(txParams)
if (err) return onTxDoneCb(err)
- idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
+ keyringController.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
if (err) return onTxDoneCb(err)
this.sendUpdate()
this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
@@ -229,9 +216,9 @@ module.exports = class MetamaskController {
}
newUnsignedMessage (msgParams, cb) {
- var state = this.idStore.getState()
+ var state = this.keyringController.getState()
if (!state.isUnlocked) {
- this.idStore.addUnconfirmedMessage(msgParams, cb)
+ this.keyringController.addUnconfirmedMessage(msgParams, cb)
this.opts.unlockAccountMessage()
} else {
this.addUnconfirmedMessage(msgParams, cb)
@@ -240,8 +227,8 @@ module.exports = class MetamaskController {
}
addUnconfirmedMessage (msgParams, cb) {
- const idStore = this.idStore
- const msgId = idStore.addUnconfirmedMessage(msgParams, cb)
+ const keyringController = this.keyringController
+ const msgId = keyringController.addUnconfirmedMessage(msgParams, cb)
this.opts.showUnconfirmedMessage(msgParams, msgId)
}
@@ -260,8 +247,8 @@ module.exports = class MetamaskController {
verifyNetwork () {
// Check network when restoring connectivity:
- if (this.idStore._currentState.network === 'loading') {
- this.idStore.getNetwork()
+ if (this.state.network === 'loading') {
+ this.getNetwork()
}
}
@@ -286,12 +273,11 @@ module.exports = class MetamaskController {
} catch (e) {
console.error('Error in checking TOS change.')
}
-
}
agreeToDisclaimer (cb) {
try {
- this.configManager.setConfirmed(true)
+ this.configManager.setConfirmedDisclaimer(true)
cb()
} catch (e) {
cb(e)
@@ -300,7 +286,7 @@ module.exports = class MetamaskController {
resetDisclaimer () {
try {
- this.configManager.setConfirmed(false)
+ this.configManager.setConfirmedDisclaimer(false)
} catch (e) {
console.error(e)
}
@@ -331,26 +317,17 @@ module.exports = class MetamaskController {
}, 300000)
}
- agreeToEthWarning (cb) {
- try {
- this.configManager.setShouldntShowWarning()
- cb()
- } catch (e) {
- cb(e)
- }
- }
-
// called from popup
setRpcTarget (rpcTarget) {
this.configManager.setRpcTarget(rpcTarget)
extension.runtime.reload()
- this.idStore.getNetwork()
+ this.getNetwork()
}
setProviderType (type) {
this.configManager.setProviderType(type)
extension.runtime.reload()
- this.idStore.getNetwork()
+ this.getNetwork()
}
useEtherscanProvider () {
@@ -361,7 +338,7 @@ module.exports = class MetamaskController {
buyEth (address, amount) {
if (!amount) amount = '5'
- var network = this.idStore._currentState.network
+ var network = this.state.network
var url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
if (network === '2') {
@@ -377,6 +354,25 @@ module.exports = class MetamaskController {
this.configManager.createShapeShiftTx(depositAddress, depositType)
}
+ getNetwork (err) {
+ if (err) {
+ this.state.network = 'loading'
+ this.sendUpdate()
+ }
+
+ this.web3.version.getNetwork((err, network) => {
+ if (err) {
+ this.state.network = 'loading'
+ return this.sendUpdate()
+ }
+ if (global.METAMASK_DEBUG) {
+ console.log('web3.getNetwork returned ' + network)
+ }
+ this.state.network = network
+ this.sendUpdate()
+ })
+ }
+
setGasMultiplier (gasMultiplier, cb) {
try {
this.configManager.setGasMultiplier(gasMultiplier)
@@ -385,4 +381,8 @@ module.exports = class MetamaskController {
cb(e)
}
}
+
+ getStateNetwork () {
+ return this.state.network
+ }
}
diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js
index 94413a1c4..0c97a5d19 100644
--- a/app/scripts/popup-core.js
+++ b/app/scripts/popup-core.js
@@ -9,7 +9,7 @@ const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
module.exports = initializePopup
-function initializePopup(connectionStream){
+function initializePopup (connectionStream) {
// setup app
connectToAccountManager(connectionStream, setupApp)
}
@@ -32,7 +32,7 @@ function setupWeb3Connection (connectionStream) {
}
function setupControllerConnection (connectionStream, cb) {
- // this is a really sneaky way of adding EventEmitter api
+ // this is a really sneaky way of adding EventEmitter api
// to a bi-directional dnode instance
var eventEmitter = new EventEmitter()
var accountManagerDnode = Dnode({
diff --git a/app/scripts/popup.js b/app/scripts/popup.js
index e6f149f96..62db68c10 100644
--- a/app/scripts/popup.js
+++ b/app/scripts/popup.js
@@ -18,7 +18,7 @@ var portStream = new PortStream(pluginPort)
startPopup(portStream)
-function closePopupIfOpen(name) {
+function closePopupIfOpen (name) {
if (name !== 'notification') {
notification.closePopup()
}