mirror of
https://github.com/corda/corda.git
synced 2025-02-20 17:33:15 +00:00
Code cleanup, add comments and tests to reflect changes.
This commit is contained in:
parent
e7cb47ecd0
commit
a5dfaa255d
@ -12,17 +12,47 @@ class MerkleTreeException(val reason: String): Exception() {
|
||||
/**
|
||||
* Building and verification of Partial Merkle Tree.
|
||||
* Partial Merkle Tree is a minimal tree needed to check that given set of leaves belongs to a full Merkle Tree.
|
||||
* todo example of partial tree
|
||||
*
|
||||
* Example of Merkle tree with 5 leaves.
|
||||
*
|
||||
* h15
|
||||
* / \
|
||||
* h14 h55
|
||||
* / \ / \
|
||||
* h12 h34 h5->d(h5)
|
||||
* / \ / \ / \
|
||||
* l1 l2 l3 l4 l5->d(l5)
|
||||
*
|
||||
* l* denote hashes of leaves, h* - hashes of nodes below.
|
||||
* h5->d(h5) denotes duplication of left hand side node. These nodes are kept in a full tree as DuplicatedLeaf.
|
||||
* When filtering the tree for l5, we don't want to keep both l5 and it's duplicate (it can also be solved using null
|
||||
* values in a tree, but this solution is clearer).
|
||||
*
|
||||
* Example of Partial tree based on the tree above.
|
||||
*
|
||||
* ___
|
||||
* / \
|
||||
* _ _
|
||||
* / \ / \
|
||||
* h12 _ _ d(h5)
|
||||
* / \ / \
|
||||
* I3 l4 I5 d(l5)
|
||||
*
|
||||
* We want to check l3 and l5 - now turned into IncudedLeaf (I3 and I5 above). To verify that these two leaves belong to
|
||||
* the tree with a hash root h15 we need to provide a Merkle branch (or partial tree). In our case we need hashes:
|
||||
* h12, l4, d(l5) and d(h5). Verification is done by hashing the partial tree to obtain the root and checking it against
|
||||
* the obtained h15 hash. Additionally we store included hashes used in calculation and compare them to leaves hashes we got
|
||||
* (there can be a difference in obtained leaves ordering - that's why it's a set comparison not hashing leaves into a tree).
|
||||
* If both equalities hold, we can assume that l3 and l5 belong to the transaction with root h15.
|
||||
*/
|
||||
class PartialMerkleTree(
|
||||
val root: PartialTree
|
||||
) {
|
||||
|
||||
class PartialMerkleTree(val root: PartialTree) {
|
||||
/**
|
||||
* The structure is a little different than that of Merkle Tree.
|
||||
* Partial Tree by might not be a full binary tree. Leaves represent either original Merkle tree leaves
|
||||
* Partial Tree might not be a full binary tree. Leaves represent either original Merkle tree leaves
|
||||
* or cut subtree node with stored hash. We differentiate between the leaves that are included in a filtered
|
||||
* transaction and leaves that just keep hashes needed for calculation. Reason for this approach: during verification
|
||||
* it's easier to extract hashes used as base for this tree.
|
||||
* it's easier to extract hashes used as a base for this tree.
|
||||
*/
|
||||
sealed class PartialTree() {
|
||||
class IncludedLeaf(val hash: SecureHash): PartialTree()
|
||||
@ -32,14 +62,14 @@ class PartialMerkleTree(
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* @param merkleRoot
|
||||
* @param includeHashes
|
||||
* @param merkleRoot Root of full Merkle tree.
|
||||
* @param includeHashes Hashes that should be included in a partial tree.
|
||||
* @return Partial Merkle tree root.
|
||||
*/
|
||||
fun build(merkleRoot: MerkleTree, includeHashes: List<SecureHash>): PartialMerkleTree {
|
||||
val usedHashes = ArrayList<SecureHash>()
|
||||
//Too much included hashes or different ones.
|
||||
val tree = buildPartialTree(merkleRoot, includeHashes, usedHashes)
|
||||
//Too much included hashes or different ones.
|
||||
if(includeHashes.size != usedHashes.size)
|
||||
throw MerkleTreeException("Some of the provided hashes are not in the tree.")
|
||||
return PartialMerkleTree(tree.second)
|
||||
@ -84,14 +114,13 @@ class PartialMerkleTree(
|
||||
}
|
||||
|
||||
/**
|
||||
* @param merkleRootHash
|
||||
* @param hashesToCheck
|
||||
* @param merkleRootHash Hash that should be checked for equality with root calculated from this partial tree.
|
||||
* @param hashesToCheck List of included leaves hashes that should be found in this partial tree.
|
||||
*/
|
||||
fun verify(merkleRootHash: SecureHash, hashesToCheck: List<SecureHash>): Boolean {
|
||||
val usedHashes = ArrayList<SecureHash>()
|
||||
val verifyRoot = verify(root, usedHashes)
|
||||
//It means that we obtained more/less hashes than needed or different sets of hashes.
|
||||
//Ordering insensitive.
|
||||
if(hashesToCheck.size != usedHashes.size || hashesToCheck.minus(usedHashes).isNotEmpty())
|
||||
return false
|
||||
return (verifyRoot == merkleRootHash)
|
||||
@ -107,7 +136,7 @@ class PartialMerkleTree(
|
||||
return node.hash
|
||||
} else if (node is PartialTree.Leaf ) {
|
||||
return node.hash
|
||||
} else if (node is PartialTree.Node){
|
||||
} else if (node is PartialTree.Node) {
|
||||
val leftHash = verify(node.left, usedHashes)
|
||||
val rightHash = verify(node.right, usedHashes)
|
||||
return leftHash.hashConcat(rightHash)
|
||||
|
@ -8,17 +8,6 @@ import com.r3corda.core.crypto.*
|
||||
import com.r3corda.core.serialization.serialize
|
||||
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, outputs, commands, attachments' refs. Merkle Tree is kept in
|
||||
* a recursive data structure. Building is done bottom up, from all leaves' hashes.
|
||||
* If a row in a tree has an odd number of elements - the final hash is hashed with itself.
|
||||
*/
|
||||
|
||||
//Todo It's just mess, move it to wtx
|
||||
/**
|
||||
* Build filtered transaction using provided filtering functions.
|
||||
*/
|
||||
@ -39,10 +28,19 @@ fun WireTransaction.calculateLeavesHashes(): List<SecureHash> {
|
||||
fun SecureHash.hashConcat(other: SecureHash) = (this.bits + other.bits).sha256()
|
||||
fun <T: Any> serializedHash(x: T) = x.serialize().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, outputs, commands, attachments' refs. Merkle Tree is kept in
|
||||
* a recursive data structure. Building is done bottom up, from all leaves' hashes.
|
||||
* If a row in a tree has an odd number of elements - the final hash is hashed with itself.
|
||||
*/
|
||||
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)
|
||||
//DuplicatedLeaf is storing a hash of the righmost node that had to be duplicated to obtain the tree.
|
||||
//DuplicatedLeaf is storing a hash of the rightmost node that had to be duplicated to obtain the tree.
|
||||
//That duplication can cause problems while building and verifying partial tree (especially for trees with duplicate
|
||||
//attachments or commands).
|
||||
class DuplicatedLeaf(val value: SecureHash): MerkleTree(value)
|
||||
@ -51,43 +49,45 @@ sealed class MerkleTree(val hash: SecureHash) {
|
||||
val newHash = this.hash.hashConcat(right.hash)
|
||||
return Node(newHash, this, right)
|
||||
}
|
||||
}
|
||||
//todo -> to wire transaction
|
||||
/**
|
||||
* Merkle tree building using hashes.
|
||||
*/
|
||||
fun getMerkleTree(allLeavesHashes: List<SecureHash>): MerkleTree {
|
||||
val leaves = allLeavesHashes.map { MerkleTree.Leaf(it) }
|
||||
return buildMerkleTree(leaves)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tailrecursive function for building a tree bottom up.
|
||||
* @param lastNodesList MerkleTree nodes from previous level.
|
||||
* @return Tree root.
|
||||
*/
|
||||
tailrec fun buildMerkleTree(lastNodesList: List<MerkleTree>): MerkleTree {
|
||||
if (lastNodesList.size < 1)
|
||||
throw MerkleTreeException("Cannot calculate Merkle root on empty hash list.")
|
||||
if (lastNodesList.size == 1) {
|
||||
return lastNodesList[0] //Root reached.
|
||||
} else {
|
||||
val newLevelHashes: MutableList<MerkleTree> = ArrayList()
|
||||
var i = 0
|
||||
while (i < lastNodesList.size) {
|
||||
val left = lastNodesList[i]
|
||||
val n = lastNodesList.size
|
||||
val right = when {
|
||||
//If there is an odd number of elements at this level,
|
||||
//the last element is hashed with itself and stored as a Leaf.
|
||||
i+1 > n-1 -> MerkleTree.DuplicatedLeaf(lastNodesList[n-1].hash)
|
||||
else -> lastNodesList[i+1]
|
||||
}
|
||||
val combined = left.hashNodes(right)
|
||||
newLevelHashes.add(combined)
|
||||
i+=2
|
||||
companion object {
|
||||
/**
|
||||
* Merkle tree building using hashes.
|
||||
*/
|
||||
fun getMerkleTree(allLeavesHashes: List<SecureHash>): MerkleTree {
|
||||
val leaves = allLeavesHashes.map { MerkleTree.Leaf(it) }
|
||||
return buildMerkleTree(leaves)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>): MerkleTree {
|
||||
if (lastNodesList.size < 1)
|
||||
throw MerkleTreeException("Cannot calculate Merkle root on empty hash list.")
|
||||
if (lastNodesList.size == 1) {
|
||||
return lastNodesList[0] //Root reached.
|
||||
} else {
|
||||
val newLevelHashes: MutableList<MerkleTree> = ArrayList()
|
||||
var i = 0
|
||||
while (i < lastNodesList.size) {
|
||||
val left = lastNodesList[i]
|
||||
val n = lastNodesList.size
|
||||
val right = when {
|
||||
//If there is an odd number of elements at this level,
|
||||
//the last element is hashed with itself and stored as a Leaf.
|
||||
i+1 > n-1 -> MerkleTree.DuplicatedLeaf(lastNodesList[n-1].hash)
|
||||
else -> lastNodesList[i+1]
|
||||
}
|
||||
val combined = left.hashNodes(right)
|
||||
newLevelHashes.add(combined)
|
||||
i+=2
|
||||
}
|
||||
return buildMerkleTree(newLevelHashes)
|
||||
}
|
||||
}
|
||||
return buildMerkleTree(newLevelHashes)
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,8 +138,9 @@ class FilteredTransaction(
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Construction of filtered transaction with Partial Merkle Tree, takes WireTransaction and filtering functions
|
||||
* for inputs, outputs, attachments, commands.
|
||||
* Construction of filtered transaction with Partial Merkle Tree.
|
||||
* @param wtx WireTransaction to be filtered.
|
||||
* @param filterFuns filtering functions for inputs, outputs, attachments, commands.
|
||||
*/
|
||||
fun buildMerkleTransaction(wtx: WireTransaction,
|
||||
filterFuns: FilterFuns
|
||||
|
@ -2,31 +2,23 @@ package com.r3corda.core.crypto
|
||||
|
||||
import com.r3corda.contracts.asset.*
|
||||
import com.r3corda.core.contracts.DOLLARS
|
||||
import com.r3corda.core.contracts.TransactionType
|
||||
import com.r3corda.core.contracts.`issued by`
|
||||
import com.r3corda.core.contracts.`with notary`
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import com.r3corda.core.transactions.FilterFuns
|
||||
import com.r3corda.core.transactions.buildFilteredTransaction
|
||||
import com.r3corda.core.transactions.getMerkleRoot
|
||||
import com.r3corda.core.transactions.hashConcat
|
||||
import com.r3corda.core.utilities.DUMMY_NOTARY
|
||||
import com.r3corda.core.transactions.*
|
||||
import com.r3corda.core.utilities.DUMMY_PUBKEY_1
|
||||
import com.r3corda.testing.*
|
||||
|
||||
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
|
||||
class PartialMerkleTreeTest{
|
||||
class PartialMerkleTreeTest {
|
||||
val nodes = "abcdef"
|
||||
val hashed = nodes.map { it.serialize().sha256() }
|
||||
val root = SecureHash.Companion.parse("F6D8FB3720114F8D040D64F633B0D9178EB09A55AA7D62FAE1A070D1BF561051")
|
||||
|
||||
private fun makeTX() = TransactionType.General.Builder(DUMMY_NOTARY).withItems(
|
||||
1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY)
|
||||
val merkleTree = MerkleTree.getMerkleTree(hashed)
|
||||
|
||||
val testLedger = ledger {
|
||||
unverifiedTransaction {
|
||||
@ -50,98 +42,105 @@ class PartialMerkleTreeTest{
|
||||
|
||||
//Building full Merkle Tree tests.
|
||||
@Test
|
||||
fun `building Merkle tree with 6 nodes - no rightmost nodes`(){
|
||||
assertEquals(6, hashed.size)
|
||||
val mr = getMerkleRoot(hashed)
|
||||
assertEquals(root, mr)
|
||||
fun `building Merkle tree with 6 nodes - no rightmost nodes`() {
|
||||
assertEquals(root, merkleTree.hash)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `building Merkle tree one node`(){
|
||||
fun `building Merkle tree - no hashes`() {
|
||||
assertFailsWith<MerkleTreeException> { MerkleTree.getMerkleTree(emptyList()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `building Merkle tree one node`() {
|
||||
val node = 'a'.serialize().sha256()
|
||||
val mr = getMerkleRoot(listOf(node))
|
||||
assertEquals(node, mr)
|
||||
val mt = MerkleTree.getMerkleTree(listOf(node))
|
||||
assertEquals(node, mt.hash)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `building Merkle tree odd number of nodes`(){
|
||||
fun `building Merkle tree odd number of nodes`() {
|
||||
val odd = hashed.subList(0, 3)
|
||||
val h1 = hashed[0].hashConcat(hashed[1])
|
||||
val h2 = hashed[2].hashConcat(hashed[2])
|
||||
val expected = h1.hashConcat(h2)
|
||||
val root = getMerkleRoot(odd)
|
||||
assertEquals(root, expected)
|
||||
val mt = MerkleTree.getMerkleTree(odd)
|
||||
assertEquals(mt.hash, expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `building Merkle tree for a transaction`(){
|
||||
fun `building Merkle tree for a transaction`() {
|
||||
val filterFuns = FilterFuns(
|
||||
filterCommands = { x -> ALICE_PUBKEY in x.signers},
|
||||
filterOutputs = {true},
|
||||
filterInputs = {true})
|
||||
filterCommands = { x -> ALICE_PUBKEY in x.signers },
|
||||
filterOutputs = { true },
|
||||
filterInputs = { true })
|
||||
val mt = testTx.buildFilteredTransaction(filterFuns)
|
||||
assert(mt.verify(testTx.id))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ordering insensitive tree`(){
|
||||
val filterFuns = FilterFuns(filterCommands = { x -> true })
|
||||
val tx1 = makeTX()
|
||||
tx1.addCommand(Cash.Commands.Issue(1), ALICE_PUBKEY)
|
||||
tx1.addCommand(Cash.Commands.Issue(0), ALICE_PUBKEY)
|
||||
val wtx1 = tx1.toWireTransaction()
|
||||
|
||||
val tx2 = makeTX()
|
||||
tx2.addCommand(Cash.Commands.Issue(0), ALICE_PUBKEY)
|
||||
tx2.addCommand(Cash.Commands.Issue(1), ALICE_PUBKEY)
|
||||
val wtx2 = tx2.toWireTransaction()
|
||||
val mt1 = wtx1.buildFilteredTransaction(filterFuns)
|
||||
val mt2 = wtx2.buildFilteredTransaction(filterFuns)
|
||||
assertEquals(wtx1.id, wtx2.id)
|
||||
assert(mt1.verify(wtx1.id))
|
||||
assert(mt2.verify(wtx2.id))
|
||||
}
|
||||
|
||||
//Partial Merkle Tree building tests
|
||||
@Test
|
||||
fun `Partial Merkle Tree, only left nodes branch`(){
|
||||
val includeLeaves = listOf(false, false, false, true, false, true)
|
||||
fun `build Partial Merkle Tree, only left nodes branch`() {
|
||||
val inclHashes = listOf(hashed[3], hashed[5])
|
||||
val pmt = PartialMerkleTree.build(includeLeaves, hashed)
|
||||
assert(!pmt.includeBranch[2] && !pmt.includeBranch[4]&& !pmt.includeBranch[8])
|
||||
assert(pmt.verify(inclHashes, root))
|
||||
|
||||
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
|
||||
assert(pmt.verify(merkleTree.hash, inclHashes))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Partial Merkle Tree, include zero leaves`(){
|
||||
val includeLeaves = listOf(false, false, false, false, false, false)
|
||||
val pmt = PartialMerkleTree.build(includeLeaves, hashed)
|
||||
assertEquals(root, pmt.branchHashes[0])
|
||||
assert(pmt.verify(emptyList(), root))
|
||||
fun `build Partial Merkle Tree, include zero leaves`() {
|
||||
val pmt = PartialMerkleTree.build(merkleTree, emptyList())
|
||||
assert(pmt.verify(merkleTree.hash, emptyList()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Partial Merkle Tree, include all leaves`(){
|
||||
val includeLeaves = listOf(true, true, true, true, true, true)
|
||||
val pmt = PartialMerkleTree.build(includeLeaves, hashed)
|
||||
assert(pmt.verify(hashed, root))
|
||||
fun `build Partial Merkle Tree, include all leaves`() {
|
||||
val pmt = PartialMerkleTree.build(merkleTree, hashed)
|
||||
assert(pmt.verify(merkleTree.hash, hashed))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify Partial Merkle Tree - too many leaves failure`(){
|
||||
val includeLeaves = listOf(false, false, false, true, false, true)
|
||||
val inclHashes = listOf(hashed[3], hashed[5], hashed[0])
|
||||
val pmt = PartialMerkleTree.build(includeLeaves, hashed)
|
||||
assertFalse(pmt.verify(inclHashes, root))
|
||||
fun `build Partial Merkle Tree - duplicate leaves failure`() {
|
||||
val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[3], hashed[5])
|
||||
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, inclHashes) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify Partial Merkle Tree - wrong root`(){
|
||||
val includeLeaves = listOf(false, false, false, true, false, true)
|
||||
fun `verify Partial Merkle Tree - too many leaves failure`() {
|
||||
val inclHashes = arrayListOf(hashed[3], hashed[5])
|
||||
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
|
||||
inclHashes.add(hashed[0])
|
||||
assertFalse(pmt.verify(merkleTree.hash, inclHashes))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify Partial Merkle Tree - too little leaves failure`() {
|
||||
val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[0])
|
||||
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
|
||||
inclHashes.remove(hashed[0])
|
||||
assertFalse(pmt.verify(merkleTree.hash, inclHashes))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify Partial Merkle Tree - duplicate leaves failure`() {
|
||||
val mt = MerkleTree.getMerkleTree(hashed.subList(0,5)) //Odd number of leaves. Last one is duplicated.
|
||||
val inclHashes = arrayListOf(hashed[3], hashed[4])
|
||||
val pmt = PartialMerkleTree.build(mt, inclHashes)
|
||||
inclHashes.add(hashed[4])
|
||||
assertFalse(pmt.verify(mt.hash, inclHashes))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify Partial Merkle Tree - different leaves failure`() {
|
||||
val inclHashes = arrayListOf(hashed[3], hashed[5])
|
||||
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
|
||||
assertFalse(pmt.verify(merkleTree.hash, listOf(hashed[2], hashed[4])))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify Partial Merkle Tree - wrong root`() {
|
||||
val inclHashes = listOf(hashed[3], hashed[5])
|
||||
val pmt = PartialMerkleTree.build(includeLeaves, hashed)
|
||||
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
|
||||
val wrongRoot = hashed[3].hashConcat(hashed[5])
|
||||
assertFalse(pmt.verify(inclHashes, wrongRoot))
|
||||
assertFalse(pmt.verify(wrongRoot, inclHashes))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user