use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::store::{ProofType, StoredProof};
#[derive(Debug, Error, Clone, Serialize, Deserialize)]
pub enum VerificationError {
#[error("Proof not found: {0}")]
ProofNotFound(String),
#[error("Invalid proof data: {0}")]
InvalidProofData(String),
#[error("Verification failed: {0}")]
VerificationFailed(String),
#[error("Unsupported proof type: {0}")]
UnsupportedProofType(String),
#[error("Missing verification data: {0}")]
MissingData(String),
#[error("Deserialization error: {0}")]
DeserializationError(String),
#[error("ZK error: {0}")]
ZkError(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationResult {
pub valid: bool,
pub proof_type: ProofType,
pub verified_at: DateTime<Utc>,
pub details: Vec<String>,
pub verification_time_us: u64,
}
impl VerificationResult {
pub fn success(proof_type: ProofType, verification_time_us: u64) -> Self {
Self {
valid: true,
proof_type,
verified_at: Utc::now(),
details: vec!["Proof verification succeeded".to_string()],
verification_time_us,
}
}
pub fn failure(proof_type: ProofType, reason: String, verification_time_us: u64) -> Self {
Self {
valid: false,
proof_type,
verified_at: Utc::now(),
details: vec![format!("Verification failed: {}", reason)],
verification_time_us,
}
}
pub fn with_detail(mut self, detail: String) -> Self {
self.details.push(detail);
self
}
}
fn reconstruct_zk_proof(proof: &StoredProof) -> Result<aingle_zk::ZkProof, VerificationError> {
let mut proof_data: serde_json::Value = serde_json::from_slice(&proof.data)
.map_err(|e| VerificationError::DeserializationError(e.to_string()))?;
if let serde_json::Value::Object(ref mut map) = proof_data {
if !map.contains_key("type") {
let tag = cortex_to_proof_data_tag(&proof.proof_type);
map.insert("type".into(), serde_json::Value::String(tag.into()));
}
}
let zk_type = cortex_to_zk_proof_type(&proof.proof_type);
let envelope = serde_json::json!({
"proof_type": serde_json::to_value(&zk_type)
.map_err(|e| VerificationError::DeserializationError(e.to_string()))?,
"proof_data": proof_data,
"timestamp": proof.created_at.timestamp() as u64,
"metadata": null
});
serde_json::from_value(envelope)
.map_err(|e| VerificationError::DeserializationError(
format!("Failed to reconstruct ZkProof envelope: {e}")
))
}
fn cortex_to_zk_proof_type(pt: &ProofType) -> aingle_zk::ProofType {
match pt {
ProofType::Schnorr | ProofType::Knowledge | ProofType::HashOpening => {
aingle_zk::ProofType::KnowledgeProof
}
ProofType::Equality => aingle_zk::ProofType::EqualityProof,
ProofType::Membership => aingle_zk::ProofType::MembershipProof,
ProofType::NonMembership => aingle_zk::ProofType::NonMembershipProof,
ProofType::Range => aingle_zk::ProofType::RangeProof,
}
}
fn cortex_to_proof_data_tag(pt: &ProofType) -> &'static str {
match pt {
ProofType::Schnorr | ProofType::Knowledge => "Knowledge",
ProofType::HashOpening => "HashOpening",
ProofType::Equality => "Equality",
ProofType::Membership | ProofType::NonMembership => "Membership",
ProofType::Range => "Knowledge",
}
}
pub struct ProofVerifier {
config: VerifierConfig,
}
#[derive(Debug, Clone)]
pub struct VerifierConfig {
pub max_proof_size: usize,
pub timeout_seconds: u64,
pub strict_mode: bool,
}
impl Default for VerifierConfig {
fn default() -> Self {
Self {
max_proof_size: 10 * 1024 * 1024, timeout_seconds: 30,
strict_mode: false,
}
}
}
impl ProofVerifier {
pub fn new() -> Self {
Self::with_config(VerifierConfig::default())
}
pub fn with_config(config: VerifierConfig) -> Self {
Self { config }
}
pub async fn verify(
&self,
proof: &StoredProof,
) -> Result<VerificationResult, VerificationError> {
let start = std::time::Instant::now();
if proof.data.len() > self.config.max_proof_size {
return Err(VerificationError::InvalidProofData(format!(
"Proof size {} exceeds maximum {}",
proof.data.len(),
self.config.max_proof_size
)));
}
let zk_proof: aingle_zk::ZkProof = serde_json::from_slice(&proof.data)
.or_else(|_| reconstruct_zk_proof(proof))?;
let valid = match proof.proof_type {
ProofType::Schnorr => self.verify_schnorr(&zk_proof).await?,
ProofType::Equality => self.verify_equality(&zk_proof).await?,
ProofType::Membership => self.verify_membership(&zk_proof).await?,
ProofType::NonMembership => self.verify_non_membership(&zk_proof).await?,
ProofType::Range => self.verify_range(&zk_proof).await?,
ProofType::HashOpening => self.verify_hash_opening(&zk_proof).await?,
ProofType::Knowledge => self.verify_knowledge(&zk_proof).await?,
};
let elapsed = start.elapsed();
let verification_time_us = elapsed.as_micros() as u64;
if valid {
Ok(VerificationResult::success(
proof.proof_type.clone(),
verification_time_us,
))
} else {
Ok(VerificationResult::failure(
proof.proof_type.clone(),
"Proof verification returned false".to_string(),
verification_time_us,
))
}
}
pub async fn batch_verify(
&self,
proofs: &[StoredProof],
) -> Vec<Result<VerificationResult, VerificationError>> {
let mut results = Vec::new();
for proof in proofs {
results.push(self.verify(proof).await);
}
results
}
async fn verify_schnorr(
&self,
zk_proof: &aingle_zk::ZkProof,
) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify(zk_proof)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
async fn verify_equality(
&self,
zk_proof: &aingle_zk::ZkProof,
) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify(zk_proof)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
async fn verify_membership(
&self,
zk_proof: &aingle_zk::ZkProof,
) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify(zk_proof)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
async fn verify_non_membership(
&self,
zk_proof: &aingle_zk::ZkProof,
) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify(zk_proof)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
async fn verify_range(&self, zk_proof: &aingle_zk::ZkProof) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify(zk_proof)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
async fn verify_hash_opening(
&self,
zk_proof: &aingle_zk::ZkProof,
) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify(zk_proof)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
async fn verify_knowledge(
&self,
zk_proof: &aingle_zk::ZkProof,
) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify(zk_proof)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
pub async fn verify_membership_with_data(
&self,
zk_proof: &aingle_zk::ZkProof,
data: &[u8],
) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify_membership(zk_proof, data)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
pub async fn verify_hash_opening_with_data(
&self,
zk_proof: &aingle_zk::ZkProof,
data: &[u8],
) -> Result<bool, VerificationError> {
aingle_zk::ProofVerifier::verify_hash_opening(zk_proof, data)
.map_err(|e| VerificationError::ZkError(e.to_string()))
}
}
impl Default for ProofVerifier {
fn default() -> Self {
Self::new()
}
}
pub struct BatchVerificationHelper {
verifier: ProofVerifier,
results: Vec<VerificationResult>,
}
impl BatchVerificationHelper {
pub fn new(verifier: ProofVerifier) -> Self {
Self {
verifier,
results: Vec::new(),
}
}
pub async fn add(&mut self, proof: &StoredProof) -> Result<(), VerificationError> {
let result = self.verifier.verify(proof).await?;
self.results.push(result);
Ok(())
}
pub fn results(&self) -> &[VerificationResult] {
&self.results
}
pub fn all_valid(&self) -> bool {
self.results.iter().all(|r| r.valid)
}
pub fn valid_count(&self) -> usize {
self.results.iter().filter(|r| r.valid).count()
}
pub fn invalid_count(&self) -> usize {
self.results.iter().filter(|r| !r.valid).count()
}
pub fn total_time_us(&self) -> u64 {
self.results.iter().map(|r| r.verification_time_us).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::proofs::store::ProofMetadata;
fn create_test_proof(proof_type: ProofType, valid: bool) -> StoredProof {
let zk_proof = if valid {
let commitment = aingle_zk::HashCommitment::commit(b"test data");
aingle_zk::ZkProof::hash_opening(&commitment)
} else {
aingle_zk::ZkProof::hash_opening(&aingle_zk::HashCommitment {
hash: [0u8; 32],
salt: [0u8; 32],
})
};
let proof_json = serde_json::to_vec(&zk_proof).unwrap();
StoredProof::new(proof_type, proof_json, ProofMetadata::default())
}
#[tokio::test]
async fn test_verify_valid_proof() {
let verifier = ProofVerifier::new();
let proof = create_test_proof(ProofType::HashOpening, true);
let result = verifier.verify(&proof).await;
assert!(result.is_ok());
let result = result.unwrap();
assert!(result.valid);
assert_eq!(result.proof_type, ProofType::HashOpening);
assert!(!result.details.is_empty());
}
#[tokio::test]
async fn test_verify_invalid_proof() {
let verifier = ProofVerifier::new();
let proof = create_test_proof(ProofType::HashOpening, false);
let result = verifier.verify(&proof).await;
assert!(result.is_ok());
let result = result.unwrap();
assert!(!result.valid);
}
#[tokio::test]
async fn test_batch_verify() {
let verifier = ProofVerifier::new();
let proofs = vec![
create_test_proof(ProofType::HashOpening, true),
create_test_proof(ProofType::HashOpening, true),
];
let results = verifier.batch_verify(&proofs).await;
assert_eq!(results.len(), 2);
assert!(results[0].is_ok());
assert!(results[1].is_ok());
}
#[tokio::test]
async fn test_batch_verification_helper() {
let verifier = ProofVerifier::new();
let mut helper = BatchVerificationHelper::new(verifier);
let proof1 = create_test_proof(ProofType::Knowledge, true);
let proof2 = create_test_proof(ProofType::Knowledge, true);
helper.add(&proof1).await.unwrap();
helper.add(&proof2).await.unwrap();
assert_eq!(helper.results().len(), 2);
assert!(helper.all_valid());
assert_eq!(helper.valid_count(), 2);
assert_eq!(helper.invalid_count(), 0);
assert!(helper.total_time_us() > 0);
}
#[tokio::test]
async fn test_verification_result_creation() {
let success = VerificationResult::success(ProofType::Schnorr, 1000);
assert!(success.valid);
assert_eq!(success.verification_time_us, 1000);
let failure =
VerificationResult::failure(ProofType::Equality, "Test failure".to_string(), 2000);
assert!(!failure.valid);
assert_eq!(failure.verification_time_us, 2000);
assert!(failure.details.iter().any(|d| d.contains("Test failure")));
}
#[tokio::test]
async fn test_verifier_config() {
let config = VerifierConfig {
max_proof_size: 1024,
timeout_seconds: 10,
strict_mode: true,
};
let verifier = ProofVerifier::with_config(config.clone());
assert_eq!(verifier.config.max_proof_size, 1024);
assert_eq!(verifier.config.timeout_seconds, 10);
assert!(verifier.config.strict_mode);
}
#[tokio::test]
async fn test_verify_proof_stored_without_envelope() {
let verifier = ProofVerifier::new();
let commitment = aingle_zk::HashCommitment::commit(b"test data");
let zk_proof = aingle_zk::ZkProof::hash_opening(&commitment);
let proof_data_only = serde_json::to_vec(&zk_proof.proof_data).unwrap();
let stored = StoredProof::new(
ProofType::HashOpening,
proof_data_only,
ProofMetadata::default(),
);
let result = verifier.verify(&stored).await;
assert!(result.is_ok(), "verify() should reconstruct ZkProof envelope: {:?}", result.err());
assert!(result.unwrap().valid);
}
#[tokio::test]
async fn test_verify_proof_without_type_tag() {
let verifier = ProofVerifier::new();
let commitment = aingle_zk::HashCommitment::commit(b"test data");
let raw = serde_json::json!({
"commitment": commitment.hash,
"salt": commitment.salt,
});
let raw_bytes = serde_json::to_vec(&raw).unwrap();
let stored = StoredProof::new(
ProofType::HashOpening,
raw_bytes,
ProofMetadata::default(),
);
let result = verifier.verify(&stored).await;
assert!(result.is_ok(), "verify() should inject type tag: {:?}", result.err());
assert!(result.unwrap().valid);
}
#[tokio::test]
async fn test_proof_size_limit() {
let config = VerifierConfig {
max_proof_size: 10, timeout_seconds: 30,
strict_mode: false,
};
let verifier = ProofVerifier::with_config(config);
let proof = create_test_proof(ProofType::Knowledge, true);
let result = verifier.verify(&proof).await;
assert!(result.is_err());
match result {
Err(VerificationError::InvalidProofData(msg)) => {
assert!(msg.contains("exceeds maximum"));
}
_ => panic!("Expected InvalidProofData error"),
}
}
}