diff --git a/bench/mnemonic.js b/bench/mnemonic.js index 21662cb0..d4f1b1da 100644 --- a/bench/mnemonic.js +++ b/bench/mnemonic.js @@ -5,8 +5,9 @@ var bench = require('./bench'); var HD = require('../lib/hd'); var Mnemonic = require('../lib/hd/mnemonic'); -var key = HD.fromMnemonic(); -var phrase = key.mnemonic.getPhrase(); +var mnemonic = new Mnemonic(); +var key = HD.fromMnemonic(mnemonic); +var phrase = mnemonic.getPhrase(); var i, end; assert.equal(Mnemonic.fromPhrase(phrase).getPhrase(), phrase); diff --git a/lib/hd/private.js b/lib/hd/private.js index 16a70cb6..df4bc3d2 100644 --- a/lib/hd/private.js +++ b/lib/hd/private.js @@ -27,14 +27,12 @@ var HDPublicKey = require('./public'); * @constructor * @param {Object|Base58String} options * @param {Base58String?} options.xkey - Serialized base58 key. - * @param {Mnemonic?} options.mnemonic * @param {Number?} options.depth * @param {Buffer?} options.parentFingerPrint * @param {Number?} options.childIndex * @param {Buffer?} options.chainCode * @param {Buffer?} options.privateKey * @property {Network} network - * @property {Mnemonic?} mnemonic * @property {Number} depth * @property {Buffer} parentFingerPrint * @property {Number} childIndex @@ -56,8 +54,6 @@ function HDPrivateKey(options) { this.publicKey = encoding.ZERO_KEY; this.fingerPrint = null; - this.mnemonic = null; - this._xprivkey = null; this._hdPublicKey = null; @@ -91,16 +87,6 @@ HDPrivateKey.prototype.fromOptions = function fromOptions(options) { this.privateKey = options.privateKey; this.publicKey = ec.publicKeyCreate(options.privateKey, true); - if (options.mnemonic) { - assert(options.mnemonic instanceof Mnemonic); - this.mnemonic = options.mnemonic; - } - - if (options.xprivkey) { - assert(typeof options.xprivkey === 'string'); - this._xprivkey = options.xprivkey; - } - return this; }; @@ -194,11 +180,6 @@ HDPrivateKey.prototype.destroy = function destroy(pub) { } this._xprivkey = null; - - if (this.mnemonic) { - this.mnemonic.destroy(); - this.mnemonic = null; - } }; /** @@ -543,21 +524,19 @@ HDPrivateKey.fromSeed = function fromSeed(seed, network) { /** * Inject properties from a mnemonic. * @private - * @param {Mnemonic|Object} mnemonic + * @param {Mnemonic} mnemonic * @param {(Network|NetworkType)?} network */ HDPrivateKey.prototype.fromMnemonic = function fromMnemonic(mnemonic, network) { - if (!(mnemonic instanceof Mnemonic)) - mnemonic = new Mnemonic(mnemonic); + assert(mnemonic instanceof Mnemonic); this.fromSeed(mnemonic.toSeed(), network); - this.mnemonic = mnemonic; return this; }; /** * Instantiate an hd private key from a mnemonic. - * @param {Mnemonic|Object} mnemonic + * @param {Mnemonic} mnemonic * @param {(Network|NetworkType)?} network * @returns {HDPrivateKey} */ @@ -566,6 +545,30 @@ HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) { return new HDPrivateKey().fromMnemonic(mnemonic, network); }; +/** + * Inject properties from a mnemonic. + * @private + * @param {String} mnemonic + * @param {(Network|NetworkType)?} network + */ + +HDPrivateKey.prototype.fromPhrase = function fromPhrase(phrase, network) { + var mnemonic = Mnemonic.fromPhrase(phrase); + this.fromMnemonic(mnemonic, network); + return this; +}; + +/** + * Instantiate an hd private key from a phrase. + * @param {String} phrase + * @param {(Network|NetworkType)?} network + * @returns {HDPrivateKey} + */ + +HDPrivateKey.fromPhrase = function fromPhrase(phrase, network) { + return new HDPrivateKey().fromPhrase(phrase, network); +}; + /** * Inject properties from privateKey and entropy. * @private @@ -722,97 +725,6 @@ HDPrivateKey.prototype.toRaw = function toRaw(network) { return this.toWriter(new StaticWriter(82), network).render(); }; -/** - * Calculate extended serialization size. - * @returns {Number} - */ - -HDPrivateKey.prototype.getExtendedSize = function getSize() { - var size = this.getSize(); - - size += 1; - - if (this.mnemonic) - size += this.mnemonic.getSize(); - - return size; -}; - -/** - * Write the key in "extended" - * format to a buffer writer. - * @param {BufferWriter} bw - * @param {(Network|NetworkType)?} network - */ - -HDPrivateKey.prototype.toExtendedWriter = function toExtendedWriter(bw, network) { - this.toWriter(bw, network); - - if (this.mnemonic) { - bw.writeU8(1); - this.mnemonic.toWriter(bw); - } else { - bw.writeU8(0); - } - - return bw; -}; - -/** - * Serialize the key in "extended" - * format (includes the mnemonic). - * @param {(Network|NetworkType)?} network - * @returns {Buffer} - */ - -HDPrivateKey.prototype.toExtended = function toExtended(network) { - var size = this.getExtendedSize(); - return this.toExtendedWriter(new StaticWriter(size), network).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -HDPrivateKey.prototype.fromExtendedReader = function fromExtendedReader(br) { - this.fromReader(br); - if (br.readU8() === 1) - this.mnemonic = Mnemonic.fromReader(br); - return this; -}; - -/** - * Inject properties from extended serialized data. - * @private - * @param {Buffer} data - */ - -HDPrivateKey.prototype.fromExtended = function fromExtended(data) { - return this.fromExtendedReader(new BufferReader(data)); -}; - -/** - * Instantiate key from "extended" buffer reader. - * @param {BufferReader} br - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromExtendedReader = function fromExtendedReader(br) { - return new HDPrivateKey().fromExtendedReader(br); -}; - -/** - * Instantiate key from "extended" serialized data. - * @param {Buffer} data - * @returns {HDPrivateKey} - */ - -HDPrivateKey.fromExtended = function fromExtended(data) { - return new HDPrivateKey().fromExtended(data); -}; - /** * Instantiate an HD private key from a base58 string. * @param {Base58String} xkey @@ -851,8 +763,7 @@ HDPrivateKey.fromRaw = function fromRaw(raw) { HDPrivateKey.prototype.toJSON = function toJSON() { return { - xprivkey: this.xprivkey(), - mnemonic: this.mnemonic ? this.mnemonic.toJSON() : null + xprivkey: this.xprivkey() }; }; @@ -868,9 +779,6 @@ HDPrivateKey.prototype.fromJSON = function fromJSON(json, network) { this.fromBase58(json.xprivkey, network); - if (json.mnemonic) - this.mnemonic = Mnemonic.fromJSON(json.mnemonic); - return this; }; @@ -894,7 +802,7 @@ HDPrivateKey.fromJSON = function fromJSON(json, network) { HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) { return obj && typeof obj.derive === 'function' - && typeof obj.toExtended === 'function' + && typeof obj.fromMnemonic === 'function' && obj.chainCode !== undefined; }; diff --git a/lib/hd/public.js b/lib/hd/public.js index f6141c1d..a14c1889 100644 --- a/lib/hd/public.js +++ b/lib/hd/public.js @@ -79,11 +79,6 @@ HDPublicKey.prototype.fromOptions = function fromOptions(options) { this.chainCode = options.chainCode; this.publicKey = options.publicKey; - if (options.xpubkey) { - assert(typeof options.xpubkey === 'string'); - this._xpubkey = options.xpubkey; - } - return this; }; diff --git a/lib/wallet/masterkey.js b/lib/wallet/masterkey.js index 30807497..ca96a4b4 100644 --- a/lib/wallet/masterkey.js +++ b/lib/wallet/masterkey.js @@ -15,6 +15,7 @@ var BufferReader = require('../utils/reader'); var StaticWriter = require('../utils/staticwriter'); var encoding = require('../utils/encoding'); var HD = require('../hd/hd'); +var Mnemonic = HD.Mnemonic; /** * Master BIP32 key which can exist @@ -32,6 +33,7 @@ function MasterKey(options) { this.iv = null; this.ciphertext = null; this.key = null; + this.mnemonic = null; this.alg = MasterKey.alg.PBKDF2; this.N = 50000; @@ -103,10 +105,15 @@ MasterKey.prototype.fromOptions = function fromOptions(options) { } if (options.key) { - assert(HD.isHD(options.key)); + assert(HD.isPrivate(options.key)); this.key = options.key; } + if (options.mnemonic) { + assert(options.mnemonic instanceof Mnemonic); + this.mnemonic = options.mnemonic; + } + if (options.alg != null) { if (typeof options.alg === 'string') { this.alg = MasterKey.alg[options.alg.toUpperCase()]; @@ -195,7 +202,7 @@ MasterKey.prototype._unlock = co(function* _unlock(passphrase, timeout) { key = yield this.derive(passphrase); data = crypto.decipher(this.ciphertext, key, this.iv); - this.key = HD.fromExtended(data); + this.fromKeyRaw(data); this.start(timeout); @@ -382,7 +389,7 @@ MasterKey.prototype._decrypt = co(function* decrypt(passphrase, clean) { key = yield this.derive(passphrase); data = crypto.decipher(this.ciphertext, key, this.iv); - this.key = HD.fromExtended(data); + this.fromKeyRaw(data); this.encrypted = false; this.iv = null; this.ciphertext = null; @@ -426,7 +433,7 @@ MasterKey.prototype._encrypt = co(function* encrypt(passphrase, clean) { if (!passphrase) throw new Error('No passphrase provided.'); - data = this.key.toExtended(); + data = this.toKeyRaw(); iv = crypto.randomBytes(16); this.stop(); @@ -435,6 +442,7 @@ MasterKey.prototype._encrypt = co(function* encrypt(passphrase, clean) { data = crypto.encipher(data, key, iv); this.key = null; + this.mnemonic = null; this.encrypted = true; this.iv = iv; this.ciphertext = data; @@ -447,6 +455,59 @@ MasterKey.prototype._encrypt = co(function* encrypt(passphrase, clean) { return key; }); +/** + * Calculate key serialization size. + * @returns {Number} + */ + +MasterKey.prototype.getKeySize = function getKeySize() { + var size = 0; + + size += this.key.getSize(); + size += 1; + + if (this.mnemonic) + size += this.mnemonic.getSize(); + + return size; +}; + +/** + * Serialize key and menmonic to a single buffer. + * @returns {Buffer} + */ + +MasterKey.prototype.toKeyRaw = function toKeyRaw() { + var bw = new StaticWriter(this.getKeySize()); + + this.key.toWriter(bw); + + if (this.mnemonic) { + bw.writeU8(1); + this.mnemonic.toWriter(bw); + } else { + bw.writeU8(0); + } + + return bw.render(); +}; + +/** + * Inject properties from serialized key. + * @param {Buffer} data + */ + +MasterKey.prototype.fromKeyRaw = function fromKeyRaw(data) { + var br = new BufferReader(data); + + this.key = HD.fromReader(br); + + if (br.readU8() === 1) + this.mnemonic = Mnemonic.fromReader(br); + + return this; +}; + /** * Calculate serialization size. * @returns {Number} @@ -464,7 +525,7 @@ MasterKey.prototype.getSize = function getSize() { } size += 1; - size += encoding.sizeVarlen(this.key.getExtendedSize()); + size += encoding.sizeVarlen(this.getKeySize()); return size; }; @@ -493,7 +554,19 @@ MasterKey.prototype.toRaw = function toRaw() { } bw.writeU8(0); - bw.writeVarBytes(this.key.toExtended()); + + // NOTE: useless varint + size = this.getKeySize(); + bw.writeVarint(size); + + bw.writeBytes(this.key.toRaw()); + + if (this.mnemonic) { + bw.writeU8(1); + this.mnemonic.toWriter(bw); + } else { + bw.writeU8(0); + } return bw.render(); }; @@ -524,7 +597,13 @@ MasterKey.prototype.fromRaw = function fromRaw(raw) { return this; } - this.key = HD.fromExtended(br.readVarBytes()); + // NOTE: useless varint + br.skipVarint(); + + this.key = HD.fromRaw(br.readBytes(82)); + + if (br.readU8() === 1) + this.mnemonic = Mnemonic.fromReader(br); return this; }; @@ -542,24 +621,27 @@ MasterKey.fromRaw = function fromRaw(raw) { * Inject properties from an HDPrivateKey. * @private * @param {HDPrivateKey} key + * @param {Mnemonic?} mnemonic */ -MasterKey.prototype.fromKey = function fromKey(key) { +MasterKey.prototype.fromKey = function fromKey(key, mnemonic) { this.encrypted = false; this.iv = null; this.ciphertext = null; this.key = key; + this.mnemonic = mnemonic || null; return this; }; /** * Instantiate master key from an HDPrivateKey. * @param {HDPrivateKey} key + * @param {Mnemonic?} mnemonic * @returns {MasterKey} */ -MasterKey.fromKey = function fromKey(key) { - return new MasterKey().fromKey(key); +MasterKey.fromKey = function fromKey(key, mnemonic) { + return new MasterKey().fromKey(key, mnemonic); }; /** @@ -585,7 +667,8 @@ MasterKey.prototype.toJSON = function toJSON(unsafe) { return { encrypted: false, - key: unsafe ? this.key.toJSON() : undefined + key: unsafe ? this.key.toJSON() : undefined, + mnemonic: unsafe && this.mnemonic ? this.mnemonic.toJSON() : undefined }; }; @@ -596,8 +679,13 @@ MasterKey.prototype.toJSON = function toJSON(unsafe) { MasterKey.prototype.inspect = function inspect() { var json = this.toJSON(true); + if (this.key) json.key = this.key.toJSON(); + + if (this.mnemonic) + json.mnemonic = this.mnemonic.toJSON(); + return json; }; diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 21bba7a8..04ab4e8c 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -31,6 +31,7 @@ var MasterKey = require('./masterkey'); var LRU = require('../utils/lru'); var policy = require('../protocol/policy'); var consensus = require('../protocol/consensus'); +var Mnemonic = HD.Mnemonic; /** * BIP44 Wallet @@ -102,26 +103,24 @@ util.inherits(Wallet, EventEmitter); Wallet.prototype.fromOptions = function fromOptions(options) { var key = options.master; - var id, token; + var id, token, mnemonic; - if (MasterKey.isMasterKey(key)) { - this.master.fromOptions(key); - } else { - if (!key) - key = HD.fromMnemonic(null, this.network); - - if (HD.isBase58(key)) + if (key) { + if (typeof key === 'string') key = HD.fromBase58(key, this.network); assert(HD.isPrivate(key), 'Must create wallet with hd private key.'); - - assert(key.verifyNetwork(this.network), - 'Network mismatch for master key.'); - - this.master.fromKey(key); + } else { + mnemonic = new Mnemonic(options.mnemonic); + key = HD.fromMnemonic(mnemonic, this.network); } + assert(key.verifyNetwork(this.network), + 'Network mismatch for master key.'); + + this.master.fromKey(key, mnemonic); + if (options.wid != null) { assert(util.isNumber(options.wid)); this.wid = options.wid; diff --git a/test/hd-test.js b/test/hd-test.js index 8048b0b2..6a4594e7 100644 --- a/test/hd-test.js +++ b/test/hd-test.js @@ -87,7 +87,7 @@ describe('HD', function() { }); it('should deserialize and reserialize', function() { - var key = HD.fromMnemonic(); + var key = HD.generate(); assert.equal(HD.fromJSON(key.toJSON()).toBase58(), key.toBase58()); }); diff --git a/test/util/memwallet.js b/test/util/memwallet.js index d91542df..d91a429a 100644 --- a/test/util/memwallet.js +++ b/test/util/memwallet.js @@ -87,7 +87,7 @@ MemWallet.prototype.init = function init() { var i; if (!this.master) - this.master = HD.PrivateKey.fromMnemonic(null, this.network); + this.master = HD.PrivateKey.generate(); if (!this.key) this.key = this.master.deriveAccount44(this.account); diff --git a/test/wallet-test.js b/test/wallet-test.js index e7f064a2..d58cc625 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -154,7 +154,7 @@ describe('Wallet', function() { n: 2 }); - k = HD.fromMnemonic().deriveAccount44(0).toPublic(); + k = HD.generate().deriveAccount44(0).toPublic(); yield w.addSharedKey(k);