From d49b04158f9d69764eee32359671cb13c021761b Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Sat, 10 May 2014 19:45:03 +0400 Subject: [PATCH] block: validate partial merkle tree --- lib/bcoin/block.js | 60 +++++++++++++++++++++++++++++++++++++++++++--- lib/bcoin/tx.js | 2 +- test/block-test.js | 39 ++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 test/block-test.js diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 8dda0653..d562ef34 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -18,7 +18,14 @@ function Block(data) { }); this.flags = data.flags; + // List of matched TXs + this.tx = []; + this.invalid = false; + this._hash = null; + + // Verify partial merkle tree and fill `ts` array + this._verifyMerkle(); } module.exports = Block; @@ -42,13 +49,60 @@ Block.prototype.abbr = function abbr() { }; Block.prototype.verify = function verify() { - return utils.testTarget(this.bits, this.hash()); + return !this.invalid && utils.testTarget(this.bits, this.hash()); }; Block.prototype.render = function render(framer) { return []; }; -Block.prototype.hasMerkle = function hasMerkle(hash) { - return this.hashes.indexOf(hash) !== -1; +Block.prototype.hasTX = function hasTX(hash) { + return this.tx.indexOf(hash) !== -1; +}; + +Block.prototype._verifyMerkle = function verifyMerkle() { + var height = 0; + + // Count leafs + for (var i = this.totalTX; i > 0; i >>= 1) + height++; + if (this.totalTX > (1 << (height - 1))) + height++; + + var tx = []; + var i = 0; + var j = 0; + var hashes = this.hashes; + var flags = this.flags; + + var root = visit(1); + if (!root || root !== this.merkleRoot) { + this.invalid = true; + return; + } + this.tx = tx; + function visit(depth) { + if (i === flags.length * 8 || j === hashes.length) + return null; + + var flag = (flags[i >> 3] >>> (i & 7)) & 1; + i++; + + if (flag === 0 || depth === height) { + if (depth === height) + tx.push(hashes[j]); + return hashes[j++]; + } + + // Go deeper + var left = visit(depth + 1); + if (!left) + return null; + var right = visit(depth + 1); + if (right === left) + return null; + if (!right) + right = left; + return utils.toHex(utils.dsha256(left + right, 'hex')); + } }; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index cfb51c13..51129c2b 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -33,7 +33,7 @@ function TX(data, block) { }, this); } - if (!data.ts && block && block.hasMerkle(this.hash('hex'))) + if (!data.ts && block && block.hasTX(this.hash('hex'))) this.ts = block.ts; } module.exports = TX; diff --git a/test/block-test.js b/test/block-test.js new file mode 100644 index 00000000..fb55b5ff --- /dev/null +++ b/test/block-test.js @@ -0,0 +1,39 @@ +var assert = require('assert'); +var bn = require('bn.js'); +var bcoin = require('../'); + +describe('Block', function() { + it('should parse partial merkle tree', function() { + var block = bcoin.block({ + type: 'block', + version: 2, + prevBlock: 'd1831d4411bdfda89d9d8c842b541beafd1437fc560dbe5c0000000000000000', + merkleRoot: '28bec1d35af480ba3884553d72694f6ba6c163a5c081d7e6edaec15f373f19af', + ts: 1399713634, + bits: 419465580, + nonce: 1186968784, + totalTX: 461, + hashes:[ + '7d22e53bce1bbb3294d1a396c5acc45bdcc8f192cb492f0d9f55421fd4c62de1', + '9d6d585fdaf3737b9a54aaee1dd003f498328d699b7dfb42dd2b44b6ebde2333', + '8b61da3053d6f382f2145bdd856bc5dcf052c3a11c1784d3d51b2cbe0f6d0923', + 'd7bbaae4716cb0d329d755b707cee588cddc68601f99bc05fef1fabeb8dfe4a0', + '7393f84cd04ca8931975c66282ebf1847c78d8de6c2578d4f9bae23bc6f30857', + 'ec8c51de3170301430ec56f6703533d9ea5b05c6fa7068954bcb90eed8c2ee5c', + 'c7c152869db09a5ae2291fa03142912d9d7aba75be7d491a8ac4230ee9a920cb', + '5adbf04583354515a225f2c418de7c5cdac4cef211820c79717cd2c50412153f', + '1f5e46b9da3a8b1241f4a1501741d3453bafddf6135b600b926e3f4056c6d564', + '33825657ba32afe269819f01993bd77baba86379043168c94845d32370e53562' ], + flags: [ 245, 90, 0 ] + }); + + assert(block.verify()); + assert.equal(block.tx.length, 2); + assert.equal( + block.tx[0], + '7393f84cd04ca8931975c66282ebf1847c78d8de6c2578d4f9bae23bc6f30857'); + assert.equal( + block.tx[1], + 'ec8c51de3170301430ec56f6703533d9ea5b05c6fa7068954bcb90eed8c2ee5c'); + }); +});