diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 3f50e4ff..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,6 +73,13 @@ 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()); } diff --git a/src/main/java/model/lightchain/Direction.java b/src/main/java/model/lightchain/Direction.java new file mode 100644 index 00000000..552336af --- /dev/null +++ b/src/main/java/model/lightchain/Direction.java @@ -0,0 +1,57 @@ +package model.lightchain; + +import java.io.Serializable; + +/** + * 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 { + /** + * 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/main/java/model/lightchain/Identifier.java b/src/main/java/model/lightchain/Identifier.java index aba739d0..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. * @@ -91,6 +111,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. * @@ -104,8 +137,7 @@ 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); 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..de51cbae 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -4,6 +4,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.Entity; import model.crypto.Sha3256Hash; +import model.lightchain.Direction; /** * A node in the Merkle tree. @@ -13,8 +14,8 @@ public class MerkleNode { private MerkleNode left; private MerkleNode right; private MerkleNode parent; - private boolean isLeft; - private Sha3256Hash hash; + private Direction direction; + private Sha3256Hash rootHash; /** * Default constructor. @@ -23,22 +24,52 @@ public MerkleNode() { this.left = null; this.right = null; this.parent = null; - this.isLeft = false; - this.hash = null; + this.rootHash = new Sha3256Hash(new byte[32]); } /** * Constructor with entity and isLeft. * - * @param e input entity - * @param isLeft boolean that specifies if the node is left child or not + * @param e input entity + * @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.hash = hasher.computeHash(e.id()); + this.direction = direction; + this.rootHash = hasher.computeHash(e.id()); + } + + /** + * Constructor with parent and isLeft. + * + * @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") + public MerkleNode(MerkleNode parent, Direction direction) { + this.left = null; + this.right = null; + this.parent = parent; + this.direction = direction; + this.rootHash = new Sha3256Hash(new byte[32]); + } + + /** + * Constructor with parent, isLeft and hash. + * + * @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 + */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") + public MerkleNode(MerkleNode parent, Direction direction, Sha3256Hash hash) { + this.left = null; + this.right = null; + this.parent = parent; + this.direction = direction; + this.rootHash = hash; } /** @@ -50,8 +81,8 @@ public MerkleNode(Sha3256Hash hash) { this.left = null; this.right = null; this.parent = null; - this.isLeft = false; - this.hash = hash; + this.direction = null; + this.rootHash = hash; } /** @@ -66,40 +97,75 @@ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.left = left; this.right = right; this.parent = null; - this.isLeft = false; - this.hash = hash; + this.direction = null; + this.rootHash = 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; } - public Sha3256Hash getHash() { - return hash; + /** + * Returns the hash corresponding to the node. + * + * @return the hash corresponding to the node + */ + public Sha3256Hash getRootHash() { + return rootHash; } + /** + * 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; + return direction.isLeft(); } - public void setLeft(boolean isLeft) { - this.isLeft = isLeft; + /** + * Sets if the node is a left child. + * + * @param direction whether the node is left child or right child of its parent. + */ + public void setDirection(Direction direction) { + this.direction = direction; } /** @@ -114,4 +180,33 @@ public MerkleNode getSibling() { return parent.getLeft(); } } + + /** + * Sets the left child of the node. + * + * @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; + } + + /** + * Sets the left child of the node. + * + * @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; + } + + /** + * Updates the hash of the node. + */ + public void updateHash() { + 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/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 9f21ab8c..a856d870 100644 --- a/src/main/java/modules/ads/merkletree/MerkleProof.java +++ b/src/main/java/modules/ads/merkletree/MerkleProof.java @@ -1,53 +1,53 @@ package modules.ads.merkletree; -import java.io.Serializable; -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; /** * A proof of membership in a Merkle tree. */ -public class MerkleProof implements MembershipProof, Serializable { - private final ArrayList isLeftNode; +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. * - * @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) { @@ -56,17 +56,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..34d5585b 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -8,11 +8,12 @@ import crypto.Sha3256Hasher; import model.Entity; import model.crypto.Sha3256Hash; +import model.lightchain.Direction; import model.lightchain.Identifier; 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 { @@ -36,6 +37,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 { @@ -46,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++; @@ -62,6 +70,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 +93,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 if the identifier is null + */ 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().getRootHash(), currentNode.isLeft()); currentNode = currentNode.getParent(); } - return new MerkleProof(path, root.getHash(), isLeftNode); + return new MerkleProof(root.getRootHash(), 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 @@ -109,16 +133,16 @@ 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()) { 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); @@ -130,6 +154,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; 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..9b1dd653 --- /dev/null +++ b/src/main/java/modules/ads/mtrie/MerklePatriciaTrie.java @@ -0,0 +1,147 @@ +package modules.ads.mtrie; + +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; +import model.crypto.Sha3256Hash; +import model.lightchain.Direction; +import model.lightchain.Identifier; +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 final Map entityHashTable; + private final ReentrantReadWriteLock lock; + private final MerkleNode rootNode; + private int size; + + /** + * Default constructor for a Merkle Patricia Trie. + */ + public MerklePatriciaTrie() { + this.size = 0; + this.rootNode = new MerkleNode(); + this.entityHashTable = new HashMap<>(); + this.lock = new ReentrantReadWriteLock(); + } + + /** + * 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) { + try { + lock.writeLock().lock(); + if (e == null) { + throw new IllegalArgumentException("attempting to put a null entity in merkle patricia trie"); + } + MerkleNode currentNode = rootNode; + + Identifier id = e.id(); + 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, Direction.LEFT)); + } + currentNode = currentNode.getLeft(); + } else { + if (currentNode.getRight() == null) { + currentNode.setRightNode(new MerkleNode(currentNode, Direction.RIGHT)); + } + currentNode = currentNode.getRight(); + } + } + MerkleNode newNode = null; + if (path.charAt(path.length() - 1) == '0') { + if (currentNode.getLeft() == null) { + 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, Direction.RIGHT, hasher.computeHash(id)); + currentNode.setRightNode(newNode); + entityHashTable.put(id, e); + size++; + } + } + for (int i = 0; i < path.length() - 1; i++) { + currentNode.updateHash(); + currentNode = currentNode.getParent(); + } + currentNode.updateHash(); + return get(id); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 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 { + try { + lock.readLock().lock(); + if (id == null) { + throw new IllegalArgumentException("identifier cannot be null"); + } + + ArrayList pathList = new ArrayList<>(); + ArrayList isLeftNode = new ArrayList<>(); + MerkleNode currNode = rootNode; + String path = id.getBitString(); + 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().getRootHash()); + } + Collections.reverse(pathList); + Collections.reverse(isLeftNode); + Entity e = entityHashTable.get(id); + MerklePath merklePath = new MerklePath(pathList, isLeftNode); + MerkleProof proof = new MerkleProof(rootNode.getRootHash(), merklePath); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Returns the size of the merkle patricia trie. + * + * @return the size of the merkle patricia trie + */ + @Override + public int size() { + return size; + } +} diff --git a/src/test/java/model/lightchain/DirectionTest.java b/src/test/java/model/lightchain/DirectionTest.java new file mode 100644 index 00000000..03f90cd1 --- /dev/null +++ b/src/test/java/model/lightchain/DirectionTest.java @@ -0,0 +1,51 @@ +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; + +/** + * Tests the Direction data type. + */ +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); + } +} + diff --git a/src/test/java/model/lightchain/IdentifierTest.java b/src/test/java/model/lightchain/IdentifierTest.java index cd6884db..1dee534a 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,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import io.ipfs.multibase.Multibase; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import unittest.fixtures.BitFixture; +import unittest.fixtures.IdentifierFixture; class IdentifierTest { private static final Random random = new Random(); @@ -142,7 +146,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 +159,54 @@ 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); + } + + /** + * 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 = BitFixture.newBitString(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'); + } + } } diff --git a/src/test/java/modules/ads/MerklePatriciaTrieTest.java b/src/test/java/modules/ads/MerklePatriciaTrieTest.java new file mode 100644 index 00000000..4c471a76 --- /dev/null +++ b/src/test/java/modules/ads/MerklePatriciaTrieTest.java @@ -0,0 +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 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.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(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)); + } + + /** + * 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/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()); diff --git a/src/test/java/unittest/fixtures/BitFixture.java b/src/test/java/unittest/fixtures/BitFixture.java new file mode 100644 index 00000000..9831ee1d --- /dev/null +++ b/src/test/java/unittest/fixtures/BitFixture.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 BitFixture { + /** + * 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 newBitString(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(); + } +} diff --git a/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java b/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java new file mode 100644 index 00000000..5061087a --- /dev/null +++ b/src/test/java/unittest/fixtures/MerklePatriciaTrieFixture.java @@ -0,0 +1,22 @@ +package unittest.fixtures; + +import modules.ads.mtrie.MerklePatriciaTrie; + +/** + * Creates a new randomly looking 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.