aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/scripts/keyring-controller.js120
-rw-r--r--app/scripts/lib/config-manager.js32
-rw-r--r--app/scripts/lib/encryptor.js137
-rw-r--r--app/scripts/metamask-controller.js43
4 files changed, 319 insertions, 13 deletions
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
new file mode 100644
index 000000000..d25dddba1
--- /dev/null
+++ b/app/scripts/keyring-controller.js
@@ -0,0 +1,120 @@
+const EventEmitter = require('events').EventEmitter
+const encryptor = require('./lib/encryptor')
+const messageManager = require('./lib/message-manager')
+
+
+module.exports = class KeyringController extends EventEmitter {
+
+ constructor (opts) {
+ super()
+ this.configManager = opts.configManager
+ this.ethStore = opts.ethStore
+ this.keyChains = []
+ }
+
+ getState() {
+ return {
+ isInitialized: !!this.configManager.getVault(),
+ isUnlocked: !!this.key,
+ isConfirmed: true, // this.configManager.getConfirmed(),
+ isEthConfirmed: this.configManager.getShouldntShowWarning(),
+ unconfTxs: this.configManager.unconfirmedTxs(),
+ transactions: this.configManager.getTxList(),
+ unconfMsgs: messageManager.unconfirmedMsgs(),
+ messages: messageManager.getMsgList(),
+ selectedAddress: this.configManager.getSelectedAccount(),
+ shapeShiftTxList: this.configManager.getShapeShiftTxList(),
+ currentFiat: this.configManager.getCurrentFiat(),
+ conversionRate: this.configManager.getConversionRate(),
+ conversionDate: this.configManager.getConversionDate(),
+ }
+ }
+
+ setStore(ethStore) {
+ this.ethStore = ethStore
+ }
+
+ createNewVault(password, entropy, cb) {
+ const salt = generateSalt()
+ this.configManager.setSalt(salt)
+ this.loadKey(password)
+ .then((key) => {
+ return encryptor.encryptWithKey(key, {})
+ })
+ .then((encryptedString) => {
+ this.configManager.setVault(encryptedString)
+ cb(null, this.getState())
+ })
+ .catch((err) => {
+ cb(err)
+ })
+ }
+
+ submitPassword(password, cb) {
+ this.loadKey(password)
+ .then((key) => {
+ cb(null, this.getState())
+ })
+ .catch((err) => {
+ cb(err)
+ })
+ }
+
+ loadKey(password) {
+ const salt = this.configManager.getSalt()
+ return encryptor.keyFromPassword(password + salt)
+ .then((key) => {
+ this.key = key
+ return key
+ })
+ }
+
+ setSelectedAddress(address, cb) {
+ this.selectedAddress = address
+ cb(null, address)
+ }
+
+ approveTransaction(txId, cb) {
+ cb()
+ }
+
+ cancelTransaction(txId, cb) {
+ if (cb && typeof cb === 'function') {
+ cb()
+ }
+ }
+
+ signMessage(msgParams, cb) {
+ cb()
+ }
+
+ cancelMessage(msgId, cb) {
+ if (cb && typeof cb === 'function') {
+ cb()
+ }
+ }
+
+ setLocked(cb) {
+ cb()
+ }
+
+ exportAccount(address, cb) {
+ cb(null, '0xPrivateKey')
+ }
+
+ saveAccountLabel(account, label, cb) {
+ cb(/* null, label */)
+ }
+
+ tryPassword(password, cb) {
+ cb()
+ }
+
+}
+
+function generateSalt (byteCount) {
+ var view = new Uint8Array(32)
+ global.crypto.getRandomValues(view)
+ var b64encoded = btoa(String.fromCharCode.apply(null, view))
+ return b64encoded
+}
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index cced32670..ae4a84082 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -110,6 +110,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
@@ -249,6 +270,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)
diff --git a/app/scripts/lib/encryptor.js b/app/scripts/lib/encryptor.js
new file mode 100644
index 000000000..3d069ab33
--- /dev/null
+++ b/app/scripts/lib/encryptor.js
@@ -0,0 +1,137 @@
+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,
+}
+
+// Takes a Pojo, returns encrypted 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
+ })
+}
+
+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
+}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 8b593d820..92551d633 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 IdentityStore = require('./keyring-controller')
const messageManager = require('./lib/message-manager')
const HostStore = require('./lib/remote-store.js').HostStore
const Web3 = require('web3')
@@ -11,6 +11,7 @@ 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)
@@ -20,6 +21,7 @@ module.exports = class MetamaskController {
this.provider = this.initializeProvider(opts)
this.ethStore = new EthStore(this.provider)
this.idStore.setStore(this.ethStore)
+ this.getNetwork()
this.messageManager = messageManager
this.publicConfigStore = this.initPublicConfigStore()
@@ -30,11 +32,11 @@ module.exports = class MetamaskController {
this.checkTOSChange()
this.scheduleConversionInterval()
-
}
getState () {
return extend(
+ this.state,
this.ethStore.getState(),
this.idStore.getState(),
this.configManager.getConfig()
@@ -59,7 +61,6 @@ module.exports = class MetamaskController {
// 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),
@@ -67,12 +68,9 @@ module.exports = class MetamaskController {
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),
// coinbase
buyEth: this.buyEth.bind(this),
// shapeshift
@@ -161,11 +159,11 @@ module.exports = class MetamaskController {
var provider = MetaMaskProvider(providerOpts)
var web3 = new Web3(provider)
+ this.web3 = web3
idStore.web3 = web3
- idStore.getNetwork()
provider.on('block', this.processBlock.bind(this))
- provider.on('error', idStore.getNetwork.bind(idStore))
+ provider.on('error', this.getNetwork.bind(this))
return provider
}
@@ -262,8 +260,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()
}
}
@@ -346,13 +344,13 @@ module.exports = class MetamaskController {
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 () {
@@ -363,7 +361,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') {
@@ -379,6 +377,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)