github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/merkle/log_verifier.go (about)

     1  // Copyright 2017 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  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"math/bits"
    22  
    23  	"github.com/google/trillian/merkle/hashers"
    24  )
    25  
    26  // RootMismatchError occurs when an inclusion proof fails.
    27  type RootMismatchError struct {
    28  	ExpectedRoot   []byte
    29  	CalculatedRoot []byte
    30  }
    31  
    32  func (e RootMismatchError) Error() string {
    33  	return fmt.Sprintf("calculated root:\n%v\n does not match expected root:\n%v", e.CalculatedRoot, e.ExpectedRoot)
    34  }
    35  
    36  // LogVerifier verifies inclusion and consistency proofs for append only logs.
    37  type LogVerifier struct {
    38  	hasher hashers.LogHasher
    39  }
    40  
    41  // NewLogVerifier returns a new LogVerifier for a tree.
    42  func NewLogVerifier(hasher hashers.LogHasher) LogVerifier {
    43  	return LogVerifier{hasher}
    44  }
    45  
    46  // VerifyInclusionProof verifies the correctness of the proof given the passed
    47  // in information about the tree and leaf.
    48  func (v LogVerifier) VerifyInclusionProof(leafIndex, treeSize int64, proof [][]byte, root []byte, leafHash []byte) error {
    49  	calcRoot, err := v.RootFromInclusionProof(leafIndex, treeSize, proof, leafHash)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	if !bytes.Equal(calcRoot, root) {
    54  		return RootMismatchError{
    55  			CalculatedRoot: calcRoot,
    56  			ExpectedRoot:   root,
    57  		}
    58  	}
    59  	return nil
    60  }
    61  
    62  // RootFromInclusionProof calculates the expected tree root given the proof and leaf.
    63  // leafIndex starts at 0.  treeSize is the number of nodes in the tree.
    64  // proof is an array of neighbor nodes from the bottom to the root.
    65  func (v LogVerifier) RootFromInclusionProof(leafIndex, treeSize int64, proof [][]byte, leafHash []byte) ([]byte, error) {
    66  	switch {
    67  	case leafIndex < 0:
    68  		return nil, fmt.Errorf("leafIndex %d < 0", leafIndex)
    69  	case treeSize < 0:
    70  		return nil, fmt.Errorf("treeSize %d < 0", treeSize)
    71  	case leafIndex >= treeSize:
    72  		return nil, fmt.Errorf("leafIndex is beyond treeSize: %d >= %d", leafIndex, treeSize)
    73  	}
    74  	if got, want := len(leafHash), v.hasher.Size(); got != want {
    75  		return nil, fmt.Errorf("leafHash has unexpected size %d, want %d", got, want)
    76  	}
    77  
    78  	inner, border := decompInclProof(leafIndex, treeSize)
    79  	if got, want := len(proof), inner+border; got != want {
    80  		return nil, fmt.Errorf("wrong proof size %d, want %d", got, want)
    81  	}
    82  
    83  	ch := hashChainer(v)
    84  	res := ch.chainInner(leafHash, proof[:inner], leafIndex)
    85  	res = ch.chainBorderRight(res, proof[inner:])
    86  	return res, nil
    87  }
    88  
    89  // VerifyConsistencyProof checks that the passed in consistency proof is valid
    90  // between the passed in tree snapshots. Snapshots are the respective tree
    91  // sizes. Accepts shapshot2 >= snapshot1 >= 0.
    92  func (v LogVerifier) VerifyConsistencyProof(snapshot1, snapshot2 int64, root1, root2 []byte, proof [][]byte) error {
    93  	switch {
    94  	case snapshot1 < 0:
    95  		return fmt.Errorf("snapshot1 (%d) < 0 ", snapshot1)
    96  	case snapshot2 < snapshot1:
    97  		return fmt.Errorf("snapshot2 (%d) < snapshot1 (%d)", snapshot1, snapshot2)
    98  	case snapshot1 == snapshot2:
    99  		if !bytes.Equal(root1, root2) {
   100  			return RootMismatchError{
   101  				CalculatedRoot: root1,
   102  				ExpectedRoot:   root2,
   103  			}
   104  		} else if len(proof) > 0 {
   105  			return errors.New("root1 and root2 match, but proof is non-empty")
   106  		}
   107  		return nil // Proof OK.
   108  	case snapshot1 == 0:
   109  		// Any snapshot greater than 0 is consistent with snapshot 0.
   110  		if len(proof) > 0 {
   111  			return fmt.Errorf("expected empty proof, but got %d components", len(proof))
   112  		}
   113  		return nil // Proof OK.
   114  	case len(proof) == 0:
   115  		return errors.New("empty proof")
   116  	}
   117  
   118  	inner, border := decompInclProof(snapshot1-1, snapshot2)
   119  	shift := bits.TrailingZeros64(uint64(snapshot1))
   120  	inner -= shift // Note: shift < inner if snapshot1 < snapshot2.
   121  
   122  	// The proof includes the root hash for the sub-tree of size 2^shift.
   123  	seed, start := proof[0], 1
   124  	if snapshot1 == 1<<uint(shift) { // Unless snapshot1 is that very 2^shift.
   125  		seed, start = root1, 0
   126  	}
   127  	if got, want := len(proof), start+inner+border; got != want {
   128  		return fmt.Errorf("wrong proof size %d, want %d", got, want)
   129  	}
   130  	proof = proof[start:]
   131  	// Now len(proof) == inner+border, and proof is effectively a suffix of
   132  	// inclusion proof for entry |snapshot1-1| in a tree of size |snapshot2|.
   133  
   134  	// Verify the first root.
   135  	ch := hashChainer(v)
   136  	mask := (snapshot1 - 1) >> uint(shift) // Start chaining from level |shift|.
   137  	hash1 := ch.chainInnerRight(seed, proof[:inner], mask)
   138  	hash1 = ch.chainBorderRight(hash1, proof[inner:])
   139  	if !bytes.Equal(hash1, root1) {
   140  		return RootMismatchError{
   141  			CalculatedRoot: hash1,
   142  			ExpectedRoot:   root1,
   143  		}
   144  	}
   145  
   146  	// Verify the second root.
   147  	hash2 := ch.chainInner(seed, proof[:inner], mask)
   148  	hash2 = ch.chainBorderRight(hash2, proof[inner:])
   149  	if !bytes.Equal(hash2, root2) {
   150  		return RootMismatchError{
   151  			CalculatedRoot: hash2,
   152  			ExpectedRoot:   root2,
   153  		}
   154  	}
   155  
   156  	return nil // Proof OK.
   157  }
   158  
   159  // VerifiedPrefixHashFromInclusionProof calculates a root hash over leaves
   160  // [0..subSize), based on the inclusion |proof| and |leafHash| for a leaf at
   161  // index |subSize-1| in a tree of the specified |size| with the passed in
   162  // |root| hash.
   163  // Returns an error if the |proof| verification fails. The resulting smaller
   164  // tree's root hash is trusted iff the bigger tree's |root| hash is trusted.
   165  func (v LogVerifier) VerifiedPrefixHashFromInclusionProof(
   166  	subSize, size int64,
   167  	proof [][]byte, root []byte, leafHash []byte,
   168  ) ([]byte, error) {
   169  	if subSize <= 0 {
   170  		return nil, fmt.Errorf("subtree size is %d, want > 0", subSize)
   171  	}
   172  	leaf := subSize - 1
   173  	if err := v.VerifyInclusionProof(leaf, size, proof, root, leafHash); err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	inner := innerProofSize(leaf, size)
   178  	ch := hashChainer(v)
   179  	res := ch.chainInnerRight(leafHash, proof[:inner], leaf)
   180  	res = ch.chainBorderRight(res, proof[inner:])
   181  	return res, nil
   182  }
   183  
   184  // decompInclProof breaks down inclusion proof for a leaf at the specified
   185  // |index| in a tree of the specified |size| into 2 components. The splitting
   186  // point between them is where paths to leaves |index| and |size-1| diverge.
   187  // Returns lengths of the bottom and upper proof parts correspondingly. The sum
   188  // of the two determines the correct length of the inclusion proof.
   189  func decompInclProof(index, size int64) (int, int) {
   190  	inner := innerProofSize(index, size)
   191  	border := bits.OnesCount64(uint64(index) >> uint(inner))
   192  	return inner, border
   193  }
   194  func innerProofSize(index, size int64) int {
   195  	return bits.Len64(uint64(index ^ (size - 1)))
   196  }