diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 3e8987c0..bb7feb8f 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -304,9 +304,6 @@ Block.prototype.verifyContext = function verifyContext() { var sigops = 0; var prev, height, ts, i, j, tx, cb, input; - if (this.subtype !== 'block') - return true; - if (this.isGenesis()) return true; @@ -363,6 +360,10 @@ Block.prototype.verifyContext = function verifyContext() { // return false; // } + // Can't verify any further when merkleblock or headers. + if (this.subtype !== 'block') + return true; + // Make sure the height contained in the coinbase is correct. if (this.version >= 2 && prev.isUpgraded(2)) { cb = bcoin.script.getCoinbaseData(this.txs[0].inputs[0].script); diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index ef57ca9c..57b4fd57 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -96,7 +96,7 @@ Chain.prototype._init = function _init() { utils.debug('Chain is loading.'); - this.preload(function(err, start) { + this._preload(function(err, start) { if (err) { utils.debug('Preloading chain failed.'); utils.debug('Reason: %s', err.message); @@ -143,7 +143,12 @@ Chain.prototype._init = function _init() { }); }; -Chain.prototype.preload = function preload(callback) { +// Stream headers from electrum.org for quickly +// preloading the chain. Electrum.org stores +// headers in the standard block header format, +// but they do not store chainwork, so we have +// to calculate it ourselves. +Chain.prototype._preload = function _preload(callback) { var self = this; var url = 'https://headers.electrum.org/blockchain_headers'; var chainHeight, buf, height, stream; @@ -202,6 +207,7 @@ Chain.prototype.preload = function preload(callback) { blocks.forEach(function(data) { var entry = bcoin.chainblock.fromRaw(self, height, data); + var block = bcoin.block(entry, 'headers'); var start; // Do some paranoid checks. @@ -212,18 +218,34 @@ Chain.prototype.preload = function preload(callback) { return callback(new Error('Corrupt headers.'), start + 1); } + // Verify the block headers. We don't want to + // trust an external centralized source completely. + // For very paranoid but slower validation: + // if (!block.verify() || !block.verifyContext()) { + if (!block.verify()) { + start = Math.max(0, height - 2); + stream.destroy(); + self.resetHeight(start); + return callback(new Error('Bad headers.'), start + 1); + } + lastEntry = entry; delete entry.chainwork; entry.chainwork = entry.getChainwork(); - // Skip the genesis block in case it ends up being corrupt + // Skip the genesis block in case + // it ends up being corrupt. if (height === 0) { height++; return; } - self._saveEntry(entry, height > chainHeight); + // Don't write blocks we already have + // (bad for calculating chainwork). + // self._saveEntry(entry, height > chainHeight); + + self._saveEntry(entry, true); height++; @@ -630,7 +652,7 @@ Chain.prototype.fillPercent = function fillPercent() { }; Chain.prototype.hashRange = function hashRange(start, end) { - var hashes; + var hashes = []; start = this.byTime(start); end = this.byTime(end); @@ -694,13 +716,15 @@ Chain.prototype.getLocator = function getLocator(start) { Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { var self = this; - var root = hash; + var root; if (utils.isBuffer(hash)) hash = utils.toHex(hash); else if (hash.hash) hash = hash.hash('hex'); + root = hash; + while (this.orphan.bmap[hash]) { root = hash; hash = this.orphan.bmap[hash].prevBlock; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 78a8e09e..62acadf3 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -1163,7 +1163,10 @@ TX.prototype.sortMembers = function sortMembers() { } this.inputs = this.inputs.slice().sort(function(a, b) { - var res = new bn(a.prevout.hash, 'hex').cmp(new bn(b.prevout.hash, 'hex')); + var h1 = utils.toArray(a.prevout.hash, 'hex'); + var h2 = utils.toArray(b.prevout.hash, 'hex'); + + var res = utils.cmp(h1, h2); if (res !== 0) return res; @@ -1178,7 +1181,7 @@ TX.prototype.sortMembers = function sortMembers() { a = bcoin.script.encode(a.script); b = bcoin.script.encode(b.script); - return new bn(a).cmp(b); + return utils.cmp(a, b); }); if (this.changeIndex !== -1) { diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index b9274113..bd0d0430 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -787,13 +787,13 @@ utils.hidden = function hidden(obj, prop, value) { utils.sortKeys = function sortKeys(keys) { return keys.slice().sort(function(a, b) { - return new bn(a).cmp(new bn(b)) > 0; + return utils.cmp(a, b); }); }; utils.sortHDKeys = function sortHDKeys(keys) { return keys.slice().sort(function(a, b) { - return new bn(a.publicKey).cmp(new bn(b.publicKey)) > 0; + return utils.cmp(a.publicKey, b.publicKey); }); }; @@ -1381,16 +1381,18 @@ utils.cmp = function(a, b) { return 0; }; -// https://cryptocoding.net/index.php/Coding_rules // memcmp in constant time (can only return true or false) +// https://cryptocoding.net/index.php/Coding_rules // $ man 3 memcmp (see NetBSD's consttime_memequal) +// This protects us against timing attacks when +// comparing an input against a secret string. utils.ccmp = function(a, b) { var res, i; assert(a.length === b.length); for (i = 0; i < a.length; i++) - res = a[i] ^ b[i]; + res |= a[i] ^ b[i]; return res === 0; };