From ed37b610d29843a8cc35e9fed7f0324eb52be59c Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Mon, 8 Aug 2016 15:59:16 -0500 Subject: [PATCH 1/2] Adding function inside of Merkle to actually build and return the MerkleTree, changing how endianess is stored inside of the Merkle root from big endian to little endian --- .../org/bitcoins/core/consensus/Merkle.scala | 68 ++++++++++--------- .../bitcoins/core/consensus/MerkleTest.scala | 10 +-- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/main/scala/org/bitcoins/core/consensus/Merkle.scala b/src/main/scala/org/bitcoins/core/consensus/Merkle.scala index 3df5e7485c..6741b1c083 100644 --- a/src/main/scala/org/bitcoins/core/consensus/Merkle.scala +++ b/src/main/scala/org/bitcoins/core/consensus/Merkle.scala @@ -3,7 +3,7 @@ package org.bitcoins.core.consensus import org.bitcoins.core.crypto.DoubleSha256Digest import org.bitcoins.core.protocol.blockchain.Block import org.bitcoins.core.protocol.transaction.Transaction -import org.bitcoins.core.util.{BitcoinSLogger, CryptoUtil} +import org.bitcoins.core.util._ import scala.annotation.tailrec @@ -14,6 +14,8 @@ import scala.annotation.tailrec * https://github.com/bitcoin/bitcoin/blob/master/src/consensus/merkle.cpp */ trait Merkle extends BitcoinSLogger { + + type MerkleTree = BinaryTree[DoubleSha256Digest] /** * Computes the merkle root for the given block * @param block the given block that needs the merkle root computed @@ -21,34 +23,6 @@ trait Merkle extends BitcoinSLogger { */ def computeBlockMerkleRoot(block : Block) : DoubleSha256Digest = computeMerkleRoot(block.transactions) - /** - * Computes the merkle root of a given sequence of hashes - * This hash function assumes that all of the hashes are big endian encoded - * @param hashes the big endian encoded hashes from which the merkle root is derived from - * @param accum the accumulator for the recursive call on this function - more than likely you do not need to worry about this - * @return the merkle root - */ - @tailrec - final def computeMerkleRoot(hashes : Seq[DoubleSha256Digest], accum : List[DoubleSha256Digest] = List()) : DoubleSha256Digest = hashes match { - case Nil => - logger.debug("No more hashes to check") - if (accum.size == 1) DoubleSha256Digest(accum.head.bytes.reverse) - else if (accum.size == 0) throw new RuntimeException("We cannot have zero hashes and nothing in the accumulator, this means there is NO transaction in the block. " + - "There always should be ATLEAST one - the coinbase tx") - else computeMerkleRoot(accum.reverse, List()) - case h :: h1 :: t => - logger.debug("We have an even amount of txids") - logger.debug("Hashes: " + hashes.map(_.hex)) - val hash = CryptoUtil.doubleSHA256(h.bytes ++ h1.bytes) - computeMerkleRoot(t, hash :: accum) - case h :: t => - logger.debug("We have an odd amount of txids") - logger.debug("Hashes: " + hashes.map(_.hex)) - //means that we have an odd amount of txids, this means we duplicate the last hash in the tree - val hash = CryptoUtil.doubleSHA256(h.bytes ++ h.bytes) - computeMerkleRoot(t,hash :: accum) - } - /** * Computes the merkle root for the given sequence of transactions * @param transactions the list of transactions whose merkle root needs to be computed @@ -58,8 +32,40 @@ trait Merkle extends BitcoinSLogger { case Nil => throw new IllegalArgumentException("We cannot have zero transactions in the block. There always should be ATLEAST one - the coinbase tx") case h :: Nil => h.txId case h :: t => - val txHashes = transactions.map(tx => tx.txId) - computeMerkleRoot(txHashes) + val leafs = transactions.map(tx => Leaf(tx.txId)) + val merkleTree = build(leafs,Nil) + merkleTree.value.get + } + + @tailrec + final def build(subTrees: Seq[MerkleTree], accum: Seq[MerkleTree]): MerkleTree = subTrees match { + case Nil => + if (accum.size == 1) accum.head + else if (accum.isEmpty) throw new IllegalArgumentException("Should never have sub tree size of zero, this implies there was zero hashes given") + else build(accum.reverse, Nil) + case h :: h1 :: t => + logger.debug("Computing the merkle tree for two sub merkle trees") + logger.debug("Subtrees: " + subTrees) + val newTree = computeTree(h,h1) + logger.debug("newTree: " + newTree) + logger.debug("new subtree seq: " + t) + build(t, newTree +: accum) + case h :: t => + logger.debug("Computing the merkle tree for one sub merkle tree - this means duplicating the subtree") + logger.debug("Subtrees: " + subTrees) + //means that we have an odd amount of txids, this means we duplicate the last hash in the tree + val newTree = computeTree(h,h) + logger.debug("newTree: " + newTree) + logger.debug("new subtree seq: " + t) + build(t, newTree +: accum) + } + + + /** Computes the merkle tree of two sub merkle trees */ + def computeTree(tree1: MerkleTree, tree2: MerkleTree): MerkleTree = { + val bytes = tree1.value.get.bytes ++ tree2.value.get.bytes + val hash = CryptoUtil.doubleSHA256(bytes) + Node(hash,tree1,tree2) } } diff --git a/src/test/scala/org/bitcoins/core/consensus/MerkleTest.scala b/src/test/scala/org/bitcoins/core/consensus/MerkleTest.scala index a3ea7ffeeb..3a2885235b 100644 --- a/src/test/scala/org/bitcoins/core/consensus/MerkleTest.scala +++ b/src/test/scala/org/bitcoins/core/consensus/MerkleTest.scala @@ -24,7 +24,7 @@ class MerkleTest extends FlatSpec with MustMatchers { require(tx1.txId.hex == BitcoinSUtil.flipEndianess("5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2")) val transactions = Seq(coinbaseTx,tx1) - Merkle.computeMerkleRoot(transactions).hex must be ("8fb300e3fdb6f30a4c67233b997f99fdd518b968b9a3fd65857bfe78b2600719") + Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("8fb300e3fdb6f30a4c67233b997f99fdd518b968b9a3fd65857bfe78b2600719")) } it must "correctly compute the merkle root for a block with 3 transactions" in { @@ -37,7 +37,7 @@ class MerkleTest extends FlatSpec with MustMatchers { require(tx2.txId.hex == BitcoinSUtil.flipEndianess("d8c9d6a13a7fb8236833b1e93d298f4626deeb78b2f1814aa9a779961c08ce39")) val transactions = Seq(coinbaseTx, tx1, tx2) - Merkle.computeMerkleRoot(transactions).hex must be ("d277b5d20fab7cdb8140ab953323b585445d4920ad7226623d9c7ed0bc6b9a57") + Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("d277b5d20fab7cdb8140ab953323b585445d4920ad7226623d9c7ed0bc6b9a57")) } it must "correctly compute the merkle root for the 100,000 block which has 4 transactions" in { @@ -50,7 +50,7 @@ class MerkleTest extends FlatSpec with MustMatchers { val transactions = Seq(coinbaseTx, tx1,tx2,tx3) - Merkle.computeMerkleRoot(transactions).hex must be ("f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766") + Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766")) } @@ -68,7 +68,7 @@ class MerkleTest extends FlatSpec with MustMatchers { require(tx4.txId.hex == BitcoinSUtil.flipEndianess("b79606d7377285e1a4f9cb757c9f1a1d36f09c10bc52be49b170c6dd40767669")) val transactions = Seq(coinbaseTx,tx1,tx2,tx3,tx4) - Merkle.computeMerkleRoot(transactions).hex must be ("36b38854f9adf76b4646ab2c0f949846408cfab2c045f110d01f84f4122c5add") + Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("36b38854f9adf76b4646ab2c0f949846408cfab2c045f110d01f84f4122c5add")) } it must "correctly compute the merkle root for a block with 11 transactions" in { @@ -98,6 +98,6 @@ class MerkleTest extends FlatSpec with MustMatchers { val transactions = Seq(coinbaseTx,tx1,tx2,tx3,tx4,tx5,tx6,tx7,tx8,tx9,tx10) - Merkle.computeMerkleRoot(transactions).hex must be ("2c8230dfb6c7ad0949cbdb7d3c3616cc10770db7e832bbf5c9b261388828c6c4") + Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("2c8230dfb6c7ad0949cbdb7d3c3616cc10770db7e832bbf5c9b261388828c6c4")) } } From fb33977e926388425f916ed80b2e7add899e337d Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Mon, 8 Aug 2016 19:54:10 -0500 Subject: [PATCH 2/2] Adding helper function to build a merkle tree, removing some logging --- .../org/bitcoins/core/consensus/Merkle.scala | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/bitcoins/core/consensus/Merkle.scala b/src/main/scala/org/bitcoins/core/consensus/Merkle.scala index 6741b1c083..43ce0730de 100644 --- a/src/main/scala/org/bitcoins/core/consensus/Merkle.scala +++ b/src/main/scala/org/bitcoins/core/consensus/Merkle.scala @@ -37,6 +37,12 @@ trait Merkle extends BitcoinSLogger { merkleTree.value.get } + /** Builds a [[MerkleTree]] from sequence of sub merkle trees. + * This subTrees can be individual txids (leafs) or full blown subtrees + * @param subTrees the trees that need to be hashed + * @param accum the accumulated merkle trees, waiting to be hashed next round + * @return the entire Merkle tree computed from the given merkle trees + */ @tailrec final def build(subTrees: Seq[MerkleTree], accum: Seq[MerkleTree]): MerkleTree = subTrees match { case Nil => @@ -44,22 +50,21 @@ trait Merkle extends BitcoinSLogger { else if (accum.isEmpty) throw new IllegalArgumentException("Should never have sub tree size of zero, this implies there was zero hashes given") else build(accum.reverse, Nil) case h :: h1 :: t => - logger.debug("Computing the merkle tree for two sub merkle trees") logger.debug("Subtrees: " + subTrees) val newTree = computeTree(h,h1) - logger.debug("newTree: " + newTree) - logger.debug("new subtree seq: " + t) build(t, newTree +: accum) case h :: t => - logger.debug("Computing the merkle tree for one sub merkle tree - this means duplicating the subtree") logger.debug("Subtrees: " + subTrees) //means that we have an odd amount of txids, this means we duplicate the last hash in the tree val newTree = computeTree(h,h) - logger.debug("newTree: " + newTree) - logger.debug("new subtree seq: " + t) build(t, newTree +: accum) } + /** Builds a merkle tree from a sequence of hashes */ + def build(hashes: Seq[DoubleSha256Digest]): MerkleTree = { + val leafs = hashes.map(Leaf(_)) + build(leafs,Nil) + } /** Computes the merkle tree of two sub merkle trees */ def computeTree(tree1: MerkleTree, tree2: MerkleTree): MerkleTree = {