From a5cf425269c66a745200d234ac244709f2af5f91 Mon Sep 17 00:00:00 2001 From: Ozan Date: Tue, 3 May 2022 00:05:07 +0300 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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()); From fefb5fbb422ae67389328b26a7ee34aa953104af Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Sun, 8 May 2022 12:56:19 +0300 Subject: [PATCH 04/19] creates foundational structure of merkle patricia trie --- src/main/java/crypto/Sha3256Hasher.java | 8 ++ .../ads/mtrie/MerklePatriciaBranchNode.java | 78 +++++++++++++++++++ .../ads/mtrie/MerklePatriciaLeafNode.java | 33 ++++++++ .../modules/ads/mtrie/MerklePatriciaNode.java | 20 +++++ .../ads/mtrie/MerklePatriciaNodeType.java | 8 ++ .../modules/ads/mtrie/MerklePatriciaTrie.java | 61 +++++++++++++++ 6 files changed, 208 insertions(+) create mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java create mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java create mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaNode.java create mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaNodeType.java create mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 3f50e4ff..42914c2a 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -76,4 +76,12 @@ public Sha3256Hash computeHash(byte[] b1, byte[] b2) { public Sha3256Hash computeHash(Sha3256Hash h1, Sha3256Hash h2) { return computeHash(h1.getBytes(), h2.getBytes()); } + + public Sha3256Hash computeHash(Sha3256Hash[] hashes) { + Sha3256Hash hash = computeHash(hashes[0].getBytes(), hashes[1].getBytes()); + for (int i = 2; i < hashes.length; i++) { + hash = computeHash(hash.getBytes(), hashes[i].getBytes()); + } + return hash; + } } diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java new file mode 100644 index 00000000..c44ae5ab --- /dev/null +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java @@ -0,0 +1,78 @@ +package modules.ads.mtrie; + +import crypto.Sha3256Hasher; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; + +public class MerklePatriciaBranchNode extends MerklePatriciaNode { + private final MerklePatriciaNode[] children = new MerklePatriciaNode[256]; + private final Sha3256Hasher hasher = new Sha3256Hasher(); + + /** + * Creates a child. + * + * @param index index of the child. + * @param child child to create. + * @return created child. + */ + public MerklePatriciaNode createChild(int index, MerklePatriciaNode child) { + children[index] = child; + return child; + } + + /** + * Creates a leaf node with the given identifier. + * + * @param idx index of the leaf node. + * @param id identifier of the leaf node. + * @return created leaf node. + */ + public MerklePatriciaNode createLeaf(int idx, Identifier id) { + return createChild(idx, new MerklePatriciaLeafNode(id)); + } + + /** + * Get the child node with the given index. + * + * @param idx index of the child node. + * @return child node with the given index. + */ + public MerklePatriciaBranchNode getChild(int idx) { + if (children[idx] == null) { + createChild(idx, new MerklePatriciaBranchNode()); + } + return (MerklePatriciaBranchNode) children[idx]; + } + + + /** + * Hash of this merkle patricia node. + * + * @return hash of this merkle patricia node. + */ + @Override + public Sha3256Hash getHash() { + Sha3256Hash[] hashes = new Sha3256Hash[256]; + for (int i = 0; i < 256; i++) { + if (children[i] != null) { + hashes[i] = children[i].getHash(); + } else { + hashes[i] = new Sha3256Hash(new byte[32]); + } + } + return hasher.computeHash(hashes); + } + + /** + * Type of this merkle patricia node. + * + * @return type of this entity. + */ + @Override + public String type() { + return MerklePatriciaNodeType.TYPE_BRANCH; + } + + public void setLeaf(int idx, Identifier id) { + } +} diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java new file mode 100644 index 00000000..1d4472cb --- /dev/null +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java @@ -0,0 +1,33 @@ +package modules.ads.mtrie; + +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; + +public class MerklePatriciaLeafNode extends MerklePatriciaNode { + private Sha3256Hash hash; + + public MerklePatriciaLeafNode(Identifier id) { + this.hash = new Sha3256Hash(id); + } + /** + * Hash of this merkle patricia node. + * + * @return hash of this merkle patricia node. + */ + @Override + public Sha3256Hash getHash() { + return this.hash; + } + + /** + * Type of this merkle patricia node. + * + * @return type of this entity. + */ + @Override + public String type() { + return MerklePatriciaNodeType.TYPE_LEAF; + } + +} diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java new file mode 100644 index 00000000..9fefec96 --- /dev/null +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java @@ -0,0 +1,20 @@ +package modules.ads.mtrie; + +import model.crypto.Sha3256Hash; + +public abstract class MerklePatriciaNode { + + /** + * Hash of this merkle patricia node. + * + * @return hash of this merkle patricia node. + */ + public abstract Sha3256Hash getHash(); + + /** + * Type of this merkle patricia node. + * + * @return type of this entity. + */ + public abstract String type(); +} diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaNodeType.java b/src/main/java/modules/ads/mtrie/MerklePatriciaNodeType.java new file mode 100644 index 00000000..65f3c68c --- /dev/null +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaNodeType.java @@ -0,0 +1,8 @@ +package modules.ads.mtrie; + +public class MerklePatriciaNodeType { + public static final String TYPE_ROOT = "type-merkle-patricia-trie-root"; + public static final String TYPE_BRANCH = "type-merkle-patricia-trie-branch"; + public static final String TYPE_LEAF = "type-merkle-patricia-trie-leaf"; + public static final String TYPE_EMPTY = "type-merkle-patricia-trie-empty"; +} diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java new file mode 100644 index 00000000..f2f3b906 --- /dev/null +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -0,0 +1,61 @@ +package modules.ads.mtrie; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import crypto.Sha3256Hasher; +import model.Entity; +import model.lightchain.Identifier; +import modules.ads.AuthenticatedEntity; +import modules.ads.merkletree.MerkleTree; + +public class MerklePatriciaTrie extends MerkleTree { + private static final Sha3256Hasher hasher = new Sha3256Hasher(); + private final ReentrantReadWriteLock lock; + private final MerklePatriciaBranchNode root; + + public MerklePatriciaTrie(ReentrantReadWriteLock lock, MerklePatriciaBranchNode root) { + this.lock = lock; + this.root = root; + } + + /** + * Adds an entity to the ADS. + * + * @param e the entity to add + * @return AuthenticatedEntity containing the entity and its membership proof + */ + @Override + public AuthenticatedEntity put(Entity e) { + int idx; + MerklePatriciaBranchNode currBranchNode = root; + byte[] bytes = e.id().getBytes(); + for (int i = 0; i < bytes.length - 1; i++) { + idx = bytes[i] + 128; + currBranchNode = currBranchNode.getChild(idx); + } + idx = bytes[bytes.length - 1] + 128; + currBranchNode.createLeaf(idx, e.id()); + return null; + } + + /** + * 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 AuthenticatedEntity get(Identifier id) { + return null; + } + + /** + * Returns the size of the ADS. + * + * @return the size of the ADS + */ + @Override + public int size() { + return 0; + } +} From d8a5833e826c15fb75f68cdae99aedba5a9ee329 Mon Sep 17 00:00:00 2001 From: Ozan Date: Sun, 8 May 2022 17:38:35 +0300 Subject: [PATCH 05/19] improvements --- .../ads/mtrie/MerklePatriciaBranchNode.java | 56 ++++++++++++++++--- .../ads/mtrie/MerklePatriciaLeafNode.java | 24 +++++++- .../modules/ads/mtrie/MerklePatriciaNode.java | 13 +++++ .../modules/ads/mtrie/MerklePatriciaTrie.java | 38 ++++++++++++- .../ads/mtrie/MerklePatriciaTrieProof.java | 30 ++++++++++ 5 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaTrieProof.java diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java index c44ae5ab..285906ef 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java @@ -7,6 +7,13 @@ public class MerklePatriciaBranchNode extends MerklePatriciaNode { private final MerklePatriciaNode[] children = new MerklePatriciaNode[256]; private final Sha3256Hasher hasher = new Sha3256Hasher(); + private Sha3256Hash hash; + private MerklePatriciaNode parent; + + public MerklePatriciaBranchNode(MerklePatriciaNode parent) { + this.hash = new Sha3256Hash(new byte[32]); + this.parent = parent; + } /** * Creates a child. @@ -27,31 +34,42 @@ public MerklePatriciaNode createChild(int index, MerklePatriciaNode child) { * @param id identifier of the leaf node. * @return created leaf node. */ - public MerklePatriciaNode createLeaf(int idx, Identifier id) { - return createChild(idx, new MerklePatriciaLeafNode(id)); + public MerklePatriciaLeafNode createLeaf(int idx, Identifier id) { + return (MerklePatriciaLeafNode) createChild(idx, new MerklePatriciaLeafNode(id, this)); } /** - * Get the child node with the given index. + * Get the child node with the given index. If child does not exist, creates a child. * * @param idx index of the child node. * @return child node with the given index. */ public MerklePatriciaBranchNode getChild(int idx) { if (children[idx] == null) { - createChild(idx, new MerklePatriciaBranchNode()); + createChild(idx, new MerklePatriciaBranchNode(this)); } return (MerklePatriciaBranchNode) children[idx]; } - /** - * Hash of this merkle patricia node. + * Get the child node with the given index. If child does not exist, returns null. * - * @return hash of this merkle patricia node. + * @param idx index of the child node. + * @return child node with the given index. + */ + public MerklePatriciaNode searchChild(int idx) { + if (children[idx] == null) { + return null; + } + return (MerklePatriciaNode) children[idx]; + } + + + /** + * Updates the hash of the merkle patricia node. */ @Override - public Sha3256Hash getHash() { + public void updateHash() { Sha3256Hash[] hashes = new Sha3256Hash[256]; for (int i = 0; i < 256; i++) { if (children[i] != null) { @@ -60,7 +78,17 @@ public Sha3256Hash getHash() { hashes[i] = new Sha3256Hash(new byte[32]); } } - return hasher.computeHash(hashes); + this.hash = hasher.computeHash(hashes); + } + + /** + * Hash of this merkle patricia node. + * + * @return hash of this merkle patricia node. + */ + @Override + public Sha3256Hash getHash() { + return this.hash; } /** @@ -73,6 +101,16 @@ public String type() { return MerklePatriciaNodeType.TYPE_BRANCH; } + /** + * Returns the parent of this merkle patricia node. + * + * @return the parent of this merkle patricia node. + */ + @Override + public MerklePatriciaNode getParent() { + return this.parent; + } + public void setLeaf(int idx, Identifier id) { } } diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java index 1d4472cb..abde1150 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java @@ -1,15 +1,25 @@ package modules.ads.mtrie; -import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; public class MerklePatriciaLeafNode extends MerklePatriciaNode { + private MerklePatriciaNode parent; private Sha3256Hash hash; - public MerklePatriciaLeafNode(Identifier id) { + public MerklePatriciaLeafNode(Identifier id, MerklePatriciaNode parent) { this.hash = new Sha3256Hash(id); + this.parent = parent; } + + /** + * Updates the hash of the merkle patricia node. + */ + @Override + public void updateHash() { + + } + /** * Hash of this merkle patricia node. * @@ -30,4 +40,14 @@ public String type() { return MerklePatriciaNodeType.TYPE_LEAF; } + /** + * Returns the parent of this merkle patricia node. + * + * @return the parent of this merkle patricia node. + */ + @Override + public MerklePatriciaNode getParent() { + return this.parent; + } + } diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java index 9fefec96..d43b4fe7 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java @@ -3,6 +3,12 @@ import model.crypto.Sha3256Hash; public abstract class MerklePatriciaNode { + private MerklePatriciaNode parent; + + /** + * Updates the hash of the merkle patricia node. + */ + public abstract void updateHash(); /** * Hash of this merkle patricia node. @@ -17,4 +23,11 @@ public abstract class MerklePatriciaNode { * @return type of this entity. */ public abstract String type(); + + /** + * Returns the parent of this merkle patricia node. + * + * @return the parent of this merkle patricia node. + */ + public abstract MerklePatriciaNode getParent(); } diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java index f2f3b906..b3cd2c8f 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -6,6 +6,7 @@ import model.Entity; import model.lightchain.Identifier; import modules.ads.AuthenticatedEntity; +import modules.ads.merkletree.MerkleProof; import modules.ads.merkletree.MerkleTree; public class MerklePatriciaTrie extends MerkleTree { @@ -27,14 +28,22 @@ public MerklePatriciaTrie(ReentrantReadWriteLock lock, MerklePatriciaBranchNode @Override public AuthenticatedEntity put(Entity e) { int idx; + MerklePatriciaNode currNode; MerklePatriciaBranchNode currBranchNode = root; + MerklePatriciaLeafNode currLeafNode = null; byte[] bytes = e.id().getBytes(); for (int i = 0; i < bytes.length - 1; i++) { idx = bytes[i] + 128; currBranchNode = currBranchNode.getChild(idx); } idx = bytes[bytes.length - 1] + 128; - currBranchNode.createLeaf(idx, e.id()); + currLeafNode = currBranchNode.createLeaf(idx, e.id()); + currNode = currLeafNode; + while (currNode.type() != MerklePatriciaNodeType.TYPE_ROOT) { + currNode.updateHash(); + currNode = currNode.getParent(); + } + currNode.updateHash(); return null; } @@ -46,6 +55,33 @@ public AuthenticatedEntity put(Entity e) { */ @Override public AuthenticatedEntity get(Identifier id) { + int idx; + MerklePatriciaBranchNode currBranchNode = root; + byte[] bytes = id.getBytes(); + for (int i = 0; i < bytes.length - 1; i++) { + idx = bytes[i] + 128; + currBranchNode = (MerklePatriciaBranchNode) currBranchNode.searchChild(idx); + if (currBranchNode == null) { + throw new IllegalArgumentException("identifier not found"); + } + } + idx = bytes[bytes.length - 1] + 128; + MerklePatriciaLeafNode currLeafNode = (MerklePatriciaLeafNode) currBranchNode.searchChild(idx); + if (currLeafNode == null) { + throw new IllegalArgumentException("identifier not found"); + } + return null; + } + + /** + * 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 if the identifier is null + */ + private MerkleProof getProof(Identifier id) throws IllegalArgumentException { return null; } diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrieProof.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrieProof.java new file mode 100644 index 00000000..fb898f18 --- /dev/null +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrieProof.java @@ -0,0 +1,30 @@ +package modules.ads.mtrie; + +import model.crypto.Sha3256Hash; +import modules.ads.MembershipProof; +import modules.ads.merkletree.MerklePath; + +public class MerklePatriciaTrieProof implements MembershipProof { + private Sha3256Hash root; + private MerklePath path; + + /** + * Root of the authenticated data structure that this proof belongs to. + * + * @return hash value of the root node. + */ + @Override + public Sha3256Hash getRoot() { + return null; + } + + /** + * Returns the merkle path of the proof of membership. + * + * @return merkle path of the proof of membership. + */ + @Override + public MerklePath getMerklePath() { + return null; + } +} From 5baa222c067c6eb33ecf74906c2d61dcb0ed63f0 Mon Sep 17 00:00:00 2001 From: Ozan Date: Sun, 8 May 2022 18:36:32 +0300 Subject: [PATCH 06/19] implements a more generic version of merkle patricia trie --- .../modules/ads/merkletree/MerkleNode.java | 54 +++++++ .../ads/mtrie/MerklePatriciaBranchNode.java | 116 --------------- .../ads/mtrie/MerklePatriciaLeafNode.java | 53 ------- .../modules/ads/mtrie/MerklePatriciaNode.java | 33 ----- .../ads/mtrie/MerklePatriciaNodeType.java | 8 -- .../modules/ads/mtrie/MerklePatriciaTrie.java | 132 +++++++++++------- .../ads/mtrie/MerklePatriciaTrieProof.java | 30 ---- 7 files changed, 133 insertions(+), 293 deletions(-) delete mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java delete mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java delete mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaNode.java delete mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaNodeType.java delete mode 100644 src/main/java/modules/ads/mtrie/MerklePatriciaTrieProof.java diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index a1b554d4..a5a67be9 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -41,6 +41,35 @@ public MerkleNode(Entity e, boolean isLeft) { this.hash = hasher.computeHash(e.id()); } + /** + * Constructor with parent and isLeft. + * + * @param parent the parent of the node + * @param isLeft boolean that specifies if the node is left child or not + */ + public MerkleNode(MerkleNode parent, boolean isLeft) { + this.left = null; + this.right = null; + this.parent = parent; + this.isLeft = isLeft; + this.hash = null; + } + + /** + * Constructor with parent, isLeft and hash. + * + * @param parent the parent of the node + * @param isLeft boolean that specifies if the node is left child or not + * @param hash input hash of the entity corresponding to that node + */ + public MerkleNode(MerkleNode parent, boolean isLeft, Sha3256Hash hash) { + this.left = null; + this.right = null; + this.parent = parent; + this.isLeft = isLeft; + this.hash = hash; + } + /** * Constructor with hash of the entity. * @@ -149,4 +178,29 @@ public MerkleNode getSibling() { return parent.getLeft(); } } + + /** + * Sets the left child of the node. + * + * @param left the left child of the node + */ + public void setLeftNode(MerkleNode left) { + this.left = left; + } + + /** + * Sets the left child of the node. + * + * @param right the right child of the node + */ + public void setRightNode(MerkleNode right) { + this.right = right; + } + + /** + * Updates the hash of the node. + */ + public void updateHash() { + this.hash = hasher.computeHash(left.getHash(), right.getHash()); + } } diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java deleted file mode 100644 index 285906ef..00000000 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaBranchNode.java +++ /dev/null @@ -1,116 +0,0 @@ -package modules.ads.mtrie; - -import crypto.Sha3256Hasher; -import model.crypto.Sha3256Hash; -import model.lightchain.Identifier; - -public class MerklePatriciaBranchNode extends MerklePatriciaNode { - private final MerklePatriciaNode[] children = new MerklePatriciaNode[256]; - private final Sha3256Hasher hasher = new Sha3256Hasher(); - private Sha3256Hash hash; - private MerklePatriciaNode parent; - - public MerklePatriciaBranchNode(MerklePatriciaNode parent) { - this.hash = new Sha3256Hash(new byte[32]); - this.parent = parent; - } - - /** - * Creates a child. - * - * @param index index of the child. - * @param child child to create. - * @return created child. - */ - public MerklePatriciaNode createChild(int index, MerklePatriciaNode child) { - children[index] = child; - return child; - } - - /** - * Creates a leaf node with the given identifier. - * - * @param idx index of the leaf node. - * @param id identifier of the leaf node. - * @return created leaf node. - */ - public MerklePatriciaLeafNode createLeaf(int idx, Identifier id) { - return (MerklePatriciaLeafNode) createChild(idx, new MerklePatriciaLeafNode(id, this)); - } - - /** - * Get the child node with the given index. If child does not exist, creates a child. - * - * @param idx index of the child node. - * @return child node with the given index. - */ - public MerklePatriciaBranchNode getChild(int idx) { - if (children[idx] == null) { - createChild(idx, new MerklePatriciaBranchNode(this)); - } - return (MerklePatriciaBranchNode) children[idx]; - } - - /** - * Get the child node with the given index. If child does not exist, returns null. - * - * @param idx index of the child node. - * @return child node with the given index. - */ - public MerklePatriciaNode searchChild(int idx) { - if (children[idx] == null) { - return null; - } - return (MerklePatriciaNode) children[idx]; - } - - - /** - * Updates the hash of the merkle patricia node. - */ - @Override - public void updateHash() { - Sha3256Hash[] hashes = new Sha3256Hash[256]; - for (int i = 0; i < 256; i++) { - if (children[i] != null) { - hashes[i] = children[i].getHash(); - } else { - hashes[i] = new Sha3256Hash(new byte[32]); - } - } - this.hash = hasher.computeHash(hashes); - } - - /** - * Hash of this merkle patricia node. - * - * @return hash of this merkle patricia node. - */ - @Override - public Sha3256Hash getHash() { - return this.hash; - } - - /** - * Type of this merkle patricia node. - * - * @return type of this entity. - */ - @Override - public String type() { - return MerklePatriciaNodeType.TYPE_BRANCH; - } - - /** - * Returns the parent of this merkle patricia node. - * - * @return the parent of this merkle patricia node. - */ - @Override - public MerklePatriciaNode getParent() { - return this.parent; - } - - public void setLeaf(int idx, Identifier id) { - } -} diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java deleted file mode 100644 index abde1150..00000000 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaLeafNode.java +++ /dev/null @@ -1,53 +0,0 @@ -package modules.ads.mtrie; - -import model.crypto.Sha3256Hash; -import model.lightchain.Identifier; - -public class MerklePatriciaLeafNode extends MerklePatriciaNode { - private MerklePatriciaNode parent; - private Sha3256Hash hash; - - public MerklePatriciaLeafNode(Identifier id, MerklePatriciaNode parent) { - this.hash = new Sha3256Hash(id); - this.parent = parent; - } - - /** - * Updates the hash of the merkle patricia node. - */ - @Override - public void updateHash() { - - } - - /** - * Hash of this merkle patricia node. - * - * @return hash of this merkle patricia node. - */ - @Override - public Sha3256Hash getHash() { - return this.hash; - } - - /** - * Type of this merkle patricia node. - * - * @return type of this entity. - */ - @Override - public String type() { - return MerklePatriciaNodeType.TYPE_LEAF; - } - - /** - * Returns the parent of this merkle patricia node. - * - * @return the parent of this merkle patricia node. - */ - @Override - public MerklePatriciaNode getParent() { - return this.parent; - } - -} diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java b/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java deleted file mode 100644 index d43b4fe7..00000000 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaNode.java +++ /dev/null @@ -1,33 +0,0 @@ -package modules.ads.mtrie; - -import model.crypto.Sha3256Hash; - -public abstract class MerklePatriciaNode { - private MerklePatriciaNode parent; - - /** - * Updates the hash of the merkle patricia node. - */ - public abstract void updateHash(); - - /** - * Hash of this merkle patricia node. - * - * @return hash of this merkle patricia node. - */ - public abstract Sha3256Hash getHash(); - - /** - * Type of this merkle patricia node. - * - * @return type of this entity. - */ - public abstract String type(); - - /** - * Returns the parent of this merkle patricia node. - * - * @return the parent of this merkle patricia node. - */ - public abstract MerklePatriciaNode getParent(); -} diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaNodeType.java b/src/main/java/modules/ads/mtrie/MerklePatriciaNodeType.java deleted file mode 100644 index 65f3c68c..00000000 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaNodeType.java +++ /dev/null @@ -1,8 +0,0 @@ -package modules.ads.mtrie; - -public class MerklePatriciaNodeType { - public static final String TYPE_ROOT = "type-merkle-patricia-trie-root"; - public static final String TYPE_BRANCH = "type-merkle-patricia-trie-branch"; - public static final String TYPE_LEAF = "type-merkle-patricia-trie-leaf"; - public static final String TYPE_EMPTY = "type-merkle-patricia-trie-empty"; -} diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java index b3cd2c8f..1756b51a 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -1,97 +1,123 @@ package modules.ads.mtrie; +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; import model.lightchain.Identifier; import modules.ads.AuthenticatedEntity; -import modules.ads.merkletree.MerkleProof; -import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.*; public class MerklePatriciaTrie extends MerkleTree { private static final Sha3256Hasher hasher = new Sha3256Hasher(); - private final ReentrantReadWriteLock lock; - private final MerklePatriciaBranchNode root; + private int size; + private MerkleNode root; + private final Map entityHashTable; - public MerklePatriciaTrie(ReentrantReadWriteLock lock, MerklePatriciaBranchNode root) { - this.lock = lock; - this.root = root; + /** + * Default constructor for a Merkle Tree. + */ + public MerklePatriciaTrie() { + this.size = 0; + this.root = new MerkleNode(); + this.entityHashTable = new HashMap<>(); } /** - * Adds an entity to the ADS. + * Adds an entity to the merkle tree. * * @param e the entity to add + * * @return AuthenticatedEntity containing the entity and its membership proof */ @Override - public AuthenticatedEntity put(Entity e) { - int idx; - MerklePatriciaNode currNode; - MerklePatriciaBranchNode currBranchNode = root; - MerklePatriciaLeafNode currLeafNode = null; - byte[] bytes = e.id().getBytes(); - for (int i = 0; i < bytes.length - 1; i++) { - idx = bytes[i] + 128; - currBranchNode = currBranchNode.getChild(idx); + public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { + ArrayList pathList = new ArrayList<>(); + ArrayList isLeftNode = new ArrayList<>();; + MerkleNode currNode = root; + Identifier id = e.id(); + byte[] bytes = id.getBytes(); + String path = ""; + for (byte b : bytes) { + path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); + } + for (int i = 0; i < path.length() - 1; i++) { + if (path.charAt(i) == '1') { + if (currNode.getLeft() == null) { + currNode.setLeftNode(new MerkleNode(currNode, true)); + } + currNode = currNode.getLeft(); + isLeftNode.add(true); + } else { + if (currNode.getRight() == null) { + currNode.setRightNode(new MerkleNode(currNode, false)); + } + currNode = currNode.getRight(); + isLeftNode.add(false); + } + pathList.add(currNode.getSibling().getHash()); } - idx = bytes[bytes.length - 1] + 128; - currLeafNode = currBranchNode.createLeaf(idx, e.id()); - currNode = currLeafNode; - while (currNode.type() != MerklePatriciaNodeType.TYPE_ROOT) { - currNode.updateHash(); - currNode = currNode.getParent(); + if (path.charAt(path.length() - 1) == '1') { + if (currNode.getLeft() == null) { + currNode.setLeftNode(new MerkleNode(currNode, true, new Sha3256Hash(id))); + } + } else { + if (currNode.getRight() == null) { + currNode.setRightNode(new MerkleNode(currNode, false, new Sha3256Hash(id))); + } } - currNode.updateHash(); - return null; + MerklePath merklePath = new MerklePath(pathList, isLeftNode); + MerkleProof proof = new MerkleProof(root.getHash(), merklePath); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), 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 */ @Override - public AuthenticatedEntity get(Identifier id) { - int idx; - MerklePatriciaBranchNode currBranchNode = root; + public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { + ArrayList pathList = new ArrayList<>(); + ArrayList isLeftNode = new ArrayList<>();; + MerkleNode currNode = root; byte[] bytes = id.getBytes(); - for (int i = 0; i < bytes.length - 1; i++) { - idx = bytes[i] + 128; - currBranchNode = (MerklePatriciaBranchNode) currBranchNode.searchChild(idx); - if (currBranchNode == null) { + String path = ""; + for (byte b : bytes) { + path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); + } + for (int i = 0; i < path.length(); i++) { + if (path.charAt(i) == '1') { + currNode = currNode.getLeft(); + isLeftNode.add(true); + } else { + currNode = currNode.getRight(); + isLeftNode.add(false); + } + if (currNode == null) { throw new IllegalArgumentException("identifier not found"); } + pathList.add(currNode.getSibling().getHash()); } - idx = bytes[bytes.length - 1] + 128; - MerklePatriciaLeafNode currLeafNode = (MerklePatriciaLeafNode) currBranchNode.searchChild(idx); - if (currLeafNode == null) { - throw new IllegalArgumentException("identifier not found"); - } - return null; - } - - /** - * 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 if the identifier is null - */ - private MerkleProof getProof(Identifier id) throws IllegalArgumentException { - return null; + Entity e = entityHashTable.get(id); + MerklePath merklePath = new MerklePath(pathList, isLeftNode); + MerkleProof proof = new MerkleProof(root.getHash(), merklePath); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } /** - * Returns the size of the ADS. + * Returns the size of the merkle tree. * - * @return the size of the ADS + * @return the size of the merkle tree */ @Override public int size() { - return 0; + return super.size(); } } diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrieProof.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrieProof.java deleted file mode 100644 index fb898f18..00000000 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrieProof.java +++ /dev/null @@ -1,30 +0,0 @@ -package modules.ads.mtrie; - -import model.crypto.Sha3256Hash; -import modules.ads.MembershipProof; -import modules.ads.merkletree.MerklePath; - -public class MerklePatriciaTrieProof implements MembershipProof { - private Sha3256Hash root; - private MerklePath path; - - /** - * Root of the authenticated data structure that this proof belongs to. - * - * @return hash value of the root node. - */ - @Override - public Sha3256Hash getRoot() { - return null; - } - - /** - * Returns the merkle path of the proof of membership. - * - * @return merkle path of the proof of membership. - */ - @Override - public MerklePath getMerklePath() { - return null; - } -} From 8cfa1fd3820c2f43842fc3d211bef2c3d96e63fc Mon Sep 17 00:00:00 2001 From: Ozan Date: Sun, 8 May 2022 18:42:39 +0300 Subject: [PATCH 07/19] adds hash updates for nodes --- .../java/modules/ads/merkletree/MerkleNode.java | 4 ++-- .../modules/ads/mtrie/MerklePatriciaTrie.java | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index a5a67be9..3925a390 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -24,7 +24,7 @@ public MerkleNode() { this.right = null; this.parent = null; this.isLeft = false; - this.hash = null; + this.hash = new Sha3256Hash(new byte[32]); } /** @@ -52,7 +52,7 @@ public MerkleNode(MerkleNode parent, boolean isLeft) { this.right = null; this.parent = parent; this.isLeft = isLeft; - this.hash = null; + this.hash = new Sha3256Hash(new byte[32]); } /** diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java index 1756b51a..710cdb1d 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; import crypto.Sha3256Hasher; import model.Entity; @@ -36,8 +35,6 @@ public MerklePatriciaTrie() { */ @Override public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { - ArrayList pathList = new ArrayList<>(); - ArrayList isLeftNode = new ArrayList<>();; MerkleNode currNode = root; Identifier id = e.id(); byte[] bytes = id.getBytes(); @@ -51,15 +48,12 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { currNode.setLeftNode(new MerkleNode(currNode, true)); } currNode = currNode.getLeft(); - isLeftNode.add(true); } else { if (currNode.getRight() == null) { currNode.setRightNode(new MerkleNode(currNode, false)); } currNode = currNode.getRight(); - isLeftNode.add(false); } - pathList.add(currNode.getSibling().getHash()); } if (path.charAt(path.length() - 1) == '1') { if (currNode.getLeft() == null) { @@ -70,9 +64,11 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { currNode.setRightNode(new MerkleNode(currNode, false, new Sha3256Hash(id))); } } - MerklePath merklePath = new MerklePath(pathList, isLeftNode); - MerkleProof proof = new MerkleProof(root.getHash(), merklePath); - return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); + for (int i = 0; i < path.length() - 1; i++) { + currNode.updateHash(); + currNode = currNode.getParent(); + } + return get(id); } /** @@ -85,7 +81,7 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { @Override public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { ArrayList pathList = new ArrayList<>(); - ArrayList isLeftNode = new ArrayList<>();; + ArrayList isLeftNode = new ArrayList<>(); MerkleNode currNode = root; byte[] bytes = id.getBytes(); String path = ""; From 8aa3f1884df5fa140a1cae4da771b2dac1475f8b Mon Sep 17 00:00:00 2001 From: Ozan Date: Sun, 8 May 2022 18:54:45 +0300 Subject: [PATCH 08/19] adds fixture and a basic test --- .../modules/ads/merkletree/MerkleNode.java | 4 ++- .../modules/ads/mtrie/MerklePatriciaTrie.java | 8 +++-- .../modules/ads/MerklePatriciaTrieTest.java | 30 +++++++++++++++++++ .../fixtures/MerklePatriciaTrieFixture.java | 20 +++++++++++++ .../unittest/fixtures/MerkleTreeFixture.java | 2 +- 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/test/java/modules/ads/MerklePatriciaTrieTest.java create mode 100644 src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 3925a390..62ece692 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -201,6 +201,8 @@ public void setRightNode(MerkleNode right) { * Updates the hash of the node. */ public void updateHash() { - this.hash = hasher.computeHash(left.getHash(), right.getHash()); + Sha3256Hash leftHash = left == null ? new Sha3256Hash(new byte[32]) : left.getHash(); + Sha3256Hash rightHash = right == null ? new Sha3256Hash(new byte[32]) : right.getHash(); + this.hash = hasher.computeHash(leftHash, rightHash); } } diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java index 710cdb1d..d08df75f 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -58,10 +58,14 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { if (path.charAt(path.length() - 1) == '1') { if (currNode.getLeft() == null) { currNode.setLeftNode(new MerkleNode(currNode, true, new Sha3256Hash(id))); + entityHashTable.put(id, e); + size++; } } else { if (currNode.getRight() == null) { currNode.setRightNode(new MerkleNode(currNode, false, new Sha3256Hash(id))); + entityHashTable.put(id, e); + size++; } } for (int i = 0; i < path.length() - 1; i++) { @@ -99,7 +103,7 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { if (currNode == null) { throw new IllegalArgumentException("identifier not found"); } - pathList.add(currNode.getSibling().getHash()); + pathList.add(currNode.getSibling() == null ? new Sha3256Hash(new byte[32]) : currNode.getSibling().getHash()); } Entity e = entityHashTable.get(id); MerklePath merklePath = new MerklePath(pathList, isLeftNode); @@ -114,6 +118,6 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { */ @Override public int size() { - return super.size(); + return size; } } diff --git a/src/test/java/modules/ads/MerklePatriciaTrieTest.java b/src/test/java/modules/ads/MerklePatriciaTrieTest.java new file mode 100644 index 00000000..24a3c56f --- /dev/null +++ b/src/test/java/modules/ads/MerklePatriciaTrieTest.java @@ -0,0 +1,30 @@ +package modules.ads; + +import model.Entity; +import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; +import modules.ads.mtrie.MerklePatriciaTrie; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import unittest.fixtures.EntityFixture; +import unittest.fixtures.MerklePatriciaTrieFixture; +import unittest.fixtures.MerkleTreeFixture; + +public class MerklePatriciaTrieTest { + /** + * A basic test for one sequential put and get operations. + */ + @Test + public void testVerification() { + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + + Entity entity = new EntityFixture(); + merklePatriciaTrie.put(entity); + Assertions.assertEquals(merklePatriciaTrie.size(), 6); + + AuthenticatedEntity authenticatedEntity = merklePatriciaTrie.get(entity.id()); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + Assertions.assertTrue(verifier.verify(authenticatedEntity)); + } +} diff --git a/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java b/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java new file mode 100644 index 00000000..4ec19081 --- /dev/null +++ b/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java @@ -0,0 +1,20 @@ +package unittest.fixtures; + +import modules.ads.merkletree.MerkleTree; +import modules.ads.mtrie.MerklePatriciaTrie; + +public class MerklePatriciaTrieFixture { + /** + * Creates a new merkle patricia trie with n random elements. + * + * @param n number of elements to create + * @return a new merkle tree with n random elements. + */ + public static MerklePatriciaTrie createMerklePatriciaTree(int n) { + MerklePatriciaTrie merklePatriciaTrie = new MerklePatriciaTrie(); + for (int i = 0; i < n; i++) { + merklePatriciaTrie.put(new EntityFixture()); + } + return merklePatriciaTrie; + } +} diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java index ebf0347c..5210324f 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -7,7 +7,7 @@ */ public class MerkleTreeFixture { /** - * Creates a new skip list with n random elements. + * Creates a new merkle tree with n random elements. * * @param n number of elements to create * @return a new merkle tree with n random elements. From 19dfe5636a8bac1675b4ff3686179d9ebdaefb53 Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Mon, 9 May 2022 00:06:21 +0300 Subject: [PATCH 09/19] implements fully functioning binary merkle patricia trie --- .../modules/ads/merkletree/MerkleNode.java | 2 ++ .../modules/ads/mtrie/MerklePatriciaTrie.java | 20 +++++++++++-------- .../modules/ads/MerklePatriciaTrieTest.java | 6 +++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 62ece692..059ed1b1 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -1,5 +1,7 @@ package modules.ads.merkletree; +import java.util.Arrays; + import crypto.Sha3256Hasher; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.Entity; diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java index d08df75f..a1e624ff 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -1,8 +1,6 @@ package modules.ads.mtrie; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import crypto.Sha3256Hasher; import model.Entity; @@ -43,7 +41,7 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); } for (int i = 0; i < path.length() - 1; i++) { - if (path.charAt(i) == '1') { + if (path.charAt(i) == '0') { if (currNode.getLeft() == null) { currNode.setLeftNode(new MerkleNode(currNode, true)); } @@ -55,15 +53,18 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { currNode = currNode.getRight(); } } - if (path.charAt(path.length() - 1) == '1') { + MerkleNode newNode = null; + if (path.charAt(path.length() - 1) == '0') { if (currNode.getLeft() == null) { - currNode.setLeftNode(new MerkleNode(currNode, true, new Sha3256Hash(id))); + newNode = new MerkleNode(currNode, true, hasher.computeHash(id)); + currNode.setLeftNode(newNode); entityHashTable.put(id, e); size++; } } else { if (currNode.getRight() == null) { - currNode.setRightNode(new MerkleNode(currNode, false, new Sha3256Hash(id))); + newNode = new MerkleNode(currNode, false, hasher.computeHash(id)); + currNode.setRightNode(newNode); entityHashTable.put(id, e); size++; } @@ -72,6 +73,7 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { currNode.updateHash(); currNode = currNode.getParent(); } + currNode.updateHash(); return get(id); } @@ -93,7 +95,7 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); } for (int i = 0; i < path.length(); i++) { - if (path.charAt(i) == '1') { + if (path.charAt(i) == '0') { currNode = currNode.getLeft(); isLeftNode.add(true); } else { @@ -105,6 +107,8 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { } pathList.add(currNode.getSibling() == null ? new Sha3256Hash(new byte[32]) : currNode.getSibling().getHash()); } + Collections.reverse(pathList); + Collections.reverse(isLeftNode); Entity e = entityHashTable.get(id); MerklePath merklePath = new MerklePath(pathList, isLeftNode); MerkleProof proof = new MerkleProof(root.getHash(), merklePath); diff --git a/src/test/java/modules/ads/MerklePatriciaTrieTest.java b/src/test/java/modules/ads/MerklePatriciaTrieTest.java index 24a3c56f..3b4de2d7 100644 --- a/src/test/java/modules/ads/MerklePatriciaTrieTest.java +++ b/src/test/java/modules/ads/MerklePatriciaTrieTest.java @@ -16,12 +16,12 @@ public class MerklePatriciaTrieTest { */ @Test public void testVerification() { - MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); - Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(0); + Assertions.assertEquals(merklePatriciaTrie.size(), 0); // fixture sanity check. Entity entity = new EntityFixture(); merklePatriciaTrie.put(entity); - Assertions.assertEquals(merklePatriciaTrie.size(), 6); + Assertions.assertEquals(merklePatriciaTrie.size(), 1); AuthenticatedEntity authenticatedEntity = merklePatriciaTrie.get(entity.id()); MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); From d0fb0c7d11d98d0989c13999a03b7c4a78f531ae Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Mon, 9 May 2022 00:31:07 +0300 Subject: [PATCH 10/19] adds tests --- src/main/java/crypto/Sha3256Hasher.java | 17 +- .../modules/ads/merkletree/MerkleNode.java | 6 +- .../modules/ads/merkletree/MerkleTree.java | 2 +- .../modules/ads/mtrie/MerklePatriciaTrie.java | 157 ++++++----- .../modules/ads/MerklePatriciaTrieTest.java | 253 +++++++++++++++++- .../fixtures/MerklePatriciaTrieFixture.java | 4 +- 6 files changed, 355 insertions(+), 84 deletions(-) diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 42914c2a..fca54299 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -58,7 +58,7 @@ public Sha3256Hash computeHash(byte[] bytes) { } /** - * Commutative hashing of two given byte arrays. + * Hashing of two given byte arrays. * * @param b1 first byte array. * @param b2 second byte array. @@ -73,15 +73,14 @@ public Sha3256Hash computeHash(byte[] b1, byte[] b2) { } } + /** + * Hashing of two SHA3-256 hash objects. + * + * @param h1 first SHA3-256 hash object. + * @param h2 second SHA3-256 hash object. + * @return SHA3-256 hash object of the concatenation of the two SHA3-256 hash objects. + */ public Sha3256Hash computeHash(Sha3256Hash h1, Sha3256Hash h2) { return computeHash(h1.getBytes(), h2.getBytes()); } - - public Sha3256Hash computeHash(Sha3256Hash[] hashes) { - Sha3256Hash hash = computeHash(hashes[0].getBytes(), hashes[1].getBytes()); - for (int i = 2; i < hashes.length; i++) { - hash = computeHash(hash.getBytes(), hashes[i].getBytes()); - } - return hash; - } } diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 059ed1b1..09597cfa 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -1,7 +1,5 @@ package modules.ads.merkletree; -import java.util.Arrays; - import crypto.Sha3256Hasher; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.Entity; @@ -49,6 +47,7 @@ public MerkleNode(Entity e, boolean isLeft) { * @param parent the parent of the node * @param isLeft boolean that specifies if the node is left child or not */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") public MerkleNode(MerkleNode parent, boolean isLeft) { this.left = null; this.right = null; @@ -64,6 +63,7 @@ public MerkleNode(MerkleNode parent, boolean isLeft) { * @param isLeft boolean that specifies if the node is left child or not * @param hash input hash of the entity corresponding to that node */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") public MerkleNode(MerkleNode parent, boolean isLeft, Sha3256Hash hash) { this.left = null; this.right = null; @@ -186,6 +186,7 @@ public MerkleNode getSibling() { * * @param left the left child of the node */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "left is intentionally mutable externally") public void setLeftNode(MerkleNode left) { this.left = left; } @@ -195,6 +196,7 @@ public void setLeftNode(MerkleNode left) { * * @param right the right child of the node */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "right is intentionally mutable externally") public void setRightNode(MerkleNode right) { this.right = right; } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index f35d0fae..496b8480 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -12,7 +12,7 @@ import modules.ads.AuthenticatedDataStructure; /** - * Implementation of an in-memory Authenticated Skip List + * Implementation of an in-memory Merkle Tree * that is capable of storing and retrieval of LightChain entities. */ public class MerkleTree implements AuthenticatedDataStructure { diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java index a1e624ff..0c557b83 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -1,6 +1,10 @@ package modules.ads.mtrie; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; import crypto.Sha3256Hasher; import model.Entity; @@ -9,116 +13,137 @@ import modules.ads.AuthenticatedEntity; import modules.ads.merkletree.*; +/** + * Implementation of an in-memory Merkle Patricia Tree + * that is capable of storing and retrieval of LightChain entities. + */ public class MerklePatriciaTrie extends MerkleTree { private static final Sha3256Hasher hasher = new Sha3256Hasher(); - private int size; - private MerkleNode root; private final Map entityHashTable; + private final ReentrantReadWriteLock lock; + private final MerkleNode root; + private int size; /** - * Default constructor for a Merkle Tree. + * Default constructor for a Merkle Patricia Trie. */ public MerklePatriciaTrie() { this.size = 0; this.root = new MerkleNode(); this.entityHashTable = new HashMap<>(); + this.lock = new ReentrantReadWriteLock(); } /** - * Adds an entity to the merkle tree. + * Adds an entity to the merkle patricia tree. * * @param e the entity to add - * * @return AuthenticatedEntity containing the entity and its membership proof */ @Override public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { - MerkleNode currNode = root; - Identifier id = e.id(); - byte[] bytes = id.getBytes(); - String path = ""; - for (byte b : bytes) { - path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); - } - for (int i = 0; i < path.length() - 1; i++) { - if (path.charAt(i) == '0') { + try { + lock.writeLock().lock(); + if (e == null) { + throw new IllegalArgumentException("entity cannot be null"); + } + MerkleNode currNode = root; + Identifier id = e.id(); + byte[] bytes = id.getBytes(); + String path = ""; + for (byte b : bytes) { + path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); + } + for (int i = 0; i < path.length() - 1; i++) { + if (path.charAt(i) == '0') { + if (currNode.getLeft() == null) { + currNode.setLeftNode(new MerkleNode(currNode, true)); + } + currNode = currNode.getLeft(); + } else { + if (currNode.getRight() == null) { + currNode.setRightNode(new MerkleNode(currNode, false)); + } + currNode = currNode.getRight(); + } + } + MerkleNode newNode = null; + if (path.charAt(path.length() - 1) == '0') { if (currNode.getLeft() == null) { - currNode.setLeftNode(new MerkleNode(currNode, true)); + newNode = new MerkleNode(currNode, true, hasher.computeHash(id)); + currNode.setLeftNode(newNode); + entityHashTable.put(id, e); + size++; } - currNode = currNode.getLeft(); } else { if (currNode.getRight() == null) { - currNode.setRightNode(new MerkleNode(currNode, false)); + newNode = new MerkleNode(currNode, false, hasher.computeHash(id)); + currNode.setRightNode(newNode); + entityHashTable.put(id, e); + size++; } - currNode = currNode.getRight(); - } - } - MerkleNode newNode = null; - if (path.charAt(path.length() - 1) == '0') { - if (currNode.getLeft() == null) { - newNode = new MerkleNode(currNode, true, hasher.computeHash(id)); - currNode.setLeftNode(newNode); - entityHashTable.put(id, e); - size++; } - } else { - if (currNode.getRight() == null) { - newNode = new MerkleNode(currNode, false, hasher.computeHash(id)); - currNode.setRightNode(newNode); - entityHashTable.put(id, e); - size++; + for (int i = 0; i < path.length() - 1; i++) { + currNode.updateHash(); + currNode = currNode.getParent(); } - } - for (int i = 0; i < path.length() - 1; i++) { currNode.updateHash(); - currNode = currNode.getParent(); + return get(id); + } finally { + lock.writeLock().unlock(); } - currNode.updateHash(); - return get(id); } /** * 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 AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { - ArrayList pathList = new ArrayList<>(); - ArrayList isLeftNode = new ArrayList<>(); - MerkleNode currNode = root; - byte[] bytes = id.getBytes(); - String path = ""; - for (byte b : bytes) { - path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); - } - for (int i = 0; i < path.length(); i++) { - if (path.charAt(i) == '0') { - currNode = currNode.getLeft(); - isLeftNode.add(true); - } else { - currNode = currNode.getRight(); - isLeftNode.add(false); + try { + lock.readLock().lock(); + if (id == null) { + throw new IllegalArgumentException("identifier cannot be null"); } - if (currNode == null) { - throw new IllegalArgumentException("identifier not found"); + + ArrayList pathList = new ArrayList<>(); + ArrayList isLeftNode = new ArrayList<>(); + MerkleNode currNode = root; + byte[] bytes = id.getBytes(); + String path = ""; + for (byte b : bytes) { + path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); + } + for (int i = 0; i < path.length(); i++) { + if (path.charAt(i) == '0') { + currNode = currNode.getLeft(); + isLeftNode.add(true); + } else { + currNode = currNode.getRight(); + isLeftNode.add(false); + } + if (currNode == null) { + throw new IllegalArgumentException("identifier not found"); + } + pathList.add(currNode.getSibling() == null ? new Sha3256Hash(new byte[32]) : currNode.getSibling().getHash()); } - pathList.add(currNode.getSibling() == null ? new Sha3256Hash(new byte[32]) : currNode.getSibling().getHash()); + Collections.reverse(pathList); + Collections.reverse(isLeftNode); + Entity e = entityHashTable.get(id); + MerklePath merklePath = new MerklePath(pathList, isLeftNode); + MerkleProof proof = new MerkleProof(root.getHash(), merklePath); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); + } finally { + lock.readLock().unlock(); } - Collections.reverse(pathList); - Collections.reverse(isLeftNode); - Entity e = entityHashTable.get(id); - MerklePath merklePath = new MerklePath(pathList, isLeftNode); - MerkleProof proof = new MerkleProof(root.getHash(), merklePath); - return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } /** - * Returns the size of the merkle tree. + * Returns the size of the merkle patricia trie. * - * @return the size of the merkle tree + * @return the size of the merkle patricia trie */ @Override public int size() { diff --git a/src/test/java/modules/ads/MerklePatriciaTrieTest.java b/src/test/java/modules/ads/MerklePatriciaTrieTest.java index 3b4de2d7..4c471a76 100644 --- a/src/test/java/modules/ads/MerklePatriciaTrieTest.java +++ b/src/test/java/modules/ads/MerklePatriciaTrieTest.java @@ -1,30 +1,273 @@ 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 modules.ads.merkletree.MerkleTree; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; +import modules.ads.merkletree.MerklePath; +import modules.ads.merkletree.MerkleProof; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; import modules.ads.mtrie.MerklePatriciaTrie; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import unittest.fixtures.EntityFixture; import unittest.fixtures.MerklePatriciaTrieFixture; -import unittest.fixtures.MerkleTreeFixture; +import unittest.fixtures.Sha3256HashFixture; +/** + * Encapsulates tests for an authenticated and concurrent implementation of MerklePatriciaTree ADS. + */ public class MerklePatriciaTrieTest { + /** * A basic test for one sequential put and get operations. */ @Test public void testVerification() { - MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(0); - Assertions.assertEquals(merklePatriciaTrie.size(), 0); // fixture sanity check. + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. Entity entity = new EntityFixture(); merklePatriciaTrie.put(entity); - Assertions.assertEquals(merklePatriciaTrie.size(), 1); + Assertions.assertEquals(merklePatriciaTrie.size(), 6); AuthenticatedEntity authenticatedEntity = merklePatriciaTrie.get(entity.id()); MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); } + + /** + * Tests both putting and getting the same entity gives same proof + * and putting another entity gives different proofs. + */ + @Test + public void testPutGetSameProof() { + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + Entity e1 = new EntityFixture(); + + // putting e1 + AuthenticatedEntity authenticatedEntityPut = merklePatriciaTrie.put(e1); + MembershipProof proofPutE1 = authenticatedEntityPut.getMembershipProof(); + Assertions.assertEquals(merklePatriciaTrie.size(), 6); + + // getting e1 + AuthenticatedEntity authE1Get = merklePatriciaTrie.get(e1.id()); + MembershipProof proofGetE1 = authE1Get.getMembershipProof(); + + // putting e2 + Entity e2 = new EntityFixture(); + AuthenticatedEntity authE2Put = merklePatriciaTrie.put(e2); + Assertions.assertEquals(merklePatriciaTrie.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); + } + + /** + * Tests putting an existing entity does not change the proof. + */ + @Test + public void testPutExistingEntity() { + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); + + // first time put + AuthenticatedEntity authenticatedEntityPut = merklePatriciaTrie.put(entity); + MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); + Assertions.assertEquals(merklePatriciaTrie.size(), 6); + + // second attempt + AuthenticatedEntity authenticatedEntityPutAgain = merklePatriciaTrie.put(entity); + MembershipProof proofPutAgain = authenticatedEntityPutAgain.getMembershipProof(); + + // proofs must be equal. + Assertions.assertEquals(proofPut, proofPutAgain); + Assertions.assertEquals(merklePatriciaTrie.size(), 6); // duplicate entity should not change the size. + } + + /** + * 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 putDone = new CountDownLatch(concurrencyDegree); + CountDownLatch getDone = new CountDownLatch(concurrencyDegree); + + Thread[] putThreads = new Thread[concurrencyDegree]; + Thread[] getThreads = new Thread[concurrencyDegree]; + + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(0); + Assertions.assertEquals(merklePatriciaTrie.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 { + merklePatriciaTrie.put(entity); + putDone.countDown(); + } catch (Exception e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : putThreads) { + t.start(); + } + try { + 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(() -> { + try { + AuthenticatedEntity authenticatedEntity = merklePatriciaTrie.get(id); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + if (!verifier.verify(authenticatedEntity)) { + threadError.getAndIncrement(); + } + getDone.countDown(); + } catch (Exception e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : getThreads) { + t.start(); + } + try { + boolean doneOneTimeGet = getDone.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTimeGet); + } catch (InterruptedException e) { + Assertions.fail(); + } + Assertions.assertEquals(0, threadError.get()); + Assertions.assertEquals(concurrencyDegree, merklePatriciaTrie.size()); + } + + /** + * Tests getting an entity that does not exist in the merkle patricia trie throws IllegalArgumentException. + */ + @Test + public void testGetNonExistingEntity() { + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + merklePatriciaTrie.get(entity.id()); + }); + } + + /** + * Tests inserting null throws IllegalArgumentException. + */ + @Test + public void testNullInsertion() { + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + Assertions.assertThrows(IllegalArgumentException.class, () -> { + merklePatriciaTrie.put(null); + }); + } + + /** + * Tests the proof verification fails when root is changed. + */ + @Test + public void testManipulatedRoot() { + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merklePatriciaTrie.put(entity); + MembershipProof proof = authenticatedEntity.getMembershipProof(); + + // creates a tampered proof with random root. + MerkleProof tamperedProof = new MerkleProof(new Sha3256Hash(new byte[32]), proof.getMerklePath()); + AuthenticatedEntity tamperedAuthenticatedEntity = new MerkleTreeAuthenticatedEntity( + tamperedProof, + authenticatedEntity.type(), + authenticatedEntity.getEntity()); + + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + // authenticated entity must be verified. + Assertions.assertTrue(verifier.verify(authenticatedEntity)); + // tampered authenticated entity must be failed. + Assertions.assertFalse(verifier.verify(tamperedAuthenticatedEntity)); + } + + /** + * Tests the proof verification fails when entity is changed. + */ + @Test + public void testManipulatedEntity() { + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merklePatriciaTrie.put(entity); + + AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( + (MerkleProof) authenticatedEntity.getMembershipProof(), + authenticatedEntity.type(), + new EntityFixture()); + + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. + Assertions.assertFalse(verifier.verify(tamperedEntity)); // tampered entity fails verification. + } + + /** + * Tests the proof fails verification when proof part of authenticated entity is changed. + */ + @Test + public void testManipulatedProof() { + MerklePatriciaTrie merklePatriciaTrie = MerklePatriciaTrieFixture.createMerklePatriciaTree(5); + Assertions.assertEquals(merklePatriciaTrie.size(), 5); // fixture sanity check. + + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merklePatriciaTrie.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(proof.getRoot(), manipulatedMerklePath), + 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. + } } diff --git a/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java b/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java index 4ec19081..5061087a 100644 --- a/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java +++ b/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java @@ -1,8 +1,10 @@ package unittest.fixtures; -import modules.ads.merkletree.MerkleTree; import modules.ads.mtrie.MerklePatriciaTrie; +/** + * Creates a new randomly looking MerklePatriciaTrie. + */ public class MerklePatriciaTrieFixture { /** * Creates a new merkle patricia trie with n random elements. From cd1f74f489a9c70be5bf665b2c515b3cdb328e07 Mon Sep 17 00:00:00 2001 From: Yahya Date: Wed, 20 Sep 2023 07:49:13 -0700 Subject: [PATCH 11/19] adds get bits and get identifier from bitstring --- .../java/model/lightchain/Identifier.java | 33 +++++++++++++ .../modules/ads/merkletree/MerkleNode.java | 24 +++++----- .../modules/ads/merkletree/MerkleTree.java | 8 ++-- .../modules/ads/mtrie/MerklePatriciaTrie.java | 46 +++++++++---------- 4 files changed, 72 insertions(+), 39 deletions(-) diff --git a/src/main/java/model/lightchain/Identifier.java b/src/main/java/model/lightchain/Identifier.java index aba739d0..7b860bab 100644 --- a/src/main/java/model/lightchain/Identifier.java +++ b/src/main/java/model/lightchain/Identifier.java @@ -91,6 +91,19 @@ public byte[] getBytes() { return this.value.clone(); } + /** + * Returns the bit representation of the identifier. + * + * @return the bit representation of the identifier as a string. + */ + public String getBitString() { + StringBuilder bits = new StringBuilder(); + for (byte b : this.value) { + bits.append(String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')); + } + return bits.toString(); + } + /** * Returns string representation of identifier in Base58BTC. * @@ -111,4 +124,24 @@ public int comparedTo(Identifier other) { int result = Arrays.compare(this.value, other.value); return Integer.compare(result, 0); } + + /** + * Converts a bit string to an identifier. + * + * @param bitString a bit string of length 256. + * @return an identifier. + */ + public static Identifier BitStringToIdentifier(String bitString) { + if(bitString.length() != 8 * Size) { + throw new IllegalArgumentException("Bit string must be 256 bits long"); + } + + byte[] bytes = new byte[Size]; + for(int i = 0; i < Size; i++) { + String byteString = bitString.substring(i * 8, (i + 1) * 8); + bytes[i] = (byte) Integer.parseInt(byteString, 2); + } + + return new Identifier(bytes); + } } diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 09597cfa..e0ffb9a3 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -14,7 +14,7 @@ public class MerkleNode { private MerkleNode right; private MerkleNode parent; private boolean isLeft; - private Sha3256Hash hash; + private Sha3256Hash rootHash; /** * Default constructor. @@ -24,7 +24,7 @@ public MerkleNode() { this.right = null; this.parent = null; this.isLeft = false; - this.hash = new Sha3256Hash(new byte[32]); + this.rootHash = new Sha3256Hash(new byte[32]); } /** @@ -38,7 +38,7 @@ public MerkleNode(Entity e, boolean isLeft) { this.right = null; this.parent = null; this.isLeft = isLeft; - this.hash = hasher.computeHash(e.id()); + this.rootHash = hasher.computeHash(e.id()); } /** @@ -53,7 +53,7 @@ public MerkleNode(MerkleNode parent, boolean isLeft) { this.right = null; this.parent = parent; this.isLeft = isLeft; - this.hash = new Sha3256Hash(new byte[32]); + this.rootHash = new Sha3256Hash(new byte[32]); } /** @@ -69,7 +69,7 @@ public MerkleNode(MerkleNode parent, boolean isLeft, Sha3256Hash hash) { this.right = null; this.parent = parent; this.isLeft = isLeft; - this.hash = hash; + this.rootHash = hash; } /** @@ -82,7 +82,7 @@ public MerkleNode(Sha3256Hash hash) { this.right = null; this.parent = null; this.isLeft = false; - this.hash = hash; + this.rootHash = hash; } /** @@ -98,7 +98,7 @@ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.right = right; this.parent = null; this.isLeft = false; - this.hash = hash; + this.rootHash = hash; } /** @@ -146,8 +146,8 @@ public void setParent(MerkleNode parent) { * * @return the hash corresponding to the node */ - public Sha3256Hash getHash() { - return hash; + public Sha3256Hash getRootHash() { + return rootHash; } /** @@ -205,8 +205,8 @@ public void setRightNode(MerkleNode right) { * Updates the hash of the node. */ public void updateHash() { - Sha3256Hash leftHash = left == null ? new Sha3256Hash(new byte[32]) : left.getHash(); - Sha3256Hash rightHash = right == null ? new Sha3256Hash(new byte[32]) : right.getHash(); - this.hash = hasher.computeHash(leftHash, rightHash); + Sha3256Hash leftHash = left == null ? new Sha3256Hash(new byte[32]) : left.getRootHash(); + Sha3256Hash rightHash = right == null ? new Sha3256Hash(new byte[32]) : right.getRootHash(); + this.rootHash = hasher.computeHash(leftHash, rightHash); } } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 496b8480..648e438d 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -109,10 +109,10 @@ private MerkleProof getProof(Identifier id) throws IllegalArgumentException { MerklePath path = new MerklePath(); MerkleNode currentNode = leafNodes.get(idx); while (currentNode != root) { - path.add(currentNode.getSibling().getHash(), currentNode.isLeft()); + path.add(currentNode.getSibling().getRootHash(), currentNode.isLeft()); currentNode = currentNode.getParent(); } - return new MerkleProof(root.getHash(), path); + return new MerkleProof(root.getRootHash(), path); } /** @@ -139,9 +139,9 @@ private void buildMerkleTree() { right = currentLevelNodes.get(i + 1); // we have a right node } else { // TODO: edge case need to get fixed. - right = new MerkleNode(left.getHash()); + right = new MerkleNode(left.getRootHash()); } - Sha3256Hash hash = hasher.computeHash(left.getHash().getBytes(), right.getHash().getBytes()); + Sha3256Hash hash = hasher.computeHash(left.getRootHash().getBytes(), right.getRootHash().getBytes()); MerkleNode parent = new MerkleNode(hash, left, right); left.setParent(parent); right.setParent(parent); diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java index 0c557b83..fa5d0855 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -21,7 +21,7 @@ public class MerklePatriciaTrie extends MerkleTree { private static final Sha3256Hasher hasher = new Sha3256Hasher(); private final Map entityHashTable; private final ReentrantReadWriteLock lock; - private final MerkleNode root; + private final MerkleNode rootNode; private int size; /** @@ -29,7 +29,7 @@ public class MerklePatriciaTrie extends MerkleTree { */ public MerklePatriciaTrie() { this.size = 0; - this.root = new MerkleNode(); + this.rootNode = new MerkleNode(); this.entityHashTable = new HashMap<>(); this.lock = new ReentrantReadWriteLock(); } @@ -41,13 +41,13 @@ public MerklePatriciaTrie() { * @return AuthenticatedEntity containing the entity and its membership proof */ @Override - public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { + public AuthenticatedEntity put(Entity e) { try { lock.writeLock().lock(); if (e == null) { - throw new IllegalArgumentException("entity cannot be null"); + throw new IllegalArgumentException("attempting to put a null entity in merkle patricia trie"); } - MerkleNode currNode = root; + MerkleNode currentNode = rootNode; Identifier id = e.id(); byte[] bytes = id.getBytes(); String path = ""; @@ -56,38 +56,38 @@ public AuthenticatedEntity put(Entity e) throws IllegalArgumentException { } for (int i = 0; i < path.length() - 1; i++) { if (path.charAt(i) == '0') { - if (currNode.getLeft() == null) { - currNode.setLeftNode(new MerkleNode(currNode, true)); + if (currentNode.getLeft() == null) { + currentNode.setLeftNode(new MerkleNode(currentNode, true)); } - currNode = currNode.getLeft(); + currentNode = currentNode.getLeft(); } else { - if (currNode.getRight() == null) { - currNode.setRightNode(new MerkleNode(currNode, false)); + if (currentNode.getRight() == null) { + currentNode.setRightNode(new MerkleNode(currentNode, false)); } - currNode = currNode.getRight(); + currentNode = currentNode.getRight(); } } MerkleNode newNode = null; if (path.charAt(path.length() - 1) == '0') { - if (currNode.getLeft() == null) { - newNode = new MerkleNode(currNode, true, hasher.computeHash(id)); - currNode.setLeftNode(newNode); + if (currentNode.getLeft() == null) { + newNode = new MerkleNode(currentNode, true, hasher.computeHash(id)); + currentNode.setLeftNode(newNode); entityHashTable.put(id, e); size++; } } else { - if (currNode.getRight() == null) { - newNode = new MerkleNode(currNode, false, hasher.computeHash(id)); - currNode.setRightNode(newNode); + if (currentNode.getRight() == null) { + newNode = new MerkleNode(currentNode, false, hasher.computeHash(id)); + currentNode.setRightNode(newNode); entityHashTable.put(id, e); size++; } } for (int i = 0; i < path.length() - 1; i++) { - currNode.updateHash(); - currNode = currNode.getParent(); + currentNode.updateHash(); + currentNode = currentNode.getParent(); } - currNode.updateHash(); + currentNode.updateHash(); return get(id); } finally { lock.writeLock().unlock(); @@ -110,7 +110,7 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { ArrayList pathList = new ArrayList<>(); ArrayList isLeftNode = new ArrayList<>(); - MerkleNode currNode = root; + MerkleNode currNode = rootNode; byte[] bytes = id.getBytes(); String path = ""; for (byte b : bytes) { @@ -127,13 +127,13 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { if (currNode == null) { throw new IllegalArgumentException("identifier not found"); } - pathList.add(currNode.getSibling() == null ? new Sha3256Hash(new byte[32]) : currNode.getSibling().getHash()); + pathList.add(currNode.getSibling() == null ? new Sha3256Hash(new byte[32]) : currNode.getSibling().getRootHash()); } Collections.reverse(pathList); Collections.reverse(isLeftNode); Entity e = entityHashTable.get(id); MerklePath merklePath = new MerklePath(pathList, isLeftNode); - MerkleProof proof = new MerkleProof(root.getHash(), merklePath); + MerkleProof proof = new MerkleProof(rootNode.getRootHash(), merklePath); return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } finally { lock.readLock().unlock(); From a030fc2b6e91786254323db6408461b8675a9a19 Mon Sep 17 00:00:00 2001 From: Yahya Date: Wed, 20 Sep 2023 08:12:43 -0700 Subject: [PATCH 12/19] fixes lint --- .../java/model/lightchain/Identifier.java | 43 +++++++++---------- .../modules/ads/merkletree/MerkleProof.java | 2 - .../java/model/lightchain/IdentifierTest.java | 21 ++++++++- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/main/java/model/lightchain/Identifier.java b/src/main/java/model/lightchain/Identifier.java index 7b860bab..824e8466 100644 --- a/src/main/java/model/lightchain/Identifier.java +++ b/src/main/java/model/lightchain/Identifier.java @@ -55,6 +55,26 @@ private static String pretty(byte[] identifier) { return Multibase.encode(Multibase.Base.Base58BTC, identifier); } + /** + * Converts a bit string to an identifier. + * + * @param bitString a bit string of length 256. + * @return an identifier. + */ + public static Identifier bitStringToIdentifier(String bitString) { + if (bitString.length() != 8 * Size) { + throw new IllegalArgumentException("Bit string must be 256 bits long"); + } + + byte[] bytes = new byte[Size]; + for (int i = 0; i < Size; i++) { + String byteString = bitString.substring(i * 8, (i + 1) * 8); + bytes[i] = (byte) Integer.parseInt(byteString, 2); + } + + return new Identifier(bytes); + } + /** * Returns if objects equal. * @@ -117,31 +137,10 @@ public String toString() { * Compares this identifier with the other identifier. * * @param other represents other identifier to compared to. - * @return 0 if two identifiers are equal, 1 if this identifier is greater than other, - * -1 if other identifier is greater than this. + * @return 0 if two identifiers are equal, 1 if this identifier is greater than other, -1 if other identifier is greater than this. */ public int comparedTo(Identifier other) { int result = Arrays.compare(this.value, other.value); return Integer.compare(result, 0); } - - /** - * Converts a bit string to an identifier. - * - * @param bitString a bit string of length 256. - * @return an identifier. - */ - public static Identifier BitStringToIdentifier(String bitString) { - if(bitString.length() != 8 * Size) { - throw new IllegalArgumentException("Bit string must be 256 bits long"); - } - - byte[] bytes = new byte[Size]; - for(int i = 0; i < Size; i++) { - String byteString = bitString.substring(i * 8, (i + 1) * 8); - bytes[i] = (byte) Integer.parseInt(byteString, 2); - } - - return new Identifier(bytes); - } } diff --git a/src/main/java/modules/ads/merkletree/MerkleProof.java b/src/main/java/modules/ads/merkletree/MerkleProof.java index 532e95ee..a856d870 100644 --- a/src/main/java/modules/ads/merkletree/MerkleProof.java +++ b/src/main/java/modules/ads/merkletree/MerkleProof.java @@ -11,7 +11,6 @@ public class MerkleProof implements MembershipProof { private final MerklePath merklePath; private final Sha3256Hash root; - private ArrayList path; /** * Constructs a proof from a list of hashes and a root. @@ -47,7 +46,6 @@ public MerklePath getMerklePath() { * Checks if two MerkleProofs are equal. * * @param o the other MerkleProof - * * @return true if the MerkleProofs are equal, false otherwise */ @Override diff --git a/src/test/java/model/lightchain/IdentifierTest.java b/src/test/java/model/lightchain/IdentifierTest.java index cd6884db..d8ed5fa8 100644 --- a/src/test/java/model/lightchain/IdentifierTest.java +++ b/src/test/java/model/lightchain/IdentifierTest.java @@ -142,7 +142,7 @@ void testCompareTo() { * input string. */ @Test - void testRoundTrip() { + void testByteStringRoundTrip() { // creates a random identifier with a random byte array. byte[] value = new byte[Identifier.Size]; random.nextBytes(value); @@ -155,4 +155,23 @@ void testRoundTrip() { // the string representation of the two identifiers should be the same regardless of the way they are created. assertEquals(identifier.toString(), identifier2.toString()); } + + /** + * Tests the round trip of Identifier. The method should return an Identifier with the same bit string representation as + * the input bit string. + */ + @Test + void testBitStringRoundTrip() { + byte[] value = new byte[Identifier.Size]; + random.nextBytes(value); + Identifier identifier = new Identifier(value); + + String bitString = identifier.getBitString(); + Identifier bitIdentifier = Identifier.bitStringToIdentifier(bitString); + + assertEquals(bitIdentifier.getBitString(), bitString); + assertEquals(bitIdentifier.getBitString(), identifier.getBitString()); + assertArrayEquals(bitIdentifier.getBytes(), identifier.getBytes()); + assertEquals(bitIdentifier.comparedTo(identifier), 0); + } } From f0373603710948d3b6aac1f63a8d0a1b2758535c Mon Sep 17 00:00:00 2001 From: Yahya Date: Wed, 20 Sep 2023 08:13:53 -0700 Subject: [PATCH 13/19] adds todos --- src/test/java/model/lightchain/IdentifierTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/model/lightchain/IdentifierTest.java b/src/test/java/model/lightchain/IdentifierTest.java index d8ed5fa8..8a06b447 100644 --- a/src/test/java/model/lightchain/IdentifierTest.java +++ b/src/test/java/model/lightchain/IdentifierTest.java @@ -174,4 +174,7 @@ void testBitStringRoundTrip() { assertArrayEquals(bitIdentifier.getBytes(), identifier.getBytes()); assertEquals(bitIdentifier.comparedTo(identifier), 0); } + + // TODO: test for no collision in the random identifier generation. + // TODO: test that binary string is only 256 bits long comprised of 0s and 1s. } From 604952369ea7749d99add74d63f9ee822f4e508a Mon Sep 17 00:00:00 2001 From: Yahya Date: Fri, 22 Sep 2023 07:16:04 -0700 Subject: [PATCH 14/19] adds bitstring fixtures --- src/test/java/unittest/fixtures/Bits.java | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/test/java/unittest/fixtures/Bits.java diff --git a/src/test/java/unittest/fixtures/Bits.java b/src/test/java/unittest/fixtures/Bits.java new file mode 100644 index 00000000..c56a75d6 --- /dev/null +++ b/src/test/java/unittest/fixtures/Bits.java @@ -0,0 +1,31 @@ +package unittest.fixtures; + +import java.util.Random; + +/** + * Bit Fixture class that creates random bit objects to be used in testing. + */ +public class Bits { + /** + * Random object used to generate random bits. + */ + private static final Random random = new Random(); + + /** + * Generates a random bit string; a string of 1s and 0s. + * + * @param length length of bit string. + * @return random bit string. + */ + public static String BitStringFixture(int length) { + StringBuilder bitString = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (random.nextBoolean()) { + bitString.append("1"); + } else { + bitString.append("0"); + } + } + return bitString.toString(); + } +} From 2a50204afee7fa2f59b72aa271ae1a83478d5230 Mon Sep 17 00:00:00 2001 From: Yahya Date: Fri, 22 Sep 2023 07:24:18 -0700 Subject: [PATCH 15/19] extends bit string tests --- .../java/model/lightchain/IdentifierTest.java | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/test/java/model/lightchain/IdentifierTest.java b/src/test/java/model/lightchain/IdentifierTest.java index 8a06b447..c5b25f2d 100644 --- a/src/test/java/model/lightchain/IdentifierTest.java +++ b/src/test/java/model/lightchain/IdentifierTest.java @@ -1,6 +1,7 @@ package model.lightchain; import java.util.Arrays; +import java.util.HashSet; import java.util.Random; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -9,7 +10,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import io.ipfs.multibase.Multibase; +import org.junit.Assert; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import unittest.fixtures.Bits; +import unittest.fixtures.IdentifierFixture; class IdentifierTest { private static final Random random = new Random(); @@ -175,6 +180,34 @@ void testBitStringRoundTrip() { assertEquals(bitIdentifier.comparedTo(identifier), 0); } - // TODO: test for no collision in the random identifier generation. - // TODO: test that binary string is only 256 bits long comprised of 0s and 1s. + /** + * Tests that unique bit strings are converted to unique identifiers. + */ + @Test + void testRandomBitStrings() { + HashSet bitStrings = new HashSet<>(); + HashSet identifiers = new HashSet<>(); + + for (int i = 0; i < 1_000_000; i++) { + String bitString = Bits.BitStringFixture(Identifier.Size * 8); + Assertions.assertFalse(bitStrings.contains(bitString)); + bitStrings.add(bitString); + Identifier identifier = Identifier.bitStringToIdentifier(bitString); + Assertions.assertFalse(identifiers.contains(identifier)); + identifiers.add(identifier); + } + } + + /** + * Tests that the bit string representation of an identifier is correct, i.e., it is a string of 0s and 1s with length 256 bits. + */ + @Test + void testBitStringCorrectness() { + Identifier identifier = IdentifierFixture.newIdentifier(); + String bitString = identifier.getBitString(); + Assertions.assertEquals(bitString.length(), Identifier.Size * 8); + for (int i = 0; i < bitString.length(); i++) { + Assertions.assertTrue(bitString.charAt(i) == '0' || bitString.charAt(i) == '1'); + } + } } From 39f54bc4ad8c807ce07dfe7e9c226af29b4e5d72 Mon Sep 17 00:00:00 2001 From: Yahya Date: Fri, 22 Sep 2023 07:30:30 -0700 Subject: [PATCH 16/19] adds direction class --- src/main/java/model/lightchain/Direction.java | 56 +++++++++++++++++++ .../java/model/lightchain/DirectionTest.java | 47 ++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/main/java/model/lightchain/Direction.java create mode 100644 src/test/java/model/lightchain/DirectionTest.java diff --git a/src/main/java/model/lightchain/Direction.java b/src/main/java/model/lightchain/Direction.java new file mode 100644 index 00000000..342adf1b --- /dev/null +++ b/src/main/java/model/lightchain/Direction.java @@ -0,0 +1,56 @@ +package model.lightchain; + +import java.io.Serializable; + +/** + * Represents the semantic of direction. + * A Direction can be either LEFT or RIGHT. + */ +public class Direction implements Serializable { + /** + * The value of the direction; either "Left" or "Right". + */ + private final String value; + + /** + * The public static instances of Left direction. + */ + public static final Direction LEFT = new Direction("Left"); + + /** + * The public static instances of Right direction. + */ + public static final Direction RIGHT = new Direction("Right"); + + /** + * Constructor. + * + * @param value The value of the direction; either "Left" or "Right". + */ + private Direction(String value) { + this.value = value; + } + + /** + * Returns true if the direction is RIGHT. + * + * @return true if the direction is RIGHT. + */ + public boolean isRight() { + return this == RIGHT; + } + + /** + * Returns true if the direction is LEFT. + * + * @return true if the direction is LEFT. + */ + public boolean isLeft() { + return this == LEFT; + } + + @Override + public String toString() { + return value; + } +} \ No newline at end of file diff --git a/src/test/java/model/lightchain/DirectionTest.java b/src/test/java/model/lightchain/DirectionTest.java new file mode 100644 index 00000000..e69493b6 --- /dev/null +++ b/src/test/java/model/lightchain/DirectionTest.java @@ -0,0 +1,47 @@ +package model.lightchain; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + +public class DirectionTest { + + @Test + public void testIsRight() { + assertTrue(Direction.RIGHT.isRight()); + assertFalse(Direction.LEFT.isRight()); + } + + @Test + public void testIsLeft() { + assertTrue(Direction.LEFT.isLeft()); + assertFalse(Direction.RIGHT.isLeft()); + } + + @Test + public void testToString() { + assertEquals("Right", Direction.RIGHT.toString()); + assertEquals("Left", Direction.LEFT.toString()); + } + + @Test + public void testEquality() { + assertEquals(Direction.LEFT, Direction.LEFT); + assertEquals(Direction.RIGHT, Direction.RIGHT); + assertNotEquals(Direction.LEFT, Direction.RIGHT); + assertNotEquals(Direction.RIGHT, Direction.LEFT); + } + + @Test + public void testSameObject() { + assertSame(Direction.LEFT, Direction.LEFT); + assertSame(Direction.RIGHT, Direction.RIGHT); + assertNotSame(Direction.LEFT, Direction.RIGHT); + assertNotSame(Direction.RIGHT, Direction.LEFT); + } +} + From b6a1568d9aa42c19e3c2fc80069ca047bd06e1c4 Mon Sep 17 00:00:00 2001 From: Yahya Date: Fri, 22 Sep 2023 08:03:34 -0700 Subject: [PATCH 17/19] simplified the code --- .../modules/ads/merkletree/MerkleNode.java | 35 ++++++++++--------- .../modules/ads/merkletree/MerkleTree.java | 5 +-- .../modules/ads/mtrie/MerklePatriciaTrie.java | 23 +++++------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index e0ffb9a3..14e5f0a1 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -4,6 +4,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.Entity; import model.crypto.Sha3256Hash; +import model.lightchain.Direction; +import org.reflections.vfs.Vfs; /** * A node in the Merkle tree. @@ -13,7 +15,7 @@ public class MerkleNode { private MerkleNode left; private MerkleNode right; private MerkleNode parent; - private boolean isLeft; + private Direction direction; private Sha3256Hash rootHash; /** @@ -23,7 +25,6 @@ public MerkleNode() { this.left = null; this.right = null; this.parent = null; - this.isLeft = false; this.rootHash = new Sha3256Hash(new byte[32]); } @@ -31,13 +32,13 @@ public MerkleNode() { * Constructor with entity and isLeft. * * @param e input entity - * @param isLeft boolean that specifies if the node is left child or not + * @param direction whether the node is left child or right child of its parent. */ - public MerkleNode(Entity e, boolean isLeft) { + public MerkleNode(Entity e, Direction direction) { this.left = null; this.right = null; this.parent = null; - this.isLeft = isLeft; + this.direction = direction; this.rootHash = hasher.computeHash(e.id()); } @@ -45,14 +46,14 @@ public MerkleNode(Entity e, boolean isLeft) { * Constructor with parent and isLeft. * * @param parent the parent of the node - * @param isLeft boolean that specifies if the node is left child or not + * @param direction boolean that specifies if the node is left child or not */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") - public MerkleNode(MerkleNode parent, boolean isLeft) { + public MerkleNode(MerkleNode parent, Direction direction) { this.left = null; this.right = null; this.parent = parent; - this.isLeft = isLeft; + this.direction = direction; this.rootHash = new Sha3256Hash(new byte[32]); } @@ -60,15 +61,15 @@ public MerkleNode(MerkleNode parent, boolean isLeft) { * Constructor with parent, isLeft and hash. * * @param parent the parent of the node - * @param isLeft boolean that specifies if the node is left child or not + * @param direction whether the node is left child or right child of its parent. * @param hash input hash of the entity corresponding to that node */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") - public MerkleNode(MerkleNode parent, boolean isLeft, Sha3256Hash hash) { + public MerkleNode(MerkleNode parent, Direction direction, Sha3256Hash hash) { this.left = null; this.right = null; this.parent = parent; - this.isLeft = isLeft; + this.direction = direction; this.rootHash = hash; } @@ -81,7 +82,7 @@ public MerkleNode(Sha3256Hash hash) { this.left = null; this.right = null; this.parent = null; - this.isLeft = false; + this.direction = null; this.rootHash = hash; } @@ -97,7 +98,7 @@ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.left = left; this.right = right; this.parent = null; - this.isLeft = false; + this.direction = null; this.rootHash = hash; } @@ -156,16 +157,16 @@ public Sha3256Hash getRootHash() { * @return true if the node is a left child, false otherwise */ public boolean isLeft() { - return isLeft; + return direction.isLeft(); } /** * Sets if the node is a left child. * - * @param isLeft true if the node is a left child, false otherwise + * @param direction whether the node is left child or right child of its parent. */ - public void setLeft(boolean isLeft) { - this.isLeft = isLeft; + public void setDirection(Direction direction) { + this.direction = direction; } /** diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 648e438d..34d5585b 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -8,6 +8,7 @@ import crypto.Sha3256Hasher; import model.Entity; import model.crypto.Sha3256Hash; +import model.lightchain.Direction; import model.lightchain.Identifier; import modules.ads.AuthenticatedDataStructure; @@ -53,7 +54,7 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); Integer idx = leafNodesHashTable.get(hash); if (idx == null) { - leafNodes.add(new MerkleNode(e, false)); + leafNodes.add(new MerkleNode(e, Direction.RIGHT)); leafNodesHashTable.put(hash, size); entityHashTable.put(e.id(), e); size++; @@ -132,7 +133,7 @@ private void buildMerkleTree() { 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); + left.setDirection(Direction.LEFT); MerkleNode right; if (i + 1 < currentLevelNodes.size()) { diff --git a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java index fa5d0855..9b1dd653 100644 --- a/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -9,6 +9,7 @@ import crypto.Sha3256Hasher; import model.Entity; import model.crypto.Sha3256Hash; +import model.lightchain.Direction; import model.lightchain.Identifier; import modules.ads.AuthenticatedEntity; import modules.ads.merkletree.*; @@ -48,21 +49,19 @@ public AuthenticatedEntity put(Entity e) { throw new IllegalArgumentException("attempting to put a null entity in merkle patricia trie"); } MerkleNode currentNode = rootNode; + Identifier id = e.id(); - byte[] bytes = id.getBytes(); - String path = ""; - for (byte b : bytes) { - path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); - } + String path = id.getBitString(); + for (int i = 0; i < path.length() - 1; i++) { if (path.charAt(i) == '0') { if (currentNode.getLeft() == null) { - currentNode.setLeftNode(new MerkleNode(currentNode, true)); + currentNode.setLeftNode(new MerkleNode(currentNode, Direction.LEFT)); } currentNode = currentNode.getLeft(); } else { if (currentNode.getRight() == null) { - currentNode.setRightNode(new MerkleNode(currentNode, false)); + currentNode.setRightNode(new MerkleNode(currentNode, Direction.RIGHT)); } currentNode = currentNode.getRight(); } @@ -70,14 +69,14 @@ public AuthenticatedEntity put(Entity e) { MerkleNode newNode = null; if (path.charAt(path.length() - 1) == '0') { if (currentNode.getLeft() == null) { - newNode = new MerkleNode(currentNode, true, hasher.computeHash(id)); + newNode = new MerkleNode(currentNode, Direction.LEFT, hasher.computeHash(id)); currentNode.setLeftNode(newNode); entityHashTable.put(id, e); size++; } } else { if (currentNode.getRight() == null) { - newNode = new MerkleNode(currentNode, false, hasher.computeHash(id)); + newNode = new MerkleNode(currentNode, Direction.RIGHT, hasher.computeHash(id)); currentNode.setRightNode(newNode); entityHashTable.put(id, e); size++; @@ -111,11 +110,7 @@ public AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { ArrayList pathList = new ArrayList<>(); ArrayList isLeftNode = new ArrayList<>(); MerkleNode currNode = rootNode; - byte[] bytes = id.getBytes(); - String path = ""; - for (byte b : bytes) { - path += String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); - } + String path = id.getBitString(); for (int i = 0; i < path.length(); i++) { if (path.charAt(i) == '0') { currNode = currNode.getLeft(); From e61fbff704a43baa90be4a13eea7ba18df75b0d6 Mon Sep 17 00:00:00 2001 From: Yahya Date: Fri, 22 Sep 2023 08:05:23 -0700 Subject: [PATCH 18/19] lint fix --- src/main/java/modules/ads/merkletree/MerkleNode.java | 9 ++++----- src/test/java/model/lightchain/DirectionTest.java | 4 ++++ src/test/java/model/lightchain/IdentifierTest.java | 5 ++--- .../unittest/fixtures/{Bits.java => BitFixture.java} | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) rename src/test/java/unittest/fixtures/{Bits.java => BitFixture.java} (89%) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 14e5f0a1..de51cbae 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -5,7 +5,6 @@ import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Direction; -import org.reflections.vfs.Vfs; /** * A node in the Merkle tree. @@ -31,7 +30,7 @@ public MerkleNode() { /** * Constructor with entity and isLeft. * - * @param e input entity + * @param e input entity * @param direction whether the node is left child or right child of its parent. */ public MerkleNode(Entity e, Direction direction) { @@ -45,7 +44,7 @@ public MerkleNode(Entity e, Direction direction) { /** * Constructor with parent and isLeft. * - * @param parent the parent of the node + * @param parent the parent of the node * @param direction boolean that specifies if the node is left child or not */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") @@ -60,9 +59,9 @@ public MerkleNode(MerkleNode parent, Direction direction) { /** * Constructor with parent, isLeft and hash. * - * @param parent the parent of the node + * @param parent the parent of the node * @param direction whether the node is left child or right child of its parent. - * @param hash input hash of the entity corresponding to that node + * @param hash input hash of the entity corresponding to that node */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") public MerkleNode(MerkleNode parent, Direction direction, Sha3256Hash hash) { diff --git a/src/test/java/model/lightchain/DirectionTest.java b/src/test/java/model/lightchain/DirectionTest.java index e69493b6..03f90cd1 100644 --- a/src/test/java/model/lightchain/DirectionTest.java +++ b/src/test/java/model/lightchain/DirectionTest.java @@ -6,8 +6,12 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.Test; +/** + * Tests the Direction data type. + */ public class DirectionTest { @Test diff --git a/src/test/java/model/lightchain/IdentifierTest.java b/src/test/java/model/lightchain/IdentifierTest.java index c5b25f2d..1dee534a 100644 --- a/src/test/java/model/lightchain/IdentifierTest.java +++ b/src/test/java/model/lightchain/IdentifierTest.java @@ -10,10 +10,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import io.ipfs.multibase.Multibase; -import org.junit.Assert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import unittest.fixtures.Bits; +import unittest.fixtures.BitFixture; import unittest.fixtures.IdentifierFixture; class IdentifierTest { @@ -189,7 +188,7 @@ void testRandomBitStrings() { HashSet identifiers = new HashSet<>(); for (int i = 0; i < 1_000_000; i++) { - String bitString = Bits.BitStringFixture(Identifier.Size * 8); + String bitString = BitFixture.newBitString(Identifier.Size * 8); Assertions.assertFalse(bitStrings.contains(bitString)); bitStrings.add(bitString); Identifier identifier = Identifier.bitStringToIdentifier(bitString); diff --git a/src/test/java/unittest/fixtures/Bits.java b/src/test/java/unittest/fixtures/BitFixture.java similarity index 89% rename from src/test/java/unittest/fixtures/Bits.java rename to src/test/java/unittest/fixtures/BitFixture.java index c56a75d6..9831ee1d 100644 --- a/src/test/java/unittest/fixtures/Bits.java +++ b/src/test/java/unittest/fixtures/BitFixture.java @@ -5,7 +5,7 @@ /** * Bit Fixture class that creates random bit objects to be used in testing. */ -public class Bits { +public class BitFixture { /** * Random object used to generate random bits. */ @@ -17,7 +17,7 @@ public class Bits { * @param length length of bit string. * @return random bit string. */ - public static String BitStringFixture(int length) { + public static String newBitString(int length) { StringBuilder bitString = new StringBuilder(); for (int i = 0; i < length; i++) { if (random.nextBoolean()) { From d01f2346107df4f2fad12f0111e9758043f91207 Mon Sep 17 00:00:00 2001 From: Yahya <19204398+yhassanzadeh13@users.noreply.github.com> Date: Thu, 10 Oct 2024 07:37:26 -0700 Subject: [PATCH 19/19] revises a comment --- src/main/java/model/lightchain/Direction.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/lightchain/Direction.java b/src/main/java/model/lightchain/Direction.java index 342adf1b..552336af 100644 --- a/src/main/java/model/lightchain/Direction.java +++ b/src/main/java/model/lightchain/Direction.java @@ -3,7 +3,8 @@ import java.io.Serializable; /** - * Represents the semantic of direction. + * Represents the semantic of direction that is used for + * traversing data structures, e.g., Merkle trees. * A Direction can be either LEFT or RIGHT. */ public class Direction implements Serializable {