use lib_q_core::{
Aead,
AeadKey,
Algorithm,
Nonce,
Result,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PerformanceTier {
UltraSecure,
Balanced,
Performance,
Hybrid,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AeadMetadata {
pub algorithm: Algorithm,
pub key_size: usize,
pub nonce_size: usize,
pub tag_size: usize,
pub security_level: u32,
pub name: &'static str,
pub description: &'static str,
pub supports_semantic_decrypt: bool,
}
impl AeadMetadata {
#[allow(clippy::too_many_arguments)]
pub const fn new(
algorithm: Algorithm,
key_size: usize,
nonce_size: usize,
tag_size: usize,
security_level: u32,
name: &'static str,
description: &'static str,
supports_semantic_decrypt: bool,
) -> Self {
Self {
algorithm,
key_size,
nonce_size,
tag_size,
security_level,
name,
description,
supports_semantic_decrypt,
}
}
pub fn performance_tier(&self) -> PerformanceTier {
match self.security_level {
1 => PerformanceTier::Performance,
3 => PerformanceTier::Balanced,
4 => PerformanceTier::UltraSecure,
5 => PerformanceTier::Hybrid,
_ => PerformanceTier::Balanced,
}
}
pub fn is_suitable_for_security_level(&self, required_level: u32) -> bool {
self.security_level >= required_level
}
pub fn total_overhead(&self) -> usize {
self.nonce_size + self.tag_size
}
pub fn validate_key_size(&self, key_size: usize) -> bool {
key_size == self.key_size
}
pub fn validate_nonce_size(&self, nonce_size: usize) -> bool {
nonce_size == self.nonce_size
}
pub fn validate_tag_size(&self, tag_size: usize) -> bool {
tag_size == self.tag_size
}
}
pub trait AeadWithMetadata: Aead {
fn metadata(&self) -> &'static AeadMetadata;
fn algorithm(&self) -> Algorithm {
self.metadata().algorithm
}
fn key_size(&self) -> usize {
self.metadata().key_size
}
fn nonce_size(&self) -> usize {
self.metadata().nonce_size
}
fn tag_size(&self) -> usize {
self.metadata().tag_size
}
fn security_level(&self) -> u32 {
self.metadata().security_level
}
fn algorithm_name(&self) -> &'static str {
self.metadata().name
}
fn algorithm_description(&self) -> &'static str {
self.metadata().description
}
fn supports_semantic_decrypt(&self) -> bool {
self.metadata().supports_semantic_decrypt
}
fn validate_key(&self, key: &AeadKey) -> Result<()> {
if !self.metadata().validate_key_size(key.as_bytes().len()) {
return Err(lib_q_core::Error::InvalidKeySize {
expected: self.key_size(),
actual: key.as_bytes().len(),
});
}
Ok(())
}
fn validate_nonce(&self, nonce: &Nonce) -> Result<()> {
if !self.metadata().validate_nonce_size(nonce.as_bytes().len()) {
return Err(lib_q_core::Error::InvalidNonceSize {
expected: self.nonce_size(),
actual: nonce.as_bytes().len(),
});
}
Ok(())
}
fn validate_ciphertext_size(&self, ciphertext_size: usize) -> Result<()> {
if ciphertext_size < self.tag_size() {
return Err(lib_q_core::Error::InvalidCiphertextSize {
expected: self.tag_size(),
actual: ciphertext_size,
});
}
Ok(())
}
}
pub fn get_metadata(algorithm: Algorithm) -> Option<&'static AeadMetadata> {
static SATURNIN_METADATA: AeadMetadata = AeadMetadata::new(
Algorithm::Saturnin,
32, 16, 32, 1, "Saturnin",
"Lightweight post-quantum symmetric algorithm suite for IoT and constrained devices",
true,
);
static SHAKE256_METADATA: AeadMetadata = AeadMetadata::new(
Algorithm::Shake256Aead,
32, 16, 32, 1, "SHAKE256-AEAD",
"SHAKE256-based AEAD construction using post-quantum hash function",
true,
);
static DUPLEX_SPONGE_AEAD_METADATA: AeadMetadata = AeadMetadata::new(
Algorithm::DuplexSpongeAead,
32,
16,
32,
4,
"Duplex-Sponge-AEAD",
"Keccak-f[1600] duplex-sponge authenticated encryption",
true,
);
static TWEAK_AEAD_METADATA: AeadMetadata = AeadMetadata::new(
Algorithm::TweakAead,
32,
16,
32,
4,
"Tweak-AEAD",
"Parallel tweakable-block CTR AEAD over Keccak-f[1600]",
true,
);
static ROMULUS_N_METADATA: AeadMetadata = AeadMetadata::new(
Algorithm::RomulusN,
16,
16,
16,
1,
"Romulus-N",
"Romulus-N nonce-based AEAD (SKINNY-128-384+), LWC v1.3",
true,
);
static ROMULUS_M_METADATA: AeadMetadata = AeadMetadata::new(
Algorithm::RomulusM,
16,
16,
16,
1,
"Romulus-M",
"Romulus-M misuse-resistant AEAD (SKINNY-128-384+), LWC v1.3",
true,
);
match algorithm {
Algorithm::Saturnin => Some(&SATURNIN_METADATA),
Algorithm::Shake256Aead => Some(&SHAKE256_METADATA),
Algorithm::DuplexSpongeAead => Some(&DUPLEX_SPONGE_AEAD_METADATA),
Algorithm::TweakAead => Some(&TWEAK_AEAD_METADATA),
Algorithm::RomulusN => Some(&ROMULUS_N_METADATA),
Algorithm::RomulusM => Some(&ROMULUS_M_METADATA),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metadata_creation() {
let metadata = AeadMetadata::new(
Algorithm::Saturnin,
32,
16,
32,
1,
"Saturnin",
"Test algorithm",
true,
);
assert_eq!(metadata.algorithm, Algorithm::Saturnin);
assert_eq!(metadata.key_size, 32);
assert_eq!(metadata.nonce_size, 16);
assert_eq!(metadata.tag_size, 32);
assert_eq!(metadata.security_level, 1);
assert_eq!(metadata.name, "Saturnin");
assert_eq!(metadata.description, "Test algorithm");
assert!(metadata.supports_semantic_decrypt);
}
#[test]
fn test_performance_tier() {
let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 1, "Test", "Test", false);
assert_eq!(metadata.performance_tier(), PerformanceTier::Performance);
let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 3, "Test", "Test", false);
assert_eq!(metadata.performance_tier(), PerformanceTier::Balanced);
let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 4, "Test", "Test", false);
assert_eq!(metadata.performance_tier(), PerformanceTier::UltraSecure);
let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 5, "Test", "Test", false);
assert_eq!(metadata.performance_tier(), PerformanceTier::Hybrid);
}
#[test]
fn test_security_level_suitability() {
let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 3, "Test", "Test", false);
assert!(metadata.is_suitable_for_security_level(1));
assert!(metadata.is_suitable_for_security_level(3));
assert!(!metadata.is_suitable_for_security_level(4));
assert!(!metadata.is_suitable_for_security_level(5));
}
#[test]
fn test_size_validation() {
let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 1, "Test", "Test", false);
assert!(metadata.validate_key_size(32));
assert!(!metadata.validate_key_size(16));
assert!(!metadata.validate_key_size(64));
assert!(metadata.validate_nonce_size(16));
assert!(!metadata.validate_nonce_size(12));
assert!(!metadata.validate_nonce_size(24));
assert!(metadata.validate_tag_size(32));
assert!(!metadata.validate_tag_size(16));
assert!(!metadata.validate_tag_size(64));
}
#[test]
fn test_total_overhead() {
let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 1, "Test", "Test", false);
assert_eq!(metadata.total_overhead(), 48); }
#[test]
fn test_get_metadata() {
let metadata = get_metadata(Algorithm::Saturnin);
assert!(metadata.is_some());
if let Some(meta) = metadata {
assert_eq!(meta.algorithm, Algorithm::Saturnin);
assert_eq!(meta.name, "Saturnin");
}
let metadata = get_metadata(Algorithm::MlKem512);
assert!(metadata.is_none());
}
#[test]
fn registered_aead_metadata_marks_semantic_decrypt() {
for alg in [
Algorithm::Saturnin,
Algorithm::Shake256Aead,
Algorithm::DuplexSpongeAead,
Algorithm::TweakAead,
Algorithm::RomulusN,
Algorithm::RomulusM,
] {
let m = get_metadata(alg).expect("AEAD metadata");
assert!(m.supports_semantic_decrypt, "{alg:?}");
}
}
}