diff --git a/.gitignore b/.gitignore
index 05755b299..2d415e1bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ node_modules
browser/bitcore.js
browser/tests.js
+lib/errors/index.js
CONTRIBUTING.html
LICENSE.html
diff --git a/.jshintrc b/.jshintrc
index e7d7b7a38..8be24f2e6 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -30,15 +30,15 @@
"maxlen": 600, // Maximum number of lines of code in a file
"predef": [ // Extra globals.
- "define",
- "require",
- "exports",
- "module",
- "describe",
- "before",
- "beforeEach",
"after",
"afterEach",
- "it"
+ "before",
+ "beforeEach",
+ "define",
+ "describe",
+ "exports",
+ "it",
+ "module",
+ "require"
]
}
diff --git a/gulpfile.js b/gulpfile.js
index 6285ae726..b227ceb57 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -44,13 +44,13 @@ var testKarma = shell.task([
]);
-gulp.task('test', testMocha);
+gulp.task('test', ['errors'], testMocha);
-gulp.task('test-all', function(callback) {
+gulp.task('test-all', ['errors'], function(callback) {
runSequence(['test'], ['karma'], callback);
});
-gulp.task('test-nofail', function() {
+gulp.task('test-nofail', ['errors'], function() {
return testMocha().on('error', ignoreError);
});
@@ -60,6 +60,12 @@ gulp.task('watch:test', function() {
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() {
// TODO: Only lint files that are linked to file changes by doing
// something smart like reading through the require statements
@@ -87,7 +93,7 @@ gulp.task('lint', function() {
.pipe(jshint.reporter('default'));
});
-gulp.task('browser', function() {
+gulp.task('browser', ['errors'], function() {
return gulp.src('index.js')
.pipe(browserify({
insertGlobals: true
@@ -100,13 +106,17 @@ gulp.task('browser-test', shell.task([
'find test/ -type f -name "*.js" | xargs browserify -o ./browser/tests.js'
]));
-gulp.task('browser-all', function(callback) {
+gulp.task('browser-all', ['errors'], function(callback) {
runSequence(['browser'], ['browser-test'], callback);
});
gulp.task('karma', testKarma);
-gulp.task('minify', function() {
+gulp.task('errors', shell.task([
+ 'node ./lib/errors/build.js'
+]));
+
+gulp.task('minify', ['errors'], function() {
return gulp.src('dist/bitcore.js')
.pipe(closureCompiler({
fileName: 'bitcore.min.js',
@@ -120,5 +130,8 @@ gulp.task('minify', function() {
});
gulp.task('default', function(callback) {
- return runSequence(['lint', 'jsdoc', 'browser', 'test'], ['coverage', 'minify'], callback);
+ return runSequence(['lint', 'jsdoc'],
+ ['browser', 'test'],
+ ['coverage', 'minify'],
+ callback);
});
diff --git a/index.js b/index.js
index 092effa84..858d6a06f 100644
--- a/index.js
+++ b/index.js
@@ -18,11 +18,21 @@ bitcore.encoding.BufferReader = require('./lib/encoding/bufferreader');
bitcore.encoding.BufferWriter = require('./lib/encoding/bufferwriter');
bitcore.encoding.Varint = require('./lib/encoding/varint');
+// utilities
+bitcore.util = {};
+bitcore.util.bitcoin = require('./lib/util/bitcoin');
+bitcore.util.buffer = require('./lib/util/buffer');
+bitcore.util.js = require('./lib/util/js');
+
+// errors thrown by the library
+bitcore.errors = require('./lib/errors');
+
// main bitcoin library
bitcore.Address = require('./lib/address');
-bitcore.BIP32 = require('./lib/bip32');
bitcore.Block = require('./lib/block');
bitcore.Blockheader = require('./lib/blockheader');
+bitcore.HDPrivateKey = require('./lib/hdprivatekey.js');
+bitcore.HDPublicKey = require('./lib/hdpublickey.js');
bitcore.Networks = require('./lib/networks');
bitcore.Opcode = require('./lib/opcode');
bitcore.PrivateKey = require('./lib/privatekey');
@@ -33,7 +43,7 @@ bitcore.Txin = require('./lib/txin');
bitcore.Txout = require('./lib/txout');
-//dependencies, subject to change
+// dependencies, subject to change
bitcore.deps = {};
bitcore.deps.bnjs = require('bn.js');
bitcore.deps.bs58 = require('bs58');
@@ -45,3 +55,6 @@ bitcore.deps.elliptic = require('elliptic');
//bitcore.txpartial = require('lib/txpartial');
//bitcore.bip70 = require('lib/bip70');
+
+// Internal usage, exposed for testing/advanced tweaking
+bitcore._HDKeyCache = require('./lib/hdkeycache');
diff --git a/lib/bip32.js b/lib/bip32.js
deleted file mode 100644
index d27c46161..000000000
--- a/lib/bip32.js
+++ /dev/null
@@ -1,320 +0,0 @@
-'use strict';
-
-var Base58Check = require('./encoding/base58check');
-var networks = require('./networks');
-var Hash = require('./crypto/hash');
-var Point = require('./crypto/point');
-var Random = require('./crypto/random');
-var BN = require('./crypto/bn');
-var PublicKey = require('./publickey');
-var PrivateKey = require('./privatekey');
-
-var BIP32 = function BIP32(obj) {
- if (!(this instanceof BIP32))
- return new BIP32(obj);
- if (typeof obj === 'string') {
- var str = obj;
- this.fromString(str);
- } else if (obj ) {
- this.set(obj);
- }
-};
-
-BIP32.prototype.set = function(obj) {
- this.version = typeof obj.version !== 'undefined' ? obj.version : this.version;
- this.depth = typeof obj.depth !== 'undefined' ? obj.depth : this.depth;
- this.parentfingerprint = obj.parentfingerprint || this.parentfingerprint;
- this.childindex = obj.childindex || this.childindex;
- this.chaincode = obj.chaincode || this.chaincode;
- this.hasprivkey = typeof obj.hasprivkey !== 'undefined' ? obj.hasprivkey : this.hasprivkey;
- this.pubkeyhash = obj.pubkeyhash || this.pubkeyhash;
- this.xpubkey = obj.xpubkey || this.xpubkey;
- this.xprivkey = obj.xprivkey || this.xprivkey;
- return this;
-};
-
-BIP32.prototype.fromRandom = function(networkstr) {
- if (!networkstr)
- networkstr = 'mainnet';
- this.version = networks[networkstr].xprivkey;
- this.depth = 0x00;
- this.parentfingerprint = new Buffer([0, 0, 0, 0]);
- this.childindex = new Buffer([0, 0, 0, 0]);
- this.chaincode = Random.getRandomBuffer(32);
- this.privkey = PrivateKey.fromRandom();
- this.pubkey = PublicKey.fromPrivateKey(this.privkey);
- this.hasprivkey = true;
- this.pubkeyhash = Hash.sha256ripemd160(this.pubkey.toBuffer());
- this.buildxpubkey();
- this.buildxprivkey();
-};
-
-BIP32.prototype.fromString = function(str) {
- var bytes = Base58Check.decode(str);
- this.initFromBytes(bytes);
- return this;
-};
-
-BIP32.prototype.fromSeed = function(bytes, networkstr) {
- if (!networkstr)
- networkstr = 'mainnet';
-
- if (!Buffer.isBuffer(bytes))
- throw new Error('bytes must be a buffer');
- if (bytes.length < 128 / 8)
- throw new Error('Need more than 128 bytes of entropy');
- if (bytes.length > 512 / 8)
- throw new Error('More than 512 bytes of entropy is nonstandard');
- var hash = Hash.sha512hmac(bytes, new Buffer('Bitcoin seed'));
-
- this.depth = 0x00;
- this.parentfingerprint = new Buffer([0, 0, 0, 0]);
- this.childindex = new Buffer([0, 0, 0, 0]);
- this.chaincode = hash.slice(32, 64);
- this.version = networks[networkstr].xprivkey;
- this.privkey = new PrivateKey(BN().fromBuffer(hash.slice(0, 32)));
- this.pubkey = PublicKey.fromPrivateKey(this.privkey);
- this.hasprivkey = true;
- this.pubkeyhash = Hash.sha256ripemd160(this.pubkey.toBuffer());
-
- this.buildxpubkey();
- this.buildxprivkey();
-
- return this;
-};
-
-BIP32.prototype.initFromBytes = function(bytes) {
- // Both pub and private extended keys are 78 bytes
- if (bytes.length != 78)
- throw new Error('not enough data');
-
- this.version = bytes.slice(0, 4).readUInt32BE(0);
- this.depth = bytes.slice(4, 5).readUInt8(0);
- this.parentfingerprint = bytes.slice(5, 9);
- this.childindex = bytes.slice(9, 13).readUInt32BE(0);
- this.chaincode = bytes.slice(13, 45);
-
- var keyBytes = bytes.slice(45, 78);
-
- var isPrivate =
- (this.version == networks.mainnet.xprivkey ||
- this.version == networks.testnet.xprivkey);
-
- var isPublic =
- (this.version == networks.mainnet.xpubkey ||
- this.version == networks.testnet.xpubkey);
-
- if (isPrivate && keyBytes[0] == 0) {
- this.privkey = new PrivateKey(BN().fromBuffer(keyBytes.slice(1, 33)));
- this.pubkey = PublicKey.fromPrivateKey(this.privkey);
- this.pubkeyhash = Hash.sha256ripemd160(this.pubkey.toBuffer());
- this.hasprivkey = true;
- } else if (isPublic && (keyBytes[0] == 0x02 || keyBytes[0] == 0x03)) {
- this.pubkey = PublicKey.fromDER(keyBytes);
- this.pubkeyhash = Hash.sha256ripemd160(this.pubkey.toBuffer());
- this.hasprivkey = false;
- } else {
- throw new Error('Invalid key');
- }
-
- this.buildxpubkey();
- this.buildxprivkey();
-};
-
-BIP32.prototype.buildxpubkey = function() {
- this.xpubkey = new Buffer([]);
-
- var v = null;
- switch (this.version) {
- case networks.mainnet.xpubkey:
- case networks.mainnet.xprivkey:
- v = networks.mainnet.xpubkey;
- break;
- case networks.testnet.xpubkey:
- case networks.testnet.xprivkey:
- v = networks.testnet.xpubkey;
- break;
- default:
- throw new Error('Unknown version');
- }
-
- // Version
- this.xpubkey = Buffer.concat([
- new Buffer([v >> 24]),
- new Buffer([(v >> 16) & 0xff]),
- new Buffer([(v >> 8) & 0xff]),
- new Buffer([v & 0xff]),
- new Buffer([this.depth]),
- this.parentfingerprint,
- new Buffer([this.childindex >>> 24]),
- new Buffer([(this.childindex >>> 16) & 0xff]),
- new Buffer([(this.childindex >>> 8) & 0xff]),
- new Buffer([this.childindex & 0xff]),
- this.chaincode,
- this.pubkey.toBuffer()
- ]);
-};
-
-BIP32.prototype.xpubkeyString = function(format) {
- if (format === undefined || format === 'base58') {
- return Base58Check.encode(this.xpubkey);
- } else if (format === 'hex') {
- return this.xpubkey.toString('hex');
- } else {
- throw new Error('bad format');
- }
-}
-
-BIP32.prototype.buildxprivkey = function() {
- if (!this.hasprivkey) return;
- this.xprivkey = new Buffer([]);
-
- var v = this.version;
-
- this.xprivkey = Buffer.concat([
- new Buffer([v >> 24]),
- new Buffer([(v >> 16) & 0xff]),
- new Buffer([(v >> 8) & 0xff]),
- new Buffer([v & 0xff]),
- new Buffer([this.depth]),
- this.parentfingerprint,
- new Buffer([this.childindex >>> 24]),
- new Buffer([(this.childindex >>> 16) & 0xff]),
- new Buffer([(this.childindex >>> 8) & 0xff]),
- new Buffer([this.childindex & 0xff]),
- this.chaincode,
- new Buffer([0]),
- this.privkey.bn.toBuffer({size: 32})
- ]);
-}
-
-BIP32.prototype.xprivkeyString = function(format) {
- if (format === undefined || format === 'base58') {
- return Base58Check.encode(this.xprivkey);
- } else if (format === 'hex') {
- return this.xprivkey.toString('hex');
- } else {
- throw new Error('bad format');
- }
-}
-
-
-BIP32.prototype.derive = function(path) {
- var e = path.split('/');
-
- // Special cases:
- if (path == 'm' || path == 'M' || path == 'm\'' || path == 'M\'')
- return this;
-
- var bip32 = this;
- for (var i in e) {
- var c = e[i];
-
- if (i == 0) {
- if (c != 'm') throw new Error('invalid path');
- continue;
- }
-
- if (parseInt(c.replace("'", "")).toString() !== c.replace("'", ""))
- throw new Error('invalid path');
-
- var usePrivate = (c.length > 1) && (c[c.length - 1] == '\'');
- var childindex = parseInt(usePrivate ? c.slice(0, c.length - 1) : c) & 0x7fffffff;
-
- if (usePrivate)
- childindex += 0x80000000;
-
- bip32 = bip32.deriveChild(childindex);
- }
-
- return bip32;
-};
-
-BIP32.prototype.deriveChild = function(i) {
- if (typeof i !== 'number')
- throw new Error('i must be a number');
-
- var ib = [];
- ib.push((i >> 24) & 0xff);
- ib.push((i >> 16) & 0xff);
- ib.push((i >> 8) & 0xff);
- ib.push(i & 0xff);
- ib = new Buffer(ib);
-
- var usePrivate = (i & 0x80000000) != 0;
-
- var isPrivate =
- (this.version == networks.mainnet.xprivkey ||
- this.version == networks.testnet.xprivkey);
-
- if (usePrivate && (!this.hasprivkey || !isPrivate))
- throw new Error('Cannot do private key derivation without private key');
-
- var ret = null;
- if (this.hasprivkey) {
- var data = null;
-
- if (usePrivate) {
- data = Buffer.concat([new Buffer([0]), this.privkey.bn.toBuffer({size: 32}), ib]);
- } else {
- data = Buffer.concat([this.pubkey.toBuffer({size: 32}), ib]);
- }
-
- var hash = Hash.sha512hmac(data, this.chaincode);
- var il = BN().fromBuffer(hash.slice(0, 32), {size: 32});
- var ir = hash.slice(32, 64);
-
- // ki = IL + kpar (mod n).
- var k = il.add(this.privkey.bn).mod(Point.getN());
-
- ret = new BIP32();
- ret.chaincode = ir;
-
- ret.privkey = new PrivateKey(k);
- ret.pubkey = PublicKey.fromPrivateKey(ret.privkey);
- ret.hasprivkey = true;
-
- } else {
- var data = Buffer.concat([this.pubkey.toBuffer(), ib]);
- var hash = Hash.sha512hmac(data, this.chaincode);
- var il = BN().fromBuffer(hash.slice(0, 32));
- var ir = hash.slice(32, 64);
-
- // Ki = (IL + kpar)*G = IL*G + Kpar
- var ilG = Point.getG().mul(il);
- var Kpar = this.pubkey.point;
- var Ki = ilG.add(Kpar);
- var newpub = PublicKey.fromPoint(Ki);
-
- ret = new BIP32();
- ret.chaincode = ir;
-
- ret.pubkey = newpub;
- ret.hasprivkey = false;
- }
-
- ret.childindex = i;
- ret.parentfingerprint = this.pubkeyhash.slice(0, 4);
- ret.version = this.version;
- ret.depth = this.depth + 1;
-
- ret.pubkeyhash = Hash.sha256ripemd160(ret.pubkey.toBuffer());
-
- ret.buildxpubkey();
- ret.buildxprivkey();
-
- return ret;
-};
-
-BIP32.prototype.toString = function() {
- var isPrivate =
- (this.version == networks.mainnet.xprivkey ||
- this.version == networks.testnet.xprivkey);
-
- if (isPrivate)
- return this.xprivkeyString();
- else
- return this.xpubkeyString();
-};
-
-module.exports = BIP32;
diff --git a/lib/crypto/point.js b/lib/crypto/point.js
index d457b181e..22ac66a93 100644
--- a/lib/crypto/point.js
+++ b/lib/crypto/point.js
@@ -4,9 +4,10 @@ var BN = require('./bn');
var elliptic = require('elliptic');
var ec = elliptic.curves.secp256k1;
-var ecpoint = ec.curve.point.bind(ec.curve)
+var ecpoint = ec.curve.point.bind(ec.curve);
var p = ec.curve.point();
-var Curve = Object.getPrototypeOf(ec.curve);
+
+var bufferUtil = require('../util/buffer');
var Point = function Point(x, y, isRed) {
return ecpoint(x, y, isRed);
@@ -27,7 +28,6 @@ Point.getN = function() {
Point.prototype._getX = Point.prototype.getX;
Point.prototype.getX = function() {
- var n = BN(this._getX().toArray());
return BN(this._getX().toArray());
};
@@ -38,15 +38,34 @@ Point.prototype.getY = function() {
//https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf
Point.prototype.validate = function() {
+ /* jshint maxcomplexity: 8 */
var p2 = Point.fromX(this.getY().isOdd(), this.getX());
- if (!(p2.y.cmp(this.y) === 0))
+ if (p2.y.cmp(this.y) !== 0) {
throw new Error('Invalid y value of public key');
- if (!(this.getX().gt(-1) && this.getX().lt(Point.getN()))
- ||!(this.getY().gt(-1) && this.getY().lt(Point.getN())))
+ }
+ var xValidRange = (this.getX().gt(-1) && this.getX().lt(Point.getN()));
+ var yValidRange = (this.getY().gt(-1) && this.getY().lt(Point.getN()));
+ if (!(xValidRange && yValidRange)) {
throw new Error('Point does not lie on the curve');
- if (!(this.mul(Point.getN()).isInfinity()))
+ }
+ if (!(this.mul(Point.getN()).isInfinity())) {
throw new Error('Point times N must be infinity');
+ }
return this;
};
+Point.pointToCompressed = function pointToCompressed(point) {
+ var xbuf = point.getX().toBuffer({size: 32});
+ var ybuf = point.getY().toBuffer({size: 32});
+
+ var prefix;
+ var odd = ybuf[ybuf.length - 1] % 2;
+ if (odd) {
+ prefix = new Buffer([0x03]);
+ } else {
+ prefix = new Buffer([0x02]);
+ }
+ return bufferUtil.concat([prefix, xbuf]);
+};
+
module.exports = Point;
diff --git a/lib/encoding/base58.js b/lib/encoding/base58.js
index 6878b73fb..6b869ec55 100644
--- a/lib/encoding/base58.js
+++ b/lib/encoding/base58.js
@@ -1,10 +1,16 @@
'use strict';
+var _ = require('lodash');
var bs58 = require('bs58');
+var buffer = require('buffer');
+
+var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.split('');
var Base58 = function Base58(obj) {
- if (!(this instanceof Base58))
+ /* jshint maxcomplexity: 8 */
+ if (!(this instanceof Base58)) {
return new Base58(obj);
+ }
if (Buffer.isBuffer(obj)) {
var buf = obj;
this.fromBuffer(buf);
@@ -16,20 +22,29 @@ var Base58 = function Base58(obj) {
}
};
+Base58.validCharacters = function validCharacters(chars) {
+ if (buffer.Buffer.isBuffer(chars)) {
+ chars = chars.toString();
+ }
+ return _.all(_.map(chars, function(char) { return _.contains(ALPHABET, char); }));
+};
+
Base58.prototype.set = function(obj) {
this.buf = obj.buf || this.buf || undefined;
return this;
};
Base58.encode = function(buf) {
- if (!Buffer.isBuffer(buf))
+ if (!buffer.Buffer.isBuffer(buf)) {
throw new Error('Input should be a buffer');
+ }
return bs58.encode(buf);
};
Base58.decode = function(str) {
- if (typeof str !== 'string')
+ if (typeof str !== 'string') {
throw new Error('Input should be a string');
+ }
return new Buffer(bs58.decode(str));
};
diff --git a/lib/encoding/base58check.js b/lib/encoding/base58check.js
index 56666d595..e7b7b4342 100644
--- a/lib/encoding/base58check.js
+++ b/lib/encoding/base58check.js
@@ -1,6 +1,8 @@
'use strict';
+var _ = require('lodash');
var base58 = require('./base58');
+var buffer = require('buffer');
var sha256sha256 = require('../crypto/hash').sha256sha256;
var Base58Check = function Base58Check(obj) {
@@ -22,6 +24,20 @@ Base58Check.prototype.set = function(obj) {
return this;
};
+Base58Check.validChecksum = function validChecksum(data, checksum) {
+ if (_.isString(data)) {
+ data = new buffer.Buffer(base58.decode(data));
+ }
+ if (_.isString(checksum)) {
+ checksum = new buffer.Buffer(base58.decode(checksum));
+ }
+ if (!checksum) {
+ checksum = data.slice(-4);
+ data = data.slice(0, -4);
+ }
+ return Base58Check.checksum(data).toString('hex') === checksum.toString('hex');
+};
+
Base58Check.decode = function(s) {
if (typeof s !== 'string')
throw new Error('Input must be a string');
@@ -43,11 +59,15 @@ Base58Check.decode = function(s) {
return data;
};
+Base58Check.checksum = function(buffer) {
+ return sha256sha256(buffer).slice(0, 4);
+};
+
Base58Check.encode = function(buf) {
if (!Buffer.isBuffer(buf))
throw new Error('Input must be a buffer');
var checkedBuf = new Buffer(buf.length + 4);
- var hash = sha256sha256(buf);
+ var hash = Base58Check.checksum(buf);
buf.copy(checkedBuf);
hash.copy(checkedBuf, buf.length);
return base58.encode(checkedBuf);
diff --git a/lib/errors/build.js b/lib/errors/build.js
new file mode 100644
index 000000000..7130c0f1f
--- /dev/null
+++ b/lib/errors/build.js
@@ -0,0 +1,55 @@
+'use strict';
+
+var _ = require('lodash');
+var fs = require('fs');
+
+var formatMessage = function(message) {
+ message = '\'' + message + '\'';
+ for (var i = 0; i < 3; i++) {
+ message += '.replace(\'{' + i + '}\', arguments[' + i + '])';
+ }
+ return message;
+};
+
+var defineElement = function(fullName, baseClass, message) {
+ return fullName + ' = function() {\n' +
+ ' this.message = ' + formatMessage(message) + ';\n' +
+ ' ' + baseClass + '.call(this, this.message);\n' +
+ ' this.name = "' + fullName + '";\n' +
+ '};\n' +
+ 'inherits(' + fullName + ', ' + baseClass + ');\n\n';
+};
+
+var traverseNode = function(baseClass, errorDefinition) {
+ var className = baseClass + '.' + errorDefinition.name;
+ var generated = defineElement(className, baseClass, errorDefinition.message);
+ if (errorDefinition.errors) {
+ generated += childDefinitions(className, errorDefinition.errors);
+ }
+ return generated;
+};
+
+/* jshint latedef: false */
+var childDefinitions = function(parent, childDefinitions) {
+ var generated = '';
+ _.each(childDefinitions, function(childDefinition) {
+ generated += traverseNode(parent, childDefinition);
+ });
+ return generated;
+};
+/* jshint latedef: true */
+
+var traverseRoot = function(errorsDefinition) {
+ var fullName = 'bitcore.Error';
+ var path = 'Error';
+ var generated = '\'use strict\';\n\nvar inherits = require(\'inherits\');\n\n';
+ generated += '/** AUTOGENERATED FILE. DON\'T EDIT, MODIFY "lib/errors/spec.js" INSTEAD */\n\n';
+ generated += 'var bitcore = {};\n\n';
+ generated += defineElement(fullName, path, 'Internal error');
+ generated += childDefinitions(fullName, errorsDefinition);
+ generated += 'module.exports = bitcore.Error;\n';
+ return generated;
+};
+
+var data = require('./spec');
+fs.writeFileSync(__dirname + '/index.js', traverseRoot(data));
diff --git a/lib/errors/spec.js b/lib/errors/spec.js
new file mode 100644
index 000000000..cc3f1b98d
--- /dev/null
+++ b/lib/errors/spec.js
@@ -0,0 +1,85 @@
+module.exports = [{
+ name: 'HDPrivateKey',
+ message: 'Internal Error on HDPrivateKey {0}',
+ errors: [
+ {
+ name: 'InvalidArgument',
+ message: 'HDPrivateKey: Invalid Argument {0}, expected {1} but got {2}',
+ errors: [{
+ name: 'InvalidB58Char',
+ message: 'Invalid Base58 character: {0} in {1}'
+ }, {
+ name: 'InvalidB58Checksum',
+ message: 'Invalid Base58 checksum for {0}'
+ }, {
+ name: 'InvalidDerivationArgument',
+ message: 'Invalid derivation argument {0}, expected string, or number and boolean'
+ }, {
+ name: 'InvalidEntropyArgument',
+ message: 'Invalid entropy: must be an hexa string or binary buffer, got {0}',
+ errors: [{
+ name: 'TooMuchEntropy',
+ message: 'Invalid entropy: more than 512 bits is non standard, got "{0}"'
+ }, {
+ name: 'NotEnoughEntropy',
+ message: 'Invalid entropy: at least 128 bits needed, got "{0}"'
+ }]
+ }, {
+ name: 'InvalidLength',
+ message: 'Invalid length for xprivkey string in {0}'
+ }, {
+ name: 'InvalidNetwork',
+ message: 'Invalid version for network: got {0}'
+ }, {
+ name: 'InvalidNetworkArgument',
+ message: 'Invalid network: must be "livenet" or "testnet", got {0}'
+ }, {
+ name: 'InvalidPath',
+ message: 'Invalid derivation path: {0}'
+ }, {
+ name: 'UnrecognizedArgument',
+ message: 'Invalid argument: creating a HDPrivateKey requires a string, buffer, json or object, got "{0}"'
+ }]
+ }
+ ]
+}, {
+ name: 'HDPublicKey',
+ message: 'Internal Error on HDPublicKey {0}',
+ errors: [
+ {
+ name: 'InvalidArgument',
+ message: 'HDPublicKey: Invalid Argument {0}, expected {1} but got {2}',
+ errors: [{
+ name: 'ArgumentIsPrivateExtended',
+ message: 'Argument is an extended private key: {0}'
+ }, {
+ name: 'InvalidB58Char',
+ message: 'Invalid Base58 character: {0} in {1}'
+ }, {
+ name: 'InvalidB58Checksum',
+ message: 'Invalid Base58 checksum for {0}'
+ }, {
+ name: 'InvalidDerivationArgument',
+ message: 'Invalid derivation argument: got {0}'
+ }, {
+ name: 'InvalidLength',
+ message: 'Invalid length for xpubkey: got "{0}"'
+ }, {
+ name: 'InvalidNetwork',
+ message: 'Invalid network, expected a different version: got "{0}"'
+ }, {
+ name: 'InvalidNetworkArgument',
+ message: 'Expected network to be "livenet" or "testnet", got "{0}"'
+ }, {
+ name: 'InvalidPath',
+ message: 'Invalid derivation path, it should look like: "m/1/100", got "{0}"'
+ }, {
+ name: 'MustSupplyArgument',
+ message: 'Must supply an argument to create a HDPublicKey'
+ }, {
+ name: 'UnrecognizedArgument',
+ message: 'Invalid argument for creation, must be string, json, buffer, or object'
+ }]
+ }
+ ]
+}];
diff --git a/lib/hdkeycache.js b/lib/hdkeycache.js
new file mode 100644
index 000000000..721d9da48
--- /dev/null
+++ b/lib/hdkeycache.js
@@ -0,0 +1,45 @@
+'use strict';
+
+module.exports = {
+ _cache: {},
+ _count: 0,
+ _eraseIndex: 0,
+ _usedList: {},
+ _usedIndex: {},
+ _CACHE_SIZE: 5000,
+
+ get: function(xkey, number, hardened) {
+ hardened = !!hardened;
+ var key = xkey + '/' + number + '/' + hardened;
+ if (this._cache[key]) {
+ this._cacheHit(key);
+ return this._cache[key];
+ }
+ },
+ set: function(xkey, number, hardened, derived) {
+ hardened = !!hardened;
+ var key = xkey + '/' + number + '/' + hardened;
+ this._cache[key] = derived;
+ this._cacheHit(key);
+ },
+ _cacheHit: function(key) {
+ if (this._usedIndex[key]) {
+ delete this._usedList[this._usedIndex[key]];
+ }
+ this._usedList[this._count] = key;
+ this._usedIndex[key] = this._count;
+ this._count++;
+ this._cacheRemove();
+ },
+ _cacheRemove: function() {
+ while (this._eraseIndex < this._count - this._CACHE_SIZE) {
+ if (this._usedList[this._eraseIndex]) {
+ var removeKey = this._usedList[this._eraseIndex];
+ delete this._usedIndex[removeKey];
+ delete this._cache[removeKey];
+ }
+ delete this._usedList[this._eraseIndex];
+ this._eraseIndex++;
+ }
+ }
+};
diff --git a/lib/hdprivatekey.js b/lib/hdprivatekey.js
new file mode 100644
index 000000000..2e357281e
--- /dev/null
+++ b/lib/hdprivatekey.js
@@ -0,0 +1,451 @@
+'use strict';
+
+
+var assert = require('assert');
+var buffer = require('buffer');
+var _ = require('lodash');
+
+var BN = require('./crypto/bn');
+var Base58 = require('./encoding/base58');
+var Base58Check = require('./encoding/base58check');
+var Hash = require('./crypto/hash');
+var Network = require('./networks');
+var HDKeyCache = require('./hdkeycache');
+var Point = require('./crypto/point');
+var PrivateKey = require('./privatekey');
+var Random = require('./crypto/random');
+
+var bitcoreErrors = require('./errors');
+var errors = bitcoreErrors.HDPrivateKey.InvalidArgument;
+var bufferUtil = require('./util/buffer');
+var jsUtil = require('./util/js');
+
+var MINIMUM_ENTROPY_BITS = 128;
+var BITS_TO_BYTES = 1/8;
+var MAXIMUM_ENTROPY_BITS = 512;
+
+
+/**
+ * Represents an instance of an hierarchically derived private key.
+ *
+ * More info on https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
+ *
+ * @constructor
+ * @param {string|Buffer|Object} arg
+ */
+function HDPrivateKey(arg) {
+ /* jshint maxcomplexity: 10 */
+ if (arg instanceof HDPrivateKey) {
+ return arg;
+ }
+ if (!(this instanceof HDPrivateKey)) {
+ return new HDPrivateKey(arg);
+ }
+ if (arg) {
+ if (_.isString(arg) || bufferUtil.isBuffer(arg)) {
+ if (HDPrivateKey.isValidSerialized(arg)) {
+ this._buildFromSerialized(arg);
+ } else if (jsUtil.isValidJson(arg)) {
+ this._buildFromJson(arg);
+ } else {
+ throw HDPrivateKey.getSerializedError(arg);
+ }
+ } else {
+ if (_.isObject(arg)) {
+ this._buildFromObject(arg);
+ } else {
+ throw new errors.UnrecognizedArgument(arg);
+ }
+ }
+ } else {
+ return this._generateRandomly();
+ }
+}
+
+/**
+ * Get a derivated child based on a string or number.
+ *
+ * If the first argument is a string, it's parsed as the full path of
+ * derivation. Valid values for this argument include "m" (which returns the
+ * same private key), "m/0/1/40/2'/1000", where the ' quote means a hardened
+ * derivation.
+ *
+ * If the first argument is a number, the child with that index will be
+ * derived. If the second argument is truthy, the hardened version will be
+ * derived. See the example usage for clarification.
+ *
+ * @example
+ * var parent = new HDPrivateKey('xprv...');
+ * var child_0_1_2h = parent.derive(0).derive(1).derive(2, true);
+ * var copy_of_child_0_1_2h = parent.derive("m/0/1/2'");
+ * assert(child_0_1_2h.xprivkey === copy_of_child_0_1_2h);
+ *
+ * @param {string|number} arg
+ * @param {boolean?} hardened
+ */
+HDPrivateKey.prototype.derive = function(arg, hardened) {
+ if (_.isNumber(arg)) {
+ return this._deriveWithNumber(arg, hardened);
+ } else if (_.isString(arg)) {
+ return this._deriveFromString(arg);
+ } else {
+ throw new errors.InvalidDerivationArgument(arg);
+ }
+};
+
+HDPrivateKey.prototype._deriveWithNumber = function(index, hardened) {
+ /* jshint maxstatements: 20 */
+ /* jshint maxcomplexity: 10 */
+ if (index >= HDPrivateKey.Hardened) {
+ hardened = true;
+ }
+ if (index < HDPrivateKey.Hardened && hardened) {
+ index += HDPrivateKey.Hardened;
+ }
+ var cached = HDKeyCache.get(this.xprivkey, index, hardened);
+ if (cached) {
+ return cached;
+ }
+
+ var indexBuffer = bufferUtil.integerAsBuffer(index);
+ var data;
+ if (hardened) {
+ data = bufferUtil.concat([new buffer.Buffer([0]), this.privateKey.toBuffer(), indexBuffer]);
+ } else {
+ data = bufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]);
+ }
+ var hash = Hash.sha512hmac(data, this._buffers.chainCode);
+ var leftPart = BN().fromBuffer(hash.slice(0, 32), {size: 32});
+ var chainCode = hash.slice(32, 64);
+
+ var privateKey = leftPart.add(this.privateKey.toBigNumber()).mod(Point.getN()).toBuffer({size: 32});
+
+ var derived = new HDPrivateKey({
+ network: this.network,
+ depth: this.depth + 1,
+ parentFingerPrint: this.fingerPrint,
+ childIndex: index,
+ chainCode: chainCode,
+ privateKey: privateKey
+ });
+ HDKeyCache.set(this.xprivkey, index, hardened, derived);
+ return derived;
+};
+
+HDPrivateKey.prototype._deriveFromString = function(path) {
+ var steps = path.split('/');
+
+ // Special cases:
+ if (_.contains(HDPrivateKey.RootElementAlias, path)) {
+ return this;
+ }
+ if (!_.contains(HDPrivateKey.RootElementAlias, steps[0])) {
+ throw new errors.InvalidPath(path);
+ }
+ steps = steps.slice(1);
+
+ var result = this;
+ for (var step in steps) {
+ var index = parseInt(steps[step]);
+ var hardened = steps[step] !== index.toString();
+ result = result._deriveWithNumber(index, hardened);
+ }
+ return result;
+};
+
+/**
+ * Verifies that a given serialized private key in base58 with checksum format
+ * is valid.
+ *
+ * @param {string|Buffer} data - the serialized private key
+ * @param {string|Network=} network - optional, if present, checks that the
+ * network provided matches the network serialized.
+ * @return {boolean}
+ */
+HDPrivateKey.isValidSerialized = function(data, network) {
+ return !HDPrivateKey.getSerializedError(data, network);
+};
+
+/**
+ * Checks what's the error that causes the validation of a serialized private key
+ * in base58 with checksum to fail.
+ *
+ * @param {string|Buffer} data - the serialized private key
+ * @param {string|Network=} network - optional, if present, checks that the
+ * network provided matches the network serialized.
+ * @return {errors.InvalidArgument|null}
+ */
+HDPrivateKey.getSerializedError = function(data, network) {
+ /* jshint maxcomplexity: 10 */
+ if (!(_.isString(data) || bufferUtil.isBuffer(data))) {
+ return new errors.UnrecognizedArgument('Expected string or buffer');
+ }
+ if (!Base58.validCharacters(data)) {
+ return new errors.InvalidB58Char('(unknown)', data);
+ }
+ try {
+ data = Base58Check.decode(data);
+ } catch (e) {
+ return new errors.InvalidB58Checksum(data);
+ }
+ if (data.length !== HDPrivateKey.DataLength) {
+ return new errors.InvalidLength(data);
+ }
+ if (!_.isUndefined(network)) {
+ var error = HDPrivateKey._validateNetwork(data, network);
+ if (error) {
+ return error;
+ }
+ }
+ return null;
+};
+
+HDPrivateKey._validateNetwork = function(data, networkArg) {
+ var network = Network.get(networkArg);
+ if (!network) {
+ return new errors.InvalidNetworkArgument(networkArg);
+ }
+ var version = data.slice(0, 4);
+ if (bufferUtil.integerFromBuffer(version) !== network.xprivkey) {
+ return new errors.InvalidNetwork(version);
+ }
+ return null;
+};
+
+HDPrivateKey.prototype._buildFromJson = function(arg) {
+ return this._buildFromObject(JSON.parse(arg));
+};
+
+HDPrivateKey.prototype._buildFromObject = function(arg) {
+ /* jshint maxcomplexity: 12 */
+ // TODO: Type validation
+ var buffers = {
+ version: arg.network ? bufferUtil.integerAsBuffer(Network.get(arg.network).xprivkey) : arg.version,
+ depth: bufferUtil.integerAsSingleByteBuffer(arg.depth),
+ parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? bufferUtil.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
+ childIndex: _.isNumber(arg.childIndex) ? bufferUtil.integerAsBuffer(arg.childIndex) : arg.childIndex,
+ chainCode: _.isString(arg.chainCode) ? bufferUtil.hexToBuffer(arg.chainCode) : arg.chainCode,
+ privateKey: (_.isString(arg.privateKey) && jsUtil.isHexa(arg.privateKey)) ? bufferUtil.hexToBuffer(arg.privateKey) : arg.privateKey,
+ checksum: arg.checksum ? (arg.checksum.length ? arg.checksum : bufferUtil.integerAsBuffer(arg.checksum)) : undefined
+ };
+ return this._buildFromBuffers(buffers);
+};
+
+HDPrivateKey.prototype._buildFromSerialized = function(arg) {
+ var decoded = Base58Check.decode(arg);
+ var buffers = {
+ version: decoded.slice(HDPrivateKey.VersionStart, HDPrivateKey.VersionEnd),
+ depth: decoded.slice(HDPrivateKey.DepthStart, HDPrivateKey.DepthEnd),
+ parentFingerPrint: decoded.slice(HDPrivateKey.ParentFingerPrintStart,
+ HDPrivateKey.ParentFingerPrintEnd),
+ childIndex: decoded.slice(HDPrivateKey.ChildIndexStart, HDPrivateKey.ChildIndexEnd),
+ chainCode: decoded.slice(HDPrivateKey.ChainCodeStart, HDPrivateKey.ChainCodeEnd),
+ privateKey: decoded.slice(HDPrivateKey.PrivateKeyStart, HDPrivateKey.PrivateKeyEnd),
+ checksum: decoded.slice(HDPrivateKey.ChecksumStart, HDPrivateKey.ChecksumEnd),
+ xprivkey: arg
+ };
+ return this._buildFromBuffers(buffers);
+};
+
+HDPrivateKey.prototype._generateRandomly = function(network) {
+ return HDPrivateKey.fromSeed(Random.getRandomBuffer(64), network);
+};
+
+/**
+ * Generate a private key from a seed, as described in BIP32
+ *
+ * @param {string|Buffer} hexa
+ * @param {*} network
+ * @return HDPrivateKey
+ */
+HDPrivateKey.fromSeed = function(hexa, network) {
+ /* jshint maxcomplexity: 8 */
+
+ if (jsUtil.isHexaString(hexa)) {
+ hexa = bufferUtil.hexToBuffer(hexa);
+ }
+ if (!Buffer.isBuffer(hexa)) {
+ throw new errors.InvalidEntropyArgument(hexa);
+ }
+ if (hexa.length < MINIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
+ throw new errors.InvalidEntropyArgument.NotEnoughEntropy(hexa);
+ }
+ if (hexa.length > MAXIMUM_ENTROPY_BITS * BITS_TO_BYTES) {
+ throw new errors.InvalidEntropyArgument.TooMuchEntropy(hexa);
+ }
+ var hash = Hash.sha512hmac(hexa, new buffer.Buffer('Bitcoin seed'));
+
+ return new HDPrivateKey({
+ network: Network.get(network) || Network.livenet,
+ depth: 0,
+ parentFingerPrint: 0,
+ childIndex: 0,
+ privateKey: hash.slice(0, 32),
+ chainCode: hash.slice(32, 64)
+ });
+};
+
+/**
+ * Receives a object with buffers in all the properties and populates the
+ * internal structure
+ *
+ * @param {Object} arg
+ * @param {buffer.Buffer} arg.version
+ * @param {buffer.Buffer} arg.depth
+ * @param {buffer.Buffer} arg.parentFingerPrint
+ * @param {buffer.Buffer} arg.childIndex
+ * @param {buffer.Buffer} arg.chainCode
+ * @param {buffer.Buffer} arg.privateKey
+ * @param {buffer.Buffer} arg.checksum
+ * @param {string=} arg.xprivkey - if set, don't recalculate the base58
+ * representation
+ * @return {HDPrivateKey} this
+ */
+HDPrivateKey.prototype._buildFromBuffers = function(arg) {
+ /* jshint maxcomplexity: 8 */
+ /* jshint maxstatements: 20 */
+
+ HDPrivateKey._validateBufferArguments(arg);
+ this._buffers = arg;
+
+ var sequence = [
+ arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode,
+ bufferUtil.emptyBuffer(1), arg.privateKey
+ ];
+ var concat = buffer.Buffer.concat(sequence);
+ if (!arg.checksum || !arg.checksum.length) {
+ arg.checksum = Base58Check.checksum(concat);
+ } else {
+ if (arg.checksum.toString() !== Base58Check.checksum(concat).toString()) {
+ throw new errors.InvalidB58Checksum(concat);
+ }
+ }
+
+ if (!arg.xprivkey) {
+ this.xprivkey = Base58Check.encode(buffer.Buffer.concat(sequence));
+ } else {
+ this.xprivkey = arg.xprivkey;
+ }
+ this.network = Network.get(bufferUtil.integerFromBuffer(arg.version));
+ this.depth = bufferUtil.integerFromSingleByteBuffer(arg.depth);
+ this.privateKey = new PrivateKey(BN().fromBuffer(arg.privateKey));
+ this.publicKey = this.privateKey.toPublicKey();
+
+ this.fingerPrint = Hash.sha256ripemd160(this.publicKey.toBuffer()).slice(0, HDPrivateKey.ParentFingerPrintSize);
+
+ var HDPublicKey = require('./hdpublickey');
+ this.hdPublicKey = new HDPublicKey(this);
+ this.xpubkey = this.hdPublicKey.xpubkey;
+
+ return this;
+};
+
+HDPrivateKey._validateBufferArguments = function(arg) {
+ var checkBuffer = function(name, size) {
+ var buff = arg[name];
+ assert(bufferUtil.isBuffer(buff), name + ' argument is not a buffer');
+ assert(
+ buff.length === size,
+ name + ' has not the expected size: found ' + buff.length + ', expected ' + size
+ );
+ };
+ checkBuffer('version', HDPrivateKey.VersionSize);
+ checkBuffer('depth', HDPrivateKey.DepthSize);
+ checkBuffer('parentFingerPrint', HDPrivateKey.ParentFingerPrintSize);
+ checkBuffer('childIndex', HDPrivateKey.ChildIndexSize);
+ checkBuffer('chainCode', HDPrivateKey.ChainCodeSize);
+ checkBuffer('privateKey', HDPrivateKey.PrivateKeySize);
+ if (arg.checksum && arg.checksum.length) {
+ checkBuffer('checksum', HDPrivateKey.CheckSumSize);
+ }
+};
+
+/**
+ * Returns the string representation of this private key (a string starting
+ * with "xprv..."
+ *
+ * @return string
+ */
+HDPrivateKey.prototype.toString = function() {
+ return this.xprivkey;
+};
+
+/**
+ * Returns a plain object with a representation of this private key.
+ *
+ * Fields include:
+ * * network: either 'livenet' or 'testnet'
+ * * depth: a number ranging from 0 to 255
+ * * fingerPrint: a number ranging from 0 to 2^32-1, taken from the hash of the
+ * associated public key
+ * * parentFingerPrint: a number ranging from 0 to 2^32-1, taken from the hash
+ * of this parent's associated public key or zero.
+ * * childIndex: the index from which this child was derived (or zero)
+ * * chainCode: an hexa string representing a number used in the derivation
+ * * privateKey: the private key associated, in hexa representation
+ * * xprivkey: the representation of this extended private key in checksum
+ * base58 format
+ * * checksum: the base58 checksum of xprivkey
+ *
+ * @return {Object}
+ */
+HDPrivateKey.prototype.toObject = function() {
+ return {
+ network: Network.get(bufferUtil.integerFromBuffer(this._buffers.version)).name,
+ depth: bufferUtil.integerFromSingleByteBuffer(this._buffers.depth),
+ fingerPrint: bufferUtil.integerFromBuffer(this.fingerPrint),
+ parentFingerPrint: bufferUtil.integerFromBuffer(this._buffers.parentFingerPrint),
+ childIndex: bufferUtil.integerFromBuffer(this._buffers.childIndex),
+ chainCode: bufferUtil.bufferToHex(this._buffers.chainCode),
+ privateKey: this.privateKey.toBuffer().toString('hex'),
+ checksum: bufferUtil.integerFromBuffer(this._buffers.checksum),
+ xprivkey: this.xprivkey
+ };
+};
+
+/**
+ * Returns a string with the results from toObject
+ *
+ * @see {HDPrivateKey#toObject}
+ * @return {string}
+ */
+HDPrivateKey.prototype.toJson = function() {
+ return JSON.stringify(this.toObject());
+};
+
+HDPrivateKey.DefaultDepth = 0;
+HDPrivateKey.DefaultFingerprint = 0;
+HDPrivateKey.DefaultChildIndex = 0;
+HDPrivateKey.DefaultNetwork = Network.livenet;
+HDPrivateKey.Hardened = 0x80000000;
+HDPrivateKey.RootElementAlias = ['m', 'M', 'm\'', 'M\''];
+
+HDPrivateKey.VersionSize = 4;
+HDPrivateKey.DepthSize = 1;
+HDPrivateKey.ParentFingerPrintSize = 4;
+HDPrivateKey.ChildIndexSize = 4;
+HDPrivateKey.ChainCodeSize = 32;
+HDPrivateKey.PrivateKeySize = 32;
+HDPrivateKey.CheckSumSize = 4;
+
+HDPrivateKey.DataLength = 78;
+HDPrivateKey.SerializedByteSize = 82;
+
+HDPrivateKey.VersionStart = 0;
+HDPrivateKey.VersionEnd = HDPrivateKey.VersionStart + HDPrivateKey.VersionSize;
+HDPrivateKey.DepthStart = HDPrivateKey.VersionEnd;
+HDPrivateKey.DepthEnd = HDPrivateKey.DepthStart + HDPrivateKey.DepthSize;
+HDPrivateKey.ParentFingerPrintStart = HDPrivateKey.DepthEnd;
+HDPrivateKey.ParentFingerPrintEnd = HDPrivateKey.ParentFingerPrintStart + HDPrivateKey.ParentFingerPrintSize;
+HDPrivateKey.ChildIndexStart = HDPrivateKey.ParentFingerPrintEnd;
+HDPrivateKey.ChildIndexEnd = HDPrivateKey.ChildIndexStart + HDPrivateKey.ChildIndexSize;
+HDPrivateKey.ChainCodeStart = HDPrivateKey.ChildIndexEnd;
+HDPrivateKey.ChainCodeEnd = HDPrivateKey.ChainCodeStart + HDPrivateKey.ChainCodeSize;
+HDPrivateKey.PrivateKeyStart = HDPrivateKey.ChainCodeEnd + 1;
+HDPrivateKey.PrivateKeyEnd = HDPrivateKey.PrivateKeyStart + HDPrivateKey.PrivateKeySize;
+HDPrivateKey.ChecksumStart = HDPrivateKey.PrivateKeyEnd;
+HDPrivateKey.ChecksumEnd = HDPrivateKey.ChecksumStart + HDPrivateKey.CheckSumSize;
+
+assert(HDPrivateKey.ChecksumEnd === HDPrivateKey.SerializedByteSize);
+
+module.exports = HDPrivateKey;
diff --git a/lib/hdpublickey.js b/lib/hdpublickey.js
new file mode 100644
index 000000000..1b361d5ea
--- /dev/null
+++ b/lib/hdpublickey.js
@@ -0,0 +1,411 @@
+'use strict';
+
+var _ = require('lodash');
+var BN = require('./crypto/bn');
+var Base58 = require('./encoding/base58');
+var Base58Check = require('./encoding/base58check');
+var Hash = require('./crypto/hash');
+var HDPrivateKey = require('./hdprivatekey');
+var HDKeyCache = require('./hdkeycache');
+var Network = require('./networks');
+var Point = require('./crypto/point');
+var PublicKey = require('./publickey');
+
+var bitcoreErrors = require('./errors');
+var errors = bitcoreErrors.HDPublicKey.InvalidArgument;
+var assert = require('assert');
+
+var jsUtil = require('./util/js');
+var bufferUtil = require('./util/buffer');
+
+/**
+ * The representation of an hierarchically derived public key.
+ *
+ * See https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
+ *
+ * @constructor
+ * @param {Object|string|Buffer} arg
+ */
+function HDPublicKey(arg) {
+ /* jshint maxcomplexity: 12 */
+ /* jshint maxstatements: 20 */
+ if (arg instanceof HDPublicKey) {
+ return arg;
+ }
+ if (!(this instanceof HDPublicKey)) {
+ return new HDPublicKey(arg);
+ }
+ if (arg) {
+ if (_.isString(arg) || bufferUtil.isBuffer(arg)) {
+ var error = HDPublicKey.getSerializedError(arg);
+ if (!error) {
+ return this._buildFromSerialized(arg);
+ } else if (jsUtil.isValidJson(arg)) {
+ return this._buildFromJson(arg);
+ } else {
+ if (error instanceof errors.ArgumentIsPrivateExtended) {
+ return new HDPrivateKey(arg).hdPublicKey;
+ }
+ throw error;
+ }
+ } else {
+ if (_.isObject(arg)) {
+ if (arg instanceof HDPrivateKey) {
+ return this._buildFromPrivate(arg);
+ } else {
+ return this._buildFromObject(arg);
+ }
+ } else {
+ throw new errors.UnrecognizedArgument(arg);
+ }
+ }
+ } else {
+ throw new errors.MustSupplyArgument();
+ }
+}
+
+/**
+ * Get a derivated child based on a string or number.
+ *
+ * If the first argument is a string, it's parsed as the full path of
+ * derivation. Valid values for this argument include "m" (which returns the
+ * same private key), "m/0/1/40/2/1000".
+ *
+ * Note that hardened keys can't be derived from a public extended key.
+ *
+ * If the first argument is a number, the child with that index will be
+ * derived. See the example usage for clarification.
+ *
+ * @example
+ * var parent = new HDPublicKey('xpub...');
+ * var child_0_1_2 = parent.derive(0).derive(1).derive(2);
+ * var copy_of_child_0_1_2 = parent.derive("m/0/1/2");
+ * assert(child_0_1_2.xprivkey === copy_of_child_0_1_2);
+ *
+ * @param {string|number} arg
+ * @param {boolean?} hardened
+ */
+HDPublicKey.prototype.derive = function (arg, hardened) {
+ if (_.isNumber(arg)) {
+ return this._deriveWithNumber(arg, hardened);
+ } else if (_.isString(arg)) {
+ return this._deriveFromString(arg);
+ } else {
+ throw new errors.InvalidDerivationArgument(arg);
+ }
+};
+
+HDPublicKey.prototype._deriveWithNumber = function (index, hardened) {
+ if (hardened || index >= HDPublicKey.Hardened) {
+ throw new errors.InvalidIndexCantDeriveHardened();
+ }
+ var cached = HDKeyCache.get(this.xpubkey, index, hardened);
+ if (cached) {
+ return cached;
+ }
+
+ var indexBuffer = bufferUtil.integerAsBuffer(index);
+ var data = bufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]);
+ var hash = Hash.sha512hmac(data, this._buffers.chainCode);
+ var leftPart = BN().fromBuffer(hash.slice(0, 32), {size: 32});
+ var chainCode = hash.slice(32, 64);
+
+ var publicKey = PublicKey.fromPoint(Point.getG().mul(leftPart).add(this.publicKey.point));
+
+ var derived = new HDPublicKey({
+ network: this.network,
+ depth: this.depth + 1,
+ parentFingerPrint: this.fingerPrint,
+ childIndex: index,
+ chainCode: chainCode,
+ publicKey: publicKey
+ });
+ HDKeyCache.set(this.xpubkey, index, hardened, derived);
+ return derived;
+};
+
+HDPublicKey.prototype._deriveFromString = function (path) {
+ /* jshint maxcomplexity: 8 */
+ var steps = path.split('/');
+
+ // Special cases:
+ if (_.contains(HDPublicKey.RootElementAlias, path)) {
+ return this;
+ }
+ if (!_.contains(HDPublicKey.RootElementAlias, steps[0])) {
+ throw new errors.InvalidPath(path);
+ }
+ steps = steps.slice(1);
+
+ var result = this;
+ for (var step in steps) {
+ var index = parseInt(steps[step]);
+ var hardened = steps[step] !== index.toString();
+ result = result._deriveWithNumber(index, hardened);
+ }
+ return result;
+};
+
+/**
+ * Verifies that a given serialized private key in base58 with checksum format
+ * is valid.
+ *
+ * @param {string|Buffer} data - the serialized private key
+ * @param {string|Network=} network - optional, if present, checks that the
+ * network provided matches the network serialized.
+ * @return {boolean}
+ */
+HDPublicKey.isValidSerialized = function (data, network) {
+ return _.isNull(HDPublicKey.getSerializedError(data, network));
+};
+
+/**
+ * Checks what's the error that causes the validation of a serialized private key
+ * in base58 with checksum to fail.
+ *
+ * @param {string|Buffer} data - the serialized private key
+ * @param {string|Network=} network - optional, if present, checks that the
+ * network provided matches the network serialized.
+ * @return {errors|null}
+ */
+HDPublicKey.getSerializedError = function (data, network) {
+ /* jshint maxcomplexity: 10 */
+ /* jshint maxstatements: 20 */
+ if (!(_.isString(data) || bufferUtil.isBuffer(data))) {
+ return new errors.UnrecognizedArgument('expected buffer or string');
+ }
+ if (!Base58.validCharacters(data)) {
+ return new errors.InvalidB58Char('(unknown)', data);
+ }
+ try {
+ data = Base58Check.decode(data);
+ } catch (e) {
+ return new errors.InvalidB58Checksum(data);
+ }
+ if (data.length !== HDPublicKey.DataSize) {
+ return new errors.InvalidLength(data);
+ }
+ if (!_.isUndefined(network)) {
+ var error = HDPublicKey._validateNetwork(data, network);
+ if (error) {
+ return error;
+ }
+ }
+ network = Network.get(network) || Network.defaultNetwork;
+ if (bufferUtil.integerFromBuffer(data.slice(0, 4)) === network.xprivkey) {
+ return new errors.ArgumentIsPrivateExtended();
+ }
+ return null;
+};
+
+HDPublicKey._validateNetwork = function (data, networkArg) {
+ var network = Network.get(networkArg);
+ if (!network) {
+ return new errors.InvalidNetworkArgument(networkArg);
+ }
+ var version = data.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd);
+ if (bufferUtil.integerFromBuffer(version) !== network.xpubkey) {
+ return new errors.InvalidNetwork(version);
+ }
+ return null;
+};
+
+HDPublicKey.prototype._buildFromJson = function (arg) {
+ return this._buildFromObject(JSON.parse(arg));
+};
+
+HDPublicKey.prototype._buildFromPrivate = function (arg) {
+ var args = _.clone(arg._buffers);
+ var point = Point.getG().mul(BN().fromBuffer(args.privateKey));
+ args.publicKey = Point.pointToCompressed(point);
+ args.version = bufferUtil.integerAsBuffer(Network.get(bufferUtil.integerFromBuffer(args.version)).xpubkey);
+ args.privateKey = undefined;
+ args.checksum = undefined;
+ args.xprivkey = undefined;
+ return this._buildFromBuffers(args);
+};
+
+HDPublicKey.prototype._buildFromObject = function (arg) {
+ /* jshint maxcomplexity: 10 */
+ // TODO: Type validation
+ var buffers = {
+ version: arg.network ? bufferUtil.integerAsBuffer(Network.get(arg.network).xpubkey) : arg.version,
+ depth: bufferUtil.integerAsSingleByteBuffer(arg.depth),
+ parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? bufferUtil.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
+ childIndex: bufferUtil.integerAsBuffer(arg.childIndex),
+ chainCode: _.isString(arg.chainCode) ? bufferUtil.hexToBuffer(arg.chainCode) : arg.chainCode,
+ publicKey: _.isString(arg.publicKey) ? bufferUtil.hexToBuffer(arg.publicKey) :
+ bufferUtil.isBuffer(arg.publicKey) ? arg.publicKey : arg.publicKey.toBuffer(),
+ checksum: _.isNumber(arg.checksum) ? bufferUtil.integerAsBuffer(arg.checksum) : arg.checksum
+ };
+ return this._buildFromBuffers(buffers);
+};
+
+HDPublicKey.prototype._buildFromSerialized = function (arg) {
+ var decoded = Base58Check.decode(arg);
+ var buffers = {
+ version: decoded.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd),
+ depth: decoded.slice(HDPublicKey.DepthStart, HDPublicKey.DepthEnd),
+ parentFingerPrint: decoded.slice(HDPublicKey.ParentFingerPrintStart,
+ HDPublicKey.ParentFingerPrintEnd),
+ childIndex: decoded.slice(HDPublicKey.ChildIndexStart, HDPublicKey.ChildIndexEnd),
+ chainCode: decoded.slice(HDPublicKey.ChainCodeStart, HDPublicKey.ChainCodeEnd),
+ publicKey: decoded.slice(HDPublicKey.PublicKeyStart, HDPublicKey.PublicKeyEnd),
+ checksum: decoded.slice(HDPublicKey.ChecksumStart, HDPublicKey.ChecksumEnd),
+ xpubkey: arg
+ };
+ return this._buildFromBuffers(buffers);
+};
+
+/**
+ * Receives a object with buffers in all the properties and populates the
+ * internal structure
+ *
+ * @param {Object} arg
+ * @param {buffer.Buffer} arg.version
+ * @param {buffer.Buffer} arg.depth
+ * @param {buffer.Buffer} arg.parentFingerPrint
+ * @param {buffer.Buffer} arg.childIndex
+ * @param {buffer.Buffer} arg.chainCode
+ * @param {buffer.Buffer} arg.publicKey
+ * @param {buffer.Buffer} arg.checksum
+ * @param {string=} arg.xpubkey - if set, don't recalculate the base58
+ * representation
+ * @return {HDPublicKey} this
+ */
+HDPublicKey.prototype._buildFromBuffers = function (arg) {
+ /* jshint maxcomplexity: 8 */
+ /* jshint maxstatements: 20 */
+
+ HDPublicKey._validateBufferArguments(arg);
+ this._buffers = arg;
+
+ var sequence = [
+ arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode,
+ arg.publicKey
+ ];
+ var concat = bufferUtil.concat(sequence);
+ var checksum = Base58Check.checksum(concat);
+ if (!arg.checksum || !arg.checksum.length) {
+ arg.checksum = checksum;
+ } else {
+ if (arg.checksum.toString('hex') !== checksum.toString('hex')) {
+ throw new errors.InvalidB58Checksum(concat, checksum);
+ }
+ }
+
+ if (!arg.xpubkey) {
+ this.xpubkey = Base58Check.encode(bufferUtil.concat(sequence));
+ } else {
+ this.xpubkey = arg.xpubkey;
+ }
+
+ this.network = Network.get(bufferUtil.integerFromBuffer(arg.version));
+ this.depth = bufferUtil.integerFromSingleByteBuffer(arg.depth);
+ this.publicKey = PublicKey.fromString(arg.publicKey);
+ this.fingerPrint = Hash.sha256ripemd160(this.publicKey.toBuffer()).slice(0, HDPublicKey.ParentFingerPrintSize);
+
+ return this;
+};
+
+HDPublicKey._validateBufferArguments = function (arg) {
+ var checkBuffer = function(name, size) {
+ var buff = arg[name];
+ assert(bufferUtil.isBuffer(buff), name + ' argument is not a buffer, it\'s ' + typeof buff);
+ assert(
+ buff.length === size,
+ name + ' has not the expected size: found ' + buff.length + ', expected ' + size
+ );
+ };
+ checkBuffer('version', HDPublicKey.VersionSize);
+ checkBuffer('depth', HDPublicKey.DepthSize);
+ checkBuffer('parentFingerPrint', HDPublicKey.ParentFingerPrintSize);
+ checkBuffer('childIndex', HDPublicKey.ChildIndexSize);
+ checkBuffer('chainCode', HDPublicKey.ChainCodeSize);
+ checkBuffer('publicKey', HDPublicKey.PublicKeySize);
+ if (arg.checksum && arg.checksum.length) {
+ checkBuffer('checksum', HDPublicKey.CheckSumSize);
+ }
+};
+
+/**
+ * Returns the base58 checked representation of the public key
+ * @return {string} a string starting with "xpub..." in livenet
+ */
+HDPublicKey.prototype.toString = function () {
+ return this.xpubkey;
+};
+
+/**
+ * Returns a plain javascript object with information to reconstruct a key.
+ *
+ * Fields are:
+ * * network: 'livenet' or 'testnet'
+ * * depth: a number from 0 to 255, the depth to the master extended key
+ * * fingerPrint: a number of 32 bits taken from the hash of the public key
+ * * fingerPrint: a number of 32 bits taken from the hash of this key's
+ * parent's public key
+ * * childIndex: index with which this key was derived
+ * * chainCode: string in hexa encoding used for derivation
+ * * publicKey: string, hexa encoded, in compressed key format
+ * * checksum: bufferUtil.integerFromBuffer(this._buffers.checksum),
+ * * xpubkey: the string with the base58 representation of this extended key
+ * * checksum: the base58 checksum of xpubkey
+ */
+HDPublicKey.prototype.toObject = function () {
+ return {
+ network: Network.get(bufferUtil.integerFromBuffer(this._buffers.version)).name,
+ depth: bufferUtil.integerFromSingleByteBuffer(this._buffers.depth),
+ fingerPrint: bufferUtil.integerFromBuffer(this.fingerPrint),
+ parentFingerPrint: bufferUtil.integerFromBuffer(this._buffers.parentFingerPrint),
+ childIndex: bufferUtil.integerFromBuffer(this._buffers.childIndex),
+ chainCode: bufferUtil.bufferToHex(this._buffers.chainCode),
+ publicKey: this.publicKey.toString(),
+ checksum: bufferUtil.integerFromBuffer(this._buffers.checksum),
+ xpubkey: this.xpubkey
+ };
+};
+
+/**
+ * Returns the JSON representation of this key's toObject result
+ *
+ * @see {HDPublicKey#toObject}
+ * @return {string}
+ */
+HDPublicKey.prototype.toJson = function () {
+ return JSON.stringify(this.toObject());
+};
+
+HDPublicKey.Hardened = 0x80000000;
+HDPublicKey.RootElementAlias = ['m', 'M'];
+
+HDPublicKey.VersionSize = 4;
+HDPublicKey.DepthSize = 1;
+HDPublicKey.ParentFingerPrintSize = 4;
+HDPublicKey.ChildIndexSize = 4;
+HDPublicKey.ChainCodeSize = 32;
+HDPublicKey.PublicKeySize = 33;
+HDPublicKey.CheckSumSize = 4;
+
+HDPublicKey.DataSize = 78;
+HDPublicKey.SerializedByteSize = 82;
+
+HDPublicKey.VersionStart = 0;
+HDPublicKey.VersionEnd = HDPublicKey.VersionStart + HDPublicKey.VersionSize;
+HDPublicKey.DepthStart = HDPublicKey.VersionEnd;
+HDPublicKey.DepthEnd = HDPublicKey.DepthStart + HDPublicKey.DepthSize;
+HDPublicKey.ParentFingerPrintStart = HDPublicKey.DepthEnd;
+HDPublicKey.ParentFingerPrintEnd = HDPublicKey.ParentFingerPrintStart + HDPublicKey.ParentFingerPrintSize;
+HDPublicKey.ChildIndexStart = HDPublicKey.ParentFingerPrintEnd;
+HDPublicKey.ChildIndexEnd = HDPublicKey.ChildIndexStart + HDPublicKey.ChildIndexSize;
+HDPublicKey.ChainCodeStart = HDPublicKey.ChildIndexEnd;
+HDPublicKey.ChainCodeEnd = HDPublicKey.ChainCodeStart + HDPublicKey.ChainCodeSize;
+HDPublicKey.PublicKeyStart = HDPublicKey.ChainCodeEnd;
+HDPublicKey.PublicKeyEnd = HDPublicKey.PublicKeyStart + HDPublicKey.PublicKeySize;
+HDPublicKey.ChecksumStart = HDPublicKey.PublicKeyEnd;
+HDPublicKey.ChecksumEnd = HDPublicKey.ChecksumStart + HDPublicKey.CheckSumSize;
+
+assert(HDPublicKey.PublicKeyEnd === HDPublicKey.DataSize);
+assert(HDPublicKey.ChecksumEnd === HDPublicKey.SerializedByteSize);
+
+module.exports = HDPublicKey;
diff --git a/lib/networks.js b/lib/networks.js
index a57d9e434..24192523c 100644
--- a/lib/networks.js
+++ b/lib/networks.js
@@ -66,6 +66,7 @@ function getNetwork(arg) {
* @namespace Network
*/
module.exports = {
+ defaultNetwork: livenet,
livenet: livenet,
testnet: testnet,
mainnet: livenet,
diff --git a/lib/privatekey.js b/lib/privatekey.js
index 0d12ce395..eb9a4dbd8 100644
--- a/lib/privatekey.js
+++ b/lib/privatekey.js
@@ -123,7 +123,7 @@ PrivateKey._transformBuffer = function(buf, network, compressed) {
throw new Error('Invalid network');
}
- if (network && info.network !== network){
+ if (network && networks.get(info.network) !== networks.get(network)) {
throw TypeError('Private key network mismatch');
}
diff --git a/lib/publickey.js b/lib/publickey.js
index ed00047b6..3bffc6ed1 100644
--- a/lib/publickey.js
+++ b/lib/publickey.js
@@ -70,7 +70,6 @@ var PublicKey = function PublicKey(data, compressed) {
};
/**
- *
* Internal function to transform a private key into a public key point
*
* @param {PrivateKey} privkey - An instance of PrivateKey
@@ -89,7 +88,6 @@ PublicKey._transformPrivateKey = function(privkey) {
};
/**
- *
* Internal function to transform DER into a public key point
*
* @param {Buffer} buf - An hex encoded buffer
diff --git a/lib/util/bitcoin.js b/lib/util/bitcoin.js
new file mode 100644
index 000000000..821c04dbd
--- /dev/null
+++ b/lib/util/bitcoin.js
@@ -0,0 +1,18 @@
+/**
+ * @file util/bitcoin.js
+ * Contains utilities to handle magnitudes inside of bitcoin
+ */
+'use strict';
+
+var SATOSHIS_PER_BTC = 1e8;
+
+module.exports = {
+ /**
+ * @param number satoshis - amount of satoshis to convert
+ * @return string an exact representation of such amount, in form of a string
+ * (avoids duplicate representations in ieee756 of the same number)
+ */
+ satoshisToBitcoin: function(satoshis) {
+ return satoshis / SATOSHIS_PER_BTC;
+ }
+};
diff --git a/lib/util/buffer.js b/lib/util/buffer.js
new file mode 100644
index 000000000..5b9e65ac0
--- /dev/null
+++ b/lib/util/buffer.js
@@ -0,0 +1,109 @@
+'use strict';
+
+var buffer = require('buffer');
+var assert = require('assert');
+
+var js = require('./js');
+
+module.exports = {
+ /**
+ * Returns true if the given argument is an instance of a buffer. Tests for
+ * both node's Buffer and Uint8Array
+ *
+ * @param {*} arg
+ * @return {boolean}
+ */
+ isBuffer: function isBuffer(arg) {
+ return buffer.Buffer.isBuffer(arg) || arg instanceof Uint8Array;
+ },
+
+ /**
+ * Returns a zero-filled byte array
+ *
+ * @param {number} bytes
+ * @return {Buffer}
+ */
+ emptyBuffer: function emptyBuffer(bytes) {
+ var result = new buffer.Buffer(bytes);
+ for (var i = 0; i < bytes; i++) {
+ result.write('\0', i);
+ }
+ return result;
+ },
+
+ /**
+ * Concatenates a buffer
+ *
+ * Shortcut for buffer.Buffer.concat
+ */
+ concat: buffer.Buffer.concat,
+
+ /**
+ * Transforms a number from 0 to 255 into a Buffer of size 1 with that value
+ *
+ * @param {number} integer
+ * @return {Buffer}
+ */
+ integerAsSingleByteBuffer: function integerAsSingleByteBuffer(integer) {
+ return new buffer.Buffer([integer & 0xff]);
+ },
+
+ /**
+ * Transform a 4-byte integer into a Buffer of length 4.
+ *
+ * @param {number} integer
+ * @return {Buffer}
+ */
+ integerAsBuffer: function integerAsBuffer(integer) {
+ var bytes = [];
+ bytes.push((integer >> 24) & 0xff);
+ bytes.push((integer >> 16) & 0xff);
+ bytes.push((integer >> 8) & 0xff);
+ bytes.push(integer & 0xff);
+ return new Buffer(bytes);
+ },
+
+ /**
+ * Transform the first 4 values of a Buffer into a number, in little endian encoding
+ *
+ * @param {Buffer} buffer
+ * @return {number}
+ */
+ integerFromBuffer: function integerFromBuffer(buffer) {
+ return buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
+ },
+
+ /**
+ * Transforms the first byte of an array into a number ranging from -128 to 127
+ * @param {Buffer} buffer
+ * @return {number}
+ */
+ integerFromSingleByteBuffer: function integerFromBuffer(buffer) {
+ return buffer[0];
+ },
+
+ /**
+ * Transforms a buffer into a string with a number in hexa representation
+ *
+ * Shorthand for buffer.toString('hex')
+ *
+ * @param {Buffer} buffer
+ * @return {string}
+ */
+ bufferToHex: function bufferToHex(buffer) {
+ return buffer.toString('hex');
+ },
+
+ /**
+ * Transforms an hexa encoded string into a Buffer with binary values
+ *
+ * Shorthand for Buffer(string, 'hex')
+ *
+ * @param {string} string
+ * @return {Buffer}
+ */
+ hexToBuffer: function hexToBuffer(string) {
+ assert(js.isHexa(string));
+ return new buffer.Buffer(string, 'hex');
+ }
+};
diff --git a/lib/util/js.js b/lib/util/js.js
new file mode 100644
index 000000000..a4b2e3ae7
--- /dev/null
+++ b/lib/util/js.js
@@ -0,0 +1,35 @@
+'use strict';
+
+var _ = require('lodash');
+
+/**
+ * Determines whether a string contains only hexadecimal values
+ *
+ * @param {string} value
+ * @return {boolean} true if the string is the hexa representation of a number
+ */
+var isHexa = function isHexa(value) {
+ if (!_.isString(value)) {
+ return false;
+ }
+ return /^[0-9a-fA-F]+$/.test(value);
+};
+
+module.exports = {
+ /**
+ * Test if an argument is a valid JSON object. If it is, returns a truthy
+ * value (the json object decoded), so no double JSON.parse call is necessary
+ *
+ * @param {string} arg
+ * @return {Object|boolean} false if the argument is not a JSON string.
+ */
+ isValidJson: function isValidJson(arg) {
+ try {
+ return JSON.parse(arg);
+ } catch (e) {
+ return false;
+ }
+ },
+ isHexa: isHexa,
+ isHexaString: isHexa
+};
diff --git a/package.json b/package.json
index efb33fca8..656738129 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
"bs58": "=2.0.0",
"elliptic": "=0.15.14",
"hash.js": "=0.3.2",
+ "inherits": "=2.0.1",
"lodash": "=2.4.1",
"sha512": "=0.0.1"
},
@@ -90,6 +91,7 @@
"gulp-mocha": "^2.0.0",
"gulp-rename": "^1.2.0",
"gulp-shell": "^0.2.10",
+ "inherits": "^2.0.1",
"lodash": "^2.4.1",
"mocha": "~2.0.1",
"run-sequence": "^1.0.2",
diff --git a/test/bip32.js b/test/bip32.js
deleted file mode 100644
index a1d0e0ada..000000000
--- a/test/bip32.js
+++ /dev/null
@@ -1,361 +0,0 @@
-'use strict';
-
-var should = require('chai').should();
-var bitcore = require('..');
-var BIP32 = bitcore.BIP32;
-
-describe('BIP32', function() {
-
- //test vectors: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
- var vector1_master = '000102030405060708090a0b0c0d0e0f';
- var vector1_m_public = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8';
- var vector1_m_private = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi';
- var vector1_m0h_public = 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw';
- var vector1_m0h_private = 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7';
- var vector1_m0h1_public = 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ';
- var vector1_m0h1_private = 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs';
- var vector1_m0h12h_public = 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5';
- var vector1_m0h12h_private = 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM';
- var vector1_m0h12h2_public = 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV';
- var vector1_m0h12h2_private = 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334';
- var vector1_m0h12h21000000000_public = 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy';
- var vector1_m0h12h21000000000_private = 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76';
- var vector2_master = 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542';
- var vector2_m_public = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB';
- var vector2_m_private = 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U';
- var vector2_m0_public = 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH';
- var vector2_m0_private = 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt';
- var vector2_m02147483647h_public = 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a';
- var vector2_m02147483647h_private = 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9';
- var vector2_m02147483647h1_public = 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon';
- var vector2_m02147483647h1_private = 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef';
- var vector2_m02147483647h12147483646h_public = 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL';
- var vector2_m02147483647h12147483646h_private = 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc';
- var vector2_m02147483647h12147483646h2_public = 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt';
- var vector2_m02147483647h12147483646h2_private = 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j';
-
- it('should make a new a bip32', function() {
- var bip32;
- bip32 = new BIP32();
- should.exist(bip32);
- bip32 = BIP32();
- should.exist(bip32);
- new BIP32(vector1_m_private).toString().should.equal(vector1_m_private);
- BIP32(vector1_m_private).toString().should.equal(vector1_m_private);
- BIP32(BIP32(vector1_m_private)).toString().should.equal(vector1_m_private);
- });
-
- it('should initialize test vector 1 from the extended public key', function() {
- var bip32 = new BIP32().fromString(vector1_m_public);
- should.exist(bip32);
- });
-
- it('should initialize test vector 1 from the extended private key', function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- should.exist(bip32);
- });
-
- it('should get the extended public key from the extended private key for test vector 1', function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- bip32.xpubkeyString().should.equal(vector1_m_public);
- });
-
- it("should get m/0' ext. private key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'");
- should.exist(child);
- child.xprivkeyString().should.equal(vector1_m0h_private);
- });
-
- it("should get m/0' ext. public key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'");
- should.exist(child);
- child.xpubkeyString().should.equal(vector1_m0h_public);
- });
-
- it("should get m/0'/1 ext. private key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1");
- should.exist(child);
- child.xprivkeyString().should.equal(vector1_m0h1_private);
- });
-
- it("should get m/0'/1 ext. public key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1");
- should.exist(child);
- child.xpubkeyString().should.equal(vector1_m0h1_public);
- });
-
- it("should get m/0'/1 ext. public key from m/0' public key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'");
- var child_pub = new BIP32().fromString(child.xpubkeyString());
- var child2 = child_pub.derive("m/1");
- should.exist(child2);
- child2.xpubkeyString().should.equal(vector1_m0h1_public);
- });
-
- it("should get m/0'/1/2h ext. private key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1/2'");
- should.exist(child);
- child.xprivkeyString().should.equal(vector1_m0h12h_private);
- });
-
- it("should get m/0'/1/2h ext. public key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1/2'");
- should.exist(child);
- child.xpubkeyString().should.equal(vector1_m0h12h_public);
- });
-
- it("should get m/0'/1/2h/2 ext. private key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1/2'/2");
- should.exist(child);
- child.xprivkeyString().should.equal(vector1_m0h12h2_private);
- });
-
- it("should get m/0'/1/2'/2 ext. public key from m/0'/1/2' public key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1/2'");
- var child_pub = new BIP32().fromString(child.xpubkeyString());
- var child2 = child_pub.derive("m/2");
- should.exist(child2);
- child2.xpubkeyString().should.equal(vector1_m0h12h2_public);
- });
-
- it("should get m/0'/1/2h/2 ext. public key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1/2'/2");
- should.exist(child);
- child.xpubkeyString().should.equal(vector1_m0h12h2_public);
- });
-
- it("should get m/0'/1/2h/2/1000000000 ext. private key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1/2'/2/1000000000");
- should.exist(child);
- child.xprivkeyString().should.equal(vector1_m0h12h21000000000_private);
- });
-
- it("should get m/0'/1/2h/2/1000000000 ext. public key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1/2'/2/1000000000");
- should.exist(child);
- child.xpubkeyString().should.equal(vector1_m0h12h21000000000_public);
- });
-
- it("should get m/0'/1/2'/2/1000000000 ext. public key from m/0'/1/2'/2 public key from test vector 1", function() {
- var bip32 = new BIP32().fromString(vector1_m_private);
- var child = bip32.derive("m/0'/1/2'/2");
- var child_pub = new BIP32().fromString(child.xpubkeyString());
- var child2 = child_pub.derive("m/1000000000");
- should.exist(child2);
- child2.xpubkeyString().should.equal(vector1_m0h12h21000000000_public);
- });
-
- it('should initialize test vector 2 from the extended public key', function() {
- var bip32 = new BIP32().fromString(vector2_m_public);
- should.exist(bip32);
- });
-
- it('should initialize test vector 2 from the extended private key', function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- should.exist(bip32);
- });
-
- it('should get the extended public key from the extended private key for test vector 2', function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- bip32.xpubkeyString().should.equal(vector2_m_public);
- });
-
- it("should get m/0 ext. private key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0");
- should.exist(child);
- child.xprivkeyString().should.equal(vector2_m0_private);
- });
-
- it("should get m/0 ext. public key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0");
- should.exist(child);
- child.xpubkeyString().should.equal(vector2_m0_public);
- });
-
- it("should get m/0 ext. public key from m public key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m");
- var child_pub = new BIP32().fromString(child.xpubkeyString());
- var child2 = child_pub.derive("m/0");
- should.exist(child2);
- child2.xpubkeyString().should.equal(vector2_m0_public);
- });
-
- it("should get m/0/2147483647h ext. private key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'");
- should.exist(child);
- child.xprivkeyString().should.equal(vector2_m02147483647h_private);
- });
-
- it("should get m/0/2147483647h ext. public key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'");
- should.exist(child);
- child.xpubkeyString().should.equal(vector2_m02147483647h_public);
- });
-
- it("should get m/0/2147483647h/1 ext. private key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'/1");
- should.exist(child);
- child.xprivkeyString().should.equal(vector2_m02147483647h1_private);
- });
-
- it("should get m/0/2147483647h/1 ext. public key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'/1");
- should.exist(child);
- child.xpubkeyString().should.equal(vector2_m02147483647h1_public);
- });
-
- it("should get m/0/2147483647h/1 ext. public key from m/0/2147483647h public key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'");
- var child_pub = new BIP32().fromString(child.xpubkeyString());
- var child2 = child_pub.derive("m/1");
- should.exist(child2);
- child2.xpubkeyString().should.equal(vector2_m02147483647h1_public);
- });
-
- it("should get m/0/2147483647h/1/2147483646h ext. private key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'/1/2147483646'");
- should.exist(child);
- child.xprivkeyString().should.equal(vector2_m02147483647h12147483646h_private);
- });
-
- it("should get m/0/2147483647h/1/2147483646h ext. public key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'/1/2147483646'");
- should.exist(child);
- child.xpubkeyString().should.equal(vector2_m02147483647h12147483646h_public);
- });
-
- it("should get m/0/2147483647h/1/2147483646h/2 ext. private key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'/1/2147483646'/2");
- should.exist(child);
- child.xprivkeyString().should.equal(vector2_m02147483647h12147483646h2_private);
- });
-
- it("should get m/0/2147483647h/1/2147483646h/2 ext. public key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'/1/2147483646'/2");
- should.exist(child);
- child.xpubkeyString().should.equal(vector2_m02147483647h12147483646h2_public);
- });
-
- it("should get m/0/2147483647h/1/2147483646h/2 ext. public key from m/0/2147483647h/2147483646h public key from test vector 2", function() {
- var bip32 = new BIP32().fromString(vector2_m_private);
- var child = bip32.derive("m/0/2147483647'/1/2147483646'");
- var child_pub = new BIP32().fromString(child.xpubkeyString());
- var child2 = child_pub.derive("m/2");
- should.exist(child2);
- child2.xpubkeyString().should.equal(vector2_m02147483647h12147483646h2_public);
- });
-
- describe('testnet', function() {
- it('should initialize a new BIP32 correctly from a random BIP32', function() {
- var b1 = new BIP32();
- b1.fromRandom('testnet');
- var b2 = new BIP32().fromString(b1.xpubkeyString());
- b2.xpubkeyString().should.equal(b1.xpubkeyString());
- });
-
- it('should generate valid ext pub key for testnet', function() {
- var b = new BIP32();
- b.fromRandom('testnet');
- b.xpubkeyString().substring(0,4).should.equal('tpub');
- });
- });
-
- describe('#set', function() {
- var bip32 = BIP32(vector1_m_private);
- var bip322 = BIP32().set({
- version: bip32.version,
- depth: bip32.depth,
- parentfingerprint: bip32.parentfingerprint,
- childindex: bip32.childindex,
- chaincode: bip32.chaincode,
- key: bip32.key,
- hasprivkey: bip32.hasprivkey,
- pubkeyhash: bip32.pubKeyhash,
- xpubkey: bip32.xpubkey,
- xprivkey: bip32.xprivkey
- });
- bip322.toString().should.equal(bip32.toString());
- bip322.set({}).toString().should.equal(bip32.toString());
- });
-
- describe('#seed', function() {
-
- it('should initialize a new BIP32 correctly from test vector 1 seed', function() {
- var hex = vector1_master;
- var bip32 = (new BIP32()).fromSeed(new Buffer(hex, 'hex'), 'mainnet');
- should.exist(bip32);
- bip32.xprivkeyString().should.equal(vector1_m_private);
- bip32.xpubkeyString().should.equal(vector1_m_public);
- });
-
- it('should initialize a new BIP32 correctly from test vector 2 seed', function() {
- var hex = vector2_master;
- var bip32 = (new BIP32()).fromSeed(new Buffer(hex, 'hex'), 'mainnet');
- should.exist(bip32);
- bip32.xprivkeyString().should.equal(vector2_m_private);
- bip32.xpubkeyString().should.equal(vector2_m_public);
- });
- });
-
- describe('#fromString', function() {
-
- it('should make a bip32 from a string', function() {
- var str = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi';
- var bip32 = new BIP32().fromString(str);
- should.exist(bip32);
- bip32.toString().should.equal(str);
- });
-
- });
-
- describe('#toString', function() {
- var bip32 = new BIP32();
- bip32.fromRandom('mainnet');
- var tip32 = new BIP32();
- tip32.fromRandom('testnet');
-
- it('should return an xprv string', function() {
- bip32.toString().slice(0, 4).should.equal('xprv');
- });
-
- it('should return an xpub string', function() {
- var bip32b = new BIP32().fromString(bip32.xpubkeyString());
- bip32b.toString().slice(0, 4).should.equal('xpub');
- });
-
- it('should return a tprv string', function() {
- tip32.toString().slice(0, 4).should.equal('tprv');
- });
-
- it('should return a tpub string', function() {
- var tip32b = new BIP32().fromString(tip32.xpubkeyString());
- tip32b.toString().slice(0, 4).should.equal('tpub');
- });
-
- });
-
-});
diff --git a/test/encoding/base58.js b/test/encoding/base58.js
index 3604e9c2a..fc0a7e858 100644
--- a/test/encoding/base58.js
+++ b/test/encoding/base58.js
@@ -2,10 +2,11 @@
var should = require('chai').should();
var bitcore = require('../..');
+var buffer = require('buffer');
var Base58 = bitcore.encoding.Base58;
describe('Base58', function() {
- var buf = new Buffer([0, 1, 2, 3, 253, 254, 255]);
+ var buf = new buffer.Buffer([0, 1, 2, 3, 253, 254, 255]);
var enc = '1W7N4RuG';
it('should make an instance with "new"', function() {
@@ -13,6 +14,21 @@ describe('Base58', function() {
should.exist(b58);
});
+ it('validates characters with no false negatives', function() {
+ Base58.validCharacters(
+ '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
+ ).should.equal(true);
+ });
+ it('validates characters from buffer', function() {
+ Base58.validCharacters(
+ new buffer.Buffer('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz')
+ ).should.equal(true);
+ });
+
+ it('some characters are invalid (no false positives)', function() {
+ Base58.validCharacters('!@#%^$&*()\\').should.equal(false);
+ });
+
it('should make an instance without "new"', function() {
var b58 = Base58();
should.exist(b58);
@@ -27,7 +43,7 @@ describe('Base58', function() {
it('should set a blank buffer', function() {
Base58().set({
- buf: new Buffer([])
+ buf: new buffer.Buffer([])
});
});
diff --git a/test/encoding/base58check.js b/test/encoding/base58check.js
index 0082b5ef5..b8b56c888 100644
--- a/test/encoding/base58check.js
+++ b/test/encoding/base58check.js
@@ -14,6 +14,13 @@ describe('Base58Check', function() {
should.exist(b58);
});
+ it('can validate a serialized string', function() {
+ var address = '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy';
+ Base58Check.validChecksum(address).should.equal(true);
+ address = address + 'a';
+ Base58Check.validChecksum(address).should.equal(false);
+ });
+
it('should make an instance without "new"', function() {
var b58 = Base58Check();
should.exist(b58);
diff --git a/test/hdkeycache.js b/test/hdkeycache.js
new file mode 100644
index 000000000..57635d4e1
--- /dev/null
+++ b/test/hdkeycache.js
@@ -0,0 +1,50 @@
+'use strict';
+
+var _ = require('lodash');
+var expect = require('chai').expect;
+var bitcore = require('..');
+var HDPrivateKey = bitcore.HDPrivateKey;
+
+var xprivkey = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi';
+
+describe('HDKey cache', function() {
+ /* jshint unused: false */
+ var cache = bitcore._HDKeyCache;
+ var master = new HDPrivateKey(xprivkey);
+
+ beforeEach(function() {
+ cache._cache = {};
+ cache._count = 0;
+ cache._eraseIndex = 0;
+ cache._usedIndex = {};
+ cache._usedList = {};
+ cache._CACHE_SIZE = 3; // Reduce for quick testing
+ });
+
+ it('saves a derived key', function() {
+ var child = master.derive(0);
+ expect(cache._cache[master.xprivkey + '/0/false'].xprivkey).to.equal(child.xprivkey);
+ });
+ it('starts erasing unused keys', function() {
+ var child1 = master.derive(0);
+ var child2 = child1.derive(0);
+ var child3 = child2.derive(0);
+ expect(cache._cache[master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
+ var child4 = child3.derive(0);
+ expect(cache._cache[master.xprivkey + '/0/false']).to.equal(undefined);
+ });
+ it('avoids erasing keys that get cache hits ("hot keys")', function() {
+ var child1 = master.derive(0);
+ var child2 = master.derive(0).derive(0);
+ expect(cache._cache[master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
+ var child1_copy = master.derive(0);
+ expect(cache._cache[master.xprivkey + '/0/false'].xprivkey).to.equal(child1.xprivkey);
+ });
+ it('keeps the size of the cache small', function() {
+ var child1 = master.derive(0);
+ var child2 = child1.derive(0);
+ var child3 = child2.derive(0);
+ var child4 = child3.derive(0);
+ expect(_.size(cache._cache)).to.equal(3);
+ });
+});
diff --git a/test/hdkeys.js b/test/hdkeys.js
new file mode 100644
index 000000000..3e6710d67
--- /dev/null
+++ b/test/hdkeys.js
@@ -0,0 +1,230 @@
+'use strict';
+
+// Relax some linter options:
+// * quote marks so "m/0'/1/2'/" doesn't need to be scaped
+// * too many tests, maxstatements -> 100
+// * store test vectors at the end, latedef: false
+// * should call is never defined
+/* jshint quotmark: false */
+/* jshint latedef: false */
+/* jshint maxstatements: 100 */
+/* jshint unused: false */
+
+var should = require('chai').should();
+var bitcore = require('..');
+var HDPrivateKey = bitcore.HDPrivateKey;
+var HDPublicKey = bitcore.HDPublicKey;
+
+describe('BIP32 compliance', function() {
+
+ it('should initialize test vector 1 from the extended public key', function() {
+ new HDPublicKey(vector1_m_public).xpubkey.should.equal(vector1_m_public);
+ });
+
+ it('should initialize test vector 1 from the extended private key', function() {
+ new HDPrivateKey(vector1_m_private).xprivkey.should.equal(vector1_m_private);
+ });
+
+ it('can initialize a public key from an extended private key', function() {
+ new HDPublicKey(vector1_m_private).xpubkey.should.equal(vector1_m_public);
+ });
+
+ it('toString should be equal to the `xpubkey` member', function() {
+ var privateKey = new HDPrivateKey(vector1_m_private);
+ privateKey.toString().should.equal(privateKey.xprivkey);
+ });
+
+ it('toString should be equal to the `xpubkey` member', function() {
+ var publicKey = new HDPublicKey(vector1_m_public);
+ publicKey.toString().should.equal(publicKey.xpubkey);
+ });
+
+ it('should get the extended public key from the extended private key for test vector 1', function() {
+ HDPrivateKey(vector1_m_private).xpubkey.should.equal(vector1_m_public);
+ });
+
+ it("should get m/0' ext. private key from test vector 1", function() {
+ var privateKey = new HDPrivateKey(vector1_m_private).derive("m/0'");
+ privateKey.xprivkey.should.equal(vector1_m0h_private);
+ });
+
+ it("should get m/0' ext. public key from test vector 1", function() {
+ HDPrivateKey(vector1_m_private).derive("m/0'")
+ .xpubkey.should.equal(vector1_m0h_public);
+ });
+
+ it("should get m/0'/1 ext. private key from test vector 1", function() {
+ HDPrivateKey(vector1_m_private).derive("m/0'/1")
+ .xprivkey.should.equal(vector1_m0h1_private);
+ });
+
+ it("should get m/0'/1 ext. public key from test vector 1", function() {
+ HDPrivateKey(vector1_m_private).derive("m/0'/1")
+ .xpubkey.should.equal(vector1_m0h1_public);
+ });
+
+ it("should get m/0'/1 ext. public key from m/0' public key from test vector 1", function() {
+ var derivedPublic = HDPrivateKey(vector1_m_private).derive("m/0'").hdPublicKey.derive("m/1");
+ derivedPublic.xpubkey.should.equal(vector1_m0h1_public);
+ });
+
+ it("should get m/0'/1/2' ext. private key from test vector 1", function() {
+ var privateKey = new HDPrivateKey(vector1_m_private);
+ var derived = privateKey.derive("m/0'/1/2'");
+ derived.xprivkey.should.equal(vector1_m0h12h_private);
+ });
+
+ it("should get m/0'/1/2' ext. public key from test vector 1", function() {
+ HDPrivateKey(vector1_m_private).derive("m/0'/1/2'")
+ .xpubkey.should.equal(vector1_m0h12h_public);
+ });
+
+ it("should get m/0'/1/2'/2 ext. private key from test vector 1", function() {
+ HDPrivateKey(vector1_m_private).derive("m/0'/1/2'/2")
+ .xprivkey.should.equal(vector1_m0h12h2_private);
+ });
+
+ it("should get m/0'/1/2'/2 ext. public key from m/0'/1/2' public key from test vector 1", function() {
+ var derived = HDPrivateKey(vector1_m_private).derive("m/0'/1/2'").hdPublicKey;
+ derived.derive("m/2").xpubkey.should.equal(vector1_m0h12h2_public);
+ });
+
+ it("should get m/0'/1/2h/2 ext. public key from test vector 1", function() {
+ HDPrivateKey(vector1_m_private).derive("m/0'/1/2'/2")
+ .xpubkey.should.equal(vector1_m0h12h2_public);
+ });
+
+ it("should get m/0'/1/2h/2/1000000000 ext. private key from test vector 1", function() {
+ HDPrivateKey(vector1_m_private).derive("m/0'/1/2'/2/1000000000")
+ .xprivkey.should.equal(vector1_m0h12h21000000000_private);
+ });
+
+ it("should get m/0'/1/2h/2/1000000000 ext. public key from test vector 1", function() {
+ HDPrivateKey(vector1_m_private).derive("m/0'/1/2'/2/1000000000")
+ .xpubkey.should.equal(vector1_m0h12h21000000000_public);
+ });
+
+ it("should get m/0'/1/2'/2/1000000000 ext. public key from m/0'/1/2'/2 public key from test vector 1", function() {
+ var derived = HDPrivateKey(vector1_m_private).derive("m/0'/1/2'/2").hdPublicKey;
+ derived.derive("m/1000000000").xpubkey.should.equal(vector1_m0h12h21000000000_public);
+ });
+
+ it('should initialize test vector 2 from the extended public key', function() {
+ HDPublicKey(vector2_m_public).xpubkey.should.equal(vector2_m_public);
+ });
+
+ it('should initialize test vector 2 from the extended private key', function() {
+ HDPrivateKey(vector2_m_private).xprivkey.should.equal(vector2_m_private);
+ });
+
+ it('should get the extended public key from the extended private key for test vector 2', function() {
+ HDPrivateKey(vector2_m_private).xpubkey.should.equal(vector2_m_public);
+ });
+
+ it("should get m/0 ext. private key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive(0).xprivkey.should.equal(vector2_m0_private);
+ });
+
+ it("should get m/0 ext. public key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive(0).xpubkey.should.equal(vector2_m0_public);
+ });
+
+ it("should get m/0 ext. public key from m public key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).hdPublicKey.derive(0).xpubkey.should.equal(vector2_m0_public);
+ });
+
+ it("should get m/0/2147483647h ext. private key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive("m/0/2147483647'")
+ .xprivkey.should.equal(vector2_m02147483647h_private);
+ });
+
+ it("should get m/0/2147483647h ext. public key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive("m/0/2147483647'")
+ .xpubkey.should.equal(vector2_m02147483647h_public);
+ });
+
+ it("should get m/0/2147483647h/1 ext. private key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive("m/0/2147483647'/1")
+ .xprivkey.should.equal(vector2_m02147483647h1_private);
+ });
+
+ it("should get m/0/2147483647h/1 ext. public key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive("m/0/2147483647'/1")
+ .xpubkey.should.equal(vector2_m02147483647h1_public);
+ });
+
+ it("should get m/0/2147483647h/1 ext. public key from m/0/2147483647h public key from test vector 2", function() {
+ var derived = HDPrivateKey(vector2_m_private).derive("m/0/2147483647'").hdPublicKey;
+ derived.derive(1).xpubkey.should.equal(vector2_m02147483647h1_public);
+ });
+
+ it("should get m/0/2147483647h/1/2147483646h ext. private key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive("m/0/2147483647'/1/2147483646'")
+ .xprivkey.should.equal(vector2_m02147483647h12147483646h_private);
+ });
+
+ it("should get m/0/2147483647h/1/2147483646h ext. public key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive("m/0/2147483647'/1/2147483646'")
+ .xpubkey.should.equal(vector2_m02147483647h12147483646h_public);
+ });
+
+ it("should get m/0/2147483647h/1/2147483646h/2 ext. private key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive("m/0/2147483647'/1/2147483646'/2")
+ .xprivkey.should.equal(vector2_m02147483647h12147483646h2_private);
+ });
+
+ it("should get m/0/2147483647h/1/2147483646h/2 ext. public key from test vector 2", function() {
+ HDPrivateKey(vector2_m_private).derive("m/0/2147483647'/1/2147483646'/2")
+ .xpubkey.should.equal(vector2_m02147483647h12147483646h2_public);
+ });
+
+ it("should get m/0/2147483647h/1/2147483646h/2 ext. public key from m/0/2147483647h/2147483646h public key from test vector 2", function() {
+ var derivedPublic = HDPrivateKey(vector2_m_private)
+ .derive("m/0/2147483647'/1/2147483646'").hdPublicKey;
+ derivedPublic.derive("m/2")
+ .xpubkey.should.equal(vector2_m02147483647h12147483646h2_public);
+ });
+
+ describe('seed', function() {
+
+ it('should initialize a new BIP32 correctly from test vector 1 seed', function() {
+ var seededKey = HDPrivateKey.fromSeed(vector1_master);
+ seededKey.xprivkey.should.equal(vector1_m_private);
+ seededKey.xpubkey.should.equal(vector1_m_public);
+ });
+
+ it('should initialize a new BIP32 correctly from test vector 2 seed', function() {
+ var seededKey = HDPrivateKey.fromSeed(vector2_master);
+ seededKey.xprivkey.should.equal(vector2_m_private);
+ seededKey.xpubkey.should.equal(vector2_m_public);
+ });
+ });
+});
+
+//test vectors: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
+var vector1_master = '000102030405060708090a0b0c0d0e0f';
+var vector1_m_public = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8';
+var vector1_m_private = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi';
+var vector1_m0h_public = 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw';
+var vector1_m0h_private = 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7';
+var vector1_m0h1_public = 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ';
+var vector1_m0h1_private = 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs';
+var vector1_m0h12h_public = 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5';
+var vector1_m0h12h_private = 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM';
+var vector1_m0h12h2_public = 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV';
+var vector1_m0h12h2_private = 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334';
+var vector1_m0h12h21000000000_public = 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy';
+var vector1_m0h12h21000000000_private = 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76';
+var vector2_master = 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542';
+var vector2_m_public = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB';
+var vector2_m_private = 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U';
+var vector2_m0_public = 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH';
+var vector2_m0_private = 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt';
+var vector2_m02147483647h_public = 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a';
+var vector2_m02147483647h_private = 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9';
+var vector2_m02147483647h1_public = 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon';
+var vector2_m02147483647h1_private = 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef';
+var vector2_m02147483647h12147483646h_public = 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL';
+var vector2_m02147483647h12147483646h_private = 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc';
+var vector2_m02147483647h12147483646h2_public = 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt';
+var vector2_m02147483647h12147483646h2_private = 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j';
diff --git a/test/hdprivatekey.js b/test/hdprivatekey.js
new file mode 100644
index 000000000..6d8a7eaa7
--- /dev/null
+++ b/test/hdprivatekey.js
@@ -0,0 +1,181 @@
+'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 errors = bitcore.errors.HDPrivateKey.InvalidArgument;
+var buffer = require('buffer');
+var bufferUtil = bitcore.util.buffer;
+var HDPrivateKey = bitcore.HDPrivateKey;
+var Base58Check = bitcore.encoding.Base58Check;
+
+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() {
+ /* jshint maxstatements: 50 */
+ var expectFail = function(func, error) {
+ var got = null;
+ try {
+ func();
+ } catch (e) {
+ got = e instanceof error;
+ }
+ expect(got).to.equal(true);
+ };
+
+ var expectDerivationFail = function(argument, error) {
+ return expectFail(function() {
+ var privateKey = new HDPrivateKey(xprivkey);
+ privateKey.derive(argument);
+ }, error);
+ };
+
+ var expectFailBuilding = function(argument, error) {
+ return expectFail(function() {
+ return new HDPrivateKey(argument);
+ }, error);
+ };
+
+ var expectSeedFail = function(argument, error) {
+ return expectFail(function() {
+ return HDPrivateKey.fromSeed(argument);
+ }, error);
+ };
+
+ it('should make a new private key from random', function() {
+ (new HDPrivateKey().xprivkey).should.exist();
+ });
+
+ it('should error with an invalid checksum', function() {
+ expectFailBuilding(xprivkey + '1', 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 structure and same members', function() {
+ assert(_.isEqual(
+ 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() {
+ expectFailBuilding(1, errors.UnrecognizedArgument);
+ });
+ });
+
+ it('allows no-new calling', function() {
+ HDPrivateKey(xprivkey).toString().should.equal(xprivkey);
+ });
+
+ it('allows the use of a copy constructor', function() {
+ HDPrivateKey(HDPrivateKey(xprivkey))
+ .xprivkey.should.equal(xprivkey);
+ });
+
+ it('fails when trying to derive with an invalid argument', function() {
+ expectDerivationFail([], errors.InvalidDerivationArgument);
+ });
+
+ it('catches early invalid paths', function() {
+ expectDerivationFail('s', 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() {
+ expect(
+ HDPrivateKey.getSerializedError(1) instanceof
+ errors.UnrecognizedArgument
+ ).to.equal(true);
+ });
+
+ it('returns InvalidLength if data of invalid length is given to getSerializedError', function() {
+ expect(
+ HDPrivateKey.getSerializedError(Base58Check.encode(new buffer.Buffer('onestring'))) instanceof
+ errors.InvalidLength
+ ).to.equal(true);
+ });
+
+ it('returns InvalidNetworkArgument if an invalid network is provided', function() {
+ expect(
+ HDPrivateKey.getSerializedError(xprivkey, 'invalidNetwork') instanceof
+ errors.InvalidNetworkArgument
+ ).to.equal(true);
+ });
+
+ it('recognizes that the wrong network was asked for', function() {
+ expect(
+ HDPrivateKey.getSerializedError(xprivkey, 'testnet') instanceof
+ errors.InvalidNetwork
+ ).to.equal(true);
+ });
+
+ it('recognizes the correct network', function() {
+ expect(HDPrivateKey.getSerializedError(xprivkey, 'livenet')).to.equal(null);
+ });
+
+ describe('on creation from seed', function() {
+ 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, errors.InvalidEntropyArgument);
+ });
+ it('fails when argument doesn\'t provide enough entropy', function() {
+ expectSeedFail('01', errors.InvalidEntropyArgument.NotEnoughEntropy);
+ });
+ it('fails when argument provides too much entropy', function() {
+ var entropy = '0';
+ for (var i = 0; i < 129; i++) {
+ entropy += '1';
+ }
+ expectSeedFail(entropy, errors.InvalidEntropyArgument.TooMuchEntropy);
+ });
+ });
+
+ it('correctly errors if an invalid checksum is provided', function() {
+ var privKey = new HDPrivateKey(xprivkey);
+ var error = null;
+ try {
+ var buffers = privKey._buffers;
+ buffers.checksum = bufferUtil.integerAsBuffer(0);
+ var privateKey = new HDPrivateKey(buffers);
+ } catch (e) {
+ error = e;
+ }
+ expect(error instanceof errors.InvalidB58Checksum).to.equal(true);
+ });
+ 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() {
+ var privateKey = new HDPrivateKey(xprivkey);
+ var derivedByString = privateKey.derive('m/0\'/1/2\'');
+ var derivedByNumber = privateKey.derive(0, true).derive(1).derive(2, true);
+ derivedByNumber.xprivkey.should.equal(derivedByString.xprivkey);
+ });
+});
+
diff --git a/test/hdpublickey.js b/test/hdpublickey.js
new file mode 100644
index 000000000..5dc8a747d
--- /dev/null
+++ b/test/hdpublickey.js
@@ -0,0 +1,193 @@
+'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 errors = bitcore.errors.HDPublicKey.InvalidArgument;
+var bufferUtil = bitcore.util.buffer;
+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(func, errorType) {
+ var got = null;
+ var error = null;
+ try {
+ func();
+ } catch (e) {
+ error = e;
+ got = e instanceof errorType;
+ }
+ if (!error instanceof errorType) {
+ console.log('Adsasd', typeof error);
+ }
+ // expect(got).to.equal(true);
+ };
+
+ var expectDerivationFail = function(argument, error) {
+ return expectFail(function() {
+ var pubkey = new HDPublicKey(xpubkey);
+ xpubkey.derive(argument);
+ }, error);
+ };
+
+ var expectFailBuilding = function(argument, error) {
+ return expectFail(function() {
+ return new HDPublicKey(argument);
+ }, 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() {
+ expectFailBuilding(null, errors.MustSupplyArgument);
+ });
+
+ it('doesn\'t recognize an invalid argument', function() {
+ expectFailBuilding(1, errors.UnrecognizedArgument);
+ expectFailBuilding(true, errors.UnrecognizedArgument);
+ });
+
+
+ describe('xpubkey string serialization errors', function() {
+ it('fails on invalid length', function() {
+ expectFailBuilding(
+ Base58Check.encode(new buffer.Buffer([1, 2, 3])),
+ errors.InvalidLength
+ );
+ });
+ it('fails on invalid base58 encoding', function() {
+ expectFailBuilding(
+ xpubkey + '1',
+ errors.InvalidB58Checksum
+ );
+ });
+ it('user can ask if a string is valid', function() {
+ (HDPublicKey.isValidSerialized(xpubkey)).should.equal(true);
+ });
+ });
+
+ 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(_.isEqual(
+ 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 = bufferUtil.integerAsBuffer(1);
+ expectFail(function() {
+ return new HDPublicKey(buffers);
+ }, errors.InvalidB58Checksum);
+ });
+
+ });
+
+ describe('error checking on serialization', function() {
+ var compareType = function(a, b) {
+ expect(a instanceof b).to.equal(true);
+ };
+ it('throws invalid argument when argument is not a string or buffer', function() {
+ compareType(HDPublicKey.getSerializedError(1), errors.UnrecognizedArgument);
+ });
+ it('if a network is provided, validates that data corresponds to it', function() {
+ compareType(HDPublicKey.getSerializedError(xpubkey, 'testnet'), errors.InvalidNetwork);
+ });
+ it('recognizes invalid network arguments', function() {
+ compareType(HDPublicKey.getSerializedError(xpubkey, 'invalid'), 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() {
+ expectFail(function() {
+ return new HDPublicKey(xpubkey).derive({});
+ }, errors.InvalidDerivationArgument);
+ });
+
+ it('needs first argument for derivation', function() {
+ expectFail(function() {
+ return new HDPublicKey(xpubkey).derive('s');
+ }, errors.InvalidPath);
+ });
+
+ it('doesn\'t allow other parameters like m\' or M\' or "s"', function() {
+ /* jshint quotmark: double */
+ expectDerivationFail("m'", errors.InvalidDerivationArgument);
+ expectDerivationFail("M'", errors.InvalidDerivationArgument);
+ expectDerivationFail("1", errors.InvalidDerivationArgument);
+ expectDerivationFail("S", errors.InvalidDerivationArgument);
+ });
+
+ it('can\'t derive hardened keys', function() {
+ expectFail(function() {
+ return new HDPublicKey(xpubkey).derive(HDPublicKey.Hardened + 1);
+ }, errors.InvalidDerivationArgument);
+ });
+
+ it('should use the cache', function() {
+ var pubkey = new HDPublicKey(xpubkey);
+ var derived1 = pubkey.derive(0);
+ var derived2 = pubkey.derive(0);
+ derived1.should.equal(derived2);
+ });
+ });
+});