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'); + }); }); }); });