From a4f1169aa9b5c8aafe4df452d6fe9dbdefc11414 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 27 Aug 2015 17:09:14 -0300 Subject: [PATCH 01/11] allow addressManager to handle BIP44 derivations --- lib/model/addressmanager.js | 19 ++++- test/models/addressmanager.js | 156 ++++++++++++++++++++++++++-------- 2 files changed, 136 insertions(+), 39 deletions(-) diff --git a/lib/model/addressmanager.js b/lib/model/addressmanager.js index 058b7fb..ca1740b 100644 --- a/lib/model/addressmanager.js +++ b/lib/model/addressmanager.js @@ -1,4 +1,10 @@ var _ = require('lodash'); +var $ = require('preconditions').singleton(); + +var STRATEGIES = { + BIP44: 'BIP44', + BIP45: 'BIP45', +}; var SHARED_INDEX = 0x80000000 - 1; @@ -9,7 +15,10 @@ AddressManager.create = function(opts) { var x = new AddressManager(); - x.version = '1.0.0'; + x.version = 2; + x.derivationStrategy = opts.derivationStrategy || STRATEGIES.BIP44; + $.checkState(_.contains(_.values(STRATEGIES), x.derivationStrategy)); + x.receiveAddressIndex = 0; x.changeAddressIndex = 0; x.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : SHARED_INDEX; @@ -17,11 +26,11 @@ AddressManager.create = function(opts) { return x; }; - AddressManager.fromObj = function(obj) { var x = new AddressManager(); x.version = obj.version; + x.derivationStrategy = obj.derivationStrategy || STRATEGIES.BIP45; x.receiveAddressIndex = obj.receiveAddressIndex; x.changeAddressIndex = obj.changeAddressIndex; x.copayerIndex = obj.copayerIndex; @@ -29,6 +38,10 @@ AddressManager.fromObj = function(obj) { return x; }; +AddressManager.prototype.supportsDerivation = function() { + // BIP44 does not support copayer specific indexes + return !(this.derivationStrategy == STRATEGIES.BIP44 && this.copayerIndex != SHARED_INDEX); +}; AddressManager.prototype._incrementIndex = function(isChange) { if (isChange) { @@ -49,7 +62,7 @@ AddressManager.prototype.rewindIndex = function(isChange, n) { AddressManager.prototype.getCurrentAddressPath = function(isChange) { return 'm/' + - this.copayerIndex + '/' + + (this.derivationStrategy == STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') + (isChange ? 1 : 0) + '/' + (isChange ? this.changeAddressIndex : this.receiveAddressIndex); }; diff --git a/test/models/addressmanager.js b/test/models/addressmanager.js index eea0286..3e34733 100644 --- a/test/models/addressmanager.js +++ b/test/models/addressmanager.js @@ -8,53 +8,137 @@ var AddressManager = require('../../lib/model/addressmanager'); describe('AddressManager', function() { - describe('#getCurrentAddressPath', function() { - it('should return a valid BIP32 path for given index', function() { - var am = AddressManager.create({ - copayerIndex: 4 - }); - am.getCurrentAddressPath(false).should.equal('m/4/0/0'); - am.getCurrentAddressPath(true).should.equal('m/4/1/0'); + describe('#create', function() { + it('should create BIP44 address manager by default', function() { + var am = AddressManager.create(); + am.derivationStrategy.should.equal('BIP44'); }); }); - describe('#getCurrentAddressPath', function() { + describe('#fromObj', function() { + it('should assume legacy address manager uses BIP45', function() { + var obj = { + version: '1.0.0', + receiveAddressIndex: 2, + changeAddressIndex: 0, + copayerIndex: 4 + }; + var am = AddressManager.fromObj(obj); + am.derivationStrategy.should.equal('BIP45'); + am.getCurrentAddressPath(false).should.equal('m/4/0/2'); + }); + }); + describe('BIP45', function() { + describe('#getCurrentAddressPath', function() { + it('should return a valid BIP32 path for given index', function() { + var am = AddressManager.create({ + derivationStrategy: 'BIP45', + copayerIndex: 4, + }); + am.supportsDerivation().should.be.true; + am.getCurrentAddressPath(false).should.equal('m/4/0/0'); + am.getCurrentAddressPath(true).should.equal('m/4/1/0'); + }); + }); it('should return a valid BIP32 path for defaut Index', function() { - var am = AddressManager.create(); + var am = AddressManager.create({ + derivationStrategy: 'BIP45', + }); + am.supportsDerivation().should.be.true; am.getCurrentAddressPath(false).should.equal('m/2147483647/0/0'); am.getCurrentAddressPath(true).should.equal('m/2147483647/1/0'); }); - }); - describe('#getNewAddressPath', function() { - it('should return a new valid BIP32 path for given index', function() { - var am = AddressManager.create({ - copayerIndex: 2 + describe('#getNewAddressPath', function() { + it('should return a new valid BIP32 path for given index', function() { + var am = AddressManager.create({ + derivationStrategy: 'BIP45', + copayerIndex: 2, + }); + am.getNewAddressPath(false).should.equal('m/2/0/0'); + am.getNewAddressPath(true).should.equal('m/2/1/0'); + am.getNewAddressPath(false).should.equal('m/2/0/1'); + am.getNewAddressPath(true).should.equal('m/2/1/1'); + }); + }); + describe('#rewindIndex', function() { + it('should rewind main index', function() { + var am = AddressManager.create({ + derivationStrategy: 'BIP45', + }); + am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); + am.getNewAddressPath(false).should.equal('m/2147483647/0/1'); + am.getNewAddressPath(false).should.equal('m/2147483647/0/2'); + am.rewindIndex(false, 2); + am.getNewAddressPath(false).should.equal('m/2147483647/0/1'); + }); + it('should rewind change index', function() { + var am = AddressManager.create({ + derivationStrategy: 'BIP45', + }); + am.getNewAddressPath(true).should.equal('m/2147483647/1/0'); + am.rewindIndex(false, 1); + am.getNewAddressPath(true).should.equal('m/2147483647/1/1'); + am.rewindIndex(true, 2); + am.getNewAddressPath(true).should.equal('m/2147483647/1/0'); + }); + it('should stop at 0', function() { + var am = AddressManager.create({ + derivationStrategy: 'BIP45', + }); + am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); + am.rewindIndex(false, 20); + am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); }); - am.getNewAddressPath(false).should.equal('m/2/0/0'); - am.getNewAddressPath(true).should.equal('m/2/1/0'); }); }); - describe('#rewindIndex', function() { - it('should rewind main index', function() { - var am = AddressManager.create({}); - am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); - am.getNewAddressPath(false).should.equal('m/2147483647/0/1'); - am.getNewAddressPath(false).should.equal('m/2147483647/0/2'); - am.rewindIndex(false, 2); - am.getNewAddressPath(false).should.equal('m/2147483647/0/1'); + describe('BIP44', function() { + describe('#getCurrentAddressPath', function() { + it('should return first address path', function() { + var am = AddressManager.create(); + am.supportsDerivation().should.be.true; + am.getCurrentAddressPath(false).should.equal('m/0/0'); + am.getCurrentAddressPath(true).should.equal('m/1/0'); + }); + it('should return address path independently of copayerIndex', function() { + var am = AddressManager.create({ + copayerIndex: 4, + }); + am.supportsDerivation().should.be.false; + am.getCurrentAddressPath(false).should.equal('m/0/0'); + am.getCurrentAddressPath(true).should.equal('m/1/0'); + }); }); - it('should rewind change index', function() { - var am = AddressManager.create({}); - am.getNewAddressPath(true).should.equal('m/2147483647/1/0'); - am.rewindIndex(false, 1); - am.getNewAddressPath(true).should.equal('m/2147483647/1/1'); - am.rewindIndex(true, 2); - am.getNewAddressPath(true).should.equal('m/2147483647/1/0'); + describe('#getNewAddressPath', function() { + it('should return a new path', function() { + var am = AddressManager.create(); + am.getNewAddressPath(false).should.equal('m/0/0'); + am.getNewAddressPath(true).should.equal('m/1/0'); + am.getNewAddressPath(false).should.equal('m/0/1'); + am.getNewAddressPath(true).should.equal('m/1/1'); + }); }); - it('should stop at 0', function() { - var am = AddressManager.create({}); - am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); - am.rewindIndex(false, 20); - am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); + describe('#rewindIndex', function() { + it('should rewind main index', function() { + var am = AddressManager.create(); + am.getNewAddressPath(false).should.equal('m/0/0'); + am.getNewAddressPath(false).should.equal('m/0/1'); + am.getNewAddressPath(false).should.equal('m/0/2'); + am.rewindIndex(false, 2); + am.getNewAddressPath(false).should.equal('m/0/1'); + }); + it('should rewind change index', function() { + var am = AddressManager.create(); + am.getNewAddressPath(true).should.equal('m/1/0'); + am.rewindIndex(false, 1); + am.getNewAddressPath(true).should.equal('m/1/1'); + am.rewindIndex(true, 2); + am.getNewAddressPath(true).should.equal('m/1/0'); + }); + it('should stop at 0', function() { + var am = AddressManager.create(); + am.getNewAddressPath(false).should.equal('m/0/0'); + am.rewindIndex(false, 20); + am.getNewAddressPath(false).should.equal('m/0/0'); + }); }); }); }); From 004890bd6b835f3aed511842eec9c6a88d351e5c Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 27 Aug 2015 17:14:33 -0300 Subject: [PATCH 02/11] use BIP45 by default --- lib/model/copayer.js | 3 ++- lib/model/wallet.js | 4 +++- lib/server.js | 15 ++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/model/copayer.js b/lib/model/copayer.js index bccdc81..d7cce72 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -37,7 +37,8 @@ Copayer.create = function(opts) { }]; x.addressManager = AddressManager.create({ - copayerIndex: opts.copayerIndex + derivationStrategy: 'BIP45', + copayerIndex: opts.copayerIndex, }); x.customData = opts.customData; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 3fc507c..5b632a6 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -29,7 +29,9 @@ Wallet.create = function(opts) { x.copayers = []; x.pubKey = opts.pubKey; x.network = opts.network; - x.addressManager = AddressManager.create(); + x.addressManager = AddressManager.create({ + derivationStrategy: 'BIP45', + }); x.scanStatus = null; return x; diff --git a/lib/server.js b/lib/server.js index df018f3..68ce60f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -941,7 +941,10 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) { self.getUtxos({}, function(err, utxos) { if (err) return cb(err); - var excludeIndex = _.reduce(utxosToExclude, function(res, val) { res[val] = val; return res; }, {}); + var excludeIndex = _.reduce(utxosToExclude, function(res, val) { + res[val] = val; + return res; + }, {}); utxos = _.reject(utxos, function(utxo) { return excludeIndex[utxo.txid + ":" + utxo.vout]; @@ -1817,10 +1820,12 @@ WalletService.prototype.scan = function(opts, cb) { }); if (opts.includeCopayerBranches) { _.each(wallet.copayers, function(copayer) { - derivators.push({ - derive: _.bind(copayer.createAddress, copayer, wallet, isChange), - rewind: _.bind(copayer.addressManager.rewindIndex, copayer.addressManager, isChange), - }); + if (copayer.addressManager.supportsDerivation()) { + derivators.push({ + derive: _.bind(copayer.createAddress, copayer, wallet, isChange), + rewind: _.bind(copayer.addressManager.rewindIndex, copayer.addressManager, isChange), + }); + } }); } }); From 3ac0bc5e27da73550aa297d585b80e6ac33c5825 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Fri, 28 Aug 2015 14:54:36 -0300 Subject: [PATCH 03/11] switch tests to bip44 --- lib/model/copayer.js | 2 +- lib/model/txproposal.js | 2 + lib/model/wallet.js | 2 +- lib/server.js | 26 +++- test/integration/server.js | 278 ++++++++++++++++++++++++++----------- test/testdata.js | 218 ++++++++++++++++------------- 6 files changed, 340 insertions(+), 188 deletions(-) diff --git a/lib/model/copayer.js b/lib/model/copayer.js index d7cce72..73233f6 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -37,7 +37,7 @@ Copayer.create = function(opts) { }]; x.addressManager = AddressManager.create({ - derivationStrategy: 'BIP45', + derivationStrategy: opts.derivationStrategy || 'BIP45', copayerIndex: opts.copayerIndex, }); diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 317d0e6..c574ae6 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -71,6 +71,7 @@ TxProposal.create = function(opts) { x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos; x.proposalSignaturePubKey = opts.proposalSignaturePubKey; x.proposalSignaturePubKeySig = opts.proposalSignaturePubKeySig; + x.derivationStrategy = opts.derivationStrategy || 'BIP45'; if (_.isFunction(TxProposal._create[x.type])) { TxProposal._create[x.type](x, opts); @@ -117,6 +118,7 @@ TxProposal.fromObj = function(obj) { x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos; x.proposalSignaturePubKey = obj.proposalSignaturePubKey; x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig; + x.derivationStrategy = obj.derivationStrategy || 'BIP45'; return x; }; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 5b632a6..545185b 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -30,7 +30,7 @@ Wallet.create = function(opts) { x.pubKey = opts.pubKey; x.network = opts.network; x.addressManager = AddressManager.create({ - derivationStrategy: 'BIP45', + derivationStrategy: opts.derivationStrategy || 'BIP45', }); x.scanStatus = null; diff --git a/lib/server.js b/lib/server.js index 68ce60f..a65f561 100644 --- a/lib/server.js +++ b/lib/server.js @@ -203,6 +203,7 @@ WalletService.prototype._runLocked = function(cb, task) { * @param {number} opts.n - Total copayers. * @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. + * @param {string} [opts.derivationStrategy = 'BIP45'] - Strategy used for address derivation (BIP44, BIP45). */ WalletService.prototype.createWallet = function(opts, cb) { var self = this, @@ -215,10 +216,14 @@ WalletService.prototype.createWallet = function(opts, cb) { if (!Wallet.verifyCopayerLimits(opts.m, opts.n)) return cb(new ClientError('Invalid combination of required copayers / total copayers')); - var network = opts.network || 'livenet'; - if (network != 'livenet' && network != 'testnet') + opts.network = opts.network || 'livenet'; + if (!_.contains(['livenet', 'testnet'], opts.network)) return cb(new ClientError('Invalid network')); + opts.derivationStrategy = opts.derivationStrategy || 'BIP45'; + if (!_.contains(['BIP44', 'BIP45'], opts.derivationStrategy)) + return cb(new ClientError('Invalid address derivation strategy')); + try { pubKey = new PublicKey.fromString(opts.pubKey); } catch (ex) { @@ -239,15 +244,16 @@ WalletService.prototype.createWallet = function(opts, cb) { }, function(acb) { var wallet = Wallet.create({ + id: opts.id, name: opts.name, m: opts.m, n: opts.n, - network: network, + network: opts.network, pubKey: pubKey.toString(), - id: opts.id, + derivationStrategy: opts.derivationStrategy, }); self.storage.storeWallet(wallet, function(err) { - log.debug('Wallet created', wallet.id, network); + log.debug('Wallet created', wallet.id, opts.network); newWallet = wallet; return acb(err); }); @@ -411,6 +417,7 @@ WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) { requestPubKey: opts.requestPubKey, signature: opts.copayerSignature, customData: opts.customData, + derivationStrategy: opts.derivationStrategy, }); self.storage.fetchCopayerLookup(copayer.id, function(err, res) { if (err) return cb(err); @@ -514,6 +521,7 @@ WalletService.prototype.addAccess = function(opts, cb) { * @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. * @param {string} opts.customData - (optional) Custom data for this copayer. + * @param {string} [opts.derivationStrategy = 'BIP45'] - Strategy used for address derivation (BIP44, BIP45). */ WalletService.prototype.joinWallet = function(opts, cb) { var self = this; @@ -524,12 +532,17 @@ WalletService.prototype.joinWallet = function(opts, cb) { if (_.isEmpty(opts.name)) return cb(new ClientError('Invalid copayer name')); + opts.derivationStrategy = opts.derivationStrategy || 'BIP45'; + if (!_.contains(['BIP44', 'BIP45'], opts.derivationStrategy)) + return cb(new ClientError('Invalid address derivation strategy')); + self.walletId = opts.walletId; self._runLocked(cb, function(cb) { self.storage.fetchWallet(opts.walletId, function(err, wallet) { - if (err) return cb(err); if (!wallet) return cb(Errors.WALLET_NOT_FOUND); + if (wallet.addressManager.derivationStrategy != opts.derivationStrategy) + return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To join this wallet, you need to upgrade your client app.')); var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey); if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) { @@ -1163,6 +1176,7 @@ WalletService.prototype.createTx = function(opts, cb) { requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1), walletN: wallet.n, excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos, + derivationStrategy: wallet.addressManager.derivationStrategy, }; if (signingKey.selfSigned) { diff --git a/test/integration/server.js b/test/integration/server.js index 5900fb7..27dd623 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -52,18 +52,25 @@ helpers._generateCopayersTestData = function(n) { var xpriv_45H = xpriv.derive(45, true); var xpub_45H = Bitcore.HDPublicKey(xpriv_45H); - var id = WalletUtils.xPubToCopayerId(xpub_45H.toString()); + var id45 = WalletUtils.xPubToCopayerId(xpub_45H.toString()); + + var xpriv_44H_0H_0H = xpriv.derive(44, true).derive(0, true).derive(0, true); + var xpub_44H_0H_0H = Bitcore.HDPublicKey(xpriv_44H_0H_0H); + var id44 = WalletUtils.xPubToCopayerId(xpub_44H_0H_0H.toString()); var xpriv_1H = xpriv.derive(1, true); var xpub_1H = Bitcore.HDPublicKey(xpriv_1H); var priv = xpriv_1H.derive(0).privateKey; var pub = xpub_1H.derive(0).publicKey; - console.log('{id: ', "'" + id + "',"); + console.log('{id44: ', "'" + id44 + "',"); + console.log('id45: ', "'" + id45 + "',"); console.log('xPrivKey: ', "'" + xpriv.toString() + "',"); console.log('xPubKey: ', "'" + xpub.toString() + "',"); console.log('xPrivKey_45H: ', "'" + xpriv_45H.toString() + "',"); console.log('xPubKey_45H: ', "'" + xpub_45H.toString() + "',"); + console.log('xPrivKey_44H_0H_0H: ', "'" + xpriv_44H_0H_0H.toString() + "',"); + console.log('xPubKey_44H_0H_0H: ', "'" + xpub_44H_0H_0H.toString() + "',"); console.log('xPrivKey_1H: ', "'" + xpriv_1H.toString() + "',"); console.log('xPubKey_1H: ', "'" + xpub_1H.toString() + "',"); console.log('privKey_1H_0: ', "'" + priv.toString() + "',"); @@ -94,19 +101,22 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) { m: m, n: n, pubKey: TestData.keyPair.pub, + derivationStrategy: opts.derivationStrategy || 'BIP44', }; server.createWallet(walletOpts, function(err, walletId) { if (err) return cb(err); async.each(_.range(n), function(i, cb) { + var copayerData = TestData.copayers[i + offset]; + var bip = opts.derivationStrategy || 'BIP44'; var copayerOpts = helpers.getSignedCopayerOpts({ walletId: walletId, name: 'copayer ' + (i + 1), - xPubKey: TestData.copayers[i + offset].xPubKey_45H, - requestPubKey: TestData.copayers[i + offset].pubKey_1H_0, + xPubKey: bip == 'BIP44' ? copayerData.xPubKey_44H_0H_0H : copayerData.xPubKey_45H, + requestPubKey: copayerData.pubKey_1H_0, customData: 'custom data ' + (i + 1), + derivationStrategy: bip, }); - server.joinWallet(copayerOpts, function(err, result) { should.not.exist(err); copayerIds.push(result.copayerId); @@ -469,7 +479,7 @@ describe('Wallet service', function() { txpId = txp.id; async.eachSeries(_.range(2), function(i, next) { var copayer = TestData.copayers[i]; - helpers.getAuthServer(copayer.id, function(server) { + helpers.getAuthServer(copayer.id44, function(server) { var signatures = helpers.clientSign(txp, copayer.xPrivKey); server.signTx({ txProposalId: txp.id, @@ -525,7 +535,7 @@ describe('Wallet service', function() { txpId = txp.id; async.eachSeries(_.range(1, 3), function(i, next) { var copayer = TestData.copayers[i]; - helpers.getAuthServer(copayer.id, function(server) { + helpers.getAuthServer(copayer.id44, function(server) { server.rejectTx({ txProposalId: txp.id, }, next); @@ -733,11 +743,13 @@ describe('Wallet service', function() { }); it('should fail when requesting for non-existent copayer', function(done) { - WalletService.getInstanceWithAuth({ - copayerId: 'ads', - message: TestData.message.text, - signature: TestData.message.signature, - }, function(err, server) { + var message = 'hello world'; + var opts = { + copayerId: 'dummy', + message: message, + signature: WalletUtils.signMessage(message, TestData.copayers[0].privKey_1H_0), + }; + WalletService.getInstanceWithAuth(opts, function(err, server) { err.code.should.equal('NOT_AUTHORIZED'); err.message.should.contain('Copayer not found'); done(); @@ -783,8 +795,7 @@ describe('Wallet service', function() { }); }); - - it('should create wallet with given id', function(done) { + it('should create wallet with given id', function(done) { var opts = { name: 'my wallet', m: 2, @@ -983,6 +994,7 @@ describe('Wallet service', function() { name: 'me', xPubKey: TestData.copayers[1].xPubKey_45H, requestPubKey: TestData.copayers[1].pubKey_1H_0, + derivationStrategy: 'BIP44', }); server.joinWallet(copayerOpts, function(err) { should.exist(err); @@ -1211,21 +1223,23 @@ describe('Wallet service', function() { }); it('should successfully verify message signature', function(done) { + var message = 'hello world'; var opts = { - message: TestData.message.text, - signature: TestData.message.signature, + message: message, + signature: WalletUtils.signMessage(message, TestData.copayers[0].privKey_1H_0), }; server.verifyMessageSignature(opts, function(err, isValid) { should.not.exist(err); - isValid.should.equal(true); + isValid.should.be.true; done(); }); }); it('should fail to verify message signature for different copayer', function(done) { + var message = 'hello world'; var opts = { - message: TestData.message.text, - signature: TestData.message.signature, + message: message, + signature: WalletUtils.signMessage(message, TestData.copayers[0].privKey_1H_0), }; helpers.getAuthServer(wallet.copayers[1].id, function(server) { server.verifyMessageSignature(opts, function(err, isValid) { @@ -1253,16 +1267,16 @@ describe('Wallet service', function() { should.exist(address); address.walletId.should.equal(wallet.id); address.network.should.equal('livenet'); - address.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg'); + address.address.should.equal('36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r'); address.isChange.should.be.false; - address.path.should.equal('m/2147483647/0/0'); + address.path.should.equal('m/0/0'); server.getNotifications({}, function(err, notifications) { should.not.exist(err); var notif = _.find(notifications, { type: 'NewAddress' }); should.exist(notif); - notif.data.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg'); + notif.data.address.should.equal('36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r'); done(); }); }); @@ -1275,7 +1289,7 @@ describe('Wallet service', function() { }, function(err, addresses) { addresses.length.should.equal(N); _.each(_.range(N), function(i) { - addresses[i].path.should.equal('m/2147483647/0/' + i); + addresses[i].path.should.equal('m/0/' + i); }); // No two identical addresses _.uniq(_.pluck(addresses, 'address')).length.should.equal(N); @@ -1296,13 +1310,59 @@ describe('Wallet service', function() { server.createAddress({}, function(err, address) { should.not.exist(err); should.exist(address); - address.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg'); - address.path.should.equal('m/2147483647/0/0'); done(); }); }); }); }); + + describe('BIP45', function() { + beforeEach(function(done) { + helpers.createAndJoinWallet(2, 2, { + derivationStrategy: 'BIP45' + }, function(s, w) { + server = s; + wallet = w; + done(); + }); + }); + + it('should create address', function(done) { + server.createAddress({}, function(err, address) { + should.not.exist(err); + should.exist(address); + address.walletId.should.equal(wallet.id); + address.network.should.equal('livenet'); + address.address.should.equal('3BVJZ4CYzeTtawDtgwHvWV5jbvnXtYe97i'); + address.isChange.should.be.false; + address.path.should.equal('m/2147483647/0/0'); + server.getNotifications({}, function(err, notifications) { + should.not.exist(err); + var notif = _.find(notifications, { + type: 'NewAddress' + }); + should.exist(notif); + notif.data.address.should.equal('3BVJZ4CYzeTtawDtgwHvWV5jbvnXtYe97i'); + done(); + }); + }); + }); + + it('should create many addresses on simultaneous requests', function(done) { + var N = 5; + async.map(_.range(N), function(i, cb) { + server.createAddress({}, cb); + }, function(err, addresses) { + addresses.length.should.equal(N); + _.each(_.range(N), function(i) { + addresses[i].path.should.equal('m/2147483647/0/' + i); + }); + // No two identical addresses + _.uniq(_.pluck(addresses, 'address')).length.should.equal(N); + done(); + }); + }); + }); }); describe('Preferences', function() { @@ -1508,10 +1568,10 @@ describe('Wallet service', function() { reqPrivKey = new Bitcore.PrivateKey(); var requestPubKey = reqPrivKey.toPublicKey(); - var xPrivKey = TestData.copayers[0].xPrivKey_45H; + var xPrivKey = TestData.copayers[0].xPrivKey_44H_0H_0H; var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey); - var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_45H); + var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_44H_0H_0H); opts = { copayerId: copayerId, requestPubKey: requestPubKey, @@ -4132,17 +4192,17 @@ describe('Wallet service', function() { it('should scan main addresses', function(done) { helpers.stubAddressActivity( - ['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A', // m/2147483647/0/0 - '3NezgtNbuDzL2sFhnfxyVy8bHp4v6ud252', // m/2147483647/0/2 - '3CQ2hCMUu1SCPVPMpfCCuT3nAfHGiHV1o7', // m/2147483647/1/0 + ['3Jc2hcN13vGBCdWR4SMNRoL9EUiBHuf4LT', // m/0/0 + '3Pk2Cn3TW6JLhGEnHg8iM2qtmx2T1uHyZM', // m/0/2 + '3GqrwYwyLt1czKMvvqFwYj2ZeooiHtbgbc', // m/1/0 ]); var expectedPaths = [ - 'm/2147483647/0/0', - 'm/2147483647/0/1', - 'm/2147483647/0/2', - 'm/2147483647/0/3', - 'm/2147483647/1/0', - 'm/2147483647/1/1', + 'm/0/0', + 'm/0/1', + 'm/0/2', + 'm/0/3', + 'm/1/0', + 'm/1/1', ]; server.scan({}, function(err) { should.not.exist(err); @@ -4156,46 +4216,13 @@ describe('Wallet service', function() { _.difference(paths, expectedPaths).length.should.equal(0); server.createAddress({}, function(err, address) { should.not.exist(err); - address.path.should.equal('m/2147483647/0/4'); + address.path.should.equal('m/0/4'); done(); }); }); }); }); }); - it('should scan main addresses & copayer addresses', function(done) { - helpers.stubAddressActivity( - ['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A', // m/2147483647/0/0 - '3CQ2hCMUu1SCPVPMpfCCuT3nAfHGiHV1o7', // m/2147483647/1/0 - '3BYHznBmosYxUj1NWcjdFKX2tdsH7UT1YG', // m/0/0/1 - '3Eg1uPkGnwyU42bRiaDuo6Cu9bjjhoG7Sh', // m/1/1/0 - '3AYmZ63tMd2AHN8QLfu5D2nfRzCH66psWx', // m/1/0/0 - ]); - var expectedPaths = [ - 'm/2147483647/0/0', - 'm/2147483647/0/1', - 'm/2147483647/1/0', - 'm/2147483647/1/1', - 'm/0/0/0', - 'm/0/0/1', - 'm/1/0/0', - 'm/1/0/1', - 'm/1/1/0', - 'm/1/1/1', - ]; - server.scan({ - includeCopayerBranches: true - }, function(err) { - should.not.exist(err); - server.storage.fetchAddresses(wallet.id, function(err, addresses) { - should.exist(addresses); - addresses.length.should.equal(expectedPaths.length); - var paths = _.pluck(addresses, 'path'); - _.difference(paths, expectedPaths).length.should.equal(0); - done(); - }) - }); - }); it('should restore wallet balance', function(done) { async.waterfall([ @@ -4253,6 +4280,93 @@ describe('Wallet service', function() { }); }); }); + + describe('BIP45', function() { + + beforeEach(function(done) { + this.timeout(5000); + WalletService.SCAN_CONFIG.scanWindow = 2; + WalletService.SCAN_CONFIG.derivationDelay = 0; + + helpers.createAndJoinWallet(1, 2, { + derivationStrategy: 'BIP45' + }, function(s, w) { + server = s; + wallet = w; + done(); + }); + }); + afterEach(function() { + WalletService.SCAN_CONFIG = scanConfigOld; + }); + + it('should scan main addresses', function(done) { + helpers.stubAddressActivity( + ['39AA1Y2VvPJhV3RFbc7cKbUax1WgkPwweR', // m/2147483647/0/0 + '3QX2MNSijnhCALBmUVnDo5UGPj3SEGASWx', // m/2147483647/0/2 + '3MzGaz4KKX66w8ShKaR536ZqzVvREBqqYu', // m/2147483647/1/0 + ]); + var expectedPaths = [ + 'm/2147483647/0/0', + 'm/2147483647/0/1', + 'm/2147483647/0/2', + 'm/2147483647/0/3', + 'm/2147483647/1/0', + 'm/2147483647/1/1', + ]; + server.scan({}, function(err) { + should.not.exist(err); + server.getWallet({}, function(err, wallet) { + should.not.exist(err); + wallet.scanStatus.should.equal('success'); + server.storage.fetchAddresses(wallet.id, function(err, addresses) { + should.exist(addresses); + addresses.length.should.equal(expectedPaths.length); + var paths = _.pluck(addresses, 'path'); + _.difference(paths, expectedPaths).length.should.equal(0); + server.createAddress({}, function(err, address) { + should.not.exist(err); + address.path.should.equal('m/2147483647/0/4'); + done(); + }); + }); + }); + }); + }); + it('should scan main addresses & copayer addresses', function(done) { + helpers.stubAddressActivity( + ['39AA1Y2VvPJhV3RFbc7cKbUax1WgkPwweR', // m/2147483647/0/0 + '3MzGaz4KKX66w8ShKaR536ZqzVvREBqqYu', // m/2147483647/1/0 + '3BYoynejwBH9q4Jhr9m9P5YTnLTu57US6g', // m/0/0/1 + '37Pb8c32hzm16tCZaVHj4Dtjva45L2a3A3', // m/1/1/0 + '32TB2n283YsXdseMqUm9zHSRcfS5JxTWxx', // m/1/0/0 + ]); + var expectedPaths = [ + 'm/2147483647/0/0', + 'm/2147483647/0/1', + 'm/2147483647/1/0', + 'm/2147483647/1/1', + 'm/0/0/0', + 'm/0/0/1', + 'm/1/0/0', + 'm/1/0/1', + 'm/1/1/0', + 'm/1/1/1', + ]; + server.scan({ + includeCopayerBranches: true + }, function(err) { + should.not.exist(err); + server.storage.fetchAddresses(wallet.id, function(err, addresses) { + should.exist(addresses); + addresses.length.should.equal(expectedPaths.length); + var paths = _.pluck(addresses, 'path'); + _.difference(paths, expectedPaths).length.should.equal(0); + done(); + }) + }); + }); + }); }); describe('#startScan', function() { @@ -4275,18 +4389,18 @@ describe('Wallet service', function() { }); it('should start an asynchronous scan', function(done) { - helpers.stubAddressActivity([ - '3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A', // m/2147483647/0/0 - '3NezgtNbuDzL2sFhnfxyVy8bHp4v6ud252', // m/2147483647/0/2 - '3CQ2hCMUu1SCPVPMpfCCuT3nAfHGiHV1o7', // m/2147483647/1/1 - ]); + helpers.stubAddressActivity( + ['3Jc2hcN13vGBCdWR4SMNRoL9EUiBHuf4LT', // m/0/0 + '3Pk2Cn3TW6JLhGEnHg8iM2qtmx2T1uHyZM', // m/0/2 + '3GqrwYwyLt1czKMvvqFwYj2ZeooiHtbgbc', // m/1/0 + ]); var expectedPaths = [ - 'm/2147483647/0/0', - 'm/2147483647/0/1', - 'm/2147483647/0/2', - 'm/2147483647/0/3', - 'm/2147483647/1/0', - 'm/2147483647/1/1', + 'm/0/0', + 'm/0/1', + 'm/0/2', + 'm/0/3', + 'm/1/0', + 'm/1/1', ]; server.messageBroker.onMessage(function(n) { if (n.type == 'ScanFinished') { @@ -4301,7 +4415,7 @@ describe('Wallet service', function() { _.difference(paths, expectedPaths).length.should.equal(0); server.createAddress({}, function(err, address) { should.not.exist(err); - address.path.should.equal('m/2147483647/0/4'); + address.path.should.equal('m/0/4'); done(); }); }) diff --git a/test/testdata.js b/test/testdata.js index 3f2c57b..2c38ac2 100644 --- a/test/testdata.js +++ b/test/testdata.js @@ -3,113 +3,136 @@ var keyPair = { pub: '026092daeed8ecb2212869395770e956ffc9bf453f803e700f64ffa70c97a00d80', }; -var message = { - text: 'hello world', - signature: '30440220091598edbe4b45d41b551c941f24401fd3d72a8ccd18721dbfc58be9bdc234d802203870d36cf1fee89bb78cf161334849774c5684d287a74a9bd6681ee5e012150d', // Signed with copayer[0].privKey_1H_0 -}; - var copayers = [{ - id: 'ec2dd28c76367b6cfbf4a8d7cc17c9473fd78c995092a83e1776e1be43a30dc6', - xPrivKey: 'xprv9s21ZrQH143K2TjAA6HcG4p2eikKc4qQMVekTPqwNcesQ89nFZHzHKmekbwMKeiK6cPSzmqAavdemUS6VUzhGc5Uf4TCtjBa9qwDfbnWQcJ', - xPubKey: 'xpub661MyMwAqRbcEwodG7pcdCkmCkap1XZFiiaMFnFYvxBrGvUvo6cEq868bukLYM938wPm1CMzFuvbDQbA2tJvsWKgTz4xDKTyEfbQvBbo1AR', - xPrivKey_45H: 'xprv9uZiJ2ZYFNQ3RDBvjtz1HcivbfWbncMAb6EvKKose8rUbRo9dwX5hnThGodivCDuV8s48xZk7DZSuNpYb9ZwCpsatsnibRyirQbqqroftMP', - xPubKey_45H: 'xpub68Z4hY6S5jxLdhGPqvX1ekff9hM6C551xKAX7iDVCUPTUE8JBUqLFanB8697EoXbwN5rjDLHgPWkbofr8otNeb8Kr8SSh8pCiNMp7sWkBj4', - xPrivKey_1H: 'xprv9uZiJ2ZYFNQ1WeKrKfTxrbUnXfNnCtpbdHVuUMSVGB57nhbmjqWbJBQbgGNa2jSbrgQX9NFHXtAoQ3y1hsEgwjKHxUU52ZYTC6m5kjXoNpJ', - xPubKey_1H: 'xpub68Z4hY6S5jxJj8QKRgzyDjRX5hDGcMYSzWRWGjr6pWc6fVvvHNpqqyj5XXbMcfW737beYZcd7EQau5HS74Ws6Ctx9XwFn2wHQjSUKLbfdFk', - privKey_1H_0: 'e334a0ce3d573bd99fc4cd7e2065e39dc7851cb61da7f381431c253c3e230828', - pubKey_1H_0: '02db35c363e2c904bba3cd0eadb6b8d68fc1d8e6160d632b8fb91a471b80e40c86', - privKey_1_0: '89833f39998ff14f2cbb461365ccba30583abfd2a7845e0bb61849275802005d', - pubKey_1_0: '022ef899f914c9b90a5544923a3aa6de5ddcd6c9d0b603b63ab9fd0c2cdc5d3772', + id44: '626452e5e0e35df4d9ae4d3e60653c9ae9a814f00c84dc40f5887069b18e2110', + id45: 'e7467366d5754be2b7d386c9737ab87214c26314bdc3489702e09c719be1bdb7', + xPrivKey: 'xprv9s21ZrQH143K2n4rV4AtAJFptEmd1tNMKCcSyQBCSuN5eq1dCUhcv6KQJS49joRxu8NNdFxy8yuwTtzCPNYUZvVGC7EPRm2st2cvE7oyTbB', + xPubKey: 'xpub661MyMwAqRbcFG9Kb5htXSCZSGc7RM6CgRY3mnap1Eu4XdLmk21sTtdt9iWAiL64KazU3QWrYEYSRAKgLvkMRe8JMxffDvt4AhCDzyMsnsT', + xPrivKey_45H: 'xprv9upyD5bqT9HDgybvt15zN7VZk9TyTzenFuBktJfyweXtVSr9eNYpnBWQQvzxaW8kDWLeiddLu9brYma5WdtD1Maev4aGUfJjiy9EBc1QSFg', + xPubKey_45H: 'xpub68pKcb8jHWqWuTgPz2czjFSJJBJTsTNdd87Mgh5bVz4sNFBJBus5KyptGBWgA4V6LGCi12s4Mw4S1JC2GkqX4NJ4kfQ47XqRZLbyM2DY9Jd', + xPrivKey_44H_0H_0H: 'xprv9zWRZ7CXrC4z9xA9RRBFXohmPKbyCajWaCNTHPtwNeJwTnysHG5QK7WMqpNLVtvqGxts7WNcNtqBLfdaFdCGknDPXjLKt2E2BUrPaFDqrLh', + xPubKey_44H_0H_0H: 'xpub6DVmxcjRgZdHNSEcXSiFtweVwMSTc3TMwRJ45nJYvyqvLbK1poPerupqh87rSoz27wvckb1CKnGZoLmLXSZyNGZtVd7neqSvdwJL6fceQpe', + xPrivKey_1H: 'xprv9upyD5bqT9HBkWym7TvTH3njEzTnjrtkLB2sg3DD2CxxA5hZKGee1sYJUtD8C4QaeATLXQ33TirRzRhuTGDBA6XRoYDMwfXAj1KSmGyNBio', + xPubKey_1H: 'xpub68pKcb8jHWqUy14EDVTTeBjTo2JH9KcbhPxUURcpaYVw2t2hroxtZfrnLBw1bWzBrHbEJA48QmZ8DB9gTvhphKSitC15SiYx9k2ncGh55Hq', + privKey_1H_0: 'a710be25950738a7d13637e2e09affd7f579a3479fd7cc024bd9459f8fba6659', + pubKey_1H_0: '026e3020913420a5b9425952627f0a074c9235e7a329869b322061f786e997ae0d' }, { - id: 'cf652642ebf997460d642231f9929c39257bcd4c42e9ecb312c226961e21c882', - xPrivKey: 'xprv9s21ZrQH143K2XtR13LpXVAPaGTFsPY5dJfNvNTShfhdVW16vdDaYGjLwHmyKLCtv3F6h9NpKsAGusmKCZNQvKcYqYf9MjCLwJwsoEyAuge', - xPubKey: 'xpub661MyMwAqRbcF1xt74sptd788JHkGrFvzXayiks4G1EcNJLFUAXq653pncPaLt6vcEARqSHopD8PHT5PWkDryuZNBYpxX5wUdeV7DtgjKoL', - xPrivKey_45H: 'xprv9v8AzYcRrUwmJQGbDDKXvydEG2NZZDqXn9J3B6au2S8yhLfJyQidhZgMfTpFxBqAkUxgtTDc9JWFt4JaTmwKNLGxr6oSKjpaheQ5EG8jHz6', - xPubKey_45H: 'xpub697XQ49KgrW4WtM4KErYJ7Zxp4D3xgZP9NDdyUzWamfxa8zTWx2tFMzqWktiJG1yDo6mYpp8NAySfzRS8JoJyz2Br26vARWY9d9QAJdYFYh', - xPrivKey_1H: 'xprv9v8AzYcRrUwjNBGAL5pzBaLvDBwepj9YVDM5UHjZXYYdoAxi9uaJjALUKPRcMxZZWq265CZuEfhTbwE6BD1tndRGESV4Jbr4c3pLanhnZHm', - xPubKey_1H: 'xpub697XQ49KgrW2afLdS7MzYiHemDn9EBsPrSGgGg9B5t5cfyHrhStZGxexAdekvK84XDZGunARoEzzHWmPjPwaRkKJwV2KWiRcHxVPfwwWKtg', - privKey_1H_0: '64274141d3ed98d3ee159409939627bd32229eac8b29cddc4c744c4e7f20235f', - pubKey_1H_0: '02787e683b17a4729e4e9405f80b74fb19b369133dd1c98b801230ae6603cc28aa' + id44: '842c048066e7d10ae1bbf67edccf69f2e5ff9a754d0c2b5524f0d01a87d6acbb', + id45: 'ee75154b646277c8d0d256fc1a0aa0470e4c3435497f208092c865737040b55b', + xPrivKey: 'xprv9s21ZrQH143K3BwkLceWNLUsgES15JoZuv8BZfnmDRcCGtDooUAPhY8KovhCWcRLXUun5AYL5vVtUNRrmPEibtfk9ongxAGLXZzEHifpvwZ', + xPubKey: 'xpub661MyMwAqRbcFg2DSeBWjURcEGGVUmXRH93nN4CNmm9B9gYxM1UeFLSofD6gtMnRYeucgPjfrWNxaAEhiT4di6HLty8Un6aheCKev4REvhZ', + xPrivKey_45H: 'xprv9uHco76yN7arptqnjGr7LiC9VqvXhTYjYeZ4k9MU6kfrX8zBme1gRM2jbBxxt3zfeKBkmpQK4FQ2XFXXkJd2rBE2qoPdMjfC4xvxJ8iqqPW', + xPubKey_45H: 'xpub68GyCcdsCV9A3NvFqJP7hr8t3sm26vGausUfYXm5f6CqPwKLKBKvy9MDSUA6ot6WMzXCWFyWCFfZyhcqK4uZGPZL83rKW9KfuGLPJ6P38Ex', + xPrivKey_44H_0H_0H: 'xprv9yBDgTV58cC2XLCp3bGpe6tvmVHxJdE2GTkqM64TUjgRdp4vBnaF4QoMrsz3BjmFQDEx4nPaC4tfk7GUrNFpJZmQgxvp1H7FoveXA7rAPqH', + xPubKey_44H_0H_0H: 'xpub6CAa5y1xxykKjpHH9coq1EqfKX8Si5wsdggS9UU535DQWcQ4jKtVcD7qi9TCvhV31N3QCLdEaftLjNdXv8R72XiorEnrTfn1ULjA35RgQVp', + xPrivKey_1H: 'xprv9uHco76yN7aptKFWZW9Cc9KQhwDbFMDeK1C5jBTrN8JKUinAzAZt9G7fL5rcDBQYtXWT7umm4r91tx3kJRZKPKVkd35dzgfwMBLUxBHneAY', + xPubKey_1H: 'xpub68GyCcdsCV986oKyfXgCyHG9Fy45eowVgE7gXZsTvTqJMX7KXht8h4S9BLnHyWx8YbT8kRX5v4xnZAZvHmdUSdQhvZ2q97PG8m8Ef86yjzK', + privKey_1H_0: 'ee062ce6dc5ece50e8110646b5e858c98dba9315cdfdd19da85ab0d33dcac74a', + pubKey_1H_0: '02c679bf169233a273dec87fae5a1830481866c4e96a350d56346ac267808c905d' }, { - id: 'ff98c0b625e7824219f6a8054470a332ce1bdc9bb5a46a57eb424ed0848c0183', - xPrivKey: 'xprv9s21ZrQH143K3F4gznfMiq1rFLNAJbWnC9HJdkdrL7xpt4YxeLWRJTPXFvMn5Ky8rqaunWNTxwrLF6Nhptjyq8mAJvmc13R3fRJLDWWmgon', - xPubKey: 'xpub661MyMwAqRbcFj9A6pCN5xxaoNCei4EdZNCuS93TtTVokrt7BspfrFi17DdM2Z4HYYseVoZ2nmeu5mCvsn8gYGyW94m7EfXQDbXymLw2Yi1', - xPrivKey_45H: 'xprv9vUaCxyzkmLHHUpZZFUvh9oqzm9R6UMCTsJn8F7mDN3AR7rwLobojATJYgSx3pJjrv3EWCaSYeBjhouiM1m9Y9kAHAku3ZiAxZyWRxQZyHf', - xPubKey_45H: 'xpub69TvcUWtb8taVxu2fH1w4HkaYnyuVw53q6ENvdXNmha9HvC5tLv4GxmnPxemekKx8jVx7kmbtATgH5KXEMibhpZh7x2NN8AXcwF7FvJmvXo', - xPrivKey_1H: 'xprv9vUaCxyzkmLFMyQhaN3knpLSWHeqTbNbxVa9cHgJ88m287K6iLazaT5CYBgCaW6PEJy7bKeDZ2Wi8UsWnzND9QtzSy9CFWdgMxZAJ3mP5hW', - xPubKey_1H: 'xpub69TvcUWtb8tYaTVAgPam9xHB4KVKs46TKiVkQg5ugUHzzueFFsuF8FPgPTLeRcZmq3EwbrnYGX6gHmShDNL1YgNqjo5ctqw5Z7WXe9LSErX', - privKey_1H_0: '25ac29ca9bffe835bdafed55feebe606c7250e2b399cd3d95347a746dd6d3388', - pubKey_1H_0: '031a69ea8a3d487743c7d6acdcecc151c685fd25f8f41b327f4c25d8b21f6c80ba' + id44: '719f4ee61c691fbf0ebefa34e2151a1a3dbe39cf2fa4a498cb6af53600d30d1a', + id45: 'acd666d7c677d9f2c85b55a5fad1610fe272eac46ef7a577c7aeeab0b1474e43', + xPrivKey: 'xprv9s21ZrQH143K3xgLzxd6SuWqG5Zp1iUmyGgSsJVhdQNeTzAqBFvXXLZqZzFZqocTx4HD9vUVYU27At5i8q46LmBXXL97fo4H9C3tHm4BnjY', + xPubKey: 'xpub661MyMwAqRbcGSkp6zA6p3TZp7QJRBCdLVc3fguKBjudLnVyioEn58tKRFPmGMkdGJWMX69mgZWHKrKmpQ3fwBXeFjLc5Sd2rnxcQthSW42', + xPrivKey_45H: 'xprv9vGShhcT8rDFomkiF72D5UXt5BsVydntE7rYbKaa9qtscnWVHaZSPkV9N9SkDkhWbDvkmTGVLYKJf23Z8QjXMRRMTwQYLj26VkPbsKxnUwV', + xPubKey_45H: 'xpub69Fo7D9LyDmZ2FqBM8ZDScUcdDhzP6WjbLn9PhzBiBRrVaqdq7sgwYodDPUzXyGxpEfTCQyTAA9baAwCApmTiing2S7KTQTmLZjw3Wmaojn', + xPrivKey_44H_0H_0H: 'xprv9yVpGpTwwtc5aNLkACpuAWmyBUZy1hYy22WQ58Cx614fM7oHpYyjrWbYbExMtZa3sTTywU9mziYBoSZtR4rXVhvfvoVmqZPsYysvpYNUDrk', + xPubKey_44H_0H_0H: 'xpub6CVAgKzqnGANnrRDGEMuXeihjWQTRAGpPFRzsWcZeLbeDv8SN6HzQJv2SWxg2QSQ7GABwgZJb9KzK8yTXB44CRkqy3cMGEEzniWvBKRCGr5', + xPrivKey_1H: 'xprv9vGShhcT8rDDu2FvheGgrGvcg5JLQce7kXqC2VnhyYdnggErZA2KJjDwSvQhFZtBVH8T7QQd7JTDXVSvgEijvoYdvCeoRyjQL5EnHRUZgre', + xPubKey_1H: 'xpub69Fo7D9LyDmX7WLPofohDQsME78pp5My7kknptCKXtAmZUa16hLZrXYRJCL7wLWtTWzMvEn1HKQ8uzp4iLbkS72WPJsLTLayGPw7HGh6hEW', + privKey_1H_0: '5009c8488e9a364fc24a999d99a81ae955271de1d06d46c2f2f09e20c6281b04', + pubKey_1H_0: '03338a3b7c08e9d9832e1baff0758e08f9cc691497dd6e91d4c191cd960fb2f043' }, { - id: 'ab0bc4e4c251376d733d9ba63242fb0ca258b0dff4def1f67e2a5e0441b86a8f', - xPrivKey: 'xprv9s21ZrQH143K24Vs7f7xhvrMgojXW5444PtT7Qkw3opZ2Dwpxwz5tERpSMtEa4YTgrfDhRqzw8X9b4TnCKrhcoZiPRgJDQaH9Lz8LEvLSpF', - xPubKey: 'xpub661MyMwAqRbcEYaLDgey54o6Eqa1uXmuRcp3uoAYc9MXu2GyWVJLS2kJHeTLPTohioPuLNadiYZDBRXzzeZLFTvZTgEvrSHDTExFuutcPX3', - xPrivKey_45H: 'xprv9tzvDbfzt6dy5aiWx2qovpw2sxLUHm5zM3ZBX3AH1oTEa7PjZoHWhfSZp5LnmMayDCHJEM4ATCDWECkQUjgxYjnEczMjyvujwzUAWXNMy8L', - xPubKey_45H: 'xpub67zGd7CtiUCGJ4nz44NpHxsmRzAxhDoqiGUnKRZta8zDSuit7LbmFTm3fMTBmgk4aiZyGMswwdEiTr3DPwhc6Gu7EEgaDyRk9G6VTTmMNTw', - xPrivKey_1H: 'xprv9tzvDbfzt6dw8EKieKg6hLykKuvTB4nKocQNp977xoQr6TCxDqqdDf5oK21i3FFjPG2jCtSxuGnJpBYnpmtHEZyRuxuG1RmLC3NVvVKzYEe', - xPubKey_1H: 'xpub67zGd7CtiUCELiQBkMD74UvUswkwaXWBAqKycXWjX8wpyFY6mP9smTQHAFFY8vUDcEnoUfbgUJsktwQ96igzGNfT22ujQFiSKBFAqzkwpcW', - privKey_1H_0: '5ff5b3cab4ae8f1487b75ee3b688d0cb54d11db3d21044ae6eaabd04a2a379c9', - pubKey_1H_0: '030da118db806d1758983ab078c8b5e5651270cba8a587e34337eb0b9b5e555b21' + id44: 'e225a29864060823df67b98432b070a40aad1bf9af517005b0b5fe09c96e29c9', + id45: 'c65a89f64794cb7e1886c7010a32dd6fa362d3e81710bac32e97e325b9109fd8', + xPrivKey: 'xprv9s21ZrQH143K48nfuK14gKJtML7eQzV2dAH1RaqAMj8v2zs79uaavA9UTWMxpBdgbMH2mhJLeKGq8AFA6GDnFyWP4rLmknqZAfgFFV718vo', + xPubKey: 'xpub661MyMwAqRbcGcs91LY53TFcuMx8pTCszPCcDyEmv4ftuoCFhStqTxTxJoy35yjp2H3qQtxDYGe1gtkZu4T7mR7ARK1MLYte2fptZVt6hkD', + xPrivKey_45H: 'xprv9vYQ7HC96LzvDwJ2r99m4ALTEtXTfUCHPbs7gpPzWboFP3NFgzMjXPf9Pp7D3vmTUqRtsZfSfVghvUhLKGGAs3LdVf9rf89TgRAvqAWMSwN', + xPubKey_45H: 'xpub69XkWnj2viZDSRNVxAgmRJHBnvMx4vv8kpniVCoc4wLEFqhQEXfz5BydF79LoErgaokMLv7u2sRNsoiJqjr5nyfBqJGykfR3kvCex7z8Ar3', + xPrivKey_44H_0H_0H: 'xprv9yn1LP3ViLTXUYgZP5sDFG94H6sd8v5AKRBbd35Ub5FjPq2HuzC9bAb6LReTXRSA4XTtYvzwpiktvuRrLY98df2KZMCuyZxXUB7FpBQ2ndA', + xPubKey_44H_0H_0H: 'xpub6CmMjtaPYi1ph2m2V7QDcQ5nq8i7YNo1ge7CRRV69QniGdMSTXWQ8xuaBiNt74nJCMZjaeNgRbMiZmdgrEqogvjARnBx8k4y853MZGAkjmo', + xPrivKey_1H: 'xprv9vYQ7HC96LztKQouECporZgezbjktKyhipWavsJvEJ4ok577RxVcmnyeRUUbAqmq5FnuhDie62Z962xNvF9JwAXoJZx2VeJPUWMPHsuoAkM', + xPubKey_1H: 'xpub69XkWnj2viZBXttNLEMpDhdPYdaFHnhZ63SBjFiXndbncsSFyVosKbJ8GjGMT7LX1rF6aisHM2YmFKye7bR3xQn5Bj875iGxyVrbRF7k5N8', + privKey_1H_0: '460ee692f05de66b5d8e2fa1d005a8b6bdb1442e2ce6b3facfcee2f9012c9474', + pubKey_1H_0: '03d0e0c526619b158aac9a8de8082f439df43d389ec50cb54386c3d87cfde4c99b' }, { - id: '65fa0d2c2bd1d09da631e12c746562687d43364104e65bc7e0009c9cd6efb9f8', - xPrivKey: 'xprv9s21ZrQH143K44coj9AbkrT5B43PjRY91RnobCw8WGQVY7VvUDDA6VauoYs2PsKrW8MWQb9qUi2KgoWatAKf2qGZLBAT5wo98zxDsm4wog8', - xPubKey: 'xpub661MyMwAqRbcGYhGqAhc7zPoj5st8tFzNeiQPbLk4bwUQuq51kXQeHuPeqb4wxi8zkrzVThRKkBEGoT5H3M4kfFrdER1BfNCr3AV7rxSHB2', - xPrivKey_45H: 'xprv9uAoUcLPWHxJS36XQi8Mt3rywiVXK5MsY4bn6hLgyPVb8WDoqW81vvXrN7kwR9r52GTDEs6CZBXdAoEbqEbEsLxPVfCRRFZSzAnXFpaxnoy', - xPubKey_45H: 'xpub68A9t7sHLfWbeXAzWjfNFBoiVkL1iY5iuHXNu5kJXj2a1JYxP3SGUirLDPueKxxHmee12FBnjcQzspG2f8KkCqoV8vaUiVt75L4Bhsqffep', - xPrivKey_1H: 'xprv9uAoUcLPWHxGY9eCE8gXxRSTUkVpjfVWg6LQaopBQHvv8LbPCzzQHcu7mi6hWsV6EGnNqPX8xxJSwroeBiRj3TVfsGH2etA6ZNtDZqskL2h', - xPubKey_1H: 'xpub68A9t7sHLfWZkdifLADYKZPC2nLK98DN3KG1PCDnxdTu18vXkYJeqRDbd1MTj2mBjYPCix49qz2wsBWAKuPZ5crUMLemYPK7mZd5GpF4s8D', - privKey_1H_0: '259dddf1b0bcc4ab005002de457ce118771cb0ebfa8711f2b65bafec09ccfa22', - pubKey_1H_0: '02e3a4d6d1d2043e4643c1505a7dfd30c27d2b34863e7dbaacd50f896dfb16dd86' + id44: '120416cd4c427a7e4d94213cebe242f56a06bc6dd5c5c6cae27dc920a0ddf1fb', + id45: '65ae087eb9efdc7e0ada3a7ef954285e9e5ba4b8c7ab2d36747ddd286f7a334f', + xPrivKey: 'xprv9s21ZrQH143K44Bb9G3EVNmLfAUKjTBAA2YtKxF4zc8SLV1o15JBoddhGHE9PGLXePMbEsSjCCvTvP3fUv6yMXZrnHigBboRBn2DmNoJkJg', + xPubKey: 'xpub661MyMwAqRbcGYG4FHaErWi5DCJp8uu1XFUV8LegYwfRDHLwYccSMRxB7Z3L1NgKychKdXQvbVEyDhSwNnNnnNKh9mBEAdQ5tv2guK8ywKU', + xPrivKey_45H: 'xprv9vKBPQfZS4jWfLtSAoSeBHdLU1yJicKVmhTdbewKwQWU2CPdm61scXvKXHaMshmDEBaspUc994GAzZK1x7PtfzZ2PGtGHHhJhDn3yoT8gAi', + xPubKey_45H: 'xpub69JXnvCTGSHospxuGpyeYRa523oo853M8vPEQ3LwVk3StzinJdL8ALEoNa8AwwJsAWrfFtwfDgTCB9o1kKwn9n2tdN9nbdrw2zs5rNqQpM2', + xPrivKey_44H_0H_0H: 'xprv9xuBGYz7dmjgyroeDKMKyjSCiWyqskWavGjLPJbC9W32TkTy8NqXX4y5WQKcK24SnGiDMpUhFUNqjh284RjHoNYC2gUKagKfVfWZqn1gey4', + xPubKey_44H_0H_0H: 'xpub6BtXg4X1U9HzCLt7KLtLLsNwGYpLHDESHVewBgzohqa1LYo7fv9n4sHZMdzdBh5pxiAgLyUvsAqAQ5J1CRK4uA9htCWbKbVxFsfMEij3MDA', + xPrivKey_1H: 'xprv9vKBPQfZS4jUjK546Cjyf8ZgtEvRhQyjfuE89DAnYatevTusA8wwXiDxEFPSFk4fp8wwC5nRGmkSnTi9ascW9LJjujm2g36eGocNMtEsDwX', + xPubKey_1H: 'xpub69JXnvCTGSHmwo9XCEGz2GWRSGkv6shb389iwbaQ6vRdoGF1hgGC5WYS5YhxaM6RpfzsbPJqfR7iCLdEMSMUYvhaG4tvmEsn8ztwtysjgp8', + privKey_1H_0: '7a5158b92d9ed4cb9644ddbd472b43428832a5f3bb91a481532a081908e62b2e', + pubKey_1H_0: '02b47d5c977c93c883f369165ebc2b564d14a52712ec6892f7097fa99e0d36ca20' }, { - id: '551984256819ededc0ee851f99546fc1856d20f883960c868917626091128d30', - xPrivKey: 'xprv9s21ZrQH143K27WsnsbtSync63dMn5ADYT7yf8ARMGtG5CyhAxhcNfjrx9yYUYTApZUfkLqvgYzeLwB5K56pfWkLxvy8wSK4Bog5o8XUKJ6', - xPubKey: 'xpub661MyMwAqRbcEbbLtu8tp7jLe5TrBXt4ug3aTWa2ucREx1JqiW1rvU4LoSwm4B1DoXhkGSTRSKpSUYQVENP9AYX3EHAP4oyeGtGBeKJ7hwd', - xPrivKey_45H: 'xprv9vdUpVGJA9iQf5GDq7eR34N75DjytYj5adyggnqdA9fCCfgofiQEELA7o4UiNQ2zf3xjHyeNDhQhib7RQjTeYnapWxu1eSQdCrExzb9J8v7', - xPubKey_45H: 'xpub69cqDzoBzXGhsZLgw9BRQCJqdFaUJ1SvwruHVBFEiVCB5U1xDFiUn8UbeKmXemb5ENTgJhoxYKe4yQazwsXFScehhMrc5vAXUVK1mQfyVST', - xPrivKey_1H: 'xprv9vdUpVGJA9iNgurZGgm6HhVHF7GQbcpWbezkQcCnwCdz5Tigip2AyEtg2Jz98e7Mthm6yEK72CzNagyMyCGQNuYhErB1Er1ykTpBKeTjsan', - xPubKey_1H: 'xpub69cqDzoBzXGfuPw2NiJ6eqS1o96u15YMxsvMCzcQVYAxxG3qGMLRX3D9sZxY3V2CLNX5rZZYB4QUg5zLb961c7CPD46fM9M4rVatc65qeF6', - privKey_1H_0: 'c8e4cd009e0ed12564eb96b2ffc68181716748ec9a7f6b218b08587337438bba', - pubKey_1H_0: '032137a03bc2cbba134fc3a1529358b56ca0764f699f6e3cc74118044f9bfad33b' + id44: '85de9f025ee190fab7cb1bd9b6772c64df26188ce705d4f258c5adaf7bc610f9', + id45: 'dacc5c350cef4449a3ca12939711c7449d0d6189e5e7f33cff60095a7a29b0f9', + xPrivKey: 'xprv9s21ZrQH143K48PpVxrh71KdViTFhAaiDSVtNFkmbWNYjwwwPbTrcqoVXsgBfue3Gq9b71hQeEbk67JgtTBcpYgKLF8pTwVnGz56f1BaCYt', + xPubKey: 'xpub661MyMwAqRbcGcUHbzPhU9GN3kHk6dJZafRVAeAP9quXckH5w8n7Ae7yP8e2Zh6SPPKFn2K6oE3GBpcz9QzfJTNRWXbY7w1L3nGLE5beZL1', + xPrivKey_45H: 'xprv9u8SxxhEJB4pzk6P3VKwmRRFqgHX94kBHDTBDaFXeaMS7dggkQcf2Tt14AvTvMJYjpogB2bVgmkHEW4Ze1iVzZeF5S5FLj6zpqwyrwh9yRW', + xPubKey_45H: 'xpub687oNUE88Yd8DEAr9Wrx8ZMzPi81YXU2eSNn1xf9CutQzS1qHwvuaGCUuTHJXkvzrRSiRtuJid3LsPaS3hUAdahdy3ZpQCcsLEQjC2Q2tNT', + xPrivKey_44H_0H_0H: 'xprv9yTxyEKAvyMLaocWZ4rXUUC7xWUqzhcvDJc2Ms7fndKaSfityJdaGrhLCSsLZz2E9NG3VkhbbsYwZVW9J5W3BQegyWWNsuEKmkdCsr9DRkM', + xPubKey_44H_0H_0H: 'xpub6CTKNjr4mLudoHgyf6PXqc8rWYKLQALmaXXdAFXHLxrZKU43Wqwppf1p3kj8sC9b9Sx5uqXfnATdvz5jeFYKELnngksCzwABLfNcW8CPkfg', + xPrivKey_1H: 'xprv9u8SxxhEJB4o6KfWV3csVTsJzhePBtMz8CmJCcn83vaWSiydgv6CADdbxjq3YsnusKPyPw1G235KibW67VjdbJ2ZoTG4nFA6ivwPRBmScKW', + xPubKey_1H: 'xpub687oNUE88Yd6Jojyb59srbp3YjUsbM5qVRgu11BjcG7VKXJnETQSi1x5p3onkhodzQcydFLTkZ3ym35UmAAWrjLY4rDAt2RjHKidx6AzgVz', + privKey_1H_0: '3c49816d4e83d8758f89e8e104e3566a8a61426a9b7d4945b34212fbbb8e8290', + pubKey_1H_0: '0307ab8c0d8eea1fe3c3781050a69e71f9e7c8cc8476a77103e08a461506a0e780' }, { - id: '364783ba3fa7f82a188ad665ddee376a0abe930194b1dfc148199b54205b992b', - xPrivKey: 'xprv9s21ZrQH143K2scQXETcb8fMvaao2ApiBWsuYiLz1GLr3T5UWi8bmEvpVWvTGEYjcMEnHEET4RrouGhYo3qBX4yuLXJ9q6viSFykJcGmNXn', - xPubKey: 'xpub661MyMwAqRbcFMgsdFzcxGc6UcRHRdYZYjoWM6kbZbspvFQd4FSrK3FJLpwdhqsEL3PYLarn1Av24qDwxfizXkgaKmvLAdJ16MPao2o4MNq', - xPrivKey_45H: 'xprv9u2Yq2Sc1xugbPWe4LBefYGiRKbytz9vpbZgWdqk1fZKoLSeTFC5Qb6ospfKzbmNai2TCEX1uRtgfaXgnnwUFyGxEUtjwVq8s39kQcv8SxU', - xPubKey_45H: 'xpub681uEXyVrLTyosb7AMif2gDSyMSUJSsnBpVHK2FMa16Jg8mnznWKxPRHj5kWjpHXiJUutUZqAmy6EpomMGRP5im9U95jq85aMwyS7YcJVim', - xPrivKey_1H: 'xprv9u2Yq2Sc1xueeYe6GSDWWr7LUJTyjzcf1EjWPLEmT6T12je2PPk8D47h65RFC5i2Xqfj5og2FAJFRSHNaa2dwBtJi4AoBDSqL1NLhyXLyGf', - xPubKey_1H: 'xpub681uEXyVrLTws2iZNTkWsz452LJU9TLWNTf7BieP1RyyuXyAvw4NkrSAwMmgHcYNTYYwtdMQPLQKXL8JeEzHAfAhYVbA2xqjjkfCnoAd4tC', - privKey_1H_0: '97456bcde0704e054a63a96c11853ba412ab7c33e2ef9fa8661a66db2ce6a94d', - pubKey_1H_0: '022edcbe1542c789bb0c39e6fef3cd15823d396e355b05139659a059d08da62e50' + id44: '4d0c1eaab0aafc08aea7328f9ed1d3fc2812791ad2ebb9cbc1a8537b51b18afa', + id45: '9129a0454adcf659f4f9d65a9b4dc4f9793bd1f59664268b56a7ef73f29f1b8a', + xPrivKey: 'xprv9s21ZrQH143K3pgRcRBRnmcxNkNNLmJrpneMkEXY6o5TWBuJLMfdRpAWdb2cG3yxbL4DxfpUnQpjfQUmwPdVrRGoDJmtAf5u8cyqKCoDV97', + xPubKey: 'xpub661MyMwAqRbcGJktiSiS9uZgvnCrkE2iC1ZxYcw9f8cSNzESstysycUzUsDCU6KnnjR29VZ1eRAXDgEXfYxGw1B9E7VLSAcHa9UuifSozmy', + xPrivKey_45H: 'xprv9um7h3RnANyeDTTn7SCVckpA65rWTL7MExcta2iz8BaDqxNic6Gmjgv9GfYVs95x5tQJUpeJiuoApbnrwZQQZja5uUQxaySV7wMBV3P27qn', + xPubKey_45H: 'xpub68kU6YxfzkXwRwYFDTjVytkte7gzrnqCcBYVNR8bgX7Cikhs9db2HVEd7yFQVRV19xJ93DHh64fr5jDyWssXGuSt5AsiBurY3eaffcwjafa', + xPrivKey_44H_0H_0H: 'xprv9z9zXfC9effsNS9zXSD4XBUjuEqh6d5ofeF3xqSZPuq1DwAhTDZVuzpNwWfzTgpTm42s6DicHaG2eWesUQ8Cm8Es7kKyt1tCq6W9VM6ot4H', + xPubKey_44H_0H_0H: 'xpub6D9LwAj3V3EAavETdTk4tKRUTGgBW5of2sAemDrAxFMz6jVqzkskTo8rnq5DsoAnTyqnukWsPG7JueVLwLAN6vLXA4MpYceDPQbA6WuFfiV', + xPrivKey_1H: 'xprv9um7h3RnANycGJkyD9iAZy2BUkMpWr6SDV5WzgNYTRjAqX4WpYS7b8Xn8hGLiwmUhCvXnwhdhPEVejSpGVKFD1mxLDy7NSJ5C5oK4SELwJu', + xPubKey_1H: 'xpub68kU6YxfzkXuUnqSKBFAw6xv2nCJvJpHai17o4nA1mG9iKPfN5kN8vrFyzU2hYTBcVcDNoxcDbyqcauCxYJZpQLFzDnaHY5Uej3FCvYwU1P', + privKey_1H_0: '87f8a2b92dd04d2782c3d40a34f09f2ab42076bd02b81fbe4a4a72f87ad2e6df', + pubKey_1H_0: '02a0370d6f1213ab3390ac666585614ad71146f3f28ec326e2e779f999c1a497eb' }, { - id: '23892f5b034019d81397f97570a1ea5b9f2977654269623f42abbdb33fe7c4e0', - xPrivKey: 'xprv9s21ZrQH143K4bhBXV3MvydmC9WKrJ4pY4w9zTyihwunrPsSim2vb5VDsX7zVzLPLXVTN6y2Gc5y7qpYr8Vdow3v3a14zgccPobib5Q5jik', - xPubKey: 'xpub661MyMwAqRbcH5medWaNJ7aVkBLpFknfuHrknrPLGHSmjCCbGJMB8sohipo1DSXpR7xcnZfsUooP6k8RVoQQ7Ewtmj7jnLbCQDGcBi9Sr3L', - xPrivKey_45H: 'xprv9twC9XfPNCFMmqBU3UyVLByyFH9QgmUuwy1gpteoAiLN4rSdG3hcLHjqXSqf36JXwavTFNhawGPYYtZUch6qJNoXfZ2XAeyeeWwrnnGdpD7', - xPubKey_45H: 'xpub67vYZ3CHCZoezKFw9WWVhKvhoJyu6ECmKBwHdH4Qj3sLwemmob1rt64KNkQVNrFhy85WAcokstPsA3JPz6qTHqumg98Lzo6ccL7wKCL3sDt', - xPrivKey_1H: 'xprv9twC9XfPNCFKozsRsSNChbiQJpRBkjVZoL4McXGyWQRD3dkkazNW7LhFP85DeM5DKw6AgfJPX7F2VggCznFqbsbUXUdwd7QEpTRYb8Agas3', - xPubKey_1H: 'xpub67vYZ3CHCZod2UwtyTuD4jf8rrFgACDRAYyxQugb4jxBvS5u8Xgkf91jESCqi5jydqLYeATXbk58bM6vfa3kviLHuyFKMVM1KSWB1uWRBdB', - privKey_1H_0: '5f0011757ae447218cc11b0845b87b7ac9742c8dc37de8d10d590fb096005430', - pubKey_1H_0: '027e8c73e3c176c7fe992de80fa891a5b73f82151f11b1f9e97ce7b902b20f81b6' + id44: '5ae7b75deb3b4d7e251f1fc5613904c9ef8548af7601d93ef668299be4f75ddd', + id45: '37b81e2544b43ce7f37a132a748426e1566ecbb758564d4d7d07b716fbe1b368', + xPrivKey: 'xprv9s21ZrQH143K3nvcmdjDDDZbDJHpfWZCUiunwraZdcamYcafHvUnZfV51fivH9FPyfo12NyKH5JDxGLsQePyWKtTiJx3pkEaiwxsMLkVapp', + xPubKey: 'xpub661MyMwAqRbcGH15sfGDaMWKmL8K4yH3qwqPkEzBBx7kRQuoqTo37ToYrvLJh7JpV5FQSverERMcdF4HcP1UCiie2ayeMXRq67zr75PzMKs', + xPrivKey_45H: 'xprv9twbreBMkWvwgk9kvzrdygVdir22YSi1dMwMd3GzdhjAD6seKqFidYpehhNR2JL3p41Yeun8ELYAs9pCvKqeH1mu2tQc4PZgWKAUJpaanKo', + xPubKey_45H: 'xpub67vxG9iFatVEuEEE32PeLpSNGsrWwuRrzarxRRgcC3G95uCnsNZyBM98Z145fPbqMgjVG4fPpKrsU7hbq33cpmPXAFF23VMCp7e9ucCGm2v', + xPrivKey_44H_0H_0H: 'xprv9yKWzK3qGKoWD7vkUpPhHQSUvyPrFBL2nBkZHDQPqjZfiR3ws6tL7LoGtrUqSye8cBeCFUzHZhG4i8RPqQuVQGVXgG6dGi2sor5SLh8bN59', + xPubKey_44H_0H_0H: 'xpub6CJsPpaj6hMoRc1DaqvheYPDV1ELee3t9QgA5bp1Q56ebDP6QeCaf97kk9SZPvxDkxt9RGmZBNdDyAyyHMfWDXYmJgAd1dutwgVaLQraVC4', + xPrivKey_1H: 'xprv9twbreBMkWvukJn57LL7aqa4vY9Zg8w8A19uUuDFqzuauaHoRmVTaETtfArRsvzdz41FY9pgLXJspuAF1nadXPTRSBgpNKoUwPM4eWTehSp', + xPubKey_1H: 'xpub67vxG9iFatVCxnrYDMs7wyWoUZz45beyXE5WHHcsQLSZnNcwyJoi82nNWSfrNKeqRnGpCuFGcsTuHi6zEBucxRJDgZbfyny2sUs88ZaMey1', + privKey_1H_0: '66230b6b8b65725162ea43313fcc233f4f0dd135cea00d04b73a84d3f681ef25', + pubKey_1H_0: '03f148bde0784c80051acd159b28a30022e685aca56418f8f50100d9f8a0192c37' }, { - id: '5f69e118def0b38ee88c8d47c7254136d081221bfd2c6c3d9d4e7890bc60c22b', - xPrivKey: 'xprv9s21ZrQH143K3bFvCtYiQZr9seRAjj5sBH3NsvF2mut5NtuSL6CVWhYiKjsdDCXLgxwYB3GUsXqpSfmkjSEoghASHxvS4fNPwbkwk3cn1DM', - xPubKey: 'xpub661MyMwAqRbcG5LPJv5imhntRgFf9BoiYVxygJeeLFR4FhEasdWk4VsCB3EEw6meT2hGYTE2dN6x1597ekpFJp5wtqoNo25QFe8LBGZatsN', - xPrivKey_45H: 'xprv9ugBxZE4n42UY137XuYcWPEf1WrtN7Gyj7LEpDyQXwhPQ6iHtXe9LuBXyMyPgFABe9sagz3KKfTXGsJJnDycv3bZv2vZLibnLZyCX5ug5PT', - xPubKey_45H: 'xpub68fYN4kxcRamkV7adw5csXBPZYhNmZzq6LFqccP26HENGu3SS4xPthW1pdKoSrb4KVonrxpE1pPvi3CjzmB1X19xpmE2dSJwdBLw98Au1EA', - xPrivKey_1H: 'xprv9ugBxZE4n42Sb78sFa42f8K3mSCm5buKAXTz4Yo7oJvfRFtuy5AtedbsdGMtFULvesJ7KjJtuq8iUv3gxksYFohBHf8jEBihrx4ZkFqLivb', - xPubKey_1H: 'xpub68fYN4kxcRajobDLMbb32GFnKU3FV4dAXkParwCjMeTeJ4E4WcV9CRvMUX3t7dUu381jjAir4hB588mN4mFkWCVtoDSH8RhEPCzXJtvzHZG', - privKey_1H_0: '084f923954681d1bfc6622ff69fe225c89561f3f8976832acb436c6b0dc6dd01', - pubKey_1H_0: '029f7003e5e0a3c53db12439b4b7862c471d4e2fd56879497022cc420ce7a13fee' + id44: '98e78a9cb2ab340a245c5082897eadb28c367319f97b93e7b51b4d5ca5cdc68e', + id45: 'e1557d3421a8884fe007674f3f0b6f0feafa76289a0edcc5ec736161b4d02257', + xPrivKey: 'xprv9s21ZrQH143K2uYgqtYtphEQkFAgiWSqahFUWjgCdKykJagiNDz6Lf7xRVQdtZ7MvkhX9V3pEcK3xTAWZ6Y6ecJqrXnCpzrH9GSHn8wyrT5', + xPubKey: 'xpub661MyMwAqRbcFPd9wv5uBqB9JH1B7yAgwvB5K85pBfWjBP1rumJLtTSSGnCdsJSXfwmTyexsRjbUhzB4J6LWfL8mC2Ka117JrnXetyCzk3r', + xPrivKey_45H: 'xprv9vQyc56VRojih8sHwa4gitBJsva7TiRnMg3pBUxFR8NRGNxAbAr3vEb9iH8fxTZmmEnvzLee5DVUckDYJKfRnuxVz6Nm4Wpq1mbHE3K7WeK', + xPubKey_45H: 'xpub69QL1adPGBJ1ucwm3bbh6283RxQbsB9dityQysMryTuQ9BHK8iAJU2udZZNN2t3MNSGnzFiu97BUCjjMUAXqb4caURCMEStMorDU3y3NtgB', + xPrivKey_44H_0H_0H: 'xprv9yowiefD68DG79Xh3J6wAo8u5aTwZ32bsa5jtB4qgKS7r6Tszm42ovSgu7naNPCwT2wt42BXFR4sviY6PEpihhz8LRoHVj2v8WRydzEzqfd', + xPubKey_44H_0H_0H: 'xpub6CoJ8AC6vVmZKdcA9KdwXw5ddcJRxVkTEo1LgZUTEey6ito2YJNHMimAkPuaZ1NCZ1iYAadqQrCvYvrwDzb6wdJpjWWK4EBzmg6mUtVdJZ5', + xPrivKey_1H: 'xprv9vQyc56VRojgmQtXPaDpf5Y1zzmA3aH1SQUwbxqJDJEbjbZSHzpuXmabA2Lx5WThvw1srKUcNez2U4ovSh237mi6bNMufk5XrUcYbXLam2u', + xPubKey_1H: 'xpub69QL1adPGBHyytxzVbkq2DUkZ2beT2zrodQYQMEumdmacPtaqY9A5Zu51KMp7cEgWVsZCADGtqFiLoVzaarkMLXxiVYBSebSBSMZWYkNVr9', + privKey_1H_0: '9e215580c8e5876215ad101ded325bcacc5ab9d97b26e8fdfab89ef5bb6e0ab7', + pubKey_1H_0: '0265d33caaa128a77cc38ab8751c7d730e0274a212f1f65b73f637eddb3a3fb151' }, { - id: 'ebd86096bf0f7902e293f9d399361204ba5a4bf484c4a7bf3e29f8891f96dca2', - xPrivKey: 'xprv9s21ZrQH143K2JQLtqcR7QgEXwhwhLWS63pAsEgmRqPu4x9d1oPcv14qTSbV19LWzFTfMbJ2nieS7oCrBxgSKF5hmqFeBCYwFh2kSV8pFRy', - xPubKey: 'xpub661MyMwAqRbcEnUozs9RUYcy5yYS6oEHTGjmfd6NzAvswkUmZLhsToPKJj4moN2BSSonQDTL9qFYtGsqoRAEwHcxrkwqBfkPBncm23QqrAg', - xPrivKey_45H: 'xprv9uj2TGqrHjzvdEQrYby5oGHPT96YBzSNUPHe82g9j6xqcJhx19KhWoPu6RxE4EisvuUTjBAwmsTnxYyT9EuSQJvyrWntQkiQ3QSjLx3XSRZ', - xPubKey_45H: 'xpub68iNrnNk87ZDqiVKedW6AQE81Aw2bTADqcDEvR5mHSVpV736Ygdx4biNwiSFYNFzTY6UziVFffDc92iL6WmANV16SexCX8WfGcDcRvaPJfh', - xPrivKey_1H: 'xprv9uj2TGqrHjztgbraNVLwq38Smz8Mi1MQE6yN3MXQSj4DaqkyfF1Td8cxpiH4Pd88X6K8qcveM7zpGxFwmi5hB8sQGn3Xnjesojznikr6n9e', - xPubKey_1H: 'xpub68iNrnNk87ZBu5w3UWsxCB5BL1xr7U5FbKtxqjw214bCTe68CnKiAvwSfzRQkT4hp27XRHRAewgYiV6uVKGgyoiFnKQaVpDWgiexFf5f4zD', - privKey_1H_0: '8c0552de8b785bf395794e88fbe6e0b87e74dbb49282e00acd98f44064913c41', - pubKey_1H_0: '034433ac6358faebed43ae5e48061b6796a7ec4523fad547e13d16433140557831' + id44: 'f716dbeec58e44c698b34c2d81bae4699ed5a5a522281733ec50aa03caf76a19', + id45: '8a6d840580549a34422c9b150dbd1e96e369c5db69ee736caab95616f8abb22b', + xPrivKey: 'xprv9s21ZrQH143K2wcRMP75tAEL5JnUx4xU2AbUBQzVVUDP7DHZJkjF3kaRE7tcnPLLLL9PGjYTWTJmCQPaQ4GGzgWEUFJ6snwJG9YnQHBFRNR', + xPubKey: 'xpub661MyMwAqRbcFRgtTQe6FJB4dLcyMXgKPPX4yoQ73okMz1chrJ3VbYtu5PRTxMBGuXt6eyqwAuG2BEBzQPLc1x8gnSQiATS3GRzKi1BuQAR', + xPrivKey_45H: 'xprv9vDicbkxQi4VK49bqmGuj4FSMSuzubjDVBqqwjBxJzUJ9JhQDtF45Nb1ex13D82uqkLyM6YhknAHZ2s4GjuYxQGySECGU82eGkrKi1Pct9v', + xPubKey_45H: 'xpub69D527HrF5cnXYE4wnov6CCAuUkVK4T4rQmSk7bZsL1H272YmRZJdAuVWEfsWKeSfVVTA4NTfyKt4ewZ6gtdPN1RuG5fRj45RYHT5LjvwsJ', + xPrivKey_44H_0H_0H: 'xprv9ynVNvxjepWN96d9wsT1jbWR6iu3YSZNSMeSWLgUqrnKKtZNquZvkd24qNc5wbSj3rvmW7WerhFutju6orHZfena88LFqzn4FUvSMJU928t', + xPubKey_44H_0H_0H: 'xpub6CmqnSVdVC4fMahd3tz26jT9ekjXwuHDoaa3Jj66QCKJCgtXPStBJRLYgfp5TcrH195VmbZUQGdYDFbYDpYKj7a7XsB34WmVDbCFSyAFty7', + xPrivKey_1H: 'xprv9vDicbkxQi4TNYTwjWnLG6kwMMEmndxZbcVRaHxoLtJQYyB5e5napwNeN7xkHhRMqrUEU1ARW6AQH4zsDzj3ra19J5Wjb4UMTxMeXWUAd37', + xPubKey_1H: 'xpub69D527HrF5ckb2YQqYKLdEhfuP5GC6gQxqR2NgNQuDqPRmWEBd6qNjh8DRJfYzQ4Y2WCw3NWR9Bn3YZkKa2yQiCG2xWKSMnMfg71pFEjCWd', + privKey_1H_0: '95951f0e40d31bafe54a3098bd0ed898d370cc5d52a9318d7b7b14568da6cb5c', + pubKey_1H_0: '0266cdb57b8a4d7c1b5b20ddeea43705420c6e3aef2c2979a3768b7b585839a0d3' }, ]; var history = [{ @@ -188,6 +211,5 @@ var history = [{ }]; module.exports.keyPair = keyPair; -module.exports.message = message; module.exports.copayers = copayers; module.exports.history = history; From fe46f95b322a3b52878a083ab06225c2f2ffc126 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 31 Aug 2015 11:59:54 -0300 Subject: [PATCH 04/11] test for legacy clients trying to join BIP44 wallet --- lib/server.js | 12 +++++- test/integration/server.js | 87 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/lib/server.js b/lib/server.js index a65f561..2e4bec1 100644 --- a/lib/server.js +++ b/lib/server.js @@ -512,6 +512,10 @@ WalletService.prototype.addAccess = function(opts, cb) { }); }; +WalletService.prototype._clientSupportsBIP44 = function() { + return !!this.clientVersion && !(/^bw.-0\.[01]\./.test(this.clientVersion)); +}; + /** * Joins a wallet in creation. * @param {Object} opts @@ -541,8 +545,12 @@ WalletService.prototype.joinWallet = function(opts, cb) { self.storage.fetchWallet(opts.walletId, function(err, wallet) { if (err) return cb(err); if (!wallet) return cb(Errors.WALLET_NOT_FOUND); - if (wallet.addressManager.derivationStrategy != opts.derivationStrategy) - return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To join this wallet, you need to upgrade your client app.')); + if (wallet.addressManager.derivationStrategy != opts.derivationStrategy) { + if (!self._clientSupportsBIP44()) { + return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To join this wallet, you need to upgrade your client app.')); + } + return cb(new ClientError('Incompatible address derivation strategy')); + } var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey); if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) { diff --git a/test/integration/server.js b/test/integration/server.js index 27dd623..a48baac 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1132,6 +1132,93 @@ describe('Wallet service', function() { }); }); + describe('Address derivation strategy', function() { + var server; + beforeEach(function() { + server = WalletService.getInstance({ + clientVersion: 'bwc-0.2.0', + }); + }); + it('should fail to join BIP45 wallet with BIP44 copayer opts', function(done) { + var walletOpts = { + name: 'my wallet', + m: 2, + n: 3, + pubKey: TestData.keyPair.pub, + derivationStrategy: 'BIP45', + }; + server.createWallet(walletOpts, function(err, wid) { + should.not.exist(err); + var copayerOpts = helpers.getSignedCopayerOpts({ + walletId: wid, + name: 'me', + xPubKey: TestData.copayers[0].xPubKey_44H_0H_0H, + requestPubKey: TestData.copayers[0].pubKey_1H_0, + customData: 'dummy custom data', + derivationStrategy: 'BIP44', + }); + server.joinWallet(copayerOpts, function(err, result) { + should.exist(err); + err.message.toLowerCase().should.contain('address derivation strategy'); + done(); + }); + }); + }); + it('should fail to join BIP44 wallet with BIP45 copayer opts', function(done) { + var walletOpts = { + name: 'my wallet', + m: 2, + n: 3, + pubKey: TestData.keyPair.pub, + derivationStrategy: 'BIP44', + }; + server.createWallet(walletOpts, function(err, wid) { + should.not.exist(err); + var copayerOpts = helpers.getSignedCopayerOpts({ + walletId: wid, + name: 'me', + xPubKey: TestData.copayers[0].xPubKey_45H, + requestPubKey: TestData.copayers[0].pubKey_1H_0, + customData: 'dummy custom data', + derivationStrategy: 'BIP45', + }); + server.joinWallet(copayerOpts, function(err, result) { + should.exist(err); + err.message.toLowerCase().should.contain('address derivation strategy'); + done(); + }); + }); + }); + it('should require upgrade when joining BIP44 wallet with BIP45 copayer opts from old client app', function(done) { + var walletOpts = { + name: 'my wallet', + m: 2, + n: 3, + pubKey: TestData.keyPair.pub, + derivationStrategy: 'BIP44', + }; + server.createWallet(walletOpts, function(err, wid) { + should.not.exist(err); + var copayerOpts = helpers.getSignedCopayerOpts({ + walletId: wid, + name: 'me', + xPubKey: TestData.copayers[0].xPubKey_45H, + requestPubKey: TestData.copayers[0].pubKey_1H_0, + customData: 'dummy custom data', + derivationStrategy: 'BIP45', + }); + server = WalletService.getInstance({ + clientVersion: 'bwc-0.1.4', + }); + server.joinWallet(copayerOpts, function(err, result) { + should.exist(err); + err.code.should.equal('UPGRADE_NEEDED'); + done(); + }); + }); + }); + }); + describe('#getStatus', function() { var server, wallet; beforeEach(function(done) { From 2048a20ef48051520f56ae93add2f0b6afbad3d5 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 31 Aug 2015 17:04:28 -0300 Subject: [PATCH 05/11] bump bwu 0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d36bd73..b3abe7c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "dependencies": { "async": "^0.9.0", "bitcore": "0.13.0", - "bitcore-wallet-utils": "0.1.2", + "bitcore-wallet-utils": "0.2.0", "body-parser": "^1.11.0", "coveralls": "^2.11.2", "email-validator": "^1.0.1", From 094e19bc314f83e70cca66d24f9d4004015afe90 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 31 Aug 2015 17:13:46 -0300 Subject: [PATCH 06/11] use constants form BWU --- lib/model/addressmanager.js | 19 ++++++++----------- lib/model/copayer.js | 2 +- lib/model/txproposal.js | 4 ++-- lib/model/wallet.js | 4 +++- lib/server.js | 8 ++++---- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/model/addressmanager.js b/lib/model/addressmanager.js index ca1740b..24f43c5 100644 --- a/lib/model/addressmanager.js +++ b/lib/model/addressmanager.js @@ -1,12 +1,9 @@ var _ = require('lodash'); var $ = require('preconditions').singleton(); -var STRATEGIES = { - BIP44: 'BIP44', - BIP45: 'BIP45', -}; +var WalletUtils = require('bitcore-wallet-utils'); -var SHARED_INDEX = 0x80000000 - 1; +var BIP45_SHARED_INDEX = 0x80000000 - 1; function AddressManager() {}; @@ -16,12 +13,12 @@ AddressManager.create = function(opts) { var x = new AddressManager(); x.version = 2; - x.derivationStrategy = opts.derivationStrategy || STRATEGIES.BIP44; - $.checkState(_.contains(_.values(STRATEGIES), x.derivationStrategy)); + x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP44; + $.checkState(_.contains(_.values(WalletUtils.DERIVATION_STRATEGIES), x.derivationStrategy)); x.receiveAddressIndex = 0; x.changeAddressIndex = 0; - x.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : SHARED_INDEX; + x.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : BIP45_SHARED_INDEX; return x; }; @@ -30,7 +27,7 @@ AddressManager.fromObj = function(obj) { var x = new AddressManager(); x.version = obj.version; - x.derivationStrategy = obj.derivationStrategy || STRATEGIES.BIP45; + x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; x.receiveAddressIndex = obj.receiveAddressIndex; x.changeAddressIndex = obj.changeAddressIndex; x.copayerIndex = obj.copayerIndex; @@ -40,7 +37,7 @@ AddressManager.fromObj = function(obj) { AddressManager.prototype.supportsDerivation = function() { // BIP44 does not support copayer specific indexes - return !(this.derivationStrategy == STRATEGIES.BIP44 && this.copayerIndex != SHARED_INDEX); + return !(this.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP44 && this.copayerIndex != BIP45_SHARED_INDEX); }; AddressManager.prototype._incrementIndex = function(isChange) { @@ -62,7 +59,7 @@ AddressManager.prototype.rewindIndex = function(isChange, n) { AddressManager.prototype.getCurrentAddressPath = function(isChange) { return 'm/' + - (this.derivationStrategy == STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') + + (this.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') + (isChange ? 1 : 0) + '/' + (isChange ? this.changeAddressIndex : this.receiveAddressIndex); }; diff --git a/lib/model/copayer.js b/lib/model/copayer.js index 73233f6..3fffe72 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -37,7 +37,7 @@ Copayer.create = function(opts) { }]; x.addressManager = AddressManager.create({ - derivationStrategy: opts.derivationStrategy || 'BIP45', + derivationStrategy: opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45, copayerIndex: opts.copayerIndex, }); diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index c574ae6..b8cd599 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -71,7 +71,7 @@ TxProposal.create = function(opts) { x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos; x.proposalSignaturePubKey = opts.proposalSignaturePubKey; x.proposalSignaturePubKeySig = opts.proposalSignaturePubKeySig; - x.derivationStrategy = opts.derivationStrategy || 'BIP45'; + x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; if (_.isFunction(TxProposal._create[x.type])) { TxProposal._create[x.type](x, opts); @@ -118,7 +118,7 @@ TxProposal.fromObj = function(obj) { x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos; x.proposalSignaturePubKey = obj.proposalSignaturePubKey; x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig; - x.derivationStrategy = obj.derivationStrategy || 'BIP45'; + x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; return x; }; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 545185b..4639073 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -29,8 +29,9 @@ Wallet.create = function(opts) { x.copayers = []; x.pubKey = opts.pubKey; x.network = opts.network; + x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; x.addressManager = AddressManager.create({ - derivationStrategy: opts.derivationStrategy || 'BIP45', + derivationStrategy: x.derivationStrategy, }); x.scanStatus = null; @@ -53,6 +54,7 @@ Wallet.fromObj = function(obj) { }); x.pubKey = obj.pubKey; x.network = obj.network; + x.derivationStrategy = obj.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; x.addressManager = AddressManager.fromObj(obj.addressManager); x.scanStatus = obj.scanStatus; diff --git a/lib/server.js b/lib/server.js index 2e4bec1..9f75a2d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -220,8 +220,8 @@ WalletService.prototype.createWallet = function(opts, cb) { if (!_.contains(['livenet', 'testnet'], opts.network)) return cb(new ClientError('Invalid network')); - opts.derivationStrategy = opts.derivationStrategy || 'BIP45'; - if (!_.contains(['BIP44', 'BIP45'], opts.derivationStrategy)) + opts.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; + if (!_.contains(_.values(WalletUtils.DERIVATION_STRATEGIES), opts.derivationStrategy)) return cb(new ClientError('Invalid address derivation strategy')); try { @@ -536,8 +536,8 @@ WalletService.prototype.joinWallet = function(opts, cb) { if (_.isEmpty(opts.name)) return cb(new ClientError('Invalid copayer name')); - opts.derivationStrategy = opts.derivationStrategy || 'BIP45'; - if (!_.contains(['BIP44', 'BIP45'], opts.derivationStrategy)) + opts.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; + if (!_.contains(_.values(WalletUtils.DERIVATION_STRATEGIES), opts.derivationStrategy)) return cb(new ClientError('Invalid address derivation strategy')); self.walletId = opts.walletId; From d1e318a5b77593990f06e9497959183462e60ba4 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 31 Aug 2015 17:16:45 -0300 Subject: [PATCH 07/11] v0.2.0 --- lib/model/addressmanager.js | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/model/addressmanager.js b/lib/model/addressmanager.js index 24f43c5..b6d739a 100644 --- a/lib/model/addressmanager.js +++ b/lib/model/addressmanager.js @@ -3,7 +3,7 @@ var $ = require('preconditions').singleton(); var WalletUtils = require('bitcore-wallet-utils'); -var BIP45_SHARED_INDEX = 0x80000000 - 1; +var SHARED_INDEX = 0x80000000 - 1; function AddressManager() {}; @@ -18,7 +18,7 @@ AddressManager.create = function(opts) { x.receiveAddressIndex = 0; x.changeAddressIndex = 0; - x.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : BIP45_SHARED_INDEX; + x.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : SHARED_INDEX; return x; }; @@ -37,7 +37,7 @@ AddressManager.fromObj = function(obj) { AddressManager.prototype.supportsDerivation = function() { // BIP44 does not support copayer specific indexes - return !(this.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP44 && this.copayerIndex != BIP45_SHARED_INDEX); + return !(this.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP44 && this.copayerIndex != SHARED_INDEX); }; AddressManager.prototype._incrementIndex = function(isChange) { diff --git a/package.json b/package.json index b3abe7c..e7511b1 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.10", + "version": "0.2.0", "keywords": [ "bitcoin", "copay", From e11da7cec5233bff4c475ce5489e25b9cbff7295 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 31 Aug 2015 17:38:39 -0300 Subject: [PATCH 08/11] remove AddressManager from copayers on BIP44 --- lib/model/addressmanager.js | 9 ++++----- lib/model/copayer.js | 15 ++++++++++----- lib/server.js | 2 +- test/integration/server.js | 4 +++- test/models/addressmanager.js | 10 ++++++---- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/model/addressmanager.js b/lib/model/addressmanager.js index b6d739a..1e47cd2 100644 --- a/lib/model/addressmanager.js +++ b/lib/model/addressmanager.js @@ -3,7 +3,7 @@ var $ = require('preconditions').singleton(); var WalletUtils = require('bitcore-wallet-utils'); -var SHARED_INDEX = 0x80000000 - 1; +var BIP45_SHARED_INDEX = 0x80000000 - 1; function AddressManager() {}; @@ -18,7 +18,7 @@ AddressManager.create = function(opts) { x.receiveAddressIndex = 0; x.changeAddressIndex = 0; - x.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : SHARED_INDEX; + x.copayerIndex = _.isNumber(opts.copayerIndex) ? opts.copayerIndex : BIP45_SHARED_INDEX; return x; }; @@ -35,9 +35,8 @@ AddressManager.fromObj = function(obj) { return x; }; -AddressManager.prototype.supportsDerivation = function() { - // BIP44 does not support copayer specific indexes - return !(this.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP44 && this.copayerIndex != SHARED_INDEX); +AddressManager.supportsCopayerBranches = function(derivationStrategy) { + return derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP45; }; AddressManager.prototype._incrementIndex = function(isChange) { diff --git a/lib/model/copayer.js b/lib/model/copayer.js index 3fffe72..d7cfd3b 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -36,10 +36,13 @@ Copayer.create = function(opts) { signature: opts.signature, }]; - x.addressManager = AddressManager.create({ - derivationStrategy: opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45, - copayerIndex: opts.copayerIndex, - }); + var derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; + if (AddressManager.supportsCopayerBranches(derivationStrategy)) { + x.addressManager = AddressManager.create({ + derivationStrategy: derivationStrategy, + copayerIndex: opts.copayerIndex, + }); + } x.customData = opts.customData; @@ -67,7 +70,9 @@ Copayer.fromObj = function(obj) { x.requestPubKeys = obj.requestPubKeys; } - x.addressManager = AddressManager.fromObj(obj.addressManager); + if (obj.addressManager) { + x.addressManager = AddressManager.fromObj(obj.addressManager); + } x.customData = obj.customData; return x; diff --git a/lib/server.js b/lib/server.js index 9f75a2d..3ffb5ba 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1842,7 +1842,7 @@ WalletService.prototype.scan = function(opts, cb) { }); if (opts.includeCopayerBranches) { _.each(wallet.copayers, function(copayer) { - if (copayer.addressManager.supportsDerivation()) { + if (copayer.addressManager) { derivators.push({ derive: _.bind(copayer.createAddress, copayer, wallet, isChange), rewind: _.bind(copayer.addressManager.rewindIndex, copayer.addressManager, isChange), diff --git a/test/integration/server.js b/test/integration/server.js index a48baac..0742d64 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1222,7 +1222,9 @@ describe('Wallet service', function() { describe('#getStatus', function() { var server, wallet; beforeEach(function(done) { - helpers.createAndJoinWallet(1, 1, function(s, w) { + helpers.createAndJoinWallet(1, 1, { + derivationStrategy: 'BIP45' + }, function(s, w) { server = s; wallet = w; done(); diff --git a/test/models/addressmanager.js b/test/models/addressmanager.js index 3e34733..975856e 100644 --- a/test/models/addressmanager.js +++ b/test/models/addressmanager.js @@ -27,6 +27,12 @@ describe('AddressManager', function() { am.getCurrentAddressPath(false).should.equal('m/4/0/2'); }); }); + describe('#supportsCopayerBranches', function() { + it('should return true for BIP45 & false for BIP44', function() { + AddressManager.supportsCopayerBranches('BIP45').should.be.true; + AddressManager.supportsCopayerBranches('BIP44').should.be.false; + }); + }); describe('BIP45', function() { describe('#getCurrentAddressPath', function() { it('should return a valid BIP32 path for given index', function() { @@ -34,7 +40,6 @@ describe('AddressManager', function() { derivationStrategy: 'BIP45', copayerIndex: 4, }); - am.supportsDerivation().should.be.true; am.getCurrentAddressPath(false).should.equal('m/4/0/0'); am.getCurrentAddressPath(true).should.equal('m/4/1/0'); }); @@ -43,7 +48,6 @@ describe('AddressManager', function() { var am = AddressManager.create({ derivationStrategy: 'BIP45', }); - am.supportsDerivation().should.be.true; am.getCurrentAddressPath(false).should.equal('m/2147483647/0/0'); am.getCurrentAddressPath(true).should.equal('m/2147483647/1/0'); }); @@ -94,7 +98,6 @@ describe('AddressManager', function() { describe('#getCurrentAddressPath', function() { it('should return first address path', function() { var am = AddressManager.create(); - am.supportsDerivation().should.be.true; am.getCurrentAddressPath(false).should.equal('m/0/0'); am.getCurrentAddressPath(true).should.equal('m/1/0'); }); @@ -102,7 +105,6 @@ describe('AddressManager', function() { var am = AddressManager.create({ copayerIndex: 4, }); - am.supportsDerivation().should.be.false; am.getCurrentAddressPath(false).should.equal('m/0/0'); am.getCurrentAddressPath(true).should.equal('m/1/0'); }); From df5fdb1f752bd3d8eb3737153bc36e684620d8ae Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 1 Sep 2015 11:53:07 -0300 Subject: [PATCH 09/11] parse client version --- lib/server.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index 3ffb5ba..6b3df1d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -512,8 +512,38 @@ WalletService.prototype.addAccess = function(opts, cb) { }); }; +WalletService.prototype._parseClientVersion = function() { + function parse(version) { + var v = {}; + + if (!version) return null; + + var x = version.split('-'); + if (x.length != 2) { + v.agent = version; + return v; + } + v.agent = _.contains(['bwc', 'bws'], x[0]) ? 'bwc' : x[0]; + x = x[1].split('.'); + v.major = parseInt(x[0]); + v.minor = parseInt(x[1]); + v.patch = parseInt(x[2]); + + return v; + }; + + if (_.isUndefined(this.parsedClientVersion)) { + this.parsedClientVersion = parse(this.clientVersion); + } + return this.parsedClientVersion; +}; + WalletService.prototype._clientSupportsBIP44 = function() { - return !!this.clientVersion && !(/^bw.-0\.[01]\./.test(this.clientVersion)); + var version = this._parseClientVersion(); + if (!version) return false; + if (version.agent != 'bwc') return true; // Asume 3rd party clients are up-to-date + if (version.major == 0 && version.minor <= 1) return false; + return true; }; /** From db5c452a7474d506b5b3d2803981ca4ed334b343 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 1 Sep 2015 12:00:06 -0300 Subject: [PATCH 10/11] replaced other regex checks for client version --- lib/server.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/server.js b/lib/server.js index 6b3df1d..012fb20 100644 --- a/lib/server.js +++ b/lib/server.js @@ -546,6 +546,14 @@ WalletService.prototype._clientSupportsBIP44 = function() { return true; }; +WalletService.prototype._clientSupportsTXPv2 = function() { + var version = this._parseClientVersion(); + if (!version) return false; + if (version.agent != 'bwc') return true; // Asume 3rd party clients are up-to-date + if (version.major == 0 && version.minor == 0) return false; + return true; +}; + /** * Joins a wallet in creation. * @param {Object} opts @@ -1224,7 +1232,7 @@ WalletService.prototype.createTx = function(opts, cb) { var txp = Model.TxProposal.create(txOpts); - if (!self.clientVersion || /^bw.-0\.0\./.test(self.clientVersion)) { + if (!self._clientSupportsTXPv2()) { txp.version = '1.0.1'; } @@ -1393,7 +1401,7 @@ WalletService.prototype.signTx = function(opts, cb) { }, function(err, txp) { if (err) return cb(err); - if (!self.clientVersion || /^bw.-0\.0\./.test(self.clientVersion)) { + if (!self._clientSupportsTXPv2()) { if (!_.startsWith(txp.version, '1.')) { return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To sign this spend proposal you need to upgrade your client app.')); } From c974cf791af04ce2412115a50034117b2afdefcd Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 1 Sep 2015 12:16:26 -0300 Subject: [PATCH 11/11] default address manager to BIP45 --- lib/model/addressmanager.js | 2 +- test/models/addressmanager.js | 43 ++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/lib/model/addressmanager.js b/lib/model/addressmanager.js index 1e47cd2..78bcc61 100644 --- a/lib/model/addressmanager.js +++ b/lib/model/addressmanager.js @@ -13,7 +13,7 @@ AddressManager.create = function(opts) { var x = new AddressManager(); x.version = 2; - x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP44; + x.derivationStrategy = opts.derivationStrategy || WalletUtils.DERIVATION_STRATEGIES.BIP45; $.checkState(_.contains(_.values(WalletUtils.DERIVATION_STRATEGIES), x.derivationStrategy)); x.receiveAddressIndex = 0; diff --git a/test/models/addressmanager.js b/test/models/addressmanager.js index 975856e..129be4f 100644 --- a/test/models/addressmanager.js +++ b/test/models/addressmanager.js @@ -9,9 +9,9 @@ var AddressManager = require('../../lib/model/addressmanager'); describe('AddressManager', function() { describe('#create', function() { - it('should create BIP44 address manager by default', function() { + it('should create BIP45 address manager by default', function() { var am = AddressManager.create(); - am.derivationStrategy.should.equal('BIP44'); + am.derivationStrategy.should.equal('BIP45'); }); }); describe('#fromObj', function() { @@ -37,7 +37,6 @@ describe('AddressManager', function() { describe('#getCurrentAddressPath', function() { it('should return a valid BIP32 path for given index', function() { var am = AddressManager.create({ - derivationStrategy: 'BIP45', copayerIndex: 4, }); am.getCurrentAddressPath(false).should.equal('m/4/0/0'); @@ -45,16 +44,13 @@ describe('AddressManager', function() { }); }); it('should return a valid BIP32 path for defaut Index', function() { - var am = AddressManager.create({ - derivationStrategy: 'BIP45', - }); + var am = AddressManager.create({}); am.getCurrentAddressPath(false).should.equal('m/2147483647/0/0'); am.getCurrentAddressPath(true).should.equal('m/2147483647/1/0'); }); describe('#getNewAddressPath', function() { it('should return a new valid BIP32 path for given index', function() { var am = AddressManager.create({ - derivationStrategy: 'BIP45', copayerIndex: 2, }); am.getNewAddressPath(false).should.equal('m/2/0/0'); @@ -65,9 +61,7 @@ describe('AddressManager', function() { }); describe('#rewindIndex', function() { it('should rewind main index', function() { - var am = AddressManager.create({ - derivationStrategy: 'BIP45', - }); + var am = AddressManager.create({}); am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); am.getNewAddressPath(false).should.equal('m/2147483647/0/1'); am.getNewAddressPath(false).should.equal('m/2147483647/0/2'); @@ -75,9 +69,7 @@ describe('AddressManager', function() { am.getNewAddressPath(false).should.equal('m/2147483647/0/1'); }); it('should rewind change index', function() { - var am = AddressManager.create({ - derivationStrategy: 'BIP45', - }); + var am = AddressManager.create({}); am.getNewAddressPath(true).should.equal('m/2147483647/1/0'); am.rewindIndex(false, 1); am.getNewAddressPath(true).should.equal('m/2147483647/1/1'); @@ -85,9 +77,7 @@ describe('AddressManager', function() { am.getNewAddressPath(true).should.equal('m/2147483647/1/0'); }); it('should stop at 0', function() { - var am = AddressManager.create({ - derivationStrategy: 'BIP45', - }); + var am = AddressManager.create({}); am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); am.rewindIndex(false, 20); am.getNewAddressPath(false).should.equal('m/2147483647/0/0'); @@ -97,12 +87,15 @@ describe('AddressManager', function() { describe('BIP44', function() { describe('#getCurrentAddressPath', function() { it('should return first address path', function() { - var am = AddressManager.create(); + var am = AddressManager.create({ + derivationStrategy: 'BIP44', + }); am.getCurrentAddressPath(false).should.equal('m/0/0'); am.getCurrentAddressPath(true).should.equal('m/1/0'); }); it('should return address path independently of copayerIndex', function() { var am = AddressManager.create({ + derivationStrategy: 'BIP44', copayerIndex: 4, }); am.getCurrentAddressPath(false).should.equal('m/0/0'); @@ -111,7 +104,9 @@ describe('AddressManager', function() { }); describe('#getNewAddressPath', function() { it('should return a new path', function() { - var am = AddressManager.create(); + var am = AddressManager.create({ + derivationStrategy: 'BIP44', + }); am.getNewAddressPath(false).should.equal('m/0/0'); am.getNewAddressPath(true).should.equal('m/1/0'); am.getNewAddressPath(false).should.equal('m/0/1'); @@ -120,7 +115,9 @@ describe('AddressManager', function() { }); describe('#rewindIndex', function() { it('should rewind main index', function() { - var am = AddressManager.create(); + var am = AddressManager.create({ + derivationStrategy: 'BIP44', + }); am.getNewAddressPath(false).should.equal('m/0/0'); am.getNewAddressPath(false).should.equal('m/0/1'); am.getNewAddressPath(false).should.equal('m/0/2'); @@ -128,7 +125,9 @@ describe('AddressManager', function() { am.getNewAddressPath(false).should.equal('m/0/1'); }); it('should rewind change index', function() { - var am = AddressManager.create(); + var am = AddressManager.create({ + derivationStrategy: 'BIP44', + }); am.getNewAddressPath(true).should.equal('m/1/0'); am.rewindIndex(false, 1); am.getNewAddressPath(true).should.equal('m/1/1'); @@ -136,7 +135,9 @@ describe('AddressManager', function() { am.getNewAddressPath(true).should.equal('m/1/0'); }); it('should stop at 0', function() { - var am = AddressManager.create(); + var am = AddressManager.create({ + derivationStrategy: 'BIP44', + }); am.getNewAddressPath(false).should.equal('m/0/0'); am.rewindIndex(false, 20); am.getNewAddressPath(false).should.equal('m/0/0');