From 575843acefe25deaea78178c29ec4dd95c945630 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 17 Jan 2016 19:30:25 -0800 Subject: [PATCH] work on chain. check for low der sigs. --- lib/bcoin/block.js | 59 +++++++++++++++++++++++++++------------------ lib/bcoin/chain.js | 54 +++++++++++++++++++++++++++-------------- lib/bcoin/pool.js | 29 ++++++++++++++++------ lib/bcoin/script.js | 4 +++ lib/bcoin/tx.js | 22 +++++++++++++---- scripts/update.js | 4 +++ 6 files changed, 118 insertions(+), 54 deletions(-) diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 58d8d80e..2ab90cd0 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -223,26 +223,37 @@ Block.prototype.getMerkleRoot = function getMerkleRoot() { return merkleTree[merkleTree.length - 1]; }; +Block.prototype._debug = function debug() { + var args = Array.prototype.slice.call(arguments); + + if (!this.chain) + return; + + args.unshift('debug'); + + return this.chain.emit.apply(this.chain, args); +}; + Block.prototype._verify = function _verify() { var uniq = {}; var i, tx, hash; // Check proof of work if (!utils.testTarget(this.bits, this.hash())) { - this.chain.emit('debug', 'Block failed POW test: %s', this.rhash); + this._debug('Block failed POW test: %s', this.rhash); return false; } // Check timestamp against now + 2 hours if (this.ts > utils.now() + 2 * 60 * 60) { - this.chain.emit('debug', 'Block timestamp is too high: %s', this.rhash); + this._debug('Block timestamp is too high: %s', this.rhash); return false; } // Verify the partial merkle tree if we are a merkleblock. if (this.subtype === 'merkleblock') { if (!this._verifyPartial()) { - this.chain.emit('debug', 'Block failed merkle test: %s', this.rhash); + this._debug('Block failed merkle test: %s', this.rhash); return false; } } @@ -254,13 +265,13 @@ Block.prototype._verify = function _verify() { // Size can't be bigger than MAX_BLOCK_SIZE if (this.txs.length > constants.block.maxSize || this.size() > constants.block.maxSize) { - this.chain.emit('debug', 'Block is too large: %s', this.rhash); + this._debug('Block is too large: %s', this.rhash); return false; } // First TX must be a coinbase if (!this.txs.length || !this.txs[0].isCoinbase()) { - this.chain.emit('debug', 'Block has no coinbase: %s', this.rhash); + this._debug('Block has no coinbase: %s', this.rhash); return false; } @@ -270,14 +281,14 @@ Block.prototype._verify = function _verify() { // The rest of the txs must not be coinbases if (i > 0 && tx.isCoinbase()) { - this.chain.emit('debug', 'Block more than one coinbase: %s', this.rhash); + this._debug('Block more than one coinbase: %s', this.rhash); return false; } // Check for duplicate txids hash = tx.hash('hex'); if (uniq[hash]) { - this.chain.emit('debug', 'Block has duplicate txids: %s', this.rhash); + this._debug('Block has duplicate txids: %s', this.rhash); return false; } uniq[hash] = true; @@ -285,14 +296,14 @@ Block.prototype._verify = function _verify() { // Check merkle root if (this.getMerkleRoot() !== this.merkleRoot) { - this.chain.emit('debug', 'Block failed merkleroot test: %s', this.rhash); + this._debug('Block failed merkleroot test: %s', this.rhash); return false; } return true; }; -Block.prototype.postVerify = function postVerify() { +Block.prototype.verifyContext = function verifyContext() { var flags = {}; var sigops = 0; var prev, height, ts, i, j, tx, cb, input; @@ -310,7 +321,7 @@ Block.prototype.postVerify = function postVerify() { // Ensure it's not an orphan if (!prev) { - this.chain.emit('debug', 'Block has no previous entry: %s', this.rhash); + this._debug('Block has no previous entry: %s', this.rhash); return false; } @@ -318,41 +329,41 @@ Block.prototype.postVerify = function postVerify() { // Ensure the timestamp is correct if (this.ts <= prev.getMedianTime()) { - this.chain.emit('debug', 'Block time is lower than median: %s', this.rhash); + this._debug('Block time is lower than median: %s', this.rhash); return false; } // Ensure the miner's target is equal to what we expect if (this.bits !== this.chain.target(prev, this)) { - this.chain.emit('debug', 'Block is using wrong target: %s', this.rhash); + this._debug('Block is using wrong target: %s', this.rhash); return false; } // Only allow version 2 blocks (coinbase height) // once the majority of blocks are using it. if (this.version < 2 && prev.isOutdated(2)) { - this.chain.emit('debug', 'Block is outdated (v2): %s', this.rhash); + this._debug('Block is outdated (v2): %s', this.rhash); return false; } // Only allow version 3 blocks (sig validation) // once the majority of blocks are using it. if (this.version < 3 && prev.isOutdated(3)) { - this.chain.emit('debug', 'Block is outdated (v3): %s', this.rhash); + this._debug('Block is outdated (v3): %s', this.rhash); return false; } // Only allow version 4 blocks (checklocktimeverify) // once the majority of blocks are using it. if (this.version < 4 && prev.isOutdated(4)) { - this.chain.emit('debug', 'Block is outdated (v4): %s', this.rhash); + this._debug('Block is outdated (v4): %s', this.rhash); return false; } // Only allow version 8 blocks (locktime median past) // once the majority of blocks are using it. // if (this.version < 8 && prev.isOutdated(8)) { - // this.chain.emit('debug', 'Block is outdated (v8): %s', this.rhash); + // this._debug('Block is outdated (v8): %s', this.rhash); // return false; // } @@ -362,13 +373,13 @@ Block.prototype.postVerify = function postVerify() { // Make sure the coinbase is parseable. if (!cb) { - this.chain.emit('debug', 'Block has malformed coinbase: %s', this.rhash); + this._debug('Block has malformed coinbase: %s', this.rhash); return false; } // Make sure coinbase height is equal to the actual height. if (cb.height !== height) { - this.chain.emit('debug', 'Block has bad coinbase height: %s', this.rhash); + this._debug('Block has bad coinbase height: %s', this.rhash); return false; } } @@ -406,7 +417,7 @@ Block.prototype.postVerify = function postVerify() { // Transactions must be finalized with // regards to nSequence and nLockTime. if (!tx.isFinal(height, ts)) { - this.chain.emit('debug', 'TX is not final: %s (%s)', this.rhash, i); + this._debug('TX is not final: %s (%s)', this.rhash, i); return false; } @@ -416,7 +427,7 @@ Block.prototype.postVerify = function postVerify() { // if (tx.sigops(true) > constants.script.maxTxSigops) { // // Block 71036 abused checksig to // // include a huge number of sigops. - // this.chain.emit('debug', 'Block TX has too many sigops: %s', this.rhash); + // this._debug('Block TX has too many sigops: %s', this.rhash); // if (!(network.type === 'main' && height === 71036)) // return false; // } @@ -430,7 +441,7 @@ Block.prototype.postVerify = function postVerify() { sigops += tx.sigops(); if (sigops > constants.script.maxBlockSigops) { - this.chain.emit('debug', 'Block has too many sigops: %s', this.rhash); + this._debug('Block has too many sigops: %s', this.rhash); return false; } @@ -439,7 +450,7 @@ Block.prototype.postVerify = function postVerify() { // Blocks 91842 and 91880 created duplicate // txids by using the same exact output script // and extraNonce. - this.chain.emit('debug', 'Block is overwriting txids: %s', this.rhash); + this._debug('Block is overwriting txids: %s', this.rhash); if (!(network.type === 'main' && (height === 91842 || height === 91880))) return false; } @@ -461,13 +472,13 @@ Block.prototype.postVerify = function postVerify() { // Verify the script if (!tx.verify(j, true, flags)) { - this.chain.emit('debug', 'Block has invalid inputs: %s', this.rhash); + this._debug('Block has invalid inputs: %s', this.rhash); return false; } // Ensure tx is not double spending an output // if (this.chain.isSpent(input.out.hash, input.out.index)) { - // this.chain.emit('debug', 'Block is using spent inputs: %s', this.rhash); + // this._debug('Block is using spent inputs: %s', this.rhash); // return false; // } } diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 0be3b7f0..0b548c4a 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -179,12 +179,6 @@ Chain.prototype._addIndex = function _addIndex(entry, save) { // could be used if you want to be on the overly // safe (see: paranoid) side. // this.resetLastCheckpoint(entry.height); - this.emit('fork', { - height: entry.height, - expected: checkpoint, - received: entry.hash, - checkpoint: true - }); return Chain.codes.badCheckpoint; } } @@ -294,18 +288,26 @@ Chain.prototype.add = function add(block, peer) { var total = 0; for (;;) { + hash = block.hash('hex'); + prevHash = block.prevBlock; + + // Find the previous block height/index. + prevHeight = this.index.heights[prevHash]; + // Validate the block we want to add. // This is only necessary for new // blocks coming in, not the resolving // orphans. if (block === initial && !block.verify()) { code = Chain.codes.invalid; + this.emit('invalid', { + height: prevHeight + 1, + hash: hash, + peer: peer + }); break; } - hash = block.hash('hex'); - prevHash = block.prevBlock; - // If the block is already known to be // an orphan, ignore it. if (this.orphan.map[prevHash]) { @@ -320,7 +322,8 @@ Chain.prototype.add = function add(block, peer) { height: -1, expected: this.orphan.map[prevHash].hash('hex'), received: hash, - checkpoint: null + checkpoint: null, + peer: peer }); code = Chain.codes.forked; break; @@ -329,9 +332,6 @@ Chain.prototype.add = function add(block, peer) { break; } - // Find the previous block height/index. - prevHeight = this.index.heights[prevHash]; - // If previous block wasn't ever seen, // add it current to orphans and break. if (prevHeight == null) { @@ -344,6 +344,7 @@ Chain.prototype.add = function add(block, peer) { break; } + // Create a new chain entry. entry = new ChainBlock(this, { hash: hash, version: block.version, @@ -381,8 +382,9 @@ Chain.prototype.add = function add(block, peer) { this.emit('fork', { height: prevHeight + 1, expected: tip.hash, - received: entry.hash, - checkpoint: null + received: hash, + checkpoint: null, + peer: peer }); code = Chain.codes.forked; break; @@ -391,10 +393,13 @@ Chain.prototype.add = function add(block, peer) { // Do "contextual" verification on our block // now that we're certain its previous // block is in the chain. - // if (0) - if (!block.postVerify()) { - throw new Error; + if (!block.verifyContext()) { code = Chain.codes.invalid; + this.emit('invalid', { + height: prevHeight + 1, + hash: hash, + peer: peer + }); break; } @@ -412,6 +417,19 @@ Chain.prototype.add = function add(block, peer) { // so we don't do it. The misbehaving peer has // been killed and hopefully we find a peer // who isn't trying to fool us. + if (code === Chain.codes.badCheckpoint) { + this.emit('fork', { + height: entry.height, + expected: network.checkpoints[entry.height], + received: entry.hash, + checkpoint: true, + peer: peer + }); + break; + } + + // Should never happen, but... something + // went wrong. Ignore this block. if (code !== Chain.codes.okay) break; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 69dda176..04ec7ebf 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -177,20 +177,35 @@ Pool.prototype._init = function _init() { }); this.chain.on('fork', function(data) { - var peer = self.peers.load; - - this.emit('debug', - 'Fork at height %d: expected=%s received=%s checkpoint=%s', + self.emit('debug', + 'Fork at height %d: expected=%s received=%s checkpoint=%s peer=%s', data.height, utils.revHex(data.expected), utils.revHex(data.received), - data.checkpoint + data.checkpoint, + data.peer ? data.peer.host : '' ); - if (!peer) + if (!data.peer) return; - peer.destroy(); + data.peer.destroy(); + }); + + this.chain.on('invalid', function(data) { + self.emit('debug', + 'Invalid block at height: %d: hash=%s peer=%s', + data.height, + utils.revHex(data.hash), + data.peer ? data.peer.host : '' + ); + + if (!data.peer) + return; + + // We should technically use a ban score + // here instead of killing the peer. + data.peer.destroy(); }); this.options.wallets.forEach(function(w) { diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 464a2b5b..95820ce7 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -791,6 +791,8 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { if (flags.strictder !== false) { if (!script.isValidSignature(sig)) return false; + if (!script.isLowDER(sig)) + return false; } type = sig[sig.length - 1]; @@ -860,6 +862,8 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { if (flags.strictder !== false) { if (!script.isValidSignature(sig)) return false; + if (!script.isLowDER(sig)) + return false; } type = sig[sig.length - 1]; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 70d09094..608cb233 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -1085,7 +1085,7 @@ TX.prototype.fill = function fill(txs) { return inputs.length === this.inputs.length; }; -// Used for postVerify/ContextualBlockCheck and miner isFinalTx call. +// Used for verifyContext/ContextualBlockCheck and miner isFinalTx call. // BIP113 will require that time-locked transactions have nLockTime set to // less than the median time of the previous block they're contained in. TX.prototype.isFinalBlock = function isFinalBlock(block, prev, useMedian) { @@ -1096,17 +1096,29 @@ TX.prototype.isFinalBlock = function isFinalBlock(block, prev, useMedian) { // Used in AcceptToMemoryPool TX.prototype.isFinalMempool = function isFinalMempool(useMedian) { - var height = this.chain.height() + 1; - var ts = useMedian + var height, ts; + + if (!this.chain) + return true; + + height = this.chain.height() + 1; + ts = useMedian ? this.chain.getTip().getMedianTime() : utils.now(); + return this.isFinal(height, ts); }; // Used in the original bitcoind code for AcceptBlock TX.prototype.isFinalLegacy = function isFinalLegacy(block) { - var ts = block ? block.ts : utils.now(); - var height = this.chain.height(); + var ts, height; + + if (!this.chain) + return true; + + ts = block ? block.ts : utils.now(); + height = this.chain.height(); + return this.isFinal(height, ts); }; diff --git a/scripts/update.js b/scripts/update.js index 0c54057c..9e3796ba 100644 --- a/scripts/update.js +++ b/scripts/update.js @@ -22,6 +22,10 @@ pool.on('error', function(err) { utils.print('Error: %s', err.message); }); +pool.on('debug', function() { + utils.print.apply(utils, arguments); +}); + console.log('Updating bcoin preloaded chain...'); pool.on('block', function(block) {