diff --git a/lib/server.js b/lib/server.js index d0529c3..4874759 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1444,33 +1444,44 @@ WalletService.prototype.sendTx = function(opts, cb) { return cb(new ClientError('Required argument missing')); self._runLocked(cb, function(cb) { - self.storage.fetchTx(self.walletId, opts.txProposalId, function(err, txp) { + self.getWallet({}, function(err, wallet) { if (err) return cb(err); - if (!txp) return cb(Errors.TX_NOT_FOUND); - if (!txp.isTemporary()) return cb(); - - var raw = txp.getRawTx(); - var validSignature = self._verifySignature(raw, opts.proposalSignature, opts.proposalSignaturePubKey); - if (!validSignature) { - return cb(new ClientError('Invalid proposal signature')); - } - // Verify UTXOs are still available - self.getUtxos({}, function(err, utxos) { + self.storage.fetchTx(self.walletId, opts.txProposalId, function(err, txp) { if (err) return cb(err); + if (!txp) return cb(Errors.TX_NOT_FOUND); + if (!txp.isTemporary()) return cb(); + + // Validate that the pubkey used to sign the proposal actually belongs to the copayer + var copayer = wallet.getCopayer(self.copayerId); + var validPubKey = !!self._getSigningKey(opts.proposalSignaturePubKey, opts.proposalSignaturePubKeySig, copayer.requestPubKeys); + if (!validPubKey) { + return cb(new ClientError('Invalid proposal signing key')); + } - var txpInputs = _.map(txp.inputs, utxoKey); - var lockedUtxoIndex = _.indexBy(_.filter(utxos, 'locked'), utxoKey); - var unavailable = _.any(txpInputs, function(i) { - return lockedUtxoIndex[i]; - }); - - if (unavailable) return cb(Errors.UNAVAILABLE_UTXOS); + var raw = txp.getRawTx(); + var validSignature = self._verifySignature(raw, opts.proposalSignature, opts.proposalSignaturePubKey); + if (!validSignature) { + return cb(new ClientError('Invalid proposal signature')); + } - txp.status = 'pending'; - self.storage.storeTx(self.walletId, txp, function(err) { + // Verify UTXOs are still available + self.getUtxos({}, function(err, utxos) { if (err) return cb(err); - return cb(); + + var txpInputs = _.map(txp.inputs, utxoKey); + var lockedUtxoIndex = _.indexBy(_.filter(utxos, 'locked'), utxoKey); + var unavailable = _.any(txpInputs, function(i) { + return lockedUtxoIndex[i]; + }); + + if (unavailable) return cb(Errors.UNAVAILABLE_UTXOS); + + txp.status = 'pending'; + self.storage.storeTx(self.walletId, txp, function(err) { + if (err) return cb(err); + return cb(); + }); }); }); }); diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 882a94f..508c579 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -397,13 +397,14 @@ helpers.createProposalOpts2 = function(outputs, moreOpts, inputs) { helpers.getProposalSignatureOpts = function(txp, signingKey) { var raw = txp.getRawTx(); var proposalSignature = helpers.signMessage(raw, signingKey); - var x = new Bitcore.PrivateKey(signingKey).toPublicKey().toString(); + var pubKey = new Bitcore.PrivateKey(signingKey).toPublicKey().toString(); + var pubKeySig = helpers.signMessage(pubKey, signingKey); return { txProposalId: txp.id, proposalSignature: proposalSignature, - proposalSignaturePubKey: new Bitcore.PrivateKey(signingKey).toPublicKey().toString(), - proposalSignaturePubKeySig: 'dummy', + proposalSignaturePubKey: pubKey, + proposalSignaturePubKeySig: pubKeySig, } }; diff --git a/test/integration/server.js b/test/integration/server.js index 9d8feb4..888580c 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -2366,7 +2366,7 @@ describe('Wallet service', function() { }); }); - it.only('should be able to send a temporary tx proposal', function(done) { + it('should be able to send a temporary tx proposal', function(done) { helpers.stubUtxos(server, wallet, [1, 2], function() { var txOpts = helpers.createProposalOpts2([{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', @@ -2405,6 +2405,49 @@ describe('Wallet service', function() { }); }); }); + it('should fail to send tx proposal with wrong signature', function(done) { + helpers.stubUtxos(server, wallet, [1, 2], function() { + var txOpts = helpers.createProposalOpts2([{ + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 0.8 + }], { + message: 'some message', + customData: 'some custom data', + }); + server.createTx(txOpts, function(err, txp) { + should.not.exist(err); + should.exist(txp); + var sendOpts = helpers.getProposalSignatureOpts(txp, TestData.copayers[0].privKey_1H_0); + sendOpts.proposalSignature = 'dummy'; + server.sendTx(sendOpts, function(err) { + should.exist(err); + err.message.should.contain('Invalid proposal signature'); + done(); + }); + }); + }); + }); + it('should fail to send tx proposal not signed by the creator', function(done) { + helpers.stubUtxos(server, wallet, [1, 2], function() { + var txOpts = helpers.createProposalOpts2([{ + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 0.8 + }], { + message: 'some message', + customData: 'some custom data', + }); + server.createTx(txOpts, function(err, txp) { + should.not.exist(err); + should.exist(txp); + var sendOpts = helpers.getProposalSignatureOpts(txp, TestData.copayers[1].privKey_1H_0); + server.sendTx(sendOpts, function(err) { + should.exist(err); + err.message.should.contain('Invalid proposal signing key'); + done(); + }); + }); + }); + }); it('should fail to send a temporary tx proposal if utxos are unavailable', function(done) { var txp1, txp2; var txOpts = helpers.createProposalOpts2([{ @@ -2432,20 +2475,12 @@ describe('Wallet service', function() { txp2 = txp; should.exist(txp1); should.exist(txp2); - server.sendTx({ - txProposalId: txp1.id, - proposalSignature: 'dummy', - proposalSignaturePubKey: 'dummy', - proposalSignaturePubKeySig: 'dummy', - }, next); + var sendOpts = helpers.getProposalSignatureOpts(txp1, TestData.copayers[0].privKey_1H_0); + server.sendTx(sendOpts, next); }, function(next) { - server.sendTx({ - txProposalId: txp2.id, - proposalSignature: 'dummy', - proposalSignaturePubKey: 'dummy', - proposalSignaturePubKeySig: 'dummy', - }, function(err) { + var sendOpts = helpers.getProposalSignatureOpts(txp2, TestData.copayers[0].privKey_1H_0); + server.sendTx(sendOpts, function(err) { should.exist(err); err.code.should.equal('UNAVAILABLE_UTXOS'); next(); @@ -2464,12 +2499,8 @@ describe('Wallet service', function() { }, function(txp3, next) { should.exist(txp3); - server.sendTx({ - txProposalId: txp3.id, - proposalSignature: 'dummy', - proposalSignaturePubKey: 'dummy', - proposalSignaturePubKeySig: 'dummy', - }, next); + var sendOpts = helpers.getProposalSignatureOpts(txp3, TestData.copayers[0].privKey_1H_0); + server.sendTx(sendOpts, next); }, function(next) { server.getPendingTxs({}, function(err, txs) { @@ -2484,6 +2515,7 @@ describe('Wallet service', function() { }); }); }); + describe('#createTx backoff time', function(done) { var server, wallet, txid;