├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ ├── MerkleHash.java │ ├── MerkleNode.java │ ├── MerkleProofHash.java │ └── MerkleTree.java └── test └── java └── NodeTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,gradle,intellij+all 3 | 4 | ### Intellij+all ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff: 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/dictionaries 12 | 13 | # Sensitive or high-churn files: 14 | .idea/**/dataSources/ 15 | .idea/**/dataSources.ids 16 | .idea/**/dataSources.xml 17 | .idea/**/dataSources.local.xml 18 | .idea/**/sqlDataSources.xml 19 | .idea/**/dynamic.xml 20 | .idea/**/uiDesigner.xml 21 | 22 | # Gradle: 23 | .idea/**/gradle.xml 24 | .idea/**/libraries 25 | 26 | # CMake 27 | cmake-build-debug/ 28 | 29 | # Mongo Explorer plugin: 30 | .idea/**/mongoSettings.xml 31 | 32 | ## File-based project format: 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Cursive Clojure plugin 47 | .idea/replstate.xml 48 | 49 | # Ruby plugin and RubyMine 50 | /.rakeTasks 51 | 52 | # Crashlytics plugin (for Android Studio and IntelliJ) 53 | com_crashlytics_export_strings.xml 54 | crashlytics.properties 55 | crashlytics-build.properties 56 | fabric.properties 57 | 58 | ### Intellij+all Patch ### 59 | # Ignores the whole idea folder 60 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 61 | 62 | .idea/ 63 | 64 | ### Java ### 65 | # Compiled class file 66 | *.class 67 | 68 | # Log file 69 | *.log 70 | 71 | # BlueJ files 72 | *.ctxt 73 | 74 | # Mobile Tools for Java (J2ME) 75 | .mtj.tmp/ 76 | 77 | # Package Files # 78 | *.jar 79 | *.war 80 | *.ear 81 | *.zip 82 | *.tar.gz 83 | *.rar 84 | 85 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 86 | hs_err_pid* 87 | 88 | ### Gradle ### 89 | .gradle 90 | **/build/ 91 | 92 | # Ignore Gradle GUI config 93 | gradle-app.setting 94 | 95 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 96 | !gradle-wrapper.jar 97 | 98 | # Cache of project 99 | .gradletasknamecache 100 | 101 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 102 | # gradle/wrapper/gradle-wrapper.properties 103 | 104 | 105 | # End of https://www.gitignore.io/api/java,gradle,intellij+all -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Royal Institute of Technology KTH - Stockholm 2 | # Merkle Tree Implementation in Java 3 | [![Build Status](https://travis-ci.org/SimoneStefani/merkle-tree-java.svg?branch=master)](https://travis-ci.org/SimoneStefani/merkle-tree-java) 4 | 5 | A **hash tree** or **Merkle tree** is a tree in which every leaf node is labelled with the hash of a data block and every non-leaf node is labelled with the cryptographic hash of the labels of its child nodes. Hash trees allow efficient and secure verification of the contents of large data structures. Hash trees are a generalization of hash lists and hash chains. 6 |
(from [Merkle Tree - Wikipedia](https://en.wikipedia.org/wiki/Merkle_tree)) 7 | 8 | _This repository contains code written during the spring semester 2018 by Simone Stefani and Dean Rauschenbusch_ 9 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'me.sstefani' 2 | version '1.0-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | 6 | sourceCompatibility = 1.8 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | testCompile group: 'junit', name: 'junit', version: '4.12' 14 | } 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimoneStefani/merkle-tree-java/7346a37dca5bd79f77064190383fb9a26af3c0bf/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Feb 10 22:17:26 CET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'merkle-tree-java' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/MerkleHash.java: -------------------------------------------------------------------------------- 1 | import java.nio.charset.StandardCharsets; 2 | import java.security.MessageDigest; 3 | import java.security.NoSuchAlgorithmException; 4 | import java.util.Arrays; 5 | import java.util.Base64; 6 | 7 | public class MerkleHash { 8 | 9 | /** 10 | * Hash value as byte array. 11 | */ 12 | private byte[] value; 13 | 14 | public MerkleHash() { 15 | } 16 | 17 | /** 18 | * Create a MerkleHash from an array of bytes. 19 | * 20 | * @param buffer of bytes 21 | * @return a MerkleHash 22 | */ 23 | public static MerkleHash create(byte[] buffer) { 24 | MerkleHash hash = new MerkleHash(); 25 | hash.computeHash(buffer); 26 | return hash; 27 | } 28 | 29 | /** 30 | * Create a MerkleHash from a string. The string needs 31 | * first to be transformed in a UTF8 sequence of bytes. 32 | * Used for leaf hashes. 33 | * 34 | * @param buffer string 35 | * @return a MerkleHash 36 | */ 37 | public static MerkleHash create(String buffer) { 38 | return create(buffer.getBytes(StandardCharsets.UTF_8)); 39 | } 40 | 41 | /** 42 | * Create a MerkleHash from two MerkleHashes by concatenation 43 | * of the byte arrays. Used for internal nodes. 44 | * 45 | * @param left subtree hash 46 | * @param right subtree hash 47 | * @return a MerkleHash 48 | */ 49 | public static MerkleHash create(MerkleHash left, MerkleHash right) { 50 | return create(concatenate(left.getValue(), right.getValue())); 51 | } 52 | 53 | /** 54 | * Get the byte value of a MerkleHash. 55 | * 56 | * @return an array of bytes 57 | */ 58 | public byte[] getValue() { 59 | return value; 60 | } 61 | 62 | /** 63 | * Compare the MerkleHash with a given byte array. 64 | * 65 | * @param hash as byte array 66 | * @return boolean 67 | */ 68 | public boolean equals(byte[] hash) { 69 | return Arrays.equals(this.value, hash); 70 | } 71 | 72 | /** 73 | * Compare the MerkleHash with a given MerkleHash. 74 | * 75 | * @param hash as MerkleHash 76 | * @return boolean 77 | */ 78 | public boolean equals(MerkleHash hash) { 79 | boolean result = false; 80 | if (hash != null) { 81 | result = Arrays.equals(this.value, hash.getValue()); 82 | } 83 | return result; 84 | } 85 | 86 | @Override 87 | public int hashCode() { 88 | return Arrays.hashCode(value); 89 | } 90 | 91 | /** 92 | * Encode in Base64 the MerkleHash. 93 | * 94 | * @return the string encoding of MerkleHash. 95 | */ 96 | @Override 97 | public String toString() { 98 | return Base64.getEncoder().encodeToString(this.value); 99 | } 100 | 101 | /** 102 | * Compute SHA256 hash of a byte array. 103 | * 104 | * @param buffer of bytes 105 | */ 106 | private void computeHash(byte[] buffer) { 107 | try { 108 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 109 | this.value = digest.digest(buffer); 110 | } catch (NoSuchAlgorithmException e) { 111 | e.printStackTrace(); 112 | } 113 | } 114 | 115 | /** 116 | * Concatenate two array of bytes. 117 | * 118 | * @param a is the first array 119 | * @param b is the second array 120 | * @return a byte array 121 | */ 122 | public static byte[] concatenate(byte[] a, byte[] b) { 123 | byte[] c = new byte[a.length + b.length]; 124 | System.arraycopy(a, 0, c, 0, a.length); 125 | System.arraycopy(b, 0, c, a.length, b.length); 126 | return c; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/MerkleNode.java: -------------------------------------------------------------------------------- 1 | import java.security.InvalidParameterException; 2 | import java.util.Objects; 3 | 4 | public class MerkleNode { 5 | private MerkleHash hash; 6 | private MerkleNode leftNode; 7 | private MerkleNode rightNode; 8 | private MerkleNode parent; 9 | 10 | 11 | public MerkleNode() { 12 | } 13 | 14 | public MerkleNode(MerkleHash hash) { 15 | this.hash = hash; 16 | } 17 | 18 | public MerkleNode(MerkleNode left, MerkleNode right) { 19 | this.leftNode = left; 20 | this.rightNode = right; 21 | this.leftNode.parent = this; 22 | if (this.rightNode != null) this.rightNode.parent = this; 23 | 24 | this.computeHash(); 25 | } 26 | 27 | public boolean isLeaf() { 28 | return this.leftNode == null && this.rightNode == null; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return hash.toString(); 34 | } 35 | 36 | public void setLeftNode(MerkleNode node) { 37 | if (node.hash == null) { 38 | throw new InvalidParameterException("Node hash must be initialized!"); 39 | } 40 | 41 | this.leftNode = node; 42 | this.leftNode.parent = this; 43 | 44 | this.computeHash(); 45 | } 46 | 47 | public void setRightNode(MerkleNode node) { 48 | if (node.hash == null) { 49 | throw new InvalidParameterException("Node hash must be initialized!"); 50 | } 51 | 52 | this.rightNode = node; 53 | this.rightNode.parent = this; 54 | 55 | if (this.leftNode != null) { 56 | this.computeHash(); 57 | } 58 | } 59 | 60 | public boolean canVerifyHash() { 61 | return (this.leftNode != null && this.rightNode != null) || (this.leftNode != null); 62 | } 63 | 64 | public boolean verifyHash() { 65 | if (this.leftNode == null && this.rightNode == null) return true; 66 | if (this.rightNode == null) return hash.equals(leftNode.hash); 67 | 68 | if (this.leftNode == null) { 69 | throw new InvalidParameterException("Left branch must be a node if right branch is a node!"); 70 | } 71 | 72 | MerkleHash leftRightHash = MerkleHash.create(this.leftNode.hash, this.rightNode.hash); 73 | return hash.equals(leftRightHash); 74 | } 75 | 76 | public boolean equals(MerkleNode other) { 77 | return this.hash.equals(other.hash); 78 | } 79 | 80 | public MerkleHash getHash() { 81 | return hash; 82 | } 83 | 84 | public MerkleNode getParent() { 85 | return parent; 86 | } 87 | 88 | public MerkleNode getLeftNode() { 89 | return leftNode; 90 | } 91 | 92 | public MerkleNode getRightNode() { 93 | return rightNode; 94 | } 95 | 96 | public void computeHash() { 97 | if (this.rightNode == null) { 98 | this.hash = this.leftNode.hash; 99 | } else { 100 | this.hash = MerkleHash.create(MerkleHash.concatenate( 101 | this.leftNode.hash.getValue(), this.rightNode.hash.getValue())); 102 | } 103 | 104 | if (this.parent != null) { 105 | this.parent.computeHash(); 106 | } 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | 112 | return Objects.hash(hash, leftNode, rightNode, parent); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/MerkleProofHash.java: -------------------------------------------------------------------------------- 1 | public class MerkleProofHash { 2 | public enum Branch { 3 | LEFT, 4 | RIGHT, 5 | OLD_ROOT 6 | } 7 | 8 | public MerkleHash hash; 9 | public Branch direction; 10 | 11 | public MerkleProofHash(MerkleHash hash, Branch direction) { 12 | this.hash = hash; 13 | this.direction = direction; 14 | } 15 | 16 | public MerkleHash getHash() { 17 | return hash; 18 | } 19 | 20 | public Branch getDirection() { 21 | return direction; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | String hash = this.hash.toString(); 27 | String direction = this.direction.toString(); 28 | return hash.concat(" is ".concat(direction).concat(" Child")); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/MerkleTree.java: -------------------------------------------------------------------------------- 1 | import java.security.InvalidParameterException; 2 | import java.util.ArrayList; 3 | import java.util.List; 4 | 5 | public class MerkleTree { 6 | 7 | private MerkleNode root; 8 | private List nodes; 9 | private List leaves; 10 | 11 | public MerkleTree() { 12 | this.nodes = new ArrayList<>(); 13 | this.leaves = new ArrayList<>(); 14 | } 15 | 16 | public List getLeaves() { 17 | return leaves; 18 | } 19 | public List getNodes() { 20 | return nodes; 21 | } 22 | public MerkleNode getRoot() { 23 | return root; 24 | } 25 | 26 | public MerkleNode appendLeaf(MerkleNode node) { 27 | this.nodes.add(node); 28 | this.leaves.add(node); 29 | return node; 30 | } 31 | 32 | public void appendLeaves(MerkleNode[] nodes) { 33 | for (MerkleNode node : nodes) { 34 | this.appendLeaf(node); 35 | } 36 | } 37 | 38 | public MerkleNode appendLeaf(MerkleHash hash) { 39 | return this.appendLeaf(new MerkleNode(hash)); 40 | } 41 | 42 | public List appendLeaves(MerkleHash[] hashes) { 43 | List nodes = new ArrayList<>(); 44 | for (MerkleHash hash : hashes) { 45 | nodes.add(this.appendLeaf(hash)); 46 | } 47 | return nodes; 48 | } 49 | 50 | public MerkleHash addTree(MerkleTree tree) { 51 | if (this.leaves.size() <= 0) throw new InvalidParameterException("Cannot add to a tree with no leaves!"); 52 | tree.leaves.forEach(this::appendLeaf); 53 | return this.buildTree(); 54 | } 55 | 56 | public MerkleHash buildTree() { 57 | if (this.leaves.size() <= 0) throw new InvalidParameterException("Cannot add to a tree with no leaves!"); 58 | this.buildTree(this.leaves); 59 | return this.root.getHash(); 60 | } 61 | 62 | public void buildTree(List nodes) { 63 | if (nodes.size() <= 0) throw new InvalidParameterException("Node list not expected to be empty!"); 64 | 65 | if (nodes.size() == 1) { 66 | this.root = nodes.get(0); 67 | } else { 68 | List parents = new ArrayList<>(); 69 | for (int i = 0; i < nodes.size(); i += 2) { 70 | MerkleNode right = (i + 1 < nodes.size()) ? nodes.get(i + 1) : null; 71 | MerkleNode parent = new MerkleNode(nodes.get(i), right); 72 | parents.add(parent); 73 | } 74 | buildTree(parents); 75 | } 76 | } 77 | 78 | public List auditProof(MerkleHash leafHash) { 79 | List auditTrail = new ArrayList<>(); 80 | 81 | MerkleNode leafNode = this.findLeaf(leafHash); 82 | 83 | if (leafNode != null) { 84 | if (leafNode.getParent() == null) throw new InvalidParameterException("Expected leaf to have a parent!"); 85 | MerkleNode parent = leafNode.getParent(); 86 | this.buildAuditTrail(auditTrail, parent, leafNode); 87 | } 88 | 89 | return auditTrail; 90 | } 91 | 92 | public static boolean verifyAudit(MerkleHash rootHash, MerkleHash leafHash, List auditTrail) { 93 | if (auditTrail.size() <= 0) throw new InvalidParameterException("Audit trail cannot be empty!"); 94 | 95 | MerkleHash testHash = leafHash; 96 | 97 | for (MerkleProofHash auditHash : auditTrail) { 98 | testHash = auditHash.direction == MerkleProofHash.Branch.RIGHT 99 | ? MerkleHash.create(testHash, auditHash.hash) 100 | : MerkleHash.create(auditHash.hash, testHash); 101 | } 102 | 103 | return testHash.equals(rootHash); 104 | } 105 | 106 | private MerkleNode findLeaf(MerkleHash hash) { 107 | return this.leaves.stream() 108 | .filter((leaf) -> leaf.getHash() == hash) 109 | .findFirst() 110 | .orElse(null); 111 | } 112 | 113 | private void buildAuditTrail(List auditTrail, MerkleNode parent, MerkleNode child) { 114 | if (parent != null) { 115 | if (child.getParent() != parent) { 116 | throw new InvalidParameterException("Parent of child is not expected parent!"); 117 | } 118 | 119 | MerkleNode nextChild = parent.getLeftNode() == child ? parent.getRightNode() : parent.getLeftNode(); 120 | MerkleProofHash.Branch direction = parent.getLeftNode() == child 121 | ? MerkleProofHash.Branch.RIGHT 122 | : MerkleProofHash.Branch.LEFT; 123 | 124 | if (nextChild != null) auditTrail.add(new MerkleProofHash(nextChild.getHash(), direction)); 125 | 126 | this.buildAuditTrail(auditTrail, parent.getParent(), child.getParent()); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/NodeTest.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | public class NodeTest { 6 | 7 | //testing if two hashes with equal input are equal 8 | @org.junit.Test 9 | public void hashesAreTheSame() { 10 | MerkleHash h1 = MerkleHash.create("hello"); 11 | MerkleHash h2 = MerkleHash.create("hello"); 12 | assertTrue(h1.equals(h2)); 13 | } 14 | 15 | //Testing if a node can be created 16 | @org.junit.Test 17 | public void createNode() { 18 | MerkleNode node = new MerkleNode(); 19 | assertNull(node.getParent()); 20 | assertNull(node.getLeftNode()); 21 | assertNull(node.getRightNode()); 22 | } 23 | 24 | //Testing if a parent node is successfully created from 25 | // one left child 26 | @org.junit.Test 27 | public void leftHashVerification() { 28 | MerkleNode parent = new MerkleNode(); 29 | MerkleNode leftChild = new MerkleNode(MerkleHash.create("hello")); 30 | parent.setLeftNode(leftChild); 31 | assertTrue(parent.verifyHash()); 32 | } 33 | 34 | //Testing if a parent node is succesfuslly create from 35 | // two children Left and Right 36 | @org.junit.Test 37 | public void leftRightHashVerification() { 38 | MerkleNode parentNode = createParentNode("foo", "bar"); 39 | assertTrue(parentNode.verifyHash()); 40 | } 41 | 42 | //Testing if a two same nodes are equal 43 | @org.junit.Test 44 | public void nodesEqual() { 45 | MerkleNode n1 = createParentNode("foo", "bar"); 46 | MerkleNode n2 = createParentNode("foo", "bar"); 47 | assertTrue(n1.equals(n2)); 48 | } 49 | 50 | //Testing if a two different nodes are different 51 | @org.junit.Test 52 | public void nodesNotEqual() { 53 | MerkleNode n1 = createParentNode("foo", "bar"); 54 | MerkleNode n2 = createParentNode("pew", "bar"); 55 | assertFalse(n1.equals(n2)); 56 | } 57 | 58 | //Testing if a tree levels 59 | @org.junit.Test 60 | public void verifyTwoLevelTree() { 61 | MerkleNode parentNode1 = createParentNode("foo", "bar"); 62 | MerkleNode parentNode2 = createParentNode("pew", "bar"); 63 | MerkleNode rootNode = new MerkleNode(); 64 | rootNode.setLeftNode(parentNode1); 65 | rootNode.setRightNode(parentNode2); 66 | assertTrue(rootNode.verifyHash()); 67 | } 68 | 69 | //Testing if a tree with even leafs can be built 70 | @org.junit.Test 71 | public void createBalancedTree() { 72 | MerkleTree tree = new MerkleTree(); 73 | tree.appendLeaf(MerkleHash.create("abc")); 74 | tree.appendLeaf(MerkleHash.create("def")); 75 | tree.appendLeaf(MerkleHash.create("ghi")); 76 | tree.appendLeaf(MerkleHash.create("jkl")); 77 | tree.buildTree(); 78 | assertNotNull(tree.getRoot()); 79 | } 80 | 81 | //Testing if a tree with uneven leafs can be built 82 | @org.junit.Test 83 | public void createUnbalancedTree() { 84 | MerkleTree tree = new MerkleTree(); 85 | tree.appendLeaf(MerkleHash.create("abc")); 86 | tree.appendLeaf(MerkleHash.create("def")); 87 | tree.appendLeaf(MerkleHash.create("ghi")); 88 | tree.buildTree(); 89 | assertNotNull(tree.getRoot()); 90 | } 91 | 92 | //Testing if it can be verified, that a leaf is part of a Tree 93 | @org.junit.Test 94 | public void auditTest() { 95 | MerkleTree tree = new MerkleTree(); 96 | MerkleHash l1 = MerkleHash.create("abc"); 97 | MerkleHash l2 = MerkleHash.create("def"); 98 | MerkleHash l3 = MerkleHash.create("ghi"); 99 | MerkleHash l4 = MerkleHash.create("jkl"); 100 | tree.appendLeaves(new MerkleHash[]{l1, l2, l3, l4}); 101 | MerkleHash rootHash = tree.buildTree(); 102 | 103 | List auditTrail = tree.auditProof(l1); 104 | assertTrue(MerkleTree.verifyAudit(rootHash, l1, auditTrail)); 105 | 106 | auditTrail = tree.auditProof(l2); 107 | assertTrue(MerkleTree.verifyAudit(rootHash, l2, auditTrail)); 108 | 109 | auditTrail = tree.auditProof(l3); 110 | assertTrue(MerkleTree.verifyAudit(rootHash, l3, auditTrail)); 111 | 112 | auditTrail = tree.auditProof(l4); 113 | assertTrue(MerkleTree.verifyAudit(rootHash, l4, auditTrail)); 114 | } 115 | 116 | //Testing if 2 trees can be merged successfully 117 | @org.junit.Test 118 | public void addTreeTest(){ 119 | 120 | // Create first Tree with 4 leaves 121 | MerkleTree tree1 = new MerkleTree(); 122 | MerkleHash l1 = MerkleHash.create("abc"); 123 | MerkleHash l2 = MerkleHash.create("def"); 124 | MerkleHash l3 = MerkleHash.create("ghi"); 125 | MerkleHash l4 = MerkleHash.create("jkl"); 126 | tree1.appendLeaves(new MerkleHash[]{l1, l2, l3, l4}); 127 | MerkleHash rootHash1 = tree1.buildTree(); 128 | 129 | // Create second Tree with 3 leaves 130 | MerkleTree tree2 = new MerkleTree(); 131 | MerkleHash l5 = MerkleHash.create("123"); 132 | MerkleHash l6 = MerkleHash.create("456"); 133 | MerkleHash l7 = MerkleHash.create("789"); 134 | tree2.appendLeaves(new MerkleHash[]{l5, l6, l7}); 135 | MerkleHash rootHash2 = tree2.buildTree(); 136 | 137 | // Merge the two trees 138 | // tree1 is the merged tree 139 | // Assert that the old root is different from the new 140 | MerkleHash rootHashAfterAddTree = tree1.addTree(tree2); 141 | assertFalse(rootHash1.equals(rootHashAfterAddTree)); 142 | 143 | //Assert that a leaf from the first tree can be verified 144 | // in the newly merged tree. 145 | List auditTrail = tree1.auditProof(l3); 146 | assertTrue(MerkleTree.verifyAudit(rootHashAfterAddTree, l3, auditTrail)); 147 | 148 | //Assert that a leaf from the second tree can be verified 149 | // in the newly merged tree. 150 | auditTrail = tree1.auditProof(l7); 151 | assertTrue(MerkleTree.verifyAudit(rootHashAfterAddTree, l7, auditTrail)); 152 | } 153 | 154 | //Helper Function to create a parent MerkleNode from two Strings 155 | private MerkleNode createParentNode(String leftData, String rightData) { 156 | MerkleNode parentNode = new MerkleNode(); 157 | MerkleNode leftNode = new MerkleNode(MerkleHash.create(leftData)); 158 | MerkleNode rightNode = new MerkleNode(MerkleHash.create(rightData)); 159 | 160 | parentNode.setLeftNode(leftNode); 161 | parentNode.setRightNode(rightNode); 162 | 163 | return parentNode; 164 | } 165 | } --------------------------------------------------------------------------------