From cde30c6b9dea74f07f36deb5ff18c65c673833eb Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 10 Feb 2015 10:22:23 -0300 Subject: [PATCH] add proposalSignature to txp --- lib/model/txproposal.js | 2 ++ lib/model/wallet.js | 4 ++++ lib/server.js | 6 ++++++ test/integration.js | 23 +++++++++++++++++++++-- test/txproposal.js | 2 ++ 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 2f8a170..b822999 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -19,6 +19,7 @@ function TxProposal(opts) { this.toAddress = opts.toAddress; this.amount = opts.amount; this.message = opts.message; + this.proposalSignature = opts.proposalSignature; this.changeAddress = opts.changeAddress; this.inputs = opts.inputs; this.inputPaths = opts.inputPaths; @@ -38,6 +39,7 @@ TxProposal.fromObj = function(obj) { x.toAddress = obj.toAddress; x.amount = obj.amount; x.message = obj.message; + x.proposalSignature = obj.proposalSignature; x.changeAddress = obj.changeAddress; x.inputs = obj.inputs; x.requiredSignatures = obj.requiredSignatures; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 5191523..7d49fc8 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -82,6 +82,10 @@ Wallet.fromObj = function(obj) { return x; }; +Wallet.prototype.isShared = function() { + return this.n > 1; +}; + Wallet.prototype.addCopayer = function(copayer) { this.copayers.push(copayer); diff --git a/lib/server.js b/lib/server.js index 2567273..b7e4c44 100644 --- a/lib/server.js +++ b/lib/server.js @@ -384,6 +384,7 @@ CopayServer.prototype._selectUtxos = function(txp, utxos) { * @param {string} opts.toAddress - Destination address. * @param {number} opts.amount - Amount to transfer in satoshi. * @param {string} opts.message - A message to attach to this transaction. + * @param {string} opts.proposalSignature - S(toAddress + '|' + amount + '|' + message). Used by other copayers to verify the proposal. Optional in 1-of-1 wallets. * @returns {TxProposal} Transaction proposal. */ CopayServer.prototype.createTx = function(opts, cb) { @@ -395,6 +396,11 @@ CopayServer.prototype.createTx = function(opts, cb) { self.getWallet({}, function(err, wallet) { if (err) return cb(err); if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); + if (wallet.isShared() && !Utils.checkRequired(opts, 'proposalSignature')) return cb(new ClientError('Proposal signature is required for shared wallets')); + + var copayer = wallet.getCopayer(self.copayerId); + var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message; + if (!self._verifySignature(msg, opts.proposalSignature, copayer.signingPubKey)) return cb(new ClientError('Invalid proposal signature')); var toAddress; try { diff --git a/test/integration.js b/test/integration.js index 2279961..1f7ab7b 100644 --- a/test/integration.js +++ b/test/integration.js @@ -201,6 +201,12 @@ helpers.clientSign = function(tx, xpriv, n) { return signatures; }; +helpers.addProposalSignature = function(server, wallet, txOpts) { + var msg = txOpts.toAddress + '|' + txOpts.amount + '|' + txOpts.message; + var copayer = wallet.getCopayer(server.copayerId); + txOpts.proposalSignature = SignUtils.sign(msg, copayer.signingPubKey); +}; + var db, storage; @@ -596,7 +602,7 @@ describe('Copay server', function() { }); }); - it('should create a tx', function(done) { + it.only('should create a tx', function(done) { helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) { helpers.stubBlockExplorer(server, utxos); var txOpts = { @@ -604,8 +610,10 @@ describe('Copay server', function() { amount: helpers.toSatoshi(80), message: 'some message', }; - + helpers.addProposalSignature(txOpts, ); + console.log(txOpts); server.createTx(txOpts, function(err, tx) { + console.log(err); should.not.exist(err); tx.should.exist; tx.message.should.equal('some message'); @@ -647,6 +655,7 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(80), + proposalSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(tx); @@ -665,6 +674,7 @@ describe('Copay server', function() { var txOpts = { toAddress: 'invalid address', amount: helpers.toSatoshi(80), + proposalSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { @@ -683,6 +693,7 @@ describe('Copay server', function() { var txOpts = { toAddress: 'myE38JHdxmQcTJGP1ZiX4BiGhDxMJDvLJD', // testnet amount: helpers.toSatoshi(80), + proposalSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { @@ -701,6 +712,7 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(120), + proposalSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { @@ -730,6 +742,7 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(12), + proposalSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -738,6 +751,7 @@ describe('Copay server', function() { var txOpts2 = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 8, + proposalSignature: 'dummy', }; server.createTx(txOpts2, function(err, tx) { should.not.exist(err); @@ -763,6 +777,7 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(12), + proposalSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -771,6 +786,7 @@ describe('Copay server', function() { var txOpts2 = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(24), + proposalSignature: 'dummy', }; server.createTx(txOpts2, function(err, tx) { err.code.should.equal('INSUFFICIENTFUNDS'); @@ -805,6 +821,7 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(80), + proposalSignature: 'dummy', }; async.map(_.range(N), function(i, cb) { server.createTx(txOpts, function(err, tx) { @@ -841,6 +858,7 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), + proposalSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -1004,6 +1022,7 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), message: 'some message', + proposalSignature: 'dummy', }; server.createTx(txOpts, function(err, txp) { should.not.exist(err); diff --git a/test/txproposal.js b/test/txproposal.js index 2fb79f0..a3709bf 100644 --- a/test/txproposal.js +++ b/test/txproposal.js @@ -89,6 +89,8 @@ var aTXP = function() { "creatorId": "1", "toAddress": "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", "amount": 50000000, + "message": 'some message', + "proposalSignature": '7035022100896aeb8db75fec22fddb5facf791927a996eb3aee23ee6deaa15471ea46047de02204c0c33f42a9d3ff93d62738712a8c8a5ecd21b45393fdd144e7b01b5a186f1f9', "changeAddress": "3CauZ5JUFfmSAx2yANvCRoNXccZ3YSUjXH", "inputs": [{ "txid": "6ee699846d2d6605f96d20c7cc8230382e5da43342adb11b499bbe73709f06ab",