ever-crypto 0.1.0

A simple Rust library providing easy-to-use APIs for XChaCha20Poly1305 and Kyber1024 post-quantum cryptography at the same time. Based on chacha20poly1305 and pqcrypto-kyber.
Documentation
//! XChaCha20Poly1305 simple API wrapper
//! 
//! Provides easy-to-use functions for authenticated encryption with XChaCha20Poly1305

use chacha20poly1305::{
    aead::{Aead, AeadCore, KeyInit, OsRng},
    XChaCha20Poly1305, XNonce as InternalXNonce,
};
use zeroize::Zeroize;
use crate::{CryptoError, Result};

/// XChaCha20Poly1305 key (256 bits / 32 bytes)
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct XChaChaKey([u8; 32]);

impl XChaChaKey {
    /// Generate a new random key
    pub fn generate() -> Self {
        let key = XChaCha20Poly1305::generate_key(&mut OsRng);
        Self(key.into())
    }
    
    /// Create a key from raw bytes
    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
        if bytes.len() != 32 {
            return Err(CryptoError::InvalidKeyLength {
                expected: 32,
                actual: bytes.len(),
            });
        }
        let mut key = [0u8; 32];
        key.copy_from_slice(bytes);
        Ok(Self(key))
    }
    
    /// Get the raw key bytes
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }
}

/// XChaCha20Poly1305 nonce (192 bits / 24 bytes)
#[derive(Clone, Copy)]
pub struct XChaChaNonce([u8; 24]);

impl XChaChaNonce {
    /// Generate a new random nonce
    pub fn generate() -> Self {
        let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
        Self(nonce.into())
    }
    
    /// Create a nonce from raw bytes
    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
        if bytes.len() != 24 {
            return Err(CryptoError::InvalidNonceLength {
                expected: 24,
                actual: bytes.len(),
            });
        }
        let mut nonce = [0u8; 24];
        nonce.copy_from_slice(bytes);
        Ok(Self(nonce))
    }
    
    /// Get the raw nonce bytes
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }
}

/// Main XChaCha20Poly1305 cryptographic operations
pub struct XChaChaCrypto;

impl XChaChaCrypto {
    /// Encrypt a message with XChaCha20Poly1305
    /// 
    /// # Arguments
    /// * `key` - The encryption key
    /// * `nonce` - A unique nonce for this message
    /// * `plaintext` - The message to encrypt
    /// * `associated_data` - Optional additional authenticated data (AAD)
    /// 
    /// # Returns
    /// The ciphertext with appended authentication tag
    pub fn encrypt(
        key: &XChaChaKey,
        nonce: &XChaChaNonce,
        plaintext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        let cipher = XChaCha20Poly1305::new(key.as_bytes().into());
        let nonce = InternalXNonce::from_slice(nonce.as_bytes());
        let aad = associated_data.unwrap_or(&[]);
        
        cipher
            .encrypt(nonce, aead::Payload { msg: plaintext, aad })
            .map_err(|e| CryptoError::EncryptionError(e.to_string()))
    }
    
    /// Decrypt a message with XChaCha20Poly1305
    /// 
    /// # Arguments
    /// * `key` - The decryption key
    /// * `nonce` - The nonce used for encryption
    /// * `ciphertext` - The ciphertext with authentication tag
    /// * `associated_data` - Optional additional authenticated data (AAD)
    /// 
    /// # Returns
    /// The decrypted plaintext
    pub fn decrypt(
        key: &XChaChaKey,
        nonce: &XChaChaNonce,
        ciphertext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>> {
        let cipher = XChaCha20Poly1305::new(key.as_bytes().into());
        let nonce = InternalXNonce::from_slice(nonce.as_bytes());
        let aad = associated_data.unwrap_or(&[]);
        
        cipher
            .decrypt(nonce, aead::Payload { msg: ciphertext, aad })
            .map_err(|e| CryptoError::DecryptionError(e.to_string()))
    }
    
    /// Encrypt with a randomly generated nonce
    /// 
    /// # Arguments
    /// * `key` - The encryption key
    /// * `plaintext` - The message to encrypt
    /// * `associated_data` - Optional additional authenticated data
    /// 
    /// # Returns
    /// A tuple of (nonce, ciphertext)
    pub fn encrypt_with_random_nonce(
        key: &XChaChaKey,
        plaintext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<(XChaChaNonce, Vec<u8>)> {
        let nonce = XChaChaNonce::generate();
        let ciphertext = Self::encrypt(key, &nonce, plaintext, associated_data)?;
        Ok((nonce, ciphertext))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_key_generation() {
        let key = XChaChaKey::generate();
        assert_eq!(key.as_bytes().len(), 32);
    }
    
    #[test]
    fn test_nonce_generation() {
        let nonce = XChaChaNonce::generate();
        assert_eq!(nonce.as_bytes().len(), 24);
    }
    
    #[test]
    fn test_encrypt_decrypt() {
        let key = XChaChaKey::generate();
        let plaintext = b"Hello, World!";
        let aad = b"Additional data";
        
        // Test with generated nonce
        let (nonce, ciphertext) = XChaChaCrypto::encrypt_with_random_nonce(
            &key,
            plaintext,
            Some(aad),
        ).unwrap();
        
        let decrypted = XChaChaCrypto::decrypt(
            &key,
            &nonce,
            &ciphertext,
            Some(aad),
        ).unwrap();
        
        assert_eq!(plaintext, &decrypted[..]);
    }
    
    #[test]
    fn test_encrypt_decrypt_no_aad() {
        let key = XChaChaKey::generate();
        let nonce = XChaChaNonce::generate();
        let plaintext = b"Secret message";
        
        let ciphertext = XChaChaCrypto::encrypt(&key, &nonce, plaintext, None).unwrap();
        let decrypted = XChaChaCrypto::decrypt(&key, &nonce, &ciphertext, None).unwrap();
        
        assert_eq!(plaintext, &decrypted[..]);
    }
    
    #[test]
    fn test_wrong_key_fails() {
        let key1 = XChaChaKey::generate();
        let key2 = XChaChaKey::generate();
        let nonce = XChaChaNonce::generate();
        let plaintext = b"Secret";
        
        let ciphertext = XChaChaCrypto::encrypt(&key1, &nonce, plaintext, None).unwrap();
        let result = XChaChaCrypto::decrypt(&key2, &nonce, &ciphertext, None);
        
        assert!(result.is_err());
    }
}