From 1735fe6a6c4e90187e9a07e2c8cf4adbd354a278 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 11 Aug 2017 04:42:44 -0700 Subject: [PATCH] hd: refactor hardening handling. --- lib/hd/common.js | 47 +++++++++++++++++++++++----------------------- lib/hd/private.js | 22 +++++++++------------- lib/hd/public.js | 15 +++++---------- lib/node/config.js | 6 +++--- 4 files changed, 40 insertions(+), 50 deletions(-) diff --git a/lib/hd/common.js b/lib/hd/common.js index 23f39ccb..df7d65ae 100644 --- a/lib/hd/common.js +++ b/lib/hd/common.js @@ -6,6 +6,7 @@ 'use strict'; +const assert = require('assert'); const LRU = require('../utils/lru'); const common = exports; @@ -17,14 +18,6 @@ const common = exports; common.HARDENED = 0x80000000; -/** - * Max index (u32max + 1). - * @const {Number} - * @default - */ - -common.MAX_INDEX = 0x100000000; - /** * Min entropy bits. * @const {Number} @@ -52,13 +45,13 @@ common.cache = new LRU(500); * Parse a derivation path and return an array of indexes. * @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki * @param {String} path - * @param {Number?} max - Max index. + * @param {Boolean} hard * @returns {Number[]} */ -common.parsePath = function parsePath(path, max) { - if (max == null) - max = common.MAX_INDEX; +common.parsePath = function parsePath(path, hard) { + assert(typeof path === 'string'); + assert(typeof hard === 'boolean'); const parts = path.split('/'); const root = parts.shift(); @@ -72,23 +65,28 @@ common.parsePath = function parsePath(path, max) { const result = []; - for (let index of parts) { - const hardened = index[index.length - 1] === '\''; + for (let part of parts) { + const hardened = part[part.length - 1] === '\''; if (hardened) - index = index.slice(0, -1); + part = part.slice(0, -1); - if (!/^\d+$/.test(index)) + if (!/^\d+$/.test(part)) throw new Error('Non-number path index.'); - index = parseInt(index, 10); + let index = parseInt(part, 10); - if (hardened) - index += common.HARDENED; - - if (!(index >= 0 && index < max)) + if ((index >>> 0) !== index) throw new Error('Index out of range.'); + if (hardened) { + index |= common.HARDENED; + index >>>= 0; + } + + if (!hard && (index & common.HARDENED)) + throw new Error('Cannot derive hardened.'); + result.push(index); } @@ -116,10 +114,11 @@ common.isMaster = function isMaster(key) { common.isBIP44 = function isBIP44(key, account) { if (account != null) { - if (key.childIndex !== common.HARDENED + account) + const index = (common.HARDENED | account) >>> 0; + if (key.childIndex !== index) return false; } - return key.depth === 3 && key.childIndex >= common.HARDENED; + return key.depth === 3 && (key.childIndex & common.HARDENED) !== 0; }; /** @@ -129,5 +128,5 @@ common.isBIP44 = function isBIP44(key, account) { */ common.isBIP45 = function isBIP45(key) { - return key.depth === 1 && key.childIndex === common.HARDENED + 45; + return key.depth === 1 && (key.childIndex & ~common.HARDENED) === 45; }; diff --git a/lib/hd/private.js b/lib/hd/private.js index 304d44b2..89c381a9 100644 --- a/lib/hd/private.js +++ b/lib/hd/private.js @@ -183,13 +183,12 @@ HDPrivateKey.prototype.destroy = function destroy(pub) { */ HDPrivateKey.prototype.derive = function derive(index, hardened) { - assert(typeof index === 'number'); + assert(util.isU32(index)); - if (hardened && index < common.HARDENED) - index += common.HARDENED; - - if (index < 0 || index >= common.MAX_INDEX) - throw new Error('Index out of range.'); + if (hardened) { + index |= common.HARDENED; + index >>>= 0; + } if (this.depth >= 0xff) throw new Error('Depth too high.'); @@ -202,7 +201,7 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) { const bw = new StaticWriter(37); - if (index >= common.HARDENED) { + if (index & common.HARDENED) { bw.writeU8(0); bw.writeBytes(this.privateKey); bw.writeU32BE(index); @@ -360,16 +359,12 @@ HDPrivateKey.isRaw = function isRaw(data, network) { /** * Test whether a string is a valid path. * @param {String} path - * @param {Boolean?} hardened * @returns {Boolean} */ HDPrivateKey.isValidPath = function isValidPath(path) { - if (typeof path !== 'string') - return false; - try { - common.parsePath(path, common.MAX_INDEX); + common.parsePath(path, true); return true; } catch (e) { return false; @@ -384,7 +379,8 @@ HDPrivateKey.isValidPath = function isValidPath(path) { */ HDPrivateKey.prototype.derivePath = function derivePath(path) { - const indexes = common.parsePath(path, common.MAX_INDEX); + const indexes = common.parsePath(path, true); + let key = this; for (const index of indexes) diff --git a/lib/hd/public.js b/lib/hd/public.js index 4c5603a1..3ce7772d 100644 --- a/lib/hd/public.js +++ b/lib/hd/public.js @@ -151,14 +151,11 @@ HDPublicKey.prototype.destroy = function destroy() { */ HDPublicKey.prototype.derive = function derive(index, hardened) { - assert(typeof index === 'number'); + assert(util.isU32(index)); - if (index >= common.HARDENED || hardened) + if ((index & common.HARDENED) || hardened) throw new Error('Cannot derive hardened.'); - if (index < 0) - throw new Error('Index out of range.'); - if (this.depth >= 0xff) throw new Error('Depth too high.'); @@ -280,11 +277,8 @@ HDPublicKey.prototype.isBIP45 = function isBIP45() { */ HDPublicKey.isValidPath = function isValidPath(path) { - if (typeof path !== 'string') - return false; - try { - common.parsePath(path, common.HARDENED); + common.parsePath(path, false); return true; } catch (e) { return false; @@ -300,7 +294,8 @@ HDPublicKey.isValidPath = function isValidPath(path) { */ HDPublicKey.prototype.derivePath = function derivePath(path) { - const indexes = common.parsePath(path, common.HARDENED); + const indexes = common.parsePath(path, false); + let key = this; for (const index of indexes) diff --git a/lib/node/config.js b/lib/node/config.js index 8500bbf9..549fffb9 100644 --- a/lib/node/config.js +++ b/lib/node/config.js @@ -767,21 +767,21 @@ Config.prototype.parseConfig = function parseConfig(text) { if (colon !== -1 && (colon < equal || equal === -1)) { if (seen && !colons) - throw new Error(`Expected \`=\` on line ${num}: "${line}".`); + throw new Error(`Expected '=' on line ${num}: "${line}".`); index = colon; seen = true; colons = true; } else if (equal !== -1) { if (seen && colons) - throw new Error(`Expected \`:\` on line ${num}: "${line}".`); + throw new Error(`Expected ':' on line ${num}: "${line}".`); index = equal; seen = true; colons = false; } else { const symbol = colons ? ':' : '='; - throw new Error(`Expected \`${symbol}\` on line ${num}: "${line}".`); + throw new Error(`Expected '${symbol}' on line ${num}: "${line}".`); } let key = line.substring(0, index).trim();