From 50e936379fc09eaaf77121e097d19ee8677dfc7d Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 28 Jan 2015 12:06:34 -0300 Subject: [PATCH] . --- lib/server.js | 71 +++++++++++++++++++++++++++++++++++++++------ lib/storage.js | 10 +++++++ test/integration.js | 40 +++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 9 deletions(-) diff --git a/lib/server.js b/lib/server.js index b9fdb40..0e0532c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -170,7 +170,28 @@ CopayServer.prototype._doCreateAddress = function (pkr, index, isChange) { }); }; -CopayServer.prototype._verifyMessageSignature = function (copayerId, message, signature) { +/** + * Verifies that a given message was actually sent by an authorized copayer. + * @param {Object} opts + * @param {string} opts.walletId - The wallet id. + * @param {string} opts.copayerId - The wallet id. + * @param {string} opts.message - The message to verify. + * @param {string} opts.signature - The signature of message to verify. + * @returns {truthy} The result of the verification. + */ +CopayServer.prototype.verifyMessageSignature = function (opts, cb) { + var self = this; + + self.storage.fetchCopayer(opts.walletId, opts.copayerId, function (err, copayer) { + if (err) return cb(err); + if (!copayer) return cb('Copayer not found'); + + var isValid = self._doVerifyMessageSignature(copayer.xPubKey, opts.message, opts.signature); + return cb(null, isValid); + }); +}; + +CopayServer.prototype._doVerifyMessageSignature = function (pubKey, message, signature) { throw 'not implemented'; }; @@ -238,25 +259,18 @@ CopayServer.prototype._doCreateTx = function (copayerId, toAddress, amount, chan * @param {Object} opts * @param {string} opts.walletId - The wallet id. * @param {string} opts.copayerId - The wallet id. - * @param {truthy} opts.otToken - A one-time token used to avoid reply attacks. * @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.requestSignature - Signature of the request (toAddress + amount + otToken). * @returns {TxProposal} Transaction proposal. */ CopayServer.prototype.createTx = function (opts, cb) { - // Client generates a unique token and signs toAddress + amount + token. - // This way we authenticate + avoid replay attacks. var self = this; self.getWallet({ id: opts.walletId }, function (err, wallet) { if (err) return cb(err); if (!wallet) return cb('Wallet not found'); - var msg = '' + opts.toAddress + opts.amount + opts.otToken; - if (!self._verifyMessageSignature(opts.copayerId, msg, opts.requestSignature)) return cb('Invalid request'); - self._getUtxos({ walletId: wallet.id }, function (err, utxos) { if (err) return cb('Could not retrieve UTXOs'); @@ -273,10 +287,49 @@ CopayServer.prototype.createTx = function (opts, cb) { }); }; +/** + * Sign a transaction proposal. + * @param {Object} opts + * @param {string} opts.walletId - The wallet id. + * @param {string} opts.copayerId - The wallet id. + * @param {string} opts.ntxid - The identifier of the transaction. + * @param {string} opts.signature - The signature of the tx for this copayer. + */ +CopayServer.prototype.signTx = function (opts, cb) { + var self = this; + + self.getWallet({ id: opts.walletId }, function (err, wallet) { + if (err) return cb(err); + if (!wallet) return cb('Wallet not found'); + + self._getUtxos({ walletId: wallet.id }, function (err, utxos) { + if (err) return cb('Could not retrieve UTXOs'); + + self._doCreateTx(opts.copayerId, opts.toAddress, opts.amount, opts.changeAddress, utxos, function (err, tx) { + if (err) return cb('Could not create transaction'); + + self.storage.storeTx(tx, function (err) { + if (err) return cb(err); + + return cb(null, tx); + }); + }); + }); + }); +}; + + +/** + * Retrieves all pending transaction proposals. + * @param {Object} opts + * @param {string} opts.walletId - The wallet id. + * @param {string} opts.copayerId - The wallet id. + * @returns {TxProposal[]} Transaction proposal. + */ CopayServer.prototype.getPendingTxs = function (opts, cb) { var self = this; - //self.storage.get + throw 'not implemented'; }; diff --git a/lib/storage.js b/lib/storage.js index 577039d..dd721ca 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -27,6 +27,16 @@ Storage.prototype.fetchWallet = function (id, cb) { }); }; +Storage.prototype.fetchCopayer = function (walletId, copayerId, cb) { + this.db.get('wallet-' + walletId + '-copayer-' + copayerId, function (err, data) { + if (err) { + if (err.notFound) return cb(); + return cb(err); + } + return cb(null, Copayer.fromObj(data)); + }); +}; + Storage.prototype.fetchCopayers = function (walletId, cb) { var copayers = []; var key = 'wallet-' + walletId + '-copayer-'; diff --git a/test/integration.js b/test/integration.js index 2312ec9..dbb656f 100644 --- a/test/integration.js +++ b/test/integration.js @@ -324,6 +324,46 @@ describe('Copay server', function() { }); }; + describe('#_verifyMessageSignature', function() { + beforeEach(function() { + server = new CopayServer({ + storage: storage, + }); + }); + + it('should successfully verify message signature', function (done) { + server._doVerifyMessageSignature = sinon.stub().returns(true); + helpers.createAndJoinWallet('123', 2, 2, function (err, wallet) { + var opts = { + walletId: '123', + copayerId: '1', + message: 'hello world', + signature: 'dummy', + }; + server.verifyMessageSignature(opts, function (err, isValid) { + should.not.exist(err); + isValid.should.be.true; + done(); + }); + }); + }); + + it('should fail to verify message signature when copayer does not exist', function (done) { + helpers.createAndJoinWallet('123', 2, 2, function (err, wallet) { + var opts = { + walletId: '123', + copayerId: '999', + message: 'hello world', + signature: 'dummy', + }; + server.verifyMessageSignature(opts, function (err, isValid) { + err.should.exist; + done(); + }); + }); + }); + }); + describe('#createAddress', function() { beforeEach(function() { server = new CopayServer({