From c0bcbc381d54c3078aa8d2c28460bda6a3a2e630 Mon Sep 17 00:00:00 2001 From: chalkido Date: Wed, 8 Feb 2017 11:46:08 +0000 Subject: [PATCH] Extract Class pattern: Move MerkleTree (from MerkleTransaction) to its own class file inside core.crypto package. --- .../net/corda/core/crypto/MerkleTree.kt | 69 +++++++++++++++++++ .../corda/core/crypto/PartialMerkleTree.kt | 2 +- .../core/transactions/MerkleTransaction.kt | 66 ------------------ .../core/transactions/WireTransaction.kt | 1 + 4 files changed, 71 insertions(+), 67 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt diff --git a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt new file mode 100644 index 0000000000..817ef515e6 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt @@ -0,0 +1,69 @@ +package net.corda.core.crypto + +import java.util.* + +/** + * Creation and verification of a Merkle Tree for a Wire Transaction. + * + * See: https://en.wikipedia.org/wiki/Merkle_tree + * + * Transaction is split into following blocks: inputs, attachments' refs, outputs, commands, notary, + * signers, tx type, timestamp. Merkle Tree is kept in a recursive data structure. Building is done bottom up, + * from all leaves' hashes. If number of leaves is not a power of two, the tree is padded with zero hashes. + */ +sealed class MerkleTree(val hash: SecureHash) { + class Leaf(val value: SecureHash) : MerkleTree(value) + class Node(val value: SecureHash, val left: MerkleTree, val right: MerkleTree) : MerkleTree(value) + + companion object { + private fun isPow2(num: Int): Boolean = num and (num-1) == 0 + + /** + * Merkle tree building using hashes, with zero hash padding to full power of 2. + */ + @Throws(IllegalArgumentException::class) + fun getMerkleTree(allLeavesHashes: List): MerkleTree { + val leaves = padWithZeros(allLeavesHashes).map { Leaf(it) } + return buildMerkleTree(leaves) + } + + // If number of leaves in the tree is not a power of 2, we need to pad it with zero hashes. + private fun padWithZeros(allLeavesHashes: List): List { + var n = allLeavesHashes.size + if (isPow2(n)) return allLeavesHashes + val paddedHashes = ArrayList(allLeavesHashes) + while (!isPow2(n)) { + paddedHashes.add(SecureHash.zeroHash) + n++ + } + return paddedHashes + } + + /** + * Tailrecursive function for building a tree bottom up. + * @param lastNodesList MerkleTree nodes from previous level. + * @return Tree root. + */ + private tailrec fun buildMerkleTree(lastNodesList: List): MerkleTree { + if (lastNodesList.isEmpty()) + throw MerkleTreeException("Cannot calculate Merkle root on empty hash list.") + if (lastNodesList.size == 1) { + return lastNodesList[0] //Root reached. + } else { + val newLevelHashes: MutableList = ArrayList() + var i = 0 + val n = lastNodesList.size + while (i < n) { + val left = lastNodesList[i] + require(i+1 <= n-1) { "Sanity check: number of nodes should be even." } + val right = lastNodesList[i+1] + val newHash = left.hash.hashConcat(right.hash) + val combined = Node(newHash, left, right) + newLevelHashes.add(combined) + i += 2 + } + return buildMerkleTree(newLevelHashes) + } + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt index d03767176c..488d4c52c7 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt @@ -1,6 +1,6 @@ package net.corda.core.crypto -import net.corda.core.transactions.MerkleTree +import net.corda.core.crypto.MerkleTree import net.corda.core.crypto.SecureHash.Companion.zeroHash import java.util.* diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index c0198ec456..2cd6811dd3 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -13,72 +13,6 @@ fun serializedHash(x: T): SecureHash { return x.serialize(kryo).hash } -/** - * Creation and verification of a Merkle Tree for a Wire Transaction. - * - * See: https://en.wikipedia.org/wiki/Merkle_tree - * - * Transaction is split into following blocks: inputs, attachments' refs, outputs, commands, notary, - * signers, tx type, timestamp. Merkle Tree is kept in a recursive data structure. Building is done bottom up, - * from all leaves' hashes. If number of leaves is not a power of two, the tree is padded with zero hashes. - */ -sealed class MerkleTree(val hash: SecureHash) { - class Leaf(val value: SecureHash) : MerkleTree(value) - class Node(val value: SecureHash, val left: MerkleTree, val right: MerkleTree) : MerkleTree(value) - - companion object { - private fun isPow2(num: Int): Boolean = num and (num-1) == 0 - - /** - * Merkle tree building using hashes, with zero hash padding to full power of 2. - */ - @Throws(IllegalArgumentException::class) - fun getMerkleTree(allLeavesHashes: List): MerkleTree { - val leaves = padWithZeros(allLeavesHashes).map { MerkleTree.Leaf(it) } - return buildMerkleTree(leaves) - } - - // If number of leaves in the tree is not a power of 2, we need to pad it with zero hashes. - private fun padWithZeros(allLeavesHashes: List): List { - var n = allLeavesHashes.size - if (isPow2(n)) return allLeavesHashes - val paddedHashes = ArrayList(allLeavesHashes) - while (!isPow2(n)) { - paddedHashes.add(zeroHash) - n++ - } - return paddedHashes - } - - /** - * Tailrecursive function for building a tree bottom up. - * @param lastNodesList MerkleTree nodes from previous level. - * @return Tree root. - */ - private tailrec fun buildMerkleTree(lastNodesList: List): MerkleTree { - if (lastNodesList.isEmpty()) - throw MerkleTreeException("Cannot calculate Merkle root on empty hash list.") - if (lastNodesList.size == 1) { - return lastNodesList[0] //Root reached. - } else { - val newLevelHashes: MutableList = ArrayList() - var i = 0 - val n = lastNodesList.size - while (i < n) { - val left = lastNodesList[i] - require(i+1 <= n-1) { "Sanity check: number of nodes should be even." } - val right = lastNodesList[i+1] - val newHash = left.hash.hashConcat(right.hash) - val combined = Node(newHash, left, right) - newLevelHashes.add(combined) - i += 2 - } - return buildMerkleTree(newLevelHashes) - } - } - } -} - /** * Interface implemented by WireTransaction and FilteredLeaves. * Property traversableList assures that we always calculate hashes in the same order, lets us define which diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 583448f518..999f4ff41c 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -3,6 +3,7 @@ package net.corda.core.transactions import com.esotericsoftware.kryo.Kryo import net.corda.core.contracts.* import net.corda.core.crypto.CompositeKey +import net.corda.core.crypto.MerkleTree import net.corda.core.crypto.Party import net.corda.core.crypto.SecureHash import net.corda.core.indexOfOrThrow