mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-26 12:29:06 +01:00
Move sigUtil and keyrings to external modules
These external modules now have their own test coverage and build enforcement. This allowed me to somewhat more easily add good tests around our personalSign strategy (held now in [eth-sig-util](https://github.com/flyswatter/eth-sig-util), and allow each of the keyrings to import that, etc.
This commit is contained in:
parent
29f07238f4
commit
0584988688
@ -5,10 +5,10 @@ const EventEmitter = require('events').EventEmitter
|
|||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const filter = require('promise-filter')
|
const filter = require('promise-filter')
|
||||||
const encryptor = require('browser-passworder')
|
const encryptor = require('browser-passworder')
|
||||||
const normalizeAddress = require('./lib/sig-util').normalize
|
const normalizeAddress = require('eth-sig-util').normalize
|
||||||
// Keyrings:
|
// Keyrings:
|
||||||
const SimpleKeyring = require('./keyrings/simple')
|
const SimpleKeyring = require('eth-simple-keyring')
|
||||||
const HdKeyring = require('./keyrings/hd')
|
const HdKeyring = require('eth-hd-keyring')
|
||||||
const keyringTypes = [
|
const keyringTypes = [
|
||||||
SimpleKeyring,
|
SimpleKeyring,
|
||||||
HdKeyring,
|
HdKeyring,
|
||||||
@ -262,6 +262,35 @@ class KeyringController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recover Personal Message
|
||||||
|
// @object msgParams
|
||||||
|
//
|
||||||
|
// returns Promise(@buffer signer)
|
||||||
|
//
|
||||||
|
// recovers a signature of the prefixed-style personalMessage signature.
|
||||||
|
recoverPersonalMessage (msgParams) {
|
||||||
|
const address = normalizeAddress(msgParams.from)
|
||||||
|
return this.getKeyringForAccount(address)
|
||||||
|
.then((keyring) => {
|
||||||
|
return keyring.recoverPersonalMessage(address, msgParams.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
//
|
//
|
||||||
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
|
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
const EventEmitter = require('events').EventEmitter
|
|
||||||
const hdkey = require('ethereumjs-wallet/hdkey')
|
|
||||||
const bip39 = require('bip39')
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
|
||||||
|
|
||||||
// *Internal Deps
|
|
||||||
const sigUtil = require('../lib/sig-util')
|
|
||||||
|
|
||||||
// Options:
|
|
||||||
const hdPathString = `m/44'/60'/0'/0`
|
|
||||||
const type = 'HD Key Tree'
|
|
||||||
|
|
||||||
class HdKeyring extends EventEmitter {
|
|
||||||
|
|
||||||
/* PUBLIC METHODS */
|
|
||||||
|
|
||||||
constructor (opts = {}) {
|
|
||||||
super()
|
|
||||||
this.type = type
|
|
||||||
this.deserialize(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize () {
|
|
||||||
return Promise.resolve({
|
|
||||||
mnemonic: this.mnemonic,
|
|
||||||
numberOfAccounts: this.wallets.length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
deserialize (opts = {}) {
|
|
||||||
this.opts = opts || {}
|
|
||||||
this.wallets = []
|
|
||||||
this.mnemonic = null
|
|
||||||
this.root = null
|
|
||||||
|
|
||||||
if (opts.mnemonic) {
|
|
||||||
this._initFromMnemonic(opts.mnemonic)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.numberOfAccounts) {
|
|
||||||
return this.addAccounts(opts.numberOfAccounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve([])
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
const hexWallets = newWallets.map(w => w.getAddress().toString('hex'))
|
|
||||||
return Promise.resolve(hexWallets)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAccounts () {
|
|
||||||
return Promise.resolve(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 Promise.resolve(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For eth_sign, we need to sign transactions:
|
|
||||||
// hd
|
|
||||||
signMessage (withAccount, data) {
|
|
||||||
const wallet = this._getWalletForAccount(withAccount)
|
|
||||||
const message = ethUtil.stripHexPrefix(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 Promise.resolve(rawMsgSig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For eth_sign, we need to sign transactions:
|
|
||||||
newGethSignMessage (withAccount, msgHex) {
|
|
||||||
const wallet = this._getWalletForAccount(withAccount)
|
|
||||||
const privKey = wallet.getPrivateKey()
|
|
||||||
const msgBuffer = ethUtil.toBuffer(msgHex)
|
|
||||||
const msgHash = ethUtil.hashPersonalMessage(msgBuffer)
|
|
||||||
const msgSig = ethUtil.ecsign(msgHash, privKey)
|
|
||||||
const rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
|
|
||||||
return Promise.resolve(rawMsgSig)
|
|
||||||
}
|
|
||||||
|
|
||||||
exportAccount (address) {
|
|
||||||
const wallet = this._getWalletForAccount(address)
|
|
||||||
return Promise.resolve(wallet.getPrivateKey().toString('hex'))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* PRIVATE METHODS */
|
|
||||||
|
|
||||||
_initFromMnemonic (mnemonic) {
|
|
||||||
this.mnemonic = mnemonic
|
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
|
||||||
this.hdWallet = hdkey.fromMasterSeed(seed)
|
|
||||||
this.root = this.hdWallet.derivePath(hdPathString)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_getWalletForAccount (account) {
|
|
||||||
const targetAddress = sigUtil.normalize(account)
|
|
||||||
return this.wallets.find((w) => {
|
|
||||||
const address = w.getAddress().toString('hex')
|
|
||||||
return ((address === targetAddress) ||
|
|
||||||
(sigUtil.normalize(address) === targetAddress))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HdKeyring.type = type
|
|
||||||
module.exports = HdKeyring
|
|
@ -1,100 +0,0 @@
|
|||||||
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')
|
|
||||||
|
|
||||||
class SimpleKeyring extends EventEmitter {
|
|
||||||
|
|
||||||
/* PUBLIC METHODS */
|
|
||||||
|
|
||||||
constructor (opts) {
|
|
||||||
super()
|
|
||||||
this.type = type
|
|
||||||
this.opts = opts || {}
|
|
||||||
this.wallets = []
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize () {
|
|
||||||
return Promise.resolve(this.wallets.map(w => w.getPrivateKey().toString('hex')))
|
|
||||||
}
|
|
||||||
|
|
||||||
deserialize (privateKeys = []) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
this.wallets = privateKeys.map((privateKey) => {
|
|
||||||
const stripped = ethUtil.stripHexPrefix(privateKey)
|
|
||||||
const buffer = new Buffer(stripped, 'hex')
|
|
||||||
const wallet = Wallet.fromPrivateKey(buffer)
|
|
||||||
return wallet
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
addAccounts (n = 1) {
|
|
||||||
var newWallets = []
|
|
||||||
for (var i = 0; i < n; i++) {
|
|
||||||
newWallets.push(Wallet.generate())
|
|
||||||
}
|
|
||||||
this.wallets = this.wallets.concat(newWallets)
|
|
||||||
const hexWallets = newWallets.map(w => ethUtil.bufferToHex(w.getAddress()))
|
|
||||||
return Promise.resolve(hexWallets)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAccounts () {
|
|
||||||
return Promise.resolve(this.wallets.map(w => ethUtil.bufferToHex(w.getAddress())))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 Promise.resolve(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For eth_sign, we need to sign transactions:
|
|
||||||
signMessage (withAccount, data) {
|
|
||||||
const wallet = this._getWalletForAccount(withAccount)
|
|
||||||
const message = ethUtil.stripHexPrefix(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 Promise.resolve(rawMsgSig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For eth_sign, we need to sign transactions:
|
|
||||||
|
|
||||||
newGethSignMessage (withAccount, msgHex) {
|
|
||||||
const wallet = this._getWalletForAccount(withAccount)
|
|
||||||
const privKey = wallet.getPrivateKey()
|
|
||||||
const msgBuffer = ethUtil.toBuffer(msgHex)
|
|
||||||
const msgHash = ethUtil.hashPersonalMessage(msgBuffer)
|
|
||||||
const msgSig = ethUtil.ecsign(msgHash, privKey)
|
|
||||||
const rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))
|
|
||||||
return Promise.resolve(rawMsgSig)
|
|
||||||
}
|
|
||||||
|
|
||||||
exportAccount (address) {
|
|
||||||
const wallet = this._getWalletForAccount(address)
|
|
||||||
return Promise.resolve(wallet.getPrivateKey().toString('hex'))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* PRIVATE METHODS */
|
|
||||||
|
|
||||||
_getWalletForAccount (account) {
|
|
||||||
const address = sigUtil.normalize(account)
|
|
||||||
let wallet = this.wallets.find(w => ethUtil.bufferToHex(w.getAddress()) === address)
|
|
||||||
if (!wallet) throw new Error('Simple Keyring - Unable to find matching address.')
|
|
||||||
return wallet
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleKeyring.type = type
|
|
||||||
module.exports = SimpleKeyring
|
|
@ -1,6 +1,6 @@
|
|||||||
const MetamaskConfig = require('../config.js')
|
const MetamaskConfig = require('../config.js')
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const normalize = require('./sig-util').normalize
|
const normalize = require('eth-sig-util').normalize
|
||||||
|
|
||||||
const TESTNET_RPC = MetamaskConfig.network.testnet
|
const TESTNET_RPC = MetamaskConfig.network.testnet
|
||||||
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const normalizeAddress = require('../sig-util').normalize
|
const normalizeAddress = require('eth-sig-util').normalize
|
||||||
|
|
||||||
class PreferencesController {
|
class PreferencesController {
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const IdentityStore = require('./idStore')
|
const IdentityStore = require('./idStore')
|
||||||
const HdKeyring = require('../keyrings/hd')
|
const HdKeyring = require('../keyrings/hd')
|
||||||
const sigUtil = require('./sig-util')
|
const sigUtil = require('eth-sig-util')
|
||||||
const normalize = sigUtil.normalize
|
const normalize = sigUtil.normalize
|
||||||
const denodeify = require('denodeify')
|
const denodeify = require('denodeify')
|
||||||
|
|
||||||
|
118
app/scripts/lib/personal-message-manager.js
Normal file
118
app/scripts/lib/personal-message-manager.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
const EventEmitter = require('events')
|
||||||
|
const ObservableStore = require('obs-store')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const createId = require('./random-id')
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = class MessageManager extends EventEmitter{
|
||||||
|
constructor (opts) {
|
||||||
|
super()
|
||||||
|
this.memStore = new ObservableStore({
|
||||||
|
unapprovedPersonalMsgs: {},
|
||||||
|
unapprovedPersonalMsgCount: 0,
|
||||||
|
})
|
||||||
|
this.messages = []
|
||||||
|
}
|
||||||
|
|
||||||
|
get unapprovedPersonalMsgCount () {
|
||||||
|
return Object.keys(this.getUnapprovedMsgs()).length
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnapprovedMsgs () {
|
||||||
|
return this.messages.filter(msg => msg.status === 'unapproved')
|
||||||
|
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
addUnapprovedMessage (msgParams) {
|
||||||
|
msgParams.data = normalizeMsgData(msgParams.data)
|
||||||
|
// 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: 'unapproved',
|
||||||
|
}
|
||||||
|
this.addMsg(msgData)
|
||||||
|
|
||||||
|
// signal update
|
||||||
|
this.emit('update')
|
||||||
|
return msgId
|
||||||
|
}
|
||||||
|
|
||||||
|
addMsg (msg) {
|
||||||
|
this.messages.push(msg)
|
||||||
|
this._saveMsgList()
|
||||||
|
}
|
||||||
|
|
||||||
|
getMsg (msgId) {
|
||||||
|
return this.messages.find(msg => msg.id === msgId)
|
||||||
|
}
|
||||||
|
|
||||||
|
approveMessage (msgParams) {
|
||||||
|
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||||
|
return this.prepMsgForSigning(msgParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
setMsgStatusApproved (msgId) {
|
||||||
|
this._setMsgStatus(msgId, 'approved')
|
||||||
|
}
|
||||||
|
|
||||||
|
setMsgStatusSigned (msgId, rawSig) {
|
||||||
|
const msg = this.getMsg(msgId)
|
||||||
|
msg.rawSig = rawSig
|
||||||
|
this._updateMsg(msg)
|
||||||
|
this._setMsgStatus(msgId, 'signed')
|
||||||
|
}
|
||||||
|
|
||||||
|
prepMsgForSigning (msgParams) {
|
||||||
|
delete msgParams.metamaskId
|
||||||
|
return Promise.resolve(msgParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectMsg (msgId) {
|
||||||
|
this._setMsgStatus(msgId, 'rejected')
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// PRIVATE METHODS
|
||||||
|
//
|
||||||
|
|
||||||
|
_setMsgStatus (msgId, status) {
|
||||||
|
const msg = this.getMsg(msgId)
|
||||||
|
if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".')
|
||||||
|
msg.status = status
|
||||||
|
this._updateMsg(msg)
|
||||||
|
this.emit(`${msgId}:${status}`, msg)
|
||||||
|
if (status === 'rejected' || status === 'signed') {
|
||||||
|
this.emit(`${msgId}:finished`, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateMsg (msg) {
|
||||||
|
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
this.messages[index] = msg
|
||||||
|
}
|
||||||
|
this._saveMsgList()
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveMsgList () {
|
||||||
|
const unapprovedPersonalMsgs = this.getUnapprovedMsgs()
|
||||||
|
const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs).length
|
||||||
|
this.memStore.updateState({ unapprovedPersonalMsgs, unapprovedPersonalMsgCount })
|
||||||
|
this.emit('updateBadge')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMsgData(data) {
|
||||||
|
if (data.slice(0, 2) === '0x') {
|
||||||
|
// data is already hex
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
// data is unicode, convert to hex
|
||||||
|
return ethUtil.bufferToHex(new Buffer(data, 'utf8'))
|
||||||
|
}
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ const async = require('async')
|
|||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const Transaction = require('ethereumjs-tx')
|
const Transaction = require('ethereumjs-tx')
|
||||||
const normalize = require('./sig-util').normalize
|
const normalize = require('eth-sig-util').normalize
|
||||||
const BN = ethUtil.BN
|
const BN = ethUtil.BN
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -16,6 +16,7 @@ const CurrencyController = require('./lib/controllers/currency')
|
|||||||
const NoticeController = require('./notice-controller')
|
const NoticeController = require('./notice-controller')
|
||||||
const ShapeShiftController = require('./lib/controllers/shapeshift')
|
const ShapeShiftController = require('./lib/controllers/shapeshift')
|
||||||
const MessageManager = require('./lib/message-manager')
|
const MessageManager = require('./lib/message-manager')
|
||||||
|
const PersonalMessageManager = require('./lib/personal-message-manager')
|
||||||
const TxManager = require('./transaction-manager')
|
const TxManager = require('./transaction-manager')
|
||||||
const ConfigManager = require('./lib/config-manager')
|
const ConfigManager = require('./lib/config-manager')
|
||||||
const extension = require('./lib/extension')
|
const extension = require('./lib/extension')
|
||||||
@ -23,6 +24,7 @@ const autoFaucet = require('./lib/auto-faucet')
|
|||||||
const nodeify = require('./lib/nodeify')
|
const nodeify = require('./lib/nodeify')
|
||||||
const IdStoreMigrator = require('./lib/idStore-migrator')
|
const IdStoreMigrator = require('./lib/idStore-migrator')
|
||||||
const accountImporter = require('./account-import-strategies')
|
const accountImporter = require('./account-import-strategies')
|
||||||
|
const sigUtil = require('eth-sig-util')
|
||||||
|
|
||||||
const version = require('../manifest.json').version
|
const version = require('../manifest.json').version
|
||||||
|
|
||||||
@ -105,6 +107,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
this.lookupNetwork()
|
this.lookupNetwork()
|
||||||
this.messageManager = new MessageManager()
|
this.messageManager = new MessageManager()
|
||||||
|
this.personalMessageManager = new PersonalMessageManager()
|
||||||
this.publicConfigStore = this.initPublicConfigStore()
|
this.publicConfigStore = this.initPublicConfigStore()
|
||||||
|
|
||||||
// TEMPORARY UNTIL FULL DEPRECATION:
|
// TEMPORARY UNTIL FULL DEPRECATION:
|
||||||
@ -163,8 +166,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
},
|
},
|
||||||
// tx signing
|
// tx signing
|
||||||
processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb),
|
processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb),
|
||||||
// msg signing
|
// old style msg signing
|
||||||
processMessage: this.newUnsignedMessage.bind(this),
|
processMessage: this.newUnsignedMessage.bind(this),
|
||||||
|
|
||||||
|
// new style msg signing
|
||||||
|
approvePersonalMessage: this.approvePersonalMessage.bind(this),
|
||||||
|
signPersonalMessage: this.signPersonalMessage.bind(this),
|
||||||
|
personalRecoverSigner: this.personalRecoverSigner.bind(this),
|
||||||
})
|
})
|
||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
@ -209,6 +217,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
this.ethStore.getState(),
|
this.ethStore.getState(),
|
||||||
this.txManager.memStore.getState(),
|
this.txManager.memStore.getState(),
|
||||||
this.messageManager.memStore.getState(),
|
this.messageManager.memStore.getState(),
|
||||||
|
this.personalMessageManager.memStore.getState(),
|
||||||
this.keyringController.memStore.getState(),
|
this.keyringController.memStore.getState(),
|
||||||
this.preferencesController.store.getState(),
|
this.preferencesController.store.getState(),
|
||||||
this.currencyController.store.getState(),
|
this.currencyController.store.getState(),
|
||||||
@ -449,6 +458,44 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
)(cb)
|
)(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefixed Style Message Signing Methods:
|
||||||
|
approvePersonalMessage (cb) {
|
||||||
|
let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
||||||
|
this.sendUpdate()
|
||||||
|
this.opts.showUnconfirmedMessage()
|
||||||
|
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
||||||
|
switch (data.status) {
|
||||||
|
case 'signed':
|
||||||
|
return cb(null, data.rawSig)
|
||||||
|
case 'rejected':
|
||||||
|
return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
|
||||||
|
default:
|
||||||
|
return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
signPersonalMessage (msgParams) {
|
||||||
|
const msgId = msgParams.metamaskId
|
||||||
|
// sets the status op the message to 'approved'
|
||||||
|
// and removes the metamaskId for signing
|
||||||
|
return this.personalMessageManager.approveMessage(msgParams)
|
||||||
|
.then((cleanMsgParams) => {
|
||||||
|
// signs the message
|
||||||
|
return this.keyringController.signPersonalMessage(cleanMsgParams)
|
||||||
|
})
|
||||||
|
.then((rawSig) => {
|
||||||
|
// tells the listener that the message has been signed
|
||||||
|
// and can be returned to the dapp
|
||||||
|
this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
|
||||||
|
return rawSig
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
personalRecoverSigner (msgParams) {
|
||||||
|
const recovered = sigUtil.recoverPersonalSignature(msgParams)
|
||||||
|
return Promise.resolve(recovered)
|
||||||
|
}
|
||||||
|
|
||||||
markAccountsFound (cb) {
|
markAccountsFound (cb) {
|
||||||
this.configManager.setLostAccounts([])
|
this.configManager.setLostAccounts([])
|
||||||
|
@ -52,8 +52,11 @@
|
|||||||
"end-of-stream": "^1.1.0",
|
"end-of-stream": "^1.1.0",
|
||||||
"ensnare": "^1.0.0",
|
"ensnare": "^1.0.0",
|
||||||
"eth-bin-to-ops": "^1.0.1",
|
"eth-bin-to-ops": "^1.0.1",
|
||||||
|
"eth-hd-keyring": "^1.1.1",
|
||||||
"eth-lightwallet": "^2.3.3",
|
"eth-lightwallet": "^2.3.3",
|
||||||
"eth-query": "^1.0.3",
|
"eth-query": "^1.0.3",
|
||||||
|
"eth-sig-util": "^1.1.1",
|
||||||
|
"eth-simple-keyring": "^1.1.0",
|
||||||
"ethereumjs-tx": "^1.0.0",
|
"ethereumjs-tx": "^1.0.0",
|
||||||
"ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
"ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||||
"ethereumjs-wallet": "^0.6.0",
|
"ethereumjs-wallet": "^0.6.0",
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
const assert = require('assert')
|
|
||||||
const extend = require('xtend')
|
|
||||||
const HdKeyring = require('../../../app/scripts/keyrings/hd')
|
|
||||||
|
|
||||||
// Sample account:
|
|
||||||
const privKeyHex = 'b8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952'
|
|
||||||
|
|
||||||
const sampleMnemonic = 'finish oppose decorate face calm tragic certain desk hour urge dinosaur mango'
|
|
||||||
const firstAcct = '1c96099350f13d558464ec79b9be4445aa0ef579'
|
|
||||||
const secondAcct = '1b00aed43a693f3a957f9feb5cc08afa031e37a0'
|
|
||||||
|
|
||||||
describe('hd-keyring', function() {
|
|
||||||
|
|
||||||
let keyring
|
|
||||||
beforeEach(function() {
|
|
||||||
keyring = new HdKeyring()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('constructor', function(done) {
|
|
||||||
keyring = new HdKeyring({
|
|
||||||
mnemonic: sampleMnemonic,
|
|
||||||
numberOfAccounts: 2,
|
|
||||||
})
|
|
||||||
|
|
||||||
const accounts = keyring.getAccounts()
|
|
||||||
.then((accounts) => {
|
|
||||||
assert.equal(accounts[0], firstAcct)
|
|
||||||
assert.equal(accounts[1], secondAcct)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Keyring.type', function() {
|
|
||||||
it('is a class property that returns the type string.', function() {
|
|
||||||
const type = HdKeyring.type
|
|
||||||
assert.equal(typeof type, 'string')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#type', function() {
|
|
||||||
it('returns the correct value', function() {
|
|
||||||
const type = keyring.type
|
|
||||||
const correct = HdKeyring.type
|
|
||||||
assert.equal(type, correct)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#serialize empty wallets.', function() {
|
|
||||||
it('serializes a new mnemonic', function() {
|
|
||||||
keyring.serialize()
|
|
||||||
.then((output) => {
|
|
||||||
assert.equal(output.numberOfAccounts, 0)
|
|
||||||
assert.equal(output.mnemonic, null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#deserialize a private key', function() {
|
|
||||||
it('serializes what it deserializes', function(done) {
|
|
||||||
keyring.deserialize({
|
|
||||||
mnemonic: sampleMnemonic,
|
|
||||||
numberOfAccounts: 1
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
assert.equal(keyring.wallets.length, 1, 'restores two accounts')
|
|
||||||
return keyring.addAccounts(1)
|
|
||||||
}).then(() => {
|
|
||||||
return keyring.getAccounts()
|
|
||||||
}).then((accounts) => {
|
|
||||||
assert.equal(accounts[0], firstAcct)
|
|
||||||
assert.equal(accounts[1], secondAcct)
|
|
||||||
assert.equal(accounts.length, 2)
|
|
||||||
|
|
||||||
return keyring.serialize()
|
|
||||||
}).then((serialized) => {
|
|
||||||
assert.equal(serialized.mnemonic, sampleMnemonic)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#addAccounts', function() {
|
|
||||||
describe('with no arguments', function() {
|
|
||||||
it('creates a single wallet', function(done) {
|
|
||||||
keyring.addAccounts()
|
|
||||||
.then(() => {
|
|
||||||
assert.equal(keyring.wallets.length, 1)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with a numeric argument', function() {
|
|
||||||
it('creates that number of wallets', function(done) {
|
|
||||||
keyring.addAccounts(3)
|
|
||||||
.then(() => {
|
|
||||||
assert.equal(keyring.wallets.length, 3)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#getAccounts', function() {
|
|
||||||
it('calls getAddress on each wallet', function(done) {
|
|
||||||
|
|
||||||
// Push a mock wallet
|
|
||||||
const desiredOutput = 'foo'
|
|
||||||
keyring.wallets.push({
|
|
||||||
getAddress() {
|
|
||||||
return {
|
|
||||||
toString() {
|
|
||||||
return desiredOutput
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const output = keyring.getAccounts()
|
|
||||||
.then((output) => {
|
|
||||||
assert.equal(output[0], desiredOutput)
|
|
||||||
assert.equal(output.length, 1)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,149 +0,0 @@
|
|||||||
const assert = require('assert')
|
|
||||||
const extend = require('xtend')
|
|
||||||
const Web3 = require('web3')
|
|
||||||
const web3 = new Web3()
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
|
||||||
const SimpleKeyring = require('../../../app/scripts/keyrings/simple')
|
|
||||||
const TYPE_STR = 'Simple Key Pair'
|
|
||||||
|
|
||||||
// Sample account:
|
|
||||||
const privKeyHex = 'b8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952'
|
|
||||||
|
|
||||||
describe('simple-keyring', function() {
|
|
||||||
|
|
||||||
let keyring
|
|
||||||
beforeEach(function() {
|
|
||||||
keyring = new SimpleKeyring()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Keyring.type', function() {
|
|
||||||
it('is a class property that returns the type string.', function() {
|
|
||||||
const type = SimpleKeyring.type
|
|
||||||
assert.equal(type, TYPE_STR)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#type', function() {
|
|
||||||
it('returns the correct value', function() {
|
|
||||||
const type = keyring.type
|
|
||||||
assert.equal(type, TYPE_STR)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#serialize empty wallets.', function() {
|
|
||||||
it('serializes an empty array', function(done) {
|
|
||||||
keyring.serialize()
|
|
||||||
.then((output) => {
|
|
||||||
assert.deepEqual(output, [])
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#deserialize a private key', function() {
|
|
||||||
it('serializes what it deserializes', function() {
|
|
||||||
keyring.deserialize([privKeyHex])
|
|
||||||
.then(() => {
|
|
||||||
assert.equal(keyring.wallets.length, 1, 'has one wallet')
|
|
||||||
const serialized = keyring.serialize()
|
|
||||||
assert.equal(serialized[0], privKeyHex)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#signMessage', function() {
|
|
||||||
const address = '0x9858e7d8b79fc3e6d989636721584498926da38a'
|
|
||||||
const message = '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0'
|
|
||||||
const privateKey = '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18'
|
|
||||||
const expectedResult = '0x28fcb6768e5110144a55b2e6ce9d1ea5a58103033632d272d2b5cf506906f7941a00b539383fd872109633d8c71c404e13dba87bc84166ee31b0e36061a69e161c'
|
|
||||||
|
|
||||||
it('passes the dennis test', function(done) {
|
|
||||||
keyring.deserialize([ privateKey ])
|
|
||||||
.then(() => {
|
|
||||||
return keyring.signMessage(address, message)
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
assert.equal(result, expectedResult)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('reliably can decode messages it signs', function (done) {
|
|
||||||
|
|
||||||
const message = 'hello there!'
|
|
||||||
const msgHashHex = web3.sha3(message)
|
|
||||||
let address
|
|
||||||
let addresses = []
|
|
||||||
|
|
||||||
keyring.deserialize([ privateKey ])
|
|
||||||
.then(() => {
|
|
||||||
keyring.addAccounts(9)
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return keyring.getAccounts()
|
|
||||||
})
|
|
||||||
.then((addrs) => {
|
|
||||||
addresses = addrs
|
|
||||||
return Promise.all(addresses.map((address) => {
|
|
||||||
return keyring.signMessage(address, msgHashHex)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.then((signatures) => {
|
|
||||||
|
|
||||||
signatures.forEach((sgn, index) => {
|
|
||||||
const address = addresses[index]
|
|
||||||
|
|
||||||
var r = ethUtil.toBuffer(sgn.slice(0,66))
|
|
||||||
var s = ethUtil.toBuffer('0x' + sgn.slice(66,130))
|
|
||||||
var v = ethUtil.bufferToInt(ethUtil.toBuffer('0x' + sgn.slice(130,132)))
|
|
||||||
var m = ethUtil.toBuffer(msgHashHex)
|
|
||||||
var pub = ethUtil.ecrecover(m, v, r, s)
|
|
||||||
var adr = '0x' + ethUtil.pubToAddress(pub).toString('hex')
|
|
||||||
|
|
||||||
assert.equal(adr, address, 'recovers address from signature correctly')
|
|
||||||
})
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#addAccounts', function() {
|
|
||||||
describe('with no arguments', function() {
|
|
||||||
it('creates a single wallet', function() {
|
|
||||||
keyring.addAccounts()
|
|
||||||
.then(() => {
|
|
||||||
assert.equal(keyring.wallets.length, 1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with a numeric argument', function() {
|
|
||||||
it('creates that number of wallets', function() {
|
|
||||||
keyring.addAccounts(3)
|
|
||||||
.then(() => {
|
|
||||||
assert.equal(keyring.wallets.length, 3)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#getAccounts', function() {
|
|
||||||
it('calls getAddress on each wallet', function(done) {
|
|
||||||
|
|
||||||
// Push a mock wallet
|
|
||||||
const desiredOutput = '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761'
|
|
||||||
keyring.wallets.push({
|
|
||||||
getAddress() {
|
|
||||||
return ethUtil.toBuffer(desiredOutput)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
keyring.getAccounts()
|
|
||||||
.then((output) => {
|
|
||||||
assert.equal(output[0], desiredOutput)
|
|
||||||
assert.equal(output.length, 1)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
89
test/unit/personal-message-manager-test.js
Normal file
89
test/unit/personal-message-manager-test.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const extend = require('xtend')
|
||||||
|
const EventEmitter = require('events')
|
||||||
|
|
||||||
|
const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager')
|
||||||
|
|
||||||
|
describe('Transaction Manager', function() {
|
||||||
|
let messageManager
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
messageManager = new PersonalMessageManager()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getMsgList', function() {
|
||||||
|
it('when new should return empty array', function() {
|
||||||
|
var result = messageManager.messages
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 0)
|
||||||
|
})
|
||||||
|
it('should also return transactions from local storage if any', function() {
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#addMsg', function() {
|
||||||
|
it('adds a Msg returned in getMsgList', function() {
|
||||||
|
var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' }
|
||||||
|
messageManager.addMsg(Msg)
|
||||||
|
var result = messageManager.messages
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 1)
|
||||||
|
assert.equal(result[0].id, 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#setMsgStatusApproved', function() {
|
||||||
|
it('sets the Msg status to approved', function() {
|
||||||
|
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
|
||||||
|
messageManager.addMsg(Msg)
|
||||||
|
messageManager.setMsgStatusApproved(1)
|
||||||
|
var result = messageManager.messages
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 1)
|
||||||
|
assert.equal(result[0].status, 'approved')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#rejectMsg', function() {
|
||||||
|
it('sets the Msg status to rejected', function() {
|
||||||
|
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
|
||||||
|
messageManager.addMsg(Msg)
|
||||||
|
messageManager.rejectMsg(1)
|
||||||
|
var result = messageManager.messages
|
||||||
|
assert.ok(Array.isArray(result))
|
||||||
|
assert.equal(result.length, 1)
|
||||||
|
assert.equal(result[0].status, 'rejected')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#_updateMsg', function() {
|
||||||
|
it('replaces the Msg with the same id', function() {
|
||||||
|
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
|
||||||
|
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
|
||||||
|
messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
|
||||||
|
var result = messageManager.getMsg('1')
|
||||||
|
assert.equal(result.hash, 'foo')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getUnapprovedMsgs', function() {
|
||||||
|
it('returns unapproved Msgs in a hash', function() {
|
||||||
|
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
|
||||||
|
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
|
||||||
|
let result = messageManager.getUnapprovedMsgs()
|
||||||
|
assert.equal(typeof result, 'object')
|
||||||
|
assert.equal(result['1'].status, 'unapproved')
|
||||||
|
assert.equal(result['2'], undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getMsg', function() {
|
||||||
|
it('returns a Msg with the requested id', function() {
|
||||||
|
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
|
||||||
|
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
|
||||||
|
assert.equal(messageManager.getMsg('1').status, 'unapproved')
|
||||||
|
assert.equal(messageManager.getMsg('2').status, 'approved')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user