From cfbf1845029b86ff961f33fa890680aa46a571dc Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 4 Aug 2015 11:19:08 -0300 Subject: [PATCH 01/14] removes isTemporaryRequestKey and replaceTemporaryRequestKey feature. No longer supports joining legacy (<0.9) copayers --- lib/model/copayer.js | 2 + lib/model/wallet.js | 20 ++-- lib/server.js | 66 +----------- test/integration/server.js | 201 +------------------------------------ 4 files changed, 16 insertions(+), 273 deletions(-) diff --git a/lib/model/copayer.js b/lib/model/copayer.js index 1845350..e99343a 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -16,6 +16,8 @@ function Copayer() { this.version = '1.0.0'; }; + +// requestPubKey could be one or an array of keys Copayer.create = function(opts) { opts = opts || {}; $.checkArgument(opts.xPubKey, 'Missing copayer extended public key'); diff --git a/lib/model/wallet.js b/lib/model/wallet.js index f95eda3..26c3cac 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -94,7 +94,7 @@ Wallet.prototype.isShared = function() { Wallet.prototype._updatePublicKeyRing = function() { this.publicKeyRing = _.map(this.copayers, function(copayer) { - return _.pick(copayer, ['xPubKey', 'requestPubKey', 'isTemporaryRequestKey']); + return _.pick(copayer, ['xPubKey']); }); }; @@ -107,20 +107,24 @@ Wallet.prototype.addCopayer = function(copayer) { this._updatePublicKeyRing(); }; -Wallet.prototype.updateCopayerRequestKey = function(copayerId, requestPubKey, signature) { +Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signature) { $.checkState(this.copayers.length == this.n); var c = _.find(this.copayers, { id: copayerId, }); + $.checkState(c); - $.checkState(c) - .checkState(c.isTemporaryRequestKey); + if (_.isArray(c.requestPubKey)) { + $.checkState(_.isArray(c.signature)); - c.requestPubKey = requestPubKey; - c.isTemporaryRequestKey = false; - c.signature = signature; - this._updatePublicKeyRing(); + c.requestPubKey.push(requestPubKey); + c.signature.push(signature); + } else { + c.requestPubKey = requestPubKey; + c.signature = signature; + } + //this._updatePublicKeyRing(); }; Wallet.prototype.getCopayer = function(copayerId) { diff --git a/lib/server.js b/lib/server.js index 45272cd..58fe2a0 100644 --- a/lib/server.js +++ b/lib/server.js @@ -272,71 +272,7 @@ WalletService.prototype.getWallet = function(opts, cb) { }); }; - -/** - * Replace temporary request key - * @param {Object} opts - * @param {string} opts.name - The copayer name. - * @param {string} opts.xPubKey - Extended Public Key for this copayer. - * @param {string} opts.requestPubKey - Public Key used to check requests from this copayer. - * @param {string} opts.copayerSignature - S(name|xPubKey|requestPubKey). Used by other copayers to verify that the copayer joining knows the wallet secret. - */ -WalletService.prototype.replaceTemporaryRequestKey = function(opts, cb) { - var self = this; - - if (!Utils.checkRequired(opts, ['name', 'xPubKey', 'requestPubKey', 'copayerSignature'])) - return cb(new ClientError('Required argument missing')); - - - if (_.isEmpty(opts.name)) - return cb(new ClientError('Invalid copayer name')); - - - if (opts.isTemporaryRequestKey) - return cb(new ClientError('Bad arguments')); - - self._runLocked(cb, function(cb) { - self.storage.fetchWallet(self.walletId, function(err, wallet) { - if (err) return cb(err); - - if (!wallet) return cb(Errors.WALLET_NOT_FOUND); - var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey); - if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) { - return cb(new ClientError()); - } - - var oldCopayerData = _.find(wallet.copayers, { - id: self.copayerId - }); - $.checkState(oldCopayerData); - - if (oldCopayerData.xPubKey !== opts.xPubKey || !oldCopayerData.isTemporaryRequestKey) - return cb(Errors.COPAYER_DATA_MISMATCH); - - if (wallet.copayers.length != wallet.n) - return cb(Errors.WALLET_NOT_COMPLETE); - - wallet.updateCopayerRequestKey(self.copayerId, opts.requestPubKey, opts.copayerSignature); - - self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { - if (err) return cb(err); - - self._notify('CopayerUpdated', { - walletId: opts.walletId, - copayerId: self.copayerId, - copayerName: opts.name, - }, function() { - return cb(null, { - copayerId: self.copayerId, - wallet: wallet - }); - }); - }); - }); - }); -}; - -/** +/* * Verifies a signature * @param text * @param signature diff --git a/test/integration/server.js b/test/integration/server.js index 2384583..6808ee8 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -4115,206 +4115,7 @@ describe('Wallet service', function() { }); }); }); - - describe('#replaceTemporaryRequestKey', function() { - var server, walletId; - beforeEach(function(done) { - server = new WalletService(); - var walletOpts = { - name: 'my wallet', - m: 2, - n: 2, - pubKey: TestData.keyPair.pub, - }; - server.createWallet(walletOpts, function(err, wId) { - should.not.exist(err); - should.exist.walletId; - walletId = wId; - done(); - }); - }); - - it('should join existing wallet with temporaryRequestKey', function(done) { - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[0].xPubKey_45H, - requestPubKey: TestData.copayers[0].pubKey_1H_0, - }); - copayerOpts.isTemporaryRequestKey = true; - - server.joinWallet(copayerOpts, function(err, result) { - should.not.exist(err); - var copayerId = result.copayerId; - helpers.getAuthServer(copayerId, function(server) { - server.getWallet({}, function(err, wallet) { - wallet.id.should.equal(walletId); - var copayer = wallet.copayers[0]; - copayer.isTemporaryRequestKey.should.equal(true); - done(); - }); - }); - }); - }); - - it('should fail to replace a temporaryRequestKey on a not-complete wallet', function(done) { - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[0].xPubKey_45H, - requestPubKey: TestData.copayers[0].pubKey_1_0, - }); - copayerOpts.isTemporaryRequestKey = true; - - server.joinWallet(copayerOpts, function(err, result) { - should.not.exist(err); - var copayerId = result.copayerId; - helpers.getAuthServer(copayerId, function(server) { - server.getWallet({}, function(err, wallet) { - - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[0].xPubKey_45H, - requestPubKey: TestData.copayers[0].pubKey_1H_0, - }); - copayerOpts.isTemporaryRequestKey = false; - server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) { - err.code.should.equal('WALLET_NOT_COMPLETE'); - done(); - }); - }); - }); - }); - }); - - - it('should fail to replace a temporaryRequestKey is Copayer is not in wallet', function(done) { - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[0].xPubKey_45H, - requestPubKey: TestData.copayers[0].pubKey_1_0, - }); - copayerOpts.isTemporaryRequestKey = true; - - server.joinWallet(copayerOpts, function(err, result) { - should.not.exist(err); - var copayerId = result.copayerId; - helpers.getAuthServer(copayerId, function(server) { - server.getWallet({}, function(err, wallet) { - - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[1].xPubKey_45H, - requestPubKey: TestData.copayers[1].pubKey_1H_0, - }); - copayerOpts.isTemporaryRequestKey = false; - server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) { - err.code.should.equal('COPAYER_DATA_MISMATCH'); - done(); - }); - }); - }); - }); - }); - - it('should fail replace a temporaryRequestKey with invalid copayer', function(done) { - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[0].xPubKey_45H, - requestPubKey: TestData.copayers[0].pubKey_1_0, - }); - copayerOpts.isTemporaryRequestKey = true; - - server.joinWallet(copayerOpts, function(err, result) { - should.not.exist(err); - - var copayerOpts2 = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[1].xPubKey_45H, - requestPubKey: TestData.copayers[1].pubKey_1H_0, - }); - copayerOpts2.isTemporaryRequestKey = false; - - server.joinWallet(copayerOpts2, function(err, result) { - should.not.exist(err); - - var copayerId = result.copayerId; - helpers.getAuthServer(copayerId, function(server) { - server.getWallet({}, function(err, wallet) { - - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[1].xPubKey_45H, - requestPubKey: TestData.copayers[1].pubKey_1H_0, - }); - copayerOpts.isTemporaryRequestKey = false; - server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) { - err.code.should.equal('COPAYER_DATA_MISMATCH'); - done(); - }); - }); - }); - }); - }); - }); - - it('should replace a temporaryRequestKey', function(done) { - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[0].xPubKey_45H, - requestPubKey: TestData.copayers[0].pubKey_1_0, - }); - copayerOpts.isTemporaryRequestKey = true; - - server.joinWallet(copayerOpts, function(err, result) { - should.not.exist(err); - var copayerId = result.copayerId; - - var copayerOpts2 = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[1].xPubKey_45H, - requestPubKey: TestData.copayers[1].pubKey_1H_0, - }); - copayerOpts2.isTemporaryRequestKey = false; - - server.joinWallet(copayerOpts2, function(err, result) { - should.not.exist(err); - var copayerId2 = result.copayerId; - - helpers.getAuthServer(copayerId, function(server) { - server.getWallet({}, function(err, wallet) { - - var copayerOpts = helpers.getSignedCopayerOpts({ - walletId: walletId, - name: 'me', - xPubKey: TestData.copayers[0].xPubKey_45H, - requestPubKey: TestData.copayers[0].pubKey_1H_0, - }); - copayerOpts.isTemporaryRequestKey = false; - server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) { - should.not.exist(err); - server.getWallet({}, function(err, wallet) { - wallet.copayers[0].isTemporaryRequestKey.should.equal(false); - wallet.copayers[1].isTemporaryRequestKey.should.equal(false); - done(); - }); - }); - }); - }); - }); - }); - }); - }); - - + describe('Legacy', function() { describe('Fees', function() { var server, wallet; From 3122e9ba7cdfa481941b1e7ff0ad7772133f68cb Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 4 Aug 2015 15:18:12 -0300 Subject: [PATCH 02/14] test passing, rebased --- lib/server.js | 2 -- test/integration/server.js | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/server.js b/lib/server.js index 58fe2a0..8682428 100644 --- a/lib/server.js +++ b/lib/server.js @@ -331,7 +331,6 @@ WalletService.prototype._notify = function(type, data, opts, cb) { * @param {string} opts.xPubKey - Extended Public Key for this copayer. * @param {string} opts.requestPubKey - Public Key used to check requests from this copayer. * @param {string} opts.copayerSignature - S(name|xPubKey|requestPubKey). Used by other copayers to verify the that the copayer joining knows the wallet secret. - * @param {string} opts.isTemporaryRequestKey - requestPubKey will be marked as 'temporary' (only used for Copay migration) */ WalletService.prototype.joinWallet = function(opts, cb) { var self = this; @@ -366,7 +365,6 @@ WalletService.prototype.joinWallet = function(opts, cb) { xPubKey: opts.xPubKey, requestPubKey: opts.requestPubKey, signature: opts.copayerSignature, - isTemporaryRequestKey: !!opts.isTemporaryRequestKey, }); self.storage.fetchCopayerLookup(copayer.id, function(err, res) { diff --git a/test/integration/server.js b/test/integration/server.js index 6808ee8..f76b859 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1404,6 +1404,32 @@ describe('Wallet service', function() { }); }); + + describe.only('Multiple request Pub Keys', function() { + var server, wallet; + beforeEach(function(done) { + helpers.createAndJoinWallet(2, 2, function(s, w) { + server = s; + wallet = w; + done(); + }); + }); + + + + it('#addCopayerRequestKey', function(done) { + helpers.stubUtxos(server, wallet, [1, 'u2', 3], function() { + server.getBalance({}, function(err, balance) { + should.not.exist(err); + should.exist(balance); + balance.totalAmount.should.equal(helpers.toSatoshi(6)); + done(); + }); + }); + }); + }); + + describe('#getBalance', function() { var server, wallet; beforeEach(function(done) { From edc3bc6713e897aa7a419fb8d5d377549d20e16b Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 4 Aug 2015 21:05:26 -0300 Subject: [PATCH 03/14] supports multiple pubkeys per copayer --- lib/expressapp.js | 10 ---------- lib/model/copayer.js | 26 ++++++++++++++++---------- lib/model/wallet.js | 13 +++++-------- lib/server.js | 28 +++++++++++++++++++++------- lib/storage.js | 12 +++++++++++- test/integration/server.js | 2 +- test/storage.js | 4 +++- 7 files changed, 57 insertions(+), 38 deletions(-) diff --git a/lib/expressapp.js b/lib/expressapp.js index 55a838a..4c53135 100644 --- a/lib/expressapp.js +++ b/lib/expressapp.js @@ -151,16 +151,6 @@ ExpressApp.prototype.start = function(opts, cb) { }); - router.put('/v1/copayers', function(req, res) { - getServerWithAuth(req, res, function(server) { - server.replaceTemporaryRequestKey(req.body, function(err, result) { - if (err) return returnError(err, res, req); - res.json(result); - }); - }); - }); - - router.get('/v1/wallets/', function(req, res) { getServerWithAuth(req, res, function(server) { var result = {}; diff --git a/lib/model/copayer.js b/lib/model/copayer.js index e99343a..92010af 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -17,11 +17,10 @@ function Copayer() { }; -// requestPubKey could be one or an array of keys Copayer.create = function(opts) { opts = opts || {}; $.checkArgument(opts.xPubKey, 'Missing copayer extended public key'); - $.checkArgument(opts.requestPubKey, 'Missing copayer request public key'); + $.checkArgument(opts.requestPubKey || opts.requestPubKeys, 'Missing copayer request public key'); opts.copayerIndex = opts.copayerIndex || 0; @@ -32,12 +31,16 @@ Copayer.create = function(opts) { x.id = WalletUtils.xPubToCopayerId(x.xPubKey); x.name = opts.name; - x.signature = opts.signature; // So third parties can check independently - x.requestPubKey = opts.requestPubKey; + + if (!x.requestPubKeys) { + x.requestPubKeys = [{ + key: opts.requestPubKey, + signature: opts.signature, + }]; + } x.addressManager = AddressManager.create({ copayerIndex: opts.copayerIndex }); - x.isTemporaryRequestKey = opts.isTemporaryRequestKey || false; return x; }; @@ -49,12 +52,15 @@ Copayer.fromObj = function(obj) { x.id = obj.id; x.name = obj.name; x.xPubKey = obj.xPubKey; - x.requestPubKey = obj.requestPubKey; - x.signature = obj.signature; - x.isTemporaryRequestKey = obj.isTemporaryRequestKey; - + if (!obj.requestPubKeys) { + x.requestPubKeys = [{ + key: obj.requestPubKey, + signature: obj.signature, + }]; + } else { + x.requestPubKeys = obj.requestPubKeys; + } x.addressManager = AddressManager.fromObj(obj.addressManager); - return x; }; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 26c3cac..d366d24 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -115,15 +115,12 @@ Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signa }); $.checkState(c); - if (_.isArray(c.requestPubKey)) { - $.checkState(_.isArray(c.signature)); + //new ones go first + c.requestPubKeys.unshift({ + key: requestPubKey, + signature: signature, + }); - c.requestPubKey.push(requestPubKey); - c.signature.push(signature); - } else { - c.requestPubKey = requestPubKey; - c.signature = signature; - } //this._updatePublicKeyRing(); }; diff --git a/lib/server.js b/lib/server.js index 8682428..b5238ef 100644 --- a/lib/server.js +++ b/lib/server.js @@ -165,7 +165,7 @@ WalletService.getInstance = function(opts) { * @param {Object} opts * @param {string} opts.copayerId - The copayer id making the request. * @param {string} opts.message - The contents of the request to be signed. - * @param {string} opts.signature - Signature of message to be verified using the copayer's requestPubKey + * @param {string} opts.signature - Signature of message to be verified using one of the copayer's requestPubKeys * @param {string} opts.clientVersion - A string that identifies the client issuing the request */ WalletService.getInstanceWithAuth = function(opts, cb) { @@ -177,7 +177,7 @@ WalletService.getInstanceWithAuth = function(opts, cb) { if (err) return cb(err); if (!copayer) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Copayer not found')); - var isValid = server._verifySignature(opts.message, opts.signature, copayer.requestPubKey); + var isValid = server._verifySignatureAgainstArray(opts.message, opts.signature, copayer.requestPubKeys); if (!isValid) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Invalid signature')); @@ -276,10 +276,24 @@ WalletService.prototype.getWallet = function(opts, cb) { * Verifies a signature * @param text * @param signature - * @param pubKey + * @param pubKeys */ -WalletService.prototype._verifySignature = function(text, signature, pubKey) { - return WalletUtils.verifyMessage(text, signature, pubKey); +WalletService.prototype._verifySignature = function(text, signature, pubkey) { + return WalletUtils.verifyMessage(text, signature, pubkey); +}; + + +/* + * Verifies signature againt a collection of pubkeys + * @param text + * @param signature + * @param pubKeys + */ +WalletService.prototype._verifySignatureAgainstArray = function(text, signature, pubKeys) { + var self = this; + return _.any(pubKeys, function(item) { + return self._verifySignature(text, signature, item.key); + }); }; /** @@ -545,7 +559,7 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) { var copayer = wallet.getCopayer(self.copayerId); - var isValid = self._verifySignature(opts.message, opts.signature, copayer.requestPubKey); + var isValid = self._verifySignatureAgainstArray(opts.message, opts.signature, copayer.requestPubKeys); return cb(null, isValid); }); }; @@ -966,7 +980,7 @@ WalletService.prototype.createTx = function(opts, cb) { hash = WalletUtils.getProposalHash(header) } - if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) + if (!self._verifySignatureAgainstArray(hash, opts.proposalSignature, copayer.requestPubKeys)) return cb(new ClientError('Invalid proposal signature')); self._canCreateTx(self.copayerId, function(err, canCreate) { diff --git a/lib/storage.js b/lib/storage.js index 0fc680b..d5d99c8 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -110,10 +110,11 @@ Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) { var self = this; var copayerLookups = _.map(wallet.copayers, function(copayer) { + $.checkState(copayer.requestPubKeys); return { copayerId: copayer.id, walletId: wallet.id, - requestPubKey: copayer.requestPubKey, + requestPubKeys: copayer.requestPubKeys, }; }); @@ -133,11 +134,20 @@ Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) { }; Storage.prototype.fetchCopayerLookup = function(copayerId, cb) { + this.db.collection(collections.COPAYERS_LOOKUP).findOne({ copayerId: copayerId }, function(err, result) { if (err) return cb(err); if (!result) return cb(); + + if (!result.requestPubKeys) { + result.requestPubKeys = [{ + key: result.requestPubKey, + signature: result.signature, + }]; + } + return cb(null, result); }); }; diff --git a/test/integration/server.js b/test/integration/server.js index f76b859..bf4b919 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1405,7 +1405,7 @@ describe('Wallet service', function() { }); - describe.only('Multiple request Pub Keys', function() { + describe.skip('Multiple request Pub Keys', function() { var server, wallet; beforeEach(function(done) { helpers.createAndJoinWallet(2, 2, function(s, w) { diff --git a/test/storage.js b/test/storage.js index db0d8a7..77e38cf 100644 --- a/test/storage.js +++ b/test/storage.js @@ -90,6 +90,7 @@ describe('Storage', function() { name: 'copayer ' + i, xPubKey: 'xPubKey ' + i, requestPubKey: 'requestPubKey ' + i, + signature: 'xxx', }); wallet.addCopayer(copayer); }); @@ -101,7 +102,8 @@ describe('Storage', function() { should.not.exist(err); should.exist(lookup); lookup.walletId.should.equal('123'); - lookup.requestPubKey.should.equal('requestPubKey 1'); + lookup.requestPubKeys[0].key.should.equal('requestPubKey 1'); + lookup.requestPubKeys[0].signature.should.equal('xxx'); done(); }) }); From dac564f290b00ef5417650a4eec53ba314f4f49e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 5 Aug 2015 16:53:06 -0300 Subject: [PATCH 04/14] adds addAccess call and tests --- lib/errors/errordefinitions.js | 2 + lib/model/txproposal.js | 4 + lib/model/wallet.js | 13 +-- lib/server.js | 176 ++++++++++++++++++++++----------- test/integration/server.js | 151 +++++++++++++++++++++++++--- 5 files changed, 269 insertions(+), 77 deletions(-) diff --git a/lib/errors/errordefinitions.js b/lib/errors/errordefinitions.js index 8a7bdbd..52da5e6 100644 --- a/lib/errors/errordefinitions.js +++ b/lib/errors/errordefinitions.js @@ -15,8 +15,10 @@ var errors = { INSUFFICIENT_FUNDS: 'Insufficient funds', INSUFFICIENT_FUNDS_FOR_FEE: 'Insufficient funds for fee', INVALID_ADDRESS: 'Invalid address', + KEY_IN_COPAYER: 'Key already registered', LOCKED_FUNDS: 'Funds are locked by pending transaction proposals', NOT_AUTHORIZED: 'Not authorized', + TO_MANY_KEYS: 'To many keys registered', TX_ALREADY_BROADCASTED: 'The transaction proposal is already broadcasted', TX_CANNOT_CREATE: 'Cannot create TX proposal during backoff time', TX_CANNOT_REMOVE: 'Cannot remove this tx proposal during locktime', diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index f541c79..042ee87 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -70,6 +70,8 @@ TxProposal.create = function(opts) { x.fee = null; x.feePerKb = opts.feePerKb; x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos; + x.proposalSignaturePubKey = opts.proposalSignaturePubKey; + x.proposalSignaturePubKeySig = opts.proposalSignaturePubKeySig; if (_.isFunction(TxProposal._create[x.type])) { TxProposal._create[x.type](x, opts); @@ -114,6 +116,8 @@ TxProposal.fromObj = function(obj) { x.network = obj.network; x.feePerKb = obj.feePerKb; x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos; + x.proposalSignaturePubKey = obj.proposalSignaturePubKey; + x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig; return x; }; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index d366d24..e32ef95 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -107,21 +107,18 @@ Wallet.prototype.addCopayer = function(copayer) { this._updatePublicKeyRing(); }; -Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signature) { +Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signature, restrictions) { $.checkState(this.copayers.length == this.n); - var c = _.find(this.copayers, { - id: copayerId, - }); - $.checkState(c); + var c = this.getCopayer(copayerId); //new ones go first c.requestPubKeys.unshift({ - key: requestPubKey, + key: requestPubKey.toString(), signature: signature, + selfSigned: true, + restrictions: restrictions, }); - - //this._updatePublicKeyRing(); }; Wallet.prototype.getCopayer = function(copayerId) { diff --git a/lib/server.js b/lib/server.js index b5238ef..4bc5c3f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -33,7 +33,7 @@ var blockchainExplorer; var blockchainExplorerOpts; var messageBroker; - +var MAX_KEYS = 100; /** * Creates an instance of the Bitcore Wallet Service. @@ -177,7 +177,7 @@ WalletService.getInstanceWithAuth = function(opts, cb) { if (err) return cb(err); if (!copayer) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Copayer not found')); - var isValid = server._verifySignatureAgainstArray(opts.message, opts.signature, copayer.requestPubKeys); + var isValid = !!server._getSigningKey(opts.message, opts.signature, copayer.requestPubKeys); if (!isValid) return cb(new ClientError(Errors.codes.NOT_AUTHORIZED, 'Invalid signature')); @@ -289,9 +289,9 @@ WalletService.prototype._verifySignature = function(text, signature, pubkey) { * @param signature * @param pubKeys */ -WalletService.prototype._verifySignatureAgainstArray = function(text, signature, pubKeys) { +WalletService.prototype._getSigningKey = function(text, signature, pubKeys) { var self = this; - return _.any(pubKeys, function(item) { + return _.find(pubKeys, function(item) { return self._verifySignature(text, signature, item.key); }); }; @@ -337,6 +337,110 @@ WalletService.prototype._notify = function(type, data, opts, cb) { }; +WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) { + var self = this; + + if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL); + + var copayer = Model.Copayer.create({ + name: opts.name, + copayerIndex: wallet.copayers.length, + xPubKey: opts.xPubKey, + requestPubKey: opts.requestPubKey, + signature: opts.copayerSignature, + }); + + self.storage.fetchCopayerLookup(copayer.id, function(err, res) { + if (err) return cb(err); + if (res) return cb(Errors.COPAYER_REGISTERED); + + wallet.addCopayer(copayer); + self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { + if (err) return cb(err); + + async.series([ + + function(next) { + self._notify('NewCopayer', { + walletId: opts.walletId, + copayerId: copayer.id, + copayerName: copayer.name, + }, next); + }, + function(next) { + if (wallet.isComplete() && wallet.isShared()) { + self._notify('WalletComplete', { + walletId: opts.walletId, + }, { + isGlobal: true + }, next); + } else { + next(); + } + }, + ], function() { + return cb(null, { + copayerId: copayer.id, + wallet: wallet + }); + }); + }); + }); +}; + + +WalletService.prototype._addKeyToCopayer = function(wallet, copayer, opts, cb) { + var self = this; + wallet.addCopayerRequestKey(copayer.copayerId, opts.requestPubKey, opts.signature, opts.restrictions); + self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { + if (err) return cb(err); + + return cb(null, { + copayerId: copayer.id, + wallet: wallet + }); + }); +}; + +/** + * Adds access to a given copayer + * + * @param {Object} opts + * @param {string} opts.copayerId - The copayer id + * @param {string} opts.requestPubKey - Public Key used to check requests from this copayer. + * @param {string} opts.copayerSignature - S(requestPubKey). Used by other copayers to verify the that the copayer is himself (signed with REQUEST_KEY_AUTH) + * @param {string} opts.restrictions + * - cannotProposeTXs + * - cannotXXX TODO + */ +WalletService.prototype.addAccess = function(opts, cb) { + var self = this; + + if (!Utils.checkRequired(opts, ['copayerId', 'requestPubKey', 'signature'])) + return cb(new ClientError('Required argument missing')); + + self.storage.fetchCopayerLookup(opts.copayerId, function(err, copayer) { + if (err) return cb(err); + if (!copayer) return cb(Errors.NOT_AUTHORIZED); + self.storage.fetchWallet(copayer.walletId, function(err, wallet) { + if (err) return cb(err); + if (!wallet) return cb(Errors.NOT_AUTHORIZED); + + var xPubKey = _.find(wallet.copayers, { + id: opts.copayerId + }).xPubKey; + if (!WalletUtils.checkRequestPubKey(opts.requestPubKey, opts.signature, xPubKey)) { + return cb(Errors.NOT_AUTHORIZED); + } + + if (copayer.requestPubKeys.length > MAX_KEYS) + return cb(Errors.TO_MANY_KEYS); + + self._addKeyToCopayer(wallet, copayer, opts, cb); + }); + }); +}; + /** * Joins a wallet in creation. * @param {Object} opts @@ -371,52 +475,7 @@ WalletService.prototype.joinWallet = function(opts, cb) { xPubKey: opts.xPubKey })) return cb(Errors.COPAYER_IN_WALLET); - if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL); - - var copayer = Model.Copayer.create({ - name: opts.name, - copayerIndex: wallet.copayers.length, - xPubKey: opts.xPubKey, - requestPubKey: opts.requestPubKey, - signature: opts.copayerSignature, - }); - - self.storage.fetchCopayerLookup(copayer.id, function(err, res) { - if (err) return cb(err); - if (res) return cb(Errors.COPAYER_REGISTERED); - - wallet.addCopayer(copayer); - self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { - if (err) return cb(err); - - async.series([ - - function(next) { - self._notify('NewCopayer', { - walletId: opts.walletId, - copayerId: copayer.id, - copayerName: copayer.name, - }, next); - }, - function(next) { - if (wallet.isComplete() && wallet.isShared()) { - self._notify('WalletComplete', { - walletId: opts.walletId, - }, { - isGlobal: true - }, next); - } else { - next(); - } - }, - ], function() { - return cb(null, { - copayerId: copayer.id, - wallet: wallet - }); - }); - }); - }); + self._addCopayerToWallet(wallet, opts, cb); }); }); }; @@ -559,7 +618,7 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) { var copayer = wallet.getCopayer(self.copayerId); - var isValid = self._verifySignatureAgainstArray(opts.message, opts.signature, copayer.requestPubKeys); + var isValid = !!self._getSigningKey(opts.message, opts.signature, copayer.requestPubKeys); return cb(null, isValid); }); }; @@ -707,7 +766,6 @@ WalletService.prototype.getBalance = function(opts, cb) { self.getUtxos({}, function(err, utxos) { if (err) return cb(err); - var balance = self._totalizeUtxos(utxos); // Compute balance by address @@ -980,7 +1038,8 @@ WalletService.prototype.createTx = function(opts, cb) { hash = WalletUtils.getProposalHash(header) } - if (!self._verifySignatureAgainstArray(hash, opts.proposalSignature, copayer.requestPubKeys)) + var signingKey = self._getSigningKey(hash, opts.proposalSignature, copayer.requestPubKeys) + if (!signingKey) return cb(new ClientError('Invalid proposal signature')); self._canCreateTx(self.copayerId, function(err, canCreate) { @@ -1014,7 +1073,7 @@ WalletService.prototype.createTx = function(opts, cb) { valid: false })) return; - var txp = Model.TxProposal.create({ + var txOpts = { type: type, walletId: self.walletId, creatorId: self.copayerId, @@ -1030,7 +1089,14 @@ WalletService.prototype.createTx = function(opts, cb) { requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1), walletN: wallet.n, excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos, - }); + }; + + if (signingKey.selfSigned) { + txOpts.proposalSignaturePubKey = signingKey.key; + txOpts.proposalSignaturePubKeySig = signingKey.signature; + } + + var txp = Model.TxProposal.create(txOpts); if (!self.clientVersion || /^bw.-0\.0\./.test(self.clientVersion)) { txp.version = '1.0.1'; diff --git a/test/integration/server.js b/test/integration/server.js index bf4b919..c8a227e 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -26,6 +26,7 @@ var WalletService = require('../../lib/server'); var EmailService = require('../../lib/emailservice'); var TestData = require('../testdata'); +var CLIENT_VERSION = 'bwc-0.1.1'; var helpers = {}; helpers.getAuthServer = function(copayerId, cb) { @@ -1405,31 +1406,154 @@ describe('Wallet service', function() { }); - describe.skip('Multiple request Pub Keys', function() { + describe('Multiple request Pub Keys', function() { var server, wallet; - beforeEach(function(done) { - helpers.createAndJoinWallet(2, 2, function(s, w) { - server = s; - wallet = w; - done(); + var opts, reqPrivKey, ws; + var getAuthServer = function(copayerId, privKey, cb) { + var msg = 'dummy'; + var sig = WalletUtils.signMessage(msg, privKey); + WalletService.getInstanceWithAuth({ + copayerId: copayerId, + message: msg, + signature: sig, + clientVersion: CLIENT_VERSION, + }, function(err, server) { + return cb(err, server); }); + }; + + beforeEach(function() { + reqPrivKey = new Bitcore.PrivateKey(); + var requestPubKey = reqPrivKey.toPublicKey(); + + var xPrivKey = TestData.copayers[0].xPrivKey_45H; + var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey); + + var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_45H); + opts = { + copayerId: copayerId, + requestPubKey: requestPubKey, + signature: sig, + }; + ws = new WalletService(); }); + describe('#addAccess 1-1', function() { + beforeEach(function(done) { + helpers.createAndJoinWallet(1, 1, function(s, w) { + server = s; + wallet = w; + helpers.stubUtxos(server, wallet, 1, function() { + done(); + }); + }); + }); - it('#addCopayerRequestKey', function(done) { - helpers.stubUtxos(server, wallet, [1, 'u2', 3], function() { - server.getBalance({}, function(err, balance) { + it('should be able to re-gain access from xPrivKey', function(done) { + ws.addAccess(opts, function(err, res) { should.not.exist(err); - should.exist(balance); - balance.totalAmount.should.equal(helpers.toSatoshi(6)); + res.wallet.copayers[0].requestPubKeys.length.should.equal(2); + res.wallet.copayers[0].requestPubKeys[0].selfSigned.should.equal(true); + + server.getBalance(res.wallet.walletId, function(err, bal) { + should.not.exist(err); + bal.totalAmount.should.equal(1e8); + getAuthServer(opts.copayerId, reqPrivKey, function(err, server2) { + server2.getBalance(res.wallet.walletId, function(err, bal2) { + should.not.exist(err); + bal2.totalAmount.should.equal(1e8); + done(); + }); + }); + }); + }); + }); + + it('should fail to gain access with wrong xPrivKey', function(done) { + opts.signature = 'xx'; + ws.addAccess(opts, function(err, res) { + err.code.should.equal('NOT_AUTHORIZED'); done(); }); }); + + it('should fail to access with wrong privkey after gaining access', function(done) { + ws.addAccess(opts, function(err, res) { + should.not.exist(err); + server.getBalance(res.wallet.walletId, function(err, bal) { + should.not.exist(err); + var privKey = new Bitcore.PrivateKey(); + (getAuthServer(opts.copayerId, privKey, function(err, server2) { + err.code.should.equal('NOT_AUTHORIZED'); + done(); + })); + }); + }); + }); + + it('should be able to create TXs after regaining access', function(done) { + ws.addAccess(opts, function(err, res) { + should.not.exist(err); + getAuthServer(opts.copayerId, reqPrivKey, function(err, server2) { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, null, reqPrivKey); + server2.createTx(txOpts, function(err, tx) { + should.not.exist(err); + done(); + }); + }); + }); + }); + }); + + describe('#addAccess 2-2', function() { + beforeEach(function(done) { + helpers.createAndJoinWallet(2, 2, function(s, w) { + server = s; + wallet = w; + helpers.stubUtxos(server, wallet, 1, function() { + done(); + }); + }); + }); + + it('should be able to re-gain access from xPrivKey', function(done) { + ws.addAccess(opts, function(err, res) { + should.not.exist(err); + server.getBalance(res.wallet.walletId, function(err, bal) { should.not.exist(err); + bal.totalAmount.should.equal(1e8); + getAuthServer(opts.copayerId, reqPrivKey, function(err, server2) { + server2.getBalance(res.wallet.walletId, function(err, bal2) { + should.not.exist(err); + bal2.totalAmount.should.equal(1e8); + done(); + }); + }); + }); + }); + }); + + it('TX proposals should include info to be verified', function(done) { + ws.addAccess(opts, function(err, res) { + should.not.exist(err); + getAuthServer(opts.copayerId, reqPrivKey, function(err, server2) { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, null, reqPrivKey); + server2.createTx(txOpts, function(err, tx) { + should.not.exist(err); + server2.getPendingTxs({}, function(err, txs) { + should.not.exist(err); + should.exist(txs[0].proposalSignaturePubKey); + should.exist(txs[0].proposalSignaturePubKeySig); + done(); + }); + }); + }); + }); + }); + }); }); - describe('#getBalance', function() { var server, wallet; beforeEach(function(done) { @@ -4141,7 +4265,7 @@ describe('Wallet service', function() { }); }); }); - + describe('Legacy', function() { describe('Fees', function() { var server, wallet; @@ -4308,7 +4432,6 @@ describe('Wallet service', function() { }); }); }); - }); }); }); From c0d2fca7922add184395ed8147a15bac62420d39 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 5 Aug 2015 16:54:35 -0300 Subject: [PATCH 05/14] adds express endpoint for addAccess --- lib/expressapp.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/expressapp.js b/lib/expressapp.js index 4c53135..dc2cb5a 100644 --- a/lib/expressapp.js +++ b/lib/expressapp.js @@ -140,6 +140,14 @@ ExpressApp.prototype.start = function(opts, cb) { }); }); + router.put('/v1/copayers/', function(req, res) { + var server = getServer(req, res); + server.addAccess(req.body, function(err, result) { + if (err) return returnError(err, res, req); + res.json(result); + }); + }); + router.post('/v1/wallets/:id/copayers/', function(req, res) { req.body.walletId = req.params['id']; var server = getServer(req, res); From 0a1b9319e55d3a9ce51143b0fe8d95c5f5887031 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 5 Aug 2015 17:25:44 -0300 Subject: [PATCH 06/14] change endpoint for add access --- lib/expressapp.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/expressapp.js b/lib/expressapp.js index dc2cb5a..acdfe8e 100644 --- a/lib/expressapp.js +++ b/lib/expressapp.js @@ -140,7 +140,8 @@ ExpressApp.prototype.start = function(opts, cb) { }); }); - router.put('/v1/copayers/', function(req, res) { + router.put('/v1/copayers/:id/', function(req, res) { + req.body.copayerId = req.params['id']; var server = getServer(req, res); server.addAccess(req.body, function(err, result) { if (err) return returnError(err, res, req); From 0433dc05cb4a5416f67fbe03a8f36dc84e78f48a Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 5 Aug 2015 20:32:20 -0300 Subject: [PATCH 07/14] add backwards compat with bwc --- lib/model/copayer.js | 37 +++++++++++++++++++++++++++++-------- lib/model/wallet.js | 3 ++- lib/storage.js | 7 +------ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/lib/model/copayer.js b/lib/model/copayer.js index 92010af..45c4aac 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -38,6 +38,13 @@ Copayer.create = function(opts) { signature: opts.signature, }]; } + + x.requestPubKey = opts.requestPubKey; + x.signature = opts.signature; + + x.requestPubKeys = opts.requestPubKeys; + Copayer.expandForCompat(x); + x.addressManager = AddressManager.create({ copayerIndex: opts.copayerIndex }); @@ -45,6 +52,21 @@ Copayer.create = function(opts) { return x; }; +Copayer.expandForCompat = function(x) { + if (!x.requestPubKeys) { + x.requestPubKeys = [{ + key: x.requestPubKey, + signature: x.signature, + }]; + } + // For backcompat + if (!x.requestPubKey) { + var l = _.last(x.requestPubKeys); + x.requestPubKey = l.key; + x.signature = l.signature; + } +}; + Copayer.fromObj = function(obj) { var x = new Copayer(); @@ -52,14 +74,13 @@ Copayer.fromObj = function(obj) { x.id = obj.id; x.name = obj.name; x.xPubKey = obj.xPubKey; - if (!obj.requestPubKeys) { - x.requestPubKeys = [{ - key: obj.requestPubKey, - signature: obj.signature, - }]; - } else { - x.requestPubKeys = obj.requestPubKeys; - } + + x.requestPubKey = obj.requestPubKey; + x.signature = obj.signature; + + x.requestPubKeys = obj.requestPubKeys; + Copayer.expandForCompat(x); + x.addressManager = AddressManager.fromObj(obj.addressManager); return x; }; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index e32ef95..3c60374 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -94,7 +94,8 @@ Wallet.prototype.isShared = function() { Wallet.prototype._updatePublicKeyRing = function() { this.publicKeyRing = _.map(this.copayers, function(copayer) { - return _.pick(copayer, ['xPubKey']); + return _.pick(copayer, ['xPubKey', 'requestPubKey']); +// return _.pick(copayer, ['xPubKey']); }); }; diff --git a/lib/storage.js b/lib/storage.js index d5d99c8..add7fd7 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -141,12 +141,7 @@ Storage.prototype.fetchCopayerLookup = function(copayerId, cb) { if (err) return cb(err); if (!result) return cb(); - if (!result.requestPubKeys) { - result.requestPubKeys = [{ - key: result.requestPubKey, - signature: result.signature, - }]; - } + Model.Copayer.expandForCompat(result); return cb(null, result); }); From e5860141a3bac9e8242fae01974b56a34c1644b3 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 10 Aug 2015 15:07:20 -0300 Subject: [PATCH 08/14] ref mnemonic --- lib/model/wallet.js | 5 +++-- lib/server.js | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 3c60374..ab22577 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -108,7 +108,7 @@ Wallet.prototype.addCopayer = function(copayer) { this._updatePublicKeyRing(); }; -Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signature, restrictions) { +Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signature, restrictions, name) { $.checkState(this.copayers.length == this.n); var c = this.getCopayer(copayerId); @@ -118,7 +118,8 @@ Wallet.prototype.addCopayerRequestKey = function(copayerId, requestPubKey, signa key: requestPubKey.toString(), signature: signature, selfSigned: true, - restrictions: restrictions, + restrictions: restrictions || {}, + name: name || null, }); }; diff --git a/lib/server.js b/lib/server.js index 4bc5c3f..b7d1d7a 100644 --- a/lib/server.js +++ b/lib/server.js @@ -391,7 +391,7 @@ WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) { WalletService.prototype._addKeyToCopayer = function(wallet, copayer, opts, cb) { var self = this; - wallet.addCopayerRequestKey(copayer.copayerId, opts.requestPubKey, opts.signature, opts.restrictions); + wallet.addCopayerRequestKey(copayer.copayerId, opts.requestPubKey, opts.signature, opts.restrictions, opts.name); self.storage.storeWalletAndUpdateCopayersLookup(wallet, function(err) { if (err) return cb(err); @@ -412,6 +412,7 @@ WalletService.prototype._addKeyToCopayer = function(wallet, copayer, opts, cb) { * @param {string} opts.restrictions * - cannotProposeTXs * - cannotXXX TODO + * @param {string} opts.name (name for the new access) */ WalletService.prototype.addAccess = function(opts, cb) { var self = this; @@ -429,7 +430,8 @@ WalletService.prototype.addAccess = function(opts, cb) { var xPubKey = _.find(wallet.copayers, { id: opts.copayerId }).xPubKey; - if (!WalletUtils.checkRequestPubKey(opts.requestPubKey, opts.signature, xPubKey)) { + + if (!WalletUtils.verifyRequestPubKey(opts.requestPubKey, opts.signature, xPubKey)) { return cb(Errors.NOT_AUTHORIZED); } From aeaa513510d615088331b86ccacb5b48f323a903 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 14 Aug 2015 12:51:48 -0300 Subject: [PATCH 09/14] update type --- lib/errors/errordefinitions.js | 2 +- lib/server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/errors/errordefinitions.js b/lib/errors/errordefinitions.js index 52da5e6..e336418 100644 --- a/lib/errors/errordefinitions.js +++ b/lib/errors/errordefinitions.js @@ -18,7 +18,7 @@ var errors = { KEY_IN_COPAYER: 'Key already registered', LOCKED_FUNDS: 'Funds are locked by pending transaction proposals', NOT_AUTHORIZED: 'Not authorized', - TO_MANY_KEYS: 'To many keys registered', + TOO_MANY_KEYS: 'To many keys registered', TX_ALREADY_BROADCASTED: 'The transaction proposal is already broadcasted', TX_CANNOT_CREATE: 'Cannot create TX proposal during backoff time', TX_CANNOT_REMOVE: 'Cannot remove this tx proposal during locktime', diff --git a/lib/server.js b/lib/server.js index b7d1d7a..d881ad9 100644 --- a/lib/server.js +++ b/lib/server.js @@ -436,7 +436,7 @@ WalletService.prototype.addAccess = function(opts, cb) { } if (copayer.requestPubKeys.length > MAX_KEYS) - return cb(Errors.TO_MANY_KEYS); + return cb(Errors.TOO_MANY_KEYS); self._addKeyToCopayer(wallet, copayer, opts, cb); }); From ec3575e629e5b7f5898ec0bab96280174afd41a1 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 14 Aug 2015 14:15:46 -0300 Subject: [PATCH 10/14] fix string --- lib/errors/errordefinitions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/errors/errordefinitions.js b/lib/errors/errordefinitions.js index e336418..93fc6e3 100644 --- a/lib/errors/errordefinitions.js +++ b/lib/errors/errordefinitions.js @@ -18,7 +18,7 @@ var errors = { KEY_IN_COPAYER: 'Key already registered', LOCKED_FUNDS: 'Funds are locked by pending transaction proposals', NOT_AUTHORIZED: 'Not authorized', - TOO_MANY_KEYS: 'To many keys registered', + TOO_MANY_KEYS: 'Too many keys registered', TX_ALREADY_BROADCASTED: 'The transaction proposal is already broadcasted', TX_CANNOT_CREATE: 'Cannot create TX proposal during backoff time', TX_CANNOT_REMOVE: 'Cannot remove this tx proposal during locktime', From 72ba0b3a589b98fe02851895471e4b446987bf49 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 14 Aug 2015 14:34:48 -0300 Subject: [PATCH 11/14] fix init --- lib/model/copayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model/copayer.js b/lib/model/copayer.js index 45c4aac..fa86c35 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -32,7 +32,7 @@ Copayer.create = function(opts) { x.id = WalletUtils.xPubToCopayerId(x.xPubKey); x.name = opts.name; - if (!x.requestPubKeys) { + if (!opts.requestPubKeys) { x.requestPubKeys = [{ key: opts.requestPubKey, signature: opts.signature, From 637a1e2d8bbf15677cbd50c43c8a31c6e90c842e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 14 Aug 2015 15:52:46 -0300 Subject: [PATCH 12/14] refactor create/fromObj --- lib/model/copayer.js | 53 ++++++++++++++++---------------------- lib/storage.js | 7 ++++- test/integration/server.js | 1 - test/storage.js | 1 + 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/lib/model/copayer.js b/lib/model/copayer.js index fa86c35..5fa31da 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -13,14 +13,18 @@ var Bitcore = WalletUtils.Bitcore; var HDPublicKey = Bitcore.HDPublicKey; function Copayer() { - this.version = '1.0.0'; + this.version = '2'; }; +Copayer.getVersion = function() { + return parseInt(this.version); +}; Copayer.create = function(opts) { opts = opts || {}; - $.checkArgument(opts.xPubKey, 'Missing copayer extended public key'); - $.checkArgument(opts.requestPubKey || opts.requestPubKeys, 'Missing copayer request public key'); + $.checkArgument(opts.xPubKey, 'Missing copayer extended public key') + .checkArgument(opts.requestPubKey, 'Missing copayer request public key') + .checkArgument(opts.signature, 'Missing copayer request public key signature'); opts.copayerIndex = opts.copayerIndex || 0; @@ -32,19 +36,14 @@ Copayer.create = function(opts) { x.id = WalletUtils.xPubToCopayerId(x.xPubKey); x.name = opts.name; - if (!opts.requestPubKeys) { - x.requestPubKeys = [{ - key: opts.requestPubKey, - signature: opts.signature, - }]; - } - x.requestPubKey = opts.requestPubKey; x.signature = opts.signature; - x.requestPubKeys = opts.requestPubKeys; - Copayer.expandForCompat(x); - + x.requestPubKeys = [{ + key: opts.requestPubKey, + signature: opts.signature, + }]; + x.addressManager = AddressManager.create({ copayerIndex: opts.copayerIndex }); @@ -52,21 +51,6 @@ Copayer.create = function(opts) { return x; }; -Copayer.expandForCompat = function(x) { - if (!x.requestPubKeys) { - x.requestPubKeys = [{ - key: x.requestPubKey, - signature: x.signature, - }]; - } - // For backcompat - if (!x.requestPubKey) { - var l = _.last(x.requestPubKeys); - x.requestPubKey = l.key; - x.signature = l.signature; - } -}; - Copayer.fromObj = function(obj) { var x = new Copayer(); @@ -78,9 +62,16 @@ Copayer.fromObj = function(obj) { x.requestPubKey = obj.requestPubKey; x.signature = obj.signature; - x.requestPubKeys = obj.requestPubKeys; - Copayer.expandForCompat(x); - + if (this.getVersion() == 1) { + x.requestPubKeys = [{ + key: x.requestPubKey, + signature: x.signature, + }]; + x.version = 2; + } else { + x.requestPubKeys = obj.requestPubKeys; + } + x.addressManager = AddressManager.fromObj(obj.addressManager); return x; }; diff --git a/lib/storage.js b/lib/storage.js index add7fd7..d5d99c8 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -141,7 +141,12 @@ Storage.prototype.fetchCopayerLookup = function(copayerId, cb) { if (err) return cb(err); if (!result) return cb(); - Model.Copayer.expandForCompat(result); + if (!result.requestPubKeys) { + result.requestPubKeys = [{ + key: result.requestPubKey, + signature: result.signature, + }]; + } return cb(null, result); }); diff --git a/test/integration/server.js b/test/integration/server.js index c8a227e..9fb0d2f 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -113,7 +113,6 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) { }); }, function(err) { if (err) return new Error('Could not generate wallet'); - helpers.getAuthServer(copayerIds[0], function(s) { s.getWallet({}, function(err, w) { cb(s, w); diff --git a/test/storage.js b/test/storage.js index 77e38cf..c5d4ed9 100644 --- a/test/storage.js +++ b/test/storage.js @@ -132,6 +132,7 @@ describe('Storage', function() { name: 'copayer ' + i, xPubKey: 'xPubKey ' + i, requestPubKey: 'requestPubKey ' + i, + signature: 'signarture ' + i, }); wallet.addCopayer(copayer); }); From c4e42da6770320ba5e5830d19201f86ba252e64c Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 20 Aug 2015 16:02:25 -0300 Subject: [PATCH 13/14] rm comment --- lib/model/wallet.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/model/wallet.js b/lib/model/wallet.js index ab22577..30beadb 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -95,7 +95,6 @@ Wallet.prototype.isShared = function() { Wallet.prototype._updatePublicKeyRing = function() { this.publicKeyRing = _.map(this.copayers, function(copayer) { return _.pick(copayer, ['xPubKey', 'requestPubKey']); -// return _.pick(copayer, ['xPubKey']); }); }; From 7481ab270e2d2272b670fadbc687798529155ed3 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 24 Aug 2015 18:13:51 -0300 Subject: [PATCH 14/14] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 59c70cb..c0ae209 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bitcore-wallet-service", "description": "A service for Mutisig HD Bitcoin Wallets", "author": "BitPay Inc", - "version": "0.1.5", + "version": "0.1.6", "keywords": [ "bitcoin", "copay",