github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/merkle/merkle_path_test.go (about) 1 // Copyright 2016 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package merkle 16 17 import ( 18 "fmt" 19 "testing" 20 21 "github.com/google/trillian/storage" 22 ) 23 24 type auditPathTestData struct { 25 treeSize int64 26 leafIndex int64 27 expectedPath []NodeFetch 28 } 29 30 type consistencyProofTestData struct { 31 priorTreeSize int64 32 treeSize int64 33 expectedProof []NodeFetch 34 } 35 36 var lastNodeWrittenVec = []struct { 37 ts int64 38 result string 39 }{ 40 {3, "101"}, 41 {5, "1001"}, 42 {11, "10101"}, 43 {14, "11011"}, 44 {15, "11101"}, 45 } 46 47 // For the path test tests at tree sizes up to this value 48 const testUpToTreeSize = 99 49 50 // Expected inclusion proof paths built by examination of the example 7 leaf tree in RFC 6962: 51 // 52 // hash <== Level 3 53 // / \ 54 // / \ 55 // / \ 56 // / \ 57 // / \ 58 // k l <== Level 2 59 // / \ / \ 60 // / \ / \ 61 // / \ / \ 62 // g h i [ ] <== Level 1 63 // / \ / \ / \ / 64 // a b c d e f j <== Level 0 65 // | | | | | | | 66 // d0 d1 d2 d3 d4 d5 d6 67 // 68 // When comparing with the document remember that our storage node layers are always 69 // populated from the bottom up, hence the gap at level 1, index 3 in the above picture. 70 71 var expectedPathSize7Index0 = []NodeFetch{ // from a 72 MustCreateNodeFetchForTreeCoords(0, 1, 64, false), // b 73 MustCreateNodeFetchForTreeCoords(1, 1, 64, false), // h 74 MustCreateNodeFetchForTreeCoords(2, 1, 64, false), // l 75 } 76 var expectedPathSize7Index3 = []NodeFetch{ // from d 77 MustCreateNodeFetchForTreeCoords(0, 2, 64, false), // c 78 MustCreateNodeFetchForTreeCoords(1, 0, 64, false), // g 79 MustCreateNodeFetchForTreeCoords(2, 1, 64, false), // l 80 } 81 var expectedPathSize7Index4 = []NodeFetch{ // from e 82 MustCreateNodeFetchForTreeCoords(0, 5, 64, false), // f 83 MustCreateNodeFetchForTreeCoords(0, 6, 64, false), // j 84 MustCreateNodeFetchForTreeCoords(2, 0, 64, false), // k 85 } 86 var expectedPathSize7Index6 = []NodeFetch{ // from j 87 MustCreateNodeFetchForTreeCoords(1, 2, 64, false), // i 88 MustCreateNodeFetchForTreeCoords(2, 0, 64, false), // k 89 } 90 91 // Expected consistency proofs built from the examples in RFC 6962. Again, in our implementation 92 // node layers are filled from the bottom upwards. 93 var expectedConsistencyProofFromSize1To2 = []NodeFetch{ 94 // hash1=g 95 // / \ 96 // hash0=a => a b 97 // | | | 98 // d0 d0 d1 99 MustCreateNodeFetchForTreeCoords(0, 1, 64, false), // b 100 } 101 var expectedConsistencyProofFromSize1To4 = []NodeFetch{ 102 // 103 // 104 // hash0=a => hash1=k 105 // | / \ 106 // d0 / \ 107 // / \ 108 // / \ 109 // g h 110 // / \ / \ 111 // a b c d 112 // | | | | 113 // d0 d1 d2 d3 114 // 115 // 116 MustCreateNodeFetchForTreeCoords(0, 1, 64, false), // b 117 MustCreateNodeFetchForTreeCoords(1, 1, 64, false), // h 118 } 119 var expectedConsistencyProofFromSize3To7 = []NodeFetch{ 120 // hash 121 // / \ 122 // / \ 123 // / \ 124 // / \ 125 // => / \ 126 // hash0 k l 127 // / \ / \ / \ 128 // / \ / \ / \ 129 // / \ / \ / \ 130 // g [ ] g h i [ ] 131 // / \ / / \ / \ / \ / 132 // a b c a b c d e f j 133 // | | | | | | | | | | 134 // d0 d1 d2 d0 d1 d2 d3 d4 d5 d6 135 MustCreateNodeFetchForTreeCoords(0, 2, 64, false), // c 136 MustCreateNodeFetchForTreeCoords(0, 3, 64, false), // d 137 MustCreateNodeFetchForTreeCoords(1, 0, 64, false), // g 138 MustCreateNodeFetchForTreeCoords(2, 1, 64, false), // l 139 } 140 var expectedConsistencyProofFromSize4To7 = []NodeFetch{ 141 // hash 142 // / \ 143 // / \ 144 // / \ 145 // / \ 146 // => / \ 147 // hash1=k k l 148 // / \ / \ / \ 149 // / \ / \ / \ 150 // / \ / \ / \ 151 // g h g h i [ ] 152 // / \ / \ / \ / \ / \ / 153 // a b c d a b c d e f j 154 // | | | | | | | | | | | 155 // d0 d1 d2 d3 d0 d1 d2 d3 d4 d5 d6 156 MustCreateNodeFetchForTreeCoords(2, 1, 64, false), // l 157 } 158 var expectedConsistencyProofFromSize6To7 = []NodeFetch{ 159 // hash2 hash 160 // / \ / \ 161 // / \ / \ 162 // / \ / \ 163 // / \ / \ 164 // / \ => / \ 165 // k [ ] k l 166 // / \ / / \ / \ 167 // / \ / / \ / \ 168 // / \ | / \ / \ 169 // g h i g h i [ ] 170 // / \ / \ / \ / \ / \ / \ / 171 // a b c d e f a b c d e f j 172 // | | | | | | | | | | | | | 173 // d0 d1 d2 d3 d4 d5 d0 d1 d2 d3 d4 d5 d6 174 MustCreateNodeFetchForTreeCoords(1, 2, 64, false), // i 175 MustCreateNodeFetchForTreeCoords(0, 6, 64, false), // j 176 MustCreateNodeFetchForTreeCoords(2, 0, 64, false), // k 177 } 178 var expectedConsistencyProofFromSize2To8 = []NodeFetch{ 179 // hash8 180 // / \ 181 // / \ 182 // / \ 183 // / \ 184 // => / \ 185 // k l 186 // / \ / \ 187 // / \ / \ 188 // hash2= / \ / \ 189 // g g h i n 190 // / \ / \ / \ / \ / \ 191 // a b a b c d e f j m 192 // | | | | | | | | | | 193 // d0 d1 d0 d1 d2 d3 d4 d5 d6 d7 194 MustCreateNodeFetchForTreeCoords(1, 1, 64, false), // h 195 MustCreateNodeFetchForTreeCoords(2, 1, 64, false), // l 196 } 197 198 // These should all successfully compute the expected path 199 var pathTests = []auditPathTestData{ 200 {1, 0, []NodeFetch{}}, 201 {7, 3, expectedPathSize7Index3}, 202 {7, 6, expectedPathSize7Index6}, 203 {7, 0, expectedPathSize7Index0}, 204 {7, 4, expectedPathSize7Index4}, 205 } 206 207 // These should all fail 208 var pathTestBad = []auditPathTestData{ 209 {0, 1, []NodeFetch{}}, 210 {1, 2, []NodeFetch{}}, 211 {0, 3, []NodeFetch{}}, 212 {-1, 3, []NodeFetch{}}, 213 {7, -1, []NodeFetch{}}, 214 {7, 8, []NodeFetch{}}, 215 } 216 217 // These should compute the expected consistency proofs 218 var consistencyTests = []consistencyProofTestData{ 219 {1, 2, expectedConsistencyProofFromSize1To2}, 220 {1, 4, expectedConsistencyProofFromSize1To4}, 221 {6, 7, expectedConsistencyProofFromSize6To7}, 222 {3, 7, expectedConsistencyProofFromSize3To7}, 223 {4, 7, expectedConsistencyProofFromSize4To7}, 224 {2, 8, expectedConsistencyProofFromSize2To8}, 225 {1, 1, []NodeFetch{}}, 226 {2, 2, []NodeFetch{}}, 227 {3, 3, []NodeFetch{}}, 228 {4, 4, []NodeFetch{}}, 229 {5, 5, []NodeFetch{}}, 230 {7, 7, []NodeFetch{}}, 231 {8, 8, []NodeFetch{}}, 232 } 233 234 // These should all fail to provide proofs 235 var consistencyTestsBad = []consistencyProofTestData{ 236 {0, -1, []NodeFetch{}}, 237 {-10, 0, []NodeFetch{}}, 238 {-1, -1, []NodeFetch{}}, 239 {0, 0, []NodeFetch{}}, 240 {9, 8, []NodeFetch{}}, 241 } 242 243 func TestCalcInclusionProofNodeAddresses(t *testing.T) { 244 for _, testCase := range pathTests { 245 path, err := CalcInclusionProofNodeAddresses(testCase.treeSize, testCase.leafIndex, testCase.treeSize, 64) 246 247 if err != nil { 248 t.Fatalf("unexpected error calculating path %v: %v", testCase, err) 249 } 250 251 comparePaths(t, fmt.Sprintf("i(%d,%d)", testCase.leafIndex, testCase.treeSize), path, testCase.expectedPath) 252 } 253 } 254 255 func TestCalcInclusionProofNodeAddressesBadRanges(t *testing.T) { 256 for _, testCase := range pathTestBad { 257 _, err := CalcInclusionProofNodeAddresses(testCase.treeSize, testCase.leafIndex, testCase.treeSize, 64) 258 259 if err == nil { 260 t.Fatalf("incorrectly accepted bad params: %v", testCase) 261 } 262 } 263 } 264 265 func TestCalcInclusionProofNodeAddressesRejectsBadBitLen(t *testing.T) { 266 _, err := CalcInclusionProofNodeAddresses(7, 3, 7, -64) 267 268 if err == nil { 269 t.Fatal("incorrectly accepted -ve maxBitLen") 270 } 271 } 272 273 func TestCalcConsistencyProofNodeAddresses(t *testing.T) { 274 for _, testCase := range consistencyTests { 275 proof, err := CalcConsistencyProofNodeAddresses(testCase.priorTreeSize, testCase.treeSize, testCase.treeSize, 64) 276 277 if err != nil { 278 t.Fatalf("failed to calculate consistency proof from %d to %d: %v", testCase.priorTreeSize, testCase.treeSize, err) 279 } 280 281 comparePaths(t, fmt.Sprintf("c(%d, %d)", testCase.priorTreeSize, testCase.treeSize), proof, testCase.expectedProof) 282 } 283 } 284 285 func TestCalcConsistencyProofNodeAddressesBadInputs(t *testing.T) { 286 for _, testCase := range consistencyTestsBad { 287 _, err := CalcConsistencyProofNodeAddresses(testCase.priorTreeSize, testCase.treeSize, testCase.treeSize, 64) 288 289 if err == nil { 290 t.Fatalf("consistency path calculation accepted bad input: %v", testCase) 291 } 292 } 293 } 294 295 func TestCalcConsistencyProofNodeAddressesRejectsBadBitLen(t *testing.T) { 296 _, err := CalcConsistencyProofNodeAddresses(6, 7, 7, -1) 297 _, err2 := CalcConsistencyProofNodeAddresses(6, 7, 7, 0) 298 299 if err == nil || err2 == nil { 300 t.Fatalf("consistency path calculation accepted bad bitlen: %v %v", err, err2) 301 } 302 } 303 304 func comparePaths(t *testing.T, desc string, got, expected []NodeFetch) { 305 if len(expected) != len(got) { 306 t.Fatalf("%s: expected %d nodes in path but got %d: %v", desc, len(expected), len(got), got) 307 } 308 309 for i := 0; i < len(expected); i++ { 310 if !expected[i].Equivalent(got[i]) { 311 t.Fatalf("%s: expected node %v (%v) at position %d but got %v (%v)", desc, expected[i], expected[i].NodeID.CoordString(), i, got[i], got[i].NodeID.CoordString()) 312 } 313 } 314 } 315 316 func TestLastNodeWritten(t *testing.T) { 317 for _, testCase := range lastNodeWrittenVec { 318 str := "" 319 for d := int64(len(testCase.result) - 1); d >= 0; d-- { 320 if lastNodePresent(d, testCase.ts) { 321 str += "1" 322 } else { 323 str += "0" 324 } 325 } 326 327 if got, want := str, testCase.result; got != want { 328 t.Errorf("lastNodeWritten(%d) got: %s, want: %s", testCase.ts, got, want) 329 } 330 } 331 } 332 333 func TestInclusionSucceedsUpToTreeSize(t *testing.T) { 334 for ts := 1; ts < testUpToTreeSize; ts++ { 335 for i := ts; i < ts; i++ { 336 if _, err := CalcInclusionProofNodeAddresses(int64(ts), int64(i), int64(ts), 64); err != nil { 337 t.Errorf("CalcInclusionProofNodeAddresses(ts:%d, i:%d) = %v", ts, i, err) 338 } 339 } 340 } 341 } 342 343 func TestConsistencySucceedsUpToTreeSize(t *testing.T) { 344 for s1 := 1; s1 < testUpToTreeSize; s1++ { 345 for s2 := s1 + 1; s2 < testUpToTreeSize; s2++ { 346 if _, err := CalcConsistencyProofNodeAddresses(int64(s1), int64(s2), int64(s2), 64); err != nil { 347 t.Errorf("CalcConsistencyProofNodeAddresses(%d, %d) = %v", s1, s2, err) 348 } 349 } 350 } 351 } 352 353 func MustCreateNodeFetchForTreeCoords(depth, index int64, maxPathBits int, rehash bool) NodeFetch { 354 n, err := storage.NewNodeIDForTreeCoords(depth, index, maxPathBits) 355 if err != nil { 356 panic(err) 357 } 358 return NodeFetch{NodeID: n, Rehash: rehash} 359 }