@@ -112,122 +112,145 @@ func FromBytesBatchProof(b []byte) (BatchProof, error) {
112
112
// TODO OH WAIT -- this is not how to to it! Don't hash all the way up to the
113
113
// roots to verify -- just hash up to any populated node! Saves a ton of CPU!
114
114
115
- // verifyBatchProof takes a block proof and reconstructs / verifies it.
116
- // takes a blockproof to verify, and the known correct roots to check against.
117
- // also takes the number of leaves and forest rows (those are redundant
118
- // if we don't do weird stuff with overly-high forests, which we might)
119
- // it returns a bool of whether the proof worked, and a map of the sparse
120
- // forest in the blockproof
121
- func verifyBatchProof (
122
- bp BatchProof , roots []Hash ,
123
- numLeaves uint64 , rows uint8 ) (bool , map [uint64 ]Hash ) {
124
-
125
- // if nothing to prove, it worked
115
+ // verifyBatchProof verifies a batchproof by checking against the set of known correct roots.
116
+ // Takes a BatchProof, the accumulator roots, and the number of leaves in the forest.
117
+ // Returns wether or not the proof verified correctly, the partial proof tree,
118
+ // and the subset of roots that was computed.
119
+ func verifyBatchProof (bp BatchProof , roots []Hash , numLeaves uint64 ,
120
+ // cached should be a function that fetches nodes from the pollard and indicates whether they
121
+ // exist or not, this is only useful for the pollard and nil should be passed for the forest.
122
+ cached func (pos uint64 ) (bool , Hash )) (bool , [][3 ]node , []node ) {
126
123
if len (bp .Targets ) == 0 {
127
- return true , nil
124
+ return true , nil , nil
128
125
}
129
126
130
- // Construct a map with positions to hashes
131
- proofmap , err := bp .Reconstruct (numLeaves , rows )
132
- if err != nil {
133
- fmt .Printf ("VerifyBlockProof Reconstruct ERROR %s\n " , err .Error ())
134
- return false , proofmap
127
+ if cached == nil {
128
+ cached = func (_ uint64 ) (bool , Hash ) { return false , empty }
135
129
}
136
130
137
- rootPositions , rootRows := getRootsReverse (numLeaves , rows )
138
-
139
- // partial forest is built, go through and hash everything to make sure
140
- // you get the right roots
131
+ rows := treeRows (numLeaves )
132
+ proofPositions , computablePositions := ProofPositions (bp .Targets , numLeaves , rows )
133
+ // targetNodes holds nodes that are known, on the bottom row those are the targets,
134
+ // on the upper rows it holds computed nodes.
135
+ // rootCandidates holds the roots that where computed, and have to be compared to the actual roots
136
+ // at the end.
137
+ targetNodes := make ([]node , 0 , len (bp .Targets )* int (rows ))
138
+ rootCandidates := make ([]node , 0 , len (roots ))
139
+ // trees is a slice of 3-Tuples, each tuple represents a parent and its children.
140
+ // tuple[0] is the parent, tuple[1] is the left child and tuple[2] is the right child.
141
+ // trees holds the entire proof tree of the batchproof in this way, sorted by the tuple[0].
142
+ trees := make ([][3 ]node , 0 , len (computablePositions ))
143
+ // initialise the targetNodes for row 0.
144
+ // TODO: this would be more straight forward if bp.Proofs wouldn't contain the targets
145
+ proofHashes := make ([]Hash , 0 , len (proofPositions ))
146
+ targets := bp .Targets
147
+ var targetsMatched uint64
148
+ for len (targets ) > 0 {
149
+ // check if the target is the row 0 root.
150
+ // this is the case if its the last leaf (pos==numLeaves-1)
151
+ // AND the tree has a root at row 0 (numLeaves&1==1)
152
+ if targets [0 ] == numLeaves - 1 && numLeaves & 1 == 1 {
153
+ // target is the row 0 root, append it to the root candidates.
154
+ rootCandidates = append (rootCandidates , node {Val : roots [0 ], Pos : targets [0 ]})
155
+ bp .Proof = bp .Proof [1 :]
156
+ break
157
+ }
141
158
142
- tagRow := bp .Targets
143
- nextRow := []uint64 {}
144
- sortUint64s (tagRow ) // probably don't need to sort
159
+ // `targets` might contain a target and its sibling or just the target, if
160
+ // only the target is present the sibling will be in `proofPositions`.
161
+ if uint64 (len (proofPositions )) > targetsMatched &&
162
+ targets [0 ]^ 1 == proofPositions [targetsMatched ] {
163
+ // the sibling of the target is included in the proof positions.
164
+ lr := targets [0 ] & 1
165
+ targetNodes = append (targetNodes , node {Pos : targets [0 ], Val : bp .Proof [lr ]})
166
+ proofHashes = append (proofHashes , bp .Proof [lr ^ 1 ])
167
+ targetsMatched ++
168
+ bp .Proof = bp .Proof [2 :]
169
+ targets = targets [1 :]
170
+ continue
171
+ }
145
172
146
- // TODO it's ugly that I keep treating the 0-row as a special case,
147
- // and has led to a number of bugs. It *is* special in a way, in that
148
- // the bottom row is the only thing you actually prove and add/delete,
149
- // but it'd be nice if it could all be treated uniformly.
173
+ // the sibling is not included in the proof positions, therefore it has to be included in `targets.
174
+ // if there are less than 2 proof hashes or less than 2 targets left the proof is invalid
175
+ // because there is a target without matching proof.
176
+ if len (bp .Proof ) < 2 || len (targets ) < 2 {
177
+ return false , nil , nil
178
+ }
150
179
151
- if verbose {
152
- fmt .Printf ("tagrow len %d\n " , len (tagRow ))
180
+ targetNodes = append (targetNodes ,
181
+ node {Pos : targets [0 ], Val : bp .Proof [0 ]},
182
+ node {Pos : targets [1 ], Val : bp .Proof [1 ]})
183
+ bp .Proof = bp .Proof [2 :]
184
+ targets = targets [2 :]
153
185
}
154
186
155
- var left , right uint64
156
-
157
- // iterate through rows
158
- for row := uint8 ( 0 ); row <= rows ; row ++ {
159
- // iterate through tagged positions in this row
160
- for len ( tagRow ) > 0 {
161
- // Efficiency gains here. If there are two or more things to verify,
162
- // check if the next thing to verify is the sibling of the current leaf
163
- // we're on. Siblingness can be checked with bitwise XOR but since targets are
164
- // sorted, we can do bitwise OR instead.
165
- if len ( tagRow ) > 1 && tagRow [ 0 ] | 1 == tagRow [ 1 ] {
166
- left = tagRow [ 0 ]
167
- right = tagRow [ 1 ]
168
- tagRow = tagRow [ 2 :]
169
- } else { // if not only use one tagged position
170
- right = tagRow [ 0 ] | 1
171
- left = right ^ 1
172
- tagRow = tagRow [ 1 :]
187
+ proofHashes = append ( proofHashes , bp . Proof ... )
188
+ bp . Proof = proofHashes
189
+
190
+ // hash every target node with its sibling (which either is contained in the proof or also a target)
191
+ for len ( targetNodes ) > 0 {
192
+ var target , proof node
193
+ target = targetNodes [ 0 ]
194
+ if len ( proofPositions ) > 0 && target . Pos ^ 1 == proofPositions [ 0 ] {
195
+ // target has a sibling in the proof positions, fetch proof
196
+ proof = node { Pos : proofPositions [ 0 ], Val : bp . Proof [ 0 ]}
197
+ proofPositions = proofPositions [ 1 :]
198
+ bp . Proof = bp . Proof [ 1 : ]
199
+ targetNodes = targetNodes [ 1 : ]
200
+ } else {
201
+ // target should have its sibling in targetNodes
202
+ if len ( targetNodes ) == 1 {
203
+ // sibling not found
204
+ return false , nil , nil
173
205
}
174
206
175
- if verbose {
176
- fmt .Printf ("left %d rootPoss %d\n " , left , rootPositions [0 ])
177
- }
178
- // check for roots
179
- if left == rootPositions [0 ] {
180
- if verbose {
181
- fmt .Printf ("one left in tagrow; should be root\n " )
182
- }
183
- // Grab the hash of this position from the map
184
- computedRootHash , ok := proofmap [left ]
185
- if ! ok {
186
- fmt .Printf ("ERR no proofmap for root at %d\n " , left )
187
- return false , nil
188
- }
189
- // Verify that this root hash matches the one we stored
190
- if computedRootHash != roots [0 ] {
191
- fmt .Printf ("row %d root, pos %d expect %04x got %04x\n " ,
192
- row , left , roots [0 ][:4 ], computedRootHash [:4 ])
193
- return false , nil
194
- }
195
- // otherwise OK and pop of the root
196
- roots = roots [1 :]
197
- rootPositions = rootPositions [1 :]
198
- rootRows = rootRows [1 :]
199
- break
200
- }
207
+ proof = targetNodes [1 ]
208
+ targetNodes = targetNodes [2 :]
209
+ }
201
210
202
- // Grab the parent position of the leaf we've verified
203
- parentPos := parent ( left , rows )
204
- if verbose {
205
- fmt . Printf ( "%d %04x %d %04x -> %d \n " ,
206
- left , proofmap [ left ], right , proofmap [ right ], parentPos )
207
- }
211
+ // figure out which node is left and which is right
212
+ left := target
213
+ right := proof
214
+ if target . Pos & 1 == 1 {
215
+ right , left = left , right
216
+ }
208
217
209
- // this will crash if either is 0000
210
- // reconstruct the next row and add the parent to the map
211
- parhash := parentHash ( proofmap [ left ], proofmap [ right ] )
212
- nextRow = append ( nextRow , parentPos )
213
- proofmap [ parentPos ] = parhash
218
+ // get the hash of the parent from the cache or compute it
219
+ parentPos := parent ( target . Pos , rows )
220
+ isParentCached , hash := cached ( parentPos )
221
+ if ! isParentCached {
222
+ hash = parentHash ( left . Val , right . Val )
214
223
}
224
+ trees = append (trees , [3 ]node {{Val : hash , Pos : parentPos }, left , right })
215
225
216
- // Make the nextRow the tagRow so we'll be iterating over it
217
- // reset th nextRow
218
- tagRow = nextRow
219
- nextRow = []uint64 {}
226
+ row := detectRow (parentPos , rows )
227
+ if numLeaves & (1 << row ) > 0 && parentPos == rootPosition (numLeaves , row , rows ) {
228
+ // the parent is a root -> store as candidate, to check against actual roots later.
229
+ rootCandidates = append (rootCandidates , node {Val : hash , Pos : parentPos })
230
+ continue
231
+ }
232
+ targetNodes = append (targetNodes , node {Val : hash , Pos : parentPos })
233
+ }
220
234
221
- // if done with row and there's a root left on this row, remove it
222
- if len (rootRows ) > 0 && rootRows [0 ] == row {
223
- // bit ugly to do these all separately eh
224
- roots = roots [1 :]
225
- rootPositions = rootPositions [1 :]
226
- rootRows = rootRows [1 :]
235
+ if len (rootCandidates ) == 0 {
236
+ // no roots to verify
237
+ return false , nil , nil
238
+ }
239
+
240
+ // `roots` is ordered, therefore to verify that `rootCandidates` holds a subset of the roots
241
+ // we count the roots that match in order.
242
+ rootMatches := 0
243
+ for _ , root := range roots {
244
+ if len (rootCandidates ) > rootMatches && root == rootCandidates [rootMatches ].Val {
245
+ rootMatches ++
227
246
}
228
247
}
248
+ if len (rootCandidates ) != rootMatches {
249
+ // the proof is invalid because some root candidates were not included in `roots`.
250
+ return false , nil , nil
251
+ }
229
252
230
- return true , proofmap
253
+ return true , trees , rootCandidates
231
254
}
232
255
233
256
// Reconstruct takes a number of leaves and rows, and turns a block proof back
0 commit comments