diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 0ad9c0ff5..ec15936b1 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -5,10 +5,11 @@ var imports = require('soop').imports(); var bitcore = require('bitcore'); var util = bitcore.util; var Transaction = bitcore.Transaction; -var Builder = bitcore.TransactionBuilder; var BuilderMockV0 = require('./BuilderMockV0');; +var TransactionBuilder = bitcore.TransactionBuilder; var Script = bitcore.Script; var buffertools = bitcore.buffertools; +var preconditions = require('preconditions').instance(); function TxProposal(opts) { this.creator = opts.creator; @@ -44,7 +45,7 @@ TxProposal.prototype.setSent = function(sentTxid) { TxProposal.fromObj = function(o) { var t = new TxProposal(o); try { - t.builder = new Builder.fromObj(o.builderObj); + t.builder = new TransactionBuilder.fromObj(o.builderObj); } catch (e) { if (!o.version) { t.builder = new BuilderMockV0(o.builderObj); @@ -54,6 +55,14 @@ TxProposal.fromObj = function(o) { return t; }; + +TxProposal.prototype.isValid = function() { + if (this.builder.signhash !== Transaction.SIGHASH_ALL) { + return false; + } + return true; +}; + TxProposal.getSentTs = function() { return this.sentTs; }; @@ -218,7 +227,6 @@ TxProposals.prototype.merge = function(inTxp, author) { return ret; }; -var preconditions = require('preconditions').instance(); TxProposals.prototype.add = function(data) { preconditions.checkArgument(data.inputChainPaths); preconditions.checkArgument(data.signedBy); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 00a1db9bc..7e37feeae 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -125,6 +125,15 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { this.log('RECV TXPROPOSAL:', data); var inTxp = TxProposals.TxProposal.fromObj(data.txProposal); + var valid = inTxp.isValid(); + if (!valid) { + var corruptEvent = { + type: 'corrupt', + cId: inTxp.creator + }; + this.emit('txProposalEvent', corruptEvent); + return; + } var mergeInfo = this.txProposals.merge(inTxp, senderId); var added = this.addSeenToTxProposals(); diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 24bc8f04d..48024a837 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -149,15 +149,17 @@ angular.module('copayApp.services') }, 3000); }); w.on('txProposalEvent', function(e) { + var user = w.publicKeyRing.nicknameForCopayer(e.cId); switch (e.type) { case 'signed': - var user = w.publicKeyRing.nicknameForCopayer(e.cId); notification.info('Transaction Update', 'A transaction was signed by ' + user); break; case 'rejected': - var user = w.publicKeyRing.nicknameForCopayer(e.cId); notification.info('Transaction Update', 'A transaction was rejected by ' + user); break; + case 'corrupt': + notification.error('Transaction Error', 'Received corrupt transaction from '+user); + break; } }); w.on('addressBookUpdated', function(dontDigest) { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 01632051c..532fe34b7 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -16,6 +16,8 @@ var Network = require('./mocks/FakeNetwork'); var Blockchain = require('./mocks/FakeBlockchain'); var bitcore = bitcore || require('bitcore'); var TransactionBuilder = bitcore.TransactionBuilder; +var Transaction = bitcore.Transaction; +var Address = bitcore.Address; var addCopayers = function(w) { for (var i = 0; i < 4; i++) { @@ -1011,4 +1013,75 @@ describe('Wallet model', function() { copayConfig.forceNetwork = backup; }); }); + + describe('validate txProposals', function() { + var a1 = 'n1pKARYYUnZwxBuGj3y7WqVDu6VLN7n971'; + var a2 = 'mtxYYJXZJmQc2iJRHQ4RZkfxU5K7TE2qMJ'; + var utxos = [{ + address: a1, + txid: '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1', + vout: 1, + scriptPubKey: Address.getScriptPubKeyFor(a1).serialize().toString('hex'), + amount: 0.5, + confirmations: 200 + }, { + address: a2, + txid: '88c4520ffd97ea565578afe0b40919120be704b36561c71ba4e450e83cb3c9fd', + vout: 1, + scriptPubKey: Address.getScriptPubKeyFor(a2).serialize().toString('hex'), + amount: 0.5001, + confirmations: 200 + }]; + var destAddress = 'myuAQcCc1REUgXGsCTiYhZvPPc3XxZ36G1'; + var outs = [{ + address: destAddress, + amount: 1.0 + }]; + + var testValidate = function(signhash, result, done) { + var w = cachedCreateW(); + var spy = sinon.spy(); + w.on('txProposalEvent', spy); + w.on('txProposalEvent', function(e) { + e.type.should.equal(result); + done(); + }); + var opts = {}; + opts.signhash = signhash; + var txb = new TransactionBuilder(opts) + .setUnspent(utxos) + .setOutputs(outs) + .sign(['cVBtNonMyTydnS3NnZyipbduXo9KZfF1aUZ3uQHcvJB6UARZbiWG', + 'cRVF68hhZp1PUQCdjr2k6aVYb2cn6uabbySDPBizAJ3PXF7vDXTL' + ]); + var txp = { + 'txProposal': { + 'builderObj': txb.toObj() + } + }; + w._handleTxProposal('senderID', txp, true); + spy.callCount.should.equal(1); + }; + + it('should validate for SIGHASH_ALL', function(done) { + var result = 'new'; + var signhash = Transaction.SIGHASH_ALL; + testValidate(signhash, result, done); + }); + it('should not validate for different SIGHASH_NONE', function(done) { + var result = 'corrupt'; + var signhash = Transaction.SIGHASH_NONE; + testValidate(signhash, result, done); + }); + it('should not validate for different SIGHASH_SINGLE', function(done) { + var result = 'corrupt'; + var signhash = Transaction.SIGHASH_SINGLE; + testValidate(signhash, result, done); + }); + it('should not validate for different SIGHASH_ANYONECANPAY', function(done) { + var result = 'corrupt'; + var signhash = Transaction.SIGHASH_ANYONECANPAY; + testValidate(signhash, result, done); + }); + }); });