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.
feature/default_network_editable
Dan Finlay 8 years ago
parent 29f07238f4
commit 0584988688
  1. 35
      app/scripts/keyring-controller.js
  2. 125
      app/scripts/keyrings/hd.js
  3. 100
      app/scripts/keyrings/simple.js
  4. 2
      app/scripts/lib/config-manager.js
  5. 2
      app/scripts/lib/controllers/preferences.js
  6. 2
      app/scripts/lib/idStore-migrator.js
  7. 118
      app/scripts/lib/personal-message-manager.js
  8. 28
      app/scripts/lib/sig-util.js
  9. 2
      app/scripts/lib/tx-utils.js
  10. 49
      app/scripts/metamask-controller.js
  11. 3
      package.json
  12. 127
      test/unit/keyrings/hd-test.js
  13. 149
      test/unit/keyrings/simple-test.js
  14. 89
      test/unit/personal-message-manager-test.js

@ -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')

@ -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()
})
})
})
})

@ -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…
Cancel
Save