1
- // Copyright 2017 Google LLC. All Rights Reserved.
1
+ // Copyright 2022 Google LLC. All Rights Reserved.
2
2
//
3
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
4
// you may not use this file except in compliance with the License.
@@ -16,161 +16,140 @@ package proof
16
16
17
17
import (
18
18
"bytes"
19
- "errors"
20
19
"fmt"
21
20
"math/bits"
22
21
23
- "github.com/transparency-dev/merkle"
22
+ "github.com/transparency-dev/merkle/compact "
24
23
)
25
24
26
- // RootMismatchError occurs when an inclusion proof fails.
25
+ // RootMismatchError is an error occuring when a proof verification fails.
27
26
type 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.
30
30
}
31
31
32
+ // Error returns the error string for RootMismatchError.
32
33
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
+ return fmt .Sprintf ("root hash at size %d mismatched: computed %x, expected %x " , e .Size , e .Computed , e . Expected )
34
35
}
35
36
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 }
39
40
}
40
41
return nil
41
42
}
42
43
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
+
43
50
// VerifyInclusion verifies the correctness of the inclusion proof for the leaf
44
51
// 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 )
50
57
}
51
- return verifyMatch ( calcRoot , root )
58
+ return verify ( nh , index , 0 , size , hash , proof , root )
52
59
}
53
60
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 )
60
68
}
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 ) )
63
71
}
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 )
68
74
}
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
95
77
}
96
78
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 )
108
90
}
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|.
112
91
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 {
118
98
return err
119
99
}
120
100
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 )
125
112
}
126
113
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
+ }
137
134
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
+ }
141
138
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 )
150
143
} else {
151
- seed = hasher .HashChildren (h , seed )
144
+ hash = nh .HashChildren (h , hash )
152
145
}
146
+ node = node .Parent ()
153
147
}
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 ])
165
150
}
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 )
174
153
}
175
- return seed
154
+ return verifyMatch ( size , hash , root )
176
155
}
0 commit comments