@@ -112,122 +112,134 @@ 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
+ if targets [0 ] == numLeaves - 1 && numLeaves & 1 == 1 {
150
+ // row 0 root.
151
+ rootCandidates = append (rootCandidates , node {Val : roots [0 ], Pos : targets [0 ]})
152
+ bp .Proof = bp .Proof [1 :]
153
+ break
154
+ }
141
155
142
- tagRow := bp .Targets
143
- nextRow := []uint64 {}
144
- sortUint64s (tagRow ) // probably don't need to sort
156
+ if uint64 (len (proofPositions )) > targetsMatched &&
157
+ targets [0 ]^ 1 == proofPositions [targetsMatched ] {
158
+ lr := targets [0 ] & 1
159
+ targetNodes = append (targetNodes , node {Pos : targets [0 ], Val : bp .Proof [lr ]})
160
+ proofHashes = append (proofHashes , bp .Proof [lr ^ 1 ])
161
+ targetsMatched ++
162
+ bp .Proof = bp .Proof [2 :]
163
+ targets = targets [1 :]
164
+ continue
165
+ }
145
166
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.
167
+ if len (bp .Proof ) < 2 || len (targets ) < 2 {
168
+ return false , nil , nil
169
+ }
150
170
151
- if verbose {
152
- fmt .Printf ("tagrow len %d\n " , len (tagRow ))
171
+ targetNodes = append (targetNodes ,
172
+ node {Pos : targets [0 ], Val : bp .Proof [0 ]},
173
+ node {Pos : targets [1 ], Val : bp .Proof [1 ]})
174
+ bp .Proof = bp .Proof [2 :]
175
+ targets = targets [2 :]
153
176
}
154
177
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 :]
173
- }
178
+ proofHashes = append (proofHashes , bp .Proof ... )
179
+ bp .Proof = proofHashes
180
+
181
+ // hash every target node with its sibling (which either is contained in the proof or also a target)
182
+ for len (targetNodes ) > 0 {
183
+ var target , proof node
184
+ target = targetNodes [0 ]
185
+ if len (proofPositions ) > 0 && target .Pos ^ 1 == proofPositions [0 ] {
186
+ // target has a sibling in the proof positions, fetch proof
187
+ proof = node {Pos : proofPositions [0 ], Val : bp .Proof [0 ]}
188
+ proofPositions = proofPositions [1 :]
189
+ bp .Proof = bp .Proof [1 :]
190
+ targetNodes = targetNodes [1 :]
191
+ goto hash
192
+ }
174
193
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
- }
194
+ // target should have its sibling in targetNodes
195
+ if len (targetNodes ) == 1 {
196
+ // sibling not found
197
+ return false , nil , nil
198
+ }
201
199
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
- }
200
+ proof = targetNodes [1 ]
201
+ targetNodes = targetNodes [2 :]
208
202
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
203
+ hash:
204
+ // figure out which node is left and which is right
205
+ left := target
206
+ right := proof
207
+ if target .Pos & 1 == 1 {
208
+ right , left = left , right
214
209
}
215
210
216
- // Make the nextRow the tagRow so we'll be iterating over it
217
- // reset th nextRow
218
- tagRow = nextRow
219
- nextRow = []uint64 {}
211
+ // get the hash of the parent from the cache or compute it
212
+ parentPos := parent (target .Pos , rows )
213
+ isParentCached , hash := cached (parentPos )
214
+ if ! isParentCached {
215
+ hash = parentHash (left .Val , right .Val )
216
+ }
217
+ trees = append (trees , [3 ]node {{Val : hash , Pos : parentPos }, left , right })
220
218
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 :]
219
+ row := detectRow (parentPos , rows )
220
+ if numLeaves & (1 << row ) > 0 && parentPos == rootPosition (numLeaves , row , rows ) {
221
+ // the parent is a root -> store as candidate, to check against actual roots later.
222
+ rootCandidates = append (rootCandidates , node {Val : hash , Pos : parentPos })
223
+ continue
227
224
}
225
+ targetNodes = append (targetNodes , node {Val : hash , Pos : parentPos })
226
+ }
227
+ if len (rootCandidates ) == 0 {
228
+ // no roots to verify
229
+ return false , nil , nil
230
+ }
231
+ rootMatches := 0
232
+ for _ , root := range roots {
233
+ if len (rootCandidates ) > rootMatches && root == rootCandidates [rootMatches ].Val {
234
+ rootMatches ++
235
+ }
236
+ }
237
+ if len (rootCandidates ) != rootMatches {
238
+ // some roots did not match
239
+ return false , nil , nil
228
240
}
229
241
230
- return true , proofmap
242
+ return true , trees , rootCandidates
231
243
}
232
244
233
245
// Reconstruct takes a number of leaves and rows, and turns a block proof back
0 commit comments