diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index b30161003..8c379b5b9 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -5,10 +5,10 @@ const EventEmitter = require('events').EventEmitter const ObservableStore = require('obs-store') const filter = require('promise-filter') const encryptor = require('browser-passworder') -const normalizeAddress = require('./lib/sig-util').normalize +const normalizeAddress = require('eth-sig-util').normalize // Keyrings: -const SimpleKeyring = require('./keyrings/simple') -const HdKeyring = require('./keyrings/hd') +const SimpleKeyring = require('eth-simple-keyring') +const HdKeyring = require('eth-hd-keyring') const keyringTypes = [ SimpleKeyring, 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 // // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER diff --git a/app/scripts/keyrings/hd.js b/app/scripts/keyrings/hd.js deleted file mode 100644 index 3a66f7868..000000000 --- a/app/scripts/keyrings/hd.js +++ /dev/null @@ -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 diff --git a/app/scripts/keyrings/simple.js b/app/scripts/keyrings/simple.js deleted file mode 100644 index 82881aa2d..000000000 --- a/app/scripts/keyrings/simple.js +++ /dev/null @@ -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 diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 6267eab68..ea5e49b19 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,6 +1,6 @@ const MetamaskConfig = require('../config.js') 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 MAINNET_RPC = MetamaskConfig.network.mainnet diff --git a/app/scripts/lib/controllers/preferences.js b/app/scripts/lib/controllers/preferences.js index dc9464c4e..c5e93a5b9 100644 --- a/app/scripts/lib/controllers/preferences.js +++ b/app/scripts/lib/controllers/preferences.js @@ -1,5 +1,5 @@ const ObservableStore = require('obs-store') -const normalizeAddress = require('../sig-util').normalize +const normalizeAddress = require('eth-sig-util').normalize class PreferencesController { diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js index 655aed0af..1485beb48 100644 --- a/app/scripts/lib/idStore-migrator.js +++ b/app/scripts/lib/idStore-migrator.js @@ -1,6 +1,6 @@ const IdentityStore = require('./idStore') const HdKeyring = require('../keyrings/hd') -const sigUtil = require('./sig-util') +const sigUtil = require('eth-sig-util') const normalize = sigUtil.normalize const denodeify = require('denodeify') diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js new file mode 100644 index 000000000..72dd1da96 --- /dev/null +++ b/app/scripts/lib/personal-message-manager.js @@ -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')) + } +} diff --git a/app/scripts/lib/sig-util.js b/app/scripts/lib/sig-util.js deleted file mode 100644 index 193dda381..000000000 --- a/app/scripts/lib/sig-util.js +++ /dev/null @@ -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 -} diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 5116cb93b..240a6ab47 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -2,7 +2,7 @@ const async = require('async') const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') -const normalize = require('./sig-util').normalize +const normalize = require('eth-sig-util').normalize const BN = ethUtil.BN /* diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 29b13dc62..62242bd83 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -16,6 +16,7 @@ const CurrencyController = require('./lib/controllers/currency') const NoticeController = require('./notice-controller') const ShapeShiftController = require('./lib/controllers/shapeshift') const MessageManager = require('./lib/message-manager') +const PersonalMessageManager = require('./lib/personal-message-manager') const TxManager = require('./transaction-manager') const ConfigManager = require('./lib/config-manager') const extension = require('./lib/extension') @@ -23,6 +24,7 @@ const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') const IdStoreMigrator = require('./lib/idStore-migrator') const accountImporter = require('./account-import-strategies') +const sigUtil = require('eth-sig-util') const version = require('../manifest.json').version @@ -105,6 +107,7 @@ module.exports = class MetamaskController extends EventEmitter { this.lookupNetwork() this.messageManager = new MessageManager() + this.personalMessageManager = new PersonalMessageManager() this.publicConfigStore = this.initPublicConfigStore() // TEMPORARY UNTIL FULL DEPRECATION: @@ -163,8 +166,13 @@ module.exports = class MetamaskController extends EventEmitter { }, // tx signing processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb), - // msg signing + // old style msg signing 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 } @@ -209,6 +217,7 @@ module.exports = class MetamaskController extends EventEmitter { this.ethStore.getState(), this.txManager.memStore.getState(), this.messageManager.memStore.getState(), + this.personalMessageManager.memStore.getState(), this.keyringController.memStore.getState(), this.preferencesController.store.getState(), this.currencyController.store.getState(), @@ -449,6 +458,44 @@ module.exports = class MetamaskController extends EventEmitter { )(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) { this.configManager.setLostAccounts([]) diff --git a/package.json b/package.json index 9f56d8b12..1542853ad 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,11 @@ "end-of-stream": "^1.1.0", "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", + "eth-hd-keyring": "^1.1.1", "eth-lightwallet": "^2.3.3", "eth-query": "^1.0.3", + "eth-sig-util": "^1.1.1", + "eth-simple-keyring": "^1.1.0", "ethereumjs-tx": "^1.0.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", diff --git a/test/unit/keyrings/hd-test.js b/test/unit/keyrings/hd-test.js deleted file mode 100644 index dfc0ec908..000000000 --- a/test/unit/keyrings/hd-test.js +++ /dev/null @@ -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() - }) - }) - }) -}) diff --git a/test/unit/keyrings/simple-test.js b/test/unit/keyrings/simple-test.js deleted file mode 100644 index ba7dd448a..000000000 --- a/test/unit/keyrings/simple-test.js +++ /dev/null @@ -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() - }) - }) - }) -}) diff --git a/test/unit/personal-message-manager-test.js b/test/unit/personal-message-manager-test.js new file mode 100644 index 000000000..657d5e675 --- /dev/null +++ b/test/unit/personal-message-manager-test.js @@ -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') + }) + }) +})