uvb-mrvb 0.2.1

Multi-Rail Verification Bus (MRVB) with post-quantum cryptography support
Documentation
//! Hybrid classical + post-quantum cryptography for MRVB
//!
//! This module provides hybrid signing that combines Ed25519 (classical) with
//! Dilithium3 (post-quantum) to provide both immediate security and future-proof
//! quantum resistance.

use crate::error::{MrvbError, MrvbResult};
use crate::pqc::{Dilithium3KeyPair, Dilithium3PublicKey, Dilithium3Signer, Dilithium3Verifier};
use base64::Engine;
use ed25519_dalek::{Signature, Signer as Ed25519SignerTrait, SigningKey, Verifier, VerifyingKey};
use serde::{Deserialize, Serialize};

/// Hybrid signature containing both classical and post-quantum signatures
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HybridSignature {
    /// Ed25519 signature (64 bytes)
    pub classical_sig: String, // base64-encoded
    /// Dilithium3 signature
    pub pqc_sig: String, // base64-encoded
    /// Algorithm identifier
    pub algorithm: String,
}

impl HybridSignature {
    /// Create a new hybrid signature
    pub fn new(classical_sig: String, pqc_sig: String) -> Self {
        Self {
            classical_sig,
            pqc_sig,
            algorithm: "Ed25519+Dilithium3".to_string(),
        }
    }

    /// Serialize to JSON
    pub fn to_json(&self) -> MrvbResult<String> {
        serde_json::to_string(self).map_err(|e| MrvbError::EncodingError(e.to_string()))
    }

    /// Deserialize from JSON
    pub fn from_json(json: &str) -> MrvbResult<Self> {
        serde_json::from_str(json).map_err(|e| MrvbError::EncodingError(e.to_string()))
    }
}

/// Hybrid key pair containing both classical and post-quantum keys
#[derive(Debug, Clone)]
pub struct HybridKeyPair {
    pub ed25519_signing_key: SigningKey,
    pub ed25519_verifying_key: VerifyingKey,
    pub dilithium3_keypair: Dilithium3KeyPair,
}

impl HybridKeyPair {
    /// Generate a new hybrid key pair
    pub fn generate() -> Self {
        let mut rng = rand::thread_rng();

        // Generate Ed25519 key pair
        let ed25519_signing_key = SigningKey::generate(&mut rng);
        let ed25519_verifying_key = ed25519_signing_key.verifying_key();

        // Generate Dilithium3 key pair
        let dilithium3_keypair = Dilithium3KeyPair::generate();

        Self {
            ed25519_signing_key,
            ed25519_verifying_key,
            dilithium3_keypair,
        }
    }

    /// Get the Ed25519 verifying key bytes
    pub fn ed25519_public_key_bytes(&self) -> [u8; 32] {
        self.ed25519_verifying_key.to_bytes()
    }

    /// Get the Dilithium3 public key
    pub fn dilithium3_public_key(&self) -> &Dilithium3PublicKey {
        &self.dilithium3_keypair.public_key
    }
}

/// Hybrid signer that produces both classical and post-quantum signatures
pub struct HybridSigner {
    ed25519_signing_key: SigningKey,
    ed25519_verifying_key: VerifyingKey,
    dilithium3_signer: Dilithium3Signer,
}

impl HybridSigner {
    /// Create a new hybrid signer from a key pair
    pub fn new(keypair: HybridKeyPair) -> Self {
        let dilithium3_signer = Dilithium3Signer::new(keypair.dilithium3_keypair);

        Self {
            ed25519_signing_key: keypair.ed25519_signing_key,
            ed25519_verifying_key: keypair.ed25519_verifying_key,
            dilithium3_signer,
        }
    }

    /// Get the Ed25519 verifying key
    pub fn ed25519_verifying_key(&self) -> &VerifyingKey {
        &self.ed25519_verifying_key
    }

    /// Get the Dilithium3 public key
    pub fn dilithium3_public_key(&self) -> &Dilithium3PublicKey {
        self.dilithium3_signer.public_key()
    }

    /// Sign a message with both Ed25519 and Dilithium3
    pub fn sign(&self, message: &[u8]) -> MrvbResult<HybridSignature> {
        // Sign with Ed25519
        let ed25519_sig: Signature = self.ed25519_signing_key.sign(message);
        let classical_sig = base64::engine::general_purpose::STANDARD.encode(ed25519_sig.to_bytes());

        // Sign with Dilithium3
        let pqc_sig = self.dilithium3_signer.sign_base64(message)?;

        Ok(HybridSignature::new(classical_sig, pqc_sig))
    }

