@@ -146,12 +146,14 @@ module.exports = class ProofSet {
146
146
147
147
// verify proofs
148
148
const results = await _verify ( {
149
- document, suites, proofSet, purpose, documentLoader, expansionMap
149
+ document, suites, proofSet, purpose, documentLoader, expansionMap,
150
150
} ) ;
151
151
if ( results . length === 0 ) {
152
- throw new Error (
153
- 'Could not verify any proofs; no proofs matched the required ' +
154
- 'suite and purpose.' ) ;
152
+ const error = new Error (
153
+ 'Did not verify any proofs; insufficient proofs matched the ' +
154
+ 'acceptable suite(s) and required purpose(s).' ) ;
155
+ error . name = 'NotFoundError' ;
156
+ throw error ;
155
157
}
156
158
157
159
// combine results
@@ -167,7 +169,7 @@ module.exports = class ProofSet {
167
169
}
168
170
return { verified, results} ;
169
171
} catch ( error ) {
170
- _addToJSON ( error ) ;
172
+ _makeSerializable ( error ) ;
171
173
return { verified : false , error} ;
172
174
}
173
175
}
@@ -197,38 +199,108 @@ async function _getProofs({document}) {
197
199
async function _verify ( {
198
200
document, suites, proofSet, purpose, documentLoader, expansionMap
199
201
} ) {
200
- // filter out matching proofs
201
- const result = await Promise . all ( proofSet . map ( proof =>
202
- purpose . match ( proof , { document, documentLoader, expansionMap} ) ) ) ;
203
- const matches = proofSet . filter ( ( value , index ) => result [ index ] ) ;
204
- if ( matches . length === 0 ) {
205
- // no matches, nothing to verify
202
+ // map each purpose to at least one proof to verify
203
+ const purposes = Array . isArray ( purpose ) ? purpose : [ purpose ] ;
204
+ const purposeToProofs = new Map ( ) ;
205
+ const proofToSuite = new Map ( ) ;
206
+ const suiteMatchQueue = new Map ( ) ;
207
+ await Promise . all ( purposes . map ( purpose => _matchProofSet ( {
208
+ purposeToProofs, proofToSuite, purpose, proofSet, suites,
209
+ suiteMatchQueue, document, documentLoader, expansionMap
210
+ } ) ) ) ;
211
+
212
+ // every purpose must have at least one matching proof or verify will fail
213
+ if ( purposeToProofs . length < purposes . length ) {
214
+ // insufficient proofs to verify, so don't bother verifying any
206
215
return [ ] ;
207
216
}
208
217
209
- // verify each matching proof
210
- return ( await Promise . all ( matches . map ( async proof => {
211
- for ( const s of suites ) {
212
- if ( await s . matchProof ( { proof, document, documentLoader, expansionMap} ) ) {
213
- return s . verifyProof ( {
214
- proof, document, purpose, documentLoader, expansionMap
215
- } ) . catch ( error => ( { verified : false , error} ) ) ;
218
+ // verify every proof in `proofToSuite`; these proofs matched a purpose
219
+ const verifyResults = new Map ( ) ;
220
+ await Promise . all ( [ ...proofToSuite . entries ( ) ] . map ( async ( [ proof , suite ] ) => {
221
+ let result ;
222
+ try {
223
+ // create backwards-compatible deferred proof purpose to capture
224
+ // verification method from old-style suites
225
+ let vm ;
226
+ const purpose = {
227
+ async validate ( proof , { verificationMethod} ) {
228
+ vm = verificationMethod ;
229
+ return { valid : true } ;
230
+ }
231
+ } ;
232
+ const { verified, verificationMethod, error} = await suite . verifyProof ( {
233
+ proof, document, purpose, documentLoader, expansionMap
234
+ } ) ;
235
+ if ( ! vm ) {
236
+ vm = verificationMethod ;
216
237
}
238
+ result = { proof, verified, verificationMethod : vm , error} ;
239
+ } catch ( error ) {
240
+ result = { proof, verified : false , error} ;
217
241
}
218
- } ) ) ) . map ( ( r , i ) => {
219
- if ( ! r ) {
220
- return null ;
221
- }
222
- if ( r . error ) {
223
- _addToJSON ( r . error ) ;
242
+
243
+ if ( result . error ) {
244
+ // ensure error is serializable
245
+ _makeSerializable ( result . error ) ;
224
246
}
225
- return { proof : matches [ i ] , ...r } ;
226
- } ) . filter ( r => r ) ;
247
+
248
+ verifyResults . set ( proof , result ) ;
249
+ } ) ) ;
250
+
251
+ // validate proof against each purpose that matched it
252
+ await Promise . all ( [ ...purposeToProofs . entries ( ) ] . map (
253
+ async ( [ purpose , proofs ] ) => {
254
+ for ( const proof of proofs ) {
255
+ const result = verifyResults . get ( proof ) ;
256
+ if ( ! result . verified ) {
257
+ // if proof was not verified, so not bother validating purpose
258
+ continue ;
259
+ }
260
+
261
+ // validate purpose
262
+ const { verificationMethod} = result ;
263
+ const suite = proofToSuite . get ( proof ) ;
264
+ let purposeResult ;
265
+ try {
266
+ purposeResult = await purpose . validate ( proof , {
267
+ document, suite, verificationMethod, documentLoader, expansionMap
268
+ } ) ;
269
+ } catch ( error ) {
270
+ purposeResult = { valid : false , error} ;
271
+ }
272
+
273
+ // add `purposeResult` to verification result regardless of validity
274
+ // to ensure that all purposes are represented
275
+ if ( result . purposeResult ) {
276
+ if ( Array . isArray ( result . purposeResult ) ) {
277
+ result . purposeResult . push ( purposeResult ) ;
278
+ } else {
279
+ result . purposeResult = [ result . purposeResult , purposeResult ] ;
280
+ }
281
+ } else {
282
+ result . purposeResult = purposeResult ;
283
+ }
284
+
285
+ if ( ! purposeResult . valid ) {
286
+ // ensure error is serializable
287
+ _makeSerializable ( purposeResult . error ) ;
288
+
289
+ // if no top level error set yet, set it
290
+ if ( ! result . error ) {
291
+ result . verified = false ;
292
+ result . error = purposeResult . error ;
293
+ }
294
+ }
295
+ }
296
+ } ) ) ;
297
+
298
+ return [ ...verifyResults . values ( ) ] ;
227
299
}
228
300
229
301
// add a `toJSON` method to an error which allows for errors in validation
230
302
// reports to be serialized properly by `JSON.stringify`.
231
- function _addToJSON ( error ) {
303
+ function _makeSerializable ( error ) {
232
304
Object . defineProperty ( error , 'toJSON' , {
233
305
value : function ( ) {
234
306
return serializeError ( this ) ;
@@ -237,3 +309,52 @@ function _addToJSON(error) {
237
309
writable : true
238
310
} ) ;
239
311
}
312
+
313
+ async function _matchProofSet ( {
314
+ purposeToProofs, proofToSuite, purpose, proofSet, suites,
315
+ suiteMatchQueue, document, documentLoader, expansionMap
316
+ } ) {
317
+ for ( const proof of proofSet ) {
318
+ // first check if the proof matches the purpose; if it doesn't continue
319
+ if ( ! await purpose . match ( proof , { document, documentLoader, expansionMap} ) ) {
320
+ continue ;
321
+ }
322
+
323
+ // next, find the suite that can verify the proof
324
+ let matched = false ;
325
+ for ( const s of suites ) {
326
+ // `matchingProofs` is a map of promises that resolve to whether a
327
+ // proof matches a suite; multiple purposes and suites may be checked
328
+ // in parallel so a promise queue is used to prevent duplicate work
329
+ let matchingProofs = suiteMatchQueue . get ( s ) ;
330
+ if ( ! matchingProofs ) {
331
+ suiteMatchQueue . set ( s , matchingProofs = new Map ( ) ) ;
332
+ }
333
+ let promise = matchingProofs . get ( proof ) ;
334
+ if ( ! promise ) {
335
+ promise = s . matchProof ( { proof, document, documentLoader, expansionMap} ) ;
336
+ matchingProofs . set ( proof , promise ) ;
337
+ }
338
+ if ( await promise ) {
339
+ // found the matching suite for the proof; there should only be one
340
+ // suite that can verify a particular proof; add the proof to the
341
+ // map of proofs to be verified along with the matching suite
342
+ matched = true ;
343
+ proofToSuite . set ( proof , s ) ;
344
+ break ;
345
+ }
346
+ }
347
+
348
+ if ( matched ) {
349
+ // note proof was a match for the purpose and an acceptable suite; it
350
+ // will need to be verified by the suite and then validated against the
351
+ // purpose
352
+ const matches = purposeToProofs . get ( purpose ) ;
353
+ if ( matches ) {
354
+ matches . push ( proof ) ;
355
+ } else {
356
+ purposeToProofs . set ( purpose , [ proof ] ) ;
357
+ }
358
+ }
359
+ }
360
+ }
0 commit comments