diff options
author | Dan Finlay <dan@danfinlay.com> | 2017-02-22 06:25:47 +0800 |
---|---|---|
committer | Dan Finlay <dan@danfinlay.com> | 2017-02-22 06:25:47 +0800 |
commit | 0584988688a471698e9b3ad05cb0597f0270ea9e (patch) | |
tree | bdcbb009636dcdea67abcc28238023dd8bf7e882 | |
parent | 29f07238f443007a03c968349af4d3fca7e10522 (diff) | |
download | dexon-wallet-0584988688a471698e9b3ad05cb0597f0270ea9e.tar.gz dexon-wallet-0584988688a471698e9b3ad05cb0597f0270ea9e.tar.zst dexon-wallet-0584988688a471698e9b3ad05cb0597f0270ea9e.zip |
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.
-rw-r--r-- | app/scripts/keyring-controller.js | 35 | ||||
-rw-r--r-- | app/scripts/keyrings/hd.js | 125 | ||||
-rw-r--r-- | app/scripts/keyrings/simple.js | 100 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 2 | ||||
-rw-r--r-- | app/scripts/lib/controllers/preferences.js | 2 | ||||
-rw-r--r-- | app/scripts/lib/idStore-migrator.js | 2 | ||||
-rw-r--r-- | app/scripts/lib/personal-message-manager.js | 118 | ||||
-rw-r--r-- | app/scripts/lib/sig-util.js | 28 | ||||
-rw-r--r-- | app/scripts/lib/tx-utils.js | 2 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 49 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | test/unit/keyrings/hd-test.js | 127 | ||||
-rw-r--r-- | test/unit/keyrings/simple-test.js | 149 | ||||
-rw-r--r-- | test/unit/personal-message-manager-test.js | 89 |
14 files changed, 294 insertions, 537 deletions
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index b3016100..8c379b5b 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 3a66f786..00000000 --- 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 82881aa2..00000000 --- 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 6267eab6..ea5e49b1 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 dc9464c4..c5e93a5b 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 655aed0a..1485beb4 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 00000000..72dd1da9 --- /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 193dda38..00000000 --- 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 5116cb93..240a6ab4 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 29b13dc6..62242bd8 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 9f56d8b1..1542853a 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 dfc0ec90..00000000 --- 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 ba7dd448..00000000 --- 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 00000000..657d5e67 --- /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') + }) + }) +}) |