diff --git a/lib/hdprivatekey.js b/lib/hdprivatekey.js index ad53e1c..8d2d222 100644 --- a/lib/hdprivatekey.js +++ b/lib/hdprivatekey.js @@ -62,6 +62,22 @@ function HDPrivateKey(arg) { } } +/** + * Verifies that a given path is valid. + * + * @param {string|number} arg + * @param {boolean?} hardened + * @return {boolean} + */ +HDPrivateKey.prototype.isValidPath = function(arg, hardened) { + try { + this.derive(arg, hardened); + return true; + } catch (err) { + return false; + } +}; + /** * Get a derivated child based on a string or number. * @@ -101,9 +117,15 @@ HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) { if (index >= HDPrivateKey.Hardened) { hardened = true; } + if (index < HDPrivateKey.Hardened && hardened) { index += HDPrivateKey.Hardened; } + + if (index < 0 || index >= HDPrivateKey.MaxIndex) { + throw new hdErrors.InvalidPath(index); + } + var cached = HDKeyCache.get(this.xprivkey, index, hardened); if (cached) { return cached; @@ -443,6 +465,8 @@ HDPrivateKey.DefaultFingerprint = 0; HDPrivateKey.DefaultChildIndex = 0; HDPrivateKey.DefaultNetwork = Network.livenet; HDPrivateKey.Hardened = 0x80000000; +HDPrivateKey.MaxIndex = 2 * HDPrivateKey.Hardened; + HDPrivateKey.RootElementAlias = ['m', 'M', 'm\'', 'M\'']; HDPrivateKey.VersionSize = 4; diff --git a/lib/hdpublickey.js b/lib/hdpublickey.js index 158aa05..979faf9 100644 --- a/lib/hdpublickey.js +++ b/lib/hdpublickey.js @@ -65,6 +65,22 @@ function HDPublicKey(arg) { } } + +/** + * Verifies that a given path is valid. + * + * @param {string|number} arg + * @return {boolean} + */ +HDPublicKey.prototype.isValidPath = function(arg) { + try { + this.derive(arg); + return true; + } catch (err) { + return false; + } +}; + /** * Get a derivated child based on a string or number. * @@ -86,11 +102,10 @@ function HDPublicKey(arg) { * ``` * * @param {string|number} arg - * @param {boolean?} hardened */ -HDPublicKey.prototype.derive = function (arg, hardened) { +HDPublicKey.prototype.derive = function (arg) { if (_.isNumber(arg)) { - return this._deriveWithNumber(arg, hardened); + return this._deriveWithNumber(arg); } else if (_.isString(arg)) { return this._deriveFromString(arg); } else { @@ -98,11 +113,14 @@ HDPublicKey.prototype.derive = function (arg, hardened) { } }; -HDPublicKey.prototype._deriveWithNumber = function (index, hardened) { - if (hardened || index >= HDPublicKey.Hardened) { +HDPublicKey.prototype._deriveWithNumber = function (index) { + if (index >= HDPublicKey.Hardened) { throw new hdErrors.InvalidIndexCantDeriveHardened(); } - var cached = HDKeyCache.get(this.xpubkey, index, hardened); + if (index < 0) { + throw new hdErrors.InvalidPath(index); + } + var cached = HDKeyCache.get(this.xpubkey, index, false); if (cached) { return cached; } @@ -123,12 +141,16 @@ HDPublicKey.prototype._deriveWithNumber = function (index, hardened) { chainCode: chainCode, publicKey: publicKey }); - HDKeyCache.set(this.xpubkey, index, hardened, derived); + HDKeyCache.set(this.xpubkey, index, false, derived); return derived; }; HDPublicKey.prototype._deriveFromString = function (path) { /* jshint maxcomplexity: 8 */ + if (_.contains(path, "'")) { + throw new hdErrors.InvalidIndexCantDeriveHardened(); + } + var steps = path.split('/'); // Special cases: @@ -143,8 +165,7 @@ HDPublicKey.prototype._deriveFromString = function (path) { var result = this; for (var step in steps) { var index = parseInt(steps[step]); - var hardened = steps[step] !== index.toString(); - result = result._deriveWithNumber(index, hardened); + result = result._deriveWithNumber(index); } return result; }; diff --git a/test/hdprivatekey.js b/test/hdprivatekey.js index 6ba3b4d..aec7563 100644 --- a/test/hdprivatekey.js +++ b/test/hdprivatekey.js @@ -190,6 +190,24 @@ describe('HDPrivate key interface', function() { derivedByNumber.xprivkey.should.equal(derivedByString.xprivkey); }); + it('validates correct paths', function() { + var privateKey = new HDPrivateKey(xprivkey); + var valid = privateKey.isValidPath('m/0\'/1/2\''); + valid.should.equal(true); + + var valid = privateKey.isValidPath(123, true); + valid.should.equal(true); + }); + + it('validates illigal paths', function() { + var privateKey = new HDPrivateKey(xprivkey); + var valid = privateKey.isValidPath('m/-1/12'); + valid.should.equal(false); + + var valid = privateKey.isValidPath(HDPrivateKey.MaxHardened); + valid.should.equal(false); + }); + describe('conversion to plain object/json', function() { var plainObject = { 'network':'livenet', diff --git a/test/hdpublickey.js b/test/hdpublickey.js index 721fca8..be25b7b 100644 --- a/test/hdpublickey.js +++ b/test/hdpublickey.js @@ -216,10 +216,28 @@ describe('HDPublicKey interface', function() { it('can\'t derive hardened keys', function() { expectFail(function() { - return new HDPublicKey(xpubkey).derive(HDPublicKey.Hardened + 1); + return new HDPublicKey(xpubkey).derive(HDPublicKey.Hardened); }, hdErrors.InvalidDerivationArgument); }); + it('validates correct paths', function() { + var publicKey = new HDPublicKey(xpubkey); + var valid = publicKey.isValidPath('m/123/12'); + valid.should.equal(true); + + var valid = publicKey.isValidPath(123, true); + valid.should.equal(true); + }); + + it('validates illigal paths', function() { + var publicKey = new HDPublicKey(xpubkey); + var valid = publicKey.isValidPath('m/-1/12'); + valid.should.equal(false); + + var valid = publicKey.isValidPath(HDPublicKey.Hardened); + valid.should.equal(false); + }); + it('should use the cache', function() { var pubkey = new HDPublicKey(xpubkey); var derived1 = pubkey.derive(0);