use super::{Error, Signature, VerificationKey, VerificationKeyBytes};
use crate::transcript::{Summary, Transcript};
use ahash::RandomState;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use commonware_math::algebra::Random;
use commonware_parallel::Strategy;
use commonware_utils::union_unique;
use core::iter::once;
use curve25519_dalek::{
constants::ED25519_BASEPOINT_POINT as B,
edwards::{CompressedEdwardsY, EdwardsPoint},
scalar::Scalar,
traits::{IsIdentity, VartimeMultiscalarMul},
};
use hashbrown::HashMap;
use rand_core::{CryptoRng, Rng};
use sha2::{digest::Update, Sha512};
const NOISE_BATCH_VERIFY: &[u8] = b"batch_verify";
fn gen_u128<R: Rng + CryptoRng>(mut rng: R) -> u128 {
let mut bytes = [0u8; 16];
rng.fill_bytes(&mut bytes[..]);
u128::from_le_bytes(bytes)
}
#[derive(Default)]
pub struct Verifier {
signatures: Vec<(VerificationKey, Vec<u8>, Signature)>,
}
impl Verifier {
pub fn new(capacity: usize) -> Self {
Self {
signatures: Vec::with_capacity(capacity),
}
}
pub fn queue(
&mut self,
vk: VerificationKey,
sig: Signature,
namespace: Option<&[u8]>,
message: &[u8],
) {
let payload = namespace.map_or_else(
|| message.to_vec(),
|namespace| union_unique(namespace, message),
);
self.signatures.push((vk, payload, sig));
}
pub fn verify<R: Rng + CryptoRng>(
self,
mut rng: R,
strategy: &impl Strategy,
) -> Result<(), Error> {
let manual = strategy.manual();
let total = self.signatures.len();
let shard_count = manual.parallelism().min(total.max(1));
let seeds: Vec<Summary> = (0..shard_count)
.map(|_| Summary::random(&mut rng))
.collect();
strategy.try_run(
total,
|| Self::verify_shard(self.signatures.iter(), total, seeds[0]),
|| {
let order = Self::partition(&self.signatures);
let shard_size = total.div_ceil(shard_count).max(1);
let shards: Vec<_> = order
.chunks(shard_size)
.zip(seeds.iter().copied())
.collect();
manual.try_fold(
shards,
|| (),
|_, (shard, seed)| {
Self::verify_shard(
shard.iter().map(|&idx| &self.signatures[idx]),
shard.len(),
seed,
)
},
|_, _| (),
)
},
)
}
fn partition(signatures: &[(VerificationKey, Vec<u8>, Signature)]) -> Vec<usize> {
let mut counts = [0; 256];
for (vk, _, _) in signatures {
counts[vk.as_bytes()[0] as usize] += 1;
}
let mut offsets = [0; 256];
let mut acc = 0;
for (offset, count) in offsets.iter_mut().zip(counts) {
*offset = acc;
acc += count;
}
let mut order = vec![0; signatures.len()];
for (i, (vk, _, _)) in signatures.iter().enumerate() {
let bucket = vk.as_bytes()[0] as usize;
order[offsets[bucket]] = i;
offsets[bucket] += 1;
}
order
}
#[allow(non_snake_case)]
fn verify_shard<'a>(
items: impl Iterator<Item = &'a (VerificationKey, Vec<u8>, Signature)>,
n: usize,
seed: Summary,
) -> Result<(), Error> {
let mut rng = Transcript::resume(seed).noise(NOISE_BATCH_VERIFY);
let mut key_indices: HashMap<&VerificationKeyBytes, usize, RandomState> =
HashMap::with_capacity_and_hasher(n, RandomState::default());
let mut A_coeffs: Vec<Scalar> = Vec::with_capacity(n);
let mut As = Vec::with_capacity(n);
let mut R_coeffs = Vec::with_capacity(n);
let mut Rs = Vec::with_capacity(n);
let mut B_coeff = Scalar::ZERO;
for (vk, payload, sig) in items {
let k = Scalar::from_hash(
Sha512::default()
.chain(&sig.R_bytes[..])
.chain(vk.as_bytes())
.chain(payload),
);
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);
let index = *key_indices.entry(&vk.A_bytes).or_insert_with(|| {
As.push(-vk.minus_A);
A_coeffs.push(Scalar::ZERO);
As.len() - 1
});
A_coeffs[index] += z * k;
}
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)
}
}
}
#[cfg(test)]
mod tests {
use super::{super::SigningKey, *};
use commonware_parallel::{Rayon, Sequential};
use commonware_utils::{test_rng, NZUsize};
use rand::RngExt as _;
fn signatures(
signers: usize,
per_signer: usize,
) -> Vec<(VerificationKey, Signature, [u8; 32])> {
let mut rng = test_rng();
let mut items = Vec::with_capacity(signers * per_signer);
for _ in 0..signers {
let sk = SigningKey::new(&mut rng);
let vk = sk.verification_key();
for _ in 0..per_signer {
let mut msg = [0u8; 32];
rng.fill(&mut msg);
items.push((vk, sk.sign(&msg), msg));
}
}
items
}
fn verify_with(
items: &[(VerificationKey, Signature, [u8; 32])],
strategy: &impl Strategy,
) -> bool {
let mut verifier = Verifier::default();
for (vk, sig, msg) in items {
verifier.queue(*vk, *sig, None, msg);
}
verifier.verify(test_rng(), strategy).is_ok()
}
fn verify(items: &[(VerificationKey, Signature, [u8; 32])]) -> bool {
let sequential = verify_with(items, &Sequential);
let parallel = verify_with(items, &Rayon::new(NZUsize!(4)).unwrap());
assert_eq!(sequential, parallel);
sequential
}
#[test]
fn test_verify_deferred_hashing() {
let mut items = signatures(4, 3);
assert!(verify(&items));
items[7].2[0] ^= 1;
assert!(!verify(&items));
}
#[test]
fn test_verify_interleaved_duplicate_keys() {
let grouped = signatures(2, 6);
let mut items = Vec::with_capacity(grouped.len());
for i in 0..6 {
items.push(grouped[i]);
items.push(grouped[6 + i]);
}
assert!(verify(&items));
items[5].2[0] ^= 1;
assert!(!verify(&items));
}
#[test]
fn test_deferred_framing_matches_union_unique() {
let mut rng = test_rng();
let sk = SigningKey::new(&mut rng);
let namespace = b"namespace";
let msg = b"message";
let sig = sk.sign(&union_unique(namespace, msg));
let mut verifier = Verifier::default();
verifier.queue(sk.verification_key(), sig, Some(namespace), msg);
assert!(verifier.verify(test_rng(), &Sequential).is_ok());
let mut verifier = Verifier::default();
verifier.queue(sk.verification_key(), sig, Some(b"other"), msg);
assert!(verifier.verify(test_rng(), &Sequential).is_err());
}
}