100% test coverage on hdpublickey

This commit is contained in:
Esteban Ordano 2014-11-28 12:11:41 -03:00
parent edc5b24d69
commit 4c0769fa09
3 changed files with 176 additions and 58 deletions

View File

@ -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;

View File

@ -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())

153
test/hdpublickey.js Normal file
View File

@ -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);
});
});
});