From 6690991acbb766c9dda01fa8e9ea622557b05f37 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 13 May 2016 19:28:19 -0700 Subject: [PATCH] more hd improvements. --- lib/bcoin/ec.js | 18 +++++++++ lib/bcoin/hd.js | 87 +++++++++++++++++++++++++++++++++---------- lib/bcoin/wallet.js | 2 +- test/hd-test.js | 2 +- test/mnemonic-test.js | 6 +-- test/wallet-test.js | 2 +- 6 files changed, 90 insertions(+), 27 deletions(-) diff --git a/lib/bcoin/ec.js b/lib/bcoin/ec.js index 739f7f2e..6fd3e7ef 100644 --- a/lib/bcoin/ec.js +++ b/lib/bcoin/ec.js @@ -174,6 +174,24 @@ ec.publicKeyVerify = function publicKeyVerify(key) { return ec.elliptic.keyPair({ pub: key }).validate(); }; +/** + * Validate a private key. + * @param {Buffer} key + * @returns {Boolean} True if buffer is a valid private key. + */ + +ec.privateKeyVerify = function privateKeyVerify(key) { + if (secp256k1) + return secp256k1.privateKeyVerify(key); + + key = new bn(key); + + if (key.cmpn(0) === 0 || key.cmp(ec.elliptic.curve.n) >= 0) + return false; + + return true; +}; + /** * Sign a message. * @param {Buffer} msg diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 8bb6c13a..be15ca69 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -243,7 +243,7 @@ Mnemonic.isMnemonic = function isMnemonic(obj) { function HD(options, network) { if (!options) - return HD.fromSeed(null, network); + return HD.fromMnemonic(null, network); return HD.fromAny(options, network); } @@ -284,6 +284,17 @@ HD.fromSeed = function fromSeed(options, network) { return HDPrivateKey.fromSeed(options, network); }; +/** + * Instantiate an hd private key from a mnemonic. + * @param {Mnemonic|Object} mnemonic + * @param {String?} network + * @returns {HDPrivateKey} + */ + +HD.fromMnemonic = function fromMnemonic(options, network) { + return HDPrivateKey.fromMnemonic(options, network); +}; + /** * Instantiate an HD key from a jsonified key object. * @param {Object} json - The jsonified transaction object. @@ -323,7 +334,7 @@ HD.fromAny = function fromAny(options, network) { if (HDPublicKey.isExtended(xkey)) return HDPublicKey.fromBase58(xkey); - return HDPrivateKey.fromSeed(options, network); + return HDPrivateKey.fromMnemonic(options, network); }; /** @@ -499,6 +510,9 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) { if (index < constants.hd.HARDENED && hardened) index += constants.hd.HARDENED; + if (!(index >= 0 && index < constants.hd.MAX_INDEX)) + throw new Error('Index out of range.'); + p = new BufferWriter(); if (hardened) { @@ -686,35 +700,33 @@ HDPrivateKey.prototype.derivePath = function derivePath(path) { */ HDPrivateKey.parseSeed = function parseSeed(seed, network) { - var data, hash; + var hash, chainCode, privateKey; if (!seed) seed = {}; - if (Buffer.isBuffer(seed)) { - data = seed; - seed = null; - } else if (seed instanceof Mnemonic) { - data = seed.toSeed(); - } else { - seed = new Mnemonic(seed); - data = seed.toSeed(); - } + assert(Buffer.isBuffer(seed)); - if (data.length < constants.hd.MIN_ENTROPY - || data.length > constants.hd.MAX_ENTROPY) { + if (seed.length < constants.hd.MIN_ENTROPY + || seed.length > constants.hd.MAX_ENTROPY) { throw new Error('Entropy not in range.'); } - hash = utils.hmac('sha512', data, 'Bitcoin seed'); + hash = utils.hmac('sha512', seed, 'Bitcoin seed'); + + privateKey = hash.slice(0, 32); + chainCode = hash.slice(32, 64); + + 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: hash.slice(32, 64), - privateKey: hash.slice(0, 32), + chainCode: chainCode, + privateKey: privateKey, mnemonic: seed }; }; @@ -731,6 +743,40 @@ HDPrivateKey.fromSeed = function fromSeed(seed, network) { return new HDPrivateKey(HDPrivateKey.parseSeed(seed, network)); }; +/** + * Instantiate an hd private key from a mnemonic. + * @param {Mnemonic|Object} mnemonic + * @param {String?} network + * @returns {HDPrivateKey} + */ + +HDPrivateKey.fromMnemonic = function fromMnemonic(mnemonic, network) { + var key; + + if (!(mnemonic instanceof Mnemonic)) + mnemonic = new Mnemonic(mnemonic); + + if (mnemonic.seed || mnemonic.phrase || mnemonic.entropy) + return HDPrivateKey.fromSeed(mnemonic.toSeed(), network); + + for (;;) { + try { + key = HDPrivateKey.fromSeed(mnemonic.toSeed(), network); + } catch (e) { + if (e.message === 'Master private key is invalid.') { + mnemonic.seed = null; + mnemonic.phrase = null; + mnemonic.entropy = null; + continue; + } + throw e; + } + break; + } + + return key; +}; + /** * Generate an hd private key from a key and/or entropy bytes. * @param {Object?} options @@ -866,6 +912,7 @@ HDPrivateKey.prototype.toJSON = function toJSON(passphrase) { var json = { v: 1, name: 'hdkey', + network: this.network, encrypted: false }; @@ -953,7 +1000,7 @@ HDPrivateKey.fromJSON = function fromJSON(json, passphrase) { } if (json.mnemonic) - return HDPrivateKey.fromSeed(json.mnemonic); + return HDPrivateKey.fromMnemonic(json.mnemonic, json.network); if (json.xpubkey) return HDPublicKey.fromBase58(json.xprivkey); @@ -1048,10 +1095,10 @@ HDPublicKey.prototype.derive = function derive(index, hardened) { return cached; if (index >= constants.hd.HARDENED || hardened) - throw new Error('Invalid index.'); + throw new Error('Index out of range.'); if (index < 0) - throw new Error('Invalid path.'); + throw new Error('Index out of range.'); p = new BufferWriter(); p.writeBytes(this.publicKey); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 22c65512..55294b71 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -70,7 +70,7 @@ function Wallet(options) { } if (!options.master) - options.master = bcoin.hd.fromSeed(null, this.network); + options.master = bcoin.hd.fromMnemonic(null, this.network); this.provider = options.provider || null; this.master = options.master || null; diff --git a/test/hd-test.js b/test/hd-test.js index d578660a..a6ce5f86 100644 --- a/test/hd-test.js +++ b/test/hd-test.js @@ -142,7 +142,7 @@ describe('HD', function() { }); it('should deserialize and reserialize', function() { - var key = bcoin.hd.fromSeed(); + var key = bcoin.hd.fromMnemonic(); assert.equal(bcoin.hd.fromJSON(key.toJSON()).xprivkey, key.xprivkey); }); diff --git a/test/mnemonic-test.js b/test/mnemonic-test.js index aab433b8..f3600ecc 100644 --- a/test/mnemonic-test.js +++ b/test/mnemonic-test.js @@ -20,7 +20,7 @@ describe('Mnemonic', function() { mnemonic.toSeed(); assert.equal(mnemonic.phrase, phrase); assert.equal(mnemonic.toSeed().toString('hex'), seed.toString('hex')); - var key = bcoin.hd.fromSeed(mnemonic); + var key = bcoin.hd.fromMnemonic(mnemonic); assert.equal(key.xprivkey, xpriv); }); }); @@ -37,11 +37,9 @@ describe('Mnemonic', function() { passphrase: passphrase }); mnemonic.toSeed(); - // utils.print(new Buffer(mnemonic.phrase, 'utf8').toString('hex')); - // utils.print(new Buffer(phrase, 'utf8').toString('hex')); assert.equal(mnemonic.phrase, phrase); assert.equal(mnemonic.toSeed().toString('hex'), seed.toString('hex')); - var key = bcoin.hd.fromSeed(mnemonic); + var key = bcoin.hd.fromMnemonic(mnemonic); assert.equal(key.xprivkey, xpriv); }); }); diff --git a/test/wallet-test.js b/test/wallet-test.js index 3526899c..8d95787b 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -113,7 +113,7 @@ describe('Wallet', function() { m: 1, n: 2 }); - var k2 = bcoin.hd.fromSeed().deriveAccount44(0).hdPublicKey; + var k2 = bcoin.hd.fromMnemonic().deriveAccount44(0).hdPublicKey; w.addKey(k2); // Input transcation