Skip to content

Commit 425d78d

Browse files
committed
Proof failure blame
1 parent 507a952 commit 425d78d

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

src/range_proof.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use core::{
1111
iter::{once, repeat},
1212
marker::PhantomData,
1313
ops::{Add, Mul, Shr},
14+
slice,
1415
slice::ChunksExact,
1516
};
1617

@@ -751,6 +752,122 @@ where
751752
Ok(masks)
752753
}
753754

755+
/// Verify a batch of proofs.
756+
/// If verification fails, this performs a binary search to identify the first failing proof in the batch and
757+
/// returns an error containing its index. If something goes wrong internally, returns `Err(None)`.
758+
///
759+
/// If initial verification of the entire batch fails, this performs a number of subsequent batch verifications that
760+
/// is logarithmic in the number of proofs. Note that this cannot tell if a batch contains multiple failing
761+
/// proofs, or just one!
762+
pub fn verify_batch_with_first_blame(
763+
transcripts: &mut [Transcript],
764+
statements: &[RangeStatement<P>],
765+
proofs: &[RangeProof<P>],
766+
action: VerifyAction,
767+
) -> Result<Vec<Option<ExtendedMask>>, Option<usize>> {
768+
// Try to verify the entire batch
769+
if let Ok(masks) = Self::verify_batch(&mut transcripts.to_vec(), statements, proofs, action) {
770+
return Ok(masks);
771+
}
772+
773+
// The batch failed, so perform a binary search to identify the first failing proof
774+
let mut left = 0;
775+
let mut right = proofs.len();
776+
777+
while left < right {
778+
#[allow(clippy::arithmetic_side_effects)]
779+
let average = left
780+
.checked_add(
781+
// This cannot underflow since `left < right`
782+
(right - left) / 2,
783+
)
784+
.ok_or(None)?;
785+
786+
#[allow(clippy::arithmetic_side_effects)]
787+
// This cannot underflow since `left < right`
788+
let mid = if (right - left) % 2 == 0 {
789+
average
790+
} else {
791+
average.checked_add(1).ok_or(None)?
792+
};
793+
794+
// Which side is the failure on?
795+
let failure_on_left = Self::verify_batch(
796+
&mut transcripts.to_vec()[left..mid],
797+
&statements[left..mid],
798+
&proofs[left..mid],
799+
action,
800+
)
801+
.is_err();
802+
803+
if failure_on_left {
804+
let left_check = mid.checked_sub(1).ok_or(None)?;
805+
806+
// Are we done?
807+
if left == left_check {
808+
return Err(Some(left));
809+
}
810+
811+
// Discard the right side and continue
812+
right = mid;
813+
} else {
814+
let right_check = mid.checked_add(1).ok_or(None)?;
815+
816+
// Are we done?
817+
if right == right_check {
818+
return Err(Some(right));
819+
}
820+
821+
// Discard the left side and continue
822+
left = mid;
823+
}
824+
}
825+
826+
// We should never get here! If we do, something has gone wrong unexpectedly
827+
Err(None)
828+
}
829+
830+
/// Verify a batch of proofs.
831+
/// If verification fails, this verifies each proof in the batch separately and returns an error containing the
832+
/// indexes of all failing proofs. If something goes wrong internally, returns `Err(None)`.
833+
///
834+
/// If initial verification of the entire batch fails, this performs a subsequent number of verifications that is
835+
/// linear in the number of proofs.
836+
pub fn verify_batch_with_full_blame(
837+
transcripts: &mut [Transcript],
838+
statements: &[RangeStatement<P>],
839+
proofs: &[RangeProof<P>],
840+
action: VerifyAction,
841+
) -> Result<Vec<Option<ExtendedMask>>, Option<Vec<usize>>> {
842+
// Try to verify the entire batch
843+
if let Ok(masks) = Self::verify_batch(&mut transcripts.to_vec(), statements, proofs, action) {
844+
return Ok(masks);
845+
}
846+
847+
let mut failures = Vec::with_capacity(proofs.len());
848+
849+
// If the batch fails, verify all proofs and identify failures
850+
for (index, (transcript, proof, statement)) in izip!(transcripts.iter_mut(), proofs, statements).enumerate() {
851+
if Self::verify_batch(
852+
slice::from_mut(transcript),
853+
slice::from_ref(statement),
854+
slice::from_ref(proof),
855+
action,
856+
)
857+
.is_err()
858+
{
859+
failures.push(index);
860+
}
861+
}
862+
863+
// Ensure that we have found at least one failed proof; otherwise, something has gone wrong unexpectedly
864+
if failures.is_empty() {
865+
return Err(None);
866+
}
867+
868+
Err(Some(failures))
869+
}
870+
754871
// Verify a batch of single and/or aggregated range proofs as a public entity, or recover the masks for single
755872
// range proofs by a party that can supply the optional seed nonces
756873
fn verify(

0 commit comments

Comments
 (0)