From 07012633a98b41f3561b7d93177fd49a1773bce6 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 2 Feb 2015 15:29:14 -0300 Subject: [PATCH] checking arguments --- lib/model/copayer.js | 14 +++++++------- lib/model/txproposal.js | 4 ++++ lib/model/wallet.js | 37 +++++++++++++++++++++++++++++-------- lib/server.js | 26 +++++++++++++++++++++++++- lib/utils.js | 14 ++++++++++++++ test/integration.js | 13 ++++++++----- 6 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 lib/utils.js diff --git a/lib/model/copayer.js b/lib/model/copayer.js index f9a223a..799b5d0 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -10,17 +10,17 @@ var MESSAGE_SIGNING_PATH = "m/1/0"; function Copayer(opts) { opts = opts || {}; + this.version = VERSION; this.createdOn = Math.floor(Date.now() / 1000); this.id = opts.id; this.name = opts.name; this.xPubKey = opts.xPubKey; - this.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently - this.version = VERSION; - this.signingPubKey = opts.signingPubKey || this.getSigningPubKey(); - + this.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently + if (opts.xPubKey) { + this.signingPubKey = this.getSigningPubKey(); + } }; - Copayer.prototype.getSigningPubKey = function () { if (!this.xPubKey) return null; return HDPublicKey.fromString(this.xPubKey).derive(MESSAGE_SIGNING_PATH).publicKey.toString(); @@ -29,13 +29,13 @@ Copayer.prototype.getSigningPubKey = function () { Copayer.fromObj = function (obj) { var x = new Copayer(); - + x.version = obj.version; x.createdOn = obj.createdOn; x.id = obj.id; x.name = obj.name; x.xPubKey = obj.xPubKey; x.xPubKeySignature = obj.xPubKeySignature; - x.signingPubKey = obj.signingPubKey || this.getSigningPubKey(); + x.signingPubKey = obj.signingPubKey; return x; }; diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index f0090c8..0c1418e 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -4,9 +4,12 @@ var _ = require('lodash'); var TxProposalAction = require('./txproposalaction'); +var VERSION = '1.0.0'; + function TxProposal(opts) { opts = opts || {}; + this.version = VERSION; this.createdOn = Math.floor(Date.now() / 1000); this.id = opts.id; this.creatorId = opts.creatorId; @@ -24,6 +27,7 @@ function TxProposal(opts) { TxProposal.fromObj = function (obj) { var x = new TxProposal(); + x.version = obj.version; x.createdOn = obj.createdOn; x.id = obj.id; x.creatorId = obj.creatorId; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 604da1a..bdb9a97 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -3,11 +3,12 @@ var _ = require('lodash'); var Copayer = require('./copayer'); -var WALLET_VERSION = '1.0.0'; +var VERSION = '1.0.0'; function Wallet(opts) { opts = opts || {}; + this.version = VERSION; this.createdOn = Math.floor(Date.now() / 1000); this.id = opts.id; this.name = opts.name; @@ -17,23 +18,43 @@ function Wallet(opts) { this.publicKeyRing = []; this.addressIndex = 0; this.copayers = []; - this.version = WALLET_VERSION; this.pubKey = opts.pubKey; }; +/* For compressed keys, m*73 + n*34 <= 496 */ +Wallet.COPAYER_PAIR_LIMITS = { + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 4, + 6: 4, + 7: 3, + 8: 3, + 9: 2, + 10: 2, + 11: 1, + 12: 1, +}; -Wallet.fromUntrustedObj = function (obj) { - - // TODO add sanity checks OR migration steps? - if (!obj.pubKey || !obj.m || !obj.n) - return cb('Wallet corrupted'); +/** + * Get the maximum allowed number of required copayers. + * This is a limit imposed by the maximum allowed size of the scriptSig. + * @param {number} totalCopayers - the total number of copayers + * @return {number} + */ +Wallet.getMaxRequiredCopayers = function(totalCopayers) { + return Wallet.COPAYER_PAIR_LIMITS[totalCopayers]; +}; - return Wallet.fromObj(obj); +Wallet.verifyCopayerLimits = function (m, n) { + return (n >= 1 && n <= 12) && (m >= 1 && m <= Wallet.COPAYER_PAIR_LIMITS[n]); }; Wallet.fromObj = function (obj) { var x = new Wallet(); + x.version = obj.version; x.createdOn = obj.createdOn; x.id = obj.id; x.name = obj.name; diff --git a/lib/server.js b/lib/server.js index e7f73c8..7b888b5 100644 --- a/lib/server.js +++ b/lib/server.js @@ -5,11 +5,13 @@ var $ = require('preconditions').singleton(); var async = require('async'); var log = require('npmlog'); log.debug = log.verbose; + var Bitcore = require('bitcore'); var PublicKey = Bitcore.PublicKey; var HDPublicKey = Bitcore.HDPublicKey; var Explorers = require('bitcore-explorers'); +var utils = require('./utils'); var Lock = require('./lock'); var Storage = require('./storage'); var SignUtils = require('./signutils'); @@ -45,6 +47,11 @@ function CopayServer(opts) { CopayServer.prototype.createWallet = function (opts, cb) { var self = this, pubKey; + utils.checkRequired(opts, ['id', 'name', 'm', 'n', 'pubKey']); + if (!Wallet.verifyCopayerLimits(opts.m, opts.n)) return cb('Incorrect m or n value'); + var network = opts.network || 'livenet'; + if (network != 'livenet' && network != 'testnet') return cb('Invalid network'); + try { pubKey = new PublicKey.fromString(opts.pubKey); } catch (e) { @@ -60,7 +67,7 @@ CopayServer.prototype.createWallet = function (opts, cb) { name: opts.name, m: opts.m, n: opts.n, - network: opts.network || 'livenet', + network: network, pubKey: pubKey, }); @@ -119,6 +126,8 @@ CopayServer.prototype._verifySignature = function (text, signature, pubKey) { CopayServer.prototype.joinWallet = function (opts, cb) { var self = this; + utils.checkRequired(opts, ['walletId', 'id', 'name', 'xPubKey', 'xPubKeySignature']); + self._runLocked(opts.walletId, cb, function (cb) { self.getWallet({ id: opts.walletId }, function (err, wallet) { if (err) return cb(err); @@ -164,6 +173,8 @@ CopayServer.prototype._doCreateAddress = function (pkr, index, isChange) { CopayServer.prototype.createAddress = function (opts, cb) { var self = this; + utils.checkRequired(opts, ['walletId', 'isChange']); + self._runLocked(opts.walletId, cb, function (cb) { self.getWallet({ id: opts.walletId }, function (err, wallet) { if (err) return cb(err); @@ -195,6 +206,8 @@ CopayServer.prototype._doCreateAddress = function (pkr, index, isChange) { CopayServer.prototype.verifyMessageSignature = function (opts, cb) { var self = this; + utils.checkRequired(opts, ['walletId', 'copayerId', 'message', 'signature']); + self.getWallet({ id: opts.walletId }, function (err, wallet) { if (err) return cb(err); @@ -275,6 +288,8 @@ CopayServer.prototype._getUtxos = function (opts, cb) { CopayServer.prototype.getBalance = function (opts, cb) { var self = this; + utils.checkRequired(opts, 'walletId'); + self._getUtxos({ walletId: opts.walletId }, function (err, utxos) { if (err) return cb(err); @@ -326,6 +341,8 @@ CopayServer.prototype._selectUtxos = function (txp, utxos) { CopayServer.prototype.createTx = function (opts, cb) { var self = this; + utils.checkRequired(opts, ['walletId', 'copayerId', 'toAddress', 'amount', 'message']); + self.getWallet({ id: opts.walletId }, function (err, wallet) { if (err) return cb(err); @@ -373,6 +390,8 @@ CopayServer.prototype._broadcastTx = function (rawTx, cb) { CopayServer.prototype.signTx = function (opts, cb) { var self = this; + utils.checkRequired(opts, ['walletId', 'copayerId', 'txProposalId', 'signature']); + self.fetchTx(opts.walletId, opts.txProposalId, function (err, txp) { if (err) return cb(err); if (!txp) return cb('Transaction proposal not found'); @@ -406,10 +425,13 @@ CopayServer.prototype.signTx = function (opts, cb) { * @param {string} opts.walletId - The wallet id. * @param {string} opts.copayerId - The wallet id. * @param {string} opts.txProposalId - The identifier of the transaction. + * @param {string} [opts.reason] - A message to other copayers explaining the rejection. */ CopayServer.prototype.rejectTx = function (opts, cb) { var self = this; + utils.checkRequired(opts, ['walletId', 'copayerId', 'txProposalId']); + self.fetchTx(opts.walletId, opts.txProposalId, function (err, txp) { if (err) return cb(err); if (!txp) return cb('Transaction proposal not found'); @@ -436,6 +458,8 @@ CopayServer.prototype.rejectTx = function (opts, cb) { CopayServer.prototype.getPendingTxs = function (opts, cb) { var self = this; + utils.checkRequired(opts, 'walletId'); + self.storage.fetchTxs(opts.walletId, function (err, txps) { if (err) return cb(err); diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..0270b2a --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,14 @@ +var $ = require('preconditions').singleton(); +var _ = require('lodash'); + +var utils = {}; + +utils.checkRequired = function (obj, args) { + args = [].concat(args); + if (!_.isObject(obj)) throw 'Required arguments missing'; + _.each(args, function (arg) { + if (!obj.hasOwnProperty(arg)) throw "Missing required argument '" + arg + "'"; + }); +}; + +module.exports = utils; diff --git a/test/integration.js b/test/integration.js index 749d74b..a88657e 100644 --- a/test/integration.js +++ b/test/integration.js @@ -428,10 +428,12 @@ describe('Copay server', function() { name: 'me', xPubKey: someXPubKeys[0], }; - server.joinWallet(copayerOpts, function(err) { - err.should.contain('Bad request'); + try { + server.joinWallet(copayerOpts, function(err) {}); + } catch (e) { + e.should.contain('xPubKeySignature'); done(); - }); + } }); }); @@ -526,11 +528,12 @@ describe('Copay server', function() { it('should create address', function(done) { server._doCreateAddress = sinon.stub().returns(new Address({ address: 'addr1', - path: 'path1' + path: 'path1', })); helpers.createAndJoinWallet('123', 2, 2, function(err, wallet) { server.createAddress({ - walletId: '123' + walletId: '123', + isChange: false, }, function(err, address) { should.not.exist(err); address.should.exist;