From d58e09a3a7eabc0ab6f137d2b184f52de5d1aa34 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Thu, 5 May 2016 00:42:05 +1000 Subject: [PATCH] Block: add checkMerkleRoot --- src/block.js | 30 ++++++++++++++++++++++++++++++ test/block.js | 38 ++++++++++++++++++++++++++++++++++++++ test/fixtures/block.json | 14 ++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/src/block.js b/src/block.js index 75a7980..9356c89 100644 --- a/src/block.js +++ b/src/block.js @@ -1,3 +1,4 @@ +var createHash = require('create-hash') var bufferutils = require('./bufferutils') var bcrypto = require('./crypto') var bufferCompare = require('buffer-compare') @@ -133,6 +134,35 @@ Block.calculateTarget = function (bits) { return target } +Block.calculateMerkleRoot = function (transactions) { + var length = transactions.length + if (length === 0) throw TypeError('Cannot compute merkle root for zero transactions') + + var hashes = transactions.map(function (transaction) { return transaction.getHash() }) + + while (length > 1) { + var j = 0 + + for (var i = 0; i < length; i += 2, ++j) { + var hasher = createHash('sha256') + hasher.update(hashes[i]) + hasher.update(i + 1 !== length ? hashes[i + 1] : hashes[i]) + hashes[j] = bcrypto.sha256(hasher.digest()) + } + + length = j + } + + return hashes[0] +} + +Block.prototype.checkMerkleRoot = function () { + if (!this.transactions) return false + + var actualMerkleRoot = Block.calculateMerkleRoot(this.transactions) + return bufferCompare(this.merkleRoot, actualMerkleRoot) === 0 +} + Block.prototype.checkProofOfWork = function () { var hash = bufferReverse(this.getHash()) var target = Block.calculateTarget(this.bits) diff --git a/test/block.js b/test/block.js index 7dd653f..97cb700 100644 --- a/test/block.js +++ b/test/block.js @@ -97,6 +97,44 @@ describe('Block', function () { }) }) + describe('calculateMerkleRoot', function () { + it('should throw on zero-length transaction array', function () { + assert.throws(function () { + Block.calculateMerkleRoot([]) + }, /Cannot compute merkle root for zero transactions/) + }) + + fixtures.valid.forEach(function (f) { + if (f.hex.length === 160) return + + var block + + beforeEach(function () { + block = Block.fromHex(f.hex) + }) + + it('returns ' + f.merkleRoot + ' for ' + f.id, function () { + assert.strictEqual(Block.calculateMerkleRoot(block.transactions).toString('hex'), f.merkleRoot) + }) + }) + }) + + describe('checkMerkleRoot', function () { + fixtures.valid.forEach(function (f) { + if (f.hex.length === 160) return + + var block + + beforeEach(function () { + block = Block.fromHex(f.hex) + }) + + it('returns ' + f.valid + ' for ' + f.id, function () { + assert.strictEqual(block.checkMerkleRoot(), true) + }) + }) + }) + describe('checkProofOfWork', function () { fixtures.valid.forEach(function (f) { var block diff --git a/test/fixtures/block.json b/test/fixtures/block.json index a18fca5..c7aa5f7 100644 --- a/test/fixtures/block.json +++ b/test/fixtures/block.json @@ -101,6 +101,20 @@ "timestamp": 1439710609, "valid": false, "version": 3 + }, + { + "description": "Genesis Block", + "bits": 486604799, + "hash": "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000", + "height": 0, + "hex": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "id": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + "merkleRoot": "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a", + "nonce": 2083236893, + "prevHash": "0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": 1231006505, + "valid": true, + "version": 1 } ], "invalid": [