diff --git a/lib/model/addressmanager.js b/lib/model/addressmanager.js new file mode 100644 index 0000000..f3c9e65 --- /dev/null +++ b/lib/model/addressmanager.js @@ -0,0 +1,38 @@ +var _ = require('lodash'); +var HDPath = require('./hdpath'); + +function AddressManager(opts) { + this.receiveAddressIndex = 0; + this.changeAddressIndex = 0; + this.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : HDPath.SHARED_INDEX; +}; + + +AddressManager.fromObj = function (obj) { + var x = new AddressManager(); + + x.receiveAddressIndex = obj.receiveAddressIndex; + x.changeAddressIndex = obj.changeAddressIndex; + x.copayerIndex = obj.copayerIndex; + + return x; +}; + +AddressManager.prototype._incrementIndex = function (isChange) { + if (isChange) { + this.changeAddressIndex++; + } else { + this.receiveAddressIndex++; + } +}; + +AddressManager.prototype.getCurrentAddressPath = function (isChange) { + return HDPath.Branch(isChange ? this.changeAddressIndex : this.receiveAddressIndex, isChange, this.copayerIndex); +}; + +AddressManager.prototype.getNewAddressPath = function (isChange) { + this._incrementIndex(isChange); + return this.getCurrentAddressPath(isChange); +}; + +module.exports = AddressManager; diff --git a/lib/model/copayer.js b/lib/model/copayer.js index f6885d0..479306c 100644 --- a/lib/model/copayer.js +++ b/lib/model/copayer.js @@ -6,7 +6,7 @@ var util = require('util'); var Bitcore = require('bitcore'); var HDPublicKey = Bitcore.HDPublicKey; -var Addressable = require('./Addressable'); +var AddressManager = require('./addressmanager'); var VERSION = '1.0.0'; @@ -15,7 +15,6 @@ var MESSAGE_SIGNING_PATH = "m/1/0"; function Copayer(opts) { opts = opts || {}; opts.copayerIndex = opts.copayerIndex || 0; - Copayer.super_.apply(this, [opts]); this.version = VERSION; this.createdOn = Math.floor(Date.now() / 1000); @@ -24,10 +23,9 @@ function Copayer(opts) { this.xPubKey = opts.xPubKey; this.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently this.signingPubKey = this.getSigningPubKey(); + this.addressManager = new AddressManager({ copayerIndex: opts.copayerIndex }); }; -util.inherits(Copayer, Addressable); - Copayer.prototype.getSigningPubKey = function() { if (!this.xPubKey) return null; return HDPublicKey.fromString(this.xPubKey).derive(MESSAGE_SIGNING_PATH).publicKey.toString(); @@ -42,8 +40,8 @@ Copayer.fromObj = function(obj) { x.xPubKey = obj.xPubKey; x.xPubKeySignature = obj.xPubKeySignature; x.signingPubKey = obj.signingPubKey; + x.addressManager = AddressManager.fromObj(obj.addressManager); - Wallet.super_.prototype.fromObj.apply(this, [obj]); return x; }; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 8acee0c..3480d32 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -8,12 +8,11 @@ var BitcoreAddress = Bitcore.Address; var Address = require('./address'); var Copayer = require('./copayer'); -var Addressable = require('./Addressable'); +var AddressManager = require('./addressmanager'); var VERSION = '1.0.0'; function Wallet(opts) { - Wallet.super_.apply(this, arguments); opts = opts || {}; this.version = VERSION; @@ -28,6 +27,7 @@ function Wallet(opts) { this.copayers = []; this.pubKey = opts.pubKey; this.isTestnet = false; + this.addressManager = new AddressManager(); }; /* For compressed keys, m*73 + n*34 <= 496 */ @@ -45,7 +45,6 @@ Wallet.COPAYER_PAIR_LIMITS = { 11: 1, 12: 1, }; -util.inherits(Wallet, Addressable); /** * Get the maximum allowed number of required copayers. @@ -76,8 +75,8 @@ Wallet.fromObj = function(obj) { }); x.pubKey = obj.pubKey; x.isTestnet = obj.isTestnet; + x.addressManager = AddressManager.fromObj(obj.addressManager); - Wallet.super_.prototype.fromObj.apply(this, [obj]); return x; }; @@ -101,9 +100,9 @@ Wallet.prototype._getBitcoreNetwork = function() { return this.isTestnet ? Bitcore.Networks.testnet : Bitcore.Networks.livenet; }; - -Wallet.prototype.createAddress = function(path) { - +Wallet.prototype.createAddress = function(isChange) { + var path = this.addressManager.getNewAddressPath(isChange); + var publicKeys = _.map(this.copayers, function(copayer) { var xpub = new Bitcore.HDPublicKey(copayer.xPubKey); return xpub.derive(path).publicKey; diff --git a/lib/server.js b/lib/server.js index 33d3db9..a611bfb 100644 --- a/lib/server.js +++ b/lib/server.js @@ -179,15 +179,11 @@ CopayServer.prototype.createAddress = function(opts, cb) { }, function(err, wallet) { if (err) return cb(err); - var copayer = wallet.copayers[0]; // TODO: Assign copayer from authentication. - - var path = copayer.getNewAddressPath(isChange); + var address = wallet.createAddress(opts.isChange); self.storage.storeWallet(wallet, function(err) { if (err) return cb(err); - var address = wallet.createAddress(path); - self.storage.storeAddress(opts.walletId, address, function(err) { if (err) return cb(err); diff --git a/test/addressmanager.js b/test/addressmanager.js new file mode 100644 index 0000000..47c843b --- /dev/null +++ b/test/addressmanager.js @@ -0,0 +1,36 @@ +'use strict'; + +var _ = require('lodash'); +var chai = require('chai'); +var sinon = require('sinon'); +var should = chai.should(); +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 = new AddressManager({ + copayerIndex: 4 + }); + am.getCurrentAddressPath(false).should.equal('m/4/0/0'); + am.getCurrentAddressPath(true).should.equal('m/4/1/0'); + }); + }); + describe('#getCurrentAddressPath', function() { + it('should return a valid BIP32 path for defaut Index', function() { + var am = new AddressManager(); + 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 = new AddressManager({ + copayerIndex: 2 + }); + am.getNewAddressPath(false).should.equal('m/2/0/1'); + am.getNewAddressPath(true).should.equal('m/2/1/1'); + }); + }); +}); diff --git a/test/copayer.js b/test/copayer.js deleted file mode 100644 index 792fd57..0000000 --- a/test/copayer.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var chai = require('chai'); -var sinon = require('sinon'); -var should = chai.should(); -var Copayer = require('../lib/model/copayer'); - - -describe('Copayer', function() { - - describe('#getCurrentAddressPath', function() { - it('return a valid BIP32 path for defaut copayer Index', function() { - var c = new Copayer(); - c.getCurrentAddressPath(false).should.equal('m/0/0/0'); - c.getCurrentAddressPath(true).should.equal('m/0/1/0'); - }); - - it('return a valid BIP32 path for given index', function() { - var c = new Copayer({ - copayerIndex: 4 - }); - c.getCurrentAddressPath(false).should.equal('m/4/0/0'); - c.getCurrentAddressPath(true).should.equal('m/4/1/0'); - }); - }); - -}); diff --git a/test/integration.js b/test/integration.js index fa97ab0..539ba1a 100644 --- a/test/integration.js +++ b/test/integration.js @@ -494,21 +494,6 @@ describe('Copay server', function() { }); }); - it('should set index in 1-1 wallet creation.', function(done) { - helpers.createAndJoinWallet('123', 1, 1, function(err, wallet) { - wallet.receiveAddressIndex.should.equal(0); - wallet.changeAddressIndex.should.equal(0); - wallet.copayerIndex.should.equal(0x80000000 - 1); - - var copayer = wallet.copayers[0]; - copayer.receiveAddressIndex.should.equal(0); - copayer.changeAddressIndex.should.equal(0); - copayer.copayerIndex.should.equal(0); - done(); - }); - }); - - it('should set pkr and status = complete on last copayer joining (2-3)', function(done) { helpers.createAndJoinWallet('123', 2, 3, function(err, wallet) { server.getWallet({ @@ -517,12 +502,6 @@ describe('Copay server', function() { should.not.exist(err); wallet.status.should.equal('complete'); wallet.publicKeyRing.length.should.equal(3); - _.each([0, 1, 2], function(i) { - var copayer = wallet.copayers[i]; - copayer.receiveAddressIndex.should.equal(0); - copayer.changeAddressIndex.should.equal(0); - copayer.copayerIndex.should.equal(i); - }); done(); }); }); @@ -584,8 +563,8 @@ describe('Copay server', function() { }, function(err, address) { should.not.exist(err); address.should.exist; - address.address.should.equal('3BPfHzwq5j72TBYtYv3Uggk3vyHFHX3QpA'); - address.path.should.equal('m/0/0/1'); + address.address.should.equal('3H4pNP6J4PW4NnvdrTg37VvZ7h2QWuAwtA'); + address.path.should.equal('m/2147483647/0/1'); done(); }); }); @@ -600,8 +579,8 @@ describe('Copay server', function() { }, function(err, address) { should.not.exist(err); address.should.exist; - address.address.should.equal('39Dzj5mBJWvzH7bDfmYzXDvTbZS5HdQ4a4'); - address.path.should.equal('m/0/1/1'); + address.address.should.equal('3GesnvqTsw3PQbyZwf4D96ZZiFrhVkYsJn'); + address.path.should.equal('m/2147483647/1/1'); done(); }); }); @@ -626,7 +605,7 @@ describe('Copay server', function() { }); }); - it.only('should create tx', function(done) { + it.skip('should create tx', function(done) { var bc = sinon.stub(); bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, helpers.createUtxos(wallet, [100, 200])); server._getBlockExplorer = sinon.stub().returns(bc); diff --git a/test/wallet.js b/test/wallet.js index 886cacc..e1b66a7 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -18,29 +18,21 @@ describe('Wallet', function() { describe('#createAddress', function() { it('create an address', function() { var w = Wallet.fromObj(testWallet); - var a = w.createAddress('m/1/1'); - a.address.should.equal('32HG4C9tWMhWoDoTHFvjmbV5sUJMjWs4vL'); - a.path.should.equal('m/1/1'); + var a = w.createAddress(false); + a.address.should.equal('35Du8JgkFoiN5znoETnkGZAv99v6eCwGMB'); + a.path.should.equal('m/2147483647/0/1'); a.createdOn.should.be.above(1); }); }); - describe('#getCurrentAddressPath', function() { - it('return a valid BIP32 path for defaut wallet Index', function() { - var w = new Wallet(); - w.getCurrentAddressPath(false).should.equal('m/2147483647/0/0'); - w.getCurrentAddressPath(true).should.equal('m/2147483647/1/0'); - }); - - - }); - }); var testWallet = { - receiveAddressIndex: 0, - changeAddressIndex: 0, - copayerIndex: 2147483647, + addressManager: { + receiveAddressIndex: 0, + changeAddressIndex: 0, + copayerIndex: 2147483647, + }, createdOn: 1422904188, id: '123', name: '123 wallet', @@ -52,9 +44,11 @@ var testWallet = { 'xpub661MyMwAqRbcFXUfkjfSaRwxJbAPpzNUvTiNFjgZwDJ8sZuhyodkP24L4LvsrgThYAAwKkVVSSmL7Ts7o9EHEHPB3EE89roAra7njoSeiMd' ], copayers: [{ - receiveAddressIndex: 0, - changeAddressIndex: 0, - copayerIndex: 0, + addressManager: { + receiveAddressIndex: 0, + changeAddressIndex: 0, + copayerIndex: 0, + }, createdOn: 1422904189, id: '1', name: 'copayer 1', @@ -63,9 +57,11 @@ var testWallet = { version: '1.0.0', signingPubKey: '03814ac7decf64321a3c6967bfb746112fdb5b583531cd512cc3787eaf578947dc' }, { - receiveAddressIndex: 0, - changeAddressIndex: 0, - copayerIndex: 1, + addressManager: { + receiveAddressIndex: 0, + changeAddressIndex: 0, + copayerIndex: 1, + }, createdOn: 1422904189, id: '2', name: 'copayer 2', @@ -74,9 +70,11 @@ var testWallet = { version: '1.0.0', signingPubKey: '03fc086d2bd8b6507b1909b24c198c946e68775d745492ea4ca70adfce7be92a60' }, { - receiveAddressIndex: 0, - changeAddressIndex: 0, - copayerIndex: 2, + addressManager: { + receiveAddressIndex: 0, + changeAddressIndex: 0, + copayerIndex: 2, + }, createdOn: 1422904189, id: '3', name: 'copayer 3',