From 60abff853abc533a95856454c37748304e9be17b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 12 Feb 2016 19:23:22 -0800 Subject: [PATCH] hd option parsing and network. --- lib/bcoin/hd.js | 163 +++++++++++++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 64 deletions(-) diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index fe7b294a..d678f0de 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -124,30 +124,24 @@ function HDPrivateKey(options) { assert(!(options instanceof HDPrivateKey)); assert(!(options instanceof HDPublicKey)); - if (options instanceof bcoin.hd.seed) - options = { seed: options }; - - if (options) { - if (options.xprivkey) - options.xkey = options.xprivkey; - if (options.xpubkey) - options.xkey = options.xpubkey; - } - - if (options) { - if (HDPublicKey.isExtended(options) || HDPublicKey.isExtended(options.xkey)) - return new HDPublicKey(options); - } - if (!options) options = { seed: bcoin.hd.seed() }; - // if (!options) - // options = this._generate(); - if (HDPrivateKey.isExtended(options)) options = { xkey: options }; + if (options.xpubkey) + options.xkey = options.xpubkey; + + if (options.xprivkey) + options.xkey = options.xprivkey; + + if (HDPublicKey.isExtended(options.xkey)) + return new HDPublicKey(options); + + if (options instanceof bcoin.hd.seed) + options = { seed: options }; + if (options.passphrase !== undefined || options.bits || options.entropy @@ -162,16 +156,21 @@ function HDPrivateKey(options) { options.seed = bcoin.hd.seed(options.seed); } + this.network = options.network || network.type; + if (options.seed) { this.seed = options.seed; data = this._seed(options.seed); } else if (options.xkey) { data = this._unbuild(options.xkey); + } else if (options.privateKey) { + data = this._generate(options.privateKey, options.chainCode); } else { - data = options; + data = options.data; } + assert(data); - data = this._normalize(data, network.prefixes.xprivkey); + data = this._normalize(data); this.data = data; @@ -199,7 +198,7 @@ HDPrivateKey.prototype.scan44 = function scan44(options, txByAddress, callback) coinType = options.coinType; if (coinType == null) - coinType = network.type === 'main' ? 0 : 1; + coinType = network[this.network].type === 'main' ? 0 : 1; assert(utils.isFinite(coinType)); @@ -240,6 +239,8 @@ HDPrivateKey.prototype.scan44 = function scan44(options, txByAddress, callback) if (txs) { if (typeof txs === 'boolean') result = txs; + else if (typeof txs === 'number') + result = txs > 0; else if (Array.isArray(txs)) result = txs.length > 0; else @@ -300,7 +301,7 @@ HDPrivateKey.prototype.deriveAccount44 = function deriveAccount44(options) { } if (coinType == null) - coinType = network.type === 'main' ? 0 : 1; + coinType = network[this.network].type === 'main' ? 0 : 1; assert(utils.isFinite(coinType)); assert(utils.isFinite(accountIndex)); @@ -387,6 +388,8 @@ HDPrivateKey.prototype.scan45 = function scan45(options, txByAddress, callback) if (txs) { if (typeof txs === 'boolean') result = txs; + else if (typeof txs === 'number') + result = txs > 0; else if (Array.isArray(txs)) result = txs.length > 0; else @@ -500,7 +503,7 @@ HDPrivateKey.getPath = function getPath(options) { purpose = 44; if (coinType == null) - coinType = network.type === 'main' ? 0 : 1; + coinType = network[this.network].type === 'main' ? 0 : 1; if (chain == null) chain = options.change ? 1 : 0; @@ -519,10 +522,12 @@ HDPrivateKey.isExtended = function isExtended(data) { return data.indexOf('xprv') === 0 || data.indexOf('tprv') === 0; }; -HDPrivateKey.prototype._normalize = function _normalize(data, version) { - data.version = version || network.prefixes.xprivkey; - data.privateKey = data.privateKey || data.priv; - data.publicKey = data.publicKey || data.pub; +HDPrivateKey.prototype._normalize = function _normalize(data) { + if (!data.version) { + data.version = (this instanceof HDPrivateKey) + ? network[this.network].prefixes.xprivkey + : network[this.network].prefixes.xpubkey; + } // version = uint_32be if (typeof data.version === 'string') @@ -581,6 +586,8 @@ HDPrivateKey.prototype._normalize = function _normalize(data, version) { }; HDPrivateKey.prototype._seed = function _seed(seed) { + var hash; + if (seed instanceof HDSeed) seed = seed.seed; @@ -592,10 +599,10 @@ HDPrivateKey.prototype._seed = function _seed(seed) { throw new Error('entropy not in range'); } - var hash = utils.sha512hmac(seed, 'Bitcoin seed'); + hash = utils.sha512hmac(seed, 'Bitcoin seed'); return { - version: null, + version: network[this.network].prefixes.xprivkey, depth: 0, parentFingerPrint: 0, childIndex: 0, @@ -605,7 +612,7 @@ HDPrivateKey.prototype._seed = function _seed(seed) { }; }; -HDPrivateKey.prototype._generate = function _generate(privateKey) { +HDPrivateKey.prototype._generate = function _generate(privateKey, entropy) { if (!privateKey) privateKey = bcoin.ecdsa.genKeyPair().getPrivate().toArray(); @@ -614,12 +621,15 @@ HDPrivateKey.prototype._generate = function _generate(privateKey) { else if (utils.isBase58(privateKey)) privateKey = bcoin.keypair.fromSecret(privateKey).getPrivate().toArray(); + if (!entropy) + entropy = elliptic.rand(32); + return { - version: null, + version: network[this.network].prefixes.xprivkey, depth: 0, parentFingerPrint: 0, childIndex: 0, - chainCode: elliptic.rand(32), + chainCode: entropy, privateKey: privateKey, checksum: null }; @@ -651,6 +661,11 @@ HDPrivateKey.prototype._unbuild = function _unbuild(xkey) { if (data.checksum !== utils.readU32BE(hash, 0)) throw new Error('checksum mismatch'); + if (data.version === network.main.prefixes.xprivkey) + this.network = 'main'; + else + this.network = 'testnet'; + return data; }; @@ -693,21 +708,25 @@ HDPrivateKey.prototype._build = function _build(data) { this.publicKey = publicKey; this.hdPublicKey = new HDPublicKey({ - version: this.version, - depth: this.depth, - parentFingerPrint: this.parentFingerPrint, - childIndex: this.childIndex, - chainCode: this.chainCode, - checksum: this.checksum, - publicKey: this.publicKey + network: this.network, + data: { + version: network[this.network].prefixes.xpubkey, + depth: this.depth, + parentFingerPrint: this.parentFingerPrint, + childIndex: this.childIndex, + chainCode: this.chainCode, + checksum: this.checksum, + publicKey: this.publicKey + } }); + this.hdPrivateKey = this; this.xpubkey = this.hdPublicKey.xpubkey; this.pair = bcoin.ecdsa.keyPair({ priv: this.privateKey }); }; HDPrivateKey.prototype.derive = function derive(index, hardened) { - var cached, data, hash, leftPart, chainCode, privateKey; + var cached, data, hash, leftPart, chainCode, privateKey, child; if (typeof index === 'string') return this.deriveString(index); @@ -734,14 +753,17 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) { .mod(ec.curve.n) .toArray('be', 32); - var child = new HDPrivateKey({ - version: this.version, - depth: new bn(this.depth).toNumber() + 1, - parentFingerPrint: this.fingerPrint, - childIndex: index, - chainCode: chainCode, - privateKey: privateKey, - checksum: null + child = new HDPrivateKey({ + network: this.network, + data: { + version: this.version, + depth: new bn(this.depth).toNumber() + 1, + parentFingerPrint: this.fingerPrint, + childIndex: index, + chainCode: chainCode, + privateKey: privateKey, + checksum: null + } }); cache.set(this.xprivkey, index, child); @@ -894,22 +916,26 @@ function HDPublicKey(options) { assert(!(options instanceof HDPrivateKey)); assert(!(options instanceof HDPublicKey)); - assert(!options.xprivkey); + if (HDPublicKey.isExtended(options)) + options = { xkey: options }; + + if (options.xprivkey) + options.xkey = options.xprivkey; if (options.xpubkey) options.xkey = options.xpubkey; - if (HDPublicKey.isExtended(options)) - options = { xkey: options }; - - if (HDPrivateKey.isExtended(options) || HDPrivateKey.isExtended(options.xkey)) + if (HDPrivateKey.isExtended(options.xkey)) throw new Error('Cannot pass xprivkey into HDPublicKey'); + this.network = options.network || network.type; + data = options.xkey ? this._unbuild(options.xkey) - : options; + : options.data; + assert(data); - data = this._normalize(data, network.prefixes.xpubkey); + data = this._normalize(data); this.data = data; @@ -977,6 +1003,11 @@ HDPublicKey.prototype._unbuild = function _unbuild(xkey) { if (data.checksum !== utils.readU32BE(hash, 0)) throw new Error('checksum mismatch'); + if (data.version === network.main.prefixes.xpubkey) + this.network = 'main'; + else + this.network = 'testnet'; + return data; }; @@ -1015,6 +1046,7 @@ HDPublicKey.prototype._build = function _build(data) { this.publicKey = publicKey; this.checksum = null; + this.hdPublicKey = this; this.xpubkey = xpubkey; this.fingerPrint = fingerPrint; @@ -1023,7 +1055,7 @@ HDPublicKey.prototype._build = function _build(data) { }; HDPublicKey.prototype.derive = function derive(index, hardened) { - var cached, data, hash, leftPart, chainCode, pair, point, publicKey; + var cached, data, hash, leftPart, chainCode, pair, point, publicKey, child; if (typeof index === 'string') return this.deriveString(index); @@ -1048,14 +1080,17 @@ HDPublicKey.prototype.derive = function derive(index, hardened) { point = ec.curve.g.mul(leftPart).add(pair.pub); publicKey = bcoin.ecdsa.keyPair({ pub: point }).getPublic(true, 'array'); - var child = new HDPublicKey({ - version: this.version, - depth: new bn(this.depth).toNumber() + 1, - parentFingerPrint: this.fingerPrint, - childIndex: index, - chainCode: chainCode, - publicKey: publicKey, - checksum: null + child = new HDPublicKey({ + network: this.network, + data: { + version: this.version, + depth: new bn(this.depth).toNumber() + 1, + parentFingerPrint: this.fingerPrint, + childIndex: index, + chainCode: chainCode, + publicKey: publicKey, + checksum: null + } }); cache.set(this.xpubkey, index, child);