├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── tree.go └── tree_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Farhan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # merkletree 2 | 3 | An implementation of a merkle tree based on the specification provided for [Certificate Transparency](https://datatracker.ietf.org/doc/html/rfc6962#section-2.1) 4 | 5 | # Usage 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | 13 | "github.com/arriqaaq/merkletree" 14 | ) 15 | 16 | /* 17 | The binary Merkle Tree with 7 leaves: 18 | 19 | hash 20 | / \ 21 | / \ 22 | / \ 23 | / \ 24 | / \ 25 | k l 26 | / \ / \ 27 | / \ / \ 28 | / \ / \ 29 | g h i j 30 | / \ / \ / \ | 31 | a b c d e f d6 32 | | | | | | | 33 | d0 d1 d2 d3 d4 d5 34 | */ 35 | 36 | 37 | func makeleaves() (D [][]byte) { 38 | for i := 0; i < 7; i++ { 39 | v := "d" + strconv.FormatInt(int64(i), 10) 40 | D = append(D, []byte(v)) 41 | } 42 | return 43 | } 44 | 45 | 46 | func main() { 47 | tree := merkletree.NewTree() 48 | 49 | // Insert 50 | D := makeleaves() 51 | tree := NewTree(D) 52 | 53 | // Root Hash 54 | hash:=tree.Hash() 55 | 56 | // Path 57 | // The audit path for d0 is [b, h, l]. 58 | path := tree.Path(0) 59 | 60 | // Proof 61 | // The consistency proof between hash0 and hash is PROOF(3, D[7]) = [c, 62 | // d, g, l]. c, g are used to verify hash0, and d, l are additionally 63 | // used to show hash is consistent with hash0. 64 | proof := tree.Proof(3) 65 | } 66 | ``` 67 | 68 | # Reference 69 | - [RFC#6962](https://datatracker.ietf.org/doc/html/rfc6962#section-2.1) 70 | - [Codenotary](https://github.com/codenotary/merkletree) 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/arriqaaq/merkletree 2 | 3 | go 1.17 4 | 5 | require github.com/stretchr/testify v1.7.1 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.0 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 7 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /tree.go: -------------------------------------------------------------------------------- 1 | // Implementation as per https://tools.ietf.org/html/rfc6962#section-2.1 2 | 3 | package merkletree 4 | 5 | import ( 6 | "crypto/sha256" 7 | ) 8 | 9 | const ( 10 | LeafPrefix = byte(0) 11 | NodePrefix = byte(1) 12 | ) 13 | 14 | type ( 15 | // Path is a list of nodes required for proving inclusion or consistency. 16 | Path [][sha256.Size]byte 17 | 18 | // Tree implements a general purpose Merkle tree. 19 | Tree struct { 20 | entries [][]byte 21 | } 22 | ) 23 | 24 | func NewTree(entries [][]byte) *Tree { 25 | return &Tree{ 26 | entries: entries, 27 | } 28 | } 29 | 30 | func (t *Tree) Hash() [sha256.Size]byte { 31 | return t.hash(t.entries) 32 | } 33 | 34 | /* 35 | Logs use a binary Merkle Hash Tree for efficient auditing. The 36 | hashing algorithm is SHA-256 [FIPS.180-4] (note that this is fixed 37 | for this experiment, but it is anticipated that each log would be 38 | able to specify a hash algorithm). The input to the Merkle Tree Hash 39 | is a list of data entries; these entries will be hashed to form the 40 | leaves of the Merkle Hash Tree. The output is a single 32-byte 41 | Merkle Tree Hash. Given an ordered list of n inputs, D[n] = {d(0), 42 | d(1), ..., d(n-1)}, the Merkle Tree Hash (MTH) is thus defined as 43 | follows: 44 | */ 45 | func (t *Tree) hash(D [][]byte) [sha256.Size]byte { 46 | n := uint64(len(D)) 47 | /* 48 | The hash of an empty list is the hash of an empty string: 49 | MTH({}) = SHA-256(). 50 | */ 51 | if n == 0 { 52 | return sha256.Sum256(nil) 53 | } 54 | /* 55 | The hash of a list with one entry (also known as a leaf hash) is: 56 | MTH({d(0)}) = SHA-256(0x00 || d(0)). 57 | */ 58 | if n == 1 { 59 | c := []byte{LeafPrefix} 60 | c = append(c, D[0]...) 61 | return sha256.Sum256(c) 62 | } 63 | 64 | /* 65 | For n > 1, let k be the largest power of two smaller than n (i.e., 66 | k < n <= 2k). The Merkle Tree Hash of an n-element list D[n] is then 67 | defined recursively as 68 | 69 | MTH(D[n]) = SHA-256(0x01 || MTH(D[0:k]) || MTH(D[k:n])), 70 | 71 | where || is concatenation and D[k1:k2] denotes the list {d(k1), 72 | d(k1+1),..., d(k2-1)} of length (k2 - k1). (Note that the hash 73 | calculations for leaves and nodes differ. This domain separation is 74 | required to give second preimage resistance.) 75 | 76 | Note that we do not require the length of the input list to be a 77 | power of two. The resulting Merkle Tree may thus not be balanced; 78 | however, its shape is uniquely determined by the number of leaves. 79 | (Note: This Merkle Tree is essentially the same as the history tree 80 | [CrosbyWallach] proposal, except our definition handles non-full 81 | trees differently.) 82 | */ 83 | k := largestPowerOf2LessThan(n) 84 | 85 | c := []byte{NodePrefix} 86 | x := t.hash(D[0:k]) 87 | c = append(c, x[:]...) 88 | x = t.hash(D[k:n]) 89 | c = append(c, x[:]...) 90 | return sha256.Sum256(c) 91 | } 92 | 93 | func largestPowerOf2LessThan(n uint64) uint64 { 94 | if n < 2 { 95 | return 0 96 | } 97 | t := uint64(0) 98 | for i := 0; i < 64; i++ { 99 | c := uint64(1 << i) 100 | if c > n-1 { 101 | return t 102 | } 103 | t = c 104 | } 105 | return 0 106 | } 107 | 108 | /* 109 | A Merkle audit path for a leaf in a Merkle Hash Tree is the shortest 110 | list of additional nodes in the Merkle Tree required to compute the 111 | Merkle Tree Hash for that tree. Each node in the tree is either a 112 | leaf node or is computed from the two nodes immediately below it 113 | (i.e., towards the leaves). At each step up the tree (towards the 114 | root), a node from the audit path is combined with the node computed 115 | so far. In other words, the audit path consists of the list of 116 | missing nodes required to compute the nodes leading from a leaf to 117 | the root of the tree. If the root computed from the audit path 118 | matches the true root, then the audit path is proof that the leaf 119 | exists in the tree. 120 | */ 121 | func (t *Tree) Path(m uint64) (path Path) { 122 | return t.path(m, t.entries) 123 | } 124 | 125 | func (t *Tree) path(m uint64, D [][]byte) Path { 126 | /* 127 | The path for the single leaf in a tree with a one-element input list 128 | D[1] = {d(0)} is empty: 129 | 130 | PATH(0, {d(0)}) = {} 131 | */ 132 | n := uint64(len(D)) 133 | p := make(Path, 0) 134 | if n == 1 && m == 0 { 135 | return p 136 | } 137 | 138 | /* 139 | For n > 1, let k be the largest power of two smaller than n. The 140 | path for the (m+1)th element d(m) in a list of n > m elements is then 141 | defined recursively as 142 | 143 | PATH(m, D[n]) = PATH(m, D[0:k]) : MTH(D[k:n]) for m < k; and 144 | 145 | PATH(m, D[n]) = PATH(m - k, D[k:n]) : MTH(D[0:k]) for m >= k, 146 | 147 | where : is concatenation of lists and D[k1:k2] denotes the length 148 | (k2 - k1) list {d(k1), d(k1+1),..., d(k2-1)} as before. 149 | */ 150 | k := largestPowerOf2LessThan(n) 151 | if m < k { 152 | p = append(p, t.path(m, D[0:k])...) 153 | p = append(p, t.hash(D[k:n])) 154 | } else { 155 | p = append(p, t.path(m-k, D[k:n])...) 156 | p = append(p, t.hash(D[0:k])) 157 | } 158 | return p 159 | } 160 | 161 | /* 162 | Merkle consistency proofs prove the append-only property of the tree. 163 | A Merkle consistency proof for a Merkle Tree Hash MTH(D[n]) and a 164 | previously advertised hash MTH(D[0:m]) of the first m leaves, m <= n, 165 | is the list of nodes in the Merkle Tree required to verify that the 166 | first m inputs D[0:m] are equal in both trees. Thus, a consistency 167 | proof must contain a set of intermediate nodes (i.e., commitments to 168 | inputs) sufficient to verify MTH(D[n]), such that (a subset of) the 169 | same nodes can be used to verify MTH(D[0:m]). We define an algorithm 170 | that outputs the (unique) minimal consistency proof. 171 | */ 172 | func (t *Tree) Proof(m uint64) Path { 173 | return t.proof(m, t.entries) 174 | } 175 | 176 | func (t *Tree) proof(m uint64, D [][]byte) Path { 177 | /* 178 | Given an ordered list of n inputs to the tree, D[n] = {d(0), ..., 179 | d(n-1)}, the Merkle consistency proof PROOF(m, D[n]) for a previous 180 | Merkle Tree Hash MTH(D[0:m]), 0 < m < n, is defined as: 181 | 182 | PROOF(m, D[n]) = SUBPROOF(m, D[n], true) 183 | 184 | The subproof for m = n is empty if m is the value for which PROOF was 185 | originally requested (meaning that the subtree Merkle Tree Hash 186 | MTH(D[0:m]) is known): 187 | 188 | SUBPROOF(m, D[m], true) = {} 189 | */ 190 | n := uint64(len(D)) 191 | if 0 < m && m < n { 192 | return t.subProof(m, D, true) 193 | } 194 | return nil 195 | } 196 | 197 | func (t *Tree) subProof(m uint64, D [][]byte, b bool) Path { 198 | /* 199 | The subproof for m = n is the Merkle Tree Hash committing inputs 200 | D[0:m]; otherwise: 201 | 202 | SUBPROOF(m, D[m], false) = {MTH(D[m])} 203 | 204 | For m < n, let k be the largest power of two smaller than n. The 205 | subproof is then defined recursively. 206 | 207 | If m <= k, the right subtree entries D[k:n] only exist in the current 208 | tree. We prove that the left subtree entries D[0:k] are consistent 209 | and add a commitment to D[k:n]: 210 | 211 | SUBPROOF(m, D[n], b) = SUBPROOF(m, D[0:k], b) : MTH(D[k:n]) 212 | 213 | If m > k, the left subtree entries D[0:k] are identical in both 214 | trees. We prove that the right subtree entries D[k:n] are consistent 215 | and add a commitment to D[0:k]. 216 | 217 | SUBPROOF(m, D[n], b) = SUBPROOF(m - k, D[k:n], false) : MTH(D[0:k]) 218 | 219 | Here, : is a concatenation of lists, and D[k1:k2] denotes the length 220 | (k2 - k1) list {d(k1), d(k1+1),..., d(k2-1)} as before. 221 | 222 | The number of nodes in the resulting proof is bounded above by 223 | ceil(log2(n)) + 1. 224 | 225 | */ 226 | 227 | path := make(Path, 0) 228 | n := uint64(len(D)) 229 | 230 | if m == n { 231 | if !b { 232 | path = append(path, t.hash(D)) 233 | } 234 | return path 235 | } 236 | 237 | if m < n { 238 | k := largestPowerOf2LessThan(n) 239 | 240 | if m <= k { 241 | path = append(path, t.subProof(m, D[0:k], b)...) 242 | path = append(path, t.hash(D[k:n])) 243 | } else { 244 | path = append(path, t.subProof(m-k, D[k:n], false)...) 245 | path = append(path, t.hash(D[0:k])) 246 | } 247 | } 248 | return path 249 | } 250 | -------------------------------------------------------------------------------- /tree_test.go: -------------------------------------------------------------------------------- 1 | // Implementation as per https://tools.ietf.org/html/rfc6962#section-2.1 2 | 3 | package merkletree 4 | 5 | import ( 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | /* 13 | The binary Merkle Tree with 7 leaves: 14 | 15 | hash 16 | / \ 17 | / \ 18 | / \ 19 | / \ 20 | / \ 21 | k l 22 | / \ / \ 23 | / \ / \ 24 | / \ / \ 25 | g h i j 26 | / \ / \ / \ | 27 | a b c d e f d6 28 | | | | | | | 29 | d0 d1 d2 d3 d4 d5 30 | */ 31 | 32 | func makeleaves() (D [][]byte) { 33 | for i := 0; i < 7; i++ { 34 | v := "d" + strconv.FormatInt(int64(i), 10) 35 | D = append(D, []byte(v)) 36 | } 37 | return 38 | } 39 | 40 | func TestAuditPath(t *testing.T) { 41 | D := makeleaves() 42 | tree := NewTree(D) 43 | // The audit path for d0 is [b, h, l]. 44 | path := tree.Path(0) 45 | assert.Len(t, path, 3) 46 | // The audit path for d3 is [c, g, l]. 47 | path = tree.Path(3) 48 | assert.Len(t, path, 3) 49 | // The audit path for d4 is [f, j, k]. 50 | path = tree.Path(4) 51 | assert.Len(t, path, 3) 52 | // The audit path for d6 is [i, k]. 53 | path = tree.Path(6) 54 | assert.Len(t, path, 2) 55 | } 56 | 57 | /* 58 | 59 | The same tree, built incrementally in four steps: 60 | 61 | hash0 hash1=k 62 | / \ / \ 63 | / \ / \ 64 | / \ / \ 65 | g c g h 66 | / \ | / \ / \ 67 | a b d2 a b c d 68 | | | | | | | 69 | d0 d1 d0 d1 d2 d3 70 | 71 | hash2 hash 72 | / \ / \ 73 | / \ / \ 74 | / \ / \ 75 | / \ / \ 76 | / \ / \ 77 | k i k l 78 | / \ / \ / \ / \ 79 | / \ e f / \ / \ 80 | / \ | | / \ / \ 81 | g h d4 d5 g h i j 82 | / \ / \ / \ / \ / \ | 83 | a b c d a b c d e f d6 84 | | | | | | | | | | | 85 | d0 d1 d2 d3 d0 d1 d2 d3 d4 d5 86 | 87 | */ 88 | 89 | func TestConsistencyProof(t *testing.T) { 90 | D := makeleaves() 91 | tree := NewTree(D) 92 | 93 | // The consistency proof between hash0 and hash is PROOF(3, D[7]) = [c, 94 | // d, g, l]. c, g are used to verify hash0, and d, l are additionally 95 | // used to show hash is consistent with hash0. 96 | path := tree.Proof(3) 97 | assert.Len(t, path, 4) 98 | 99 | // The consistency proof between hash1 and hash is PROOF(4, D[7]) = [l]. 100 | // hash can be verified using hash1=k and l. 101 | path = tree.Proof(4) 102 | assert.Len(t, path, 1) 103 | 104 | // The consistency proof between hash2 and hash is PROOF(6, D[7]) = [i, 105 | // j, k]. k, i are used to verify hash2, and j is additionally used to 106 | // show hash is consistent with hash2. 107 | path = tree.Proof(6) 108 | assert.Len(t, path, 3) 109 | } 110 | --------------------------------------------------------------------------------