100% test coverage for HDPrivateKey
This commit is contained in:
parent
950ea6ed1a
commit
edc5b24d69
16
.jshintrc
16
.jshintrc
|
@ -30,15 +30,15 @@
|
||||||
"maxlen": 600, // Maximum number of lines of code in a file
|
"maxlen": 600, // Maximum number of lines of code in a file
|
||||||
|
|
||||||
"predef": [ // Extra globals.
|
"predef": [ // Extra globals.
|
||||||
"define",
|
|
||||||
"require",
|
|
||||||
"exports",
|
|
||||||
"module",
|
|
||||||
"describe",
|
|
||||||
"before",
|
|
||||||
"beforeEach",
|
|
||||||
"after",
|
"after",
|
||||||
"afterEach",
|
"afterEach",
|
||||||
"it"
|
"before",
|
||||||
|
"beforeEach",
|
||||||
|
"define",
|
||||||
|
"describe",
|
||||||
|
"exports",
|
||||||
|
"it",
|
||||||
|
"module",
|
||||||
|
"require"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,12 @@ gulp.task('watch:test', function() {
|
||||||
return gulp.watch(alljs, ['test-nofail']);
|
return gulp.watch(alljs, ['test-nofail']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('watch:coverage', function() {
|
||||||
|
// TODO: Only run tests that are linked to file changes by doing
|
||||||
|
// something smart like reading through the require statements
|
||||||
|
return gulp.watch(alljs, ['coverage']);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('watch:lint', function() {
|
gulp.task('watch:lint', function() {
|
||||||
// TODO: Only lint files that are linked to file changes by doing
|
// TODO: Only lint files that are linked to file changes by doing
|
||||||
// something smart like reading through the require statements
|
// something smart like reading through the require statements
|
||||||
|
|
2
index.js
2
index.js
|
@ -18,6 +18,8 @@ bitcore.encoding.BufferReader = require('./lib/encoding/bufferreader');
|
||||||
bitcore.encoding.BufferWriter = require('./lib/encoding/bufferwriter');
|
bitcore.encoding.BufferWriter = require('./lib/encoding/bufferwriter');
|
||||||
bitcore.encoding.Varint = require('./lib/encoding/varint');
|
bitcore.encoding.Varint = require('./lib/encoding/varint');
|
||||||
|
|
||||||
|
bitcore.util = require('./lib/util');
|
||||||
|
|
||||||
// main bitcoin library
|
// main bitcoin library
|
||||||
bitcore.Address = require('./lib/address');
|
bitcore.Address = require('./lib/address');
|
||||||
bitcore.Block = require('./lib/block');
|
bitcore.Block = require('./lib/block');
|
||||||
|
|
|
@ -31,20 +31,20 @@ function HDPrivateKey(arg) {
|
||||||
if (_.isString(arg) || buffer.Buffer.isBuffer(arg)) {
|
if (_.isString(arg) || buffer.Buffer.isBuffer(arg)) {
|
||||||
if (HDPrivateKey.isValidSerialized(arg)) {
|
if (HDPrivateKey.isValidSerialized(arg)) {
|
||||||
this._buildFromSerialized(arg);
|
this._buildFromSerialized(arg);
|
||||||
|
} else if (util.isValidJson(arg)) {
|
||||||
|
this._buildFromJson(arg);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(HDPrivateKey.getSerializedError(arg));
|
throw new Error(HDPrivateKey.getSerializedError(arg));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_.isObject(arg)) {
|
if (_.isObject(arg)) {
|
||||||
this._buildFromObject(arg);
|
this._buildFromObject(arg);
|
||||||
} else if (util.isValidJson(arg)) {
|
|
||||||
this._buildFromJson(arg);
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(HDPrivateKey.Errors.UnrecognizedArgument);
|
throw new Error(HDPrivateKey.Errors.UnrecognizedArgument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._generateRandomly();
|
return this._generateRandomly();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,8 +162,8 @@ HDPrivateKey._validateNetwork = function(data, network) {
|
||||||
if (!network) {
|
if (!network) {
|
||||||
return HDPrivateKey.Errors.InvalidNetworkArgument;
|
return HDPrivateKey.Errors.InvalidNetworkArgument;
|
||||||
}
|
}
|
||||||
var version = data.slice(4);
|
var version = data.slice(0, 4);
|
||||||
if (version.toString() !== network.xprivkey.toString()) {
|
if (util.integerFromBuffer(version) !== network.xprivkey) {
|
||||||
return HDPrivateKey.Errors.InvalidNetwork;
|
return HDPrivateKey.Errors.InvalidNetwork;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -176,13 +176,13 @@ HDPrivateKey.prototype._buildFromJson = function(arg) {
|
||||||
HDPrivateKey.prototype._buildFromObject = function(arg) {
|
HDPrivateKey.prototype._buildFromObject = function(arg) {
|
||||||
// TODO: Type validation
|
// TODO: Type validation
|
||||||
var buffers = {
|
var buffers = {
|
||||||
version: util.integerAsBuffer(Network.get(arg.network).xprivkey),
|
version: arg.network ? util.integerAsBuffer(Network.get(arg.network).xprivkey) : arg.version,
|
||||||
depth: util.integerAsSingleByteBuffer(arg.depth),
|
depth: util.integerAsSingleByteBuffer(arg.depth),
|
||||||
parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? util.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
|
parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? util.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
|
||||||
childIndex: _.isNumber(arg.childIndex) ? util.integerAsBuffer(arg.childIndex) : arg.childIndex,
|
childIndex: _.isNumber(arg.childIndex) ? util.integerAsBuffer(arg.childIndex) : arg.childIndex,
|
||||||
chainCode: _.isString(arg.chainCode) ? util.hexToBuffer(arg.chainCode) : arg.chainCode,
|
chainCode: _.isString(arg.chainCode) ? util.hexToBuffer(arg.chainCode) : arg.chainCode,
|
||||||
privateKey: _.isString(arg.privateKey) ? util.hexToBuffer(arg.privateKey) : arg.privateKey,
|
privateKey: (_.isString(arg.privateKey) && util.isHexa(arg.privateKey)) ? util.hexToBuffer(arg.privateKey) : arg.privateKey,
|
||||||
checksum: arg.checksum && arg.checksum.length ? util.integerAsBuffer(arg.checksum) : undefined
|
checksum: arg.checksum ? (arg.checksum.length ? arg.checksum : util.integerAsBuffer(arg.checksum)) : undefined
|
||||||
};
|
};
|
||||||
return this._buildFromBuffers(buffers);
|
return this._buildFromBuffers(buffers);
|
||||||
};
|
};
|
||||||
|
@ -214,13 +214,13 @@ HDPrivateKey.fromSeed = function(hexa, network) {
|
||||||
hexa = util.hexToBuffer(hexa);
|
hexa = util.hexToBuffer(hexa);
|
||||||
}
|
}
|
||||||
if (!Buffer.isBuffer(hexa)) {
|
if (!Buffer.isBuffer(hexa)) {
|
||||||
throw new Error(HDPrivateKey.InvalidEntropyArg);
|
throw new Error(HDPrivateKey.Errors.InvalidEntropyArg);
|
||||||
}
|
}
|
||||||
if (hexa.length < MINIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
|
if (hexa.length < MINIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
|
||||||
throw new Error(HDPrivateKey.NotEnoughEntropy);
|
throw new Error(HDPrivateKey.Errors.NotEnoughEntropy);
|
||||||
}
|
}
|
||||||
if (hexa.length > MAXIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
|
if (hexa.length > MAXIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
|
||||||
throw new Error('More than 512 bytes of entropy is nonstandard');
|
throw new Error(HDPrivateKey.Errors.TooMuchEntropy);
|
||||||
}
|
}
|
||||||
var hash = Hash.sha512hmac(hexa, new buffer.Buffer('Bitcoin seed'));
|
var hash = Hash.sha512hmac(hexa, new buffer.Buffer('Bitcoin seed'));
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ HDPrivateKey.prototype._buildFromBuffers = function(arg) {
|
||||||
if (!arg.checksum || !arg.checksum.length) {
|
if (!arg.checksum || !arg.checksum.length) {
|
||||||
arg.checksum = Base58Check.checksum(buffer.Buffer.concat(sequence));
|
arg.checksum = Base58Check.checksum(buffer.Buffer.concat(sequence));
|
||||||
} else {
|
} else {
|
||||||
if (arg.checksum.toString() !== sequence.toString()) {
|
if (arg.checksum.toString() !== Base58Check.checksum(buffer.Buffer.concat(sequence)).toString()) {
|
||||||
throw new Error(HDPrivateKey.Errors.InvalidB58Checksum);
|
throw new Error(HDPrivateKey.Errors.InvalidB58Checksum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,11 +316,11 @@ HDPrivateKey.prototype.toObject = function() {
|
||||||
return {
|
return {
|
||||||
network: Network.get(util.integerFromBuffer(this._buffers.version)).name,
|
network: Network.get(util.integerFromBuffer(this._buffers.version)).name,
|
||||||
depth: util.integerFromSingleByteBuffer(this._buffers.depth),
|
depth: util.integerFromSingleByteBuffer(this._buffers.depth),
|
||||||
fingerPrint: this.fingerPrint,
|
fingerPrint: util.integerFromBuffer(this.fingerPrint),
|
||||||
parentFingerPrint: util.integerFromBuffer(this._buffers.parentFingerPrint),
|
parentFingerPrint: util.integerFromBuffer(this._buffers.parentFingerPrint),
|
||||||
childIndex: util.integerFromBuffer(this._buffers.childIndex),
|
childIndex: util.integerFromBuffer(this._buffers.childIndex),
|
||||||
chainCode: util.bufferToHex(this._buffers.chainCode),
|
chainCode: util.bufferToHex(this._buffers.chainCode),
|
||||||
privateKey: this.privateKey.toString(),
|
privateKey: this.privateKey.toBuffer().toString('hex'),
|
||||||
checksum: util.integerFromBuffer(this._buffers.checksum),
|
checksum: util.integerFromBuffer(this._buffers.checksum),
|
||||||
xprivkey: this.xprivkey
|
xprivkey: this.xprivkey
|
||||||
};
|
};
|
||||||
|
|
11
lib/util.js
11
lib/util.js
|
@ -11,7 +11,18 @@ var isHexa = function isHexa(value) {
|
||||||
return /^[0-9a-fA-F]+$/.test(value);
|
return /^[0-9a-fA-F]+$/.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var shallowEquals = function(obj1, obj2) {
|
||||||
|
var keys1 = _.keys(obj1);
|
||||||
|
var keys2 = _.keys(obj2);
|
||||||
|
if (_.size(keys1) !== _.size(keys2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var compare = function(key) { return obj1[key] === obj2[key]; };
|
||||||
|
return _.all(keys1, compare) && _.all(keys2, compare);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
shallowEquals: shallowEquals,
|
||||||
isValidJson: function isValidJson(arg) {
|
isValidJson: function isValidJson(arg) {
|
||||||
try {
|
try {
|
||||||
JSON.parse(arg);
|
JSON.parse(arg);
|
||||||
|
|
|
@ -1,15 +1,56 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
/* jshint unused: false */
|
/* jshint unused: false */
|
||||||
|
var _ = require('lodash');
|
||||||
|
var assert = require('assert');
|
||||||
var should = require('chai').should();
|
var should = require('chai').should();
|
||||||
|
var expect = require('chai').expect;
|
||||||
var bitcore = require('..');
|
var bitcore = require('..');
|
||||||
|
var buffer = require('buffer');
|
||||||
|
var util = bitcore.util;
|
||||||
var HDPrivateKey = bitcore.HDPrivateKey;
|
var HDPrivateKey = bitcore.HDPrivateKey;
|
||||||
|
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"}';
|
||||||
|
|
||||||
describe('HDPrivate key interface', function() {
|
describe('HDPrivate key interface', function() {
|
||||||
|
/* jshint maxstatements: 50 */
|
||||||
|
var expectFail = function(argument, error) {
|
||||||
|
expect(function() {
|
||||||
|
var privateKey = new HDPrivateKey(argument);
|
||||||
|
}).to.throw(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectDerivationFail = function(argument, error) {
|
||||||
|
expect(function() {
|
||||||
|
var privateKey = new HDPrivateKey(xprivkey);
|
||||||
|
privateKey.derive(argument);
|
||||||
|
}).to.throw(error);
|
||||||
|
};
|
||||||
it('should make a new private key from random', function() {
|
it('should make a new private key from random', function() {
|
||||||
new HDPrivateKey().should.exist();
|
(new HDPrivateKey().xprivkey).should.exist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error with an invalid checksum', function() {
|
||||||
|
expectFail(xprivkey + '1', HDPrivateKey.Errors.InvalidB58Checksum);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be rebuilt from a json generated by itself', function() {
|
||||||
|
var regenerate = new HDPrivateKey(json);
|
||||||
|
regenerate.xprivkey.should.equal(xprivkey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds a json keeping the same interface than previous versions', function() {
|
||||||
|
assert(util.shallowEquals(
|
||||||
|
JSON.parse(new HDPrivateKey(json).toJson()),
|
||||||
|
JSON.parse(new HDPrivateKey(xprivkey).toJson())
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('should error with a nonsensical argument', function() {
|
||||||
|
it('like a number', function() {
|
||||||
|
expectFail(1, HDPrivateKey.Errors.UnrecognizedArgument);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows no-new calling', function() {
|
it('allows no-new calling', function() {
|
||||||
|
@ -21,11 +62,91 @@ describe('HDPrivate key interface', function() {
|
||||||
.xprivkey.should.equal(xprivkey);
|
.xprivkey.should.equal(xprivkey);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails when trying to derive with an invalid argument', function() {
|
||||||
|
expectDerivationFail([], HDPrivateKey.Errors.InvalidDerivationArgument);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('catches early invalid paths', function() {
|
||||||
|
expectDerivationFail('s', HDPrivateKey.Errors.InvalidPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows derivation of hardened keys by passing a very big number', function() {
|
||||||
|
var privateKey = new HDPrivateKey(xprivkey);
|
||||||
|
var derivedByNumber = privateKey.derive(0x80000000);
|
||||||
|
var derivedByArgument = privateKey.derive(0, true);
|
||||||
|
derivedByNumber.xprivkey.should.equal(derivedByArgument.xprivkey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns itself with "m" parameter', function() {
|
||||||
|
var privateKey = new HDPrivateKey(xprivkey);
|
||||||
|
privateKey.should.equal(privateKey.derive('m'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns InvalidArgument if invalid data is given to getSerializedError', function() {
|
||||||
|
HDPrivateKey.getSerializedError(1).should.equal(HDPrivateKey.Errors.InvalidArgument);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns InvalidLength if data of invalid length is given to getSerializedError', function() {
|
||||||
|
HDPrivateKey.getSerializedError(Base58Check.encode(new buffer.Buffer('onestring'))).should.equal(HDPrivateKey.Errors.InvalidLength);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns InvalidNetworkArgument if an invalid network is provided', function() {
|
||||||
|
HDPrivateKey.getSerializedError(xprivkey, 'invalidNetwork').should.equal(HDPrivateKey.Errors.InvalidNetworkArgument);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes that the wrong network was asked for', function() {
|
||||||
|
HDPrivateKey.getSerializedError(xprivkey, 'testnet').should.equal(HDPrivateKey.Errors.InvalidNetwork);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes the correct network', function() {
|
||||||
|
expect(HDPrivateKey.getSerializedError(xprivkey, 'livenet')).to.equal(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('on creation from seed', function() {
|
||||||
|
var expectSeedFail = function(argument, error) {
|
||||||
|
expect(function() {
|
||||||
|
return HDPrivateKey.fromSeed(argument);
|
||||||
|
}).to.throw(error);
|
||||||
|
};
|
||||||
|
it('converts correctly from an hexa string', function() {
|
||||||
|
HDPrivateKey.fromSeed('01234567890abcdef01234567890abcdef').xprivkey.should.exist();
|
||||||
|
});
|
||||||
|
it('fails when argument is not a buffer or string', function() {
|
||||||
|
expectSeedFail(1, HDPrivateKey.Errors.InvalidEntropyArg);
|
||||||
|
});
|
||||||
|
it('fails when argument doesn\'t provide enough entropy', function() {
|
||||||
|
expectSeedFail('01', HDPrivateKey.Errors.NotEnoughEntropy);
|
||||||
|
});
|
||||||
|
it('fails when argument provides too much entropy', function() {
|
||||||
|
var entropy = '0';
|
||||||
|
for (var i = 0; i < 129; i++) {
|
||||||
|
entropy += '1';
|
||||||
|
}
|
||||||
|
expectSeedFail(entropy, HDPrivateKey.Errors.TooMuchEntropy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly errors if an invalid checksum is provided', function() {
|
||||||
|
var privKey = new HDPrivateKey(xprivkey);
|
||||||
|
expect(function() {
|
||||||
|
var buffers = privKey._buffers;
|
||||||
|
buffers.checksum = util.integerAsBuffer(0);
|
||||||
|
return new HDPrivateKey(buffers);
|
||||||
|
}).to.throw(HDPrivateKey.Errors.InvalidB58Checksum);
|
||||||
|
});
|
||||||
|
it('correctly validates the checksum', function() {
|
||||||
|
var privKey = new HDPrivateKey(xprivkey);
|
||||||
|
expect(function() {
|
||||||
|
var buffers = privKey._buffers;
|
||||||
|
return new HDPrivateKey(buffers);
|
||||||
|
}).to.not.throw();
|
||||||
|
});
|
||||||
|
|
||||||
it('shouldn\'t matter if derivations are made with strings or numbers', function() {
|
it('shouldn\'t matter if derivations are made with strings or numbers', function() {
|
||||||
var privateKey = new HDPrivateKey(xprivkey);
|
var privateKey = new HDPrivateKey(xprivkey);
|
||||||
var derivedByString = privateKey.derive('m/0\'/1/2\'');
|
var derivedByString = privateKey.derive('m/0\'/1/2\'');
|
||||||
var derivedByNumber = privateKey.derive(0, true).derive(1).derive(2, true);
|
var derivedByNumber = privateKey.derive(0, true).derive(1).derive(2, true);
|
||||||
derivedByNumber.xprivkey.should.equal(derivedByString.xprivkey);
|
derivedByNumber.xprivkey.should.equal(derivedByString.xprivkey);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue