use crate::crypto::{Signature, verify_signature};
use crate::error::{Error, Result};
use ed25519_dalek::{VerifyingKey, PUBLIC_KEY_LENGTH};
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone)]
pub struct VerificationItem {
pub public_key: VerifyingKey,
pub message: Vec<u8>,
pub signature: Signature,
pub id: Option<String>,
}
impl VerificationItem {
pub fn new(public_key: VerifyingKey, message: Vec<u8>, signature: Signature) -> Self {
Self {
public_key,
message,
signature,
id: None,
}
}
pub fn with_id(
id: String,
public_key: VerifyingKey,
message: Vec<u8>,
signature: Signature,
) -> Self {
Self {
public_key,
message,
signature,
id: Some(id),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchResult {
pub total: usize,
pub valid: usize,
pub invalid: usize,
pub verification_time: Duration,
pub throughput: f64,
pub results: Vec<ItemResult>,
}
impl BatchResult {
pub fn success_rate(&self) -> f64 {
if self.total == 0 {
0.0
} else {
(self.valid as f64 / self.total as f64) * 100.0
}
}
pub fn all_valid(&self) -> bool {
self.valid == self.total
}
pub fn invalid_items(&self) -> Vec<&ItemResult> {
self.results.iter().filter(|r| !r.is_valid).collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemResult {
pub id: Option<String>,
pub is_valid: bool,
pub public_key: [u8; PUBLIC_KEY_LENGTH],
pub error: Option<String>,
}
pub struct BatchVerifier {
max_batch_size: usize,
}
impl BatchVerifier {
pub fn new() -> Self {
Self::with_max_batch_size(1000)
}
pub fn with_max_batch_size(max_batch_size: usize) -> Self {
Self { max_batch_size }
}
pub async fn verify_batch(&self, items: Vec<VerificationItem>) -> Result<BatchResult> {
if items.is_empty() {
return Ok(BatchResult {
total: 0,
valid: 0,
invalid: 0,
verification_time: Duration::ZERO,
throughput: 0.0,
results: vec![],
});
}
let start = SystemTime::now();
let total = items.len();
let mut tasks = Vec::new();
for item in items {
let task = tokio::spawn(async move {
Self::verify_item(item).await
});
tasks.push(task);
}
let mut results = Vec::new();
let mut valid = 0;
for task in tasks {
let result = task.await
.map_err(|e| Error::TaskJoin(e.to_string()))?;
if result.is_valid {
valid += 1;
}
results.push(result);
}
let verification_time = start.elapsed().unwrap_or(Duration::ZERO);
let throughput = if verification_time.as_secs_f64() > 0.0 {
total as f64 / verification_time.as_secs_f64()
} else {
0.0
};
Ok(BatchResult {
total,
valid,
invalid: total - valid,
verification_time,
throughput,
results,
})
}
async fn verify_item(item: VerificationItem) -> ItemResult {
match verify_signature(&item.public_key, &item.message, &item.signature) {
Ok(is_valid) => ItemResult {
id: item.id,
is_valid,
public_key: item.public_key.to_bytes(),
error: None,
},
Err(e) => ItemResult {
id: item.id,
is_valid: false,
public_key: item.public_key.to_bytes(),
error: Some(e.to_string()),
},
}
}
pub async fn verify_large_batch(&self, items: Vec<VerificationItem>) -> Result<BatchResult> {
if items.len() <= self.max_batch_size {
return self.verify_batch(items).await;
}
let start = SystemTime::now();
let total = items.len();
let chunks: Vec<_> = items
.chunks(self.max_batch_size)
.map(|chunk| chunk.to_vec())
.collect();
let mut tasks = Vec::new();
for chunk in chunks {
let verifier = Self::with_max_batch_size(self.max_batch_size);
let task = tokio::spawn(async move {
verifier.verify_batch(chunk).await
});
tasks.push(task);
}
let mut all_results = Vec::new();
let mut valid = 0;
for task in tasks {
let batch_result = task.await
.map_err(|e| Error::TaskJoin(e.to_string()))??;
valid += batch_result.valid;
all_results.extend(batch_result.results);
}
let verification_time = start.elapsed().unwrap_or(Duration::ZERO);
let throughput = if verification_time.as_secs_f64() > 0.0 {
total as f64 / verification_time.as_secs_f64()
} else {
0.0
};
Ok(BatchResult {
total,
valid,
invalid: total - valid,
verification_time,
throughput,
results: all_results,
})
}
pub fn max_batch_size(&self) -> usize {
self.max_batch_size
}
}
impl Default for BatchVerifier {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::{generate_keypair, sign_message};
#[tokio::test]
async fn test_batch_verify_empty() {
let verifier = BatchVerifier::new();
let result = verifier.verify_batch(vec![]).await.unwrap();
assert_eq!(result.total, 0);
assert_eq!(result.valid, 0);
}
#[tokio::test]
async fn test_batch_verify_single() {
let verifier = BatchVerifier::new();
let (signing_key, verifying_key) = generate_keypair().unwrap();
let message = b"test message".to_vec();
let signature = sign_message(&signing_key, &message).unwrap();
let items = vec![
VerificationItem::new(verifying_key, message, signature)
];
let result = verifier.verify_batch(items).await.unwrap();
assert_eq!(result.total, 1);
assert_eq!(result.valid, 1);
assert!(result.all_valid());
}
#[tokio::test]
async fn test_batch_verify_multiple() {
let verifier = BatchVerifier::new();
let mut items = Vec::new();
for i in 0..10 {
let (signing_key, verifying_key) = generate_keypair().unwrap();
let message = format!("message {}", i).into_bytes();
let signature = sign_message(&signing_key, &message).unwrap();
items.push(VerificationItem::with_id(
format!("item-{}", i),
verifying_key,
message,
signature,
));
}
let result = verifier.verify_batch(items).await.unwrap();
assert_eq!(result.total, 10);
assert_eq!(result.valid, 10);
assert!(result.all_valid());
assert_eq!(result.success_rate(), 100.0);
}
#[tokio::test]
async fn test_batch_verify_mixed() {
let verifier = BatchVerifier::new();
let mut items = Vec::new();
for i in 0..5 {
let (signing_key, verifying_key) = generate_keypair().unwrap();
let message = format!("message {}", i).into_bytes();
let signature = sign_message(&signing_key, &message).unwrap();
items.push(VerificationItem::new(verifying_key, message, signature));
}
for i in 5..10 {
let (signing_key, _) = generate_keypair().unwrap();
let (_, wrong_key) = generate_keypair().unwrap();
let message = format!("message {}", i).into_bytes();
let signature = sign_message(&signing_key, &message).unwrap();
items.push(VerificationItem::new(wrong_key, message, signature));
}
let result = verifier.verify_batch(items).await.unwrap();
assert_eq!(result.total, 10);
assert_eq!(result.valid, 5);
assert_eq!(result.invalid, 5);
assert!(!result.all_valid());
assert_eq!(result.success_rate(), 50.0);
}
#[tokio::test]
async fn test_batch_verify_large() {
let verifier = BatchVerifier::with_max_batch_size(10);
let mut items = Vec::new();
for i in 0..100 {
let (signing_key, verifying_key) = generate_keypair().unwrap();
let message = format!("message {}", i).into_bytes();
let signature = sign_message(&signing_key, &message).unwrap();
items.push(VerificationItem::new(verifying_key, message, signature));
}
let result = verifier.verify_large_batch(items).await.unwrap();
assert_eq!(result.total, 100);
assert_eq!(result.valid, 100);
assert!(result.all_valid());
assert!(result.throughput > 0.0);
}
#[tokio::test]
async fn test_batch_result_invalid_items() {
let verifier = BatchVerifier::new();
let mut items = Vec::new();
let (sk1, vk1) = generate_keypair().unwrap();
let msg1 = b"valid".to_vec();
let sig1 = sign_message(&sk1, &msg1).unwrap();
items.push(VerificationItem::with_id("valid".to_string(), vk1, msg1, sig1));
let (sk2, _) = generate_keypair().unwrap();
let (_, vk3) = generate_keypair().unwrap();
let msg2 = b"invalid".to_vec();
let sig2 = sign_message(&sk2, &msg2).unwrap();
items.push(VerificationItem::with_id("invalid".to_string(), vk3, msg2, sig2));
let result = verifier.verify_batch(items).await.unwrap();
let invalid = result.invalid_items();
assert_eq!(invalid.len(), 1);
assert_eq!(invalid[0].id.as_ref().unwrap(), "invalid");
}
}