1- // Copyright 2017 Google LLC. All Rights Reserved.
1+ // Copyright 2022 Google LLC. All Rights Reserved.
22//
33// Licensed under the Apache License, Version 2.0 (the "License");
44// you may not use this file except in compliance with the License.
@@ -16,161 +16,140 @@ package proof
1616
1717import (
1818 "bytes"
19- "errors"
2019 "fmt"
2120 "math/bits"
2221
23- "github.com/transparency-dev/merkle"
22+ "github.com/transparency-dev/merkle/compact "
2423)
2524
26- // RootMismatchError occurs when an inclusion proof fails.
25+ // RootMismatchError is an error occuring when a proof verification fails.
2726type RootMismatchError struct {
28- ExpectedRoot []byte
29- CalculatedRoot []byte
27+ Size uint64 // The size at which the root hash mismatch happened.
28+ Computed []byte // The computed root hash at this size.
29+ Expected []byte // The expected root hash at this size.
3030}
3131
32+ // Error returns the error string for RootMismatchError.
3233func (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+ return fmt .Sprintf ("root hash at size %d mismatched: computed %x, expected %x " , e .Size , e .Computed , e . Expected )
3435}
3536
36- func verifyMatch (calculated , expected []byte ) error {
37- if ! bytes .Equal (calculated , expected ) {
38- return RootMismatchError {ExpectedRoot : expected , CalculatedRoot : calculated }
37+ func verifyMatch (size uint64 , computed , expected []byte ) error {
38+ if ! bytes .Equal (computed , expected ) {
39+ return RootMismatchError {Size : size , Computed : computed , Expected : expected }
3940 }
4041 return nil
4142}
4243
44+ // NodeHasher allows computing hashes of internal nodes of the Merkle tree.
45+ type NodeHasher interface {
46+ // HashChildren returns hash of a tree node based on hashes of its children.
47+ HashChildren (left , right []byte ) []byte
48+ }
49+
4350// VerifyInclusion verifies the correctness of the inclusion proof for the leaf
4451// with the specified hash and index, relatively to the tree of the given size
45- // and root hash. Requires 0 <= index < size.
46- func VerifyInclusion ( hasher merkle. LogHasher , index , size uint64 , leafHash [] byte , proof [][] byte , root [] byte ) error {
47- calcRoot , err := RootFromInclusionProof ( hasher , index , size , leafHash , proof )
48- if err != nil {
49- return err
52+ // and root hash. Requires 0 <= index < size. Returns RootMismatchError if the
53+ // computed root hash does not match the provided one.
54+ func VerifyInclusion ( nh NodeHasher , index , size uint64 , hash [] byte , proof [][] byte , root [] byte ) error {
55+ if index >= size {
56+ return fmt . Errorf ( "index %d out of range for size %d" , index , size )
5057 }
51- return verifyMatch ( calcRoot , root )
58+ return verify ( nh , index , 0 , size , hash , proof , root )
5259}
5360
54- // RootFromInclusionProof calculates the expected root hash for a tree of the
55- // given size, provided a leaf index and hash with the corresponding inclusion
56- // proof. Requires 0 <= index < size.
57- func RootFromInclusionProof (hasher merkle.LogHasher , index , size uint64 , leafHash []byte , proof [][]byte ) ([]byte , error ) {
58- if index >= size {
59- return nil , fmt .Errorf ("index is beyond size: %d >= %d" , index , size )
61+ // VerifyConsistency verifies that the consistency proof is valid between the
62+ // two given tree sizes, with the corresponding root hashes.
63+ // Requires 0 <= size1 <= size2. Returns RootMismatchError if any of the
64+ // computed root hashes at size1 or size2 does not match the provided one.
65+ func VerifyConsistency (nh NodeHasher , size1 , size2 uint64 , proof [][]byte , root1 , root2 []byte ) error {
66+ if size1 > size2 {
67+ return fmt .Errorf ("tree size %d > %d" , size1 , size2 )
6068 }
61- if got , want := len ( leafHash ), hasher . Size (); got != want {
62- return nil , fmt .Errorf ("leafHash has unexpected size %d, want %d " , got , want )
69+ if ( size1 == size2 || size1 == 0 ) && len ( proof ) != 0 {
70+ return fmt .Errorf ("incorrect proof size: got %d, want 0 " , len ( proof ) )
6371 }
64-
65- inner , border := decompInclProof (index , size )
66- if got , want := len (proof ), inner + border ; got != want {
67- return nil , fmt .Errorf ("wrong proof size %d, want %d" , got , want )
72+ if size1 == size2 {
73+ return verifyMatch (size1 , root1 , root2 )
6874 }
69-
70- res := chainInner (hasher , leafHash , proof [:inner ], index )
71- res = chainBorderRight (hasher , res , proof [inner :])
72- return res , nil
73- }
74-
75- // VerifyConsistency checks that the passed-in consistency proof is valid
76- // between the passed in tree sizes, with respect to the corresponding root
77- // hashes. Requires 0 <= size1 <= size2.
78- func VerifyConsistency (hasher merkle.LogHasher , size1 , size2 uint64 , proof [][]byte , root1 , root2 []byte ) error {
79- switch {
80- case size2 < size1 :
81- return fmt .Errorf ("size2 (%d) < size1 (%d)" , size1 , size2 )
82- case size1 == size2 :
83- if len (proof ) > 0 {
84- return errors .New ("size1=size2, but proof is not empty" )
85- }
86- return verifyMatch (root1 , root2 )
87- case size1 == 0 :
88- // Any size greater than 0 is consistent with size 0.
89- if len (proof ) > 0 {
90- return fmt .Errorf ("expected empty proof, but got %d components" , len (proof ))
91- }
92- return nil // Proof OK.
93- case len (proof ) == 0 :
94- return errors .New ("empty proof" )
75+ if size1 == 0 {
76+ return nil
9577 }
9678
97- inner , border := decompInclProof ( size1 - 1 , size2 )
98- shift := bits .TrailingZeros64 (size1 )
99- inner -= shift // Note: shift < inner if size1 < size2.
100-
101- // The proof includes the root hash for the sub-tree of size 2^shift.
102- seed , start := proof [ 0 ], 1
103- if size1 == 1 << uint ( shift ) { // Unless size1 is that very 2^shift .
104- seed , start = root1 , 0
105- }
106- if got , want := len ( proof ), start + inner + border ; got != want {
107- return fmt . Errorf ( "wrong proof size %d, want %d" , got , want )
79+ // Find the root of the biggest perfect subtree that ends at size1.
80+ level := uint ( bits .TrailingZeros64 (size1 ) )
81+ index := ( size1 - 1 ) >> level
82+ // The consistency proof consists of this node (except if size1 is a power of
83+ // two, in which case adding this node would be redundant because the client
84+ // is assumed to know it from a checkpoint), and nodes of the inclusion proof
85+ // into this node in the tree of size2 .
86+
87+ // Handle the case when size1 is a power of 2.
88+ if index == 0 {
89+ return verify ( nh , index , level , size2 , root1 , proof , root2 )
10890 }
109- proof = proof [start :]
110- // Now len(proof) == inner+border, and proof is effectively a suffix of
111- // inclusion proof for entry |size1-1| in a tree of size |size2|.
11291
113- // Verify the first root.
114- mask := (size1 - 1 ) >> uint (shift ) // Start chaining from level |shift|.
115- hash1 := chainInnerRight (hasher , seed , proof [:inner ], mask )
116- hash1 = chainBorderRight (hasher , hash1 , proof [inner :])
117- if err := verifyMatch (hash1 , root1 ); err != nil {
92+ // Otherwise, the consistency proof is equivalent to an inclusion proof of
93+ // its first hash. Verify it below.
94+ if got , want := len (proof ), 1 + bits .Len64 (size2 - 1 )- int (level ); got != want {
95+ return fmt .Errorf ("incorrect proof size: %d, want %d" , got , want )
96+ }
97+ if err := verify (nh , index , level , size2 , proof [0 ], proof [1 :], root2 ); err != nil {
11898 return err
11999 }
120100
121- // Verify the second root.
122- hash2 := chainInner (hasher , seed , proof [:inner ], mask )
123- hash2 = chainBorderRight (hasher , hash2 , proof [inner :])
124- return verifyMatch (hash2 , root2 )
101+ inner := bits .Len64 (index ^ (size2 >> level )) - 1
102+ hash := proof [0 ]
103+ for i , h := range proof [1 : 1 + inner ] {
104+ if (index >> uint (i ))& 1 == 1 {
105+ hash = nh .HashChildren (h , hash )
106+ }
107+ }
108+ for _ , h := range proof [1 + inner :] {
109+ hash = nh .HashChildren (h , hash )
110+ }
111+ return verifyMatch (size1 , hash , root1 )
125112}
126113
127- // decompInclProof breaks down inclusion proof for a leaf at the specified
128- // |index| in a tree of the specified |size| into 2 components. The splitting
129- // point between them is where paths to leaves |index| and |size-1| diverge.
130- // Returns lengths of the bottom and upper proof parts correspondingly. The sum
131- // of the two determines the correct length of the inclusion proof.
132- func decompInclProof (index , size uint64 ) (int , int ) {
133- inner := innerProofSize (index , size )
134- border := bits .OnesCount64 (index >> uint (inner ))
135- return inner , border
136- }
114+ func verify (nh NodeHasher , index uint64 , level uint , size uint64 , hash []byte , proof [][]byte , root []byte ) error {
115+ // Compute the `fork` node, where the path from root to (level, index) node
116+ // diverges from the path to (0, size).
117+ //
118+ // The sibling of this node is the ephemeral node which represents a subtree
119+ // that is not complete in the tree of the given size. To compute the hash
120+ // of the ephemeral node, we need all the non-ephemeral nodes that cover the
121+ // same range of leaves.
122+ //
123+ // The `inner` variable is how many layers up from (level, index) the `fork`
124+ // and the ephemeral nodes are.
125+ inner := bits .Len64 (index ^ (size >> level )) - 1
126+ fork := compact .NewNodeID (level + uint (inner ), index >> inner )
127+
128+ begin , end := fork .Coverage ()
129+ left := compact .RangeSize (0 , begin )
130+ right := 1
131+ if end == size { // No ephemeral nodes.
132+ right = 0
133+ }
137134
138- func innerProofSize ( index , size uint64 ) int {
139- return bits . Len64 ( index ^ ( size - 1 ) )
140- }
135+ if got , want := len ( proof ), inner + right + left ; got != want {
136+ return fmt . Errorf ( "incorrect proof size: %d, want %d" , got , want )
137+ }
141138
142- // chainInner computes a subtree hash for a node on or below the tree's right
143- // border. Assumes |proof| hashes are ordered from lower levels to upper, and
144- // |seed| is the initial subtree/leaf hash on the path located at the specified
145- // |index| on its level.
146- func chainInner (hasher merkle.LogHasher , seed []byte , proof [][]byte , index uint64 ) []byte {
147- for i , h := range proof {
148- if (index >> uint (i ))& 1 == 0 {
149- seed = hasher .HashChildren (seed , h )
139+ node := compact .NewNodeID (level , index )
140+ for _ , h := range proof [:inner ] {
141+ if node .Index & 1 == 0 {
142+ hash = nh .HashChildren (hash , h )
150143 } else {
151- seed = hasher .HashChildren (h , seed )
144+ hash = nh .HashChildren (h , hash )
152145 }
146+ node = node .Parent ()
153147 }
154- return seed
155- }
156-
157- // chainInnerRight computes a subtree hash like chainInner, but only takes
158- // hashes to the left from the path into consideration, which effectively means
159- // the result is a hash of the corresponding earlier version of this subtree.
160- func chainInnerRight (hasher merkle.LogHasher , seed []byte , proof [][]byte , index uint64 ) []byte {
161- for i , h := range proof {
162- if (index >> uint (i ))& 1 == 1 {
163- seed = hasher .HashChildren (h , seed )
164- }
148+ if right == 1 {
149+ hash = nh .HashChildren (hash , proof [inner ])
165150 }
166- return seed
167- }
168-
169- // chainBorderRight chains proof hashes along tree borders. This differs from
170- // inner chaining because |proof| contains only left-side subtree hashes.
171- func chainBorderRight (hasher merkle.LogHasher , seed []byte , proof [][]byte ) []byte {
172- for _ , h := range proof {
173- seed = hasher .HashChildren (h , seed )
151+ for _ , h := range proof [inner + right :] {
152+ hash = nh .HashChildren (h , hash )
174153 }
175- return seed
154+ return verifyMatch ( size , hash , root )
176155}
0 commit comments