Merge pull request #577 from eordano/feature/hdprivate
Refactor BIP32 -> HDPrivateKey and HDPublicKey
This commit is contained in:
commit
756cec6e17
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@ node_modules
|
|||||||
browser/bitcore.js
|
browser/bitcore.js
|
||||||
browser/tests.js
|
browser/tests.js
|
||||||
|
|
||||||
|
lib/errors/index.js
|
||||||
|
|
||||||
CONTRIBUTING.html
|
CONTRIBUTING.html
|
||||||
LICENSE.html
|
LICENSE.html
|
||||||
|
|||||||
16
.jshintrc
16
.jshintrc
@ -30,15 +30,15 @@
|
|||||||
"maxlen": 600, // Maximum number of lines of code in a file
|
"maxlen": 600, // Maximum number of lines of code in a file
|
||||||
|
|
||||||
"predef": [ // Extra globals.
|
"predef": [ // Extra globals.
|
||||||
"define",
|
|
||||||
"require",
|
|
||||||
"exports",
|
|
||||||
"module",
|
|
||||||
"describe",
|
|
||||||
"before",
|
|
||||||
"beforeEach",
|
|
||||||
"after",
|
"after",
|
||||||
"afterEach",
|
"afterEach",
|
||||||
"it"
|
"before",
|
||||||
|
"beforeEach",
|
||||||
|
"define",
|
||||||
|
"describe",
|
||||||
|
"exports",
|
||||||
|
"it",
|
||||||
|
"module",
|
||||||
|
"require"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
27
gulpfile.js
27
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);
|
runSequence(['test'], ['karma'], callback);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test-nofail', function() {
|
gulp.task('test-nofail', ['errors'], function() {
|
||||||
return testMocha().on('error', ignoreError);
|
return testMocha().on('error', ignoreError);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,6 +60,12 @@ gulp.task('watch:test', function() {
|
|||||||
return gulp.watch(alljs, ['test-nofail']);
|
return gulp.watch(alljs, ['test-nofail']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('watch:coverage', function() {
|
||||||
|
// TODO: Only run tests that are linked to file changes by doing
|
||||||
|
// something smart like reading through the require statements
|
||||||
|
return gulp.watch(alljs, ['coverage']);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('watch:lint', function() {
|
gulp.task('watch:lint', function() {
|
||||||
// TODO: Only lint files that are linked to file changes by doing
|
// TODO: Only lint files that are linked to file changes by doing
|
||||||
// something smart like reading through the require statements
|
// something smart like reading through the require statements
|
||||||
@ -87,7 +93,7 @@ gulp.task('lint', function() {
|
|||||||
.pipe(jshint.reporter('default'));
|
.pipe(jshint.reporter('default'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('browser', function() {
|
gulp.task('browser', ['errors'], function() {
|
||||||
return gulp.src('index.js')
|
return gulp.src('index.js')
|
||||||
.pipe(browserify({
|
.pipe(browserify({
|
||||||
insertGlobals: true
|
insertGlobals: true
|
||||||
@ -100,13 +106,17 @@ gulp.task('browser-test', shell.task([
|
|||||||
'find test/ -type f -name "*.js" | xargs browserify -o ./browser/tests.js'
|
'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);
|
runSequence(['browser'], ['browser-test'], callback);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('karma', testKarma);
|
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')
|
return gulp.src('dist/bitcore.js')
|
||||||
.pipe(closureCompiler({
|
.pipe(closureCompiler({
|
||||||
fileName: 'bitcore.min.js',
|
fileName: 'bitcore.min.js',
|
||||||
@ -120,5 +130,8 @@ gulp.task('minify', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('default', function(callback) {
|
gulp.task('default', function(callback) {
|
||||||
return runSequence(['lint', 'jsdoc', 'browser', 'test'], ['coverage', 'minify'], callback);
|
return runSequence(['lint', 'jsdoc'],
|
||||||
|
['browser', 'test'],
|
||||||
|
['coverage', 'minify'],
|
||||||
|
callback);
|
||||||
});
|
});
|
||||||
|
|||||||
17
index.js
17
index.js
@ -18,11 +18,21 @@ bitcore.encoding.BufferReader = require('./lib/encoding/bufferreader');
|
|||||||
bitcore.encoding.BufferWriter = require('./lib/encoding/bufferwriter');
|
bitcore.encoding.BufferWriter = require('./lib/encoding/bufferwriter');
|
||||||
bitcore.encoding.Varint = require('./lib/encoding/varint');
|
bitcore.encoding.Varint = require('./lib/encoding/varint');
|
||||||
|
|
||||||
|
// 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
|
// main bitcoin library
|
||||||
bitcore.Address = require('./lib/address');
|
bitcore.Address = require('./lib/address');
|
||||||
bitcore.BIP32 = require('./lib/bip32');
|
|
||||||
bitcore.Block = require('./lib/block');
|
bitcore.Block = require('./lib/block');
|
||||||
bitcore.Blockheader = require('./lib/blockheader');
|
bitcore.Blockheader = require('./lib/blockheader');
|
||||||
|
bitcore.HDPrivateKey = require('./lib/hdprivatekey.js');
|
||||||
|
bitcore.HDPublicKey = require('./lib/hdpublickey.js');
|
||||||
bitcore.Networks = require('./lib/networks');
|
bitcore.Networks = require('./lib/networks');
|
||||||
bitcore.Opcode = require('./lib/opcode');
|
bitcore.Opcode = require('./lib/opcode');
|
||||||
bitcore.PrivateKey = require('./lib/privatekey');
|
bitcore.PrivateKey = require('./lib/privatekey');
|
||||||
@ -33,7 +43,7 @@ bitcore.Txin = require('./lib/txin');
|
|||||||
bitcore.Txout = require('./lib/txout');
|
bitcore.Txout = require('./lib/txout');
|
||||||
|
|
||||||
|
|
||||||
//dependencies, subject to change
|
// dependencies, subject to change
|
||||||
bitcore.deps = {};
|
bitcore.deps = {};
|
||||||
bitcore.deps.bnjs = require('bn.js');
|
bitcore.deps.bnjs = require('bn.js');
|
||||||
bitcore.deps.bs58 = require('bs58');
|
bitcore.deps.bs58 = require('bs58');
|
||||||
@ -45,3 +55,6 @@ bitcore.deps.elliptic = require('elliptic');
|
|||||||
//bitcore.txpartial = require('lib/txpartial');
|
//bitcore.txpartial = require('lib/txpartial');
|
||||||
|
|
||||||
//bitcore.bip70 = require('lib/bip70');
|
//bitcore.bip70 = require('lib/bip70');
|
||||||
|
|
||||||
|
// Internal usage, exposed for testing/advanced tweaking
|
||||||
|
bitcore._HDKeyCache = require('./lib/hdkeycache');
|
||||||
|
|||||||
320
lib/bip32.js
320
lib/bip32.js
@ -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;
|
|
||||||
@ -4,9 +4,10 @@ var BN = require('./bn');
|
|||||||
var elliptic = require('elliptic');
|
var elliptic = require('elliptic');
|
||||||
|
|
||||||
var ec = elliptic.curves.secp256k1;
|
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 p = ec.curve.point();
|
||||||
var Curve = Object.getPrototypeOf(ec.curve);
|
|
||||||
|
var bufferUtil = require('../util/buffer');
|
||||||
|
|
||||||
var Point = function Point(x, y, isRed) {
|
var Point = function Point(x, y, isRed) {
|
||||||
return ecpoint(x, y, isRed);
|
return ecpoint(x, y, isRed);
|
||||||
@ -27,7 +28,6 @@ Point.getN = function() {
|
|||||||
|
|
||||||
Point.prototype._getX = Point.prototype.getX;
|
Point.prototype._getX = Point.prototype.getX;
|
||||||
Point.prototype.getX = function() {
|
Point.prototype.getX = function() {
|
||||||
var n = BN(this._getX().toArray());
|
|
||||||
return 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
|
//https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf
|
||||||
Point.prototype.validate = function() {
|
Point.prototype.validate = function() {
|
||||||
|
/* jshint maxcomplexity: 8 */
|
||||||
var p2 = Point.fromX(this.getY().isOdd(), this.getX());
|
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');
|
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');
|
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');
|
throw new Error('Point times N must be infinity');
|
||||||
|
}
|
||||||
return this;
|
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;
|
module.exports = Point;
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
var bs58 = require('bs58');
|
var bs58 = require('bs58');
|
||||||
|
var buffer = require('buffer');
|
||||||
|
|
||||||
|
var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.split('');
|
||||||
|
|
||||||
var Base58 = function Base58(obj) {
|
var Base58 = function Base58(obj) {
|
||||||
if (!(this instanceof Base58))
|
/* jshint maxcomplexity: 8 */
|
||||||
|
if (!(this instanceof Base58)) {
|
||||||
return new Base58(obj);
|
return new Base58(obj);
|
||||||
|
}
|
||||||
if (Buffer.isBuffer(obj)) {
|
if (Buffer.isBuffer(obj)) {
|
||||||
var buf = obj;
|
var buf = obj;
|
||||||
this.fromBuffer(buf);
|
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) {
|
Base58.prototype.set = function(obj) {
|
||||||
this.buf = obj.buf || this.buf || undefined;
|
this.buf = obj.buf || this.buf || undefined;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Base58.encode = function(buf) {
|
Base58.encode = function(buf) {
|
||||||
if (!Buffer.isBuffer(buf))
|
if (!buffer.Buffer.isBuffer(buf)) {
|
||||||
throw new Error('Input should be a buffer');
|
throw new Error('Input should be a buffer');
|
||||||
|
}
|
||||||
return bs58.encode(buf);
|
return bs58.encode(buf);
|
||||||
};
|
};
|
||||||
|
|
||||||
Base58.decode = function(str) {
|
Base58.decode = function(str) {
|
||||||
if (typeof str !== 'string')
|
if (typeof str !== 'string') {
|
||||||
throw new Error('Input should be a string');
|
throw new Error('Input should be a string');
|
||||||
|
}
|
||||||
return new Buffer(bs58.decode(str));
|
return new Buffer(bs58.decode(str));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
var base58 = require('./base58');
|
var base58 = require('./base58');
|
||||||
|
var buffer = require('buffer');
|
||||||
var sha256sha256 = require('../crypto/hash').sha256sha256;
|
var sha256sha256 = require('../crypto/hash').sha256sha256;
|
||||||
|
|
||||||
var Base58Check = function Base58Check(obj) {
|
var Base58Check = function Base58Check(obj) {
|
||||||
@ -22,6 +24,20 @@ Base58Check.prototype.set = function(obj) {
|
|||||||
return this;
|
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) {
|
Base58Check.decode = function(s) {
|
||||||
if (typeof s !== 'string')
|
if (typeof s !== 'string')
|
||||||
throw new Error('Input must be a string');
|
throw new Error('Input must be a string');
|
||||||
@ -43,11 +59,15 @@ Base58Check.decode = function(s) {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Base58Check.checksum = function(buffer) {
|
||||||
|
return sha256sha256(buffer).slice(0, 4);
|
||||||
|
};
|
||||||
|
|
||||||
Base58Check.encode = function(buf) {
|
Base58Check.encode = function(buf) {
|
||||||
if (!Buffer.isBuffer(buf))
|
if (!Buffer.isBuffer(buf))
|
||||||
throw new Error('Input must be a buffer');
|
throw new Error('Input must be a buffer');
|
||||||
var checkedBuf = new Buffer(buf.length + 4);
|
var checkedBuf = new Buffer(buf.length + 4);
|
||||||
var hash = sha256sha256(buf);
|
var hash = Base58Check.checksum(buf);
|
||||||
buf.copy(checkedBuf);
|
buf.copy(checkedBuf);
|
||||||
hash.copy(checkedBuf, buf.length);
|
hash.copy(checkedBuf, buf.length);
|
||||||
return base58.encode(checkedBuf);
|
return base58.encode(checkedBuf);
|
||||||
|
|||||||
55
lib/errors/build.js
Normal file
55
lib/errors/build.js
Normal file
@ -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));
|
||||||
85
lib/errors/spec.js
Normal file
85
lib/errors/spec.js
Normal file
@ -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'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}];
|
||||||
45
lib/hdkeycache.js
Normal file
45
lib/hdkeycache.js
Normal file
@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
451
lib/hdprivatekey.js
Normal file
451
lib/hdprivatekey.js
Normal file
@ -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 <tt>toObject</tt>
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
411
lib/hdpublickey.js
Normal file
411
lib/hdpublickey.js
Normal file
@ -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 <tt>toObject</tt> 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;
|
||||||
@ -66,6 +66,7 @@ function getNetwork(arg) {
|
|||||||
* @namespace Network
|
* @namespace Network
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
defaultNetwork: livenet,
|
||||||
livenet: livenet,
|
livenet: livenet,
|
||||||
testnet: testnet,
|
testnet: testnet,
|
||||||
mainnet: livenet,
|
mainnet: livenet,
|
||||||
|
|||||||
@ -123,7 +123,7 @@ PrivateKey._transformBuffer = function(buf, network, compressed) {
|
|||||||
throw new Error('Invalid network');
|
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');
|
throw TypeError('Private key network mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,6 @@ var PublicKey = function PublicKey(data, compressed) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* Internal function to transform a private key into a public key point
|
* Internal function to transform a private key into a public key point
|
||||||
*
|
*
|
||||||
* @param {PrivateKey} privkey - An instance of PrivateKey
|
* @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
|
* Internal function to transform DER into a public key point
|
||||||
*
|
*
|
||||||
* @param {Buffer} buf - An hex encoded buffer
|
* @param {Buffer} buf - An hex encoded buffer
|
||||||
|
|||||||
18
lib/util/bitcoin.js
Normal file
18
lib/util/bitcoin.js
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
109
lib/util/buffer.js
Normal file
109
lib/util/buffer.js
Normal file
@ -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 <tt>buffer.Buffer.concat</tt>
|
||||||
|
*/
|
||||||
|
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 <tt>buffer.toString('hex')</tt>
|
||||||
|
*
|
||||||
|
* @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 <tt>Buffer(string, 'hex')</tt>
|
||||||
|
*
|
||||||
|
* @param {string} string
|
||||||
|
* @return {Buffer}
|
||||||
|
*/
|
||||||
|
hexToBuffer: function hexToBuffer(string) {
|
||||||
|
assert(js.isHexa(string));
|
||||||
|
return new buffer.Buffer(string, 'hex');
|
||||||
|
}
|
||||||
|
};
|
||||||
35
lib/util/js.js
Normal file
35
lib/util/js.js
Normal file
@ -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
|
||||||
|
};
|
||||||
@ -75,6 +75,7 @@
|
|||||||
"bs58": "=2.0.0",
|
"bs58": "=2.0.0",
|
||||||
"elliptic": "=0.15.14",
|
"elliptic": "=0.15.14",
|
||||||
"hash.js": "=0.3.2",
|
"hash.js": "=0.3.2",
|
||||||
|
"inherits": "=2.0.1",
|
||||||
"lodash": "=2.4.1",
|
"lodash": "=2.4.1",
|
||||||
"sha512": "=0.0.1"
|
"sha512": "=0.0.1"
|
||||||
},
|
},
|
||||||
@ -90,6 +91,7 @@
|
|||||||
"gulp-mocha": "^2.0.0",
|
"gulp-mocha": "^2.0.0",
|
||||||
"gulp-rename": "^1.2.0",
|
"gulp-rename": "^1.2.0",
|
||||||
"gulp-shell": "^0.2.10",
|
"gulp-shell": "^0.2.10",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
"lodash": "^2.4.1",
|
"lodash": "^2.4.1",
|
||||||
"mocha": "~2.0.1",
|
"mocha": "~2.0.1",
|
||||||
"run-sequence": "^1.0.2",
|
"run-sequence": "^1.0.2",
|
||||||
|
|||||||
361
test/bip32.js
361
test/bip32.js
@ -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');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
var should = require('chai').should();
|
var should = require('chai').should();
|
||||||
var bitcore = require('../..');
|
var bitcore = require('../..');
|
||||||
|
var buffer = require('buffer');
|
||||||
var Base58 = bitcore.encoding.Base58;
|
var Base58 = bitcore.encoding.Base58;
|
||||||
|
|
||||||
describe('Base58', function() {
|
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';
|
var enc = '1W7N4RuG';
|
||||||
|
|
||||||
it('should make an instance with "new"', function() {
|
it('should make an instance with "new"', function() {
|
||||||
@ -13,6 +14,21 @@ describe('Base58', function() {
|
|||||||
should.exist(b58);
|
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() {
|
it('should make an instance without "new"', function() {
|
||||||
var b58 = Base58();
|
var b58 = Base58();
|
||||||
should.exist(b58);
|
should.exist(b58);
|
||||||
@ -27,7 +43,7 @@ describe('Base58', function() {
|
|||||||
|
|
||||||
it('should set a blank buffer', function() {
|
it('should set a blank buffer', function() {
|
||||||
Base58().set({
|
Base58().set({
|
||||||
buf: new Buffer([])
|
buf: new buffer.Buffer([])
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,13 @@ describe('Base58Check', function() {
|
|||||||
should.exist(b58);
|
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() {
|
it('should make an instance without "new"', function() {
|
||||||
var b58 = Base58Check();
|
var b58 = Base58Check();
|
||||||
should.exist(b58);
|
should.exist(b58);
|
||||||
|
|||||||
50
test/hdkeycache.js
Normal file
50
test/hdkeycache.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
230
test/hdkeys.js
Normal file
230
test/hdkeys.js
Normal file
@ -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';
|
||||||
181
test/hdprivatekey.js
Normal file
181
test/hdprivatekey.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
193
test/hdpublickey.js
Normal file
193
test/hdpublickey.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user