@@ -59,9 +59,24 @@ pub enum ProofError {
5959 /// Proof deserialization failed.
6060 #[ snafu( display( "Proof deserialization failed" ) ) ]
6161 FailedDeserialization ,
62- /// Proof verification failed.
63- #[ snafu[ display( "Proof verification failed" ) ] ]
62+ /// Single proof verification failed.
63+ #[ snafu[ display( "Single proof verification failed" ) ] ]
6464 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+ } ,
6580}
6681
6782impl Proof {
@@ -369,14 +384,131 @@ impl Proof {
369384 )
370385 }
371386
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+
372503 /// Verify a batch of Triptych [`Proofs`](`Proof`).
373504 ///
505+ /// An empty batch is valid by definition.
506+ ///
374507 /// Verification requires that the `statements` and `transcripts` match those used when the `proofs` were generated,
375508 /// and that they share a common [`InputSet`](`crate::statement::InputSet`) and
376509 /// [`Parameters`](`crate::parameters::Parameters`).
377510 ///
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`].
380512 #[ allow( clippy:: too_many_lines, non_snake_case) ]
381513 pub fn verify_batch (
382514 statements : & [ Statement ] ,
@@ -391,8 +523,11 @@ impl Proof {
391523 return Err ( ProofError :: InvalidParameter ) ;
392524 }
393525
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+ } ;
396531
397532 // Each statement must use the same input set (checked using the hash for efficiency)
398533 if !statements
@@ -799,7 +934,7 @@ mod test {
799934
800935 use crate :: {
801936 parameters:: Parameters ,
802- proof:: Proof ,
937+ proof:: { Proof , ProofError } ,
803938 statement:: { InputSet , Statement } ,
804939 witness:: Witness ,
805940 } ;
@@ -948,17 +1083,110 @@ mod test {
9481083 let mut rng = ChaCha12Rng :: seed_from_u64 ( 8675309 ) ;
9491084 let ( witnesses, statements, mut transcripts) = generate_data ( n, m, batch, & mut rng) ;
9501085
951- // Generate the proofs and verify as a batch
1086+ // Generate the proofs
9521087 let proofs = izip ! ( witnesses. iter( ) , statements. iter( ) , transcripts. clone( ) . iter_mut( ) )
9531088 . map ( |( w, s, t) | Proof :: prove_with_rng_vartime ( w, s, & mut rng, t) . unwrap ( ) )
9541089 . 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( ) ) ;
9561095 }
9571096
9581097 #[ test]
9591098 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+ }
9621190 }
9631191
9641192 #[ test]
0 commit comments