mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 03:59:22 +00:00
Implement a binary search for BatchVerifier blame
Adds helper functions to verify and, on failure, blame, which move an unwrap from callers into multiexp where it's guaranteed to be safe and easily verified to be proper. Closes https://github.com/serai-dex/serai/issues/10.
This commit is contained in:
parent
c90e957e6a
commit
469ce9106b
4 changed files with 51 additions and 32 deletions
|
@ -225,10 +225,7 @@ fn complete_r2<R: RngCore + CryptoRng, C: Curve>(
|
||||||
|
|
||||||
batch.queue(rng, *l, values);
|
batch.queue(rng, *l, values);
|
||||||
}
|
}
|
||||||
|
batch.verify_with_vartime_blame().map_err(|l| FrostError::InvalidCommitment(l))?;
|
||||||
if !batch.verify() {
|
|
||||||
Err(FrostError::InvalidCommitment(batch.blame_vartime().unwrap()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Clear the original share
|
// TODO: Clear the original share
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,12 @@ pub(crate) fn batch_verify<C: Curve, R: RngCore + CryptoRng>(
|
||||||
triplets: &[(u16, C::G, C::F, SchnorrSignature<C>)]
|
triplets: &[(u16, C::G, C::F, SchnorrSignature<C>)]
|
||||||
) -> Result<(), u16> {
|
) -> Result<(), u16> {
|
||||||
let mut values = [(C::F::one(), C::G::generator()); 3];
|
let mut values = [(C::F::one(), C::G::generator()); 3];
|
||||||
let mut batch = BatchVerifier::new(triplets.len() * 3, C::little_endian());
|
let mut batch = BatchVerifier::new(triplets.len(), C::little_endian());
|
||||||
for triple in triplets {
|
for triple in triplets {
|
||||||
|
// s = r + ca
|
||||||
|
// sG == R + cA
|
||||||
|
// R + cA - sG == 0
|
||||||
|
|
||||||
// R
|
// R
|
||||||
values[0].1 = triple.3.R;
|
values[0].1 = triple.3.R;
|
||||||
// cA
|
// cA
|
||||||
|
@ -59,12 +63,5 @@ pub(crate) fn batch_verify<C: Curve, R: RngCore + CryptoRng>(
|
||||||
batch.queue(rng, triple.0, values);
|
batch.queue(rng, triple.0, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// s = r + ca
|
batch.verify_vartime_with_vartime_blame()
|
||||||
// sG == R + cA
|
|
||||||
// R + cA - sG == 0
|
|
||||||
if batch.verify_vartime() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(batch.blame_vartime().unwrap())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,18 +31,18 @@ pub(crate) fn verify<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn batch_verify<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
pub(crate) fn batch_verify<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
// Create 3 signatures
|
// Create 5 signatures
|
||||||
let mut keys = vec![];
|
let mut keys = vec![];
|
||||||
let mut challenges = vec![];
|
let mut challenges = vec![];
|
||||||
let mut sigs = vec![];
|
let mut sigs = vec![];
|
||||||
for i in 0 .. 3 {
|
for i in 0 .. 5 {
|
||||||
keys.push(C::F::random(&mut *rng));
|
keys.push(C::F::random(&mut *rng));
|
||||||
challenges.push(C::F::random(&mut *rng));
|
challenges.push(C::F::random(&mut *rng));
|
||||||
sigs.push(schnorr::sign::<C>(keys[i], C::F::random(&mut *rng), challenges[i]));
|
sigs.push(schnorr::sign::<C>(keys[i], C::F::random(&mut *rng), challenges[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch verify
|
// Batch verify
|
||||||
let mut triplets = (0 .. 3).map(
|
let triplets = (0 .. 5).map(
|
||||||
|i| (u16::try_from(i + 1).unwrap(), C::generator_table() * keys[i], challenges[i], sigs[i])
|
|i| (u16::try_from(i + 1).unwrap(), C::generator_table() * keys[i], challenges[i], sigs[i])
|
||||||
).collect::<Vec<_>>();
|
).collect::<Vec<_>>();
|
||||||
schnorr::batch_verify(rng, &triplets).unwrap();
|
schnorr::batch_verify(rng, &triplets).unwrap();
|
||||||
|
@ -59,14 +59,15 @@ pub(crate) fn batch_verify<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
assert!(false);
|
assert!(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sanity
|
|
||||||
schnorr::batch_verify(rng, &triplets).unwrap();
|
|
||||||
|
|
||||||
// Make sure a completely invalid signature fails when included
|
// Make sure a completely invalid signature fails when included
|
||||||
triplets[0].3.s = C::F::random(&mut *rng);
|
for i in 0 .. 5 {
|
||||||
if let Err(blame) = schnorr::batch_verify(rng, &triplets) {
|
let mut triplets = triplets.clone();
|
||||||
assert_eq!(blame, 1);
|
triplets[i].3.s = C::F::random(&mut *rng);
|
||||||
} else {
|
if let Err(blame) = schnorr::batch_verify(rng, &triplets) {
|
||||||
assert!(false);
|
assert_eq!(blame, u16::try_from(i + 1).unwrap());
|
||||||
|
} else {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,27 +106,51 @@ impl<Id: Copy, G: Group> BatchVerifier<Id, G> {
|
||||||
|
|
||||||
pub fn verify(&self) -> bool {
|
pub fn verify(&self) -> bool {
|
||||||
multiexp(
|
multiexp(
|
||||||
self.0.iter().flat_map(|sets| sets.1.iter()).cloned(),
|
self.0.iter().flat_map(|pairs| pairs.1.iter()).cloned(),
|
||||||
self.1
|
self.1
|
||||||
).is_identity().into()
|
).is_identity().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_vartime(&self) -> bool {
|
pub fn verify_vartime(&self) -> bool {
|
||||||
multiexp_vartime(
|
multiexp_vartime(
|
||||||
self.0.iter().flat_map(|sets| sets.1.iter()).cloned(),
|
self.0.iter().flat_map(|pairs| pairs.1.iter()).cloned(),
|
||||||
self.1
|
self.1
|
||||||
).is_identity().into()
|
).is_identity().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solely has a vartime variant as there shouldn't be any reason for this to not be vartime, yet
|
// A constant time variant may be beneficial for robust protocols
|
||||||
// we should explicitly label vartime software as vartime
|
|
||||||
// TODO: Binary search, or at least randomly sort
|
|
||||||
pub fn blame_vartime(&self) -> Option<Id> {
|
pub fn blame_vartime(&self) -> Option<Id> {
|
||||||
for value in &self.0 {
|
let mut slice = self.0.as_slice();
|
||||||
if !bool::from(multiexp_vartime(value.1.clone(), self.1).is_identity()) {
|
while slice.len() > 1 {
|
||||||
return Some(value.0);
|
let split = slice.len() / 2;
|
||||||
|
if multiexp_vartime(
|
||||||
|
slice[.. split].iter().flat_map(|pairs| pairs.1.iter()).cloned(),
|
||||||
|
self.1
|
||||||
|
).is_identity().into() {
|
||||||
|
slice = &slice[split ..];
|
||||||
|
} else {
|
||||||
|
slice = &slice[.. split];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
|
slice.get(0).filter(
|
||||||
|
|(_, value)| !bool::from(multiexp_vartime(value.clone(), self.1).is_identity())
|
||||||
|
).map(|(id, _)| *id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_with_vartime_blame(&self) -> Result<(), Id> {
|
||||||
|
if self.verify() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(self.blame_vartime().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_vartime_with_vartime_blame(&self) -> Result<(), Id> {
|
||||||
|
if self.verify_vartime() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(self.blame_vartime().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue