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")) } }