Browse Source

Merge pull request #1016 from yemel/fix/path-validation

Fix hd string path parsing
patch-2
Manuel Aráoz 10 years ago
parent
commit
fe731371cf
  1. 54
      lib/hdprivatekey.js
  2. 81
      test/hdprivatekey.js

54
lib/hdprivatekey.js

@ -21,7 +21,7 @@ var BufferUtil = require('./util/buffer');
var JSUtil = require('./util/js'); var JSUtil = require('./util/js');
var MINIMUM_ENTROPY_BITS = 128; var MINIMUM_ENTROPY_BITS = 128;
var BITS_TO_BYTES = 1/8; var BITS_TO_BYTES = 1 / 8;
var MAXIMUM_ENTROPY_BITS = 512; var MAXIMUM_ENTROPY_BITS = 512;
@ -108,13 +108,23 @@ HDPrivateKey._getDerivationIndexes = function(path) {
} }
var indexes = steps.slice(1).map(function(step) { var indexes = steps.slice(1).map(function(step) {
var index = parseInt(step); var isHardened = step.slice(-1) === '\'';
index += step != index.toString() ? HDPrivateKey.Hardened : 0; if (isHardened) {
step = step.slice(0, -1);
}
if (!step || step[0] === '-') {
return NaN;
}
var index = +step; // cast to number
if (isHardened) {
index += HDPrivateKey.Hardened;
}
return index; return index;
}); });
return _.any(indexes, isNaN) ? null : indexes; return _.any(indexes, isNaN) ? null : indexes;
} };
/** /**
* Get a derivated child based on a string or number. * Get a derivated child based on a string or number.
@ -174,10 +184,14 @@ HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) {
data = BufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]); data = BufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]);
} }
var hash = Hash.sha512hmac(data, this._buffers.chainCode); var hash = Hash.sha512hmac(data, this._buffers.chainCode);
var leftPart = BN.fromBuffer(hash.slice(0, 32), {size: 32}); var leftPart = BN.fromBuffer(hash.slice(0, 32), {
size: 32
});
var chainCode = hash.slice(32, 64); var chainCode = hash.slice(32, 64);
var privateKey = leftPart.add(this.privateKey.toBigNumber()).mod(Point.getN()).toBuffer({size: 32}); var privateKey = leftPart.add(this.privateKey.toBigNumber()).mod(Point.getN()).toBuffer({
size: 32
});
var derived = new HDPrivateKey({ var derived = new HDPrivateKey({
network: this.network, network: this.network,
@ -292,7 +306,7 @@ HDPrivateKey.prototype._buildFromSerialized = function(arg) {
version: decoded.slice(HDPrivateKey.VersionStart, HDPrivateKey.VersionEnd), version: decoded.slice(HDPrivateKey.VersionStart, HDPrivateKey.VersionEnd),
depth: decoded.slice(HDPrivateKey.DepthStart, HDPrivateKey.DepthEnd), depth: decoded.slice(HDPrivateKey.DepthStart, HDPrivateKey.DepthEnd),
parentFingerPrint: decoded.slice(HDPrivateKey.ParentFingerPrintStart, parentFingerPrint: decoded.slice(HDPrivateKey.ParentFingerPrintStart,
HDPrivateKey.ParentFingerPrintEnd), HDPrivateKey.ParentFingerPrintEnd),
childIndex: decoded.slice(HDPrivateKey.ChildIndexStart, HDPrivateKey.ChildIndexEnd), childIndex: decoded.slice(HDPrivateKey.ChildIndexStart, HDPrivateKey.ChildIndexEnd),
chainCode: decoded.slice(HDPrivateKey.ChainCodeStart, HDPrivateKey.ChainCodeEnd), chainCode: decoded.slice(HDPrivateKey.ChainCodeStart, HDPrivateKey.ChainCodeEnd),
privateKey: decoded.slice(HDPrivateKey.PrivateKeyStart, HDPrivateKey.PrivateKeyEnd), privateKey: decoded.slice(HDPrivateKey.PrivateKeyStart, HDPrivateKey.PrivateKeyEnd),
@ -525,20 +539,20 @@ HDPrivateKey.CheckSumSize = 4;
HDPrivateKey.DataLength = 78; HDPrivateKey.DataLength = 78;
HDPrivateKey.SerializedByteSize = 82; HDPrivateKey.SerializedByteSize = 82;
HDPrivateKey.VersionStart = 0; HDPrivateKey.VersionStart = 0;
HDPrivateKey.VersionEnd = HDPrivateKey.VersionStart + HDPrivateKey.VersionSize; HDPrivateKey.VersionEnd = HDPrivateKey.VersionStart + HDPrivateKey.VersionSize;
HDPrivateKey.DepthStart = HDPrivateKey.VersionEnd; HDPrivateKey.DepthStart = HDPrivateKey.VersionEnd;
HDPrivateKey.DepthEnd = HDPrivateKey.DepthStart + HDPrivateKey.DepthSize; HDPrivateKey.DepthEnd = HDPrivateKey.DepthStart + HDPrivateKey.DepthSize;
HDPrivateKey.ParentFingerPrintStart = HDPrivateKey.DepthEnd; HDPrivateKey.ParentFingerPrintStart = HDPrivateKey.DepthEnd;
HDPrivateKey.ParentFingerPrintEnd = HDPrivateKey.ParentFingerPrintStart + HDPrivateKey.ParentFingerPrintSize; HDPrivateKey.ParentFingerPrintEnd = HDPrivateKey.ParentFingerPrintStart + HDPrivateKey.ParentFingerPrintSize;
HDPrivateKey.ChildIndexStart = HDPrivateKey.ParentFingerPrintEnd; HDPrivateKey.ChildIndexStart = HDPrivateKey.ParentFingerPrintEnd;
HDPrivateKey.ChildIndexEnd = HDPrivateKey.ChildIndexStart + HDPrivateKey.ChildIndexSize; HDPrivateKey.ChildIndexEnd = HDPrivateKey.ChildIndexStart + HDPrivateKey.ChildIndexSize;
HDPrivateKey.ChainCodeStart = HDPrivateKey.ChildIndexEnd; HDPrivateKey.ChainCodeStart = HDPrivateKey.ChildIndexEnd;
HDPrivateKey.ChainCodeEnd = HDPrivateKey.ChainCodeStart + HDPrivateKey.ChainCodeSize; HDPrivateKey.ChainCodeEnd = HDPrivateKey.ChainCodeStart + HDPrivateKey.ChainCodeSize;
HDPrivateKey.PrivateKeyStart = HDPrivateKey.ChainCodeEnd + 1; HDPrivateKey.PrivateKeyStart = HDPrivateKey.ChainCodeEnd + 1;
HDPrivateKey.PrivateKeyEnd = HDPrivateKey.PrivateKeyStart + HDPrivateKey.PrivateKeySize; HDPrivateKey.PrivateKeyEnd = HDPrivateKey.PrivateKeyStart + HDPrivateKey.PrivateKeySize;
HDPrivateKey.ChecksumStart = HDPrivateKey.PrivateKeyEnd; HDPrivateKey.ChecksumStart = HDPrivateKey.PrivateKeyEnd;
HDPrivateKey.ChecksumEnd = HDPrivateKey.ChecksumStart + HDPrivateKey.CheckSumSize; HDPrivateKey.ChecksumEnd = HDPrivateKey.ChecksumStart + HDPrivateKey.CheckSumSize;
assert(HDPrivateKey.ChecksumEnd === HDPrivateKey.SerializedByteSize); assert(HDPrivateKey.ChecksumEnd === HDPrivateKey.SerializedByteSize);

81
test/hdprivatekey.js

@ -14,7 +14,6 @@ var Base58Check = bitcore.encoding.Base58Check;
var xprivkey = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'; var xprivkey = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi';
var json = '{"network":"livenet","depth":0,"fingerPrint":876747070,"parentFingerPrint":0,"childIndex":0,"chainCode":"873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508","privateKey":"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35","checksum":-411132559,"xprivkey":"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"}'; var json = '{"network":"livenet","depth":0,"fingerPrint":876747070,"parentFingerPrint":0,"childIndex":0,"chainCode":"873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508","privateKey":"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35","checksum":-411132559,"xprivkey":"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"}';
describe('HDPrivate key interface', function() { describe('HDPrivate key interface', function() {
/* jshint maxstatements: 50 */ /* jshint maxstatements: 50 */
var expectFail = function(func, error) { var expectFail = function(func, error) {
@ -113,36 +112,33 @@ describe('HDPrivate key interface', function() {
derivedByNumber.xprivkey.should.equal(derivedByArgument.xprivkey); derivedByNumber.xprivkey.should.equal(derivedByArgument.xprivkey);
}); });
it('returns itself with "m" parameter', function() { it('returns itself with \'m\' parameter', function() {
var privateKey = new HDPrivateKey(xprivkey); var privateKey = new HDPrivateKey(xprivkey);
privateKey.should.equal(privateKey.derive('m')); privateKey.should.equal(privateKey.derive('m'));
}); });
it('returns InvalidArgument if invalid data is given to getSerializedError', function() { it('returns InvalidArgument if invalid data is given to getSerializedError', function() {
expect( expect(
HDPrivateKey.getSerializedError(1) instanceof HDPrivateKey.getSerializedError(1) instanceof hdErrors.UnrecognizedArgument
hdErrors.UnrecognizedArgument
).to.equal(true); ).to.equal(true);
}); });
it('returns InvalidLength if data of invalid length is given to getSerializedError', function() { it('returns InvalidLength if data of invalid length is given to getSerializedError', function() {
var b58s = Base58Check.encode(new buffer.Buffer('onestring'));
expect( expect(
HDPrivateKey.getSerializedError(Base58Check.encode(new buffer.Buffer('onestring'))) instanceof HDPrivateKey.getSerializedError(b58s) instanceof hdErrors.InvalidLength
hdErrors.InvalidLength
).to.equal(true); ).to.equal(true);
}); });
it('returns InvalidNetworkArgument if an invalid network is provided', function() { it('returns InvalidNetworkArgument if an invalid network is provided', function() {
expect( expect(
HDPrivateKey.getSerializedError(xprivkey, 'invalidNetwork') instanceof HDPrivateKey.getSerializedError(xprivkey, 'invalidNetwork') instanceof errors.InvalidNetworkArgument
errors.InvalidNetworkArgument
).to.equal(true); ).to.equal(true);
}); });
it('recognizes that the wrong network was asked for', function() { it('recognizes that the wrong network was asked for', function() {
expect( expect(
HDPrivateKey.getSerializedError(xprivkey, 'testnet') instanceof HDPrivateKey.getSerializedError(xprivkey, 'testnet') instanceof errors.InvalidNetwork
errors.InvalidNetwork
).to.equal(true); ).to.equal(true);
}); });
@ -200,7 +196,7 @@ describe('HDPrivate key interface', function() {
it('validates correct paths', function() { it('validates correct paths', function() {
var valid; var valid;
valid = HDPrivateKey.isValidPath("m/0'/1/2'"); valid = HDPrivateKey.isValidPath('m/0\'/1/2\'');
valid.should.equal(true); valid.should.equal(true);
valid = HDPrivateKey.isValidPath('m'); valid = HDPrivateKey.isValidPath('m');
@ -219,47 +215,36 @@ describe('HDPrivate key interface', function() {
valid.should.equal(true); valid.should.equal(true);
}); });
it('rejects illegal paths', function() {
var valid;
valid = HDPrivateKey.isValidPath('m/-1/12');
valid.should.equal(false);
valid = HDPrivateKey.isValidPath('bad path'); var invalid = [
valid.should.equal(false); 'm/-1/12',
'bad path',
'K',
'm/',
'm/12asd',
'm/1/2//3'
];
valid = HDPrivateKey.isValidPath('K'); invalid.forEach(function(datum) {
valid.should.equal(false); it('rejects illegal path ' + datum, function() {
HDPrivateKey.isValidPath(datum).should.equal(false);
valid = HDPrivateKey.isValidPath('m/'); expect(HDPrivateKey._getDerivationIndexes(datum)).to.equal(null);
valid.should.equal(false); });
valid = HDPrivateKey.isValidPath(HDPrivateKey.MaxHardened);
valid.should.equal(false);
}); });
it('generates deriving indexes correctly', function() { it('generates deriving indexes correctly', function() {
var indexes; var indexes;
indexes = HDPrivateKey._getDerivationIndexes('m/-1/12'); indexes = HDPrivateKey._getDerivationIndexes('m/-1/12');
indexes.should.eql([-1, 12]); expect(indexes).to.equal(null);
indexes = HDPrivateKey._getDerivationIndexes("m/0/12/12'"); indexes = HDPrivateKey._getDerivationIndexes('m/0/12/12\'');
indexes.should.eql([0, 12, HDPrivateKey.Hardened + 12]); indexes.should.eql([0, 12, HDPrivateKey.Hardened + 12]);
indexes = HDPrivateKey._getDerivationIndexes("m/0/12/12'"); indexes = HDPrivateKey._getDerivationIndexes('m/0/12/12\'');
indexes.should.eql([0, 12, HDPrivateKey.Hardened + 12]); indexes.should.eql([0, 12, HDPrivateKey.Hardened + 12]);
}); });
it('rejects invalid derivation path', function() {
var indexes;
indexes = HDPrivateKey._getDerivationIndexes("m/");
expect(indexes).to.be.null;
indexes = HDPrivateKey._getDerivationIndexes("bad path");
expect(indexes).to.be.null;
});
}); });
describe('conversion to/from buffer', function() { describe('conversion to/from buffer', function() {
@ -275,15 +260,16 @@ describe('HDPrivate key interface', function() {
describe('conversion to plain object/json', function() { describe('conversion to plain object/json', function() {
var plainObject = { var plainObject = {
'network':'livenet', 'network': 'livenet',
'depth':0, 'depth': 0,
'fingerPrint':876747070, 'fingerPrint': 876747070,
'parentFingerPrint':0, 'parentFingerPrint': 0,
'childIndex':0, 'childIndex': 0,
'chainCode':'873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508', 'chainCode': '873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508',
'privateKey':'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35', 'privateKey': 'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35',
'checksum':-411132559, 'checksum': -411132559,
'xprivkey':'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' 'xprivkey': 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvN' +
'KmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'
}; };
it('toObject leaves no Buffer instances', function() { it('toObject leaves no Buffer instances', function() {
var privKey = new HDPrivateKey(xprivkey); var privKey = new HDPrivateKey(xprivkey);
@ -307,4 +293,3 @@ describe('HDPrivate key interface', function() {
}); });
}); });
}); });

Loading…
Cancel
Save