Esteban Ordano
10 years ago
27 changed files with 2000 additions and 713 deletions
@ -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; |
@ -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)); |
@ -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' |
|||
}] |
|||
} |
|||
] |
|||
}]; |
@ -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++; |
|||
} |
|||
} |
|||
}; |
@ -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; |
@ -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; |
@ -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; |
|||
} |
|||
}; |
@ -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'); |
|||
} |
|||
}; |
@ -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 |
|||
}; |
@ -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'); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
}); |
@ -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); |
|||
}); |
|||
}); |
@ -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'; |
@ -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); |
|||
}); |
|||
}); |
|||
|
@ -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