From 83d68a09f7dfd179528566f9e7c9713faca4dee5 Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 13 Apr 2022 18:28:35 +0300 Subject: [PATCH 01/28] implements merkle tree implementation and insertion to it --- src/main/java/crypto/Sha3256Hasher.java | 31 ++++++ .../modules/ads/merkletree/MerkleNode.java | 87 +++++++++++++++++ .../modules/ads/merkletree/MerkleTree.java | 97 +++++++++++++++++++ .../java/modules/ads/skiplist/SkipList.java | 22 ----- ...{SkipListTest.java => MerkleTreeTest.java} | 20 +++- 5 files changed, 234 insertions(+), 23 deletions(-) create mode 100644 src/main/java/modules/ads/merkletree/MerkleNode.java create mode 100644 src/main/java/modules/ads/merkletree/MerkleTree.java delete mode 100644 src/main/java/modules/ads/skiplist/SkipList.java rename src/test/java/modules/ads/{SkipListTest.java => MerkleTreeTest.java} (56%) diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 4a954990..fce60614 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -1,10 +1,13 @@ package crypto; +import java.io.ByteArrayOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import model.codec.EncodedEntity; import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; /** * Implements SHA3-256 hashing functionality. @@ -12,6 +15,14 @@ public class Sha3256Hasher implements Hasher { private static final String HASH_ALG_SHA_3_256 = "SHA3-256"; + private static final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + private static byte[] concat(final byte[] e1, final byte[] e2) { + byte[] res = new byte[e1.length + e2.length]; + System.arraycopy(e1, 0, res, 0, e1.length); + System.arraycopy(e2, 0, res, e1.length, e2.length); + return res; + } /** * Computes hash of the given encoded entity. @@ -29,4 +40,24 @@ public Sha3256Hash computeHash(EncodedEntity e) { throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); } } + + public Sha3256Hash computeHash(Identifier id) { + try { + MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); + byte[] hashValue = md.digest(id.getBytes()); + return new Sha3256Hash(hashValue); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); + } + } + + public Sha3256Hash computeHash(byte[] b1, byte[] b2) { + try { + MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); + byte[] hashValue = md.digest(concat(b1, b2)); + return new Sha3256Hash(hashValue); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); + } + } } diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java new file mode 100644 index 00000000..bd93178e --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -0,0 +1,87 @@ +package modules.ads.merkletree; + +import crypto.Sha3256Hasher; +import model.Entity; +import model.crypto.Sha3256Hash; + +public class MerkleNode { + private static final Sha3256Hasher hasher = new Sha3256Hasher(); + private MerkleNode left; + private MerkleNode right; + private MerkleNode parent; + private boolean isLeft; + private Sha3256Hash hash; + + public MerkleNode() { + this.left = null; + this.right = null; + this.parent = null; + this.isLeft = false; + this.hash = null; + } + + public MerkleNode(Entity e, boolean isLeft) { + this.left = null; + this.right = null; + this.parent = null; + this.isLeft = isLeft; + this.hash = hasher.computeHash(e.id()); + } + + public MerkleNode(Sha3256Hash hash) { + this.left = null; + this.right = null; + this.parent = null; + this.isLeft = false; + this.hash = hash; + } + + public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { + this.left = left; + this.right = right; + this.parent = null; + this.isLeft = false; + this.hash = hash; + } + + public MerkleNode getLeft() { + return left; + } + + public void setLeft(MerkleNode left) { + this.left = left; + } + + public MerkleNode getRight() { + return right; + } + + public void setRight(MerkleNode right) { + this.right = right; + } + + public MerkleNode getParent() { + return parent; + } + + public void setParent(MerkleNode parent) { + this.parent = parent; + } + + public Sha3256Hash getHash() { + return hash; + } + + public void setHash(Sha3256Hash hash) { + this.hash = hash; + } + + public boolean isLeft() { + return isLeft; + } + + public void setLeft(boolean isLeft) { + this.isLeft = isLeft; + } + +} diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java new file mode 100644 index 00000000..e9f6f03b --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -0,0 +1,97 @@ +package modules.ads.merkletree; + +import crypto.Sha3256Hasher; +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; +import modules.ads.AuthenticatedDataStructure; +import modules.ads.AuthenticatedEntity; +import org.apache.commons.collections.list.TreeList; + +import java.lang.reflect.Array; +import java.util.*; + +/** + * Implementation of an in-memory Authenticated Skip List + * that is capable of storing and retrieval of LightChain entities. + */ +public class MerkleTree implements AuthenticatedDataStructure { + private static final Sha3256Hasher hasher = new Sha3256Hasher(); + private MerkleNode root = new MerkleNode(); + private final ArrayList leafNodes = new ArrayList<>(); + private final ArrayList entities = new ArrayList<>(); + + public MerkleTree() { + } + + @Override + public AuthenticatedEntity put(Entity e) { + entities.add(e); + leafNodes.add(new MerkleNode(e, false)); + buildMerkleTree(); + printLevelOrderTraversal(root); + return null; + } + + @Override + public AuthenticatedEntity get(Identifier id) { + return null; + } + + private void buildMerkleTree() { + ArrayList parentNodes = new ArrayList<>(); + ArrayList childNodes = new ArrayList<>(leafNodes); + while (childNodes.size() > 1) { + int idx = 0; + int len = childNodes.size(); + while (idx < len) { + MerkleNode left = childNodes.get(idx); + left.setLeft(true); + MerkleNode right = null; + if (idx + 1 < len) { + right = childNodes.get(idx + 1); + } else { + right = new MerkleNode(left.getHash()); + } + Sha3256Hash hash = hasher.computeHash(left.getHash().getBytes(), right.getHash().getBytes()); + MerkleNode parent = new MerkleNode(hash, left, right); + left.setParent(parent); + right.setParent(parent); + parentNodes.add(parent); + idx += 2; + } + childNodes = parentNodes; + parentNodes = new ArrayList<>(); + } + root = childNodes.get(0); + } + + private static void printLevelOrderTraversal(MerkleNode r) { + if (r == null) { + return; + } + if ((r.getLeft() == null && r.getRight() == null)) { + System.out.println(Arrays.toString(r.getHash().getBytes())); + } + Queue queue = new LinkedList<>(); + queue.add(r); + queue.add(null); + while (!queue.isEmpty()) { + MerkleNode node = queue.poll(); + if (node != null) { + System.out.println(Arrays.toString(node.getHash().getBytes())); + } else { + System.out.println(); + if (!queue.isEmpty()) { + queue.add(null); + } + } + if (node != null && node.getLeft() != null) { + queue.add(node.getLeft()); + } + if (node != null && node.getRight() != null) { + queue.add(node.getRight()); + } + } + } +} diff --git a/src/main/java/modules/ads/skiplist/SkipList.java b/src/main/java/modules/ads/skiplist/SkipList.java deleted file mode 100644 index 6a77ecdb..00000000 --- a/src/main/java/modules/ads/skiplist/SkipList.java +++ /dev/null @@ -1,22 +0,0 @@ -package modules.ads.skiplist; - -import model.Entity; -import model.lightchain.Identifier; -import modules.ads.AuthenticatedDataStructure; -import modules.ads.AuthenticatedEntity; - -/** - * Implementation of an in-memory Authenticated Skip List - * that is capable of storing and retrieval of LightChain entities. - */ -public class SkipList implements AuthenticatedDataStructure { - @Override - public AuthenticatedEntity put(Entity e) { - return null; - } - - @Override - public AuthenticatedEntity get(Identifier id) { - return null; - } -} diff --git a/src/test/java/modules/ads/SkipListTest.java b/src/test/java/modules/ads/MerkleTreeTest.java similarity index 56% rename from src/test/java/modules/ads/SkipListTest.java rename to src/test/java/modules/ads/MerkleTreeTest.java index 7b48990a..f6fa76ea 100644 --- a/src/test/java/modules/ads/SkipListTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -1,9 +1,13 @@ package modules.ads; +import modules.ads.merkletree.MerkleTree; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import unittest.fixtures.EntityFixture; /** * Encapsulates tests for an authenticated and concurrent implementation of SkipList ADS. */ -public class SkipListTest { +public class MerkleTreeTest { // TODO: writing tests to cover // 1. When putting a unique entity into skip list, we can recover it. // 2. Proof of membership for putting and getting an entity is the same. @@ -15,4 +19,18 @@ public class SkipListTest { // 8. Tampering with root identifier of an authenticated entity fails its verification. // 9. Tampering with entity of an authenticated entity fails its verification. // 10. Tampering with proof of an authenticated entity fails its verification. + + @Test + public void TestVerification() { // Do not work always + MerkleTree merkleTree = new MerkleTree(); + EntityFixture entityFixture1 = new EntityFixture(); + merkleTree.put(entityFixture1); + EntityFixture entityFixture2 = new EntityFixture(); + merkleTree.put(entityFixture2); + EntityFixture entityFixture3 = new EntityFixture(); + merkleTree.put(entityFixture3); + System.out.println("---"); + EntityFixture entityFixture4 = new EntityFixture(); + merkleTree.put(entityFixture4); + } } From 877ba354b38c8e50b950403c57329cd8df07efb3 Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 13 Apr 2022 21:39:26 +0300 Subject: [PATCH 02/28] implements merkle tree authentication --- src/main/java/crypto/Sha3256Hasher.java | 11 +++- .../ads/AuthenticatedDataStructure.java | 2 +- .../java/modules/ads/AuthenticatedEntity.java | 4 +- .../java/modules/ads/MembershipProof.java | 14 +++-- .../ads/merkletree/AuthenticatedEntity.java | 42 +++++++++++++ .../modules/ads/merkletree/MerkleNode.java | 8 +++ .../modules/ads/merkletree/MerkleTree.java | 59 ++++++++----------- .../java/modules/ads/merkletree/Proof.java | 34 +++++++++++ .../java/modules/ads/merkletree/Verifier.java | 31 ++++++++++ src/test/java/modules/ads/MerkleTreeTest.java | 27 +++++---- .../unittest/fixtures/MerkleTreeFixture.java | 19 ++++++ 11 files changed, 193 insertions(+), 58 deletions(-) create mode 100644 src/main/java/modules/ads/merkletree/AuthenticatedEntity.java create mode 100644 src/main/java/modules/ads/merkletree/Proof.java create mode 100644 src/main/java/modules/ads/merkletree/Verifier.java create mode 100644 src/test/java/unittest/fixtures/MerkleTreeFixture.java diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index fce60614..9888a248 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -54,10 +54,17 @@ public Sha3256Hash computeHash(Identifier id) { public Sha3256Hash computeHash(byte[] b1, byte[] b2) { try { MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); - byte[] hashValue = md.digest(concat(b1, b2)); - return new Sha3256Hash(hashValue); + int compare = Arrays.compare(b1, b2); + if (compare > 0) { + return new Sha3256Hash(md.digest(concat(b1, b2))); + } + return new Sha3256Hash(md.digest(concat(b2, b1))); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); } } + + public Sha3256Hash computeHash(Sha3256Hash sha3256Hash, Sha3256Hash sha3256Hash1) { + return computeHash(sha3256Hash.getBytes(), sha3256Hash1.getBytes()); + } } diff --git a/src/main/java/modules/ads/AuthenticatedDataStructure.java b/src/main/java/modules/ads/AuthenticatedDataStructure.java index 8fde6250..fc8db5e3 100644 --- a/src/main/java/modules/ads/AuthenticatedDataStructure.java +++ b/src/main/java/modules/ads/AuthenticatedDataStructure.java @@ -9,5 +9,5 @@ public interface AuthenticatedDataStructure { AuthenticatedEntity put(Entity e); - AuthenticatedEntity get(Identifier id); + AuthenticatedEntity get(Entity e); } diff --git a/src/main/java/modules/ads/AuthenticatedEntity.java b/src/main/java/modules/ads/AuthenticatedEntity.java index d66d35a8..449aa167 100644 --- a/src/main/java/modules/ads/AuthenticatedEntity.java +++ b/src/main/java/modules/ads/AuthenticatedEntity.java @@ -7,7 +7,7 @@ * that entity against a root identifier. */ public abstract class AuthenticatedEntity extends Entity { - abstract Entity getEntity(); + public abstract Entity getEntity(); - abstract MembershipProof getMembershipProof(); + public abstract MembershipProof getMembershipProof(); } diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index 50078bd9..d7e2ef01 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -1,7 +1,10 @@ package modules.ads; +import model.crypto.Sha3256Hash; import model.lightchain.Identifier; +import java.util.ArrayList; + /** * Represents a Merkle Proof of membership against a certain root identifier. */ @@ -9,15 +12,14 @@ public interface MembershipProof { /** * Root of the authenticated data structure that this proof belongs to. * - * @return root identifier. + * @return hash value of the root node. */ - Identifier getRoot(); + Sha3256Hash getRoot(); /** - * Sibling of the given identifier on the membership Merkle Proof path to the root. + * Returns the path of the proof of membership. * - * @param identifier identifier of the entity. - * @return sibling of given identifier. + * @return path of the proof of membership. */ - Identifier getSiblingOf(Identifier identifier); + ArrayList getPath(); } diff --git a/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java b/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java new file mode 100644 index 00000000..9a28d982 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java @@ -0,0 +1,42 @@ +package modules.ads.merkletree; + +import model.Entity; +import modules.ads.MembershipProof; + +public class AuthenticatedEntity extends modules.ads.AuthenticatedEntity { + private MembershipProof membershipProof; + private String type; + private Entity entity; + + public AuthenticatedEntity(Proof proof, String type, Entity e) { + this.membershipProof = proof; + this.type = type; + this.entity = e; + } + + @Override + public String type() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public Entity getEntity() { + return entity; + } + + public void setEntity(Entity entity) { + this.entity = entity; + } + @Override + public MembershipProof getMembershipProof() { + return membershipProof; + } + + public void setMembershipProof(MembershipProof membershipProof) { + this.membershipProof = membershipProof; + } +} diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index bd93178e..8e84fc13 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -84,4 +84,12 @@ public void setLeft(boolean isLeft) { this.isLeft = isLeft; } + public MerkleNode getSibling() { + if (isLeft()) { + return parent.getRight(); + } else { + return parent.getLeft(); + } + } + } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index e9f6f03b..e498f37c 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -6,9 +6,7 @@ import model.lightchain.Identifier; import modules.ads.AuthenticatedDataStructure; import modules.ads.AuthenticatedEntity; -import org.apache.commons.collections.list.TreeList; -import java.lang.reflect.Array; import java.util.*; /** @@ -29,15 +27,35 @@ public AuthenticatedEntity put(Entity e) { entities.add(e); leafNodes.add(new MerkleNode(e, false)); buildMerkleTree(); - printLevelOrderTraversal(root); - return null; + Proof proof = getProof(e.id()); + return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); } @Override - public AuthenticatedEntity get(Identifier id) { - return null; + public AuthenticatedEntity get(Entity e) { + Proof proof = getProof(e.id()); + return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); } + private Proof getProof(Identifier id) { + int idx = -1; + for (int i = 0; i < entities.size(); i++) { + if (entities.get(i).id().equals(id)) { + idx = i; + } + } + if (idx == -1) { + return null; + } + ArrayList path = new ArrayList<>(); + MerkleNode currNode = leafNodes.get(idx); + path.add(currNode.getHash()); + while (currNode != root) { + path.add(currNode.getSibling().getHash()); + currNode = currNode.getParent(); + } + return new Proof(path, root.getHash()); + } private void buildMerkleTree() { ArrayList parentNodes = new ArrayList<>(); ArrayList childNodes = new ArrayList<>(leafNodes); @@ -65,33 +83,4 @@ private void buildMerkleTree() { } root = childNodes.get(0); } - - private static void printLevelOrderTraversal(MerkleNode r) { - if (r == null) { - return; - } - if ((r.getLeft() == null && r.getRight() == null)) { - System.out.println(Arrays.toString(r.getHash().getBytes())); - } - Queue queue = new LinkedList<>(); - queue.add(r); - queue.add(null); - while (!queue.isEmpty()) { - MerkleNode node = queue.poll(); - if (node != null) { - System.out.println(Arrays.toString(node.getHash().getBytes())); - } else { - System.out.println(); - if (!queue.isEmpty()) { - queue.add(null); - } - } - if (node != null && node.getLeft() != null) { - queue.add(node.getLeft()); - } - if (node != null && node.getRight() != null) { - queue.add(node.getRight()); - } - } - } } diff --git a/src/main/java/modules/ads/merkletree/Proof.java b/src/main/java/modules/ads/merkletree/Proof.java new file mode 100644 index 00000000..0fb43d01 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/Proof.java @@ -0,0 +1,34 @@ +package modules.ads.merkletree; + +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; +import modules.ads.MembershipProof; + +import java.util.ArrayList; + +public class Proof implements MembershipProof { + private ArrayList path; + private Sha3256Hash root; + + public Proof(ArrayList path, Sha3256Hash root) { + this.path = path; + this.root = root; + } + + @Override + public ArrayList getPath() { + return path; + } + + public void setPath(ArrayList path) { + this.path = path; + } + + public Sha3256Hash getRoot() { + return root; + } + + public void setRoot(Sha3256Hash root) { + this.root = root; + } +} diff --git a/src/main/java/modules/ads/merkletree/Verifier.java b/src/main/java/modules/ads/merkletree/Verifier.java new file mode 100644 index 00000000..7a8b5dfa --- /dev/null +++ b/src/main/java/modules/ads/merkletree/Verifier.java @@ -0,0 +1,31 @@ +package modules.ads.merkletree; + +import java.util.ArrayList; +import java.util.Arrays; + +import crypto.Sha3256Hasher; +import model.crypto.Sha3256Hash; +import modules.ads.AuthenticatedEntity; +import modules.ads.AuthenticatedEntityVerifier; +import modules.ads.MembershipProof; + +public class Verifier implements AuthenticatedEntityVerifier { + private static final Sha3256Hasher hasher = new Sha3256Hasher(); + /** + * Verifies the AuthenticatedEntity against its self-contained proof. + * + * @param authenticatedEntity the AuthenticatedEntity to verify. + * @return true if entity contains a valid Merkle Proof against its root identifier, false otherwise. + */ + @Override + public boolean verify(AuthenticatedEntity authenticatedEntity) { + MembershipProof proof = authenticatedEntity.getMembershipProof(); + Sha3256Hash root = proof.getRoot(); + ArrayList proofPath = proof.getPath(); + Sha3256Hash currHash = hasher.computeHash(proofPath.get(0), proofPath.get(1)); + for (int i = 2; i < proofPath.size(); i++) { + currHash = hasher.computeHash(currHash, proofPath.get(i)); + } + return Arrays.equals(root.getBytes(), currHash.getBytes()); + } +} \ No newline at end of file diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index f6fa76ea..d7db9a56 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -1,15 +1,22 @@ package modules.ads; +import model.crypto.Sha3256Hash; import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.Verifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import unittest.fixtures.EntityFixture; +import unittest.fixtures.MerkleTreeFixture; + +import java.util.ArrayList; +import java.util.Arrays; + /** * Encapsulates tests for an authenticated and concurrent implementation of SkipList ADS. */ public class MerkleTreeTest { // TODO: writing tests to cover - // 1. When putting a unique entity into skip list, we can recover it. + // 1. When putting a unique entity into merkle tree, we can recover it. // 2. Proof of membership for putting and getting an entity is the same. // 3. Putting an already existing entity does not change its membership proof. // 4. Putting 100 distinct entities concurrently inserts all of them into skip list with correct membership proofs, @@ -21,16 +28,12 @@ public class MerkleTreeTest { // 10. Tampering with proof of an authenticated entity fails its verification. @Test - public void TestVerification() { // Do not work always - MerkleTree merkleTree = new MerkleTree(); - EntityFixture entityFixture1 = new EntityFixture(); - merkleTree.put(entityFixture1); - EntityFixture entityFixture2 = new EntityFixture(); - merkleTree.put(entityFixture2); - EntityFixture entityFixture3 = new EntityFixture(); - merkleTree.put(entityFixture3); - System.out.println("---"); - EntityFixture entityFixture4 = new EntityFixture(); - merkleTree.put(entityFixture4); + public void TestVerification() { + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + EntityFixture entityFixture = new EntityFixture(); + merkleTree.put(entityFixture); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entityFixture); + Verifier verifier = new Verifier(); + Assertions.assertTrue(verifier.verify(authenticatedEntity)); } } diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java new file mode 100644 index 00000000..3abcb66f --- /dev/null +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -0,0 +1,19 @@ +package unittest.fixtures; + +import modules.ads.merkletree.MerkleTree; + +public class MerkleTreeFixture { + /** + * Creates a new skip list with n random elements. + * + * @param n number of elements to create + * @return a new skip list with n random elements + */ + public static MerkleTree createSkipList(int n) { + MerkleTree merkleTree = new MerkleTree(); + for (int i = 0; i < n; i++) { + merkleTree.put(new EntityFixture()); + } + return merkleTree; + } +} From b4cf75f195ccd1c742bb2879ea75d20bab6735e1 Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 13 Apr 2022 22:16:32 +0300 Subject: [PATCH 03/28] implements tests --- .../java/modules/ads/AuthenticatedEntity.java | 5 ++ .../java/modules/ads/MembershipProof.java | 12 +++ .../modules/ads/merkletree/MerkleTree.java | 28 +++++-- .../java/modules/ads/merkletree/Proof.java | 21 +++++ .../java/modules/ads/merkletree/Verifier.java | 5 +- src/test/java/modules/ads/MerkleTreeTest.java | 82 ++++++++++++++++++- 6 files changed, 143 insertions(+), 10 deletions(-) diff --git a/src/main/java/modules/ads/AuthenticatedEntity.java b/src/main/java/modules/ads/AuthenticatedEntity.java index 449aa167..57019f20 100644 --- a/src/main/java/modules/ads/AuthenticatedEntity.java +++ b/src/main/java/modules/ads/AuthenticatedEntity.java @@ -10,4 +10,9 @@ public abstract class AuthenticatedEntity extends Entity { public abstract Entity getEntity(); public abstract MembershipProof getMembershipProof(); + + public abstract void setMembershipProof(MembershipProof proof); + + public void setEntity(Entity entity) { + } } diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index d7e2ef01..d0816156 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -16,10 +16,22 @@ public interface MembershipProof { */ Sha3256Hash getRoot(); + /** + * Sets the root of the authenticated data structure that this proof belongs to. + * + */ + void setRoot(Sha3256Hash root); + /** * Returns the path of the proof of membership. * * @return path of the proof of membership. */ ArrayList getPath(); + + /** + * Sets the path of the proof of membership. + * + */ + void setPath(ArrayList path); } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index e498f37c..a25ba2a4 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -24,16 +24,33 @@ public MerkleTree() { @Override public AuthenticatedEntity put(Entity e) { - entities.add(e); - leafNodes.add(new MerkleNode(e, false)); - buildMerkleTree(); - Proof proof = getProof(e.id()); - return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); + if (e == null) { + return null; + } + int idx = -1; + for (int i = 0; i < entities.size(); i++) { + if (entities.get(i).id().equals(e.id())) { + idx = i; + } + } + if (idx == -1) { + entities.add(e); + leafNodes.add(new MerkleNode(e, false)); + buildMerkleTree(); + Proof proof = getProof(e.id()); + return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); + } else { + Proof proof = getProof(e.id()); + return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); + } } @Override public AuthenticatedEntity get(Entity e) { Proof proof = getProof(e.id()); + if (proof == null) { + return null; + } return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); } @@ -49,7 +66,6 @@ private Proof getProof(Identifier id) { } ArrayList path = new ArrayList<>(); MerkleNode currNode = leafNodes.get(idx); - path.add(currNode.getHash()); while (currNode != root) { path.add(currNode.getSibling().getHash()); currNode = currNode.getParent(); diff --git a/src/main/java/modules/ads/merkletree/Proof.java b/src/main/java/modules/ads/merkletree/Proof.java index 0fb43d01..a77e2d2d 100644 --- a/src/main/java/modules/ads/merkletree/Proof.java +++ b/src/main/java/modules/ads/merkletree/Proof.java @@ -5,6 +5,8 @@ import modules.ads.MembershipProof; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; public class Proof implements MembershipProof { private ArrayList path; @@ -28,7 +30,26 @@ public Sha3256Hash getRoot() { return root; } + @Override public void setRoot(Sha3256Hash root) { this.root = root; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Proof proof = (Proof) o; + for (int i = 0; i < path.size(); i++) { + if (!Arrays.equals(path.get(i).getBytes(), proof.path.get(i).getBytes())) { + return false; + } + } + return root.equals(proof.root); + } + + @Override + public int hashCode() { + return Objects.hash(path, root); + } } diff --git a/src/main/java/modules/ads/merkletree/Verifier.java b/src/main/java/modules/ads/merkletree/Verifier.java index 7a8b5dfa..7135df33 100644 --- a/src/main/java/modules/ads/merkletree/Verifier.java +++ b/src/main/java/modules/ads/merkletree/Verifier.java @@ -22,8 +22,9 @@ public boolean verify(AuthenticatedEntity authenticatedEntity) { MembershipProof proof = authenticatedEntity.getMembershipProof(); Sha3256Hash root = proof.getRoot(); ArrayList proofPath = proof.getPath(); - Sha3256Hash currHash = hasher.computeHash(proofPath.get(0), proofPath.get(1)); - for (int i = 2; i < proofPath.size(); i++) { + Sha3256Hash initialHash = hasher.computeHash(authenticatedEntity.getEntity().id()); + Sha3256Hash currHash = hasher.computeHash(initialHash, proofPath.get(0)); + for (int i = 1; i < proofPath.size(); i++) { currHash = hasher.computeHash(currHash, proofPath.get(i)); } return Arrays.equals(root.getBytes(), currHash.getBytes()); diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index d7db9a56..5018979f 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -2,6 +2,7 @@ import model.crypto.Sha3256Hash; import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.Proof; import modules.ads.merkletree.Verifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -19,7 +20,7 @@ public class MerkleTreeTest { // 1. When putting a unique entity into merkle tree, we can recover it. // 2. Proof of membership for putting and getting an entity is the same. // 3. Putting an already existing entity does not change its membership proof. - // 4. Putting 100 distinct entities concurrently inserts all of them into skip list with correct membership proofs, + // 4. Putting 100 distinct entities concurrently inserts all of them into merkle tree with correct membership proofs, // and also, makes them all retrievable with correct membership proofs. // 5. Getting non-existing identifiers returns null. // 7. Putting null returns null. @@ -28,7 +29,7 @@ public class MerkleTreeTest { // 10. Tampering with proof of an authenticated entity fails its verification. @Test - public void TestVerification() { + public void TestVerification() { // Test 1 MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); EntityFixture entityFixture = new EntityFixture(); merkleTree.put(entityFixture); @@ -36,4 +37,81 @@ public void TestVerification() { Verifier verifier = new Verifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); } + + @Test + public void TestPutGetSameProof() { // Test 2 + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + EntityFixture entityFixture = new EntityFixture(); + AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entityFixture); + MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); + AuthenticatedEntity authenticatedEntityGet = merkleTree.get(entityFixture); + MembershipProof proofGet = authenticatedEntityGet.getMembershipProof(); + EntityFixture entityFixture2 = new EntityFixture(); + AuthenticatedEntity authenticatedEntityPut2 = merkleTree.put(entityFixture2); + MembershipProof proofPut2 = authenticatedEntityPut2.getMembershipProof(); + Assertions.assertEquals(proofPut, proofGet); + Assertions.assertNotEquals(proofPut, proofPut2); + } + + @Test + public void TestPutExistingEntity() { // Test 3 + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + EntityFixture entityFixture = new EntityFixture(); + AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entityFixture); + MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); + AuthenticatedEntity authenticatedEntityPutAgain = merkleTree.put(entityFixture); + MembershipProof proofPutAgain = authenticatedEntityPutAgain.getMembershipProof(); + Assertions.assertEquals(proofPut, proofPutAgain); + } + + @Test + public void TestGetNonExistingEntity() { // Test 5 + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + EntityFixture entityFixture = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entityFixture); + Assertions.assertNull(authenticatedEntity); + } + + @Test + public void TestNullInsertion() { // Test 7 + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + AuthenticatedEntity authenticatedEntity = merkleTree.put(null); + Assertions.assertNull(authenticatedEntity); + } + + @Test + public void TestManipulatedRoot() { // Test 8 + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + EntityFixture entityFixture = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entityFixture); + MembershipProof proof = authenticatedEntity.getMembershipProof(); + proof.setRoot(new Sha3256Hash(new byte[32])); + authenticatedEntity.setMembershipProof(proof); + Verifier verifier = new Verifier(); + Assertions.assertFalse(verifier.verify(authenticatedEntity)); + } + + @Test + public void TestManipulatedEntity() { // Test 9 + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + EntityFixture entityFixture = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entityFixture); + authenticatedEntity.setEntity(new EntityFixture()); + Verifier verifier = new Verifier(); + Assertions.assertFalse(verifier.verify(authenticatedEntity)); + } + + @Test + public void TestManipulatedProof() { // Test 10 + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + EntityFixture entityFixture = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entityFixture); + MembershipProof proof = authenticatedEntity.getMembershipProof(); + ArrayList proofPath = proof.getPath(); + proofPath.add(new Sha3256Hash(new byte[32])); + proof.setPath(proofPath); + authenticatedEntity.setMembershipProof(proof); + Verifier verifier = new Verifier(); + Assertions.assertFalse(verifier.verify(authenticatedEntity)); + } } From 2846da5196634cfe0edb47134c5ffd6067aec345 Mon Sep 17 00:00:00 2001 From: Ozan Date: Sun, 17 Apr 2022 20:27:12 +0300 Subject: [PATCH 04/28] implements hash table for searching in merkle tree --- .../modules/ads/merkletree/MerkleTree.java | 29 +++++++++---------- src/test/java/modules/ads/MerkleTreeTest.java | 2 -- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index a25ba2a4..11a23776 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -15,9 +15,10 @@ */ public class MerkleTree implements AuthenticatedDataStructure { private static final Sha3256Hasher hasher = new Sha3256Hasher(); - private MerkleNode root = new MerkleNode(); private final ArrayList leafNodes = new ArrayList<>(); - private final ArrayList entities = new ArrayList<>(); + private final Map leafNodesHashTable = new HashMap<>(); + private MerkleNode root = new MerkleNode(); + private int size = 0; public MerkleTree() { } @@ -27,15 +28,12 @@ public AuthenticatedEntity put(Entity e) { if (e == null) { return null; } - int idx = -1; - for (int i = 0; i < entities.size(); i++) { - if (entities.get(i).id().equals(e.id())) { - idx = i; - } - } - if (idx == -1) { - entities.add(e); + Sha3256Hash hash = hasher.computeHash(e.id()); + Integer idx = leafNodesHashTable.get(hash); + if (idx == null) { leafNodes.add(new MerkleNode(e, false)); + leafNodesHashTable.put(hash, size); + size++; buildMerkleTree(); Proof proof = getProof(e.id()); return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); @@ -55,13 +53,11 @@ public AuthenticatedEntity get(Entity e) { } private Proof getProof(Identifier id) { - int idx = -1; - for (int i = 0; i < entities.size(); i++) { - if (entities.get(i).id().equals(id)) { - idx = i; - } + if (id == null) { + return null; } - if (idx == -1) { + Integer idx = leafNodesHashTable.get(hasher.computeHash(id)); + if (idx == null) { return null; } ArrayList path = new ArrayList<>(); @@ -72,6 +68,7 @@ private Proof getProof(Identifier id) { } return new Proof(path, root.getHash()); } + private void buildMerkleTree() { ArrayList parentNodes = new ArrayList<>(); ArrayList childNodes = new ArrayList<>(leafNodes); diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 5018979f..5045fd25 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -2,7 +2,6 @@ import model.crypto.Sha3256Hash; import modules.ads.merkletree.MerkleTree; -import modules.ads.merkletree.Proof; import modules.ads.merkletree.Verifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -10,7 +9,6 @@ import unittest.fixtures.MerkleTreeFixture; import java.util.ArrayList; -import java.util.Arrays; /** * Encapsulates tests for an authenticated and concurrent implementation of SkipList ADS. From 99f469f478c9830eb6944875d2b03df799f7d02a Mon Sep 17 00:00:00 2001 From: Ozan Date: Sun, 17 Apr 2022 20:55:49 +0300 Subject: [PATCH 05/28] implements thread safety to merkle tree implementation --- .../modules/ads/merkletree/MerkleTree.java | 22 +++++- src/test/java/modules/ads/MerkleTreeTest.java | 77 ++++++++++++++----- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 11a23776..aef97ccf 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -8,24 +8,33 @@ import modules.ads.AuthenticatedEntity; import java.util.*; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Implementation of an in-memory Authenticated Skip List * that is capable of storing and retrieval of LightChain entities. */ public class MerkleTree implements AuthenticatedDataStructure { + private int size; + private MerkleNode root; + private final ReentrantReadWriteLock lock; + private final ArrayList leafNodes; + private final Map leafNodesHashTable; private static final Sha3256Hasher hasher = new Sha3256Hasher(); - private final ArrayList leafNodes = new ArrayList<>(); - private final Map leafNodesHashTable = new HashMap<>(); - private MerkleNode root = new MerkleNode(); - private int size = 0; public MerkleTree() { + this.size = 0; + this.root = new MerkleNode(); + this.leafNodes = new ArrayList<>(); + this.lock = new ReentrantReadWriteLock(); + this.leafNodesHashTable = new HashMap<>(); } @Override public AuthenticatedEntity put(Entity e) { + lock.writeLock().lock(); if (e == null) { + lock.writeLock().unlock(); return null; } Sha3256Hash hash = hasher.computeHash(e.id()); @@ -36,19 +45,24 @@ public AuthenticatedEntity put(Entity e) { size++; buildMerkleTree(); Proof proof = getProof(e.id()); + lock.writeLock().unlock(); return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); } else { Proof proof = getProof(e.id()); + lock.writeLock().unlock(); return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); } } @Override public AuthenticatedEntity get(Entity e) { + lock.readLock().lock(); Proof proof = getProof(e.id()); if (proof == null) { + lock.readLock().unlock(); return null; } + lock.readLock().unlock(); return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); } diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 5045fd25..993216a4 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -1,5 +1,6 @@ package modules.ads; +import model.Entity; import model.crypto.Sha3256Hash; import modules.ads.merkletree.MerkleTree; import modules.ads.merkletree.Verifier; @@ -9,6 +10,9 @@ import unittest.fixtures.MerkleTreeFixture; import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; /** * Encapsulates tests for an authenticated and concurrent implementation of SkipList ADS. @@ -29,9 +33,9 @@ public class MerkleTreeTest { @Test public void TestVerification() { // Test 1 MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); - EntityFixture entityFixture = new EntityFixture(); - merkleTree.put(entityFixture); - AuthenticatedEntity authenticatedEntity = merkleTree.get(entityFixture); + Entity entity = new EntityFixture(); + merkleTree.put(entity); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); Verifier verifier = new Verifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); } @@ -39,13 +43,13 @@ public void TestVerification() { // Test 1 @Test public void TestPutGetSameProof() { // Test 2 MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); - EntityFixture entityFixture = new EntityFixture(); - AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entityFixture); + Entity entity1 = new EntityFixture(); + AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity1); MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); - AuthenticatedEntity authenticatedEntityGet = merkleTree.get(entityFixture); + AuthenticatedEntity authenticatedEntityGet = merkleTree.get(entity1); MembershipProof proofGet = authenticatedEntityGet.getMembershipProof(); - EntityFixture entityFixture2 = new EntityFixture(); - AuthenticatedEntity authenticatedEntityPut2 = merkleTree.put(entityFixture2); + Entity entity2 = new EntityFixture(); + AuthenticatedEntity authenticatedEntityPut2 = merkleTree.put(entity2); MembershipProof proofPut2 = authenticatedEntityPut2.getMembershipProof(); Assertions.assertEquals(proofPut, proofGet); Assertions.assertNotEquals(proofPut, proofPut2); @@ -54,19 +58,54 @@ public void TestPutGetSameProof() { // Test 2 @Test public void TestPutExistingEntity() { // Test 3 MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); - EntityFixture entityFixture = new EntityFixture(); - AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entityFixture); + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity); MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); - AuthenticatedEntity authenticatedEntityPutAgain = merkleTree.put(entityFixture); + AuthenticatedEntity authenticatedEntityPutAgain = merkleTree.put(entity); MembershipProof proofPutAgain = authenticatedEntityPutAgain.getMembershipProof(); Assertions.assertEquals(proofPut, proofPutAgain); } + @Test + public void TestConcurrentPut() { // Test 4 + int concurrencyDegree = 100; + AtomicInteger threadError = new AtomicInteger(); + CountDownLatch countDownLatch = new CountDownLatch(concurrencyDegree); + Thread[] merkleTreeThreads = new Thread[concurrencyDegree]; + MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + for (int i = 0; i < concurrencyDegree; i++) { + merkleTreeThreads[i] = new Thread(() -> { + Entity entity = new EntityFixture(); + try { + merkleTree.put(entity); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); + Verifier verifier = new Verifier(); + if (!verifier.verify(authenticatedEntity)) { + threadError.getAndIncrement(); + } + countDownLatch.countDown(); + } catch (NullPointerException e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : merkleTreeThreads) { + t.start(); + } + try { + boolean doneOneTime = countDownLatch.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + Assertions.assertEquals(0, threadError.get()); + } + @Test public void TestGetNonExistingEntity() { // Test 5 MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); - EntityFixture entityFixture = new EntityFixture(); - AuthenticatedEntity authenticatedEntity = merkleTree.get(entityFixture); + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); Assertions.assertNull(authenticatedEntity); } @@ -80,8 +119,8 @@ public void TestNullInsertion() { // Test 7 @Test public void TestManipulatedRoot() { // Test 8 MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); - EntityFixture entityFixture = new EntityFixture(); - AuthenticatedEntity authenticatedEntity = merkleTree.put(entityFixture); + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); proof.setRoot(new Sha3256Hash(new byte[32])); authenticatedEntity.setMembershipProof(proof); @@ -92,8 +131,8 @@ public void TestManipulatedRoot() { // Test 8 @Test public void TestManipulatedEntity() { // Test 9 MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); - EntityFixture entityFixture = new EntityFixture(); - AuthenticatedEntity authenticatedEntity = merkleTree.put(entityFixture); + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); authenticatedEntity.setEntity(new EntityFixture()); Verifier verifier = new Verifier(); Assertions.assertFalse(verifier.verify(authenticatedEntity)); @@ -102,8 +141,8 @@ public void TestManipulatedEntity() { // Test 9 @Test public void TestManipulatedProof() { // Test 10 MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); - EntityFixture entityFixture = new EntityFixture(); - AuthenticatedEntity authenticatedEntity = merkleTree.put(entityFixture); + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); ArrayList proofPath = proof.getPath(); proofPath.add(new Sha3256Hash(new byte[32])); From d5d5d1484375dc4dfd0625f5570e6a20fb262a1b Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Sun, 17 Apr 2022 21:48:33 +0300 Subject: [PATCH 06/28] fixes lint & spotbugs issues --- src/main/java/crypto/Sha3256Hasher.java | 14 +++++ .../ads/AuthenticatedDataStructure.java | 1 - .../java/modules/ads/MembershipProof.java | 7 +-- .../ads/merkletree/AuthenticatedEntity.java | 42 --------------- .../AuthenticatedLightChainEntity.java | 53 +++++++++++++++++++ .../modules/ads/merkletree/MerkleNode.java | 37 +++++++++++-- .../modules/ads/merkletree/MerkleTree.java | 38 +++++++------ .../java/modules/ads/merkletree/Proof.java | 24 +++++---- .../java/modules/ads/merkletree/Verifier.java | 4 ++ src/test/java/modules/ads/MerkleTreeTest.java | 39 +++++--------- .../unittest/fixtures/MerkleTreeFixture.java | 3 ++ 11 files changed, 161 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/modules/ads/merkletree/AuthenticatedEntity.java create mode 100644 src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 9888a248..048d11e4 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -41,6 +41,12 @@ public Sha3256Hash computeHash(EncodedEntity e) { } } + /** + * Computes hash of the given identifier. + * + * @param id input identifier + * @return SHA3-256 hash object of the entity + */ public Sha3256Hash computeHash(Identifier id) { try { MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); @@ -51,6 +57,14 @@ public Sha3256Hash computeHash(Identifier id) { } } + /** + * Commutative hashing of two given byte arrays. + * + * @param b1 first byte array. + * @param b2 second byte array. + * @return SHA3-256 hash object of the commutative concatenation (i.e. max(b1,b2) || min(b1,b2)) + * of the two byte arrays. + */ public Sha3256Hash computeHash(byte[] b1, byte[] b2) { try { MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); diff --git a/src/main/java/modules/ads/AuthenticatedDataStructure.java b/src/main/java/modules/ads/AuthenticatedDataStructure.java index fc8db5e3..63252676 100644 --- a/src/main/java/modules/ads/AuthenticatedDataStructure.java +++ b/src/main/java/modules/ads/AuthenticatedDataStructure.java @@ -1,7 +1,6 @@ package modules.ads; import model.Entity; -import model.lightchain.Identifier; /** * Models AuthenticatedDataStructure (ADS) and a key-value store of entities supported with membership proofs. diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index d0816156..942fe523 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -1,10 +1,9 @@ package modules.ads; -import model.crypto.Sha3256Hash; -import model.lightchain.Identifier; - import java.util.ArrayList; +import model.crypto.Sha3256Hash; + /** * Represents a Merkle Proof of membership against a certain root identifier. */ @@ -18,7 +17,6 @@ public interface MembershipProof { /** * Sets the root of the authenticated data structure that this proof belongs to. - * */ void setRoot(Sha3256Hash root); @@ -31,7 +29,6 @@ public interface MembershipProof { /** * Sets the path of the proof of membership. - * */ void setPath(ArrayList path); } diff --git a/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java b/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java deleted file mode 100644 index 9a28d982..00000000 --- a/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java +++ /dev/null @@ -1,42 +0,0 @@ -package modules.ads.merkletree; - -import model.Entity; -import modules.ads.MembershipProof; - -public class AuthenticatedEntity extends modules.ads.AuthenticatedEntity { - private MembershipProof membershipProof; - private String type; - private Entity entity; - - public AuthenticatedEntity(Proof proof, String type, Entity e) { - this.membershipProof = proof; - this.type = type; - this.entity = e; - } - - @Override - public String type() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - @Override - public Entity getEntity() { - return entity; - } - - public void setEntity(Entity entity) { - this.entity = entity; - } - @Override - public MembershipProof getMembershipProof() { - return membershipProof; - } - - public void setMembershipProof(MembershipProof membershipProof) { - this.membershipProof = membershipProof; - } -} diff --git a/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java b/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java new file mode 100644 index 00000000..95fe9790 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java @@ -0,0 +1,53 @@ +package modules.ads.merkletree; + +import model.Entity; +import modules.ads.MembershipProof; + +/** + * An entity with its membership proof and type. + */ +public class AuthenticatedLightChainEntity extends modules.ads.AuthenticatedEntity { + private MembershipProof membershipProof; + private String type; + private Entity entity; + + /** + * Constructor of an authenticated entity. + * + * @param proof the membership proof + * @param type the type of the entity + * @param e the entity + */ + public AuthenticatedLightChainEntity(Proof proof, String type, Entity e) { + this.membershipProof = new Proof(proof.getPath(), proof.getRoot()); + this.type = type; + this.entity = e; + } + + @Override + public String type() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public Entity getEntity() { + return entity; + } + + public void setEntity(Entity entity) { + this.entity = entity; + } + + @Override + public MembershipProof getMembershipProof() { + return new Proof(membershipProof.getPath(), membershipProof.getRoot()); + } + + public void setMembershipProof(MembershipProof membershipProof) { + this.membershipProof = new Proof(membershipProof.getPath(), membershipProof.getRoot());; + } +} diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 8e84fc13..d2d9d4d2 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -4,6 +4,9 @@ import model.Entity; import model.crypto.Sha3256Hash; +/** + * A node in the Merkle tree. + */ public class MerkleNode { private static final Sha3256Hasher hasher = new Sha3256Hasher(); private MerkleNode left; @@ -12,6 +15,9 @@ public class MerkleNode { private boolean isLeft; private Sha3256Hash hash; + /** + * Default constructor. + */ public MerkleNode() { this.left = null; this.right = null; @@ -20,6 +26,12 @@ public MerkleNode() { this.hash = null; } + /** + * Constructor with entity and isLeft. + * + * @param e input entity + * @param isLeft boolean that specifies if the node is left child or not + */ public MerkleNode(Entity e, boolean isLeft) { this.left = null; this.right = null; @@ -28,6 +40,11 @@ public MerkleNode(Entity e, boolean isLeft) { this.hash = hasher.computeHash(e.id()); } + /** + * Constructor with hash of the entity. + * + * @param hash input hash of the entity corresponding to that node + */ public MerkleNode(Sha3256Hash hash) { this.left = null; this.right = null; @@ -36,6 +53,13 @@ public MerkleNode(Sha3256Hash hash) { this.hash = hash; } + /** + * Constructor of a parent node. + * + * @param hash input hash of the entity corresponding to that node + * @param left left child of the node + * @param right right child of the node + */ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.left = left; this.right = right; @@ -48,10 +72,6 @@ public MerkleNode getLeft() { return left; } - public void setLeft(MerkleNode left) { - this.left = left; - } - public MerkleNode getRight() { return right; } @@ -80,10 +100,19 @@ public boolean isLeft() { return isLeft; } + public void setLeft(MerkleNode left) { + this.left = left; + } + public void setLeft(boolean isLeft) { this.isLeft = isLeft; } + /** + * Returns the sibling of the node. + * + * @return the sibling of the node + */ public MerkleNode getSibling() { if (isLeft()) { return parent.getRight(); diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index aef97ccf..a2c1d97e 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -1,5 +1,10 @@ package modules.ads.merkletree; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; + import crypto.Sha3256Hasher; import model.Entity; import model.crypto.Sha3256Hash; @@ -7,21 +12,21 @@ import modules.ads.AuthenticatedDataStructure; import modules.ads.AuthenticatedEntity; -import java.util.*; -import java.util.concurrent.locks.ReentrantReadWriteLock; - /** * Implementation of an in-memory Authenticated Skip List * that is capable of storing and retrieval of LightChain entities. */ public class MerkleTree implements AuthenticatedDataStructure { - private int size; - private MerkleNode root; + private static final Sha3256Hasher hasher = new Sha3256Hasher(); private final ReentrantReadWriteLock lock; private final ArrayList leafNodes; private final Map leafNodesHashTable; - private static final Sha3256Hasher hasher = new Sha3256Hasher(); + private int size; + private MerkleNode root; + /** + * Default constructor for a Merkle Tree. + */ public MerkleTree() { this.size = 0; this.root = new MerkleNode(); @@ -46,30 +51,33 @@ public AuthenticatedEntity put(Entity e) { buildMerkleTree(); Proof proof = getProof(e.id()); lock.writeLock().unlock(); - return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); + return new AuthenticatedLightChainEntity(proof, e.type(), e); } else { Proof proof = getProof(e.id()); lock.writeLock().unlock(); - return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); + return new AuthenticatedLightChainEntity(proof, e.type(), e); } } @Override public AuthenticatedEntity get(Entity e) { + Proof proof; + if (e == null) { + return null; + } lock.readLock().lock(); - Proof proof = getProof(e.id()); - if (proof == null) { + try { + proof = getProof(e.id()); + } finally { lock.readLock().unlock(); + } + if (proof == null) { return null; } - lock.readLock().unlock(); - return new modules.ads.merkletree.AuthenticatedEntity(proof, e.type(), e); + return new AuthenticatedLightChainEntity(proof, e.type(), e); } private Proof getProof(Identifier id) { - if (id == null) { - return null; - } Integer idx = leafNodesHashTable.get(hasher.computeHash(id)); if (idx == null) { return null; diff --git a/src/main/java/modules/ads/merkletree/Proof.java b/src/main/java/modules/ads/merkletree/Proof.java index a77e2d2d..a30738d5 100644 --- a/src/main/java/modules/ads/merkletree/Proof.java +++ b/src/main/java/modules/ads/merkletree/Proof.java @@ -1,29 +1,31 @@ package modules.ads.merkletree; -import model.crypto.Sha3256Hash; -import model.lightchain.Identifier; -import modules.ads.MembershipProof; - import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; +import model.crypto.Sha3256Hash; +import modules.ads.MembershipProof; + +/** + * A proof of membership in a Merkle tree. + */ public class Proof implements MembershipProof { private ArrayList path; private Sha3256Hash root; public Proof(ArrayList path, Sha3256Hash root) { - this.path = path; + this.path = new ArrayList<>(path); this.root = root; } @Override public ArrayList getPath() { - return path; + return new ArrayList<>(path); } public void setPath(ArrayList path) { - this.path = path; + this.path = new ArrayList<>(path); } public Sha3256Hash getRoot() { @@ -37,8 +39,12 @@ public void setRoot(Sha3256Hash root) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Proof proof = (Proof) o; for (int i = 0; i < path.size(); i++) { if (!Arrays.equals(path.get(i).getBytes(), proof.path.get(i).getBytes())) { diff --git a/src/main/java/modules/ads/merkletree/Verifier.java b/src/main/java/modules/ads/merkletree/Verifier.java index 7135df33..63c9659b 100644 --- a/src/main/java/modules/ads/merkletree/Verifier.java +++ b/src/main/java/modules/ads/merkletree/Verifier.java @@ -9,8 +9,12 @@ import modules.ads.AuthenticatedEntityVerifier; import modules.ads.MembershipProof; +/** + * Verifies the AuthenticatedEntity against its self-contained proof. + */ public class Verifier implements AuthenticatedEntityVerifier { private static final Sha3256Hasher hasher = new Sha3256Hasher(); + /** * Verifies the AuthenticatedEntity against its self-contained proof. * diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 993216a4..10da3269 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -1,5 +1,10 @@ package modules.ads; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + import model.Entity; import model.crypto.Sha3256Hash; import modules.ads.merkletree.MerkleTree; @@ -9,29 +14,13 @@ import unittest.fixtures.EntityFixture; import unittest.fixtures.MerkleTreeFixture; -import java.util.ArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - /** * Encapsulates tests for an authenticated and concurrent implementation of SkipList ADS. */ public class MerkleTreeTest { - // TODO: writing tests to cover - // 1. When putting a unique entity into merkle tree, we can recover it. - // 2. Proof of membership for putting and getting an entity is the same. - // 3. Putting an already existing entity does not change its membership proof. - // 4. Putting 100 distinct entities concurrently inserts all of them into merkle tree with correct membership proofs, - // and also, makes them all retrievable with correct membership proofs. - // 5. Getting non-existing identifiers returns null. - // 7. Putting null returns null. - // 8. Tampering with root identifier of an authenticated entity fails its verification. - // 9. Tampering with entity of an authenticated entity fails its verification. - // 10. Tampering with proof of an authenticated entity fails its verification. @Test - public void TestVerification() { // Test 1 + public void testVerification() { MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); Entity entity = new EntityFixture(); merkleTree.put(entity); @@ -41,7 +30,7 @@ public void TestVerification() { // Test 1 } @Test - public void TestPutGetSameProof() { // Test 2 + public void testPutGetSameProof() { MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); Entity entity1 = new EntityFixture(); AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity1); @@ -56,7 +45,7 @@ public void TestPutGetSameProof() { // Test 2 } @Test - public void TestPutExistingEntity() { // Test 3 + public void testPutExistingEntity() { MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity); @@ -67,7 +56,7 @@ public void TestPutExistingEntity() { // Test 3 } @Test - public void TestConcurrentPut() { // Test 4 + public void testConcurrentPut() { int concurrencyDegree = 100; AtomicInteger threadError = new AtomicInteger(); CountDownLatch countDownLatch = new CountDownLatch(concurrencyDegree); @@ -102,7 +91,7 @@ public void TestConcurrentPut() { // Test 4 } @Test - public void TestGetNonExistingEntity() { // Test 5 + public void testGetNonExistingEntity() { MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); @@ -110,14 +99,14 @@ public void TestGetNonExistingEntity() { // Test 5 } @Test - public void TestNullInsertion() { // Test 7 + public void testNullInsertion() { MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); AuthenticatedEntity authenticatedEntity = merkleTree.put(null); Assertions.assertNull(authenticatedEntity); } @Test - public void TestManipulatedRoot() { // Test 8 + public void testManipulatedRoot() { MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); @@ -129,7 +118,7 @@ public void TestManipulatedRoot() { // Test 8 } @Test - public void TestManipulatedEntity() { // Test 9 + public void testManipulatedEntity() { MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); @@ -139,7 +128,7 @@ public void TestManipulatedEntity() { // Test 9 } @Test - public void TestManipulatedProof() { // Test 10 + public void testManipulatedProof() { MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java index 3abcb66f..4e30eba8 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -2,6 +2,9 @@ import modules.ads.merkletree.MerkleTree; +/** + * Creates a new randomly looking MerkleTree. + */ public class MerkleTreeFixture { /** * Creates a new skip list with n random elements. From 29bb463d0eb39091a9e52fee2cce2bfbf95a482b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCn=20Ozan=20Nacitarhan?= Date: Wed, 20 Apr 2022 01:45:47 +0300 Subject: [PATCH 07/28] renames Merkle Tree creation function in MerkleTreeFixture.java --- src/test/java/modules/ads/MerkleTreeTest.java | 18 +++++++++--------- .../unittest/fixtures/MerkleTreeFixture.java | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 10da3269..758d2502 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -21,7 +21,7 @@ public class MerkleTreeTest { @Test public void testVerification() { - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); merkleTree.put(entity); AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); @@ -31,7 +31,7 @@ public void testVerification() { @Test public void testPutGetSameProof() { - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity1 = new EntityFixture(); AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity1); MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); @@ -46,7 +46,7 @@ public void testPutGetSameProof() { @Test public void testPutExistingEntity() { - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity); MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); @@ -61,7 +61,7 @@ public void testConcurrentPut() { AtomicInteger threadError = new AtomicInteger(); CountDownLatch countDownLatch = new CountDownLatch(concurrencyDegree); Thread[] merkleTreeThreads = new Thread[concurrencyDegree]; - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); for (int i = 0; i < concurrencyDegree; i++) { merkleTreeThreads[i] = new Thread(() -> { Entity entity = new EntityFixture(); @@ -92,7 +92,7 @@ public void testConcurrentPut() { @Test public void testGetNonExistingEntity() { - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); Assertions.assertNull(authenticatedEntity); @@ -100,14 +100,14 @@ public void testGetNonExistingEntity() { @Test public void testNullInsertion() { - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); AuthenticatedEntity authenticatedEntity = merkleTree.put(null); Assertions.assertNull(authenticatedEntity); } @Test public void testManipulatedRoot() { - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); @@ -119,7 +119,7 @@ public void testManipulatedRoot() { @Test public void testManipulatedEntity() { - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); authenticatedEntity.setEntity(new EntityFixture()); @@ -129,7 +129,7 @@ public void testManipulatedEntity() { @Test public void testManipulatedProof() { - MerkleTree merkleTree = MerkleTreeFixture.createSkipList(5); + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java index 4e30eba8..1365ef23 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -12,7 +12,7 @@ public class MerkleTreeFixture { * @param n number of elements to create * @return a new skip list with n random elements */ - public static MerkleTree createSkipList(int n) { + public static MerkleTree createMerkleTree(int n) { MerkleTree merkleTree = new MerkleTree(); for (int i = 0; i < n; i++) { merkleTree.put(new EntityFixture()); From ff38dea58acd882ed1f59b9caa359b59483515bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCn=20Ozan=20Nacitarhan?= Date: Wed, 20 Apr 2022 01:50:53 +0300 Subject: [PATCH 08/28] suppresses EI_EXPOSE_REP warnings in spotbugs --- src/main/java/modules/ads/merkletree/MerkleNode.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index d2d9d4d2..ca319fc6 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -60,6 +60,7 @@ public MerkleNode(Sha3256Hash hash) { * @param left left child of the node * @param right right child of the node */ + @SuppressWarnings("EI_EXPOSE_REP2") public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.left = left; this.right = right; @@ -68,10 +69,12 @@ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.hash = hash; } + @SuppressWarnings("EI_EXPOSE_REP") public MerkleNode getLeft() { return left; } + @SuppressWarnings("EI_EXPOSE_REP") public MerkleNode getRight() { return right; } @@ -80,10 +83,12 @@ public void setRight(MerkleNode right) { this.right = right; } + @SuppressWarnings("EI_EXPOSE_REP") public MerkleNode getParent() { return parent; } + @SuppressWarnings("EI_EXPOSE_REP2") public void setParent(MerkleNode parent) { this.parent = parent; } @@ -100,10 +105,12 @@ public boolean isLeft() { return isLeft; } + @SuppressWarnings("EI_EXPOSE_REP2") public void setLeft(MerkleNode left) { this.left = left; } + @SuppressWarnings("EI_EXPOSE_REP2") public void setLeft(boolean isLeft) { this.isLeft = isLeft; } From f767ff80264c1af9d3e2172e62b0a490a39de2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCn=20Ozan=20Nacitarhan?= Date: Wed, 20 Apr 2022 01:58:30 +0300 Subject: [PATCH 09/28] suppresses warnings in spotbugs --- .../modules/ads/merkletree/MerkleNode.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index ca319fc6..91922dec 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -1,6 +1,7 @@ package modules.ads.merkletree; import crypto.Sha3256Hasher; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.Entity; import model.crypto.Sha3256Hash; @@ -56,11 +57,11 @@ public MerkleNode(Sha3256Hash hash) { /** * Constructor of a parent node. * - * @param hash input hash of the entity corresponding to that node - * @param left left child of the node + * @param hash input hash of the entity corresponding to that node + * @param left left child of the node * @param right right child of the node */ - @SuppressWarnings("EI_EXPOSE_REP2") + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "left and right is intentionally mutable externally") public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.left = left; this.right = right; @@ -69,26 +70,27 @@ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.hash = hash; } - @SuppressWarnings("EI_EXPOSE_REP") + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getLeft() { return left; } - @SuppressWarnings("EI_EXPOSE_REP") + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getRight() { return right; } + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "right is intentionally mutable externally") public void setRight(MerkleNode right) { this.right = right; } - @SuppressWarnings("EI_EXPOSE_REP") + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getParent() { return parent; } - @SuppressWarnings("EI_EXPOSE_REP2") + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") public void setParent(MerkleNode parent) { this.parent = parent; } @@ -105,12 +107,11 @@ public boolean isLeft() { return isLeft; } - @SuppressWarnings("EI_EXPOSE_REP2") + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "left is intentionally mutable externally") public void setLeft(MerkleNode left) { this.left = left; } - @SuppressWarnings("EI_EXPOSE_REP2") public void setLeft(boolean isLeft) { this.isLeft = isLeft; } From df3b4332ea8c901784d87ed1d7dd494d2459d0bf Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Wed, 20 Apr 2022 16:22:17 +0300 Subject: [PATCH 10/28] adds comments for unit tests --- src/test/java/modules/ads/MerkleTreeTest.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 758d2502..36e69274 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -15,10 +15,13 @@ import unittest.fixtures.MerkleTreeFixture; /** - * Encapsulates tests for an authenticated and concurrent implementation of SkipList ADS. + * Encapsulates tests for an authenticated and concurrent implementation of MerkleTree ADS. */ public class MerkleTreeTest { + /** + * A basic test for one concurrent put and get operations. + */ @Test public void testVerification() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); @@ -29,6 +32,10 @@ public void testVerification() { Assertions.assertTrue(verifier.verify(authenticatedEntity)); } + /** + * Tests both if putting and getting the same entity gives same proof + * and putting another entity gives different proofs. + */ @Test public void testPutGetSameProof() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); @@ -44,6 +51,9 @@ public void testPutGetSameProof() { Assertions.assertNotEquals(proofPut, proofPut2); } + /** + * Tests if putting an existing entity does not change the proof. + */ @Test public void testPutExistingEntity() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); @@ -55,6 +65,9 @@ public void testPutExistingEntity() { Assertions.assertEquals(proofPut, proofPutAgain); } + /** + * Concurrently puts and gets entities and checks if their proofs are correct (thread safety check). + */ @Test public void testConcurrentPut() { int concurrencyDegree = 100; @@ -90,6 +103,9 @@ public void testConcurrentPut() { Assertions.assertEquals(0, threadError.get()); } + /** + * Tests if getting an entity that does not exist in the merkle tree gives null. + */ @Test public void testGetNonExistingEntity() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); @@ -98,6 +114,9 @@ public void testGetNonExistingEntity() { Assertions.assertNull(authenticatedEntity); } + /** + * Tests if inserting null gives null. + */ @Test public void testNullInsertion() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); @@ -105,6 +124,9 @@ public void testNullInsertion() { Assertions.assertNull(authenticatedEntity); } + /** + * Tests if the proof verifies when root is changed. + */ @Test public void testManipulatedRoot() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); @@ -117,6 +139,9 @@ public void testManipulatedRoot() { Assertions.assertFalse(verifier.verify(authenticatedEntity)); } + /** + * Tests if the proof verifies when entity is changed. + */ @Test public void testManipulatedEntity() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); @@ -127,6 +152,9 @@ public void testManipulatedEntity() { Assertions.assertFalse(verifier.verify(authenticatedEntity)); } + /** + * Tests if the proof verifies when proof is changed. + */ @Test public void testManipulatedProof() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); From 94cfe9bd0d5e163b9ae22553b839d4e6cb93dc0e Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Wed, 20 Apr 2022 16:52:19 +0300 Subject: [PATCH 11/28] handles exceptions on MerkleTree.java --- src/main/java/modules/ads/merkletree/MerkleTree.java | 8 ++++---- src/test/java/modules/ads/MerkleTreeTest.java | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index a2c1d97e..e5caf8c9 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -36,11 +36,11 @@ public MerkleTree() { } @Override - public AuthenticatedEntity put(Entity e) { + public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { lock.writeLock().lock(); if (e == null) { lock.writeLock().unlock(); - return null; + throw new IllegalArgumentException("Entity cannot be null"); } Sha3256Hash hash = hasher.computeHash(e.id()); Integer idx = leafNodesHashTable.get(hash); @@ -60,10 +60,10 @@ public AuthenticatedEntity put(Entity e) { } @Override - public AuthenticatedEntity get(Entity e) { + public AuthenticatedEntity get(Entity e) throws IllegalArgumentException { Proof proof; if (e == null) { - return null; + throw new IllegalArgumentException("Entity cannot be null"); } lock.readLock().lock(); try { diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 36e69274..678ffa59 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -115,13 +115,14 @@ public void testGetNonExistingEntity() { } /** - * Tests if inserting null gives null. + * Tests if inserting null throws IllegalArgumentException. */ @Test public void testNullInsertion() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); - AuthenticatedEntity authenticatedEntity = merkleTree.put(null); - Assertions.assertNull(authenticatedEntity); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + AuthenticatedEntity authenticatedEntity = merkleTree.put(null); + }); } /** From d3b1343c705ed3440ea4ed551c09c564515e0f76 Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Wed, 20 Apr 2022 16:57:28 +0300 Subject: [PATCH 12/28] changes how id of an entity is stored on the merkle tree, changed from hash(id) to id --- src/main/java/modules/ads/merkletree/MerkleTree.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index e5caf8c9..b38e6a29 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -42,7 +42,7 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { lock.writeLock().unlock(); throw new IllegalArgumentException("Entity cannot be null"); } - Sha3256Hash hash = hasher.computeHash(e.id()); + Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); Integer idx = leafNodesHashTable.get(hash); if (idx == null) { leafNodes.add(new MerkleNode(e, false)); @@ -78,7 +78,8 @@ public AuthenticatedEntity get(Entity e) throws IllegalArgumentException { } private Proof getProof(Identifier id) { - Integer idx = leafNodesHashTable.get(hasher.computeHash(id)); + Sha3256Hash hash = new Sha3256Hash(id.getBytes()); + Integer idx = leafNodesHashTable.get(hash); if (idx == null) { return null; } From ccffc171c15990d92443776cd6df8067827ce2ca Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Wed, 20 Apr 2022 17:05:53 +0300 Subject: [PATCH 13/28] handles exceptions better for locks --- .../modules/ads/merkletree/MerkleTree.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index b38e6a29..08743e73 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -37,25 +37,26 @@ public MerkleTree() { @Override public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { - lock.writeLock().lock(); - if (e == null) { - lock.writeLock().unlock(); - throw new IllegalArgumentException("Entity cannot be null"); - } - Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); - Integer idx = leafNodesHashTable.get(hash); - if (idx == null) { - leafNodes.add(new MerkleNode(e, false)); - leafNodesHashTable.put(hash, size); - size++; - buildMerkleTree(); - Proof proof = getProof(e.id()); - lock.writeLock().unlock(); - return new AuthenticatedLightChainEntity(proof, e.type(), e); - } else { - Proof proof = getProof(e.id()); + try { + lock.writeLock().lock(); + if (e == null) { + throw new IllegalArgumentException("Entity cannot be null"); + } + Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); + Integer idx = leafNodesHashTable.get(hash); + if (idx == null) { + leafNodes.add(new MerkleNode(e, false)); + leafNodesHashTable.put(hash, size); + size++; + buildMerkleTree(); + Proof proof = getProof(e.id()); + return new AuthenticatedLightChainEntity(proof, e.type(), e); + } else { + Proof proof = getProof(e.id()); + return new AuthenticatedLightChainEntity(proof, e.type(), e); + } + } finally { lock.writeLock().unlock(); - return new AuthenticatedLightChainEntity(proof, e.type(), e); } } @@ -65,8 +66,8 @@ public AuthenticatedEntity get(Entity e) throws IllegalArgumentException { if (e == null) { throw new IllegalArgumentException("Entity cannot be null"); } - lock.readLock().lock(); try { + lock.readLock().lock(); proof = getProof(e.id()); } finally { lock.readLock().unlock(); From cc555b4babb882ce872821a6449aeafa1cdfc0f7 Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Wed, 20 Apr 2022 17:48:28 +0300 Subject: [PATCH 14/28] changes get method in a way it gets the AuthenticatedEntity by id not Entity --- .../java/modules/ads/AuthenticatedDataStructure.java | 3 ++- src/main/java/modules/ads/merkletree/MerkleTree.java | 12 ++++++++---- src/test/java/modules/ads/MerkleTreeTest.java | 8 ++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/java/modules/ads/AuthenticatedDataStructure.java b/src/main/java/modules/ads/AuthenticatedDataStructure.java index 63252676..8fde6250 100644 --- a/src/main/java/modules/ads/AuthenticatedDataStructure.java +++ b/src/main/java/modules/ads/AuthenticatedDataStructure.java @@ -1,6 +1,7 @@ package modules.ads; import model.Entity; +import model.lightchain.Identifier; /** * Models AuthenticatedDataStructure (ADS) and a key-value store of entities supported with membership proofs. @@ -8,5 +9,5 @@ public interface AuthenticatedDataStructure { AuthenticatedEntity put(Entity e); - AuthenticatedEntity get(Entity e); + AuthenticatedEntity get(Identifier id); } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 08743e73..5cc0ebff 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -21,6 +21,7 @@ public class MerkleTree implements AuthenticatedDataStructure { private final ReentrantReadWriteLock lock; private final ArrayList leafNodes; private final Map leafNodesHashTable; + private final Map entityHashTable; private int size; private MerkleNode root; @@ -33,6 +34,7 @@ public MerkleTree() { this.leafNodes = new ArrayList<>(); this.lock = new ReentrantReadWriteLock(); this.leafNodesHashTable = new HashMap<>(); + this.entityHashTable = new HashMap<>(); } @Override @@ -47,6 +49,7 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { if (idx == null) { leafNodes.add(new MerkleNode(e, false)); leafNodesHashTable.put(hash, size); + entityHashTable.put(e.id(), e); size++; buildMerkleTree(); Proof proof = getProof(e.id()); @@ -61,20 +64,21 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { } @Override - public AuthenticatedEntity get(Entity e) throws IllegalArgumentException { + public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { Proof proof; - if (e == null) { - throw new IllegalArgumentException("Entity cannot be null"); + if (id == null) { + throw new IllegalArgumentException("Identifier cannot be null"); } try { lock.readLock().lock(); - proof = getProof(e.id()); + proof = getProof(id); } finally { lock.readLock().unlock(); } if (proof == null) { return null; } + Entity e = entityHashTable.get(id); return new AuthenticatedLightChainEntity(proof, e.type(), e); } diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 678ffa59..39bee8e5 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -27,7 +27,7 @@ public void testVerification() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); merkleTree.put(entity); - AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entity.id()); Verifier verifier = new Verifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); } @@ -42,7 +42,7 @@ public void testPutGetSameProof() { Entity entity1 = new EntityFixture(); AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity1); MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); - AuthenticatedEntity authenticatedEntityGet = merkleTree.get(entity1); + AuthenticatedEntity authenticatedEntityGet = merkleTree.get(entity1.id()); MembershipProof proofGet = authenticatedEntityGet.getMembershipProof(); Entity entity2 = new EntityFixture(); AuthenticatedEntity authenticatedEntityPut2 = merkleTree.put(entity2); @@ -80,7 +80,7 @@ public void testConcurrentPut() { Entity entity = new EntityFixture(); try { merkleTree.put(entity); - AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entity.id()); Verifier verifier = new Verifier(); if (!verifier.verify(authenticatedEntity)) { threadError.getAndIncrement(); @@ -110,7 +110,7 @@ public void testConcurrentPut() { public void testGetNonExistingEntity() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); - AuthenticatedEntity authenticatedEntity = merkleTree.get(entity); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entity.id()); Assertions.assertNull(authenticatedEntity); } From 8cdb9a28db950d6a3d02dfea8e81c543635be578 Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Wed, 20 Apr 2022 18:31:05 +0300 Subject: [PATCH 15/28] improves thread safety check --- .../modules/ads/merkletree/MerkleTree.java | 2 +- src/test/java/modules/ads/MerkleTreeTest.java | 51 +++++++++++++++---- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 5cc0ebff..1cca64d9 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -106,7 +106,7 @@ private void buildMerkleTree() { while (idx < len) { MerkleNode left = childNodes.get(idx); left.setLeft(true); - MerkleNode right = null; + MerkleNode right; if (idx + 1 < len) { right = childNodes.get(idx + 1); } else { diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 39bee8e5..66916ea0 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -7,6 +7,7 @@ import model.Entity; import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; import modules.ads.merkletree.MerkleTree; import modules.ads.merkletree.Verifier; import org.junit.jupiter.api.Assertions; @@ -69,34 +70,62 @@ public void testPutExistingEntity() { * Concurrently puts and gets entities and checks if their proofs are correct (thread safety check). */ @Test - public void testConcurrentPut() { + public void testConcurrentPutGet() { int concurrencyDegree = 100; + ArrayList entities = new ArrayList<>(); + ArrayList ids = new ArrayList<>(); AtomicInteger threadError = new AtomicInteger(); - CountDownLatch countDownLatch = new CountDownLatch(concurrencyDegree); - Thread[] merkleTreeThreads = new Thread[concurrencyDegree]; + CountDownLatch countDownLatchPut = new CountDownLatch(concurrencyDegree); + CountDownLatch countDownLatchGet = new CountDownLatch(concurrencyDegree); + Thread[] putThreads = new Thread[concurrencyDegree]; + Thread[] getThreads = new Thread[concurrencyDegree]; MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); for (int i = 0; i < concurrencyDegree; i++) { - merkleTreeThreads[i] = new Thread(() -> { - Entity entity = new EntityFixture(); + Entity entity = new EntityFixture(); + entities.add(entity); + ids.add(entity.id()); + } + for (int i = 0; i < concurrencyDegree; i++) { + Entity entity = entities.get(i); + putThreads[i] = new Thread(() -> { try { merkleTree.put(entity); - AuthenticatedEntity authenticatedEntity = merkleTree.get(entity.id()); + countDownLatchPut.countDown(); + } catch (NullPointerException e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : putThreads) { + t.start(); + } + try { + boolean doneOneTimePut = countDownLatchPut.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTimePut); + } catch (InterruptedException e) { + Assertions.fail(); + } + for (int i = 0; i < concurrencyDegree; i++) { + Identifier id = ids.get(i); + getThreads[i] = new Thread(() -> { + try { + AuthenticatedEntity authenticatedEntity = merkleTree.get(id); Verifier verifier = new Verifier(); if (!verifier.verify(authenticatedEntity)) { threadError.getAndIncrement(); } - countDownLatch.countDown(); + countDownLatchGet.countDown(); } catch (NullPointerException e) { threadError.getAndIncrement(); } }); } - for (Thread t : merkleTreeThreads) { + for (Thread t : getThreads) { t.start(); } try { - boolean doneOneTime = countDownLatch.await(60, TimeUnit.SECONDS); - Assertions.assertTrue(doneOneTime); + boolean doneOneTimeGet = countDownLatchGet.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTimeGet); } catch (InterruptedException e) { Assertions.fail(); } @@ -121,7 +150,7 @@ public void testGetNonExistingEntity() { public void testNullInsertion() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Assertions.assertThrows(IllegalArgumentException.class, () -> { - AuthenticatedEntity authenticatedEntity = merkleTree.put(null); + merkleTree.put(null); }); } From 205970f31a77d2becb838840311053949f68d4e4 Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Wed, 20 Apr 2022 18:35:39 +0300 Subject: [PATCH 16/28] improves exception handling1 --- src/main/java/modules/ads/merkletree/MerkleTree.java | 7 ++----- src/test/java/modules/ads/MerkleTreeTest.java | 7 ++++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 1cca64d9..883ebd06 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -75,18 +75,15 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { } finally { lock.readLock().unlock(); } - if (proof == null) { - return null; - } Entity e = entityHashTable.get(id); return new AuthenticatedLightChainEntity(proof, e.type(), e); } - private Proof getProof(Identifier id) { + private Proof getProof(Identifier id) throws IllegalArgumentException { Sha3256Hash hash = new Sha3256Hash(id.getBytes()); Integer idx = leafNodesHashTable.get(hash); if (idx == null) { - return null; + throw new IllegalArgumentException("Identifier not found"); } ArrayList path = new ArrayList<>(); MerkleNode currNode = leafNodes.get(idx); diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 66916ea0..edef8cfc 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -133,14 +133,15 @@ public void testConcurrentPutGet() { } /** - * Tests if getting an entity that does not exist in the merkle tree gives null. + * Tests if getting an entity that does not exist in the merkle tree throws IllegalArgumentException. */ @Test public void testGetNonExistingEntity() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); Entity entity = new EntityFixture(); - AuthenticatedEntity authenticatedEntity = merkleTree.get(entity.id()); - Assertions.assertNull(authenticatedEntity); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + merkleTree.get(entity.id()); + }); } /** From d0f93abbd7cd5216c659633295de486e6f16bc1e Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Wed, 20 Apr 2022 18:57:24 +0300 Subject: [PATCH 17/28] improves the hashing and how proofs are created in merkle tree --- src/main/java/crypto/Sha3256Hasher.java | 7 +----- .../java/modules/ads/MembershipProof.java | 2 ++ .../AuthenticatedLightChainEntity.java | 7 +++--- .../modules/ads/merkletree/MerkleNode.java | 2 +- .../modules/ads/merkletree/MerkleTree.java | 4 +++- .../java/modules/ads/merkletree/Proof.java | 22 ++++++++++++++++++- .../java/modules/ads/merkletree/Verifier.java | 14 ++++++++++-- src/test/java/modules/ads/MerkleTreeTest.java | 2 +- 8 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 048d11e4..8533390c 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -3,7 +3,6 @@ import java.io.ByteArrayOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import model.codec.EncodedEntity; import model.crypto.Sha3256Hash; @@ -68,11 +67,7 @@ public Sha3256Hash computeHash(Identifier id) { public Sha3256Hash computeHash(byte[] b1, byte[] b2) { try { MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); - int compare = Arrays.compare(b1, b2); - if (compare > 0) { - return new Sha3256Hash(md.digest(concat(b1, b2))); - } - return new Sha3256Hash(md.digest(concat(b2, b1))); + return new Sha3256Hash(md.digest(concat(b1, b2))); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); } diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index 942fe523..05840d92 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -31,4 +31,6 @@ public interface MembershipProof { * Sets the path of the proof of membership. */ void setPath(ArrayList path); + + ArrayList getIsLeftNode(); } diff --git a/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java b/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java index 95fe9790..711516bd 100644 --- a/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java +++ b/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java @@ -19,7 +19,7 @@ public class AuthenticatedLightChainEntity extends modules.ads.AuthenticatedEnti * @param e the entity */ public AuthenticatedLightChainEntity(Proof proof, String type, Entity e) { - this.membershipProof = new Proof(proof.getPath(), proof.getRoot()); + this.membershipProof = new Proof(proof.getPath(), proof.getRoot(), proof.getIsLeftNode()); this.type = type; this.entity = e; } @@ -44,10 +44,11 @@ public void setEntity(Entity entity) { @Override public MembershipProof getMembershipProof() { - return new Proof(membershipProof.getPath(), membershipProof.getRoot()); + return new Proof(membershipProof.getPath(), membershipProof.getRoot(), membershipProof.getIsLeftNode()); } public void setMembershipProof(MembershipProof membershipProof) { - this.membershipProof = new Proof(membershipProof.getPath(), membershipProof.getRoot());; + this.membershipProof = new Proof(membershipProof.getPath(), membershipProof.getRoot(), + membershipProof.getIsLeftNode()); } } diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 91922dec..21752b3b 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -61,7 +61,7 @@ public MerkleNode(Sha3256Hash hash) { * @param left left child of the node * @param right right child of the node */ - @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "left and right is intentionally mutable externally") + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "left and right are intentionally mutable externally") public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.left = left; this.right = right; diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 883ebd06..eb6a57f4 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -80,6 +80,7 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { } private Proof getProof(Identifier id) throws IllegalArgumentException { + ArrayList isLeftNode = new ArrayList<>(); Sha3256Hash hash = new Sha3256Hash(id.getBytes()); Integer idx = leafNodesHashTable.get(hash); if (idx == null) { @@ -89,9 +90,10 @@ private Proof getProof(Identifier id) throws IllegalArgumentException { MerkleNode currNode = leafNodes.get(idx); while (currNode != root) { path.add(currNode.getSibling().getHash()); + isLeftNode.add(currNode.isLeft()); currNode = currNode.getParent(); } - return new Proof(path, root.getHash()); + return new Proof(path, root.getHash(), isLeftNode); } private void buildMerkleTree() { diff --git a/src/main/java/modules/ads/merkletree/Proof.java b/src/main/java/modules/ads/merkletree/Proof.java index a30738d5..f5e1bb46 100644 --- a/src/main/java/modules/ads/merkletree/Proof.java +++ b/src/main/java/modules/ads/merkletree/Proof.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Objects; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.crypto.Sha3256Hash; import modules.ads.MembershipProof; @@ -12,11 +13,20 @@ */ public class Proof implements MembershipProof { private ArrayList path; + private ArrayList isLeftNode; private Sha3256Hash root; - public Proof(ArrayList path, Sha3256Hash root) { + /** + * Constructs a proof from a list of hashes and a root. + * + * @param path the list of hashes + * @param root the root + * @param isLeftNode the list of isLeft Boolean values of the hashes + */ + public Proof(ArrayList path, Sha3256Hash root, ArrayList isLeftNode) { this.path = new ArrayList<>(path); this.root = root; + this.isLeftNode = new ArrayList<>(isLeftNode); } @Override @@ -28,6 +38,16 @@ public void setPath(ArrayList path) { this.path = new ArrayList<>(path); } + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") + public ArrayList getIsLeftNode() { + return isLeftNode; + } + + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "setIsLeftNode is intentionally mutable externally") + public void setIsLeftNode(ArrayList isLeftNode) { + this.isLeftNode = isLeftNode; + } + public Sha3256Hash getRoot() { return root; } diff --git a/src/main/java/modules/ads/merkletree/Verifier.java b/src/main/java/modules/ads/merkletree/Verifier.java index 63c9659b..a76a5ba6 100644 --- a/src/main/java/modules/ads/merkletree/Verifier.java +++ b/src/main/java/modules/ads/merkletree/Verifier.java @@ -24,12 +24,22 @@ public class Verifier implements AuthenticatedEntityVerifier { @Override public boolean verify(AuthenticatedEntity authenticatedEntity) { MembershipProof proof = authenticatedEntity.getMembershipProof(); + ArrayList isLeftNode = proof.getIsLeftNode(); Sha3256Hash root = proof.getRoot(); ArrayList proofPath = proof.getPath(); Sha3256Hash initialHash = hasher.computeHash(authenticatedEntity.getEntity().id()); - Sha3256Hash currHash = hasher.computeHash(initialHash, proofPath.get(0)); + Sha3256Hash currHash; + if (isLeftNode.get(0)) { + currHash = hasher.computeHash(initialHash, proofPath.get(0)); + } else { + currHash = hasher.computeHash(proofPath.get(0), initialHash); + } for (int i = 1; i < proofPath.size(); i++) { - currHash = hasher.computeHash(currHash, proofPath.get(i)); + if (isLeftNode.get(i)) { + currHash = hasher.computeHash(currHash, proofPath.get(i)); + } else { + currHash = hasher.computeHash(proofPath.get(i), currHash); + } } return Arrays.equals(root.getBytes(), currHash.getBytes()); } diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index edef8cfc..b7a01793 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -193,7 +193,7 @@ public void testManipulatedProof() { AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); ArrayList proofPath = proof.getPath(); - proofPath.add(new Sha3256Hash(new byte[32])); + proofPath.set(0, new Sha3256Hash(new byte[32])); proof.setPath(proofPath); authenticatedEntity.setMembershipProof(proof); Verifier verifier = new Verifier(); From 21cef64e6c6b8704e4d7537fe4cc793633f0b44a Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 06:14:51 +0400 Subject: [PATCH 18/28] applies revisions --- src/main/java/crypto/Sha3256Hasher.java | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 8533390c..5158d03c 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -1,6 +1,5 @@ package crypto; -import java.io.ByteArrayOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -12,15 +11,13 @@ * Implements SHA3-256 hashing functionality. */ public class Sha3256Hasher implements Hasher { - private static final String HASH_ALG_SHA_3_256 = "SHA3-256"; - private static final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); private static byte[] concat(final byte[] e1, final byte[] e2) { - byte[] res = new byte[e1.length + e2.length]; - System.arraycopy(e1, 0, res, 0, e1.length); - System.arraycopy(e2, 0, res, e1.length, e2.length); - return res; + byte[] result = new byte[e1.length + e2.length]; + System.arraycopy(e1, 0, result, 0, e1.length); + System.arraycopy(e2, 0, result, e1.length, e2.length); + return result; } /** @@ -31,13 +28,7 @@ private static byte[] concat(final byte[] e1, final byte[] e2) { */ @Override public Sha3256Hash computeHash(EncodedEntity e) { - try { - MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); - byte[] hashValue = md.digest(e.getBytes()); - return new Sha3256Hash(hashValue); - } catch (NoSuchAlgorithmException ex) { - throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); - } + return this.computeHash(e.getBytes()); } /** @@ -47,9 +38,19 @@ public Sha3256Hash computeHash(EncodedEntity e) { * @return SHA3-256 hash object of the entity */ public Sha3256Hash computeHash(Identifier id) { + return this.computeHash(id.getBytes()); + } + + /** + * Computes hash of the given bytes. + * + * @param bytes input bytes. + * @return SHA3-256 hash object of the given bytes. + */ + public Sha3256Hash computeHash(byte[] bytes) { try { MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); - byte[] hashValue = md.digest(id.getBytes()); + byte[] hashValue = md.digest(bytes); return new Sha3256Hash(hashValue); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); @@ -73,7 +74,7 @@ public Sha3256Hash computeHash(byte[] b1, byte[] b2) { } } - public Sha3256Hash computeHash(Sha3256Hash sha3256Hash, Sha3256Hash sha3256Hash1) { - return computeHash(sha3256Hash.getBytes(), sha3256Hash1.getBytes()); + public Sha3256Hash computeHash(Sha3256Hash h1, Sha3256Hash h2) { + return computeHash(h1.getBytes(), h2.getBytes()); } } From a3b33b70c4678605367c318f1a5f3cd74b93752a Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 06:15:37 +0400 Subject: [PATCH 19/28] applies revisions --- src/main/java/crypto/Sha3256Hasher.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 5158d03c..3f50e4ff 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -62,8 +62,7 @@ public Sha3256Hash computeHash(byte[] bytes) { * * @param b1 first byte array. * @param b2 second byte array. - * @return SHA3-256 hash object of the commutative concatenation (i.e. max(b1,b2) || min(b1,b2)) - * of the two byte arrays. + * @return SHA3-256 hash object of the commutative concatenation of the two byte arrays. */ public Sha3256Hash computeHash(byte[] b1, byte[] b2) { try { From 9d20b36dc2f74919487e126054fa27c5c6e1f907 Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 06:29:59 +0400 Subject: [PATCH 20/28] applies revisions --- .../ads/AuthenticatedDataStructure.java | 2 + ...inEntity.java => AuthenticatedEntity.java} | 4 +- ....java => AuthenticatedEntityVerifier.java} | 3 +- .../modules/ads/merkletree/MerkleTree.java | 15 +++--- src/test/java/modules/ads/MerkleTreeTest.java | 53 +++++++++++++------ 5 files changed, 50 insertions(+), 27 deletions(-) rename src/main/java/modules/ads/merkletree/{AuthenticatedLightChainEntity.java => AuthenticatedEntity.java} (88%) rename src/main/java/modules/ads/merkletree/{Verifier.java => AuthenticatedEntityVerifier.java} (93%) diff --git a/src/main/java/modules/ads/AuthenticatedDataStructure.java b/src/main/java/modules/ads/AuthenticatedDataStructure.java index 8fde6250..8b360017 100644 --- a/src/main/java/modules/ads/AuthenticatedDataStructure.java +++ b/src/main/java/modules/ads/AuthenticatedDataStructure.java @@ -10,4 +10,6 @@ public interface AuthenticatedDataStructure { AuthenticatedEntity put(Entity e); AuthenticatedEntity get(Identifier id); + + int size(); } diff --git a/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java b/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java similarity index 88% rename from src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java rename to src/main/java/modules/ads/merkletree/AuthenticatedEntity.java index 711516bd..09d7a7b1 100644 --- a/src/main/java/modules/ads/merkletree/AuthenticatedLightChainEntity.java +++ b/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java @@ -6,7 +6,7 @@ /** * An entity with its membership proof and type. */ -public class AuthenticatedLightChainEntity extends modules.ads.AuthenticatedEntity { +public class AuthenticatedEntity extends modules.ads.AuthenticatedEntity { private MembershipProof membershipProof; private String type; private Entity entity; @@ -18,7 +18,7 @@ public class AuthenticatedLightChainEntity extends modules.ads.AuthenticatedEnti * @param type the type of the entity * @param e the entity */ - public AuthenticatedLightChainEntity(Proof proof, String type, Entity e) { + public AuthenticatedEntity(Proof proof, String type, Entity e) { this.membershipProof = new Proof(proof.getPath(), proof.getRoot(), proof.getIsLeftNode()); this.type = type; this.entity = e; diff --git a/src/main/java/modules/ads/merkletree/Verifier.java b/src/main/java/modules/ads/merkletree/AuthenticatedEntityVerifier.java similarity index 93% rename from src/main/java/modules/ads/merkletree/Verifier.java rename to src/main/java/modules/ads/merkletree/AuthenticatedEntityVerifier.java index a76a5ba6..a2f61a0f 100644 --- a/src/main/java/modules/ads/merkletree/Verifier.java +++ b/src/main/java/modules/ads/merkletree/AuthenticatedEntityVerifier.java @@ -6,13 +6,12 @@ import crypto.Sha3256Hasher; import model.crypto.Sha3256Hash; import modules.ads.AuthenticatedEntity; -import modules.ads.AuthenticatedEntityVerifier; import modules.ads.MembershipProof; /** * Verifies the AuthenticatedEntity against its self-contained proof. */ -public class Verifier implements AuthenticatedEntityVerifier { +public class AuthenticatedEntityVerifier implements modules.ads.AuthenticatedEntityVerifier { private static final Sha3256Hasher hasher = new Sha3256Hasher(); /** diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index eb6a57f4..258724df 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -10,7 +10,6 @@ import model.crypto.Sha3256Hash; import model.lightchain.Identifier; import modules.ads.AuthenticatedDataStructure; -import modules.ads.AuthenticatedEntity; /** * Implementation of an in-memory Authenticated Skip List @@ -38,7 +37,7 @@ public MerkleTree() { } @Override - public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { + public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException { try { lock.writeLock().lock(); if (e == null) { @@ -53,10 +52,10 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { size++; buildMerkleTree(); Proof proof = getProof(e.id()); - return new AuthenticatedLightChainEntity(proof, e.type(), e); + return new AuthenticatedEntity(proof, e.type(), e); } else { Proof proof = getProof(e.id()); - return new AuthenticatedLightChainEntity(proof, e.type(), e); + return new AuthenticatedEntity(proof, e.type(), e); } } finally { lock.writeLock().unlock(); @@ -64,7 +63,7 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { } @Override - public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { + public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { Proof proof; if (id == null) { throw new IllegalArgumentException("Identifier cannot be null"); @@ -76,7 +75,7 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { lock.readLock().unlock(); } Entity e = entityHashTable.get(id); - return new AuthenticatedLightChainEntity(proof, e.type(), e); + return new AuthenticatedEntity(proof, e.type(), e); } private Proof getProof(Identifier id) throws IllegalArgumentException { @@ -123,4 +122,8 @@ private void buildMerkleTree() { } root = childNodes.get(0); } + + public int size() { + return this.size; + } } diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index b7a01793..65b83194 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -8,8 +8,8 @@ import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; +import modules.ads.merkletree.AuthenticatedEntityVerifier; import modules.ads.merkletree.MerkleTree; -import modules.ads.merkletree.Verifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import unittest.fixtures.EntityFixture; @@ -21,15 +21,19 @@ public class MerkleTreeTest { /** - * A basic test for one concurrent put and get operations. + * A basic test for one sequential put and get operations. */ @Test public void testVerification() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); merkleTree.put(entity); + Assertions.assertEquals(merkleTree.size(), 6); + AuthenticatedEntity authenticatedEntity = merkleTree.get(entity.id()); - Verifier verifier = new Verifier(); + AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); } @@ -40,16 +44,31 @@ public void testVerification() { @Test public void testPutGetSameProof() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); - Entity entity1 = new EntityFixture(); - AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity1); - MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); - AuthenticatedEntity authenticatedEntityGet = merkleTree.get(entity1.id()); - MembershipProof proofGet = authenticatedEntityGet.getMembershipProof(); - Entity entity2 = new EntityFixture(); - AuthenticatedEntity authenticatedEntityPut2 = merkleTree.put(entity2); - MembershipProof proofPut2 = authenticatedEntityPut2.getMembershipProof(); - Assertions.assertEquals(proofPut, proofGet); - Assertions.assertNotEquals(proofPut, proofPut2); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Entity e1 = new EntityFixture(); + + // putting e1 + AuthenticatedEntity authenticatedEntityPut = merkleTree.put(e1); + MembershipProof proofPutE1 = authenticatedEntityPut.getMembershipProof(); + Assertions.assertEquals(merkleTree.size(), 6); + + // getting e1 + AuthenticatedEntity authE1Get = merkleTree.get(e1.id()); + MembershipProof proofGetE1 = authE1Get.getMembershipProof(); + + // putting e2 + Entity e2 = new EntityFixture(); + AuthenticatedEntity authE2Put = merkleTree.put(e2); + Assertions.assertEquals(merkleTree.size(), 7); + + // getting e2 + MembershipProof proofPutE2 = authE2Put.getMembershipProof(); + + // proofs for putting and getting e1 should be the same. + Assertions.assertEquals(proofPutE1, proofGetE1); + + // proofs for putting e1 and e2 must be different. + Assertions.assertNotEquals(proofPutE1, proofPutE2); } /** @@ -110,7 +129,7 @@ public void testConcurrentPutGet() { getThreads[i] = new Thread(() -> { try { AuthenticatedEntity authenticatedEntity = merkleTree.get(id); - Verifier verifier = new Verifier(); + AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); if (!verifier.verify(authenticatedEntity)) { threadError.getAndIncrement(); } @@ -166,7 +185,7 @@ public void testManipulatedRoot() { MembershipProof proof = authenticatedEntity.getMembershipProof(); proof.setRoot(new Sha3256Hash(new byte[32])); authenticatedEntity.setMembershipProof(proof); - Verifier verifier = new Verifier(); + AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); Assertions.assertFalse(verifier.verify(authenticatedEntity)); } @@ -179,7 +198,7 @@ public void testManipulatedEntity() { Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); authenticatedEntity.setEntity(new EntityFixture()); - Verifier verifier = new Verifier(); + AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); Assertions.assertFalse(verifier.verify(authenticatedEntity)); } @@ -196,7 +215,7 @@ public void testManipulatedProof() { proofPath.set(0, new Sha3256Hash(new byte[32])); proof.setPath(proofPath); authenticatedEntity.setMembershipProof(proof); - Verifier verifier = new Verifier(); + AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); Assertions.assertFalse(verifier.verify(authenticatedEntity)); } } From d4a77a776b5172810349266d471c17d27e43b97d Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 07:07:33 +0400 Subject: [PATCH 21/28] applies revisions --- src/test/java/modules/ads/MerkleTreeTest.java | 105 +++++++++++++----- .../unittest/fixtures/Sha3256HashFixture.java | 16 +++ 2 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 65b83194..312c7fc4 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -10,10 +10,12 @@ import model.lightchain.Identifier; import modules.ads.merkletree.AuthenticatedEntityVerifier; import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.Proof; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import unittest.fixtures.EntityFixture; import unittest.fixtures.MerkleTreeFixture; +import unittest.fixtures.Sha3256HashFixture; /** * Encapsulates tests for an authenticated and concurrent implementation of MerkleTree ADS. @@ -38,7 +40,7 @@ public void testVerification() { } /** - * Tests both if putting and getting the same entity gives same proof + * Tests both putting and getting the same entity gives same proof * and putting another entity gives different proofs. */ @Test @@ -72,45 +74,61 @@ public void testPutGetSameProof() { } /** - * Tests if putting an existing entity does not change the proof. + * Tests putting an existing entity does not change the proof. */ @Test public void testPutExistingEntity() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. Entity entity = new EntityFixture(); + + // first time put AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity); MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); + Assertions.assertEquals(merkleTree.size(), 6); + + // second attempt AuthenticatedEntity authenticatedEntityPutAgain = merkleTree.put(entity); MembershipProof proofPutAgain = authenticatedEntityPutAgain.getMembershipProof(); + + // proofs must be equal. Assertions.assertEquals(proofPut, proofPutAgain); + Assertions.assertEquals(merkleTree.size(), 6); // duplicate entity should not change the size. } /** - * Concurrently puts and gets entities and checks if their proofs are correct (thread safety check). + * Concurrently puts and gets entities and checks their proofs are correct (thread safety check). */ @Test public void testConcurrentPutGet() { int concurrencyDegree = 100; ArrayList entities = new ArrayList<>(); ArrayList ids = new ArrayList<>(); + AtomicInteger threadError = new AtomicInteger(); - CountDownLatch countDownLatchPut = new CountDownLatch(concurrencyDegree); - CountDownLatch countDownLatchGet = new CountDownLatch(concurrencyDegree); + CountDownLatch putDone = new CountDownLatch(concurrencyDegree); + CountDownLatch getDone = new CountDownLatch(concurrencyDegree); + Thread[] putThreads = new Thread[concurrencyDegree]; Thread[] getThreads = new Thread[concurrencyDegree]; - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(0); + Assertions.assertEquals(merkleTree.size(), 0); // fixture sanity check. + for (int i = 0; i < concurrencyDegree; i++) { Entity entity = new EntityFixture(); entities.add(entity); ids.add(entity.id()); } + + // put for (int i = 0; i < concurrencyDegree; i++) { Entity entity = entities.get(i); putThreads[i] = new Thread(() -> { try { merkleTree.put(entity); - countDownLatchPut.countDown(); - } catch (NullPointerException e) { + putDone.countDown(); + } catch (Exception e) { threadError.getAndIncrement(); } }); @@ -119,11 +137,13 @@ public void testConcurrentPutGet() { t.start(); } try { - boolean doneOneTimePut = countDownLatchPut.await(60, TimeUnit.SECONDS); + boolean doneOneTimePut = putDone.await(60, TimeUnit.SECONDS); Assertions.assertTrue(doneOneTimePut); } catch (InterruptedException e) { Assertions.fail(); } + + // get for (int i = 0; i < concurrencyDegree; i++) { Identifier id = ids.get(i); getThreads[i] = new Thread(() -> { @@ -133,8 +153,8 @@ public void testConcurrentPutGet() { if (!verifier.verify(authenticatedEntity)) { threadError.getAndIncrement(); } - countDownLatchGet.countDown(); - } catch (NullPointerException e) { + getDone.countDown(); + } catch (Exception e) { threadError.getAndIncrement(); } }); @@ -143,79 +163,110 @@ public void testConcurrentPutGet() { t.start(); } try { - boolean doneOneTimeGet = countDownLatchGet.await(60, TimeUnit.SECONDS); + boolean doneOneTimeGet = getDone.await(60, TimeUnit.SECONDS); Assertions.assertTrue(doneOneTimeGet); } catch (InterruptedException e) { Assertions.fail(); } Assertions.assertEquals(0, threadError.get()); + Assertions.assertEquals(concurrencyDegree, merkleTree.size()); } /** - * Tests if getting an entity that does not exist in the merkle tree throws IllegalArgumentException. + * Tests getting an entity that does not exist in the merkle tree throws IllegalArgumentException. */ @Test public void testGetNonExistingEntity() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. Entity entity = new EntityFixture(); + Assertions.assertThrows(IllegalArgumentException.class, () -> { merkleTree.get(entity.id()); }); } /** - * Tests if inserting null throws IllegalArgumentException. + * Tests inserting null throws IllegalArgumentException. */ @Test public void testNullInsertion() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. Assertions.assertThrows(IllegalArgumentException.class, () -> { merkleTree.put(null); }); } /** - * Tests if the proof verifies when root is changed. + * Tests the proof verification fails when root is changed. */ @Test public void testManipulatedRoot() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); - proof.setRoot(new Sha3256Hash(new byte[32])); - authenticatedEntity.setMembershipProof(proof); + + // creates a tampered proof with random root. + Proof tamperedProof = new Proof(proof.getPath(), new Sha3256Hash(new byte[32]), proof.getIsLeftNode()); + AuthenticatedEntity tamperedAuthenticatedEntity = new modules.ads.merkletree.AuthenticatedEntity( + tamperedProof, + authenticatedEntity.type(), + authenticatedEntity.getEntity()); + + AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); - Assertions.assertFalse(verifier.verify(authenticatedEntity)); + // authenticated entity must be verified. + Assertions.assertTrue(verifier.verify(authenticatedEntity)); + // tampered authenticated entity must be failed. + Assertions.assertFalse(verifier.verify(tamperedAuthenticatedEntity)); } /** - * Tests if the proof verifies when entity is changed. + * Tests the proof verification fails when entity is changed. */ @Test public void testManipulatedEntity() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); - authenticatedEntity.setEntity(new EntityFixture()); + + AuthenticatedEntity tamperedEntity = new modules.ads.merkletree.AuthenticatedEntity( + (Proof) authenticatedEntity.getMembershipProof(), + authenticatedEntity.type(), + new EntityFixture()); + AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); - Assertions.assertFalse(verifier.verify(authenticatedEntity)); + Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. + Assertions.assertFalse(verifier.verify(tamperedEntity)); // tampered entity fails verification. } /** - * Tests if the proof verifies when proof is changed. + * Tests the proof fails verification when proof part of authenticated entity is changed. */ @Test public void testManipulatedProof() { MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); - ArrayList proofPath = proof.getPath(); - proofPath.set(0, new Sha3256Hash(new byte[32])); - proof.setPath(proofPath); - authenticatedEntity.setMembershipProof(proof); + + AuthenticatedEntity tamperedEntity = new modules.ads.merkletree.AuthenticatedEntity( + new Proof(Sha3256HashFixture.newSha3256HashArrayList( + proof.getPath().size()), + proof.getRoot(), + proof.getIsLeftNode()), + authenticatedEntity.type(), + authenticatedEntity.getEntity()); + + AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); - Assertions.assertFalse(verifier.verify(authenticatedEntity)); + Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. + Assertions.assertFalse(verifier.verify(tamperedEntity)); // tampered entity fails verification. } } diff --git a/src/test/java/unittest/fixtures/Sha3256HashFixture.java b/src/test/java/unittest/fixtures/Sha3256HashFixture.java index 2a0bc7e6..41c5bbc7 100644 --- a/src/test/java/unittest/fixtures/Sha3256HashFixture.java +++ b/src/test/java/unittest/fixtures/Sha3256HashFixture.java @@ -1,5 +1,7 @@ package unittest.fixtures; +import java.util.ArrayList; + import model.crypto.Sha3256Hash; /** @@ -28,4 +30,18 @@ public static model.crypto.Sha3256Hash[] newSha3256HashArray() { } return hashArray; } + + /** + * Generates an ArrayList of random looking SHA3-256 hash values. + * + * @param size size of array list. + * @return an array filled with randomly generated SHA3-256 hash values. + */ + public static ArrayList newSha3256HashArrayList(int size) { + ArrayList hashArray = new ArrayList<>(); + for (int i = 0; i < size; i++) { + hashArray.add(newSha3256Hash()); + } + return hashArray; + } } From 8696b0fa78b79e69d79b6e8133cdf3492ccfd520 Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 11:36:06 +0400 Subject: [PATCH 22/28] fixes lint --- src/main/java/modules/ads/merkletree/MerkleTree.java | 6 +++--- ...catedEntity.java => MerkleTreeAuthenticatedEntity.java} | 4 ++-- src/test/java/modules/ads/MerkleTreeTest.java | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) rename src/main/java/modules/ads/merkletree/{AuthenticatedEntity.java => MerkleTreeAuthenticatedEntity.java} (88%) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 258724df..c9af93fb 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -52,10 +52,10 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep size++; buildMerkleTree(); Proof proof = getProof(e.id()); - return new AuthenticatedEntity(proof, e.type(), e); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } else { Proof proof = getProof(e.id()); - return new AuthenticatedEntity(proof, e.type(), e); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } } finally { lock.writeLock().unlock(); @@ -75,7 +75,7 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument lock.readLock().unlock(); } Entity e = entityHashTable.get(id); - return new AuthenticatedEntity(proof, e.type(), e); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } private Proof getProof(Identifier id) throws IllegalArgumentException { diff --git a/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java similarity index 88% rename from src/main/java/modules/ads/merkletree/AuthenticatedEntity.java rename to src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java index 09d7a7b1..775b6ac7 100644 --- a/src/main/java/modules/ads/merkletree/AuthenticatedEntity.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java @@ -6,7 +6,7 @@ /** * An entity with its membership proof and type. */ -public class AuthenticatedEntity extends modules.ads.AuthenticatedEntity { +public class MerkleTreeAuthenticatedEntity extends modules.ads.AuthenticatedEntity { private MembershipProof membershipProof; private String type; private Entity entity; @@ -18,7 +18,7 @@ public class AuthenticatedEntity extends modules.ads.AuthenticatedEntity { * @param type the type of the entity * @param e the entity */ - public AuthenticatedEntity(Proof proof, String type, Entity e) { + public MerkleTreeAuthenticatedEntity(Proof proof, String type, Entity e) { this.membershipProof = new Proof(proof.getPath(), proof.getRoot(), proof.getIsLeftNode()); this.type = type; this.entity = e; diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 312c7fc4..1819a025 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -10,6 +10,7 @@ import model.lightchain.Identifier; import modules.ads.merkletree.AuthenticatedEntityVerifier; import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; import modules.ads.merkletree.Proof; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -211,7 +212,7 @@ public void testManipulatedRoot() { // creates a tampered proof with random root. Proof tamperedProof = new Proof(proof.getPath(), new Sha3256Hash(new byte[32]), proof.getIsLeftNode()); - AuthenticatedEntity tamperedAuthenticatedEntity = new modules.ads.merkletree.AuthenticatedEntity( + AuthenticatedEntity tamperedAuthenticatedEntity = new MerkleTreeAuthenticatedEntity( tamperedProof, authenticatedEntity.type(), authenticatedEntity.getEntity()); @@ -234,7 +235,7 @@ public void testManipulatedEntity() { Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); - AuthenticatedEntity tamperedEntity = new modules.ads.merkletree.AuthenticatedEntity( + AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( (Proof) authenticatedEntity.getMembershipProof(), authenticatedEntity.type(), new EntityFixture()); @@ -256,7 +257,7 @@ public void testManipulatedProof() { AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); - AuthenticatedEntity tamperedEntity = new modules.ads.merkletree.AuthenticatedEntity( + AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( new Proof(Sha3256HashFixture.newSha3256HashArrayList( proof.getPath().size()), proof.getRoot(), From df806a8f13ca38e3b663e62201a3e7218e1c7d42 Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 11:38:37 +0400 Subject: [PATCH 23/28] removes unnecessary setters --- .../java/modules/ads/AuthenticatedEntity.java | 5 ----- .../MerkleTreeAuthenticatedEntity.java | 19 +++---------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/main/java/modules/ads/AuthenticatedEntity.java b/src/main/java/modules/ads/AuthenticatedEntity.java index 57019f20..449aa167 100644 --- a/src/main/java/modules/ads/AuthenticatedEntity.java +++ b/src/main/java/modules/ads/AuthenticatedEntity.java @@ -10,9 +10,4 @@ public abstract class AuthenticatedEntity extends Entity { public abstract Entity getEntity(); public abstract MembershipProof getMembershipProof(); - - public abstract void setMembershipProof(MembershipProof proof); - - public void setEntity(Entity entity) { - } } diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java index 775b6ac7..4b15df6f 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java @@ -7,9 +7,9 @@ * An entity with its membership proof and type. */ public class MerkleTreeAuthenticatedEntity extends modules.ads.AuthenticatedEntity { - private MembershipProof membershipProof; - private String type; - private Entity entity; + private final MembershipProof membershipProof; + private final String type; + private final Entity entity; /** * Constructor of an authenticated entity. @@ -29,26 +29,13 @@ public String type() { return type; } - public void setType(String type) { - this.type = type; - } - @Override public Entity getEntity() { return entity; } - public void setEntity(Entity entity) { - this.entity = entity; - } - @Override public MembershipProof getMembershipProof() { return new Proof(membershipProof.getPath(), membershipProof.getRoot(), membershipProof.getIsLeftNode()); } - - public void setMembershipProof(MembershipProof membershipProof) { - this.membershipProof = new Proof(membershipProof.getPath(), membershipProof.getRoot(), - membershipProof.getIsLeftNode()); - } } From 8b5d912b117fa6c87e4986430a26a5117e6b7873 Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 11:40:49 +0400 Subject: [PATCH 24/28] removes unnecessary setters --- src/main/java/modules/ads/MembershipProof.java | 9 --------- .../merkletree/{Proof.java => MerkleProof.java} | 16 +++------------- .../java/modules/ads/merkletree/MerkleTree.java | 10 +++++----- .../MerkleTreeAuthenticatedEntity.java | 6 +++--- src/test/java/modules/ads/MerkleTreeTest.java | 8 ++++---- 5 files changed, 15 insertions(+), 34 deletions(-) rename src/main/java/modules/ads/merkletree/{Proof.java => MerkleProof.java} (77%) diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index 05840d92..fcc50f14 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -15,11 +15,6 @@ public interface MembershipProof { */ Sha3256Hash getRoot(); - /** - * Sets the root of the authenticated data structure that this proof belongs to. - */ - void setRoot(Sha3256Hash root); - /** * Returns the path of the proof of membership. * @@ -27,10 +22,6 @@ public interface MembershipProof { */ ArrayList getPath(); - /** - * Sets the path of the proof of membership. - */ - void setPath(ArrayList path); ArrayList getIsLeftNode(); } diff --git a/src/main/java/modules/ads/merkletree/Proof.java b/src/main/java/modules/ads/merkletree/MerkleProof.java similarity index 77% rename from src/main/java/modules/ads/merkletree/Proof.java rename to src/main/java/modules/ads/merkletree/MerkleProof.java index f5e1bb46..f316d696 100644 --- a/src/main/java/modules/ads/merkletree/Proof.java +++ b/src/main/java/modules/ads/merkletree/MerkleProof.java @@ -11,7 +11,7 @@ /** * A proof of membership in a Merkle tree. */ -public class Proof implements MembershipProof { +public class MerkleProof implements MembershipProof { private ArrayList path; private ArrayList isLeftNode; private Sha3256Hash root; @@ -23,7 +23,7 @@ public class Proof implements MembershipProof { * @param root the root * @param isLeftNode the list of isLeft Boolean values of the hashes */ - public Proof(ArrayList path, Sha3256Hash root, ArrayList isLeftNode) { + public MerkleProof(ArrayList path, Sha3256Hash root, ArrayList isLeftNode) { this.path = new ArrayList<>(path); this.root = root; this.isLeftNode = new ArrayList<>(isLeftNode); @@ -43,20 +43,10 @@ public ArrayList getIsLeftNode() { return isLeftNode; } - @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "setIsLeftNode is intentionally mutable externally") - public void setIsLeftNode(ArrayList isLeftNode) { - this.isLeftNode = isLeftNode; - } - public Sha3256Hash getRoot() { return root; } - @Override - public void setRoot(Sha3256Hash root) { - this.root = root; - } - @Override public boolean equals(Object o) { if (this == o) { @@ -65,7 +55,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - Proof proof = (Proof) o; + MerkleProof proof = (MerkleProof) o; for (int i = 0; i < path.size(); i++) { if (!Arrays.equals(path.get(i).getBytes(), proof.path.get(i).getBytes())) { return false; diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index c9af93fb..3bcdd957 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -51,10 +51,10 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep entityHashTable.put(e.id(), e); size++; buildMerkleTree(); - Proof proof = getProof(e.id()); + MerkleProof proof = getProof(e.id()); return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } else { - Proof proof = getProof(e.id()); + MerkleProof proof = getProof(e.id()); return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } } finally { @@ -64,7 +64,7 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep @Override public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { - Proof proof; + MerkleProof proof; if (id == null) { throw new IllegalArgumentException("Identifier cannot be null"); } @@ -78,7 +78,7 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } - private Proof getProof(Identifier id) throws IllegalArgumentException { + private MerkleProof getProof(Identifier id) throws IllegalArgumentException { ArrayList isLeftNode = new ArrayList<>(); Sha3256Hash hash = new Sha3256Hash(id.getBytes()); Integer idx = leafNodesHashTable.get(hash); @@ -92,7 +92,7 @@ private Proof getProof(Identifier id) throws IllegalArgumentException { isLeftNode.add(currNode.isLeft()); currNode = currNode.getParent(); } - return new Proof(path, root.getHash(), isLeftNode); + return new MerkleProof(path, root.getHash(), isLeftNode); } private void buildMerkleTree() { diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java index 4b15df6f..a6aa2f33 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java @@ -18,8 +18,8 @@ public class MerkleTreeAuthenticatedEntity extends modules.ads.AuthenticatedEnti * @param type the type of the entity * @param e the entity */ - public MerkleTreeAuthenticatedEntity(Proof proof, String type, Entity e) { - this.membershipProof = new Proof(proof.getPath(), proof.getRoot(), proof.getIsLeftNode()); + public MerkleTreeAuthenticatedEntity(MerkleProof proof, String type, Entity e) { + this.membershipProof = new MerkleProof(proof.getPath(), proof.getRoot(), proof.getIsLeftNode()); this.type = type; this.entity = e; } @@ -36,6 +36,6 @@ public Entity getEntity() { @Override public MembershipProof getMembershipProof() { - return new Proof(membershipProof.getPath(), membershipProof.getRoot(), membershipProof.getIsLeftNode()); + return new MerkleProof(membershipProof.getPath(), membershipProof.getRoot(), membershipProof.getIsLeftNode()); } } diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 1819a025..f95296ef 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -11,7 +11,7 @@ import modules.ads.merkletree.AuthenticatedEntityVerifier; import modules.ads.merkletree.MerkleTree; import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; -import modules.ads.merkletree.Proof; +import modules.ads.merkletree.MerkleProof; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import unittest.fixtures.EntityFixture; @@ -211,7 +211,7 @@ public void testManipulatedRoot() { MembershipProof proof = authenticatedEntity.getMembershipProof(); // creates a tampered proof with random root. - Proof tamperedProof = new Proof(proof.getPath(), new Sha3256Hash(new byte[32]), proof.getIsLeftNode()); + MerkleProof tamperedProof = new MerkleProof(proof.getPath(), new Sha3256Hash(new byte[32]), proof.getIsLeftNode()); AuthenticatedEntity tamperedAuthenticatedEntity = new MerkleTreeAuthenticatedEntity( tamperedProof, authenticatedEntity.type(), @@ -236,7 +236,7 @@ public void testManipulatedEntity() { AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( - (Proof) authenticatedEntity.getMembershipProof(), + (MerkleProof) authenticatedEntity.getMembershipProof(), authenticatedEntity.type(), new EntityFixture()); @@ -258,7 +258,7 @@ public void testManipulatedProof() { MembershipProof proof = authenticatedEntity.getMembershipProof(); AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( - new Proof(Sha3256HashFixture.newSha3256HashArrayList( + new MerkleProof(Sha3256HashFixture.newSha3256HashArrayList( proof.getPath().size()), proof.getRoot(), proof.getIsLeftNode()), From bd1ad65167ba2e26f7a7b13327437a1a2a826f35 Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 11:42:36 +0400 Subject: [PATCH 25/28] fixes a java doc --- src/test/java/unittest/fixtures/MerkleTreeFixture.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java index 1365ef23..ebf0347c 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -10,7 +10,7 @@ public class MerkleTreeFixture { * Creates a new skip list with n random elements. * * @param n number of elements to create - * @return a new skip list with n random elements + * @return a new merkle tree with n random elements. */ public static MerkleTree createMerkleTree(int n) { MerkleTree merkleTree = new MerkleTree(); From afee4e13770a13ba57b902267c75c9bfc995a706 Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 11:47:34 +0400 Subject: [PATCH 26/28] applies revisions --- ...MerkleTreeAuthenticatedEntityVerifier.java} | 18 +++++++++--------- src/test/java/modules/ads/MerkleTreeTest.java | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) rename src/main/java/modules/ads/merkletree/{AuthenticatedEntityVerifier.java => MerkleTreeAuthenticatedEntityVerifier.java} (65%) diff --git a/src/main/java/modules/ads/merkletree/AuthenticatedEntityVerifier.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java similarity index 65% rename from src/main/java/modules/ads/merkletree/AuthenticatedEntityVerifier.java rename to src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java index a2f61a0f..bdea1990 100644 --- a/src/main/java/modules/ads/merkletree/AuthenticatedEntityVerifier.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java @@ -11,8 +11,7 @@ /** * Verifies the AuthenticatedEntity against its self-contained proof. */ -public class AuthenticatedEntityVerifier implements modules.ads.AuthenticatedEntityVerifier { - private static final Sha3256Hasher hasher = new Sha3256Hasher(); +public class MerkleTreeAuthenticatedEntityVerifier implements modules.ads.AuthenticatedEntityVerifier { /** * Verifies the AuthenticatedEntity against its self-contained proof. @@ -22,24 +21,25 @@ public class AuthenticatedEntityVerifier implements modules.ads.AuthenticatedEnt */ @Override public boolean verify(AuthenticatedEntity authenticatedEntity) { + Sha3256Hasher hasher = new Sha3256Hasher(); MembershipProof proof = authenticatedEntity.getMembershipProof(); ArrayList isLeftNode = proof.getIsLeftNode(); - Sha3256Hash root = proof.getRoot(); ArrayList proofPath = proof.getPath(); + Sha3256Hash initialHash = hasher.computeHash(authenticatedEntity.getEntity().id()); - Sha3256Hash currHash; + Sha3256Hash currentHash; if (isLeftNode.get(0)) { - currHash = hasher.computeHash(initialHash, proofPath.get(0)); + currentHash = hasher.computeHash(initialHash, proofPath.get(0)); } else { - currHash = hasher.computeHash(proofPath.get(0), initialHash); + currentHash = hasher.computeHash(proofPath.get(0), initialHash); } for (int i = 1; i < proofPath.size(); i++) { if (isLeftNode.get(i)) { - currHash = hasher.computeHash(currHash, proofPath.get(i)); + currentHash = hasher.computeHash(currentHash, proofPath.get(i)); } else { - currHash = hasher.computeHash(proofPath.get(i), currHash); + currentHash = hasher.computeHash(proofPath.get(i), currentHash); } } - return Arrays.equals(root.getBytes(), currHash.getBytes()); + return Arrays.equals(proof.getRoot().getBytes(), currentHash.getBytes()); } } \ No newline at end of file diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index f95296ef..eaea8748 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -8,7 +8,7 @@ import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; -import modules.ads.merkletree.AuthenticatedEntityVerifier; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; import modules.ads.merkletree.MerkleTree; import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; import modules.ads.merkletree.MerkleProof; @@ -36,7 +36,7 @@ public void testVerification() { Assertions.assertEquals(merkleTree.size(), 6); AuthenticatedEntity authenticatedEntity = merkleTree.get(entity.id()); - AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); } @@ -150,7 +150,7 @@ public void testConcurrentPutGet() { getThreads[i] = new Thread(() -> { try { AuthenticatedEntity authenticatedEntity = merkleTree.get(id); - AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); if (!verifier.verify(authenticatedEntity)) { threadError.getAndIncrement(); } @@ -218,7 +218,7 @@ public void testManipulatedRoot() { authenticatedEntity.getEntity()); - AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); // authenticated entity must be verified. Assertions.assertTrue(verifier.verify(authenticatedEntity)); // tampered authenticated entity must be failed. @@ -240,7 +240,7 @@ public void testManipulatedEntity() { authenticatedEntity.type(), new EntityFixture()); - AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. Assertions.assertFalse(verifier.verify(tamperedEntity)); // tampered entity fails verification. } @@ -266,7 +266,7 @@ public void testManipulatedProof() { authenticatedEntity.getEntity()); - AuthenticatedEntityVerifier verifier = new AuthenticatedEntityVerifier(); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. Assertions.assertFalse(verifier.verify(tamperedEntity)); // tampered entity fails verification. } From b4a394b593b07c08d5906d209f65ffa1f00b96ff Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 11:58:38 +0400 Subject: [PATCH 27/28] fixes lint --- src/main/java/modules/ads/MembershipProof.java | 1 - .../java/modules/ads/merkletree/MerkleNode.java | 15 --------------- .../java/modules/ads/merkletree/MerkleProof.java | 8 ++++---- .../java/modules/ads/merkletree/MerkleTree.java | 10 +++++----- src/test/java/modules/ads/MerkleTreeTest.java | 6 ++---- 5 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index fcc50f14..724792ff 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -22,6 +22,5 @@ public interface MembershipProof { */ ArrayList getPath(); - ArrayList getIsLeftNode(); } diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 21752b3b..4551b1bf 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -80,11 +80,6 @@ public MerkleNode getRight() { return right; } - @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "right is intentionally mutable externally") - public void setRight(MerkleNode right) { - this.right = right; - } - @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getParent() { return parent; @@ -99,19 +94,10 @@ public Sha3256Hash getHash() { return hash; } - public void setHash(Sha3256Hash hash) { - this.hash = hash; - } - public boolean isLeft() { return isLeft; } - @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "left is intentionally mutable externally") - public void setLeft(MerkleNode left) { - this.left = left; - } - public void setLeft(boolean isLeft) { this.isLeft = isLeft; } @@ -128,5 +114,4 @@ public MerkleNode getSibling() { return parent.getLeft(); } } - } diff --git a/src/main/java/modules/ads/merkletree/MerkleProof.java b/src/main/java/modules/ads/merkletree/MerkleProof.java index f316d696..c0b3e8c4 100644 --- a/src/main/java/modules/ads/merkletree/MerkleProof.java +++ b/src/main/java/modules/ads/merkletree/MerkleProof.java @@ -13,14 +13,14 @@ */ public class MerkleProof implements MembershipProof { private ArrayList path; - private ArrayList isLeftNode; - private Sha3256Hash root; + private final ArrayList isLeftNode; + private final Sha3256Hash root; /** * Constructs a proof from a list of hashes and a root. * - * @param path the list of hashes - * @param root the root + * @param path the list of hashes + * @param root the root * @param isLeftNode the list of isLeft Boolean values of the hashes */ public MerkleProof(ArrayList path, Sha3256Hash root, ArrayList isLeftNode) { diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 3bcdd957..eb304921 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -41,7 +41,7 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep try { lock.writeLock().lock(); if (e == null) { - throw new IllegalArgumentException("Entity cannot be null"); + throw new IllegalArgumentException("entity cannot be null"); } Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); Integer idx = leafNodesHashTable.get(hash); @@ -66,16 +66,16 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { MerkleProof proof; if (id == null) { - throw new IllegalArgumentException("Identifier cannot be null"); + throw new IllegalArgumentException("identifier cannot be null"); } try { lock.readLock().lock(); proof = getProof(id); + Entity e = entityHashTable.get(id); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } finally { lock.readLock().unlock(); } - Entity e = entityHashTable.get(id); - return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } private MerkleProof getProof(Identifier id) throws IllegalArgumentException { @@ -83,7 +83,7 @@ private MerkleProof getProof(Identifier id) throws IllegalArgumentException { Sha3256Hash hash = new Sha3256Hash(id.getBytes()); Integer idx = leafNodesHashTable.get(hash); if (idx == null) { - throw new IllegalArgumentException("Identifier not found"); + throw new IllegalArgumentException("identifier not found"); } ArrayList path = new ArrayList<>(); MerkleNode currNode = leafNodes.get(idx); diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index eaea8748..95a290dc 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -8,10 +8,10 @@ import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; -import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; +import modules.ads.merkletree.MerkleProof; import modules.ads.merkletree.MerkleTree; import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; -import modules.ads.merkletree.MerkleProof; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import unittest.fixtures.EntityFixture; @@ -217,7 +217,6 @@ public void testManipulatedRoot() { authenticatedEntity.type(), authenticatedEntity.getEntity()); - MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); // authenticated entity must be verified. Assertions.assertTrue(verifier.verify(authenticatedEntity)); @@ -265,7 +264,6 @@ public void testManipulatedProof() { authenticatedEntity.type(), authenticatedEntity.getEntity()); - MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. Assertions.assertFalse(verifier.verify(tamperedEntity)); // tampered entity fails verification. From 2ad6832e8097f9c82f701601e06e487b8675fa11 Mon Sep 17 00:00:00 2001 From: Yahya Date: Sun, 24 Apr 2022 12:10:49 +0400 Subject: [PATCH 28/28] applies revisions --- .../modules/ads/merkletree/MerkleTree.java | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index eb304921..2c194c4a 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -86,41 +86,48 @@ private MerkleProof getProof(Identifier id) throws IllegalArgumentException { throw new IllegalArgumentException("identifier not found"); } ArrayList path = new ArrayList<>(); - MerkleNode currNode = leafNodes.get(idx); - while (currNode != root) { - path.add(currNode.getSibling().getHash()); - isLeftNode.add(currNode.isLeft()); - currNode = currNode.getParent(); + MerkleNode currentNode = leafNodes.get(idx); + while (currentNode != root) { + path.add(currentNode.getSibling().getHash()); + isLeftNode.add(currentNode.isLeft()); + currentNode = currentNode.getParent(); } return new MerkleProof(path, root.getHash(), isLeftNode); } private void buildMerkleTree() { - ArrayList parentNodes = new ArrayList<>(); - ArrayList childNodes = new ArrayList<>(leafNodes); - while (childNodes.size() > 1) { - int idx = 0; - int len = childNodes.size(); - while (idx < len) { - MerkleNode left = childNodes.get(idx); + // keeps nodes of the current level of the merkle tree + // will be updated bottom up + // initialized with leaves + ArrayList currentLevelNodes = new ArrayList<>(leafNodes); + + // keeps nodes of the next level of merkle tree + // used as an intermediary data structure. + ArrayList nextLevelNodes = new ArrayList<>(); + + while (currentLevelNodes.size() > 1) { // more than one current node, means we have not yet reached root. + for (int i = 0; i < currentLevelNodes.size(); i += 2) { + // pairs up current level nodes as siblings for next level. + MerkleNode left = currentLevelNodes.get(i); left.setLeft(true); + MerkleNode right; - if (idx + 1 < len) { - right = childNodes.get(idx + 1); + if (i + 1 < currentLevelNodes.size()) { + right = currentLevelNodes.get(i + 1); // we have a right node } else { + // TODO: edge case need to get fixed. right = new MerkleNode(left.getHash()); } Sha3256Hash hash = hasher.computeHash(left.getHash().getBytes(), right.getHash().getBytes()); MerkleNode parent = new MerkleNode(hash, left, right); left.setParent(parent); right.setParent(parent); - parentNodes.add(parent); - idx += 2; + nextLevelNodes.add(parent); } - childNodes = parentNodes; - parentNodes = new ArrayList<>(); + currentLevelNodes = nextLevelNodes; + nextLevelNodes = new ArrayList<>(); } - root = childNodes.get(0); + root = currentLevelNodes.get(0); } public int size() {