    /// Sign a message and return JSON-encoded signature
    pub fn sign_json(&self, message: &[u8]) -> MrvbResult<String> {
        let sig = self.sign(message)?;
        sig.to_json()
    }
}

impl std::fmt::Debug for HybridSigner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("HybridSigner")
            .field("ed25519_verifying_key", &self.ed25519_verifying_key)
            .field("dilithium3_signer", &self.dilithium3_signer)
            .finish()
    }
}

/// Hybrid verifier that verifies both classical and post-quantum signatures
#[derive(Debug, Clone)]
pub struct HybridVerifier {
    ed25519_verifying_key: VerifyingKey,
    dilithium3_verifier: Dilithium3Verifier,
}

impl HybridVerifier {
    /// Create a new hybrid verifier
    pub fn new(
        ed25519_verifying_key: VerifyingKey,
        dilithium3_public_key: Dilithium3PublicKey,
    ) -> Self {
        let dilithium3_verifier = Dilithium3Verifier::new(dilithium3_public_key);

        Self {
            ed25519_verifying_key,
            dilithium3_verifier,
        }
    }

    /// Create from raw public key bytes
    pub fn from_public_key_bytes(
        ed25519_public_key: &[u8; 32],
        dilithium3_public_key_bytes: &[u8],
    ) -> MrvbResult<Self> {
        let ed25519_verifying_key = VerifyingKey::from_bytes(ed25519_public_key)
            .map_err(|e| MrvbError::CryptoError(format!("Invalid Ed25519 public key: {}", e)))?;

        let dilithium3_public_key = Dilithium3PublicKey::from_bytes(dilithium3_public_key_bytes)?;

        Ok(Self::new(ed25519_verifying_key, dilithium3_public_key))
    }

    /// Get the Ed25519 verifying key
    pub fn ed25519_verifying_key(&self) -> &VerifyingKey {
        &self.ed25519_verifying_key
    }

    /// Get the Dilithium3 public key
    pub fn dilithium3_public_key(&self) -> &Dilithium3PublicKey {
        self.dilithium3_verifier.public_key()
    }

    /// Verify a hybrid signature
    ///
    /// Returns `Ok(true)` only if BOTH signatures are valid.
    /// Returns `Ok(false)` if either signature is invalid.
    /// Returns `Err` if there's a processing error.
    pub fn verify(&self, message: &[u8], signature: &HybridSignature) -> MrvbResult<bool> {
        // Verify algorithm
        if signature.algorithm != "Ed25519+Dilithium3" {
            return Err(MrvbError::InvalidAlgorithm {
                expected: "Ed25519+Dilithium3".to_string(),
                actual: signature.algorithm.clone(),
            });
        }

        // Verify Ed25519 signature
        let ed25519_sig_bytes = base64::engine::general_purpose::STANDARD
            .decode(&signature.classical_sig)
            .map_err(|e| MrvbError::EncodingError(e.to_string()))?;

        let ed25519_sig = Signature::from_slice(&ed25519_sig_bytes)
            .map_err(|e| MrvbError::CryptoError(format!("Invalid Ed25519 signature: {}", e)))?;

        let ed25519_valid = self
            .ed25519_verifying_key
            .verify(message, &ed25519_sig)
            .is_ok();

        if !ed25519_valid {
            // Classical signature failed - no need to check PQC
            return Ok(false);
        }

        // Verify Dilithium3 signature
        let dilithium3_valid = self
            .dilithium3_verifier
            .verify_base64(message, &signature.pqc_sig)?;

        // Both must be valid
        Ok(ed25519_valid && dilithium3_valid)
    }

