Skip to content

Commit

Permalink
Add validations to derivation path
Browse files Browse the repository at this point in the history
  • Loading branch information
yemel committed Jan 2, 2015
1 parent 754add3 commit 2aa5c65
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 10 deletions.
24 changes: 24 additions & 0 deletions lib/hdprivatekey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
39 changes: 30 additions & 9 deletions lib/hdpublickey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -86,23 +102,25 @@ 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 {
throw new hdErrors.InvalidDerivationArgument(arg);
}
};

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;
}
Expand All @@ -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:
Expand All @@ -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;
};
Expand Down
18 changes: 18 additions & 0 deletions test/hdprivatekey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
20 changes: 19 additions & 1 deletion test/hdpublickey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 2aa5c65

Please sign in to comment.