From 4bf31f32bf2840cb6ebfd5811bf6aae189c53553 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 1 Jul 2016 19:21:20 -0700 Subject: [PATCH] hd refactor. --- lib/bcoin/ec.js | 9 +++++ lib/bcoin/hd.js | 60 +++++++++++++++++++-------------- lib/bcoin/protocol/constants.js | 4 +-- lib/bcoin/script.js | 6 ++-- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/lib/bcoin/ec.js b/lib/bcoin/ec.js index b11c348a..480ec2c8 100644 --- a/lib/bcoin/ec.js +++ b/lib/bcoin/ec.js @@ -38,6 +38,7 @@ ec.elliptic = elliptic.ec('secp256k1'); /** * elliptic.js signature constructor. * @static + * @type {Function} */ ec.signature = require('elliptic/lib/elliptic/ec/signature'); @@ -45,10 +46,18 @@ ec.signature = require('elliptic/lib/elliptic/ec/signature'); /** * elliptic.js keypair constructor. * @static + * @type {Function} */ ec.keypair = require('elliptic/lib/elliptic/ec/key'); +/** + * A reference to the secp256k1 curve. + * @const {Object} + */ + +ec.curve = ec.elliptic.curve; + /** * Generate a private key. * @returns {Buffer} Private key. diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 78ff23b2..564320f7 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -122,7 +122,7 @@ function Mnemonic(options) { if (!(this instanceof Mnemonic)) return new Mnemonic(options); - this.bits = 128; + this.bits = constants.hd.MIN_ENTROPY; this.language = 'english'; this.entropy = null; this.phrase = null; @@ -159,7 +159,8 @@ Mnemonic.prototype.fromOptions = function fromOptions(options) { if (options.bits != null) { assert(utils.isNumber(options.bits)); - assert(options.bits >= 128); + assert(options.bits >= constants.hd.MIN_ENTROPY); + assert(options.bits <= constants.hd.MAX_ENTROPY); assert(options.bits % 32 === 0); this.bits = options.bits; } @@ -309,12 +310,20 @@ Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) { words = phrase.split(/[ \u3000]+/); bits = words.length * 11; - lang = Mnemonic.getLanguage(words[0]); - ent = new Buffer(Math.ceil(bits / 8)); - wordlist = Mnemonic.getWordlist(lang); + cbits = bits % 32; + cbytes = Math.ceil(cbits / 8); + bits -= cbits; + assert(bits >= constants.hd.MIN_ENTROPY); + assert(bits <= constants.hd.MAX_ENTROPY); + assert(bits % 32 === 0); + + ent = new Buffer(Math.ceil((bits + cbits) / 8)); ent.fill(0); + lang = Mnemonic.getLanguage(words[0]); + wordlist = Mnemonic.getWordlist(lang); + for (i = 0; i < words.length; i++) { word = words[i]; index = wordlist.indexOf(word); @@ -331,10 +340,6 @@ Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) { } } - // Checksum bits: - cbits = bits % 32; - cbytes = Math.ceil(cbits / 8); - entropy = ent.slice(0, ent.length - cbytes); ent = ent.slice(ent.length - cbytes); chk = utils.sha256(entropy); @@ -348,11 +353,7 @@ Mnemonic.prototype.fromPhrase = function fromPhrase(phrase) { throw new Error('Invalid checksum.'); } - bits -= cbits; - assert(bits / 8 === entropy.length); - assert(bits >= 128); - assert(bits % 32 === 0); this.bits = bits; this.language = lang; @@ -382,7 +383,8 @@ Mnemonic.fromPhrase = function fromPhrase(phrase) { Mnemonic.prototype.fromEntropy = function fromEntropy(entropy, lang) { assert(Buffer.isBuffer(entropy)); - assert(entropy.length * 8 >= 128); + assert(entropy.length * 8 >= constants.hd.MIN_ENTROPY); + assert(entropy.length * 8 <= constants.hd.MAX_ENTROPY); assert((entropy.length * 8) % 32 === 0); assert(!lang || Mnemonic.languages.indexOf(lang) !== -1); @@ -478,6 +480,10 @@ Mnemonic.prototype.fromJSON = function fromJSON(json) { assert(typeof json.entropy === 'string'); assert(typeof json.phrase === 'string'); assert(typeof json.passphrase === 'string'); + assert(json.bits >= constants.hd.MIN_ENTROPY); + assert(json.bits <= constants.hd.MAX_ENTROPY); + assert(json.bits % 32 === 0); + assert(json.bits / 8 === json.entropy.length / 2); this.bits = json.bits; this.language = json.language; @@ -485,10 +491,6 @@ Mnemonic.prototype.fromJSON = function fromJSON(json) { this.phrase = json.phrase; this.passphrase = json.passphrase; - assert(this.bits >= 128); - assert(this.bits % 32 === 0); - assert(this.bits / 8 === this.entropy.length); - return this; }; @@ -541,7 +543,8 @@ Mnemonic.prototype.fromRaw = function fromRaw(data) { this.passphrase = p.readVarString('utf8'); assert(this.language); - assert(this.bits >= 128); + assert(this.bits >= constants.hd.MIN_ENTROPY); + assert(this.bits <= constants.hd.MAX_ENTROPY); assert(this.bits % 32 === 0); return this; @@ -881,7 +884,6 @@ HDPrivateKey.prototype.fromOptions = function fromOptions(options) { this.childIndex = options.childIndex; this.chainCode = options.chainCode; this.privateKey = options.privateKey; - this.publicKey = ec.publicKeyCreate(options.privateKey, true); if (options.mnemonic) { @@ -983,9 +985,13 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) { privateKey = left .add(new bn(this.privateKey)) - .mod(ec.elliptic.curve.n) + .mod(ec.curve.n) .toArrayLike(Buffer, 'be', 32); + // Only a 1 in 2^127 chance of happening. + if (!ec.privateKeyVerify(privateKey)) + throw new Error('Private key is invalid.'); + if (!this.fingerPrint) this.fingerPrint = utils.hash160(this.publicKey).slice(0, 4); @@ -1176,8 +1182,8 @@ HDPrivateKey.prototype.fromSeed = function fromSeed(seed, network) { assert(Buffer.isBuffer(seed)); - if (seed.length < constants.hd.MIN_ENTROPY - || seed.length > constants.hd.MAX_ENTROPY) { + if (!(seed.length * 8 >= constants.hd.MIN_ENTROPY + && seed.length * 8 <= constants.hd.MAX_ENTROPY)) { throw new Error('Entropy not in range.'); } @@ -1186,6 +1192,7 @@ HDPrivateKey.prototype.fromSeed = function fromSeed(seed, network) { privateKey = hash.slice(0, 32); chainCode = hash.slice(32, 64); + // Only a 1 in 2^127 chance of happening. if (!ec.privateKeyVerify(privateKey)) throw new Error('Master private key is invalid.'); @@ -1622,11 +1629,14 @@ HDPublicKey.prototype.derive = function derive(index, hardened) { left = new bn(hash.slice(0, 32)); chainCode = hash.slice(32, 64); - point = ec.elliptic.curve.decodePoint(this.publicKey); - point = ec.elliptic.curve.g.mul(left).add(point); + point = ec.decodePoint(this.publicKey); + point = ec.curve.g.mul(left).add(point); publicKey = new Buffer(point.encode('array', true)); assert(publicKey.length === 33); + if (!ec.publicKeyVerify(publicKey)) + throw new Error('Public key is invalid.'); + if (!this.fingerPrint) this.fingerPrint = utils.hash160(this.publicKey).slice(0, 4); diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index bd68ad34..de70ff91 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -522,8 +522,8 @@ exports.rejectByVal = utils.revMap(exports.reject); exports.hd = { HARDENED: 0x80000000, MAX_INDEX: 0x100000000, - MIN_ENTROPY: 128 / 8, - MAX_ENTROPY: 512 / 8 + MIN_ENTROPY: 128, + MAX_ENTROPY: 512 }; /** diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 21aaef4e..c145d683 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -178,7 +178,7 @@ Witness.prototype.getInputType = function getInputType() { /** * "Guess" the address of the witness. * This method is not 100% reliable. - * @returns {String|null} + * @returns {Address|null} */ Witness.prototype.getInputAddress = function getInputAddress() { @@ -2807,7 +2807,7 @@ Script.prototype.getSize = function getSize() { /** * "Guess" the address of the input script. * This method is not 100% reliable. - * @returns {Base58Address|null} + * @returns {Address|null} */ Script.prototype.getInputAddress = function getInputAddress() { @@ -2818,7 +2818,7 @@ Script.prototype.getInputAddress = function getInputAddress() { * Get the address of the script if present. Note that * pubkey and multisig scripts will be treated as though * they are pubkeyhash and scripthashes respectively. - * @returns {Base58Address|null} + * @returns {Address|null} */ Script.prototype.getAddress = function getAddress() {