    /// Verify a JSON-encoded hybrid signature
    pub fn verify_json(&self, message: &[u8], signature_json: &str) -> MrvbResult<bool> {
        let signature = HybridSignature::from_json(signature_json)?;
        self.verify(message, &signature)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_hybrid_key_generation() {
        let keypair = HybridKeyPair::generate();

        // Check Ed25519 key
        assert_eq!(keypair.ed25519_public_key_bytes().len(), 32);

        // Check Dilithium3 key
        assert!(!keypair.dilithium3_public_key().as_bytes().is_empty());
    }

    #[test]
    fn test_hybrid_sign_and_verify() {
        let keypair = HybridKeyPair::generate();
        let signer = HybridSigner::new(keypair.clone());

        let verifier = HybridVerifier::new(
            keypair.ed25519_verifying_key,
            keypair.dilithium3_keypair.public_key,
        );

        let message = b"Hybrid quantum-resistant message";
        let signature = signer.sign(message).unwrap();

        // Should verify with correct message
        assert!(verifier.verify(message, &signature).unwrap());

        // Should fail with wrong message
        let wrong_message = b"Wrong message";
        assert!(!verifier.verify(wrong_message, &signature).unwrap());
    }

    #[test]
    fn test_hybrid_signature_json() {
        let keypair = HybridKeyPair::generate();
        let signer = HybridSigner::new(keypair.clone());

        let message = b"Test message";
        let sig_json = signer.sign_json(message).unwrap();

        // Verify JSON contains expected fields
        assert!(sig_json.contains("classical_sig"));
        assert!(sig_json.contains("pqc_sig"));
        assert!(sig_json.contains("Ed25519+Dilithium3"));

        // Verify with JSON signature
        let verifier = HybridVerifier::new(
            keypair.ed25519_verifying_key,
            keypair.dilithium3_keypair.public_key,
        );
        assert!(verifier.verify_json(message, &sig_json).unwrap());
    }

    #[test]
    fn test_hybrid_signature_structure() {
        let keypair = HybridKeyPair::generate();
        let signer = HybridSigner::new(keypair);

        let message = b"Test";
        let signature = signer.sign(message).unwrap();

        // Check algorithm
        assert_eq!(signature.algorithm, "Ed25519+Dilithium3");

        // Check both signatures are present and base64-encoded
        assert!(!signature.classical_sig.is_empty());
        assert!(!signature.pqc_sig.is_empty());

        // Verify can be serialized/deserialized
        let json = signature.to_json().unwrap();
        let restored = HybridSignature::from_json(&json).unwrap();

        assert_eq!(signature.classical_sig, restored.classical_sig);
        assert_eq!(signature.pqc_sig, restored.pqc_sig);
        assert_eq!(signature.algorithm, restored.algorithm);
    }

    #[test]
    fn test_invalid_algorithm_rejection() {
        let keypair = HybridKeyPair::generate();
        let signer = HybridSigner::new(keypair.clone());
        let verifier = HybridVerifier::new(
            keypair.ed25519_verifying_key,
            keypair.dilithium3_keypair.public_key,
        );

        let message = b"Test";
        let mut signature = signer.sign(message).unwrap();

        // Tamper with algorithm
        signature.algorithm = "WrongAlgorithm".to_string();

        // Should reject
        let result = verifier.verify(message, &signature);
        assert!(result.is_err());

        match result {
            Err(MrvbError::InvalidAlgorithm { expected, actual }) => {
                assert_eq!(expected, "Ed25519+Dilithium3");
                assert_eq!(actual, "WrongAlgorithm");
            }
            _ => panic!("Expected InvalidAlgorithm error"),
        }
    }

    #[test]
    fn test_tampered_classical_signature_rejected() {
        let keypair = HybridKeyPair::generate();
        let signer = HybridSigner::new(keypair.clone());
        let verifier = HybridVerifier::new(
            keypair.ed25519_verifying_key,
            keypair.dilithium3_keypair.public_key,
        );

        let message = b"Test";
        let mut signature = signer.sign(message).unwrap();

        // Tamper with classical signature
        signature.classical_sig = base64::engine::general_purpose::STANDARD.encode(&[0u8; 64]);

        // Should reject
        assert!(!verifier.verify(message, &signature).unwrap());
    }

    #[test]
    fn test_tampered_pqc_signature_rejected() {
        let keypair = HybridKeyPair::generate();
        let signer = HybridSigner::new(keypair.clone());
        let verifier = HybridVerifier::new(
            keypair.ed25519_verifying_key,
            keypair.dilithium3_keypair.public_key,
        );

        let message = b"Test";
        let mut signature = signer.sign(message).unwrap();

        // Tamper with PQC signature
        signature.pqc_sig = base64::engine::general_purpose::STANDARD.encode(&[0u8; 100]);

        // Should reject (will likely fail during signature decoding or verification)
        let result = verifier.verify(message, &signature);
        // Either error or false is acceptable for tampered signature
        assert!(result.is_err() || !result.unwrap());
    }

    #[test]
    fn test_both_signatures_must_be_valid() {
        // Generate two different key pairs
        let keypair1 = HybridKeyPair::generate();
        let keypair2 = HybridKeyPair::generate();

        let signer = HybridSigner::new(keypair1.clone());

        // Create verifier with mixed keys (Ed25519 from keypair1, Dilithium3 from keypair2)
        let verifier = HybridVerifier::new(
            keypair1.ed25519_verifying_key, // Correct Ed25519 key
            keypair2.dilithium3_keypair.public_key, // Wrong Dilithium3 key
        );

        let message = b"Test";
        let signature = signer.sign(message).unwrap();

        // Should fail because Dilithium3 key doesn't match
        assert!(!verifier.verify(message, &signature).unwrap());
    }
}