@@ -59,9 +59,24 @@ pub enum ProofError {
59
59
/// Proof deserialization failed.
60
60
#[ snafu( display( "Proof deserialization failed" ) ) ]
61
61
FailedDeserialization ,
62
- /// Proof verification failed.
63
- #[ snafu[ display( "Proof verification failed" ) ] ]
62
+ /// Single proof verification failed.
63
+ #[ snafu[ display( "Single proof verification failed" ) ] ]
64
64
FailedVerification ,
65
+ /// Batch proof verification failed.
66
+ #[ snafu[ display( "Batch proof verification failed" ) ] ]
67
+ FailedBatchVerification ,
68
+ /// Batch proof verification failed.
69
+ #[ snafu[ display( "Batch proof verification failed" ) ] ]
70
+ FailedBatchVerificationWithSingleBlame {
71
+ /// The index of a failed proof, or `None` if no such index was found due to an internal error.
72
+ index : Option < usize > ,
73
+ } ,
74
+ /// Batch proof verification failed.
75
+ #[ snafu[ display( "Batch proof verification failed" ) ] ]
76
+ FailedBatchVerificationWithFullBlame {
77
+ /// The indexes of all failed proofs.
78
+ indexes : Vec < usize > ,
79
+ } ,
65
80
}
66
81
67
82
impl Proof {
@@ -369,14 +384,131 @@ impl Proof {
369
384
)
370
385
}
371
386
387
+ /// Verify a batch of Triptych [`Proofs`](`Proof`), identifying a single invalid proof if verification fails.
388
+ ///
389
+ /// An empty batch is valid by definition.
390
+ ///
391
+ /// If verification fails, this performs a subsequent number of verifications logarithmic in the size of the batch.
392
+ ///
393
+ /// Verification requires that the `statements` and `transcripts` match those used when the `proofs` were generated,
394
+ /// and that they share a common [`InputSet`](`crate::statement::InputSet`) and
395
+ /// [`Parameters`](`crate::parameters::Parameters`).
396
+ ///
397
+ /// If any of the above requirements are not met, returns a [`ProofError`].
398
+ /// If any batch in the proof is invalid, returns a [`ProofError`] containing the index of an invalid proof.
399
+ /// It is not guaranteed that this index represents the _only_ invalid proof in the batch.
400
+ pub fn verify_batch_with_single_blame (
401
+ statements : & [ Statement ] ,
402
+ proofs : & [ Proof ] ,
403
+ transcripts : & mut [ Transcript ] ,
404
+ ) -> Result < ( ) , ProofError > {
405
+ // Try to verify the full batch
406
+ if Self :: verify_batch ( statements, proofs, & mut transcripts. to_vec ( ) ) . is_ok ( ) {
407
+ return Ok ( ( ) ) ;
408
+ }
409
+
410
+ // The batch failed, so find an invalid proof using a binary search
411
+ let mut left = 0 ;
412
+ let mut right = proofs. len ( ) ;
413
+
414
+ while left < right {
415
+ #[ allow( clippy:: arithmetic_side_effects) ]
416
+ let average = left
417
+ . checked_add (
418
+ // This cannot underflow since `left < right`
419
+ ( right - left) / 2 ,
420
+ )
421
+ . ok_or ( ProofError :: FailedBatchVerificationWithSingleBlame { index : None } ) ?;
422
+
423
+ #[ allow( clippy:: arithmetic_side_effects) ]
424
+ // This cannot underflow since `left < right`
425
+ let mid = if ( right - left) % 2 == 0 {
426
+ average
427
+ } else {
428
+ average
429
+ . checked_add ( 1 )
430
+ . ok_or ( ProofError :: FailedBatchVerificationWithSingleBlame { index : None } ) ?
431
+ } ;
432
+
433
+ let failure_on_left = Self :: verify_batch (
434
+ & statements[ left..mid] ,
435
+ & proofs[ left..mid] ,
436
+ & mut transcripts. to_vec ( ) [ left..mid] ,
437
+ )
438
+ . is_err ( ) ;
439
+
440
+ if failure_on_left {
441
+ let left_check = mid
442
+ . checked_sub ( 1 )
443
+ . ok_or ( ProofError :: FailedBatchVerificationWithSingleBlame { index : None } ) ?;
444
+ if left == left_check {
445
+ return Err ( ProofError :: FailedBatchVerificationWithSingleBlame { index : Some ( left) } ) ;
446
+ }
447
+
448
+ right = mid;
449
+ } else {
450
+ let right_check = mid
451
+ . checked_add ( 1 )
452
+ . ok_or ( ProofError :: FailedBatchVerificationWithSingleBlame { index : None } ) ?;
453
+ if right == right_check {
454
+ let right_result = right
455
+ . checked_sub ( 1 )
456
+ . ok_or ( ProofError :: FailedBatchVerificationWithSingleBlame { index : None } ) ?;
457
+ return Err ( ProofError :: FailedBatchVerificationWithSingleBlame {
458
+ index : Some ( right_result) ,
459
+ } ) ;
460
+ }
461
+
462
+ left = mid
463
+ }
464
+ }
465
+
466
+ // The batch failed, but we couldn't find a single failure! This should never happen.
467
+ Err ( ProofError :: FailedBatchVerificationWithSingleBlame { index : None } )
468
+ }
469
+
470
+ /// Verify a batch of Triptych [`Proofs`](`Proof`), identifying all invalid proofs if verification fails.
471
+ ///
472
+ /// An empty batch is valid by definition.
473
+ ///
474
+ /// If verification fails, this performs a subsequent number of verifications linear in the size of the batch.
475
+ ///
476
+ /// Verification requires that the `statements` and `transcripts` match those used when the `proofs` were generated,
477
+ /// and that they share a common [`InputSet`](`crate::statement::InputSet`) and
478
+ /// [`Parameters`](`crate::parameters::Parameters`).
479
+ ///
480
+ /// If any of the above requirements are not met, returns a [`ProofError`].
481
+ /// If any batch in the proof is invalid, returns a [`ProofError`] containing the indexes of all invalid proofs.
482
+ pub fn verify_batch_with_full_blame (
483
+ statements : & [ Statement ] ,
484
+ proofs : & [ Proof ] ,
485
+ transcripts : & mut [ Transcript ] ,
486
+ ) -> Result < ( ) , ProofError > {
487
+ // Try to verify the full batch
488
+ if Self :: verify_batch ( statements, proofs, & mut transcripts. to_vec ( ) ) . is_ok ( ) {
489
+ return Ok ( ( ) ) ;
490
+ }
491
+
492
+ // The batch failed, so check each proof and keep track of which are invalid
493
+ let mut failures = Vec :: with_capacity ( proofs. len ( ) ) ;
494
+ for ( index, ( statement, proof, transcript) ) in izip ! ( statements, proofs, transcripts. iter_mut( ) ) . enumerate ( ) {
495
+ if proof. verify ( statement, transcript) . is_err ( ) {
496
+ failures. push ( index) ;
497
+ }
498
+ }
499
+
500
+ Err ( ProofError :: FailedBatchVerificationWithFullBlame { indexes : failures } )
501
+ }
502
+
372
503
/// Verify a batch of Triptych [`Proofs`](`Proof`).
373
504
///
505
+ /// An empty batch is valid by definition.
506
+ ///
374
507
/// Verification requires that the `statements` and `transcripts` match those used when the `proofs` were generated,
375
508
/// and that they share a common [`InputSet`](`crate::statement::InputSet`) and
376
509
/// [`Parameters`](`crate::parameters::Parameters`).
377
510
///
378
- /// If any of the above requirements are not met, or if the batch is empty, or if any proof is invalid, returns a
379
- /// [`ProofError`].
511
+ /// If any of the above requirements are not met, or if any proof is invalid, returns a [`ProofError`].
380
512
#[ allow( clippy:: too_many_lines, non_snake_case) ]
381
513
pub fn verify_batch (
382
514
statements : & [ Statement ] ,
@@ -391,8 +523,11 @@ impl Proof {
391
523
return Err ( ProofError :: InvalidParameter ) ;
392
524
}
393
525
394
- // An empty batch is considered trivially invalid
395
- let first_statement = statements. first ( ) . ok_or ( ProofError :: InvalidParameter ) ?;
526
+ // An empty batch is considered trivially valid
527
+ let first_statement = match statements. first ( ) {
528
+ Some ( statement) => statement,
529
+ None => return Ok ( ( ) ) ,
530
+ } ;
396
531
397
532
// Each statement must use the same input set (checked using the hash for efficiency)
398
533
if !statements
@@ -799,7 +934,7 @@ mod test {
799
934
800
935
use crate :: {
801
936
parameters:: Parameters ,
802
- proof:: Proof ,
937
+ proof:: { Proof , ProofError } ,
803
938
statement:: { InputSet , Statement } ,
804
939
witness:: Witness ,
805
940
} ;
@@ -948,17 +1083,110 @@ mod test {
948
1083
let mut rng = ChaCha12Rng :: seed_from_u64 ( 8675309 ) ;
949
1084
let ( witnesses, statements, mut transcripts) = generate_data ( n, m, batch, & mut rng) ;
950
1085
951
- // Generate the proofs and verify as a batch
1086
+ // Generate the proofs
952
1087
let proofs = izip ! ( witnesses. iter( ) , statements. iter( ) , transcripts. clone( ) . iter_mut( ) )
953
1088
. map ( |( w, s, t) | Proof :: prove_with_rng_vartime ( w, s, & mut rng, t) . unwrap ( ) )
954
1089
. collect :: < Vec < Proof > > ( ) ;
955
- assert ! ( Proof :: verify_batch( & statements, & proofs, & mut transcripts) . is_ok( ) ) ;
1090
+
1091
+ // Verify the batch with and without blame
1092
+ assert ! ( Proof :: verify_batch( & statements, & proofs, & mut transcripts. clone( ) ) . is_ok( ) ) ;
1093
+ assert ! ( Proof :: verify_batch_with_single_blame( & statements, & proofs, & mut transcripts. clone( ) ) . is_ok( ) ) ;
1094
+ assert ! ( Proof :: verify_batch_with_full_blame( & statements, & proofs, & mut transcripts) . is_ok( ) ) ;
956
1095
}
957
1096
958
1097
#[ test]
959
1098
fn test_prove_verify_empty_batch ( ) {
960
- // An empty batch is invalid by definition
961
- assert ! ( Proof :: verify_batch( & [ ] , & [ ] , & mut [ ] ) . is_err( ) ) ;
1099
+ // An empty batch is valid by definition
1100
+ assert ! ( Proof :: verify_batch( & [ ] , & [ ] , & mut [ ] ) . is_ok( ) ) ;
1101
+ assert ! ( Proof :: verify_batch_with_single_blame( & [ ] , & [ ] , & mut [ ] ) . is_ok( ) ) ;
1102
+ assert ! ( Proof :: verify_batch_with_full_blame( & [ ] , & [ ] , & mut [ ] ) . is_ok( ) ) ;
1103
+ }
1104
+
1105
+ #[ test]
1106
+ #[ allow( non_snake_case, non_upper_case_globals) ]
1107
+ fn test_prove_verify_invalid_batch ( ) {
1108
+ // Generate data
1109
+ const n: u32 = 2 ;
1110
+ const m: u32 = 4 ;
1111
+ const batch: usize = 3 ; // batch size
1112
+ let mut rng = ChaCha12Rng :: seed_from_u64 ( 8675309 ) ;
1113
+ let ( witnesses, statements, mut transcripts) = generate_data ( n, m, batch, & mut rng) ;
1114
+
1115
+ // Generate the proofs
1116
+ let proofs = izip ! ( witnesses. iter( ) , statements. iter( ) , transcripts. clone( ) . iter_mut( ) )
1117
+ . map ( |( w, s, t) | Proof :: prove_with_rng_vartime ( w, s, & mut rng, t) . unwrap ( ) )
1118
+ . collect :: < Vec < Proof > > ( ) ;
1119
+
1120
+ // Manipulate a transcript so the corresponding proof is invalid
1121
+ transcripts[ 0 ] = Transcript :: new ( b"Evil transcript" ) ;
1122
+
1123
+ // Verification should fail
1124
+ assert ! ( Proof :: verify_batch( & statements, & proofs, & mut transcripts) . is_err( ) ) ;
1125
+ }
1126
+
1127
+ #[ test]
1128
+ #[ allow( non_snake_case, non_upper_case_globals) ]
1129
+ fn test_prove_verify_invalid_batch_single_blame ( ) {
1130
+ // Generate data
1131
+ const n: u32 = 2 ;
1132
+ const m: u32 = 4 ;
1133
+
1134
+ // Test against batches of even and odd size
1135
+ for batch in [ 4 , 5 ] {
1136
+ let mut rng = ChaCha12Rng :: seed_from_u64 ( 8675309 ) ;
1137
+ let ( witnesses, statements, transcripts) = generate_data ( n, m, batch, & mut rng) ;
1138
+
1139
+ // Generate the proofs
1140
+ let proofs = izip ! ( witnesses. iter( ) , statements. iter( ) , transcripts. clone( ) . iter_mut( ) )
1141
+ . map ( |( w, s, t) | Proof :: prove_with_rng_vartime ( w, s, & mut rng, t) . unwrap ( ) )
1142
+ . collect :: < Vec < Proof > > ( ) ;
1143
+
1144
+ // Iteratively manipulate each transcript to make the corresponding proof invalid
1145
+ for i in 0 ..proofs. len ( ) {
1146
+ let mut evil_transcripts = transcripts. clone ( ) ;
1147
+ evil_transcripts[ i] = Transcript :: new ( b"Evil transcript" ) ;
1148
+
1149
+ // Verification should fail and blame the correct proof
1150
+ let error =
1151
+ Proof :: verify_batch_with_single_blame ( & statements, & proofs, & mut evil_transcripts) . unwrap_err ( ) ;
1152
+ if let ProofError :: FailedBatchVerificationWithSingleBlame { index : Some ( index) } = error {
1153
+ assert_eq ! ( index, i) ;
1154
+ } else {
1155
+ panic ! ( ) ;
1156
+ }
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ #[ test]
1162
+ #[ allow( non_snake_case, non_upper_case_globals) ]
1163
+ fn test_prove_verify_invalid_batch_full_blame ( ) {
1164
+ // Generate data
1165
+ const n: u32 = 2 ;
1166
+ const m: u32 = 4 ;
1167
+ const batch: usize = 4 ;
1168
+ const failures: [ usize ; 2 ] = [ 1 , 3 ] ;
1169
+
1170
+ let mut rng = ChaCha12Rng :: seed_from_u64 ( 8675309 ) ;
1171
+ let ( witnesses, statements, mut transcripts) = generate_data ( n, m, batch, & mut rng) ;
1172
+
1173
+ // Generate the proofs
1174
+ let proofs = izip ! ( witnesses. iter( ) , statements. iter( ) , transcripts. clone( ) . iter_mut( ) )
1175
+ . map ( |( w, s, t) | Proof :: prove_with_rng_vartime ( w, s, & mut rng, t) . unwrap ( ) )
1176
+ . collect :: < Vec < Proof > > ( ) ;
1177
+
1178
+ // Manipulate some of the transcripts to make the corresponding proofs invalid
1179
+ for i in failures {
1180
+ transcripts[ i] = Transcript :: new ( b"Evil transcript" ) ;
1181
+ }
1182
+
1183
+ // Verification should fail and blame the correct proof
1184
+ let error = Proof :: verify_batch_with_full_blame ( & statements, & proofs, & mut transcripts) . unwrap_err ( ) ;
1185
+ if let ProofError :: FailedBatchVerificationWithFullBlame { indexes } = error {
1186
+ assert_eq ! ( indexes, failures) ;
1187
+ } else {
1188
+ panic ! ( ) ;
1189
+ }
962
1190
}
963
1191
964
1192
#[ test]
0 commit comments