Esteban Ordano
10 years ago
9 changed files with 484 additions and 386 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,399 @@ |
|||||
|
'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 Network = require('./networks'); |
||||
|
var Point = require('./crypto/point'); |
||||
|
var PublicKey = require('./publickey'); |
||||
|
var Random = require('./crypto/random'); |
||||
|
|
||||
|
var assert = require('assert'); |
||||
|
var buffer = require('buffer'); |
||||
|
var util = require('./util'); |
||||
|
|
||||
|
var MINIMUM_ENTROPY_BITS = 128; |
||||
|
var BITS_TO_BYTES = 1/8; |
||||
|
var MAXIMUM_ENTROPY_BITS = 512; |
||||
|
|
||||
|
|
||||
|
function HDPublicKey(arg) { |
||||
|
/* jshint maxcomplexity: 12 */ |
||||
|
/* jshint maxstatements: 20 */ |
||||
|
if (arg instanceof HDPublicKey) { |
||||
|
return arg; |
||||
|
} |
||||
|
if (!(this instanceof HDPublicKey)) { |
||||
|
return new HDPublicKey(arg); |
||||
|
} |
||||
|
if (arg) { |
||||
|
if (_.isString(arg) || buffer.Buffer.isBuffer(arg)) { |
||||
|
if (HDPublicKey.isValidSerialized(arg)) { |
||||
|
this._buildFromSerialized(arg); |
||||
|
} else { |
||||
|
var error = HDPublicKey.getSerializedError(arg); |
||||
|
if (error === HDPublicKey.Errors.ArgumentIsPrivateExtended) { |
||||
|
return new HDPrivateKey(arg).hdPublicKey; |
||||
|
} |
||||
|
throw new Error(error); |
||||
|
} |
||||
|
} else { |
||||
|
if (_.isObject(arg)) { |
||||
|
if (arg instanceof HDPrivateKey) { |
||||
|
this._buildFromPrivate(arg); |
||||
|
} else { |
||||
|
this._buildFromObject(arg); |
||||
|
} |
||||
|
} else if (util.isValidJson(arg)) { |
||||
|
this._buildFromJson(arg); |
||||
|
} else { |
||||
|
throw new Error(HDPublicKey.Errors.UnrecognizedArgument); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
this._generateRandomly(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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 Error(HDPublicKey.Errors.InvalidDerivationArgument); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey.prototype._deriveWithNumber = function (index, hardened) { |
||||
|
if (hardened || index >= HDPublicKey.Hardened) { |
||||
|
throw new Error(HDPublicKey.Errors.InvalidIndexCantDeriveHardened); |
||||
|
} |
||||
|
|
||||
|
var indexBuffer = util.integerAsBuffer(index); |
||||
|
var data = buffer.Buffer.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)); |
||||
|
|
||||
|
return new HDPublicKey({ |
||||
|
network: this.network, |
||||
|
depth: this.depth + 1, |
||||
|
parentFingerPrint: this.fingerPrint, |
||||
|
childIndex: index, |
||||
|
chainCode: chainCode, |
||||
|
publicKey: publicKey |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
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 Error(HDPublicKey.Errors.InvalidPath); |
||||
|
} |
||||
|
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 !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 {HDPublicKey.Errors|null} |
||||
|
*/ |
||||
|
HDPublicKey.getSerializedError = function (data, network) { |
||||
|
/* jshint maxcomplexity: 10 */ |
||||
|
network = Network.get(network) || Network.defaultNetwork; |
||||
|
if (!(_.isString(data) || buffer.Buffer.isBuffer(data))) { |
||||
|
return HDPublicKey.Errors.InvalidArgument; |
||||
|
} |
||||
|
if (!Base58.validCharacters(data)) { |
||||
|
return HDPublicKey.Errors.InvalidB58Char; |
||||
|
} |
||||
|
try { |
||||
|
data = Base58Check.decode(data); |
||||
|
} catch (e) { |
||||
|
return HDPublicKey.Errors.InvalidB58Checksum; |
||||
|
} |
||||
|
if (data.length !== 78) { |
||||
|
return HDPublicKey.Errors.InvalidLength; |
||||
|
} |
||||
|
if (util.integerFromBuffer(data.slice(0, 4)) === network.xprivkey) { |
||||
|
return HDPublicKey.Errors.ArgumentIsPrivateExtended; |
||||
|
} |
||||
|
if (!_.isUndefined(network)) { |
||||
|
var error = HDPublicKey._validateNetwork(data, network); |
||||
|
if (error) { |
||||
|
return error; |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey._validateNetwork = function (data, network) { |
||||
|
network = Network.get(network); |
||||
|
if (!network) { |
||||
|
return HDPublicKey.Errors.InvalidNetworkArgument; |
||||
|
} |
||||
|
var version = data.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd); |
||||
|
if (util.integerFromBuffer(version) !== network.xpubkey) { |
||||
|
return HDPublicKey.Errors.InvalidNetwork; |
||||
|
} |
||||
|
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 = util.pointToCompressed(point); |
||||
|
args.version = util.integerAsBuffer(Network.get(util.integerFromBuffer(args.version)).xpubkey); |
||||
|
args.privateKey = undefined; |
||||
|
args.checksum = undefined; |
||||
|
args.xprivkey = undefined; |
||||
|
return this._buildFromBuffers(args); |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey.prototype._buildFromObject = function (arg) { |
||||
|
/* jshint maxcomplexity: 8 */ |
||||
|
// TODO: Type validation
|
||||
|
var buffers = { |
||||
|
version: util.integerAsBuffer(Network.get(arg.network).xpubkey), |
||||
|
depth: util.integerAsSingleByteBuffer(arg.depth), |
||||
|
parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? util.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint, |
||||
|
childIndex: util.integerAsBuffer(arg.childIndex), |
||||
|
chainCode: _.isString(arg.chainCode) ? util.hexToBuffer(arg.chainCode) : arg.chainCode, |
||||
|
publicKey: _.isString(arg.publicKey) ? util.hexToBuffer(arg.publicKey) : |
||||
|
buffer.Buffer.isBuffer(arg.publicKey) ? arg.publicKey : arg.publicKey.toBuffer(), |
||||
|
checksum: arg.checksum && arg.checksum.length ? util.integerAsBuffer(arg.checksum) : undefined |
||||
|
}; |
||||
|
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); |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey.prototype._generateRandomly = function (network) { |
||||
|
return HDPublicKey.fromSeed(Random.getRandomBytes(64), network); |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey.fromSeed = function (hexa, network) { |
||||
|
/* jshint maxcomplexity: 8 */ |
||||
|
|
||||
|
if (util.isHexaString(hexa)) { |
||||
|
hexa = util.hexToBuffer(hexa); |
||||
|
} |
||||
|
if (!Buffer.isBuffer(hexa)) { |
||||
|
throw new Error(HDPublicKey.InvalidEntropyArg); |
||||
|
} |
||||
|
if (hexa.length < MINIMUM_ENTROPY_BITS * BITS_TO_BYTES) { |
||||
|
throw new Error(HDPublicKey.NotEnoughEntropy); |
||||
|
} |
||||
|
if (hexa.length > MAXIMUM_ENTROPY_BITS * BITS_TO_BYTES) { |
||||
|
throw new Error('More than 512 bytes of entropy is nonstandard'); |
||||
|
} |
||||
|
var hash = Hash.sha512hmac(hexa, new buffer.Buffer('Bitcoin seed')); |
||||
|
|
||||
|
return new HDPublicKey({ |
||||
|
network: Network.get(network) || Network.livenet, |
||||
|
depth: 0, |
||||
|
parentFingerPrint: 0, |
||||
|
childIndex: 0, |
||||
|
publicKey: hash.slice(0, 32), |
||||
|
chainCode: hash.slice(32, 64) |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Receives a object with buffers in all the properties and populates the |
||||
|
* internal structure |
||||
|
* |
||||
|
* @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 */ |
||||
|
|
||||
|
HDPublicKey._validateBufferArguments(arg); |
||||
|
this._buffers = arg; |
||||
|
|
||||
|
var sequence = [ |
||||
|
arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode, |
||||
|
arg.publicKey |
||||
|
]; |
||||
|
if (!arg.checksum || !arg.checksum.length) { |
||||
|
arg.checksum = Base58Check.checksum(buffer.Buffer.concat(sequence)); |
||||
|
} else { |
||||
|
if (arg.checksum.toString() !== sequence.toString()) { |
||||
|
throw new Error(HDPublicKey.Errors.InvalidB58Checksum); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!arg.xpubkey) { |
||||
|
this.xpubkey = Base58Check.encode(buffer.Buffer.concat(sequence)); |
||||
|
} else { |
||||
|
this.xpubkey = arg.xpubkey; |
||||
|
} |
||||
|
|
||||
|
this.network = Network.get(util.integerFromBuffer(arg.version)); |
||||
|
this.depth = util.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(buffer.Buffer.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); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey.prototype.toString = function () { |
||||
|
return this.xpubkey; |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey.prototype.toObject = function () { |
||||
|
return { |
||||
|
network: Network.get(util.integerFromBuffer(this._buffers.version)), |
||||
|
depth: util.integerFromSingleByteBuffer(this._buffers.depth), |
||||
|
fingerPrint: util.integerFromBuffer(this.fingerPrint), |
||||
|
parentFingerPrint: util.integerFromBuffer(this._buffers.parentFingerPrint), |
||||
|
childIndex: util.integerFromBuffer(this._buffers.childIndex), |
||||
|
chainCode: util.bufferToHex(this._buffers.chainCode), |
||||
|
publicKey: this.publicKey.toString(), |
||||
|
checksum: util.integerFromBuffer(this._buffers.checksum), |
||||
|
xpubkey: this.xpubkey |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey.prototype.toJson = function () { |
||||
|
return JSON.stringify(this.toObject()); |
||||
|
}; |
||||
|
|
||||
|
HDPublicKey.DefaultDepth = 0; |
||||
|
HDPublicKey.DefaultFingerprint = 0; |
||||
|
HDPublicKey.DefaultChildIndex = 0; |
||||
|
HDPublicKey.DefaultNetwork = Network.livenet; |
||||
|
HDPublicKey.Hardened = 0x80000000; |
||||
|
HDPublicKey.RootElementAlias = ['m', 'M', 'm\'', 'M\'']; |
||||
|
|
||||
|
HDPublicKey.VersionSize = 4; |
||||
|
HDPublicKey.DepthSize = 1; |
||||
|
HDPublicKey.ParentFingerPrintSize = 4; |
||||
|
HDPublicKey.ChildIndexSize = 4; |
||||
|
HDPublicKey.ChainCodeSize = 32; |
||||
|
HDPublicKey.PublicKeySize = 33; |
||||
|
HDPublicKey.CheckSumSize = 4; |
||||
|
|
||||
|
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.ChecksumEnd === HDPublicKey.SerializedByteSize); |
||||
|
|
||||
|
HDPublicKey.Errors = {}; |
||||
|
HDPublicKey.Errors.ArgumentIsPrivateExtended = 'Argument starts with xpriv..., it\'s a private key'; |
||||
|
HDPublicKey.Errors.InvalidArgument = 'Invalid argument, expected string or Buffer'; |
||||
|
HDPublicKey.Errors.InvalidB58Char = 'Invalid Base 58 character'; |
||||
|
HDPublicKey.Errors.InvalidB58Checksum = 'Invalid Base 58 checksum'; |
||||
|
HDPublicKey.Errors.InvalidChildIndex = 'Invalid Child Index - must be a number'; |
||||
|
HDPublicKey.Errors.InvalidConstant = 'Unrecognized xpubkey version'; |
||||
|
HDPublicKey.Errors.InvalidDepth = 'Invalid depth parameter - must be a number'; |
||||
|
HDPublicKey.Errors.InvalidDerivationArgument = 'Invalid argument, expected number and boolean or string'; |
||||
|
HDPublicKey.Errors.InvalidEntropyArg = 'Invalid argument: entropy must be an hexa string or binary buffer'; |
||||
|
HDPublicKey.Errors.InvalidLength = 'Invalid length for xpubkey format'; |
||||
|
HDPublicKey.Errors.InvalidNetwork = 'Unexpected version for network'; |
||||
|
HDPublicKey.Errors.InvalidNetworkArgument = 'Network argument must be \'livenet\' or \'testnet\''; |
||||
|
HDPublicKey.Errors.InvalidParentFingerPrint = 'Invalid Parent Fingerprint - must be a number'; |
||||
|
HDPublicKey.Errors.InvalidPath = 'Invalid path for derivation: must start with "m"'; |
||||
|
HDPublicKey.Errors.UnrecognizedArgument = 'Creating a HDPublicKey requires a string, a buffer, a json, or an object'; |
||||
|
|
||||
|
module.exports = HDPublicKey; |
||||
|
|
Loading…
Reference in new issue