From 9b81126ea46446a27d8d0b673f570ba0129f4c0a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 25 Jun 2016 03:40:54 -0700 Subject: [PATCH] hd refactor. --- lib/bcoin/hd.js | 331 +++++++++++++++++++++++++++++++----------------- test/hd-test.js | 4 +- 2 files changed, 216 insertions(+), 119 deletions(-) diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 006203cf..64dd63a4 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -118,6 +118,24 @@ function Mnemonic(options) { if (typeof options === 'string') options = { phrase: options }; + this.bits = 128; + this.entropy = null; + this.phrase = null; + this.passphrase = ''; + this.language = 'english'; + this.seed = null; + + if (options) + this.fromOptions(options); +} + +Mnemonic.prototype.fromOptions = function fromOptions(options) { + if (!options) + options = {}; + + if (typeof options === 'string') + options = { phrase: options }; + this.bits = options.bits || 128; this.entropy = options.entropy; this.phrase = options.phrase; @@ -127,7 +145,66 @@ function Mnemonic(options) { assert(this.bits >= 128); assert(this.bits % 32 === 0); -} +}; + +Mnemonic.fromOptions = function fromOptions(options) { + return new Mnemonic().fromOptions(options); +}; + +Mnemonic.prototype.toJSON = function toJSON() { + return { + bits: this.bits, + entropy: this.entropy ? this.entropy.toString('hex') : null, + phrase: this.phrase, + passphrase: this.passphrase, + language: this.language, + seed: this.seed ? this.seed.toString('hex') : null + }; +}; + +Mnemonic.prototype.fromJSON = function fromJSON(json) { + this.bits = json.bits; + if (json.entropy) + this.entropy = new Buffer(json.entropy, 'hex'); + this.phrase = json.phrase; + this.passphrase = json.passphrase; + this.language = json.language; + if (json.seed) + this.seed = new Buffer(json.seed, 'hex'); + return this; +}; + +Mnemonic.fromJSON = function fromJSON(json) { + return new Mnemonic().fromJSON(json); +}; + +Mnemonic.prototype.toRaw = function toRaw(writer) { + var p = new BufferWriter(writer); + p.writeU16(this.bits); + p.writeBytes(this.entropy); + p.writeVarString(this.phrase, 'utf8'); + p.writeVarString(this.passphrase, 'utf8'); + p.writeVarString(this.language, 'utf8'); + p.writeBytes(this.seed); + if (!writer) + p = p.render(); + return p; +}; + +Mnemonic.prototype.fromRaw = function fromRaw(data) { + var p = new BufferReader(data); + this.bits = p.readU16(); + this.entropy = p.readBytes(this.bits / 8); + this.phrase = p.readVarString('utf8'); + this.passphrase = p.readVarString('utf8'); + this.language = p.readVarString('utf8'); + this.seed = p.readBytes(64); + return this; +}; + +Mnemonic.fromRaw = function fromRaw(data) { + return new Mnemonic().fromRaw(data); +}; /** * Generate the seed. @@ -471,6 +548,30 @@ function HDPrivateKey(options) { if (!(this instanceof HDPrivateKey)) return new HDPrivateKey(options); + this.network = null; + this.depth = null; + this.parentFingerPrint = null; + this.childIndex = null; + this.chainCode = null; + this.privateKey = null; + + this.publicKey = null; + this.fingerPrint = null; + + this.mnemonic = null; + + this._xprivkey = null; + + this.hdPrivateKey = this; + this._hdPublicKey = null; + + if (options) + this.fromOptions(options); +} + +utils.inherits(HDPrivateKey, HD); + +HDPrivateKey.prototype.fromOptions = function fromOptions(options) { assert(options, 'No options for HD private key.'); assert(options.depth <= 0xff, 'Depth is too high.'); @@ -482,17 +583,16 @@ function HDPrivateKey(options) { this.privateKey = options.privateKey; this.publicKey = ec.publicKeyCreate(options.privateKey, true); - this.fingerPrint = null; this.mnemonic = options.mnemonic || null; - this._xprivkey = options.xprivkey || null; - this.hdPrivateKey = this; - this._hdPublicKey = null; -} + return this; +}; -utils.inherits(HDPrivateKey, HD); +HDPrivateKey.fromOptions = function fromOptions(options) { + return new HDPrivateKey().fromOptions(options); +}; HDPrivateKey.prototype.__defineGetter__('hdPublicKey', function() { if (!this._hdPublicKey) { @@ -755,7 +855,7 @@ HDPrivateKey.prototype.equal = function equal(obj) { * for passing to the HDPrivateKey constructor). */ -HDPrivateKey.parseSeed = function parseSeed(seed, network) { +HDPrivateKey.prototype.fromSeed = function fromSeed(seed, network) { var hash, chainCode, privateKey; assert(Buffer.isBuffer(seed)); @@ -773,14 +873,15 @@ HDPrivateKey.parseSeed = function parseSeed(seed, network) { if (!ec.privateKeyVerify(privateKey)) throw new Error('Master private key is invalid.'); - return { - network: network, - depth: 0, - parentFingerPrint: new Buffer([0, 0, 0, 0]), - childIndex: 0, - chainCode: chainCode, - privateKey: privateKey - }; + this.network = bcoin.network.get(network).type; + this.depth = 0; + this.parentFingerPrint = new Buffer([0, 0, 0, 0]); + this.childIndex = 0; + this.chainCode = chainCode; + this.privateKey = privateKey; + this.publicKey = ec.publicKeyCreate(this.privateKey, true); + + return this; }; /** @@ -792,7 +893,7 @@ HDPrivateKey.parseSeed = function parseSeed(seed, network) { */ HDPrivateKey.fromSeed = function fromSeed(seed, network) { - return new HDPrivateKey(HDPrivateKey.parseSeed(seed, network)); + return new HDPrivateKey().fromSeed(seed, network); }; /** @@ -802,25 +903,24 @@ HDPrivateKey.fromSeed = function fromSeed(seed, network) { * @returns {HDPrivateKey} */ -HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) { +HDPrivateKey.prototype.fromMnemonic = function fromMnemonic(mnemonic, network) { var key; if (!(mnemonic instanceof Mnemonic)) mnemonic = new Mnemonic(mnemonic); if (mnemonic.seed || mnemonic.phrase || mnemonic.entropy) { - key = HDPrivateKey.parseSeed(mnemonic.toSeed(), network); - key.mnemonic = mnemonic; - return new HDPrivateKey(key); + this.fromSeed(mnemonic.toSeed(), network); + this.mnemonic = mnemonic; + return this; } // Very unlikely, but not impossible // to get an invalid private key. for (;;) { try { - key = HDPrivateKey.parseSeed(mnemonic.toSeed(), network); - key.mnemonic = mnemonic; - key = new HDPrivateKey(key); + this.fromSeed(mnemonic.toSeed(), network); + this.mnemonic = mnemonic; } catch (e) { if (e.message === 'Master private key is invalid.') { mnemonic.seed = null; @@ -833,7 +933,11 @@ HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) { break; } - return key; + return this; +}; + +HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) { + return new HDPrivateKey().fromMnemonic(mnemonic, network); }; /** @@ -847,7 +951,7 @@ HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) { * for passing to the HDPrivateKey constructor). */ -HDPrivateKey._generate = function _generate(options, network) { +HDPrivateKey.prototype.generate = function _generate(options, network) { var privateKey, entropy; if (!options) @@ -865,14 +969,15 @@ HDPrivateKey._generate = function _generate(options, network) { if (!entropy) entropy = ec.random(32); - return { - network: network, - depth: 0, - parentFingerPrint: new Buffer([0, 0, 0, 0]), - childIndex: 0, - chainCode: entropy, - privateKey: privateKey - }; + this.network = bcoin.network.get(network).type; + this.depth = 0; + this.parentFingerPrint = new Buffer([0, 0, 0, 0]); + this.childIndex = 0; + this.chainCode = entropy; + this.privateKey = privateKey; + this.publicKey = ec.publicKeyCreate(this.privateKey, true); + + return this; }; /** @@ -885,7 +990,7 @@ HDPrivateKey._generate = function _generate(options, network) { */ HDPrivateKey.generate = function generate(options, network) { - return new HDPrivateKey(HDPrivateKey._generate(options, network)); + return new HDPrivateKey().generate(options, network); }; /** @@ -894,10 +999,10 @@ HDPrivateKey.generate = function generate(options, network) { * @returns {Object} */ -HDPrivateKey.parseBase58 = function parseBase58(xkey) { - var data = HDPrivateKey.parseRaw(utils.fromBase58(xkey)); - data.xprivkey = xkey; - return data; +HDPrivateKey.prototype.fromBase58 = function fromBase58(xkey) { + this.fromRaw(utils.fromBase58(xkey)); + this._xprivkey = xkey; + return this; }; /** @@ -906,32 +1011,32 @@ HDPrivateKey.parseBase58 = function parseBase58(xkey) { * @returns {Object} */ -HDPrivateKey.parseRaw = function parseRaw(raw) { +HDPrivateKey.prototype.fromRaw = function fromRaw(raw) { var p = new BufferReader(raw); - var data = {}; var i, type, prefix; - data.version = p.readU32BE(); - data.depth = p.readU8(); - data.parentFingerPrint = p.readBytes(4); - data.childIndex = p.readU32BE(); - data.chainCode = p.readBytes(32); + this.version = p.readU32BE(); + this.depth = p.readU8(); + this.parentFingerPrint = p.readBytes(4); + this.childIndex = p.readU32BE(); + this.chainCode = p.readBytes(32); p.readU8(); - data.privateKey = p.readBytes(32); + this.privateKey = p.readBytes(32); p.verifyChecksum(); for (i = 0; i < network.types.length; i++) { type = network.types[i]; prefix = network[type].prefixes.xprivkey; - if (data.version === prefix) + if (this.version === prefix) break; } assert(i < network.types.length, 'Network not found.'); - data.network = type; + this.publicKey = ec.publicKeyCreate(this.privateKey, true); + this.network = type; - return data; + return this; }; /** @@ -980,7 +1085,7 @@ HDPrivateKey.prototype.toRaw = function toRaw(network, writer) { */ HDPrivateKey.fromBase58 = function fromBase58(xkey) { - return new HDPrivateKey(HDPrivateKey.parseBase58(xkey)); + return new HDPrivateKey().fromBase58(xkey); }; /** @@ -990,7 +1095,7 @@ HDPrivateKey.fromBase58 = function fromBase58(xkey) { */ HDPrivateKey.fromRaw = function fromRaw(raw) { - return new HDPrivateKey(HDPrivateKey.parseRaw(raw)); + return new HDPrivateKey().fromRaw(raw); }; /** @@ -999,22 +1104,10 @@ HDPrivateKey.fromRaw = function fromRaw(raw) { */ HDPrivateKey.prototype.toJSON = function toJSON() { - var json = { - network: this.network + return { + xprivkey: this.xprivkey, + mnemonic: this.mnemonic ? this.mnemonic.toJSON() : null }; - - if (this instanceof HDPrivateKey) { - if (this.mnemonic) { - json.phrase = this.mnemonic.phrase; - json.passphrase = this.mnemonic.passphrase; - } - json.xprivkey = this.xprivkey; - return json; - } - - json.xpubkey = this.xpubkey; - - return json; }; /** @@ -1023,21 +1116,15 @@ HDPrivateKey.prototype.toJSON = function toJSON() { * @returns {Object} A "naked" HDPrivateKey. */ -HDPrivateKey.parseJSON = function parseJSON(json) { - var data = {}; - - if (json.phrase) { - data.mnemonic = { - phrase: json.phrase, - passphrase: json.passphrase - }; - } - +HDPrivateKey.prototype.fromJSON = function fromJSON(json) { assert(json.xprivkey, 'Could not handle key JSON.'); - data.xprivkey = json.xprivkey; + if (json.mnemonic) + this.mnemonic = Mnemonic.fromJSON(json.mnemonic); - return data; + this.fromBase58(json.xprivkey); + + return this; }; /** @@ -1047,11 +1134,7 @@ HDPrivateKey.parseJSON = function parseJSON(json) { */ HDPrivateKey.fromJSON = function fromJSON(json) { - var key; - json = HDPrivateKey.parseJSON(json); - key = HDPrivateKey.fromBase58(json.xprivkey); - key.mnemonic = json.mnemonic ? new Mnemonic(json.mnemonic) : null; - return key; + return new HDPrivateKey().fromJSON(json); }; /** @@ -1091,6 +1174,28 @@ function HDPublicKey(options) { if (!(this instanceof HDPublicKey)) return new HDPublicKey(options); + this.network = null; + this.depth = null; + this.parentFingerPrint = null; + this.childIndex = null; + this.chainCode = null; + this.publicKey = null; + + this.privateKey = null; + this.fingerPrint = null; + + this._xpubkey = null; + + this.hdPublicKey = this; + this.hdPrivateKey = null; + + if (options) + this.fromOptions(options); +} + +utils.inherits(HDPublicKey, HD); + +HDPublicKey.prototype.fromOptions = function fromOptions(options) { assert(options, 'No options for HDPublicKey'); assert(options.depth <= 0xff, 'Depth is too high.'); @@ -1101,17 +1206,14 @@ function HDPublicKey(options) { this.chainCode = options.chainCode; this.publicKey = options.publicKey; - this.privateKey = null; - this.fingerPrint = null; - - this.xprivkey = null; this._xpubkey = options.xpubkey; - this.hdPublicKey = this; - this.hdPrivateKey = null; -} + return this; +}; -utils.inherits(HDPublicKey, HD); +HDPublicKey.fromOptions = function fromOptions(options) { + return new HDPublicKey().fromOptions(options); +}; HDPublicKey.prototype.__defineGetter__('xpubkey', function() { if (!this._xpubkey) @@ -1292,23 +1394,20 @@ HDPublicKey.prototype.equal = function equal(obj) { HDPublicKey.prototype.toJSON = function toJSON() { return { - network: this.network, xpubkey: this.xpubkey }; }; - /** * Handle a deserialized JSON HDPublicKey object. * @param {Object} json * @returns {Object} A "naked" HDPublicKey. */ -HDPublicKey.parseJSON = function parseJSON(json) { +HDPublicKey.prototype.fromJSON = function fromJSON(json) { assert(json.xpubkey, 'Could not handle HD key JSON.'); - return { - xpubkey: json.xpubkey - }; + this.fromBase58(json.xpubkey); + return this; }; /** @@ -1319,8 +1418,7 @@ HDPublicKey.parseJSON = function parseJSON(json) { */ HDPublicKey.fromJSON = function fromJSON(json) { - json = HDPrivateKey.parseJSON(json); - return HDPublicKey.fromBase58(json.xpubkey); + return new HDPublicKey().fromJSON(json); }; /** @@ -1375,10 +1473,10 @@ HDPublicKey.hasPrefix = function hasPrefix(data) { * @returns {Object} */ -HDPublicKey.parseBase58 = function parseBase58(xkey) { - var data = HDPublicKey.parseRaw(utils.fromBase58(xkey)); - data.xpubkey = xkey; - return data; +HDPublicKey.prototype.fromBase58 = function fromBase58(xkey) { + this.fromRaw(utils.fromBase58(xkey)); + this._xpubkey = xkey; + return this; }; /** @@ -1387,31 +1485,30 @@ HDPublicKey.parseBase58 = function parseBase58(xkey) { * @returns {Object} */ -HDPublicKey.parseRaw = function parseRaw(raw) { +HDPublicKey.prototype.fromRaw = function fromRaw(raw) { var p = new BufferReader(raw); - var data = {}; var i, type, prefix; - data.version = p.readU32BE(); - data.depth = p.readU8(); - data.parentFingerPrint = p.readBytes(4); - data.childIndex = p.readU32BE(); - data.chainCode = p.readBytes(32); - data.publicKey = p.readBytes(33); + this.version = p.readU32BE(); + this.depth = p.readU8(); + this.parentFingerPrint = p.readBytes(4); + this.childIndex = p.readU32BE(); + this.chainCode = p.readBytes(32); + this.publicKey = p.readBytes(33); p.verifyChecksum(); for (i = 0; i < network.types.length; i++) { type = network.types[i]; prefix = network[type].prefixes.xpubkey; - if (data.version === prefix) + if (this.version === prefix) break; } assert(i < network.types.length, 'Network not found.'); - data.network = type; + this.network = type; - return data; + return this; }; /** @@ -1459,7 +1556,7 @@ HDPublicKey.prototype.toRaw = function toRaw(network, writer) { */ HDPublicKey.fromBase58 = function fromBase58(xkey) { - return new HDPublicKey(HDPublicKey.parseBase58(xkey)); + return new HDPublicKey().fromBase58(xkey); }; /** @@ -1469,7 +1566,7 @@ HDPublicKey.fromBase58 = function fromBase58(xkey) { */ HDPublicKey.fromRaw = function fromRaw(data) { - return new HDPublicKey(HDPublicKey.parseRaw(data)); + return new HDPublicKey().fromRaw(data); }; /** diff --git a/test/hd-test.js b/test/hd-test.js index a496de50..f77df8ed 100644 --- a/test/hd-test.js +++ b/test/hd-test.js @@ -136,11 +136,11 @@ describe('HD', function() { }); it('should deserialize master private key', function() { - bcoin.hd.PrivateKey.parseBase58(master.xprivkey); + bcoin.hd.PrivateKey.fromBase58(master.xprivkey); }); it('should deserialize master public key', function() { - bcoin.hd.PublicKey.parseBase58(master.hdPublicKey.xpubkey); + bcoin.hd.PublicKey.fromBase58(master.hdPublicKey.xpubkey); }); it('should deserialize and reserialize', function() {