diff --git a/lib/model/copayer.js b/lib/model/copayer.js index f70e648..0590e6a 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -14,26 +14,26 @@ var MESSAGE_SIGNING_PATH = "m/1/0"; function Copayer(opts) { opts = opts || {}; - opts.copayerIndex = opts.copayerIndex || 0; + opts.copayerIndex = opts.copayerIndex || 0; Copayer.super_.apply(this, [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.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.signingPubKey = opts.signingPubKey || this.getSigningPubKey(); }; util.inherits(Copayer, Addressable); -Copayer.prototype.getSigningPubKey = function () { +Copayer.prototype.getSigningPubKey = function() { if (!this.xPubKey) return null; return HDPublicKey.fromString(this.xPubKey).derive(MESSAGE_SIGNING_PATH).publicKey.toString(); }; -Copayer.fromObj = function (obj) { +Copayer.fromObj = function(obj) { var x = new Copayer(); x.createdOn = obj.createdOn; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 40193f0..8acee0c 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -57,11 +57,11 @@ Wallet.getMaxRequiredCopayers = function(totalCopayers) { return Wallet.COPAYER_PAIR_LIMITS[totalCopayers]; }; -Wallet.verifyCopayerLimits = function (m, n) { +Wallet.verifyCopayerLimits = function(m, n) { return (n >= 1 && n <= 12) && (m >= 1 && m <= Wallet.COPAYER_PAIR_LIMITS[n]); }; -Wallet.fromObj = function (obj) { +Wallet.fromObj = function(obj) { var x = new Wallet(); x.createdOn = obj.createdOn; @@ -71,7 +71,7 @@ Wallet.fromObj = function (obj) { x.n = obj.n; x.status = obj.status; x.publicKeyRing = obj.publicKeyRing; - x.copayers = _.map(obj.copayers, function (copayer) { + x.copayers = _.map(obj.copayers, function(copayer) { return new Copayer(copayer); }); x.pubKey = obj.pubKey; @@ -81,26 +81,28 @@ Wallet.fromObj = function (obj) { return x; }; -Wallet.prototype.addCopayer = function (copayer) { +Wallet.prototype.addCopayer = function(copayer) { this.copayers.push(copayer); if (this.copayers.length < this.n) return; - + this.status = 'complete'; this.publicKeyRing = _.pluck(this.copayers, 'xPubKey'); }; -Wallet.prototype.getCopayer = function (copayerId) { - return _.find(this.copayers, { id: copayerId }); +Wallet.prototype.getCopayer = function(copayerId) { + return _.find(this.copayers, { + id: copayerId + }); }; -Wallet.prototype._getBitcoreNetwork = function () { +Wallet.prototype._getBitcoreNetwork = function() { return this.isTestnet ? Bitcore.Networks.testnet : Bitcore.Networks.livenet; }; -Wallet.prototype.createAddress = function (path) { +Wallet.prototype.createAddress = function(path) { var publicKeys = _.map(this.copayers, function(copayer) { var xpub = new Bitcore.HDPublicKey(copayer.xPubKey); diff --git a/lib/server.js b/lib/server.js index 8a7c484..54c417d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -31,12 +31,12 @@ var TxProposal = require('./model/txproposal'); */ function CopayServer(opts) { opts = opts || {}; - this.storage = opts.storage || new Storage(); + this.storage = opts.storage ||  new Storage(); }; inherits(CopayServer, events.EventEmitter); -CopayServer._emit = function (event) { +CopayServer._emit = function(event) { var args = Array.prototype.slice.call(arguments); log.debug('Emitting: ', args); this.emit.apply(this, arguments); @@ -52,8 +52,9 @@ CopayServer._emit = function (event) { * @param {string} opts.pubKey - Public key to verify copayers joining have access to the wallet secret. * @param {string} [opts.network = 'livenet'] - The Bitcoin network for this wallet. */ -CopayServer.prototype.createWallet = function (opts, cb) { - var self = this, pubKey; +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'); @@ -66,7 +67,7 @@ CopayServer.prototype.createWallet = function (opts, cb) { return cb(e.toString()); }; - self.storage.fetchWallet(opts.id, function (err, wallet) { + self.storage.fetchWallet(opts.id, function(err, wallet) { if (err) return cb(err); if (wallet) return cb('Wallet already exists'); @@ -89,10 +90,10 @@ CopayServer.prototype.createWallet = function (opts, cb) { * @param {string} opts.id - The wallet id. * @returns {Object} wallet */ - CopayServer.prototype.getWallet = function (opts, cb) { +CopayServer.prototype.getWallet = function(opts, cb) { var self = this; - self.storage.fetchWallet(opts.id, function (err, wallet) { + self.storage.fetchWallet(opts.id, function(err, wallet) { if (err) return cb(err); if (!wallet) return cb('Wallet not found'); return cb(null, wallet); @@ -119,13 +120,15 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) { * @param {number} opts.xPubKey - Extended Public Key for this copayer. * @param {number} opts.xPubKeySignature - Signature of xPubKey using the wallet pubKey. */ - CopayServer.prototype.joinWallet = function (opts, cb) { +CopayServer.prototype.joinWallet = function(opts, cb) { var self = this; Utils.checkRequired(opts, ['walletId', 'id', 'name', 'xPubKey', 'xPubKeySignature']); - Utils.runLocked(opts.walletId, cb, function (cb) { - self.getWallet({ id: opts.walletId }, function (err, wallet) { + Utils.runLocked(opts.walletId, cb, function(cb) { + self.getWallet({ + id: opts.walletId + }, function(err, wallet) { if (err) return cb(err); if (!self._verifySignature(opts.xPubKey, opts.xPubKeySignature, wallet.pubKey)) { @@ -144,10 +147,10 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) { xPubKeySignature: opts.xPubKeySignature, copayerIndex: wallet.copayers.length, }); - + wallet.addCopayer(copayer); - self.storage.storeWallet(wallet, function (err) { + self.storage.storeWallet(wallet, function(err) { return cb(err); }); }); @@ -164,16 +167,18 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) { * @param {truthy} opts.isChange - Indicates whether this is a regular address or a change address. * @returns {Address} address */ - CopayServer.prototype.createAddress = function (opts, cb) { +CopayServer.prototype.createAddress = function(opts, cb) { var self = this; var isChange = opts.isChange || false; Utils.checkRequired(opts, ['walletId', 'isChange']); - Utils.runLocked(opts.walletId, cb, function (cb) { - self.getWallet({ id: opts.walletId }, function (err, wallet) { + Utils.runLocked(opts.walletId, cb, function(cb) { + self.getWallet({ + id: opts.walletId + }, function(err, wallet) { if (err) return cb(err); - + var copayer = wallet.copayers[0]; // TODO: Assign copayer from authentication. var path = copayer.getNewAddressPath(isChange); @@ -202,12 +207,14 @@ CopayServer.prototype._verifySignature = function(text, signature, pubKey) { * @param {string} opts.signature - The signature of message to verify. * @returns {truthy} The result of the verification. */ -CopayServer.prototype.verifyMessageSignature = function (opts, cb) { +CopayServer.prototype.verifyMessageSignature = function(opts, cb) { var self = this; Utils.checkRequired(opts, ['walletId', 'copayerId', 'message', 'signature']); - self.getWallet({ id: opts.walletId }, function (err, wallet) { + self.getWallet({ + id: opts.walletId + }, function(err, wallet) { if (err) return cb(err); var copayer = wallet.getCopayer(opts.copayerId); @@ -219,7 +226,7 @@ CopayServer.prototype.verifyMessageSignature = function (opts, cb) { }; -CopayServer.prototype._getBlockExplorer = function (provider, network) { +CopayServer.prototype._getBlockExplorer = function(provider, network) { var url; switch (provider) { @@ -239,33 +246,37 @@ CopayServer.prototype._getBlockExplorer = function (provider, network) { } }; -CopayServer.prototype._getUtxos = function (opts, cb) { +CopayServer.prototype._getUtxos = function(opts, cb) { var self = this; // Get addresses for this wallet - self.storage.fetchAddresses(opts.walletId, function (err, addresses) { + self.storage.fetchAddresses(opts.walletId, function(err, addresses) { if (err) return cb(err); if (addresses.length == 0) return cb('The wallet has no addresses'); var addresses = _.pluck(addresses, 'address'); var bc = self._getBlockExplorer('insight', opts.network); - bc.getUnspentUtxos(addresses, function (err, utxos) { + bc.getUnspentUtxos(addresses, function(err, utxos) { if (err) return cb(err); - self.getPendingTxs({ walletId: opts.walletId }, function (err, txps) { + self.getPendingTxs({ + walletId: opts.walletId + }, function(err, txps) { if (err) return cb(err); var inputs = _.chain(txps) .pluck('inputs') .flatten() - .map(function (utxo) { return utxo.txid + '|' + utxo.vout }); + .map(function(utxo) { + return utxo.txid + '|' + utxo.vout + }); - var dictionary = _.groupBy(utxos, function (utxo) { + var dictionary = _.groupBy(utxos, function(utxo) { return utxo.txid + '|' + utxo.vout; }); - _.each(inputs, function (input) { + _.each(inputs, function(input) { if (dictionary[input]) { dictionary[input].locked = true; } @@ -283,17 +294,25 @@ CopayServer.prototype._getUtxos = function (opts, cb) { * @param {string} opts.walletId - The wallet id. * @returns {Object} balance - Total amount & locked amount. */ - CopayServer.prototype.getBalance = function (opts, cb) { +CopayServer.prototype.getBalance = function(opts, cb) { var self = this; Utils.checkRequired(opts, 'walletId'); - self._getUtxos({ walletId: opts.walletId }, function (err, utxos) { + self._getUtxos({ + walletId: opts.walletId + }, function(err, utxos) { if (err) return cb(err); var balance = {}; - balance.totalAmount = _.reduce(utxos, function(sum, utxo) { return sum + utxo.amount; }, 0); - balance.lockedAmount = _.reduce(_.without(utxos, { locked: true }), function(sum, utxo) { return sum + utxo.amount; }, 0); + balance.totalAmount = _.reduce(utxos, function(sum, utxo) { + return sum + utxo.amount; + }, 0); + balance.lockedAmount = _.reduce(_.without(utxos, { + locked: true + }), function(sum, utxo) { + return sum + utxo.amount; + }, 0); return cb(null, balance); }); @@ -336,18 +355,24 @@ CopayServer.prototype._selectUtxos = function(txp, utxos) { * @param {string} opts.message - A message to attach to this transaction. * @returns {TxProposal} Transaction proposal. */ -CopayServer.prototype.createTx = function (opts, cb) { +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) { + self.getWallet({ + id: opts.walletId + }, function(err, wallet) { if (err) return cb(err); - self._getUtxos({ walletId: wallet.id }, function (err, utxos) { + self._getUtxos({ + walletId: wallet.id + }, function(err, utxos) { if (err) return cb(err); - utxos = _.without(utxos, { locked: true }); + utxos = _.without(utxos, { + locked: true + }); var txp = new TxProposal({ creatorId: opts.copayerId, @@ -361,18 +386,18 @@ CopayServer.prototype.createTx = function (opts, cb) { txp.rawTx = self._createRawTx(txp); - self.storage.storeTx(wallet.id, txp, function (err) { + self.storage.storeTx(wallet.id, txp, function(err) { if (err) return cb(err); return cb(null, txp); }); }); }); -}; +}; -CopayServer.prototype._broadcastTx = function (rawTx, cb) { +CopayServer.prototype._broadcastTx = function(rawTx, cb) { // TODO: this should attempt to broadcast _all_ accepted and not-yet broadcasted (status=='accepted') txps? - cb = cb || function () {}; + cb = cb || function() {}; throw 'not implemented'; }; @@ -385,29 +410,31 @@ CopayServer.prototype._broadcastTx = function (rawTx, cb) { * @param {string} opts.txProposalId - The identifier of the transaction. * @param {string} opts.signature - The signature of the tx for this copayer. */ -CopayServer.prototype.signTx = function (opts, 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) { + self.fetchTx(opts.walletId, opts.txProposalId, function(err, txp) { if (err) return cb(err); if (!txp) return cb('Transaction proposal not found'); - var action = _.find(txp.actions, { copayerId: opts.copayerId }); + var action = _.find(txp.actions, { + copayerId: opts.copayerId + }); if (action) return cb('Copayer already voted on this transaction proposal'); if (txp.status != 'pending') return cb('The transaction proposal is not pending'); txp.sign(opts.copayerId, opts.signature); - self.storage.storeTx(opts.walletId, txp, function (err) { + self.storage.storeTx(opts.walletId, txp, function(err) { if (err) return cb(err); if (txp.status == 'accepted'); - self._broadcastTx(txp.rawTx, function (err, txid) { + self._broadcastTx(txp.rawTx, function(err, txid) { if (err) return cb(err); tx.setBroadcasted(txid); - self.storage.storeTx(opts.walletId, txp, function (err) { + self.storage.storeTx(opts.walletId, txp, function(err) { if (err) return cb(err); return cb(); @@ -425,21 +452,23 @@ CopayServer.prototype.signTx = function (opts, cb) { * @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) { +CopayServer.prototype.rejectTx = function(opts, cb) { var self = this; Utils.checkRequired(opts, ['walletId', 'copayerId', 'txProposalId']); - self.fetchTx(opts.walletId, opts.txProposalId, function (err, txp) { + self.fetchTx(opts.walletId, opts.txProposalId, function(err, txp) { if (err) return cb(err); if (!txp) return cb('Transaction proposal not found'); - var action = _.find(txp.actions, { copayerId: opts.copayerId }); + var action = _.find(txp.actions, { + copayerId: opts.copayerId + }); if (action) return cb('Copayer already voted on this transaction proposal'); if (txp.status != 'pending') return cb('The transaction proposal is not pending'); txp.reject(opts.copayerId); - self.storage.storeTx(opts.walletId, txp, function (err) { + self.storage.storeTx(opts.walletId, txp, function(err) { if (err) return cb(err); return cb(); @@ -453,15 +482,17 @@ CopayServer.prototype.rejectTx = function (opts, cb) { * @param {string} opts.walletId - The wallet id. * @returns {TxProposal[]} Transaction proposal. */ -CopayServer.prototype.getPendingTxs = function (opts, cb) { +CopayServer.prototype.getPendingTxs = function(opts, cb) { var self = this; Utils.checkRequired(opts, 'walletId'); - self.storage.fetchTxs(opts.walletId, function (err, txps) { + self.storage.fetchTxs(opts.walletId, function(err, txps) { if (err) return cb(err); - var pending = _.filter(txps, { status: 'pending' }); + var pending = _.filter(txps, { + status: 'pending' + }); return cb(null, pending); }); }; diff --git a/test/integration.js b/test/integration.js index c002305..c929ee9 100644 --- a/test/integration.js +++ b/test/integration.js @@ -53,7 +53,7 @@ var someXPubKeysSignatures = [ //Copayer signature var aText = 'hello world'; -var aTextSignature = '3045022100addd20e5413865d65d561ad2979f2289a40d52594b1f804840babd9a63e4ebbf02204b86285e1fcab02df772e7a1325fc4b511ecad79a8f80a2bd1ad8bfa858ac3d4'; // with someXPrivKey[0].derive('m/1/0')=5c0e043a513032907d181325a8e7990b076c0af15ed13dc5e611cda9bb3ae52a; +var aTextSignature = '3045022100addd20e5413865d65d561ad2979f2289a40d52594b1f804840babd9a63e4ebbf02204b86285e1fcab02df772e7a1325fc4b511ecad79a8f80a2bd1ad8bfa858ac3d4'; // with someXPrivKey[0].derive('m/1/0')=5c0e043a513032907d181325a8e7990b076c0af15ed13dc5e611cda9bb3ae52a; var helpers = {}; @@ -432,7 +432,7 @@ describe('Copay server', function() { xPubKey: someXPubKeys[0], }; try { - server.joinWallet(copayerOpts, function(err) {}); + server.joinWallet(copayerOpts, function(err) {}); } catch (e) { e.should.contain('xPubKeySignature'); done(); @@ -477,7 +477,7 @@ describe('Copay server', function() { done(); }); }); - + it('should set pkr and status = complete on last copayer joining (2-3)', function(done) { helpers.createAndJoinWallet('123', 2, 3, function(err, wallet) { @@ -487,7 +487,7 @@ describe('Copay server', function() { should.not.exist(err); wallet.status.should.equal('complete'); wallet.publicKeyRing.length.should.equal(3); - _.each([0,1,2], function(i) { + _.each([0, 1, 2], function(i) { var copayer = wallet.copayers[i]; copayer.receiveAddressIndex.should.equal(0); copayer.changeAddressIndex.should.equal(0); @@ -584,20 +584,17 @@ describe('Copay server', function() { server = new CopayServer({ storage: storage, }); - server._doCreateAddress = sinon.stub().returns(new Address({ - address: 'addr1', - path: 'path1' - })); helpers.createAndJoinWallet('123', 2, 2, function(err, wallet) { server.createAddress({ - walletId: '123' + walletId: '123', + isChange: false, }, function(err, address) { done(); }); }); }); - it.skip('should create tx', function(done) { + it.only('should create tx', function(done) { var bc = sinon.stub(); bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, helpers.createUtxos([100, 200])); server._getBlockExplorer = sinon.stub().returns(bc);