use super::{Error, Signature, VerificationKey};
use crate::transcript::{Summary, Transcript};
#[cfg(not(feature = "std"))]
use alloc::{collections::BTreeMap as Map, vec::Vec};
use commonware_math::algebra::Random;
use commonware_parallel::Strategy;
use core::iter::once;
use curve25519_dalek::{
constants::ED25519_BASEPOINT_POINT as B,
edwards::{CompressedEdwardsY, EdwardsPoint},
scalar::Scalar,
traits::{IsIdentity, VartimeMultiscalarMul},
};
use rand_core::{CryptoRng, RngCore};
use sha2::{digest::Update, Sha512};
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(feature = "std")]
type Map<K, V> = HashMap<K, V>;
const NOISE_BATCH_VERIFY: &[u8] = b"batch_verify";
fn gen_u128<R: RngCore + CryptoRng>(mut rng: R) -> u128 {
let mut bytes = [0u8; 16];
rng.fill_bytes(&mut bytes[..]);
u128::from_le_bytes(bytes)
}
#[derive(Clone, Debug)]
pub struct Item {
vk: VerificationKey,
sig: Signature,
k: Scalar,
}
impl<'msg, M: AsRef<[u8]> + ?Sized> From<(VerificationKey, Signature, &'msg M)> for Item {
fn from(tup: (VerificationKey, Signature, &'msg M)) -> Self {
let (vk, sig, msg) = tup;
let k = Scalar::from_hash(
Sha512::default()
.chain(&sig.R_bytes[..])
.chain(vk.as_bytes())
.chain(msg),
);
Self { vk, sig, k }
}
}
#[derive(Default)]
pub struct Verifier {
signatures: Map<VerificationKey, Vec<(Scalar, Signature)>>,
batch_size: usize,
}
impl Verifier {
pub fn new() -> Self {
Self::default()
}
pub fn queue<I: Into<Item>>(&mut self, item: I) {
let Item { vk, sig, k } = item.into();
self.signatures
.entry(vk)
.or_insert_with(|| Vec::with_capacity(1))
.push((k, sig));
self.batch_size += 1;
}
#[allow(non_snake_case)]
pub fn verify<R: RngCore + CryptoRng>(
self,
mut rng: R,
strategy: &impl Strategy,
) -> Result<(), Error> {
let groups: Vec<_> = self.signatures.into_iter().collect();
let shard_count = strategy.parallelism_hint().max(1).min(groups.len().max(1));
let shard_size = groups.len().div_ceil(shard_count).max(1);
let mut shards = Vec::with_capacity(shard_count);
for shard in groups.chunks(shard_size) {
let seed = Summary::random(&mut rng);
shards.push((shard, seed));
}
strategy.fold(
shards,
|| Ok(()),
|result, (shard, seed)| {
result?;
let mut rng = Transcript::resume(seed).noise(NOISE_BATCH_VERIFY);
let m = shard.len();
let batch_size = shard.iter().map(|(_, sigs)| sigs.len()).sum();
let mut A_coeffs = Vec::with_capacity(m);
let mut As = Vec::with_capacity(m);
let mut R_coeffs = Vec::with_capacity(batch_size);
let mut Rs = Vec::with_capacity(batch_size);
let mut B_coeff = Scalar::ZERO;
for (vk, sigs) in shard {
let A = -vk.minus_A;
let mut A_coeff = Scalar::ZERO;
for (k, sig) in sigs.iter() {
let R = CompressedEdwardsY(sig.R_bytes)
.decompress()
.ok_or(Error::InvalidSignature)?;
let s = Scalar::from_canonical_bytes(sig.s_bytes)
.into_option()
.ok_or(Error::InvalidSignature)?;
let z = Scalar::from(gen_u128(&mut rng));
B_coeff -= z * s;
Rs.push(R);
R_coeffs.push(z);
A_coeff += z * k;
}
As.push(A);
A_coeffs.push(A_coeff);
}
let check = EdwardsPoint::vartime_multiscalar_mul(
once(&B_coeff).chain(A_coeffs.iter()).chain(R_coeffs.iter()),
once(&B).chain(As.iter()).chain(Rs.iter()),
);
if check.mul_by_cofactor().is_identity() {
Ok(())
} else {
Err(Error::InvalidSignature)
}
},
|left, right| left.and(right),
)
}
}