100% test coverage on hdpublickey
This commit is contained in:
parent
edc5b24d69
commit
4c0769fa09
|
@ -9,16 +9,11 @@ var HDPrivateKey = require('./hdprivatekey');
|
|||
var Network = require('./networks');
|
||||
var Point = require('./crypto/point');
|
||||
var PublicKey = require('./publickey');
|
||||
var Random = require('./crypto/random');
|
||||
|
||||
var assert = require('assert');
|
||||
var buffer = require('buffer');
|
||||
var util = require('./util');
|
||||
|
||||
var MINIMUM_ENTROPY_BITS = 128;
|
||||
var BITS_TO_BYTES = 1/8;
|
||||
var MAXIMUM_ENTROPY_BITS = 512;
|
||||
|
||||
|
||||
function HDPublicKey(arg) {
|
||||
/* jshint maxcomplexity: 12 */
|
||||
|
@ -32,7 +27,9 @@ function HDPublicKey(arg) {
|
|||
if (arg) {
|
||||
if (_.isString(arg) || buffer.Buffer.isBuffer(arg)) {
|
||||
if (HDPublicKey.isValidSerialized(arg)) {
|
||||
this._buildFromSerialized(arg);
|
||||
return this._buildFromSerialized(arg);
|
||||
} else if (util.isValidJson(arg)) {
|
||||
return this._buildFromJson(arg);
|
||||
} else {
|
||||
var error = HDPublicKey.getSerializedError(arg);
|
||||
if (error === HDPublicKey.Errors.ArgumentIsPrivateExtended) {
|
||||
|
@ -43,18 +40,16 @@ function HDPublicKey(arg) {
|
|||
} else {
|
||||
if (_.isObject(arg)) {
|
||||
if (arg instanceof HDPrivateKey) {
|
||||
this._buildFromPrivate(arg);
|
||||
return this._buildFromPrivate(arg);
|
||||
} else {
|
||||
this._buildFromObject(arg);
|
||||
return this._buildFromObject(arg);
|
||||
}
|
||||
} else if (util.isValidJson(arg)) {
|
||||
this._buildFromJson(arg);
|
||||
} else {
|
||||
throw new Error(HDPublicKey.Errors.UnrecognizedArgument);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._generateRandomly();
|
||||
throw new Error(HDPublicKey.Errors.MustSupplyArgument);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,7 +132,7 @@ HDPublicKey.isValidSerialized = function (data, network) {
|
|||
*/
|
||||
HDPublicKey.getSerializedError = function (data, network) {
|
||||
/* jshint maxcomplexity: 10 */
|
||||
network = Network.get(network) || Network.defaultNetwork;
|
||||
/* jshint maxstatements: 20 */
|
||||
if (!(_.isString(data) || buffer.Buffer.isBuffer(data))) {
|
||||
return HDPublicKey.Errors.InvalidArgument;
|
||||
}
|
||||
|
@ -152,15 +147,16 @@ HDPublicKey.getSerializedError = function (data, network) {
|
|||
if (data.length !== 78) {
|
||||
return HDPublicKey.Errors.InvalidLength;
|
||||
}
|
||||
if (util.integerFromBuffer(data.slice(0, 4)) === network.xprivkey) {
|
||||
return HDPublicKey.Errors.ArgumentIsPrivateExtended;
|
||||
}
|
||||
if (!_.isUndefined(network)) {
|
||||
var error = HDPublicKey._validateNetwork(data, network);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
network = Network.get(network) || Network.defaultNetwork;
|
||||
if (util.integerFromBuffer(data.slice(0, 4)) === network.xprivkey) {
|
||||
return HDPublicKey.Errors.ArgumentIsPrivateExtended;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
@ -192,17 +188,17 @@ HDPublicKey.prototype._buildFromPrivate = function (arg) {
|
|||
};
|
||||
|
||||
HDPublicKey.prototype._buildFromObject = function (arg) {
|
||||
/* jshint maxcomplexity: 8 */
|
||||
/* jshint maxcomplexity: 10 */
|
||||
// TODO: Type validation
|
||||
var buffers = {
|
||||
version: util.integerAsBuffer(Network.get(arg.network).xpubkey),
|
||||
version: arg.network ? util.integerAsBuffer(Network.get(arg.network).xpubkey) : arg.version,
|
||||
depth: util.integerAsSingleByteBuffer(arg.depth),
|
||||
parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? util.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
|
||||
childIndex: util.integerAsBuffer(arg.childIndex),
|
||||
chainCode: _.isString(arg.chainCode) ? util.hexToBuffer(arg.chainCode) : arg.chainCode,
|
||||
publicKey: _.isString(arg.publicKey) ? util.hexToBuffer(arg.publicKey) :
|
||||
buffer.Buffer.isBuffer(arg.publicKey) ? arg.publicKey : arg.publicKey.toBuffer(),
|
||||
checksum: arg.checksum && arg.checksum.length ? util.integerAsBuffer(arg.checksum) : undefined
|
||||
checksum: _.isNumber(arg.checksum) ? util.integerAsBuffer(arg.checksum) : arg.checksum
|
||||
};
|
||||
return this._buildFromBuffers(buffers);
|
||||
};
|
||||
|
@ -223,37 +219,6 @@ HDPublicKey.prototype._buildFromSerialized = function (arg) {
|
|||
return this._buildFromBuffers(buffers);
|
||||
};
|
||||
|
||||
HDPublicKey.prototype._generateRandomly = function (network) {
|
||||
return HDPublicKey.fromSeed(Random.getRandomBytes(64), network);
|
||||
};
|
||||
|
||||
HDPublicKey.fromSeed = function (hexa, network) {
|
||||
/* jshint maxcomplexity: 8 */
|
||||
|
||||
if (util.isHexaString(hexa)) {
|
||||
hexa = util.hexToBuffer(hexa);
|
||||
}
|
||||
if (!Buffer.isBuffer(hexa)) {
|
||||
throw new Error(HDPublicKey.InvalidEntropyArg);
|
||||
}
|
||||
if (hexa.length < MINIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
|
||||
throw new Error(HDPublicKey.NotEnoughEntropy);
|
||||
}
|
||||
if (hexa.length > MAXIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
|
||||
throw new Error('More than 512 bytes of entropy is nonstandard');
|
||||
}
|
||||
var hash = Hash.sha512hmac(hexa, new buffer.Buffer('Bitcoin seed'));
|
||||
|
||||
return new HDPublicKey({
|
||||
network: Network.get(network) || Network.livenet,
|
||||
depth: 0,
|
||||
parentFingerPrint: 0,
|
||||
childIndex: 0,
|
||||
publicKey: hash.slice(0, 32),
|
||||
chainCode: hash.slice(32, 64)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Receives a object with buffers in all the properties and populates the
|
||||
* internal structure
|
||||
|
@ -272,6 +237,7 @@ HDPublicKey.fromSeed = function (hexa, network) {
|
|||
*/
|
||||
HDPublicKey.prototype._buildFromBuffers = function (arg) {
|
||||
/* jshint maxcomplexity: 8 */
|
||||
/* jshint maxstatements: 20 */
|
||||
|
||||
HDPublicKey._validateBufferArguments(arg);
|
||||
this._buffers = arg;
|
||||
|
@ -280,10 +246,12 @@ HDPublicKey.prototype._buildFromBuffers = function (arg) {
|
|||
arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode,
|
||||
arg.publicKey
|
||||
];
|
||||
var concat = buffer.Buffer.concat(sequence);
|
||||
var checksum = Base58Check.checksum(concat);
|
||||
if (!arg.checksum || !arg.checksum.length) {
|
||||
arg.checksum = Base58Check.checksum(buffer.Buffer.concat(sequence));
|
||||
arg.checksum = checksum;
|
||||
} else {
|
||||
if (arg.checksum.toString() !== sequence.toString()) {
|
||||
if (arg.checksum.toString('hex') !== checksum.toString('hex')) {
|
||||
throw new Error(HDPublicKey.Errors.InvalidB58Checksum);
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +296,7 @@ HDPublicKey.prototype.toString = function () {
|
|||
|
||||
HDPublicKey.prototype.toObject = function () {
|
||||
return {
|
||||
network: Network.get(util.integerFromBuffer(this._buffers.version)),
|
||||
network: Network.get(util.integerFromBuffer(this._buffers.version)).name,
|
||||
depth: util.integerFromSingleByteBuffer(this._buffers.depth),
|
||||
fingerPrint: util.integerFromBuffer(this.fingerPrint),
|
||||
parentFingerPrint: util.integerFromBuffer(this._buffers.parentFingerPrint),
|
||||
|
@ -344,12 +312,8 @@ HDPublicKey.prototype.toJson = function () {
|
|||
return JSON.stringify(this.toObject());
|
||||
};
|
||||
|
||||
HDPublicKey.DefaultDepth = 0;
|
||||
HDPublicKey.DefaultFingerprint = 0;
|
||||
HDPublicKey.DefaultChildIndex = 0;
|
||||
HDPublicKey.DefaultNetwork = Network.livenet;
|
||||
HDPublicKey.Hardened = 0x80000000;
|
||||
HDPublicKey.RootElementAlias = ['m', 'M', 'm\'', 'M\''];
|
||||
HDPublicKey.RootElementAlias = ['m', 'M'];
|
||||
|
||||
HDPublicKey.VersionSize = 4;
|
||||
HDPublicKey.DepthSize = 1;
|
||||
|
@ -393,6 +357,7 @@ HDPublicKey.Errors.InvalidNetwork = 'Unexpected version for network';
|
|||
HDPublicKey.Errors.InvalidNetworkArgument = 'Network argument must be \'livenet\' or \'testnet\'';
|
||||
HDPublicKey.Errors.InvalidParentFingerPrint = 'Invalid Parent Fingerprint - must be a number';
|
||||
HDPublicKey.Errors.InvalidPath = 'Invalid path for derivation: must start with "m"';
|
||||
HDPublicKey.Errors.MustSupplyArgument = 'Must supply an argument for the constructor';
|
||||
HDPublicKey.Errors.UnrecognizedArgument = 'Creating a HDPublicKey requires a string, a buffer, a json, or an object';
|
||||
|
||||
module.exports = HDPublicKey;
|
||||
|
|
|
@ -40,7 +40,7 @@ describe('HDPrivate key interface', function() {
|
|||
regenerate.xprivkey.should.equal(xprivkey);
|
||||
});
|
||||
|
||||
it('builds a json keeping the same interface than previous versions', function() {
|
||||
it('builds a json keeping the structure and same members', function() {
|
||||
assert(util.shallowEquals(
|
||||
JSON.parse(new HDPrivateKey(json).toJson()),
|
||||
JSON.parse(new HDPrivateKey(xprivkey).toJson())
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint unused: false */
|
||||
var _ = require('lodash');
|
||||
var assert = require('assert');
|
||||
var should = require('chai').should();
|
||||
var expect = require('chai').expect;
|
||||
var bitcore = require('..');
|
||||
var buffer = require('buffer');
|
||||
var util = bitcore.util;
|
||||
var HDPrivateKey = bitcore.HDPrivateKey;
|
||||
var HDPublicKey = bitcore.HDPublicKey;
|
||||
var Base58Check = bitcore.encoding.Base58Check;
|
||||
|
||||
var xprivkey = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi';
|
||||
var xpubkey = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8';
|
||||
var json = '{"network":"livenet","depth":0,"fingerPrint":876747070,"parentFingerPrint":0,"childIndex":0,"chainCode":"873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508","publicKey":"0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2","checksum":-1421395167,"xpubkey":"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"}';
|
||||
var derived_0_1_200000 = 'xpub6BqyndF6rkBNTV6LXwiY8Pco8aqctqq7tGEUdA8fmGDTnDJphn2fmxr3eM8Lm3m8TrNUsLbEjHvpa3adBU18YpEx4tp2Zp6nqax3mQkudhX';
|
||||
|
||||
describe('HDPublicKey interface', function() {
|
||||
|
||||
var expectFail = function(argument, error) {
|
||||
return function() {
|
||||
expect(function() {
|
||||
return new HDPublicKey(argument);
|
||||
}).to.throw(error);
|
||||
};
|
||||
};
|
||||
describe('creation formats', function() {
|
||||
|
||||
it('returns same argument if already an instance of HDPublicKey', function() {
|
||||
var publicKey = new HDPublicKey(xpubkey);
|
||||
publicKey.should.equal(new HDPublicKey(publicKey));
|
||||
});
|
||||
|
||||
it('returns the correct xpubkey for a xprivkey', function() {
|
||||
var publicKey = new HDPublicKey(xprivkey);
|
||||
publicKey.xpubkey.should.equal(xpubkey);
|
||||
});
|
||||
|
||||
it('allows to call the argument with no "new" keyword', function() {
|
||||
HDPublicKey(xpubkey).xpubkey.should.equal(new HDPublicKey(xpubkey).xpubkey);
|
||||
});
|
||||
|
||||
it('fails when user doesn\'t supply an argument', function() {
|
||||
expect(function() { return new HDPublicKey(); }).to.throw(HDPublicKey.Errors.MustSupplyArgument);
|
||||
});
|
||||
|
||||
it('doesn\'t recognize an invalid argument', function() {
|
||||
var expectCreationFail = function(argument) {
|
||||
expect(function() { return new HDPublicKey(argument); }).to.throw(HDPublicKey.Errors.UnrecognizedArgument);
|
||||
};
|
||||
expectCreationFail(1);
|
||||
expectCreationFail(true);
|
||||
});
|
||||
|
||||
|
||||
describe('xpubkey string serialization errors', function() {
|
||||
it('fails on invalid length', expectFail(
|
||||
Base58Check.encode(new buffer.Buffer([1, 2, 3])),
|
||||
HDPublicKey.Errors.InvalidLength
|
||||
));
|
||||
it('fails on invalid base58 encoding', expectFail(
|
||||
xpubkey + '1',
|
||||
HDPublicKey.Errors.InvalidB58Checksum
|
||||
));
|
||||
});
|
||||
|
||||
it('can be generated from a json', function() {
|
||||
expect(new HDPublicKey(json).xpubkey).to.equal(xpubkey);
|
||||
});
|
||||
|
||||
it('can generate a json that has a particular structure', function() {
|
||||
assert(util.shallowEquals(
|
||||
JSON.parse(new HDPublicKey(json).toJson()),
|
||||
JSON.parse(new HDPublicKey(xpubkey).toJson())
|
||||
));
|
||||
});
|
||||
|
||||
it('builds from a buffer object', function() {
|
||||
(new HDPublicKey(new HDPublicKey(xpubkey)._buffers)).xpubkey.should.equal(xpubkey);
|
||||
});
|
||||
|
||||
it('checks the checksum', function() {
|
||||
var buffers = new HDPublicKey(xpubkey)._buffers;
|
||||
buffers.checksum = util.integerAsBuffer(1);
|
||||
expectFail(buffers, HDPublicKey.Errors.InvalidB58Checksum)();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('error checking on serialization', function() {
|
||||
it('throws invalid argument when argument is not a string or buffer', function() {
|
||||
HDPublicKey.getSerializedError(1).should.equal(HDPublicKey.Errors.InvalidArgument);
|
||||
});
|
||||
it('if a network is provided, validates that data corresponds to it', function() {
|
||||
HDPublicKey.getSerializedError(xpubkey, 'testnet').should.equal(HDPublicKey.Errors.InvalidNetwork);
|
||||
});
|
||||
it('recognizes invalid network arguments', function() {
|
||||
HDPublicKey.getSerializedError(xpubkey, 'invalid').should.equal(HDPublicKey.Errors.InvalidNetworkArgument);
|
||||
});
|
||||
it('recognizes a valid network', function() {
|
||||
expect(HDPublicKey.getSerializedError(xpubkey, 'livenet')).to.equal(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('toString() returns the same value as .xpubkey', function() {
|
||||
var pubKey = new HDPublicKey(xpubkey);
|
||||
pubKey.toString().should.equal(pubKey.xpubkey);
|
||||
});
|
||||
|
||||
describe('derivation', function() {
|
||||
it('derivation is the same whether deriving with number or string', function() {
|
||||
var pubkey = new HDPublicKey(xpubkey);
|
||||
var derived1 = pubkey.derive(0).derive(1).derive(200000);
|
||||
var derived2 = pubkey.derive('m/0/1/200000');
|
||||
derived1.xpubkey.should.equal(derived_0_1_200000);
|
||||
derived2.xpubkey.should.equal(derived_0_1_200000);
|
||||
});
|
||||
|
||||
it('allows special parameters m, M', function() {
|
||||
var expectDerivationSuccess = function(argument) {
|
||||
new HDPublicKey(xpubkey).derive(argument).xpubkey.should.equal(xpubkey);
|
||||
};
|
||||
expectDerivationSuccess('m');
|
||||
expectDerivationSuccess('M');
|
||||
});
|
||||
|
||||
it('doesn\'t allow object arguments for derivation', function() {
|
||||
expect(function() {
|
||||
return new HDPublicKey(xpubkey).derive({});
|
||||
}).to.throw(HDPublicKey.Errors.InvalidDerivationArgument);
|
||||
});
|
||||
|
||||
it('doesn\'t allow other parameters like m\' or M\' or "s"', function() {
|
||||
var expectDerivationFail = function(argument) {
|
||||
expect(function() {
|
||||
return new HDPublicKey(xpubkey).derive(argument);
|
||||
}).to.throw(HDPublicKey.Errors.InvalidPath);
|
||||
};
|
||||
/* jshint quotmark: double */
|
||||
expectDerivationFail("m'");
|
||||
expectDerivationFail("M'");
|
||||
expectDerivationFail("1");
|
||||
expectDerivationFail("S");
|
||||
});
|
||||
|
||||
it('can\'t derive hardened keys', function() {
|
||||
expect(function() { return new HDPublicKey(xpubkey).derive(HDPublicKey.Hardened + 1); })
|
||||
.to.throw(HDPublicKey.Errors.InvalidIndexCantDeriveHardened);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue