use super::proof_certificate::{ProofCertificate, Verified};
use aes_gcm::{
Aes256Gcm, Nonce,
aead::{Aead, KeyInit, Payload},
};
pub const AES_GCM_ROUNDTRIP_CERT: ProofCertificate = ProofCertificate::new(
300, 1, 2026_0205, 1, );
pub const AES_GCM_INTEGRITY_CERT: ProofCertificate = ProofCertificate::new(
301, 1, 2026_0205, 1, );
pub const NONCE_UNIQUENESS_CERT: ProofCertificate = ProofCertificate::new(
302, 1, 2026_0205, 1, );
pub const IND_CCA2_CERT: ProofCertificate = ProofCertificate::new(
303, 1, 2026_0205, 2, );
pub struct VerifiedAesGcm;
impl VerifiedAesGcm {
pub fn encrypt(
key: &[u8; 32],
nonce: &[u8; 12],
plaintext: &[u8],
associated_data: &[u8],
) -> Result<Vec<u8>, String> {
assert_ne!(key, &[0u8; 32], "AES-256 key is all zeros (degenerate key)");
assert_ne!(nonce, &[0u8; 12], "GCM nonce is all zeros (weak nonce)");
let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| e.to_string())?;
let nonce_obj = Nonce::from_slice(nonce);
let payload = Payload {
msg: plaintext,
aad: associated_data,
};
cipher
.encrypt(nonce_obj, payload)
.map_err(|e| e.to_string())
}
pub fn decrypt(
key: &[u8; 32],
nonce: &[u8; 12],
ciphertext: &[u8],
associated_data: &[u8],
) -> Result<Vec<u8>, String> {
let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| e.to_string())?;
let nonce_obj = Nonce::from_slice(nonce);
let payload = Payload {
msg: ciphertext,
aad: associated_data,
};
cipher.decrypt(nonce_obj, payload).map_err(|_| {
"Authentication failed: ciphertext tampered or wrong key/nonce".to_string()
})
}
pub fn nonce_from_position(position: u64) -> [u8; 12] {
let mut nonce = [0u8; 12];
let biased = position
.checked_add(1)
.expect("nonce position overflow: position must be less than u64::MAX");
nonce[0..8].copy_from_slice(&biased.to_le_bytes());
nonce
}
}
impl Verified for VerifiedAesGcm {
fn proof_certificate() -> ProofCertificate {
AES_GCM_ROUNDTRIP_CERT
}
fn theorem_name() -> &'static str {
"aes_gcm_roundtrip"
}
fn theorem_description() -> &'static str {
"AES-256-GCM encryption/decryption roundtrip: decrypt(encrypt(plaintext)) = plaintext"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt_decrypt_roundtrip() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let plaintext = b"secret message";
let ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, plaintext, b"").expect("encryption failed");
let decrypted =
VerifiedAesGcm::decrypt(&key, &nonce, &ciphertext, b"").expect("decryption failed");
assert_eq!(plaintext, &decrypted[..]);
}
#[test]
fn test_with_associated_data() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let plaintext = b"secret message";
let aad = b"additional context";
let ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, plaintext, aad).expect("encryption failed");
let decrypted =
VerifiedAesGcm::decrypt(&key, &nonce, &ciphertext, aad).expect("decryption failed");
assert_eq!(plaintext, &decrypted[..]);
}
#[test]
fn test_wrong_key_fails() {
let key = [0x42; 32];
let wrong_key = [0x43; 32];
let nonce = [0x01; 12];
let plaintext = b"secret";
let ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, plaintext, b"").expect("encryption failed");
let result = VerifiedAesGcm::decrypt(&wrong_key, &nonce, &ciphertext, b"");
assert!(result.is_err());
}
#[test]
fn test_wrong_nonce_fails() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let wrong_nonce = [0x02; 12];
let plaintext = b"secret";
let ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, plaintext, b"").expect("encryption failed");
let result = VerifiedAesGcm::decrypt(&key, &wrong_nonce, &ciphertext, b"");
assert!(result.is_err());
}
#[test]
fn test_wrong_aad_fails() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let plaintext = b"secret";
let aad = b"context";
let wrong_aad = b"wrong";
let ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, plaintext, aad).expect("encryption failed");
let result = VerifiedAesGcm::decrypt(&key, &nonce, &ciphertext, wrong_aad);
assert!(result.is_err());
}
#[test]
fn test_tampered_ciphertext_fails() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let plaintext = b"secret message";
let mut ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, plaintext, b"").expect("encryption failed");
if !ciphertext.is_empty() {
ciphertext[0] ^= 0xFF;
}
let result = VerifiedAesGcm::decrypt(&key, &nonce, &ciphertext, b"");
assert!(result.is_err());
}
#[test]
fn test_tampered_tag_fails() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let plaintext = b"secret message";
let mut ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, plaintext, b"").expect("encryption failed");
if ciphertext.len() >= 16 {
let len = ciphertext.len();
ciphertext[len - 1] ^= 0xFF;
}
let result = VerifiedAesGcm::decrypt(&key, &nonce, &ciphertext, b"");
assert!(result.is_err());
}
#[test]
fn test_empty_plaintext() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let plaintext = b"";
let ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, plaintext, b"").expect("encryption failed");
assert_eq!(ciphertext.len(), 16);
let decrypted =
VerifiedAesGcm::decrypt(&key, &nonce, &ciphertext, b"").expect("decryption failed");
assert_eq!(plaintext, &decrypted[..]);
}
#[test]
fn test_large_plaintext() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let plaintext = vec![0xAB; 100_000];
let ciphertext =
VerifiedAesGcm::encrypt(&key, &nonce, &plaintext, b"").expect("encryption failed");
let decrypted =
VerifiedAesGcm::decrypt(&key, &nonce, &ciphertext, b"").expect("decryption failed");
assert_eq!(&plaintext[..], &decrypted[..]);
}
#[test]
fn test_nonce_from_position_unique() {
let nonce1 = VerifiedAesGcm::nonce_from_position(0);
let nonce2 = VerifiedAesGcm::nonce_from_position(1);
let nonce3 = VerifiedAesGcm::nonce_from_position(1000);
assert_ne!(nonce1, nonce2);
assert_ne!(nonce1, nonce3);
assert_ne!(nonce2, nonce3);
}
#[test]
fn test_nonce_from_position_deterministic() {
let nonce1 = VerifiedAesGcm::nonce_from_position(42);
let nonce2 = VerifiedAesGcm::nonce_from_position(42);
assert_eq!(nonce1, nonce2);
}
#[test]
fn test_nonce_from_position_layout() {
let position: u64 = 0x0123_4567_89AB_CDEF;
let nonce = VerifiedAesGcm::nonce_from_position(position);
let reconstructed = u64::from_le_bytes(nonce[0..8].try_into().unwrap());
assert_eq!(reconstructed, position + 1);
assert_eq!(&nonce[8..12], &[0, 0, 0, 0]);
}
#[test]
fn test_proof_certificate() {
let cert = VerifiedAesGcm::proof_certificate();
assert_eq!(cert.theorem_id, 300);
assert_eq!(cert.proof_system_id, 1);
assert_eq!(cert.verified_at, 20_260_205);
assert_eq!(cert.assumption_count, 1);
assert!(!cert.is_complete()); }
#[test]
fn test_verified_trait() {
assert_eq!(VerifiedAesGcm::theorem_name(), "aes_gcm_roundtrip");
assert!(VerifiedAesGcm::theorem_description().contains("roundtrip"));
}
#[test]
fn test_different_plaintexts_different_ciphertexts() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let ct1 =
VerifiedAesGcm::encrypt(&key, &nonce, b"message1", b"").expect("encryption failed");
let ct2 =
VerifiedAesGcm::encrypt(&key, &nonce, b"message2", b"").expect("encryption failed");
assert_ne!(ct1, ct2);
}
#[test]
#[should_panic(expected = "AES-256 key is all zeros")]
fn test_encrypt_rejects_zero_key() {
let key = [0u8; 32];
let nonce = [0x01; 12];
let _ = VerifiedAesGcm::encrypt(&key, &nonce, b"test", b"");
}
#[test]
#[should_panic(expected = "GCM nonce is all zeros")]
fn test_encrypt_rejects_zero_nonce() {
let key = [0x42; 32];
let nonce = [0u8; 12];
let _ = VerifiedAesGcm::encrypt(&key, &nonce, b"test", b"");
}
#[test]
#[should_panic(expected = "nonce position overflow")]
fn test_nonce_from_position_overflow() {
VerifiedAesGcm::nonce_from_position(u64::MAX);
}
#[test]
fn test_deterministic_encryption() {
let key = [0x42; 32];
let nonce = [0x01; 12];
let plaintext = b"deterministic test";
let ct1 = VerifiedAesGcm::encrypt(&key, &nonce, plaintext, b"").expect("encryption failed");
let ct2 = VerifiedAesGcm::encrypt(&key, &nonce, plaintext, b"").expect("encryption failed");
assert_eq!(ct1, ct2);
}
}
#[cfg(kani)]
mod kani_harness {
#[kani::proof]
#[kani::unwind(1)]
fn verify_aes_gcm_key_size_bounded() {
let key_len = core::mem::size_of::<[u8; 32]>();
assert_eq!(key_len, 32, "AES-256-GCM key must be exactly 32 bytes");
}
}