1use lib_q_core::{
7 Aead,
8 AeadKey,
9 Algorithm,
10 Nonce,
11 Result,
12};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum PerformanceTier {
17 UltraSecure,
19 Balanced,
21 Performance,
23 Hybrid,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct AeadMetadata {
30 pub algorithm: Algorithm,
32 pub key_size: usize,
34 pub nonce_size: usize,
36 pub tag_size: usize,
38 pub security_level: u32,
40 pub name: &'static str,
42 pub description: &'static str,
44 pub supports_semantic_decrypt: bool,
50}
51
52impl AeadMetadata {
53 #[allow(clippy::too_many_arguments)]
58 pub const fn new(
59 algorithm: Algorithm,
60 key_size: usize,
61 nonce_size: usize,
62 tag_size: usize,
63 security_level: u32,
64 name: &'static str,
65 description: &'static str,
66 supports_semantic_decrypt: bool,
67 ) -> Self {
68 Self {
69 algorithm,
70 key_size,
71 nonce_size,
72 tag_size,
73 security_level,
74 name,
75 description,
76 supports_semantic_decrypt,
77 }
78 }
79
80 pub fn performance_tier(&self) -> PerformanceTier {
82 match self.security_level {
83 1 => PerformanceTier::Performance,
84 3 => PerformanceTier::Balanced,
85 4 => PerformanceTier::UltraSecure,
86 5 => PerformanceTier::Hybrid,
87 _ => PerformanceTier::Balanced,
88 }
89 }
90
91 pub fn is_suitable_for_security_level(&self, required_level: u32) -> bool {
93 self.security_level >= required_level
94 }
95
96 pub fn total_overhead(&self) -> usize {
98 self.nonce_size + self.tag_size
99 }
100
101 pub fn validate_key_size(&self, key_size: usize) -> bool {
103 key_size == self.key_size
104 }
105
106 pub fn validate_nonce_size(&self, nonce_size: usize) -> bool {
108 nonce_size == self.nonce_size
109 }
110
111 pub fn validate_tag_size(&self, tag_size: usize) -> bool {
113 tag_size == self.tag_size
114 }
115}
116
117pub trait AeadWithMetadata: Aead {
119 fn metadata(&self) -> &'static AeadMetadata;
121
122 fn algorithm(&self) -> Algorithm {
124 self.metadata().algorithm
125 }
126
127 fn key_size(&self) -> usize {
129 self.metadata().key_size
130 }
131
132 fn nonce_size(&self) -> usize {
134 self.metadata().nonce_size
135 }
136
137 fn tag_size(&self) -> usize {
139 self.metadata().tag_size
140 }
141
142 fn security_level(&self) -> u32 {
144 self.metadata().security_level
145 }
146
147 fn algorithm_name(&self) -> &'static str {
149 self.metadata().name
150 }
151
152 fn algorithm_description(&self) -> &'static str {
154 self.metadata().description
155 }
156
157 fn supports_semantic_decrypt(&self) -> bool {
163 self.metadata().supports_semantic_decrypt
164 }
165
166 fn validate_key(&self, key: &AeadKey) -> Result<()> {
168 if !self.metadata().validate_key_size(key.as_bytes().len()) {
169 return Err(lib_q_core::Error::InvalidKeySize {
170 expected: self.key_size(),
171 actual: key.as_bytes().len(),
172 });
173 }
174 Ok(())
175 }
176
177 fn validate_nonce(&self, nonce: &Nonce) -> Result<()> {
179 if !self.metadata().validate_nonce_size(nonce.as_bytes().len()) {
180 return Err(lib_q_core::Error::InvalidNonceSize {
181 expected: self.nonce_size(),
182 actual: nonce.as_bytes().len(),
183 });
184 }
185 Ok(())
186 }
187
188 fn validate_ciphertext_size(&self, ciphertext_size: usize) -> Result<()> {
190 if ciphertext_size < self.tag_size() {
191 return Err(lib_q_core::Error::InvalidCiphertextSize {
192 expected: self.tag_size(),
193 actual: ciphertext_size,
194 });
195 }
196 Ok(())
197 }
198}
199
200pub fn get_metadata(algorithm: Algorithm) -> Option<&'static AeadMetadata> {
202 static SATURNIN_METADATA: AeadMetadata = AeadMetadata::new(
204 Algorithm::Saturnin,
205 32, 16, 32, 1, "Saturnin",
210 "Lightweight post-quantum symmetric algorithm suite for IoT and constrained devices",
211 true,
212 );
213
214 static SHAKE256_METADATA: AeadMetadata = AeadMetadata::new(
215 Algorithm::Shake256Aead,
216 32, 16, 32, 1, "SHAKE256-AEAD",
221 "SHAKE256-based AEAD construction using post-quantum hash function",
222 true,
223 );
224
225 static DUPLEX_SPONGE_AEAD_METADATA: AeadMetadata = AeadMetadata::new(
226 Algorithm::DuplexSpongeAead,
227 32,
228 16,
229 32,
230 4,
231 "Duplex-Sponge-AEAD",
232 "Keccak-f[1600] duplex-sponge authenticated encryption",
233 true,
234 );
235
236 static TWEAK_AEAD_METADATA: AeadMetadata = AeadMetadata::new(
237 Algorithm::TweakAead,
238 32,
239 16,
240 32,
241 4,
242 "Tweak-AEAD",
243 "Parallel tweakable-block CTR AEAD over Keccak-f[1600]",
244 true,
245 );
246
247 static ROMULUS_N_METADATA: AeadMetadata = AeadMetadata::new(
248 Algorithm::RomulusN,
249 16,
250 16,
251 16,
252 1,
253 "Romulus-N",
254 "Romulus-N nonce-based AEAD (SKINNY-128-384+), LWC v1.3",
255 true,
256 );
257
258 static ROMULUS_M_METADATA: AeadMetadata = AeadMetadata::new(
259 Algorithm::RomulusM,
260 16,
261 16,
262 16,
263 1,
264 "Romulus-M",
265 "Romulus-M misuse-resistant AEAD (SKINNY-128-384+), LWC v1.3",
266 true,
267 );
268
269 match algorithm {
270 Algorithm::Saturnin => Some(&SATURNIN_METADATA),
271 Algorithm::Shake256Aead => Some(&SHAKE256_METADATA),
272 Algorithm::DuplexSpongeAead => Some(&DUPLEX_SPONGE_AEAD_METADATA),
273 Algorithm::TweakAead => Some(&TWEAK_AEAD_METADATA),
274 Algorithm::RomulusN => Some(&ROMULUS_N_METADATA),
275 Algorithm::RomulusM => Some(&ROMULUS_M_METADATA),
276 _ => None,
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_metadata_creation() {
286 let metadata = AeadMetadata::new(
287 Algorithm::Saturnin,
288 32,
289 16,
290 32,
291 1,
292 "Saturnin",
293 "Test algorithm",
294 true,
295 );
296
297 assert_eq!(metadata.algorithm, Algorithm::Saturnin);
298 assert_eq!(metadata.key_size, 32);
299 assert_eq!(metadata.nonce_size, 16);
300 assert_eq!(metadata.tag_size, 32);
301 assert_eq!(metadata.security_level, 1);
302 assert_eq!(metadata.name, "Saturnin");
303 assert_eq!(metadata.description, "Test algorithm");
304 assert!(metadata.supports_semantic_decrypt);
305 }
306
307 #[test]
308 fn test_performance_tier() {
309 let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 1, "Test", "Test", false);
310 assert_eq!(metadata.performance_tier(), PerformanceTier::Performance);
311
312 let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 3, "Test", "Test", false);
313 assert_eq!(metadata.performance_tier(), PerformanceTier::Balanced);
314
315 let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 4, "Test", "Test", false);
316 assert_eq!(metadata.performance_tier(), PerformanceTier::UltraSecure);
317
318 let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 5, "Test", "Test", false);
319 assert_eq!(metadata.performance_tier(), PerformanceTier::Hybrid);
320 }
321
322 #[test]
323 fn test_security_level_suitability() {
324 let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 3, "Test", "Test", false);
325
326 assert!(metadata.is_suitable_for_security_level(1));
327 assert!(metadata.is_suitable_for_security_level(3));
328 assert!(!metadata.is_suitable_for_security_level(4));
329 assert!(!metadata.is_suitable_for_security_level(5));
330 }
331
332 #[test]
333 fn test_size_validation() {
334 let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 1, "Test", "Test", false);
335
336 assert!(metadata.validate_key_size(32));
337 assert!(!metadata.validate_key_size(16));
338 assert!(!metadata.validate_key_size(64));
339
340 assert!(metadata.validate_nonce_size(16));
341 assert!(!metadata.validate_nonce_size(12));
342 assert!(!metadata.validate_nonce_size(24));
343
344 assert!(metadata.validate_tag_size(32));
345 assert!(!metadata.validate_tag_size(16));
346 assert!(!metadata.validate_tag_size(64));
347 }
348
349 #[test]
350 fn test_total_overhead() {
351 let metadata = AeadMetadata::new(Algorithm::Saturnin, 32, 16, 32, 1, "Test", "Test", false);
352
353 assert_eq!(metadata.total_overhead(), 48); }
355
356 #[test]
357 fn test_get_metadata() {
358 let metadata = get_metadata(Algorithm::Saturnin);
359 assert!(metadata.is_some());
360
361 if let Some(meta) = metadata {
362 assert_eq!(meta.algorithm, Algorithm::Saturnin);
363 assert_eq!(meta.name, "Saturnin");
364 }
365
366 let metadata = get_metadata(Algorithm::MlKem512);
367 assert!(metadata.is_none());
368 }
369
370 #[test]
371 fn registered_aead_metadata_marks_semantic_decrypt() {
372 for alg in [
373 Algorithm::Saturnin,
374 Algorithm::Shake256Aead,
375 Algorithm::DuplexSpongeAead,
376 Algorithm::TweakAead,
377 Algorithm::RomulusN,
378 Algorithm::RomulusM,
379 ] {
380 let m = get_metadata(alg).expect("AEAD metadata");
381 assert!(m.supports_semantic_decrypt, "{alg:?}");
382 }
383 }
384}