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  }