diff --git a/lib/client/api.js b/lib/client/api.js index 83a6427..e007378 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -15,7 +15,7 @@ var ServerCompromisedError = require('./servercompromisederror') var BASE_URL = 'http://localhost:3001/copay/api'; -var WALLET_CRITICAL_DATA = ['copayerId', 'xPrivKey', 'm', 'walletPrivKey', 'publicKeyRing']; +var WALLET_CRITICAL_DATA = ['xPrivKey', 'm', 'publicKeyRing']; function _createProposalOpts(opts, signingKey) { var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message; diff --git a/lib/model/hdpath.js b/lib/hdpath.js similarity index 100% rename from lib/model/hdpath.js rename to lib/hdpath.js diff --git a/lib/model/addressable.js b/lib/model/addressable.js deleted file mode 100644 index 1c9600f..0000000 --- a/lib/model/addressable.js +++ /dev/null @@ -1,34 +0,0 @@ -var _ = require('lodash'); -var HDPath = require('./hdpath'); - -function Addressable (opts) { - this.receiveAddressIndex = 0; - this.changeAddressIndex = 0; - this.copayerIndex = ( opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : HDPath.SHARED_INDEX; -}; - - -Addressable.prototype.fromObj = function (obj) { - this.receiveAddressIndex = obj.receiveAddressIndex; - this.changeAddressIndex = obj.changeAddressIndex; - this.copayerIndex = obj.copayerIndex; -}; - -Addressable.prototype.addAddress = function (isChange) { - if (isChange) { - this.changeAddressIndex++; - } else { - this.receiveAddressIndex++; - } -}; - -Addressable.prototype.getCurrentAddressPath = function (isChange) { - return HDPath.Branch(isChange ? this.changeAddressIndex : this.receiveAddressIndex, isChange, this.copayerIndex); -}; - -Addressable.prototype.getNewAddressPath = function (isChange) { - this.addAddress(isChange); - return this.getCurrentAddressPath(isChange); -}; - -module.exports = Addressable; diff --git a/lib/model/addressmanager.js b/lib/model/addressmanager.js index da059b9..6786016 100644 --- a/lib/model/addressmanager.js +++ b/lib/model/addressmanager.js @@ -1,5 +1,5 @@ var _ = require('lodash'); -var HDPath = require('./hdpath'); +var HDPath = require('../hdpath'); function AddressManager(opts) { this.receiveAddressIndex = 0; diff --git a/lib/model/copayer.js b/lib/model/copayer.js index 605816a..2c70adc 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -1,5 +1,6 @@ 'use strict'; +var $ = require('preconditions').singleton(); var _ = require('lodash'); var util = require('util'); @@ -7,20 +8,22 @@ var Bitcore = require('bitcore'); var HDPublicKey = Bitcore.HDPublicKey; var Uuid = require('uuid'); var AddressManager = require('./addressmanager'); +var Utils = require('../bitcoinutils'); var VERSION = '1.0.0'; var MESSAGE_SIGNING_PATH = "m/1/0"; function Copayer(opts) { - opts = opts || {}; + $.checkArgument(opts && opts.xPubKey, 'need to provide an xPubKey'); opts.copayerIndex = opts.copayerIndex || 0; - this.id = Uuid.v4(); + this.xPubKey = opts.xPubKey; + this.id = Utils.xpubToCopayerId(this.xPubKey); + this.version = VERSION; this.createdOn = Math.floor(Date.now() / 1000); this.name = opts.name; - this.xPubKey = opts.xPubKey; this.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently if (this.xPubKey) this.signingPubKey = this.getSigningPubKey(); @@ -40,14 +43,14 @@ Copayer.prototype.getSigningPubKey = function() { }; Copayer.fromObj = function(obj) { - var x = new Copayer(); + var x = new Copayer({ + xPubKey: obj.xPubKey, + }); x.createdOn = obj.createdOn; x.id = obj.id; x.name = obj.name; - x.xPubKey = obj.xPubKey; x.xPubKeySignature = obj.xPubKeySignature; - x.signingPubKey = obj.signingPubKey; x.addressManager = AddressManager.fromObj(obj.addressManager); return x; diff --git a/lib/server.js b/lib/server.js index ce3c3c6..7b76300 100644 --- a/lib/server.js +++ b/lib/server.js @@ -208,6 +208,7 @@ CopayServer.prototype.joinWallet = function(opts, cb) { if (_.find(wallet.copayers, { xPubKey: opts.xPubKey })) return cb(new ClientError('CINWALLET', 'Copayer already in wallet')); + if (wallet.copayers.length == wallet.n) return cb(new ClientError('WFULL', 'Wallet full')); @@ -218,18 +219,24 @@ CopayServer.prototype.joinWallet = function(opts, cb) { copayerIndex: wallet.copayers.length, }); - wallet.addCopayer(copayer); - self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { + self.storage.fetchCopayerLookup(copayer.id, function(err, res) { if (err) return cb(err); + if (res) + return cb(new ClientError('CREGISTED', 'Copayer ID already registered on server')); - self._notify('NewCopayer', { - walletId: opts.walletId, - copayerId: copayer.id, - copayerName: copayer.name, - }); - return cb(null, { - copayerId: copayer.id, - wallet: wallet + wallet.addCopayer(copayer); + self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { + if (err) return cb(err); + + self._notify('NewCopayer', { + walletId: opts.walletId, + copayerId: copayer.id, + copayerName: copayer.name, + }); + return cb(null, { + copayerId: copayer.id, + wallet: wallet + }); }); }); }); diff --git a/test/hdpath.js b/test/hdpath.js index ba74469..7717c1f 100644 --- a/test/hdpath.js +++ b/test/hdpath.js @@ -1,6 +1,6 @@ 'use strict'; -var HDPath = require('../lib/model/hdpath'); +var HDPath = require('../lib/hdpath'); describe('HDPath model', function() { it('should have the correct constants', function() { diff --git a/test/integration/server.js b/test/integration/server.js index 51c0a53..ad48009 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -39,6 +39,7 @@ helpers.getAuthServer = function(copayerId, cb) { helpers.createAndJoinWallet = function(m, n, cb) { var server = new CopayServer(); var copayerIds = []; + var offset = helpers.offset || 0; var walletOpts = { name: 'a wallet', @@ -53,11 +54,12 @@ helpers.createAndJoinWallet = function(m, n, cb) { var copayerOpts = { walletId: walletId, name: 'copayer ' + (i + 1), - xPubKey: TestData.copayers[i].xPubKey, - xPubKeySignature: TestData.copayers[i].xPubKeySignature, + xPubKey: TestData.copayers[i + offset].xPubKey, + xPubKeySignature: TestData.copayers[i + offset].xPubKeySignature, }; server.joinWallet(copayerOpts, function(err, result) { + should.not.exist(err); copayerIds.push(result.copayerId); return cb(err); }); @@ -198,12 +200,14 @@ describe('Copay server', function() { CopayServer.initialize({ storage: storage }); + helpers.offset = 0; }); describe('#getInstanceWithAuth', function() { beforeEach(function() {}); it('should get server instance for existing copayer', function(done) { + helpers.createAndJoinWallet(1, 2, function(s, wallet) { var xpriv = TestData.copayers[0].xPrivKey; var priv = Bitcore.HDPrivateKey @@ -544,36 +548,6 @@ describe('Copay server', function() { }); }); - it('should fail to create address when wallet is not complete', function(done) { - var server = new CopayServer(); - var walletOpts = { - name: 'my wallet', - m: 2, - n: 3, - pubKey: TestData.keyPair.pub, - }; - server.createWallet(walletOpts, function(err, walletId) { - should.not.exist(err); - var copayerOpts = { - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[0].xPubKey, - xPubKeySignature: TestData.copayers[0].xPubKeySignature, - }; - server.joinWallet(copayerOpts, function(err, result) { - should.not.exist(err); - helpers.getAuthServer(result.copayerId, function(server) { - server.createAddress({}, function(err, address) { - should.not.exist(address); - err.should.exist; - err.message.should.contain('not complete'); - done(); - }); - }); - }); - }); - }); - it('should create many addresses on simultaneous requests', function(done) { var N = 5; async.map(_.range(N), function(i, cb) { @@ -611,35 +585,31 @@ describe('Copay server', function() { }); }); - describe('#createTx', function() { - var server, wallet; - beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w) { - server = s; - wallet = w; - server.createAddress({}, function(err, address) { - done(); - }); - }); - }); - it('should create a tx', function(done) { - helpers.createUtxos(server, wallet, [100, 200], function(utxos) { - helpers.stubBlockExplorer(server, utxos); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey); - server.createTx(txOpts, function(err, tx) { + describe('Wallet not complete tests', function() { + it('should fail to create address when wallet is not complete', function(done) { + var server = new CopayServer(); + var walletOpts = { + name: 'my wallet', + m: 2, + n: 3, + pubKey: TestData.keyPair.pub, + }; + server.createWallet(walletOpts, function(err, walletId) { + should.not.exist(err); + var copayerOpts = { + walletId: walletId, + name: 'me', + xPubKey: TestData.copayers[0].xPubKey, + xPubKeySignature: TestData.copayers[0].xPubKeySignature, + }; + server.joinWallet(copayerOpts, function(err, result) { should.not.exist(err); - tx.should.exist; - tx.message.should.equal('some message'); - tx.isAccepted().should.equal.false; - tx.isRejected().should.equal.false; - server.getPendingTxs({}, function(err, txs) { - should.not.exist(err); - txs.length.should.equal(1); - server.getBalance({}, function(err, balance) { - should.not.exist(err); - balance.totalAmount.should.equal(helpers.toSatoshi(300)); - balance.lockedAmount.should.equal(helpers.toSatoshi(100)); + helpers.getAuthServer(result.copayerId, function(server) { + server.createAddress({}, function(err, address) { + should.not.exist(address); + err.should.exist; + err.message.should.contain('not complete'); done(); }); }); @@ -677,6 +647,45 @@ describe('Copay server', function() { }); }); }); + }); + + + describe('#createTx', function() { + var server, wallet; + beforeEach(function(done) { + helpers.createAndJoinWallet(2, 3, function(s, w) { + server = s; + wallet = w; + server.createAddress({}, function(err, address) { + done(); + }); + }); + }); + + it('should create a tx', function(done) { + helpers.createUtxos(server, wallet, [100, 200], function(utxos) { + helpers.stubBlockExplorer(server, utxos); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + tx.should.exist; + tx.message.should.equal('some message'); + tx.isAccepted().should.equal.false; + tx.isRejected().should.equal.false; + server.getPendingTxs({}, function(err, txs) { + should.not.exist(err); + txs.length.should.equal(1); + server.getBalance({}, function(err, balance) { + should.not.exist(err); + balance.totalAmount.should.equal(helpers.toSatoshi(300)); + balance.lockedAmount.should.equal(helpers.toSatoshi(100)); + done(); + }); + }); + }); + }); + }); + it('should fail to create tx with invalid proposal signature', function(done) { helpers.createUtxos(server, wallet, [100, 200], function(utxos) { @@ -1637,6 +1646,7 @@ describe('Copay server', function() { var before = _.clone(db); db.length.should.above(1); + helpers.offset = 1; helpers.createAndJoinWallet(2, 3, function(s, w) { server = s; wallet = w;