1use serde::{Deserialize, Serialize};
10
11pub type AlgorithmId = u16;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[non_exhaustive]
17pub enum Family {
18 Classical,
20 Hybrid,
22 PostQuantum,
24 MaxSecure,
26 FnDsa,
28 Experimental,
30 Hqc,
32 MlKem,
34 MlDsa,
36 SlhDsa,
38}
39
40impl Family {
41 #[must_use]
43 pub const fn name(self) -> &'static str {
44 match self {
45 Self::Classical => "Classical",
46 Self::Hybrid => "Hybrid",
47 Self::PostQuantum => "Post-quantum",
48 Self::MaxSecure => "Max Secure",
49 Self::FnDsa => "FN-DSA",
50 Self::Experimental => "Experimental",
51 Self::Hqc => "HQC",
52 Self::MlKem => "ML-KEM (FIPS 203)",
53 Self::MlDsa => "ML-DSA (FIPS 204)",
54 Self::SlhDsa => "SLH-DSA (FIPS 205)",
55 }
56 }
57}
58
59macro_rules! algorithms {
60 ($(
61 $(#[doc = $doc:literal])*
62 $variant:ident = $id:literal, $canonical:literal, $name:literal, $family:ident, $c_name:ident;
63 )*) => {
64 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
70 #[repr(u16)]
71 #[non_exhaustive]
72 pub enum Algorithm {
73 $( $(#[doc = $doc])* $variant = $id, )*
74 }
75
76 impl Algorithm {
77 pub const ALL: &'static [Self] = &[ $( Self::$variant, )* ];
79
80 #[must_use]
91 pub const fn from_id(id: u16) -> Option<Self> {
92 match id {
93 $( $id => Some(Self::$variant), )*
94 _ => None,
95 }
96 }
97
98 #[must_use]
103 pub const fn canonical_name(self) -> &'static str {
104 match self {
105 $( Self::$variant => $canonical, )*
106 }
107 }
108
109 #[must_use]
119 pub const fn name(self) -> &'static str {
120 match self {
121 $( Self::$variant => $name, )*
122 }
123 }
124
125 #[must_use]
127 pub const fn family(self) -> Family {
128 match self {
129 $( Self::$variant => Family::$family, )*
130 }
131 }
132
133 #[must_use]
136 pub const fn c_constant(self) -> &'static str {
137 match self {
138 $( Self::$variant => stringify!($c_name), )*
139 }
140 }
141 }
142
143 pub mod c_constants {
146 $(
147 #[doc = concat!("Algorithm ID for ", $name, " (", stringify!($id), ")")]
148 #[unsafe(no_mangle)]
149 pub static $c_name: u16 = $id;
150 )*
151 }
152 };
153}
154
155impl Algorithm {
156 #[must_use]
173 pub fn from_name(name: &str) -> Option<Self> {
174 let lower = name.to_lowercase();
175 let compact: String = lower.chars().filter(|c| c.is_alphanumeric()).collect();
177 Self::ALL.iter().copied().find(|algo| {
178 let canon = algo.canonical_name();
179 let canon_compact: String = canon.chars().filter(char::is_ascii_alphanumeric).collect();
180 lower == canon || compact == canon_compact
181 })
182 }
183
184 #[must_use]
194 pub const fn as_id(self) -> u16 {
195 self as u16
196 }
197
198 #[must_use]
203 pub const fn is_experimental(self) -> bool {
204 matches!(self.family(), Family::Experimental)
205 }
206
207 #[must_use]
212 pub fn all() -> Vec<Self> {
213 Self::ALL.to_vec()
214 }
215}
216
217algorithms! {
221 Classical = 0x0050, "classical", "Classical", Classical, PQC_ALGORITHM_CLASSICAL;
224 PasswordClassical = 0x0051, "password-classical", "Password Classical", Classical, PQC_ALGORITHM_PASSWORD_CLASSICAL;
226 Hybrid = 0x0100, "hybrid", "Hybrid", Hybrid, PQC_ALGORITHM_HYBRID;
229 PostQuantum = 0x0200, "post-quantum", "Post-Quantum", PostQuantum, PQC_ALGORITHM_POST_QUANTUM;
232 MultiAlgorithm = 0x0201, "multi-algorithm", "Multi-Algorithm", PostQuantum, PQC_ALGORITHM_MULTI_ALGORITHM;
234 MlKem1024 = 0x0202, "ml-kem-1024", "ML-KEM-1024", PostQuantum, PQC_ALGORITHM_ML_KEM_1024;
236 MultiKem = 0x0203, "multi-kem", "Multi-KEM Dual Layer", PostQuantum, PQC_ALGORITHM_MULTI_KEM;
238 MultiKemTriple = 0x0204, "multi-kem-triple", "Multi-KEM Triple Layer", PostQuantum, PQC_ALGORITHM_MULTI_KEM_TRIPLE;
240 QuadLayer = 0x0205, "quad-layer", "Quad-Layer", PostQuantum, PQC_ALGORITHM_QUAD_LAYER;
242 LatticeCodeHybrid = 0x0206, "lattice-code-hybrid", "Lattice-Code Hybrid", PostQuantum, PQC_ALGORITHM_LATTICE_CODE_HYBRID;
244 Pq3Stack = 0x0207, "pq3-stack", "PQ3-Stack", PostQuantum, PQC_ALGORITHM_PQ3_STACK;
246 X25519MlKem768 = 0x0208, "x25519-mlkem768", "X25519MLKEM768", Hybrid, PQC_ALGORITHM_X25519_MLKEM768;
248 FrodoKem1344 = 0x0209, "frodokem-1344", "FrodoKEM-1344", PostQuantum, PQC_ALGORITHM_FRODOKEM_1344;
250 ClassicMcEliece = 0x020A, "classic-mceliece", "Classic-McEliece-6960119", PostQuantum, PQC_ALGORITHM_CLASSIC_MCELIECE;
252 FrodoKem976 = 0x020B, "frodokem-976", "FrodoKEM-976", PostQuantum, PQC_ALGORITHM_FRODOKEM_976;
254 MaxSecureLightweight = 0x0300, "max-secure-lightweight", "Max Secure: PQ Lightweight", MaxSecure, PQC_ALGORITHM_MAX_SECURE_LIGHTWEIGHT;
257 MaxSecurePurePq = 0x0301, "max-secure-pure-pq", "Max Secure: Pure PQ", MaxSecure, PQC_ALGORITHM_MAX_SECURE_PURE_PQ;
259 MaxSecureHybrid = 0x0302, "max-secure-hybrid", "Max Secure: Hybrid", MaxSecure, PQC_ALGORITHM_MAX_SECURE_HYBRID;
261 MaxSecureStateless = 0x0303, "max-secure-stateless", "Max Secure: Stateless", MaxSecure, PQC_ALGORITHM_MAX_SECURE_STATELESS;
263 MaxSecureCryptoAgile = 0x0304, "max-secure-crypto-agile", "Max Secure: Crypto-Agile", MaxSecure, PQC_ALGORITHM_MAX_SECURE_CRYPTO_AGILE;
265 MaxSecurePqcZk = 0x0305, "max-secure-pqc-zk", "Max Secure: PQC + ZK", MaxSecure, PQC_ALGORITHM_MAX_SECURE_PQC_ZK;
267 MaxSecureHybridTransition = 0x0306, "max-secure-hybrid-transition", "Max Secure: Hybrid Transition", MaxSecure, PQC_ALGORITHM_MAX_SECURE_HYBRID_TRANSITION;
269 FnDsa512Compact = 0x0400, "fn-dsa-512-compact", "FN-DSA 512: Compact", FnDsa, PQC_ALGORITHM_FN_DSA_512_COMPACT;
272 FnDsa1024Security = 0x0401, "fn-dsa-1024-security", "FN-DSA 1024: High-Security", FnDsa, PQC_ALGORITHM_FN_DSA_1024_SECURITY;
274 FnDsaFpHardened = 0x0402, "fn-dsa-fp-hardened", "FN-DSA: Floating-Point Hardened", FnDsa, PQC_ALGORITHM_FN_DSA_FP_HARDENED;
276 FnDsaDualSignature = 0x0403, "fn-dsa-dual-signature", "FN-DSA: Dual Signature", FnDsa, PQC_ALGORITHM_FN_DSA_DUAL_SIGNATURE;
278 FnDsaTransition = 0x0404, "fn-dsa-transition", "FN-DSA: Transition Stack", FnDsa, PQC_ALGORITHM_FN_DSA_TRANSITION;
280 FnDsaZk = 0x0405, "fn-dsa-zk", "FN-DSA + ZK Stack", FnDsa, PQC_ALGORITHM_FN_DSA_ZK;
282 FnDsaZkStack = 0x0406, "fn-dsa-zk-stack", "FN-DSA + ZK Stack Enhanced", FnDsa, PQC_ALGORITHM_FN_DSA_ZK_STACK;
284 FnDsaTransitionStack = 0x0407, "fn-dsa-transition-stack", "FN-DSA: Transition Stack Enhanced", FnDsa, PQC_ALGORITHM_FN_DSA_TRANSITION_STACK;
286 QuantumLatticeFusion = 0x0500, "quantum-lattice-fusion", "Quantum-Inspired Lattice Fusion", Experimental, PQC_ALGORITHM_QUANTUM_LATTICE_FUSION;
289 PostZkHomomorphic = 0x0501, "post-zk-homomorphic", "Post-ZK Homomorphic", Experimental, PQC_ALGORITHM_POST_ZK_HOMOMORPHIC;
291 QuantumResistantConsensus = 0x0502, "quantum-resistant-consensus", "Quantum-Resistant Consensus", Experimental, PQC_ALGORITHM_QUANTUM_RESISTANT_CONSENSUS;
293 EntropyOrchestrated = 0x0503, "entropy-orchestrated", "Entropy-Orchestrated", Experimental, PQC_ALGORITHM_ENTROPY_ORCHESTRATED;
295 LatticeCodeHybridFn = 0x0504, "lattice-code-hybrid-fn", "Lattice-Code Hybrid FN", Experimental, PQC_ALGORITHM_LATTICE_CODE_HYBRID_FN;
297 AiSynthesizedCryptoAgile = 0x0505, "ai-synthesized-crypto-agile", "AI-Synthesized Crypto-Agile", Experimental, PQC_ALGORITHM_AI_SYNTHESIZED_CRYPTO_AGILE;
299 Experimental = 0x0506, "experimental", "Experimental Engine", Experimental, PQC_ALGORITHM_EXPERIMENTAL;
301 Hqc128 = 0x0600, "hqc-128", "HQC-128", Hqc, PQC_ALGORITHM_HQC_128;
304 Hqc192 = 0x0601, "hqc-192", "HQC-192", Hqc, PQC_ALGORITHM_HQC_192;
306 Hqc256 = 0x0602, "hqc-256", "HQC-256", Hqc, PQC_ALGORITHM_HQC_256;
308 MlKem512 = 0x0700, "ml-kem-512", "ML-KEM-512", MlKem, PQC_ALGORITHM_ML_KEM_512;
311 MlKem768 = 0x0701, "ml-kem-768", "ML-KEM-768", MlKem, PQC_ALGORITHM_ML_KEM_768;
313 MlDsa44 = 0x0800, "ml-dsa-44", "ML-DSA-44", MlDsa, PQC_ALGORITHM_ML_DSA_44;
316 MlDsa65 = 0x0801, "ml-dsa-65", "ML-DSA-65", MlDsa, PQC_ALGORITHM_ML_DSA_65;
318 MlDsa87 = 0x0802, "ml-dsa-87", "ML-DSA-87", MlDsa, PQC_ALGORITHM_ML_DSA_87;
320 SlhDsaSha2_128s = 0x0900, "slh-dsa-sha2-128s", "SLH-DSA-SHA2-128s", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_128S;
323 SlhDsaSha2_128f = 0x0901, "slh-dsa-sha2-128f", "SLH-DSA-SHA2-128f", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_128F;
325 SlhDsaSha2_192s = 0x0902, "slh-dsa-sha2-192s", "SLH-DSA-SHA2-192s", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_192S;
327 SlhDsaSha2_192f = 0x0903, "slh-dsa-sha2-192f", "SLH-DSA-SHA2-192f", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_192F;
329 SlhDsaSha2_256s = 0x0904, "slh-dsa-sha2-256s", "SLH-DSA-SHA2-256s", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_256S;
331 SlhDsaSha2_256f = 0x0905, "slh-dsa-sha2-256f", "SLH-DSA-SHA2-256f", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_256F;
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn test_algorithm_roundtrip() {
341 for &algo in Algorithm::ALL {
342 assert_eq!(Algorithm::from_id(algo.as_id()), Some(algo));
343 assert_eq!(Algorithm::from_name(algo.canonical_name()), Some(algo));
344 }
345 }
346
347 #[test]
348 fn identifiers_and_names_are_unique_and_ordered() {
349 let ids: Vec<u16> = Algorithm::ALL.iter().map(|a| a.as_id()).collect();
350 assert!(
351 ids.windows(2).all(|w| w[0] < w[1]),
352 "ALL is in identifier order: {ids:x?}"
353 );
354 let mut canon: Vec<&str> = Algorithm::ALL.iter().map(|a| a.canonical_name()).collect();
355 canon.sort_unstable();
356 canon.dedup();
357 assert_eq!(canon.len(), Algorithm::ALL.len());
358 }
359
360 #[test]
363 fn c_constants_follow_the_canonical_names() {
364 for &algo in Algorithm::ALL {
365 let expected = format!(
366 "PQC_ALGORITHM_{}",
367 algo.canonical_name().to_uppercase().replace('-', "_")
368 );
369 assert_eq!(algo.c_constant(), expected);
370 }
371 assert_eq!(c_constants::PQC_ALGORITHM_LATTICE_CODE_HYBRID, 0x0206);
372 assert_eq!(c_constants::PQC_ALGORITHM_X25519_MLKEM768, 0x0208);
373 }
374
375 #[test]
376 fn test_invalid_algorithm_id() {
377 assert!(Algorithm::from_id(0xFFFF).is_none());
378 }
379
380 #[test]
381 fn test_experimental_detection() {
382 assert!(Algorithm::QuantumLatticeFusion.is_experimental());
383 assert!(!Algorithm::Hybrid.is_experimental());
384 assert_eq!(
385 Algorithm::ALL
386 .iter()
387 .filter(|a| a.is_experimental())
388 .count(),
389 Algorithm::ALL
390 .iter()
391 .filter(|a| (0x0500..0x0600).contains(&a.as_id()))
392 .count()
393 );
394 }
395
396 #[test]
397 fn the_platform_assignments_resolve() {
398 for (id, name) in [
399 (0x0208, "x25519-mlkem768"),
400 (0x0209, "frodokem-1344"),
401 (0x020A, "classic-mceliece"),
402 (0x020B, "frodokem-976"),
403 ] {
404 assert_eq!(
405 Algorithm::from_id(id).map(Algorithm::canonical_name),
406 Some(name)
407 );
408 }
409 }
410}