Skip to main content

pqc_binary_format/
algorithm.rs

1//! Algorithm identifiers for supported post-quantum cryptographic algorithms.
2//!
3//! One table, at the end of this file, is the registry: each algorithm's
4//! variant, 16-bit identifier, canonical name, display name, family and C
5//! constant. The enum, every lookup and the `PQC_ALGORITHM_*` constants the C
6//! API exports are generated from it, so they cannot disagree; the C header
7//! and the Go binding are held to it by `tests/registry.rs`.
8
9use serde::{Deserialize, Serialize};
10
11/// Algorithm identifier type
12pub type AlgorithmId = u16;
13
14/// The family an algorithm belongs to: what kind of construction it is.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[non_exhaustive]
17pub enum Family {
18    /// Classical cryptography only
19    Classical,
20    /// Classical and post-quantum combined
21    Hybrid,
22    /// Post-quantum stacks and KEMs
23    PostQuantum,
24    /// The Max Secure series
25    MaxSecure,
26    /// FN-DSA (Falcon-based) signature stacks
27    FnDsa,
28    /// Research constructions with reduced guarantees
29    Experimental,
30    /// HQC code-based KEMs (NIST 2025 backup KEM)
31    Hqc,
32    /// NIST ML-KEM parameter sets (FIPS 203)
33    MlKem,
34    /// NIST ML-DSA parameter sets (FIPS 204)
35    MlDsa,
36    /// NIST SLH-DSA parameter sets (FIPS 205)
37    SlhDsa,
38}
39
40impl Family {
41    /// Human-readable name of the family
42    #[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        /// Supported cryptographic algorithms with unique identifiers
65        ///
66        /// Each algorithm has a unique 16-bit identifier used in the binary
67        /// format. New identifiers are added in minor releases, so a `match`
68        /// on this enum needs a wildcard arm.
69        #[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            /// Every algorithm in the registry, in identifier order.
78            pub const ALL: &'static [Self] = &[ $( Self::$variant, )* ];
79
80            /// Get algorithm from u16 identifier
81            ///
82            /// # Example
83            ///
84            /// ```
85            /// use pqc_binary_format::Algorithm;
86            ///
87            /// assert_eq!(Algorithm::from_id(0x0100), Some(Algorithm::Hybrid));
88            /// assert_eq!(Algorithm::from_id(0xFFFF), None);
89            /// ```
90            #[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            /// Canonical kebab-case name matching the PQCrypta API / web UI identifier.
99            ///
100            /// This is the stable machine name (e.g. `"ml-kem-1024"`), distinct from
101            /// [`Algorithm::name`] which is the human-readable display label.
102            #[must_use]
103            pub const fn canonical_name(self) -> &'static str {
104                match self {
105                    $( Self::$variant => $canonical, )*
106                }
107            }
108
109            /// Get human-readable name for this algorithm
110            ///
111            /// # Example
112            ///
113            /// ```
114            /// use pqc_binary_format::Algorithm;
115            ///
116            /// assert_eq!(Algorithm::Hybrid.name(), "Hybrid");
117            /// ```
118            #[must_use]
119            pub const fn name(self) -> &'static str {
120                match self {
121                    $( Self::$variant => $name, )*
122                }
123            }
124
125            /// The family this algorithm belongs to
126            #[must_use]
127            pub const fn family(self) -> Family {
128                match self {
129                    $( Self::$variant => Family::$family, )*
130                }
131            }
132
133            /// The name of the constant the C API exports for this algorithm's
134            /// identifier (`PQC_ALGORITHM_HYBRID`)
135            #[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        /// The identifiers as the C API exports them, one constant per
144        /// algorithm, declared in `include/pqc_binary_format.h`.
145        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    /// Resolve an algorithm from its name.
157    ///
158    /// Accepts the canonical kebab-case names used by the PQCrypta API and web
159    /// UI (e.g. `"ml-kem-1024"`, `"max-secure-pure-pq"`) as well as compact
160    /// aliases with separators stripped (e.g. `"mlkem1024"`). Matching is
161    /// case-insensitive.
162    ///
163    /// # Example
164    ///
165    /// ```
166    /// use pqc_binary_format::Algorithm;
167    ///
168    /// assert_eq!(Algorithm::from_name("ml-kem-1024"), Some(Algorithm::MlKem1024));
169    /// assert_eq!(Algorithm::from_name("Max-Secure-Pure-PQ"), Some(Algorithm::MaxSecurePurePq));
170    /// assert_eq!(Algorithm::from_name("nope"), None);
171    /// ```
172    #[must_use]
173    pub fn from_name(name: &str) -> Option<Self> {
174        let lower = name.to_lowercase();
175        // Compact form: strip separators so "ml-kem-1024" == "mlkem1024".
176        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    /// Get u16 identifier for this algorithm
185    ///
186    /// # Example
187    ///
188    /// ```
189    /// use pqc_binary_format::Algorithm;
190    ///
191    /// assert_eq!(Algorithm::Hybrid.as_id(), 0x0100);
192    /// ```
193    #[must_use]
194    pub const fn as_id(self) -> u16 {
195        self as u16
196    }
197
198    /// Check if this algorithm is marked as experimental
199    ///
200    /// Experimental algorithms may have reduced security guarantees
201    /// and are intended for research purposes.
202    #[must_use]
203    pub const fn is_experimental(self) -> bool {
204        matches!(self.family(), Family::Experimental)
205    }
206
207    /// Get all defined algorithm identifiers
208    ///
209    /// Useful for iteration and testing; [`Algorithm::ALL`] is the same list
210    /// without allocating.
211    #[must_use]
212    pub fn all() -> Vec<Self> {
213        Self::ALL.to_vec()
214    }
215}
216
217// The registry: variant = identifier, canonical name, display name, family,
218// C constant. Identifiers are grouped by range; the PQCrypta platform assigned
219// 0x0208-0x020B in the post-quantum range, the X-Wing hybrid among them.
220algorithms! {
221    // Classical algorithms (0x0050-0x00FF)
222    /// Classical cryptography: X25519 + Ed25519 + AES-256-GCM
223    Classical = 0x0050, "classical", "Classical", Classical, PQC_ALGORITHM_CLASSICAL;
224    /// Password-based classical encryption
225    PasswordClassical = 0x0051, "password-classical", "Password Classical", Classical, PQC_ALGORITHM_PASSWORD_CLASSICAL;
226    // Hybrid algorithms (0x0100-0x01FF)
227    /// Hybrid: ML-KEM-1024 + X25519 + ML-DSA-87 + Ed25519 + AES-256-GCM
228    Hybrid = 0x0100, "hybrid", "Hybrid", Hybrid, PQC_ALGORITHM_HYBRID;
229    // Pure post-quantum algorithms (0x0200-0x02FF)
230    /// Post-quantum: ML-KEM-1024 + ML-DSA-87 + AES-256-GCM
231    PostQuantum = 0x0200, "post-quantum", "Post-Quantum", PostQuantum, PQC_ALGORITHM_POST_QUANTUM;
232    /// Multi-algorithm runtime selection
233    MultiAlgorithm = 0x0201, "multi-algorithm", "Multi-Algorithm", PostQuantum, PQC_ALGORITHM_MULTI_ALGORITHM;
234    /// ML-KEM-1024 with AES-256-GCM
235    MlKem1024 = 0x0202, "ml-kem-1024", "ML-KEM-1024", PostQuantum, PQC_ALGORITHM_ML_KEM_1024;
236    /// Multi-KEM dual layer
237    MultiKem = 0x0203, "multi-kem", "Multi-KEM Dual Layer", PostQuantum, PQC_ALGORITHM_MULTI_KEM;
238    /// Multi-KEM triple layer
239    MultiKemTriple = 0x0204, "multi-kem-triple", "Multi-KEM Triple Layer", PostQuantum, PQC_ALGORITHM_MULTI_KEM_TRIPLE;
240    /// Quad-layer redundant security
241    QuadLayer = 0x0205, "quad-layer", "Quad-Layer", PostQuantum, PQC_ALGORITHM_QUAD_LAYER;
242    /// Lattice-Code hybrid stack
243    LatticeCodeHybrid = 0x0206, "lattice-code-hybrid", "Lattice-Code Hybrid", PostQuantum, PQC_ALGORITHM_LATTICE_CODE_HYBRID;
244    /// PQ3-Stack with forward secrecy
245    Pq3Stack = 0x0207, "pq3-stack", "PQ3-Stack", PostQuantum, PQC_ALGORITHM_PQ3_STACK;
246    /// X-Wing hybrid KEM: ML-KEM-768 (FIPS 203) combined with X25519, the pair TLS negotiates as X25519MLKEM768
247    X25519MlKem768 = 0x0208, "x25519-mlkem768", "X25519MLKEM768", Hybrid, PQC_ALGORITHM_X25519_MLKEM768;
248    /// FrodoKEM-1344: unstructured-lattice KEM, NIST level 5 (ISO/IEC 18033-2)
249    FrodoKem1344 = 0x0209, "frodokem-1344", "FrodoKEM-1344", PostQuantum, PQC_ALGORITHM_FRODOKEM_1344;
250    /// Classic McEliece 6960119: code-based KEM (ISO/IEC 18033-2)
251    ClassicMcEliece = 0x020A, "classic-mceliece", "Classic-McEliece-6960119", PostQuantum, PQC_ALGORITHM_CLASSIC_MCELIECE;
252    /// FrodoKEM-976: unstructured-lattice KEM, NIST level 3
253    FrodoKem976 = 0x020B, "frodokem-976", "FrodoKEM-976", PostQuantum, PQC_ALGORITHM_FRODOKEM_976;
254    // Max Secure series (0x0300-0x03FF)
255    /// Max Secure: PQ Lightweight
256    MaxSecureLightweight = 0x0300, "max-secure-lightweight", "Max Secure: PQ Lightweight", MaxSecure, PQC_ALGORITHM_MAX_SECURE_LIGHTWEIGHT;
257    /// Max Secure: Pure PQ
258    MaxSecurePurePq = 0x0301, "max-secure-pure-pq", "Max Secure: Pure PQ", MaxSecure, PQC_ALGORITHM_MAX_SECURE_PURE_PQ;
259    /// Max Secure: Hybrid Transition
260    MaxSecureHybrid = 0x0302, "max-secure-hybrid", "Max Secure: Hybrid", MaxSecure, PQC_ALGORITHM_MAX_SECURE_HYBRID;
261    /// Max Secure: Stateless
262    MaxSecureStateless = 0x0303, "max-secure-stateless", "Max Secure: Stateless", MaxSecure, PQC_ALGORITHM_MAX_SECURE_STATELESS;
263    /// Max Secure: Crypto-Agile
264    MaxSecureCryptoAgile = 0x0304, "max-secure-crypto-agile", "Max Secure: Crypto-Agile", MaxSecure, PQC_ALGORITHM_MAX_SECURE_CRYPTO_AGILE;
265    /// Max Secure: PQC + Zero-Knowledge
266    MaxSecurePqcZk = 0x0305, "max-secure-pqc-zk", "Max Secure: PQC + ZK", MaxSecure, PQC_ALGORITHM_MAX_SECURE_PQC_ZK;
267    /// Max Secure: Hybrid Transition
268    MaxSecureHybridTransition = 0x0306, "max-secure-hybrid-transition", "Max Secure: Hybrid Transition", MaxSecure, PQC_ALGORITHM_MAX_SECURE_HYBRID_TRANSITION;
269    // FN-DSA series (Falcon-based signatures) (0x0400-0x04FF)
270    /// FN-DSA 512: Compact
271    FnDsa512Compact = 0x0400, "fn-dsa-512-compact", "FN-DSA 512: Compact", FnDsa, PQC_ALGORITHM_FN_DSA_512_COMPACT;
272    /// FN-DSA 1024: High-Security
273    FnDsa1024Security = 0x0401, "fn-dsa-1024-security", "FN-DSA 1024: High-Security", FnDsa, PQC_ALGORITHM_FN_DSA_1024_SECURITY;
274    /// FN-DSA: Floating-Point Hardened
275    FnDsaFpHardened = 0x0402, "fn-dsa-fp-hardened", "FN-DSA: Floating-Point Hardened", FnDsa, PQC_ALGORITHM_FN_DSA_FP_HARDENED;
276    /// FN-DSA: Dual Signature
277    FnDsaDualSignature = 0x0403, "fn-dsa-dual-signature", "FN-DSA: Dual Signature", FnDsa, PQC_ALGORITHM_FN_DSA_DUAL_SIGNATURE;
278    /// FN-DSA: Transition Stack
279    FnDsaTransition = 0x0404, "fn-dsa-transition", "FN-DSA: Transition Stack", FnDsa, PQC_ALGORITHM_FN_DSA_TRANSITION;
280    /// FN-DSA + Zero-Knowledge Stack
281    FnDsaZk = 0x0405, "fn-dsa-zk", "FN-DSA + ZK Stack", FnDsa, PQC_ALGORITHM_FN_DSA_ZK;
282    /// FN-DSA + ZK Stack Enhanced
283    FnDsaZkStack = 0x0406, "fn-dsa-zk-stack", "FN-DSA + ZK Stack Enhanced", FnDsa, PQC_ALGORITHM_FN_DSA_ZK_STACK;
284    /// FN-DSA: Transition Stack Enhanced
285    FnDsaTransitionStack = 0x0407, "fn-dsa-transition-stack", "FN-DSA: Transition Stack Enhanced", FnDsa, PQC_ALGORITHM_FN_DSA_TRANSITION_STACK;
286    // Experimental algorithms (0x0500-0x05FF)
287    /// Quantum-Inspired Lattice Fusion
288    QuantumLatticeFusion = 0x0500, "quantum-lattice-fusion", "Quantum-Inspired Lattice Fusion", Experimental, PQC_ALGORITHM_QUANTUM_LATTICE_FUSION;
289    /// Post-ZK Homomorphic with LFHE 2023
290    PostZkHomomorphic = 0x0501, "post-zk-homomorphic", "Post-ZK Homomorphic", Experimental, PQC_ALGORITHM_POST_ZK_HOMOMORPHIC;
291    /// Quantum-Resistant Consensus
292    QuantumResistantConsensus = 0x0502, "quantum-resistant-consensus", "Quantum-Resistant Consensus", Experimental, PQC_ALGORITHM_QUANTUM_RESISTANT_CONSENSUS;
293    /// Entropy-Orchestrated PQ Stack
294    EntropyOrchestrated = 0x0503, "entropy-orchestrated", "Entropy-Orchestrated", Experimental, PQC_ALGORITHM_ENTROPY_ORCHESTRATED;
295    /// Lattice-Code Hybrid FN
296    LatticeCodeHybridFn = 0x0504, "lattice-code-hybrid-fn", "Lattice-Code Hybrid FN", Experimental, PQC_ALGORITHM_LATTICE_CODE_HYBRID_FN;
297    /// AI-Synthesized Crypto-Agile
298    AiSynthesizedCryptoAgile = 0x0505, "ai-synthesized-crypto-agile", "AI-Synthesized Crypto-Agile", Experimental, PQC_ALGORITHM_AI_SYNTHESIZED_CRYPTO_AGILE;
299    /// Experimental Engine (generic)
300    Experimental = 0x0506, "experimental", "Experimental Engine", Experimental, PQC_ALGORITHM_EXPERIMENTAL;
301    // HQC Code-Based series (NIST 2025 Backup KEM) (0x0600-0x06FF)
302    /// HQC-128 (NIST Level 1, 128-bit security)
303    Hqc128 = 0x0600, "hqc-128", "HQC-128", Hqc, PQC_ALGORITHM_HQC_128;
304    /// HQC-192 (NIST Level 3, 192-bit security)
305    Hqc192 = 0x0601, "hqc-192", "HQC-192", Hqc, PQC_ALGORITHM_HQC_192;
306    /// HQC-256 (NIST Level 5, 256-bit security)
307    Hqc256 = 0x0602, "hqc-256", "HQC-256", Hqc, PQC_ALGORITHM_HQC_256;
308    // NIST ML-KEM variants (FIPS 203) (0x0700-0x07FF)
309    /// ML-KEM-512 (NIST Level 1, 128-bit security)
310    MlKem512 = 0x0700, "ml-kem-512", "ML-KEM-512", MlKem, PQC_ALGORITHM_ML_KEM_512;
311    /// ML-KEM-768 (NIST Level 3, 192-bit security)
312    MlKem768 = 0x0701, "ml-kem-768", "ML-KEM-768", MlKem, PQC_ALGORITHM_ML_KEM_768;
313    // NIST ML-DSA variants (FIPS 204) (0x0800-0x08FF)
314    /// ML-DSA-44 (NIST Level 2, 128-bit security)
315    MlDsa44 = 0x0800, "ml-dsa-44", "ML-DSA-44", MlDsa, PQC_ALGORITHM_ML_DSA_44;
316    /// ML-DSA-65 (NIST Level 3, 192-bit security)
317    MlDsa65 = 0x0801, "ml-dsa-65", "ML-DSA-65", MlDsa, PQC_ALGORITHM_ML_DSA_65;
318    /// ML-DSA-87 (NIST Level 5, 256-bit security)
319    MlDsa87 = 0x0802, "ml-dsa-87", "ML-DSA-87", MlDsa, PQC_ALGORITHM_ML_DSA_87;
320    // NIST SLH-DSA variants (FIPS 205) (0x0900-0x09FF)
321    /// SLH-DSA-SHA2-128s (NIST Level 1, small signatures)
322    SlhDsaSha2_128s = 0x0900, "slh-dsa-sha2-128s", "SLH-DSA-SHA2-128s", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_128S;
323    /// SLH-DSA-SHA2-128f (NIST Level 1, fast signatures)
324    SlhDsaSha2_128f = 0x0901, "slh-dsa-sha2-128f", "SLH-DSA-SHA2-128f", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_128F;
325    /// SLH-DSA-SHA2-192s (NIST Level 3, small signatures)
326    SlhDsaSha2_192s = 0x0902, "slh-dsa-sha2-192s", "SLH-DSA-SHA2-192s", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_192S;
327    /// SLH-DSA-SHA2-192f (NIST Level 3, fast signatures)
328    SlhDsaSha2_192f = 0x0903, "slh-dsa-sha2-192f", "SLH-DSA-SHA2-192f", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_192F;
329    /// SLH-DSA-SHA2-256s (NIST Level 5, small signatures)
330    SlhDsaSha2_256s = 0x0904, "slh-dsa-sha2-256s", "SLH-DSA-SHA2-256s", SlhDsa, PQC_ALGORITHM_SLH_DSA_SHA2_256S;
331    /// SLH-DSA-SHA2-256f (NIST Level 5, fast signatures)
332    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    /// The C constant is the canonical name in capitals, so the C API's
361    /// names follow from the registry.
362    #[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}