From a5cf425269c66a745200d234ac244709f2af5f91 Mon Sep 17 00:00:00 2001 From: Ozan Date: Tue, 3 May 2022 00:05:07 +0300 Subject: [PATCH 1/3] enhances the merkle tree implementation --- .../ads/AuthenticatedDataStructure.java | 19 ++++ .../java/modules/ads/MembershipProof.java | 11 +- .../modules/ads/merkletree/MerkleNode.java | 35 ++++++ .../modules/ads/merkletree/MerklePath.java | 102 ++++++++++++++++++ .../modules/ads/merkletree/MerkleProof.java | 66 ++++++------ .../modules/ads/merkletree/MerkleTree.java | 38 ++++++- .../MerkleTreeAuthenticatedEntity.java | 19 +++- ...MerkleTreeAuthenticatedEntityVerifier.java | 6 +- 8 files changed, 248 insertions(+), 48 deletions(-) create mode 100644 src/main/java/modules/ads/merkletree/MerklePath.java diff --git a/src/main/java/modules/ads/AuthenticatedDataStructure.java b/src/main/java/modules/ads/AuthenticatedDataStructure.java index 8b360017..443318d0 100644 --- a/src/main/java/modules/ads/AuthenticatedDataStructure.java +++ b/src/main/java/modules/ads/AuthenticatedDataStructure.java @@ -7,9 +7,28 @@ * Models AuthenticatedDataStructure (ADS) and a key-value store of entities supported with membership proofs. */ public interface AuthenticatedDataStructure { + /** + * Adds an entity to the ADS. + * + * @param e the entity to add + * + * @return AuthenticatedEntity containing the entity and its membership proof + */ AuthenticatedEntity put(Entity e); + /** + * Returns the AuthenticatedEntity corresponding to the given identifier. + * + * @param id the identifier of the entity to retrieve + * + * @return the AuthenticatedEntity corresponding to the given identifier + */ AuthenticatedEntity get(Identifier id); + /** + * Returns the size of the ADS. + * + * @return the size of the ADS + */ int size(); } diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index 724792ff..ff5f21e4 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -1,8 +1,7 @@ package modules.ads; -import java.util.ArrayList; - import model.crypto.Sha3256Hash; +import modules.ads.merkletree.MerklePath; /** * Represents a Merkle Proof of membership against a certain root identifier. @@ -16,11 +15,9 @@ public interface MembershipProof { Sha3256Hash getRoot(); /** - * Returns the path of the proof of membership. + * Returns the merkle path of the proof of membership. * - * @return path of the proof of membership. + * @return merkle path of the proof of membership. */ - ArrayList getPath(); - - ArrayList getIsLeftNode(); + MerklePath getMerklePath(); } diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 4551b1bf..a1b554d4 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -70,34 +70,69 @@ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.hash = hash; } + /** + * Returns the left child of the node. + * + * @return the left child of the node + */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getLeft() { return left; } + /** + * Returns the right child of the node. + * + * @return the right child of the node + */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getRight() { return right; } + /** + * Returns the parent of the node. + * + * @return the parent of the node + */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getParent() { return parent; } + /** + * Sets the parent of the node. + * + * @param parent the parent of the node + */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") public void setParent(MerkleNode parent) { this.parent = parent; } + /** + * Returns the hash corresponding to the node. + * + * @return the hash corresponding to the node + */ public Sha3256Hash getHash() { return hash; } + /** + * Returns true if the node is a left child, false otherwise. + * + * @return true if the node is a left child, false otherwise + */ public boolean isLeft() { return isLeft; } + /** + * Sets if the node is a left child. + * + * @param isLeft true if the node is a left child, false otherwise + */ public void setLeft(boolean isLeft) { this.isLeft = isLeft; } diff --git a/src/main/java/modules/ads/merkletree/MerklePath.java b/src/main/java/modules/ads/merkletree/MerklePath.java new file mode 100644 index 00000000..e2a711da --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerklePath.java @@ -0,0 +1,102 @@ +package modules.ads.merkletree; + +import java.util.ArrayList; +import java.util.Objects; + +import model.crypto.Sha3256Hash; + +/** + * A MerklePath is a list of hashes that represents the path from the node to the root and an arraylist which + * contains which child (left or right) the nodes in the path is. + */ +public class MerklePath { + private ArrayList path; + private ArrayList isLeftNode; + + /** + * Default constructor for a MerklePath. + */ + public MerklePath() { + this.path = new ArrayList<>(); + this.isLeftNode = new ArrayList<>(); + } + + /** + * Constructor for a MerklePath from another MerklePath. + * + * @param merklePath the MerklePath to copy. + */ + public MerklePath(MerklePath merklePath) { + this.path = new ArrayList<>(merklePath.path); + this.isLeftNode = new ArrayList<>(merklePath.isLeftNode); + } + + /** + * Constructor with path and isLeftNode. + * + * @param path the path of the proof. + * @param isLeftNode the isLeftNode of the MerklePath. + */ + public MerklePath(ArrayList path, ArrayList isLeftNode) { + this.path = new ArrayList<>(path); + this.isLeftNode = new ArrayList<>(isLeftNode); + } + + /** + * Adds a new node and its isLeft boolean to the merklePath. + * + * @param hash the hash of the node. + * @param isLeftNode the isLeftNode of the node. + */ + public void add(Sha3256Hash hash, boolean isLeftNode) { + this.path.add(hash); + this.isLeftNode.add(isLeftNode); + } + + /** + * Returns the path of the MerklePath. + * + * @return the path of the MerklePath. + */ + public ArrayList getPath() { + return new ArrayList<>(path); + } + + /** + * Returns the isLeftNode of the MerklePath. + * + * @return the isLeftNode of the MerklePath. + */ + public ArrayList getIsLeftNode() { + return new ArrayList<>(isLeftNode); + } + + /** + * Checks if two MerklePaths are equal. + * + * @param o the other MerklePath. + * + * @return true if the MerklePaths are equal, false otherwise. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MerklePath that = (MerklePath) o; + return path.equals(that.path) && isLeftNode.equals(that.isLeftNode); + } + + /** + * Returns the hashcode of the MerklePath. + * + * @return the hashcode of the MerklePath. + */ + @Override + public int hashCode() { + return Objects.hash(path, isLeftNode); + } +} diff --git a/src/main/java/modules/ads/merkletree/MerkleProof.java b/src/main/java/modules/ads/merkletree/MerkleProof.java index c0b3e8c4..0cebafb0 100644 --- a/src/main/java/modules/ads/merkletree/MerkleProof.java +++ b/src/main/java/modules/ads/merkletree/MerkleProof.java @@ -1,10 +1,7 @@ package modules.ads.merkletree; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Objects; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.crypto.Sha3256Hash; import modules.ads.MembershipProof; @@ -12,41 +9,46 @@ * A proof of membership in a Merkle tree. */ public class MerkleProof implements MembershipProof { - private ArrayList path; - private final ArrayList isLeftNode; + private final MerklePath merklePath; 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 isLeftNode the list of isLeft Boolean values of the hashes + * @param merklePath the merkle path of the proof and their isLeft booleans */ - public MerkleProof(ArrayList path, Sha3256Hash root, ArrayList isLeftNode) { - this.path = new ArrayList<>(path); + public MerkleProof(Sha3256Hash root, MerklePath merklePath) { this.root = root; - this.isLeftNode = new ArrayList<>(isLeftNode); - } - - @Override - public ArrayList getPath() { - return new ArrayList<>(path); - } - - 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; + this.merklePath = new MerklePath(merklePath); } + /** + * Return the root of the Merkle tree. + * + * @return the root of the Merkle tree. + */ public Sha3256Hash getRoot() { return root; } + /** + * Returns the merkle path of the proof of membership. + * + * @return merkle path of the proof of membership. + */ + @Override + public MerklePath getMerklePath() { + return new MerklePath(merklePath); + } + + /** + * Checks if two MerkleProofs are equal. + * + * @param o the other MerkleProof + * + * @return true if the MerkleProofs are equal, false otherwise + */ @Override public boolean equals(Object o) { if (this == o) { @@ -55,17 +57,17 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - 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; - } - } - return root.equals(proof.root); + MerkleProof that = (MerkleProof) o; + return merklePath.equals(that.merklePath) && root.equals(that.root); } + /** + * Returns the hash code of the MerkleProof. + * + * @return the hash code of the MerkleProof. + */ @Override public int hashCode() { - return Objects.hash(path, root); + return Objects.hash(merklePath, root); } } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 2c194c4a..b0ebcc20 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -36,6 +36,13 @@ public MerkleTree() { this.entityHashTable = new HashMap<>(); } + /** + * Adds an entity to the merkle tree. + * + * @param e the entity to add + * + * @return AuthenticatedEntity containing the entity and its membership proof + */ @Override public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException { try { @@ -62,6 +69,13 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep } } + /** + * Returns the AuthenticatedEntity corresponding to the given identifier. + * + * @param id the identifier of the entity to retrieve + * + * @return the AuthenticatedEntity corresponding to the given identifier + */ @Override public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { MerkleProof proof; @@ -78,23 +92,32 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument } } + /** + * Returns the MerkleProof corresponding to the given identifier. + * + * @param id the identifier of the entity to retrieve + * + * @return the MerkleProof corresponding to the given identifier + * @throws IllegalArgumentException + */ private MerkleProof getProof(Identifier id) throws IllegalArgumentException { - ArrayList isLeftNode = new ArrayList<>(); Sha3256Hash hash = new Sha3256Hash(id.getBytes()); Integer idx = leafNodesHashTable.get(hash); if (idx == null) { throw new IllegalArgumentException("identifier not found"); } - ArrayList path = new ArrayList<>(); + MerklePath path = new MerklePath(); MerkleNode currentNode = leafNodes.get(idx); while (currentNode != root) { - path.add(currentNode.getSibling().getHash()); - isLeftNode.add(currentNode.isLeft()); + path.add(currentNode.getSibling().getHash(), currentNode.isLeft()); currentNode = currentNode.getParent(); } - return new MerkleProof(path, root.getHash(), isLeftNode); + return new MerkleProof(root.getHash(), path); } + /** + * Builds the Merkle Tree in a bottom-up manner. + */ private void buildMerkleTree() { // keeps nodes of the current level of the merkle tree // will be updated bottom up @@ -130,6 +153,11 @@ private void buildMerkleTree() { root = currentLevelNodes.get(0); } + /** + * Returns the size of the merkle tree. + * + * @return the size of the merkle tree + */ public int size() { return this.size; } diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java index a6aa2f33..54439cf1 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java @@ -19,23 +19,38 @@ public class MerkleTreeAuthenticatedEntity extends modules.ads.AuthenticatedEnti * @param e the entity */ public MerkleTreeAuthenticatedEntity(MerkleProof proof, String type, Entity e) { - this.membershipProof = new MerkleProof(proof.getPath(), proof.getRoot(), proof.getIsLeftNode()); + this.membershipProof = new MerkleProof(proof.getRoot(), proof.getMerklePath()); this.type = type; this.entity = e; } + /** + * Gets the type of the entity. + * + * @return the type of the entity + */ @Override public String type() { return type; } + /** + * Gets the entity. + * + * @return the entity + */ @Override public Entity getEntity() { return entity; } + /** + * Gets the membership proof. + * + * @return the membership proof + */ @Override public MembershipProof getMembershipProof() { - return new MerkleProof(membershipProof.getPath(), membershipProof.getRoot(), membershipProof.getIsLeftNode()); + return new MerkleProof(membershipProof.getRoot(), membershipProof.getMerklePath()); } } diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java index bdea1990..956fb0a8 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java @@ -17,14 +17,16 @@ public class MerkleTreeAuthenticatedEntityVerifier implements modules.ads.Authen * 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) { Sha3256Hasher hasher = new Sha3256Hasher(); MembershipProof proof = authenticatedEntity.getMembershipProof(); - ArrayList isLeftNode = proof.getIsLeftNode(); - ArrayList proofPath = proof.getPath(); + MerklePath path = proof.getMerklePath(); + ArrayList isLeftNode = path.getIsLeftNode(); + ArrayList proofPath = path.getPath(); Sha3256Hash initialHash = hasher.computeHash(authenticatedEntity.getEntity().id()); Sha3256Hash currentHash; From 289e82979467044f55871005f4813261b1f77acf Mon Sep 17 00:00:00 2001 From: Ozan Date: Tue, 3 May 2022 00:07:47 +0300 Subject: [PATCH 2/3] adds missing test files --- src/test/java/modules/ads/MerkleTreeTest.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 95a290dc..fce1c785 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -8,10 +8,7 @@ import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; -import modules.ads.merkletree.MerkleProof; -import modules.ads.merkletree.MerkleTree; -import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; -import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; +import modules.ads.merkletree.*; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import unittest.fixtures.EntityFixture; @@ -211,7 +208,7 @@ public void testManipulatedRoot() { MembershipProof proof = authenticatedEntity.getMembershipProof(); // creates a tampered proof with random root. - MerkleProof tamperedProof = new MerkleProof(proof.getPath(), new Sha3256Hash(new byte[32]), proof.getIsLeftNode()); + MerkleProof tamperedProof = new MerkleProof(new Sha3256Hash(new byte[32]), proof.getMerklePath()); AuthenticatedEntity tamperedAuthenticatedEntity = new MerkleTreeAuthenticatedEntity( tamperedProof, authenticatedEntity.type(), @@ -255,12 +252,13 @@ public void testManipulatedProof() { Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); - + MerklePath merklePath = proof.getMerklePath(); + ArrayList path = merklePath.getPath(); + ArrayList isLeft = merklePath.getIsLeftNode(); + ArrayList newPath = Sha3256HashFixture.newSha3256HashArrayList(path.size()); + MerklePath manipulatedMerklePath = new MerklePath(newPath, isLeft); AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( - new MerkleProof(Sha3256HashFixture.newSha3256HashArrayList( - proof.getPath().size()), - proof.getRoot(), - proof.getIsLeftNode()), + new MerkleProof(proof.getRoot(), manipulatedMerklePath), authenticatedEntity.type(), authenticatedEntity.getEntity()); From 1c8ab722e68aa725b0bcdf2b2898ad2bfcc22b31 Mon Sep 17 00:00:00 2001 From: Ozan Date: Tue, 3 May 2022 00:10:55 +0300 Subject: [PATCH 3/3] fixes a lint issue --- src/main/java/modules/ads/merkletree/MerkleTree.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index b0ebcc20..f35d0fae 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -98,7 +98,7 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument * @param id the identifier of the entity to retrieve * * @return the MerkleProof corresponding to the given identifier - * @throws IllegalArgumentException + * @throws IllegalArgumentException if the identifier is null */ private MerkleProof getProof(Identifier id) throws IllegalArgumentException { Sha3256Hash hash = new Sha3256Hash(id.getBytes());